autodev-cli 1.4.182 → 1.4.184

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -197,6 +197,15 @@ export interface AutodevSettings {
197
197
  * is never retried here — it has its own pause/resume path.
198
198
  */
199
199
  maxProviderRetries: number;
200
+ /**
201
+ * How many times a STRANDED task (the CLI exited leaving it [~] in-progress
202
+ * without completing it) is reset to pending [ ] and re-attempted before it is
203
+ * finally flagged BLOCKED / needs-attention. A stranded task is NEVER marked
204
+ * [x] done — correctness over efficiency; the user prefers never-false-done.
205
+ * Distinct from maxProviderRetries (a detected provider hard-failure) and
206
+ * maxTaskAttempts (turns that RAN but did not mark the task done). Default: 3.
207
+ */
208
+ maxStrandRetries: number;
200
209
  /**
201
210
  * Move completed [x] tasks from TODO.md into DONE.md every N
202
211
  * completed tasks (0 = disabled). Keeps the active TODO file short.
@@ -98,6 +98,7 @@ exports.SETTINGS_DEFAULTS = {
98
98
  resetSessionEveryNTurns: 0,
99
99
  maxTaskAttempts: 3,
100
100
  maxProviderRetries: 10,
101
+ maxStrandRetries: 3,
101
102
  pruneTodoEveryNTasks: 0,
102
103
  journalLearnEveryNTasks: 0,
103
104
  profileEveryNTasks: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"settingsLoader.js","sourceRoot":"","sources":["../../src/core/settingsLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiWA,gCAWC;AAiBD,8CAEC;AAQD,gDAOC;AAGD,4CAMC;AA0BD,kDA0BC;AA3cD,uCAAyB;AACzB,2CAA6B;AAoRhB,QAAA,iBAAiB,GAAoB;IAChD,QAAQ,EAAE,YAA0B;IACpC,KAAK,EAAE,EAAE;IACT,aAAa,EAAE,EAAE;IACjB,YAAY,EAAE,EAAE;IAChB,WAAW,EAAE,EAAE;IACf,YAAY,EAAE,EAAE;IAChB,gBAAgB,EAAE,EAAE;IACpB,aAAa,EAAE,EAAE;IACjB,YAAY,EAAE,EAAE;IAChB,kBAAkB,EAAE,EAAE;IACtB,kBAAkB,EAAE,EAAE;IACtB,cAAc,EAAE,KAAK;IACrB,qBAAqB,EAAE,IAAI;IAC3B,WAAW,EAAE,EAAE;IACf,QAAQ,EAAE,EAAE;IACZ,aAAa,EAAE,KAAK;IACpB,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,EAAE;IACf,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,EAAE;IACf,WAAW,EAAE,EAAE;IACf,SAAS,EAAE,EAAE;IACb,YAAY,EAAE,EAAE;IAChB,iBAAiB,EAAE,KAAK;IACxB,UAAU,EAAE,KAAK;IACjB,gBAAgB,EAAE,KAAK;IACvB,kBAAkB,EAAE,KAAK;IACzB,YAAY,EAAE,KAAK;IACnB,UAAU,EAAE,SAAS;IACrB,oBAAoB,EAAE,KAAK;IAC3B,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,EAAE;IAChB,WAAW,EAAE,EAAE;IACf,aAAa,EAAE,EAAE;IACjB,WAAW,EAAE,EAAE;IACf,SAAS,EAAE,EAAE;IACb,UAAU,EAAE,EAAE;IACd,oBAAoB,EAAE,KAAK;IAC3B,UAAU,EAAE,EAAE;IACd,kBAAkB,EAAE,EAAE;IACtB,OAAO,EAAE,KAAK;IACd,gBAAgB,EAAE,cAA4B;IAC9C,uBAAuB,EAAE,KAAK;IAC9B,sBAAsB,EAAE,EAAE;IAC1B,iBAAiB,EAAE,EAAE;IACrB,WAAW,EAAE,KAAK;IAClB,mBAAmB,EAAE,CAAC;IACtB,eAAe,EAAE,CAAC;IAClB,oBAAoB,EAAE,CAAC;IACvB,uBAAuB,EAAE,CAAC;IAC1B,eAAe,EAAE,CAAC;IAClB,kBAAkB,EAAE,EAAE;IACtB,oBAAoB,EAAE,CAAC;IACvB,uBAAuB,EAAE,CAAC;IAC1B,kBAAkB,EAAE,CAAC;IACrB,kBAAkB,EAAE,CAAC;IACrB,gBAAgB,EAAE,CAAC;IACnB,iBAAiB,EAAE,CAAC;IACpB,kBAAkB,EAAE,CAAC;IACrB,kBAAkB,EAAE,EAAE;IACtB,aAAa,EAAE,KAAK;IACpB,iBAAiB,EAAE,KAAK;IACxB,OAAO,EAAE,EAAE;IACX,oBAAoB,EAAE,EAAE;IACxB,gBAAgB,EAAE,KAAK;CACxB,CAAC;AAEF;;;GAGG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAC3F,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,KAAK,GAAM,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAO,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACtD,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QACd,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,0BAA0B;AAC1B,EAAE;AACF,4CAA4C;AAC5C,yEAAyE;AACzE,EAAE;AACF,wEAAwE;AACxE,6EAA6E;AAC7E,yEAAyE;AACzE,8EAA8E;AAEjE,QAAA,qBAAqB,GAAG,wBAAwB,CAAC;AACjD,QAAA,wBAAwB,GAAG,sBAAsB,CAAC;AAE/D,+EAA+E;AAC/E,SAAgB,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,IAAY,EAAE,GAA0B;IACzE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAA4B,CAAC;QACjF,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC3B,CAAC;AAED,8EAA8E;AAC9E,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IAC1D,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAAC,OAAO,MAAM,CAAC;IAAC,CAAC;IAC7C,OAAO,SAAS,CAAC,CAAC,6CAA6C;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,gBAAgB,GAA0C;IAC9D,WAAW,EAAE,OAAO;IACpB,MAAM,EAAE,cAAc;IACtB,IAAI,EAAE,aAAa;CACpB,CAAC;AAEF,gHAAgH;AAChH,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,OAAO,EAAE,GAAG,yBAAiB,EAAE,CAAC;QAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAA6B,CAAC;QAClF,MAAM,MAAM,GAAG,EAAE,GAAG,yBAAiB,EAAE,GAAG,GAAG,EAAE,CAAC;QAEhD,6EAA6E;QAC7E,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAClE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YACxB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3D,MAAkC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QACD,kFAAkF;QAClF,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;YAC5C,MAAM,CAAC,YAAY,GAAI,MAAM,CAAC,YAAY,CAAC;YAC3C,MAAM,CAAC,WAAW,GAAK,MAAM,CAAC,WAAW,CAAC;QAC5C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,yBAAiB,EAAE,CAAC;IAClC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"settingsLoader.js","sourceRoot":"","sources":["../../src/core/settingsLoader.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,12 @@ interface GrokRunOptions {
25
25
  */
26
26
  resumeProbeMs?: number;
27
27
  onResumeHang?: () => void;
28
+ /**
29
+ * Fired ONCE when a launched RESUME (opts.sessionId present) demonstrably begins
30
+ * answering — the first `first_token`. Lets the caller clear its consecutive-resume-
31
+ * failure counter so a prior transient hiccup does not keep biasing later turns.
32
+ */
33
+ onResumeOk?: () => void;
28
34
  }
