@wrongstack/cli 1.0.24 → 1.0.27
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-M4565YUW.js} +33 -20
- 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 +12 -1
- package/dist/boot/dispatch-webui.d.ts +2 -0
- package/dist/boot/goal-tui-default.d.ts +10 -0
- 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-CZTXF6HY.js +140 -0
- package/dist/{chunk-DRM7PSW7.js → chunk-ENRYW73H.js} +527 -381
- package/dist/{chunk-SKPDE62W.js → chunk-EPR42SWM.js} +36 -6
- package/dist/{chunk-YBUCXXYZ.js → chunk-F5DCFAKI.js} +1 -1
- package/dist/chunk-HDMFKTT3.js +157 -0
- package/dist/{chunk-PGKJ2OQP.js → chunk-HFZH2XJN.js} +5 -5
- package/dist/chunk-HLSHFXTO.js +97 -0
- 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-N57IU5FG.js +58 -0
- package/dist/{chunk-2EBVTYJR.js → chunk-NRXWOMK7.js} +17 -3
- package/dist/chunk-PZQBGUVZ.js +18 -0
- package/dist/{chunk-MHEBWVXL.js → chunk-Q4INH2DG.js} +46 -2
- package/dist/{chunk-5SEYGOCH.js → chunk-RDGGWMTI.js} +2 -2
- package/dist/{chunk-BLD5DSVA.js → chunk-RGFWGUAB.js} +27 -15
- package/dist/{chunk-ZZRSG7VK.js → chunk-RZ4ZDRYA.js} +3 -3
- package/dist/{chunk-S4FFHDJG.js → chunk-SDJCLEK6.js} +6 -2
- package/dist/{chunk-U46QR4WR.js → chunk-TI7KLHYX.js} +132 -3
- 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-C7RBSWHF.js} +434 -327
- package/dist/{cli-main-AELJDBGU.js → cli-main-TQTH73ZK.js} +2948 -2315
- 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-3STBD2U7.js} +2167 -1831
- package/dist/execution-chimera-review.d.ts +18 -1
- 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-44F4OQH2.js} +9 -8
- package/dist/hq-fleet-control.d.ts +36 -0
- package/dist/hq-live-status.d.ts +22 -0
- package/dist/hq-publisher.d.ts +18 -0
- package/dist/hq-server/routes/command-handlers.d.ts +12 -1
- package/dist/hq-server/routes/data-handlers.d.ts +1 -1
- package/dist/hq-server/socket-credentials.d.ts +63 -0
- package/dist/hq-server/upgrade-handler.d.ts +2 -0
- package/dist/hq-server/ws.d.ts +24 -0
- package/dist/{hq-server-PC7GZMPT.js → hq-server-UIB24LU6.js} +2 -2
- package/dist/{hq-service-NW56MUP2.js → hq-service-BMMJAHRT.js} +4 -4
- package/dist/import-claude-code-AYG24RG2.js +268 -0
- package/dist/index.js +12 -8
- package/dist/live-settings-input.d.ts +1 -0
- package/dist/{mailbox-serve-WDOCH6KR.js → mailbox-serve-GNGXEQ2R.js} +3 -3
- package/dist/{modeldiag-36OGAL5C.js → modeldiag-W7XAE625.js} +9 -9
- 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-GB5VXCCT.js +576 -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-XHLXJ5C3.js} +109 -10
- package/dist/short-circuit-flags-5TNTJPQR.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/slash-commands/theme.d.ts +15 -1
- 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 +52 -0
- package/dist/subcommands/handlers/permissions.d.ts +9 -3
- package/dist/subcommands/handlers/remote.d.ts +6 -0
- package/dist/subcommands/handlers/sessions-fleet.d.ts +0 -12
- package/dist/subcommands/handlers/version-help.d.ts +5 -0
- package/dist/{subcommands-ZTSIN7LG.js → subcommands-BHQYLDSD.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-GCXIO3JY.js} +515 -365
- package/dist/webui-server-lifecycle.d.ts +57 -0
- package/dist/webui-server-options.d.ts +10 -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/command-host-state.d.ts +2 -0
- package/dist/wiring/hq-telemetry.d.ts +12 -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 +29 -28
- package/dist/chunk-2BKFWIER.js +0 -98
- package/dist/chunk-424ORSE7.js +0 -56
- package/dist/chunk-OWNC7LMD.js +0 -78
- 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
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createHqFleetControl,
|
|
2
3
|
getSuggestions,
|
|
3
4
|
normalizeTuiThinkingWord,
|
|
5
|
+
resolveExecutionMode,
|
|
4
6
|
runGit,
|
|
5
7
|
setAutoSuggestions,
|
|
6
8
|
setSuggestions
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-HDMFKTT3.js";
|
|
8
10
|
import {
|
|
9
11
|
deriveFsAccessPair,
|
|
10
12
|
persistConfigSetting
|
|
11
13
|
} from "./chunk-SFBFYIOW.js";
|
|
14
|
+
import {
|
|
15
|
+
checkStructuredAnswer,
|
|
16
|
+
parseOutputFormat,
|
|
17
|
+
repairPrompt,
|
|
18
|
+
startStreamJson,
|
|
19
|
+
streamJsonInit,
|
|
20
|
+
withSchemaInstruction
|
|
21
|
+
} from "./chunk-LKPEBZBE.js";
|
|
22
|
+
import {
|
|
23
|
+
theme
|
|
24
|
+
} from "./chunk-QD544J2B.js";
|
|
25
|
+
import {
|
|
26
|
+
WEBUI_SESSION_CHILD_CAPABILITIES,
|
|
27
|
+
writeWebuiSessionChildError,
|
|
28
|
+
writeWebuiSessionChildReady
|
|
29
|
+
} from "./chunk-ZMX5HRLI.js";
|
|
30
|
+
import {
|
|
31
|
+
fmtTok
|
|
32
|
+
} from "./chunk-EWKESL4L.js";
|
|
12
33
|
import "./chunk-WHOIR577.js";
|
|
13
34
|
import {
|
|
14
35
|
advanceToNextTask,
|
|
@@ -23,49 +44,38 @@ import {
|
|
|
23
44
|
trySaveSpecFromAIOutput,
|
|
24
45
|
trySaveTasksFromAIOutput
|
|
25
46
|
} 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
47
|
import {
|
|
38
48
|
terminalLink,
|
|
39
49
|
terminalText
|
|
40
50
|
} from "./chunk-HXJYHAR4.js";
|
|
41
|
-
import {
|
|
42
|
-
activeProfileConfigPath
|
|
43
|
-
} from "./chunk-YMXXOOFN.js";
|
|
44
|
-
import {
|
|
45
|
-
backupCurrent
|
|
46
|
-
} from "./chunk-WSTVGC2C.js";
|
|
47
51
|
import {
|
|
48
52
|
startCliHqConnection
|
|
49
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-RGFWGUAB.js";
|
|
54
|
+
import {
|
|
55
|
+
CLI_VERSION
|
|
56
|
+
} from "./chunk-F5DCFAKI.js";
|
|
50
57
|
import {
|
|
51
58
|
resolveActiveApiKey
|
|
52
59
|
} from "./chunk-LUMETJFO.js";
|
|
53
60
|
import {
|
|
54
|
-
|
|
55
|
-
} from "./chunk-
|
|
61
|
+
activeProfileConfigPath
|
|
62
|
+
} from "./chunk-YMXXOOFN.js";
|
|
63
|
+
import {
|
|
64
|
+
backupCurrent
|
|
65
|
+
} from "./chunk-WSTVGC2C.js";
|
|
56
66
|
|
|
57
67
|
// src/execution.ts
|
|
58
|
-
import * as
|
|
68
|
+
import * as path14 from "node:path";
|
|
59
69
|
import { effectiveFallbackChain as effectiveFallbackChain2, setQueuedMessagesSnapshot as setQueuedMessagesSnapshot2 } from "@wrongstack/core/agent";
|
|
60
70
|
import { LeaderAutoWakeController } from "@wrongstack/core/coordination";
|
|
61
71
|
import { updateReviewReportEvidence } from "@wrongstack/core/plugin";
|
|
62
72
|
import { noOpVault } from "@wrongstack/core/security";
|
|
63
|
-
import { attachTodosCheckpoint as attachTodosCheckpoint2 } from "@wrongstack/core/storage";
|
|
73
|
+
import { attachTodosCheckpoint as attachTodosCheckpoint2, QueueStore } from "@wrongstack/core/storage";
|
|
64
74
|
import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
75
|
+
import { mergeCustomModelDefs, sessionScopedPath as sessionScopedPath3 } from "@wrongstack/core/utils";
|
|
76
|
+
import { listGitWorktrees } from "@wrongstack/core/worktree";
|
|
77
|
+
import { capabilitiesFor, catalogProviderIdFor } from "@wrongstack/providers";
|
|
67
78
|
import { createToolVisionAdapters } from "@wrongstack/runtime/vision";
|
|
68
|
-
import { parseNextSteps as parseNextSteps2 } from "@wrongstack/tools/next-steps";
|
|
69
79
|
|
|
70
80
|
// src/boot/dispatch-singleshot.ts
|
|
71
81
|
import { color, writeOut } from "@wrongstack/core/utils";
|
|
@@ -87,20 +97,130 @@ ${err.describe()}`);
|
|
|
87
97
|
].join("\n");
|
|
88
98
|
}
|
|
89
99
|
|
|
100
|
+
// src/boot/single-shot-budget.ts
|
|
101
|
+
function parseMaxBudgetUsd(value) {
|
|
102
|
+
if (value === void 0) return void 0;
|
|
103
|
+
const amount = typeof value === "string" ? Number(value.trim()) : Number.NaN;
|
|
104
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
105
|
+
throw new Error("--max-budget-usd needs a positive dollar amount (e.g. --max-budget-usd 0.50)");
|
|
106
|
+
}
|
|
107
|
+
return amount;
|
|
108
|
+
}
|
|
109
|
+
function watchSingleShotBudget(opts) {
|
|
110
|
+
const leaderStart = opts.tokenCounter.estimateCost().total;
|
|
111
|
+
const subagentCost = /* @__PURE__ */ new Map();
|
|
112
|
+
let exceeded = false;
|
|
113
|
+
const spent = () => {
|
|
114
|
+
let total = opts.tokenCounter.estimateCost().total - leaderStart;
|
|
115
|
+
for (const cost of subagentCost.values()) total += cost;
|
|
116
|
+
return total;
|
|
117
|
+
};
|
|
118
|
+
const check = () => {
|
|
119
|
+
if (exceeded || spent() <= opts.limitUsd) return;
|
|
120
|
+
exceeded = true;
|
|
121
|
+
opts.onExceeded();
|
|
122
|
+
};
|
|
123
|
+
const offLeader = opts.events.on("token.accounted", check);
|
|
124
|
+
const offSubagent = opts.events.on("subagent.token_accounted", (payload) => {
|
|
125
|
+
subagentCost.set(payload.subagentId, payload.cost.total);
|
|
126
|
+
check();
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
spent,
|
|
130
|
+
get exceeded() {
|
|
131
|
+
return exceeded;
|
|
132
|
+
},
|
|
133
|
+
dispose() {
|
|
134
|
+
offLeader();
|
|
135
|
+
offSubagent();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
90
140
|
// src/boot/dispatch-singleshot.ts
|
|
141
|
+
function exitCodeForStatus(status) {
|
|
142
|
+
if (status === "failed" || status === "max_iterations") return 1;
|
|
143
|
+
if (status === "aborted") return 130;
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
91
146
|
async function runSingleShotDispatch(ctx) {
|
|
92
147
|
const { agent, query, flags, tokenCounter, renderer } = ctx;
|
|
148
|
+
let limitUsd;
|
|
149
|
+
let format;
|
|
150
|
+
try {
|
|
151
|
+
limitUsd = parseMaxBudgetUsd(flags["max-budget-usd"]);
|
|
152
|
+
format = parseOutputFormat(flags["output-format"]) ?? (flags["output-json"] ? "json" : "text");
|
|
153
|
+
} catch (err) {
|
|
154
|
+
renderer.writeError(err instanceof Error ? err.message : String(err));
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
93
157
|
const ctrl = new AbortController();
|
|
94
158
|
const onSigint = () => ctrl.abort();
|
|
95
159
|
process.on("SIGINT", onSigint);
|
|
160
|
+
const interrupt = ctx.interruptController;
|
|
161
|
+
const previousAbortLeader = interrupt?.abortLeader;
|
|
162
|
+
if (interrupt !== void 0) {
|
|
163
|
+
interrupt.abortLeader = () => {
|
|
164
|
+
if (ctrl.signal.aborted) return false;
|
|
165
|
+
ctrl.abort();
|
|
166
|
+
return true;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
let budget;
|
|
170
|
+
if (limitUsd !== void 0 && ctx.events) {
|
|
171
|
+
budget = watchSingleShotBudget({
|
|
172
|
+
limitUsd,
|
|
173
|
+
tokenCounter,
|
|
174
|
+
events: ctx.events,
|
|
175
|
+
onExceeded: () => ctrl.abort()
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
let stopStream;
|
|
179
|
+
if (format === "stream-json") {
|
|
180
|
+
const tools = agent.tools?.listForProvider?.() ?? agent.tools?.list?.() ?? [];
|
|
181
|
+
writeOut(
|
|
182
|
+
streamJsonInit({
|
|
183
|
+
sessionId: agent.ctx?.session?.id ?? null,
|
|
184
|
+
provider: agent.ctx?.provider?.id ?? null,
|
|
185
|
+
model: agent.ctx?.model ?? null,
|
|
186
|
+
cwd: agent.ctx?.cwd ?? process.cwd(),
|
|
187
|
+
tools: tools.map((tool) => tool.name)
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
if (ctx.events) {
|
|
191
|
+
stopStream = startStreamJson({
|
|
192
|
+
events: ctx.events,
|
|
193
|
+
write: (line) => writeOut(line),
|
|
194
|
+
includePartialMessages: flags["include-partial-messages"] === true
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
96
198
|
const startedAt = Date.now();
|
|
97
199
|
const before = tokenCounter.total();
|
|
98
200
|
const costBefore = tokenCounter.estimateCost().total;
|
|
201
|
+
const schema = typeof flags["json-schema"] === "string" ? JSON.parse(flags["json-schema"]) : void 0;
|
|
99
202
|
let result;
|
|
203
|
+
let structured;
|
|
100
204
|
try {
|
|
101
|
-
result = await agent.run(query,
|
|
205
|
+
result = await agent.run(schema ? withSchemaInstruction(query, schema) : query, {
|
|
206
|
+
signal: ctrl.signal
|
|
207
|
+
});
|
|
208
|
+
if (schema && result.status === "done") {
|
|
209
|
+
structured = checkStructuredAnswer(result.finalText, schema);
|
|
210
|
+
if (!structured.ok && !ctrl.signal.aborted) {
|
|
211
|
+
const first = result;
|
|
212
|
+
result = await agent.run(repairPrompt(structured.errors), { signal: ctrl.signal });
|
|
213
|
+
result = { ...result, iterations: first.iterations + result.iterations };
|
|
214
|
+
if (result.status === "done") structured = checkStructuredAnswer(result.finalText, schema);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
102
217
|
} finally {
|
|
218
|
+
stopStream?.();
|
|
219
|
+
budget?.dispose();
|
|
103
220
|
process.off("SIGINT", onSigint);
|
|
221
|
+
if (interrupt !== void 0 && previousAbortLeader !== void 0) {
|
|
222
|
+
interrupt.abortLeader = previousAbortLeader;
|
|
223
|
+
}
|
|
104
224
|
const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
|
|
105
225
|
getProcessRegistry2().killAll({ preserveBackground: true });
|
|
106
226
|
}
|
|
@@ -113,9 +233,17 @@ async function runSingleShotDispatch(ctx) {
|
|
|
113
233
|
cost: costAfter - costBefore,
|
|
114
234
|
elapsedMs: Date.now() - startedAt
|
|
115
235
|
};
|
|
116
|
-
|
|
236
|
+
const schemaFailed = structured !== void 0 && !structured.ok;
|
|
237
|
+
const exitCode = budget?.exceeded || schemaFailed ? 1 : exitCodeForStatus(result.status);
|
|
238
|
+
if (format !== "text") {
|
|
117
239
|
const json = JSON.stringify({
|
|
240
|
+
// In a stream the result is one event among many; name it.
|
|
241
|
+
...format === "stream-json" ? { type: "result" } : {},
|
|
118
242
|
status: result.status,
|
|
243
|
+
// Lets a script continue the conversation: `wstack --resume <sessionId>`.
|
|
244
|
+
// Read defensively — the session is swapped in by /resume and absent in
|
|
245
|
+
// some embedders.
|
|
246
|
+
sessionId: agent.ctx?.session?.id ?? null,
|
|
119
247
|
finalText: result.finalText ?? null,
|
|
120
248
|
error: result.error ? {
|
|
121
249
|
code: result.error.code,
|
|
@@ -125,14 +253,22 @@ async function runSingleShotDispatch(ctx) {
|
|
|
125
253
|
message: result.error.message,
|
|
126
254
|
context: result.error.context ?? null
|
|
127
255
|
} : null,
|
|
128
|
-
usage
|
|
256
|
+
usage,
|
|
257
|
+
...limitUsd !== void 0 ? { budget: { limitUsd, exceeded: budget?.exceeded ?? false } } : {},
|
|
258
|
+
...schema ? {
|
|
259
|
+
structuredOutput: structured?.ok ? structured.value : null,
|
|
260
|
+
schemaErrors: structured && !structured.ok ? structured.errors : null
|
|
261
|
+
} : {}
|
|
129
262
|
});
|
|
130
263
|
writeOut(json + "\n");
|
|
131
|
-
return
|
|
264
|
+
return exitCode;
|
|
132
265
|
}
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
266
|
+
const code = exitCode;
|
|
267
|
+
if (budget?.exceeded) {
|
|
268
|
+
renderer.writeError(
|
|
269
|
+
`Stopped: spend $${budget.spent().toFixed(4)} exceeded --max-budget-usd $${limitUsd}.`
|
|
270
|
+
);
|
|
271
|
+
} else if (result.status === "failed") {
|
|
136
272
|
const err = result.error;
|
|
137
273
|
if (err) {
|
|
138
274
|
const tag = err.recoverable ? " (recoverable)" : "";
|
|
@@ -143,11 +279,11 @@ async function runSingleShotDispatch(ctx) {
|
|
|
143
279
|
renderer.writeError("Failed.");
|
|
144
280
|
}
|
|
145
281
|
} else if (result.status === "aborted") {
|
|
146
|
-
code = 130;
|
|
147
282
|
renderer.writeWarning("Aborted.");
|
|
148
283
|
} else if (result.status === "max_iterations") {
|
|
149
|
-
code = 1;
|
|
150
284
|
renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
|
|
285
|
+
} else if (structured && !structured.ok) {
|
|
286
|
+
renderer.writeError(`Answer did not match --json-schema: ${structured.errors.join("; ")}`);
|
|
151
287
|
}
|
|
152
288
|
if (result.finalText) renderer.write("\n" + result.finalText + "\n");
|
|
153
289
|
const r = result;
|
|
@@ -208,6 +344,8 @@ async function runWebUIDispatch(ctx) {
|
|
|
208
344
|
onKanbanDispatch,
|
|
209
345
|
getFleetBudget,
|
|
210
346
|
stopSessionFleet,
|
|
347
|
+
interruptController,
|
|
348
|
+
hqFleetControl,
|
|
211
349
|
onSessionRetired,
|
|
212
350
|
leaderAutoWake,
|
|
213
351
|
webuiSessionChild
|
|
@@ -216,7 +354,7 @@ async function runWebUIDispatch(ctx) {
|
|
|
216
354
|
const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
|
|
217
355
|
agent.disableInteractiveConfirmation();
|
|
218
356
|
renderer.setSilent(true);
|
|
219
|
-
const { runWebUI } = await import("./webui-server-
|
|
357
|
+
const { runWebUI } = await import("./webui-server-GCXIO3JY.js");
|
|
220
358
|
const flagValue = (names) => {
|
|
221
359
|
for (const name of names) {
|
|
222
360
|
if (!Object.hasOwn(flags, name)) continue;
|
|
@@ -318,6 +456,8 @@ async function runWebUIDispatch(ctx) {
|
|
|
318
456
|
sessionStore,
|
|
319
457
|
...getFleetBudget ? { getFleetBudget } : {},
|
|
320
458
|
...stopSessionFleet ? { stopSessionFleet } : {},
|
|
459
|
+
...interruptController ? { interruptController } : {},
|
|
460
|
+
...hqFleetControl ? { hqFleetControl } : {},
|
|
321
461
|
...onSessionRetired ? { onSessionRetired } : {},
|
|
322
462
|
...leaderAutoWake ? { leaderAutoWake } : {},
|
|
323
463
|
sessionsDir: projectSessionsDir,
|
|
@@ -426,14 +566,14 @@ async function runWebUIDispatch(ctx) {
|
|
|
426
566
|
);
|
|
427
567
|
}
|
|
428
568
|
});
|
|
429
|
-
const webuiExit = new Promise((
|
|
569
|
+
const webuiExit = new Promise((resolve5) => {
|
|
430
570
|
webuiPromise.then(() => {
|
|
431
571
|
renderer.setSilent(false);
|
|
432
572
|
renderer.write("\n");
|
|
433
573
|
renderer.writeInfo(
|
|
434
574
|
color2.yellow(` Shutting down ${isSimpleUi ? "SimpleUI" : "WebUI"} server\u2026`)
|
|
435
575
|
);
|
|
436
|
-
|
|
576
|
+
resolve5(0);
|
|
437
577
|
}).catch((err) => {
|
|
438
578
|
if (webuiSessionChild) {
|
|
439
579
|
void writeWebuiSessionChildError(webuiSessionChild.readyFile, {
|
|
@@ -451,20 +591,72 @@ async function runWebUIDispatch(ctx) {
|
|
|
451
591
|
` \u2717 ${isSimpleUi ? "SimpleUI" : "WebUI"} failed to start: ${err instanceof Error ? err.message : String(err)}`
|
|
452
592
|
)
|
|
453
593
|
);
|
|
454
|
-
|
|
594
|
+
resolve5(1);
|
|
455
595
|
});
|
|
456
596
|
});
|
|
457
597
|
return webuiExit;
|
|
458
598
|
}
|
|
459
599
|
|
|
460
|
-
// src/boot/
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
600
|
+
// src/boot/piped-stdin.ts
|
|
601
|
+
var PIPED_STDIN_FIRST_BYTE_TIMEOUT_MS = 3e3;
|
|
602
|
+
var PIPED_STDIN_MAX_BYTES = 2 * 1024 * 1024;
|
|
603
|
+
var EMPTY = { text: "", truncated: false, timedOut: false };
|
|
604
|
+
function readPipedStdin(source, opts = {}) {
|
|
605
|
+
if (source.isTTY) return Promise.resolve(EMPTY);
|
|
606
|
+
const firstByteTimeoutMs = opts.firstByteTimeoutMs ?? PIPED_STDIN_FIRST_BYTE_TIMEOUT_MS;
|
|
607
|
+
const maxBytes = opts.maxBytes ?? PIPED_STDIN_MAX_BYTES;
|
|
608
|
+
return new Promise((resolve5) => {
|
|
609
|
+
const chunks = [];
|
|
610
|
+
let size = 0;
|
|
611
|
+
let truncated = false;
|
|
612
|
+
let settled = false;
|
|
613
|
+
const finish = (timedOut) => {
|
|
614
|
+
if (settled) return;
|
|
615
|
+
settled = true;
|
|
616
|
+
clearTimeout(timer);
|
|
617
|
+
source.removeListener("data", onData);
|
|
618
|
+
source.removeListener("end", onEnd);
|
|
619
|
+
source.removeListener("close", onEnd);
|
|
620
|
+
source.removeListener("error", onError);
|
|
621
|
+
source.pause();
|
|
622
|
+
if (timedOut || truncated) source.unref?.();
|
|
623
|
+
const text = timedOut ? "" : Buffer.concat(chunks).toString("utf8").trimEnd();
|
|
624
|
+
resolve5({ text, truncated, timedOut });
|
|
625
|
+
};
|
|
626
|
+
const onData = (chunk) => {
|
|
627
|
+
clearTimeout(timer);
|
|
628
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
629
|
+
const room = maxBytes - size;
|
|
630
|
+
if (buf.length >= room) {
|
|
631
|
+
chunks.push(buf.subarray(0, Math.max(0, room)));
|
|
632
|
+
size = maxBytes;
|
|
633
|
+
truncated = true;
|
|
634
|
+
finish(false);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
chunks.push(buf);
|
|
638
|
+
size += buf.length;
|
|
639
|
+
};
|
|
640
|
+
const onEnd = () => finish(false);
|
|
641
|
+
const onError = () => finish(false);
|
|
642
|
+
const timer = setTimeout(() => {
|
|
643
|
+
if (size === 0) finish(true);
|
|
644
|
+
}, firstByteTimeoutMs);
|
|
645
|
+
source.on("data", onData);
|
|
646
|
+
source.on("end", onEnd);
|
|
647
|
+
source.on("close", onEnd);
|
|
648
|
+
source.on("error", onError);
|
|
649
|
+
source.resume();
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
function appendPipedStdin(query, piped) {
|
|
653
|
+
if (!piped) return query;
|
|
654
|
+
if (!query.trim()) return piped;
|
|
655
|
+
return `${query}
|
|
656
|
+
|
|
657
|
+
<stdin>
|
|
658
|
+
${piped}
|
|
659
|
+
</stdin>`;
|
|
468
660
|
}
|
|
469
661
|
|
|
470
662
|
// src/boot/tui-coordinator-callbacks.ts
|
|
@@ -656,7 +848,6 @@ Reply with ONLY the JSON object.`
|
|
|
656
848
|
content: [userPrompt]
|
|
657
849
|
}
|
|
658
850
|
],
|
|
659
|
-
maxTokens: 1024,
|
|
660
851
|
temperature: 0
|
|
661
852
|
},
|
|
662
853
|
{ signal: context.signal }
|
|
@@ -1022,6 +1213,8 @@ import {
|
|
|
1022
1213
|
} from "@wrongstack/core/agent";
|
|
1023
1214
|
import { resetSessionSubagentPolicy } from "@wrongstack/core/coordination";
|
|
1024
1215
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
1216
|
+
import { restoreSessionPermissionOverrides } from "@wrongstack/core/security";
|
|
1217
|
+
import { restoreRequiredSkillsFromEvents } from "@wrongstack/core/skills";
|
|
1025
1218
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
1026
1219
|
import {
|
|
1027
1220
|
activateProjectStateGuard,
|
|
@@ -1056,7 +1249,8 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
|
|
|
1056
1249
|
await fs2.mkdir(nextWpaths.projectSessions, { recursive: true });
|
|
1057
1250
|
const nextSessionStore = new DefaultSessionStore({
|
|
1058
1251
|
dir: nextWpaths.projectSessions,
|
|
1059
|
-
projectRoot: resolved
|
|
1252
|
+
projectRoot: resolved,
|
|
1253
|
+
events
|
|
1060
1254
|
});
|
|
1061
1255
|
const nextWriter = await nextSessionStore.create({
|
|
1062
1256
|
id: "",
|
|
@@ -1130,8 +1324,11 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
|
|
|
1130
1324
|
context.projectRoot = resolved;
|
|
1131
1325
|
context.workingDir = resolved;
|
|
1132
1326
|
context.session = nextWriter;
|
|
1327
|
+
if (state.sessionRef) state.sessionRef.current = nextWriter;
|
|
1133
1328
|
context.state.replaceMessages([]);
|
|
1134
1329
|
resetSessionSubagentPolicy(context);
|
|
1330
|
+
restoreSessionPermissionOverrides(context.meta, {});
|
|
1331
|
+
restoreRequiredSkillsFromEvents(context, []);
|
|
1135
1332
|
context.state.replaceTodos([]);
|
|
1136
1333
|
context.clearFileTracking();
|
|
1137
1334
|
context.clearMemoryEvidence?.();
|
|
@@ -1266,574 +1463,132 @@ async function onSDDOutput(output) {
|
|
|
1266
1463
|
return messages;
|
|
1267
1464
|
}
|
|
1268
1465
|
|
|
1269
|
-
// src/boot/tui-session-
|
|
1466
|
+
// src/boot/tui-session-stub-enrich.ts
|
|
1467
|
+
import * as fs3 from "node:fs/promises";
|
|
1270
1468
|
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");
|
|
1469
|
+
var SLICE_BYTES = 256 * 1024;
|
|
1470
|
+
var MAX_TITLE_CHARS = 80;
|
|
1471
|
+
var PICKER_POOL_CEILING = 1500;
|
|
1472
|
+
var ENRICH_CONCURRENCY = 16;
|
|
1473
|
+
function isStubSummary(s) {
|
|
1474
|
+
return !s.title && !s.endedAt;
|
|
1287
1475
|
}
|
|
1288
|
-
function
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1476
|
+
function containedJsonlPath(sessionsDir, id) {
|
|
1477
|
+
const root = path7.resolve(sessionsDir);
|
|
1478
|
+
const file = path7.resolve(root, `${id}.jsonl`);
|
|
1479
|
+
if (file !== root && !file.startsWith(root + path7.sep)) return void 0;
|
|
1480
|
+
return file;
|
|
1481
|
+
}
|
|
1482
|
+
function stripControlChars(text) {
|
|
1483
|
+
return text.replaceAll(
|
|
1484
|
+
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07]*(?:\x07|\x1b\\)|[\u0000-\u001f\u007f-\u009f]/g,
|
|
1485
|
+
""
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
function truncate(text) {
|
|
1489
|
+
const oneLine = stripControlChars(text).replaceAll(/\s+/g, " ").trim();
|
|
1490
|
+
return oneLine.length > MAX_TITLE_CHARS ? `${oneLine.slice(0, MAX_TITLE_CHARS - 1)}\u2026` : oneLine;
|
|
1491
|
+
}
|
|
1492
|
+
function contentToText(content) {
|
|
1493
|
+
if (typeof content === "string") return content;
|
|
1494
|
+
if (Array.isArray(content)) {
|
|
1495
|
+
return content.map(
|
|
1496
|
+
(block) => block && typeof block === "object" && "text" in block && typeof block.text === "string" ? block.text : ""
|
|
1497
|
+
).filter(Boolean).join(" ");
|
|
1294
1498
|
}
|
|
1295
|
-
return
|
|
1499
|
+
return "";
|
|
1296
1500
|
}
|
|
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;
|
|
1501
|
+
function scanUserInputs(chunk) {
|
|
1502
|
+
let first;
|
|
1503
|
+
let last;
|
|
1504
|
+
for (const line of chunk.split("\n")) {
|
|
1505
|
+
const trimmed = line.trim();
|
|
1506
|
+
if (!trimmed.includes('"user_input"')) continue;
|
|
1313
1507
|
try {
|
|
1314
|
-
|
|
1508
|
+
const event = JSON.parse(trimmed);
|
|
1509
|
+
if (event.type !== "user_input" || typeof event.ts !== "string") continue;
|
|
1510
|
+
const text = contentToText(event.content).trim();
|
|
1511
|
+
if (!text) continue;
|
|
1512
|
+
const hit = { ts: event.ts, text };
|
|
1513
|
+
if (!first) first = hit;
|
|
1514
|
+
last = hit;
|
|
1315
1515
|
} catch {
|
|
1316
1516
|
}
|
|
1317
|
-
};
|
|
1318
|
-
if (!state.activeSessionStore) {
|
|
1319
|
-
return fail("no_session_store", "No session store is bound to this project.");
|
|
1320
1517
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1518
|
+
return { first, last };
|
|
1519
|
+
}
|
|
1520
|
+
async function firstUserInput(file) {
|
|
1521
|
+
let handle;
|
|
1323
1522
|
try {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1523
|
+
handle = await fs3.open(file, "r");
|
|
1524
|
+
const { size } = await handle.stat();
|
|
1525
|
+
const length = Math.min(SLICE_BYTES, size);
|
|
1526
|
+
const buffer = Buffer.alloc(length);
|
|
1527
|
+
await handle.read(buffer, 0, length, 0);
|
|
1528
|
+
const text = buffer.toString("utf8");
|
|
1529
|
+
const cutoff = text.lastIndexOf("\n");
|
|
1530
|
+
return scanUserInputs(cutoff > 0 ? text.slice(0, cutoff) : text).first;
|
|
1531
|
+
} catch {
|
|
1532
|
+
return void 0;
|
|
1533
|
+
} finally {
|
|
1534
|
+
await handle?.close().catch(() => void 0);
|
|
1330
1535
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1536
|
+
}
|
|
1537
|
+
async function lastUserInput(file) {
|
|
1538
|
+
let handle;
|
|
1333
1539
|
try {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
);
|
|
1347
|
-
return fail(stage, toErrorMessage(err));
|
|
1540
|
+
handle = await fs3.open(file, "r");
|
|
1541
|
+
const { size } = await handle.stat();
|
|
1542
|
+
const start = Math.max(0, size - SLICE_BYTES);
|
|
1543
|
+
const length = size - start;
|
|
1544
|
+
const buffer = Buffer.alloc(length);
|
|
1545
|
+
await handle.read(buffer, 0, length, start);
|
|
1546
|
+
const text = buffer.toString("utf8");
|
|
1547
|
+
const from = text.indexOf("\n");
|
|
1548
|
+
return scanUserInputs(from >= 0 ? text.slice(from + 1) : text).last;
|
|
1549
|
+
} catch {
|
|
1550
|
+
return void 0;
|
|
1551
|
+
} finally {
|
|
1552
|
+
await handle?.close().catch(() => void 0);
|
|
1348
1553
|
}
|
|
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);
|
|
1554
|
+
}
|
|
1555
|
+
function effectiveLastActivity(s) {
|
|
1556
|
+
return s.lastActivityAt ?? s.endedAt ?? s.startedAt ?? "";
|
|
1557
|
+
}
|
|
1558
|
+
function hasActivity(s) {
|
|
1559
|
+
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());
|
|
1560
|
+
}
|
|
1561
|
+
async function selectPickerSessions(storeList, sessionsDir, limit) {
|
|
1562
|
+
const poolSize = Math.max(limit, Math.min(PICKER_POOL_CEILING, limit * 3));
|
|
1563
|
+
const enriched = await enrichStubSummaries(await storeList(poolSize), sessionsDir);
|
|
1564
|
+
return enriched.filter(hasActivity).sort((a, b) => effectiveLastActivity(b).localeCompare(effectiveLastActivity(a))).slice(0, limit);
|
|
1565
|
+
}
|
|
1566
|
+
async function enrichStubSummaries(summaries, sessionsDir) {
|
|
1567
|
+
const out = [...summaries];
|
|
1568
|
+
const pending = summaries.reduce((indexes, summary, index) => {
|
|
1569
|
+
if (isStubSummary(summary)) indexes.push(index);
|
|
1570
|
+
return indexes;
|
|
1571
|
+
}, []);
|
|
1572
|
+
if (pending.length === 0) return out;
|
|
1573
|
+
let cursor = 0;
|
|
1574
|
+
const worker = async () => {
|
|
1575
|
+
for (; ; ) {
|
|
1576
|
+
const slot = cursor++;
|
|
1577
|
+
if (slot >= pending.length) return;
|
|
1578
|
+
const index = pending[slot];
|
|
1579
|
+
const summary = out[index];
|
|
1580
|
+
const file = containedJsonlPath(sessionsDir, summary.id);
|
|
1581
|
+
if (!file) continue;
|
|
1502
1582
|
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
|
-
};
|
|
1583
|
+
const stat3 = await fs3.stat(file);
|
|
1584
|
+
const [first, last] = await Promise.all([firstUserInput(file), lastUserInput(file)]);
|
|
1585
|
+
const title = truncate(first?.text ?? "");
|
|
1586
|
+
out[index] = {
|
|
1587
|
+
...summary,
|
|
1588
|
+
...title ? { title } : {},
|
|
1589
|
+
...last ? { lastUserMessage: truncate(last.text) } : {},
|
|
1590
|
+
lastActivityAt: stat3.mtime.toISOString()
|
|
1591
|
+
};
|
|
1837
1592
|
} catch {
|
|
1838
1593
|
}
|
|
1839
1594
|
}
|
|
@@ -1919,6 +1674,7 @@ function createSettingsAdapter(ctx) {
|
|
|
1919
1674
|
featureMemory: cfg.features?.memory !== false,
|
|
1920
1675
|
featureSkills: cfg.features?.skills !== false,
|
|
1921
1676
|
featureModelsRegistry: cfg.features?.modelsRegistry !== false,
|
|
1677
|
+
featureToolCoach: cfg.features?.toolCoach !== false,
|
|
1922
1678
|
// Preserve the 'auto' sentinel for the picker DISPLAY (normalize would
|
|
1923
1679
|
// collapse it to 'off', which would then overwrite 'auto' on save);
|
|
1924
1680
|
// everything else normalizes to a concrete tier.
|
|
@@ -1989,7 +1745,7 @@ function createSettingsAdapter(ctx) {
|
|
|
1989
1745
|
}
|
|
1990
1746
|
async function saveSettings(s) {
|
|
1991
1747
|
try {
|
|
1992
|
-
if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.preRefineSeconds !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.toolResultViewMode !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSidebar !== void 0 || s.panelPositions !== void 0 || s.lastSettingsField !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0 || // WrongProxy / WrongTrace: gate the persisted-section write on
|
|
1748
|
+
if (s.mode !== void 0 || s.delayMs !== void 0 || s.titleAnimation !== void 0 || s.yolo !== void 0 || s.fleetChatVerbosity !== void 0 || s.chime !== void 0 || s.confirmExit !== void 0 || s.mouseMode !== void 0 || s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureToolCoach !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 || s.maxConcurrent !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.shellBangWarningDontShowAgain !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0 || s.enhanceEnabled !== void 0 || s.preRefineSeconds !== void 0 || s.enhanceLanguage !== void 0 || s.midRunSendPicker !== void 0 || s.statuslineMode !== void 0 || s.thinkingWord !== void 0 || s.animationStyle !== void 0 || s.autonomyNextPrompt !== void 0 || s.autoProceedMaxIterations !== void 0 || s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 || s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 || s.showModelReasoning !== void 0 || s.toolResultViewMode !== void 0 || s.showAgentSwarmPanel !== void 0 || s.showSidebar !== void 0 || s.panelPositions !== void 0 || s.lastSettingsField !== void 0 || s.showSageMemoryInject !== void 0 || s.sageMemoryInjectThreshold !== void 0 || s.readSymbols !== void 0 || // WrongProxy / WrongTrace: gate the persisted-section write on
|
|
1993
1749
|
// either key being present in the live patch. Without this, a
|
|
1994
1750
|
// picker toggle round-trip would silently no-op the persistence
|
|
1995
1751
|
// layer (the runtime probe would never read the change).
|
|
@@ -2052,7 +1808,7 @@ function createSettingsAdapter(ctx) {
|
|
|
2052
1808
|
if (s.nextPrediction !== void 0) decrypted.nextPrediction = s.nextPrediction;
|
|
2053
1809
|
if (s.yolo !== void 0) decrypted.yolo = s.yolo;
|
|
2054
1810
|
fsAccess = deriveFsAccess(s);
|
|
2055
|
-
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0) {
|
|
1811
|
+
if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureToolCoach !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0) {
|
|
2056
1812
|
const feats = decrypted.features ?? {};
|
|
2057
1813
|
if (s.featureMcp !== void 0) feats.mcp = s.featureMcp;
|
|
2058
1814
|
if (s.featurePlugins !== void 0) feats.plugins = s.featurePlugins;
|
|
@@ -2060,6 +1816,7 @@ function createSettingsAdapter(ctx) {
|
|
|
2060
1816
|
if (s.featureSkills !== void 0) feats.skills = s.featureSkills;
|
|
2061
1817
|
if (s.featureModelsRegistry !== void 0)
|
|
2062
1818
|
feats.modelsRegistry = s.featureModelsRegistry;
|
|
1819
|
+
if (s.featureToolCoach !== void 0) feats.toolCoach = s.featureToolCoach;
|
|
2063
1820
|
if (s.featureTokenSaving !== void 0) feats.tokenSavingMode = s.featureTokenSaving;
|
|
2064
1821
|
if (fsAccess !== void 0)
|
|
2065
1822
|
feats.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
|
|
@@ -2141,1171 +1898,1689 @@ function createSettingsAdapter(ctx) {
|
|
|
2141
1898
|
sageSec.inject = inject;
|
|
2142
1899
|
decrypted.Sage = sageSec;
|
|
2143
1900
|
}
|
|
2144
|
-
if (s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0) {
|
|
2145
|
-
const cb = decrypted.circuitBreaker ?? {};
|
|
2146
|
-
if (s.breakerEnabled !== void 0) cb.enabled = s.breakerEnabled;
|
|
2147
|
-
if (s.breakerAutoKillResetMs !== void 0)
|
|
2148
|
-
cb.autoKillResetMs = s.breakerAutoKillResetMs;
|
|
2149
|
-
decrypted.circuitBreaker = cb;
|
|
1901
|
+
if (s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0) {
|
|
1902
|
+
const cb = decrypted.circuitBreaker ?? {};
|
|
1903
|
+
if (s.breakerEnabled !== void 0) cb.enabled = s.breakerEnabled;
|
|
1904
|
+
if (s.breakerAutoKillResetMs !== void 0)
|
|
1905
|
+
cb.autoKillResetMs = s.breakerAutoKillResetMs;
|
|
1906
|
+
decrypted.circuitBreaker = cb;
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
const effectiveScope = s.configScope ?? cfg.configScope;
|
|
1910
|
+
const hasProfileOnlySettings = s.mode !== void 0 || s.yolo !== void 0 || fsAccess !== void 0 || s.maxIterations !== void 0 || s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0;
|
|
1911
|
+
if (effectiveScope === "project" && hasProfileOnlySettings) {
|
|
1912
|
+
await persistConfigSetting(
|
|
1913
|
+
{ ...persistDeps, forceGlobal: true, updateStore: false },
|
|
1914
|
+
(profileConfig) => {
|
|
1915
|
+
if (s.mode !== void 0 || s.yolo !== void 0) {
|
|
1916
|
+
const autonomy = profileConfig.autonomy ?? {};
|
|
1917
|
+
if (s.mode !== void 0) autonomy.defaultMode = s.mode;
|
|
1918
|
+
if (s.yolo !== void 0) autonomy.yolo = s.yolo;
|
|
1919
|
+
profileConfig.autonomy = autonomy;
|
|
1920
|
+
}
|
|
1921
|
+
if (s.yolo !== void 0) profileConfig.yolo = s.yolo;
|
|
1922
|
+
if (fsAccess !== void 0) {
|
|
1923
|
+
const features = profileConfig.features ?? {};
|
|
1924
|
+
features.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
|
|
1925
|
+
profileConfig.features = features;
|
|
1926
|
+
}
|
|
1927
|
+
if (fsAccess !== void 0 || s.maxIterations !== void 0 || s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0) {
|
|
1928
|
+
const tools = profileConfig.tools ?? {};
|
|
1929
|
+
if (fsAccess !== void 0)
|
|
1930
|
+
tools.restrictToProjectRoot = fsAccess.restrictToProjectRoot;
|
|
1931
|
+
if (s.maxIterations !== void 0) tools.maxIterations = s.maxIterations;
|
|
1932
|
+
if (s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0) {
|
|
1933
|
+
const wrongProxy = tools.wrongProxy ?? {};
|
|
1934
|
+
if (s.wrongProxyEnabled !== void 0) wrongProxy.enabled = s.wrongProxyEnabled;
|
|
1935
|
+
if (s.wrongProxyUrl !== void 0) wrongProxy.url = s.wrongProxyUrl;
|
|
1936
|
+
tools.wrongProxy = wrongProxy;
|
|
1937
|
+
}
|
|
1938
|
+
profileConfig.tools = tools;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
if (s.debugStream !== void 0) {
|
|
1944
|
+
const { setDebugStreamEnabled } = await import("@wrongstack/providers");
|
|
1945
|
+
setDebugStreamEnabled(s.debugStream);
|
|
1946
|
+
}
|
|
1947
|
+
const currentConfig = configStore.get();
|
|
1948
|
+
const nextModelRuntime = {
|
|
1949
|
+
...currentConfig.modelRuntime,
|
|
1950
|
+
...decrypted.modelRuntime ?? {}
|
|
1951
|
+
};
|
|
1952
|
+
if (s.cacheTtl === "default") {
|
|
1953
|
+
delete nextModelRuntime.cache;
|
|
1954
|
+
}
|
|
1955
|
+
configStore.update({
|
|
1956
|
+
...s.nextPrediction !== void 0 ? { nextPrediction: s.nextPrediction } : {},
|
|
1957
|
+
...s.yolo !== void 0 ? { yolo: s.yolo } : {},
|
|
1958
|
+
...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureToolCoach !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0 ? {
|
|
1959
|
+
features: {
|
|
1960
|
+
...currentConfig.features,
|
|
1961
|
+
...decrypted.features ?? {}
|
|
1962
|
+
}
|
|
1963
|
+
} : {},
|
|
1964
|
+
...s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 ? {
|
|
1965
|
+
context: {
|
|
1966
|
+
...currentConfig.context,
|
|
1967
|
+
...decrypted.context ?? {}
|
|
1968
|
+
}
|
|
1969
|
+
} : {},
|
|
1970
|
+
...s.maxConcurrent !== void 0 ? { maxConcurrent: s.maxConcurrent } : {},
|
|
1971
|
+
...s.logLevel !== void 0 ? {
|
|
1972
|
+
log: {
|
|
1973
|
+
...currentConfig.log,
|
|
1974
|
+
...decrypted.log ?? {}
|
|
1975
|
+
}
|
|
1976
|
+
} : {},
|
|
1977
|
+
...s.auditLevel !== void 0 ? {
|
|
1978
|
+
session: {
|
|
1979
|
+
...currentConfig.session,
|
|
1980
|
+
...decrypted.session ?? {}
|
|
1981
|
+
}
|
|
1982
|
+
} : {},
|
|
1983
|
+
...s.indexOnStart !== void 0 ? {
|
|
1984
|
+
indexing: {
|
|
1985
|
+
...currentConfig.indexing,
|
|
1986
|
+
...decrypted.indexing ?? {}
|
|
1987
|
+
}
|
|
1988
|
+
} : {},
|
|
1989
|
+
...s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || fsAccess !== void 0 || // WrongProxy / WrongTrace: must be in the tools guard or the
|
|
1990
|
+
// in-memory ConfigStore never sees the freshly-saved values,
|
|
1991
|
+
// and the next picker open in the same process would overwrite
|
|
1992
|
+
// the on-disk selection with stale state. See Chimera review.
|
|
1993
|
+
s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0 ? {
|
|
1994
|
+
tools: {
|
|
1995
|
+
...currentConfig.tools,
|
|
1996
|
+
...decrypted.tools ?? {}
|
|
1997
|
+
}
|
|
1998
|
+
} : {},
|
|
1999
|
+
...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
|
|
2000
|
+
...s.configScope !== void 0 ? { configScope: s.configScope } : {},
|
|
2001
|
+
autonomy: {
|
|
2002
|
+
...currentConfig.autonomy,
|
|
2003
|
+
...decrypted.autonomy ?? {}
|
|
2004
|
+
},
|
|
2005
|
+
...s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 ? {
|
|
2006
|
+
modelRuntime: nextModelRuntime
|
|
2007
|
+
} : {},
|
|
2008
|
+
...s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 ? {
|
|
2009
|
+
circuitBreaker: {
|
|
2010
|
+
...currentConfig.circuitBreaker,
|
|
2011
|
+
...decrypted.circuitBreaker ?? {}
|
|
2012
|
+
}
|
|
2013
|
+
} : {},
|
|
2014
|
+
...s.sageMemoryInjectThreshold !== void 0 ? {
|
|
2015
|
+
Sage: {
|
|
2016
|
+
...currentConfig.Sage,
|
|
2017
|
+
...decrypted.Sage ?? {},
|
|
2018
|
+
inject: {
|
|
2019
|
+
...currentConfig.Sage?.inject ?? {},
|
|
2020
|
+
...decrypted.Sage?.inject ?? {}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
} : {}
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
if (s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0) {
|
|
2027
|
+
getProcessRegistry().setBreakerConfig({
|
|
2028
|
+
...s.breakerEnabled !== void 0 ? { enabled: s.breakerEnabled } : {},
|
|
2029
|
+
...s.breakerAutoKillResetMs !== void 0 ? { autoKillResetMs: s.breakerAutoKillResetMs } : {}
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
if (s.fleetChatVerbosity !== void 0) {
|
|
2033
|
+
if (fleetStreamController?.setMode) fleetStreamController.setMode(s.fleetChatVerbosity);
|
|
2034
|
+
}
|
|
2035
|
+
applyLiveSettings?.(s);
|
|
2036
|
+
return null;
|
|
2037
|
+
} catch (err) {
|
|
2038
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2039
|
+
console.debug(
|
|
2040
|
+
JSON.stringify({
|
|
2041
|
+
level: "error",
|
|
2042
|
+
event: "execution.settings_persist_failed",
|
|
2043
|
+
message,
|
|
2044
|
+
errorName: err instanceof Error ? err.name : void 0,
|
|
2045
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2046
|
+
})
|
|
2047
|
+
);
|
|
2048
|
+
return message;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return { getSettings, saveSettings };
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
// src/boot/tui-theme-adapter.ts
|
|
2055
|
+
import * as fs4 from "node:fs/promises";
|
|
2056
|
+
import * as path8 from "node:path";
|
|
2057
|
+
import { THEME_PRESET_IDS } from "@wrongstack/core/types";
|
|
2058
|
+
import { updateJsonObjectFile } from "@wrongstack/core/utils";
|
|
2059
|
+
var VALID_PRESETS = new Set(THEME_PRESET_IDS);
|
|
2060
|
+
function isThemePresetId(candidate) {
|
|
2061
|
+
return VALID_PRESETS.has(candidate);
|
|
2062
|
+
}
|
|
2063
|
+
function createThemeAdapter({ configStore, wpaths }) {
|
|
2064
|
+
return {
|
|
2065
|
+
getThemePreset: () => {
|
|
2066
|
+
const live = configStore.get();
|
|
2067
|
+
const candidate = live.themePreset;
|
|
2068
|
+
return candidate && isThemePresetId(candidate) ? candidate : void 0;
|
|
2069
|
+
},
|
|
2070
|
+
saveThemePreset: async (preset) => {
|
|
2071
|
+
if (!isThemePresetId(preset)) {
|
|
2072
|
+
throw new Error(`Unknown theme preset: ${preset}`);
|
|
2073
|
+
}
|
|
2074
|
+
const configPath = activeProfileConfigPath(wpaths, configStore.get());
|
|
2075
|
+
await fs4.mkdir(path8.dirname(configPath), { recursive: true });
|
|
2076
|
+
await updateJsonObjectFile(configPath, async (config) => {
|
|
2077
|
+
await backupCurrent(void 0, configPath);
|
|
2078
|
+
config.themePreset = preset;
|
|
2079
|
+
});
|
|
2080
|
+
configStore.update({ themePreset: preset });
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// src/boot/worktree-sessions.ts
|
|
2086
|
+
import * as path9 from "node:path";
|
|
2087
|
+
function samePath(a, b) {
|
|
2088
|
+
const ra = path9.resolve(a);
|
|
2089
|
+
const rb = path9.resolve(b);
|
|
2090
|
+
return process.platform === "win32" ? ra.toLowerCase() === rb.toLowerCase() : ra === rb;
|
|
2091
|
+
}
|
|
2092
|
+
function worktreeOfSession(summary, worktrees) {
|
|
2093
|
+
const checkout = summary.checkout;
|
|
2094
|
+
if (!checkout) return void 0;
|
|
2095
|
+
const wt = worktrees.find((w) => samePath(w.root, checkout));
|
|
2096
|
+
if (!wt || wt.current) return void 0;
|
|
2097
|
+
return {
|
|
2098
|
+
root: wt.root,
|
|
2099
|
+
name: path9.basename(wt.root) || wt.root,
|
|
2100
|
+
...wt.branch ? { branch: wt.branch } : {}
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
function withWorktreeSwitch(resume, lookup, switchProject) {
|
|
2104
|
+
return async (sessionId, ...rest) => {
|
|
2105
|
+
const target = lookup(sessionId);
|
|
2106
|
+
if (target) {
|
|
2107
|
+
const error = await switchProject(target.root, target.name);
|
|
2108
|
+
if (error) throw new Error(`Could not switch to worktree ${target.name}: ${error}`);
|
|
2109
|
+
}
|
|
2110
|
+
return resume(sessionId, ...rest);
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// src/brain-menu/panel-service.ts
|
|
2115
|
+
import { BUILTIN_COUNCIL_PERSONAS } from "@wrongstack/core/execution";
|
|
2116
|
+
var PERSONA_CATALOG = BUILTIN_COUNCIL_PERSONAS.map((persona) => ({
|
|
2117
|
+
id: persona.id,
|
|
2118
|
+
name: persona.name,
|
|
2119
|
+
description: persona.description,
|
|
2120
|
+
defaultVeto: persona.defaultVeto
|
|
2121
|
+
}));
|
|
2122
|
+
var PERSONA_CYCLE = PERSONA_CATALOG.map((persona) => persona.id);
|
|
2123
|
+
function compactEntry(entry) {
|
|
2124
|
+
return entry.provider ? `${entry.provider}/${entry.model}` : entry.model;
|
|
2125
|
+
}
|
|
2126
|
+
function createBrainPanelHost(deps) {
|
|
2127
|
+
const { brainRuntime } = deps;
|
|
2128
|
+
const apply = async (patch) => {
|
|
2129
|
+
try {
|
|
2130
|
+
const { persisted } = brainRuntime.apply(patch);
|
|
2131
|
+
const result = await persisted;
|
|
2132
|
+
return result.ok ? null : `Applied live but NOT saved: ${result.error ?? "unknown error"}`;
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
return err instanceof Error ? err.message : String(err);
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
const currentPool = () => brainRuntime.getSnapshot().models.map(compactEntry);
|
|
2138
|
+
const currentVoters = () => brainRuntime.getSnapshot().council.voters.map((v) => ({ ...v }));
|
|
2139
|
+
const applyVoters = (voters) => apply({ council: { voters: voters.length > 0 ? voters : null } });
|
|
2140
|
+
return {
|
|
2141
|
+
getSettings() {
|
|
2142
|
+
const snap = brainRuntime.getSnapshot();
|
|
2143
|
+
return {
|
|
2144
|
+
mode: snap.mode,
|
|
2145
|
+
riskLevel: snap.maxAutoRisk,
|
|
2146
|
+
strategy: snap.strategy,
|
|
2147
|
+
decisionTimeoutMs: snap.decisionTimeoutMs,
|
|
2148
|
+
humanTimeoutMs: snap.humanTimeoutMs,
|
|
2149
|
+
pool: snap.models.map(compactEntry),
|
|
2150
|
+
poolResolved: snap.poolLabels,
|
|
2151
|
+
usingSessionModel: snap.usingSessionModel,
|
|
2152
|
+
councilEnabled: snap.council.enabled,
|
|
2153
|
+
councilMinRisk: snap.council.minRisk,
|
|
2154
|
+
voters: snap.council.voters.map((v) => ({
|
|
2155
|
+
label: compactEntry(v),
|
|
2156
|
+
persona: v.persona,
|
|
2157
|
+
veto: v.veto,
|
|
2158
|
+
weight: v.weight
|
|
2159
|
+
})),
|
|
2160
|
+
councilSeats: snap.councilLabels,
|
|
2161
|
+
personaCatalog: PERSONA_CATALOG.map((persona) => ({ ...persona })),
|
|
2162
|
+
councilQuorum: snap.council.quorum,
|
|
2163
|
+
councilApproval: snap.council.approval,
|
|
2164
|
+
councilDistinctness: snap.council.distinctness,
|
|
2165
|
+
councilPerCallTimeoutMs: snap.council.perCallTimeoutMs,
|
|
2166
|
+
councilMaxConcurrency: snap.council.maxConcurrency,
|
|
2167
|
+
councilVoterMaxTokens: snap.council.voterMaxTokens,
|
|
2168
|
+
councilDeliberationRounds: snap.council.deliberationRounds,
|
|
2169
|
+
councilJudgeMaxTokens: snap.council.judgeMaxTokens,
|
|
2170
|
+
// The EFFECTIVE judge, not `council.judge`. The configured one is
|
|
2171
|
+
// usually absent, so the panel used to read "auto" and say nothing
|
|
2172
|
+
// about who actually breaks the panel's ties — including when that is
|
|
2173
|
+
// one of the seats that produced the tie.
|
|
2174
|
+
judgeLabel: snap.judgeLabel,
|
|
2175
|
+
judgeConfigured: snap.council.judge !== void 0,
|
|
2176
|
+
judgeIsVoter: snap.judgeIsVoter,
|
|
2177
|
+
ledgerEnabled: snap.ledger.enabled,
|
|
2178
|
+
autoDenyAfterFailures: snap.ledger.autoDenyAfterFailures,
|
|
2179
|
+
terminalPolicy: snap.terminalPolicy,
|
|
2180
|
+
heuristics: {
|
|
2181
|
+
lowRiskAutoAnswer: snap.heuristics.lowRiskAutoAnswer,
|
|
2182
|
+
blockedResolved: snap.heuristics.blockedResolved,
|
|
2183
|
+
deadlockSkip: snap.heuristics.deadlockSkip,
|
|
2184
|
+
retryExhausted: snap.heuristics.retryExhausted,
|
|
2185
|
+
continuePing: snap.heuristics.continuePing,
|
|
2186
|
+
blockedResolvedMarkers: snap.heuristics.blockedResolvedMarkers
|
|
2187
|
+
},
|
|
2188
|
+
llmMaxTokens: snap.llm.maxTokens,
|
|
2189
|
+
llmRejectUncertain: snap.llm.rejectUncertain,
|
|
2190
|
+
llmMinConfidence: snap.llm.minConfidence,
|
|
2191
|
+
llmDenyIsTerminal: snap.llm.denyIsTerminal,
|
|
2192
|
+
cacheEnabled: snap.cache.enabled,
|
|
2193
|
+
cacheTtlMs: snap.cache.ttlMs,
|
|
2194
|
+
cacheMaxEntries: snap.cache.maxEntries,
|
|
2195
|
+
cacheHits: snap.cache.hits,
|
|
2196
|
+
cacheMisses: snap.cache.misses,
|
|
2197
|
+
cacheSize: snap.cache.size,
|
|
2198
|
+
traceEnabled: snap.trace.enabled,
|
|
2199
|
+
traceContent: snap.trace.content,
|
|
2200
|
+
tracePath: snap.trace.path,
|
|
2201
|
+
circuitState: snap.circuit?.state,
|
|
2202
|
+
circuitFailures: snap.circuit?.consecutiveFailures,
|
|
2203
|
+
ruleCount: snap.rules.length,
|
|
2204
|
+
ruleErrors: [...snap.ruleErrors]
|
|
2205
|
+
};
|
|
2206
|
+
},
|
|
2207
|
+
setMode: (mode) => apply({ mode }),
|
|
2208
|
+
setRisk: (level) => apply({ maxAutoRisk: level }),
|
|
2209
|
+
setStrategy: (strategy) => apply({ strategy }),
|
|
2210
|
+
setDecisionTimeout: (ms) => apply({ decisionTimeoutMs: ms ?? null }),
|
|
2211
|
+
setHumanTimeout: (ms) => apply({ humanTimeoutMs: ms ?? null }),
|
|
2212
|
+
addPoolModel: (providerId, model) => apply({ models: [...currentPool(), `${providerId}/${model}`] }),
|
|
2213
|
+
removePoolModel: (index) => {
|
|
2214
|
+
const next = currentPool().filter((_, i) => i !== index);
|
|
2215
|
+
return apply({ models: next.length > 0 ? next : null });
|
|
2216
|
+
},
|
|
2217
|
+
clearPool: () => apply({ models: null }),
|
|
2218
|
+
setCouncilEnabled: (on) => apply({ council: { enabled: on } }),
|
|
2219
|
+
setCouncilMinRisk: (risk) => apply({ council: { minRisk: risk } }),
|
|
2220
|
+
addVoter: (providerId, model) => {
|
|
2221
|
+
const voters = currentVoters();
|
|
2222
|
+
const persona = PERSONA_CYCLE[voters.length % PERSONA_CYCLE.length];
|
|
2223
|
+
return applyVoters([...voters, { provider: providerId, model, persona }]);
|
|
2224
|
+
},
|
|
2225
|
+
removeVoter: (index) => applyVoters(currentVoters().filter((_, i) => i !== index)),
|
|
2226
|
+
cycleVoterPersona: (index) => {
|
|
2227
|
+
const voters = currentVoters();
|
|
2228
|
+
const voter = voters[index];
|
|
2229
|
+
if (!voter) return Promise.resolve("No such voter.");
|
|
2230
|
+
const at = PERSONA_CYCLE.indexOf(voter.persona ?? "");
|
|
2231
|
+
voter.persona = PERSONA_CYCLE[(at + 1) % PERSONA_CYCLE.length];
|
|
2232
|
+
return applyVoters(voters);
|
|
2233
|
+
},
|
|
2234
|
+
toggleVoterVeto: (index) => {
|
|
2235
|
+
const voters = currentVoters();
|
|
2236
|
+
const voter = voters[index];
|
|
2237
|
+
if (!voter) return Promise.resolve("No such voter.");
|
|
2238
|
+
voter.veto = !voter.veto;
|
|
2239
|
+
return applyVoters(voters);
|
|
2240
|
+
},
|
|
2241
|
+
setJudge: (providerId, model) => apply({ council: { judge: `${providerId}/${model}` } }),
|
|
2242
|
+
clearJudge: () => apply({ council: { judge: null } }),
|
|
2243
|
+
// Resolution + budget knobs. `BrainCouncilPatch` accepts null for the
|
|
2244
|
+
// three integer fields, so `undefined` here really does clear to default.
|
|
2245
|
+
setCouncilQuorum: (fraction) => apply({ council: { quorum: fraction } }),
|
|
2246
|
+
setCouncilApproval: (fraction) => apply({ council: { approval: fraction } }),
|
|
2247
|
+
setCouncilDistinctness: (mode) => apply({ council: { distinctness: mode } }),
|
|
2248
|
+
setCouncilPerCallTimeout: (ms) => apply({ council: { perCallTimeoutMs: ms ?? null } }),
|
|
2249
|
+
setCouncilMaxConcurrency: (count) => apply({ council: { maxConcurrency: count ?? null } }),
|
|
2250
|
+
setCouncilVoterMaxTokens: (tokens) => apply({ council: { voterMaxTokens: tokens ?? null } }),
|
|
2251
|
+
setCouncilDeliberationRounds: (rounds) => apply({ council: { deliberationRounds: rounds ?? null } }),
|
|
2252
|
+
setCouncilJudgeMaxTokens: (tokens) => apply({ council: { judgeMaxTokens: tokens ?? null } }),
|
|
2253
|
+
setLedgerEnabled: (on) => apply({ ledger: { enabled: on } }),
|
|
2254
|
+
setAutoDeny: (count) => apply({ ledger: { autoDenyAfterFailures: count ?? null } }),
|
|
2255
|
+
setTerminalPolicy: (policy) => apply({ terminalPolicy: policy }),
|
|
2256
|
+
// Every sub-block below is MERGED field-by-field by `mergePatch`, so a
|
|
2257
|
+
// single-key patch leaves the operator's other knobs alone.
|
|
2258
|
+
setHeuristic: (key, on) => apply({ heuristics: { [key]: on } }),
|
|
2259
|
+
setLlmMaxTokens: (tokens) => apply({ llm: { maxTokens: tokens ?? null } }),
|
|
2260
|
+
setLlmRejectUncertain: (on) => apply({ llm: { rejectUncertain: on } }),
|
|
2261
|
+
setLlmMinConfidence: (value) => apply({ llm: { minConfidence: value } }),
|
|
2262
|
+
setLlmDenyIsTerminal: (mode) => apply({ llm: { denyIsTerminal: mode } }),
|
|
2263
|
+
setCacheEnabled: (on) => apply({ cache: { enabled: on } }),
|
|
2264
|
+
setCacheTtl: (ms) => apply({ cache: { ttlMs: ms } }),
|
|
2265
|
+
setCacheMaxEntries: (count) => apply({ cache: { maxEntries: count } }),
|
|
2266
|
+
setTraceEnabled: (on) => apply({ trace: { enabled: on } }),
|
|
2267
|
+
setTraceContent: (content) => apply({ trace: { content } })
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/chimera-reviewer-policy.ts
|
|
2272
|
+
import { parseModelRef } from "@wrongstack/core/agent";
|
|
2273
|
+
import {
|
|
2274
|
+
buildReviewerModelPool,
|
|
2275
|
+
selectRoundRobinReviewerAssignment
|
|
2276
|
+
} from "@wrongstack/core/plugin";
|
|
2277
|
+
function resolveReviewerFallbackModels(reviewFallbackModels, sessionRef) {
|
|
2278
|
+
const base = reviewFallbackModels && reviewFallbackModels.length > 0 ? [...reviewFallbackModels] : [];
|
|
2279
|
+
if (sessionRef && !base.includes(sessionRef)) {
|
|
2280
|
+
base.push(sessionRef);
|
|
2281
|
+
}
|
|
2282
|
+
return base;
|
|
2283
|
+
}
|
|
2284
|
+
var reviewerRoundRobinCursor = 0;
|
|
2285
|
+
function __resetReviewerRoundRobinCursor(value = 0) {
|
|
2286
|
+
reviewerRoundRobinCursor = value;
|
|
2287
|
+
}
|
|
2288
|
+
function assignReviewerModels(provider, model, fallbackModels, selection = "round-robin", statusTracker, random = Math.random) {
|
|
2289
|
+
const pool = buildReviewerModelPool(provider, model, fallbackModels, statusTracker);
|
|
2290
|
+
if (pool.length <= 1) {
|
|
2291
|
+
return {
|
|
2292
|
+
provider,
|
|
2293
|
+
model,
|
|
2294
|
+
fallbackModels: [...fallbackModels]
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
const cursor = selection === "random" ? Math.min(pool.length - 1, Math.floor(Math.max(0, random()) * pool.length)) : reviewerRoundRobinCursor;
|
|
2298
|
+
const assignment = selectRoundRobinReviewerAssignment(
|
|
2299
|
+
pool,
|
|
2300
|
+
cursor,
|
|
2301
|
+
provider,
|
|
2302
|
+
model,
|
|
2303
|
+
statusTracker
|
|
2304
|
+
);
|
|
2305
|
+
if (selection === "round-robin") reviewerRoundRobinCursor = assignment.nextCursor;
|
|
2306
|
+
return {
|
|
2307
|
+
provider: assignment.provider,
|
|
2308
|
+
model: assignment.model,
|
|
2309
|
+
fallbackModels: assignment.fallbackModels
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
function assignReviewerModelsRoundRobin(provider, model, fallbackModels, statusTracker) {
|
|
2313
|
+
return assignReviewerModels(provider, model, fallbackModels, "round-robin", statusTracker);
|
|
2314
|
+
}
|
|
2315
|
+
var MAX_REVIEWER_ATTEMPTS = 4;
|
|
2316
|
+
var REVIEWER_ATTEMPT_TIMEOUT_MS = 9e5;
|
|
2317
|
+
var REVIEWER_LADDER_BUDGET_MS = 12e5;
|
|
2318
|
+
var REVIEWER_MIN_ATTEMPT_MS = 12e4;
|
|
2319
|
+
function buildReviewerAttemptLadder(input) {
|
|
2320
|
+
const assignedChain = [...input.assigned.fallbackModels ?? []];
|
|
2321
|
+
const profileChain = [...input.profileChain ?? []];
|
|
2322
|
+
const rungs = [];
|
|
2323
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2324
|
+
const key = (provider, model) => `${provider.trim()}/${model.trim()}`;
|
|
2325
|
+
const push = (provider, model, fallbackModels, tier) => {
|
|
2326
|
+
const p = provider.trim();
|
|
2327
|
+
const m = model.trim();
|
|
2328
|
+
if (!p || !m) return;
|
|
2329
|
+
if (seen.has(key(p, m))) return;
|
|
2330
|
+
seen.add(key(p, m));
|
|
2331
|
+
rungs.push({ provider: p, model: m, fallbackModels, tier });
|
|
2332
|
+
};
|
|
2333
|
+
push(input.assigned.provider, input.assigned.model, [...assignedChain], "primary");
|
|
2334
|
+
for (const [chain, tier] of [
|
|
2335
|
+
[assignedChain, "fallback"],
|
|
2336
|
+
[profileChain, "fallback"]
|
|
2337
|
+
]) {
|
|
2338
|
+
for (let i = 0; i < chain.length; i++) {
|
|
2339
|
+
const parsed = parseModelRef(chain[i]);
|
|
2340
|
+
push(parsed.provider ?? "", parsed.model, chain.slice(i + 1), tier);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
const sessionKey = key(input.session.provider, input.session.model);
|
|
2344
|
+
push(input.session.provider, input.session.model, [], "session");
|
|
2345
|
+
const cap = Math.max(1, Math.floor(input.maxAttempts ?? MAX_REVIEWER_ATTEMPTS));
|
|
2346
|
+
if (rungs.length <= cap) return rungs;
|
|
2347
|
+
const trimmed = rungs.slice(0, cap);
|
|
2348
|
+
if (!trimmed.some((r) => key(r.provider, r.model) === sessionKey)) {
|
|
2349
|
+
const sessionRung = rungs.find((r) => key(r.provider, r.model) === sessionKey);
|
|
2350
|
+
if (sessionRung) trimmed[trimmed.length - 1] = sessionRung;
|
|
2351
|
+
}
|
|
2352
|
+
return trimmed;
|
|
2353
|
+
}
|
|
2354
|
+
var MAX_MUTATING_ATTEMPTS = 2;
|
|
2355
|
+
function buildMutatingAgentLadder(input) {
|
|
2356
|
+
const cap = Math.max(1, Math.floor(input.maxAttempts ?? MAX_MUTATING_ATTEMPTS));
|
|
2357
|
+
const inherit = {
|
|
2358
|
+
provider: "",
|
|
2359
|
+
model: "",
|
|
2360
|
+
fallbackModels: [],
|
|
2361
|
+
tier: "inherit"
|
|
2362
|
+
};
|
|
2363
|
+
if (cap === 1) return [inherit];
|
|
2364
|
+
return [
|
|
2365
|
+
inherit,
|
|
2366
|
+
...buildReviewerAttemptLadder({
|
|
2367
|
+
assigned: { provider: "", model: "" },
|
|
2368
|
+
profileChain: input.profileChain,
|
|
2369
|
+
session: input.session,
|
|
2370
|
+
maxAttempts: cap - 1
|
|
2371
|
+
})
|
|
2372
|
+
];
|
|
2373
|
+
}
|
|
2374
|
+
function shouldAdvanceReviewerLadder(status, error) {
|
|
2375
|
+
if (status === "success") return false;
|
|
2376
|
+
if (status === "stopped") return false;
|
|
2377
|
+
if (error?.kind === "aborted_by_parent") return false;
|
|
2378
|
+
return true;
|
|
2379
|
+
}
|
|
2380
|
+
function reviewerAttemptTimeoutMs(remainingMs, attemptTimeoutMs = REVIEWER_ATTEMPT_TIMEOUT_MS, minAttemptMs = REVIEWER_MIN_ATTEMPT_MS) {
|
|
2381
|
+
if (remainingMs < minAttemptMs) return 0;
|
|
2382
|
+
return Math.min(attemptTimeoutMs, Math.floor(remainingMs));
|
|
2383
|
+
}
|
|
2384
|
+
var CHIMERA_REVIEW_READ_ONLY_TOOLS = Object.freeze([
|
|
2385
|
+
"read",
|
|
2386
|
+
"grep",
|
|
2387
|
+
"glob",
|
|
2388
|
+
"tree",
|
|
2389
|
+
"codebase-stats",
|
|
2390
|
+
"codebase-search",
|
|
2391
|
+
"codebase-skeleton",
|
|
2392
|
+
"codebase-context",
|
|
2393
|
+
"codebase-repo-map",
|
|
2394
|
+
"codebase-incoming-calls",
|
|
2395
|
+
"codebase-outgoing-calls"
|
|
2396
|
+
]);
|
|
2397
|
+
function applyChimeraReviewerReadOnlyPolicy(config) {
|
|
2398
|
+
return {
|
|
2399
|
+
...config,
|
|
2400
|
+
// A security boundary, not an additive default: discard any inherited
|
|
2401
|
+
// mutation tools/capabilities so Chimera reviewers cannot repair files.
|
|
2402
|
+
tools: [...CHIMERA_REVIEW_READ_ONLY_TOOLS],
|
|
2403
|
+
allowedCapabilities: ["fs.read"],
|
|
2404
|
+
worktree: "off",
|
|
2405
|
+
// Mandatory model-driven completion: a reviewer must never be killed by
|
|
2406
|
+
// the wall-clock watchdog. At its deadline (or when the leader's session
|
|
2407
|
+
// ends and `Director.requestFinish()` fires) it receives an in-band
|
|
2408
|
+
// `subagent.finish_requested` notification between tool batches and
|
|
2409
|
+
// finishes its report in its own turn within the granted grace window.
|
|
2410
|
+
// Only if that window also elapses does the terminal stop apply — the
|
|
2411
|
+
// reviewer's bounded maximum lifetime.
|
|
2412
|
+
gracefulFinish: true
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/execution-cleanup.ts
|
|
2417
|
+
async function finalizeExecutionCleanup(input) {
|
|
2418
|
+
const {
|
|
2419
|
+
offStorageObservability,
|
|
2420
|
+
fleetStatusLine,
|
|
2421
|
+
onCoordinatorStop,
|
|
2422
|
+
stats,
|
|
2423
|
+
renderer,
|
|
2424
|
+
detachTodosCheckpoint,
|
|
2425
|
+
mcpRegistry,
|
|
2426
|
+
agent,
|
|
2427
|
+
session,
|
|
2428
|
+
tokenCounter,
|
|
2429
|
+
events,
|
|
2430
|
+
chimeraWork,
|
|
2431
|
+
director,
|
|
2432
|
+
reader
|
|
2433
|
+
} = input;
|
|
2434
|
+
offStorageObservability();
|
|
2435
|
+
fleetStatusLine?.stop();
|
|
2436
|
+
onCoordinatorStop?.();
|
|
2437
|
+
try {
|
|
2438
|
+
stats.render(renderer);
|
|
2439
|
+
} catch (_err) {
|
|
2440
|
+
}
|
|
2441
|
+
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
2442
|
+
try {
|
|
2443
|
+
const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
|
|
2444
|
+
getProcessRegistry2().killAll({ force: true, includeProtected: true });
|
|
2445
|
+
} catch (err) {
|
|
2446
|
+
console.warn(
|
|
2447
|
+
JSON.stringify({
|
|
2448
|
+
level: "warn",
|
|
2449
|
+
event: "shutdown.process_kill_all_failed",
|
|
2450
|
+
message: `Process registry killAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2451
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2452
|
+
})
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
await mcpRegistry.stopAll().catch((err) => {
|
|
2456
|
+
console.warn(
|
|
2457
|
+
JSON.stringify({
|
|
2458
|
+
level: "warn",
|
|
2459
|
+
event: "shutdown.mcp_stop_failed",
|
|
2460
|
+
message: `MCP registry stopAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2461
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2462
|
+
})
|
|
2463
|
+
);
|
|
2464
|
+
});
|
|
2465
|
+
const activeSession = agent.ctx.session ?? session;
|
|
2466
|
+
const pending = activeSession.pendingToolUses;
|
|
2467
|
+
await activeSession.append({
|
|
2468
|
+
type: "session_end",
|
|
2469
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2470
|
+
usage: tokenCounter.total(),
|
|
2471
|
+
pendingToolUses: pending.length > 0 ? pending : void 0
|
|
2472
|
+
}).catch((err) => {
|
|
2473
|
+
console.warn(
|
|
2474
|
+
JSON.stringify({
|
|
2475
|
+
level: "warn",
|
|
2476
|
+
event: "shutdown.session_end_append_failed",
|
|
2477
|
+
message: `session_end append failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2478
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2479
|
+
})
|
|
2480
|
+
);
|
|
2481
|
+
});
|
|
2482
|
+
const sessionEndProducers = /* @__PURE__ */ new Set();
|
|
2483
|
+
events.emit("session.ended", {
|
|
2484
|
+
id: activeSession.id,
|
|
2485
|
+
sessionId: activeSession.id,
|
|
2486
|
+
usage: tokenCounter.total(),
|
|
2487
|
+
waitUntil: (work) => {
|
|
2488
|
+
const tracked = Promise.resolve(work).finally(() => sessionEndProducers.delete(tracked));
|
|
2489
|
+
void tracked.catch(() => void 0);
|
|
2490
|
+
sessionEndProducers.add(tracked);
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
try {
|
|
2494
|
+
const model = agent.ctx.model;
|
|
2495
|
+
if (!model) {
|
|
2496
|
+
console.warn(
|
|
2497
|
+
JSON.stringify({
|
|
2498
|
+
level: "warn",
|
|
2499
|
+
event: "shutdown.wrongtrace_telemetry_skipped",
|
|
2500
|
+
message: "Skipping WrongTrace telemetry: agent model unknown at session end",
|
|
2501
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2502
|
+
})
|
|
2503
|
+
);
|
|
2504
|
+
} else {
|
|
2505
|
+
const { reportWrongTraceSessionTelemetry } = await import("./wrongtrace-telemetry-3ASFKOZO.js");
|
|
2506
|
+
const usage = tokenCounter.total();
|
|
2507
|
+
const report = reportWrongTraceSessionTelemetry({
|
|
2508
|
+
sessionId: activeSession.id,
|
|
2509
|
+
agentName: "wrongstack-cli",
|
|
2510
|
+
model,
|
|
2511
|
+
provider: agent.ctx.provider?.id ?? "",
|
|
2512
|
+
usage: {
|
|
2513
|
+
input: usage.input,
|
|
2514
|
+
output: usage.output,
|
|
2515
|
+
cacheRead: usage.cacheRead,
|
|
2516
|
+
cacheWrite: usage.cacheWrite
|
|
2517
|
+
},
|
|
2518
|
+
costUsd: tokenCounter.estimateCost().total
|
|
2519
|
+
});
|
|
2520
|
+
const deadline = new Promise((resolve5) => {
|
|
2521
|
+
const t = setTimeout(resolve5, 5e3);
|
|
2522
|
+
t.unref?.();
|
|
2523
|
+
});
|
|
2524
|
+
const tracked = Promise.race([report, deadline]).finally(
|
|
2525
|
+
() => sessionEndProducers.delete(tracked)
|
|
2526
|
+
);
|
|
2527
|
+
void tracked.catch(() => void 0);
|
|
2528
|
+
sessionEndProducers.add(tracked);
|
|
2529
|
+
}
|
|
2530
|
+
} catch {
|
|
2531
|
+
}
|
|
2532
|
+
try {
|
|
2533
|
+
const { snapshotGateDecisions, persistWrongTraceGateCounters } = await import("./wrongtrace-gate-counters-7C2EAAZL.js");
|
|
2534
|
+
const projectRoot = agent.ctx.projectRoot;
|
|
2535
|
+
if (projectRoot) {
|
|
2536
|
+
await persistWrongTraceGateCounters(projectRoot, snapshotGateDecisions());
|
|
2537
|
+
}
|
|
2538
|
+
} catch {
|
|
2539
|
+
}
|
|
2540
|
+
if (director) {
|
|
2541
|
+
try {
|
|
2542
|
+
director.requestFinish("leader session ended \u2014 finish your review now");
|
|
2543
|
+
} catch (finishErr) {
|
|
2544
|
+
console.warn(
|
|
2545
|
+
JSON.stringify({
|
|
2546
|
+
level: "warn",
|
|
2547
|
+
event: "shutdown.subagent_finish_notify_failed",
|
|
2548
|
+
message: finishErr instanceof Error ? finishErr.message : String(finishErr),
|
|
2549
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2550
|
+
})
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
try {
|
|
2555
|
+
while (sessionEndProducers.size > 0) {
|
|
2556
|
+
await Promise.allSettled([...sessionEndProducers]);
|
|
2557
|
+
}
|
|
2558
|
+
if (director) {
|
|
2559
|
+
try {
|
|
2560
|
+
director.requestFinish("leader session ended \u2014 finish your review now");
|
|
2561
|
+
} catch {
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
await chimeraWork?.drainAndClose();
|
|
2565
|
+
} catch (err) {
|
|
2566
|
+
console.warn(
|
|
2567
|
+
JSON.stringify({
|
|
2568
|
+
level: "warn",
|
|
2569
|
+
event: "shutdown.chimera_work_failed",
|
|
2570
|
+
message: `Pending chimera work failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2571
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2572
|
+
})
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
if (director) {
|
|
2576
|
+
try {
|
|
2577
|
+
director.requestFinish("leader session ended \u2014 finish your review now");
|
|
2578
|
+
} catch (finishErr) {
|
|
2579
|
+
console.warn(
|
|
2580
|
+
JSON.stringify({
|
|
2581
|
+
level: "warn",
|
|
2582
|
+
event: "shutdown.subagent_finish_notify_failed",
|
|
2583
|
+
message: finishErr instanceof Error ? finishErr.message : String(finishErr),
|
|
2584
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2585
|
+
})
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
try {
|
|
2589
|
+
await director.terminateAll();
|
|
2590
|
+
} catch (termErr) {
|
|
2591
|
+
console.warn(
|
|
2592
|
+
JSON.stringify({
|
|
2593
|
+
level: "warn",
|
|
2594
|
+
event: "shutdown.director_terminate_all_failed",
|
|
2595
|
+
message: termErr instanceof Error ? termErr.message : String(termErr),
|
|
2596
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2597
|
+
})
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
await activeSession.close().catch((err) => {
|
|
2602
|
+
console.warn(
|
|
2603
|
+
JSON.stringify({
|
|
2604
|
+
level: "warn",
|
|
2605
|
+
event: "shutdown.session_close_failed",
|
|
2606
|
+
message: `Session close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2607
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2608
|
+
})
|
|
2609
|
+
);
|
|
2610
|
+
});
|
|
2611
|
+
await reader.close().catch((err) => {
|
|
2612
|
+
console.warn(
|
|
2613
|
+
JSON.stringify({
|
|
2614
|
+
level: "warn",
|
|
2615
|
+
event: "shutdown.reader_close_failed",
|
|
2616
|
+
message: `Input reader close failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2617
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2618
|
+
})
|
|
2619
|
+
);
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/execution-kanban-dispatch.ts
|
|
2624
|
+
import { randomUUID } from "node:crypto";
|
|
2625
|
+
import { missingRequiredRuntimeTools } from "@wrongstack/core/agent-catalog";
|
|
2626
|
+
import { WIDE_SUBAGENT_CAPABILITIES } from "@wrongstack/core/security";
|
|
2627
|
+
import { stripFrontmatter } from "@wrongstack/core/skills";
|
|
2628
|
+
|
|
2629
|
+
// src/kanban-dispatch-route.ts
|
|
2630
|
+
import { fallbackProfileChain, parseModelRef as parseModelRef2 } from "@wrongstack/core/agent";
|
|
2631
|
+
import { resolveTier } from "@wrongstack/core/coordination";
|
|
2632
|
+
function resolveKanbanDispatchRoute(config, spawnOpts) {
|
|
2633
|
+
let provider = spawnOpts?.provider;
|
|
2634
|
+
let model = spawnOpts?.model;
|
|
2635
|
+
let fallbackModels = spawnOpts?.fallbackModels;
|
|
2636
|
+
let tier;
|
|
2637
|
+
if (spawnOpts?.fallbackProfile) {
|
|
2638
|
+
const chain = fallbackProfileChain(config, spawnOpts.fallbackProfile);
|
|
2639
|
+
const primary = chain[0] ? parseModelRef2(chain[0]) : void 0;
|
|
2640
|
+
if (primary?.model) {
|
|
2641
|
+
provider = primary.provider ?? config.provider;
|
|
2642
|
+
model = primary.model;
|
|
2643
|
+
fallbackModels = spawnOpts.fallbackModels ?? chain.slice(1);
|
|
2644
|
+
}
|
|
2645
|
+
} else if (spawnOpts?.tier || spawnOpts?.role) {
|
|
2646
|
+
const resolved = resolveTier(config, {
|
|
2647
|
+
...spawnOpts.tier ? { tier: spawnOpts.tier } : {},
|
|
2648
|
+
...spawnOpts.role ? { role: spawnOpts.role } : {}
|
|
2649
|
+
});
|
|
2650
|
+
if (resolved?.model) {
|
|
2651
|
+
tier = resolved.tier;
|
|
2652
|
+
provider = provider ?? resolved.provider ?? config.provider;
|
|
2653
|
+
model = model ?? resolved.model;
|
|
2654
|
+
fallbackModels = fallbackModels ?? resolved.fallbackModels?.slice() ?? void 0;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return { provider, model, fallbackModels, ...tier ? { tier } : {} };
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
// src/execution-kanban-dispatch.ts
|
|
2661
|
+
function createKanbanDispatchHandler({
|
|
2662
|
+
config,
|
|
2663
|
+
events,
|
|
2664
|
+
skillLoader,
|
|
2665
|
+
sddSubagentFactory
|
|
2666
|
+
}) {
|
|
2667
|
+
if (!sddSubagentFactory) return {};
|
|
2668
|
+
const onKanbanDispatch = async (description, spawnOpts) => {
|
|
2669
|
+
const subagentId = `kanban-${randomUUID().slice(0, 8)}`;
|
|
2670
|
+
const taskId = randomUUID();
|
|
2671
|
+
const name = spawnOpts?.name ?? "kanban-agent";
|
|
2672
|
+
const {
|
|
2673
|
+
provider: resolvedProvider,
|
|
2674
|
+
model: resolvedModel,
|
|
2675
|
+
fallbackModels: resolvedFallbackModels
|
|
2676
|
+
} = resolveKanbanDispatchRoute(config, spawnOpts);
|
|
2677
|
+
let agentDescription = description;
|
|
2678
|
+
if (spawnOpts?.skills?.length && skillLoader) {
|
|
2679
|
+
const availableToolNames = spawnOpts.tools;
|
|
2680
|
+
const loaded = [];
|
|
2681
|
+
for (const skillName of spawnOpts.skills) {
|
|
2682
|
+
try {
|
|
2683
|
+
const manifest = await skillLoader.find(skillName);
|
|
2684
|
+
if (!manifest) {
|
|
2685
|
+
process.emitWarning(`Kanban dispatch skill not found, skipped: ${skillName}`, {
|
|
2686
|
+
code: "WRONGSTACK_KANBAN_SKILL_SKIPPED"
|
|
2687
|
+
});
|
|
2688
|
+
continue;
|
|
2689
|
+
}
|
|
2690
|
+
if (availableToolNames !== void 0 && missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0) {
|
|
2691
|
+
process.emitWarning(
|
|
2692
|
+
`Kanban dispatch skill "${skillName}" needs tools this worker does not have, skipped.`,
|
|
2693
|
+
{ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
|
|
2694
|
+
);
|
|
2695
|
+
continue;
|
|
2150
2696
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
if (s.mode !== void 0) autonomy.defaultMode = s.mode;
|
|
2161
|
-
if (s.yolo !== void 0) autonomy.yolo = s.yolo;
|
|
2162
|
-
profileConfig.autonomy = autonomy;
|
|
2163
|
-
}
|
|
2164
|
-
if (s.yolo !== void 0) profileConfig.yolo = s.yolo;
|
|
2165
|
-
if (fsAccess !== void 0) {
|
|
2166
|
-
const features = profileConfig.features ?? {};
|
|
2167
|
-
features.allowOutsideProjectRoot = fsAccess.allowOutsideProjectRoot;
|
|
2168
|
-
profileConfig.features = features;
|
|
2169
|
-
}
|
|
2170
|
-
if (fsAccess !== void 0 || s.maxIterations !== void 0 || s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0) {
|
|
2171
|
-
const tools = profileConfig.tools ?? {};
|
|
2172
|
-
if (fsAccess !== void 0)
|
|
2173
|
-
tools.restrictToProjectRoot = fsAccess.restrictToProjectRoot;
|
|
2174
|
-
if (s.maxIterations !== void 0) tools.maxIterations = s.maxIterations;
|
|
2175
|
-
if (s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0) {
|
|
2176
|
-
const wrongProxy = tools.wrongProxy ?? {};
|
|
2177
|
-
if (s.wrongProxyEnabled !== void 0) wrongProxy.enabled = s.wrongProxyEnabled;
|
|
2178
|
-
if (s.wrongProxyUrl !== void 0) wrongProxy.url = s.wrongProxyUrl;
|
|
2179
|
-
tools.wrongProxy = wrongProxy;
|
|
2180
|
-
}
|
|
2181
|
-
profileConfig.tools = tools;
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2697
|
+
const body = stripFrontmatter(await skillLoader.readBody(skillName)).trim();
|
|
2698
|
+
if (!body) continue;
|
|
2699
|
+
loaded.push(`## Required skill: ${skillName}
|
|
2700
|
+
|
|
2701
|
+
${body}`);
|
|
2702
|
+
} catch (err) {
|
|
2703
|
+
process.emitWarning(
|
|
2704
|
+
`Kanban dispatch skill "${skillName}" could not be loaded, skipped: ${err instanceof Error ? err.message : String(err)}`,
|
|
2705
|
+
{ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
|
|
2184
2706
|
);
|
|
2185
2707
|
}
|
|
2186
|
-
if (s.debugStream !== void 0) {
|
|
2187
|
-
const { setDebugStreamEnabled } = await import("@wrongstack/providers");
|
|
2188
|
-
setDebugStreamEnabled(s.debugStream);
|
|
2189
|
-
}
|
|
2190
|
-
const currentConfig = configStore.get();
|
|
2191
|
-
const nextModelRuntime = {
|
|
2192
|
-
...currentConfig.modelRuntime,
|
|
2193
|
-
...decrypted.modelRuntime ?? {}
|
|
2194
|
-
};
|
|
2195
|
-
if (s.cacheTtl === "default") {
|
|
2196
|
-
delete nextModelRuntime.cache;
|
|
2197
|
-
}
|
|
2198
|
-
configStore.update({
|
|
2199
|
-
...s.nextPrediction !== void 0 ? { nextPrediction: s.nextPrediction } : {},
|
|
2200
|
-
...s.yolo !== void 0 ? { yolo: s.yolo } : {},
|
|
2201
|
-
...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || fsAccess !== void 0 ? {
|
|
2202
|
-
features: {
|
|
2203
|
-
...currentConfig.features,
|
|
2204
|
-
...decrypted.features ?? {}
|
|
2205
|
-
}
|
|
2206
|
-
} : {},
|
|
2207
|
-
...s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.contextMode !== void 0 ? {
|
|
2208
|
-
context: {
|
|
2209
|
-
...currentConfig.context,
|
|
2210
|
-
...decrypted.context ?? {}
|
|
2211
|
-
}
|
|
2212
|
-
} : {},
|
|
2213
|
-
...s.maxConcurrent !== void 0 ? { maxConcurrent: s.maxConcurrent } : {},
|
|
2214
|
-
...s.logLevel !== void 0 ? {
|
|
2215
|
-
log: {
|
|
2216
|
-
...currentConfig.log,
|
|
2217
|
-
...decrypted.log ?? {}
|
|
2218
|
-
}
|
|
2219
|
-
} : {},
|
|
2220
|
-
...s.auditLevel !== void 0 ? {
|
|
2221
|
-
session: {
|
|
2222
|
-
...currentConfig.session,
|
|
2223
|
-
...decrypted.session ?? {}
|
|
2224
|
-
}
|
|
2225
|
-
} : {},
|
|
2226
|
-
...s.indexOnStart !== void 0 ? {
|
|
2227
|
-
indexing: {
|
|
2228
|
-
...currentConfig.indexing,
|
|
2229
|
-
...decrypted.indexing ?? {}
|
|
2230
|
-
}
|
|
2231
|
-
} : {},
|
|
2232
|
-
...s.maxIterations !== void 0 || s.multiDiffSummaryThreshold !== void 0 || s.nextStepsTool !== void 0 || fsAccess !== void 0 || // WrongProxy / WrongTrace: must be in the tools guard or the
|
|
2233
|
-
// in-memory ConfigStore never sees the freshly-saved values,
|
|
2234
|
-
// and the next picker open in the same process would overwrite
|
|
2235
|
-
// the on-disk selection with stale state. See Chimera review.
|
|
2236
|
-
s.wrongProxyEnabled !== void 0 || s.wrongProxyUrl !== void 0 ? {
|
|
2237
|
-
tools: {
|
|
2238
|
-
...currentConfig.tools,
|
|
2239
|
-
...decrypted.tools ?? {}
|
|
2240
|
-
}
|
|
2241
|
-
} : {},
|
|
2242
|
-
...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
|
|
2243
|
-
...s.configScope !== void 0 ? { configScope: s.configScope } : {},
|
|
2244
|
-
autonomy: {
|
|
2245
|
-
...currentConfig.autonomy,
|
|
2246
|
-
...decrypted.autonomy ?? {}
|
|
2247
|
-
},
|
|
2248
|
-
...s.reasoningMode !== void 0 || s.reasoningEffort !== void 0 || s.reasoningPreserve !== void 0 || s.cacheTtl !== void 0 ? {
|
|
2249
|
-
modelRuntime: nextModelRuntime
|
|
2250
|
-
} : {},
|
|
2251
|
-
...s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0 ? {
|
|
2252
|
-
circuitBreaker: {
|
|
2253
|
-
...currentConfig.circuitBreaker,
|
|
2254
|
-
...decrypted.circuitBreaker ?? {}
|
|
2255
|
-
}
|
|
2256
|
-
} : {},
|
|
2257
|
-
...s.sageMemoryInjectThreshold !== void 0 ? {
|
|
2258
|
-
Sage: {
|
|
2259
|
-
...currentConfig.Sage,
|
|
2260
|
-
...decrypted.Sage ?? {},
|
|
2261
|
-
inject: {
|
|
2262
|
-
...currentConfig.Sage?.inject ?? {},
|
|
2263
|
-
...decrypted.Sage?.inject ?? {}
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
} : {}
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
if (s.breakerEnabled !== void 0 || s.breakerAutoKillResetMs !== void 0) {
|
|
2270
|
-
getProcessRegistry().setBreakerConfig({
|
|
2271
|
-
...s.breakerEnabled !== void 0 ? { enabled: s.breakerEnabled } : {},
|
|
2272
|
-
...s.breakerAutoKillResetMs !== void 0 ? { autoKillResetMs: s.breakerAutoKillResetMs } : {}
|
|
2273
|
-
});
|
|
2274
|
-
}
|
|
2275
|
-
if (s.fleetChatVerbosity !== void 0) {
|
|
2276
|
-
if (fleetStreamController?.setMode) fleetStreamController.setMode(s.fleetChatVerbosity);
|
|
2277
2708
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
} catch (err) {
|
|
2281
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2282
|
-
console.debug(
|
|
2283
|
-
JSON.stringify({
|
|
2284
|
-
level: "error",
|
|
2285
|
-
event: "execution.settings_persist_failed",
|
|
2286
|
-
message,
|
|
2287
|
-
errorName: err instanceof Error ? err.name : void 0,
|
|
2288
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2289
|
-
})
|
|
2290
|
-
);
|
|
2291
|
-
return message;
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
return { getSettings, saveSettings };
|
|
2295
|
-
}
|
|
2709
|
+
if (loaded.length > 0) {
|
|
2710
|
+
agentDescription = `${description}
|
|
2296
2711
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
import { THEME_PRESET_IDS } from "@wrongstack/core/types";
|
|
2301
|
-
import { updateJsonObjectFile } from "@wrongstack/core/utils";
|
|
2302
|
-
var VALID_PRESETS = new Set(THEME_PRESET_IDS);
|
|
2303
|
-
function createThemeAdapter({ configStore, wpaths }) {
|
|
2304
|
-
return {
|
|
2305
|
-
getThemePreset: () => {
|
|
2306
|
-
const live = configStore.get();
|
|
2307
|
-
const candidate = live.themePreset;
|
|
2308
|
-
return candidate && VALID_PRESETS.has(candidate) ? candidate : void 0;
|
|
2309
|
-
},
|
|
2310
|
-
saveThemePreset: async (preset) => {
|
|
2311
|
-
if (!VALID_PRESETS.has(preset)) {
|
|
2312
|
-
throw new Error(`Unknown theme preset: ${preset}`);
|
|
2712
|
+
# Required agentic skill instructions
|
|
2713
|
+
|
|
2714
|
+
${loaded.join("\n\n")}`;
|
|
2313
2715
|
}
|
|
2314
|
-
const configPath = activeProfileConfigPath(wpaths, configStore.get());
|
|
2315
|
-
await fs4.mkdir(path9.dirname(configPath), { recursive: true });
|
|
2316
|
-
await updateJsonObjectFile(configPath, async (config) => {
|
|
2317
|
-
await backupCurrent(void 0, configPath);
|
|
2318
|
-
config.themePreset = preset;
|
|
2319
|
-
});
|
|
2320
|
-
configStore.update({ themePreset: preset });
|
|
2321
2716
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2717
|
+
void (async () => {
|
|
2718
|
+
let built;
|
|
2719
|
+
let completionReported = false;
|
|
2720
|
+
try {
|
|
2721
|
+
if (spawnOpts?.signal?.aborted) throw new Error("Kanban dispatch cancelled.");
|
|
2722
|
+
built = await sddSubagentFactory(
|
|
2723
|
+
{
|
|
2724
|
+
id: subagentId,
|
|
2725
|
+
name,
|
|
2726
|
+
role: "kanban-agent",
|
|
2727
|
+
prompt: agentDescription,
|
|
2728
|
+
allowedCapabilities: spawnOpts?.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES,
|
|
2729
|
+
...resolvedProvider ? { provider: resolvedProvider } : {},
|
|
2730
|
+
...resolvedModel ? { model: resolvedModel } : {},
|
|
2731
|
+
...resolvedFallbackModels ? { fallbackModels: resolvedFallbackModels } : {},
|
|
2732
|
+
...spawnOpts?.tools ? { tools: spawnOpts.tools } : {}
|
|
2733
|
+
},
|
|
2734
|
+
...spawnOpts?.context ? [{ id: taskId, description: agentDescription, context: spawnOpts.context }] : []
|
|
2735
|
+
);
|
|
2736
|
+
if (spawnOpts?.signal?.aborted) throw new Error("Kanban dispatch cancelled.");
|
|
2737
|
+
const result = await built.agent.run(
|
|
2738
|
+
agentDescription,
|
|
2739
|
+
...spawnOpts?.signal ? [{ signal: spawnOpts.signal }] : []
|
|
2740
|
+
);
|
|
2741
|
+
completionReported = true;
|
|
2742
|
+
await spawnOpts?.onDone?.({
|
|
2743
|
+
status: result.status === "done" ? "completed" : "failed",
|
|
2744
|
+
result: result.finalText,
|
|
2745
|
+
..."error" in result && result.error?.message ? { error: result.error.message } : {}
|
|
2746
|
+
});
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
if (!completionReported) {
|
|
2749
|
+
await spawnOpts?.onDone?.({
|
|
2750
|
+
status: "failed",
|
|
2751
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
throw err;
|
|
2755
|
+
} finally {
|
|
2756
|
+
await built?.dispose?.();
|
|
2757
|
+
}
|
|
2758
|
+
})().catch((err) => {
|
|
2759
|
+
events.emit("error", {
|
|
2760
|
+
err: err instanceof Error ? err : new Error(String(err)),
|
|
2761
|
+
phase: "kanban.dispatch"
|
|
2762
|
+
});
|
|
2763
|
+
});
|
|
2764
|
+
const tags = [];
|
|
2765
|
+
if (resolvedProvider) tags.push(resolvedProvider);
|
|
2766
|
+
if (resolvedModel) tags.push(resolvedModel);
|
|
2767
|
+
if (resolvedFallbackModels?.length) {
|
|
2768
|
+
tags.push(`fallback=${resolvedFallbackModels.join(",")}`);
|
|
2346
2769
|
}
|
|
2770
|
+
if (spawnOpts?.fallbackProfile) tags.push(`profile=${spawnOpts.fallbackProfile}`);
|
|
2771
|
+
if (spawnOpts?.skills?.length) tags.push(`skills=${spawnOpts.skills.join(",")}`);
|
|
2772
|
+
if (spawnOpts?.name) tags.push(`"${spawnOpts.name}"`);
|
|
2773
|
+
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
2774
|
+
return `Spawned subagent ${subagentId}${tag} for task ${taskId}.`;
|
|
2347
2775
|
};
|
|
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
|
-
};
|
|
2776
|
+
return { onKanbanDispatch };
|
|
2480
2777
|
}
|
|
2481
2778
|
|
|
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;
|
|
2779
|
+
// src/execution-repl-fleet-callbacks.ts
|
|
2780
|
+
function createReplFleetCallbacks({
|
|
2781
|
+
director,
|
|
2782
|
+
getDirector
|
|
2783
|
+
}) {
|
|
2784
|
+
const resolveDirector = () => getDirector?.() ?? director;
|
|
2517
2785
|
return {
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2786
|
+
onInterruptFleet: () => {
|
|
2787
|
+
const dir = resolveDirector();
|
|
2788
|
+
if (!dir) return 0;
|
|
2789
|
+
let killed = 0;
|
|
2790
|
+
for (const sa of dir.status().subagents) {
|
|
2791
|
+
if (sa.status === "running" || sa.status === "idle") {
|
|
2792
|
+
try {
|
|
2793
|
+
void dir.remove(sa.id);
|
|
2794
|
+
killed++;
|
|
2795
|
+
} catch {
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return killed;
|
|
2800
|
+
},
|
|
2801
|
+
onAgentIterationComplete: (tokens) => {
|
|
2802
|
+
const dir = resolveDirector();
|
|
2803
|
+
dir?.setLeaderContextPressure(tokens);
|
|
2804
|
+
}
|
|
2521
2805
|
};
|
|
2522
2806
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2807
|
+
|
|
2808
|
+
// src/fleet-statusline.ts
|
|
2809
|
+
import { color as color5, onResize } from "@wrongstack/core/utils";
|
|
2810
|
+
function fmtElapsed(ms) {
|
|
2811
|
+
const s = Math.floor(ms / 1e3);
|
|
2812
|
+
if (s < 60) return `${s}s`;
|
|
2813
|
+
const m = Math.floor(s / 60);
|
|
2814
|
+
const rem = s % 60;
|
|
2815
|
+
if (m < 60) return `${m}m${rem.toString().padStart(2, "0")}s`;
|
|
2816
|
+
const h = Math.floor(m / 60);
|
|
2817
|
+
return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
|
|
2525
2818
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
const
|
|
2532
|
-
const
|
|
2533
|
-
const
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2819
|
+
function renderFleetLine(states, now, columns, version) {
|
|
2820
|
+
const all = [...states.values()];
|
|
2821
|
+
if (all.length === 0) return "";
|
|
2822
|
+
const running = all.filter((a) => a.status === "running");
|
|
2823
|
+
const done = all.filter((a) => a.status === "done").length;
|
|
2824
|
+
const failed = all.filter((a) => a.status === "failed").length;
|
|
2825
|
+
const versionChip = version ? `${color5.bold("WS")}${color5.dim(` v${version}`)} ${color5.dim("\u2502")} ` : "";
|
|
2826
|
+
const counts = versionChip + `${color5.cyan("\u27F3 fleet")} ${color5.yellow(`\u25B6${running.length}`)} ${color5.green(`\u2713${done}`)}` + (failed > 0 ? ` ${color5.red(`\u2717${failed}`)}` : "");
|
|
2827
|
+
const shown = running.sort((a, b) => a.startedAt - b.startedAt).slice(0, 4).map((a) => {
|
|
2828
|
+
const elapsed = fmtElapsed(Math.max(0, now - a.startedAt));
|
|
2829
|
+
const tool = a.lastTool ? ` ${color5.dim(a.lastTool)}` : "";
|
|
2830
|
+
const ext = a.extensions && a.extensions > 0 ? ` ${color5.yellow(`\u26A1${a.extensions}`)}` : "";
|
|
2831
|
+
return `${color5.bold(a.name)} ${color5.yellow("\u25B6")} ${color5.dim(elapsed)} ${color5.dim(`L${a.iterations}`)} ${color5.dim(`${a.toolCalls}t`)}${tool}${ext}`;
|
|
2832
|
+
});
|
|
2833
|
+
let line = shown.length > 0 ? `${counts} ${color5.dim("\u2502")} ${shown.join(color5.dim(" \xB7 "))}` : counts;
|
|
2834
|
+
const max = Math.max(20, columns - 1);
|
|
2835
|
+
const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2836
|
+
if (visible.length > max) {
|
|
2837
|
+
let count = 0;
|
|
2838
|
+
const out = [];
|
|
2839
|
+
let i = 0;
|
|
2840
|
+
while (i < line.length && count < max - 1) {
|
|
2841
|
+
if (line[i] === "\x1B") {
|
|
2842
|
+
const end = line.indexOf("m", i);
|
|
2843
|
+
if (end !== -1) {
|
|
2844
|
+
out.push(line.slice(i, end + 1));
|
|
2845
|
+
i = end + 1;
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
if (line[i] !== void 0) out.push(line[i]);
|
|
2850
|
+
count++;
|
|
2851
|
+
i++;
|
|
2852
|
+
}
|
|
2853
|
+
line = out.join("") + "\u2026";
|
|
2854
|
+
}
|
|
2855
|
+
return line;
|
|
2856
|
+
}
|
|
2857
|
+
var FleetStatusLine = class {
|
|
2858
|
+
events;
|
|
2859
|
+
out;
|
|
2860
|
+
throttleMs;
|
|
2861
|
+
now;
|
|
2862
|
+
version;
|
|
2863
|
+
states = /* @__PURE__ */ new Map();
|
|
2864
|
+
unsubs = [];
|
|
2865
|
+
active = false;
|
|
2866
|
+
rows = 0;
|
|
2867
|
+
repaintTimer = null;
|
|
2868
|
+
tickTimer = null;
|
|
2869
|
+
deactivateTimer = null;
|
|
2870
|
+
resizeCleanup = null;
|
|
2871
|
+
lastPaint = 0;
|
|
2872
|
+
onResize = () => {
|
|
2873
|
+
if (this.active) {
|
|
2874
|
+
this.rows = this.out.rows ?? 24;
|
|
2875
|
+
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
2876
|
+
this.paint(true);
|
|
2877
|
+
}
|
|
2543
2878
|
};
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2879
|
+
constructor(opts) {
|
|
2880
|
+
this.events = opts.events;
|
|
2881
|
+
this.out = opts.out ?? process.stdout;
|
|
2882
|
+
this.throttleMs = opts.throttleMs ?? 150;
|
|
2883
|
+
this.now = opts.now ?? Date.now;
|
|
2884
|
+
this.version = opts.version;
|
|
2885
|
+
}
|
|
2886
|
+
/** Subscribe to host fleet events. No terminal output until a subagent spawns. */
|
|
2887
|
+
start() {
|
|
2888
|
+
const ensure = (id, name) => {
|
|
2889
|
+
let s = this.states.get(id);
|
|
2890
|
+
if (!s) {
|
|
2891
|
+
s = {
|
|
2892
|
+
id,
|
|
2893
|
+
name: name ?? id,
|
|
2894
|
+
status: "running",
|
|
2895
|
+
iterations: 0,
|
|
2896
|
+
toolCalls: 0,
|
|
2897
|
+
startedAt: this.now()
|
|
2898
|
+
};
|
|
2899
|
+
this.states.set(id, s);
|
|
2900
|
+
} else if (name && s.name === s.id) {
|
|
2901
|
+
s.name = name;
|
|
2902
|
+
}
|
|
2903
|
+
return s;
|
|
2904
|
+
};
|
|
2905
|
+
this.unsubs.push(
|
|
2906
|
+
this.events.on("subagent.spawned", (e) => {
|
|
2907
|
+
ensure(e.subagentId, e.name);
|
|
2908
|
+
this.activate();
|
|
2909
|
+
this.schedulePaint();
|
|
2910
|
+
}),
|
|
2911
|
+
this.events.on("subagent.task_started", (e) => {
|
|
2912
|
+
const s = ensure(e.subagentId);
|
|
2913
|
+
s.status = "running";
|
|
2914
|
+
this.activate();
|
|
2915
|
+
this.schedulePaint();
|
|
2916
|
+
}),
|
|
2917
|
+
this.events.on("subagent.tool_executed", (e) => {
|
|
2918
|
+
const s = ensure(e.subagentId);
|
|
2919
|
+
s.toolCalls++;
|
|
2920
|
+
s.lastTool = e.name;
|
|
2921
|
+
this.activate();
|
|
2922
|
+
this.schedulePaint();
|
|
2923
|
+
}),
|
|
2924
|
+
this.events.on("subagent.iteration_summary", (e) => {
|
|
2925
|
+
const s = ensure(e.subagentId);
|
|
2926
|
+
s.iterations = e.iteration;
|
|
2927
|
+
if (typeof e.toolCalls === "number") s.toolCalls = e.toolCalls;
|
|
2928
|
+
if (e.currentTool) s.lastTool = e.currentTool;
|
|
2929
|
+
this.schedulePaint();
|
|
2930
|
+
}),
|
|
2931
|
+
this.events.on("subagent.budget_extended", (e) => {
|
|
2932
|
+
const s = ensure(e.subagentId);
|
|
2933
|
+
s.extensions = e.totalExtensions;
|
|
2934
|
+
this.activate();
|
|
2935
|
+
this.schedulePaint();
|
|
2936
|
+
}),
|
|
2937
|
+
this.events.on("subagent.task_completed", (e) => {
|
|
2938
|
+
const s = ensure(e.subagentId);
|
|
2939
|
+
s.status = e.status === "success" ? "done" : "failed";
|
|
2940
|
+
s.iterations = e.iterations;
|
|
2941
|
+
s.toolCalls = e.toolCalls;
|
|
2942
|
+
s.endedAt = this.now();
|
|
2943
|
+
this.schedulePaint();
|
|
2944
|
+
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
2945
|
+
if (this.deactivateTimer) clearTimeout(this.deactivateTimer);
|
|
2946
|
+
this.deactivateTimer = setTimeout(() => {
|
|
2947
|
+
this.deactivateTimer = null;
|
|
2948
|
+
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
2949
|
+
this.deactivate();
|
|
2950
|
+
}
|
|
2951
|
+
}, 800);
|
|
2952
|
+
}
|
|
2953
|
+
}),
|
|
2954
|
+
this.events.on("subagent.removed", (e) => {
|
|
2955
|
+
this.states.delete(e.subagentId);
|
|
2956
|
+
this.schedulePaint();
|
|
2957
|
+
})
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
/** Unsubscribe and restore the terminal. Idempotent. */
|
|
2961
|
+
stop() {
|
|
2962
|
+
for (const u of this.unsubs.splice(0)) u();
|
|
2963
|
+
this.deactivate();
|
|
2964
|
+
}
|
|
2965
|
+
isTty() {
|
|
2966
|
+
return !!this.out.isTTY;
|
|
2967
|
+
}
|
|
2968
|
+
activate() {
|
|
2969
|
+
if (this.active || !this.isTty()) return;
|
|
2970
|
+
this.active = true;
|
|
2971
|
+
this.rows = this.out.rows ?? 24;
|
|
2972
|
+
this.out.write("\n");
|
|
2973
|
+
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
2974
|
+
this.out.write(`\x1B[${this.rows - 1};1H`);
|
|
2975
|
+
this.resizeCleanup = onResize(() => this.onResize(), this.out);
|
|
2976
|
+
this.tickTimer = setInterval(() => this.paint(true), 1e3);
|
|
2977
|
+
if (this.tickTimer.unref) this.tickTimer.unref();
|
|
2978
|
+
this.paint(true);
|
|
2979
|
+
}
|
|
2980
|
+
deactivate() {
|
|
2981
|
+
if (!this.active) return;
|
|
2982
|
+
this.active = false;
|
|
2983
|
+
if (this.tickTimer) {
|
|
2984
|
+
clearInterval(this.tickTimer);
|
|
2985
|
+
this.tickTimer = null;
|
|
2986
|
+
}
|
|
2987
|
+
if (this.repaintTimer) {
|
|
2988
|
+
clearTimeout(this.repaintTimer);
|
|
2989
|
+
this.repaintTimer = null;
|
|
2990
|
+
}
|
|
2991
|
+
if (this.deactivateTimer) {
|
|
2992
|
+
clearTimeout(this.deactivateTimer);
|
|
2993
|
+
this.deactivateTimer = null;
|
|
2994
|
+
}
|
|
2995
|
+
this.resizeCleanup?.();
|
|
2996
|
+
this.resizeCleanup = null;
|
|
2997
|
+
if (this.isTty()) {
|
|
2998
|
+
this.out.write("\x1B7");
|
|
2999
|
+
this.out.write(`\x1B[${this.rows};1H`);
|
|
3000
|
+
this.out.write("\x1B[2K");
|
|
3001
|
+
this.out.write("\x1B8");
|
|
3002
|
+
this.out.write("\x1B[r");
|
|
2552
3003
|
}
|
|
2553
3004
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
if (
|
|
3005
|
+
schedulePaint() {
|
|
3006
|
+
if (!this.active) return;
|
|
3007
|
+
const since = this.now() - this.lastPaint;
|
|
3008
|
+
if (since >= this.throttleMs) {
|
|
3009
|
+
this.paint(false);
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
if (this.repaintTimer) return;
|
|
3013
|
+
this.repaintTimer = setTimeout(() => {
|
|
3014
|
+
this.repaintTimer = null;
|
|
3015
|
+
this.paint(false);
|
|
3016
|
+
}, this.throttleMs - since);
|
|
3017
|
+
if (this.repaintTimer.unref) this.repaintTimer.unref();
|
|
2562
3018
|
}
|
|
2563
|
-
|
|
3019
|
+
paint(force) {
|
|
3020
|
+
if (!this.active || !this.isTty()) return;
|
|
3021
|
+
if (!force) this.lastPaint = this.now();
|
|
3022
|
+
else this.lastPaint = this.now();
|
|
3023
|
+
const line = renderFleetLine(this.states, this.now(), this.out.columns ?? 80, this.version);
|
|
3024
|
+
this.out.write("\x1B7");
|
|
3025
|
+
this.out.write(`\x1B[${this.rows};1H`);
|
|
3026
|
+
this.out.write("\x1B[2K");
|
|
3027
|
+
this.out.write(line);
|
|
3028
|
+
this.out.write("\x1B8");
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
|
|
3032
|
+
// src/subagent-models/panel-service.ts
|
|
3033
|
+
import {
|
|
3034
|
+
emptySubagentModelPlan,
|
|
3035
|
+
formatSubagentSlot,
|
|
3036
|
+
getSessionSubagentModelPlan,
|
|
3037
|
+
setSessionSubagentModelPlan,
|
|
3038
|
+
subagentSlotOccupancy
|
|
3039
|
+
} from "@wrongstack/core/coordination";
|
|
3040
|
+
function currentPlan(sessionId) {
|
|
3041
|
+
return getSessionSubagentModelPlan(sessionId) ?? emptySubagentModelPlan();
|
|
2564
3042
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
3043
|
+
function createSubagentModelsPanelHost(deps) {
|
|
3044
|
+
const write = async (mutate) => {
|
|
3045
|
+
const ctx = deps.getContext();
|
|
3046
|
+
if (!ctx?.session) return "No active session \u2014 the plan cannot be saved.";
|
|
3047
|
+
const plan = structuredClone(currentPlan(ctx.session.id));
|
|
3048
|
+
mutate(plan);
|
|
3049
|
+
try {
|
|
3050
|
+
await setSessionSubagentModelPlan(ctx, plan);
|
|
3051
|
+
return null;
|
|
3052
|
+
} catch (err) {
|
|
3053
|
+
return err instanceof Error ? err.message : String(err);
|
|
3054
|
+
}
|
|
2573
3055
|
};
|
|
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
3056
|
return {
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
session,
|
|
2639
|
-
tokenCounter,
|
|
2640
|
-
events,
|
|
2641
|
-
chimeraWork,
|
|
2642
|
-
director,
|
|
2643
|
-
reader
|
|
2644
|
-
} = input;
|
|
2645
|
-
offStorageObservability();
|
|
2646
|
-
fleetStatusLine?.stop();
|
|
2647
|
-
onCoordinatorStop?.();
|
|
2648
|
-
try {
|
|
2649
|
-
stats.render(renderer);
|
|
2650
|
-
} catch (_err) {
|
|
2651
|
-
}
|
|
2652
|
-
await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
|
|
2653
|
-
try {
|
|
2654
|
-
const { getProcessRegistry: getProcessRegistry2 } = await import("@wrongstack/tools");
|
|
2655
|
-
getProcessRegistry2().killAll({ force: true, includeProtected: true });
|
|
2656
|
-
} catch (err) {
|
|
2657
|
-
console.warn(
|
|
2658
|
-
JSON.stringify({
|
|
2659
|
-
level: "warn",
|
|
2660
|
-
event: "shutdown.process_kill_all_failed",
|
|
2661
|
-
message: `Process registry killAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2662
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2663
|
-
})
|
|
2664
|
-
);
|
|
2665
|
-
}
|
|
2666
|
-
await mcpRegistry.stopAll().catch((err) => {
|
|
2667
|
-
console.warn(
|
|
2668
|
-
JSON.stringify({
|
|
2669
|
-
level: "warn",
|
|
2670
|
-
event: "shutdown.mcp_stop_failed",
|
|
2671
|
-
message: `MCP registry stopAll failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2672
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2673
|
-
})
|
|
2674
|
-
);
|
|
2675
|
-
});
|
|
2676
|
-
const activeSession = agent.ctx.session ?? session;
|
|
2677
|
-
const pending = activeSession.pendingToolUses;
|
|
2678
|
-
await activeSession.append({
|
|
2679
|
-
type: "session_end",
|
|
2680
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2681
|
-
usage: tokenCounter.total(),
|
|
2682
|
-
pendingToolUses: pending.length > 0 ? pending : void 0
|
|
2683
|
-
}).catch((err) => {
|
|
2684
|
-
console.warn(
|
|
2685
|
-
JSON.stringify({
|
|
2686
|
-
level: "warn",
|
|
2687
|
-
event: "shutdown.session_end_append_failed",
|
|
2688
|
-
message: `session_end append failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2689
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
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);
|
|
2702
|
-
}
|
|
2703
|
-
});
|
|
2704
|
-
try {
|
|
2705
|
-
const model = agent.ctx.model;
|
|
2706
|
-
if (!model) {
|
|
2707
|
-
console.warn(
|
|
2708
|
-
JSON.stringify({
|
|
2709
|
-
level: "warn",
|
|
2710
|
-
event: "shutdown.wrongtrace_telemetry_skipped",
|
|
2711
|
-
message: "Skipping WrongTrace telemetry: agent model unknown at session end",
|
|
2712
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2713
|
-
})
|
|
2714
|
-
);
|
|
2715
|
-
} else {
|
|
2716
|
-
const { reportWrongTraceSessionTelemetry } = await import("./wrongtrace-telemetry-3ASFKOZO.js");
|
|
2717
|
-
const usage = tokenCounter.total();
|
|
2718
|
-
const report = reportWrongTraceSessionTelemetry({
|
|
2719
|
-
sessionId: activeSession.id,
|
|
2720
|
-
agentName: "wrongstack-cli",
|
|
2721
|
-
model,
|
|
2722
|
-
provider: agent.ctx.provider?.id ?? "",
|
|
2723
|
-
usage: {
|
|
2724
|
-
input: usage.input,
|
|
2725
|
-
output: usage.output,
|
|
2726
|
-
cacheRead: usage.cacheRead,
|
|
2727
|
-
cacheWrite: usage.cacheWrite
|
|
2728
|
-
},
|
|
2729
|
-
costUsd: tokenCounter.estimateCost().total
|
|
3057
|
+
snapshot() {
|
|
3058
|
+
const sessionId = deps.getContext()?.session?.id;
|
|
3059
|
+
const plan = currentPlan(sessionId);
|
|
3060
|
+
const occupancy = subagentSlotOccupancy(sessionId);
|
|
3061
|
+
const session = deps.getSessionTarget?.();
|
|
3062
|
+
return {
|
|
3063
|
+
enabled: plan.enabled,
|
|
3064
|
+
lock: plan.lock,
|
|
3065
|
+
followSessionModel: plan.followSessionModel === true,
|
|
3066
|
+
sessionTarget: session?.provider ? `${session.provider}/${session.model ?? "?"}` : session?.model ?? "",
|
|
3067
|
+
lanes: plan.slots.map((slot, i) => ({
|
|
3068
|
+
target: formatSubagentSlot(slot),
|
|
3069
|
+
busy: occupancy[i]?.subagentIds.length ?? 0,
|
|
3070
|
+
...slot.label ? { label: slot.label } : {}
|
|
3071
|
+
})),
|
|
3072
|
+
roles: Object.entries(plan.roles ?? {}).map(([role, slot]) => ({ role, target: formatSubagentSlot(slot) })).sort((a, b) => a.role.localeCompare(b.role))
|
|
3073
|
+
};
|
|
3074
|
+
},
|
|
3075
|
+
setLane(index, target) {
|
|
3076
|
+
return write((plan) => {
|
|
3077
|
+
if (index < 0 || index >= plan.slots.length) return;
|
|
3078
|
+
const slot = {};
|
|
3079
|
+
if (target.provider) slot.provider = target.provider;
|
|
3080
|
+
if (target.model) slot.model = target.model;
|
|
3081
|
+
if (target.tier) slot.tier = target.tier;
|
|
3082
|
+
if (target.fallbackProfile) slot.fallbackProfile = target.fallbackProfile;
|
|
3083
|
+
if (target.label) slot.label = target.label;
|
|
3084
|
+
plan.slots[index] = slot;
|
|
2730
3085
|
});
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
3086
|
+
},
|
|
3087
|
+
clearLane(index) {
|
|
3088
|
+
return write((plan) => {
|
|
3089
|
+
if (index < 0 || index >= plan.slots.length) return;
|
|
3090
|
+
plan.slots[index] = {};
|
|
3091
|
+
});
|
|
3092
|
+
},
|
|
3093
|
+
toggle(field) {
|
|
3094
|
+
return write((plan) => {
|
|
3095
|
+
plan[field] = plan[field] !== true;
|
|
2734
3096
|
});
|
|
2735
|
-
const tracked = Promise.race([report, deadline]).finally(
|
|
2736
|
-
() => sessionEndProducers.delete(tracked)
|
|
2737
|
-
);
|
|
2738
|
-
void tracked.catch(() => void 0);
|
|
2739
|
-
sessionEndProducers.add(tracked);
|
|
2740
3097
|
}
|
|
2741
|
-
}
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// src/tui-resume-callback.ts
|
|
3102
|
+
import { hasOpenTodos } from "@wrongstack/core/utils";
|
|
3103
|
+
import { parseNextSteps } from "@wrongstack/tools/next-steps";
|
|
3104
|
+
|
|
3105
|
+
// src/boot/tui-session-resume.ts
|
|
3106
|
+
import * as path10 from "node:path";
|
|
3107
|
+
import {
|
|
3108
|
+
restoreSessionSubagentModelPlan,
|
|
3109
|
+
restoreSessionSubagentPolicy
|
|
3110
|
+
} from "@wrongstack/core/coordination";
|
|
3111
|
+
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
3112
|
+
import { restoreSessionPermissionOverrides as restoreSessionPermissionOverrides2 } from "@wrongstack/core/security";
|
|
3113
|
+
import { restoreRequiredSkillsFromEvents as restoreRequiredSkillsFromEvents2 } from "@wrongstack/core/skills";
|
|
3114
|
+
import { attachTodosCheckpoint, loadTodosCheckpoint } from "@wrongstack/core/storage";
|
|
3115
|
+
import { projectLastRequestTokens } from "@wrongstack/core/types/session-timeline";
|
|
3116
|
+
import { sessionScopedPath as sessionScopedPath2, toErrorMessage } from "@wrongstack/core/utils";
|
|
3117
|
+
var RESERVATION_WINDOW_MS = 6e4;
|
|
3118
|
+
var RESERVATION_RENEW_MS = 2e4;
|
|
3119
|
+
function messageText(content) {
|
|
3120
|
+
if (typeof content === "string") return content;
|
|
3121
|
+
if (!Array.isArray(content)) return "";
|
|
3122
|
+
return content.map(
|
|
3123
|
+
(block) => block && typeof block === "object" && "text" in block && typeof block.text === "string" ? block.text : ""
|
|
3124
|
+
).filter(Boolean).join("\n");
|
|
3125
|
+
}
|
|
3126
|
+
function lastAssistantTextOf(messages) {
|
|
3127
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
3128
|
+
const message = messages[index];
|
|
3129
|
+
if (message?.role !== "assistant") continue;
|
|
3130
|
+
const text = messageText(message.content).trim();
|
|
3131
|
+
if (text) return text;
|
|
2742
3132
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
3133
|
+
return void 0;
|
|
3134
|
+
}
|
|
3135
|
+
async function resumeSession(ctx, sessionId) {
|
|
3136
|
+
const { state, agent, tokenCounter, switchProviderAndModel, events, onLoadProgress } = ctx;
|
|
3137
|
+
const fail = (stage2, message) => {
|
|
3138
|
+
try {
|
|
3139
|
+
ctx.onFailure?.({ stage: stage2, message: message || "unknown error" });
|
|
3140
|
+
} catch {
|
|
2748
3141
|
}
|
|
2749
|
-
|
|
2750
|
-
}
|
|
2751
|
-
|
|
3142
|
+
return null;
|
|
3143
|
+
};
|
|
3144
|
+
const warnings = [];
|
|
3145
|
+
const warn = (label, err) => {
|
|
3146
|
+
warnings.push(`${label}: ${toErrorMessage(err)}`);
|
|
3147
|
+
};
|
|
3148
|
+
let stage = "start";
|
|
3149
|
+
const setStage = (next) => {
|
|
3150
|
+
stage = next;
|
|
2752
3151
|
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
|
-
);
|
|
3152
|
+
ctx.onStage?.(next);
|
|
3153
|
+
} catch {
|
|
2763
3154
|
}
|
|
3155
|
+
};
|
|
3156
|
+
if (!state.activeSessionStore) {
|
|
3157
|
+
return fail("no_session_store", "No session store is bound to this project.");
|
|
2764
3158
|
}
|
|
3159
|
+
setStage("load_replay_module");
|
|
3160
|
+
let replaySessionMessages;
|
|
2765
3161
|
try {
|
|
2766
|
-
|
|
2767
|
-
|
|
3162
|
+
({ replaySessionMessages } = await import("@wrongstack/tui"));
|
|
3163
|
+
if (typeof replaySessionMessages !== "function") {
|
|
3164
|
+
throw new TypeError("@wrongstack/tui does not export replaySessionMessages");
|
|
2768
3165
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3166
|
+
} catch (err) {
|
|
3167
|
+
return fail(stage, toErrorMessage(err));
|
|
3168
|
+
}
|
|
3169
|
+
setStage("resolve_id");
|
|
3170
|
+
let canonicalSessionId = sessionId;
|
|
3171
|
+
try {
|
|
3172
|
+
if (state.activeSessionStore.resolveId) {
|
|
3173
|
+
canonicalSessionId = await state.activeSessionStore.resolveId(sessionId);
|
|
2774
3174
|
}
|
|
2775
|
-
await chimeraWork?.drainAndClose();
|
|
2776
3175
|
} catch (err) {
|
|
2777
|
-
console.
|
|
3176
|
+
console.error(
|
|
2778
3177
|
JSON.stringify({
|
|
2779
|
-
level: "
|
|
2780
|
-
event: "
|
|
2781
|
-
|
|
3178
|
+
level: "error",
|
|
3179
|
+
event: "execution.resume_id_resolve_failed",
|
|
3180
|
+
sessionId,
|
|
3181
|
+
message: toErrorMessage(err),
|
|
2782
3182
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2783
3183
|
})
|
|
2784
3184
|
);
|
|
3185
|
+
return fail(stage, toErrorMessage(err));
|
|
2785
3186
|
}
|
|
2786
|
-
|
|
3187
|
+
const previousSessionId = agent.ctx.session?.id;
|
|
3188
|
+
let hydrated;
|
|
3189
|
+
let identityClaimed = false;
|
|
3190
|
+
let resumeClaim;
|
|
3191
|
+
let writerSwapped = false;
|
|
3192
|
+
let openedWriter;
|
|
3193
|
+
let oldWriter;
|
|
3194
|
+
let oldMessages;
|
|
3195
|
+
let oldSessionRefCurrent;
|
|
3196
|
+
let previousDetachFn;
|
|
3197
|
+
let oldTodos = [];
|
|
3198
|
+
let oldPlanPath;
|
|
3199
|
+
let oldTaskPath;
|
|
3200
|
+
try {
|
|
3201
|
+
setStage("reserve_ownership");
|
|
3202
|
+
if (state.wpaths.projectSlug && state.wpaths.globalRoot) {
|
|
3203
|
+
const { getSessionRegistry } = await import("@wrongstack/core/storage");
|
|
3204
|
+
const registry = getSessionRegistry(state.wpaths.globalRoot);
|
|
3205
|
+
resumeClaim = await registry.reserveResume({
|
|
3206
|
+
sessionId: canonicalSessionId,
|
|
3207
|
+
projectSlug: state.wpaths.projectSlug,
|
|
3208
|
+
projectRoot: state.projectRoot,
|
|
3209
|
+
reservationMs: RESERVATION_WINDOW_MS
|
|
3210
|
+
});
|
|
3211
|
+
} else if (state.activateSessionIdentity) {
|
|
3212
|
+
await state.activateSessionIdentity(canonicalSessionId);
|
|
3213
|
+
identityClaimed = true;
|
|
3214
|
+
}
|
|
3215
|
+
setStage("open_journal");
|
|
3216
|
+
const claimToRenew = resumeClaim;
|
|
3217
|
+
let renewFailures = 0;
|
|
3218
|
+
const renew = () => {
|
|
3219
|
+
if (!claimToRenew) return;
|
|
3220
|
+
void claimToRenew.renew(RESERVATION_WINDOW_MS).catch((err) => {
|
|
3221
|
+
if (renewFailures++ > 0) return;
|
|
3222
|
+
console.error(
|
|
3223
|
+
JSON.stringify({
|
|
3224
|
+
level: "warn",
|
|
3225
|
+
event: "execution.resume_reservation_renew_failed",
|
|
3226
|
+
sessionId: canonicalSessionId,
|
|
3227
|
+
message: toErrorMessage(err),
|
|
3228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3229
|
+
})
|
|
3230
|
+
);
|
|
3231
|
+
});
|
|
3232
|
+
};
|
|
3233
|
+
const renewTimer = setInterval(renew, RESERVATION_RENEW_MS);
|
|
3234
|
+
renewTimer.unref?.();
|
|
3235
|
+
let resumed;
|
|
2787
3236
|
try {
|
|
2788
|
-
|
|
2789
|
-
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3237
|
+
resumed = await state.activeSessionStore.resume(canonicalSessionId, onLoadProgress);
|
|
3238
|
+
} finally {
|
|
3239
|
+
clearInterval(renewTimer);
|
|
3240
|
+
}
|
|
3241
|
+
openedWriter = resumed.writer;
|
|
3242
|
+
hydrated = resumed.data;
|
|
3243
|
+
setStage("activate_ownership");
|
|
3244
|
+
await Promise.resolve(resumeClaim?.renew(RESERVATION_WINDOW_MS)).catch(() => void 0);
|
|
3245
|
+
if (resumeClaim) {
|
|
3246
|
+
await resumeClaim.activate({
|
|
3247
|
+
sessionId: canonicalSessionId,
|
|
3248
|
+
projectSlug: state.wpaths.projectSlug,
|
|
3249
|
+
projectRoot: state.projectRoot,
|
|
3250
|
+
projectName: path10.basename(state.projectRoot),
|
|
3251
|
+
workingDir: agent.ctx.workingDir,
|
|
3252
|
+
clientType: "tui",
|
|
3253
|
+
pid: process.pid,
|
|
3254
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3255
|
+
});
|
|
3256
|
+
identityClaimed = true;
|
|
3257
|
+
await state.activateSessionIdentity?.(canonicalSessionId);
|
|
3258
|
+
}
|
|
3259
|
+
setStage("replay_history");
|
|
3260
|
+
const meta = resumed.data.metadata;
|
|
3261
|
+
const entries = replaySessionMessages(
|
|
3262
|
+
resumed.data.messages,
|
|
3263
|
+
resumed.data.events,
|
|
3264
|
+
/* startId */
|
|
3265
|
+
1
|
|
3266
|
+
);
|
|
3267
|
+
try {
|
|
3268
|
+
agent.container?.safeResolve?.(TOKENS2.DelegationTracker)?.rehydrate(canonicalSessionId, resumed.data.events ?? []);
|
|
3269
|
+
} catch (err) {
|
|
3270
|
+
warn("delegation rehydrate", err);
|
|
3271
|
+
}
|
|
3272
|
+
setStage("read_sidecars");
|
|
3273
|
+
const sessionsDir = state.wpaths.projectSessions;
|
|
3274
|
+
const resumedTodosPath = sessionScopedPath2(sessionsDir, resumed.writer.id, ".todos.json");
|
|
3275
|
+
const restoredTodos = await loadTodosCheckpoint(
|
|
3276
|
+
resumedTodosPath,
|
|
3277
|
+
events,
|
|
3278
|
+
agent.ctx.traceId,
|
|
3279
|
+
resumed.writer.id
|
|
3280
|
+
).catch(() => null);
|
|
3281
|
+
setStage("swap_writer");
|
|
3282
|
+
oldWriter = agent.ctx.session;
|
|
3283
|
+
oldMessages = [...agent.ctx.messages];
|
|
3284
|
+
agent.ctx.session = resumed.writer;
|
|
3285
|
+
try {
|
|
3286
|
+
agent.ctx.state.replaceMessages(resumed.data.messages);
|
|
3287
|
+
restoreSessionSubagentPolicy(agent.ctx, resumed.data.events, resumed.data.subagentsAllowed);
|
|
3288
|
+
restoreSessionSubagentModelPlan(agent.ctx, resumed.data.events);
|
|
3289
|
+
restoreSessionPermissionOverrides2(agent.ctx.meta, resumed.data);
|
|
3290
|
+
restoreRequiredSkillsFromEvents2(agent.ctx, resumed.data.events);
|
|
3291
|
+
} catch (err) {
|
|
3292
|
+
agent.ctx.session = oldWriter;
|
|
3293
|
+
agent.ctx.state.replaceMessages(oldMessages);
|
|
3294
|
+
throw err;
|
|
3295
|
+
}
|
|
3296
|
+
writerSwapped = true;
|
|
3297
|
+
if (state.sessionRef) {
|
|
3298
|
+
oldSessionRefCurrent = state.sessionRef.current;
|
|
3299
|
+
state.sessionRef.current = resumed.writer;
|
|
2798
3300
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
console.warn(
|
|
3301
|
+
setStage("flush_journal");
|
|
3302
|
+
await agent.ctx.flushConversationJournal().catch((err) => {
|
|
3303
|
+
console.error(
|
|
2803
3304
|
JSON.stringify({
|
|
2804
|
-
level: "
|
|
2805
|
-
event: "
|
|
2806
|
-
message:
|
|
3305
|
+
level: "error",
|
|
3306
|
+
event: "execution.resume_journal_flush_failed",
|
|
3307
|
+
message: toErrorMessage(err),
|
|
2807
3308
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2808
3309
|
})
|
|
2809
3310
|
);
|
|
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 } : {}
|
|
3311
|
+
warn("conversation journal did not flush", err);
|
|
2860
3312
|
});
|
|
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
|
-
}
|
|
3313
|
+
setStage("repoint_sidecars");
|
|
3314
|
+
previousDetachFn = state.detachActiveTodosCheckpoint;
|
|
3315
|
+
oldTodos = agent.ctx.state.todos ? [...agent.ctx.state.todos] : [];
|
|
3316
|
+
oldPlanPath = agent.ctx.state.meta?.["plan.path"];
|
|
3317
|
+
oldTaskPath = agent.ctx.state.meta?.["task.path"];
|
|
3318
|
+
try {
|
|
3319
|
+
await Promise.resolve(previousDetachFn?.()).catch(() => void 0);
|
|
3320
|
+
agent.ctx.state.setMeta(
|
|
3321
|
+
"plan.path",
|
|
3322
|
+
sessionScopedPath2(sessionsDir, resumed.writer.id, ".plan.json")
|
|
3323
|
+
);
|
|
3324
|
+
agent.ctx.state.setMeta(
|
|
3325
|
+
"task.path",
|
|
3326
|
+
sessionScopedPath2(sessionsDir, resumed.writer.id, ".tasks.json")
|
|
3327
|
+
);
|
|
3328
|
+
agent.ctx.state.replaceTodos(restoredTodos ?? []);
|
|
3329
|
+
try {
|
|
3330
|
+
state.detachActiveTodosCheckpoint = attachTodosCheckpoint(
|
|
3331
|
+
agent.ctx.state,
|
|
3332
|
+
resumedTodosPath,
|
|
3333
|
+
resumed.writer.id,
|
|
3334
|
+
events,
|
|
3335
|
+
agent.ctx.traceId
|
|
3336
|
+
);
|
|
3337
|
+
} catch {
|
|
3338
|
+
state.detachActiveTodosCheckpoint = void 0;
|
|
2919
3339
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
3340
|
+
} catch (err) {
|
|
3341
|
+
warn("todo/plan sidecars were not re-pointed", err);
|
|
3342
|
+
try {
|
|
3343
|
+
state.detachActiveTodosCheckpoint = previousDetachFn;
|
|
3344
|
+
agent.ctx.state.replaceTodos([...oldTodos]);
|
|
3345
|
+
if (oldPlanPath !== void 0) agent.ctx.state.setMeta("plan.path", oldPlanPath);
|
|
3346
|
+
if (oldTaskPath !== void 0) agent.ctx.state.setMeta("task.path", oldTaskPath);
|
|
3347
|
+
} catch {
|
|
2926
3348
|
}
|
|
2927
3349
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3350
|
+
setStage("restore_model");
|
|
3351
|
+
const currentProviderId = agent.ctx.provider.id;
|
|
3352
|
+
const savedProvider = typeof meta.provider === "string" ? meta.provider.trim() : "";
|
|
3353
|
+
const savedModel = typeof meta.model === "string" ? meta.model.trim() : "";
|
|
3354
|
+
const hasSavedRoute = savedProvider.length > 0 && savedProvider.toLowerCase() !== "unknown" && savedModel.length > 0 && savedModel.toLowerCase() !== "unknown";
|
|
3355
|
+
const targetProviderId = hasSavedRoute ? savedProvider : currentProviderId;
|
|
3356
|
+
const targetModel = hasSavedRoute ? savedModel : agent.ctx.model;
|
|
3357
|
+
if (switchProviderAndModel && targetProviderId && (targetProviderId !== currentProviderId || targetModel !== agent.ctx.model)) {
|
|
2931
3358
|
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
|
-
});
|
|
3359
|
+
const error = await Promise.resolve(switchProviderAndModel(targetProviderId, targetModel));
|
|
3360
|
+
if (typeof error === "string" && error)
|
|
3361
|
+
warn(`could not restore ${targetProviderId}/${targetModel}`, error);
|
|
2958
3362
|
} catch (err) {
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
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") {
|
|
3003
|
-
try {
|
|
3004
|
-
void dir.remove(sa.id);
|
|
3005
|
-
killed++;
|
|
3006
|
-
} catch {
|
|
3007
|
-
}
|
|
3008
|
-
}
|
|
3363
|
+
console.error(
|
|
3364
|
+
JSON.stringify({
|
|
3365
|
+
level: "error",
|
|
3366
|
+
event: "execution.resume_model_restore_failed",
|
|
3367
|
+
provider: targetProviderId,
|
|
3368
|
+
model: targetModel,
|
|
3369
|
+
message: toErrorMessage(err),
|
|
3370
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3371
|
+
})
|
|
3372
|
+
);
|
|
3373
|
+
warn(`could not restore ${targetProviderId}/${targetModel}`, err);
|
|
3009
3374
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
onAgentIterationComplete: (tokens) => {
|
|
3013
|
-
const dir = resolveDirector();
|
|
3014
|
-
dir?.setLeaderContextPressure(tokens);
|
|
3375
|
+
} else if (targetModel !== agent.ctx.model) {
|
|
3376
|
+
agent.ctx.model = targetModel;
|
|
3015
3377
|
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
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;
|
|
3378
|
+
setStage("finalize_previous_session");
|
|
3379
|
+
let finalizeOldWriter;
|
|
3380
|
+
if (oldWriter && oldWriter !== resumed.writer) {
|
|
3381
|
+
const staleWriter = oldWriter;
|
|
3382
|
+
const endedUsage = tokenCounter.total();
|
|
3383
|
+
finalizeOldWriter = async () => {
|
|
3384
|
+
let appendOk = false;
|
|
3385
|
+
try {
|
|
3386
|
+
await staleWriter.append({
|
|
3387
|
+
type: "session_end",
|
|
3388
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3389
|
+
usage: endedUsage
|
|
3390
|
+
});
|
|
3391
|
+
appendOk = true;
|
|
3392
|
+
} catch (err) {
|
|
3393
|
+
console.error(
|
|
3394
|
+
JSON.stringify({
|
|
3395
|
+
level: "error",
|
|
3396
|
+
event: "execution.session_end_append_failed",
|
|
3397
|
+
message: toErrorMessage(err),
|
|
3398
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3399
|
+
})
|
|
3400
|
+
);
|
|
3058
3401
|
}
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3402
|
+
if (appendOk) {
|
|
3403
|
+
try {
|
|
3404
|
+
await staleWriter.close();
|
|
3405
|
+
} catch (err) {
|
|
3406
|
+
console.error(
|
|
3407
|
+
JSON.stringify({
|
|
3408
|
+
level: "error",
|
|
3409
|
+
event: "execution.session_close_failed",
|
|
3410
|
+
message: toErrorMessage(err),
|
|
3411
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3412
|
+
})
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3063
3417
|
}
|
|
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);
|
|
3418
|
+
setStage("token_accounting");
|
|
3419
|
+
try {
|
|
3420
|
+
tokenCounter.reset();
|
|
3421
|
+
tokenCounter.account(resumed.data.usage, targetModel, targetProviderId);
|
|
3422
|
+
} catch (err) {
|
|
3423
|
+
warn("token accounting for the resumed session failed", err);
|
|
3088
3424
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
id,
|
|
3104
|
-
name: name ?? id,
|
|
3105
|
-
status: "running",
|
|
3106
|
-
iterations: 0,
|
|
3107
|
-
toolCalls: 0,
|
|
3108
|
-
startedAt: this.now()
|
|
3109
|
-
};
|
|
3110
|
-
this.states.set(id, s);
|
|
3111
|
-
} else if (name && s.name === s.id) {
|
|
3112
|
-
s.name = name;
|
|
3113
|
-
}
|
|
3114
|
-
return s;
|
|
3425
|
+
const resumedRequestTokens = projectLastRequestTokens(resumed.data.events);
|
|
3426
|
+
agent.ctx.lastRequestTokens = resumedRequestTokens;
|
|
3427
|
+
tokenCounter.setCurrentRequestTokens?.(resumedRequestTokens ?? 0);
|
|
3428
|
+
const tokens = resumedRequestTokens ?? 0;
|
|
3429
|
+
const maxContext = agent.ctx.provider?.capabilities?.maxContext ?? 0;
|
|
3430
|
+
const finalText = lastAssistantTextOf(resumed.data.messages);
|
|
3431
|
+
const result = {
|
|
3432
|
+
entries,
|
|
3433
|
+
nextId: entries.length + 1,
|
|
3434
|
+
sessionId: resumed.writer.id,
|
|
3435
|
+
contextSnapshot: { tokens, maxContext },
|
|
3436
|
+
attached: true,
|
|
3437
|
+
warnings,
|
|
3438
|
+
...finalText !== void 0 ? { lastAssistantText: finalText } : {}
|
|
3115
3439
|
};
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
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;
|
|
3440
|
+
if (finalizeOldWriter) void finalizeOldWriter();
|
|
3441
|
+
return result;
|
|
3442
|
+
} catch (err) {
|
|
3443
|
+
if (!identityClaimed) await resumeClaim?.cancel().catch(() => void 0);
|
|
3444
|
+
if (identityClaimed && previousSessionId) {
|
|
3445
|
+
await state.activateSessionIdentity?.(previousSessionId).catch((rollbackErr) => {
|
|
3446
|
+
console.error(
|
|
3447
|
+
JSON.stringify({
|
|
3448
|
+
level: "error",
|
|
3449
|
+
event: "execution.resume_identity_rollback_failed",
|
|
3450
|
+
sessionId: previousSessionId,
|
|
3451
|
+
message: toErrorMessage(rollbackErr),
|
|
3452
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3453
|
+
})
|
|
3454
|
+
);
|
|
3455
|
+
});
|
|
3197
3456
|
}
|
|
3198
|
-
if (
|
|
3199
|
-
|
|
3200
|
-
|
|
3457
|
+
if (!writerSwapped) {
|
|
3458
|
+
await openedWriter?.close().catch(() => void 0);
|
|
3459
|
+
} else {
|
|
3460
|
+
try {
|
|
3461
|
+
if (oldWriter !== void 0) agent.ctx.session = oldWriter;
|
|
3462
|
+
if (state.sessionRef) state.sessionRef.current = oldSessionRefCurrent;
|
|
3463
|
+
if (oldMessages !== void 0) agent.ctx.state.replaceMessages(oldMessages);
|
|
3464
|
+
if (previousDetachFn !== void 0) state.detachActiveTodosCheckpoint = previousDetachFn;
|
|
3465
|
+
agent.ctx.state.replaceTodos([...oldTodos]);
|
|
3466
|
+
if (oldPlanPath !== void 0) agent.ctx.state.setMeta("plan.path", oldPlanPath);
|
|
3467
|
+
if (oldTaskPath !== void 0) agent.ctx.state.setMeta("task.path", oldTaskPath);
|
|
3468
|
+
} catch (rollbackErr) {
|
|
3469
|
+
console.error(
|
|
3470
|
+
JSON.stringify({
|
|
3471
|
+
level: "error",
|
|
3472
|
+
event: "execution.resume_post_swap_rollback_failed",
|
|
3473
|
+
message: toErrorMessage(rollbackErr),
|
|
3474
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3475
|
+
})
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3478
|
+
if (openedWriter && openedWriter !== oldWriter) {
|
|
3479
|
+
await openedWriter.close().catch((closeErr) => {
|
|
3480
|
+
console.error(
|
|
3481
|
+
JSON.stringify({
|
|
3482
|
+
level: "error",
|
|
3483
|
+
event: "execution.resume_opened_writer_close_failed",
|
|
3484
|
+
message: toErrorMessage(closeErr),
|
|
3485
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3486
|
+
})
|
|
3487
|
+
);
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3201
3490
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3491
|
+
const reason = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
3492
|
+
console.error(
|
|
3493
|
+
JSON.stringify({
|
|
3494
|
+
level: "error",
|
|
3495
|
+
event: "execution.resume_session_failed",
|
|
3496
|
+
sessionId: canonicalSessionId,
|
|
3497
|
+
stage,
|
|
3498
|
+
message: reason,
|
|
3499
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3500
|
+
})
|
|
3501
|
+
);
|
|
3502
|
+
const message = toErrorMessage(err);
|
|
3503
|
+
if (err instanceof Error && err.name === "SessionOwnershipConflictError") {
|
|
3504
|
+
return fail(stage, message);
|
|
3205
3505
|
}
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
this.out.write("\x1B[r");
|
|
3506
|
+
let data = hydrated;
|
|
3507
|
+
if (!data) {
|
|
3508
|
+
try {
|
|
3509
|
+
data = await state.activeSessionStore.load(canonicalSessionId, onLoadProgress);
|
|
3510
|
+
} catch {
|
|
3511
|
+
data = void 0;
|
|
3512
|
+
}
|
|
3214
3513
|
}
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3514
|
+
if (data) {
|
|
3515
|
+
try {
|
|
3516
|
+
const entries = replaySessionMessages(
|
|
3517
|
+
data.messages,
|
|
3518
|
+
data.events,
|
|
3519
|
+
/* startId */
|
|
3520
|
+
1
|
|
3521
|
+
);
|
|
3522
|
+
const finalText = lastAssistantTextOf(data.messages);
|
|
3523
|
+
fail(stage, message);
|
|
3524
|
+
return {
|
|
3525
|
+
entries,
|
|
3526
|
+
nextId: entries.length + 1,
|
|
3527
|
+
sessionId: canonicalSessionId,
|
|
3528
|
+
// Deliberately no contextSnapshot: the chip reports the LIVE
|
|
3529
|
+
// window, and the live session is still the one we rolled back to.
|
|
3530
|
+
// Painting the viewed session's fill over it would be a lie.
|
|
3531
|
+
attached: false,
|
|
3532
|
+
warnings: [...warnings, `not attached (at ${stage}): ${message}`],
|
|
3533
|
+
...finalText !== void 0 ? { lastAssistantText: finalText } : {}
|
|
3534
|
+
};
|
|
3535
|
+
} catch (renderErr) {
|
|
3536
|
+
console.error(
|
|
3537
|
+
JSON.stringify({
|
|
3538
|
+
level: "error",
|
|
3539
|
+
event: "execution.resume_readonly_render_failed",
|
|
3540
|
+
sessionId: canonicalSessionId,
|
|
3541
|
+
message: toErrorMessage(renderErr),
|
|
3542
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3543
|
+
})
|
|
3544
|
+
);
|
|
3545
|
+
}
|
|
3222
3546
|
}
|
|
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");
|
|
3547
|
+
return fail(stage, message);
|
|
3240
3548
|
}
|
|
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
3549
|
}
|
|
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
|
-
});
|
|
3550
|
+
|
|
3551
|
+
// src/tui-resume-callback.ts
|
|
3552
|
+
function createTuiResumeCallback({
|
|
3553
|
+
state,
|
|
3554
|
+
agent,
|
|
3555
|
+
tokenCounter,
|
|
3556
|
+
switchProviderAndModel,
|
|
3557
|
+
events
|
|
3558
|
+
}) {
|
|
3559
|
+
return async (sessionId, onLoadProgress, onStage) => {
|
|
3560
|
+
let failure;
|
|
3561
|
+
const result = await resumeSession(
|
|
3562
|
+
{
|
|
3563
|
+
state,
|
|
3564
|
+
agent,
|
|
3565
|
+
tokenCounter,
|
|
3566
|
+
switchProviderAndModel,
|
|
3567
|
+
events,
|
|
3568
|
+
onLoadProgress,
|
|
3569
|
+
onStage,
|
|
3570
|
+
onFailure: (info) => {
|
|
3571
|
+
failure = info;
|
|
3572
|
+
}
|
|
3573
|
+
},
|
|
3574
|
+
sessionId
|
|
3575
|
+
);
|
|
3576
|
+
if (!result) {
|
|
3577
|
+
throw new Error(
|
|
3578
|
+
failure ? `${failure.message} (at ${failure.stage})` : `Session "${sessionId}" could not be resumed.`
|
|
3579
|
+
);
|
|
3308
3580
|
}
|
|
3581
|
+
setAutoSuggestions([]);
|
|
3582
|
+
const resumedNextSteps = result.attached && !hasOpenTodos(agent.ctx.todos) && result.lastAssistantText ? parseNextSteps(result.lastAssistantText).texts : [];
|
|
3583
|
+
return { ...result, nextSteps: resumedNextSteps };
|
|
3309
3584
|
};
|
|
3310
3585
|
}
|
|
3311
3586
|
|
|
@@ -3348,11 +3623,16 @@ function createChimeraWorkRegistry() {
|
|
|
3348
3623
|
|
|
3349
3624
|
// src/execution-chimera-cascade.ts
|
|
3350
3625
|
import * as fsp from "node:fs/promises";
|
|
3351
|
-
import * as
|
|
3626
|
+
import * as path11 from "node:path";
|
|
3352
3627
|
import { emitReviewIfChanged } from "@wrongstack/core/plugin";
|
|
3353
3628
|
|
|
3354
3629
|
// src/chimera-cascade-evidence.ts
|
|
3355
3630
|
import { spawn as spawn2 } from "node:child_process";
|
|
3631
|
+
import {
|
|
3632
|
+
buildWin32CmdShimInvocation,
|
|
3633
|
+
isWinCmdShim,
|
|
3634
|
+
resolveWin32Command
|
|
3635
|
+
} from "@wrongstack/tools/win32";
|
|
3356
3636
|
var JSON_BLOCK_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
3357
3637
|
function tryParseJson(raw) {
|
|
3358
3638
|
try {
|
|
@@ -3404,14 +3684,9 @@ function parseSingleCheck(raw) {
|
|
|
3404
3684
|
if (typeof exitCode !== "number" || !Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) {
|
|
3405
3685
|
return void 0;
|
|
3406
3686
|
}
|
|
3407
|
-
const output = typeof check.output === "string" ?
|
|
3687
|
+
const output = typeof check.output === "string" ? check.output : void 0;
|
|
3408
3688
|
return { command: command.trim(), exitCode, output };
|
|
3409
3689
|
}
|
|
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
3690
|
var SAFE_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
3416
3691
|
"pnpm",
|
|
3417
3692
|
"npm",
|
|
@@ -3440,23 +3715,39 @@ var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
|
|
|
3440
3715
|
return Promise.resolve({ exitCode: CASCADE_EVIDENCE_UNSAFE_EXIT });
|
|
3441
3716
|
}
|
|
3442
3717
|
const tokens = command.trim().split(/\s+/);
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3718
|
+
let executable = tokens[0];
|
|
3719
|
+
let args = tokens.slice(1);
|
|
3720
|
+
let windowsVerbatimArguments = false;
|
|
3721
|
+
if (process.platform === "win32") {
|
|
3722
|
+
const resolved = resolveWin32Command(executable);
|
|
3723
|
+
if (isWinCmdShim(resolved)) {
|
|
3724
|
+
let shim;
|
|
3725
|
+
try {
|
|
3726
|
+
shim = buildWin32CmdShimInvocation(resolved, args);
|
|
3727
|
+
} catch {
|
|
3728
|
+
return Promise.resolve({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
|
|
3729
|
+
}
|
|
3730
|
+
executable = shim.command;
|
|
3731
|
+
args = shim.args;
|
|
3732
|
+
windowsVerbatimArguments = true;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
return new Promise((resolve5) => {
|
|
3446
3736
|
let child;
|
|
3447
3737
|
try {
|
|
3448
3738
|
child = spawn2(executable, args, {
|
|
3449
3739
|
cwd,
|
|
3450
3740
|
stdio: "ignore",
|
|
3451
3741
|
windowsHide: true,
|
|
3452
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
3742
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
3743
|
+
...windowsVerbatimArguments ? { windowsVerbatimArguments } : {}
|
|
3453
3744
|
});
|
|
3454
3745
|
} catch {
|
|
3455
|
-
|
|
3746
|
+
resolve5({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
|
|
3456
3747
|
return;
|
|
3457
3748
|
}
|
|
3458
|
-
child.on("error", () =>
|
|
3459
|
-
child.on("close", (code) =>
|
|
3749
|
+
child.on("error", () => resolve5({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
|
|
3750
|
+
child.on("close", (code) => resolve5({ exitCode: code ?? CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
|
|
3460
3751
|
});
|
|
3461
3752
|
};
|
|
3462
3753
|
async function verifyCascadeEvidence(evidence, cwd, runCommand = runCascadeVerificationCommand, timeoutMs = CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS) {
|
|
@@ -3974,7 +4265,7 @@ async function maybeReReviewCascade({
|
|
|
3974
4265
|
const reReadFiles = [];
|
|
3975
4266
|
for (const f of evidenceBundle.files) {
|
|
3976
4267
|
try {
|
|
3977
|
-
const absPath =
|
|
4268
|
+
const absPath = path11.join(evidenceBundle.cwd, f.path);
|
|
3978
4269
|
const content = await fsp.readFile(absPath, "utf8");
|
|
3979
4270
|
reReadFiles.push({ path: f.path, status: "modified", content });
|
|
3980
4271
|
} catch {
|
|
@@ -4051,7 +4342,7 @@ function errorText2(error) {
|
|
|
4051
4342
|
|
|
4052
4343
|
// src/execution-chimera-review.ts
|
|
4053
4344
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4054
|
-
import
|
|
4345
|
+
import path12 from "node:path";
|
|
4055
4346
|
import { effectiveFallbackChain, fallbackProfileChain as fallbackProfileChain2 } from "@wrongstack/core/agent";
|
|
4056
4347
|
import {
|
|
4057
4348
|
areSubagentsAllowedForSession
|
|
@@ -4069,10 +4360,14 @@ import {
|
|
|
4069
4360
|
function normalizeFileKeyForCitation(raw, cwd) {
|
|
4070
4361
|
const forward = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4071
4362
|
const isAbsolute = forward.startsWith("/") || /^[a-zA-Z]:\//.test(forward);
|
|
4072
|
-
const pathMod = process.platform === "win32" ?
|
|
4363
|
+
const pathMod = process.platform === "win32" ? path12.win32 : path12;
|
|
4073
4364
|
const relative = isAbsolute ? pathMod.relative(cwd, forward).replace(/\\/g, "/").replace(/^\.\//, "") : forward;
|
|
4074
4365
|
return process.platform === "win32" ? relative.toLowerCase() : relative;
|
|
4075
4366
|
}
|
|
4367
|
+
function readPositiveNumber(config, key) {
|
|
4368
|
+
const raw = config.extensions?.["wstack-chimera"]?.[key];
|
|
4369
|
+
return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : void 0;
|
|
4370
|
+
}
|
|
4076
4371
|
function installChimeraReviewHandler({
|
|
4077
4372
|
events,
|
|
4078
4373
|
director,
|
|
@@ -4122,7 +4417,12 @@ function installChimeraReviewHandler({
|
|
|
4122
4417
|
};
|
|
4123
4418
|
const persistAndEmitCompletion = async (completion) => {
|
|
4124
4419
|
try {
|
|
4125
|
-
await persistReview(completion, projectDir
|
|
4420
|
+
await persistReview(completion, projectDir, {
|
|
4421
|
+
// Read per completion, not once at install time, so an operator
|
|
4422
|
+
// editing either key takes effect without a restart.
|
|
4423
|
+
openRetentionDays: readPositiveNumber(config, "openRetentionDays"),
|
|
4424
|
+
activeRetentionDays: readPositiveNumber(config, "activeRetentionDays")
|
|
4425
|
+
});
|
|
4126
4426
|
events.emitCustom("chimera.review_persisted", {
|
|
4127
4427
|
reportId: completion.reportId,
|
|
4128
4428
|
sessionId: completion.sessionId,
|
|
@@ -4296,10 +4596,10 @@ function installChimeraReviewHandler({
|
|
|
4296
4596
|
const changedBasenameIndex = /* @__PURE__ */ new Map();
|
|
4297
4597
|
for (const file of p.files) {
|
|
4298
4598
|
const normalized = normalizeFileKeyForCitation(file.path, p.cwd);
|
|
4299
|
-
const
|
|
4300
|
-
const bucket = changedBasenameIndex.get(
|
|
4599
|
+
const basename8 = path12.basename(file.path).toLowerCase();
|
|
4600
|
+
const bucket = changedBasenameIndex.get(basename8);
|
|
4301
4601
|
if (bucket) bucket.push(normalized);
|
|
4302
|
-
else changedBasenameIndex.set(
|
|
4602
|
+
else changedBasenameIndex.set(basename8, [normalized]);
|
|
4303
4603
|
}
|
|
4304
4604
|
const inScopeCited = citedFindings.filter((finding) => {
|
|
4305
4605
|
const citedKey = normalizeFileKeyForCitation(finding.location.file, p.cwd);
|
|
@@ -4308,8 +4608,8 @@ function installChimeraReviewHandler({
|
|
|
4308
4608
|
(cp) => cp.endsWith(`/${citedKey}`) || cp === citedKey
|
|
4309
4609
|
);
|
|
4310
4610
|
if (suffixMatches.length === 1) return true;
|
|
4311
|
-
const
|
|
4312
|
-
const bucket = changedBasenameIndex.get(
|
|
4611
|
+
const basename8 = path12.basename(finding.location.file).toLowerCase();
|
|
4612
|
+
const bucket = changedBasenameIndex.get(basename8);
|
|
4313
4613
|
return !!(bucket && bucket.length === 1);
|
|
4314
4614
|
});
|
|
4315
4615
|
const droppedCitations = citedFindings.filter(
|
|
@@ -4458,13 +4758,13 @@ ${reviewBody}`;
|
|
|
4458
4758
|
);
|
|
4459
4759
|
if (off) teardownHandlers?.push(off);
|
|
4460
4760
|
}
|
|
4461
|
-
async function persistChimeraReview(payload, projectDir) {
|
|
4761
|
+
async function persistChimeraReview(payload, projectDir, maintenance = {}) {
|
|
4462
4762
|
const reportId = payload.reportId;
|
|
4463
4763
|
if (!reportId) throw new Error("Chimera completion is missing reportId");
|
|
4464
4764
|
await persistReviewReport(payload, reportId, projectDir);
|
|
4465
4765
|
await integrateFindings(payload, projectDir, reportId);
|
|
4466
4766
|
try {
|
|
4467
|
-
await maybeCompactReviewStores(projectDir);
|
|
4767
|
+
await maybeCompactReviewStores(projectDir, maintenance);
|
|
4468
4768
|
} catch (error) {
|
|
4469
4769
|
console.error(
|
|
4470
4770
|
JSON.stringify({
|
|
@@ -4609,21 +4909,13 @@ function installStorageObservability(events, rootTraceId) {
|
|
|
4609
4909
|
// src/next-task-predictor.ts
|
|
4610
4910
|
import { readBundledInstructionText } from "@wrongstack/core/utils";
|
|
4611
4911
|
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
4912
|
function buildPredictionPrompt(input) {
|
|
4619
4913
|
const parts = [];
|
|
4620
4914
|
parts.push(`The user asked:
|
|
4621
|
-
${
|
|
4915
|
+
${input.userRequest.trim() || "(no text)"}`);
|
|
4622
4916
|
if (input.assistantSummary.trim()) {
|
|
4623
|
-
parts.push(
|
|
4624
|
-
|
|
4625
|
-
${clamp(input.assistantSummary, MAX_SUMMARY_CHARS)}`
|
|
4626
|
-
);
|
|
4917
|
+
parts.push(`The assistant just finished and reported:
|
|
4918
|
+
${input.assistantSummary.trim()}`);
|
|
4627
4919
|
}
|
|
4628
4920
|
const pending = input.todos.filter((t) => t.status !== "completed");
|
|
4629
4921
|
if (pending.length > 0) {
|
|
@@ -4678,7 +4970,6 @@ async function predictNextTasks(input, opts) {
|
|
|
4678
4970
|
messages: [
|
|
4679
4971
|
{ role: "user", content: [{ type: "text", text: buildPredictionPrompt(input) }] }
|
|
4680
4972
|
],
|
|
4681
|
-
maxTokens: 160,
|
|
4682
4973
|
temperature: 0.3
|
|
4683
4974
|
},
|
|
4684
4975
|
{ signal: internal.signal }
|
|
@@ -4697,7 +4988,7 @@ import { detectContinueIntent, InputBuilder, resolveContinuation } from "@wrongs
|
|
|
4697
4988
|
import {
|
|
4698
4989
|
color as color11,
|
|
4699
4990
|
estimateRequestTokensCalibrated as estimateRequestTokensCalibrated2,
|
|
4700
|
-
hasOpenTodos as
|
|
4991
|
+
hasOpenTodos as hasOpenTodos3,
|
|
4701
4992
|
toErrorMessage as toErrorMessage4
|
|
4702
4993
|
} from "@wrongstack/core/utils";
|
|
4703
4994
|
import { readClipboardImage, routeImagesForModel } from "@wrongstack/runtime";
|
|
@@ -4711,14 +5002,14 @@ import {
|
|
|
4711
5002
|
} from "@wrongstack/tools/auto-proceed-loop-guard";
|
|
4712
5003
|
|
|
4713
5004
|
// src/repl-suggestions.ts
|
|
4714
|
-
import { hasOpenTodos } from "@wrongstack/core/utils";
|
|
4715
|
-
import { parseNextSteps } from "@wrongstack/tools/next-steps";
|
|
5005
|
+
import { hasOpenTodos as hasOpenTodos2 } from "@wrongstack/core/utils";
|
|
5006
|
+
import { parseNextSteps as parseNextSteps2 } from "@wrongstack/tools/next-steps";
|
|
4716
5007
|
function parseSuggestionsFromOutput(finalText, todos) {
|
|
4717
|
-
if (
|
|
5008
|
+
if (hasOpenTodos2(todos)) {
|
|
4718
5009
|
setAutoSuggestions([]);
|
|
4719
5010
|
return null;
|
|
4720
5011
|
}
|
|
4721
|
-
const { texts, autoTexts } =
|
|
5012
|
+
const { texts, autoTexts } = parseNextSteps2(finalText);
|
|
4722
5013
|
if (autoTexts.length > 0) {
|
|
4723
5014
|
setAutoSuggestions(autoTexts);
|
|
4724
5015
|
}
|
|
@@ -4822,12 +5113,12 @@ ${color6.cyan("\u23F3 Auto")} ${color6.dim("(Ctrl+C to cancel)")}
|
|
|
4822
5113
|
let interval;
|
|
4823
5114
|
let lastTickedSecond = sec + 1;
|
|
4824
5115
|
let onAbort;
|
|
4825
|
-
return new Promise((
|
|
4826
|
-
onAbort = () =>
|
|
5116
|
+
return new Promise((resolve5) => {
|
|
5117
|
+
onAbort = () => resolve5(false);
|
|
4827
5118
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
4828
5119
|
interval = setInterval(() => {
|
|
4829
5120
|
if (signal.aborted) {
|
|
4830
|
-
|
|
5121
|
+
resolve5(false);
|
|
4831
5122
|
return;
|
|
4832
5123
|
}
|
|
4833
5124
|
const elapsed = Date.now() - start;
|
|
@@ -4835,7 +5126,7 @@ ${color6.cyan("\u23F3 Auto")} ${color6.dim("(Ctrl+C to cancel)")}
|
|
|
4835
5126
|
if (remaining <= 0) {
|
|
4836
5127
|
opts.renderer.write(color6.dim(` \u21B3 ${truncated}
|
|
4837
5128
|
`));
|
|
4838
|
-
|
|
5129
|
+
resolve5(true);
|
|
4839
5130
|
return;
|
|
4840
5131
|
}
|
|
4841
5132
|
if (opts.onCountdownTick && remaining !== lastTickedSecond) {
|
|
@@ -4846,7 +5137,7 @@ ${color6.cyan("\u23F3 Auto")} ${color6.dim("(Ctrl+C to cancel)")}
|
|
|
4846
5137
|
opts.renderer.write(
|
|
4847
5138
|
color6.yellow(" \u21B3 Countdown cancelled \u2014 switching to manual mode\n")
|
|
4848
5139
|
);
|
|
4849
|
-
|
|
5140
|
+
resolve5(false);
|
|
4850
5141
|
return;
|
|
4851
5142
|
}
|
|
4852
5143
|
} catch {
|
|
@@ -4878,7 +5169,7 @@ async function runContextCompaction(opts, _signal) {
|
|
|
4878
5169
|
|
|
4879
5170
|
// src/repl-client-registration.ts
|
|
4880
5171
|
import * as crypto from "node:crypto";
|
|
4881
|
-
import * as
|
|
5172
|
+
import * as path13 from "node:path";
|
|
4882
5173
|
import { getSharedProjectMailbox, resolveProjectDir } from "@wrongstack/core/coordination";
|
|
4883
5174
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
4884
5175
|
function registerReplClient(opts) {
|
|
@@ -4888,9 +5179,11 @@ function registerReplClient(opts) {
|
|
|
4888
5179
|
const hqConnection = startCliHqConnection({
|
|
4889
5180
|
clientKind: "repl",
|
|
4890
5181
|
projectRoot: replProjectRoot,
|
|
4891
|
-
projectName:
|
|
5182
|
+
projectName: path13.basename(replProjectRoot),
|
|
4892
5183
|
appConfig: opts.appConfig,
|
|
4893
|
-
capabilities: ["telemetry.publish", "mailbox.summary"]
|
|
5184
|
+
capabilities: ["telemetry.publish", "mailbox.summary"],
|
|
5185
|
+
// The CLI host's own connection in this process syncs the boards.
|
|
5186
|
+
ownKanbanSync: false
|
|
4894
5187
|
});
|
|
4895
5188
|
const clientMailbox = getSharedProjectMailbox(
|
|
4896
5189
|
projectDir,
|
|
@@ -4902,7 +5195,7 @@ function registerReplClient(opts) {
|
|
|
4902
5195
|
clientMailbox.registerClient({
|
|
4903
5196
|
clientId,
|
|
4904
5197
|
sessionId: opts.getSessionId?.() ?? replProjectRoot,
|
|
4905
|
-
name: `REPL [${
|
|
5198
|
+
name: `REPL [${path13.basename(replProjectRoot)}]`,
|
|
4906
5199
|
source: "repl",
|
|
4907
5200
|
pid: process.pid
|
|
4908
5201
|
}).then(() => {
|
|
@@ -4914,6 +5207,7 @@ function registerReplClient(opts) {
|
|
|
4914
5207
|
clientMailbox.clientHeartbeat({ clientId, sessionId: opts.getSessionId?.() ?? replProjectRoot }).catch(() => {
|
|
4915
5208
|
});
|
|
4916
5209
|
}, 15e3);
|
|
5210
|
+
clientHeartbeat.unref?.();
|
|
4917
5211
|
}).catch(() => {
|
|
4918
5212
|
});
|
|
4919
5213
|
return {
|
|
@@ -5046,7 +5340,7 @@ async function runReplEternalLoop(opts, onInterruptsReset) {
|
|
|
5046
5340
|
} catch (err) {
|
|
5047
5341
|
opts.renderer.writeError(`[eternal] ${toErrorMessage3(err)}`);
|
|
5048
5342
|
}
|
|
5049
|
-
await new Promise((
|
|
5343
|
+
await new Promise((resolve5) => setTimeout(resolve5, 250));
|
|
5050
5344
|
return true;
|
|
5051
5345
|
}
|
|
5052
5346
|
if (autonomy === "eternal-parallel") {
|
|
@@ -5117,7 +5411,7 @@ async function runReplEternalLoop(opts, onInterruptsReset) {
|
|
|
5117
5411
|
} catch (err) {
|
|
5118
5412
|
opts.renderer.writeError(`[parallel] ${toErrorMessage3(err)}`);
|
|
5119
5413
|
}
|
|
5120
|
-
await new Promise((
|
|
5414
|
+
await new Promise((resolve5) => setTimeout(resolve5, 250));
|
|
5121
5415
|
return true;
|
|
5122
5416
|
}
|
|
5123
5417
|
return false;
|
|
@@ -5154,7 +5448,7 @@ async function readPossiblyMultiline(opts) {
|
|
|
5154
5448
|
// src/repl-rendering.ts
|
|
5155
5449
|
import { color as color10 } from "@wrongstack/core/utils";
|
|
5156
5450
|
var FILLED = "\u2588";
|
|
5157
|
-
var
|
|
5451
|
+
var EMPTY2 = "\u2591";
|
|
5158
5452
|
function renderContextChip(used, max) {
|
|
5159
5453
|
const ratio = Math.max(0, Math.min(1, used / max));
|
|
5160
5454
|
const pct = Math.round(ratio * 100);
|
|
@@ -5165,7 +5459,7 @@ function renderProgress(ratio, width) {
|
|
|
5165
5459
|
const clamped = Math.max(0, Math.min(1, ratio));
|
|
5166
5460
|
const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped * width));
|
|
5167
5461
|
const capped = Math.min(width, filled);
|
|
5168
|
-
return FILLED.repeat(capped) +
|
|
5462
|
+
return FILLED.repeat(capped) + EMPTY2.repeat(width - capped);
|
|
5169
5463
|
}
|
|
5170
5464
|
function printBanner(renderer, projectName) {
|
|
5171
5465
|
const lines = [
|
|
@@ -5657,7 +5951,7 @@ ${color11.dim(
|
|
|
5657
5951
|
}
|
|
5658
5952
|
if (result.status === "done" && opts.getAutonomy) {
|
|
5659
5953
|
const autonomy2 = opts.getAutonomy();
|
|
5660
|
-
if (autonomy2 === "suggest" && !
|
|
5954
|
+
if (autonomy2 === "suggest" && !hasOpenTodos3(opts.agent.ctx.todos)) {
|
|
5661
5955
|
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
5956
|
const suggestBlocks = [{ type: "text", text: suggestPrompt }];
|
|
5663
5957
|
const suggestCtrl = new AbortController();
|
|
@@ -5694,7 +5988,7 @@ ${suggestResult.finalText}
|
|
|
5694
5988
|
}
|
|
5695
5989
|
}
|
|
5696
5990
|
}
|
|
5697
|
-
if (result.status === "done" && opts.getNextPredict?.() && !
|
|
5991
|
+
if (result.status === "done" && opts.getNextPredict?.() && !hasOpenTodos3(opts.agent.ctx.todos)) {
|
|
5698
5992
|
const autonomy2 = opts.getAutonomy?.() ?? "off";
|
|
5699
5993
|
if (autonomy2 === "off") {
|
|
5700
5994
|
const predictCtrl = new AbortController();
|
|
@@ -6001,8 +6295,8 @@ async function profileMenu(store, paths) {
|
|
|
6001
6295
|
}
|
|
6002
6296
|
const items = await Promise.all(
|
|
6003
6297
|
names.map(async (name) => {
|
|
6004
|
-
const
|
|
6005
|
-
const data = await readJsonObjectFile(
|
|
6298
|
+
const path15 = paths.profileConfig(name);
|
|
6299
|
+
const data = await readJsonObjectFile(path15);
|
|
6006
6300
|
const provider = typeof data["provider"] === "string" ? data["provider"] : "unset";
|
|
6007
6301
|
const model = typeof data["model"] === "string" ? data["model"] : "unset";
|
|
6008
6302
|
const fallbackCount = Array.isArray(data["fallbackModels"]) ? data["fallbackModels"].length : 0;
|
|
@@ -6023,7 +6317,7 @@ async function profileMenu(store, paths) {
|
|
|
6023
6317
|
},
|
|
6024
6318
|
{ label: "fallbacks", value: String(fallbackCount) },
|
|
6025
6319
|
{ label: "fallback profiles", value: String(profileCount) },
|
|
6026
|
-
{ label: "config", value:
|
|
6320
|
+
{ label: "config", value: path15 }
|
|
6027
6321
|
],
|
|
6028
6322
|
body: isActive ? "This profile owns the current session configuration." : "Switching profiles closes this session so every service restarts with the selected configuration.",
|
|
6029
6323
|
actions: isActive ? [] : [{ key: "s", label: "switch", command: `/profile switch ${name}`, confirm: true }]
|
|
@@ -6153,19 +6447,19 @@ async function gitMenu(projectRoot) {
|
|
|
6153
6447
|
},
|
|
6154
6448
|
...lines.map((line, index) => {
|
|
6155
6449
|
const code = line.slice(0, 2);
|
|
6156
|
-
const
|
|
6450
|
+
const path15 = line.slice(3);
|
|
6157
6451
|
const staged = code[0] !== " " && code[0] !== "?";
|
|
6158
6452
|
const unstaged = code[1] !== " " || code === "??";
|
|
6159
6453
|
return {
|
|
6160
|
-
id: `change:${index}:${
|
|
6161
|
-
label:
|
|
6454
|
+
id: `change:${index}:${path15}`,
|
|
6455
|
+
label: path15,
|
|
6162
6456
|
status: staged ? "good" : "warn",
|
|
6163
6457
|
summary: `${code} \xB7 ${staged ? "staged" : "not staged"}${unstaged ? " \xB7 working tree" : ""}`,
|
|
6164
6458
|
details: [
|
|
6165
6459
|
{ label: "status", value: code },
|
|
6166
6460
|
{ label: "staged", value: staged ? "yes" : "no" },
|
|
6167
6461
|
{ label: "unstaged", value: unstaged ? "yes" : "no" },
|
|
6168
|
-
{ label: "path", value:
|
|
6462
|
+
{ label: "path", value: path15 }
|
|
6169
6463
|
],
|
|
6170
6464
|
actions: [
|
|
6171
6465
|
{ key: "d", label: "diff summary", command: staged ? "/git diff --staged" : "/git diff" }
|
|
@@ -6194,20 +6488,20 @@ async function worktreeMenu(projectRoot) {
|
|
|
6194
6488
|
return split < 0 ? [line, "yes"] : [line.slice(0, split), line.slice(split + 1)];
|
|
6195
6489
|
})
|
|
6196
6490
|
);
|
|
6197
|
-
const
|
|
6491
|
+
const path15 = fields["worktree"] ?? "(unknown path)";
|
|
6198
6492
|
const branchRef = fields["branch"];
|
|
6199
6493
|
const branch = branchRef?.replace(/^refs\/heads\//, "") ?? (fields["detached"] ? "(detached)" : "(bare)");
|
|
6200
|
-
const status =
|
|
6494
|
+
const status = path15 === "(unknown path)" || fields["prunable"] ? null : await runGit(["status", "--porcelain=v1"], path15);
|
|
6201
6495
|
const changes = status?.code === 0 ? status.stdout.split(/\r?\n/).filter(Boolean) : [];
|
|
6202
6496
|
const dirty = changes.length > 0;
|
|
6203
6497
|
const isMain = index === 0;
|
|
6204
6498
|
return {
|
|
6205
|
-
id:
|
|
6499
|
+
id: path15,
|
|
6206
6500
|
label: branch,
|
|
6207
6501
|
status: fields["prunable"] ? "bad" : dirty ? "warn" : "good",
|
|
6208
6502
|
summary: `${isMain ? "main" : "linked"} \xB7 ${dirty ? `${changes.length} changes` : "clean"}`,
|
|
6209
6503
|
details: [
|
|
6210
|
-
{ label: "path", value:
|
|
6504
|
+
{ label: "path", value: path15 },
|
|
6211
6505
|
{ label: "branch", value: branch },
|
|
6212
6506
|
{ label: "HEAD", value: fields["HEAD"]?.slice(0, 12) ?? "unknown" },
|
|
6213
6507
|
{ label: "working tree", value: dirty ? `${changes.length} changed paths` : "clean" },
|
|
@@ -6442,6 +6736,7 @@ async function execute(deps) {
|
|
|
6442
6736
|
});
|
|
6443
6737
|
let code = 0;
|
|
6444
6738
|
let backgroundKanbanSupervisor;
|
|
6739
|
+
let autoUpdate;
|
|
6445
6740
|
let fleetStatusLine = null;
|
|
6446
6741
|
try {
|
|
6447
6742
|
const visionAdapters = () => createToolVisionAdapters(agent.tools);
|
|
@@ -6453,7 +6748,8 @@ async function execute(deps) {
|
|
|
6453
6748
|
modelsRegistry,
|
|
6454
6749
|
context.provider.id,
|
|
6455
6750
|
context.model,
|
|
6456
|
-
mergedModels
|
|
6751
|
+
mergedModels,
|
|
6752
|
+
{ catalogProviderId: catalogProviderIdFor(context.provider.id, providerConfig?.type) }
|
|
6457
6753
|
);
|
|
6458
6754
|
return caps.vision;
|
|
6459
6755
|
} catch {
|
|
@@ -6466,9 +6762,6 @@ async function execute(deps) {
|
|
|
6466
6762
|
}
|
|
6467
6763
|
const goalFlag = typeof flags["goal"] === "string" ? flags["goal"] : void 0;
|
|
6468
6764
|
const askFlag = typeof flags["ask"] === "string" ? flags["ask"] : void 0;
|
|
6469
|
-
if ((goalFlag || askFlag) && positional.length === 0 && !promptFlag) {
|
|
6470
|
-
flags.tui = true;
|
|
6471
|
-
}
|
|
6472
6765
|
const executionMode = resolveExecutionMode(positional, flags);
|
|
6473
6766
|
if (projectRoot && executionMode !== "webui") {
|
|
6474
6767
|
backgroundKanbanSupervisor = (await import("./kanban-supervisor-67ECABTD.js")).createKanbanSupervisor({
|
|
@@ -6484,18 +6777,35 @@ async function execute(deps) {
|
|
|
6484
6777
|
log: (message) => console.log(message)
|
|
6485
6778
|
});
|
|
6486
6779
|
}
|
|
6780
|
+
if (executionMode !== "single-shot" && !webuiSessionChild) {
|
|
6781
|
+
autoUpdate = (await import("./standalone-auto-update-TOTRGCSA.js")).startStandaloneAutoUpdate({
|
|
6782
|
+
getConfig: () => configStore.get(),
|
|
6783
|
+
updateInfo: bootUpdateInfo
|
|
6784
|
+
});
|
|
6785
|
+
}
|
|
6487
6786
|
const enteringTui = executionMode === "tui";
|
|
6488
6787
|
if (!enteringTui) {
|
|
6489
6788
|
fleetStatusLine = new FleetStatusLine({ events, version: CLI_VERSION });
|
|
6490
6789
|
fleetStatusLine.start();
|
|
6491
6790
|
}
|
|
6492
6791
|
if (executionMode === "single-shot") {
|
|
6792
|
+
const piped = await readPipedStdin(process.stdin);
|
|
6793
|
+
if (piped.timedOut) {
|
|
6794
|
+
renderer.writeWarning(
|
|
6795
|
+
"No stdin data received in time; continuing without it. Redirect stdin (< /dev/null) to skip the wait."
|
|
6796
|
+
);
|
|
6797
|
+
} else if (piped.truncated) {
|
|
6798
|
+
renderer.writeWarning("Piped stdin exceeded 2 MB and was truncated.");
|
|
6799
|
+
}
|
|
6800
|
+
await mcpRegistry.whenStarted();
|
|
6493
6801
|
code = await runSingleShotDispatch({
|
|
6494
6802
|
agent,
|
|
6495
|
-
query: positional.join(" "),
|
|
6803
|
+
query: appendPipedStdin(positional.join(" "), piped.text),
|
|
6496
6804
|
flags,
|
|
6497
6805
|
tokenCounter,
|
|
6498
|
-
renderer
|
|
6806
|
+
renderer,
|
|
6807
|
+
events,
|
|
6808
|
+
interruptController
|
|
6499
6809
|
});
|
|
6500
6810
|
} else if (executionMode === "tui") {
|
|
6501
6811
|
agent.disableInteractiveConfirmation();
|
|
@@ -6558,6 +6868,7 @@ async function execute(deps) {
|
|
|
6558
6868
|
const switchProjectInPlace2 = async (targetRoot, displayName) => {
|
|
6559
6869
|
return switchProjectInPlace(switchCtx, targetRoot, displayName);
|
|
6560
6870
|
};
|
|
6871
|
+
const worktreeSessions = /* @__PURE__ */ new Map();
|
|
6561
6872
|
const pickerCtx = {
|
|
6562
6873
|
state,
|
|
6563
6874
|
renderer,
|
|
@@ -6582,6 +6893,10 @@ async function execute(deps) {
|
|
|
6582
6893
|
code = await runTuiDispatch({
|
|
6583
6894
|
leaderAutoWake,
|
|
6584
6895
|
agent,
|
|
6896
|
+
// The session store's root. Without it /rewind, the checkpoint
|
|
6897
|
+
// timeline and /rewind redo resolve every session file against ''
|
|
6898
|
+
// and fail, and the layout store and history archive stay ephemeral.
|
|
6899
|
+
sessionsDir: wpaths.projectSessions,
|
|
6585
6900
|
events,
|
|
6586
6901
|
slashRegistry,
|
|
6587
6902
|
skillLoader,
|
|
@@ -6600,6 +6915,17 @@ async function execute(deps) {
|
|
|
6600
6915
|
model: context.model,
|
|
6601
6916
|
banner: !flags["no-banner"],
|
|
6602
6917
|
queueStore,
|
|
6918
|
+
// `/resume` brings the resumed session's queue along; the WebUI
|
|
6919
|
+
// keeps a session's queue in the same file.
|
|
6920
|
+
queueStoreFor: (sessionId) => {
|
|
6921
|
+
try {
|
|
6922
|
+
return new QueueStore({
|
|
6923
|
+
dir: sessionScopedPath3(state.wpaths.projectSessions, sessionId, "")
|
|
6924
|
+
});
|
|
6925
|
+
} catch {
|
|
6926
|
+
return void 0;
|
|
6927
|
+
}
|
|
6928
|
+
},
|
|
6603
6929
|
onQueueChange: (items) => {
|
|
6604
6930
|
setQueuedMessagesSnapshot2(context, items);
|
|
6605
6931
|
},
|
|
@@ -6623,6 +6949,7 @@ async function execute(deps) {
|
|
|
6623
6949
|
appVersion: CLI_VERSION,
|
|
6624
6950
|
latestVersion: bootUpdateInfo?.latest,
|
|
6625
6951
|
updateAvailable: bootUpdateInfo?.outdated,
|
|
6952
|
+
subscribeUpdateReady: autoUpdate?.subscribe,
|
|
6626
6953
|
provider: config.provider,
|
|
6627
6954
|
family: banneredFamily,
|
|
6628
6955
|
keyTail: banneredKeyTail,
|
|
@@ -6663,7 +6990,14 @@ async function execute(deps) {
|
|
|
6663
6990
|
onClearHistory: (dispatch) => {
|
|
6664
6991
|
void attachments.clear().catch(() => {
|
|
6665
6992
|
});
|
|
6666
|
-
dispatch({
|
|
6993
|
+
dispatch({
|
|
6994
|
+
type: "clearHistory",
|
|
6995
|
+
model: context.model,
|
|
6996
|
+
provider: context.provider.id,
|
|
6997
|
+
restoredMessages: null,
|
|
6998
|
+
restoredToolCalls: null,
|
|
6999
|
+
restoredEvents: null
|
|
7000
|
+
});
|
|
6667
7001
|
dispatch({ type: "resetContextChip" });
|
|
6668
7002
|
dispatch({ type: "streamReset" });
|
|
6669
7003
|
dispatch({ type: "toolStreamClear" });
|
|
@@ -6766,53 +7100,49 @@ async function execute(deps) {
|
|
|
6766
7100
|
} catch {
|
|
6767
7101
|
}
|
|
6768
7102
|
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
|
-
|
|
7103
|
+
const worktrees = await listGitWorktrees(state.projectRoot);
|
|
7104
|
+
worktreeSessions.clear();
|
|
7105
|
+
const toEntry = (s, worktree = worktreeOfSession(s, worktrees)) => {
|
|
7106
|
+
if (worktree) worktreeSessions.set(s.id, worktree);
|
|
7107
|
+
return {
|
|
7108
|
+
id: s.id,
|
|
7109
|
+
title: s.title ?? "",
|
|
7110
|
+
name: s.name,
|
|
7111
|
+
lastUserMessage: s.lastUserMessage,
|
|
7112
|
+
messageCount: s.messageCount,
|
|
7113
|
+
lastActivityAt: s.lastActivityAt,
|
|
7114
|
+
startedAt: s.startedAt ?? "",
|
|
7115
|
+
endedAt: s.endedAt,
|
|
7116
|
+
tokenTotal: s.tokenTotal ?? 0,
|
|
7117
|
+
iterationCount: s.iterationCount ?? 0,
|
|
7118
|
+
toolCallCount: s.toolCallCount ?? 0,
|
|
7119
|
+
toolErrorCount: s.toolErrorCount ?? 0,
|
|
7120
|
+
outcome: s.outcome,
|
|
7121
|
+
forkedFrom: s.forkedFrom,
|
|
7122
|
+
...worktree ? { worktree } : {},
|
|
7123
|
+
isCurrent: s.id === currentId,
|
|
7124
|
+
// The session this process owns is `isCurrent`, not "live
|
|
7125
|
+
// elsewhere" — it holds its own lease and would otherwise be
|
|
7126
|
+
// labelled as owned by another surface.
|
|
7127
|
+
...s.id !== currentId && liveBySession.has(s.id) ? { live: liveBySession.get(s.id) } : {}
|
|
7128
|
+
};
|
|
7129
|
+
};
|
|
7130
|
+
return summaries.map((s) => toEntry(s));
|
|
6789
7131
|
},
|
|
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 };
|
|
7132
|
+
forkSession: async (sessionId, checkpointPromptIndex) => {
|
|
7133
|
+
const store = state.activeSessionStore;
|
|
7134
|
+
if (!store?.fork) throw new Error("This session store cannot fork sessions.");
|
|
7135
|
+
const forked = await store.fork(sessionId, {
|
|
7136
|
+
checkpointPromptIndex,
|
|
7137
|
+
beforeCheckpointPrompt: true
|
|
7138
|
+
});
|
|
7139
|
+
return { id: forked.id };
|
|
6815
7140
|
},
|
|
7141
|
+
onResumeSession: withWorktreeSwitch(
|
|
7142
|
+
createTuiResumeCallback({ state, agent, tokenCounter, switchProviderAndModel, events }),
|
|
7143
|
+
(id) => worktreeSessions.get(id),
|
|
7144
|
+
switchProjectInPlace2
|
|
7145
|
+
),
|
|
6816
7146
|
getProjectPickerItems: () => getProjectPickerItems(pickerCtx),
|
|
6817
7147
|
onProjectSelect: (slug, kind) => onProjectSelect(pickerCtx, slug, kind),
|
|
6818
7148
|
initialAgentsMonitorOpen: !!flags.quick,
|
|
@@ -6845,6 +7175,7 @@ async function execute(deps) {
|
|
|
6845
7175
|
try {
|
|
6846
7176
|
code = await runWebUIDispatch({
|
|
6847
7177
|
leaderAutoWake: webuiLeaderAutoWake,
|
|
7178
|
+
interruptController,
|
|
6848
7179
|
agent,
|
|
6849
7180
|
events,
|
|
6850
7181
|
session,
|
|
@@ -6887,9 +7218,13 @@ async function execute(deps) {
|
|
|
6887
7218
|
// workers it is BLOCKED on; anything started with `spawn_subagent` +
|
|
6888
7219
|
// `assign_task` keeps going unless asked to stop. Scoped to the
|
|
6889
7220
|
// session so one tab's Stop never reaches another tab's fleet.
|
|
6890
|
-
stopSessionFleet:
|
|
6891
|
-
|
|
6892
|
-
|
|
7221
|
+
stopSessionFleet: (sessionId) => getDirector?.()?.terminateSession(sessionId),
|
|
7222
|
+
// HQ's per-tab abort fleet / abort agent / spawn, same scoping.
|
|
7223
|
+
hqFleetControl: createHqFleetControl(
|
|
7224
|
+
() => getDirector?.() ?? null,
|
|
7225
|
+
() => "",
|
|
7226
|
+
{ multiConversation: true }
|
|
7227
|
+
),
|
|
6893
7228
|
// Closing a tab is not stopping it. The run keeps going and keeps its
|
|
6894
7229
|
// fleet; what goes is the background help pinned to that conversation
|
|
6895
7230
|
// — the explore companion's poll timer and the shadow reviewer's
|
|
@@ -6940,7 +7275,7 @@ async function execute(deps) {
|
|
|
6940
7275
|
attachments,
|
|
6941
7276
|
effectiveMaxContext,
|
|
6942
7277
|
getEffectiveMaxContext,
|
|
6943
|
-
projectName:
|
|
7278
|
+
projectName: path14.basename(projectRoot) || void 0,
|
|
6944
7279
|
projectRoot,
|
|
6945
7280
|
appConfig: config,
|
|
6946
7281
|
getSessionId: () => agent.ctx.session?.id ?? session.id,
|
|
@@ -6972,6 +7307,7 @@ async function execute(deps) {
|
|
|
6972
7307
|
}
|
|
6973
7308
|
} finally {
|
|
6974
7309
|
backgroundKanbanSupervisor?.dispose();
|
|
7310
|
+
autoUpdate?.dispose();
|
|
6975
7311
|
for (const off of chimeraTeardowns.splice(0)) {
|
|
6976
7312
|
try {
|
|
6977
7313
|
off();
|
|
@@ -7006,4 +7342,4 @@ export {
|
|
|
7006
7342
|
execute,
|
|
7007
7343
|
resolveReviewerFallbackModels
|
|
7008
7344
|
};
|
|
7009
|
-
//# sourceMappingURL=execution-
|
|
7345
|
+
//# sourceMappingURL=execution-3STBD2U7.js.map
|