@wrongstack/cli 0.303.0 → 0.305.1

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.
Files changed (32) hide show
  1. package/dist/{acp-AQMGKW6O.js → acp-5ZLGFHWP.js} +4 -2
  2. package/dist/{auth-K7CYVVKH.js → auth-EPERLKYI.js} +3 -3
  3. package/dist/boot/system-prompt-builder.d.ts +24 -1
  4. package/dist/boot/tui-project-switch.d.ts +1 -1
  5. package/dist/boot/tui-runtime-state.d.ts +14 -0
  6. package/dist/{chunk-C5NXM2SI.js → chunk-2H47YMCV.js} +5 -1
  7. package/dist/{chunk-FJVCVZIV.js → chunk-5KFLLTSQ.js} +2 -2
  8. package/dist/{chunk-JW75HY4F.js → chunk-FKWHSFX4.js} +33 -4
  9. package/dist/{chunk-LUAFQIXT.js → chunk-RHLQT3EV.js} +11 -1
  10. package/dist/{cli-main-37XO26GK.js → cli-main-6YE423OH.js} +479 -326
  11. package/dist/diag-doctor-WTBHPOMB.js +329 -0
  12. package/dist/execute-deps.d.ts +16 -0
  13. package/dist/{execution-MVOH6PAQ.js → execution-QYFWDD3Y.js} +70 -35
  14. package/dist/fleet/host-context.d.ts +2 -0
  15. package/dist/fleet/host-session-writer.d.ts +33 -0
  16. package/dist/fleet/host.d.ts +10 -0
  17. package/dist/{hq-CD77GVSS.js → hq-5G6D5YIU.js} +2 -2
  18. package/dist/{hq-server-DME6EWIO.js → hq-server-IVZLVCFV.js} +2 -2
  19. package/dist/index.js +17 -60
  20. package/dist/{mailbox-serve-DBOOOOWZ.js → mailbox-serve-V3QIJBDH.js} +2 -2
  21. package/dist/{mcp-AX6ZAFJR.js → mcp-MSARYOPN.js} +4 -2
  22. package/dist/replay-XU6ZD7GM.js +34 -0
  23. package/dist/slash-commands/memory-triage.d.ts +4 -17
  24. package/dist/subcommands/handlers/daemon-inventory.d.ts +41 -0
  25. package/dist/{webui-server-E3OPEKU5.js → webui-server-FTG7JAZU.js} +5 -5
  26. package/dist/wiring/domain-glossary.d.ts +58 -0
  27. package/dist/wiring/domain-terms-mirror.d.ts +52 -0
  28. package/dist/wiring/pipeline.d.ts +2 -0
  29. package/dist/wiring/replay.d.ts +11 -1
  30. package/dist/wiring/sage.d.ts +4 -16
  31. package/package.json +24 -23
  32. package/dist/diag-doctor-ZF5BF2MK.js +0 -169
