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.
- package/out/core/settingsLoader.d.ts +9 -0
- package/out/core/settingsLoader.js +1 -0
- package/out/core/settingsLoader.js.map +1 -1
- package/out/providers/grok/tui.d.ts +42 -13
- package/out/providers/grok/tui.js +170 -82
- package/out/providers/grok/tui.js.map +1 -1
- package/out/taskLoop.d.ts +43 -0
- package/out/taskLoop.js +112 -17
- package/out/taskLoop.js.map +1 -1
- package/package.json +2 -2
|
@@ -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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settingsLoader.js","sourceRoot":"","sources":["../../src/core/settingsLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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
|
|
62
|
-
* turn. Extracted so the resume/bootstrap/stateless routing is
|
|
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
|
-
* -
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* -
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
-
|
|
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
|
-
* '
|
|
81
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
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
|
|
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:
|
|
1752
|
-
// THIS turn stateless
|
|
1753
|
-
//
|
|
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
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
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
|
|
2086
|
-
* turn. Extracted so the resume/bootstrap/stateless routing is
|
|
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
|
-
* -
|
|
2089
|
-
*
|
|
2090
|
-
*
|
|
2091
|
-
*
|
|
2092
|
-
* -
|
|
2093
|
-
*
|
|
2094
|
-
*
|
|
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.
|
|
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
|
|
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
|
-
* '
|
|
2107
|
-
*
|
|
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
|
-
*
|
|
2137
|
-
*
|
|
2138
|
-
*
|
|
2139
|
-
*
|
|
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
|
|
2151
|
-
//
|
|
2152
|
-
//
|
|
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
|
-
|
|
2235
|
+
_noteResumeFailure(root);
|
|
2155
2236
|
runStateless();
|
|
2156
2237
|
};
|
|
2157
|
-
|
|
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
|
-
|
|
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
|
|
2172
|
-
// resume. A fresh `--session-id` isn't a `--resume`, but keep the
|
|
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
|
|
2257
|
-
// loop/run re-attempts persistent --resume
|
|
2258
|
-
|
|
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
|
-
|
|
2366
|
+
_resumeFailures.clear();
|
|
2279
2367
|
}
|
|
2280
2368
|
// ---------------------------------------------------------------------------
|
|
2281
2369
|
// detectGrokTuiRateLimit
|