29
35
  export declare function sendGrokPrompt(root: string, promptFilePath: string, stdoutFile: string, exitFile: string, log: (msg: string) => void, opts?: GrokRunOptions): void;
30
36
  /**
@@ -58,34 +64,57 @@ export declare function decideGrokRunMode(opts: {
58
64
  useTmux: boolean;
59
65
  };
60
66
  /**
61
- * Pure decision for how the HEADLESS grok-tui path (no tmux) should launch this
62
- * turn. Extracted so the resume/bootstrap/stateless routing is unit-testable.
67
+ * Pure decision for how the HEADLESS grok-tui / grok-cli path (no tmux) should
68
+ * launch this turn. Extracted so the resume/bootstrap/stateless routing is
69
+ * unit-testable. The `maxConsecutiveFailures` guard REPLACES the old permanent
70
+ * "resumeBroken" flag that doomed a whole run to statelessness after one hiccup.
63
71
  *
64
- * - resumeBroken (this run) → 'stateless': `--resume` already hung this run, so
65
- * skip straight to the proven stateless invocation (no repeated ~90s probe).
66
- * - a stored session id → 'resume': attempt persistent `grok --resume <id>`
67
- * (the pre-1.4.178 behavior), gated by the first_token hang-probe.
68
- * - no stored id → 'new-persistent': bootstrap persistence with a fresh
69
- * named session (`--session-id`, mints + persists a UUID; NOT `--resume`, so it
70
- * can't hit the resume-hang), also behind the probe as a safety net.
72
+ * - no stored session id → 'new-persistent': there is
73
+ * no id to reuse, so bootstrap persistence with a fresh named session
74
+ * (`--session-id`, mints + persists a UUID; NOT `--resume`, so it can't hit a
75
+ * resume-hang), behind the probe as a safety net.
76
+ * - a stored id, failures < max → 'resume': attempt persistent
77
+ * `grok --resume <id>` (the reliable, near-instant path on grok 1.0.13), gated by
78
+ * the first_token hang-probe. This is the STEADY STATE — every turn resumes.
79
+ * - a stored id, failures >= max (sustained resume hang) → 'stateless' for exactly ONE
80
+ * breather turn (skip the probe cost once); the caller resets the counter so the
81
+ * NEXT turn returns to 'resume'. The stored id is preserved throughout, so resume
82
+ * is NEVER permanently abandoned — persistence self-heals.
71
83
  */
