@wrongstack/cli 1.0.24 → 1.0.26
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/data/providers.json +44 -0
- package/dist/{acp-AJWSKWE5.js → acp-LI665LO2.js} +5 -5
- package/dist/ascii-mode-SG4PRNLP.js +7 -0
- package/dist/{auth-GRBZ7Y3D.js → auth-TE55JF4Z.js} +14 -14
- package/dist/{bench-N5K7Z5O3.js → bench-GTLPM22B.js} +2 -2
- package/dist/boot/append-system-prompt.d.ts +10 -0
- package/dist/boot/ascii-mode.d.ts +12 -0
- package/dist/boot/container-wiring.d.ts +4 -2
- package/dist/boot/dispatch-singleshot.d.ts +4 -1
- package/dist/boot/mcp-config-flag.d.ts +22 -0
- package/dist/boot/piped-stdin.d.ts +40 -0
- package/dist/boot/restricted-mode.d.ts +15 -0
- package/dist/boot/safe-mode.d.ts +30 -0
- package/dist/boot/single-shot-budget.d.ts +30 -0
- package/dist/boot/stream-json.d.ts +36 -0
- package/dist/boot/structured-output.d.ts +16 -0
- package/dist/boot/system-prompt-builder.d.ts +2 -0
- package/dist/boot/tool-restriction-flags.d.ts +36 -0
- package/dist/boot/worktree-sessions.d.ts +21 -0
- package/dist/boot-provider-selection.d.ts +67 -0
- package/dist/boot.d.ts +1 -38
- package/dist/{chunk-LFMVHT3R.js → chunk-2ERX3RPN.js} +11 -13
- package/dist/chunk-4TVYGG6G.js +62 -0
- package/dist/chunk-5JYDKG5B.js +154 -0
- package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
- package/dist/chunk-BEHGRNOQ.js +58 -0
- package/dist/chunk-CZTXF6HY.js +140 -0
- package/dist/{chunk-SKPDE62W.js → chunk-EPR42SWM.js} +36 -6
- package/dist/{chunk-YBUCXXYZ.js → chunk-F5DCFAKI.js} +1 -1
- package/dist/chunk-HLSHFXTO.js +97 -0
- package/dist/{chunk-BLD5DSVA.js → chunk-KJS5543T.js} +2 -2
- package/dist/{chunk-QFPQXKCG.js → chunk-KOEO4CJG.js} +21 -6
- package/dist/chunk-LBPLTTZ6.js +105 -0
- package/dist/{chunk-3OCUS3VN.js → chunk-LDCGZJTC.js} +4 -4
- package/dist/chunk-LKPEBZBE.js +143 -0
- package/dist/{chunk-DRM7PSW7.js → chunk-OAVBM55Q.js} +3 -3
- package/dist/chunk-PZQBGUVZ.js +18 -0
- package/dist/{chunk-MHEBWVXL.js → chunk-Q4INH2DG.js} +46 -2
- package/dist/{chunk-ZZRSG7VK.js → chunk-RZ4ZDRYA.js} +3 -3
- package/dist/{chunk-U46QR4WR.js → chunk-TI7KLHYX.js} +132 -3
- package/dist/{chunk-PGKJ2OQP.js → chunk-WQV2NP57.js} +5 -5
- package/dist/chunk-X756ZS72.js +329 -0
- package/dist/{chunk-JF2MULWF.js → chunk-YHYINL47.js} +47 -15
- package/dist/{cli-context-34Q5IJRA.js → cli-context-UVGAN3NO.js} +407 -327
- package/dist/{cli-main-AELJDBGU.js → cli-main-TN3KZ2P6.js} +2652 -1978
- package/dist/cli-update-notice.d.ts +2 -1
- package/dist/config-doctor.d.ts +1 -1
- package/dist/{diag-doctor-WIFJH3XH.js → diag-doctor-BIZAFIAT.js} +2 -2
- package/dist/{execution-PYC5FCS4.js → execution-7QIP6GRY.js} +1948 -1666
- package/dist/fleet/host-types.d.ts +3 -1
- package/dist/goal-commands.d.ts +47 -0
- package/dist/goal-host.d.ts +1 -14
- package/dist/{hq-JTUTFI4L.js → hq-GLZ7QESM.js} +6 -6
- package/dist/{hq-server-PC7GZMPT.js → hq-server-YTKDVQPY.js} +2 -2
- package/dist/{hq-service-NW56MUP2.js → hq-service-BMMJAHRT.js} +4 -4
- package/dist/import-claude-code-32PCSIQI.js +236 -0
- package/dist/index.js +12 -8
- package/dist/{mailbox-serve-WDOCH6KR.js → mailbox-serve-NW5VZG7R.js} +3 -3
- package/dist/{modeldiag-36OGAL5C.js → modeldiag-36VODVIH.js} +8 -8
- package/dist/{per-subcommand-help-XE77MTGJ.js → per-subcommand-help-QDAQASPZ.js} +2 -2
- package/dist/permission-prompt.d.ts +14 -0
- package/dist/permission-rules-view.d.ts +18 -0
- package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
- package/dist/{plugin-usage-S5LVGMBW.js → plugin-usage-CIGOOTX3.js} +2 -2
- package/dist/{plugins-RHBIVLI7.js → plugins-HQTXUD6A.js} +4 -2
- package/dist/preflight.d.ts +1 -1
- package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
- package/dist/provider-helpers.d.ts +7 -11
- package/dist/{providers-models-2CFG46RD.js → providers-models-5Z2PJTEW.js} +5 -5
- package/dist/release-asset-download.d.ts +10 -0
- package/dist/release-attestation.d.ts +28 -0
- package/dist/remote/remote-session.d.ts +64 -0
- package/dist/remote/remote-target.d.ts +69 -0
- package/dist/remote-O5WXHLWV.js +561 -0
- package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
- package/dist/services/commit-message.d.ts +2 -1
- package/dist/session-move-P2EC2COA.js +44 -0
- package/dist/session-move.d.ts +20 -0
- package/dist/session-stats.d.ts +10 -0
- package/dist/{sessions-config-IQUH2GXT.js → sessions-config-SXTTLNXA.js} +35 -5
- package/dist/short-circuit-flags-74Y5N6CF.js +10 -0
- package/dist/slash-commands/command-context.d.ts +15 -5
- package/dist/slash-commands/goal-refiner.d.ts +4 -49
- package/dist/slash-commands/mcp.d.ts +2 -1
- package/dist/slash-commands/permissions.d.ts +4 -0
- package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
- package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
- package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
- package/dist/standalone-auto-update.d.ts +65 -0
- package/dist/standalone-update-LZO3MNLH.js +24 -0
- package/dist/standalone-update.d.ts +37 -1
- package/dist/subcommands/handlers/import-claude-code.d.ts +42 -0
- package/dist/subcommands/handlers/permissions.d.ts +9 -3
- package/dist/subcommands/handlers/remote.d.ts +6 -0
- package/dist/subcommands/handlers/version-help.d.ts +5 -0
- package/dist/{subcommands-ZTSIN7LG.js → subcommands-Q6ORSB2E.js} +2 -2
- package/dist/tui-resume-callback.d.ts +18 -0
- package/dist/{typesafe-HVEVPA6R.js → typesafe-U7YB2RRV.js} +5 -5
- package/dist/{typesafe-topic-replay-4W73BOLV.js → typesafe-topic-replay-ZILIU3WS.js} +18 -2
- package/dist/{update-7XIOEXTD.js → update-TDZX4IL2.js} +4 -4
- package/dist/{update-check-2POMBHFB.js → update-check-J5ZVKS73.js} +3 -3
- package/dist/version-JB2UAV2A.js +11 -0
- package/dist/version-help-XJJQ7YSH.js +10 -0
- package/dist/webui-server/route-contexts.d.ts +1 -1
- package/dist/webui-server/session-start-payload.d.ts +5 -1
- package/dist/{webui-server-L2SICPTB.js → webui-server-YTHLRFD2.js} +509 -366
- package/dist/webui-server-lifecycle.d.ts +57 -0
- package/dist/webui-server.d.ts +0 -14
- package/dist/wiring/brain-and-orchestration.d.ts +3 -1
- package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
- package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
- package/dist/wiring/lifecycle-plugins.d.ts +4 -2
- package/dist/wiring/metrics.d.ts +9 -1
- package/dist/wiring/pipeline.d.ts +2 -7
- package/dist/wiring/plugins.d.ts +3 -0
- package/dist/wiring/provider-status.d.ts +1 -0
- package/dist/wiring/provider-utility-tools.d.ts +3 -1
- package/package.json +28 -28
- package/dist/chunk-2BKFWIER.js +0 -98
- package/dist/chunk-424ORSE7.js +0 -56
- package/dist/chunk-QBBF7GIK.js +0 -65
- package/dist/short-circuit-flags-SDT53OML.js +0 -10
- package/dist/standalone-update-MW47J3AK.js +0 -164
- package/dist/version-help-WZ623NWM.js +0 -10
|
@@ -9,6 +9,25 @@ import {
|
|
|
9
9
|
deriveFsAccessPair,
|
|
10
10
|
persistConfigSetting
|
|
11
11
|
} from "./chunk-SFBFYIOW.js";
|
|
12
|
+
import {
|
|
13
|
+
checkStructuredAnswer,
|
|
14
|
+
parseOutputFormat,
|
|
15
|
+
repairPrompt,
|
|
16
|
+
startStreamJson,
|
|
17
|
+
streamJsonInit,
|
|
18
|
+
withSchemaInstruction
|
|
19
|
+
} from "./chunk-LKPEBZBE.js";
|
|
20
|
+
import {
|
|
21
|
+
theme
|
|
22
|
+
} from "./chunk-QD544J2B.js";
|
|
23
|
+
import {
|
|
24
|
+
WEBUI_SESSION_CHILD_CAPABILITIES,
|
|
25
|
+
writeWebuiSessionChildError,
|
|
26
|
+
writeWebuiSessionChildReady
|
|
27
|
+
} from "./chunk-ZMX5HRLI.js";
|
|
28
|
+
import {
|
|
29
|
+
fmtTok
|
|
30
|
+
} from "./chunk-EWKESL4L.js";
|
|
12
31
|
import "./chunk-WHOIR577.js";
|
|
13
32
|
import {
|
|
14
33
|
advanceToNextTask,
|
|
@@ -23,49 +42,38 @@ import {
|
|
|
23
42
|
trySaveSpecFromAIOutput,
|
|
24
43
|
trySaveTasksFromAIOutput
|
|
25
44
|
} from "./chunk-JHUJUIOH.js";
|
|
26
|
-
import {
|
|
27
|
-
theme
|
|
28
|
-
} from "./chunk-QD544J2B.js";
|
|
29
|
-
import {
|
|
30
|
-
fmtTok
|
|
31
|
-
} from "./chunk-EWKESL4L.js";
|
|
32
|
-
import {
|
|
33
|
-
WEBUI_SESSION_CHILD_CAPABILITIES,
|
|
34
|
-
writeWebuiSessionChildError,
|
|
35
|
-
writeWebuiSessionChildReady
|
|
36
|
-
} from "./chunk-ZMX5HRLI.js";
|
|
37
45
|
import {
|
|
38
46
|
terminalLink,
|
|
39
47
|
terminalText
|
|
40
48
|
} from "./chunk-HXJYHAR4.js";
|
|
41
|
-
import {
|
|
42
|
-
activeProfileConfigPath
|
|
43
|
-
} from "./chunk-YMXXOOFN.js";
|
|
44
|
-
import {
|
|
45
|
-
backupCurrent
|
|
46
|
-
} from "./chunk-WSTVGC2C.js";
|
|
47
49
|
import {
|
|
48
50
|
startCliHqConnection
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-KJS5543T.js";
|
|
52
|
+
import {
|
|
53
|
+
CLI_VERSION
|
|
54
|
+
} from "./chunk-F5DCFAKI.js";
|
|
50
55
|
import {
|
|
51
56
|
resolveActiveApiKey
|
|
52
57
|
} from "./chunk-LUMETJFO.js";
|
|
53
58
|
import {
|
|
54
|
-
|
|
55
|
-
} from "./chunk-
|
|
59
|
+
activeProfileConfigPath
|
|
60
|
+
} from "./chunk-YMXXOOFN.js";
|
|
61
|
+
import {
|
|
62
|
+
backupCurrent
|
|
63
|
+
} from "./chunk-WSTVGC2C.js";
|
|
56
64
|
|
|
57
65
|
// src/execution.ts
|
|
58
|
-
import * as
|
|
66
|
+
import * as path14 from "node:path";
|
|
59
67
|
import { effectiveFallbackChain as effectiveFallbackChain2, setQueuedMessagesSnapshot as setQueuedMessagesSnapshot2 } from "@wrongstack/core/agent";
|
|
60
68
|
import { LeaderAutoWakeController } from "@wrongstack/core/coordination";
|
|
61
69
|
import { updateReviewReportEvidence } from "@wrongstack/core/plugin";
|
|
62
70
|
import { noOpVault } from "@wrongstack/core/security";
|
|
63
|
-
import { attachTodosCheckpoint as attachTodosCheckpoint2 } from "@wrongstack/core/storage";
|
|
71
|
+
import { attachTodosCheckpoint as attachTodosCheckpoint2, QueueStore } from "@wrongstack/core/storage";
|
|
64
72
|
import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
73
|
+
import { mergeCustomModelDefs, sessionScopedPath as sessionScopedPath3 } from "@wrongstack/core/utils";
|
|
74
|
+
import { listGitWorktrees } from "@wrongstack/core/worktree";
|
|
75
|
+
import { capabilitiesFor, catalogProviderIdFor } from "@wrongstack/providers";
|
|
67
76
|
import { createToolVisionAdapters } from "@wrongstack/runtime/vision";
|
|
68
|
-
import { parseNextSteps as parseNextSteps2 } from "@wrongstack/tools/next-steps";
|
|
69
77
|
|
|
70
78
|
// src/boot/dispatch-singleshot.ts
|
|
71
79
|
import { color, writeOut } from "@wrongstack/core/utils";
|
|
@@ -87,19 +95,117 @@ ${err.describe()}`);
|
|
|
87
95
|
].join("\n");
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
// src/boot/single-shot-budget.ts
|
|
99
|
+
function parseMaxBudgetUsd(value) {
|
|
100
|
+
if (value === void 0) return void 0;
|
|
101
|
+
const amount = typeof value === "string" ? Number(value.trim()) : Number.NaN;
|
|
102
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
103
|
+
throw new Error("--max-budget-usd needs a positive dollar amount (e.g. --max-budget-usd 0.50)");
|
|
104
|
+
}
|
|
105
|
+
return amount;
|
|
106
|
+
}
|
|
107
|
+
function watchSingleShotBudget(opts) {
|
|
108
|
+
const leaderStart = opts.tokenCounter.estimateCost().total;
|
|
109
|
+
const subagentCost = /* @__PURE__ */ new Map();
|
|
110
|
+
let exceeded = false;
|
|
111
|
+
const spent = () => {
|
|
112
|
+
let total = opts.tokenCounter.estimateCost().total - leaderStart;
|
|
113
|
+
for (const cost of subagentCost.values()) total += cost;
|
|
114
|
+
return total;
|
|
115
|
+
};
|
|
116
|
+
const check = () => {
|
|
117
|
+
if (exceeded || spent() <= opts.limitUsd) return;
|
|
118
|
+
exceeded = true;
|
|
119
|
+
opts.onExceeded();
|
|
120
|
+
};
|
|
121
|
+
const offLeader = opts.events.on("token.accounted", check);
|
|
122
|
+
const offSubagent = opts.events.on("subagent.token_accounted", (payload) => {
|
|
123
|
+
subagentCost.set(payload.subagentId, payload.cost.total);
|
|
124
|
+
check();
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
spent,
|
|
128
|
+
get exceeded() {
|
|
129
|
+
return exceeded;
|
|
130
|
+
},
|
|
131
|
+
dispose() {
|
|
132
|
+
offLeader();
|
|
133
|
+
offSubagent();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
90
138
|
// src/boot/dispatch-singleshot.ts
|
|
139
|
+
function exitCodeForStatus(status) {
|
|
140
|
+
if (status === "failed" || status === "max_iterations") return 1;
|
|
141
|
+
if (status === "aborted") return 130;
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
91
144
|
async function runSingleShotDispatch(ctx) {
|
|
92
145
|
const { agent, query, flags, tokenCounter, renderer } = ctx;
|
|
146
|
+
let limitUsd;
|
|
147
|
+
let format;
|
|
148
|
+
try {
|
|
149
|
+
limitUsd = parseMaxBudgetUsd(flags["max-budget-usd"]);
|
|
150
|
+
format = parseOutputFormat(flags["output-format"]) ?? (flags["output-json"] ? "json" : "text");
|
|
151
|
+
} catch (err) {
|
|
152
|
+
renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
153
|
+
return 2;
|
|
154
|
+
}
|
|
93
155
|
const ctrl = new AbortController();
|
|
94
156
|
const onSigint = () => ctrl.abort();
|
|
95
157
|
process.on("SIGINT", onSigint);
|
|
158
|
+
let budget;
|
|
159
|
+
if (limitUsd !== void 0 && ctx.events) {
|
|
160
|
+
budget = watchSingleShotBudget({
|
|
161
|
+
limitUsd,
|
|
162
|
+
tokenCounter,
|
|
163
|
+
events: ctx.events,
|
|
164
|
+
onExceeded: () => ctrl.abort()
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
let stopStream;
|
|
168
|
+
if (format === "stream-json") {
|
|
169
|
+
const tools = agent.tools?.listForProvider?.() ?? agent.tools?.list?.() ?? [];
|
|
170
|
+
writeOut(
|
|
171
|
+
streamJsonInit({
|
|
172
|
+
sessionId: agent.ctx?.session?.id ?? null,
|
|
173
|
+
provider: agent.ctx?.provider?.id ?? null,
|
|
174
|
+
model: agent.ctx?.model ?? null,
|
|
175
|
+
cwd: agent.ctx?.cwd ?? process.cwd(),
|
|
176
|
+
tools: tools.map((tool) => tool.name)
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
if (ctx.events) {
|
|
180
|
+
stopStream = startStreamJson({
|
|
181
|
+
events: ctx.events,
|
|
182
|
+
write: (line) => writeOut(line),
|
|
183
|
+
includePartialMessages: flags["include-partial-messages"] === true
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
96
187
|
const startedAt = Date.now();
|
|
97
188
|
const before = tokenCounter.total();
|
|
98
189
|
const costBefore = tokenCounter.estimateCost().total;
|
|
190
|
+
const schema = typeof flags["json-schema"] === "string" ? JSON.parse(flags["json-schema"]) : void 0;
|
|
99
191
|
let result;
|
|
192
|
+
let structured;
|
|
100
193
|
try {
|
|
101
|
-
result = await agent.run(query,
|
|
194
|
+
result = await agent.run(schema ? withSchemaInstruction(query, schema) : query, {
|
|
195
|
+
signal: ctrl.signal
|
|
196
|
+
});
|
|
197
|
+
if (schema && result.status === "done") {
|
|
198
|
+
structured = checkStructuredAnswer(result.finalText, schema);
|
|
199
|
+
if (!structured.ok && !ctrl.signal.aborted) {
|
|
200
|
+
const first = result;
|
|
201
|
+
result = await agent.run(repairPrompt(structured.errors), { signal: ctrl.signal });
|
|
202
|
+
result = { ...result, iterations: first.iterations + result.iterations };
|
|
203
|
+
if (result.status === "done") structured = checkStructuredAnswer(result.finalText, schema);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
102
206
|
} finally {
|
|
207
|
+
stopStream?.();
|
|
208
|
+
budget?.dispose();
|
|
103
209
|
process.off("SIGINT", onSigint);
|
|
104
210
|
const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
|
|
105
211
|
getProcessRegistry2().killAll({ preserveBackground: true });
|
|
@@ -113,9 +219,17 @@ async function runSingleShotDispatch(ctx) {
|
|
|
113
219
|
cost: costAfter - costBefore,
|
|
114
220
|
elapsedMs: Date.now() - startedAt
|
|
115
221
|
};
|
|
116
|
-
|
|
222
|
+
const schemaFailed = structured !== void 0 && !structured.ok;
|
|
223
|
+
const exitCode = budget?.exceeded || schemaFailed ? 1 : exitCodeForStatus(result.status);
|
|
224
|
+
if (format !== "text") {
|
|
117
225
|
const json = JSON.stringify({
|
|
226
|
+
// In a stream the result is one event among many; name it.
|
|
227
|
+
...format === "stream-json" ? { type: "result" } : {},
|
|
118
228
|
status: result.status,
|
|
229
|
+
// Lets a script continue the conversation: `wstack --resume <sessionId>`.
|
|
230
|
+
// Read defensively — the session is swapped in by /resume and absent in
|
|
231
|
+
// some embedders.
|
|
232
|
+
sessionId: agent.ctx?.session?.id ?? null,
|
|
119
233
|
finalText: result.finalText ?? null,
|
|
120
234
|
error: result.error ? {
|
|
121
235
|
code: result.error.code,
|
|
@@ -125,14 +239,22 @@ async function runSingleShotDispatch(ctx) {
|
|
|
125
239
|
message: result.error.message,
|
|
126
240
|
context: result.error.context ?? null
|
|
127
241
|
} : null,
|
|
128
|
-
usage
|
|
242
|
+
usage,
|
|
243
|
+
...limitUsd !== void 0 ? { budget: { limitUsd, exceeded: budget?.exceeded ?? false } } : {},
|
|
244
|
+
...schema ? {
|
|
245
|
+
structuredOutput: structured?.ok ? structured.value : null,
|
|
246
|
+
schemaErrors: structured && !structured.ok ? structured.errors : null
|
|
247
|
+
} : {}
|
|
129
248
|
});
|
|
130
249
|
writeOut(json + "\n");
|
|
131
|
-
return
|
|
250
|
+
return exitCode;
|
|
132
251
|
}
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
252
|
+
const code = exitCode;
|
|
253
|
+
if (budget?.exceeded) {
|
|
254
|
+
renderer.writeError(
|
|
255
|
+
`Stopped: spend $${budget.spent().toFixed(4)} exceeded --max-budget-usd $${limitUsd}.`
|
|
256
|
+
);
|
|
257
|
+
} else if (result.status === "failed") {
|
|
136
258
|
const err = result.error;
|
|
137
259
|
if (err) {
|
|
138
260
|
const tag = err.recoverable ? " (recoverable)" : "";
|
|
@@ -143,11 +265,11 @@ async function runSingleShotDispatch(ctx) {
|
|
|
143
265
|
renderer.writeError("Failed.");
|
|
144
266
|
}
|
|
145
267
|
} else if (result.status === "aborted") {
|
|
146
|
-
code = 130;
|
|
147
268
|
renderer.writeWarning("Aborted.");
|
|
148
269
|
} else if (result.status === "max_iterations") {
|
|
149
|
-
code = 1;
|
|
150
270
|
renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
271
|
+
} else if (structured && !structured.ok) {
|
|
272
|
+
renderer.writeError(`Answer did not match --json-schema: ${structured.errors.join("; ")}`);
|
|
151
273
|
}
|
|
152
274
|
if (result.finalText) renderer.write("\n" + result.finalText + "\n");
|
|
153
275
|
const r = result;
|
|
@@ -216,7 +338,7 @@ async function runWebUIDispatch(ctx) {
|
|
|
216
338
|
const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
|
|
217
339
|
agent.disableInteractiveConfirmation();
|
|
218
340
|
renderer.setSilent(true);
|
|
219
|
-
const { runWebUI } = await import("./webui-server-
|
|
341
|
+
const { runWebUI } = await import("./webui-server-YTHLRFD2.js");
|
|
220
342
|
const flagValue = (names) => {
|
|
221
343
|
for (const name of names) {
|
|
222
344
|
if (!Object.hasOwn(flags, name)) continue;
|
|
@@ -426,14 +548,14 @@ async function runWebUIDispatch(ctx) {
|
|
|
426
548
|
);
|
|
427
549
|
}
|
|
428
550
|
});
|
|
429
|
-
const webuiExit = new Promise((
|
|
551
|
+
const webuiExit = new Promise((resolve5) => {
|
|
430
552
|
webuiPromise.then(() => {
|
|
431
553
|
renderer.setSilent(false);
|
|
432
554
|
renderer.write("\n");
|
|
433
555
|
renderer.writeInfo(
|
|
434
556
|
color2.yellow(` Shutting down ${isSimpleUi ? "SimpleUI" : "WebUI"} server\u2026`)
|
|
435
557
|
);
|
|
436
|
-
|
|
558
|
+
resolve5(0);
|
|
437
559
|
}).catch((err) => {
|
|
438
560
|
if (webuiSessionChild) {
|
|
439
561
|
void writeWebuiSessionChildError(webuiSessionChild.readyFile, {
|
|
@@ -451,7 +573,7 @@ async function runWebUIDispatch(ctx) {
|
|
|
451
573
|
` \u2717 ${isSimpleUi ? "SimpleUI" : "WebUI"} failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
452
574
|
)
|
|
453
575
|
);
|
|
454
|
-
|
|
576
|
+
resolve5(1);
|
|
455
577
|
});
|
|
456
578
|
});
|
|
457
579
|
return webuiExit;
|
|
@@ -467,6 +589,68 @@ function resolveExecutionMode(positional, flags) {
|
|
|
467
589
|
return "repl";
|
|
468
590
|
}
|
|
469
591
|
|
|
592
|
+
// src/boot/piped-stdin.ts
|
|
593
|
+
var PIPED_STDIN_FIRST_BYTE_TIMEOUT_MS = 3e3;
|
|
594
|
+
var PIPED_STDIN_MAX_BYTES = 2 * 1024 * 1024;
|
|
595
|
+
var EMPTY = { text: "", truncated: false, timedOut: false };
|
|
596
|
+
function readPipedStdin(source, opts = {}) {
|
|
597
|
+
if (source.isTTY) return Promise.resolve(EMPTY);
|
|
598
|
+
const firstByteTimeoutMs = opts.firstByteTimeoutMs ?? PIPED_STDIN_FIRST_BYTE_TIMEOUT_MS;
|
|
599
|
+
const maxBytes = opts.maxBytes ?? PIPED_STDIN_MAX_BYTES;
|
|
600
|
+
return new Promise((resolve5) => {
|
|
601
|
+
const chunks = [];
|
|
602
|
+
let size = 0;
|
|
603
|
+
let truncated = false;
|
|
604
|
+
let settled = false;
|
|
605
|
+
const finish = (timedOut) => {
|
|
606
|
+
if (settled) return;
|
|
607
|
+
settled = true;
|
|
608
|
+
clearTimeout(timer);
|
|
609
|
+
source.removeListener("data", onData);
|
|
610
|
+
source.removeListener("end", onEnd);
|
|
611
|
+
source.removeListener("close", onEnd);
|
|
612
|
+
source.removeListener("error", onError);
|
|
613
|
+
source.pause();
|
|
614
|
+
if (timedOut || truncated) source.unref?.();
|
|
615
|
+
const text = timedOut ? "" : Buffer.concat(chunks).toString("utf8").trimEnd();
|
|
616
|
+
resolve5({ text, truncated, timedOut });
|
|
617
|
+
};
|
|
618
|
+
const onData = (chunk) => {
|
|
619
|
+
clearTimeout(timer);
|
|
620
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
621
|
+
const room = maxBytes - size;
|
|
622
|
+
if (buf.length >= room) {
|
|
623
|
+
chunks.push(buf.subarray(0, Math.max(0, room)));
|
|
624
|
+
size = maxBytes;
|
|
625
|
+
truncated = true;
|
|
626
|
+
finish(false);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
chunks.push(buf);
|
|
630
|
+
size += buf.length;
|
|
631
|
+
};
|
|
632
|
+
const onEnd = () => finish(false);
|
|
633
|
+
const onError = () => finish(false);
|
|
634
|
+
const timer = setTimeout(() => {
|
|
635
|
+
if (size === 0) finish(true);
|
|
636
|
+
}, firstByteTimeoutMs);
|
|
637
|
+
source.on("data", onData);
|
|
638
|
+
source.on("end", onEnd);
|
|
639
|
+
source.on("close", onEnd);
|
|
640
|
+
source.on("error", onError);
|
|
641
|
+
source.resume();
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function appendPipedStdin(query, piped) {
|
|
645
|
+
if (!piped) return query;
|
|
646
|
+
if (!query.trim()) return piped;
|
|
647
|
+
return `${query}
|
|
648
|
+
|
|
649
|
+
<stdin>
|
|
650
|
+
${piped}
|
|
651
|
+
</stdin>`;
|
|
652
|
+
}
|
|
653
|
+
|
|
470
654
|
// src/boot/tui-coordinator-callbacks.ts
|
|
471
655
|
function createTuiCoordinatorCallbacks({
|
|
472
656
|
state,
|
|
@@ -656,7 +840,6 @@ Reply with ONLY the JSON object.`
|
|
|
656
840
|
content: [userPrompt]
|
|
657
841
|
}
|
|
658
842
|
],
|
|
659
|
-
maxTokens: 1024,
|
|
660
843
|
temperature: 0
|
|
661
844
|
},
|
|
662
845
|
{ signal: context.signal }
|
|
@@ -1022,6 +1205,7 @@ import {
|
|
|
1022
1205
|
} from "@wrongstack/core/agent";
|
|
1023
1206
|
import { resetSessionSubagentPolicy } from "@wrongstack/core/coordination";
|
|
1024
1207
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
1208
|
+
import { restoreSessionPermissionOverrides } from "@wrongstack/core/security";
|
|
1025
1209
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
1026
1210
|
import {
|
|
1027
1211
|
activateProjectStateGuard,
|
|
@@ -1056,7 +1240,8 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
|
|
|
1056
1240
|
await fs2.mkdir(nextWpaths.projectSessions, { recursive: true });
|
|
1057
1241
|
const nextSessionStore = new DefaultSessionStore({
|
|
1058
1242
|
dir: nextWpaths.projectSessions,
|
|
1059
|
-
projectRoot: resolved
|
|
1243
|
+
projectRoot: resolved,
|
|
1244
|
+
events
|
|
1060
1245
|
});
|
|
1061
1246
|
const nextWriter = await nextSessionStore.create({
|
|
1062
1247
|
id: "",
|
|
@@ -1132,6 +1317,7 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
|
|
|
1132
1317
|
context.session = nextWriter;
|
|
1133
1318
|
context.state.replaceMessages([]);
|
|
1134
1319
|
resetSessionSubagentPolicy(context);
|
|
1320
|
+
restoreSessionPermissionOverrides(context.meta, {});
|
|
1135
1321
|
context.state.replaceTodos([]);
|
|
1136
1322
|
context.clearFileTracking();
|
|
1137
1323
|
context.clearMemoryEvidence?.();
|
|
@@ -1266,574 +1452,132 @@ async function onSDDOutput(output) {
|
|
|
1266
1452
|
return messages;
|
|
1267
1453
|
}
|
|
1268
1454
|
|
|
1269
|
-
// src/boot/tui-session-
|
|
1455
|
+
// src/boot/tui-session-stub-enrich.ts
|
|
1456
|
+
import * as fs3 from "node:fs/promises";
|
|
1270
1457
|
import * as path7 from "node:path";
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
import { projectLastRequestTokens } from "@wrongstack/core/types/session-timeline";
|
|
1278
|
-
import { sessionScopedPath as sessionScopedPath2, toErrorMessage } from "@wrongstack/core/utils";
|
|
1279
|
-
var RESERVATION_WINDOW_MS = 6e4;
|
|
1280
|
-
var RESERVATION_RENEW_MS = 2e4;
|
|
1281
|
-
function messageText(content) {
|
|
1282
|
-
if (typeof content === "string") return content;
|
|
1283
|
-
if (!Array.isArray(content)) return "";
|
|
1284
|
-
return content.map(
|
|
1285
|
-
(block) => block && typeof block === "object" && "text" in block && typeof block.text === "string" ? block.text : ""
|
|
1286
|
-
).filter(Boolean).join("\n");
|
|
1458
|
+
var SLICE_BYTES = 256 * 1024;
|
|
1459
|
+
var MAX_TITLE_CHARS = 80;
|
|
1460
|
+
var PICKER_POOL_CEILING = 1500;
|
|
1461
|
+
var ENRICH_CONCURRENCY = 16;
|
|
1462
|
+
function isStubSummary(s) {
|
|
1463
|
+
return !s.title && !s.endedAt;
|
|
1287
1464
|
}
|
|
1288
|
-
function
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1465
|
+
function containedJsonlPath(sessionsDir, id) {
|
|
1466
|
+
const root = path7.resolve(sessionsDir);
|
|
1467
|
+
const file = path7.resolve(root, `${id}.jsonl`);
|
|
1468
|
+
if (file !== root && !file.startsWith(root + path7.sep)) return void 0;
|
|
1469
|
+
return file;
|
|
1470
|
+
}
|
|
1471
|
+
function stripControlChars(text) {
|
|
1472
|
+
return text.replaceAll(
|
|
1473
|
+
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07]*(?:\x07|\x1b\\)|[\u0000-\u001f\u007f-\u009f]/g,
|
|
1474
|
+
""
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
function truncate(text) {
|
|
1478
|
+
const oneLine = stripControlChars(text).replaceAll(/\s+/g, " ").trim();
|
|
1479
|
+
return oneLine.length > MAX_TITLE_CHARS ? `${oneLine.slice(0, MAX_TITLE_CHARS - 1)}\u2026` : oneLine;
|
|
1480
|
+
}
|
|
1481
|
+
function contentToText(content) {
|
|
1482
|
+
if (typeof content === "string") return content;
|
|
1483
|
+
if (Array.isArray(content)) {
|
|
1484
|
+
return content.map(
|
|
1485
|
+
(block) => block && typeof block === "object" && "text" in block && typeof block.text === "string" ? block.text : ""
|
|
1486
|
+
).filter(Boolean).join(" ");
|
|
1294
1487
|
}
|
|
1295
|
-
return
|
|
1488
|
+
return "";
|
|
1296
1489
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
}
|
|
1304
|
-
return null;
|
|
1305
|
-
};
|
|
1306
|
-
const warnings = [];
|
|
1307
|
-
const warn = (label, err) => {
|
|
1308
|
-
warnings.push(`${label}: ${toErrorMessage(err)}`);
|
|
1309
|
-
};
|
|
1310
|
-
let stage = "start";
|
|
1311
|
-
const setStage = (next) => {
|
|
1312
|
-
stage = next;
|
|
1490
|
+
function scanUserInputs(chunk) {
|
|
1491
|
+
let first;
|
|
1492
|
+
let last;
|
|
1493
|
+
for (const line of chunk.split("\n")) {
|
|
1494
|
+
const trimmed = line.trim();
|
|
1495
|
+
if (!trimmed.includes('"user_input"')) continue;
|
|
1313
1496
|
try {
|
|
1314
|
-
|
|
1497
|
+
const event = JSON.parse(trimmed);
|
|
1498
|
+
if (event.type !== "user_input" || typeof event.ts !== "string") continue;
|
|
1499
|
+
const text = contentToText(event.content).trim();
|
|
1500
|
+
if (!text) continue;
|
|
1501
|
+
const hit = { ts: event.ts, text };
|
|
1502
|
+
if (!first) first = hit;
|
|
1503
|
+
last = hit;
|
|
1315
1504
|
} catch {
|
|
1316
1505
|
}
|
|
1317
|
-
};
|
|
1318
|
-
if (!state.activeSessionStore) {
|
|
1319
|
-
return fail("no_session_store", "No session store is bound to this project.");
|
|
1320
1506
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1507
|
+
return { first, last };
|
|
1508
|
+
}
|
|
1509
|
+
async function firstUserInput(file) {
|
|
1510
|
+
let handle;
|
|
1323
1511
|
try {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1512
|
+
handle = await fs3.open(file, "r");
|
|
1513
|
+
const { size } = await handle.stat();
|
|
1514
|
+
const length = Math.min(SLICE_BYTES, size);
|
|
1515
|
+
const buffer = Buffer.alloc(length);
|
|
1516
|
+
await handle.read(buffer, 0, length, 0);
|
|
1517
|
+
const text = buffer.toString("utf8");
|
|
1518
|
+
const cutoff = text.lastIndexOf("\n");
|
|
1519
|
+
return scanUserInputs(cutoff > 0 ? text.slice(0, cutoff) : text).first;
|
|
1520
|
+
} catch {
|
|
1521
|
+
return void 0;
|
|
1522
|
+
} finally {
|
|
1523
|
+
await handle?.close().catch(() => void 0);
|
|
1330
1524
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1525
|
+
}
|
|
1526
|
+
async function lastUserInput(file) {
|
|
1527
|
+
let handle;
|
|
1333
1528
|
try {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
);
|
|
1347
|
-
return fail(stage, toErrorMessage(err));
|
|
1529
|
+
handle = await fs3.open(file, "r");
|
|
1530
|
+
const { size } = await handle.stat();
|
|
1531
|
+
const start = Math.max(0, size - SLICE_BYTES);
|
|
1532
|
+
const length = size - start;
|
|
1533
|
+
const buffer = Buffer.alloc(length);
|
|
1534
|
+
await handle.read(buffer, 0, length, start);
|
|
1535
|
+
const text = buffer.toString("utf8");
|
|
1536
|
+
const from = text.indexOf("\n");
|
|
1537
|
+
return scanUserInputs(from >= 0 ? text.slice(from + 1) : text).last;
|
|
1538
|
+
} catch {
|
|
1539
|
+
return void 0;
|
|
1540
|
+
} finally {
|
|
1541
|
+
await handle?.close().catch(() => void 0);
|
|
1348
1542
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
if (
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
setStage("open_journal");
|
|
1378
|
-
const claimToRenew = resumeClaim;
|
|
1379
|
-
let renewFailures = 0;
|
|
1380
|
-
const renew = () => {
|
|
1381
|
-
if (!claimToRenew) return;
|
|
1382
|
-
void claimToRenew.renew(RESERVATION_WINDOW_MS).catch((err) => {
|
|
1383
|
-
if (renewFailures++ > 0) return;
|
|
1384
|
-
console.error(
|
|
1385
|
-
JSON.stringify({
|
|
1386
|
-
level: "warn",
|
|
1387
|
-
event: "execution.resume_reservation_renew_failed",
|
|
1388
|
-
sessionId: canonicalSessionId,
|
|
1389
|
-
message: toErrorMessage(err),
|
|
1390
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1391
|
-
})
|
|
1392
|
-
);
|
|
1393
|
-
});
|
|
1394
|
-
};
|
|
1395
|
-
const renewTimer = setInterval(renew, RESERVATION_RENEW_MS);
|
|
1396
|
-
renewTimer.unref?.();
|
|
1397
|
-
let resumed;
|
|
1398
|
-
try {
|
|
1399
|
-
resumed = await state.activeSessionStore.resume(canonicalSessionId, onLoadProgress);
|
|
1400
|
-
} finally {
|
|
1401
|
-
clearInterval(renewTimer);
|
|
1402
|
-
}
|
|
1403
|
-
openedWriter = resumed.writer;
|
|
1404
|
-
hydrated = resumed.data;
|
|
1405
|
-
setStage("activate_ownership");
|
|
1406
|
-
await Promise.resolve(resumeClaim?.renew(RESERVATION_WINDOW_MS)).catch(() => void 0);
|
|
1407
|
-
if (resumeClaim) {
|
|
1408
|
-
await resumeClaim.activate({
|
|
1409
|
-
sessionId: canonicalSessionId,
|
|
1410
|
-
projectSlug: state.wpaths.projectSlug,
|
|
1411
|
-
projectRoot: state.projectRoot,
|
|
1412
|
-
projectName: path7.basename(state.projectRoot),
|
|
1413
|
-
workingDir: agent.ctx.workingDir,
|
|
1414
|
-
clientType: "tui",
|
|
1415
|
-
pid: process.pid,
|
|
1416
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1417
|
-
});
|
|
1418
|
-
identityClaimed = true;
|
|
1419
|
-
await state.activateSessionIdentity?.(canonicalSessionId);
|
|
1420
|
-
}
|
|
1421
|
-
setStage("replay_history");
|
|
1422
|
-
const meta = resumed.data.metadata;
|
|
1423
|
-
const entries = replaySessionMessages(
|
|
1424
|
-
resumed.data.messages,
|
|
1425
|
-
resumed.data.events,
|
|
1426
|
-
/* startId */
|
|
1427
|
-
1
|
|
1428
|
-
);
|
|
1429
|
-
try {
|
|
1430
|
-
agent.container?.safeResolve?.(TOKENS2.DelegationTracker)?.rehydrate(canonicalSessionId, resumed.data.events ?? []);
|
|
1431
|
-
} catch (err) {
|
|
1432
|
-
warn("delegation rehydrate", err);
|
|
1433
|
-
}
|
|
1434
|
-
setStage("read_sidecars");
|
|
1435
|
-
const sessionsDir = state.wpaths.projectSessions;
|
|
1436
|
-
const resumedTodosPath = sessionScopedPath2(sessionsDir, resumed.writer.id, ".todos.json");
|
|
1437
|
-
const restoredTodos = await loadTodosCheckpoint(
|
|
1438
|
-
resumedTodosPath,
|
|
1439
|
-
events,
|
|
1440
|
-
agent.ctx.traceId,
|
|
1441
|
-
resumed.writer.id
|
|
1442
|
-
).catch(() => null);
|
|
1443
|
-
setStage("swap_writer");
|
|
1444
|
-
oldWriter = agent.ctx.session;
|
|
1445
|
-
oldMessages = [...agent.ctx.messages];
|
|
1446
|
-
agent.ctx.session = resumed.writer;
|
|
1447
|
-
try {
|
|
1448
|
-
agent.ctx.state.replaceMessages(resumed.data.messages);
|
|
1449
|
-
restoreSessionSubagentPolicy(agent.ctx, resumed.data.events, resumed.data.subagentsAllowed);
|
|
1450
|
-
restoreSessionSubagentModelPlan(agent.ctx, resumed.data.events);
|
|
1451
|
-
} catch (err) {
|
|
1452
|
-
agent.ctx.session = oldWriter;
|
|
1453
|
-
agent.ctx.state.replaceMessages(oldMessages);
|
|
1454
|
-
throw err;
|
|
1455
|
-
}
|
|
1456
|
-
writerSwapped = true;
|
|
1457
|
-
if (state.sessionRef) {
|
|
1458
|
-
oldSessionRefCurrent = state.sessionRef.current;
|
|
1459
|
-
state.sessionRef.current = resumed.writer;
|
|
1460
|
-
}
|
|
1461
|
-
setStage("flush_journal");
|
|
1462
|
-
await agent.ctx.flushConversationJournal().catch((err) => {
|
|
1463
|
-
console.error(
|
|
1464
|
-
JSON.stringify({
|
|
1465
|
-
level: "error",
|
|
1466
|
-
event: "execution.resume_journal_flush_failed",
|
|
1467
|
-
message: toErrorMessage(err),
|
|
1468
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1469
|
-
})
|
|
1470
|
-
);
|
|
1471
|
-
warn("conversation journal did not flush", err);
|
|
1472
|
-
});
|
|
1473
|
-
setStage("repoint_sidecars");
|
|
1474
|
-
previousDetachFn = state.detachActiveTodosCheckpoint;
|
|
1475
|
-
oldTodos = agent.ctx.state.todos ? [...agent.ctx.state.todos] : [];
|
|
1476
|
-
oldPlanPath = agent.ctx.state.meta?.["plan.path"];
|
|
1477
|
-
oldTaskPath = agent.ctx.state.meta?.["task.path"];
|
|
1478
|
-
try {
|
|
1479
|
-
await Promise.resolve(previousDetachFn?.()).catch(() => void 0);
|
|
1480
|
-
agent.ctx.state.setMeta(
|
|
1481
|
-
"plan.path",
|
|
1482
|
-
sessionScopedPath2(sessionsDir, resumed.writer.id, ".plan.json")
|
|
1483
|
-
);
|
|
1484
|
-
agent.ctx.state.setMeta(
|
|
1485
|
-
"task.path",
|
|
1486
|
-
sessionScopedPath2(sessionsDir, resumed.writer.id, ".tasks.json")
|
|
1487
|
-
);
|
|
1488
|
-
agent.ctx.state.replaceTodos(restoredTodos ?? []);
|
|
1489
|
-
try {
|
|
1490
|
-
state.detachActiveTodosCheckpoint = attachTodosCheckpoint(
|
|
1491
|
-
agent.ctx.state,
|
|
1492
|
-
resumedTodosPath,
|
|
1493
|
-
resumed.writer.id,
|
|
1494
|
-
events,
|
|
1495
|
-
agent.ctx.traceId
|
|
1496
|
-
);
|
|
1497
|
-
} catch {
|
|
1498
|
-
state.detachActiveTodosCheckpoint = void 0;
|
|
1499
|
-
}
|
|
1500
|
-
} catch (err) {
|
|
1501
|
-
warn("todo/plan sidecars were not re-pointed", err);
|
|
1543
|
+
}
|
|
1544
|
+
function effectiveLastActivity(s) {
|
|
1545
|
+
return s.lastActivityAt ?? s.endedAt ?? s.startedAt ?? "";
|
|
1546
|
+
}
|
|
1547
|
+
function hasActivity(s) {
|
|
1548
|
+
return (s.messageCount ?? 0) > 0 || (s.iterationCount ?? 0) > 0 || (s.toolCallCount ?? 0) > 0 || (s.tokenTotal ?? 0) > 0 || Boolean(s.title?.trim()) || Boolean(s.lastUserMessage?.trim());
|
|
1549
|
+
}
|
|
1550
|
+
async function selectPickerSessions(storeList, sessionsDir, limit) {
|
|
1551
|
+
const poolSize = Math.max(limit, Math.min(PICKER_POOL_CEILING, limit * 3));
|
|
1552
|
+
const enriched = await enrichStubSummaries(await storeList(poolSize), sessionsDir);
|
|
1553
|
+
return enriched.filter(hasActivity).sort((a, b) => effectiveLastActivity(b).localeCompare(effectiveLastActivity(a))).slice(0, limit);
|
|
1554
|
+
}
|
|
1555
|
+
async function enrichStubSummaries(summaries, sessionsDir) {
|
|
1556
|
+
const out = [...summaries];
|
|
1557
|
+
const pending = summaries.reduce((indexes, summary, index) => {
|
|
1558
|
+
if (isStubSummary(summary)) indexes.push(index);
|
|
1559
|
+
return indexes;
|
|
1560
|
+
}, []);
|
|
1561
|
+
if (pending.length === 0) return out;
|
|
1562
|
+
let cursor = 0;
|
|
1563
|
+
const worker = async () => {
|
|
1564
|
+
for (; ; ) {
|
|
1565
|
+
const slot = cursor++;
|
|
1566
|
+
if (slot >= pending.length) return;
|
|
1567
|
+
const index = pending[slot];
|
|
1568
|
+
const summary = out[index];
|
|
1569
|
+
const file = containedJsonlPath(sessionsDir, summary.id);
|
|
1570
|
+
if (!file) continue;
|
|
1502
1571
|
try {
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const savedProvider = typeof meta.provider === "string" ? meta.provider.trim() : "";
|
|
1513
|
-
const savedModel = typeof meta.model === "string" ? meta.model.trim() : "";
|
|
1514
|
-
const hasSavedRoute = savedProvider.length > 0 && savedProvider.toLowerCase() !== "unknown" && savedModel.length > 0 && savedModel.toLowerCase() !== "unknown";
|
|
1515
|
-
const targetProviderId = hasSavedRoute ? savedProvider : currentProviderId;
|
|
1516
|
-
const targetModel = hasSavedRoute ? savedModel : agent.ctx.model;
|
|
1517
|
-
if (switchProviderAndModel && targetProviderId && (targetProviderId !== currentProviderId || targetModel !== agent.ctx.model)) {
|
|
1518
|
-
try {
|
|
1519
|
-
const error = await Promise.resolve(switchProviderAndModel(targetProviderId, targetModel));
|
|
1520
|
-
if (typeof error === "string" && error)
|
|
1521
|
-
warn(`could not restore ${targetProviderId}/${targetModel}`, error);
|
|
1522
|
-
} catch (err) {
|
|
1523
|
-
console.error(
|
|
1524
|
-
JSON.stringify({
|
|
1525
|
-
level: "error",
|
|
1526
|
-
event: "execution.resume_model_restore_failed",
|
|
1527
|
-
provider: targetProviderId,
|
|
1528
|
-
model: targetModel,
|
|
1529
|
-
message: toErrorMessage(err),
|
|
1530
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1531
|
-
})
|
|
1532
|
-
);
|
|
1533
|
-
warn(`could not restore ${targetProviderId}/${targetModel}`, err);
|
|
1534
|
-
}
|
|
1535
|
-
} else if (targetModel !== agent.ctx.model) {
|
|
1536
|
-
agent.ctx.model = targetModel;
|
|
1537
|
-
}
|
|
1538
|
-
setStage("finalize_previous_session");
|
|
1539
|
-
let finalizeOldWriter;
|
|
1540
|
-
if (oldWriter && oldWriter !== resumed.writer) {
|
|
1541
|
-
const staleWriter = oldWriter;
|
|
1542
|
-
const endedUsage = tokenCounter.total();
|
|
1543
|
-
finalizeOldWriter = async () => {
|
|
1544
|
-
let appendOk = false;
|
|
1545
|
-
try {
|
|
1546
|
-
await staleWriter.append({
|
|
1547
|
-
type: "session_end",
|
|
1548
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1549
|
-
usage: endedUsage
|
|
1550
|
-
});
|
|
1551
|
-
appendOk = true;
|
|
1552
|
-
} catch (err) {
|
|
1553
|
-
console.error(
|
|
1554
|
-
JSON.stringify({
|
|
1555
|
-
level: "error",
|
|
1556
|
-
event: "execution.session_end_append_failed",
|
|
1557
|
-
message: toErrorMessage(err),
|
|
1558
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1559
|
-
})
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1562
|
-
if (appendOk) {
|
|
1563
|
-
try {
|
|
1564
|
-
await staleWriter.close();
|
|
1565
|
-
} catch (err) {
|
|
1566
|
-
console.error(
|
|
1567
|
-
JSON.stringify({
|
|
1568
|
-
level: "error",
|
|
1569
|
-
event: "execution.session_close_failed",
|
|
1570
|
-
message: toErrorMessage(err),
|
|
1571
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1572
|
-
})
|
|
1573
|
-
);
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
};
|
|
1577
|
-
}
|
|
1578
|
-
setStage("token_accounting");
|
|
1579
|
-
try {
|
|
1580
|
-
tokenCounter.reset();
|
|
1581
|
-
tokenCounter.account(resumed.data.usage, targetModel, targetProviderId);
|
|
1582
|
-
} catch (err) {
|
|
1583
|
-
warn("token accounting for the resumed session failed", err);
|
|
1584
|
-
}
|
|
1585
|
-
const resumedRequestTokens = projectLastRequestTokens(resumed.data.events);
|
|
1586
|
-
agent.ctx.lastRequestTokens = resumedRequestTokens;
|
|
1587
|
-
tokenCounter.setCurrentRequestTokens?.(resumedRequestTokens ?? 0);
|
|
1588
|
-
const tokens = resumedRequestTokens ?? 0;
|
|
1589
|
-
const maxContext = agent.ctx.provider?.capabilities?.maxContext ?? 0;
|
|
1590
|
-
const finalText = lastAssistantTextOf(resumed.data.messages);
|
|
1591
|
-
const result = {
|
|
1592
|
-
entries,
|
|
1593
|
-
nextId: entries.length + 1,
|
|
1594
|
-
sessionId: resumed.writer.id,
|
|
1595
|
-
contextSnapshot: { tokens, maxContext },
|
|
1596
|
-
attached: true,
|
|
1597
|
-
warnings,
|
|
1598
|
-
...finalText !== void 0 ? { lastAssistantText: finalText } : {}
|
|
1599
|
-
};
|
|
1600
|
-
if (finalizeOldWriter) void finalizeOldWriter();
|
|
1601
|
-
return result;
|
|
1602
|
-
} catch (err) {
|
|
1603
|
-
if (!identityClaimed) await resumeClaim?.cancel().catch(() => void 0);
|
|
1604
|
-
if (identityClaimed && previousSessionId) {
|
|
1605
|
-
await state.activateSessionIdentity?.(previousSessionId).catch((rollbackErr) => {
|
|
1606
|
-
console.error(
|
|
1607
|
-
JSON.stringify({
|
|
1608
|
-
level: "error",
|
|
1609
|
-
event: "execution.resume_identity_rollback_failed",
|
|
1610
|
-
sessionId: previousSessionId,
|
|
1611
|
-
message: toErrorMessage(rollbackErr),
|
|
1612
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1613
|
-
})
|
|
1614
|
-
);
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
1617
|
-
if (!writerSwapped) {
|
|
1618
|
-
await openedWriter?.close().catch(() => void 0);
|
|
1619
|
-
} else {
|
|
1620
|
-
try {
|
|
1621
|
-
if (oldWriter !== void 0) agent.ctx.session = oldWriter;
|
|
1622
|
-
if (state.sessionRef) state.sessionRef.current = oldSessionRefCurrent;
|
|
1623
|
-
if (oldMessages !== void 0) agent.ctx.state.replaceMessages(oldMessages);
|
|
1624
|
-
if (previousDetachFn !== void 0) state.detachActiveTodosCheckpoint = previousDetachFn;
|
|
1625
|
-
agent.ctx.state.replaceTodos([...oldTodos]);
|
|
1626
|
-
if (oldPlanPath !== void 0) agent.ctx.state.setMeta("plan.path", oldPlanPath);
|
|
1627
|
-
if (oldTaskPath !== void 0) agent.ctx.state.setMeta("task.path", oldTaskPath);
|
|
1628
|
-
} catch (rollbackErr) {
|
|
1629
|
-
console.error(
|
|
1630
|
-
JSON.stringify({
|
|
1631
|
-
level: "error",
|
|
1632
|
-
event: "execution.resume_post_swap_rollback_failed",
|
|
1633
|
-
message: toErrorMessage(rollbackErr),
|
|
1634
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1635
|
-
})
|
|
1636
|
-
);
|
|
1637
|
-
}
|
|
1638
|
-
if (openedWriter && openedWriter !== oldWriter) {
|
|
1639
|
-
await openedWriter.close().catch((closeErr) => {
|
|
1640
|
-
console.error(
|
|
1641
|
-
JSON.stringify({
|
|
1642
|
-
level: "error",
|
|
1643
|
-
event: "execution.resume_opened_writer_close_failed",
|
|
1644
|
-
message: toErrorMessage(closeErr),
|
|
1645
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1646
|
-
})
|
|
1647
|
-
);
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
const reason = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
1652
|
-
console.error(
|
|
1653
|
-
JSON.stringify({
|
|
1654
|
-
level: "error",
|
|
1655
|
-
event: "execution.resume_session_failed",
|
|
1656
|
-
sessionId: canonicalSessionId,
|
|
1657
|
-
stage,
|
|
1658
|
-
message: reason,
|
|
1659
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1660
|
-
})
|
|
1661
|
-
);
|
|
1662
|
-
const message = toErrorMessage(err);
|
|
1663
|
-
if (err instanceof Error && err.name === "SessionOwnershipConflictError") {
|
|
1664
|
-
return fail(stage, message);
|
|
1665
|
-
}
|
|
1666
|
-
let data = hydrated;
|
|
1667
|
-
if (!data) {
|
|
1668
|
-
try {
|
|
1669
|
-
data = await state.activeSessionStore.load(canonicalSessionId, onLoadProgress);
|
|
1670
|
-
} catch {
|
|
1671
|
-
data = void 0;
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
if (data) {
|
|
1675
|
-
try {
|
|
1676
|
-
const entries = replaySessionMessages(
|
|
1677
|
-
data.messages,
|
|
1678
|
-
data.events,
|
|
1679
|
-
/* startId */
|
|
1680
|
-
1
|
|
1681
|
-
);
|
|
1682
|
-
const finalText = lastAssistantTextOf(data.messages);
|
|
1683
|
-
fail(stage, message);
|
|
1684
|
-
return {
|
|
1685
|
-
entries,
|
|
1686
|
-
nextId: entries.length + 1,
|
|
1687
|
-
sessionId: canonicalSessionId,
|
|
1688
|
-
// Deliberately no contextSnapshot: the chip reports the LIVE
|
|
1689
|
-
// window, and the live session is still the one we rolled back to.
|
|
1690
|
-
// Painting the viewed session's fill over it would be a lie.
|
|
1691
|
-
attached: false,
|
|
1692
|
-
warnings: [...warnings, `not attached (at ${stage}): ${message}`],
|
|
1693
|
-
...finalText !== void 0 ? { lastAssistantText: finalText } : {}
|
|
1694
|
-
};
|
|
1695
|
-
} catch (renderErr) {
|
|
1696
|
-
console.error(
|
|
1697
|
-
JSON.stringify({
|
|
1698
|
-
level: "error",
|
|
1699
|
-
event: "execution.resume_readonly_render_failed",
|
|
1700
|
-
sessionId: canonicalSessionId,
|
|
1701
|
-
message: toErrorMessage(renderErr),
|
|
1702
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1703
|
-
})
|
|
1704
|
-
);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
return fail(stage, message);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
// src/boot/tui-session-stub-enrich.ts
|
|
1712
|
-
import * as fs3 from "node:fs/promises";
|
|
1713
|
-
import * as path8 from "node:path";
|
|
1714
|
-
var SLICE_BYTES = 256 * 1024;
|
|
1715
|
-
var MAX_TITLE_CHARS = 80;
|
|
1716
|
-
var PICKER_POOL_CEILING = 1500;
|
|
1717
|
-
var ENRICH_CONCURRENCY = 16;
|
|
1718
|
-
function isStubSummary(s) {
|
|
1719
|
-
return !s.title && !s.endedAt;
|
|
1720
|
-
}
|
|
1721
|
-
function containedJsonlPath(sessionsDir, id) {
|
|
1722
|
-
const root = path8.resolve(sessionsDir);
|
|
1723
|
-
const file = path8.resolve(root, `${id}.jsonl`);
|
|
1724
|
-
if (file !== root && !file.startsWith(root + path8.sep)) return void 0;
|
|
1725
|
-
return file;
|
|
1726
|
-
}
|
|
1727
|
-
function stripControlChars(text) {
|
|
1728
|
-
return text.replaceAll(
|
|
1729
|
-
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07]*(?:\x07|\x1b\\)|[\u0000-\u001f\u007f-\u009f]/g,
|
|
1730
|
-
""
|
|
1731
|
-
);
|
|
1732
|
-
}
|
|
1733
|
-
function truncate(text) {
|
|
1734
|
-
const oneLine = stripControlChars(text).replaceAll(/\s+/g, " ").trim();
|
|
1735
|
-
return oneLine.length > MAX_TITLE_CHARS ? `${oneLine.slice(0, MAX_TITLE_CHARS - 1)}\u2026` : oneLine;
|
|
1736
|
-
}
|
|
1737
|
-
function contentToText(content) {
|
|
1738
|
-
if (typeof content === "string") return content;
|
|
1739
|
-
if (Array.isArray(content)) {
|
|
1740
|
-
return content.map(
|
|
1741
|
-
(block) => block && typeof block === "object" && "text" in block && typeof block.text === "string" ? block.text : ""
|
|
1742
|
-
).filter(Boolean).join(" ");
|
|
1743
|
-
}
|
|
1744
|
-
return "";
|
|
1745
|
-
}
|
|
1746
|
-
function scanUserInputs(chunk) {
|
|
1747
|
-
let first;
|
|
1748
|
-
let last;
|
|
1749
|
-
for (const line of chunk.split("\n")) {
|
|
1750
|
-
const trimmed = line.trim();
|
|
1751
|
-
if (!trimmed.includes('"user_input"')) continue;
|
|
1752
|
-
try {
|
|
1753
|
-
const event = JSON.parse(trimmed);
|
|
1754
|
-
if (event.type !== "user_input" || typeof event.ts !== "string") continue;
|
|
1755
|
-
const text = contentToText(event.content).trim();
|
|
1756
|
-
if (!text) continue;
|
|
1757
|
-
const hit = { ts: event.ts, text };
|
|
1758
|
-
if (!first) first = hit;
|
|
1759
|
-
last = hit;
|
|
1760
|
-
} catch {
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
return { first, last };
|
|
1764
|
-
}
|
|
1765
|
-
async function firstUserInput(file) {
|
|
1766
|
-
let handle;
|
|
1767
|
-
try {
|
|
1768
|
-
handle = await fs3.open(file, "r");
|
|
1769
|
-
const { size } = await handle.stat();
|
|
1770
|
-
const length = Math.min(SLICE_BYTES, size);
|
|
1771
|
-
const buffer = Buffer.alloc(length);
|
|
1772
|
-
await handle.read(buffer, 0, length, 0);
|
|
1773
|
-
const text = buffer.toString("utf8");
|
|
1774
|
-
const cutoff = text.lastIndexOf("\n");
|
|
1775
|
-
return scanUserInputs(cutoff > 0 ? text.slice(0, cutoff) : text).first;
|
|
1776
|
-
} catch {
|
|
1777
|
-
return void 0;
|
|
1778
|
-
} finally {
|
|
1779
|
-
await handle?.close().catch(() => void 0);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
async function lastUserInput(file) {
|
|
1783
|
-
let handle;
|
|
1784
|
-
try {
|
|
1785
|
-
handle = await fs3.open(file, "r");
|
|
1786
|
-
const { size } = await handle.stat();
|
|
1787
|
-
const start = Math.max(0, size - SLICE_BYTES);
|
|
1788
|
-
const length = size - start;
|
|
1789
|
-
const buffer = Buffer.alloc(length);
|
|
1790
|
-
await handle.read(buffer, 0, length, start);
|
|
1791
|
-
const text = buffer.toString("utf8");
|
|
1792
|
-
const from = text.indexOf("\n");
|
|
1793
|
-
return scanUserInputs(from >= 0 ? text.slice(from + 1) : text).last;
|
|
1794
|
-
} catch {
|
|
1795
|
-
return void 0;
|
|
1796
|
-
} finally {
|
|
1797
|
-
await handle?.close().catch(() => void 0);
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
function effectiveLastActivity(s) {
|
|
1801
|
-
return s.lastActivityAt ?? s.endedAt ?? s.startedAt ?? "";
|
|
1802
|
-
}
|
|
1803
|
-
function hasActivity(s) {
|
|
1804
|
-
return (s.messageCount ?? 0) > 0 || (s.iterationCount ?? 0) > 0 || (s.toolCallCount ?? 0) > 0 || (s.tokenTotal ?? 0) > 0 || Boolean(s.title?.trim()) || Boolean(s.lastUserMessage?.trim());
|
|
1805
|
-
}
|
|
1806
|
-
async function selectPickerSessions(storeList, sessionsDir, limit) {
|
|
1807
|
-
const poolSize = Math.max(limit, Math.min(PICKER_POOL_CEILING, limit * 3));
|
|
1808
|
-
const enriched = await enrichStubSummaries(await storeList(poolSize), sessionsDir);
|
|
1809
|
-
return enriched.filter(hasActivity).sort((a, b) => effectiveLastActivity(b).localeCompare(effectiveLastActivity(a))).slice(0, limit);
|
|
1810
|
-
}
|
|
1811
|
-
async function enrichStubSummaries(summaries, sessionsDir) {
|
|
1812
|
-
const out = [...summaries];
|
|
1813
|
-
const pending = summaries.reduce((indexes, summary, index) => {
|
|
1814
|
-
if (isStubSummary(summary)) indexes.push(index);
|
|
1815
|
-
return indexes;
|
|
1816
|
-
}, []);
|
|
1817
|
-
if (pending.length === 0) return out;
|
|
1818
|
-
let cursor = 0;
|
|
1819
|
-
const worker = async () => {
|
|
1820
|
-
for (; ; ) {
|
|
1821
|
-
const slot = cursor++;
|
|
1822
|
-
if (slot >= pending.length) return;
|
|
1823
|
-
const index = pending[slot];
|
|
1824
|
-
const summary = out[index];
|
|
1825
|
-
const file = containedJsonlPath(sessionsDir, summary.id);
|
|
1826
|
-
if (!file) continue;
|
|
1827
|
-
try {
|
|
1828
|
-
const stat3 = await fs3.stat(file);
|
|
1829
|
-
const [first, last] = await Promise.all([firstUserInput(file), lastUserInput(file)]);
|
|
1830
|
-
const title = truncate(first?.text ?? "");
|
|
1831
|
-
out[index] = {
|
|
1832
|
-
...summary,
|
|
1833
|
-
...title ? { title } : {},
|
|
1834
|
-
...last ? { lastUserMessage: truncate(last.text) } : {},
|
|
1835
|
-
lastActivityAt: stat3.mtime.toISOString()
|
|
1836
|
-
};
|
|
1572
|
+
const stat3 = await fs3.stat(file);
|
|
1573
|
+
const [first, last] = await Promise.all([firstUserInput(file), lastUserInput(file)]);
|
|
1574
|
+
const title = truncate(first?.text ?? "");
|
|
1575
|
+
out[index] = {
|
|
1576
|
+
...summary,
|
|
1577
|
+
...title ? { title } : {},
|
|
1578
|
+
...last ? { lastUserMessage: truncate(last.text) } : {},
|
|
1579
|
+
lastActivityAt: stat3.mtime.toISOString()
|
|
1580
|
+
};
|
|
1837
1581
|
} catch {
|
|
1838
1582
|
}
|
|
1839
1583
|
}
|
|
@@ -2275,1037 +2019,1550 @@ function createSettingsAdapter(ctx) {
|
|
|
2275
2019
|
if (s.fleetChatVerbosity !== void 0) {
|
|
2276
2020
|
if (fleetStreamController?.setMode) fleetStreamController.setMode(s.fleetChatVerbosity);
|
|
2277
2021
|
}
|
|
2278
|
-
applyLiveSettings?.(s);
|
|
2279
|
-
return null;
|
|
2280
|
-
} catch (err) {
|
|
2281
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2282
|
-
console.debug(
|
|
2022
|
+
applyLiveSettings?.(s);
|
|
2023
|
+
return null;
|
|
2024
|
+
} catch (err) {
|
|
2025
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2026
|
+
console.debug(
|
|
2027
|
+
JSON.stringify({
|
|
2028
|
+
level: "error",
|
|
2029
|
+
event: "execution.settings_persist_failed",
|
|
2030
|
+
message,
|
|
2031
|
+
errorName: err instanceof Error ? err.name : void 0,
|
|
2032
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2033
|
+
})
|
|
2034
|
+
);
|
|
2035
|
+
return message;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
return { getSettings, saveSettings };
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/boot/tui-theme-adapter.ts
|
|
2042
|
+
import * as fs4 from "node:fs/promises";
|
|
2043
|
+
import * as path8 from "node:path";
|
|
2044
|
+
import { THEME_PRESET_IDS } from "@wrongstack/core/types";
|
|
2045
|
+
import { updateJsonObjectFile } from "@wrongstack/core/utils";
|
|
2046
|
+
var VALID_PRESETS = new Set(THEME_PRESET_IDS);
|
|
2047
|
+
function createThemeAdapter({ configStore, wpaths }) {
|
|
2048
|
+
return {
|
|
2049
|
+
getThemePreset: () => {
|
|
2050
|
+
const live = configStore.get();
|
|
2051
|
+
const candidate = live.themePreset;
|
|
2052
|
+
return candidate && VALID_PRESETS.has(candidate) ? candidate : void 0;
|
|
2053
|
+
},
|
|
2054
|
+
saveThemePreset: async (preset) => {
|
|
2055
|
+
if (!VALID_PRESETS.has(preset)) {
|
|
2056
|
+
throw new Error(`Unknown theme preset: ${preset}`);
|
|
2057
|
+
}
|
|
2058
|
+
const configPath = activeProfileConfigPath(wpaths, configStore.get());
|
|
2059
|
+
await fs4.mkdir(path8.dirname(configPath), { recursive: true });
|
|
2060
|
+
await updateJsonObjectFile(configPath, async (config) => {
|
|
2061
|
+
await backupCurrent(void 0, configPath);
|
|
2062
|
+
config.themePreset = preset;
|
|
2063
|
+
});
|
|
2064
|
+
configStore.update({ themePreset: preset });
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// src/boot/worktree-sessions.ts
|
|
2070
|
+
import * as path9 from "node:path";
|
|
2071
|
+
function samePath(a, b) {
|
|
2072
|
+
const ra = path9.resolve(a);
|
|
2073
|
+
const rb = path9.resolve(b);
|
|
2074
|
+
return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
|
|
2075
|
+
}
|
|
2076
|
+
function worktreeOfSession(summary, worktrees) {
|
|
2077
|
+
const checkout = summary.checkout;
|
|
2078
|
+
if (!checkout) return void 0;
|
|
2079
|
+
const wt = worktrees.find((w) => samePath(w.root, checkout));
|
|
2080
|
+
if (!wt || wt.current) return void 0;
|
|
2081
|
+
return {
|
|
2082
|
+
root: wt.root,
|
|
2083
|
+
name: path9.basename(wt.root) || wt.root,
|
|
2084
|
+
...wt.branch ? { branch: wt.branch } : {}
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function withWorktreeSwitch(resume, lookup, switchProject) {
|
|
2088
|
+
return async (sessionId, ...rest) => {
|
|
2089
|
+
const target = lookup(sessionId);
|
|
2090
|
+
if (target) {
|
|
2091
|
+
const error = await switchProject(target.root, target.name);
|
|
2092
|
+
if (error) throw new Error(`Could not switch to worktree ${target.name}: ${error}`);
|
|
2093
|
+
}
|
|
2094
|
+
return resume(sessionId, ...rest);
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// src/brain-menu/panel-service.ts
|
|
2099
|
+
import { BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
|
|
2100
|
+
var PERSONA_CATALOG = BUILTIN_COUNCIL_PERSONAS.map((persona) => ({
|
|
2101
|
+
id: persona.id,
|
|
2102
|
+
name: persona.name,
|
|
2103
|
+
description: persona.description,
|
|
2104
|
+
defaultVeto: persona.defaultVeto
|
|
2105
|
+
}));
|
|
2106
|
+
var PERSONA_CYCLE = PERSONA_CATALOG.map((persona) => persona.id);
|
|
2107
|
+
function compactEntry(entry) {
|
|
2108
|
+
return entry.provider ? `${entry.provider}/${entry.model}` : entry.model;
|
|
2109
|
+
}
|
|
2110
|
+
function createBrainPanelHost(deps) {
|
|
2111
|
+
const { brainRuntime } = deps;
|
|
2112
|
+
const apply = async (patch) => {
|
|
2113
|
+
try {
|
|
2114
|
+
const { persisted } = brainRuntime.apply(patch);
|
|
2115
|
+
const result = await persisted;
|
|
2116
|
+
return result.ok ? null : `Applied live but NOT saved: ${result.error ?? "unknown error"}`;
|
|
2117
|
+
} catch (err) {
|
|
2118
|
+
return err instanceof Error ? err.message : String(err);
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
const currentPool = () => brainRuntime.getSnapshot().models.map(compactEntry);
|
|
2122
|
+
const currentVoters = () => brainRuntime.getSnapshot().council.voters.map((v) => ({ ...v }));
|
|
2123
|
+
const applyVoters = (voters) => apply({ council: { voters: voters.length > 0 ? voters : null } });
|
|
2124
|
+
return {
|
|
2125
|
+
getSettings() {
|
|
2126
|
+
const snap = brainRuntime.getSnapshot();
|
|
2127
|
+
return {
|
|
2128
|
+
mode: snap.mode,
|
|
2129
|
+
riskLevel: snap.maxAutoRisk,
|
|
2130
|
+
strategy: snap.strategy,
|
|
2131
|
+
decisionTimeoutMs: snap.decisionTimeoutMs,
|
|
2132
|
+
humanTimeoutMs: snap.humanTimeoutMs,
|
|
2133
|
+
pool: snap.models.map(compactEntry),
|
|
2134
|
+
poolResolved: snap.poolLabels,
|
|
2135
|
+
usingSessionModel: snap.usingSessionModel,
|
|
2136
|
+
councilEnabled: snap.council.enabled,
|
|
2137
|
+
councilMinRisk: snap.council.minRisk,
|
|
2138
|
+
voters: snap.council.voters.map((v) => ({
|
|
2139
|
+
label: compactEntry(v),
|
|
2140
|
+
persona: v.persona,
|
|
2141
|
+
veto: v.veto,
|
|
2142
|
+
weight: v.weight
|
|
2143
|
+
})),
|
|
2144
|
+
councilSeats: snap.councilLabels,
|
|
2145
|
+
personaCatalog: PERSONA_CATALOG.map((persona) => ({ ...persona })),
|
|
2146
|
+
councilQuorum: snap.council.quorum,
|
|
2147
|
+
councilApproval: snap.council.approval,
|
|
2148
|
+
councilDistinctness: snap.council.distinctness,
|
|
2149
|
+
councilPerCallTimeoutMs: snap.council.perCallTimeoutMs,
|
|
2150
|
+
councilMaxConcurrency: snap.council.maxConcurrency,
|
|
2151
|
+
councilVoterMaxTokens: snap.council.voterMaxTokens,
|
|
2152
|
+
councilDeliberationRounds: snap.council.deliberationRounds,
|
|
2153
|
+
councilJudgeMaxTokens: snap.council.judgeMaxTokens,
|
|
2154
|
+
// The EFFECTIVE judge, not `council.judge`. The configured one is
|
|
2155
|
+
// usually absent, so the panel used to read "auto" and say nothing
|
|
2156
|
+
// about who actually breaks the panel's ties — including when that is
|
|
2157
|
+
// one of the seats that produced the tie.
|
|
2158
|
+
judgeLabel: snap.judgeLabel,
|
|
2159
|
+
judgeConfigured: snap.council.judge !== void 0,
|
|
2160
|
+
judgeIsVoter: snap.judgeIsVoter,
|
|
2161
|
+
ledgerEnabled: snap.ledger.enabled,
|
|
2162
|
+
autoDenyAfterFailures: snap.ledger.autoDenyAfterFailures,
|
|
2163
|
+
terminalPolicy: snap.terminalPolicy,
|
|
2164
|
+
heuristics: {
|
|
2165
|
+
lowRiskAutoAnswer: snap.heuristics.lowRiskAutoAnswer,
|
|
2166
|
+
blockedResolved: snap.heuristics.blockedResolved,
|
|
2167
|
+
deadlockSkip: snap.heuristics.deadlockSkip,
|
|
2168
|
+
retryExhausted: snap.heuristics.retryExhausted,
|
|
2169
|
+
continuePing: snap.heuristics.continuePing,
|
|
2170
|
+
blockedResolvedMarkers: snap.heuristics.blockedResolvedMarkers
|
|
2171
|
+
},
|
|
2172
|
+
llmMaxTokens: snap.llm.maxTokens,
|
|
2173
|
+
llmRejectUncertain: snap.llm.rejectUncertain,
|
|
2174
|
+
llmMinConfidence: snap.llm.minConfidence,
|
|
2175
|
+
llmDenyIsTerminal: snap.llm.denyIsTerminal,
|
|
2176
|
+
cacheEnabled: snap.cache.enabled,
|
|
2177
|
+
cacheTtlMs: snap.cache.ttlMs,
|
|
2178
|
+
cacheMaxEntries: snap.cache.maxEntries,
|
|
2179
|
+
cacheHits: snap.cache.hits,
|
|
2180
|
+
cacheMisses: snap.cache.misses,
|
|
2181
|
+
cacheSize: snap.cache.size,
|
|
2182
|
+
traceEnabled: snap.trace.enabled,
|
|
2183
|
+
traceContent: snap.trace.content,
|
|
2184
|
+
tracePath: snap.trace.path,
|
|
2185
|
+
circuitState: snap.circuit?.state,
|
|
2186
|
+
circuitFailures: snap.circuit?.consecutiveFailures,
|
|
2187
|
+
ruleCount: snap.rules.length,
|
|
2188
|
+
ruleErrors: [...snap.ruleErrors]
|
|
2189
|
+
};
|
|
2190
|
+
},
|
|
2191
|
+
setMode: (mode) => apply({ mode }),
|
|
2192
|
+
setRisk: (level) => apply({ maxAutoRisk: level }),
|
|
2193
|
+
setStrategy: (strategy) => apply({ strategy }),
|
|
2194
|
+
setDecisionTimeout: (ms) => apply({ decisionTimeoutMs: ms ?? null }),
|
|
2195
|
+
setHumanTimeout: (ms) => apply({ humanTimeoutMs: ms ?? null }),
|
|
2196
|
+
addPoolModel: (providerId, model) => apply({ models: [...currentPool(), `${providerId}/${model}`] }),
|
|
2197
|
+
removePoolModel: (index) => {
|
|
2198
|
+
const next = currentPool().filter((_, i) => i !== index);
|
|
2199
|
+
return apply({ models: next.length > 0 ? next : null });
|
|
2200
|
+
},
|
|
2201
|
+
clearPool: () => apply({ models: null }),
|
|
2202
|
+
setCouncilEnabled: (on) => apply({ council: { enabled: on } }),
|
|
2203
|
+
setCouncilMinRisk: (risk) => apply({ council: { minRisk: risk } }),
|
|
2204
|
+
addVoter: (providerId, model) => {
|
|
2205
|
+
const voters = currentVoters();
|
|
2206
|
+
const persona = PERSONA_CYCLE[voters.length % PERSONA_CYCLE.length];
|
|
2207
|
+
return applyVoters([...voters, { provider: providerId, model, persona }]);
|
|
2208
|
+
},
|
|
2209
|
+
removeVoter: (index) => applyVoters(currentVoters().filter((_, i) => i !== index)),
|
|
2210
|
+
cycleVoterPersona: (index) => {
|
|
2211
|
+
const voters = currentVoters();
|
|
2212
|
+
const voter = voters[index];
|
|
2213
|
+
if (!voter) return Promise.resolve("No such voter.");
|
|
2214
|
+
const at = PERSONA_CYCLE.indexOf(voter.persona ?? "");
|
|
2215
|
+
voter.persona = PERSONA_CYCLE[(at + 1) % PERSONA_CYCLE.length];
|
|
2216
|
+
return applyVoters(voters);
|
|
2217
|
+
},
|
|
2218
|
+
toggleVoterVeto: (index) => {
|
|
2219
|
+
const voters = currentVoters();
|
|
2220
|
+
const voter = voters[index];
|
|
2221
|
+
if (!voter) return Promise.resolve("No such voter.");
|
|
2222
|
+
voter.veto = !voter.veto;
|
|
2223
|
+
return applyVoters(voters);
|
|
2224
|
+
},
|
|
2225
|
+
setJudge: (providerId, model) => apply({ council: { judge: `${providerId}/${model}` } }),
|
|
2226
|
+
clearJudge: () => apply({ council: { judge: null } }),
|
|
2227
|
+
// Resolution + budget knobs. `BrainCouncilPatch` accepts null for the
|
|
2228
|
+
// three integer fields, so `undefined` here really does clear to default.
|
|
2229
|
+
setCouncilQuorum: (fraction) => apply({ council: { quorum: fraction } }),
|
|
2230
|
+
setCouncilApproval: (fraction) => apply({ council: { approval: fraction } }),
|
|
2231
|
+
setCouncilDistinctness: (mode) => apply({ council: { distinctness: mode } }),
|
|
2232
|
+
setCouncilPerCallTimeout: (ms) => apply({ council: { perCallTimeoutMs: ms ?? null } }),
|
|
2233
|
+
setCouncilMaxConcurrency: (count) => apply({ council: { maxConcurrency: count ?? null } }),
|
|
2234
|
+
setCouncilVoterMaxTokens: (tokens) => apply({ council: { voterMaxTokens: tokens ?? null } }),
|
|
2235
|
+
setCouncilDeliberationRounds: (rounds) => apply({ council: { deliberationRounds: rounds ?? null } }),
|
|
2236
|
+
setCouncilJudgeMaxTokens: (tokens) => apply({ council: { judgeMaxTokens: tokens ?? null } }),
|
|
2237
|
+
setLedgerEnabled: (on) => apply({ ledger: { enabled: on } }),
|
|
2238
|
+
setAutoDeny: (count) => apply({ ledger: { autoDenyAfterFailures: count ?? null } }),
|
|
2239
|
+
setTerminalPolicy: (policy) => apply({ terminalPolicy: policy }),
|
|
2240
|
+
// Every sub-block below is MERGED field-by-field by `mergePatch`, so a
|
|
2241
|
+
// single-key patch leaves the operator's other knobs alone.
|
|
2242
|
+
setHeuristic: (key, on) => apply({ heuristics: { [key]: on } }),
|
|
2243
|
+
setLlmMaxTokens: (tokens) => apply({ llm: { maxTokens: tokens ?? null } }),
|
|
2244
|
+
setLlmRejectUncertain: (on) => apply({ llm: { rejectUncertain: on } }),
|
|
2245
|
+
setLlmMinConfidence: (value) => apply({ llm: { minConfidence: value } }),
|
|
2246
|
+
setLlmDenyIsTerminal: (mode) => apply({ llm: { denyIsTerminal: mode } }),
|
|
2247
|
+
setCacheEnabled: (on) => apply({ cache: { enabled: on } }),
|
|
2248
|
+
setCacheTtl: (ms) => apply({ cache: { ttlMs: ms } }),
|
|
2249
|
+
setCacheMaxEntries: (count) => apply({ cache: { maxEntries: count } }),
|
|
2250
|
+
setTraceEnabled: (on) => apply({ trace: { enabled: on } }),
|
|
2251
|
+
setTraceContent: (content) => apply({ trace: { content } })
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// src/chimera-reviewer-policy.ts
|
|
2256
|
+
import { parseModelRef } from "@wrongstack/core/agent";
|
|
2257
|
+
import {
|
|
2258
|
+
buildReviewerModelPool,
|
|
2259
|
+
selectRoundRobinReviewerAssignment
|
|
2260
|
+
} from "@wrongstack/core/plugin";
|
|
2261
|
+
function resolveReviewerFallbackModels(reviewFallbackModels, sessionRef) {
|
|
2262
|
+
const base = reviewFallbackModels && reviewFallbackModels.length > 0 ? [...reviewFallbackModels] : [];
|
|
2263
|
+
if (sessionRef && !base.includes(sessionRef)) {
|
|
2264
|
+
base.push(sessionRef);
|
|
2265
|
+
}
|
|
2266
|
+
return base;
|
|
2267
|
+
}
|
|
2268
|
+
var reviewerRoundRobinCursor = 0;
|
|
2269
|
+
function __resetReviewerRoundRobinCursor(value = 0) {
|
|
2270
|
+
reviewerRoundRobinCursor = value;
|
|
2271
|
+
}
|
|
2272
|
+
function assignReviewerModels(provider, model, fallbackModels, selection = "round-robin", statusTracker, random = Math.random) {
|
|
2273
|
+
const pool = buildReviewerModelPool(provider, model, fallbackModels, statusTracker);
|
|
2274
|
+
if (pool.length <= 1) {
|
|
2275
|
+
return {
|
|
2276
|
+
provider,
|
|
2277
|
+
model,
|
|
2278
|
+
fallbackModels: [...fallbackModels]
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
const cursor = selection === "random" ? Math.min(pool.length - 1, Math.floor(Math.max(0, random()) * pool.length)) : reviewerRoundRobinCursor;
|
|
2282
|
+
const assignment = selectRoundRobinReviewerAssignment(
|
|
2283
|
+
pool,
|
|
2284
|
+
cursor,
|
|
2285
|
+
provider,
|
|
2286
|
+
model,
|
|
2287
|
+
statusTracker
|
|
2288
|
+
);
|
|
2289
|
+
if (selection === "round-robin") reviewerRoundRobinCursor = assignment.nextCursor;
|
|
2290
|
+
return {
|
|
2291
|
+
provider: assignment.provider,
|
|
2292
|
+
model: assignment.model,
|
|
2293
|
+
fallbackModels: assignment.fallbackModels
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function assignReviewerModelsRoundRobin(provider, model, fallbackModels, statusTracker) {
|
|
2297
|
+
return assignReviewerModels(provider, model, fallbackModels, "round-robin", statusTracker);
|
|
2298
|
+
}
|
|
2299
|
+
var MAX_REVIEWER_ATTEMPTS = 4;
|
|
2300
|
+
var REVIEWER_ATTEMPT_TIMEOUT_MS = 9e5;
|
|
2301
|
+
var REVIEWER_LADDER_BUDGET_MS = 12e5;
|
|
2302
|
+
var REVIEWER_MIN_ATTEMPT_MS = 12e4;
|
|
2303
|
+
function buildReviewerAttemptLadder(input) {
|
|
2304
|
+
const assignedChain = [...input.assigned.fallbackModels ?? []];
|
|
2305
|
+
const profileChain = [...input.profileChain ?? []];
|
|
2306
|
+
const rungs = [];
|
|
2307
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2308
|
+
const key = (provider, model) => `${provider.trim()}/${model.trim()}`;
|
|
2309
|
+
const push = (provider, model, fallbackModels, tier) => {
|
|
2310
|
+
const p = provider.trim();
|
|
2311
|
+
const m = model.trim();
|
|
2312
|
+
if (!p || !m) return;
|
|
2313
|
+
if (seen.has(key(p, m))) return;
|
|
2314
|
+
seen.add(key(p, m));
|
|
2315
|
+
rungs.push({ provider: p, model: m, fallbackModels, tier });
|
|
2316
|
+
};
|
|
2317
|
+
push(input.assigned.provider, input.assigned.model, [...assignedChain], "primary");
|
|
2318
|
+
for (const [chain, tier] of [
|
|
2319
|
+
[assignedChain, "fallback"],
|
|
2320
|
+
[profileChain, "fallback"]
|
|
2321
|
+
]) {
|
|
2322
|
+
for (let i = 0; i < chain.length; i++) {
|
|
2323
|
+
const parsed = parseModelRef(chain[i]);
|
|
2324
|
+
push(parsed.provider ?? "", parsed.model, chain.slice(i + 1), tier);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
const sessionKey = key(input.session.provider, input.session.model);
|
|
2328
|
+
push(input.session.provider, input.session.model, [], "session");
|
|
2329
|
+
const cap = Math.max(1, Math.floor(input.maxAttempts ?? MAX_REVIEWER_ATTEMPTS));
|
|
2330
|
+
if (rungs.length <= cap) return rungs;
|
|
2331
|
+
const trimmed = rungs.slice(0, cap);
|
|
2332
|
+
if (!trimmed.some((r) => key(r.provider, r.model) === sessionKey)) {
|
|
2333
|
+
const sessionRung = rungs.find((r) => key(r.provider, r.model) === sessionKey);
|
|
2334
|
+
if (sessionRung) trimmed[trimmed.length - 1] = sessionRung;
|
|
2335
|
+
}
|
|
2336
|
+
return trimmed;
|
|
2337
|
+
}
|
|
2338
|
+
var MAX_MUTATING_ATTEMPTS = 2;
|
|
2339
|
+
function buildMutatingAgentLadder(input) {
|
|
2340
|
+
const cap = Math.max(1, Math.floor(input.maxAttempts ?? MAX_MUTATING_ATTEMPTS));
|
|
2341
|
+
const inherit = {
|
|
2342
|
+
provider: "",
|
|
2343
|
+
model: "",
|
|
2344
|
+
fallbackModels: [],
|
|
2345
|
+
tier: "inherit"
|
|
2346
|
+
};
|
|
2347
|
+
if (cap === 1) return [inherit];
|
|
2348
|
+
return [
|
|
2349
|
+
inherit,
|
|
2350
|
+
...buildReviewerAttemptLadder({
|
|
2351
|
+
assigned: { provider: "", model: "" },
|
|
2352
|
+
profileChain: input.profileChain,
|
|
2353
|
+
session: input.session,
|
|
2354
|
+
maxAttempts: cap - 1
|
|
2355
|
+
})
|
|
2356
|
+
];
|
|
2357
|
+
}
|
|
2358
|
+
function shouldAdvanceReviewerLadder(status, error) {
|
|
2359
|
+
if (status === "success") return false;
|
|
2360
|
+
if (status === "stopped") return false;
|
|
2361
|
+
if (error?.kind === "aborted_by_parent") return false;
|
|
2362
|
+
return true;
|
|
2363
|
+
}
|
|
2364
|
+
function reviewerAttemptTimeoutMs(remainingMs, attemptTimeoutMs = REVIEWER_ATTEMPT_TIMEOUT_MS, minAttemptMs = REVIEWER_MIN_ATTEMPT_MS) {
|
|
2365
|
+
if (remainingMs < minAttemptMs) return 0;
|
|
2366
|
+
return Math.min(attemptTimeoutMs, Math.floor(remainingMs));
|
|
2367
|
+
}
|
|
2368
|
+
var CHIMERA_REVIEW_READ_ONLY_TOOLS = Object.freeze([
|
|
2369
|
+
"read",
|
|
2370
|
+
"grep",
|
|
2371
|
+
"glob",
|
|
2372
|
+
"tree",
|
|
2373
|
+
"codebase-stats",
|
|
2374
|
+
"codebase-search",
|
|
2375
|
+
"codebase-skeleton",
|
|
2376
|
+
"codebase-context",
|
|
2377
|
+
"codebase-repo-map",
|
|
2378
|
+
"codebase-incoming-calls",
|
|
2379
|
+
"codebase-outgoing-calls"
|
|
2380
|
+
]);
|
|
2381
|
+
function applyChimeraReviewerReadOnlyPolicy(config) {
|
|
2382
|
+
return {
|
|
2383
|
+
...config,
|
|
2384
|
+
// A security boundary, not an additive default: discard any inherited
|
|
2385
|
+
// mutation tools/capabilities so Chimera reviewers cannot repair files.
|
|
2386
|
+
tools: [...CHIMERA_REVIEW_READ_ONLY_TOOLS],
|
|
2387
|
+
allowedCapabilities: ["fs.read"],
|
|
2388
|
+
worktree: "off",
|
|
2389
|
+
// Mandatory model-driven completion: a reviewer must never be killed by
|
|
2390
|
+
// the wall-clock watchdog. At its deadline (or when the leader's session
|
|
2391
|
+
// ends and `Director.requestFinish()` fires) it receives an in-band
|
|
2392
|
+
// `subagent.finish_requested` notification between tool batches and
|
|
2393
|
+
// finishes its report in its own turn within the granted grace window.
|
|
2394
|
+
// Only if that window also elapses does the terminal stop apply — the
|
|
2395
|
+
// reviewer's bounded maximum lifetime.
|
|
2396
|
+
gracefulFinish: true
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
// src/execution-cleanup.ts
|
|
2401
|
+
async function finalizeExecutionCleanup(input) {
|
|
2402
|
+
const {
|
|
2403
|
+
offStorageObservability,
|
|
2404
|
+
fleetStatusLine,
|
|
2405
|
+
onCoordinatorStop,
|
|
2406
|
+
stats,
|
|
2407
|
+
renderer,
|
|
2408
|
+
detachTodosCheckpoint,
|
|
2409
|
+
mcpRegistry,
|
|
2410
|
+
agent,
|
|
2411
|
+
session,
|
|
2412
|
+
tokenCounter,
|
|
2413
|
+
events,
|
|
2414
|
+
chimeraWork,
|
|
2415
|
+
director,
|
|
2416
|
+
reader
|
|
2417
|
+
} = input;
|
|
2418
|
+
offStorageObservability();
|
|
2419
|
+
fleetStatusLine?.stop();
|
|
2420
|
+
onCoordinatorStop?.();
|
|
2421
|
+
try {
|
|
2422
|
+
stats.render(renderer);
|
|
2423
|
+
} catch (_err) {
|
|
2424
|
+
}
|
|
2425
|
+
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
2426
|
+
try {
|
|
2427
|
+
const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
|
|
2428
|
+
getProcessRegistry2().killAll({ force: true, includeProtected: true });
|
|
2429
|
+
} catch (err) {
|
|
2430
|
+
console.warn(
|
|
2431
|
+
JSON.stringify({
|
|
2432
|
+
level: "warn",
|
|
2433
|
+
event: "shutdown.process_kill_all_failed",
|
|
2434
|
+
message: `Process registry killAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2435
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2436
|
+
})
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
await mcpRegistry.stopAll().catch((err) => {
|
|
2440
|
+
console.warn(
|
|
2441
|
+
JSON.stringify({
|
|
2442
|
+
level: "warn",
|
|
2443
|
+
event: "shutdown.mcp_stop_failed",
|
|
2444
|
+
message: `MCP registry stopAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2445
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2446
|
+
})
|
|
2447
|
+
);
|
|
2448
|
+
});
|
|
2449
|
+
const activeSession = agent.ctx.session ?? session;
|
|
2450
|
+
const pending = activeSession.pendingToolUses;
|
|
2451
|
+
await activeSession.append({
|
|
2452
|
+
type: "session_end",
|
|
2453
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2454
|
+
usage: tokenCounter.total(),
|
|
2455
|
+
pendingToolUses: pending.length > 0 ? pending : void 0
|
|
2456
|
+
}).catch((err) => {
|
|
2457
|
+
console.warn(
|
|
2458
|
+
JSON.stringify({
|
|
2459
|
+
level: "warn",
|
|
2460
|
+
event: "shutdown.session_end_append_failed",
|
|
2461
|
+
message: `session_end append failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2462
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2463
|
+
})
|
|
2464
|
+
);
|
|
2465
|
+
});
|
|
2466
|
+
const sessionEndProducers = /* @__PURE__ */ new Set();
|
|
2467
|
+
events.emit("session.ended", {
|
|
2468
|
+
id: activeSession.id,
|
|
2469
|
+
sessionId: activeSession.id,
|
|
2470
|
+
usage: tokenCounter.total(),
|
|
2471
|
+
waitUntil: (work) => {
|
|
2472
|
+
const tracked = Promise.resolve(work).finally(() => sessionEndProducers.delete(tracked));
|
|
2473
|
+
void tracked.catch(() => void 0);
|
|
2474
|
+
sessionEndProducers.add(tracked);
|
|
2475
|
+
}
|
|
2476
|
+
});
|
|
2477
|
+
try {
|
|
2478
|
+
const model = agent.ctx.model;
|
|
2479
|
+
if (!model) {
|
|
2480
|
+
console.warn(
|
|
2481
|
+
JSON.stringify({
|
|
2482
|
+
level: "warn",
|
|
2483
|
+
event: "shutdown.wrongtrace_telemetry_skipped",
|
|
2484
|
+
message: "Skipping WrongTrace telemetry: agent model unknown at session end",
|
|
2485
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2486
|
+
})
|
|
2487
|
+
);
|
|
2488
|
+
} else {
|
|
2489
|
+
const { reportWrongTraceSessionTelemetry } = await import("./wrongtrace-telemetry-3ASFKOZO.js");
|
|
2490
|
+
const usage = tokenCounter.total();
|
|
2491
|
+
const report = reportWrongTraceSessionTelemetry({
|
|
2492
|
+
sessionId: activeSession.id,
|
|
2493
|
+
agentName: "wrongstack-cli",
|
|
2494
|
+
model,
|
|
2495
|
+
provider: agent.ctx.provider?.id ?? "",
|
|
2496
|
+
usage: {
|
|
2497
|
+
input: usage.input,
|
|
2498
|
+
output: usage.output,
|
|
2499
|
+
cacheRead: usage.cacheRead,
|
|
2500
|
+
cacheWrite: usage.cacheWrite
|
|
2501
|
+
},
|
|
2502
|
+
costUsd: tokenCounter.estimateCost().total
|
|
2503
|
+
});
|
|
2504
|
+
const deadline = new Promise((resolve5) => {
|
|
2505
|
+
const t = setTimeout(resolve5, 5e3);
|
|
2506
|
+
t.unref?.();
|
|
2507
|
+
});
|
|
2508
|
+
const tracked = Promise.race([report, deadline]).finally(
|
|
2509
|
+
() => sessionEndProducers.delete(tracked)
|
|
2510
|
+
);
|
|
2511
|
+
void tracked.catch(() => void 0);
|
|
2512
|
+
sessionEndProducers.add(tracked);
|
|
2513
|
+
}
|
|
2514
|
+
} catch {
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
const { snapshotGateDecisions, persistWrongTraceGateCounters } = await import("./wrongtrace-gate-counters-7C2EAAZL.js");
|
|
2518
|
+
const projectRoot = agent.ctx.projectRoot;
|
|
2519
|
+
if (projectRoot) {
|
|
2520
|
+
await persistWrongTraceGateCounters(projectRoot, snapshotGateDecisions());
|
|
2521
|
+
}
|
|
2522
|
+
} catch {
|
|
2523
|
+
}
|
|
2524
|
+
if (director) {
|
|
2525
|
+
try {
|
|
2526
|
+
director.requestFinish("leader session ended \u2014 finish your review now");
|
|
2527
|
+
} catch (finishErr) {
|
|
2528
|
+
console.warn(
|
|
2529
|
+
JSON.stringify({
|
|
2530
|
+
level: "warn",
|
|
2531
|
+
event: "shutdown.subagent_finish_notify_failed",
|
|
2532
|
+
message: finishErr instanceof Error ? finishErr.message : String(finishErr),
|
|
2533
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2534
|
+
})
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
try {
|
|
2539
|
+
while (sessionEndProducers.size > 0) {
|
|
2540
|
+
await Promise.allSettled([...sessionEndProducers]);
|
|
2541
|
+
}
|
|
2542
|
+
if (director) {
|
|
2543
|
+
try {
|
|
2544
|
+
director.requestFinish("leader session ended \u2014 finish your review now");
|
|
2545
|
+
} catch {
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
await chimeraWork?.drainAndClose();
|
|
2549
|
+
} catch (err) {
|
|
2550
|
+
console.warn(
|
|
2551
|
+
JSON.stringify({
|
|
2552
|
+
level: "warn",
|
|
2553
|
+
event: "shutdown.chimera_work_failed",
|
|
2554
|
+
message: `Pending chimera work failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2555
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2556
|
+
})
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2559
|
+
if (director) {
|
|
2560
|
+
try {
|
|
2561
|
+
director.requestFinish("leader session ended \u2014 finish your review now");
|
|
2562
|
+
} catch (finishErr) {
|
|
2563
|
+
console.warn(
|
|
2283
2564
|
JSON.stringify({
|
|
2284
|
-
level: "
|
|
2285
|
-
event: "
|
|
2286
|
-
message,
|
|
2287
|
-
|
|
2565
|
+
level: "warn",
|
|
2566
|
+
event: "shutdown.subagent_finish_notify_failed",
|
|
2567
|
+
message: finishErr instanceof Error ? finishErr.message : String(finishErr),
|
|
2568
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2569
|
+
})
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
try {
|
|
2573
|
+
await director.terminateAll();
|
|
2574
|
+
} catch (termErr) {
|
|
2575
|
+
console.warn(
|
|
2576
|
+
JSON.stringify({
|
|
2577
|
+
level: "warn",
|
|
2578
|
+
event: "shutdown.director_terminate_all_failed",
|
|
2579
|
+
message: termErr instanceof Error ? termErr.message : String(termErr),
|
|
2288
2580
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2289
2581
|
})
|
|
2290
2582
|
);
|
|
2291
|
-
return message;
|
|
2292
2583
|
}
|
|
2293
2584
|
}
|
|
2294
|
-
|
|
2585
|
+
await activeSession.close().catch((err) => {
|
|
2586
|
+
console.warn(
|
|
2587
|
+
JSON.stringify({
|
|
2588
|
+
level: "warn",
|
|
2589
|
+
event: "shutdown.session_close_failed",
|
|
2590
|
+
message: `Session close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2591
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2592
|
+
})
|
|
2593
|
+
);
|
|
2594
|
+
});
|
|
2595
|
+
await reader.close().catch((err) => {
|
|
2596
|
+
console.warn(
|
|
2597
|
+
JSON.stringify({
|
|
2598
|
+
level: "warn",
|
|
2599
|
+
event: "shutdown.reader_close_failed",
|
|
2600
|
+
message: `Input reader close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2601
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2602
|
+
})
|
|
2603
|
+
);
|
|
2604
|
+
});
|
|
2295
2605
|
}
|
|
2296
2606
|
|
|
2297
|
-
// src/
|
|
2298
|
-
import
|
|
2299
|
-
import
|
|
2300
|
-
import {
|
|
2301
|
-
import {
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2607
|
+
// src/execution-kanban-dispatch.ts
|
|
2608
|
+
import { randomUUID } from "node:crypto";
|
|
2609
|
+
import { missingRequiredRuntimeTools } from "@wrongstack/core/agent-catalog";
|
|
2610
|
+
import { WIDE_SUBAGENT_CAPABILITIES } from "@wrongstack/core/security";
|
|
2611
|
+
import { stripFrontmatter } from "@wrongstack/core/skills";
|
|
2612
|
+
|
|
2613
|
+
// src/kanban-dispatch-route.ts
|
|
2614
|
+
import { fallbackProfileChain, parseModelRef as parseModelRef2 } from "@wrongstack/core/agent";
|
|
2615
|
+
import { resolveTier } from "@wrongstack/core/coordination";
|
|
2616
|
+
function resolveKanbanDispatchRoute(config, spawnOpts) {
|
|
2617
|
+
let provider = spawnOpts?.provider;
|
|
2618
|
+
let model = spawnOpts?.model;
|
|
2619
|
+
let fallbackModels = spawnOpts?.fallbackModels;
|
|
2620
|
+
let tier;
|
|
2621
|
+
if (spawnOpts?.fallbackProfile) {
|
|
2622
|
+
const chain = fallbackProfileChain(config, spawnOpts.fallbackProfile);
|
|
2623
|
+
const primary = chain[0] ? parseModelRef2(chain[0]) : void 0;
|
|
2624
|
+
if (primary?.model) {
|
|
2625
|
+
provider = primary.provider ?? config.provider;
|
|
2626
|
+
model = primary.model;
|
|
2627
|
+
fallbackModels = spawnOpts.fallbackModels ?? chain.slice(1);
|
|
2628
|
+
}
|
|
2629
|
+
} else if (spawnOpts?.tier || spawnOpts?.role) {
|
|
2630
|
+
const resolved = resolveTier(config, {
|
|
2631
|
+
...spawnOpts.tier ? { tier: spawnOpts.tier } : {},
|
|
2632
|
+
...spawnOpts.role ? { role: spawnOpts.role } : {}
|
|
2633
|
+
});
|
|
2634
|
+
if (resolved?.model) {
|
|
2635
|
+
tier = resolved.tier;
|
|
2636
|
+
provider = provider ?? resolved.provider ?? config.provider;
|
|
2637
|
+
model = model ?? resolved.model;
|
|
2638
|
+
fallbackModels = fallbackModels ?? resolved.fallbackModels?.slice() ?? void 0;
|
|
2321
2639
|
}
|
|
2322
|
-
}
|
|
2640
|
+
}
|
|
2641
|
+
return { provider, model, fallbackModels, ...tier ? { tier } : {} };
|
|
2323
2642
|
}
|
|
2324
2643
|
|
|
2325
|
-
// src/
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2644
|
+
// src/execution-kanban-dispatch.ts
|
|
2645
|
+
function createKanbanDispatchHandler({
|
|
2646
|
+
config,
|
|
2647
|
+
events,
|
|
2648
|
+
skillLoader,
|
|
2649
|
+
sddSubagentFactory
|
|
2650
|
+
}) {
|
|
2651
|
+
if (!sddSubagentFactory) return {};
|
|
2652
|
+
const onKanbanDispatch = async (description, spawnOpts) => {
|
|
2653
|
+
const subagentId = `kanban-${randomUUID().slice(0, 8)}`;
|
|
2654
|
+
const taskId = randomUUID();
|
|
2655
|
+
const name = spawnOpts?.name ?? "kanban-agent";
|
|
2656
|
+
const {
|
|
2657
|
+
provider: resolvedProvider,
|
|
2658
|
+
model: resolvedModel,
|
|
2659
|
+
fallbackModels: resolvedFallbackModels
|
|
2660
|
+
} = resolveKanbanDispatchRoute(config, spawnOpts);
|
|
2661
|
+
let agentDescription = description;
|
|
2662
|
+
if (spawnOpts?.skills?.length && skillLoader) {
|
|
2663
|
+
const availableToolNames = spawnOpts.tools;
|
|
2664
|
+
const loaded = [];
|
|
2665
|
+
for (const skillName of spawnOpts.skills) {
|
|
2666
|
+
try {
|
|
2667
|
+
const manifest = await skillLoader.find(skillName);
|
|
2668
|
+
if (!manifest) {
|
|
2669
|
+
process.emitWarning(`Kanban dispatch skill not found, skipped: ${skillName}`, {
|
|
2670
|
+
code: "WRONGSTACK_KANBAN_SKILL_SKIPPED"
|
|
2671
|
+
});
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
if (availableToolNames !== void 0 && missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0) {
|
|
2675
|
+
process.emitWarning(
|
|
2676
|
+
`Kanban dispatch skill "${skillName}" needs tools this worker does not have, skipped.`,
|
|
2677
|
+
{ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
|
|
2678
|
+
);
|
|
2679
|
+
continue;
|
|
2680
|
+
}
|
|
2681
|
+
const body = stripFrontmatter(await skillLoader.readBody(skillName)).trim();
|
|
2682
|
+
if (!body) continue;
|
|
2683
|
+
loaded.push(`## Required skill: ${skillName}
|
|
2684
|
+
|
|
2685
|
+
${body}`);
|
|
2686
|
+
} catch (err) {
|
|
2687
|
+
process.emitWarning(
|
|
2688
|
+
`Kanban dispatch skill "${skillName}" could not be loaded, skipped: ${err instanceof Error ? err.message : String(err)}`,
|
|
2689
|
+
{ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
if (loaded.length > 0) {
|
|
2694
|
+
agentDescription = `${description}
|
|
2695
|
+
|
|
2696
|
+
# Required agentic skill instructions
|
|
2697
|
+
|
|
2698
|
+
${loaded.join("\n\n")}`;
|
|
2699
|
+
}
|
|
2346
2700
|
}
|
|
2701
|
+
void (async () => {
|
|
2702
|
+
let built;
|
|
2703
|
+
let completionReported = false;
|
|
2704
|
+
try {
|
|
2705
|
+
if (spawnOpts?.signal?.aborted) throw new Error("Kanban dispatch cancelled.");
|
|
2706
|
+
built = await sddSubagentFactory(
|
|
2707
|
+
{
|
|
2708
|
+
id: subagentId,
|
|
2709
|
+
name,
|
|
2710
|
+
role: "kanban-agent",
|
|
2711
|
+
prompt: agentDescription,
|
|
2712
|
+
allowedCapabilities: spawnOpts?.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES,
|
|
2713
|
+
...resolvedProvider ? { provider: resolvedProvider } : {},
|
|
2714
|
+
...resolvedModel ? { model: resolvedModel } : {},
|
|
2715
|
+
...resolvedFallbackModels ? { fallbackModels: resolvedFallbackModels } : {},
|
|
2716
|
+
...spawnOpts?.tools ? { tools: spawnOpts.tools } : {}
|
|
2717
|
+
},
|
|
2718
|
+
...spawnOpts?.context ? [{ id: taskId, description: agentDescription, context: spawnOpts.context }] : []
|
|
2719
|
+
);
|
|
2720
|
+
if (spawnOpts?.signal?.aborted) throw new Error("Kanban dispatch cancelled.");
|
|
2721
|
+
const result = await built.agent.run(
|
|
2722
|
+
agentDescription,
|
|
2723
|
+
...spawnOpts?.signal ? [{ signal: spawnOpts.signal }] : []
|
|
2724
|
+
);
|
|
2725
|
+
completionReported = true;
|
|
2726
|
+
await spawnOpts?.onDone?.({
|
|
2727
|
+
status: result.status === "done" ? "completed" : "failed",
|
|
2728
|
+
result: result.finalText,
|
|
2729
|
+
..."error" in result && result.error?.message ? { error: result.error.message } : {}
|
|
2730
|
+
});
|
|
2731
|
+
} catch (err) {
|
|
2732
|
+
if (!completionReported) {
|
|
2733
|
+
await spawnOpts?.onDone?.({
|
|
2734
|
+
status: "failed",
|
|
2735
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
throw err;
|
|
2739
|
+
} finally {
|
|
2740
|
+
await built?.dispose?.();
|
|
2741
|
+
}
|
|
2742
|
+
})().catch((err) => {
|
|
2743
|
+
events.emit("error", {
|
|
2744
|
+
err: err instanceof Error ? err : new Error(String(err)),
|
|
2745
|
+
phase: "kanban.dispatch"
|
|
2746
|
+
});
|
|
2747
|
+
});
|
|
2748
|
+
const tags = [];
|
|
2749
|
+
if (resolvedProvider) tags.push(resolvedProvider);
|
|
2750
|
+
if (resolvedModel) tags.push(resolvedModel);
|
|
2751
|
+
if (resolvedFallbackModels?.length) {
|
|
2752
|
+
tags.push(`fallback=${resolvedFallbackModels.join(",")}`);
|
|
2753
|
+
}
|
|
2754
|
+
if (spawnOpts?.fallbackProfile) tags.push(`profile=${spawnOpts.fallbackProfile}`);
|
|
2755
|
+
if (spawnOpts?.skills?.length) tags.push(`skills=${spawnOpts.skills.join(",")}`);
|
|
2756
|
+
if (spawnOpts?.name) tags.push(`"${spawnOpts.name}"`);
|
|
2757
|
+
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
2758
|
+
return `Spawned subagent ${subagentId}${tag} for task ${taskId}.`;
|
|
2347
2759
|
};
|
|
2348
|
-
|
|
2349
|
-
const currentVoters = () => brainRuntime.getSnapshot().council.voters.map((v) => ({ ...v }));
|
|
2350
|
-
const applyVoters = (voters) => apply({ council: { voters: voters.length > 0 ? voters : null } });
|
|
2351
|
-
return {
|
|
2352
|
-
getSettings() {
|
|
2353
|
-
const snap = brainRuntime.getSnapshot();
|
|
2354
|
-
return {
|
|
2355
|
-
mode: snap.mode,
|
|
2356
|
-
riskLevel: snap.maxAutoRisk,
|
|
2357
|
-
strategy: snap.strategy,
|
|
2358
|
-
decisionTimeoutMs: snap.decisionTimeoutMs,
|
|
2359
|
-
humanTimeoutMs: snap.humanTimeoutMs,
|
|
2360
|
-
pool: snap.models.map(compactEntry),
|
|
2361
|
-
poolResolved: snap.poolLabels,
|
|
2362
|
-
usingSessionModel: snap.usingSessionModel,
|
|
2363
|
-
councilEnabled: snap.council.enabled,
|
|
2364
|
-
councilMinRisk: snap.council.minRisk,
|
|
2365
|
-
voters: snap.council.voters.map((v) => ({
|
|
2366
|
-
label: compactEntry(v),
|
|
2367
|
-
persona: v.persona,
|
|
2368
|
-
veto: v.veto,
|
|
2369
|
-
weight: v.weight
|
|
2370
|
-
})),
|
|
2371
|
-
councilSeats: snap.councilLabels,
|
|
2372
|
-
personaCatalog: PERSONA_CATALOG.map((persona) => ({ ...persona })),
|
|
2373
|
-
councilQuorum: snap.council.quorum,
|
|
2374
|
-
councilApproval: snap.council.approval,
|
|
2375
|
-
councilDistinctness: snap.council.distinctness,
|
|
2376
|
-
councilPerCallTimeoutMs: snap.council.perCallTimeoutMs,
|
|
2377
|
-
councilMaxConcurrency: snap.council.maxConcurrency,
|
|
2378
|
-
councilVoterMaxTokens: snap.council.voterMaxTokens,
|
|
2379
|
-
councilDeliberationRounds: snap.council.deliberationRounds,
|
|
2380
|
-
councilJudgeMaxTokens: snap.council.judgeMaxTokens,
|
|
2381
|
-
// The EFFECTIVE judge, not `council.judge`. The configured one is
|
|
2382
|
-
// usually absent, so the panel used to read "auto" and say nothing
|
|
2383
|
-
// about who actually breaks the panel's ties — including when that is
|
|
2384
|
-
// one of the seats that produced the tie.
|
|
2385
|
-
judgeLabel: snap.judgeLabel,
|
|
2386
|
-
judgeConfigured: snap.council.judge !== void 0,
|
|
2387
|
-
judgeIsVoter: snap.judgeIsVoter,
|
|
2388
|
-
ledgerEnabled: snap.ledger.enabled,
|
|
2389
|
-
autoDenyAfterFailures: snap.ledger.autoDenyAfterFailures,
|
|
2390
|
-
terminalPolicy: snap.terminalPolicy,
|
|
2391
|
-
heuristics: {
|
|
2392
|
-
lowRiskAutoAnswer: snap.heuristics.lowRiskAutoAnswer,
|
|
2393
|
-
blockedResolved: snap.heuristics.blockedResolved,
|
|
2394
|
-
deadlockSkip: snap.heuristics.deadlockSkip,
|
|
2395
|
-
retryExhausted: snap.heuristics.retryExhausted,
|
|
2396
|
-
continuePing: snap.heuristics.continuePing,
|
|
2397
|
-
blockedResolvedMarkers: snap.heuristics.blockedResolvedMarkers
|
|
2398
|
-
},
|
|
2399
|
-
llmMaxTokens: snap.llm.maxTokens,
|
|
2400
|
-
llmRejectUncertain: snap.llm.rejectUncertain,
|
|
2401
|
-
llmMinConfidence: snap.llm.minConfidence,
|
|
2402
|
-
llmDenyIsTerminal: snap.llm.denyIsTerminal,
|
|
2403
|
-
cacheEnabled: snap.cache.enabled,
|
|
2404
|
-
cacheTtlMs: snap.cache.ttlMs,
|
|
2405
|
-
cacheMaxEntries: snap.cache.maxEntries,
|
|
2406
|
-
cacheHits: snap.cache.hits,
|
|
2407
|
-
cacheMisses: snap.cache.misses,
|
|
2408
|
-
cacheSize: snap.cache.size,
|
|
2409
|
-
traceEnabled: snap.trace.enabled,
|
|
2410
|
-
traceContent: snap.trace.content,
|
|
2411
|
-
tracePath: snap.trace.path,
|
|
2412
|
-
circuitState: snap.circuit?.state,
|
|
2413
|
-
circuitFailures: snap.circuit?.consecutiveFailures,
|
|
2414
|
-
ruleCount: snap.rules.length,
|
|
2415
|
-
ruleErrors: [...snap.ruleErrors]
|
|
2416
|
-
};
|
|
2417
|
-
},
|
|
2418
|
-
setMode: (mode) => apply({ mode }),
|
|
2419
|
-
setRisk: (level) => apply({ maxAutoRisk: level }),
|
|
2420
|
-
setStrategy: (strategy) => apply({ strategy }),
|
|
2421
|
-
setDecisionTimeout: (ms) => apply({ decisionTimeoutMs: ms ?? null }),
|
|
2422
|
-
setHumanTimeout: (ms) => apply({ humanTimeoutMs: ms ?? null }),
|
|
2423
|
-
addPoolModel: (providerId, model) => apply({ models: [...currentPool(), `${providerId}/${model}`] }),
|
|
2424
|
-
removePoolModel: (index) => {
|
|
2425
|
-
const next = currentPool().filter((_, i) => i !== index);
|
|
2426
|
-
return apply({ models: next.length > 0 ? next : null });
|
|
2427
|
-
},
|
|
2428
|
-
clearPool: () => apply({ models: null }),
|
|
2429
|
-
setCouncilEnabled: (on) => apply({ council: { enabled: on } }),
|
|
2430
|
-
setCouncilMinRisk: (risk) => apply({ council: { minRisk: risk } }),
|
|
2431
|
-
addVoter: (providerId, model) => {
|
|
2432
|
-
const voters = currentVoters();
|
|
2433
|
-
const persona = PERSONA_CYCLE[voters.length % PERSONA_CYCLE.length];
|
|
2434
|
-
return applyVoters([...voters, { provider: providerId, model, persona }]);
|
|
2435
|
-
},
|
|
2436
|
-
removeVoter: (index) => applyVoters(currentVoters().filter((_, i) => i !== index)),
|
|
2437
|
-
cycleVoterPersona: (index) => {
|
|
2438
|
-
const voters = currentVoters();
|
|
2439
|
-
const voter = voters[index];
|
|
2440
|
-
if (!voter) return Promise.resolve("No such voter.");
|
|
2441
|
-
const at = PERSONA_CYCLE.indexOf(voter.persona ?? "");
|
|
2442
|
-
voter.persona = PERSONA_CYCLE[(at + 1) % PERSONA_CYCLE.length];
|
|
2443
|
-
return applyVoters(voters);
|
|
2444
|
-
},
|
|
2445
|
-
toggleVoterVeto: (index) => {
|
|
2446
|
-
const voters = currentVoters();
|
|
2447
|
-
const voter = voters[index];
|
|
2448
|
-
if (!voter) return Promise.resolve("No such voter.");
|
|
2449
|
-
voter.veto = !voter.veto;
|
|
2450
|
-
return applyVoters(voters);
|
|
2451
|
-
},
|
|
2452
|
-
setJudge: (providerId, model) => apply({ council: { judge: `${providerId}/${model}` } }),
|
|
2453
|
-
clearJudge: () => apply({ council: { judge: null } }),
|
|
2454
|
-
// Resolution + budget knobs. `BrainCouncilPatch` accepts null for the
|
|
2455
|
-
// three integer fields, so `undefined` here really does clear to default.
|
|
2456
|
-
setCouncilQuorum: (fraction) => apply({ council: { quorum: fraction } }),
|
|
2457
|
-
setCouncilApproval: (fraction) => apply({ council: { approval: fraction } }),
|
|
2458
|
-
setCouncilDistinctness: (mode) => apply({ council: { distinctness: mode } }),
|
|
2459
|
-
setCouncilPerCallTimeout: (ms) => apply({ council: { perCallTimeoutMs: ms ?? null } }),
|
|
2460
|
-
setCouncilMaxConcurrency: (count) => apply({ council: { maxConcurrency: count ?? null } }),
|
|
2461
|
-
setCouncilVoterMaxTokens: (tokens) => apply({ council: { voterMaxTokens: tokens ?? null } }),
|
|
2462
|
-
setCouncilDeliberationRounds: (rounds) => apply({ council: { deliberationRounds: rounds ?? null } }),
|
|
2463
|
-
setCouncilJudgeMaxTokens: (tokens) => apply({ council: { judgeMaxTokens: tokens ?? null } }),
|
|
2464
|
-
setLedgerEnabled: (on) => apply({ ledger: { enabled: on } }),
|
|
2465
|
-
setAutoDeny: (count) => apply({ ledger: { autoDenyAfterFailures: count ?? null } }),
|
|
2466
|
-
setTerminalPolicy: (policy) => apply({ terminalPolicy: policy }),
|
|
2467
|
-
// Every sub-block below is MERGED field-by-field by `mergePatch`, so a
|
|
2468
|
-
// single-key patch leaves the operator's other knobs alone.
|
|
2469
|
-
setHeuristic: (key, on) => apply({ heuristics: { [key]: on } }),
|
|
2470
|
-
setLlmMaxTokens: (tokens) => apply({ llm: { maxTokens: tokens } }),
|
|
2471
|
-
setLlmRejectUncertain: (on) => apply({ llm: { rejectUncertain: on } }),
|
|
2472
|
-
setLlmMinConfidence: (value) => apply({ llm: { minConfidence: value } }),
|
|
2473
|
-
setLlmDenyIsTerminal: (mode) => apply({ llm: { denyIsTerminal: mode } }),
|
|
2474
|
-
setCacheEnabled: (on) => apply({ cache: { enabled: on } }),
|
|
2475
|
-
setCacheTtl: (ms) => apply({ cache: { ttlMs: ms } }),
|
|
2476
|
-
setCacheMaxEntries: (count) => apply({ cache: { maxEntries: count } }),
|
|
2477
|
-
setTraceEnabled: (on) => apply({ trace: { enabled: on } }),
|
|
2478
|
-
setTraceContent: (content) => apply({ trace: { content } })
|
|
2479
|
-
};
|
|
2760
|
+
return { onKanbanDispatch };
|
|
2480
2761
|
}
|
|
2481
2762
|
|
|
2482
|
-
// src/
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
function resolveReviewerFallbackModels(reviewFallbackModels, sessionRef) {
|
|
2489
|
-
const base = reviewFallbackModels && reviewFallbackModels.length > 0 ? [...reviewFallbackModels] : [];
|
|
2490
|
-
if (sessionRef && !base.includes(sessionRef)) {
|
|
2491
|
-
base.push(sessionRef);
|
|
2492
|
-
}
|
|
2493
|
-
return base;
|
|
2494
|
-
}
|
|
2495
|
-
var reviewerRoundRobinCursor = 0;
|
|
2496
|
-
function __resetReviewerRoundRobinCursor(value = 0) {
|
|
2497
|
-
reviewerRoundRobinCursor = value;
|
|
2498
|
-
}
|
|
2499
|
-
function assignReviewerModels(provider, model, fallbackModels, selection = "round-robin", statusTracker, random = Math.random) {
|
|
2500
|
-
const pool = buildReviewerModelPool(provider, model, fallbackModels, statusTracker);
|
|
2501
|
-
if (pool.length <= 1) {
|
|
2502
|
-
return {
|
|
2503
|
-
provider,
|
|
2504
|
-
model,
|
|
2505
|
-
fallbackModels: [...fallbackModels]
|
|
2506
|
-
};
|
|
2507
|
-
}
|
|
2508
|
-
const cursor = selection === "random" ? Math.min(pool.length - 1, Math.floor(Math.max(0, random()) * pool.length)) : reviewerRoundRobinCursor;
|
|
2509
|
-
const assignment = selectRoundRobinReviewerAssignment(
|
|
2510
|
-
pool,
|
|
2511
|
-
cursor,
|
|
2512
|
-
provider,
|
|
2513
|
-
model,
|
|
2514
|
-
statusTracker
|
|
2515
|
-
);
|
|
2516
|
-
if (selection === "round-robin") reviewerRoundRobinCursor = assignment.nextCursor;
|
|
2763
|
+
// src/execution-repl-fleet-callbacks.ts
|
|
2764
|
+
function createReplFleetCallbacks({
|
|
2765
|
+
director,
|
|
2766
|
+
getDirector
|
|
2767
|
+
}) {
|
|
2768
|
+
const resolveDirector = () => getDirector?.() ?? director;
|
|
2517
2769
|
return {
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2770
|
+
onInterruptFleet: () => {
|
|
2771
|
+
const dir = resolveDirector();
|
|
2772
|
+
if (!dir) return 0;
|
|
2773
|
+
let killed = 0;
|
|
2774
|
+
for (const sa of dir.status().subagents) {
|
|
2775
|
+
if (sa.status === "running" || sa.status === "idle") {
|
|
2776
|
+
try {
|
|
2777
|
+
void dir.remove(sa.id);
|
|
2778
|
+
killed++;
|
|
2779
|
+
} catch {
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
return killed;
|
|
2784
|
+
},
|
|
2785
|
+
onAgentIterationComplete: (tokens) => {
|
|
2786
|
+
const dir = resolveDirector();
|
|
2787
|
+
dir?.setLeaderContextPressure(tokens);
|
|
2788
|
+
}
|
|
2521
2789
|
};
|
|
2522
2790
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2791
|
+
|
|
2792
|
+
// src/fleet-statusline.ts
|
|
2793
|
+
import { color as color5, onResize } from "@wrongstack/core/utils";
|
|
2794
|
+
function fmtElapsed(ms) {
|
|
2795
|
+
const s = Math.floor(ms / 1e3);
|
|
2796
|
+
if (s < 60) return `${s}s`;
|
|
2797
|
+
const m = Math.floor(s / 60);
|
|
2798
|
+
const rem = s % 60;
|
|
2799
|
+
if (m < 60) return `${m}m${rem.toString().padStart(2, "0")}s`;
|
|
2800
|
+
const h = Math.floor(m / 60);
|
|
2801
|
+
return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
|
|
2525
2802
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
const
|
|
2532
|
-
const
|
|
2533
|
-
const
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2803
|
+
function renderFleetLine(states, now, columns, version) {
|
|
2804
|
+
const all = [...states.values()];
|
|
2805
|
+
if (all.length === 0) return "";
|
|
2806
|
+
const running = all.filter((a) => a.status === "running");
|
|
2807
|
+
const done = all.filter((a) => a.status === "done").length;
|
|
2808
|
+
const failed = all.filter((a) => a.status === "failed").length;
|
|
2809
|
+
const versionChip = version ? `${color5.bold("WS")}${color5.dim(` v${version}`)} ${color5.dim("\u2502")} ` : "";
|
|
2810
|
+
const counts = versionChip + `${color5.cyan("\u27F3 fleet")} ${color5.yellow(`\u25B6${running.length}`)} ${color5.green(`\u2713${done}`)}` + (failed > 0 ? ` ${color5.red(`\u2717${failed}`)}` : "");
|
|
2811
|
+
const shown = running.sort((a, b) => a.startedAt - b.startedAt).slice(0, 4).map((a) => {
|
|
2812
|
+
const elapsed = fmtElapsed(Math.max(0, now - a.startedAt));
|
|
2813
|
+
const tool = a.lastTool ? ` ${color5.dim(a.lastTool)}` : "";
|
|
2814
|
+
const ext = a.extensions && a.extensions > 0 ? ` ${color5.yellow(`\u26A1${a.extensions}`)}` : "";
|
|
2815
|
+
return `${color5.bold(a.name)} ${color5.yellow("\u25B6")} ${color5.dim(elapsed)} ${color5.dim(`L${a.iterations}`)} ${color5.dim(`${a.toolCalls}t`)}${tool}${ext}`;
|
|
2816
|
+
});
|
|
2817
|
+
let line = shown.length > 0 ? `${counts} ${color5.dim("\u2502")} ${shown.join(color5.dim(" \xB7 "))}` : counts;
|
|
2818
|
+
const max = Math.max(20, columns - 1);
|
|
2819
|
+
const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2820
|
+
if (visible.length > max) {
|
|
2821
|
+
let count = 0;
|
|
2822
|
+
const out = [];
|
|
2823
|
+
let i = 0;
|
|
2824
|
+
while (i < line.length && count < max - 1) {
|
|
2825
|
+
if (line[i] === "\x1B") {
|
|
2826
|
+
const end = line.indexOf("m", i);
|
|
2827
|
+
if (end !== -1) {
|
|
2828
|
+
out.push(line.slice(i, end + 1));
|
|
2829
|
+
i = end + 1;
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
if (line[i] !== void 0) out.push(line[i]);
|
|
2834
|
+
count++;
|
|
2835
|
+
i++;
|
|
2552
2836
|
}
|
|
2837
|
+
line = out.join("") + "\u2026";
|
|
2553
2838
|
}
|
|
2554
|
-
|
|
2555
|
-
push(input.session.provider, input.session.model, [], "session");
|
|
2556
|
-
const cap = Math.max(1, Math.floor(input.maxAttempts ?? MAX_REVIEWER_ATTEMPTS));
|
|
2557
|
-
if (rungs.length <= cap) return rungs;
|
|
2558
|
-
const trimmed = rungs.slice(0, cap);
|
|
2559
|
-
if (!trimmed.some((r) => key(r.provider, r.model) === sessionKey)) {
|
|
2560
|
-
const sessionRung = rungs.find((r) => key(r.provider, r.model) === sessionKey);
|
|
2561
|
-
if (sessionRung) trimmed[trimmed.length - 1] = sessionRung;
|
|
2562
|
-
}
|
|
2563
|
-
return trimmed;
|
|
2564
|
-
}
|
|
2565
|
-
var MAX_MUTATING_ATTEMPTS = 2;
|
|
2566
|
-
function buildMutatingAgentLadder(input) {
|
|
2567
|
-
const cap = Math.max(1, Math.floor(input.maxAttempts ?? MAX_MUTATING_ATTEMPTS));
|
|
2568
|
-
const inherit = {
|
|
2569
|
-
provider: "",
|
|
2570
|
-
model: "",
|
|
2571
|
-
fallbackModels: [],
|
|
2572
|
-
tier: "inherit"
|
|
2573
|
-
};
|
|
2574
|
-
if (cap === 1) return [inherit];
|
|
2575
|
-
return [
|
|
2576
|
-
inherit,
|
|
2577
|
-
...buildReviewerAttemptLadder({
|
|
2578
|
-
assigned: { provider: "", model: "" },
|
|
2579
|
-
profileChain: input.profileChain,
|
|
2580
|
-
session: input.session,
|
|
2581
|
-
maxAttempts: cap - 1
|
|
2582
|
-
})
|
|
2583
|
-
];
|
|
2584
|
-
}
|
|
2585
|
-
function shouldAdvanceReviewerLadder(status, error) {
|
|
2586
|
-
if (status === "success") return false;
|
|
2587
|
-
if (status === "stopped") return false;
|
|
2588
|
-
if (error?.kind === "aborted_by_parent") return false;
|
|
2589
|
-
return true;
|
|
2590
|
-
}
|
|
2591
|
-
function reviewerAttemptTimeoutMs(remainingMs, attemptTimeoutMs = REVIEWER_ATTEMPT_TIMEOUT_MS, minAttemptMs = REVIEWER_MIN_ATTEMPT_MS) {
|
|
2592
|
-
if (remainingMs < minAttemptMs) return 0;
|
|
2593
|
-
return Math.min(attemptTimeoutMs, Math.floor(remainingMs));
|
|
2594
|
-
}
|
|
2595
|
-
var CHIMERA_REVIEW_READ_ONLY_TOOLS = Object.freeze([
|
|
2596
|
-
"read",
|
|
2597
|
-
"grep",
|
|
2598
|
-
"glob",
|
|
2599
|
-
"tree",
|
|
2600
|
-
"codebase-stats",
|
|
2601
|
-
"codebase-search",
|
|
2602
|
-
"codebase-skeleton",
|
|
2603
|
-
"codebase-context",
|
|
2604
|
-
"codebase-repo-map",
|
|
2605
|
-
"codebase-incoming-calls",
|
|
2606
|
-
"codebase-outgoing-calls"
|
|
2607
|
-
]);
|
|
2608
|
-
function applyChimeraReviewerReadOnlyPolicy(config) {
|
|
2609
|
-
return {
|
|
2610
|
-
...config,
|
|
2611
|
-
// A security boundary, not an additive default: discard any inherited
|
|
2612
|
-
// mutation tools/capabilities so Chimera reviewers cannot repair files.
|
|
2613
|
-
tools: [...CHIMERA_REVIEW_READ_ONLY_TOOLS],
|
|
2614
|
-
allowedCapabilities: ["fs.read"],
|
|
2615
|
-
worktree: "off",
|
|
2616
|
-
// Mandatory model-driven completion: a reviewer must never be killed by
|
|
2617
|
-
// the wall-clock watchdog. At its deadline (or when the leader's session
|
|
2618
|
-
// ends and `Director.requestFinish()` fires) it receives an in-band
|
|
2619
|
-
// `subagent.finish_requested` notification between tool batches and
|
|
2620
|
-
// finishes its report in its own turn within the granted grace window.
|
|
2621
|
-
// Only if that window also elapses does the terminal stop apply — the
|
|
2622
|
-
// reviewer's bounded maximum lifetime.
|
|
2623
|
-
gracefulFinish: true
|
|
2624
|
-
};
|
|
2839
|
+
return line;
|
|
2625
2840
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2841
|
+
var FleetStatusLine = class {
|
|
2842
|
+
events;
|
|
2843
|
+
out;
|
|
2844
|
+
throttleMs;
|
|
2845
|
+
now;
|
|
2846
|
+
version;
|
|
2847
|
+
states = /* @__PURE__ */ new Map();
|
|
2848
|
+
unsubs = [];
|
|
2849
|
+
active = false;
|
|
2850
|
+
rows = 0;
|
|
2851
|
+
repaintTimer = null;
|
|
2852
|
+
tickTimer = null;
|
|
2853
|
+
deactivateTimer = null;
|
|
2854
|
+
resizeCleanup = null;
|
|
2855
|
+
lastPaint = 0;
|
|
2856
|
+
onResize = () => {
|
|
2857
|
+
if (this.active) {
|
|
2858
|
+
this.rows = this.out.rows ?? 24;
|
|
2859
|
+
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
2860
|
+
this.paint(true);
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2863
|
+
constructor(opts) {
|
|
2864
|
+
this.events = opts.events;
|
|
2865
|
+
this.out = opts.out ?? process.stdout;
|
|
2866
|
+
this.throttleMs = opts.throttleMs ?? 150;
|
|
2867
|
+
this.now = opts.now ?? Date.now;
|
|
2868
|
+
this.version = opts.version;
|
|
2651
2869
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
const
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2870
|
+
/** Subscribe to host fleet events. No terminal output until a subagent spawns. */
|
|
2871
|
+
start() {
|
|
2872
|
+
const ensure = (id, name) => {
|
|
2873
|
+
let s = this.states.get(id);
|
|
2874
|
+
if (!s) {
|
|
2875
|
+
s = {
|
|
2876
|
+
id,
|
|
2877
|
+
name: name ?? id,
|
|
2878
|
+
status: "running",
|
|
2879
|
+
iterations: 0,
|
|
2880
|
+
toolCalls: 0,
|
|
2881
|
+
startedAt: this.now()
|
|
2882
|
+
};
|
|
2883
|
+
this.states.set(id, s);
|
|
2884
|
+
} else if (name && s.name === s.id) {
|
|
2885
|
+
s.name = name;
|
|
2886
|
+
}
|
|
2887
|
+
return s;
|
|
2888
|
+
};
|
|
2889
|
+
this.unsubs.push(
|
|
2890
|
+
this.events.on("subagent.spawned", (e) => {
|
|
2891
|
+
ensure(e.subagentId, e.name);
|
|
2892
|
+
this.activate();
|
|
2893
|
+
this.schedulePaint();
|
|
2894
|
+
}),
|
|
2895
|
+
this.events.on("subagent.task_started", (e) => {
|
|
2896
|
+
const s = ensure(e.subagentId);
|
|
2897
|
+
s.status = "running";
|
|
2898
|
+
this.activate();
|
|
2899
|
+
this.schedulePaint();
|
|
2900
|
+
}),
|
|
2901
|
+
this.events.on("subagent.tool_executed", (e) => {
|
|
2902
|
+
const s = ensure(e.subagentId);
|
|
2903
|
+
s.toolCalls++;
|
|
2904
|
+
s.lastTool = e.name;
|
|
2905
|
+
this.activate();
|
|
2906
|
+
this.schedulePaint();
|
|
2907
|
+
}),
|
|
2908
|
+
this.events.on("subagent.iteration_summary", (e) => {
|
|
2909
|
+
const s = ensure(e.subagentId);
|
|
2910
|
+
s.iterations = e.iteration;
|
|
2911
|
+
if (typeof e.toolCalls === "number") s.toolCalls = e.toolCalls;
|
|
2912
|
+
if (e.currentTool) s.lastTool = e.currentTool;
|
|
2913
|
+
this.schedulePaint();
|
|
2914
|
+
}),
|
|
2915
|
+
this.events.on("subagent.budget_extended", (e) => {
|
|
2916
|
+
const s = ensure(e.subagentId);
|
|
2917
|
+
s.extensions = e.totalExtensions;
|
|
2918
|
+
this.activate();
|
|
2919
|
+
this.schedulePaint();
|
|
2920
|
+
}),
|
|
2921
|
+
this.events.on("subagent.task_completed", (e) => {
|
|
2922
|
+
const s = ensure(e.subagentId);
|
|
2923
|
+
s.status = e.status === "success" ? "done" : "failed";
|
|
2924
|
+
s.iterations = e.iterations;
|
|
2925
|
+
s.toolCalls = e.toolCalls;
|
|
2926
|
+
s.endedAt = this.now();
|
|
2927
|
+
this.schedulePaint();
|
|
2928
|
+
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
2929
|
+
if (this.deactivateTimer) clearTimeout(this.deactivateTimer);
|
|
2930
|
+
this.deactivateTimer = setTimeout(() => {
|
|
2931
|
+
this.deactivateTimer = null;
|
|
2932
|
+
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
2933
|
+
this.deactivate();
|
|
2934
|
+
}
|
|
2935
|
+
}, 800);
|
|
2936
|
+
}
|
|
2937
|
+
}),
|
|
2938
|
+
this.events.on("subagent.removed", (e) => {
|
|
2939
|
+
this.states.delete(e.subagentId);
|
|
2940
|
+
this.schedulePaint();
|
|
2663
2941
|
})
|
|
2664
2942
|
);
|
|
2665
2943
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
});
|
|
2693
|
-
const sessionEndProducers = /* @__PURE__ */ new Set();
|
|
2694
|
-
events.emit("session.ended", {
|
|
2695
|
-
id: activeSession.id,
|
|
2696
|
-
sessionId: activeSession.id,
|
|
2697
|
-
usage: tokenCounter.total(),
|
|
2698
|
-
waitUntil: (work) => {
|
|
2699
|
-
const tracked = Promise.resolve(work).finally(() => sessionEndProducers.delete(tracked));
|
|
2700
|
-
void tracked.catch(() => void 0);
|
|
2701
|
-
sessionEndProducers.add(tracked);
|
|
2944
|
+
/** Unsubscribe and restore the terminal. Idempotent. */
|
|
2945
|
+
stop() {
|
|
2946
|
+
for (const u of this.unsubs.splice(0)) u();
|
|
2947
|
+
this.deactivate();
|
|
2948
|
+
}
|
|
2949
|
+
isTty() {
|
|
2950
|
+
return !!this.out.isTTY;
|
|
2951
|
+
}
|
|
2952
|
+
activate() {
|
|
2953
|
+
if (this.active || !this.isTty()) return;
|
|
2954
|
+
this.active = true;
|
|
2955
|
+
this.rows = this.out.rows ?? 24;
|
|
2956
|
+
this.out.write("\n");
|
|
2957
|
+
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
2958
|
+
this.out.write(`\x1B[${this.rows - 1};1H`);
|
|
2959
|
+
this.resizeCleanup = onResize(() => this.onResize(), this.out);
|
|
2960
|
+
this.tickTimer = setInterval(() => this.paint(true), 1e3);
|
|
2961
|
+
if (this.tickTimer.unref) this.tickTimer.unref();
|
|
2962
|
+
this.paint(true);
|
|
2963
|
+
}
|
|
2964
|
+
deactivate() {
|
|
2965
|
+
if (!this.active) return;
|
|
2966
|
+
this.active = false;
|
|
2967
|
+
if (this.tickTimer) {
|
|
2968
|
+
clearInterval(this.tickTimer);
|
|
2969
|
+
this.tickTimer = null;
|
|
2702
2970
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
);
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2971
|
+
if (this.repaintTimer) {
|
|
2972
|
+
clearTimeout(this.repaintTimer);
|
|
2973
|
+
this.repaintTimer = null;
|
|
2974
|
+
}
|
|
2975
|
+
if (this.deactivateTimer) {
|
|
2976
|
+
clearTimeout(this.deactivateTimer);
|
|
2977
|
+
this.deactivateTimer = null;
|
|
2978
|
+
}
|
|
2979
|
+
this.resizeCleanup?.();
|
|
2980
|
+
this.resizeCleanup = null;
|
|
2981
|
+
if (this.isTty()) {
|
|
2982
|
+
this.out.write("\x1B7");
|
|
2983
|
+
this.out.write(`\x1B[${this.rows};1H`);
|
|
2984
|
+
this.out.write("\x1B[2K");
|
|
2985
|
+
this.out.write("\x1B8");
|
|
2986
|
+
this.out.write("\x1B[r");
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
schedulePaint() {
|
|
2990
|
+
if (!this.active) return;
|
|
2991
|
+
const since = this.now() - this.lastPaint;
|
|
2992
|
+
if (since >= this.throttleMs) {
|
|
2993
|
+
this.paint(false);
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (this.repaintTimer) return;
|
|
2997
|
+
this.repaintTimer = setTimeout(() => {
|
|
2998
|
+
this.repaintTimer = null;
|
|
2999
|
+
this.paint(false);
|
|
3000
|
+
}, this.throttleMs - since);
|
|
3001
|
+
if (this.repaintTimer.unref) this.repaintTimer.unref();
|
|
3002
|
+
}
|
|
3003
|
+
paint(force) {
|
|
3004
|
+
if (!this.active || !this.isTty()) return;
|
|
3005
|
+
if (!force) this.lastPaint = this.now();
|
|
3006
|
+
else this.lastPaint = this.now();
|
|
3007
|
+
const line = renderFleetLine(this.states, this.now(), this.out.columns ?? 80, this.version);
|
|
3008
|
+
this.out.write("\x1B7");
|
|
3009
|
+
this.out.write(`\x1B[${this.rows};1H`);
|
|
3010
|
+
this.out.write("\x1B[2K");
|
|
3011
|
+
this.out.write(line);
|
|
3012
|
+
this.out.write("\x1B8");
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
|
|
3016
|
+
// src/subagent-models/panel-service.ts
|
|
3017
|
+
import {
|
|
3018
|
+
emptySubagentModelPlan,
|
|
3019
|
+
formatSubagentSlot,
|
|
3020
|
+
getSessionSubagentModelPlan,
|
|
3021
|
+
setSessionSubagentModelPlan,
|
|
3022
|
+
subagentSlotOccupancy
|
|
3023
|
+
} from "@wrongstack/core/coordination";
|
|
3024
|
+
function currentPlan(sessionId) {
|
|
3025
|
+
return getSessionSubagentModelPlan(sessionId) ?? emptySubagentModelPlan();
|
|
3026
|
+
}
|
|
3027
|
+
function createSubagentModelsPanelHost(deps) {
|
|
3028
|
+
const write = async (mutate) => {
|
|
3029
|
+
const ctx = deps.getContext();
|
|
3030
|
+
if (!ctx?.session) return "No active session \u2014 the plan cannot be saved.";
|
|
3031
|
+
const plan = structuredClone(currentPlan(ctx.session.id));
|
|
3032
|
+
mutate(plan);
|
|
3033
|
+
try {
|
|
3034
|
+
await setSessionSubagentModelPlan(ctx, plan);
|
|
3035
|
+
return null;
|
|
3036
|
+
} catch (err) {
|
|
3037
|
+
return err instanceof Error ? err.message : String(err);
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
return {
|
|
3041
|
+
snapshot() {
|
|
3042
|
+
const sessionId = deps.getContext()?.session?.id;
|
|
3043
|
+
const plan = currentPlan(sessionId);
|
|
3044
|
+
const occupancy = subagentSlotOccupancy(sessionId);
|
|
3045
|
+
const session = deps.getSessionTarget?.();
|
|
3046
|
+
return {
|
|
3047
|
+
enabled: plan.enabled,
|
|
3048
|
+
lock: plan.lock,
|
|
3049
|
+
followSessionModel: plan.followSessionModel === true,
|
|
3050
|
+
sessionTarget: session?.provider ? `${session.provider}/${session.model ?? "?"}` : session?.model ?? "",
|
|
3051
|
+
lanes: plan.slots.map((slot, i) => ({
|
|
3052
|
+
target: formatSubagentSlot(slot),
|
|
3053
|
+
busy: occupancy[i]?.subagentIds.length ?? 0,
|
|
3054
|
+
...slot.label ? { label: slot.label } : {}
|
|
3055
|
+
})),
|
|
3056
|
+
roles: Object.entries(plan.roles ?? {}).map(([role, slot]) => ({ role, target: formatSubagentSlot(slot) })).sort((a, b) => a.role.localeCompare(b.role))
|
|
3057
|
+
};
|
|
3058
|
+
},
|
|
3059
|
+
setLane(index, target) {
|
|
3060
|
+
return write((plan) => {
|
|
3061
|
+
if (index < 0 || index >= plan.slots.length) return;
|
|
3062
|
+
const slot = {};
|
|
3063
|
+
if (target.provider) slot.provider = target.provider;
|
|
3064
|
+
if (target.model) slot.model = target.model;
|
|
3065
|
+
if (target.tier) slot.tier = target.tier;
|
|
3066
|
+
if (target.fallbackProfile) slot.fallbackProfile = target.fallbackProfile;
|
|
3067
|
+
if (target.label) slot.label = target.label;
|
|
3068
|
+
plan.slots[index] = slot;
|
|
2730
3069
|
});
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
3070
|
+
},
|
|
3071
|
+
clearLane(index) {
|
|
3072
|
+
return write((plan) => {
|
|
3073
|
+
if (index < 0 || index >= plan.slots.length) return;
|
|
3074
|
+
plan.slots[index] = {};
|
|
3075
|
+
});
|
|
3076
|
+
},
|
|
3077
|
+
toggle(field) {
|
|
3078
|
+
return write((plan) => {
|
|
3079
|
+
plan[field] = plan[field] !== true;
|
|
2734
3080
|
});
|
|
2735
|
-
const tracked = Promise.race([report, deadline]).finally(
|
|
2736
|
-
() => sessionEndProducers.delete(tracked)
|
|
2737
|
-
);
|
|
2738
|
-
void tracked.catch(() => void 0);
|
|
2739
|
-
sessionEndProducers.add(tracked);
|
|
2740
3081
|
}
|
|
2741
|
-
}
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
// src/tui-resume-callback.ts
|
|
3086
|
+
import { hasOpenTodos } from "@wrongstack/core/utils";
|
|
3087
|
+
import { parseNextSteps } from "@wrongstack/tools/next-steps";
|
|
3088
|
+
|
|
3089
|
+
// src/boot/tui-session-resume.ts
|
|
3090
|
+
import * as path10 from "node:path";
|
|
3091
|
+
import {
|
|
3092
|
+
restoreSessionSubagentModelPlan,
|
|
3093
|
+
restoreSessionSubagentPolicy
|
|
3094
|
+
} from "@wrongstack/core/coordination";
|
|
3095
|
+
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
3096
|
+
import { restoreSessionPermissionOverrides as restoreSessionPermissionOverrides2 } from "@wrongstack/core/security";
|
|
3097
|
+
import { attachTodosCheckpoint, loadTodosCheckpoint } from "@wrongstack/core/storage";
|
|
3098
|
+
import { projectLastRequestTokens } from "@wrongstack/core/types/session-timeline";
|
|
3099
|
+
import { sessionScopedPath as sessionScopedPath2, toErrorMessage } from "@wrongstack/core/utils";
|
|
3100
|
+
var RESERVATION_WINDOW_MS = 6e4;
|
|
3101
|
+
var RESERVATION_RENEW_MS = 2e4;
|
|
3102
|
+
function messageText(content) {
|
|
3103
|
+
if (typeof content === "string") return content;
|
|
3104
|
+
if (!Array.isArray(content)) return "";
|
|
3105
|
+
return content.map(
|
|
3106
|
+
(block) => block && typeof block === "object" && "text" in block && typeof block.text === "string" ? block.text : ""
|
|
3107
|
+
).filter(Boolean).join("\n");
|
|
3108
|
+
}
|
|
3109
|
+
function lastAssistantTextOf(messages) {
|
|
3110
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3111
|
+
const message = messages[index];
|
|
3112
|
+
if (message?.role !== "assistant") continue;
|
|
3113
|
+
const text = messageText(message.content).trim();
|
|
3114
|
+
if (text) return text;
|
|
2742
3115
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
3116
|
+
return void 0;
|
|
3117
|
+
}
|
|
3118
|
+
async function resumeSession(ctx, sessionId) {
|
|
3119
|
+
const { state, agent, tokenCounter, switchProviderAndModel, events, onLoadProgress } = ctx;
|
|
3120
|
+
const fail = (stage2, message) => {
|
|
3121
|
+
try {
|
|
3122
|
+
ctx.onFailure?.({ stage: stage2, message: message || "unknown error" });
|
|
3123
|
+
} catch {
|
|
2748
3124
|
}
|
|
2749
|
-
|
|
2750
|
-
}
|
|
2751
|
-
|
|
3125
|
+
return null;
|
|
3126
|
+
};
|
|
3127
|
+
const warnings = [];
|
|
3128
|
+
const warn = (label, err) => {
|
|
3129
|
+
warnings.push(`${label}: ${toErrorMessage(err)}`);
|
|
3130
|
+
};
|
|
3131
|
+
let stage = "start";
|
|
3132
|
+
const setStage = (next) => {
|
|
3133
|
+
stage = next;
|
|
2752
3134
|
try {
|
|
2753
|
-
|
|
2754
|
-
} catch
|
|
2755
|
-
console.warn(
|
|
2756
|
-
JSON.stringify({
|
|
2757
|
-
level: "warn",
|
|
2758
|
-
event: "shutdown.subagent_finish_notify_failed",
|
|
2759
|
-
message: finishErr instanceof Error ? finishErr.message : String(finishErr),
|
|
2760
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2761
|
-
})
|
|
2762
|
-
);
|
|
3135
|
+
ctx.onStage?.(next);
|
|
3136
|
+
} catch {
|
|
2763
3137
|
}
|
|
3138
|
+
};
|
|
3139
|
+
if (!state.activeSessionStore) {
|
|
3140
|
+
return fail("no_session_store", "No session store is bound to this project.");
|
|
2764
3141
|
}
|
|
3142
|
+
setStage("load_replay_module");
|
|
3143
|
+
let replaySessionMessages;
|
|
2765
3144
|
try {
|
|
2766
|
-
|
|
2767
|
-
|
|
3145
|
+
({ replaySessionMessages } = await import("@wrongstack/tui"));
|
|
3146
|
+
if (typeof replaySessionMessages !== "function") {
|
|
3147
|
+
throw new TypeError("@wrongstack/tui does not export replaySessionMessages");
|
|
2768
3148
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
return fail(stage, toErrorMessage(err));
|
|
3151
|
+
}
|
|
3152
|
+
setStage("resolve_id");
|
|
3153
|
+
let canonicalSessionId = sessionId;
|
|
3154
|
+
try {
|
|
3155
|
+
if (state.activeSessionStore.resolveId) {
|
|
3156
|
+
canonicalSessionId = await state.activeSessionStore.resolveId(sessionId);
|
|
2774
3157
|
}
|
|
2775
|
-
await chimeraWork?.drainAndClose();
|
|
2776
3158
|
} catch (err) {
|
|
2777
|
-
console.
|
|
3159
|
+
console.error(
|
|
2778
3160
|
JSON.stringify({
|
|
2779
|
-
level: "
|
|
2780
|
-
event: "
|
|
2781
|
-
|
|
3161
|
+
level: "error",
|
|
3162
|
+
event: "execution.resume_id_resolve_failed",
|
|
3163
|
+
sessionId,
|
|
3164
|
+
message: toErrorMessage(err),
|
|
2782
3165
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2783
3166
|
})
|
|
2784
3167
|
);
|
|
3168
|
+
return fail(stage, toErrorMessage(err));
|
|
2785
3169
|
}
|
|
2786
|
-
|
|
3170
|
+
const previousSessionId = agent.ctx.session?.id;
|
|
3171
|
+
let hydrated;
|
|
3172
|
+
let identityClaimed = false;
|
|
3173
|
+
let resumeClaim;
|
|
3174
|
+
let writerSwapped = false;
|
|
3175
|
+
let openedWriter;
|
|
3176
|
+
let oldWriter;
|
|
3177
|
+
let oldMessages;
|
|
3178
|
+
let oldSessionRefCurrent;
|
|
3179
|
+
let previousDetachFn;
|
|
3180
|
+
let oldTodos = [];
|
|
3181
|
+
let oldPlanPath;
|
|
3182
|
+
let oldTaskPath;
|
|
3183
|
+
try {
|
|
3184
|
+
setStage("reserve_ownership");
|
|
3185
|
+
if (state.wpaths.projectSlug && state.wpaths.globalRoot) {
|
|
3186
|
+
const { getSessionRegistry } = await import("@wrongstack/core/storage");
|
|
3187
|
+
const registry = getSessionRegistry(state.wpaths.globalRoot);
|
|
3188
|
+
resumeClaim = await registry.reserveResume({
|
|
3189
|
+
sessionId: canonicalSessionId,
|
|
3190
|
+
projectSlug: state.wpaths.projectSlug,
|
|
3191
|
+
projectRoot: state.projectRoot,
|
|
3192
|
+
reservationMs: RESERVATION_WINDOW_MS
|
|
3193
|
+
});
|
|
3194
|
+
} else if (state.activateSessionIdentity) {
|
|
3195
|
+
await state.activateSessionIdentity(canonicalSessionId);
|
|
3196
|
+
identityClaimed = true;
|
|
3197
|
+
}
|
|
3198
|
+
setStage("open_journal");
|
|
3199
|
+
const claimToRenew = resumeClaim;
|
|
3200
|
+
let renewFailures = 0;
|
|
3201
|
+
const renew = () => {
|
|
3202
|
+
if (!claimToRenew) return;
|
|
3203
|
+
void claimToRenew.renew(RESERVATION_WINDOW_MS).catch((err) => {
|
|
3204
|
+
if (renewFailures++ > 0) return;
|
|
3205
|
+
console.error(
|
|
3206
|
+
JSON.stringify({
|
|
3207
|
+
level: "warn",
|
|
3208
|
+
event: "execution.resume_reservation_renew_failed",
|
|
3209
|
+
sessionId: canonicalSessionId,
|
|
3210
|
+
message: toErrorMessage(err),
|
|
3211
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3212
|
+
})
|
|
3213
|
+
);
|
|
3214
|
+
});
|
|
3215
|
+
};
|
|
3216
|
+
const renewTimer = setInterval(renew, RESERVATION_RENEW_MS);
|
|
3217
|
+
renewTimer.unref?.();
|
|
3218
|
+
let resumed;
|
|
2787
3219
|
try {
|
|
2788
|
-
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3220
|
+
resumed = await state.activeSessionStore.resume(canonicalSessionId, onLoadProgress);
|
|
3221
|
+
} finally {
|
|
3222
|
+
clearInterval(renewTimer);
|
|
3223
|
+
}
|
|
3224
|
+
openedWriter = resumed.writer;
|
|
3225
|
+
hydrated = resumed.data;
|
|
3226
|
+
setStage("activate_ownership");
|
|
3227
|
+
await Promise.resolve(resumeClaim?.renew(RESERVATION_WINDOW_MS)).catch(() => void 0);
|
|
3228
|
+
if (resumeClaim) {
|
|
3229
|
+
await resumeClaim.activate({
|
|
3230
|
+
sessionId: canonicalSessionId,
|
|
3231
|
+
projectSlug: state.wpaths.projectSlug,
|
|
3232
|
+
projectRoot: state.projectRoot,
|
|
3233
|
+
projectName: path10.basename(state.projectRoot),
|
|
3234
|
+
workingDir: agent.ctx.workingDir,
|
|
3235
|
+
clientType: "tui",
|
|
3236
|
+
pid: process.pid,
|
|
3237
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3238
|
+
});
|
|
3239
|
+
identityClaimed = true;
|
|
3240
|
+
await state.activateSessionIdentity?.(canonicalSessionId);
|
|
3241
|
+
}
|
|
3242
|
+
setStage("replay_history");
|
|
3243
|
+
const meta = resumed.data.metadata;
|
|
3244
|
+
const entries = replaySessionMessages(
|
|
3245
|
+
resumed.data.messages,
|
|
3246
|
+
resumed.data.events,
|
|
3247
|
+
/* startId */
|
|
3248
|
+
1
|
|
3249
|
+
);
|
|
3250
|
+
try {
|
|
3251
|
+
agent.container?.safeResolve?.(TOKENS2.DelegationTracker)?.rehydrate(canonicalSessionId, resumed.data.events ?? []);
|
|
3252
|
+
} catch (err) {
|
|
3253
|
+
warn("delegation rehydrate", err);
|
|
2798
3254
|
}
|
|
3255
|
+
setStage("read_sidecars");
|
|
3256
|
+
const sessionsDir = state.wpaths.projectSessions;
|
|
3257
|
+
const resumedTodosPath = sessionScopedPath2(sessionsDir, resumed.writer.id, ".todos.json");
|
|
3258
|
+
const restoredTodos = await loadTodosCheckpoint(
|
|
3259
|
+
resumedTodosPath,
|
|
3260
|
+
events,
|
|
3261
|
+
agent.ctx.traceId,
|
|
3262
|
+
resumed.writer.id
|
|
3263
|
+
).catch(() => null);
|
|
3264
|
+
setStage("swap_writer");
|
|
3265
|
+
oldWriter = agent.ctx.session;
|
|
3266
|
+
oldMessages = [...agent.ctx.messages];
|
|
3267
|
+
agent.ctx.session = resumed.writer;
|
|
2799
3268
|
try {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
3269
|
+
agent.ctx.state.replaceMessages(resumed.data.messages);
|
|
3270
|
+
restoreSessionSubagentPolicy(agent.ctx, resumed.data.events, resumed.data.subagentsAllowed);
|
|
3271
|
+
restoreSessionSubagentModelPlan(agent.ctx, resumed.data.events);
|
|
3272
|
+
restoreSessionPermissionOverrides2(agent.ctx.meta, resumed.data);
|
|
3273
|
+
} catch (err) {
|
|
3274
|
+
agent.ctx.session = oldWriter;
|
|
3275
|
+
agent.ctx.state.replaceMessages(oldMessages);
|
|
3276
|
+
throw err;
|
|
3277
|
+
}
|
|
3278
|
+
writerSwapped = true;
|
|
3279
|
+
if (state.sessionRef) {
|
|
3280
|
+
oldSessionRefCurrent = state.sessionRef.current;
|
|
3281
|
+
state.sessionRef.current = resumed.writer;
|
|
3282
|
+
}
|
|
3283
|
+
setStage("flush_journal");
|
|
3284
|
+
await agent.ctx.flushConversationJournal().catch((err) => {
|
|
3285
|
+
console.error(
|
|
2803
3286
|
JSON.stringify({
|
|
2804
|
-
level: "
|
|
2805
|
-
event: "
|
|
2806
|
-
message:
|
|
3287
|
+
level: "error",
|
|
3288
|
+
event: "execution.resume_journal_flush_failed",
|
|
3289
|
+
message: toErrorMessage(err),
|
|
2807
3290
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2808
3291
|
})
|
|
2809
3292
|
);
|
|
2810
|
-
|
|
2811
|
-
}
|
|
2812
|
-
await activeSession.close().catch((err) => {
|
|
2813
|
-
console.warn(
|
|
2814
|
-
JSON.stringify({
|
|
2815
|
-
level: "warn",
|
|
2816
|
-
event: "shutdown.session_close_failed",
|
|
2817
|
-
message: `Session close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2818
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2819
|
-
})
|
|
2820
|
-
);
|
|
2821
|
-
});
|
|
2822
|
-
await reader.close().catch((err) => {
|
|
2823
|
-
console.warn(
|
|
2824
|
-
JSON.stringify({
|
|
2825
|
-
level: "warn",
|
|
2826
|
-
event: "shutdown.reader_close_failed",
|
|
2827
|
-
message: `Input reader close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2828
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2829
|
-
})
|
|
2830
|
-
);
|
|
2831
|
-
});
|
|
2832
|
-
}
|
|
2833
|
-
|
|
2834
|
-
// src/execution-kanban-dispatch.ts
|
|
2835
|
-
import { randomUUID } from "node:crypto";
|
|
2836
|
-
import { missingRequiredRuntimeTools } from "@wrongstack/core/agent-catalog";
|
|
2837
|
-
import { WIDE_SUBAGENT_CAPABILITIES } from "@wrongstack/core/security";
|
|
2838
|
-
import { stripFrontmatter } from "@wrongstack/core/skills";
|
|
2839
|
-
|
|
2840
|
-
// src/kanban-dispatch-route.ts
|
|
2841
|
-
import { fallbackProfileChain, parseModelRef as parseModelRef2 } from "@wrongstack/core/agent";
|
|
2842
|
-
import { resolveTier } from "@wrongstack/core/coordination";
|
|
2843
|
-
function resolveKanbanDispatchRoute(config, spawnOpts) {
|
|
2844
|
-
let provider = spawnOpts?.provider;
|
|
2845
|
-
let model = spawnOpts?.model;
|
|
2846
|
-
let fallbackModels = spawnOpts?.fallbackModels;
|
|
2847
|
-
let tier;
|
|
2848
|
-
if (spawnOpts?.fallbackProfile) {
|
|
2849
|
-
const chain = fallbackProfileChain(config, spawnOpts.fallbackProfile);
|
|
2850
|
-
const primary = chain[0] ? parseModelRef2(chain[0]) : void 0;
|
|
2851
|
-
if (primary?.model) {
|
|
2852
|
-
provider = primary.provider ?? config.provider;
|
|
2853
|
-
model = primary.model;
|
|
2854
|
-
fallbackModels = spawnOpts.fallbackModels ?? chain.slice(1);
|
|
2855
|
-
}
|
|
2856
|
-
} else if (spawnOpts?.tier || spawnOpts?.role) {
|
|
2857
|
-
const resolved = resolveTier(config, {
|
|
2858
|
-
...spawnOpts.tier ? { tier: spawnOpts.tier } : {},
|
|
2859
|
-
...spawnOpts.role ? { role: spawnOpts.role } : {}
|
|
3293
|
+
warn("conversation journal did not flush", err);
|
|
2860
3294
|
});
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
} = resolveKanbanDispatchRoute(config, spawnOpts);
|
|
2888
|
-
let agentDescription = description;
|
|
2889
|
-
if (spawnOpts?.skills?.length && skillLoader) {
|
|
2890
|
-
const availableToolNames = spawnOpts.tools;
|
|
2891
|
-
const loaded = [];
|
|
2892
|
-
for (const skillName of spawnOpts.skills) {
|
|
2893
|
-
try {
|
|
2894
|
-
const manifest = await skillLoader.find(skillName);
|
|
2895
|
-
if (!manifest) {
|
|
2896
|
-
process.emitWarning(`Kanban dispatch skill not found, skipped: ${skillName}`, {
|
|
2897
|
-
code: "WRONGSTACK_KANBAN_SKILL_SKIPPED"
|
|
2898
|
-
});
|
|
2899
|
-
continue;
|
|
2900
|
-
}
|
|
2901
|
-
if (availableToolNames !== void 0 && missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0) {
|
|
2902
|
-
process.emitWarning(
|
|
2903
|
-
`Kanban dispatch skill "${skillName}" needs tools this worker does not have, skipped.`,
|
|
2904
|
-
{ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
|
|
2905
|
-
);
|
|
2906
|
-
continue;
|
|
2907
|
-
}
|
|
2908
|
-
const body = stripFrontmatter(await skillLoader.readBody(skillName)).trim();
|
|
2909
|
-
if (!body) continue;
|
|
2910
|
-
loaded.push(`## Required skill: ${skillName}
|
|
2911
|
-
|
|
2912
|
-
${body}`);
|
|
2913
|
-
} catch (err) {
|
|
2914
|
-
process.emitWarning(
|
|
2915
|
-
`Kanban dispatch skill "${skillName}" could not be loaded, skipped: ${err instanceof Error ? err.message : String(err)}`,
|
|
2916
|
-
{ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
|
|
2917
|
-
);
|
|
2918
|
-
}
|
|
3295
|
+
setStage("repoint_sidecars");
|
|
3296
|
+
previousDetachFn = state.detachActiveTodosCheckpoint;
|
|
3297
|
+
oldTodos = agent.ctx.state.todos ? [...agent.ctx.state.todos] : [];
|
|
3298
|
+
oldPlanPath = agent.ctx.state.meta?.["plan.path"];
|
|
3299
|
+
oldTaskPath = agent.ctx.state.meta?.["task.path"];
|
|
3300
|
+
try {
|
|
3301
|
+
await Promise.resolve(previousDetachFn?.()).catch(() => void 0);
|
|
3302
|
+
agent.ctx.state.setMeta(
|
|
3303
|
+
"plan.path",
|
|
3304
|
+
sessionScopedPath2(sessionsDir, resumed.writer.id, ".plan.json")
|
|
3305
|
+
);
|
|
3306
|
+
agent.ctx.state.setMeta(
|
|
3307
|
+
"task.path",
|
|
3308
|
+
sessionScopedPath2(sessionsDir, resumed.writer.id, ".tasks.json")
|
|
3309
|
+
);
|
|
3310
|
+
agent.ctx.state.replaceTodos(restoredTodos ?? []);
|
|
3311
|
+
try {
|
|
3312
|
+
state.detachActiveTodosCheckpoint = attachTodosCheckpoint(
|
|
3313
|
+
agent.ctx.state,
|
|
3314
|
+
resumedTodosPath,
|
|
3315
|
+
resumed.writer.id,
|
|
3316
|
+
events,
|
|
3317
|
+
agent.ctx.traceId
|
|
3318
|
+
);
|
|
3319
|
+
} catch {
|
|
3320
|
+
state.detachActiveTodosCheckpoint = void 0;
|
|
2919
3321
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
warn("todo/plan sidecars were not re-pointed", err);
|
|
3324
|
+
try {
|
|
3325
|
+
state.detachActiveTodosCheckpoint = previousDetachFn;
|
|
3326
|
+
agent.ctx.state.replaceTodos([...oldTodos]);
|
|
3327
|
+
if (oldPlanPath !== void 0) agent.ctx.state.setMeta("plan.path", oldPlanPath);
|
|
3328
|
+
if (oldTaskPath !== void 0) agent.ctx.state.setMeta("task.path", oldTaskPath);
|
|
3329
|
+
} catch {
|
|
2926
3330
|
}
|
|
2927
3331
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3332
|
+
setStage("restore_model");
|
|
3333
|
+
const currentProviderId = agent.ctx.provider.id;
|
|
3334
|
+
const savedProvider = typeof meta.provider === "string" ? meta.provider.trim() : "";
|
|
3335
|
+
const savedModel = typeof meta.model === "string" ? meta.model.trim() : "";
|
|
3336
|
+
const hasSavedRoute = savedProvider.length > 0 && savedProvider.toLowerCase() !== "unknown" && savedModel.length > 0 && savedModel.toLowerCase() !== "unknown";
|
|
3337
|
+
const targetProviderId = hasSavedRoute ? savedProvider : currentProviderId;
|
|
3338
|
+
const targetModel = hasSavedRoute ? savedModel : agent.ctx.model;
|
|
3339
|
+
if (switchProviderAndModel && targetProviderId && (targetProviderId !== currentProviderId || targetModel !== agent.ctx.model)) {
|
|
2931
3340
|
try {
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
{
|
|
2935
|
-
id: subagentId,
|
|
2936
|
-
name,
|
|
2937
|
-
role: "kanban-agent",
|
|
2938
|
-
prompt: agentDescription,
|
|
2939
|
-
allowedCapabilities: spawnOpts?.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES,
|
|
2940
|
-
...resolvedProvider ? { provider: resolvedProvider } : {},
|
|
2941
|
-
...resolvedModel ? { model: resolvedModel } : {},
|
|
2942
|
-
...resolvedFallbackModels ? { fallbackModels: resolvedFallbackModels } : {},
|
|
2943
|
-
...spawnOpts?.tools ? { tools: spawnOpts.tools } : {}
|
|
2944
|
-
},
|
|
2945
|
-
...spawnOpts?.context ? [{ id: taskId, description: agentDescription, context: spawnOpts.context }] : []
|
|
2946
|
-
);
|
|
2947
|
-
if (spawnOpts?.signal?.aborted) throw new Error("Kanban dispatch cancelled.");
|
|
2948
|
-
const result = await built.agent.run(
|
|
2949
|
-
agentDescription,
|
|
2950
|
-
...spawnOpts?.signal ? [{ signal: spawnOpts.signal }] : []
|
|
2951
|
-
);
|
|
2952
|
-
completionReported = true;
|
|
2953
|
-
await spawnOpts?.onDone?.({
|
|
2954
|
-
status: result.status === "done" ? "completed" : "failed",
|
|
2955
|
-
result: result.finalText,
|
|
2956
|
-
..."error" in result && result.error?.message ? { error: result.error.message } : {}
|
|
2957
|
-
});
|
|
3341
|
+
const error = await Promise.resolve(switchProviderAndModel(targetProviderId, targetModel));
|
|
3342
|
+
if (typeof error === "string" && error)
|
|
3343
|
+
warn(`could not restore ${targetProviderId}/${targetModel}`, error);
|
|
2958
3344
|
} catch (err) {
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
3345
|
+
console.error(
|
|
3346
|
+
JSON.stringify({
|
|
3347
|
+
level: "error",
|
|
3348
|
+
event: "execution.resume_model_restore_failed",
|
|
3349
|
+
provider: targetProviderId,
|
|
3350
|
+
model: targetModel,
|
|
3351
|
+
message: toErrorMessage(err),
|
|
3352
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3353
|
+
})
|
|
3354
|
+
);
|
|
3355
|
+
warn(`could not restore ${targetProviderId}/${targetModel}`, err);
|
|
3356
|
+
}
|
|
3357
|
+
} else if (targetModel !== agent.ctx.model) {
|
|
3358
|
+
agent.ctx.model = targetModel;
|
|
3359
|
+
}
|
|
3360
|
+
setStage("finalize_previous_session");
|
|
3361
|
+
let finalizeOldWriter;
|
|
3362
|
+
if (oldWriter && oldWriter !== resumed.writer) {
|
|
3363
|
+
const staleWriter = oldWriter;
|
|
3364
|
+
const endedUsage = tokenCounter.total();
|
|
3365
|
+
finalizeOldWriter = async () => {
|
|
3366
|
+
let appendOk = false;
|
|
3367
|
+
try {
|
|
3368
|
+
await staleWriter.append({
|
|
3369
|
+
type: "session_end",
|
|
3370
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3371
|
+
usage: endedUsage
|
|
2963
3372
|
});
|
|
3373
|
+
appendOk = true;
|
|
3374
|
+
} catch (err) {
|
|
3375
|
+
console.error(
|
|
3376
|
+
JSON.stringify({
|
|
3377
|
+
level: "error",
|
|
3378
|
+
event: "execution.session_end_append_failed",
|
|
3379
|
+
message: toErrorMessage(err),
|
|
3380
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3381
|
+
})
|
|
3382
|
+
);
|
|
2964
3383
|
}
|
|
2965
|
-
|
|
2966
|
-
} finally {
|
|
2967
|
-
await built?.dispose?.();
|
|
2968
|
-
}
|
|
2969
|
-
})().catch((err) => {
|
|
2970
|
-
events.emit("error", {
|
|
2971
|
-
err: err instanceof Error ? err : new Error(String(err)),
|
|
2972
|
-
phase: "kanban.dispatch"
|
|
2973
|
-
});
|
|
2974
|
-
});
|
|
2975
|
-
const tags = [];
|
|
2976
|
-
if (resolvedProvider) tags.push(resolvedProvider);
|
|
2977
|
-
if (resolvedModel) tags.push(resolvedModel);
|
|
2978
|
-
if (resolvedFallbackModels?.length) {
|
|
2979
|
-
tags.push(`fallback=${resolvedFallbackModels.join(",")}`);
|
|
2980
|
-
}
|
|
2981
|
-
if (spawnOpts?.fallbackProfile) tags.push(`profile=${spawnOpts.fallbackProfile}`);
|
|
2982
|
-
if (spawnOpts?.skills?.length) tags.push(`skills=${spawnOpts.skills.join(",")}`);
|
|
2983
|
-
if (spawnOpts?.name) tags.push(`"${spawnOpts.name}"`);
|
|
2984
|
-
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
2985
|
-
return `Spawned subagent ${subagentId}${tag} for task ${taskId}.`;
|
|
2986
|
-
};
|
|
2987
|
-
return { onKanbanDispatch };
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
// src/execution-repl-fleet-callbacks.ts
|
|
2991
|
-
function createReplFleetCallbacks({
|
|
2992
|
-
director,
|
|
2993
|
-
getDirector
|
|
2994
|
-
}) {
|
|
2995
|
-
const resolveDirector = () => getDirector?.() ?? director;
|
|
2996
|
-
return {
|
|
2997
|
-
onInterruptFleet: () => {
|
|
2998
|
-
const dir = resolveDirector();
|
|
2999
|
-
if (!dir) return 0;
|
|
3000
|
-
let killed = 0;
|
|
3001
|
-
for (const sa of dir.status().subagents) {
|
|
3002
|
-
if (sa.status === "running" || sa.status === "idle") {
|
|
3384
|
+
if (appendOk) {
|
|
3003
3385
|
try {
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3386
|
+
await staleWriter.close();
|
|
3387
|
+
} catch (err) {
|
|
3388
|
+
console.error(
|
|
3389
|
+
JSON.stringify({
|
|
3390
|
+
level: "error",
|
|
3391
|
+
event: "execution.session_close_failed",
|
|
3392
|
+
message: toErrorMessage(err),
|
|
3393
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3394
|
+
})
|
|
3395
|
+
);
|
|
3007
3396
|
}
|
|
3008
3397
|
}
|
|
3009
|
-
}
|
|
3010
|
-
return killed;
|
|
3011
|
-
},
|
|
3012
|
-
onAgentIterationComplete: (tokens) => {
|
|
3013
|
-
const dir = resolveDirector();
|
|
3014
|
-
dir?.setLeaderContextPressure(tokens);
|
|
3015
|
-
}
|
|
3016
|
-
};
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
// src/fleet-statusline.ts
|
|
3020
|
-
import { color as color5, onResize } from "@wrongstack/core/utils";
|
|
3021
|
-
function fmtElapsed(ms) {
|
|
3022
|
-
const s = Math.floor(ms / 1e3);
|
|
3023
|
-
if (s < 60) return `${s}s`;
|
|
3024
|
-
const m = Math.floor(s / 60);
|
|
3025
|
-
const rem = s % 60;
|
|
3026
|
-
if (m < 60) return `${m}m${rem.toString().padStart(2, "0")}s`;
|
|
3027
|
-
const h = Math.floor(m / 60);
|
|
3028
|
-
return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
|
|
3029
|
-
}
|
|
3030
|
-
function renderFleetLine(states, now, columns, version) {
|
|
3031
|
-
const all = [...states.values()];
|
|
3032
|
-
if (all.length === 0) return "";
|
|
3033
|
-
const running = all.filter((a) => a.status === "running");
|
|
3034
|
-
const done = all.filter((a) => a.status === "done").length;
|
|
3035
|
-
const failed = all.filter((a) => a.status === "failed").length;
|
|
3036
|
-
const versionChip = version ? `${color5.bold("WS")}${color5.dim(` v${version}`)} ${color5.dim("\u2502")} ` : "";
|
|
3037
|
-
const counts = versionChip + `${color5.cyan("\u27F3 fleet")} ${color5.yellow(`\u25B6${running.length}`)} ${color5.green(`\u2713${done}`)}` + (failed > 0 ? ` ${color5.red(`\u2717${failed}`)}` : "");
|
|
3038
|
-
const shown = running.sort((a, b) => a.startedAt - b.startedAt).slice(0, 4).map((a) => {
|
|
3039
|
-
const elapsed = fmtElapsed(Math.max(0, now - a.startedAt));
|
|
3040
|
-
const tool = a.lastTool ? ` ${color5.dim(a.lastTool)}` : "";
|
|
3041
|
-
const ext = a.extensions && a.extensions > 0 ? ` ${color5.yellow(`\u26A1${a.extensions}`)}` : "";
|
|
3042
|
-
return `${color5.bold(a.name)} ${color5.yellow("\u25B6")} ${color5.dim(elapsed)} ${color5.dim(`L${a.iterations}`)} ${color5.dim(`${a.toolCalls}t`)}${tool}${ext}`;
|
|
3043
|
-
});
|
|
3044
|
-
let line = shown.length > 0 ? `${counts} ${color5.dim("\u2502")} ${shown.join(color5.dim(" \xB7 "))}` : counts;
|
|
3045
|
-
const max = Math.max(20, columns - 1);
|
|
3046
|
-
const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3047
|
-
if (visible.length > max) {
|
|
3048
|
-
let count = 0;
|
|
3049
|
-
const out = [];
|
|
3050
|
-
let i = 0;
|
|
3051
|
-
while (i < line.length && count < max - 1) {
|
|
3052
|
-
if (line[i] === "\x1B") {
|
|
3053
|
-
const end = line.indexOf("m", i);
|
|
3054
|
-
if (end !== -1) {
|
|
3055
|
-
out.push(line.slice(i, end + 1));
|
|
3056
|
-
i = end + 1;
|
|
3057
|
-
continue;
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
if (line[i] !== void 0) out.push(line[i]);
|
|
3061
|
-
count++;
|
|
3062
|
-
i++;
|
|
3398
|
+
};
|
|
3063
3399
|
}
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
out;
|
|
3071
|
-
throttleMs;
|
|
3072
|
-
now;
|
|
3073
|
-
version;
|
|
3074
|
-
states = /* @__PURE__ */ new Map();
|
|
3075
|
-
unsubs = [];
|
|
3076
|
-
active = false;
|
|
3077
|
-
rows = 0;
|
|
3078
|
-
repaintTimer = null;
|
|
3079
|
-
tickTimer = null;
|
|
3080
|
-
deactivateTimer = null;
|
|
3081
|
-
resizeCleanup = null;
|
|
3082
|
-
lastPaint = 0;
|
|
3083
|
-
onResize = () => {
|
|
3084
|
-
if (this.active) {
|
|
3085
|
-
this.rows = this.out.rows ?? 24;
|
|
3086
|
-
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
3087
|
-
this.paint(true);
|
|
3400
|
+
setStage("token_accounting");
|
|
3401
|
+
try {
|
|
3402
|
+
tokenCounter.reset();
|
|
3403
|
+
tokenCounter.account(resumed.data.usage, targetModel, targetProviderId);
|
|
3404
|
+
} catch (err) {
|
|
3405
|
+
warn("token accounting for the resumed session failed", err);
|
|
3088
3406
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
this.schedulePaint();
|
|
3121
|
-
}),
|
|
3122
|
-
this.events.on("subagent.task_started", (e) => {
|
|
3123
|
-
const s = ensure(e.subagentId);
|
|
3124
|
-
s.status = "running";
|
|
3125
|
-
this.activate();
|
|
3126
|
-
this.schedulePaint();
|
|
3127
|
-
}),
|
|
3128
|
-
this.events.on("subagent.tool_executed", (e) => {
|
|
3129
|
-
const s = ensure(e.subagentId);
|
|
3130
|
-
s.toolCalls++;
|
|
3131
|
-
s.lastTool = e.name;
|
|
3132
|
-
this.activate();
|
|
3133
|
-
this.schedulePaint();
|
|
3134
|
-
}),
|
|
3135
|
-
this.events.on("subagent.iteration_summary", (e) => {
|
|
3136
|
-
const s = ensure(e.subagentId);
|
|
3137
|
-
s.iterations = e.iteration;
|
|
3138
|
-
if (typeof e.toolCalls === "number") s.toolCalls = e.toolCalls;
|
|
3139
|
-
if (e.currentTool) s.lastTool = e.currentTool;
|
|
3140
|
-
this.schedulePaint();
|
|
3141
|
-
}),
|
|
3142
|
-
this.events.on("subagent.budget_extended", (e) => {
|
|
3143
|
-
const s = ensure(e.subagentId);
|
|
3144
|
-
s.extensions = e.totalExtensions;
|
|
3145
|
-
this.activate();
|
|
3146
|
-
this.schedulePaint();
|
|
3147
|
-
}),
|
|
3148
|
-
this.events.on("subagent.task_completed", (e) => {
|
|
3149
|
-
const s = ensure(e.subagentId);
|
|
3150
|
-
s.status = e.status === "success" ? "done" : "failed";
|
|
3151
|
-
s.iterations = e.iterations;
|
|
3152
|
-
s.toolCalls = e.toolCalls;
|
|
3153
|
-
s.endedAt = this.now();
|
|
3154
|
-
this.schedulePaint();
|
|
3155
|
-
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
3156
|
-
if (this.deactivateTimer) clearTimeout(this.deactivateTimer);
|
|
3157
|
-
this.deactivateTimer = setTimeout(() => {
|
|
3158
|
-
this.deactivateTimer = null;
|
|
3159
|
-
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
3160
|
-
this.deactivate();
|
|
3161
|
-
}
|
|
3162
|
-
}, 800);
|
|
3163
|
-
}
|
|
3164
|
-
}),
|
|
3165
|
-
this.events.on("subagent.removed", (e) => {
|
|
3166
|
-
this.states.delete(e.subagentId);
|
|
3167
|
-
this.schedulePaint();
|
|
3168
|
-
})
|
|
3169
|
-
);
|
|
3170
|
-
}
|
|
3171
|
-
/** Unsubscribe and restore the terminal. Idempotent. */
|
|
3172
|
-
stop() {
|
|
3173
|
-
for (const u of this.unsubs.splice(0)) u();
|
|
3174
|
-
this.deactivate();
|
|
3175
|
-
}
|
|
3176
|
-
isTty() {
|
|
3177
|
-
return !!this.out.isTTY;
|
|
3178
|
-
}
|
|
3179
|
-
activate() {
|
|
3180
|
-
if (this.active || !this.isTty()) return;
|
|
3181
|
-
this.active = true;
|
|
3182
|
-
this.rows = this.out.rows ?? 24;
|
|
3183
|
-
this.out.write("\n");
|
|
3184
|
-
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
3185
|
-
this.out.write(`\x1B[${this.rows - 1};1H`);
|
|
3186
|
-
this.resizeCleanup = onResize(() => this.onResize(), this.out);
|
|
3187
|
-
this.tickTimer = setInterval(() => this.paint(true), 1e3);
|
|
3188
|
-
if (this.tickTimer.unref) this.tickTimer.unref();
|
|
3189
|
-
this.paint(true);
|
|
3190
|
-
}
|
|
3191
|
-
deactivate() {
|
|
3192
|
-
if (!this.active) return;
|
|
3193
|
-
this.active = false;
|
|
3194
|
-
if (this.tickTimer) {
|
|
3195
|
-
clearInterval(this.tickTimer);
|
|
3196
|
-
this.tickTimer = null;
|
|
3407
|
+
const resumedRequestTokens = projectLastRequestTokens(resumed.data.events);
|
|
3408
|
+
agent.ctx.lastRequestTokens = resumedRequestTokens;
|
|
3409
|
+
tokenCounter.setCurrentRequestTokens?.(resumedRequestTokens ?? 0);
|
|
3410
|
+
const tokens = resumedRequestTokens ?? 0;
|
|
3411
|
+
const maxContext = agent.ctx.provider?.capabilities?.maxContext ?? 0;
|
|
3412
|
+
const finalText = lastAssistantTextOf(resumed.data.messages);
|
|
3413
|
+
const result = {
|
|
3414
|
+
entries,
|
|
3415
|
+
nextId: entries.length + 1,
|
|
3416
|
+
sessionId: resumed.writer.id,
|
|
3417
|
+
contextSnapshot: { tokens, maxContext },
|
|
3418
|
+
attached: true,
|
|
3419
|
+
warnings,
|
|
3420
|
+
...finalText !== void 0 ? { lastAssistantText: finalText } : {}
|
|
3421
|
+
};
|
|
3422
|
+
if (finalizeOldWriter) void finalizeOldWriter();
|
|
3423
|
+
return result;
|
|
3424
|
+
} catch (err) {
|
|
3425
|
+
if (!identityClaimed) await resumeClaim?.cancel().catch(() => void 0);
|
|
3426
|
+
if (identityClaimed && previousSessionId) {
|
|
3427
|
+
await state.activateSessionIdentity?.(previousSessionId).catch((rollbackErr) => {
|
|
3428
|
+
console.error(
|
|
3429
|
+
JSON.stringify({
|
|
3430
|
+
level: "error",
|
|
3431
|
+
event: "execution.resume_identity_rollback_failed",
|
|
3432
|
+
sessionId: previousSessionId,
|
|
3433
|
+
message: toErrorMessage(rollbackErr),
|
|
3434
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3435
|
+
})
|
|
3436
|
+
);
|
|
3437
|
+
});
|
|
3197
3438
|
}
|
|
3198
|
-
if (
|
|
3199
|
-
|
|
3200
|
-
|
|
3439
|
+
if (!writerSwapped) {
|
|
3440
|
+
await openedWriter?.close().catch(() => void 0);
|
|
3441
|
+
} else {
|
|
3442
|
+
try {
|
|
3443
|
+
if (oldWriter !== void 0) agent.ctx.session = oldWriter;
|
|
3444
|
+
if (state.sessionRef) state.sessionRef.current = oldSessionRefCurrent;
|
|
3445
|
+
if (oldMessages !== void 0) agent.ctx.state.replaceMessages(oldMessages);
|
|
3446
|
+
if (previousDetachFn !== void 0) state.detachActiveTodosCheckpoint = previousDetachFn;
|
|
3447
|
+
agent.ctx.state.replaceTodos([...oldTodos]);
|
|
3448
|
+
if (oldPlanPath !== void 0) agent.ctx.state.setMeta("plan.path", oldPlanPath);
|
|
3449
|
+
if (oldTaskPath !== void 0) agent.ctx.state.setMeta("task.path", oldTaskPath);
|
|
3450
|
+
} catch (rollbackErr) {
|
|
3451
|
+
console.error(
|
|
3452
|
+
JSON.stringify({
|
|
3453
|
+
level: "error",
|
|
3454
|
+
event: "execution.resume_post_swap_rollback_failed",
|
|
3455
|
+
message: toErrorMessage(rollbackErr),
|
|
3456
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3457
|
+
})
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
if (openedWriter && openedWriter !== oldWriter) {
|
|
3461
|
+
await openedWriter.close().catch((closeErr) => {
|
|
3462
|
+
console.error(
|
|
3463
|
+
JSON.stringify({
|
|
3464
|
+
level: "error",
|
|
3465
|
+
event: "execution.resume_opened_writer_close_failed",
|
|
3466
|
+
message: toErrorMessage(closeErr),
|
|
3467
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3468
|
+
})
|
|
3469
|
+
);
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3201
3472
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3473
|
+
const reason = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
3474
|
+
console.error(
|
|
3475
|
+
JSON.stringify({
|
|
3476
|
+
level: "error",
|
|
3477
|
+
event: "execution.resume_session_failed",
|
|
3478
|
+
sessionId: canonicalSessionId,
|
|
3479
|
+
stage,
|
|
3480
|
+
message: reason,
|
|
3481
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3482
|
+
})
|
|
3483
|
+
);
|
|
3484
|
+
const message = toErrorMessage(err);
|
|
3485
|
+
if (err instanceof Error && err.name === "SessionOwnershipConflictError") {
|
|
3486
|
+
return fail(stage, message);
|
|
3205
3487
|
}
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
this.out.write("\x1B[r");
|
|
3488
|
+
let data = hydrated;
|
|
3489
|
+
if (!data) {
|
|
3490
|
+
try {
|
|
3491
|
+
data = await state.activeSessionStore.load(canonicalSessionId, onLoadProgress);
|
|
3492
|
+
} catch {
|
|
3493
|
+
data = void 0;
|
|
3494
|
+
}
|
|
3214
3495
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3496
|
+
if (data) {
|
|
3497
|
+
try {
|
|
3498
|
+
const entries = replaySessionMessages(
|
|
3499
|
+
data.messages,
|
|
3500
|
+
data.events,
|
|
3501
|
+
/* startId */
|
|
3502
|
+
1
|
|
3503
|
+
);
|
|
3504
|
+
const finalText = lastAssistantTextOf(data.messages);
|
|
3505
|
+
fail(stage, message);
|
|
3506
|
+
return {
|
|
3507
|
+
entries,
|
|
3508
|
+
nextId: entries.length + 1,
|
|
3509
|
+
sessionId: canonicalSessionId,
|
|
3510
|
+
// Deliberately no contextSnapshot: the chip reports the LIVE
|
|
3511
|
+
// window, and the live session is still the one we rolled back to.
|
|
3512
|
+
// Painting the viewed session's fill over it would be a lie.
|
|
3513
|
+
attached: false,
|
|
3514
|
+
warnings: [...warnings, `not attached (at ${stage}): ${message}`],
|
|
3515
|
+
...finalText !== void 0 ? { lastAssistantText: finalText } : {}
|
|
3516
|
+
};
|
|
3517
|
+
} catch (renderErr) {
|
|
3518
|
+
console.error(
|
|
3519
|
+
JSON.stringify({
|
|
3520
|
+
level: "error",
|
|
3521
|
+
event: "execution.resume_readonly_render_failed",
|
|
3522
|
+
sessionId: canonicalSessionId,
|
|
3523
|
+
message: toErrorMessage(renderErr),
|
|
3524
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3525
|
+
})
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3222
3528
|
}
|
|
3223
|
-
|
|
3224
|
-
this.repaintTimer = setTimeout(() => {
|
|
3225
|
-
this.repaintTimer = null;
|
|
3226
|
-
this.paint(false);
|
|
3227
|
-
}, this.throttleMs - since);
|
|
3228
|
-
if (this.repaintTimer.unref) this.repaintTimer.unref();
|
|
3229
|
-
}
|
|
3230
|
-
paint(force) {
|
|
3231
|
-
if (!this.active || !this.isTty()) return;
|
|
3232
|
-
if (!force) this.lastPaint = this.now();
|
|
3233
|
-
else this.lastPaint = this.now();
|
|
3234
|
-
const line = renderFleetLine(this.states, this.now(), this.out.columns ?? 80, this.version);
|
|
3235
|
-
this.out.write("\x1B7");
|
|
3236
|
-
this.out.write(`\x1B[${this.rows};1H`);
|
|
3237
|
-
this.out.write("\x1B[2K");
|
|
3238
|
-
this.out.write(line);
|
|
3239
|
-
this.out.write("\x1B8");
|
|
3529
|
+
return fail(stage, message);
|
|
3240
3530
|
}
|
|
3241
|
-
};
|
|
3242
|
-
|
|
3243
|
-
// src/subagent-models/panel-service.ts
|
|
3244
|
-
import {
|
|
3245
|
-
emptySubagentModelPlan,
|
|
3246
|
-
formatSubagentSlot,
|
|
3247
|
-
getSessionSubagentModelPlan,
|
|
3248
|
-
setSessionSubagentModelPlan,
|
|
3249
|
-
subagentSlotOccupancy
|
|
3250
|
-
} from "@wrongstack/core/coordination";
|
|
3251
|
-
function currentPlan(sessionId) {
|
|
3252
|
-
return getSessionSubagentModelPlan(sessionId) ?? emptySubagentModelPlan();
|
|
3253
3531
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
})
|
|
3283
|
-
|
|
3284
|
-
};
|
|
3285
|
-
},
|
|
3286
|
-
setLane(index, target) {
|
|
3287
|
-
return write((plan) => {
|
|
3288
|
-
if (index < 0 || index >= plan.slots.length) return;
|
|
3289
|
-
const slot = {};
|
|
3290
|
-
if (target.provider) slot.provider = target.provider;
|
|
3291
|
-
if (target.model) slot.model = target.model;
|
|
3292
|
-
if (target.tier) slot.tier = target.tier;
|
|
3293
|
-
if (target.fallbackProfile) slot.fallbackProfile = target.fallbackProfile;
|
|
3294
|
-
if (target.label) slot.label = target.label;
|
|
3295
|
-
plan.slots[index] = slot;
|
|
3296
|
-
});
|
|
3297
|
-
},
|
|
3298
|
-
clearLane(index) {
|
|
3299
|
-
return write((plan) => {
|
|
3300
|
-
if (index < 0 || index >= plan.slots.length) return;
|
|
3301
|
-
plan.slots[index] = {};
|
|
3302
|
-
});
|
|
3303
|
-
},
|
|
3304
|
-
toggle(field) {
|
|
3305
|
-
return write((plan) => {
|
|
3306
|
-
plan[field] = plan[field] !== true;
|
|
3307
|
-
});
|
|
3532
|
+
|
|
3533
|
+
// src/tui-resume-callback.ts
|
|
3534
|
+
function createTuiResumeCallback({
|
|
3535
|
+
state,
|
|
3536
|
+
agent,
|
|
3537
|
+
tokenCounter,
|
|
3538
|
+
switchProviderAndModel,
|
|
3539
|
+
events
|
|
3540
|
+
}) {
|
|
3541
|
+
return async (sessionId, onLoadProgress, onStage) => {
|
|
3542
|
+
let failure;
|
|
3543
|
+
const result = await resumeSession(
|
|
3544
|
+
{
|
|
3545
|
+
state,
|
|
3546
|
+
agent,
|
|
3547
|
+
tokenCounter,
|
|
3548
|
+
switchProviderAndModel,
|
|
3549
|
+
events,
|
|
3550
|
+
onLoadProgress,
|
|
3551
|
+
onStage,
|
|
3552
|
+
onFailure: (info) => {
|
|
3553
|
+
failure = info;
|
|
3554
|
+
}
|
|
3555
|
+
},
|
|
3556
|
+
sessionId
|
|
3557
|
+
);
|
|
3558
|
+
if (!result) {
|
|
3559
|
+
throw new Error(
|
|
3560
|
+
failure ? `${failure.message} (at ${failure.stage})` : `Session "${sessionId}" could not be resumed.`
|
|
3561
|
+
);
|
|
3308
3562
|
}
|
|
3563
|
+
setAutoSuggestions([]);
|
|
3564
|
+
const resumedNextSteps = result.attached && !hasOpenTodos(agent.ctx.todos) && result.lastAssistantText ? parseNextSteps(result.lastAssistantText).texts : [];
|
|
3565
|
+
return { ...result, nextSteps: resumedNextSteps };
|
|
3309
3566
|
};
|
|
3310
3567
|
}
|
|
3311
3568
|
|
|
@@ -3348,7 +3605,7 @@ function createChimeraWorkRegistry() {
|
|
|
3348
3605
|
|
|
3349
3606
|
// src/execution-chimera-cascade.ts
|
|
3350
3607
|
import * as fsp from "node:fs/promises";
|
|
3351
|
-
import * as
|
|
3608
|
+
import * as path11 from "node:path";
|
|
3352
3609
|
import { emitReviewIfChanged } from "@wrongstack/core/plugin";
|
|
3353
3610
|
|
|
3354
3611
|
// src/chimera-cascade-evidence.ts
|
|
@@ -3404,14 +3661,9 @@ function parseSingleCheck(raw) {
|
|
|
3404
3661
|
if (typeof exitCode !== "number" || !Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) {
|
|
3405
3662
|
return void 0;
|
|
3406
3663
|
}
|
|
3407
|
-
const output = typeof check.output === "string" ?
|
|
3664
|
+
const output = typeof check.output === "string" ? check.output : void 0;
|
|
3408
3665
|
return { command: command.trim(), exitCode, output };
|
|
3409
3666
|
}
|
|
3410
|
-
var MAX_EVIDENCE_OUTPUT_CHARS = 2e3;
|
|
3411
|
-
function truncateOutput(output) {
|
|
3412
|
-
return output.length > MAX_EVIDENCE_OUTPUT_CHARS ? `${output.slice(0, MAX_EVIDENCE_OUTPUT_CHARS)}
|
|
3413
|
-
\u2026 (truncated)` : output;
|
|
3414
|
-
}
|
|
3415
3667
|
var SAFE_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
3416
3668
|
"pnpm",
|
|
3417
3669
|
"npm",
|
|
@@ -3442,7 +3694,7 @@ var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
|
|
|
3442
3694
|
const tokens = command.trim().split(/\s+/);
|
|
3443
3695
|
const executable = tokens[0];
|
|
3444
3696
|
const args = tokens.slice(1);
|
|
3445
|
-
return new Promise((
|
|
3697
|
+
return new Promise((resolve5) => {
|
|
3446
3698
|
let child;
|
|
3447
3699
|
try {
|
|
3448
3700
|
child = spawn2(executable, args, {
|
|
@@ -3452,11 +3704,11 @@ var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
|
|
|
3452
3704
|
signal: AbortSignal.timeout(timeoutMs)
|
|
3453
3705
|
});
|
|
3454
3706
|
} catch {
|
|
3455
|
-
|
|
3707
|
+
resolve5({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
|
|
3456
3708
|
return;
|
|
3457
3709
|
}
|
|
3458
|
-
child.on("error", () =>
|
|
3459
|
-
child.on("close", (code) =>
|
|
3710
|
+
child.on("error", () => resolve5({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
|
|
3711
|
+
child.on("close", (code) => resolve5({ exitCode: code ?? CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
|
|
3460
3712
|
});
|
|
3461
3713
|
};
|
|
3462
3714
|
async function verifyCascadeEvidence(evidence, cwd, runCommand = runCascadeVerificationCommand, timeoutMs = CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS) {
|
|
@@ -3974,7 +4226,7 @@ async function maybeReReviewCascade({
|
|
|
3974
4226
|
const reReadFiles = [];
|
|
3975
4227
|
for (const f of evidenceBundle.files) {
|
|
3976
4228
|
try {
|
|
3977
|
-
const absPath =
|
|
4229
|
+
const absPath = path11.join(evidenceBundle.cwd, f.path);
|
|
3978
4230
|
const content = await fsp.readFile(absPath, "utf8");
|
|
3979
4231
|
reReadFiles.push({ path: f.path, status: "modified", content });
|
|
3980
4232
|
} catch {
|
|
@@ -4051,7 +4303,7 @@ function errorText2(error) {
|
|
|
4051
4303
|
|
|
4052
4304
|
// src/execution-chimera-review.ts
|
|
4053
4305
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4054
|
-
import
|
|
4306
|
+
import path12 from "node:path";
|
|
4055
4307
|
import { effectiveFallbackChain, fallbackProfileChain as fallbackProfileChain2 } from "@wrongstack/core/agent";
|
|
4056
4308
|
import {
|
|
4057
4309
|
areSubagentsAllowedForSession
|
|
@@ -4069,7 +4321,7 @@ import {
|
|
|
4069
4321
|
function normalizeFileKeyForCitation(raw, cwd) {
|
|
4070
4322
|
const forward = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4071
4323
|
const isAbsolute = forward.startsWith("/") || /^[a-zA-Z]:\//.test(forward);
|
|
4072
|
-
const pathMod = process.platform === "win32" ?
|
|
4324
|
+
const pathMod = process.platform === "win32" ? path12.win32 : path12;
|
|
4073
4325
|
const relative = isAbsolute ? pathMod.relative(cwd, forward).replace(/\\/g, "/").replace(/^\.\//, "") : forward;
|
|
4074
4326
|
return process.platform === "win32" ? relative.toLowerCase() : relative;
|
|
4075
4327
|
}
|
|
@@ -4296,10 +4548,10 @@ function installChimeraReviewHandler({
|
|
|
4296
4548
|
const changedBasenameIndex = /* @__PURE__ */ new Map();
|
|
4297
4549
|
for (const file of p.files) {
|
|
4298
4550
|
const normalized = normalizeFileKeyForCitation(file.path, p.cwd);
|
|
4299
|
-
const
|
|
4300
|
-
const bucket = changedBasenameIndex.get(
|
|
4551
|
+
const basename8 = path12.basename(file.path).toLowerCase();
|
|
4552
|
+
const bucket = changedBasenameIndex.get(basename8);
|
|
4301
4553
|
if (bucket) bucket.push(normalized);
|
|
4302
|
-
else changedBasenameIndex.set(
|
|
4554
|
+
else changedBasenameIndex.set(basename8, [normalized]);
|
|
4303
4555
|
}
|
|
4304
4556
|
const inScopeCited = citedFindings.filter((finding) => {
|
|
4305
4557
|
const citedKey = normalizeFileKeyForCitation(finding.location.file, p.cwd);
|
|
@@ -4308,8 +4560,8 @@ function installChimeraReviewHandler({
|
|
|
4308
4560
|
(cp) => cp.endsWith(`/${citedKey}`) || cp === citedKey
|
|
4309
4561
|
);
|
|
4310
4562
|
if (suffixMatches.length === 1) return true;
|
|
4311
|
-
const
|
|
4312
|
-
const bucket = changedBasenameIndex.get(
|
|
4563
|
+
const basename8 = path12.basename(finding.location.file).toLowerCase();
|
|
4564
|
+
const bucket = changedBasenameIndex.get(basename8);
|
|
4313
4565
|
return !!(bucket && bucket.length === 1);
|
|
4314
4566
|
});
|
|
4315
4567
|
const droppedCitations = citedFindings.filter(
|
|
@@ -4609,21 +4861,13 @@ function installStorageObservability(events, rootTraceId) {
|
|
|
4609
4861
|
// src/next-task-predictor.ts
|
|
4610
4862
|
import { readBundledInstructionText } from "@wrongstack/core/utils";
|
|
4611
4863
|
var SYSTEM_PROMPT = readBundledInstructionText("cli/next-task-predictor.md");
|
|
4612
|
-
var MAX_REQUEST_CHARS = 1200;
|
|
4613
|
-
var MAX_SUMMARY_CHARS = 1200;
|
|
4614
|
-
function clamp(text, n) {
|
|
4615
|
-
const t = text.trim();
|
|
4616
|
-
return t.length <= n ? t : `${t.slice(0, n)}\u2026`;
|
|
4617
|
-
}
|
|
4618
4864
|
function buildPredictionPrompt(input) {
|
|
4619
4865
|
const parts = [];
|
|
4620
4866
|
parts.push(`The user asked:
|
|
4621
|
-
${
|
|
4867
|
+
${input.userRequest.trim() || "(no text)"}`);
|
|
4622
4868
|
if (input.assistantSummary.trim()) {
|
|
4623
|
-
parts.push(
|
|
4624
|
-
|
|
4625
|
-
${clamp(input.assistantSummary, MAX_SUMMARY_CHARS)}`
|
|
4626
|
-
);
|
|
4869
|
+
parts.push(`The assistant just finished and reported:
|
|
4870
|
+
${input.assistantSummary.trim()}`);
|
|
4627
4871
|
}
|
|
4628
4872
|
const pending = input.todos.filter((t) => t.status !== "completed");
|
|
4629
4873
|
if (pending.length > 0) {
|
|
@@ -4678,7 +4922,6 @@ async function predictNextTasks(input, opts) {
|
|
|
4678
4922
|
messages: [
|
|
4679
4923
|
{ role: "user", content: [{ type: "text", text: buildPredictionPrompt(input) }] }
|
|
4680
4924
|
],
|
|
4681
|
-
maxTokens: 160,
|
|
4682
4925
|
temperature: 0.3
|
|
4683
4926
|
},
|
|
4684
4927
|
{ signal: internal.signal }
|
|
@@ -4697,7 +4940,7 @@ import { detectContinueIntent, InputBuilder, resolveContinuation } from "@wrongs
|
|
|
4697
4940
|
import {
|
|
4698
4941
|
color as color11,
|
|
4699
4942
|
estimateRequestTokensCalibrated as estimateRequestTokensCalibrated2,
|
|
4700
|
-
hasOpenTodos as
|
|
4943
|
+
hasOpenTodos as hasOpenTodos3,
|
|
4701
4944
|
toErrorMessage as toErrorMessage4
|
|
4702
4945
|
} from "@wrongstack/core/utils";
|
|
4703
4946
|
import { readClipboardImage, routeImagesForModel } from "@wrongstack/runtime";
|
|
@@ -4711,14 +4954,14 @@ import {
|
|
|
4711
4954
|
} from "@wrongstack/tools/auto-proceed-loop-guard";
|
|
4712
4955
|
|
|
4713
4956
|
// src/repl-suggestions.ts
|
|
4714
|
-
import { hasOpenTodos } from "@wrongstack/core/utils";
|
|
4715
|
-
import { parseNextSteps } from "@wrongstack/tools/next-steps";
|
|
4957
|
+
import { hasOpenTodos as hasOpenTodos2 } from "@wrongstack/core/utils";
|
|
4958
|
+
import { parseNextSteps as parseNextSteps2 } from "@wrongstack/tools/next-steps";
|
|
4716
4959
|
function parseSuggestionsFromOutput(finalText, todos) {
|
|
4717
|
-
if (
|
|
4960
|
+
if (hasOpenTodos2(todos)) {
|
|
4718
4961
|
setAutoSuggestions([]);
|
|
4719
4962
|
return null;
|
|
4720
4963
|
}
|
|
4721
|
-
const { texts, autoTexts } =
|
|
4964
|
+
const { texts, autoTexts } = parseNextSteps2(finalText);
|
|
4722
4965
|
if (autoTexts.length > 0) {
|
|
4723
4966
|
setAutoSuggestions(autoTexts);
|
|
4724
4967
|
}
|
|
@@ -4822,12 +5065,12 @@ ${color6.cyan("\u23F3 Auto")} ${color6.dim("(Ctrl+C to cancel)")}
|
|
|
4822
5065
|
let interval;
|
|
4823
5066
|
let lastTickedSecond = sec + 1;
|
|
4824
5067
|
let onAbort;
|
|
4825
|
-
return new Promise((
|
|
4826
|
-
onAbort = () =>
|
|
5068
|
+
return new Promise((resolve5) => {
|
|
5069
|
+
onAbort = () => resolve5(false);
|
|
4827
5070
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4828
5071
|
interval = setInterval(() => {
|
|
4829
5072
|
if (signal.aborted) {
|
|
4830
|
-
|
|
5073
|
+
resolve5(false);
|
|
4831
5074
|
return;
|
|
4832
5075
|
}
|
|
4833
5076
|
const elapsed = Date.now() - start;
|
|
@@ -4835,7 +5078,7 @@ ${color6.cyan("\u23F3 Auto")} ${color6.dim("(Ctrl+C to cancel)")}
|
|
|
4835
5078
|
if (remaining <= 0) {
|
|
4836
5079
|
opts.renderer.write(color6.dim(` \u21B3 ${truncated}
|
|
4837
5080
|
`));
|
|
4838
|
-
|
|
5081
|
+
resolve5(true);
|
|
4839
5082
|
return;
|
|
4840
5083
|
}
|
|
4841
5084
|
if (opts.onCountdownTick && remaining !== lastTickedSecond) {
|
|
@@ -4846,7 +5089,7 @@ ${color6.cyan("\u23F3 Auto")} ${color6.dim("(Ctrl+C to cancel)")}
|
|
|
4846
5089
|
opts.renderer.write(
|
|
4847
5090
|
color6.yellow(" \u21B3 Countdown cancelled \u2014 switching to manual mode\n")
|
|
4848
5091
|
);
|
|
4849
|
-
|
|
5092
|
+
resolve5(false);
|
|
4850
5093
|
return;
|
|
4851
5094
|
}
|
|
4852
5095
|
} catch {
|
|
@@ -4878,7 +5121,7 @@ async function runContextCompaction(opts, _signal) {
|
|
|
4878
5121
|
|
|
4879
5122
|
// src/repl-client-registration.ts
|
|
4880
5123
|
import * as crypto from "node:crypto";
|
|
4881
|
-
import * as
|
|
5124
|
+
import * as path13 from "node:path";
|
|
4882
5125
|
import { getSharedProjectMailbox, resolveProjectDir } from "@wrongstack/core/coordination";
|
|
4883
5126
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
4884
5127
|
function registerReplClient(opts) {
|
|
@@ -4888,7 +5131,7 @@ function registerReplClient(opts) {
|
|
|
4888
5131
|
const hqConnection = startCliHqConnection({
|
|
4889
5132
|
clientKind: "repl",
|
|
4890
5133
|
projectRoot: replProjectRoot,
|
|
4891
|
-
projectName:
|
|
5134
|
+
projectName: path13.basename(replProjectRoot),
|
|
4892
5135
|
appConfig: opts.appConfig,
|
|
4893
5136
|
capabilities: ["telemetry.publish", "mailbox.summary"]
|
|
4894
5137
|
});
|
|
@@ -4902,7 +5145,7 @@ function registerReplClient(opts) {
|
|
|
4902
5145
|
clientMailbox.registerClient({
|
|
4903
5146
|
clientId,
|
|
4904
5147
|
sessionId: opts.getSessionId?.() ?? replProjectRoot,
|
|
4905
|
-
name: `REPL [${
|
|
5148
|
+
name: `REPL [${path13.basename(replProjectRoot)}]`,
|
|
4906
5149
|
source: "repl",
|
|
4907
5150
|
pid: process.pid
|
|
4908
5151
|
}).then(() => {
|
|
@@ -5046,7 +5289,7 @@ async function runReplEternalLoop(opts, onInterruptsReset) {
|
|
|
5046
5289
|
} catch (err) {
|
|
5047
5290
|
opts.renderer.writeError(`[eternal] ${toErrorMessage3(err)}`);
|
|
5048
5291
|
}
|
|
5049
|
-
await new Promise((
|
|
5292
|
+
await new Promise((resolve5) => setTimeout(resolve5, 250));
|
|
5050
5293
|
return true;
|
|
5051
5294
|
}
|
|
5052
5295
|
if (autonomy === "eternal-parallel") {
|
|
@@ -5117,7 +5360,7 @@ async function runReplEternalLoop(opts, onInterruptsReset) {
|
|
|
5117
5360
|
} catch (err) {
|
|
5118
5361
|
opts.renderer.writeError(`[parallel] ${toErrorMessage3(err)}`);
|
|
5119
5362
|
}
|
|
5120
|
-
await new Promise((
|
|
5363
|
+
await new Promise((resolve5) => setTimeout(resolve5, 250));
|
|
5121
5364
|
return true;
|
|
5122
5365
|
}
|
|
5123
5366
|
return false;
|
|
@@ -5154,7 +5397,7 @@ async function readPossiblyMultiline(opts) {
|
|
|
5154
5397
|
// src/repl-rendering.ts
|
|
5155
5398
|
import { color as color10 } from "@wrongstack/core/utils";
|
|
5156
5399
|
var FILLED = "\u2588";
|
|
5157
|
-
var
|
|
5400
|
+
var EMPTY2 = "\u2591";
|
|
5158
5401
|
function renderContextChip(used, max) {
|
|
5159
5402
|
const ratio = Math.max(0, Math.min(1, used / max));
|
|
5160
5403
|
const pct = Math.round(ratio * 100);
|
|
@@ -5165,7 +5408,7 @@ function renderProgress(ratio, width) {
|
|
|
5165
5408
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
5166
5409
|
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
5167
5410
|
const capped = Math.min(width, filled);
|
|
5168
|
-
return FILLED.repeat(capped) +
|
|
5411
|
+
return FILLED.repeat(capped) + EMPTY2.repeat(width - capped);
|
|
5169
5412
|
}
|
|
5170
5413
|
function printBanner(renderer, projectName) {
|
|
5171
5414
|
const lines = [
|
|
@@ -5657,7 +5900,7 @@ ${color11.dim(
|
|
|
5657
5900
|
}
|
|
5658
5901
|
if (result.status === "done" && opts.getAutonomy) {
|
|
5659
5902
|
const autonomy2 = opts.getAutonomy();
|
|
5660
|
-
if (autonomy2 === "suggest" && !
|
|
5903
|
+
if (autonomy2 === "suggest" && !hasOpenTodos3(opts.agent.ctx.todos)) {
|
|
5661
5904
|
const suggestPrompt = 'Based on what you just did, suggest 3 exact prompt messages that can be submitted back to you through the TUI or WebUI. Each prompt must ask the agent to perform work; never assign a manual chore to the user. If you include suggestions, wrap them in a balanced <nextsteps>...</nextsteps> block, with one numbered prompt per line and no explanation. If there is nothing meaningful left, say "No further steps needed."';
|
|
5662
5905
|
const suggestBlocks = [{ type: "text", text: suggestPrompt }];
|
|
5663
5906
|
const suggestCtrl = new AbortController();
|
|
@@ -5694,7 +5937,7 @@ ${suggestResult.finalText}
|
|
|
5694
5937
|
}
|
|
5695
5938
|
}
|
|
5696
5939
|
}
|
|
5697
|
-
if (result.status === "done" && opts.getNextPredict?.() && !
|
|
5940
|
+
if (result.status === "done" && opts.getNextPredict?.() && !hasOpenTodos3(opts.agent.ctx.todos)) {
|
|
5698
5941
|
const autonomy2 = opts.getAutonomy?.() ?? "off";
|
|
5699
5942
|
if (autonomy2 === "off") {
|
|
5700
5943
|
const predictCtrl = new AbortController();
|
|
@@ -6001,8 +6244,8 @@ async function profileMenu(store, paths) {
|
|
|
6001
6244
|
}
|
|
6002
6245
|
const items = await Promise.all(
|
|
6003
6246
|
names.map(async (name) => {
|
|
6004
|
-
const
|
|
6005
|
-
const data = await readJsonObjectFile(
|
|
6247
|
+
const path15 = paths.profileConfig(name);
|
|
6248
|
+
const data = await readJsonObjectFile(path15);
|
|
6006
6249
|
const provider = typeof data["provider"] === "string" ? data["provider"] : "unset";
|
|
6007
6250
|
const model = typeof data["model"] === "string" ? data["model"] : "unset";
|
|
6008
6251
|
const fallbackCount = Array.isArray(data["fallbackModels"]) ? data["fallbackModels"].length : 0;
|
|
@@ -6023,7 +6266,7 @@ async function profileMenu(store, paths) {
|
|
|
6023
6266
|
},
|
|
6024
6267
|
{ label: "fallbacks", value: String(fallbackCount) },
|
|
6025
6268
|
{ label: "fallback profiles", value: String(profileCount) },
|
|
6026
|
-
{ label: "config", value:
|
|
6269
|
+
{ label: "config", value: path15 }
|
|
6027
6270
|
],
|
|
6028
6271
|
body: isActive ? "This profile owns the current session configuration." : "Switching profiles closes this session so every service restarts with the selected configuration.",
|
|
6029
6272
|
actions: isActive ? [] : [{ key: "s", label: "switch", command: `/profile switch ${name}`, confirm: true }]
|
|
@@ -6153,19 +6396,19 @@ async function gitMenu(projectRoot) {
|
|
|
6153
6396
|
},
|
|
6154
6397
|
...lines.map((line, index) => {
|
|
6155
6398
|
const code = line.slice(0, 2);
|
|
6156
|
-
const
|
|
6399
|
+
const path15 = line.slice(3);
|
|
6157
6400
|
const staged = code[0] !== " " && code[0] !== "?";
|
|
6158
6401
|
const unstaged = code[1] !== " " || code === "??";
|
|
6159
6402
|
return {
|
|
6160
|
-
id: `change:${index}:${
|
|
6161
|
-
label:
|
|
6403
|
+
id: `change:${index}:${path15}`,
|
|
6404
|
+
label: path15,
|
|
6162
6405
|
status: staged ? "good" : "warn",
|
|
6163
6406
|
summary: `${code} \xB7 ${staged ? "staged" : "not staged"}${unstaged ? " \xB7 working tree" : ""}`,
|
|
6164
6407
|
details: [
|
|
6165
6408
|
{ label: "status", value: code },
|
|
6166
6409
|
{ label: "staged", value: staged ? "yes" : "no" },
|
|
6167
6410
|
{ label: "unstaged", value: unstaged ? "yes" : "no" },
|
|
6168
|
-
{ label: "path", value:
|
|
6411
|
+
{ label: "path", value: path15 }
|
|
6169
6412
|
],
|
|
6170
6413
|
actions: [
|
|
6171
6414
|
{ key: "d", label: "diff summary", command: staged ? "/git diff --staged" : "/git diff" }
|
|
@@ -6194,20 +6437,20 @@ async function worktreeMenu(projectRoot) {
|
|
|
6194
6437
|
return split < 0 ? [line, "yes"] : [line.slice(0, split), line.slice(split + 1)];
|
|
6195
6438
|
})
|
|
6196
6439
|
);
|
|
6197
|
-
const
|
|
6440
|
+
const path15 = fields["worktree"] ?? "(unknown path)";
|
|
6198
6441
|
const branchRef = fields["branch"];
|
|
6199
6442
|
const branch = branchRef?.replace(/^refs\/heads\//, "") ?? (fields["detached"] ? "(detached)" : "(bare)");
|
|
6200
|
-
const status =
|
|
6443
|
+
const status = path15 === "(unknown path)" || fields["prunable"] ? null : await runGit(["status", "--porcelain=v1"], path15);
|
|
6201
6444
|
const changes = status?.code === 0 ? status.stdout.split(/\r?\n/).filter(Boolean) : [];
|
|
6202
6445
|
const dirty = changes.length > 0;
|
|
6203
6446
|
const isMain = index === 0;
|
|
6204
6447
|
return {
|
|
6205
|
-
id:
|
|
6448
|
+
id: path15,
|
|
6206
6449
|
label: branch,
|
|
6207
6450
|
status: fields["prunable"] ? "bad" : dirty ? "warn" : "good",
|
|
6208
6451
|
summary: `${isMain ? "main" : "linked"} \xB7 ${dirty ? `${changes.length} changes` : "clean"}`,
|
|
6209
6452
|
details: [
|
|
6210
|
-
{ label: "path", value:
|
|
6453
|
+
{ label: "path", value: path15 },
|
|
6211
6454
|
{ label: "branch", value: branch },
|
|
6212
6455
|
{ label: "HEAD", value: fields["HEAD"]?.slice(0, 12) ?? "unknown" },
|
|
6213
6456
|
{ label: "working tree", value: dirty ? `${changes.length} changed paths` : "clean" },
|
|
@@ -6442,6 +6685,7 @@ async function execute(deps) {
|
|
|
6442
6685
|
});
|
|
6443
6686
|
let code = 0;
|
|
6444
6687
|
let backgroundKanbanSupervisor;
|
|
6688
|
+
let autoUpdate;
|
|
6445
6689
|
let fleetStatusLine = null;
|
|
6446
6690
|
try {
|
|
6447
6691
|
const visionAdapters = () => createToolVisionAdapters(agent.tools);
|
|
@@ -6453,7 +6697,8 @@ async function execute(deps) {
|
|
|
6453
6697
|
modelsRegistry,
|
|
6454
6698
|
context.provider.id,
|
|
6455
6699
|
context.model,
|
|
6456
|
-
mergedModels
|
|
6700
|
+
mergedModels,
|
|
6701
|
+
{ catalogProviderId: catalogProviderIdFor(context.provider.id, providerConfig?.type) }
|
|
6457
6702
|
);
|
|
6458
6703
|
return caps.vision;
|
|
6459
6704
|
} catch {
|
|
@@ -6484,18 +6729,34 @@ async function execute(deps) {
|
|
|
6484
6729
|
log: (message) => console.log(message)
|
|
6485
6730
|
});
|
|
6486
6731
|
}
|
|
6732
|
+
if (executionMode !== "single-shot" && !webuiSessionChild) {
|
|
6733
|
+
autoUpdate = (await import("./standalone-auto-update-TOTRGCSA.js")).startStandaloneAutoUpdate({
|
|
6734
|
+
getConfig: () => configStore.get(),
|
|
6735
|
+
updateInfo: bootUpdateInfo
|
|
6736
|
+
});
|
|
6737
|
+
}
|
|
6487
6738
|
const enteringTui = executionMode === "tui";
|
|
6488
6739
|
if (!enteringTui) {
|
|
6489
6740
|
fleetStatusLine = new FleetStatusLine({ events, version: CLI_VERSION });
|
|
6490
6741
|
fleetStatusLine.start();
|
|
6491
6742
|
}
|
|
6492
6743
|
if (executionMode === "single-shot") {
|
|
6744
|
+
const piped = await readPipedStdin(process.stdin);
|
|
6745
|
+
if (piped.timedOut) {
|
|
6746
|
+
renderer.writeWarning(
|
|
6747
|
+
"No stdin data received in time; continuing without it. Redirect stdin (< /dev/null) to skip the wait."
|
|
6748
|
+
);
|
|
6749
|
+
} else if (piped.truncated) {
|
|
6750
|
+
renderer.writeWarning("Piped stdin exceeded 2 MB and was truncated.");
|
|
6751
|
+
}
|
|
6752
|
+
await mcpRegistry.whenStarted();
|
|
6493
6753
|
code = await runSingleShotDispatch({
|
|
6494
6754
|
agent,
|
|
6495
|
-
query: positional.join(" "),
|
|
6755
|
+
query: appendPipedStdin(positional.join(" "), piped.text),
|
|
6496
6756
|
flags,
|
|
6497
6757
|
tokenCounter,
|
|
6498
|
-
renderer
|
|
6758
|
+
renderer,
|
|
6759
|
+
events
|
|
6499
6760
|
});
|
|
6500
6761
|
} else if (executionMode === "tui") {
|
|
6501
6762
|
agent.disableInteractiveConfirmation();
|
|
@@ -6558,6 +6819,7 @@ async function execute(deps) {
|
|
|
6558
6819
|
const switchProjectInPlace2 = async (targetRoot, displayName) => {
|
|
6559
6820
|
return switchProjectInPlace(switchCtx, targetRoot, displayName);
|
|
6560
6821
|
};
|
|
6822
|
+
const worktreeSessions = /* @__PURE__ */ new Map();
|
|
6561
6823
|
const pickerCtx = {
|
|
6562
6824
|
state,
|
|
6563
6825
|
renderer,
|
|
@@ -6582,6 +6844,10 @@ async function execute(deps) {
|
|
|
6582
6844
|
code = await runTuiDispatch({
|
|
6583
6845
|
leaderAutoWake,
|
|
6584
6846
|
agent,
|
|
6847
|
+
// The session store's root. Without it /rewind, the checkpoint
|
|
6848
|
+
// timeline and /rewind redo resolve every session file against ''
|
|
6849
|
+
// and fail, and the layout store and history archive stay ephemeral.
|
|
6850
|
+
sessionsDir: wpaths.projectSessions,
|
|
6585
6851
|
events,
|
|
6586
6852
|
slashRegistry,
|
|
6587
6853
|
skillLoader,
|
|
@@ -6600,6 +6866,17 @@ async function execute(deps) {
|
|
|
6600
6866
|
model: context.model,
|
|
6601
6867
|
banner: !flags["no-banner"],
|
|
6602
6868
|
queueStore,
|
|
6869
|
+
// `/resume` brings the resumed session's queue along; the WebUI
|
|
6870
|
+
// keeps a session's queue in the same file.
|
|
6871
|
+
queueStoreFor: (sessionId) => {
|
|
6872
|
+
try {
|
|
6873
|
+
return new QueueStore({
|
|
6874
|
+
dir: sessionScopedPath3(state.wpaths.projectSessions, sessionId, "")
|
|
6875
|
+
});
|
|
6876
|
+
} catch {
|
|
6877
|
+
return void 0;
|
|
6878
|
+
}
|
|
6879
|
+
},
|
|
6603
6880
|
onQueueChange: (items) => {
|
|
6604
6881
|
setQueuedMessagesSnapshot2(context, items);
|
|
6605
6882
|
},
|
|
@@ -6623,6 +6900,7 @@ async function execute(deps) {
|
|
|
6623
6900
|
appVersion: CLI_VERSION,
|
|
6624
6901
|
latestVersion: bootUpdateInfo?.latest,
|
|
6625
6902
|
updateAvailable: bootUpdateInfo?.outdated,
|
|
6903
|
+
subscribeUpdateReady: autoUpdate?.subscribe,
|
|
6626
6904
|
provider: config.provider,
|
|
6627
6905
|
family: banneredFamily,
|
|
6628
6906
|
keyTail: banneredKeyTail,
|
|
@@ -6663,7 +6941,14 @@ async function execute(deps) {
|
|
|
6663
6941
|
onClearHistory: (dispatch) => {
|
|
6664
6942
|
void attachments.clear().catch(() => {
|
|
6665
6943
|
});
|
|
6666
|
-
dispatch({
|
|
6944
|
+
dispatch({
|
|
6945
|
+
type: "clearHistory",
|
|
6946
|
+
model: context.model,
|
|
6947
|
+
provider: context.provider.id,
|
|
6948
|
+
restoredMessages: null,
|
|
6949
|
+
restoredToolCalls: null,
|
|
6950
|
+
restoredEvents: null
|
|
6951
|
+
});
|
|
6667
6952
|
dispatch({ type: "resetContextChip" });
|
|
6668
6953
|
dispatch({ type: "streamReset" });
|
|
6669
6954
|
dispatch({ type: "toolStreamClear" });
|
|
@@ -6766,53 +7051,49 @@ async function execute(deps) {
|
|
|
6766
7051
|
} catch {
|
|
6767
7052
|
}
|
|
6768
7053
|
const currentId = agent.ctx.session?.id ?? session.id;
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
7054
|
+
const worktrees = await listGitWorktrees(state.projectRoot);
|
|
7055
|
+
worktreeSessions.clear();
|
|
7056
|
+
const toEntry = (s, worktree = worktreeOfSession(s, worktrees)) => {
|
|
7057
|
+
if (worktree) worktreeSessions.set(s.id, worktree);
|
|
7058
|
+
return {
|
|
7059
|
+
id: s.id,
|
|
7060
|
+
title: s.title ?? "",
|
|
7061
|
+
name: s.name,
|
|
7062
|
+
lastUserMessage: s.lastUserMessage,
|
|
7063
|
+
messageCount: s.messageCount,
|
|
7064
|
+
lastActivityAt: s.lastActivityAt,
|
|
7065
|
+
startedAt: s.startedAt ?? "",
|
|
7066
|
+
endedAt: s.endedAt,
|
|
7067
|
+
tokenTotal: s.tokenTotal ?? 0,
|
|
7068
|
+
iterationCount: s.iterationCount ?? 0,
|
|
7069
|
+
toolCallCount: s.toolCallCount ?? 0,
|
|
7070
|
+
toolErrorCount: s.toolErrorCount ?? 0,
|
|
7071
|
+
outcome: s.outcome,
|
|
7072
|
+
forkedFrom: s.forkedFrom,
|
|
7073
|
+
...worktree ? { worktree } : {},
|
|
7074
|
+
isCurrent: s.id === currentId,
|
|
7075
|
+
// The session this process owns is `isCurrent`, not "live
|
|
7076
|
+
// elsewhere" — it holds its own lease and would otherwise be
|
|
7077
|
+
// labelled as owned by another surface.
|
|
7078
|
+
...s.id !== currentId && liveBySession.has(s.id) ? { live: liveBySession.get(s.id) } : {}
|
|
7079
|
+
};
|
|
7080
|
+
};
|
|
7081
|
+
return summaries.map((s) => toEntry(s));
|
|
6789
7082
|
},
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
events,
|
|
6799
|
-
onLoadProgress,
|
|
6800
|
-
onStage,
|
|
6801
|
-
onFailure: (info) => {
|
|
6802
|
-
failure = info;
|
|
6803
|
-
}
|
|
6804
|
-
},
|
|
6805
|
-
sessionId
|
|
6806
|
-
);
|
|
6807
|
-
if (!result) {
|
|
6808
|
-
throw new Error(
|
|
6809
|
-
failure ? `${failure.message} (at ${failure.stage})` : `Session "${sessionId}" could not be resumed.`
|
|
6810
|
-
);
|
|
6811
|
-
}
|
|
6812
|
-
setAutoSuggestions([]);
|
|
6813
|
-
const resumedNextSteps = result.attached && !hasOpenTodos3(agent.ctx.todos) && result.lastAssistantText ? parseNextSteps2(result.lastAssistantText).texts : [];
|
|
6814
|
-
return { ...result, nextSteps: resumedNextSteps };
|
|
7083
|
+
forkSession: async (sessionId, checkpointPromptIndex) => {
|
|
7084
|
+
const store = state.activeSessionStore;
|
|
7085
|
+
if (!store?.fork) throw new Error("This session store cannot fork sessions.");
|
|
7086
|
+
const forked = await store.fork(sessionId, {
|
|
7087
|
+
checkpointPromptIndex,
|
|
7088
|
+
beforeCheckpointPrompt: true
|
|
7089
|
+
});
|
|
7090
|
+
return { id: forked.id };
|
|
6815
7091
|
},
|
|
7092
|
+
onResumeSession: withWorktreeSwitch(
|
|
7093
|
+
createTuiResumeCallback({ state, agent, tokenCounter, switchProviderAndModel, events }),
|
|
7094
|
+
(id) => worktreeSessions.get(id),
|
|
7095
|
+
switchProjectInPlace2
|
|
7096
|
+
),
|
|
6816
7097
|
getProjectPickerItems: () => getProjectPickerItems(pickerCtx),
|
|
6817
7098
|
onProjectSelect: (slug, kind) => onProjectSelect(pickerCtx, slug, kind),
|
|
6818
7099
|
initialAgentsMonitorOpen: !!flags.quick,
|
|
@@ -6940,7 +7221,7 @@ async function execute(deps) {
|
|
|
6940
7221
|
attachments,
|
|
6941
7222
|
effectiveMaxContext,
|
|
6942
7223
|
getEffectiveMaxContext,
|
|
6943
|
-
projectName:
|
|
7224
|
+
projectName: path14.basename(projectRoot) || void 0,
|
|
6944
7225
|
projectRoot,
|
|
6945
7226
|
appConfig: config,
|
|
6946
7227
|
getSessionId: () => agent.ctx.session?.id ?? session.id,
|
|
@@ -6972,6 +7253,7 @@ async function execute(deps) {
|
|
|
6972
7253
|
}
|
|
6973
7254
|
} finally {
|
|
6974
7255
|
backgroundKanbanSupervisor?.dispose();
|
|
7256
|
+
autoUpdate?.dispose();
|
|
6975
7257
|
for (const off of chimeraTeardowns.splice(0)) {
|
|
6976
7258
|
try {
|
|
6977
7259
|
off();
|
|
@@ -7006,4 +7288,4 @@ export {
|
|
|
7006
7288
|
execute,
|
|
7007
7289
|
resolveReviewerFallbackModels
|
|
7008
7290
|
};
|
|
7009
|
-
//# sourceMappingURL=execution-
|
|
7291
|
+
//# sourceMappingURL=execution-7QIP6GRY.js.map
|