@@ -0,0 +1,329 @@
1
+ import {
2
+ API_VERSION
3
+ } from "./chunk-XJXDOF63.js";
4
+ import "./chunk-7OCVIDC7.js";
5
+
6
+ // src/subcommands/handlers/diag-doctor.ts
7
+ import * as fs2 from "node:fs/promises";
8
+ import * as os from "node:os";
9
+ import * as path2 from "node:path";
10
+ import { color, toErrorMessage } from "@wrongstack/core/utils";
11
+
12
+ // src/subcommands/handlers/daemon-inventory.ts
13
+ import * as fs from "node:fs/promises";
14
+ import * as path from "node:path";
15
+ import {
16
+ chronicleProjectServerEndpoint,
17
+ chronicleProjectServerMetadataPath
18
+ } from "@wrongstack/core/chronicle";
19
+ import {
20
+ mailboxProjectServerEndpoint,
21
+ mailboxProjectServerMetadataPath
22
+ } from "@wrongstack/core/coordination";
23
+ import {
24
+ sessionCatalogProjectServerEndpoint,
25
+ sessionCatalogProjectServerMetadataPath
26
+ } from "@wrongstack/core/session-catalog";
27
+ import {
28
+ KANBAN_PROJECT_SERVER_METADATA_FILE,
29
+ kanbanProjectServerEndpoint
30
+ } from "@wrongstack/kanban";
31
+ import { isProjectEndpointLive } from "@wrongstack/persistence";
32
+ import { sageProjectServerEndpoint, sageProjectServerMetadataPath } from "@wrongstack/sage";
33
+ import {
34
+ projectIndexServerEndpoint,
35
+ projectIndexServerMetadataPath
36
+ } from "@wrongstack/tools/codebase-index";
37
+ function describeDaemons(options) {
38
+ const { projectRoot, projectDir } = options;
39
+ return [
40
+ {
41
+ name: "kanban",
42
+ // In-repo `<projectRoot>/.wrongstack/`, not the global per-project state
43
+ // directory the other daemons use. Kanban deliberately keeps its server
44
+ // file next to the boards it owns.
45
+ metadataPath: path.join(projectRoot, ".wrongstack", KANBAN_PROJECT_SERVER_METADATA_FILE),
46
+ endpoint: kanbanProjectServerEndpoint(projectRoot)
47
+ },
48
+ {
49
+ name: "sage",
50
+ endpoint: sageProjectServerEndpoint(projectRoot),
51
+ metadataPath: sageProjectServerMetadataPath(projectRoot)
52
+ },
53
+ {
54
+ name: "chronicle",
55
+ endpoint: chronicleProjectServerEndpoint(projectDir),
56
+ metadataPath: chronicleProjectServerMetadataPath(projectDir)
57
+ },
58
+ {
59
+ name: "mailbox",
60
+ endpoint: mailboxProjectServerEndpoint(projectDir),
61
+ metadataPath: mailboxProjectServerMetadataPath(projectDir)
62
+ },
63
+ {
64
+ name: "session-catalog",
65
+ endpoint: sessionCatalogProjectServerEndpoint(projectDir),
66
+ metadataPath: sessionCatalogProjectServerMetadataPath(projectDir)
67
+ },
68
+ {
69
+ name: "codebase-index",
70
+ endpoint: projectIndexServerEndpoint(projectRoot),
71
+ metadataPath: projectIndexServerMetadataPath(projectRoot)
72
+ }
73
+ ];
74
+ }
75
+ async function readPid(metadataPath) {
76
+ if (!metadataPath) return void 0;
77
+ try {
78
+ const parsed = JSON.parse(await fs.readFile(metadataPath, "utf8"));
79
+ return typeof parsed.pid === "number" ? parsed.pid : void 0;
80
+ } catch {
81
+ return void 0;
82
+ }
83
+ }
84
+ async function endpointExists(endpoint) {
85
+ if (process.platform === "win32") return false;
86
+ try {
87
+ await fs.stat(endpoint);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ async function collectDaemonReports(options) {
94
+ return Promise.all(
95
+ describeDaemons(options).map(async (descriptor) => {
96
+ const live = await isProjectEndpointLive(descriptor.endpoint);
97
+ const status = live ? "live" : await endpointExists(descriptor.endpoint) ? "stale" : "stopped";
98
+ const pid = live ? await readPid(descriptor.metadataPath) : void 0;
99
+ return pid === void 0 ? { name: descriptor.name, endpoint: descriptor.endpoint, status } : { name: descriptor.name, endpoint: descriptor.endpoint, status, pid };
100
+ })
101
+ );
102
+ }
103
+ async function clearStaleDaemonEndpoints(options) {
104
+ const cleared = [];
105
+ for (const report of await collectDaemonReports(options)) {
106
+ if (report.status !== "stale") continue;
107
+ try {
108
+ await fs.rm(report.endpoint, { force: true });
109
+ cleared.push(report.name);
110
+ } catch {
111
+ }
112
+ }
113
+ return cleared;
114
+ }
115
+
116
+ // src/subcommands/handlers/diag-doctor.ts
117
+ var diagCmd = async (_args, deps) => {
118
+ const cfg = deps.config;
119
+ const age = await deps.modelsRegistry.ageSeconds();
120
+ const lines = [
121
+ color.bold("WrongStack diagnostics"),
122
+ ` apiVersion: ${API_VERSION}`,
123
+ ` cwd: ${deps.cwd}`,
124
+ ` projectRoot: ${deps.projectRoot}`,
125
+ ` projectHash: ${deps.paths.projectHash}`,
126
+ ` projectDir: ${deps.paths.projectDir}`,
127
+ ` globalRoot: ${deps.paths.globalRoot}`,
128
+ ` modelsCache: ${deps.paths.modelsCache}`,
129
+ ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
130
+ ` node: ${process.version}`,
131
+ ` os: ${os.platform()} ${os.release()}`,
132
+ ` provider: ${cfg.provider ?? "<unset>"}`,
133
+ ` model: ${cfg.model ?? "<unset>"}`,
134
+ ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
135
+ ` plugins: ${cfg.plugins?.length ?? 0}`,
136
+ ` mcpServers: ${Object.keys(cfg.mcpServers ?? {}).length}`
137
+ ];
138
+ deps.renderer.write(lines.join("\n") + "\n");
139
+ return 0;
140
+ };
141
+ async function reportDaemons(deps, opts) {
142
+ const inventory = { projectRoot: deps.projectRoot, projectDir: deps.paths.projectDir };
143
+ let reports = await collectDaemonReports(inventory);
144
+ deps.renderer.write(color.bold("WrongStack project daemons\n\n"));
145
+ const icon = (status) => status === "live" ? color.green("\u2713") : status === "stale" ? color.red("\u2717") : color.dim("\xB7");
146
+ for (const report of reports) {
147
+ const suffix = report.pid === void 0 ? "" : ` pid ${report.pid}`;
148
+ deps.renderer.write(
149
+ ` ${icon(report.status)} ${report.name.padEnd(16)} ${report.status.padEnd(8)}${color.dim(report.endpoint + suffix)}
150
+ `
151
+ );
152
+ }
153
+ const stale = reports.filter((report) => report.status === "stale");
154
+ if (stale.length === 0) {
155
+ deps.renderer.write(color.green("\nNo wedged endpoints.\n"));
156
+ return 0;
157
+ }
158
+ if (!opts.clear) {
159
+ deps.renderer.write(
160
+ color.amber(
161
+ `
162
+ ${stale.length} stale endpoint${stale.length === 1 ? "" : "s"}: ${stale.map((report) => report.name).join(", ")}
163
+ `
164
+ )
165
+ );
166
+ deps.renderer.write(
167
+ color.dim(
168
+ " A daemon died without releasing its endpoint. Current daemons reclaim\n this automatically on next start; clear it now with:\n wstack doctor --daemons --clear-stale\n"
169
+ )
170
+ );
171
+ return 1;
172
+ }
173
+ const cleared = await clearStaleDaemonEndpoints(inventory);
174
+ reports = await collectDaemonReports(inventory);
175
+ const remaining = reports.filter((report) => report.status === "stale");
176
+ if (cleared.length > 0) {
177
+ deps.renderer.write(color.green(`
178
+ Cleared: ${cleared.join(", ")}
179
+ `));
180
+ }
181
+ if (remaining.length > 0) {
182
+ deps.renderer.write(
183
+ color.red(`Still wedged: ${remaining.map((report) => report.name).join(", ")}
184
+ `)
185
+ );
186
+ return 1;
187
+ }
188
+ deps.renderer.write(color.dim("Daemons restart on demand \u2014 no further action needed.\n"));
189
+ return 0;
190
+ }
191
+ var doctorCmd = async (args, deps) => {
192
+ const flagged = (name) => deps.flags?.[name] === true || deps.flags?.[name] === "true" || args.includes(`--${name}`);
193
+ if (flagged("daemons")) {
194
+ return reportDaemons(deps, { clear: flagged("clear-stale") });
195
+ }
196
+ const checks = [];
197
+ const cfg = deps.config;
198
+ if (!cfg.provider)
199
+ checks.push({
200
+ name: "provider",
201
+ status: "fail",
202
+ detail: "no provider configured \u2014 run `wstack auth` to set up"
203
+ });
204
+ else checks.push({ name: "provider", status: "ok", detail: cfg.provider });
205
+ if (!cfg.model)
206
+ checks.push({
207
+ name: "model",
208
+ status: "fail",
209
+ detail: "no model configured \u2014 run `wstack auth` to configure"
210
+ });
211
+ else checks.push({ name: "model", status: "ok", detail: cfg.model });
212
+ if (cfg.provider) {
213
+ const providerCfg = cfg.providers?.[cfg.provider];
214
+ const hasVaultKey = typeof providerCfg?.apiKey === "string" && providerCfg.apiKey.length > 0;
215
+ const envHit = providerCfg?.envVars?.some((v) => process.env[v]) ?? false;
216
+ if (hasVaultKey || envHit)
217
+ checks.push({
218
+ name: "api key",
219
+ status: "ok",
220
+ detail: hasVaultKey ? "found in vault" : "found in env"
221
+ });
222
+ else
223
+ checks.push({
224
+ name: "api key",
225
+ status: "fail",
226
+ detail: `no key for "${cfg.provider}" in vault or env \u2014 run \`wstack auth ${cfg.provider}\``
227
+ });
228
+ }
229
+ try {
230
+ const age = await deps.modelsRegistry.ageSeconds();
231
+ if (!isFinite(age))
232
+ checks.push({
233
+ name: "models cache",
234
+ status: "warn",
235
+ detail: "never fetched \u2014 run `wstack models refresh`"
236
+ });
237
+ else if (age > 7 * 24 * 3600)
238
+ checks.push({
239
+ name: "models cache",
240
+ status: "warn",
241
+ detail: `${Math.round(age / 86400)} days old \u2014 run \`wstack models refresh\``
242
+ });
243
+ else
244
+ checks.push({ name: "models cache", status: "ok", detail: `${Math.round(age / 60)}m old` });
245
+ } catch (err) {
246
+ checks.push({
247
+ name: "models cache",
248
+ status: "warn",
249
+ detail: `read failed: ${toErrorMessage(err)}`
250
+ });
251
+ }
252
+ try {
253
+ await fs2.access(deps.paths.secretsKey);
254
+ checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
255
+ } catch {
256
+ checks.push({
257
+ name: "secret vault",
258
+ status: "warn",
259
+ detail: "not yet initialized (created lazily on first encrypt)"
260
+ });
261
+ }
262
+ try {
263
+ await fs2.mkdir(deps.paths.projectSessions, { recursive: true });
264
+ const probe = path2.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
265
+ await fs2.writeFile(probe, "");
266
+ await fs2.unlink(probe);
267
+ checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
268
+ } catch (err) {
269
+ checks.push({
270
+ name: "sessions writable",
271
+ status: "fail",
272
+ detail: `cannot write to ${deps.paths.projectSessions}: ${toErrorMessage(err)}`
273
+ });
274
+ }
275
+ const mcpEntries = Object.entries(cfg.mcpServers ?? {});
276
+ for (const [name, srv] of mcpEntries) {
277
+ if (!srv.enabled) continue;
278
+ if ((srv.transport === "sse" || srv.transport === "streamable-http") && !srv.url)
279
+ checks.push({ name: `mcp:${name}`, status: "fail", detail: "transport requires url" });
280
+ else if (srv.transport === "stdio" && !srv.command)
281
+ checks.push({
282
+ name: `mcp:${name}`,
283
+ status: "fail",
284
+ detail: "stdio transport requires command"
285
+ });
286
+ else
287
+ checks.push({
288
+ name: `mcp:${name}`,
289
+ status: "ok",
290
+ detail: `${srv.transport} ${srv.command ?? srv.url ?? ""}`.trim()
291
+ });
292
+ }
293
+ const major = Number.parseInt(process.version.replace(/^v/, "").split(".")[0] ?? "0", 10);
294
+ if (major < 22)
295
+ checks.push({ name: "node", status: "fail", detail: `${process.version} (need \u226522)` });
296
+ else checks.push({ name: "node", status: "ok", detail: process.version });
297
+ deps.renderer.write(color.bold("WrongStack doctor\n\n"));
298
+ let failed = 0;
299
+ let warned = 0;
300
+ for (const c of checks) {
301
+ const icon = c.status === "ok" ? color.green("\u2713") : c.status === "warn" ? color.amber("\u25CF") : color.red("\u2717");
302
+ deps.renderer.write(` ${icon} ${c.name.padEnd(20)} ${color.dim(c.detail)}
303
+ `);
304
+ if (c.status === "fail") failed++;
305
+ if (c.status === "warn") warned++;
306
+ }
307
+ deps.renderer.write("\n");
308
+ if (failed > 0) {
309
+ deps.renderer.write(
310
+ color.red(`${failed} failed, ${warned} warning${warned === 1 ? "" : "s"}
311
+ `)
312
+ );
313
+ return 1;
314
+ }
315
+ if (warned > 0) {
316
+ deps.renderer.write(
317
+ color.amber(`All checks passed (${warned} warning${warned === 1 ? "" : "s"})
318
+ `)
319
+ );
320
+ return 0;
321
+ }
322
+ deps.renderer.write(color.green("All checks passed.\n"));
323
+ return 0;
324
+ };
325
+ export {
326
+ diagCmd,
327
+ doctorCmd
328
+ };
329
+ //# sourceMappingURL=diag-doctor-WTBHPOMB.js.map
@@ -48,6 +48,7 @@ export interface ToolPickerItem {
48
48
  owner: string;
49
49
  category: string;
50
50
  enabled: boolean;
51
+ exposure: 'direct' | 'lazy' | 'disabled';
51
52
  mutating: boolean;
52
53
  permission: string;
53
54
  descMode: 'extend' | 'simple';
@@ -89,6 +90,21 @@ export interface CoreDeps {
89
90
  positional: string[];
90
91
  slashRegistry: SlashCommandRegistry;
91
92
  tokenCounter: TokenCounter;
93
+ /**
94
+ * Forward-declared session ref owned by the host (cli-main). When an
95
+ * in-process `/resume` swaps the agent's active writer, the resume
96
+ * handler repoints `sessionRef.current` so provider-side
97
+ * `getSessionId: () => sessionRef.current?.id` callbacks and the
98
+ * record-mode `bindReplayToContainer` binding follow the resumed
99
+ * session instead of staying pinned to the boot session.
100
+ *
101
+ * Optional: hosts that don't need post-resume propagation (or tests
102
+ * that predate the refactor) can omit it; provider calls then stay
103
+ * pinned to the boot session — same behavior as before this existed.
104
+ */
105
+ sessionRef?: {
106
+ current: import('@wrongstack/core/types').SessionWriter | undefined;
107
+ } | undefined;
92
108
  /** Atomically move this process's SessionRegistry ownership after explicit resume. */
93
109
  activateSessionIdentity?: ((sessionId: string, target?: import('./wiring/session-registry.js').SessionIdentityTarget) => Promise<void>) | undefined;
94
110
  /**
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-V3XH6XBJ.js";
11
11
  import {
12
12
  startCliHqConnection
13
- } from "./chunk-JW75HY4F.js";
13
+ } from "./chunk-FKWHSFX4.js";
14
14
  import "./chunk-WHOIR577.js";
15
15
  import {
16
16
  advanceToNextTask,
@@ -25,17 +25,17 @@ import {
25
25
  trySaveSpecFromAIOutput,
26
26
  trySaveTasksFromAIOutput
27
27
  } from "./chunk-DGXNL7OT.js";
28
- import {
29
- WEBUI_SESSION_CHILD_CAPABILITIES,
30
- writeWebuiSessionChildError,
31
- writeWebuiSessionChildReady
32
- } from "./chunk-3JPTNADJ.js";
33
28
  import {
34
29
  theme
35
30
  } from "./chunk-QD544J2B.js";
36
31
  import {
37
32
  fmtTok
38
33
  } from "./chunk-TYO2OVAD.js";
34
+ import {
35
+ WEBUI_SESSION_CHILD_CAPABILITIES,
36
+ writeWebuiSessionChildError,
37
+ writeWebuiSessionChildReady
38
+ } from "./chunk-3JPTNADJ.js";
39
39
  import {
40
40
  resolveActiveApiKey
41
41
  } from "./chunk-SZ42FYPT.js";
@@ -203,7 +203,7 @@ async function runWebUIDispatch(ctx) {
203
203
  const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
204
204
  agent.disableInteractiveConfirmation();
205
205
  renderer.setSilent(true);
206
- const { runWebUI } = await import("./webui-server-E3OPEKU5.js");
206
+ const { runWebUI } = await import("./webui-server-FTG7JAZU.js");
207
207
  const flagValue = (names) => {
208
208
  for (const name of names) {
209
209
  if (!Object.hasOwn(flags, name)) continue;
@@ -985,8 +985,13 @@ import {
985
985
  DefaultSystemPromptBuilder,
986
986
  setQueuedMessagesSnapshot
987
987
  } from "@wrongstack/core/agent";
988
+ import { TOKENS } from "@wrongstack/core/kernel";
988
989
  import { DefaultSessionStore } from "@wrongstack/core/storage";
989
- import { resolveWstackPaths, sessionScopedPath } from "@wrongstack/core/utils";
990
+ import {
991
+ activateProjectStateGuard,
992
+ resolveWstackPaths,
993
+ sessionScopedPath
994
+ } from "@wrongstack/core/utils";
990
995
  async function switchProjectInPlace(ctx, targetRoot, displayName) {
991
996
  const {
992
997
  state,
@@ -1064,6 +1069,7 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
1064
1069
  });
1065
1070
  targetActivated = true;
1066
1071
  await state.activateSessionIdentity(nextWriter.id, target);
1072
+ await activateProjectStateGuard(resolved);
1067
1073
  } catch (err) {
1068
1074
  if (!targetActivated) await targetClaim?.cancel().catch(() => void 0);
1069
1075
  else if (oldWriter) {
@@ -1124,6 +1130,13 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
1124
1130
  modeStore,
1125
1131
  modeId: modeId ?? "default",
1126
1132
  modePrompt: switchMode?.prompt ?? "",
1133
+ tokenSavingMode: config.features.tokenSavingMode,
1134
+ modelCapabilities: {
1135
+ maxContextTokens: context.provider.capabilities.maxContext,
1136
+ supportsTools: !!context.provider.capabilities.tools,
1137
+ supportsVision: !!context.provider.capabilities.vision,
1138
+ supportsReasoning: !!context.provider.capabilities.reasoning
1139
+ },
1127
1140
  instructionPaths: {
1128
1141
  globalDir: nextWpaths.globalInstructions,
1129
1142
  projectDir: nextWpaths.inProjectInstructions,
@@ -1138,6 +1151,11 @@ async function switchProjectInPlace(ctx, targetRoot, displayName) {
1138
1151
  provider: context.provider.id,
1139
1152
  model: context.model
1140
1153
  });
1154
+ if (agent.container.has(TOKENS.SystemPromptBuilder)) {
1155
+ agent.container.override(TOKENS.SystemPromptBuilder, () => switchBuilder, {
1156
+ owner: "tui-project-switch"
1157
+ });
1158
+ }
1141
1159
  } catch (err) {
1142
1160
  console.error(
1143
1161
  JSON.stringify({
@@ -1298,6 +1316,7 @@ async function resumeSession(ctx, sessionId) {
1298
1316
  throw err;
1299
1317
  }
1300
1318
  writerSwapped = true;
1319
+ state.sessionRef && (state.sessionRef.current = resumed.writer);
1301
1320
  await agent.ctx.flushConversationJournal().catch((err) => {
1302
1321
  console.error(
1303
1322
  JSON.stringify({
@@ -1854,24 +1873,9 @@ function createSettingsAdapter(ctx) {
1854
1873
  // src/boot/tui-theme-adapter.ts
1855
1874
  import * as fs4 from "node:fs/promises";
1856
1875
  import * as path9 from "node:path";
1876
+ import { THEME_PRESET_IDS } from "@wrongstack/core/types";
1857
1877
  import { atomicWrite as atomicWrite2 } from "@wrongstack/core/utils";
1858
- var VALID_PRESETS = /* @__PURE__ */ new Set([
1859
- "catppuccin",
1860
- "tokyo-night",
1861
- "nord",
1862
- "cyberpunk",
1863
- "dracula",
1864
- "gruvbox-dark",
1865
- "solarized-dark",
1866
- "one-dark",
1867
- "monokai",
1868
- "rose-pine",
1869
- "kanagawa",
1870
- "ayu-dark",
1871
- "everforest",
1872
- "night-owl",
1873
- "synthwave"
1874
- ]);
1878
+ var VALID_PRESETS = new Set(THEME_PRESET_IDS);
1875
1879
  function createThemeAdapter({ configStore, wpaths }) {
1876
1880
  return {
1877
1881
  getThemePreset: () => {
@@ -2319,7 +2323,9 @@ async function finalizeExecutionCleanup(input) {
2319
2323
 
2320
2324
  // src/execution-kanban-dispatch.ts
2321
2325
  import { randomUUID } from "node:crypto";
2326
+ import { missingRequiredRuntimeTools } from "@wrongstack/core/agent-catalog";
2322
2327
  import { WIDE_SUBAGENT_CAPABILITIES } from "@wrongstack/core/security";
2328
+ import { stripFrontmatter } from "@wrongstack/core/skills";
2323
2329
 
2324
2330
  // src/kanban-dispatch-route.ts
2325
2331
  import { fallbackProfileChain, parseModelRef as parseModelRef2 } from "@wrongstack/core/agent";
@@ -2358,21 +2364,43 @@ function createKanbanDispatchHandler({
2358
2364
  } = resolveKanbanDispatchRoute(config, spawnOpts);
2359
2365
  let agentDescription = description;
2360
2366
  if (spawnOpts?.skills?.length && skillLoader) {
2361
- const loaded = await Promise.all(
2362
- spawnOpts.skills.map(async (skillName) => {
2367
+ const availableToolNames = spawnOpts.tools;
2368
+ const loaded = [];
2369
+ for (const skillName of spawnOpts.skills) {
2370
+ try {
2363
2371
  const manifest = await skillLoader.find(skillName);
2364
- if (!manifest) throw new Error(`Kanban skill not found: ${skillName}`);
2365
- const body = await skillLoader.readBody(skillName);
2366
- return `## Required skill: ${skillName}
2372
+ if (!manifest) {
2373
+ process.emitWarning(`Kanban dispatch skill not found, skipped: ${skillName}`, {
2374
+ code: "WRONGSTACK_KANBAN_SKILL_SKIPPED"
2375
+ });
2376
+ continue;
2377
+ }
2378
+ if (availableToolNames !== void 0 && missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0) {
2379
+ process.emitWarning(
2380
+ `Kanban dispatch skill "${skillName}" needs tools this worker does not have, skipped.`,
2381
+ { code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
2382
+ );
2383
+ continue;
2384
+ }
2385
+ const body = stripFrontmatter(await skillLoader.readBody(skillName)).trim();
2386
+ if (!body) continue;
2387
+ loaded.push(`## Required skill: ${skillName}
2367
2388
 
2368
- ${body}`;
2369
- })
2370
- );
2371
- agentDescription = `${description}
2389
+ ${body}`);
2390
+ } catch (err) {
2391
+ process.emitWarning(
2392
+ `Kanban dispatch skill "${skillName}" could not be loaded, skipped: ${err instanceof Error ? err.message : String(err)}`,
2393
+ { code: "WRONGSTACK_KANBAN_SKILL_SKIPPED" }
2394
+ );
2395
+ }
2396
+ }
2397
+ if (loaded.length > 0) {
2398
+ agentDescription = `${description}
2372
2399
 
2373
2400
  # Required agentic skill instructions
2374
2401
 
2375
2402
  ${loaded.join("\n\n")}`;
2403
+ }
2376
2404
  }
2377
2405
  void (async () => {
2378
2406
  const built = await sddSubagentFactory({
@@ -4738,6 +4766,7 @@ async function execute(deps) {
4738
4766
  positional,
4739
4767
  slashRegistry,
4740
4768
  tokenCounter,
4769
+ sessionRef,
4741
4770
  activateSessionIdentity,
4742
4771
  updateInfo: initialUpdateInfo,
4743
4772
  webuiSessionChild
@@ -4934,6 +4963,12 @@ async function execute(deps) {
4934
4963
  activeSessionStore,
4935
4964
  activateSessionIdentity,
4936
4965
  detachActiveTodosCheckpoint,
4966
+ // Plumbed from cli-main.ts so `resumeSession` can repoint the ref
4967
+ // when an in-process `/resume` swaps the active writer. Optional
4968
+ // on TuiRuntimeState; tests/hosts that omit it revert to the
4969
+ // pre-refactor behavior where provider calls stay pinned to the
4970
+ // boot session.
4971
+ sessionRef,
4937
4972
  pendingProjectSwitch: null,
4938
4973
  autonomousCoordinator: null,
4939
4974
  coordinatorRun: null,
@@ -5312,4 +5347,4 @@ export {
5312
5347
  execute,
5313
5348
  resolveReviewerFallbackModels
5314
5349
  };
5315
- //# sourceMappingURL=execution-MVOH6PAQ.js.map
5350
+ //# sourceMappingURL=execution-QYFWDD3Y.js.map
@@ -6,6 +6,8 @@ export interface SkillResolutionReport {
6
6
  selected: string[];
7
7
  /** skill → why it was dropped, so the omission is never silent. */
8
8
  dropped: Record<string, 'not-found' | 'missing-capability' | 'missing-tool' | 'budget' | 'empty'>;
9
+ /** Skills kept under budget by shortening their bundled body. */
10
+ trimmed: string[];
9
11
  }
10
12
  export declare function resolveHostSubagentSkillResolution(deps: MultiAgentDeps, roster: Record<string, SubagentConfig>, subCfg: SubagentConfig, availableToolNames?: readonly string[]): Promise<SkillResolutionReport>;
11
13
  /** Backwards-compatible wrapper for callers that only need the prompt text. */
@@ -1,3 +1,36 @@
1
1
  import type { SessionWriter } from '@wrongstack/core/types';
2
+ /**
3
+ * Session writer for a subagent that has no journal of its own: its events are
4
+ * interleaved into the parent's JSONL.
5
+ *
6
+ * Lifecycle and rewind *control* stay no-ops — `writeCheckpoint`,
7
+ * `writeInFlightMarker`, `truncateToCheckpoint`, `clearSession` and `close`
8
+ * belong to the parent, and a subagent driving them would corrupt the parent's
9
+ * checkpoint chain and crash-recovery markers.
10
+ *
11
+ * Rewind *evidence* is a different thing and is forwarded. A subagent's file
12
+ * mutations are real edits to the user's working tree, made under the parent
13
+ * prompt that spawned it; dropping their `file_snapshot` records left `/rewind`
14
+ * quietly unable to undo them, with nothing in the transcript saying so. The
15
+ * parent writer files them against its own `activePromptIndex`, which is
16
+ * exactly the prompt a user rewinding this work would target.
17
+ */
2
18
  export declare function createParentSubagentSessionWriter(parentSession: SessionWriter): SessionWriter;
19
+ /**
20
+ * Wrap a subagent's own `SessionWriter` so its file mutations are ALSO recorded
21
+ * against the parent session.
22
+ *
23
+ * When a subagent gets a real per-subagent JSONL, its `file_snapshot` events
24
+ * land in that file — under the director run directory, which
25
+ * `DefaultSessionRewinder` never reads: it resolves one path, the session being
26
+ * rewound. So the richer, "proper" subagent-session path was the one that lost
27
+ * rewind coverage entirely, while the fallback shim above at least had the
28
+ * parent handle in reach.
29
+ *
30
+ * Recording to the parent as well keeps one authority for undo (the session the
31
+ * user actually rewinds) without taking anything away from the subagent's own
32
+ * transcript, which still receives every other event including the conversation
33
+ * that produced the edit.
34
+ */
35
+ export declare function withParentFileSnapshots(subagentSession: SessionWriter, parentSession: SessionWriter): SessionWriter;
3
36
  //# sourceMappingURL=host-session-writer.d.ts.map
@@ -211,6 +211,16 @@ export declare class MultiAgentHost {
211
211
  * Returns null when auto-optimization is switched off.
212
212
  */
213
213
  private getLearningOptimizer;
214
+ /**
215
+ * Break a dispatch tie with a model when the keyword heuristic is ambiguous.
216
+ *
217
+ * Routes through the `dispatcher` model-matrix slot when one is configured —
218
+ * picking a role is a short classification, so it belongs on a cheap fast
219
+ * model rather than the leader's — and the session default otherwise.
220
+ * Returning `null` on any failure leaves the dispatcher on its heuristic
221
+ * result, so routing degrades rather than breaking a spawn.
222
+ */
223
+ private classifyDispatch;
214
224
  /**
215
225
  * Resolve a model for the distillation pass. Uses the `memory-curator` slot
216
226
  * of the model matrix when one is configured — curating learned knowledge is
@@ -47,7 +47,7 @@ var hqCmd = async (args, deps) => {
47
47
  return 1;
48
48
  };
49
49
  async function startServer(deps) {
50
- const { startHqServer } = await import("./hq-server-DME6EWIO.js");
50
+ const { startHqServer } = await import("./hq-server-IVZLVCFV.js");
51
51
  const dataDir = resolveDataDir(deps);
52
52
  const flags = deps.flags ?? {};
53
53
  const host = typeof flags["host"] === "string" ? flags["host"] : HQ_CLI_DEFAULT_HOST;
@@ -612,4 +612,4 @@ export {
612
612
  hqCmd,
613
613
  resolveAuditActor
614
614
  };
615
- //# sourceMappingURL=hq-CD77GVSS.js.map
615
+ //# sourceMappingURL=hq-5G6D5YIU.js.map
@@ -15,7 +15,7 @@ import {
15
15
  readLocalSubagentTranscript,
16
16
  sanitizeApiError,
17
17
  startHqServer
18
- } from "./chunk-LUAFQIXT.js";
18
+ } from "./chunk-RHLQT3EV.js";
19
19
  import "./chunk-KE7E7DPX.js";
20
20
  import "./chunk-Q5GTM25S.js";
21
21
  import "./chunk-7OCVIDC7.js";
@@ -37,4 +37,4 @@ export {
37
37
  sanitizeApiError,
38
38
  startHqServer
39
39
  };
40
- //# sourceMappingURL=hq-server-DME6EWIO.js.map
40
+ //# sourceMappingURL=hq-server-IVZLVCFV.js.map