@wrongstack/cli 0.148.0 → 0.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens } from '@wrongstack/core';
3
2
  import * as fsp4 from 'fs/promises';
4
- import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
5
3
  import * as path10 from 'path';
6
4
  import { join } from 'path';
5
+ import { color, writeErr, expectDefined, atomicWrite, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, resolveContextWindowPolicy, DEFAULT_CONTEXT_WINDOW_MODE_ID, listContextWindowModes, repairToolUseAdjacency, TOKENS, DefaultPathResolver, EventBus, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, noOpVault, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore as DefaultSessionStore$1, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens, AGENTS_BY_PHASE } from '@wrongstack/core';
6
+ import { decryptConfigSecrets, encryptConfigSecrets, DefaultSecretVault, isSecretField } from '@wrongstack/core/security';
7
7
  import { createRequire } from 'module';
8
8
  import * as os2 from 'os';
9
9
  import os2__default from 'os';
10
10
  import * as crypto2 from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
13
+ import { DefaultSessionStore } from '@wrongstack/core/storage';
13
14
  import { WebSocketServer, WebSocket } from 'ws';
14
15
  import { spawn, exec, execFileSync } from 'child_process';
15
16
  import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
@@ -36,6 +37,218 @@ var __export = (target, all) => {
36
37
  for (var name in all)
37
38
  __defProp(target, name, { get: all[name], enumerable: true });
38
39
  };
