@@ -158,8 +158,11 @@ function resolveGatewayLiveSuiteTimeoutMs(maxModels: number): number {
|
158 | 158 | if (maxModels <= 0) { |
159 | 159 | return GATEWAY_LIVE_UNBOUNDED_TIMEOUT_MS; |
160 | 160 | } |
161 | | -// Gateway live runs multiple probes per model; scale timeout by model cap. |
162 | | -const estimated = 5 * 60 * 1000 + maxModels * 90 * 1000; |
| 161 | +// Gateway live runs multiple probes per model and may retry with another |
| 162 | +// profile key before moving on, so the suite budget has to scale with the |
| 163 | +// model timeout rather than only the first prompt. |
| 164 | +const perModelBudgetMs = Math.max(3 * 60 * 1000, GATEWAY_LIVE_MODEL_TIMEOUT_MS * 3); |
| 165 | +const estimated = 10 * 60 * 1000 + maxModels * perModelBudgetMs; |
163 | 166 | return Math.max( |
164 | 167 | GATEWAY_LIVE_DEFAULT_TIMEOUT_MS, |
165 | 168 | Math.min(GATEWAY_LIVE_MAX_TIMEOUT_MS, estimated), |
@@ -533,6 +536,20 @@ describe("resolveGatewayLiveModelTimeoutMs", () => {
|
533 | 536 | }); |
534 | 537 | }); |
535 | 538 | |
| 539 | +describe("resolveGatewayLiveSuiteTimeoutMs", () => { |
| 540 | +it("leaves uncapped explicit sweeps bounded by the unbounded live timeout", () => { |
| 541 | +expect(resolveGatewayLiveSuiteTimeoutMs(0)).toBe(GATEWAY_LIVE_UNBOUNDED_TIMEOUT_MS); |
| 542 | +}); |
| 543 | + |
| 544 | +it("scales model-capped sweeps for multi-probe retries", () => { |
| 545 | +expect(resolveGatewayLiveSuiteTimeoutMs(2)).toBeGreaterThan(GATEWAY_LIVE_DEFAULT_TIMEOUT_MS); |
| 546 | +}); |
| 547 | + |
| 548 | +it("caps very large model sweeps", () => { |
| 549 | +expect(resolveGatewayLiveSuiteTimeoutMs(999)).toBe(GATEWAY_LIVE_MAX_TIMEOUT_MS); |
| 550 | +}); |
| 551 | +}); |
| 552 | + |
536 | 553 | describe("resolveGatewayLiveMaxModels", () => { |
537 | 554 | const originalGatewayModels = process.env.OPENCLAW_LIVE_GATEWAY_MODELS; |
538 | 555 | const originalGatewayMax = process.env.OPENCLAW_LIVE_GATEWAY_MAX_MODELS; |
|