72
84
  export declare function decideHeadlessGrokMode(args: {
73
85
  hasSessionId: boolean;
74
- resumeBroken: boolean;
86
+ consecutiveResumeFailures: number;
87
+ maxConsecutiveFailures?: number;
75
88
  }): 'resume' | 'new-persistent' | 'stateless';
76
89
  /**
77
90
  * Pure verdict for the headless resume hang-probe.
78
91
  * 'ok' — grok emitted first_token → the resumed prompt is being answered;
79
92
  * let the persistent turn run to its normal turn-end.
80
- * 'fallback' — the probe window elapsed with NO first_token → the grok 1.0.41
81
- * headless `--resume` hang; kill grok and relaunch stateless.
93
+ * 'extend' — the window elapsed with NO first_token, but grok HAS written new
94
+ * session events since launch (it loaded the session and is actively
95
+ * working — e.g. a large session still streaming, or a tool call
96
+ * before the first text). NOT the hang signature → grant one more
97
+ * window instead of killing a healthy-but-slow resume. Only offered
98
+ * once (alreadyExtended gates it), so total probe time stays bounded
99
+ * well under the run watchdog.
100
+ * 'fallback' — the window elapsed with NO first_token AND no new events (a truly
101
+ * silent/loaded-but-idle resume) → the hang; kill + relaunch stateless.
82
102
  * 'wait' — neither yet (window not elapsed and no first_token) → keep waiting.
103
+ *
104
+ * Rationale (grok 1.0.13 measured): a healthy `--resume` produces first_token in
105
+ * ~40ms, so a plain time window is NOT a false trigger for normal sessions; the
106
+ * `grewSinceSeed` grace exists only so a very large session's slower load isn't
107
+ * mistaken for the loaded-but-never-answers hang. `grewSinceSeed`/`alreadyExtended`
108
+ * default false → the original single-window behavior is preserved for callers that
109
+ * don't pass them.
83
110
  */
84
111
  export declare function resumeProbeVerdict(args: {
85
112
  sawFirstToken: boolean;
86
113
  elapsedMs: number;
87
114
  probeMs: number;
88
- }): 'ok' | 'fallback' | 'wait';
115
+ grewSinceSeed?: boolean;
116
+ alreadyExtended?: boolean;
117
+ }): 'ok' | 'extend' | 'fallback' | 'wait';
89
118
  export declare function sendGrokTuiPrompt(root: string, promptFilePath: string, resolvedSessionId: string | undefined, stdoutFile: string, exitFile: string, log: (msg: string) => void, model?: string, showOutput?: () => void): void;
90
119
  export declare function steerGrokTui(root: string, text: string, log: (msg: string) => void): Promise<boolean>;
91
120
  /**
@@ -133,14 +133,27 @@ const _busyRoots = new Set();
133
133
  /** Active HEADLESS child processes by root — grok-cli + tmux-unavailable fallback. */
134
134
  const _activeChildren = new Map();