40
+ function parseSubcommand(args) {
41
+ const parts = args.trim().split(/\s+/);
42
+ return { cmd: (parts[0] ?? "").toLowerCase(), rest: parts.slice(1) };
43
+ }
44
+ function unknownSubcommand(cmd, valid, name) {
45
+ const list = valid.join(", ");
46
+ return name ? `Unknown subcommand "${cmd}" for /${name}. Valid: ${list}.` : `Unknown subcommand "${cmd}". Valid: ${list}.`;
47
+ }
48
+ async function pathExists(file) {
49
+ try {
50
+ await fsp4.access(file);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ async function detectPackageManager(root, declared) {
57
+ if (declared) {
58
+ const name = declared.split("@")[0];
59
+ if (name) return name;
60
+ }
61
+ if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
62
+ if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
63
+ if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
64
+ if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
65
+ return "npm";
66
+ }
67
+ function hasUsableScript(scripts, name) {
68
+ const script = scripts[name];
69
+ if (typeof script !== "string" || script.trim() === "") return false;
70
+ if (name === "test" && /no test specified/i.test(script)) return false;
71
+ return true;
72
+ }
73
+ function parseMakeTargets(makefile) {
74
+ const targets = /* @__PURE__ */ new Set();
75
+ for (const line of makefile.split(/\r?\n/)) {
76
+ if (line.startsWith(" ") || line.trimStart().startsWith("#")) continue;
77
+ const match = /^([A-Za-z0-9_.-]+)\s*:(?![=])/.exec(line);
78
+ if (match?.[1]) targets.add(match[1]);
79
+ }
80
+ return targets;
81
+ }
82
+ async function detectProjectFacts(root) {
83
+ const facts = { hints: [] };
84
+ try {
85
+ const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
86
+ const scripts = pkg.scripts ?? {};
87
+ const pm = await detectPackageManager(root, pkg.packageManager);
88
+ if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
89
+ if (hasUsableScript(scripts, "test")) facts.test = `${pm} test`;
90
+ if (hasUsableScript(scripts, "lint")) facts.lint = `${pm} run lint`;
91
+ const runScript = ["dev", "start", "serve", "preview"].find(
92
+ (name) => hasUsableScript(scripts, name)
93
+ );
94
+ if (runScript) facts.run = `${pm} run ${runScript}`;
95
+ facts.hints.push(Object.keys(scripts).length > 0 ? "package.json scripts" : "package.json");
96
+ } catch {
97
+ }
98
+ try {
99
+ if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
100
+ facts.test ??= "pytest";
101
+ facts.lint ??= "ruff check .";
102
+ facts.hints.push("pyproject.toml");
103
+ } catch {
104
+ }
105
+ try {
106
+ if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
107
+ facts.build ??= "go build ./...";
108
+ facts.test ??= "go test ./...";
109
+ facts.run ??= "go run .";
110
+ facts.hints.push("go.mod");
111
+ } catch {
112
+ }
113
+ try {
114
+ if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
115
+ facts.build ??= "cargo build";
116
+ facts.test ??= "cargo test";
117
+ facts.lint ??= "cargo clippy";
118
+ facts.run ??= "cargo run";
119
+ facts.hints.push("Cargo.toml");
120
+ } catch {
121
+ }
122
+ try {
123
+ const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
124
+ const targets = parseMakeTargets(makefile);
125
+ facts.build ??= targets.has("build") ? "make build" : "make";
126
+ if (targets.has("test")) facts.test ??= "make test";
127
+ if (targets.has("lint")) facts.lint ??= "make lint";
128
+ const runTarget = ["run", "dev", "start", "serve"].find((name) => targets.has(name));
129
+ if (runTarget) facts.run ??= `make ${runTarget}`;
130
+ facts.hints.push("Makefile");
131
+ } catch {
132
+ }
133
+ return facts;
134
+ }
135
+ function renderAgentsTemplate(f) {
136
+ const cmd = (s) => s ? `\`${s}\`` : "_TODO_";
137
+ const hints = f.hints.length > 0 ? `
138
+
139
+ > Auto-detected: ${f.hints.join(", ")}` : "";
140
+ return `# AGENTS.md
141
+
142
+ > **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
143
+ > persistent project context. Previous content here may contain decisions,
144
+ > architecture notes, domain knowledge, or verification history that should be
145
+ > preserved. Merge additions rather than replacing.
146
+
147
+ ## Project brief
148
+
149
+ - **Purpose:** _What does this project do and why does it exist?_
150
+ - **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
151
+ - **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
152
+
153
+ ## How to work safely
154
+
155
+ - _Project-specific rules the agent should always follow._
156
+ - _Files, generated artifacts, migrations, or config the agent should not edit without asking._
157
+ - _Preferred style or architecture choices not obvious from the code._
158
+ - _Known fragile areas or historical bugs that deserve extra caution._
159
+
160
+ ## Commands
161
+
162
+ | Command | Script |
163
+ |---------|--------|
164
+ | Build | ${cmd(f.build)} |
165
+ | Test | ${cmd(f.test)} |
166
+ | Lint | ${cmd(f.lint)} |
167
+ | Run locally | ${cmd(f.run)} |
168
+
169
+ ## Key files and entry points
170
+
171
+ | File / directory | Role |
172
+ |---|---|
173
+ | _src/_ | _Main source entry point(s)_ |
174
+ | _tests/_ | _Test root or convention_ |
175
+ | _docs/_ | _Architecture, runbooks, design notes_ |
176
+ | _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
177
+
178
+ ## Architecture notes
179
+
180
+ _Summarize the important modules, data flow, boundaries, and ownership rules.
181
+ Mention anything a newcomer might misread or that looks unusual but is intentional._
182
+
183
+ ### Dependency layers
184
+
185
+ _Describe the key dependency direction or layered structure, e.g.: "core has no
186
+ runtime deps; cli assembles everything above it."_
187
+
188
+ ### Extension points
189
+
190
+ _Plugin, MCP, extension hooks, custom tools \u2014 what's wired up and how._
191
+
192
+ ## Domain knowledge
193
+
194
+ _Business rules, acronyms, invariants, external services, and notes where the
195
+ code looks unusual but is intentional. E.g.: "IDs are ULIDs, not UUIDs", "the
196
+ \`draft\` flag means uncommitted billing metadata", "MCP servers are restarted
197
+ on disconnect with exponential backoff, up to 3 attempts"._
198
+
199
+ ## Verification checklist
200
+
201
+ - _What should be run after code changes?_
202
+ - _What manual smoke test proves the common path still works?_
203
+ - _What failure modes deserve extra attention?_
204
+ - _Any known flaky tests or environment-dependent behavior?_
205
+
206
+ ## Useful pointers
207
+
208
+ - _Docs, dashboards, runbooks, issue trackers, design notes, owner contacts._
209
+ - _Related projects or repositories._`;
210
+ }
211
+ function countTurnPairs(messages) {
212
+ let count = 0;
213
+ for (const m of messages) {
214
+ if (m.role === "user" || m.role === "assistant") count++;
215
+ }
216
+ return Math.floor(count / 2);
217
+ }
218
+ function countToolUses(messages) {
219
+ let count = 0;
220
+ for (const m of messages) {
221
+ if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_use").length;
222
+ }
223
+ return count;
224
+ }
225
+ function countToolResults(messages) {
226
+ let count = 0;
227
+ for (const m of messages) {
228
+ if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_result").length;
229
+ }
230
+ return count;
231
+ }
232
+ function estimateTokens(messages) {
233
+ return estimateMessageTokens(messages);
234
+ }
235
+ var init_helpers = __esm({
236
+ "src/slash-commands/helpers.ts"() {
237
+ }
238
+ });
239
+
240
+ // src/provider-config-utils.ts
241
+ var provider_config_utils_exports = {};
242
+ __export(provider_config_utils_exports, {
243
+ activeLabel: () => activeLabel,
244
+ expectDefined: () => expectDefined,
245
+ loadConfigProviders: () => loadConfigProviders,
246
+ maskedKey: () => maskedKey,
247
+ mutateConfigProviders: () => mutateConfigProviders,
248
+ normalizeKeys: () => normalizeKeys,
249
+ nowIso: () => nowIso,
250
+ writeKeysBack: () => writeKeysBack
251
+ });
39
252
  function normalizeKeys(cfg) {
40
253
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
41
254
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -655,17 +868,17 @@ function buildSddCommand(opts) {
655
868
  const specsDir = opts.paths.projectSpecs;
656
869
  const specStore = new SpecStore({ baseDir: specsDir });
657
870
  const versioning = sddState.getVersioning();
658
- const [verb, ...rest] = args.trim().split(/\s+/);
659
- const restJoined = rest.join(" ").trim();
660
- switch (verb) {
871
+ const { cmd, rest: restArgs } = parseSubcommand(args);
872
+ const restJoined = restArgs.join(" ").trim();
873
+ switch (cmd) {
661
874
  case "":
662
875
  case "help":
663
876
  return { message: sddHelp() };
664
877
  // ── AI-Driven Spec Session ─────────────────────────────────────────
665
878
  case "new":
666
879
  case "create": {
667
- const forceFlag = rest.includes("--force") || rest.includes("-f");
668
- const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
880
+ const forceFlag = restArgs.includes("--force") || restArgs.includes("-f");
881
+ const title = restArgs.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
669
882
  if (!sessionState.getBuilder() && !forceFlag) {
670
883
  const sessionPath = opts.paths.projectSddSession;
671
884
  try {
@@ -1571,7 +1784,7 @@ ${lines.join("\n")}`
1571
1784
  }
1572
1785
  default:
1573
1786
  return {
1574
- message: `Unknown command "${verb}".
1787
+ message: `${unknownSubcommand(cmd, ["new", "approve", "execute", "cancel", "status", "list", "show", "templates", "resume"], "sdd")}
1575
1788
 
1576
1789
  ${sddHelp()}`
1577
1790
  };
@@ -1581,6 +1794,7 @@ ${sddHelp()}`
1581
1794
  }
1582
1795
  var init_sdd = __esm({
1583
1796
  "src/slash-commands/sdd.ts"() {
1797
+ init_helpers();
1584
1798
  init_state();
1585
1799
  init_task_manager();
1586
1800
  init_project_context();
@@ -1722,6 +1936,91 @@ var webui_server_exports = {};
1722
1936
  __export(webui_server_exports, {
1723
1937
  runWebUI: () => runWebUI
1724
1938
  });
1939
+ function isHiddenEntry(name) {
1940
+ return name.startsWith(".") && !KEEP_DOTFILES.has(name);
1941
+ }
1942
+ function rankFiles(paths, query, limit) {
1943
+ const q = query.toLowerCase();
1944
+ const scored = [];
1945
+ for (const p of paths) {
1946
+ if (!q) {
1947
+ scored.push({ path: p, score: 0 });
1948
+ continue;
1949
+ }
1950
+ const lower = p.toLowerCase();
1951
+ const base = lower.split("/").pop() ?? lower;
1952
+ let score = 0;
1953
+ if (base === q) score = 100;
1954
+ else if (base.startsWith(q)) score = 60;
1955
+ else if (lower.includes(q)) score = 20;
1956
+ else continue;
1957
+ score -= p.split("/").length;
1958
+ scored.push({ path: p, score });
1959
+ }
1960
+ scored.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
1961
+ return scored.slice(0, limit).map((s) => s.path);
1962
+ }
1963
+ function estimateTokens2(s) {
1964
+ return Math.ceil(s.length / 4);
1965
+ }
1966
+ function stringifyContent(c) {
1967
+ if (typeof c === "string") return c;
1968
+ try {
1969
+ return JSON.stringify(c);
1970
+ } catch {
1971
+ return String(c);
1972
+ }
1973
+ }
1974
+ function messageTokens(content) {
1975
+ if (typeof content === "string") return estimateTokens2(content);
1976
+ if (!Array.isArray(content)) return 0;
1977
+ let tk = 0;
1978
+ for (const b of content) {
1979
+ if (b.type === "text") tk += estimateTokens2(b.text ?? "");
1980
+ else if (b.type === "tool_use") tk += estimateTokens2(stringifyContent(b.input));
1981
+ else if (b.type === "tool_result") tk += estimateTokens2(stringifyContent(b.content));
1982
+ else tk += estimateTokens2(stringifyContent(b));
1983
+ }
1984
+ return tk;
1985
+ }
1986
+ function messagePreview(content) {
1987
+ if (typeof content === "string") return content.slice(0, 60);
1988
+ if (!Array.isArray(content)) return "";
1989
+ return content.map((b) => b.type === "text" ? (b.text ?? "").slice(0, 40) : b.type === "tool_use" ? `[tool_use: ${b.name}]` : b.type === "tool_result" ? "[tool_result]" : `[${b.type}]`).join(" ").slice(0, 60);
1990
+ }
1991
+ function estimateContextBreakdown(input) {
1992
+ const sysTokens = input.systemPrompt.reduce((acc, b) => acc + estimateTokens2(b.text ?? ""), 0);
1993
+ const toolBreakdown = input.tools.map((t) => {
1994
+ const schema = t.inputSchema ?? {};
1995
+ const desc = t.description ?? "";
1996
+ return { name: t.name, tokens: estimateTokens2(t.name) + estimateTokens2(desc) + estimateTokens2(stringifyContent(schema)) };
1997
+ });
1998
+ const toolTokens = toolBreakdown.reduce((a, b) => a + b.tokens, 0);
1999
+ const messageBreakdown = input.messages.map((m, i) => ({
2000
+ index: i,
2001
+ role: m.role,
2002
+ tokens: messageTokens(m.content),
2003
+ preview: messagePreview(m.content)
2004
+ }));
2005
+ const msgTokens = messageBreakdown.reduce((a, b) => a + b.tokens, 0);
2006
+ return {
2007
+ total: sysTokens + toolTokens + msgTokens,
2008
+ systemPrompt: sysTokens,
2009
+ tools: { total: toolTokens, count: input.tools.length, breakdown: toolBreakdown },
2010
+ messages: { total: msgTokens, count: input.messages.length, breakdown: messageBreakdown }
2011
+ };
2012
+ }
2013
+ function getCostRates(model) {
2014
+ const cost = model?.cost;
2015
+ return {
2016
+ input: cost?.input ?? 0,
2017
+ output: cost?.output ?? 0,
2018
+ cacheRead: cost?.cache_read ?? 0
2019
+ };
2020
+ }
2021
+ function computeUsageCost(usage, rates) {
2022
+ return (usage.input * rates.input + usage.output * rates.output + (usage.cacheRead ?? 0) * rates.cacheRead) / 1e6;
2023
+ }
1725
2024
  async function runWebUI(opts) {
1726
2025
  const host = "127.0.0.1";
1727
2026
  const requestedWsPort = opts.port ?? 3457;
@@ -2069,7 +2368,9 @@ async function runWebUI(opts) {
2069
2368
  sessionId: opts.session.id,
2070
2369
  model: opts.agent.ctx.model,
2071
2370
  provider: opts.agent.ctx.provider.id,
2072
- wsToken: authToken
2371
+ wsToken: authToken,
2372
+ mode: opts.modeId ?? "default",
2373
+ projectName: opts.projectRoot ? path10.basename(opts.projectRoot) : void 0
2073
2374
  }
2074
2375
  });
2075
2376
  });
@@ -2159,50 +2460,729 @@ async function runWebUI(opts) {
2159
2460
  await handleProviderRemove(ws, m.payload.providerId);
2160
2461
  break;
2161
2462
  }
2162
- default: {
2163
- console.debug(
2164
- `[WebUI] Unhandled message type: ${String(msg.type)}`
2165
- );
2463
+ case "todos.get": {
2464
+ send(ws, {
2465
+ type: "todos.updated",
2466
+ payload: { todos: [...opts.agent.ctx.todos] }
2467
+ });
2166
2468
  break;
2167
2469
  }
2168
- }
2169
- }
2170
- async function handleUserMessage(ws, _client, content) {
2171
- if (abortController) {
2172
- send(ws, {
2173
- type: "error",
2174
- payload: { phase: "agent.run", message: "A run is already in progress. Abort it first." }
2175
- });
2176
- return;
2177
- }
2178
- abortController = new AbortController();
2179
- try {
2180
- const result = await opts.agent.run(content, {
2181
- signal: abortController.signal
2182
- });
2183
- send(ws, {
2184
- type: "run.result",
2185
- payload: {
2186
- status: result.status,
2187
- iterations: result.iterations,
2188
- finalText: result.finalText,
2189
- error: result.error ? {
2190
- code: result.error.code,
2191
- message: result.error.message,
2192
- recoverable: result.error.recoverable
2193
- } : void 0
2194
- }
2195
- });
2196
- } catch (err) {
2197
- send(ws, {
2198
- type: "error",
2199
- payload: {
2200
- phase: "agent.run",
2201
- message: err instanceof Error ? err.message : String(err)
2470
+ case "goal.get": {
2471
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
2472
+ try {
2473
+ const goalPath = path10.join(projectRoot, ".wrongstack", "goal.json");
2474
+ const raw = await fsp4.readFile(goalPath, "utf8");
2475
+ const goal = JSON.parse(raw);
2476
+ broadcast({ type: "goal.updated", payload: goal });
2477
+ } catch {
2478
+ broadcast({ type: "goal.updated", payload: null });
2202
2479
  }
2203
- });
2204
- } finally {
2205
- abortController = null;
2480
+ break;
2481
+ }
2482
+ case "sessions.list": {
2483
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
2484
+ const sessionsDir = path10.join(projectRoot, ".wrongstack", "sessions");
2485
+ const limit = msg.payload?.limit ?? 50;
2486
+ try {
2487
+ const store = new DefaultSessionStore({ dir: sessionsDir });
2488
+ const list = await store.list(limit);
2489
+ send(ws, {
2490
+ type: "sessions.list",
2491
+ payload: {
2492
+ sessions: list.map((s) => ({
2493
+ id: s.id,
2494
+ title: s.title,
2495
+ startedAt: s.startedAt,
2496
+ model: s.model,
2497
+ provider: s.provider,
2498
+ tokenTotal: s.tokenTotal,
2499
+ isCurrent: s.id === opts.session.id
2500
+ }))
2501
+ }
2502
+ });
2503
+ } catch (err) {
2504
+ send(ws, {
2505
+ type: "sessions.list",
2506
+ payload: { sessions: [], error: err instanceof Error ? err.message : String(err) }
2507
+ });
2508
+ }
2509
+ break;
2510
+ }
2511
+ case "session.new": {
2512
+ const ctx = opts.agent.ctx;
2513
+ ctx.state.replaceMessages([]);
2514
+ ctx.state.replaceTodos([]);
2515
+ ctx.readFiles.clear();
2516
+ ctx.fileMtimes.clear();
2517
+ broadcast({
2518
+ type: "session.start",
2519
+ payload: {
2520
+ sessionId: opts.session.id,
2521
+ model: ctx.model,
2522
+ provider: opts.agent.ctx.provider.id,
2523
+ reset: true
2524
+ }
2525
+ });
2526
+ break;
2527
+ }
2528
+ case "todos.clear": {
2529
+ opts.agent.ctx.state.replaceTodos([]);
2530
+ sendResult(ws, true, "Todos cleared");
2531
+ broadcast({ type: "todos.updated", payload: { todos: [] } });
2532
+ break;
2533
+ }
2534
+ case "todos.remove": {
2535
+ const payload = msg.payload;
2536
+ if (!payload) {
2537
+ sendResult(ws, false, "Missing id or index");
2538
+ break;
2539
+ }
2540
+ const { id, index } = payload;
2541
+ const todos = opts.agent.ctx.todos;
2542
+ let targetIdx = -1;
2543
+ if (typeof id === "string") {
2544
+ targetIdx = todos.findIndex((t) => t.id === id);
2545
+ } else if (typeof index === "number" && index > 0) {
2546
+ targetIdx = index - 1;
2547
+ }
2548
+ if (targetIdx < 0 || !todos[targetIdx]) {
2549
+ sendResult(ws, false, "Todo not found");
2550
+ break;
2551
+ }
2552
+ const removed = expectDefined(todos[targetIdx]);
2553
+ const next = [...todos.slice(0, targetIdx), ...todos.slice(targetIdx + 1)];
2554
+ opts.agent.ctx.state.replaceTodos(next);
2555
+ sendResult(ws, true, `Removed: ${removed.content}`);
2556
+ broadcast({ type: "todos.updated", payload: { todos: next } });
2557
+ break;
2558
+ }
2559
+ case "context.clear": {
2560
+ const ctx = opts.agent.ctx;
2561
+ ctx.state.replaceMessages([]);
2562
+ ctx.state.replaceTodos([]);
2563
+ ctx.readFiles.clear();
2564
+ ctx.fileMtimes.clear();
2565
+ sendResult(ws, true, "Context cleared");
2566
+ broadcast({
2567
+ type: "session.start",
2568
+ payload: {
2569
+ sessionId: opts.session.id,
2570
+ model: ctx.model,
2571
+ provider: ctx.provider.id,
2572
+ reset: true
2573
+ }
2574
+ });
2575
+ break;
2576
+ }
2577
+ case "process.list": {
2578
+ try {
2579
+ const { getProcessRegistry } = await import('@wrongstack/tools');
2580
+ const procs = getProcessRegistry().list();
2581
+ send(ws, {
2582
+ type: "process.list",
2583
+ payload: {
2584
+ processes: procs.map((p) => ({
2585
+ pid: p.pid,
2586
+ command: p.command,
2587
+ tool: p.name,
2588
+ startedAt: p.startedAt,
2589
+ status: p.killed ? "killed" : "running",
2590
+ protected: p.protected
2591
+ }))
2592
+ }
2593
+ });
2594
+ } catch {
2595
+ send(ws, { type: "process.list", payload: { processes: [] } });
2596
+ }
2597
+ break;
2598
+ }
2599
+ case "process.kill": {
2600
+ const { pid } = msg.payload;
2601
+ try {
2602
+ const { getProcessRegistry } = await import('@wrongstack/tools');
2603
+ const proc = getProcessRegistry().get(pid);
2604
+ if (proc?.protected) {
2605
+ sendResult(ws, false, `Cannot kill protected process (PID ${pid})`);
2606
+ break;
2607
+ }
2608
+ getProcessRegistry().kill(pid);
2609
+ sendResult(ws, true, `Killed PID ${pid}`);
2610
+ } catch (err) {
2611
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
2612
+ }
2613
+ break;
2614
+ }
2615
+ case "process.killAll": {
2616
+ try {
2617
+ const { getProcessRegistry } = await import('@wrongstack/tools');
2618
+ getProcessRegistry().killAll();
2619
+ sendResult(ws, true, "All processes killed");
2620
+ } catch (err) {
2621
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
2622
+ }
2623
+ break;
2624
+ }
2625
+ case "diag.get": {
2626
+ const ctx = opts.agent.ctx;
2627
+ const tools = opts.agent.tools.list();
2628
+ send(ws, {
2629
+ type: "diag.get",
2630
+ payload: {
2631
+ provider: ctx.provider.id,
2632
+ model: ctx.model,
2633
+ cwd: opts.projectRoot ?? ctx.projectRoot,
2634
+ sessionId: opts.session.id,
2635
+ tools: {
2636
+ count: tools.length,
2637
+ names: tools.map((t) => t.name)
2638
+ },
2639
+ features: {},
2640
+ mode: "default",
2641
+ usage: ctx.tokenCounter.total(),
2642
+ messages: ctx.messages.length,
2643
+ todos: ctx.todos.length
2644
+ }
2645
+ });
2646
+ break;
2647
+ }
2648
+ case "stats.get": {
2649
+ const ctx = opts.agent.ctx;
2650
+ const usage = ctx.tokenCounter.total();
2651
+ const cacheStats = ctx.tokenCounter.cacheStats();
2652
+ let cost = null;
2653
+ try {
2654
+ if (opts.modelsRegistry) {
2655
+ const model = await opts.modelsRegistry.getModel(
2656
+ ctx.provider.id,
2657
+ ctx.model
2658
+ );
2659
+ const rates = getCostRates(model);
2660
+ cost = computeUsageCost(
2661
+ { input: usage.input, output: usage.output, cacheRead: cacheStats.readTokens },
2662
+ rates
2663
+ );
2664
+ }
2665
+ } catch {
2666
+ }
2667
+ send(ws, {
2668
+ type: "stats.get",
2669
+ payload: {
2670
+ sessionId: opts.session.id,
2671
+ provider: ctx.provider.id,
2672
+ model: ctx.model,
2673
+ usage,
2674
+ cache: cacheStats,
2675
+ cost,
2676
+ messages: ctx.messages.length,
2677
+ readFiles: ctx.readFiles.size,
2678
+ tools: opts.agent.tools.list().length,
2679
+ elapsedMs: 0
2680
+ }
2681
+ });
2682
+ break;
2683
+ }
2684
+ case "autonomy.switch": {
2685
+ const { mode } = msg.payload;
2686
+ opts.agent.ctx.meta["autonomy"] = mode;
2687
+ sendResult(ws, true, `Autonomy mode set to "${mode}"`);
2688
+ break;
2689
+ }
2690
+ case "tools.list": {
2691
+ const list = opts.agent.tools.list().map((t) => {
2692
+ const schema = t.inputSchema ?? {};
2693
+ const params = schema.properties ? Object.keys(schema.properties) : [];
2694
+ return {
2695
+ name: t.name,
2696
+ description: t.description ?? "",
2697
+ params
2698
+ };
2699
+ });
2700
+ send(ws, { type: "tools.list", payload: { tools: list } });
2701
+ break;
2702
+ }
2703
+ case "session.checkpoints": {
2704
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
2705
+ try {
2706
+ const { DefaultSessionRewinder: DefaultSessionRewinder2 } = await import('@wrongstack/core');
2707
+ const rewinder = new DefaultSessionRewinder2(
2708
+ path10.join(projectRoot, ".wrongstack", "sessions"),
2709
+ projectRoot
2710
+ );
2711
+ const checkpoints = await rewinder.listCheckpoints(opts.session.id);
2712
+ send(ws, {
2713
+ type: "session.checkpoints",
2714
+ payload: { checkpoints }
2715
+ });
2716
+ } catch {
2717
+ send(ws, {
2718
+ type: "session.checkpoints",
2719
+ payload: { checkpoints: [] }
2720
+ });
2721
+ }
2722
+ break;
2723
+ }
2724
+ case "files.list": {
2725
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
2726
+ const payload = msg.payload ?? {};
2727
+ const limit = payload.limit ?? 50;
2728
+ const results = [];
2729
+ async function walk(dir, rel, depth) {
2730
+ if (depth > 8 || results.length >= 600) return;
2731
+ let entries = [];
2732
+ try {
2733
+ entries = await fsp4.readdir(dir, { withFileTypes: true });
2734
+ } catch {
2735
+ return;
2736
+ }
2737
+ for (const e of entries) {
2738
+ if (results.length >= 600) return;
2739
+ if (isHiddenEntry(e.name)) continue;
2740
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
2741
+ if (e.isDirectory()) {
2742
+ if (SKIP_DIRS.has(e.name)) continue;
2743
+ await walk(path10.join(dir, e.name), childRel, depth + 1);
2744
+ } else if (e.isFile()) {
2745
+ results.push(childRel);
2746
+ }
2747
+ }
2748
+ }
2749
+ await walk(projectRoot, "", 0);
2750
+ send(ws, {
2751
+ type: "files.list",
2752
+ payload: { files: rankFiles(results, payload.query ?? "", limit) }
2753
+ });
2754
+ break;
2755
+ }
2756
+ case "session.delete": {
2757
+ const { id } = msg.payload;
2758
+ if (id === opts.session.id) {
2759
+ sendResult(ws, false, "Cannot delete the active session");
2760
+ break;
2761
+ }
2762
+ const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
2763
+ try {
2764
+ const store = new DefaultSessionStore({
2765
+ dir: path10.join(projectRoot, ".wrongstack", "sessions")
2766
+ });
2767
+ await store.delete(id);
2768
+ sendResult(ws, true, `Session ${id} deleted`);
2769
+ } catch (err) {
2770
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
2771
+ }
2772
+ break;
2773
+ }
2774
+ case "session.save":
2775
+ sendResult(ws, true, `Session ${opts.session.id} is auto-saved`);
2776
+ break;
2777
+ case "plan.get": {
2778
+ const planPath = opts.agent.ctx.meta["plan.path"];
2779
+ if (typeof planPath === "string" && planPath) {
2780
+ try {
2781
+ const { loadPlan: loadPlan2 } = await import('@wrongstack/core');
2782
+ const plan = await loadPlan2(planPath);
2783
+ send(ws, {
2784
+ type: "plan.updated",
2785
+ payload: {
2786
+ plan: plan ?? {
2787
+ version: 1,
2788
+ sessionId: opts.session.id,
2789
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2790
+ items: []
2791
+ }
2792
+ }
2793
+ });
2794
+ } catch {
2795
+ send(ws, {
2796
+ type: "plan.updated",
2797
+ payload: {
2798
+ plan: {
2799
+ version: 1,
2800
+ sessionId: opts.session.id,
2801
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2802
+ items: []
2803
+ }
2804
+ }
2805
+ });
2806
+ }
2807
+ } else {
2808
+ send(ws, {
2809
+ type: "plan.updated",
2810
+ payload: { plan: null, error: "Plan storage is not configured for this session." }
2811
+ });
2812
+ }
2813
+ break;
2814
+ }
2815
+ case "memory.list": {
2816
+ if (!opts.memoryStore) {
2817
+ send(ws, { type: "memory.list", payload: { text: "", error: "Memory store not available" } });
2818
+ break;
2819
+ }
2820
+ try {
2821
+ const text = await opts.memoryStore.readAll();
2822
+ send(ws, { type: "memory.list", payload: { text } });
2823
+ } catch (err) {
2824
+ send(ws, {
2825
+ type: "memory.list",
2826
+ payload: { text: "", error: err instanceof Error ? err.message : String(err) }
2827
+ });
2828
+ }
2829
+ break;
2830
+ }
2831
+ case "memory.remember": {
2832
+ if (!opts.memoryStore) {
2833
+ sendResult(ws, false, "Memory store not available");
2834
+ break;
2835
+ }
2836
+ const { text, scope } = msg.payload;
2837
+ try {
2838
+ await opts.memoryStore.remember(text, scope ?? "project-memory");
2839
+ sendResult(ws, true, "Saved to memory");
2840
+ } catch (err) {
2841
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
2842
+ }
2843
+ break;
2844
+ }
2845
+ case "memory.forget": {
2846
+ if (!opts.memoryStore) {
2847
+ sendResult(ws, false, "Memory store not available");
2848
+ break;
2849
+ }
2850
+ const { text, scope } = msg.payload;
2851
+ try {
2852
+ const removed = await opts.memoryStore.forget(text, scope ?? "project-memory");
2853
+ sendResult(
2854
+ ws,
2855
+ removed > 0,
2856
+ removed > 0 ? `Removed ${removed} entr${removed === 1 ? "y" : "ies"}` : "No matching entries"
2857
+ );
2858
+ } catch (err) {
2859
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
2860
+ }
2861
+ break;
2862
+ }
2863
+ case "skills.list": {
2864
+ if (!opts.skillLoader) {
2865
+ send(ws, { type: "skills.list", payload: { skills: [], enabled: false } });
2866
+ break;
2867
+ }
2868
+ try {
2869
+ const manifests = await opts.skillLoader.list();
2870
+ const entries = await opts.skillLoader.listEntries();
2871
+ const byName = new Map(entries.map((e) => [e.name, e]));
2872
+ send(ws, {
2873
+ type: "skills.list",
2874
+ payload: {
2875
+ enabled: true,
2876
+ skills: manifests.map((m) => ({
2877
+ name: m.name,
2878
+ description: m.description,
2879
+ version: m.version ?? "",
2880
+ source: m.source,
2881
+ path: m.path,
2882
+ trigger: byName.get(m.name)?.trigger ?? "",
2883
+ scope: byName.get(m.name)?.scope ?? []
2884
+ }))
2885
+ }
2886
+ });
2887
+ } catch (err) {
2888
+ send(ws, {
2889
+ type: "skills.list",
2890
+ payload: { skills: [], enabled: true, error: err instanceof Error ? err.message : String(err) }
2891
+ });
2892
+ }
2893
+ break;
2894
+ }
2895
+ case "modes.list": {
2896
+ if (!opts.modeStore) {
2897
+ send(ws, {
2898
+ type: "modes.list",
2899
+ payload: { modes: [], activeId: "default", error: "Mode store not available" }
2900
+ });
2901
+ break;
2902
+ }
2903
+ try {
2904
+ const modes = await opts.modeStore.listModes();
2905
+ const active = await opts.modeStore.getActiveMode();
2906
+ send(ws, {
2907
+ type: "modes.list",
2908
+ payload: {
2909
+ modes: modes.map((m) => ({
2910
+ id: m.id,
2911
+ name: m.name,
2912
+ description: m.description,
2913
+ isActive: m.id === (active?.id ?? "default")
2914
+ })),
2915
+ activeId: active?.id ?? "default"
2916
+ }
2917
+ });
2918
+ } catch (err) {
2919
+ send(ws, {
2920
+ type: "modes.list",
2921
+ payload: { modes: [], activeId: "default", error: err instanceof Error ? err.message : String(err) }
2922
+ });
2923
+ }
2924
+ break;
2925
+ }
2926
+ case "mode.switch": {
2927
+ if (!opts.modeStore) {
2928
+ sendResult(ws, false, "Mode store not available");
2929
+ break;
2930
+ }
2931
+ const { id } = msg.payload;
2932
+ try {
2933
+ if (id === "default") {
2934
+ await opts.modeStore.setActiveMode(null);
2935
+ } else {
2936
+ const found = await opts.modeStore.getMode(id);
2937
+ if (!found) throw new Error(`Unknown mode "${id}"`);
2938
+ await opts.modeStore.setActiveMode(id);
2939
+ }
2940
+ opts.agent.ctx.meta["mode"] = id;
2941
+ sendResult(ws, true, `Switched to mode "${id}"`);
2942
+ broadcast({
2943
+ type: "session.start",
2944
+ payload: {
2945
+ sessionId: opts.session.id,
2946
+ model: opts.agent.ctx.model,
2947
+ provider: opts.agent.ctx.provider.id,
2948
+ mode: id,
2949
+ reset: true
2950
+ }
2951
+ });
2952
+ } catch (err) {
2953
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
2954
+ }
2955
+ break;
2956
+ }
2957
+ case "model.switch": {
2958
+ const { provider: newProvider, model: newModel } = msg.payload;
2959
+ try {
2960
+ const ctx = opts.agent.ctx;
2961
+ ctx.model = newModel;
2962
+ const { makeProviderFromConfig: makeProviderFromConfig5 } = await import('@wrongstack/providers');
2963
+ const { loadConfigProviders: loadConfigProviders2 } = await Promise.resolve().then(() => (init_provider_config_utils(), provider_config_utils_exports));
2964
+ const saved = opts.globalConfigPath ? await loadConfigProviders2(opts.globalConfigPath, getVault()) : {};
2965
+ const providerCfg = saved[newProvider] ?? { type: newProvider };
2966
+ const newProv = makeProviderFromConfig5(newProvider, providerCfg);
2967
+ ctx.provider = newProv;
2968
+ send(ws, {
2969
+ type: "key.operation_result",
2970
+ payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
2971
+ });
2972
+ broadcast({
2973
+ type: "session.start",
2974
+ payload: {
2975
+ sessionId: opts.session.id,
2976
+ model: newModel,
2977
+ provider: newProvider
2978
+ }
2979
+ });
2980
+ } catch (err) {
2981
+ send(ws, {
2982
+ type: "key.operation_result",
2983
+ payload: {
2984
+ success: false,
2985
+ message: `Switch failed: ${err instanceof Error ? err.message : String(err)}`
2986
+ }
2987
+ });
2988
+ }
2989
+ break;
2990
+ }
2991
+ case "session.resume": {
2992
+ if (!opts.sessionStore) {
2993
+ sendResult(ws, false, "Session store not available");
2994
+ break;
2995
+ }
2996
+ const { id } = msg.payload;
2997
+ try {
2998
+ if (id === opts.session.id) {
2999
+ sendResult(ws, false, "Session is already active");
3000
+ break;
3001
+ }
3002
+ const resumed = await opts.sessionStore.resume(id);
3003
+ const ctx = opts.agent.ctx;
3004
+ ctx.state.replaceMessages(resumed.data.messages);
3005
+ ctx.state.replaceTodos([]);
3006
+ ctx.readFiles.clear();
3007
+ ctx.fileMtimes.clear();
3008
+ ctx.tokenCounter.reset();
3009
+ ctx.tokenCounter.account(resumed.data.usage, ctx.model);
3010
+ broadcast({
3011
+ type: "session.start",
3012
+ payload: {
3013
+ sessionId: opts.session.id,
3014
+ model: ctx.model,
3015
+ provider: ctx.provider.id,
3016
+ reset: true,
3017
+ replayMessages: resumed.data.messages,
3018
+ replayUsage: resumed.data.usage
3019
+ }
3020
+ });
3021
+ sendResult(ws, true, `Resumed session ${id}`);
3022
+ } catch (err) {
3023
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
3024
+ }
3025
+ break;
3026
+ }
3027
+ case "context.debug": {
3028
+ const ctx = opts.agent.ctx;
3029
+ const breakdown = estimateContextBreakdown({
3030
+ systemPrompt: ctx.systemPrompt,
3031
+ tools: opts.agent.tools.list(),
3032
+ messages: ctx.messages
3033
+ });
3034
+ send(ws, {
3035
+ type: "context.debug",
3036
+ payload: {
3037
+ ...breakdown,
3038
+ mode: ctx.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID,
3039
+ policy: ctx.meta["contextWindowPolicy"] ?? null
3040
+ }
3041
+ });
3042
+ break;
3043
+ }
3044
+ case "context.compact": {
3045
+ const aggressive = !!msg.payload?.aggressive;
3046
+ try {
3047
+ const compactor = opts.agent.container.resolve(TOKENS.Compactor);
3048
+ if (!compactor) {
3049
+ sendResult(ws, false, "Compactor not available");
3050
+ break;
3051
+ }
3052
+ const before = opts.agent.ctx.tokenCounter.total();
3053
+ const report = await compactor.compact(opts.agent.ctx, { aggressive });
3054
+ const after = opts.agent.ctx.tokenCounter.total();
3055
+ send(ws, {
3056
+ type: "context.compacted",
3057
+ payload: {
3058
+ before: before.input + before.output,
3059
+ after: after.input + after.output,
3060
+ saved: Math.max(0, before.input + before.output - after.input - after.output),
3061
+ reductions: report.reductions ?? [],
3062
+ repaired: report.repaired ?? false
3063
+ }
3064
+ });
3065
+ sendResult(
3066
+ ws,
3067
+ true,
3068
+ `Compacted: ${before.input + before.output} \u2192 ${after.input + after.output} tokens`
3069
+ );
3070
+ } catch (err) {
3071
+ sendResult(ws, false, err instanceof Error ? err.message : String(err));
3072
+ }
3073
+ break;
3074
+ }
3075
+ case "context.repair": {
3076
+ const ctx = opts.agent.ctx;
3077
+ const beforeMessages = ctx.messages.length;
3078
+ const repaired = repairToolUseAdjacency(ctx.messages);
3079
+ if (repaired.report.changed) {
3080
+ ctx.state.replaceMessages(repaired.messages);
3081
+ }
3082
+ const payload = {
3083
+ removedToolUses: repaired.report.removedToolUses,
3084
+ removedToolResults: repaired.report.removedToolResults,
3085
+ removedMessages: repaired.report.removedMessages,
3086
+ beforeMessages,
3087
+ afterMessages: ctx.messages.length
3088
+ };
3089
+ broadcast({ type: "context.repaired", payload });
3090
+ const removed = payload.removedToolUses.length + payload.removedToolResults.length + payload.removedMessages;
3091
+ sendResult(
3092
+ ws,
3093
+ true,
3094
+ removed > 0 ? `Context repaired: removed ${removed} orphan protocol item(s)` : "Context repair found no orphan protocol blocks"
3095
+ );
3096
+ break;
3097
+ }
3098
+ case "context.modes.list": {
3099
+ const active = String(
3100
+ opts.agent.ctx.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
3101
+ );
3102
+ send(ws, {
3103
+ type: "context.modes.list",
3104
+ payload: {
3105
+ activeId: active,
3106
+ modes: listContextWindowModes().map((m) => ({
3107
+ id: m.id,
3108
+ name: m.name,
3109
+ description: m.description,
3110
+ isActive: m.id === active,
3111
+ thresholds: m.thresholds,
3112
+ preserveK: m.preserveK,
3113
+ eliseThreshold: m.eliseThreshold
3114
+ }))
3115
+ }
3116
+ });
3117
+ break;
3118
+ }
3119
+ case "context.mode.switch": {
3120
+ const { id } = msg.payload;
3121
+ const policy = resolveContextWindowPolicy({}, id);
3122
+ if (policy.id !== id) {
3123
+ sendResult(ws, false, `Unknown context mode "${id}"`);
3124
+ break;
3125
+ }
3126
+ opts.agent.ctx.meta["contextWindowMode"] = policy.id;
3127
+ opts.agent.ctx.meta["contextWindowPolicy"] = policy;
3128
+ sendResult(ws, true, `Context mode switched to ${policy.id}`);
3129
+ broadcast({
3130
+ type: "context.mode.changed",
3131
+ payload: { id: policy.id, name: policy.name, policy }
3132
+ });
3133
+ break;
3134
+ }
3135
+ // Collaboration messages — the CLI webui-server doesn't run a
3136
+ // full collab hub; silently acknowledge and ignore.
3137
+ case "collab.join":
3138
+ case "collab.leave":
3139
+ case "collab.annotate":
3140
+ case "collab.resolve":
3141
+ break;
3142
+ default: {
3143
+ console.debug(
3144
+ `[WebUI] Unhandled message type: ${String(msg.type)}`
3145
+ );
3146
+ break;
3147
+ }
3148
+ }
3149
+ }
3150
+ async function handleUserMessage(ws, _client, content) {
3151
+ if (abortController) {
3152
+ send(ws, {
3153
+ type: "error",
3154
+ payload: { phase: "agent.run", message: "A run is already in progress. Abort it first." }
3155
+ });
3156
+ return;
3157
+ }
3158
+ abortController = new AbortController();
3159
+ try {
3160
+ const result = await opts.agent.run(content, {
3161
+ signal: abortController.signal
3162
+ });
3163
+ send(ws, {
3164
+ type: "run.result",
3165
+ payload: {
3166
+ status: result.status,
3167
+ iterations: result.iterations,
3168
+ finalText: result.finalText,
3169
+ error: result.error ? {
3170
+ code: result.error.code,
3171
+ message: result.error.message,
3172
+ recoverable: result.error.recoverable
3173
+ } : void 0
3174
+ }
3175
+ });
3176
+ } catch (err) {
3177
+ send(ws, {
3178
+ type: "error",
3179
+ payload: {
3180
+ phase: "agent.run",
3181
+ message: err instanceof Error ? err.message : String(err)
3182
+ }
3183
+ });
3184
+ } finally {
3185
+ abortController = null;
2206
3186
  }
2207
3187
  }
2208
3188
  function send(ws, msg) {
@@ -2423,9 +3403,32 @@ async function runWebUI(opts) {
2423
3403
  send(ws, { type: "key.operation_result", payload: { success, message } });
2424
3404
  }
2425
3405
  }
3406
+ var SKIP_DIRS, KEEP_DOTFILES;
2426
3407
  var init_webui_server = __esm({
2427
3408
  "src/webui-server.ts"() {
2428
3409
  init_provider_config_utils();
3410
+ SKIP_DIRS = /* @__PURE__ */ new Set([
3411
+ ".git",
3412
+ "node_modules",
3413
+ "dist",
3414
+ "build",
3415
+ ".next",
3416
+ ".turbo",
3417
+ ".cache",
3418
+ "target",
3419
+ "coverage",
3420
+ ".nyc_output",
3421
+ "out",
3422
+ ".pnpm-store",
3423
+ ".parcel-cache"
3424
+ ]);
3425
+ KEEP_DOTFILES = /* @__PURE__ */ new Set([
3426
+ ".wrongstack",
3427
+ ".env.example",
3428
+ ".gitignore",
3429
+ ".eslintrc",
3430
+ ".prettierrc"
3431
+ ]);
2429
3432
  }
2430
3433
  });
2431
3434
  var req = createRequire(import.meta.url);
@@ -2693,193 +3696,9 @@ function parseSpawnFlags(input) {
2693
3696
  }
2694
3697
  return { description: rest.trim(), opts };
2695
3698
  }
2696
- async function pathExists(file) {
2697
- try {
2698
- await fsp4.access(file);
2699
- return true;
2700
- } catch {
2701
- return false;
2702
- }
2703
- }
2704
- async function detectPackageManager(root, declared) {
2705
- if (declared) {
2706
- const name = declared.split("@")[0];
2707
- if (name) return name;
2708
- }
2709
- if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
2710
- if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
2711
- if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
2712
- if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
2713
- return "npm";
2714
- }
2715
- function hasUsableScript(scripts, name) {
2716
- const script = scripts[name];
2717
- if (typeof script !== "string" || script.trim() === "") return false;
2718
- if (name === "test" && /no test specified/i.test(script)) return false;
2719
- return true;
2720
- }
2721
- function parseMakeTargets(makefile) {
2722
- const targets = /* @__PURE__ */ new Set();
2723
- for (const line of makefile.split(/\r?\n/)) {
2724
- if (line.startsWith(" ") || line.trimStart().startsWith("#")) continue;
2725
- const match = /^([A-Za-z0-9_.-]+)\s*:(?![=])/.exec(line);
2726
- if (match?.[1]) targets.add(match[1]);
2727
- }
2728
- return targets;
2729
- }
2730
- async function detectProjectFacts(root) {
2731
- const facts = { hints: [] };
2732
- try {
2733
- const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
2734
- const scripts = pkg.scripts ?? {};
2735
- const pm = await detectPackageManager(root, pkg.packageManager);
2736
- if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
2737
- if (hasUsableScript(scripts, "test")) facts.test = `${pm} test`;
2738
- if (hasUsableScript(scripts, "lint")) facts.lint = `${pm} run lint`;
2739
- const runScript = ["dev", "start", "serve", "preview"].find(
2740
- (name) => hasUsableScript(scripts, name)
2741
- );
2742
- if (runScript) facts.run = `${pm} run ${runScript}`;
2743
- facts.hints.push(Object.keys(scripts).length > 0 ? "package.json scripts" : "package.json");
2744
- } catch {
2745
- }
2746
- try {
2747
- if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
2748
- facts.test ??= "pytest";
2749
- facts.lint ??= "ruff check .";
2750
- facts.hints.push("pyproject.toml");
2751
- } catch {
2752
- }
2753
- try {
2754
- if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
2755
- facts.build ??= "go build ./...";
2756
- facts.test ??= "go test ./...";
2757
- facts.run ??= "go run .";
2758
- facts.hints.push("go.mod");
2759
- } catch {
2760
- }
2761
- try {
2762
- if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
2763
- facts.build ??= "cargo build";
2764
- facts.test ??= "cargo test";
2765
- facts.lint ??= "cargo clippy";
2766
- facts.run ??= "cargo run";
2767
- facts.hints.push("Cargo.toml");
2768
- } catch {
2769
- }
2770
- try {
2771
- const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
2772
- const targets = parseMakeTargets(makefile);
2773
- facts.build ??= targets.has("build") ? "make build" : "make";
2774
- if (targets.has("test")) facts.test ??= "make test";
2775
- if (targets.has("lint")) facts.lint ??= "make lint";
2776
- const runTarget = ["run", "dev", "start", "serve"].find((name) => targets.has(name));
2777
- if (runTarget) facts.run ??= `make ${runTarget}`;
2778
- facts.hints.push("Makefile");
2779
- } catch {
2780
- }
2781
- return facts;
2782
- }
2783
- function renderAgentsTemplate(f) {
2784
- const cmd = (s) => s ? `\`${s}\`` : "_TODO_";
2785
- const hints = f.hints.length > 0 ? `
2786
-
2787
- > Auto-detected: ${f.hints.join(", ")}` : "";
2788
- return `# AGENTS.md
2789
-
2790
- > **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
2791
- > persistent project context. Previous content here may contain decisions,
2792
- > architecture notes, domain knowledge, or verification history that should be
2793
- > preserved. Merge additions rather than replacing.
2794
-
2795
- ## Project brief
2796
-
2797
- - **Purpose:** _What does this project do and why does it exist?_
2798
- - **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
2799
- - **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
2800
-
2801
- ## How to work safely
2802
-
2803
- - _Project-specific rules the agent should always follow._
2804
- - _Files, generated artifacts, migrations, or config the agent should not edit without asking._
2805
- - _Preferred style or architecture choices not obvious from the code._
2806
- - _Known fragile areas or historical bugs that deserve extra caution._
2807
-
2808
- ## Commands
2809
-
2810
- | Command | Script |
2811
- |---------|--------|
2812
- | Build | ${cmd(f.build)} |
2813
- | Test | ${cmd(f.test)} |
2814
- | Lint | ${cmd(f.lint)} |
2815
- | Run locally | ${cmd(f.run)} |
2816
-
2817
- ## Key files and entry points
2818
-
2819
- | File / directory | Role |
2820
- |---|---|
2821
- | _src/_ | _Main source entry point(s)_ |
2822
- | _tests/_ | _Test root or convention_ |
2823
- | _docs/_ | _Architecture, runbooks, design notes_ |
2824
- | _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
2825
-
2826
- ## Architecture notes
2827
-
2828
- _Summarize the important modules, data flow, boundaries, and ownership rules.
2829
- Mention anything a newcomer might misread or that looks unusual but is intentional._
2830
-
2831
- ### Dependency layers
2832
-
2833
- _Describe the key dependency direction or layered structure, e.g.: "core has no
2834
- runtime deps; cli assembles everything above it."_
2835
-
2836
- ### Extension points
2837
-
2838
- _Plugin, MCP, extension hooks, custom tools \u2014 what's wired up and how._
2839
-
2840
- ## Domain knowledge
2841
-
2842
- _Business rules, acronyms, invariants, external services, and notes where the
2843
- code looks unusual but is intentional. E.g.: "IDs are ULIDs, not UUIDs", "the
2844
- \`draft\` flag means uncommitted billing metadata", "MCP servers are restarted
2845
- on disconnect with exponential backoff, up to 3 attempts"._
2846
-
2847
- ## Verification checklist
2848
3699
 
2849
- - _What should be run after code changes?_
2850
- - _What manual smoke test proves the common path still works?_
2851
- - _What failure modes deserve extra attention?_
2852
- - _Any known flaky tests or environment-dependent behavior?_
2853
-
2854
- ## Useful pointers
2855
-
2856
- - _Docs, dashboards, runbooks, issue trackers, design notes, owner contacts._
2857
- - _Related projects or repositories._`;
2858
- }
2859
- function countTurnPairs(messages) {
2860
- let count = 0;
2861
- for (const m of messages) {
2862
- if (m.role === "user" || m.role === "assistant") count++;
2863
- }
2864
- return Math.floor(count / 2);
2865
- }
2866
- function countToolUses(messages) {
2867
- let count = 0;
2868
- for (const m of messages) {
2869
- if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_use").length;
2870
- }
2871
- return count;
2872
- }
2873
- function countToolResults(messages) {
2874
- let count = 0;
2875
- for (const m of messages) {
2876
- if (Array.isArray(m.content)) count += m.content.filter((b) => b.type === "tool_result").length;
2877
- }
2878
- return count;
2879
- }
2880
- function estimateTokens(messages) {
2881
- return estimateMessageTokens(messages);
2882
- }
3700
+ // src/slash-commands/index.ts
3701
+ init_helpers();
2883
3702
 
2884
3703
  // src/slash-commands/auth.ts
2885
3704
  init_provider_config_utils();
@@ -3024,6 +3843,9 @@ function buildAuthCommand(opts) {
3024
3843
  }
3025
3844
  };
3026
3845
  }
3846
+
3847
+ // src/slash-commands/autophase.ts
3848
+ init_helpers();
3027
3849
  function getStore(opts) {
3028
3850
  if (!opts.paths) throw new Error("PhaseStore not available \u2014 paths not configured.");
3029
3851
  return new PhaseStore({ baseDir: opts.paths.projectAutophase });
@@ -3092,12 +3914,12 @@ function buildAutoPhaseCommand(opts) {
3092
3914
  ""
3093
3915
  ].join("\n"),
3094
3916
  async run(args) {
3095
- const parts = args.trim().split(/\s+/).filter(Boolean);
3096
- const sub = parts[0] ?? "status";
3917
+ const { cmd, rest } = parseSubcommand(args);
3918
+ const sub = cmd || "status";
3097
3919
  const store = getStore(opts);
3098
3920
  switch (sub) {
3099
3921
  case "start": {
3100
- const goal = parts.slice(1).join(" ").trim();
3922
+ const goal = rest.join(" ").trim();
3101
3923
  if (!goal) {
3102
3924
  return { message: "Usage: /autophase start <goal> \u2014 describe what to build." };
3103
3925
  }
@@ -3142,7 +3964,7 @@ function buildAutoPhaseCommand(opts) {
3142
3964
  return { message: `\u{1F4BE} AutoPhase saved: ${view.graph.title}` };
3143
3965
  }
3144
3966
  case "load": {
3145
- const title = parts.slice(1).join(" ").trim();
3967
+ const title = rest.join(" ").trim();
3146
3968
  const graphs = await store.list();
3147
3969
  if (graphs.length === 0) return { message: "\u274C No saved projects." };
3148
3970
  const entry = title ? graphs.find((g) => g.title.toLowerCase().includes(title.toLowerCase())) : graphs[0];
@@ -3179,10 +4001,24 @@ function buildAutoPhaseCommand(opts) {
3179
4001
  };
3180
4002
  }
3181
4003
  }
3182
- return { message: `Unknown subcommand "${sub}". Run \`/autophase\` for usage.` };
4004
+ return { message: unknownSubcommand(sub, ["start", "pause", "resume", "stop", "save", "load", "list", "status"], "autophase") };
3183
4005
  }
3184
4006
  };
3185
4007
  }
4008
+ var MODE_LABELS = {
4009
+ off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
4010
+ suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
4011
+ auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
4012
+ eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
4013
+ "eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
4014
+ };
4015
+ var MODE_LABELS_SHORT = {
4016
+ off: `${color.green("OFF")} \u2014 agent stops after each turn`,
4017
+ suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
4018
+ auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
4019
+ eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`,
4020
+ "eternal-parallel": `${color.magenta("PARALLEL")} \u2014 fan-out 4-8 subagents per tick`
4021
+ };
3186
4022
  function buildAutonomyCommand(opts) {
3187
4023
  return {
3188
4024
  name: "autonomy",
@@ -3231,14 +4067,7 @@ function buildAutonomyCommand(opts) {
3231
4067
  }
3232
4068
  if (!arg || arg === "status") {
3233
4069
  const current = opts.onAutonomy();
3234
- const labels2 = {
3235
- off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
3236
- suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
3237
- auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
3238
- eternal: `${color.red("ETERNAL")} ${color.dim("(goal-driven loop \u2014 YOLO, until /autonomy stop)")}`,
3239
- "eternal-parallel": `${color.magenta("PARALLEL")} ${color.dim("(4-8 subagents per tick \u2014 fan-out, until /autonomy stop)")}`
3240
- };
3241
- const lines = [`Autonomy mode: ${labels2[current] ?? current}`];
4070
+ const lines = [`Autonomy mode: ${MODE_LABELS[current] ?? current}`];
3242
4071
  try {
3243
4072
  const goal = await loadGoal(goalFilePath(opts.projectRoot));
3244
4073
  if (goal) {
@@ -3379,14 +4208,7 @@ ${color.dim("Regular YOLO enabled; destructive-gated calls still use the permiss
3379
4208
  opts.onEternalStop();
3380
4209
  }
3381
4210
  opts.onAutonomy(newMode);
3382
- const labels = {
3383
- off: `${color.green("OFF")} \u2014 agent stops after each turn`,
3384
- suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
3385
- auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
3386
- eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`,
3387
- "eternal-parallel": `${color.magenta("PARALLEL")} \u2014 fan-out 4-8 subagents per tick`
3388
- };
3389
- const msg = `Autonomy mode: ${labels[newMode]}`;
4211
+ const msg = `Autonomy mode: ${MODE_LABELS_SHORT[newMode]}`;
3390
4212
  opts.renderer.write(msg);
3391
4213
  return { message: msg };
3392
4214
  }
@@ -3496,21 +4318,24 @@ ${color.yellow(` ${r.errors.length} file(s) had errors`)}` : "");
3496
4318
  }
3497
4319
  };
3498
4320
  }
4321
+
4322
+ // src/slash-commands/collab.ts
4323
+ init_helpers();
3499
4324
  function buildCollabCommand(opts) {
3500
4325
  return {
3501
4326
  name: "collab",
3502
4327
  category: "Agent",
3503
4328
  description: "Live collaboration helpers (status / invite / history).",
3504
4329
  async run(args, ctx) {
3505
- const parts = args.split(/\s+/).filter(Boolean);
3506
- const sub = (parts[0] ?? "status").toLowerCase();
4330
+ const { cmd, rest } = parseSubcommand(args);
4331
+ const sub = cmd || "status";
3507
4332
  switch (sub) {
3508
4333
  case "status":
3509
4334
  return statusCommand(ctx.session?.id);
3510
4335
  case "invite":
3511
4336
  return inviteCommand(ctx.session?.id);
3512
4337
  case "history":
3513
- return historyCommand(opts, ctx.session?.id, parts.slice(1));
4338
+ return historyCommand(opts, ctx.session?.id, rest);
3514
4339
  case "annotations":
3515
4340
  case "notes":
3516
4341
  return annotationsCommand(opts, ctx.session?.id);
@@ -3521,7 +4346,7 @@ function buildCollabCommand(opts) {
3521
4346
  default:
3522
4347
  return {
3523
4348
  message: color.yellow(
3524
- `Unknown subcommand: ${sub}. Try: /collab status | invite | history | annotations`
4349
+ unknownSubcommand(sub, ["status", "invite", "history", "annotations"], "collab")
3525
4350
  )
3526
4351
  };
3527
4352
  }
@@ -3716,6 +4541,9 @@ function buildCompactCommand(opts) {
3716
4541
  }
3717
4542
  };
3718
4543
  }
4544
+
4545
+ // src/slash-commands/context.ts
4546
+ init_helpers();
3719
4547
  function buildContextCommand(opts) {
3720
4548
  return {
3721
4549
  name: "context",
@@ -3958,8 +4786,173 @@ function parseThreshold(raw) {
3958
4786
  function formatLimit(limit) {
3959
4787
  return limit > 0 ? `${limit.toLocaleString()} tokens` : "unknown";
3960
4788
  }
3961
- function pct(n) {
3962
- return `${Math.round(n * 100)}%`;
4789
+ function pct(n) {
4790
+ return `${Math.round(n * 100)}%`;
4791
+ }
4792
+ function buildDelegateCommand(opts) {
4793
+ return {
4794
+ name: "delegate",
4795
+ category: "Agent",
4796
+ description: "Hand a task to a specialist subagent. /delegate [--role=<role>] <task>. Auto-dispatches if no role given.",
4797
+ argsHint: "[--role=<role>] [--name=<label>] <task description>",
4798
+ help: [
4799
+ "User-facing counterpart to the AI's `delegate` tool.",
4800
+ "",
4801
+ "Usage:",
4802
+ " /delegate <task description> Auto-dispatch to best agent",
4803
+ " /delegate --role=<role> <task description> Spawn a specific role",
4804
+ " /delegate --role=<role> --name=<label> <task> Spawn with custom name",
4805
+ " /delegate list List available roles",
4806
+ "",
4807
+ "Examples:",
4808
+ ' /delegate "audit packages/core for null-deref bugs"',
4809
+ ' /delegate --role=bug-hunter "find the race condition in session.ts"',
4810
+ ' /delegate --role=security-scanner --name=sec-audit "scan configs for secrets"',
4811
+ "",
4812
+ "Smart dispatch uses the same engine as /fleet dispatch: heuristic keyword",
4813
+ "matching with LLM fallback when ambiguous. The chosen agent is shown before",
4814
+ "spawning so you can confirm or cancel.",
4815
+ "",
4816
+ "Requires director mode. Run /director first, or start with wstack --director.",
4817
+ "",
4818
+ "Related: /spawn (fire-and-forget), /fleet dispatch (smart routing with fleet status)."
4819
+ ].join("\n"),
4820
+ async run(args) {
4821
+ const trimmed = args.trim();
4822
+ if (trimmed === "list" || trimmed === "roles" || trimmed === "ls") {
4823
+ return listRoles();
4824
+ }
4825
+ if (!trimmed) {
4826
+ return {
4827
+ message: [
4828
+ `${color.bold("/delegate")} \u2014 Hand a task to a specialist subagent`,
4829
+ "",
4830
+ "Usage:",
4831
+ ` ${color.cyan("/delegate <task>")} Auto-dispatch to best agent`,
4832
+ ` ${color.cyan("/delegate --role=<role> <task>")} Spawn a specific role`,
4833
+ ` ${color.cyan("/delegate list")} List available roles`,
4834
+ "",
4835
+ "Requires director mode. Run /director first."
4836
+ ].join("\n")
4837
+ };
4838
+ }
4839
+ let role;
4840
+ let name;
4841
+ const taskParts = [];
4842
+ const tokens = trimmed.split(/\s+/);
4843
+ for (let i = 0; i < tokens.length; i++) {
4844
+ const t = tokens[i] ?? "";
4845
+ if (t.startsWith("--role=")) {
4846
+ role = t.slice("--role=".length);
4847
+ } else if (t === "--role" && tokens[i + 1]) {
4848
+ role = tokens[++i];
4849
+ } else if (t.startsWith("--name=")) {
4850
+ name = t.slice("--name=".length);
4851
+ } else if (t === "--name" && tokens[i + 1]) {
4852
+ name = tokens[++i];
4853
+ } else if (!t.startsWith("-")) {
4854
+ taskParts.push(t);
4855
+ }
4856
+ }
4857
+ const task = taskParts.join(" ").trim();
4858
+ if (!task) {
4859
+ return {
4860
+ message: `${color.amber("Usage:")} /delegate [--role=<role>] [--name=<label>] <task description>`
4861
+ };
4862
+ }
4863
+ if (role) {
4864
+ const normalized = role.toLowerCase();
4865
+ const def = AGENT_CATALOG[normalized];
4866
+ if (!def) {
4867
+ const available = Object.keys(AGENT_CATALOG).sort().join(", ");
4868
+ return {
4869
+ message: `${color.red("Unknown role")} "${role}". Available roles:
4870
+ ${color.dim(available)}
4871
+
4872
+ Use ${color.cyan("/delegate list")} to browse by phase.`
4873
+ };
4874
+ }
4875
+ return await spawnAgent(
4876
+ opts,
4877
+ normalized,
4878
+ task,
4879
+ name ?? normalized,
4880
+ `${color.green("\u2713")} Delegating to ${color.bold(normalized)}: ${color.dim(task)}`
4881
+ );
4882
+ }
4883
+ const decision = await dispatchAgent(task, {
4884
+ classifier: opts.onDispatchClassify
4885
+ });
4886
+ const pct2 = Math.round(decision.confidence * 100);
4887
+ const decisionMsg = [
4888
+ `${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`,
4889
+ ` ${color.dim(decision.definition.capability.summary)}`,
4890
+ ` ${color.dim("why:")} ${decision.reason}`,
4891
+ decision.alternatives.length > 0 ? ` ${color.dim("alternatives:")} ${decision.alternatives.slice(0, 3).map((a) => a.role).join(", ")}` : ""
4892
+ ].filter(Boolean).join("\n");
4893
+ opts.renderer.write(decisionMsg);
4894
+ return await spawnAgent(
4895
+ opts,
4896
+ decision.role,
4897
+ task,
4898
+ name ?? decision.role,
4899
+ decisionMsg
4900
+ );
4901
+ }
4902
+ };
4903
+ }
4904
+ async function spawnAgent(opts, role, _task, _name, header) {
4905
+ if (!opts.onFleetSpawn) {
4906
+ const msg = `${color.amber("\u26A0 No fleet active.")} Run ${color.bold("/director")} first, or start with ${color.bold("wstack --director")}.`;
4907
+ opts.renderer.writeWarning(msg);
4908
+ return { message: msg };
4909
+ }
4910
+ try {
4911
+ const id = await opts.onFleetSpawn(role);
4912
+ const msg = [
4913
+ header,
4914
+ ` ${color.green("\u2713 spawned")} as ${color.dim(id)}`
4915
+ ].join("\n");
4916
+ opts.renderer.write(msg);
4917
+ return { message: msg };
4918
+ } catch (err) {
4919
+ const msg = `${color.red("\u2717 Spawn failed")}: ${err instanceof Error ? err.message : String(err)}`;
4920
+ opts.renderer.writeWarning(msg);
4921
+ return { message: msg };
4922
+ }
4923
+ }
4924
+ function listRoles() {
4925
+ const PHASE_ORDER2 = [
4926
+ { phase: "discovery", label: "1 \xB7 Discovery" },
4927
+ { phase: "planning", label: "2 \xB7 Planning" },
4928
+ { phase: "build", label: "3 \xB7 Build" },
4929
+ { phase: "verify", label: "4 \xB7 Verify" },
4930
+ { phase: "review", label: "5 \xB7 Review" },
4931
+ { phase: "domain", label: "6 \xB7 Domain" },
4932
+ { phase: "knowledge", label: "7 \xB7 Knowledge" },
4933
+ { phase: "delivery", label: "8 \xB7 Delivery & Ops" },
4934
+ { phase: "meta", label: "9 \xB7 Meta" }
4935
+ ];
4936
+ const totalRoles = Object.keys(AGENT_CATALOG).length;
4937
+ const lines = [
4938
+ `${color.bold("Available Agent Roles")} ${color.dim(`(${totalRoles} total)`)}`,
4939
+ "",
4940
+ `${color.dim("Use /delegate --role=<role> <task> to pick one,")}`,
4941
+ `${color.dim("or /delegate <task> for smart dispatch.")}`,
4942
+ ""
4943
+ ];
4944
+ for (const { phase, label } of PHASE_ORDER2) {
4945
+ const agents = AGENTS_BY_PHASE[phase];
4946
+ if (!agents || agents.length === 0) continue;
4947
+ lines.push(color.cyan(` Phase ${label}`));
4948
+ for (const def of agents.sort(
4949
+ (a, b) => (a.config.role ?? "").localeCompare(b.config.role ?? "")
4950
+ )) {
4951
+ const role = (def.config.role ?? "unknown").padEnd(20);
4952
+ lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
4953
+ }
4954
+ }
4955
+ return { message: lines.join("\n") };
3963
4956
  }
3964
4957
  var DEFAULT_TIMEOUT_MS = 6e4;
3965
4958
  var MAX_OUTPUT_LINES = 500;
@@ -4222,11 +5215,6 @@ async function persistTelegramConfig(deps, mutator) {
4222
5215
  }
4223
5216
 
4224
5217
  // src/slash-commands/enhance.ts
4225
- var noOpVault = {
4226
- encrypt: (v) => v,
4227
- decrypt: (v) => v,
4228
- isEncrypted: () => false
4229
- };
4230
5218
  function buildEnhanceCommand(opts) {
4231
5219
  const controller = opts.enhanceController;
4232
5220
  return {
@@ -5055,7 +6043,7 @@ function skillLabel(skillHints) {
5055
6043
  return skillHints.map((s) => `\`${s}\``).join(", ");
5056
6044
  }
5057
6045
  function categoryLabel(cli) {
5058
- if (cli.language !== "unknown" && cli.language !== "unknown") {
6046
+ if (cli.language !== "unknown") {
5059
6047
  return `${cli.subcategory} (${cli.category}, ${cli.language})`;
5060
6048
  }
5061
6049
  return `${cli.subcategory} (${cli.category})`;
@@ -5257,273 +6245,323 @@ function buildFleetCommand(opts) {
5257
6245
  const parts = args.trim().split(/\s+/);
5258
6246
  const cmd = parts[0]?.toLowerCase() ?? "";
5259
6247
  const subargs = parts.slice(1);
5260
- if (!cmd || cmd === "status" || cmd === "info" || cmd === "manifest") {
5261
- if (opts.onFleetStatus) {
5262
- const status = opts.onFleetStatus();
5263
- if (!status) {
5264
- const msg4 = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
5265
- opts.renderer.write(msg4);
5266
- return { message: msg4 };
5267
- }
5268
- const lines = [];
5269
- lines.push(`${color.bold("Fleet Status")}`);
5270
- lines.push(
5271
- color.dim(
5272
- ` coordinator: ${status.coordinatorId} \xB7 pending: ${status.pendingTasks} \xB7 done: ${status.completedTasks}`
5273
- )
5274
- );
5275
- if (status.subagents.length === 0) {
5276
- lines.push(color.dim(" No active subagents."));
5277
- } else {
5278
- lines.push("");
5279
- lines.push(
5280
- ` ${color.bold("ID").padEnd(36)} ${color.bold("NAME").padEnd(16)} ${color.bold("STATUS").padEnd(10)} ${color.bold("TASK")}`
5281
- );
5282
- lines.push(color.dim(" " + "\u2500".repeat(80)));
5283
- for (const sa of status.subagents) {
5284
- const id = sa.id?.padEnd(36) ?? "".padEnd(36);
5285
- const name = (sa.name ?? "worker").padEnd(16);
5286
- const statusColor = sa.status === "running" ? color.green(sa.status.padEnd(10)) : sa.status === "idle" ? color.dim(sa.status.padEnd(10)) : color.dim(sa.status.padEnd(10));
5287
- const ext = sa.extensions && sa.extensions > 0 ? `${color.yellow(`\u26A1\xD7${sa.extensions}`)} ` : "";
5288
- const task = sa.currentTask ?? color.dim("\u2014");
5289
- lines.push(` ${id} ${name} ${statusColor} ${ext}${task}`);
5290
- }
5291
- }
5292
- const msg3 = lines.join("\n");
5293
- opts.renderer.write(msg3);
5294
- return { message: msg3 };
5295
- }
5296
- if (opts.onFleet) {
5297
- const msg3 = await opts.onFleet(cmd || "status", void 0);
5298
- return { message: msg3 };
5299
- }
5300
- const msg2 = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
5301
- opts.renderer.write(msg2);
5302
- return { message: msg2 };
5303
- }
5304
- if (cmd === "usage" || cmd === "cost" || cmd === "tokens") {
5305
- if (opts.onFleetUsage) {
5306
- const usage = opts.onFleetUsage();
5307
- if (!usage) {
5308
- const msg4 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
5309
- opts.renderer.write(msg4);
5310
- return { message: msg4 };
5311
- }
5312
- const totalCost = usage.total?.cost ?? 0;
5313
- const totalIn = usage.total?.input ?? 0;
5314
- const totalOut = usage.total?.output ?? 0;
5315
- const lines = [];
5316
- lines.push(`${color.bold("Fleet Usage")}`);
5317
- lines.push(
5318
- ` ${color.dim("Total:")} ${color.green(`${totalCost.toFixed(4)}`)} \xB7 ${color.cyan(totalIn.toLocaleString())} in \xB7 ${color.cyan(totalOut.toLocaleString())} out`
5319
- );
5320
- const subagents = Object.values(usage.perSubagent);
5321
- if (subagents.length > 0) {
5322
- lines.push("");
5323
- for (const sa of subagents) {
5324
- const name = (sa.subagentId ?? "?").padEnd(20);
5325
- const cost = `${(sa.cost ?? 0).toFixed(4)}`.padStart(10);
5326
- const tokens = `${sa.input ?? 0} in / ${sa.output ?? 0} out`.padEnd(30);
5327
- lines.push(` ${color.dim(name)} ${color.cyan(cost)} ${color.dim(tokens)}`);
5328
- }
5329
- }
5330
- const msg3 = lines.join("\n");
5331
- opts.renderer.write(msg3);
5332
- return { message: msg3 };
5333
- }
5334
- if (opts.onFleet) {
5335
- const msg3 = await opts.onFleet("usage", void 0);
5336
- return { message: msg3 };
5337
- }
5338
- const msg2 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
5339
- opts.renderer.write(msg2);
5340
- return { message: msg2 };
5341
- }
5342
- if (cmd === "retry") {
5343
- if (opts.onFleetRetry) {
5344
- const targetId = subargs[0];
5345
- const msg3 = await opts.onFleetRetry(targetId);
5346
- return { message: msg3 };
5347
- }
5348
- if (opts.onFleet) {
5349
- const msg3 = await opts.onFleet("retry", subargs[0]);
5350
- return { message: msg3 };
5351
- }
5352
- const msg2 = `Retry is only available when director mode is active.`;
5353
- opts.renderer.writeWarning(msg2);
5354
- return { message: msg2 };
5355
- }
5356
- if (cmd === "journal" || cmd === "log") {
5357
- if (opts.onFleetLog) {
5358
- const subagentId = subargs[0];
5359
- const mode = subargs[1] === "raw" ? "raw" : "summary";
5360
- const msg3 = await opts.onFleetLog(subagentId, mode);
5361
- return { message: msg3 };
5362
- }
5363
- if (opts.onFleet) {
5364
- const msg3 = await opts.onFleet("log", subargs[0]);
5365
- return { message: msg3 };
5366
- }
5367
- const msg2 = `${color.dim("No journal entries yet.")}`;
5368
- opts.renderer.write(msg2);
5369
- return { message: msg2 };
5370
- }
5371
- if (cmd === "kill" || cmd === "stop-all") {
5372
- const targetId = subargs[0];
5373
- if (targetId) {
5374
- if (opts.onFleet) {
5375
- const msg4 = await opts.onFleet("kill", targetId);
5376
- return { message: msg4 };
5377
- }
5378
- const msg3 = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
5379
- opts.renderer.writeWarning(msg3);
5380
- return { message: msg3 };
5381
- }
5382
- if (opts.onFleetKill) {
5383
- const killed = opts.onFleetKill();
5384
- const msg3 = `${color.red("\u2717 Killed")} ${killed} subagent(s).`;
5385
- opts.renderer.write(msg3);
5386
- return { message: msg3 };
5387
- }
5388
- const msg2 = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
5389
- opts.renderer.writeWarning(msg2);
5390
- return { message: msg2 };
5391
- }
5392
- if (cmd === "terminate" || cmd === "stop") {
5393
- const targetId = subargs[0];
5394
- if (!targetId) {
5395
- const msg3 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
5396
- opts.renderer.writeWarning(msg3);
5397
- return { message: msg3 };
5398
- }
5399
- if (!opts.onFleetTerminate) {
5400
- const msg3 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
5401
- opts.renderer.writeWarning(msg3);
5402
- return { message: msg3 };
5403
- }
5404
- const ok = opts.onFleetTerminate(targetId);
5405
- if (ok) {
5406
- const msg3 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
5407
- opts.renderer.write(msg3);
5408
- return { message: msg3 };
5409
- }
5410
- const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
5411
- opts.renderer.writeWarning(msg2);
5412
- return { message: msg2 };
5413
- }
5414
- if (cmd === "spawn" || cmd === "add") {
5415
- const role = subargs[0] ?? "worker";
5416
- const count = Math.min(16, Math.max(1, Number.parseInt(subargs[1] ?? "1", 10) || 1));
5417
- if (!opts.onFleetSpawn) {
5418
- const msg3 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
5419
- opts.renderer.writeWarning(msg3);
5420
- return { message: msg3 };
5421
- }
5422
- const spawned = [];
5423
- let msg2;
5424
- for (let i = 0; i < count; i++) {
5425
- try {
5426
- const id = await opts.onFleetSpawn(role);
5427
- spawned.push(id);
5428
- } catch (err) {
5429
- const msg3 = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${err instanceof Error ? err.message : String(err)}`;
5430
- opts.renderer.writeWarning(msg3);
5431
- }
5432
- }
5433
- if (spawned.length === count) {
5434
- msg2 = `${color.green("\u2713 Spawned")} ${count} subagent(s) of role ${color.bold(role)}.`;
5435
- opts.renderer.write(msg2);
5436
- } else {
5437
- msg2 = `${color.amber("\u26A0 Spawned")} ${spawned.length}/${count} subagent(s). Check /fleet for details.`;
5438
- opts.renderer.writeWarning(msg2);
5439
- }
5440
- return { message: msg2 };
5441
- }
5442
- if (cmd === "list" || cmd === "roster" || cmd === "agents") {
5443
- const lines = [`${color.bold("Agent Roster")} ${color.dim("(spawn with /fleet spawn <role>)")}`];
5444
- for (const { phase, label } of PHASE_ORDER) {
5445
- const defs = AGENTS_BY_PHASE[phase];
5446
- if (!defs || defs.length === 0) continue;
5447
- lines.push("");
5448
- lines.push(color.cyan(` Phase ${label}`));
5449
- for (const def of defs) {
5450
- const role = (def.config.role ?? "").padEnd(18);
5451
- lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
5452
- }
5453
- }
5454
- const msg2 = lines.join("\n");
5455
- opts.renderer.write(msg2);
5456
- return { message: msg2 };
5457
- }
5458
- if (cmd === "dispatch" || cmd === "route") {
5459
- const task = subargs.join(" ").trim();
5460
- if (!task) {
5461
- const msg3 = `Usage: /fleet dispatch <task description> \u2014 routes the task to the best agent.`;
5462
- opts.renderer.writeWarning(msg3);
5463
- return { message: msg3 };
5464
- }
5465
- const decision = await dispatchAgent(task, { classifier: opts.onDispatchClassify });
5466
- const pct2 = Math.round(decision.confidence * 100);
5467
- const lines = [];
5468
- lines.push(
5469
- `${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`
5470
- );
5471
- lines.push(` ${color.dim(decision.definition.capability.summary)}`);
5472
- lines.push(` ${color.dim("why:")} ${decision.reason}`);
5473
- if (decision.alternatives.length > 0) {
5474
- const alts = decision.alternatives.slice(0, 3).map((a) => a.role).join(", ");
5475
- lines.push(` ${color.dim("alternatives:")} ${alts}`);
5476
- }
5477
- if (opts.onFleetSpawn) {
5478
- try {
5479
- const id = await opts.onFleetSpawn(decision.role);
5480
- lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
5481
- } catch (err) {
5482
- lines.push(
5483
- ` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
5484
- );
5485
- }
5486
- } else {
5487
- lines.push(` ${color.dim("(no fleet active \u2014 run /autonomy parallel or --director to spawn)")}`);
5488
- }
5489
- const msg2 = lines.join("\n");
5490
- opts.renderer.write(msg2);
5491
- return { message: msg2 };
5492
- }
5493
- if (cmd === "concurrency" || cmd === "slots" || cmd === "parallel") {
5494
- if (opts.onFleet) {
5495
- const n = subargs[0];
5496
- const msg3 = await opts.onFleet("concurrency", n || void 0);
5497
- return { message: msg3 };
6248
+ switch (cmd) {
6249
+ // ── /fleet (default / status / manifest) ────────────────────────────
6250
+ case "":
6251
+ case "status":
6252
+ case "info":
6253
+ case "manifest":
6254
+ return await handleStatus(opts, cmd);
6255
+ // ── /fleet usage ────────────────────────────────────────────────────
6256
+ case "usage":
6257
+ case "cost":
6258
+ case "tokens":
6259
+ return await handleUsage(opts);
6260
+ // ── /fleet retry ─────────────────────────────────────────────────────
6261
+ case "retry":
6262
+ return await handleRetry(opts, subargs);
6263
+ // ── /fleet journal / log ─────────────────────────────────────────────
6264
+ case "journal":
6265
+ case "log":
6266
+ return await handleJournal(opts, subargs);
6267
+ // ── /fleet kill ──────────────────────────────────────────────────────
6268
+ case "kill":
6269
+ case "stop-all":
6270
+ return await handleKill(opts, subargs);
6271
+ // ── /fleet terminate <subagentId> ────────────────────────────────────
6272
+ case "terminate":
6273
+ case "stop":
6274
+ return handleTerminate(opts, subargs);
6275
+ // ── /fleet spawn <role> [count] ──────────────────────────────────────
6276
+ case "spawn":
6277
+ case "add":
6278
+ return await handleSpawn(opts, subargs);
6279
+ // ── /fleet list ──────────────────────────────────────────────────────
6280
+ case "list":
6281
+ case "roster":
6282
+ case "agents":
6283
+ return handleList();
6284
+ // ── /fleet dispatch <task> ───────────────────────────────────────────
6285
+ case "dispatch":
6286
+ case "route":
6287
+ return await handleDispatch(opts, subargs);
6288
+ // ── /fleet concurrency [n] ───────────────────────────────────────────
6289
+ case "concurrency":
6290
+ case "slots":
6291
+ case "parallel":
6292
+ return await handleConcurrency(opts, subargs);
6293
+ // ── /fleet help ──────────────────────────────────────────────────────
6294
+ case "help":
6295
+ case "?":
6296
+ return handleFleetHelp(opts);
6297
+ // ── Unknown command ──────────────────────────────────────────────────
6298
+ default: {
6299
+ const valid = ["status", "list", "dispatch", "usage", "spawn", "terminate", "kill", "retry", "concurrency", "journal"];
6300
+ const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
6301
+ opts.renderer.writeWarning(msg);
6302
+ return { message: msg };
5498
6303
  }
5499
- const msg2 = `${color.amber("\u26A0 /fleet concurrency is not wired in this session.")}`;
5500
- opts.renderer.writeWarning(msg2);
5501
- return { message: msg2 };
5502
6304
  }
5503
- if (cmd === "help" || cmd === "?") {
5504
- const msg2 = [
5505
- `${color.bold("Fleet Commands")}`,
5506
- ` ${color.dim("/fleet")} Show fleet status (default)`,
5507
- ` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
5508
- ` ${color.dim("/fleet list")} List the agent roster grouped by phase`,
5509
- ` ${color.dim("/fleet dispatch <task>")} Route a task to the best agent and spawn it`,
5510
- ` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
5511
- ` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
5512
- ` ${color.dim("/fleet kill")} Stop all running subagents`,
5513
- ` ${color.dim("/fleet usage")} Token and cost breakdown across the fleet`,
5514
- ` ${color.dim("/fleet concurrency [n]")} Show or set the concurrent-subagent ceiling`,
5515
- ` ${color.dim("/fleet journal")} Show recent journal entries from /goal journal`
5516
- ].join("\n");
5517
- opts.renderer.write(msg2);
5518
- return { message: msg2 };
5519
- }
5520
- const valid = ["status", "list", "dispatch", "usage", "spawn", "terminate", "kill", "retry", "concurrency", "journal"];
5521
- const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
5522
- opts.renderer.writeWarning(msg);
5523
- return { message: msg };
5524
6305
  }
5525
6306
  };
5526
6307
  }
6308
+ async function handleStatus(opts, cmd) {
6309
+ if (opts.onFleetStatus) {
6310
+ const status = opts.onFleetStatus();
6311
+ if (!status) {
6312
+ const msg3 = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
6313
+ opts.renderer.write(msg3);
6314
+ return { message: msg3 };
6315
+ }
6316
+ const lines = [];
6317
+ lines.push(`${color.bold("Fleet Status")}`);
6318
+ lines.push(
6319
+ color.dim(
6320
+ ` coordinator: ${status.coordinatorId} \xB7 pending: ${status.pendingTasks} \xB7 done: ${status.completedTasks}`
6321
+ )
6322
+ );
6323
+ if (status.subagents.length === 0) {
6324
+ lines.push(color.dim(" No active subagents."));
6325
+ } else {
6326
+ lines.push("");
6327
+ lines.push(
6328
+ ` ${color.bold("ID").padEnd(36)} ${color.bold("NAME").padEnd(16)} ${color.bold("STATUS").padEnd(10)} ${color.bold("TASK")}`
6329
+ );
6330
+ lines.push(color.dim(" " + "\u2500".repeat(80)));
6331
+ for (const sa of status.subagents) {
6332
+ const id = sa.id?.padEnd(36) ?? "".padEnd(36);
6333
+ const name = (sa.name ?? "worker").padEnd(16);
6334
+ const statusColor = sa.status === "running" ? color.green(sa.status.padEnd(10)) : sa.status === "idle" ? color.dim(sa.status.padEnd(10)) : color.dim(sa.status.padEnd(10));
6335
+ const ext = sa.extensions && sa.extensions > 0 ? `${color.yellow(`\u26A1\xD7${sa.extensions}`)} ` : "";
6336
+ const task = sa.currentTask ?? color.dim("\u2014");
6337
+ lines.push(` ${id} ${name} ${statusColor} ${ext}${task}`);
6338
+ }
6339
+ }
6340
+ const msg2 = lines.join("\n");
6341
+ opts.renderer.write(msg2);
6342
+ return { message: msg2 };
6343
+ }
6344
+ if (opts.onFleet) {
6345
+ const msg2 = await opts.onFleet(cmd || "status", void 0);
6346
+ return { message: msg2 };
6347
+ }
6348
+ const msg = `${color.amber("\u26A0 No fleet active.")} Start /autonomy parallel first, or pass --director to a session.`;
6349
+ opts.renderer.write(msg);
6350
+ return { message: msg };
6351
+ }
6352
+ async function handleUsage(opts) {
6353
+ if (opts.onFleetUsage) {
6354
+ const usage = opts.onFleetUsage();
6355
+ if (!usage) {
6356
+ const msg3 = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
6357
+ opts.renderer.write(msg3);
6358
+ return { message: msg3 };
6359
+ }
6360
+ const totalCost = usage.total?.cost ?? 0;
6361
+ const totalIn = usage.total?.input ?? 0;
6362
+ const totalOut = usage.total?.output ?? 0;
6363
+ const lines = [];
6364
+ lines.push(`${color.bold("Fleet Usage")}`);
6365
+ lines.push(
6366
+ ` ${color.dim("Total:")} ${color.green(`${totalCost.toFixed(4)}`)} \xB7 ${color.cyan(totalIn.toLocaleString())} in \xB7 ${color.cyan(totalOut.toLocaleString())} out`
6367
+ );
6368
+ const subagents = Object.values(usage.perSubagent);
6369
+ if (subagents.length > 0) {
6370
+ lines.push("");
6371
+ for (const sa of subagents) {
6372
+ const name = (sa.subagentId ?? "?").padEnd(20);
6373
+ const cost = `${(sa.cost ?? 0).toFixed(4)}`.padStart(10);
6374
+ const tokens = `${sa.input ?? 0} in / ${sa.output ?? 0} out`.padEnd(30);
6375
+ lines.push(` ${color.dim(name)} ${color.cyan(cost)} ${color.dim(tokens)}`);
6376
+ }
6377
+ }
6378
+ const msg2 = lines.join("\n");
6379
+ opts.renderer.write(msg2);
6380
+ return { message: msg2 };
6381
+ }
6382
+ if (opts.onFleet) {
6383
+ const msg2 = await opts.onFleet("usage", void 0);
6384
+ return { message: msg2 };
6385
+ }
6386
+ const msg = `${color.amber("\u26A0 No fleet usage data.")} Start /autonomy parallel first.`;
6387
+ opts.renderer.write(msg);
6388
+ return { message: msg };
6389
+ }
6390
+ async function handleRetry(opts, subargs) {
6391
+ if (opts.onFleetRetry) {
6392
+ const targetId = subargs[0];
6393
+ const msg2 = await opts.onFleetRetry(targetId);
6394
+ return { message: msg2 };
6395
+ }
6396
+ if (opts.onFleet) {
6397
+ const msg2 = await opts.onFleet("retry", subargs[0]);
6398
+ return { message: msg2 };
6399
+ }
6400
+ const msg = `Retry is only available when director mode is active.`;
6401
+ opts.renderer.writeWarning(msg);
6402
+ return { message: msg };
6403
+ }
6404
+ async function handleJournal(opts, subargs) {
6405
+ if (opts.onFleetLog) {
6406
+ const subagentId = subargs[0];
6407
+ const mode = subargs[1] === "raw" ? "raw" : "summary";
6408
+ const msg2 = await opts.onFleetLog(subagentId, mode);
6409
+ return { message: msg2 };
6410
+ }
6411
+ if (opts.onFleet) {
6412
+ const msg2 = await opts.onFleet("log", subargs[0]);
6413
+ return { message: msg2 };
6414
+ }
6415
+ const msg = `${color.dim("No journal entries yet.")}`;
6416
+ opts.renderer.write(msg);
6417
+ return { message: msg };
6418
+ }
6419
+ async function handleKill(opts, subargs) {
6420
+ const targetId = subargs[0];
6421
+ if (targetId) {
6422
+ if (opts.onFleet) {
6423
+ const msg3 = await opts.onFleet("kill", targetId);
6424
+ return { message: msg3 };
6425
+ }
6426
+ const msg2 = `${color.amber("\u26A0 /fleet kill <id> is not wired in this session.")}`;
6427
+ opts.renderer.writeWarning(msg2);
6428
+ return { message: msg2 };
6429
+ }
6430
+ if (opts.onFleetKill) {
6431
+ const killed = opts.onFleetKill();
6432
+ const msg2 = `${color.red("\u2717 Killed")} ${killed} subagent(s).`;
6433
+ opts.renderer.write(msg2);
6434
+ return { message: msg2 };
6435
+ }
6436
+ const msg = `${color.amber("\u26A0 /fleet kill is not wired in this session.")}`;
6437
+ opts.renderer.writeWarning(msg);
6438
+ return { message: msg };
6439
+ }
6440
+ function handleTerminate(opts, subargs) {
6441
+ const targetId = subargs[0];
6442
+ if (!targetId) {
6443
+ const msg2 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
6444
+ opts.renderer.writeWarning(msg2);
6445
+ return { message: msg2 };
6446
+ }
6447
+ if (!opts.onFleetTerminate) {
6448
+ const msg2 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
6449
+ opts.renderer.writeWarning(msg2);
6450
+ return { message: msg2 };
6451
+ }
6452
+ const ok = opts.onFleetTerminate(targetId);
6453
+ if (ok) {
6454
+ const msg2 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
6455
+ opts.renderer.write(msg2);
6456
+ return { message: msg2 };
6457
+ }
6458
+ const msg = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
6459
+ opts.renderer.writeWarning(msg);
6460
+ return { message: msg };
6461
+ }
6462
+ async function handleSpawn(opts, subargs) {
6463
+ const role = subargs[0] ?? "worker";
6464
+ const count = Math.min(16, Math.max(1, Number.parseInt(subargs[1] ?? "1", 10) || 1));
6465
+ if (!opts.onFleetSpawn) {
6466
+ const msg2 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
6467
+ opts.renderer.writeWarning(msg2);
6468
+ return { message: msg2 };
6469
+ }
6470
+ const spawned = [];
6471
+ for (let i = 0; i < count; i++) {
6472
+ try {
6473
+ const id = await opts.onFleetSpawn(role);
6474
+ spawned.push(id);
6475
+ } catch (err) {
6476
+ const warnMsg = `${color.red("\u2717 Spawn failed")} for slot ${i + 1}: ${err instanceof Error ? err.message : String(err)}`;
6477
+ opts.renderer.writeWarning(warnMsg);
6478
+ }
6479
+ }
6480
+ if (spawned.length === count) {
6481
+ const msg2 = `${color.green("\u2713 Spawned")} ${count} subagent(s) of role ${color.bold(role)}.`;
6482
+ opts.renderer.write(msg2);
6483
+ return { message: msg2 };
6484
+ }
6485
+ const msg = `${color.amber("\u26A0 Spawned")} ${spawned.length}/${count} subagent(s). Check /fleet for details.`;
6486
+ opts.renderer.writeWarning(msg);
6487
+ return { message: msg };
6488
+ }
6489
+ function handleList() {
6490
+ const lines = [`${color.bold("Agent Roster")} ${color.dim("(spawn with /fleet spawn <role>)")}`];
6491
+ for (const { phase, label } of PHASE_ORDER) {
6492
+ const defs = AGENTS_BY_PHASE[phase];
6493
+ if (!defs || defs.length === 0) continue;
6494
+ lines.push("");
6495
+ lines.push(color.cyan(` Phase ${label}`));
6496
+ for (const def of defs) {
6497
+ const role = (def.config.role ?? "").padEnd(18);
6498
+ lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
6499
+ }
6500
+ }
6501
+ return { message: lines.join("\n") };
6502
+ }
6503
+ async function handleDispatch(opts, subargs) {
6504
+ const task = subargs.join(" ").trim();
6505
+ if (!task) {
6506
+ const msg2 = `Usage: /fleet dispatch <task description> \u2014 routes the task to the best agent.`;
6507
+ opts.renderer.writeWarning(msg2);
6508
+ return { message: msg2 };
6509
+ }
6510
+ const decision = await dispatchAgent(task, { classifier: opts.onDispatchClassify });
6511
+ const pct2 = Math.round(decision.confidence * 100);
6512
+ const lines = [];
6513
+ lines.push(
6514
+ `${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`
6515
+ );
6516
+ lines.push(` ${color.dim(decision.definition.capability.summary)}`);
6517
+ lines.push(` ${color.dim("why:")} ${decision.reason}`);
6518
+ if (decision.alternatives.length > 0) {
6519
+ const alts = decision.alternatives.slice(0, 3).map((a) => a.role).join(", ");
6520
+ lines.push(` ${color.dim("alternatives:")} ${alts}`);
6521
+ }
6522
+ if (opts.onFleetSpawn) {
6523
+ try {
6524
+ const id = await opts.onFleetSpawn(decision.role);
6525
+ lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
6526
+ } catch (err) {
6527
+ lines.push(
6528
+ ` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
6529
+ );
6530
+ }
6531
+ } else {
6532
+ lines.push(` ${color.dim("(no fleet active \u2014 run /autonomy parallel or --director to spawn)")}`);
6533
+ }
6534
+ const msg = lines.join("\n");
6535
+ opts.renderer.write(msg);
6536
+ return { message: msg };
6537
+ }
6538
+ async function handleConcurrency(opts, subargs) {
6539
+ if (opts.onFleet) {
6540
+ const n = subargs[0];
6541
+ const msg2 = await opts.onFleet("concurrency", n || void 0);
6542
+ return { message: msg2 };
6543
+ }
6544
+ const msg = `${color.amber("\u26A0 /fleet concurrency is not wired in this session.")}`;
6545
+ opts.renderer.writeWarning(msg);
6546
+ return { message: msg };
6547
+ }
6548
+ function handleFleetHelp(opts) {
6549
+ const msg = [
6550
+ `${color.bold("Fleet Commands")}`,
6551
+ ` ${color.dim("/fleet")} Show fleet status (default)`,
6552
+ ` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
6553
+ ` ${color.dim("/fleet list")} List the agent roster grouped by phase`,
6554
+ ` ${color.dim("/fleet dispatch <task>")} Route a task to the best agent and spawn it`,
6555
+ ` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
6556
+ ` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
6557
+ ` ${color.dim("/fleet kill")} Stop all running subagents`,
6558
+ ` ${color.dim("/fleet usage")} Token and cost breakdown across the fleet`,
6559
+ ` ${color.dim("/fleet concurrency [n]")} Show or set the concurrent-subagent ceiling`,
6560
+ ` ${color.dim("/fleet journal")} Show recent journal entries from /goal journal`
6561
+ ].join("\n");
6562
+ opts.renderer.write(msg);
6563
+ return { message: msg };
6564
+ }
5527
6565
 
5528
6566
  // src/slash-commands/goal-refiner.ts
5529
6567
  async function refineGoal(rawGoal, provider, model) {
@@ -5934,6 +6972,9 @@ function buildHelpCommand(opts) {
5934
6972
  }
5935
6973
  };
5936
6974
  }
6975
+
6976
+ // src/slash-commands/init.ts
6977
+ init_helpers();
5937
6978
  function buildInitCommand(opts) {
5938
6979
  return {
5939
6980
  name: "init",
@@ -6233,6 +7274,7 @@ function buildMcpSlashCommand(opts) {
6233
7274
  }
6234
7275
 
6235
7276
  // src/slash-commands/memory.ts
7277
+ init_helpers();
6236
7278
  function buildMemoryCommand(opts) {
6237
7279
  return {
6238
7280
  name: "memory",
@@ -6241,9 +7283,9 @@ function buildMemoryCommand(opts) {
6241
7283
  async run(args) {
6242
7284
  const store = opts.memoryStore;
6243
7285
  if (!store) return { message: "No memory store configured." };
6244
- const [verb, ...rest] = args.trim().split(/\s+/);
7286
+ const { cmd, rest } = parseSubcommand(args);
6245
7287
  const restJoined = rest.join(" ").trim();
6246
- switch (verb) {
7288
+ switch (cmd) {
6247
7289
  case "":
6248
7290
  case "show":
6249
7291
  case "list": {
@@ -6278,27 +7320,19 @@ function buildMemoryCommand(opts) {
6278
7320
  }
6279
7321
  default:
6280
7322
  return {
6281
- message: `Unknown subcommand "${verb}". Try: show | remember <text> | forget <query> | clear | compact | stats`
7323
+ message: unknownSubcommand(cmd, ["show", "remember", "forget", "clear", "compact", "stats"], "memory")
6282
7324
  };
6283
7325
  }
6284
7326
  }
6285
7327
  };
6286
7328
  }
6287
- function buildCompactPrompt(entries) {
6288
- const entriesBlock = entries.map(
6289
- (e, i) => `${i + 1}. [${e.ts.slice(0, 10)}] ${e.id}
6290
- ${e.text}${e.tags ? `
6291
- tags: ${e.tags.join(", ")}` : ""}${e.type ? `
6292
- type: ${e.type}` : ""}${e.priority ? `
6293
- priority: ${e.priority}` : ""}`
6294
- ).join("\n\n");
6295
- return `You are a memory curator. Your task is to review, deduplicate, and improve a set of long-term memory entries.
7329
+ var COMPACT_SYSTEM_PROMPT = `You are a memory curator. Your task is to review, deduplicate, and improve a set of long-term memory entries.
6296
7330
 
6297
7331
  These entries are injected into the context of an AI coding agent. Every token counts. The memory must be concise, accurate, and free of noise.
6298
7332
 
6299
7333
  ## Current Memory Entries
6300
7334
 
6301
- ${entriesBlock}
7335
+ __ENTRIES__
6302
7336
 
6303
7337
  ## Your Task
6304
7338
 
@@ -6336,6 +7370,15 @@ Return ONLY valid JSON with this structure:
6336
7370
  }
6337
7371
 
6338
7372
  Use the EXACT entry IDs from the list above for "targets". No markdown, no explanation outside the JSON.`;
7373
+ function buildCompactPrompt(entries) {
7374
+ const entriesBlock = entries.map(
7375
+ (e, i) => `${i + 1}. [${e.ts.slice(0, 10)}] ${e.id}
7376
+ ${e.text}${e.tags ? `
7377
+ tags: ${e.tags.join(", ")}` : ""}${e.type ? `
7378
+ type: ${e.type}` : ""}${e.priority ? `
7379
+ priority: ${e.priority}` : ""}`
7380
+ ).join("\n\n");
7381
+ return COMPACT_SYSTEM_PROMPT.replace("__ENTRIES__", entriesBlock);
6339
7382
  }
6340
7383
  async function runCompact(opts) {
6341
7384
  const store = opts.memoryStore;
@@ -6717,11 +7760,9 @@ ${targetMode.description}`
6717
7760
  }
6718
7761
  };
6719
7762
  }
6720
- var noOpVault2 = {
6721
- encrypt: (v) => v,
6722
- decrypt: (v) => v,
6723
- isEncrypted: () => false
6724
- };
7763
+
7764
+ // src/slash-commands/models.ts
7765
+ init_helpers();
6725
7766
  async function patchGlobalConfig(globalConfigPath, mutate) {
6726
7767
  let raw = "{}";
6727
7768
  let fileExists2 = true;
@@ -6740,9 +7781,9 @@ async function patchGlobalConfig(globalConfigPath, mutate) {
6740
7781
  }
6741
7782
  parsed = {};
6742
7783
  }
6743
- const decrypted = decryptConfigSecrets$1(parsed, noOpVault2);
7784
+ const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
6744
7785
  mutate(decrypted);
6745
- const encrypted = encryptConfigSecrets$1(decrypted, noOpVault2);
7786
+ const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
6746
7787
  await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
6747
7788
  return decrypted;
6748
7789
  }
@@ -6851,8 +7892,8 @@ function buildModelsCommand(opts) {
6851
7892
  description: "Manage custom model definitions.",
6852
7893
  help,
6853
7894
  async run(args) {
6854
- const parts = args.trim().split(/\s+/).filter(Boolean);
6855
- const sub = (parts[0] ?? "").toLowerCase();
7895
+ const { cmd, rest } = parseSubcommand(args);
7896
+ const sub = cmd;
6856
7897
  if (sub === "help" || sub === "--help") return { message: help };
6857
7898
  if (!opts.configStore || !opts.paths) {
6858
7899
  return { message: `${color.red("Error")} config store not available.` };
@@ -6883,7 +7924,7 @@ function buildModelsCommand(opts) {
6883
7924
  }
6884
7925
  try {
6885
7926
  if (sub === "add") {
6886
- const { modelId, def, error } = parseFlags(parts.slice(1));
7927
+ const { modelId, def, error } = parseFlags(rest);
6887
7928
  if (error) {
6888
7929
  return { message: `${color.red("Error")}: ${error}. ${color.dim("/models help")}` };
6889
7930
  }
@@ -6910,7 +7951,7 @@ function buildModelsCommand(opts) {
6910
7951
  return { message: `${color.green("\u2713")} ${color.amber(modelId)} ${existed ? "updated" : "added"}.` };
6911
7952
  }
6912
7953
  if (sub === "remove" || sub === "rm") {
6913
- const modelId = parts[1];
7954
+ const modelId = rest[0];
6914
7955
  if (!modelId) {
6915
7956
  return { message: `${color.amber("Usage:")} /models remove <id>` };
6916
7957
  }
@@ -6929,7 +7970,7 @@ function buildModelsCommand(opts) {
6929
7970
  return { message: `${color.green("\u2713")} removed ${color.amber(modelId)}` };
6930
7971
  }
6931
7972
  return {
6932
- message: `${color.red("Unknown subcommand")} "${sub}". Try ${color.dim("/models")}, ${color.dim("/models add")}, or ${color.dim("/models help")}.`
7973
+ message: `${color.red("Unknown subcommand")} "${sub}". ${unknownSubcommand(sub, ["add", "remove"], "models")}`
6933
7974
  };
6934
7975
  } catch (err) {
6935
7976
  return {
@@ -6939,6 +7980,20 @@ function buildModelsCommand(opts) {
6939
7980
  }
6940
7981
  };
6941
7982
  }
7983
+
7984
+ // src/slash-commands/suggestion-store.ts
7985
+ var sharedSuggestions = [];
7986
+ function setSuggestions(suggestions) {
7987
+ sharedSuggestions = suggestions;
7988
+ }
7989
+ function getSuggestions() {
7990
+ return sharedSuggestions;
7991
+ }
7992
+ function clearSuggestions() {
7993
+ sharedSuggestions = [];
7994
+ }
7995
+
7996
+ // src/slash-commands/next.ts
6942
7997
  function buildNextCommand(opts) {
6943
7998
  return {
6944
7999
  name: "next",
@@ -6968,9 +8023,10 @@ function buildNextCommand(opts) {
6968
8023
  }
6969
8024
  const arg = trimmed.toLowerCase();
6970
8025
  if (arg === "list" || arg === "ls" || arg === "show") {
6971
- return handleList(opts);
8026
+ return handleList2(opts);
6972
8027
  }
6973
8028
  if (arg === "clear" || arg === "reset") {
8029
+ clearSuggestions();
6974
8030
  opts.onSuggestions?.([]);
6975
8031
  return { message: color.dim("Suggestion list cleared.") };
6976
8032
  }
@@ -6982,7 +8038,8 @@ function buildNextCommand(opts) {
6982
8038
  const current = opts.onNextPredict();
6983
8039
  const label = (on) => on ? `${color.cyan("ON")} ${color.dim("(predicted next steps shown after each turn)")}` : `${color.green("OFF")} ${color.dim("(no predictions)")}`;
6984
8040
  if (!arg || arg === "status") {
6985
- const suggestions = opts.onSuggestions?.() ?? [];
8041
+ const stored = getSuggestions();
8042
+ const suggestions = stored.length > 0 ? stored : opts.onSuggestions?.() ?? [];
6986
8043
  const msg2 = [
6987
8044
  `Next-task prediction: ${label(current)}`,
6988
8045
  suggestions.length > 0 ? color.dim(` ${suggestions.length} suggestion(s) available \u2014 use /next list to view, /next 1 to execute`) : color.dim(" No suggestions stored \u2014 use /suggest to generate")
@@ -7015,7 +8072,7 @@ function handleSelection(input, opts) {
7015
8072
  if (indices.length === 0) {
7016
8073
  return { message: color.amber("No valid suggestion numbers found. Use /next 1, /next 1 2 3, etc.") };
7017
8074
  }
7018
- const suggestions = opts.onSuggestions?.() ?? [];
8075
+ const suggestions = getSuggestions().length > 0 ? getSuggestions() : opts.onSuggestions?.() ?? [];
7019
8076
  if (suggestions.length === 0) {
7020
8077
  return {
7021
8078
  message: color.amber("No suggestions available. Run /suggest first, or enable prediction with /next on.")
@@ -7049,8 +8106,8 @@ function handleSelection(input, opts) {
7049
8106
  runText
7050
8107
  };
7051
8108
  }
7052
- function handleList(opts) {
7053
- const suggestions = opts.onSuggestions?.() ?? [];
8109
+ function handleList2(opts) {
8110
+ const suggestions = getSuggestions().length > 0 ? getSuggestions() : opts.onSuggestions?.() ?? [];
7054
8111
  if (suggestions.length === 0) {
7055
8112
  return {
7056
8113
  message: [
@@ -7196,9 +8253,9 @@ function buildSaveCommand(opts) {
7196
8253
  }
7197
8254
  function buildLoadCommand(opts) {
7198
8255
  return {
7199
- name: "resume",
8256
+ name: "sessions",
7200
8257
  category: "Session",
7201
- aliases: ["load", "sessions"],
8258
+ aliases: ["resume", "load"],
7202
8259
  description: "List recent sessions, show incomplete ones (--incomplete), or plan a recovery (--recover <id>).",
7203
8260
  async run(args) {
7204
8261
  const parts = args.split(/\s+/).filter(Boolean);
@@ -7370,11 +8427,6 @@ function summariseEvent(ev) {
7370
8427
  return color.dim("\u2026");
7371
8428
  }
7372
8429
  }
7373
- var noOpVault3 = {
7374
- encrypt: (v) => v,
7375
- decrypt: (v) => v,
7376
- isEncrypted: () => false
7377
- };
7378
8430
  function providerHasKey(entry) {
7379
8431
  if (!entry) return false;
7380
8432
  if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
@@ -7422,9 +8474,9 @@ async function patchGlobalConfig2(globalConfigPath, mutate) {
7422
8474
  throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
7423
8475
  parsed = {};
7424
8476
  }
7425
- const decrypted = decryptConfigSecrets$1(parsed, noOpVault3);
8477
+ const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
7426
8478
  mutate(decrypted);
7427
- const encrypted = encryptConfigSecrets$1(decrypted, noOpVault3);
8479
+ const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
7428
8480
  await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
7429
8481
  return decrypted;
7430
8482
  }
@@ -7922,12 +8974,14 @@ function buildSuggestCommand(opts) {
7922
8974
  const fast = /\b(--fast|-f)\b/.test(trimmed);
7923
8975
  if (fast) {
7924
8976
  const suggestions = generateHeuristicSuggestions(opts);
8977
+ setSuggestions(suggestions);
7925
8978
  opts.onSuggestions?.(suggestions);
7926
8979
  const display = formatSuggestions(suggestions);
7927
8980
  return { message: display };
7928
8981
  }
7929
8982
  if (!opts.onSpawnAndWait) {
7930
8983
  const suggestions = generateHeuristicSuggestions(opts);
8984
+ setSuggestions(suggestions);
7931
8985
  opts.onSuggestions?.(suggestions);
7932
8986
  const display = formatSuggestions(suggestions) + "\n" + color.dim("(Heuristic fallback \u2014 multi-agent not enabled)");
7933
8987
  return { message: display };
@@ -7945,9 +8999,11 @@ function buildSuggestCommand(opts) {
7945
8999
  const suggestions = parseSuggestions(raw);
7946
9000
  if (suggestions.length === 0) {
7947
9001
  const fallback = ["No pending actions \u2014 everything is up to date."];
9002
+ setSuggestions(fallback);
7948
9003
  opts.onSuggestions?.(fallback);
7949
9004
  return { message: formatSuggestions(fallback) };
7950
9005
  }
9006
+ setSuggestions(suggestions);
7951
9007
  opts.onSuggestions?.(suggestions);
7952
9008
  return { message: formatSuggestions(suggestions) };
7953
9009
  } catch (err) {
@@ -8013,11 +9069,7 @@ function formatSuggestions(suggestions) {
8013
9069
  }
8014
9070
  return lines.join("\n");
8015
9071
  }
8016
- var noOpVault4 = {
8017
- encrypt: (v) => v,
8018
- decrypt: (v) => v,
8019
- isEncrypted: () => false
8020
- };
9072
+ init_helpers();
8021
9073
  function formatDelay(ms) {
8022
9074
  if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
8023
9075
  if (ms === 0) return "disabled";
@@ -8061,8 +9113,8 @@ function buildSettingsCommand(opts) {
8061
9113
  description: "View or change settings (auto-proceed delay, default autonomy mode, launch hints).",
8062
9114
  help,
8063
9115
  async run(args) {
8064
- const parts = args.trim().split(/\s+/).filter(Boolean);
8065
- const sub = (parts[0] ?? "").toLowerCase();
9116
+ const { cmd, rest } = parseSubcommand(args);
9117
+ const sub = cmd;
8066
9118
  if (sub === "help" || sub === "--help") {
8067
9119
  return { message: this.help ?? "" };
8068
9120
  }
@@ -8090,11 +9142,11 @@ function buildSettingsCommand(opts) {
8090
9142
  configStore: opts.configStore,
8091
9143
  globalConfigPath: opts.paths.globalConfig,
8092
9144
  inProjectConfigPath: opts.paths.inProjectConfig,
8093
- vault: noOpVault4
9145
+ vault: noOpVault
8094
9146
  };
8095
9147
  try {
8096
9148
  if (sub === "delay") {
8097
- const raw = parts[1];
9149
+ const raw = rest[0];
8098
9150
  if (raw === void 0) {
8099
9151
  return {
8100
9152
  message: `${color.amber("Usage:")} /settings delay <seconds> ${color.dim("(0 disables)")}`
@@ -8113,7 +9165,7 @@ function buildSettingsCommand(opts) {
8113
9165
  return { message: `${color.green("\u2713")} auto-proceed delay \u2192 ${formatDelay(ms)}` };
8114
9166
  }
8115
9167
  if (sub === "mode") {
8116
- const raw = (parts[1] ?? "").toLowerCase();
9168
+ const raw = (rest[0] ?? "").toLowerCase();
8117
9169
  const modes = ["off", "suggest", "auto"];
8118
9170
  if (!modes.includes(raw)) {
8119
9171
  return { message: `${color.amber("Usage:")} /settings mode off|suggest|auto` };
@@ -8124,7 +9176,7 @@ function buildSettingsCommand(opts) {
8124
9176
  return { message: `${color.green("\u2713")} default autonomy \u2192 ${color.bold(raw)}` };
8125
9177
  }
8126
9178
  if (sub === "hints") {
8127
- const raw = (parts[1] ?? "").toLowerCase();
9179
+ const raw = (rest[0] ?? "").toLowerCase();
8128
9180
  if (!["on", "off"].includes(raw)) {
8129
9181
  return { message: `${color.amber("Usage:")} /settings hints on|off` };
8130
9182
  }
@@ -8135,7 +9187,7 @@ function buildSettingsCommand(opts) {
8135
9187
  return { message: `${color.green("\u2713")} launch hints \u2192 ${on ? color.cyan("on") : color.dim("off")}` };
8136
9188
  }
8137
9189
  if (sub === "debug-stream") {
8138
- const raw = (parts[1] ?? "").toLowerCase();
9190
+ const raw = (rest[0] ?? "").toLowerCase();
8139
9191
  if (!["on", "off"].includes(raw)) {
8140
9192
  return { message: `${color.amber("Usage:")} /settings debug-stream on|off` };
8141
9193
  }
@@ -8148,7 +9200,7 @@ function buildSettingsCommand(opts) {
8148
9200
  return { message: `${color.green("\u2713")} debug stream \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim("raw SSE hex-dump to stderr")}` };
8149
9201
  }
8150
9202
  if (sub === "config-scope") {
8151
- const raw = (parts[1] ?? "").toLowerCase();
9203
+ const raw = (rest[0] ?? "").toLowerCase();
8152
9204
  if (!["global", "project"].includes(raw)) {
8153
9205
  return { message: `${color.amber("Usage:")} /settings config-scope global|project` };
8154
9206
  }
@@ -8159,7 +9211,7 @@ function buildSettingsCommand(opts) {
8159
9211
  return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
8160
9212
  }
8161
9213
  return {
8162
- message: `${color.red("Unknown setting")} "${sub}". Try ${color.dim("/settings")}, ${color.dim("/settings delay <s>")}, ${color.dim("/settings mode <m>")}, ${color.dim("/settings hints on|off")}, ${color.dim("/settings debug-stream on|off")}, or ${color.dim("/settings config-scope global|project")}.`
9214
+ message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "defaults"], "settings")}`
8163
9215
  };
8164
9216
  } catch (err) {
8165
9217
  return {
@@ -8169,11 +9221,6 @@ function buildSettingsCommand(opts) {
8169
9221
  }
8170
9222
  };
8171
9223
  }
8172
- var noOpVault5 = {
8173
- encrypt: (v) => v,
8174
- decrypt: (v) => v,
8175
- isEncrypted: () => false
8176
- };
8177
9224
  var HELP = [
8178
9225
  "Usage:",
8179
9226
  " /telegram-setup Show setup instructions",
@@ -8275,7 +9322,7 @@ function buildTelegramSetupCommand(opts) {
8275
9322
  const persistDeps = {
8276
9323
  configStore: opts.configStore,
8277
9324
  globalConfigPath: opts.paths?.globalConfig ?? "",
8278
- vault: noOpVault5
9325
+ vault: noOpVault
8279
9326
  };
8280
9327
  if (!persistDeps.globalConfigPath) {
8281
9328
  return {
@@ -8328,11 +9375,31 @@ function buildSpawnCommand(opts) {
8328
9375
  name: "spawn",
8329
9376
  category: "Agent",
8330
9377
  description: "Spawn an isolated subagent to handle a task.",
9378
+ argsHint: "[--name=<label>] [--model=<id>] <task description>",
9379
+ help: [
9380
+ "Fire-and-forget subagent spawn. The subagent runs independently; check",
9381
+ "its status with /agents or /fleet.",
9382
+ "",
9383
+ "Usage:",
9384
+ " /spawn <task description>",
9385
+ " /spawn --name=<label> --model=<id> <task description>",
9386
+ "",
9387
+ "Flags:",
9388
+ " --name=<label> Display name for the subagent",
9389
+ " --provider=<id> Override the provider (defaults to leader provider)",
9390
+ " --model=<id> Override the model (defaults to leader model)",
9391
+ " --tools=a,b,c Comma-separated list of tool names to grant",
9392
+ "",
9393
+ "For smart routing (auto-picking the right agent role), use /fleet dispatch.",
9394
+ "For explicit role assignment, use /fleet spawn <role>.",
9395
+ "",
9396
+ "Requires director mode. Run /director first."
9397
+ ].join("\n"),
8331
9398
  async run(args) {
8332
9399
  const { description, opts: parsed } = parseSpawnFlags(args.trim());
8333
9400
  if (!description)
8334
9401
  return {
8335
- message: "Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>"
9402
+ message: 'Usage: /spawn [--name=<label>] [--model=<id>] <task description>\n\nExamples:\n /spawn "fix the auth bug in session.ts"\n /spawn --name=fixer "audit core for null-deref bugs"\n\nRequires director mode. Run /director first.'
8336
9403
  };
8337
9404
  if (!opts.onSpawn) return { message: "Multi-agent is not enabled in this session." };
8338
9405
  try {
@@ -8387,6 +9454,19 @@ function buildDirectorCommand(opts) {
8387
9454
  name: "director",
8388
9455
  category: "Agent",
8389
9456
  description: "Promote this session to director mode, enabling fleet orchestration tools. Only works before any subagents are spawned.",
9457
+ help: [
9458
+ "Promotes the current session to director mode, which unlocks:",
9459
+ "",
9460
+ " /fleet \u2014 fleet status, spawn, dispatch, kill, usage",
9461
+ " /spawn \u2014 fire-and-forget subagent spawns",
9462
+ " /agents \u2014 subagent status dashboard",
9463
+ "",
9464
+ "Director mode must be activated BEFORE any subagents are spawned.",
9465
+ "Alternatively, start a session with --director: wstack --director",
9466
+ "",
9467
+ "Director mode is a prerequisite for /fleet dispatch, /fleet spawn,",
9468
+ 'and /spawn. Without it, those commands will report "not wired."'
9469
+ ].join("\n"),
8390
9470
  async run() {
8391
9471
  if (!opts.onDirector) return { message: "Director promotion is not available in this session." };
8392
9472
  const result = await opts.onDirector();
@@ -8508,6 +9588,9 @@ function buildStatuslineCommand(deps) {
8508
9588
  }
8509
9589
  };
8510
9590
  }
9591
+
9592
+ // src/slash-commands/todos.ts
9593
+ init_helpers();
8511
9594
  function findTodo(todos, query) {
8512
9595
  const asIndex = Number.parseInt(query, 10);
8513
9596
  if (!Number.isNaN(asIndex)) {
@@ -8536,9 +9619,9 @@ function buildTodosCommand(opts) {
8536
9619
  async run(args) {
8537
9620
  const ctx = opts.context;
8538
9621
  if (!ctx) return { message: "No active context." };
8539
- const [verb, ...rest] = args.trim().split(/\s+/);
9622
+ const { cmd, rest } = parseSubcommand(args);
8540
9623
  const restJoined = rest.join(" ").trim();
8541
- switch (verb) {
9624
+ switch (cmd) {
8542
9625
  case "":
8543
9626
  case "show":
8544
9627
  case "list": {
@@ -8589,12 +9672,15 @@ function buildTodosCommand(opts) {
8589
9672
  }
8590
9673
  default:
8591
9674
  return {
8592
- message: `Unknown subcommand "${verb}". Try: show | clear | add <text> | done <id|index> | remove <id|index>`
9675
+ message: unknownSubcommand(cmd, ["show", "clear", "add", "done", "remove"], "todos")
8593
9676
  };
8594
9677
  }
8595
9678
  }
8596
9679
  };
8597
9680
  }
9681
+
9682
+ // src/slash-commands/tasks.ts
9683
+ init_helpers();
8598
9684
  function findTask(tasks, query) {
8599
9685
  const asIndex = Number.parseInt(query, 10);
8600
9686
  if (!Number.isNaN(asIndex)) {
@@ -8656,9 +9742,9 @@ function buildTasksCommand(_opts) {
8656
9742
  }
8657
9743
  const sessionId = ctx.session?.id ?? "unknown";
8658
9744
  const file = await loadTasks(taskPath) ?? emptyTaskFile(sessionId);
8659
- const [verb, ...rest] = args.trim().split(/\s+/);
9745
+ const { cmd, rest } = parseSubcommand(args);
8660
9746
  const restJoined = rest.join(" ").trim();
8661
- switch (verb) {
9747
+ switch (cmd) {
8662
9748
  case "":
8663
9749
  case "show":
8664
9750
  case "list":
@@ -8691,7 +9777,7 @@ ${formatTaskProgress(file.tasks)}` };
8691
9777
  case "start":
8692
9778
  case "done":
8693
9779
  case "fail": {
8694
- if (!restJoined) return { message: `Usage: /tasks ${verb} <id|index>` };
9780
+ if (!restJoined) return { message: `Usage: /tasks ${cmd} <id|index>` };
8695
9781
  const found = findTask(file.tasks, restJoined);
8696
9782
  if (!found) return { message: `No task matched "${restJoined}".` };
8697
9783
  const statusMap = {
@@ -8699,7 +9785,13 @@ ${formatTaskProgress(file.tasks)}` };
8699
9785
  done: "completed",
8700
9786
  fail: "failed"
8701
9787
  };
8702
- found.item.status = statusMap[verb] ?? "pending";
9788
+ const verbMap = {
9789
+ start: "Started",
9790
+ done: "Completed",
9791
+ fail: "Failed"
9792
+ };
9793
+ const verb = verbMap[cmd] ?? cmd;
9794
+ found.item.status = statusMap[cmd] ?? "pending";
8703
9795
  found.item.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
8704
9796
  await saveTasks(taskPath, file);
8705
9797
  return { message: `Marked ${verb}: ${found.item.title}
@@ -8781,7 +9873,7 @@ ${formatTaskProgress(file.tasks)}`
8781
9873
  }
8782
9874
  default:
8783
9875
  return {
8784
- message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id> | done <id> | fail <id> | status <id> <s> | depends <id> <deps> | assign <id> <agent> | promote <id> | clear`
9876
+ message: unknownSubcommand(cmd, ["show", "add", "start", "done", "fail", "status", "depends", "assign", "promote", "clear"], "tasks")
8785
9877
  };
8786
9878
  }
8787
9879
  }
@@ -9080,8 +10172,18 @@ function buildYoloCommand(opts) {
9080
10172
  newState = false;
9081
10173
  } else if (arg === "toggle") {
9082
10174
  newState = !opts.onYolo();
10175
+ } else if (arg === "destructive") {
10176
+ const currentMode = opts.onYolo();
10177
+ if (!currentMode) {
10178
+ const msg3 = `${color.amber("YOLO is OFF.")} Enable YOLO first with /yolo on, then /yolo destructive will control the confirmation gate.`;
10179
+ opts.renderer.writeWarning(msg3);
10180
+ return { message: msg3 };
10181
+ }
10182
+ const msg2 = `${color.amber("Destructive gate:")} ${color.dim("YOLO is ON \u2014 destructive operations require confirmation. Use /yolo destructive to toggle this gate.")}`;
10183
+ opts.renderer.writeWarning(msg2);
10184
+ return { message: msg2 };
9083
10185
  } else {
9084
- const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, or /yolo toggle.`;
10186
+ const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, /yolo toggle, or /yolo destructive.`;
9085
10187
  opts.renderer.writeWarning(msg2);
9086
10188
  return { message: msg2 };
9087
10189
  }
@@ -9102,6 +10204,7 @@ function buildBuiltinSlashCommands(opts) {
9102
10204
  buildClearCommand(opts),
9103
10205
  buildCompactCommand(opts),
9104
10206
  buildContextCommand(opts),
10207
+ buildDelegateCommand(opts),
9105
10208
  buildDevCommand(opts),
9106
10209
  buildCodebaseReindexCommand(opts),
9107
10210
  buildTechStackCommand(opts),
@@ -9964,10 +11067,10 @@ var theme = { primary: color.amber };
9964
11067
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
9965
11068
  try {
9966
11069
  const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
9967
- const fs30 = await import('fs/promises');
11070
+ const fs31 = await import('fs/promises');
9968
11071
  let existing = {};
9969
11072
  try {
9970
- const raw = await fs30.readFile(configPath2, "utf8");
11073
+ const raw = await fs31.readFile(configPath2, "utf8");
9971
11074
  existing = JSON.parse(raw);
9972
11075
  } catch {
9973
11076
  }
@@ -11982,6 +13085,9 @@ var exportCmd = async (args, deps) => {
11982
13085
  }
11983
13086
  return 0;
11984
13087
  };
13088
+
13089
+ // src/subcommands/handlers/init.ts
13090
+ init_helpers();
11985
13091
  var initCmd = async (_args, deps) => {
11986
13092
  deps.renderer.write(color.bold("WrongStack init\n"));
11987
13093
  deps.renderer.writeInfo("Loading provider catalog from models.dev (cached locally)\u2026");
@@ -13293,7 +14399,7 @@ var rewindCmd = async (args, deps) => {
13293
14399
  if (result.revertedFiles.length === 0) {
13294
14400
  deps.renderer.write("No files to revert.\n");
13295
14401
  if (flags.resume) {
13296
- const store = new DefaultSessionStore({ dir: sessionsDir });
14402
+ const store = new DefaultSessionStore$1({ dir: sessionsDir });
13297
14403
  const resumed = await store.resume(targetSessionId);
13298
14404
  const toIdx = result.toPromptIndex;
13299
14405
  await resumed.writer.truncateToCheckpoint(toIdx);
@@ -13311,7 +14417,7 @@ Reverted ${result.revertedFiles.length} file(s):
13311
14417
  `);
13312
14418
  }
13313
14419
  if (flags.resume) {
13314
- const store = new DefaultSessionStore({ dir: sessionsDir });
14420
+ const store = new DefaultSessionStore$1({ dir: sessionsDir });
13315
14421
  const resumed = await store.resume(targetSessionId);
13316
14422
  const toIdx = result.toPromptIndex;
13317
14423
  const removed = await resumed.writer.truncateToCheckpoint(toIdx);
@@ -13461,10 +14567,10 @@ var auditCmd = async (args, deps) => {
13461
14567
  return verify.ok ? 0 : 1;
13462
14568
  };
13463
14569
  async function listAudits(log, dir, deps) {
13464
- const fs30 = await import('fs/promises');
14570
+ const fs31 = await import('fs/promises');
13465
14571
  let entries;
13466
14572
  try {
13467
- entries = await fs30.readdir(dir);
14573
+ entries = await fs31.readdir(dir);
13468
14574
  } catch {
13469
14575
  deps.renderer.write(
13470
14576
  color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
@@ -14364,22 +15470,22 @@ function fmtDuration(ms) {
14364
15470
  const remMin = m - h * 60;
14365
15471
  return `${h}h${remMin}m`;
14366
15472
  }
14367
- function fmtTaskResultLine(r, color62) {
15473
+ function fmtTaskResultLine(r, color63) {
14368
15474
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
14369
15475
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
14370
15476
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
14371
15477
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
14372
- const errKindChip = errKind ? color62.dim(` [${errKind}]`) : "";
14373
- const errSnip = errMsg || errKind ? `${errKindChip}${color62.dim(errTail)}` : "";
15478
+ const errKindChip = errKind ? color63.dim(` [${errKind}]`) : "";
15479
+ const errSnip = errMsg || errKind ? `${errKindChip}${color63.dim(errTail)}` : "";
14374
15480
  switch (r.status) {
14375
15481
  case "success":
14376
- return { mark: color62.green("\u2713"), stats, tail: "" };
15482
+ return { mark: color63.green("\u2713"), stats, tail: "" };
14377
15483
  case "timeout":
14378
- return { mark: color62.yellow("\u23F1"), stats: `${color62.yellow("timeout")} ${stats}`, tail: errSnip };
15484
+ return { mark: color63.yellow("\u23F1"), stats: `${color63.yellow("timeout")} ${stats}`, tail: errSnip };
14379
15485
  case "stopped":
14380
- return { mark: color62.dim("\u2298"), stats: `${color62.dim("stopped")} ${stats}`, tail: errSnip };
15486
+ return { mark: color63.dim("\u2298"), stats: `${color63.dim("stopped")} ${stats}`, tail: errSnip };
14381
15487
  case "failed":
14382
- return { mark: color62.red("\u2717"), stats: `${color62.red("failed")} ${stats}`, tail: errSnip };
15488
+ return { mark: color63.red("\u2717"), stats: `${color63.red("failed")} ${stats}`, tail: errSnip };
14383
15489
  }
14384
15490
  }
14385
15491
 
@@ -15919,7 +17025,7 @@ async function execute(deps) {
15919
17025
  onAutonomy,
15920
17026
  getNextPredict,
15921
17027
  onSuggestionsParsed,
15922
- getSuggestions,
17028
+ getSuggestions: getSuggestions2,
15923
17029
  autoProceedDelayMs,
15924
17030
  autoProceedMaxIterations,
15925
17031
  onValidateAutoProceed,
@@ -15928,7 +17034,10 @@ async function execute(deps) {
15928
17034
  subscribeEternalIteration,
15929
17035
  subscribeEternalStage,
15930
17036
  skillLoader,
15931
- modeId
17037
+ modeId,
17038
+ sessionStore,
17039
+ memoryStore,
17040
+ modeStore
15932
17041
  } = deps;
15933
17042
  let code = 0;
15934
17043
  let fleetStatusLine = null;
@@ -16164,7 +17273,7 @@ async function execute(deps) {
16164
17273
  configStore,
16165
17274
  globalConfigPath: wpaths.globalConfig,
16166
17275
  inProjectConfigPath: wpaths.inProjectConfig,
16167
- vault: { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false }
17276
+ vault: noOpVault
16168
17277
  },
16169
17278
  (autonomy) => {
16170
17279
  autonomy.defaultMode = s.mode;
@@ -16182,8 +17291,7 @@ async function execute(deps) {
16182
17291
  const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
16183
17292
  const raw = await fsp4.readFile(targetPath, "utf8").catch(() => "{}");
16184
17293
  const parsed = JSON.parse(raw);
16185
- const vault = { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false };
16186
- const decrypted = decryptConfigSecrets$1(parsed, vault);
17294
+ const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
16187
17295
  if (s.nextPrediction !== void 0) {
16188
17296
  decrypted.nextPrediction = s.nextPrediction;
16189
17297
  }
@@ -16241,7 +17349,7 @@ async function execute(deps) {
16241
17349
  decrypted.autonomy = autonomy;
16242
17350
  }
16243
17351
  const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
16244
- const encrypted = encryptConfigSecrets$1(toWrite, vault);
17352
+ const encrypted = encryptConfigSecrets$1(toWrite, noOpVault);
16245
17353
  if (targetPath !== wpaths.globalConfig) {
16246
17354
  await fsp4.mkdir(path10.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
16247
17355
  }
@@ -16360,7 +17468,12 @@ async function execute(deps) {
16360
17468
  open: !!flags.open,
16361
17469
  modelsRegistry,
16362
17470
  globalConfigPath: wpaths.globalConfig,
16363
- subscribeEternalIteration
17471
+ subscribeEternalIteration,
17472
+ sessionStore,
17473
+ memoryStore,
17474
+ skillLoader,
17475
+ modeStore,
17476
+ modeId
16364
17477
  });
16365
17478
  try {
16366
17479
  code = await runRepl({
@@ -16379,7 +17492,7 @@ async function execute(deps) {
16379
17492
  onAutonomy,
16380
17493
  getNextPredict,
16381
17494
  onSuggestionsParsed,
16382
- getSuggestions,
17495
+ getSuggestions: getSuggestions2,
16383
17496
  autoProceedDelayMs,
16384
17497
  autoProceedMaxIterations,
16385
17498
  onValidateAutoProceed,
@@ -16411,7 +17524,7 @@ async function execute(deps) {
16411
17524
  onAutonomy,
16412
17525
  getNextPredict,
16413
17526
  onSuggestionsParsed,
16414
- getSuggestions,
17527
+ getSuggestions: getSuggestions2,
16415
17528
  autoProceedDelayMs,
16416
17529
  onValidateAutoProceed,
16417
17530
  autoProceedMaxIterations,
@@ -18090,14 +19203,14 @@ function isIgnored(rel) {
18090
19203
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
18091
19204
  }
18092
19205
  async function setupCodebaseIndexing(deps) {
18093
- const { config, pipelines, projectRoot, logger } = deps;
19206
+ const { config, context, pipelines, projectRoot, logger } = deps;
18094
19207
  const idx = config.indexing;
18095
19208
  if (!idx) return () => {
18096
19209
  };
18097
19210
  const debounceMs = idx.debounceMs ?? 400;
18098
19211
  const onError = (err) => logger.debug(`codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`);
18099
19212
  if (idx.onSessionStart) {
18100
- void runStartupIndex({ projectRoot }).then((r) => {
19213
+ void runStartupIndex({ projectRoot, signal: context.signal }).then((r) => {
18101
19214
  logger.info(
18102
19215
  `codebase index ready: ${r.symbolsIndexed} symbols \xB7 ${r.filesIndexed} files \xB7 ${r.durationMs}ms`
18103
19216
  );
@@ -19754,8 +20867,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
19754
20867
  onSuggestions: (suggestions) => {
19755
20868
  if (suggestions !== void 0) {
19756
20869
  currentSuggestions = suggestions;
20870
+ setSuggestions(suggestions);
19757
20871
  }
19758
- return currentSuggestions;
20872
+ const shared = getSuggestions();
20873
+ return shared.length > 0 ? shared : currentSuggestions;
19759
20874
  },
19760
20875
  onAutonomy: (setTo) => {
19761
20876
  if (setTo !== void 0) {
@@ -19951,6 +21066,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
19951
21066
  }
19952
21067
  const disposeIndexing = await setupCodebaseIndexing({
19953
21068
  config,
21069
+ context,
19954
21070
  pipelines,
19955
21071
  projectRoot,
19956
21072
  logger
@@ -20005,8 +21121,12 @@ Restart WrongStack to load or unload plugin code in this session.`;
20005
21121
  getNextPredict: () => nextPredictEnabled,
20006
21122
  onSuggestionsParsed: (suggestions) => {
20007
21123
  currentSuggestions = suggestions ?? [];
21124
+ setSuggestions(suggestions ?? []);
21125
+ },
21126
+ getSuggestions: () => {
21127
+ const shared = getSuggestions();
21128
+ return shared.length > 0 ? shared : currentSuggestions;
20008
21129
  },
20009
- getSuggestions: () => currentSuggestions,
20010
21130
  autoProceedDelayMs: config.autonomy?.autoProceedDelayMs ?? 45e3,
20011
21131
  autoProceedMaxIterations: config.autonomy?.autoProceedMaxIterations ?? 50,
20012
21132
  onValidateAutoProceed: async (suggestion, lastOutput) => {
@@ -20062,7 +21182,10 @@ Reply YES to auto-proceed, NO to wait for human input.`
20062
21182
  return () => stageListeners.delete(fn);
20063
21183
  },
20064
21184
  skillLoader: config.features.skills ? skillLoader : void 0,
20065
- modeId
21185
+ modeId,
21186
+ sessionStore,
21187
+ memoryStore,
21188
+ modeStore
20066
21189
  });
20067
21190
  }
20068
21191
  var isMain = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}` || process.argv[1]?.endsWith("/cli/dist/index.js") || process.argv[1]?.endsWith("\\cli\\dist\\index.js");