autodev-cli 1.4.187 → 1.4.188
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/providers/grok/GrokAcpSession.d.ts +122 -0
- package/out/providers/grok/GrokAcpSession.js +506 -0
- package/out/providers/grok/GrokAcpSession.js.map +1 -0
- package/out/providers/grok/tui.d.ts +25 -0
- package/out/providers/grok/tui.js +220 -0
- package/out/providers/grok/tui.js.map +1 -1
- package/out/taskLoop.js +10 -0
- package/out/taskLoop.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { RateLimitDetector } from '../../rateLimit';
|
|
2
2
|
import type { ProviderId } from '../../providers';
|
|
3
|
+
export declare function grokAgentStdioSupported(): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether the persistent ACP path is active. Gated on grok supporting `agent
|
|
6
|
+
* stdio` AND the AUTODEV_GROK_PERSISTENT env override (default ON when supported;
|
|
7
|
+
* set to 'false'/'0'/'off' to force the legacy one-shot headless path).
|
|
8
|
+
*/
|
|
9
|
+
export declare function grokAcpEnabled(): boolean;
|
|
3
10
|
/** True while a grok turn is running for the given workspace root. */
|
|
4
11
|
export declare function isGrokTuiBusy(root: string): boolean;
|
|
5
12
|
/** Epoch-ms of the most recent streamed pane activity, or 0 if none. */
|
|
@@ -69,6 +76,24 @@ export declare function sendGrokPrompt(root: string, promptFilePath: string, std
|
|
|
69
76
|
* to a stateless relaunch instead of hanging the loop.
|
|
70
77
|
*/
|
|
71
78
|
export declare function sendGrokCliPrompt(root: string, promptFilePath: string, resolvedSessionId: string | undefined, stdoutFile: string, exitFile: string, log: (msg: string) => void, model?: string, showOutput?: () => void): void;
|
|
79
|
+
/**
|
|
80
|
+
* Fire-and-forget: run ONE turn through the persistent ACP session. Streams
|
|
81
|
+
* assistant text to stdoutFile and writes the exit code to exitFile exactly once
|
|
82
|
+
* at turn end — preserving the taskLoop contract byte-for-byte. The grok process
|
|
83
|
+
* STAYS ALIVE between turns; a crash/exit is detected and the NEXT turn respawns
|
|
84
|
+
* + session/load restores context. If the ACP handshake can't be established
|
|
85
|
+
* (older grok, or a transient spawn failure) it transparently FALLS BACK to the
|
|
86
|
+
* legacy one-shot headless path so nothing regresses.
|
|
87
|
+
*/
|
|
88
|
+
export declare function sendGrokAcpPrompt(root: string, promptFilePath: string, resolvedSessionId: string | undefined, stdoutFile: string, exitFile: string, log: (msg: string) => void, providerId: 'grok-cli' | 'grok-tui', model?: string, showOutput?: () => void): void;
|
|
89
|
+
/**
|
|
90
|
+
* Deliver a mid-run steer to the LIVE persistent grok process (a queued
|
|
91
|
+
* session/prompt). Returns true only when a live ACP session accepted it; false
|
|
92
|
+
* lets the caller fall back to the durable TODO buffer (at-least-once).
|
|
93
|
+
*/
|
|
94
|
+
export declare function steerGrokAcp(root: string, text: string, log: (msg: string) => void): boolean;
|
|
95
|
+
/** True if a persistent ACP session for this root is mid-turn. */
|
|
96
|
+
export declare function isGrokAcpBusy(root: string): boolean;
|
|
72
97
|
/**
|
|
73
98
|
* grok-tui: persistent — run the turn in the per-workspace tmux session so
|
|
74
99
|
* grok's in-process context accumulates across tasks. Falls back to the old
|
|
@@ -70,6 +70,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
70
70
|
};
|
|
71
71
|
})();
|
|
72
72
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
73
|
+
exports.grokAgentStdioSupported = grokAgentStdioSupported;
|
|
74
|
+
exports.grokAcpEnabled = grokAcpEnabled;
|
|
73
75
|
exports.isGrokTuiBusy = isGrokTuiBusy;
|
|
74
76
|
exports.getGrokTuiLastActivity = getGrokTuiLastActivity;
|
|
75
77
|
exports.forceIdleGrokTui = forceIdleGrokTui;
|
|
@@ -77,6 +79,9 @@ exports.tmuxAvailable = tmuxAvailable;
|
|
|
77
79
|
exports.grokSessionArgsFor = grokSessionArgsFor;
|
|
78
80
|
exports.sendGrokPrompt = sendGrokPrompt;
|
|
79
81
|
exports.sendGrokCliPrompt = sendGrokCliPrompt;
|
|
82
|
+
exports.sendGrokAcpPrompt = sendGrokAcpPrompt;
|
|
83
|
+
exports.steerGrokAcp = steerGrokAcp;
|
|
84
|
+
exports.isGrokAcpBusy = isGrokAcpBusy;
|
|
80
85
|
exports.decideGrokRunMode = decideGrokRunMode;
|
|
81
86
|
exports.decideHeadlessGrokMode = decideHeadlessGrokMode;
|
|
82
87
|
exports.resumeProbeVerdict = resumeProbeVerdict;
|
|
@@ -102,6 +107,7 @@ const authClassify_1 = require("./authClassify");
|
|
|
102
107
|
const grokEvents_1 = require("../../core/grokEvents");
|
|
103
108
|
const sessions_1 = require("./sessions");
|
|
104
109
|
const redactSecrets_1 = require("../../core/redactSecrets");
|
|
110
|
+
const GrokAcpSession_1 = require("./GrokAcpSession");
|
|
105
111
|
// ---------------------------------------------------------------------------
|
|
106
112
|
// Constants
|
|
107
113
|
// ---------------------------------------------------------------------------
|
|
@@ -156,6 +162,39 @@ const _resumeFailCount = (root) => _resumeFailures.get(root) ?? 0;
|
|
|
156
162
|
const _noteResumeFailure = (root) => { _resumeFailures.set(root, _resumeFailCount(root) + 1); };
|
|
157
163
|
const _noteResumeSuccess = (root) => { _resumeFailures.delete(root); };
|
|
158
164
|
const _tmuxSessions = new Map();
|
|
165
|
+
/**
|
|
166
|
+
* Live PERSISTENT `grok agent stdio` ACP sessions by root (the persistent-process
|
|
167
|
+
* model). ONE long-lived grok per workspace serves many turns without exiting;
|
|
168
|
+
* resume happens only at (re)start via session/load. See GrokAcpSession.
|
|
169
|
+
*/
|
|
170
|
+
const _acpSessions = new Map();
|
|
171
|
+
/** Cached probe: does this grok build expose `agent stdio` (the ACP transport)? */
|
|
172
|
+
let _grokAgentStdioSupported = null;
|
|
173
|
+
function grokAgentStdioSupported() {
|
|
174
|
+
if (_grokAgentStdioSupported !== null) {
|
|
175
|
+
return _grokAgentStdioSupported;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const out = child_process.execFileSync(GROK_BIN, ['agent', '--help'], { encoding: 'utf8', timeout: 8_000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
179
|
+
_grokAgentStdioSupported = /\bstdio\b/.test(out);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
_grokAgentStdioSupported = false;
|
|
183
|
+
}
|
|
184
|
+
return _grokAgentStdioSupported;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Whether the persistent ACP path is active. Gated on grok supporting `agent
|
|
188
|
+
* stdio` AND the AUTODEV_GROK_PERSISTENT env override (default ON when supported;
|
|
189
|
+
* set to 'false'/'0'/'off' to force the legacy one-shot headless path).
|
|
190
|
+
*/
|
|
191
|
+
function grokAcpEnabled() {
|
|
192
|
+
const raw = String(process.env['AUTODEV_GROK_PERSISTENT'] ?? '').toLowerCase();
|
|
193
|
+
if (raw === 'false' || raw === '0' || raw === 'off' || raw === 'no') {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return grokAgentStdioSupported();
|
|
197
|
+
}
|
|
159
198
|
// Per-root set of grok tool_call ids already emitted as PreToolUse, so a re-scan of
|
|
160
199
|
// the transcript can't emit the SAME tool call twice → duplicate rows in the office
|
|
161
200
|
// hook feed (observed ~2.4× per call). PostToolUse is already dedup'd by pending.delete;
|
|
@@ -2257,8 +2296,154 @@ function sendGrokPrompt(root, promptFilePath, stdoutFile, exitFile, log, opts =
|
|
|
2257
2296
|
* to a stateless relaunch instead of hanging the loop.
|
|
2258
2297
|
*/
|
|
2259
2298
|
function sendGrokCliPrompt(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, model, showOutput) {
|
|
2299
|
+
if (grokAcpEnabled()) {
|
|
2300
|
+
sendGrokAcpPrompt(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, 'grok-cli', model, showOutput);
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2260
2303
|
sendGrokPersistentHeadless(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, 'grok-cli', model, showOutput);
|
|
2261
2304
|
}
|
|
2305
|
+
// ---------------------------------------------------------------------------
|
|
2306
|
+
// PERSISTENT-PROCESS (ACP) path — one long-lived `grok agent stdio` per root.
|
|
2307
|
+
// ---------------------------------------------------------------------------
|
|
2308
|
+
/** Get (or lazily create) the persistent ACP session for a root. */
|
|
2309
|
+
function getOrCreateAcpSession(root, providerId, model, log, resolvedSessionId) {
|
|
2310
|
+
let sess = _acpSessions.get(root);
|
|
2311
|
+
if (sess) {
|
|
2312
|
+
return sess;
|
|
2313
|
+
}
|
|
2314
|
+
// Resume-on-(re)start: seed the id to session/load from the persisted id, else
|
|
2315
|
+
// the newest grok session on disk for this cwd (works whether that session was
|
|
2316
|
+
// created by a prior ACP run or the legacy headless `--continue`).
|
|
2317
|
+
const seedId = resolvedSessionId || (0, sessionState_1.getSessionId)(root, providerId) || (0, sessions_1.getGrokLatestSessionId)(root) || undefined;
|
|
2318
|
+
const hooks = {
|
|
2319
|
+
emitHook: (name, extra) => {
|
|
2320
|
+
if (name === 'Notification' && typeof extra['message'] === 'string') {
|
|
2321
|
+
const msg = (0, redactSecrets_1.redactSecrets)(String(extra['message'])).slice(0, 1200);
|
|
2322
|
+
if (!msg.trim()) {
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
_emitGrokHook(root, 'Notification', { ...extra, message: msg });
|
|
2326
|
+
}
|
|
2327
|
+
else {
|
|
2328
|
+
_emitGrokHook(root, name, (0, redactSecrets_1.redactDeep)(extra));
|
|
2329
|
+
}
|
|
2330
|
+
},
|
|
2331
|
+
log,
|
|
2332
|
+
};
|
|
2333
|
+
sess = new GrokAcpSession_1.GrokAcpSession({ root, model, bin: GROK_BIN, hooks, initialSessionId: seedId });
|
|
2334
|
+
_acpSessions.set(root, sess);
|
|
2335
|
+
return sess;
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Fire-and-forget: run ONE turn through the persistent ACP session. Streams
|
|
2339
|
+
* assistant text to stdoutFile and writes the exit code to exitFile exactly once
|
|
2340
|
+
* at turn end — preserving the taskLoop contract byte-for-byte. The grok process
|
|
2341
|
+
* STAYS ALIVE between turns; a crash/exit is detected and the NEXT turn respawns
|
|
2342
|
+
* + session/load restores context. If the ACP handshake can't be established
|
|
2343
|
+
* (older grok, or a transient spawn failure) it transparently FALLS BACK to the
|
|
2344
|
+
* legacy one-shot headless path so nothing regresses.
|
|
2345
|
+
*/
|
|
2346
|
+
function sendGrokAcpPrompt(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, providerId, model, showOutput) {
|
|
2347
|
+
try {
|
|
2348
|
+
fs.writeFileSync(stdoutFile, '', 'utf8');
|
|
2349
|
+
}
|
|
2350
|
+
catch { /* ignore */ }
|
|
2351
|
+
try {
|
|
2352
|
+
fs.writeFileSync(exitFile, '', 'utf8');
|
|
2353
|
+
}
|
|
2354
|
+
catch { /* ignore */ }
|
|
2355
|
+
let promptText = '';
|
|
2356
|
+
try {
|
|
2357
|
+
promptText = fs.readFileSync(promptFilePath, 'utf8');
|
|
2358
|
+
}
|
|
2359
|
+
catch (err) {
|
|
2360
|
+
try {
|
|
2361
|
+
fs.appendFileSync(stdoutFile, `\n[Grok ACP: cannot read prompt file: ${err?.message ?? err}]\n`, 'utf8');
|
|
2362
|
+
}
|
|
2363
|
+
catch { /* ignore */ }
|
|
2364
|
+
try {
|
|
2365
|
+
fs.writeFileSync(exitFile, '1\n', 'utf8');
|
|
2366
|
+
}
|
|
2367
|
+
catch { /* ignore */ }
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
const sess = getOrCreateAcpSession(root, providerId, model, log, resolvedSessionId);
|
|
2371
|
+
_busyRoots.add(root);
|
|
2372
|
+
if (showOutput) {
|
|
2373
|
+
try {
|
|
2374
|
+
showOutput();
|
|
2375
|
+
}
|
|
2376
|
+
catch { /* ignore */ }
|
|
2377
|
+
}
|
|
2378
|
+
const sink = (text) => { try {
|
|
2379
|
+
fs.appendFileSync(stdoutFile, text, 'utf8');
|
|
2380
|
+
}
|
|
2381
|
+
catch { /* ignore */ } };
|
|
2382
|
+
void (async () => {
|
|
2383
|
+
try {
|
|
2384
|
+
await sess.ensureReady();
|
|
2385
|
+
}
|
|
2386
|
+
catch (err) {
|
|
2387
|
+
// ACP unavailable / handshake failed → clean fallback to the one-shot path
|
|
2388
|
+
// (which writes its own stdout/exit + hooks). Nothing regresses.
|
|
2389
|
+
log(`Grok ACP: startup failed (${err?.message ?? err}); stderr="${sess.stderrSnapshot.slice(0, 200)}" — falling back to headless one-shot`);
|
|
2390
|
+
try {
|
|
2391
|
+
sess.close();
|
|
2392
|
+
}
|
|
2393
|
+
catch { /* ignore */ }
|
|
2394
|
+
_acpSessions.delete(root);
|
|
2395
|
+
_busyRoots.delete(root);
|
|
2396
|
+
sendGrokPersistentHeadless(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, providerId, model, showOutput);
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
// Persist the session id so a restart can session/load it (resume-on-restart).
|
|
2400
|
+
if (sess.sessionId) {
|
|
2401
|
+
try {
|
|
2402
|
+
(0, sessionState_1.saveSessionId)(root, providerId, sess.sessionId);
|
|
2403
|
+
}
|
|
2404
|
+
catch { /* ignore */ }
|
|
2405
|
+
}
|
|
2406
|
+
let res;
|
|
2407
|
+
try {
|
|
2408
|
+
res = await sess.runTurn(promptText, sink);
|
|
2409
|
+
}
|
|
2410
|
+
catch (err) {
|
|
2411
|
+
res = { clean: false, stopReason: 'exception', error: err?.message ?? String(err) };
|
|
2412
|
+
}
|
|
2413
|
+
if (!res.clean && res.error) {
|
|
2414
|
+
try {
|
|
2415
|
+
fs.appendFileSync(stdoutFile, `\n[Grok ACP: turn ended ${res.stopReason} — ${res.error}]\n`, 'utf8');
|
|
2416
|
+
}
|
|
2417
|
+
catch { /* ignore */ }
|
|
2418
|
+
}
|
|
2419
|
+
try {
|
|
2420
|
+
fs.writeFileSync(exitFile, `${res.clean ? 0 : 1}\n`, 'utf8');
|
|
2421
|
+
}
|
|
2422
|
+
catch { /* ignore */ }
|
|
2423
|
+
_busyRoots.delete(root);
|
|
2424
|
+
})();
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Deliver a mid-run steer to the LIVE persistent grok process (a queued
|
|
2428
|
+
* session/prompt). Returns true only when a live ACP session accepted it; false
|
|
2429
|
+
* lets the caller fall back to the durable TODO buffer (at-least-once).
|
|
2430
|
+
*/
|
|
2431
|
+
function steerGrokAcp(root, text, log) {
|
|
2432
|
+
const sess = _acpSessions.get(root);
|
|
2433
|
+
if (!sess || !sess.isAlive()) {
|
|
2434
|
+
return false;
|
|
2435
|
+
}
|
|
2436
|
+
const ok = sess.steer(text);
|
|
2437
|
+
if (ok) {
|
|
2438
|
+
log(`Grok ACP: steered live process (${text.length} chars) — queued to the running session`);
|
|
2439
|
+
}
|
|
2440
|
+
return ok;
|
|
2441
|
+
}
|
|
2442
|
+
/** True if a persistent ACP session for this root is mid-turn. */
|
|
2443
|
+
function isGrokAcpBusy(root) {
|
|
2444
|
+
const sess = _acpSessions.get(root);
|
|
2445
|
+
return !!sess && sess.isBusy();
|
|
2446
|
+
}
|
|
2262
2447
|
/**
|
|
2263
2448
|
* grok-tui: persistent — run the turn in the per-workspace tmux session so
|
|
2264
2449
|
* grok's in-process context accumulates across tasks. Falls back to the old
|
|
@@ -2357,6 +2542,12 @@ function resumeProbeVerdict(args) {
|
|
|
2357
2542
|
return 'wait';
|
|
2358
2543
|
}
|
|
2359
2544
|
function sendGrokTuiPrompt(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, model, showOutput) {
|
|
2545
|
+
// Preferred persistent model: ONE long-lived `grok agent stdio` (ACP). Cleaner
|
|
2546
|
+
// than the tmux PTY (no pane-scraping, real steering, unified with grok-cli).
|
|
2547
|
+
if (grokAcpEnabled()) {
|
|
2548
|
+
sendGrokAcpPrompt(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, 'grok-tui', model, showOutput);
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2360
2551
|
const mode = decideGrokRunMode({ tmuxAvailable: tmuxAvailable(), providerId: 'grok-tui' });
|
|
2361
2552
|
if (mode.useTmux) {
|
|
2362
2553
|
runGrokTmuxTurn(root, promptFilePath, resolvedSessionId, stdoutFile, exitFile, log, model, showOutput);
|
|
@@ -2491,6 +2682,22 @@ async function steerGrokTui(root, text, log) {
|
|
|
2491
2682
|
* persisted session id so a later task relaunches with --resume (context intact).
|
|
2492
2683
|
*/
|
|
2493
2684
|
function closeGrokTuiSession(root, _log) {
|
|
2685
|
+
const acp = _acpSessions.get(root);
|
|
2686
|
+
if (acp) {
|
|
2687
|
+
// Reap grok's whole worker subtree (grok forks a per-turn worker child), then
|
|
2688
|
+
// close — SIGKILL of just the parent would orphan the worker to init.
|
|
2689
|
+
if (acp.pid) {
|
|
2690
|
+
try {
|
|
2691
|
+
(0, killProcessTree_1.killProcessTree)(acp.pid);
|
|
2692
|
+
}
|
|
2693
|
+
catch { /* ignore */ }
|
|
2694
|
+
}
|
|
2695
|
+
try {
|
|
2696
|
+
acp.close();
|
|
2697
|
+
}
|
|
2698
|
+
catch { /* ignore */ }
|
|
2699
|
+
_acpSessions.delete(root);
|
|
2700
|
+
}
|
|
2494
2701
|
const child = _activeChildren.get(root);
|
|
2495
2702
|
if (child) {
|
|
2496
2703
|
if (child.pid) {
|
|
@@ -2522,6 +2729,19 @@ function closeGrokTuiSession(root, _log) {
|
|
|
2522
2729
|
}
|
|
2523
2730
|
/** Kill all live grok sessions — called on SDK/extension shutdown. */
|
|
2524
2731
|
function closeAllGrokTuiSessions() {
|
|
2732
|
+
for (const acp of _acpSessions.values()) {
|
|
2733
|
+
if (acp.pid) {
|
|
2734
|
+
try {
|
|
2735
|
+
(0, killProcessTree_1.killProcessTree)(acp.pid);
|
|
2736
|
+
}
|
|
2737
|
+
catch { /* ignore */ }
|
|
2738
|
+
}
|
|
2739
|
+
try {
|
|
2740
|
+
acp.close();
|
|
2741
|
+
}
|
|
2742
|
+
catch { /* ignore */ }
|
|
2743
|
+
}
|
|
2744
|
+
_acpSessions.clear();
|
|
2525
2745
|
for (const child of _activeChildren.values()) {
|
|
2526
2746
|
if (child.pid) {
|
|
2527
2747
|
(0, killProcessTree_1.killProcessTree)(child.pid);
|