135
135
  /**
136
- * Roots whose HEADLESS `grok --resume` has proven to hang THIS run (loaded the
137
- * session but the first_token hang-probe timed out). Once a root is in here the
138
- * headless path skips straight to stateless instead of paying the ~90s probe on
139
- * every turn. Reset when the grok session is closed (a new loop/run gets a clean
140
- * slate and re-attempts resume once). Only ever consulted on the headless (no
141
- * tmux) path — the tmux persistent path is unaffected.
136
+ * CONSECUTIVE headless-`grok --resume` failures per root (probe hang OR an
137
+ * error-before-first-token). This REPLACES the old permanent `_resumeBroken` set,
138
+ * which — after a SINGLE transient resume hiccup — forced every later turn STATELESS
139
+ * (a fresh, unknown session id each turn → no conversation continuity for the rest
140
+ * of the run). Empirically `grok --resume <id>` is reliable and near-instant on the
141
+ * current grok (1.0.13: first_token ~40ms on resume, correct cross-turn recall), so
142
+ * a resume failure is treated as TRANSIENT, not terminal: the count is RESET to 0 the
143
+ * moment a resumed turn produces its first_token, and the headless path RE-ATTEMPTS
144
+ * `--resume <storedId>` on the very next turn. Only after RESUME_MAX_CONSEC_FAILURES
145
+ * straight failures does one turn take a stateless "breather" (skip the probe once),
146
+ * after which the count resets and resume is tried again — so persistence is never
147
+ * permanently abandoned. The STORED session id is preserved across every fallback
148
+ * (stateless relaunch uses persist:false and never overwrites it), so continuity
149
+ * resumes as soon as the transient condition clears. Cleared when the session closes.
142
150
  */
143
- const _resumeBroken = new Set();
151
+ const _resumeFailures = new Map();
152
+ /** Straight resume failures before ONE stateless breather turn (then retry resume). */
153
+ const RESUME_MAX_CONSEC_FAILURES = Math.max(1, envNum('AUTODEV_GROK_RESUME_MAX_FAILS', 3));
154
+ const _resumeFailCount = (root) => _resumeFailures.get(root) ?? 0;
155
+ const _noteResumeFailure = (root) => { _resumeFailures.set(root, _resumeFailCount(root) + 1); };
156
+ const _noteResumeSuccess = (root) => { _resumeFailures.delete(root); };
144
157
  const _tmuxSessions = new Map();
145
158
  // Per-root set of grok tool_call ids already emitted as PreToolUse, so a re-scan of
146
159
  // the transcript can't emit the SAME tool call twice → duplicate rows in the office
@@ -1652,6 +1665,7 @@ function sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, opts =
1652
1665
  // 0 (all stateless / non-probed calls) leaves the probe disarmed.
1653
1666
  const RESUME_PROBE_MS = Math.max(0, opts.resumeProbeMs ?? 0);
1654
1667
  let _sawFirstToken = false; // grok emitted first_token → resume works
1668
+ let _grokWroteEvents = false; // grok appended ANY session events this turn (loaded + active)
1655
1669
  let _resumeAbandoned = false; // probe killed this grok; a stateless relaunch owns the turn
1656
1670
  const _grokEmittedIds = _emittedToolIds.get(root) ?? (() => { const s = new Set(); _emittedToolIds.set(root, s); return s; })();
