@prestyj/cli 5.6.0 → 5.7.0
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/dist/app-sidecar.js +127 -39
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-review-coverage.test.d.ts +2 -0
- package/dist/core/agent-session-review-coverage.test.d.ts.map +1 -0
- package/dist/core/agent-session-review-coverage.test.js +57 -0
- package/dist/core/agent-session-review-coverage.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +11 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +133 -22
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +1 -1
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +2 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/ideal-review.d.ts +27 -0
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +64 -0
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +34 -1
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/lsp/client.d.ts +3 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +20 -0
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/lsp/manager.d.ts +32 -9
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +89 -37
- package/dist/core/lsp/manager.js.map +1 -1
- package/dist/core/lsp/manager.test.js +56 -4
- package/dist/core/lsp/manager.test.js.map +1 -1
- package/dist/core/run-lifecycle.d.ts +40 -0
- package/dist/core/run-lifecycle.d.ts.map +1 -0
- package/dist/core/run-lifecycle.js +95 -0
- package/dist/core/run-lifecycle.js.map +1 -0
- package/dist/core/run-lifecycle.test.d.ts +2 -0
- package/dist/core/run-lifecycle.test.d.ts.map +1 -0
- package/dist/core/run-lifecycle.test.js +64 -0
- package/dist/core/run-lifecycle.test.js.map +1 -0
- package/dist/core/session-manager.d.ts +25 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +66 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +80 -2
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +40 -3
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +342 -30
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +189 -3
- package/dist/core/subagent-manager.test.js.map +1 -1
- package/dist/core/subagent-store.d.ts +20 -0
- package/dist/core/subagent-store.d.ts.map +1 -0
- package/dist/core/subagent-store.js +177 -0
- package/dist/core/subagent-store.js.map +1 -0
- package/dist/core/subagent-store.test.d.ts +2 -0
- package/dist/core/subagent-store.test.d.ts.map +1 -0
- package/dist/core/subagent-store.test.js +94 -0
- package/dist/core/subagent-store.test.js.map +1 -0
- package/dist/modes/agent-home-mode.d.ts.map +1 -1
- package/dist/modes/agent-home-mode.js +10 -3
- package/dist/modes/agent-home-mode.js.map +1 -1
- package/dist/modes/serve-mode.d.ts.map +1 -1
- package/dist/modes/serve-mode.js +10 -3
- package/dist/modes/serve-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts +2 -0
- package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
- package/dist/modes/subagent-worker-mode.js +13 -3
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/tools/goals.d.ts +3 -3
- package/dist/tools/tasks.d.ts +2 -2
- package/dist/tools/web-fetch.d.ts +1 -1
- package/dist/ui/App.d.ts +7 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +63 -17
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +1 -0
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +3 -3
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/Footer.test.d.ts +2 -0
- package/dist/ui/components/Footer.test.d.ts.map +1 -0
- package/dist/ui/components/Footer.test.js +16 -0
- package/dist/ui/components/Footer.test.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts +8 -3
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +63 -7
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useSessionPersistence.d.ts +4 -2
- package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
- package/dist/ui/hooks/useSessionPersistence.js +7 -1
- package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
- package/dist/ui/render.d.ts +7 -0
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +4 -0
- package/dist/ui/render.js.map +1 -1
- package/package.json +5 -5
package/dist/app-sidecar.js
CHANGED
|
@@ -18,10 +18,11 @@ import os from "node:os";
|
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
20
|
import { parseArgs } from "node:util";
|
|
21
|
-
import { formatError } from "@prestyj/ai";
|
|
21
|
+
import { environmentSecrets, formatError, redactValue } from "@prestyj/ai";
|
|
22
22
|
import { runJsonMode } from "./modes/json-mode.js";
|
|
23
23
|
import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
|
|
24
24
|
import { AgentSession } from "./core/agent-session.js";
|
|
25
|
+
import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
25
26
|
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, sessionsDirForChatAgent, switchChatAgent, } from "./chat-agents/index.js";
|
|
26
27
|
import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
|
|
27
28
|
import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
|
|
@@ -929,7 +930,8 @@ async function createSession(deps, opts) {
|
|
|
929
930
|
const clients = new Set();
|
|
930
931
|
let clientSeq = 0;
|
|
931
932
|
function broadcast(type, data) {
|
|
932
|
-
const
|
|
933
|
+
const safePayload = redactValue({ type, data }, { secrets: environmentSecrets(process.env) });
|
|
934
|
+
const frame = `data: ${JSON.stringify(safePayload)}\n\n`;
|
|
933
935
|
for (const c of clients)
|
|
934
936
|
c.res.write(frame);
|
|
935
937
|
}
|
|
@@ -1137,6 +1139,13 @@ async function createSession(deps, opts) {
|
|
|
1137
1139
|
session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
|
|
1138
1140
|
session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
|
|
1139
1141
|
let running = false;
|
|
1142
|
+
const runLifecycle = new RunLifecycle((runState) => {
|
|
1143
|
+
running = runState !== "idle";
|
|
1144
|
+
if (runState === "cancelling")
|
|
1145
|
+
broadcast("run_cancelling", { runState });
|
|
1146
|
+
});
|
|
1147
|
+
const cancelledRunEndGenerations = new Set();
|
|
1148
|
+
let pendingCancelDrain = null;
|
|
1140
1149
|
let titleGenerated = false;
|
|
1141
1150
|
// Bumped by /cancel — a run whose cancel generation changed mid-flight was
|
|
1142
1151
|
// canceled and earns no XP.
|
|
@@ -1159,6 +1168,7 @@ async function createSession(deps, opts) {
|
|
|
1159
1168
|
let autopilotCancelled = false;
|
|
1160
1169
|
// Hard cap on review→prompt→review rounds per user turn (loop safety).
|
|
1161
1170
|
const MAX_AUTOPILOT_ROUNDS = 3;
|
|
1171
|
+
const CANCEL_TIMEOUT_MS = 5_000;
|
|
1162
1172
|
// Prompt bodies Autopilot Nolan injected into the BUILD session this
|
|
1163
1173
|
// conversation. Passed into every Nolan digest so injected prompts render as
|
|
1164
1174
|
// "Nolan autopilot (injected)" instead of `**User:**` — otherwise multi-round
|
|
@@ -1365,29 +1375,63 @@ async function createSession(deps, opts) {
|
|
|
1365
1375
|
});
|
|
1366
1376
|
}
|
|
1367
1377
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1378
|
+
function abortOwnedWork() {
|
|
1379
|
+
cancelGeneration++;
|
|
1380
|
+
abort.abort();
|
|
1381
|
+
// Stop a run-all sweep and every async child through AgentSession's signal.
|
|
1382
|
+
taskRunAll = false;
|
|
1383
|
+
autopilotCancelled = true;
|
|
1384
|
+
nolanAutoAbort.abort();
|
|
1385
|
+
}
|
|
1386
|
+
function installFreshRunControllers() {
|
|
1387
|
+
abort = new AbortController();
|
|
1388
|
+
session.setSignal(abort.signal);
|
|
1389
|
+
nolanAutoAbort = new AbortController();
|
|
1390
|
+
nolanAutoSession?.setSignal(nolanAutoAbort.signal);
|
|
1391
|
+
}
|
|
1392
|
+
function finishOwnedGeneration(generation, emitCancelledFallback) {
|
|
1393
|
+
const cancelled = runLifecycle.isCancellationRequested(generation);
|
|
1394
|
+
const settlement = runLifecycle.settle(generation);
|
|
1395
|
+
if (!settlement.settled)
|
|
1396
|
+
return cancelled;
|
|
1397
|
+
// A replacement signal is safe only after the provider-backed owner settled.
|
|
1398
|
+
installFreshRunControllers();
|
|
1399
|
+
if (cancelled && emitCancelledFallback && !cancelledRunEndGenerations.has(generation)) {
|
|
1400
|
+
cancelledRunEndGenerations.add(generation);
|
|
1401
|
+
broadcast("run_end", { cancelled: true, runState: runLifecycle.state });
|
|
1402
|
+
}
|
|
1403
|
+
return cancelled;
|
|
1404
|
+
}
|
|
1405
|
+
// Core provider-run bracket. Standalone runs own a lifecycle generation;
|
|
1406
|
+
// injected autopilot runs share the cycle's outer generation.
|
|
1372
1407
|
async function runAgent(label, run) {
|
|
1373
|
-
|
|
1408
|
+
const ownsGeneration = !runLifecycle.running;
|
|
1409
|
+
const generation = ownsGeneration
|
|
1410
|
+
? runLifecycle.begin(abortOwnedWork).generation
|
|
1411
|
+
: runLifecycle.generation;
|
|
1412
|
+
if (ownsGeneration)
|
|
1413
|
+
pendingCancelDrain = null;
|
|
1374
1414
|
// Progress (Ranks): completed, non-canceled runs with ≥1 assistant turn earn
|
|
1375
1415
|
// XP — prompt + any commits authored during the run window.
|
|
1376
1416
|
const runStartedAt = Date.now();
|
|
1377
1417
|
const cancelGenAtStart = cancelGeneration;
|
|
1378
1418
|
const assistantsBeforeRun = countAssistantMessages(session.getMessages());
|
|
1379
1419
|
let runSucceeded = false;
|
|
1380
|
-
broadcast("run_start", { text: label });
|
|
1420
|
+
broadcast("run_start", { text: label, runState: runLifecycle.state });
|
|
1381
1421
|
try {
|
|
1382
|
-
|
|
1422
|
+
if (!runLifecycle.isCancellationRequested(generation))
|
|
1423
|
+
await run();
|
|
1383
1424
|
runSucceeded = true;
|
|
1384
1425
|
}
|
|
1385
1426
|
catch (err) {
|
|
1386
|
-
|
|
1427
|
+
if (!runLifecycle.isCancellationRequested(generation)) {
|
|
1428
|
+
broadcastError("error", "run failed", err);
|
|
1429
|
+
}
|
|
1387
1430
|
}
|
|
1388
1431
|
finally {
|
|
1389
|
-
|
|
1432
|
+
const cancelled = runLifecycle.isCancellationRequested(generation);
|
|
1390
1433
|
if (runSucceeded &&
|
|
1434
|
+
!cancelled &&
|
|
1391
1435
|
cancelGeneration === cancelGenAtStart &&
|
|
1392
1436
|
countAssistantMessages(session.getMessages()) > assistantsBeforeRun) {
|
|
1393
1437
|
// Fire-and-forget — XP must never delay or break run teardown.
|
|
@@ -1397,7 +1441,18 @@ async function createSession(deps, opts) {
|
|
|
1397
1441
|
// background tasks — refresh the footer extras once it settles.
|
|
1398
1442
|
gitBranch = await getGitBranch(cwd).catch(() => gitBranch);
|
|
1399
1443
|
gitIsRepo = await isGitRepo(cwd).catch(() => gitIsRepo);
|
|
1400
|
-
|
|
1444
|
+
if (ownsGeneration)
|
|
1445
|
+
finishOwnedGeneration(generation, false);
|
|
1446
|
+
// A cancelled injected run is still owned by the surrounding autopilot
|
|
1447
|
+
// cycle; its outer finalizer emits the one terminal cancelled run_end.
|
|
1448
|
+
if (!(cancelled && !ownsGeneration)) {
|
|
1449
|
+
if (cancelled)
|
|
1450
|
+
cancelledRunEndGenerations.add(generation);
|
|
1451
|
+
broadcast("run_end", {
|
|
1452
|
+
...(cancelled ? { cancelled: true } : {}),
|
|
1453
|
+
runState: runLifecycle.state,
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1401
1456
|
// Autopilot's review loop is driven explicitly from POST /prompt (see
|
|
1402
1457
|
// runAutopilotCycle), NOT from this shared finally — that keeps the
|
|
1403
1458
|
// injected EZ Coder runs this cycle triggers from recursively re-entering
|
|
@@ -1406,12 +1461,8 @@ async function createSession(deps, opts) {
|
|
|
1406
1461
|
// completed ones so they drop out of the Tasks modal automatically (users
|
|
1407
1462
|
// never have to delete finished tasks by hand).
|
|
1408
1463
|
broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
|
|
1409
|
-
// Queue drains into the run as steering, so it's empty by run_end —
|
|
1410
|
-
// sync the webview indicator.
|
|
1411
1464
|
broadcast("queued", { count: session.getQueuedCount() });
|
|
1412
1465
|
broadcast("extras", footerExtras());
|
|
1413
|
-
// Generate a session title once, after the first run, for the title bar
|
|
1414
|
-
// (best-effort, async — don't block the response).
|
|
1415
1466
|
if (!titleGenerated) {
|
|
1416
1467
|
titleGenerated = true;
|
|
1417
1468
|
void session.generateTitle().then((title) => {
|
|
@@ -1444,7 +1495,8 @@ async function createSession(deps, opts) {
|
|
|
1444
1495
|
return parseAutopilotVerdict(lastAssistantText(ken.getMessages()));
|
|
1445
1496
|
}
|
|
1446
1497
|
catch (err) {
|
|
1447
|
-
|
|
1498
|
+
if (!autopilotCancelled)
|
|
1499
|
+
broadcastError("autopilot_error", "autopilot review failed", err);
|
|
1448
1500
|
return null;
|
|
1449
1501
|
}
|
|
1450
1502
|
finally {
|
|
@@ -1519,6 +1571,8 @@ async function createSession(deps, opts) {
|
|
|
1519
1571
|
async function runAutopilotCycle(originalRequest) {
|
|
1520
1572
|
if (!autopilot || autopilotCancelled)
|
|
1521
1573
|
return;
|
|
1574
|
+
const generation = runLifecycle.begin(abortOwnedWork).generation;
|
|
1575
|
+
pendingCancelDrain = null;
|
|
1522
1576
|
autopilotActive = true;
|
|
1523
1577
|
// Generation captured by the last plan review; acceptPlan re-checks it so
|
|
1524
1578
|
// a user Accept/Reject landing mid-review always wins.
|
|
@@ -1633,6 +1687,8 @@ async function createSession(deps, opts) {
|
|
|
1633
1687
|
}
|
|
1634
1688
|
finally {
|
|
1635
1689
|
autopilotActive = false;
|
|
1690
|
+
finishOwnedGeneration(generation, true);
|
|
1691
|
+
queueMicrotask(() => void runStrandedQueue());
|
|
1636
1692
|
}
|
|
1637
1693
|
}
|
|
1638
1694
|
// ── Stranded-queue drain ───────────────────────────────
|
|
@@ -1825,7 +1881,7 @@ async function createSession(deps, opts) {
|
|
|
1825
1881
|
});
|
|
1826
1882
|
}
|
|
1827
1883
|
function json(res, status, body) {
|
|
1828
|
-
const payload = JSON.stringify(body);
|
|
1884
|
+
const payload = JSON.stringify(redactValue(body, { secrets: environmentSecrets(process.env) }));
|
|
1829
1885
|
res.writeHead(status, {
|
|
1830
1886
|
"content-type": "application/json",
|
|
1831
1887
|
"access-control-allow-origin": "*",
|
|
@@ -1841,6 +1897,7 @@ async function createSession(deps, opts) {
|
|
|
1841
1897
|
mode,
|
|
1842
1898
|
chatAgent,
|
|
1843
1899
|
running,
|
|
1900
|
+
runState: runLifecycle.state,
|
|
1844
1901
|
ready: true,
|
|
1845
1902
|
thinkingLevel: session.getThinkingLevel() ?? null,
|
|
1846
1903
|
supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
|
|
@@ -1913,6 +1970,7 @@ async function createSession(deps, opts) {
|
|
|
1913
1970
|
mode,
|
|
1914
1971
|
chatAgent,
|
|
1915
1972
|
running,
|
|
1973
|
+
runState: runLifecycle.state,
|
|
1916
1974
|
thinkingLevel: session.getThinkingLevel() ?? null,
|
|
1917
1975
|
supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
|
|
1918
1976
|
supportsVideo: getModel(st.model)?.supportsVideo ?? false,
|
|
@@ -2409,6 +2467,13 @@ async function createSession(deps, opts) {
|
|
|
2409
2467
|
json(res, 400, { error: "empty prompt" });
|
|
2410
2468
|
return;
|
|
2411
2469
|
}
|
|
2470
|
+
if (runLifecycle.running && runLifecycle.isCancellationRequested(runLifecycle.generation)) {
|
|
2471
|
+
json(res, 409, {
|
|
2472
|
+
error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
|
|
2473
|
+
runState: runLifecycle.state,
|
|
2474
|
+
});
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2412
2477
|
if (running || autopilotActive) {
|
|
2413
2478
|
// Queue prompts as mid-run steering (mirrors the CLI). Also queue while
|
|
2414
2479
|
// an autopilot cycle is active but between injected runs (build idle,
|
|
@@ -2982,27 +3047,50 @@ async function createSession(deps, opts) {
|
|
|
2982
3047
|
return;
|
|
2983
3048
|
}
|
|
2984
3049
|
if (method === "POST" && url === "/cancel") {
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3050
|
+
void (async () => {
|
|
3051
|
+
// Even between task runs, cancellation stops the sweep. Active provider
|
|
3052
|
+
// ownership invokes the full abort hook exactly once through lifecycle.
|
|
3053
|
+
taskRunAll = false;
|
|
3054
|
+
autopilotCancelled = true;
|
|
3055
|
+
if (!runLifecycle.running) {
|
|
3056
|
+
nolanAutoAbort.abort();
|
|
3057
|
+
nolanAutoAbort = new AbortController();
|
|
3058
|
+
nolanAutoSession?.setSignal(nolanAutoAbort.signal);
|
|
3059
|
+
}
|
|
3060
|
+
const generation = runLifecycle.generation;
|
|
3061
|
+
if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
|
|
3062
|
+
pendingCancelDrain = { generation, text: session.drainQueue() };
|
|
3063
|
+
broadcast("queued", { count: 0 });
|
|
3064
|
+
}
|
|
3065
|
+
const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
|
|
3066
|
+
const drained = pendingCancelDrain.text;
|
|
3067
|
+
if (result.status === "failed") {
|
|
3068
|
+
broadcast("cancel_failed", {
|
|
3069
|
+
error: "cancel_failed",
|
|
3070
|
+
reason: result.reason,
|
|
3071
|
+
runState: runLifecycle.state,
|
|
3072
|
+
});
|
|
3073
|
+
json(res, 504, {
|
|
3074
|
+
error: "cancel_failed",
|
|
3075
|
+
reason: result.reason,
|
|
3076
|
+
runState: runLifecycle.state,
|
|
3077
|
+
drained,
|
|
3078
|
+
});
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
json(res, 200, {
|
|
3082
|
+
cancelled: result.status === "cancelled",
|
|
3083
|
+
runState: runLifecycle.state,
|
|
3084
|
+
drained,
|
|
3085
|
+
});
|
|
3086
|
+
})().catch((error) => {
|
|
3087
|
+
broadcast("cancel_failed", { error: "cancel_failed", runState: runLifecycle.state });
|
|
3088
|
+
json(res, 500, {
|
|
3089
|
+
error: "cancel_failed",
|
|
3090
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3091
|
+
runState: runLifecycle.state,
|
|
3092
|
+
});
|
|
3093
|
+
});
|
|
3006
3094
|
return;
|
|
3007
3095
|
}
|
|
3008
3096
|
if (method === "POST" && url === "/new-session") {
|