autodev-cli 1.4.191 → 1.4.193
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/cli.js +4 -0
- package/out/cli.js.map +1 -1
- package/out/commands/grokKeepalive.d.ts +10 -0
- package/out/commands/grokKeepalive.js +27 -0
- package/out/commands/grokKeepalive.js.map +1 -0
- package/out/commands/leaderStop.d.ts +10 -0
- package/out/commands/leaderStop.js +63 -0
- package/out/commands/leaderStop.js.map +1 -0
- package/out/pendingSteers.d.ts +28 -0
- package/out/pendingSteers.js +145 -0
- package/out/pendingSteers.js.map +1 -0
- package/out/providers/acp/AcpSession.d.ts +31 -0
- package/out/providers/acp/AcpSession.js +70 -1
- package/out/providers/acp/AcpSession.js.map +1 -1
- package/out/providers/grok/GrokAcpSession.d.ts +14 -1
- package/out/providers/grok/GrokAcpSession.js +29 -2
- package/out/providers/grok/GrokAcpSession.js.map +1 -1
- package/out/providers/grok/keepalive.d.ts +15 -0
- package/out/providers/grok/keepalive.js +141 -0
- package/out/providers/grok/keepalive.js.map +1 -0
- package/out/providers/grok/keepaliveMain.d.ts +1 -0
- package/out/providers/grok/keepaliveMain.js +22 -0
- package/out/providers/grok/keepaliveMain.js.map +1 -0
- package/out/providers/grok/leader.d.ts +66 -0
- package/out/providers/grok/leader.js +390 -0
- package/out/providers/grok/leader.js.map +1 -0
- package/out/providers/grok/tui.d.ts +18 -0
- package/out/providers/grok/tui.js +187 -28
- package/out/providers/grok/tui.js.map +1 -1
- package/out/providers/opencode/acp.d.ts +13 -1
- package/out/providers/opencode/acp.js +45 -0
- package/out/providers/opencode/acp.js.map +1 -1
- package/out/providers/opencode/sdk.d.ts +1 -0
- package/out/providers/opencode/sdk.js +8 -0
- package/out/providers/opencode/sdk.js.map +1 -1
- package/out/taskLoop.d.ts +29 -0
- package/out/taskLoop.js +177 -2
- package/out/taskLoop.js.map +1 -1
- package/out/webSocketPoller.d.ts +6 -2
- package/out/webSocketPoller.js +8 -2
- package/out/webSocketPoller.js.map +1 -1
- package/out/webhookPoller.d.ts +4 -1
- package/out/webhookPoller.js +7 -0
- package/out/webhookPoller.js.map +1 -1
- package/package.json +2 -2
|
@@ -82,6 +82,9 @@ exports.sendGrokCliPrompt = sendGrokCliPrompt;
|
|
|
82
82
|
exports.sendGrokAcpPrompt = sendGrokAcpPrompt;
|
|
83
83
|
exports.steerGrokAcp = steerGrokAcp;
|
|
84
84
|
exports.isGrokAcpBusy = isGrokAcpBusy;
|
|
85
|
+
exports.hasGrokAcpSession = hasGrokAcpSession;
|
|
86
|
+
exports.steerGrokAcpTracked = steerGrokAcpTracked;
|
|
87
|
+
exports.ensureGrokAcpSteer = ensureGrokAcpSteer;
|
|
85
88
|
exports.decideGrokRunMode = decideGrokRunMode;
|
|
86
89
|
exports.decideHeadlessGrokMode = decideHeadlessGrokMode;
|
|
87
90
|
exports.resumeProbeVerdict = resumeProbeVerdict;
|
|
@@ -108,6 +111,8 @@ const grokEvents_1 = require("../../core/grokEvents");
|
|
|
108
111
|
const sessions_1 = require("./sessions");
|
|
109
112
|
const redactSecrets_1 = require("../../core/redactSecrets");
|
|
110
113
|
const GrokAcpSession_1 = require("./GrokAcpSession");
|
|
114
|
+
const AcpSession_1 = require("../acp/AcpSession");
|
|
115
|
+
const leader_1 = require("./leader");
|
|
111
116
|
// ---------------------------------------------------------------------------
|
|
112
117
|
// Constants
|
|
113
118
|
// ---------------------------------------------------------------------------
|
|
@@ -2375,20 +2380,10 @@ function sendGrokCliPrompt(root, promptFilePath, resolvedSessionId, stdoutFile,
|
|
|
2375
2380
|
// ---------------------------------------------------------------------------
|
|
2376
2381
|
// PERSISTENT-PROCESS (ACP) path — one long-lived `grok agent stdio` per root.
|
|
2377
2382
|
// ---------------------------------------------------------------------------
|
|
2378
|
-
/**
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
return sess;
|
|
2383
|
-
}
|
|
2384
|
-
// Resume-on-(re)start: seed the id to session/load from the persisted id, else
|
|
2385
|
-
// the newest grok session on disk for this cwd (works whether that session was
|
|
2386
|
-
// created by a prior ACP run or the legacy headless `--continue`).
|
|
2387
|
-
// Layer A.2: PREFER the persisted id (session-state.json) over the disk-latest
|
|
2388
|
-
// fallback — disk-latest can resume the WRONG session (a stray/newer dir). Use
|
|
2389
|
-
// disk-latest ONLY when there is no persisted id.
|
|
2390
|
-
const seedId = (0, sessionState_1.getSessionId)(root, providerId) || resolvedSessionId || (0, sessions_1.getGrokLatestSessionId)(root) || undefined;
|
|
2391
|
-
const hooks = {
|
|
2383
|
+
/** Redaction-aware AcpHostHooks that append to this root's hook feed. Shared by
|
|
2384
|
+
* the non-leader and leader ACP paths so hook emission stays in one place. */
|
|
2385
|
+
function makeAcpHooks(root, log) {
|
|
2386
|
+
return {
|
|
2392
2387
|
emitHook: (name, extra) => {
|
|
2393
2388
|
if (name === 'Notification' && typeof extra['message'] === 'string') {
|
|
2394
2389
|
const msg = (0, redactSecrets_1.redactSecrets)(String(extra['message'])).slice(0, 1200);
|
|
@@ -2403,10 +2398,88 @@ function getOrCreateAcpSession(root, providerId, model, log, resolvedSessionId)
|
|
|
2403
2398
|
},
|
|
2404
2399
|
log,
|
|
2405
2400
|
};
|
|
2406
|
-
|
|
2401
|
+
}
|
|
2402
|
+
/** Get (or lazily create) the NON-LEADER persistent ACP session for a root. */
|
|
2403
|
+
function getOrCreateAcpSession(root, providerId, model, log, resolvedSessionId) {
|
|
2404
|
+
let sess = _acpSessions.get(root);
|
|
2405
|
+
if (sess) {
|
|
2406
|
+
return sess;
|
|
2407
|
+
}
|
|
2408
|
+
// Resume-on-(re)start: seed the id to session/load from the persisted id, else
|
|
2409
|
+
// the newest grok session on disk for this cwd (works whether that session was
|
|
2410
|
+
// created by a prior ACP run or the legacy headless `--continue`).
|
|
2411
|
+
// Layer A.2: PREFER the persisted id (session-state.json) over the disk-latest
|
|
2412
|
+
// fallback — disk-latest can resume the WRONG session (a stray/newer dir). Use
|
|
2413
|
+
// disk-latest ONLY when there is no persisted id.
|
|
2414
|
+
const seedId = (0, sessionState_1.getSessionId)(root, providerId) || resolvedSessionId || (0, sessions_1.getGrokLatestSessionId)(root) || undefined;
|
|
2415
|
+
sess = new GrokAcpSession_1.GrokAcpSession({ root, model, bin: GROK_BIN, hooks: makeAcpHooks(root, log), initialSessionId: seedId });
|
|
2407
2416
|
_acpSessions.set(root, sess);
|
|
2408
2417
|
return sess;
|
|
2409
2418
|
}
|
|
2419
|
+
/**
|
|
2420
|
+
* LEADER-AWARE ensure (Layer B, 1.4.193). When AUTODEV_GROK_LEADER is ON, ensure a
|
|
2421
|
+
* per-agent leader + keepalive backend (orphan-sweeping/adopting/spawning as
|
|
2422
|
+
* needed) and return a LEADER-CLIENT ACP session that ATTACHES to the warm session
|
|
2423
|
+
* WITHOUT session/load. When the flag is OFF — or the leader backend can't be
|
|
2424
|
+
* established — this returns the existing non-leader session UNCHANGED. Async
|
|
2425
|
+
* because ensuring the leader spawns detached processes on a cold start.
|
|
2426
|
+
*/
|
|
2427
|
+
async function ensureAcpSession(root, providerId, model, log, resolvedSessionId) {
|
|
2428
|
+
if ((0, leader_1.leaderModeEnabled)()) {
|
|
2429
|
+
const cached = _acpSessions.get(root);
|
|
2430
|
+
if (cached && cached.isAlive()) {
|
|
2431
|
+
return cached;
|
|
2432
|
+
}
|
|
2433
|
+
if (cached) {
|
|
2434
|
+
try {
|
|
2435
|
+
cached.close();
|
|
2436
|
+
}
|
|
2437
|
+
catch { /* ignore */ }
|
|
2438
|
+
_acpSessions.delete(root);
|
|
2439
|
+
}
|
|
2440
|
+
const info = await (0, leader_1.ensureLeader)({ root, bin: GROK_BIN, providerId, model, log });
|
|
2441
|
+
if (info) {
|
|
2442
|
+
const sess = new GrokAcpSession_1.GrokAcpSession({
|
|
2443
|
+
root, model, bin: GROK_BIN, hooks: makeAcpHooks(root, log),
|
|
2444
|
+
initialSessionId: info.sessionId, leaderSocket: info.socket, attachWithoutLoad: true,
|
|
2445
|
+
});
|
|
2446
|
+
_acpSessions.set(root, sess);
|
|
2447
|
+
return sess;
|
|
2448
|
+
}
|
|
2449
|
+
log('grok leader: backend unavailable — falling back to the non-leader ACP path');
|
|
2450
|
+
}
|
|
2451
|
+
return getOrCreateAcpSession(root, providerId, model, log, resolvedSessionId);
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Leader POISON-ESCAPE: the attached session id was rejected by the leader
|
|
2455
|
+
* ("unknown session id" — the keepalive died / a wrong id). Reap the leader
|
|
2456
|
+
* backend, clear the persisted id, drop the thin client — the caller then
|
|
2457
|
+
* re-ensures a FRESH leader with session/new.
|
|
2458
|
+
*/
|
|
2459
|
+
function recoverLeaderPoison(root, providerId, log) {
|
|
2460
|
+
try {
|
|
2461
|
+
(0, leader_1.stopLeader)(root, log);
|
|
2462
|
+
}
|
|
2463
|
+
catch { /* ignore */ }
|
|
2464
|
+
try {
|
|
2465
|
+
(0, sessionState_1.clearSessionId)(root, providerId);
|
|
2466
|
+
}
|
|
2467
|
+
catch { /* ignore */ }
|
|
2468
|
+
const s = _acpSessions.get(root);
|
|
2469
|
+
if (s) {
|
|
2470
|
+
if (s.pid) {
|
|
2471
|
+
try {
|
|
2472
|
+
(0, killProcessTree_1.killProcessTree)(s.pid);
|
|
2473
|
+
}
|
|
2474
|
+
catch { /* ignore */ }
|
|
2475
|
+
}
|
|
2476
|
+
try {
|
|
2477
|
+
s.close();
|
|
2478
|
+
}
|
|
2479
|
+
catch { /* ignore */ }
|
|
2480
|
+
_acpSessions.delete(root);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2410
2483
|
/**
|
|
2411
2484
|
* Fire-and-forget: run ONE turn through the persistent ACP session. Streams
|
|
2412
2485
|
* assistant text to stdoutFile and writes the exit code to exitFile exactly once
|
|
@@ -2440,7 +2513,6 @@ function sendGrokAcpPrompt(root, promptFilePath, resolvedSessionId, stdoutFile,
|
|
|
2440
2513
|
catch { /* ignore */ }
|
|
2441
2514
|
return;
|
|
2442
2515
|
}
|
|
2443
|
-
const sess = getOrCreateAcpSession(root, providerId, model, log, resolvedSessionId);
|
|
2444
2516
|
_busyRoots.add(root);
|
|
2445
2517
|
if (showOutput) {
|
|
2446
2518
|
try {
|
|
@@ -2453,23 +2525,29 @@ function sendGrokAcpPrompt(root, promptFilePath, resolvedSessionId, stdoutFile,
|
|
|
2453
2525
|
}
|
|
2454
2526
|
catch { /* ignore */ } };
|
|
2455
2527
|
void (async () => {
|
|
2528
|
+
let sess;
|
|
2456
2529
|
try {
|
|
2530
|
+
sess = await ensureAcpSession(root, providerId, model, log, resolvedSessionId);
|
|
2457
2531
|
await sess.ensureReady();
|
|
2458
2532
|
}
|
|
2459
2533
|
catch (err) {
|
|
2460
|
-
const
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2534
|
+
const cur = _acpSessions.get(root);
|
|
2535
|
+
const stderr = cur?.stderrSnapshot ?? '';
|
|
2536
|
+
const startupErr = `${err?.message ?? err} ${stderr}`;
|
|
2537
|
+
log(`Grok ACP: startup failed (${err?.message ?? err}); stderr="${stderr.slice(0, 200)}" — falling back to headless one-shot`);
|
|
2538
|
+
if (cur) {
|
|
2539
|
+
try {
|
|
2540
|
+
cur.close();
|
|
2541
|
+
}
|
|
2542
|
+
catch { /* ignore */ }
|
|
2464
2543
|
}
|
|
2465
|
-
catch { /* ignore */ }
|
|
2466
2544
|
_acpSessions.delete(root);
|
|
2467
2545
|
_busyRoots.delete(root);
|
|
2468
2546
|
// AUTH failure at ACP startup (grok not signed in): the headless fallback
|
|
2469
2547
|
// would just re-hit the same logged-out gate, so short-circuit to
|
|
2470
2548
|
// reauth_required (non-retryable) instead of falling through.
|
|
2471
2549
|
if ((0, authClassify_1.isGrokAuthFailure)(startupErr)) {
|
|
2472
|
-
_emitGrokReauthHook(root, stdoutFile, log, 'ACP startup (not signed in)', providerId,
|
|
2550
|
+
_emitGrokReauthHook(root, stdoutFile, log, 'ACP startup (not signed in)', providerId, stderr.slice(0, 160));
|
|
2473
2551
|
try {
|
|
2474
2552
|
fs.writeFileSync(exitFile, '1\n', 'utf8');
|
|
2475
2553
|
}
|
|
@@ -2488,12 +2566,34 @@ function sendGrokAcpPrompt(root, promptFilePath, resolvedSessionId, stdoutFile,
|
|
|
2488
2566
|
}
|
|
2489
2567
|
catch { /* ignore */ }
|
|
2490
2568
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2569
|
+
const runTurnSafe = async (s) => {
|
|
2570
|
+
try {
|
|
2571
|
+
return await s.runTurn(promptText, sink);
|
|
2572
|
+
}
|
|
2573
|
+
catch (err) {
|
|
2574
|
+
return { clean: false, stopReason: 'exception', error: err?.message ?? String(err) };
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
let res = await runTurnSafe(sess);
|
|
2578
|
+
// Leader POISON-ESCAPE: the warm session was rejected (keepalive died / stale
|
|
2579
|
+
// id). Reap the leader + session/new on a FRESH leader, then retry ONCE.
|
|
2580
|
+
if ((0, leader_1.leaderModeEnabled)() && sess.attachMode && !res.clean && AcpSession_1.AcpSession.isUnknownSessionError(res.error)) {
|
|
2581
|
+
log('Grok leader: attach rejected (poison) — reaping leader, starting a fresh session, retrying once');
|
|
2582
|
+
recoverLeaderPoison(root, providerId, log);
|
|
2583
|
+
try {
|
|
2584
|
+
sess = await ensureAcpSession(root, providerId, model, log, undefined);
|
|
2585
|
+
await sess.ensureReady();
|
|
2586
|
+
if (sess.sessionId) {
|
|
2587
|
+
try {
|
|
2588
|
+
(0, sessionState_1.saveSessionId)(root, providerId, sess.sessionId);
|
|
2589
|
+
}
|
|
2590
|
+
catch { /* ignore */ }
|
|
2591
|
+
}
|
|
2592
|
+
res = await runTurnSafe(sess);
|
|
2593
|
+
}
|
|
2594
|
+
catch (err) {
|
|
2595
|
+
res = { clean: false, stopReason: 'poison-recover-failed', error: err?.message ?? String(err) };
|
|
2596
|
+
}
|
|
2497
2597
|
}
|
|
2498
2598
|
if (!res.clean && res.error) {
|
|
2499
2599
|
try {
|
|
@@ -2535,6 +2635,61 @@ function isGrokAcpBusy(root) {
|
|
|
2535
2635
|
const sess = _acpSessions.get(root);
|
|
2536
2636
|
return !!sess && sess.isBusy();
|
|
2537
2637
|
}
|
|
2638
|
+
/** True when a LIVE (alive) persistent grok ACP session is held for this root. */
|
|
2639
|
+
function hasGrokAcpSession(root) {
|
|
2640
|
+
const sess = _acpSessions.get(root);
|
|
2641
|
+
return !!sess && sess.isAlive();
|
|
2642
|
+
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Durable-steer delivery to an EXISTING live grok ACP session. Returns a promise
|
|
2645
|
+
* that resolves when the steered turn COMPLETES (so the caller can ack + de-queue
|
|
2646
|
+
* only then), or null when no live session exists (the caller keeps it queued and
|
|
2647
|
+
* retries via {@link ensureGrokAcpSteer} at the next boundary).
|
|
2648
|
+
*/
|
|
2649
|
+
function steerGrokAcpTracked(root, text, log) {
|
|
2650
|
+
const sess = _acpSessions.get(root);
|
|
2651
|
+
if (!sess || !sess.isAlive()) {
|
|
2652
|
+
return null;
|
|
2653
|
+
}
|
|
2654
|
+
const p = sess.steerTracked(text);
|
|
2655
|
+
if (p) {
|
|
2656
|
+
log(`Grok ACP: durable steer delivered to live session (${text.length} chars) — awaiting completion`);
|
|
2657
|
+
}
|
|
2658
|
+
return p;
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* Restart-drain delivery: ENSURE a live grok ACP session (spawn + session/load if
|
|
2662
|
+
* none is held — e.g. right after a loop restart with an empty TODO), then deliver
|
|
2663
|
+
* the steer and await its completion. Returns the turn result, or null when the
|
|
2664
|
+
* session could not be brought up (caller keeps the steer queued for a later
|
|
2665
|
+
* boundary). Grok stays ALIVE between turns, so the common case just reuses the
|
|
2666
|
+
* live session.
|
|
2667
|
+
*/
|
|
2668
|
+
async function ensureGrokAcpSteer(root, providerId, model, text, log) {
|
|
2669
|
+
if (!grokAcpEnabled()) {
|
|
2670
|
+
return null;
|
|
2671
|
+
}
|
|
2672
|
+
const sess = await ensureAcpSession(root, providerId, model, log, undefined);
|
|
2673
|
+
try {
|
|
2674
|
+
await sess.ensureReady();
|
|
2675
|
+
}
|
|
2676
|
+
catch (err) {
|
|
2677
|
+
log(`Grok ACP: durable-steer ensureReady failed (${err?.message ?? err})`);
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2680
|
+
if (sess.sessionId) {
|
|
2681
|
+
try {
|
|
2682
|
+
(0, sessionState_1.saveSessionId)(root, providerId, sess.sessionId);
|
|
2683
|
+
}
|
|
2684
|
+
catch { /* ignore */ }
|
|
2685
|
+
}
|
|
2686
|
+
const p = sess.steerTracked(text);
|
|
2687
|
+
if (!p) {
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
log(`Grok ACP: durable steer (re)delivered on restart-drain (${text.length} chars) — awaiting completion`);
|
|
2691
|
+
return p;
|
|
2692
|
+
}
|
|
2538
2693
|
/**
|
|
2539
2694
|
* grok-tui: persistent — run the turn in the per-workspace tmux session so
|
|
2540
2695
|
* grok's in-process context accumulates across tasks. Falls back to the old
|
|
@@ -2777,6 +2932,10 @@ function closeGrokTuiSession(root, _log) {
|
|
|
2777
2932
|
if (acp) {
|
|
2778
2933
|
// Reap grok's whole worker subtree (grok forks a per-turn worker child), then
|
|
2779
2934
|
// close — SIGKILL of just the parent would orphan the worker to init.
|
|
2935
|
+
// LEADER-AWARE (Layer B): acp.pid here is the THIN CLIENT. The leader + keepalive
|
|
2936
|
+
// are DETACHED (their own process groups, not in the client's tree), so this
|
|
2937
|
+
// reaps ONLY the client — the leader survives the loop restart, exactly as
|
|
2938
|
+
// intended. The app reaps the leader itself via `autodev leader-stop <root>`.
|
|
2780
2939
|
if (acp.pid) {
|
|
2781
2940
|
try {
|
|
2782
2941
|
(0, killProcessTree_1.killProcessTree)(acp.pid);
|