1657
1671
  const _grokSize = (f) => { try {
@@ -1748,9 +1762,11 @@ function sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, opts =
1748
1762
  // failing fast, a poisoned/stale session, or any unrecoverable resume error).
1749
1763
  // Reporting a task failure here would just re-resume the SAME broken session
1750
1764
  // next turn → the infinite `retry 1/10` loop. Instead, reuse the resume
1751
- // hang-probe's machinery: mark the root non-resumable for this run and relaunch
1752
- // THIS turn stateless (decideHeadlessGrokMode then routes later turns to
1753
- // new-persistent). Only for a launched RESUME (opts.sessionId present, probe
1765
+ // hang-probe's machinery: count ONE transient resume failure (bounded, NOT a
1766
+ // permanent poison) and relaunch THIS turn stateless so the task completes. The
1767
+ // STORED session id is preserved (stateless uses persist:false), so the next turn
1768
+ // re-attempts `--resume <storedId>` — continuity resumes as soon as the transient
1769
+ // condition clears. Only for a launched RESUME (opts.sessionId present, probe
1754
1770
  // armed); a new-persistent/stateless error falls through to the normal report.
1755
1771
  if (!clean && !_sawFirstToken && opts.sessionId && opts.onResumeHang && !_resumeAbandoned) {
1756
1772
  _resumeAbandoned = true; // close skips the exit file — the relaunch owns the turn
@@ -1844,10 +1860,21 @@ function sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, opts =
1844
1860
  _evOff = ev.offset;
1845
1861
  const ch = drainNewJsonlLines(chFile, _chOff);
1846
1862
  _chOff = ch.offset;
1863
+ if (ev.lines.length) {
1864
+ _grokWroteEvents = true;
1865
+ } // grok is writing → loaded + active (not a dead-load hang)
1847
1866
  // Resume hang-probe signal: grok emitted first_token → the (resumed) prompt is
1848
- // being answered, so the probe below leaves the turn to run normally.
1867
+ // being answered, so the probe below leaves the turn to run normally. On a RESUME
1868
+ // launch this is also proof the resume succeeded → clear the failure counter so a
1869
+ // prior transient hiccup stops biasing later turns.
1849
1870
  if (!_sawFirstToken && (0, grokEvents_1.scanGrokFirstToken)(ev.lines)) {
1850
1871
  _sawFirstToken = true;
1872
+ if (opts.sessionId && opts.onResumeOk) {
1873
+ try {
1874
+ opts.onResumeOk();
1875
+ }
1876
+ catch { /* best effort */ }
1877
+ }
1851
1878
  }
1852
1879
  // Turn-start signal: THIS turn has demonstrably begun in the tailed window
1853
1880
  // (turn_started/loop_started/first_token). A trailing turn_ended is only the
@@ -1908,46 +1935,65 @@ function sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, opts =
1908
1935
  // first, making the probe a no-op. Well under MAX_RUN_MS so it never races the
1909
1936
  // watchdog.
1910
1937
  let resumeProbeTimer = null;
1938
+ let _probeExtended = false; // the one grace window has been granted
1911
1939
  if (RESUME_PROBE_MS > 0 && opts.onResumeHang) {
1912
- resumeProbeTimer = setTimeout(() => {
1913
- // Final drain to catch a first_token / turn_ended written between polls.
1914
- try {
1915
- pumpGrokSessionFiles();
1916
- }
1917
- catch { /* best effort */ }
1918
- if (_endSeen || killed || _resumeAbandoned) {
1919
- return;
1920
- } // turn already resolved
1921
- if (resumeProbeVerdict({ sawFirstToken: _sawFirstToken, elapsedMs: RESUME_PROBE_MS, probeMs: RESUME_PROBE_MS }) === 'ok') {
1922
- return; // grok is answering — resume works; let the persistent turn run
1923
- }
1924
- // The hang: kill grok and relaunch this turn stateless. Mark abandoned FIRST
1925
- // so this child's `close` skips writing the exit file / clearing busy state —
1926
- // the stateless relaunch (invoked below) owns the turn's plumbing now.
1927
- _resumeAbandoned = true;
1928
- log(`Grok TUI: headless --resume produced no output in ${Math.round(RESUME_PROBE_MS / 1000)}s — falling back to stateless for this turn (session marked non-resumable)`);
1929
- if (grokFilePoll) {
1930
- clearInterval(grokFilePoll);
1931
- }
1932
- if (watchdog) {
1933
- clearTimeout(watchdog);
1934
- }
1935
- killed = true; // suppress the watchdog's own kill/report
1936
- if (child.pid) {
1937
- (0, killProcessTree_1.killProcessTree)(child.pid);
1938
- }
1939
- try {
1940
- child.kill('SIGKILL');
1941
- }
1942
- catch { /* ignore */ }
1943
- try {
1944
- opts.onResumeHang();
1945
- }
1946
- catch (e) {
1947
- log(`Grok TUI: stateless relaunch failed: ${e?.message ?? String(e)}`);
1948
- }
1949
- }, RESUME_PROBE_MS);
1950
- resumeProbeTimer.unref?.();
1940
+ const armProbe = () => {
1941
+ resumeProbeTimer = setTimeout(() => {
1942
+ // Final drain to catch a first_token / turn_ended written between polls.
1943
+ try {
1944
+ pumpGrokSessionFiles();
1945
+ }
1946
+ catch { /* best effort */ }
1947
+ if (_endSeen || killed || _resumeAbandoned) {
1948
+ return;
1949
+ } // turn already resolved
1950
+ const verdict = resumeProbeVerdict({
1951
+ sawFirstToken: _sawFirstToken, elapsedMs: RESUME_PROBE_MS, probeMs: RESUME_PROBE_MS,
1952
+ grewSinceSeed: _grokWroteEvents, alreadyExtended: _probeExtended,
1953
+ });
1954
+ if (verdict === 'ok') {
1955
+ return;
1956
+ } // grok is answering — resume works; let the turn run
1957
+ if (verdict === 'extend') {
1958
+ // Loaded + actively writing but not answering yet (large session / a tool
1959
+ // call before first text) → NOT the hang. Grant ONE more window rather than
1960
+ // killing a healthy-but-slow resume. Bounded (once) and well under the watchdog.
1961
+ _probeExtended = true;
1962
+ log(`Grok TUI: --resume still loading after ${Math.round(RESUME_PROBE_MS / 1000)}s but grok is writing session events — extending the probe once`);
1963
+ armProbe();
1964
+ return;
1965
+ }
1966
+ // The hang (verdict 'fallback'): grok produced nothing at all. Kill it and
1967
+ // relaunch THIS turn stateless. Mark abandoned FIRST so this child's `close`
1968
+ // skips the exit file / busy clear — the stateless relaunch owns the turn now.
1969
+ // NOTE: onResumeHang only counts a TRANSIENT failure (bounded counter); it does
1970
+ // NOT permanently disable resume — the next turn re-attempts `--resume`.
1971
+ _resumeAbandoned = true;
1972
+ log(`Grok TUI: headless --resume produced no output in ${Math.round(RESUME_PROBE_MS / 1000)}s — stateless for this turn only (stored session id kept; resume retried next turn)`);
1973
+ if (grokFilePoll) {
1974
+ clearInterval(grokFilePoll);
1975
+ }
1976
+ if (watchdog) {
1977
+ clearTimeout(watchdog);
1978
+ }
1979
+ killed = true; // suppress the watchdog's own kill/report
1980
+ if (child.pid) {
1981
+ (0, killProcessTree_1.killProcessTree)(child.pid);
1982
+ }
1983
+ try {
1984
+ child.kill('SIGKILL');
1985
+ }
1986
+ catch { /* ignore */ }
1987
+ try {
1988
+ opts.onResumeHang();
1989
+ }
1990
+ catch (e) {
1991
+ log(`Grok TUI: stateless relaunch failed: ${e?.message ?? String(e)}`);
1992
+ }
1993
+ }, RESUME_PROBE_MS);
1994
+ resumeProbeTimer.unref?.();
1995
+ };
1996
+ armProbe();
1951
1997
  }
1952
1998
  rl.on('line', (line) => {
1953
1999
  if (!line.trim()) {
@@ -2082,36 +2128,63 @@ function decideGrokRunMode(opts) {
2082
2128
  return { useTmux: opts.providerId === 'grok-tui' && opts.tmuxAvailable };
2083
2129
  }
2084
2130
  /**
2085
- * Pure decision for how the HEADLESS grok-tui path (no tmux) should launch this
2086
- * turn. Extracted so the resume/bootstrap/stateless routing is unit-testable.
2131
+ * Pure decision for how the HEADLESS grok-tui / grok-cli path (no tmux) should
2132
+ * launch this turn. Extracted so the resume/bootstrap/stateless routing is
2133
+ * unit-testable. The `maxConsecutiveFailures` guard REPLACES the old permanent
2134
+ * "resumeBroken" flag that doomed a whole run to statelessness after one hiccup.
2087
2135
  *
2088
- * - resumeBroken (this run) → 'stateless': `--resume` already hung this run, so
2089
- * skip straight to the proven stateless invocation (no repeated ~90s probe).
2090
- * - a stored session id → 'resume': attempt persistent `grok --resume <id>`
2091
- * (the pre-1.4.178 behavior), gated by the first_token hang-probe.
2092
- * - no stored id → 'new-persistent': bootstrap persistence with a fresh
2093
- * named session (`--session-id`, mints + persists a UUID; NOT `--resume`, so it
2094
- * can't hit the resume-hang), also behind the probe as a safety net.
2136
+ * - no stored session id → 'new-persistent': there is
2137
+ * no id to reuse, so bootstrap persistence with a fresh named session
2138
+ * (`--session-id`, mints + persists a UUID; NOT `--resume`, so it can't hit a
2139
+ * resume-hang), behind the probe as a safety net.
2140
+ * - a stored id, failures < max → 'resume': attempt persistent
2141
+ * `grok --resume <id>` (the reliable, near-instant path on grok 1.0.13), gated by
2142
+ * the first_token hang-probe. This is the STEADY STATE — every turn resumes.
2143
+ * - a stored id, failures >= max (sustained resume hang) → 'stateless' for exactly ONE
2144
+ * breather turn (skip the probe cost once); the caller resets the counter so the
2145
+ * NEXT turn returns to 'resume'. The stored id is preserved throughout, so resume
2146
+ * is NEVER permanently abandoned — persistence self-heals.
2095
2147
  */
2096
2148
  function decideHeadlessGrokMode(args) {
2097
- if (args.resumeBroken) {
2149
+ if (!args.hasSessionId) {
2150
+ return 'new-persistent';
2151
+ }
2152
+ const max = args.maxConsecutiveFailures ?? RESUME_MAX_CONSEC_FAILURES;
2153
+ if (args.consecutiveResumeFailures >= max) {
2098
2154
  return 'stateless';
2099
2155
  }
2100
- return args.hasSessionId ? 'resume' : 'new-persistent';
2156
+ return 'resume';
2101
2157
  }
2102
2158
  /**
2103
2159
  * Pure verdict for the headless resume hang-probe.
2104
2160
  * 'ok' — grok emitted first_token → the resumed prompt is being answered;
2105
2161
  * let the persistent turn run to its normal turn-end.
2106
- * 'fallback' — the probe window elapsed with NO first_token → the grok 1.0.41
2107
- * headless `--resume` hang; kill grok and relaunch stateless.
2162
+ * 'extend' — the window elapsed with NO first_token, but grok HAS written new
2163
+ * session events since launch (it loaded the session and is actively
2164
+ * working — e.g. a large session still streaming, or a tool call
2165
+ * before the first text). NOT the hang signature → grant one more
2166
+ * window instead of killing a healthy-but-slow resume. Only offered
2167
+ * once (alreadyExtended gates it), so total probe time stays bounded
2168
+ * well under the run watchdog.
2169
+ * 'fallback' — the window elapsed with NO first_token AND no new events (a truly
2170
+ * silent/loaded-but-idle resume) → the hang; kill + relaunch stateless.
2108
2171
  * 'wait' — neither yet (window not elapsed and no first_token) → keep waiting.
2172
+ *
2173
+ * Rationale (grok 1.0.13 measured): a healthy `--resume` produces first_token in
2174
+ * ~40ms, so a plain time window is NOT a false trigger for normal sessions; the
2175
+ * `grewSinceSeed` grace exists only so a very large session's slower load isn't
2176
+ * mistaken for the loaded-but-never-answers hang. `grewSinceSeed`/`alreadyExtended`
2177
+ * default false → the original single-window behavior is preserved for callers that
2178
+ * don't pass them.
2109
2179
  */
2110
2180
  function resumeProbeVerdict(args) {
2111
2181
  if (args.sawFirstToken) {
2112
2182
  return 'ok';
2113
2183
  }
2114
2184
  if (args.probeMs > 0 && args.elapsedMs >= args.probeMs) {
2185
+ if (args.grewSinceSeed && !args.alreadyExtended) {
2186
+ return 'extend';
2187
+ }
2115
2188
  return 'fallback';
2116
2189
  }
2117
2190
  return 'wait';
@@ -2132,31 +2205,46 @@ function sendGrokTuiPrompt(root, promptFilePath, resolvedSessionId, stdoutFile,
2132
2205
  * the resumed turn (loaded-but-never-answers) → the 10-min watchdog, so every
2133
2206
  * persistent launch is gated behind a FAST first_token hang-probe: if grok begins
2134
2207
  * answering within AUTODEV_GROK_RESUME_PROBE_MS the turn runs normally (persistent,
2135
- * context intact); if not, sendGrokPrompt kills it and calls onResumeHang to
2136
- * relaunch THIS turn stateless, and we mark the root non-resumable so later turns
2137
- * skip the probe. The stateless path stays intact as the safety net (sendGrokPrompt's
2138
- * session-log tailing still forwards tool/narration events and detects turn_ended
2139
- * either way).
2208
+ * context intact); if not, sendGrokPrompt kills it and calls onResumeHang to relaunch
2209
+ * THIS turn stateless. A resume failure is COUNTED (bounded, NOT a permanent poison):
2210
+ * the STORED session id is preserved and the NEXT turn re-attempts `--resume`, so a
2211
+ * transient hiccup can't strand the run in per-turn statelessness (the pre-1.4.184 bug).
2212
+ * Only after RESUME_MAX_CONSEC_FAILURES straight failures does a single breather turn
2213
+ * skip resume, after which resume is tried again. The stateless path stays intact as the
2214
+ * safety net (sendGrokPrompt's session-log tailing still forwards tool/narration events
2215
+ * and detects turn_ended either way).
2140
2216
  */
2141
2217
  function sendGrokPersistentHeadless(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, providerId, model, showOutput) {
2142
2218
  const label = providerId === 'grok-cli' ? 'Grok CLI' : 'Grok TUI';
2143
2219
  const PROBE_MS = envNum('AUTODEV_GROK_RESUME_PROBE_MS', 90_000);
2144
2220
  const persistedSid = resolvedSessionId || (0, sessionState_1.getSessionId)(root, providerId);
2221
+ // Stateless relaunch: grok mints a throwaway id for THIS turn. persist:false means
2222
+ // it never overwrites the STORED session id in session-state.json → the next turn
2223
+ // still resumes the real conversation. This is the per-turn safety net, NOT the
2224
+ // steady state (the steady state is 'resume').
2145
2225
  const runStateless = () => {
2146
2226
  sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, {
2147
2227
  model, showOutput, persist: false, providerId,
2148
2228
  });
2149
2229
  };
2150
- // The fallback the probe invokes on a hang: mark this root non-resumable for the
2151
- // rest of the run, then relaunch this same turn stateless (reusing the SAME
2152
- // stdout/exit-file plumbing) so the task still completes.
2230
+ // The fallback the probe / error-before-first-token path invokes: count ONE
2231
+ // transient resume failure (bounded — NOT a permanent poison) and relaunch this
2232
+ // same turn stateless so the task completes. The stored id is untouched, so the
2233
+ // next turn re-attempts `--resume <storedId>`.
2153
2234
  const onResumeHang = () => {
2154
- _resumeBroken.add(root);
2235
+ _noteResumeFailure(root);
2155
2236
  runStateless();
2156
2237
  };
2157
- const decision = decideHeadlessGrokMode({ hasSessionId: !!persistedSid, resumeBroken: _resumeBroken.has(root) });
2238
+ // Clear the failure counter the instant a resumed turn starts answering.
2239
+ const onResumeOk = () => { _noteResumeSuccess(root); };
2240
+ const decision = decideHeadlessGrokMode({
2241
+ hasSessionId: !!persistedSid, consecutiveResumeFailures: _resumeFailCount(root),
2242
+ });
2158
2243
  if (decision === 'stateless') {
2159
- log(`${label}: no tmux and --resume marked non-resumable this run — running stateless`);
2244
+ // Sustained resume failures → ONE breather turn (skip the probe cost), then the
2245
+ // counter resets so the NEXT turn resumes again. Persistence is never abandoned.
2246
+ log(`${label}: --resume failed ${_resumeFailCount(root)}× in a row — one stateless breather turn, will retry --resume next turn`);
2247
+ _noteResumeSuccess(root); // reset so the next turn returns to 'resume'
2160
2248
  runStateless();
2161
2249
  return;
2162
2250
  }
@@ -2164,13 +2252,13 @@ function sendGrokPersistentHeadless(root, promptFilePath, resolvedSessionId, std
2164
2252
  log(`${label}: resuming session ${persistedSid.slice(0, 8)}… (headless)`);
2165
2253
  sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, {
2166
2254
  model, showOutput, persist: true, sessionId: persistedSid, providerId,
2167
- resumeProbeMs: PROBE_MS, onResumeHang,
2255
+ resumeProbeMs: PROBE_MS, onResumeHang, onResumeOk,
2168
2256
  });
2169
2257
  return;
2170
2258
  }
2171
- // new-persistent — bootstrap a fresh named session so later turns have an id to
2172
- // resume. A fresh `--session-id` isn't a `--resume`, but keep the probe as a
2173
- // belt-and-braces safety net (and it can never exceed the watchdog).
2259
+ // new-persistent — no stored id yet: bootstrap a fresh named session so later turns
2260
+ // have an id to resume. A fresh `--session-id` isn't a `--resume`, but keep the
2261
+ // probe as a belt-and-braces safety net (and it can never exceed the watchdog).
2174
2262
  log(`${label}: starting a new persistent session (headless)`);
2175
2263
  sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, {
2176
2264
  model, showOutput, persist: true, providerId,
@@ -2253,9 +2341,9 @@ function closeGrokTuiSession(root, _log) {
2253
2341
  }
2254
2342
  _busyRoots.delete(root);
2255
2343
  _emittedToolIds.delete(root);
2256
- // A close marks a run boundary — clear the non-resumable mark so the next
2257
- // loop/run re-attempts persistent --resume once (behind the hang-probe).
2258
- _resumeBroken.delete(root);
2344
+ // A close marks a run boundary — clear the transient resume-failure count so the
2345
+ // next loop/run starts clean and re-attempts persistent --resume immediately.
2346
+ _resumeFailures.delete(root);
2259
2347
  }
2260
2348
  /** Kill all live grok sessions — called on SDK/extension shutdown. */
2261
2349
  function closeAllGrokTuiSessions() {
@@ -2275,7 +2363,7 @@ function closeAllGrokTuiSessions() {
2275
2363
  _tmuxSessions.clear();
2276
2364
  _busyRoots.clear();
2277
2365
  _emittedToolIds.clear();
2278
- _resumeBroken.clear();
2366
+ _resumeFailures.clear();
2279
2367
  }
2280
2368
  // ---------------------------------------------------------------------------
2281
2369
  // detectGrokTuiRateLimit