























@@ -106,6 +106,7 @@ describe("resolveRunFailoverDecision", () => {
106106failoverFailure: false,
107107failoverReason: "rate_limit",
108108timedOut: false,
109+idleTimedOut: false,
109110timedOutDuringCompaction: false,
110111timedOutDuringToolExecution: false,
111112profileRotated: false,
@@ -167,6 +168,7 @@ describe("resolveRunFailoverDecision", () => {
167168failoverFailure: false,
168169failoverReason: "rate_limit",
169170timedOut: false,
171+idleTimedOut: false,
170172timedOutDuringCompaction: false,
171173timedOutDuringToolExecution: false,
172174profileRotated: true,
@@ -187,6 +189,7 @@ describe("resolveRunFailoverDecision", () => {
187189failoverFailure: false,
188190failoverReason: null,
189191timedOut: false,
192+idleTimedOut: false,
190193timedOutDuringCompaction: false,
191194timedOutDuringToolExecution: false,
192195profileRotated: false,
@@ -223,6 +226,7 @@ describe("resolveRunFailoverDecision", () => {
223226failoverFailure: false,
224227failoverReason: null,
225228timedOut: true,
229+idleTimedOut: false,
226230timedOutDuringCompaction: false,
227231timedOutDuringToolExecution: true,
228232profileRotated: false,
@@ -242,6 +246,7 @@ describe("resolveRunFailoverDecision", () => {
242246failoverFailure: false,
243247failoverReason: null,
244248timedOut: true,
249+idleTimedOut: false,
245250timedOutDuringCompaction: false,
246251timedOutDuringToolExecution: true,
247252profileRotated: true,
@@ -261,6 +266,7 @@ describe("resolveRunFailoverDecision", () => {
261266failoverFailure: false,
262267failoverReason: null,
263268timedOut: true,
269+idleTimedOut: false,
264270timedOutDuringCompaction: false,
265271timedOutDuringToolExecution: false,
266272profileRotated: false,
@@ -281,6 +287,95 @@ describe("resolveRunFailoverDecision", () => {
281287failoverFailure: false,
282288failoverReason: null,
283289timedOut: true,
290+idleTimedOut: false,
291+timedOutDuringCompaction: false,
292+timedOutDuringToolExecution: false,
293+profileRotated: false,
294+}),
295+).toEqual({
296+action: "surface_error",
297+reason: null,
298+});
299+});
300+});
301+302+it("rotates profile on LLM idle timeout before falling back", () => {
303+// idleTimedOut = model produced no tokens; no provider API error was classified.
304+// Before this fix, failoverReason=null + timedOut=false → shouldRotateAssistant=false
305+// → continue_normal, causing a silent agent freeze.
306+expect(
307+resolveRunFailoverDecision({
308+stage: "assistant",
309+aborted: false,
310+externalAbort: false,
311+fallbackConfigured: true,
312+failoverFailure: false,
313+failoverReason: null,
314+timedOut: false,
315+idleTimedOut: true,
316+timedOutDuringCompaction: false,
317+timedOutDuringToolExecution: false,
318+profileRotated: false,
319+}),
320+).toEqual({
321+action: "rotate_profile",
322+reason: null,
323+});
324+});
325+326+it("escalates LLM idle timeout to fallback_model after profile rotation is exhausted", () => {
327+expect(
328+resolveRunFailoverDecision({
329+stage: "assistant",
330+aborted: false,
331+externalAbort: false,
332+fallbackConfigured: true,
333+failoverFailure: false,
334+failoverReason: null,
335+timedOut: false,
336+idleTimedOut: true,
337+timedOutDuringCompaction: false,
338+timedOutDuringToolExecution: false,
339+profileRotated: true,
340+}),
341+).toEqual({
342+action: "fallback_model",
343+reason: "timeout",
344+});
345+});
346+347+it("surfaces error on LLM idle timeout when no fallback is configured and rotation is exhausted", () => {
348+expect(
349+resolveRunFailoverDecision({
350+stage: "assistant",
351+aborted: false,
352+externalAbort: false,
353+fallbackConfigured: false,
354+failoverFailure: false,
355+failoverReason: null,
356+timedOut: false,
357+idleTimedOut: true,
358+timedOutDuringCompaction: false,
359+timedOutDuringToolExecution: false,
360+profileRotated: true,
361+}),
362+).toEqual({
363+action: "surface_error",
364+reason: null,
365+});
366+});
367+368+it("does not escalate LLM idle timeout after an external abort", () => {
369+expect(
370+resolveRunFailoverDecision({
371+stage: "assistant",
372+aborted: false,
373+externalAbort: true,
374+fallbackConfigured: true,
375+failoverFailure: false,
376+failoverReason: null,
377+timedOut: false,
378+idleTimedOut: true,
284379timedOutDuringCompaction: false,
285380timedOutDuringToolExecution: false,
286381profileRotated: false,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。