@wrongstack/cli 0.264.0 → 0.265.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,25 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fsp5 from 'fs/promises';
3
- import * as path39 from 'path';
3
+ import * as path4 from 'path';
4
4
  import { join } from 'path';
5
- import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, wstackGlobalRoot, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, DefaultLogger, DefaultModelsRegistry, isStdinTTY, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, AutonomousCoordinator, noOpVault, decryptConfigSecrets, encryptConfigSecrets, atomicWrite, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
6
- import { DefaultSecretVault, encryptConfigSecrets as encryptConfigSecrets$1, decryptConfigSecrets as decryptConfigSecrets$1, isSecretField } from '@wrongstack/core/security';
5
+ import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, DefaultSecretScrubber, projectHash, wstackGlobalRoot, resolveProjectDir, GlobalMailbox, TOKENS, ToolRegistry, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, DEFAULT_CONTEXT_WINDOW_MODE_ID, recentTextTurns, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, DefaultLogger, DefaultModelsRegistry, isStdinTTY, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, AutonomousCoordinator, noOpVault, decryptConfigSecrets, encryptConfigSecrets, atomicWrite, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, FsError, ConfigError, InputBuilder, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, resolveMailboxIdentity, isSecretField as isSecretField$1 } from '@wrongstack/core';
6
+ import { decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DefaultSecretVault, isSecretField } from '@wrongstack/core/security';
7
7
  import * as crypto3 from 'crypto';
8
8
  import { createHash, randomBytes, randomUUID } from 'crypto';
9
9
  import { createRequire } from 'module';
10
10
  import * as os from 'os';
11
11
  import os__default from 'os';
12
- import { findFreePort, AutoPhaseWebSocketHandler, handleShellOpen, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
12
+ import { findFreePort, AutoPhaseWebSocketHandler, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
13
13
  import { makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry } from '@wrongstack/providers';
14
14
  import { toErrorMessage } from '@wrongstack/core/utils';
15
- import { getProcessRegistry, builtinToolsPack, TIER1_TOOLS, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, resetIndexCircuitBreaker } from '@wrongstack/tools';
15
+ import { getProcessRegistry, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
16
16
  import { DefaultSessionStore } from '@wrongstack/core/storage';
17
17
  import { probeLocalLlm } from '@wrongstack/runtime/probe';
18
+ import * as fs2 from 'fs';
19
+ import { watch, writeFileSync, existsSync, readFileSync } from 'fs';
20
+ import { SkillInstaller } from '@wrongstack/core/skills';
18
21
  import { WebSocketServer, WebSocket } from 'ws';
19
22
  import { spawn, execFile, execFileSync } from 'child_process';
20
23
  import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
21
- import * as fs2 from 'fs';
22
- import { writeFileSync, existsSync, readFileSync } from 'fs';
23
24
  import { fileURLToPath } from 'url';
24
25
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
25
26
  import * as readline from 'readline';
@@ -68,15 +69,57 @@ async function pathExists(file) {
68
69
  return false;
69
70
  }
70
71
  }
72
+ async function hasRootFileWithSuffix(root, suffixes) {
73
+ let names;
74
+ try {
75
+ names = await fsp5.readdir(root);
76
+ } catch {
77
+ return false;
78
+ }
79
+ const lower = suffixes.map((s) => s.toLowerCase());
80
+ return names.some((n) => lower.some((s) => n.toLowerCase().endsWith(s)));
81
+ }
82
+ function parseCiRunCommands(yaml) {
83
+ const commands = [];
84
+ const lines = yaml.split(/\r?\n/);
85
+ for (let i = 0; i < lines.length; i++) {
86
+ const line = lines[i] ?? "";
87
+ const m = /^(\s*)(?:-\s+)?run:\s*(.*)$/.exec(line);
88
+ if (!m) continue;
89
+ const indent = (m[1] ?? "").length;
90
+ const inline = (m[2] ?? "").trim();
91
+ if (inline && inline !== "|" && inline !== ">" && !/^[|>][+-]?$/.test(inline)) {
92
+ commands.push(inline);
93
+ continue;
94
+ }
95
+ for (let j = i + 1; j < lines.length; j++) {
96
+ const body = lines[j] ?? "";
97
+ if (body.trim() === "") continue;
98
+ const bodyIndent = body.length - body.trimStart().length;
99
+ if (bodyIndent <= indent) break;
100
+ commands.push(body.trim());
101
+ i = j;
102
+ }
103
+ }
104
+ return commands.filter((c) => c !== "" && !/^(#|cd\s|echo\s|export\s|set\s|if\s)/.test(c));
105
+ }
106
+ function applyCiCommands(facts, commands) {
107
+ const find = (re) => commands.find((c) => re.test(c));
108
+ facts.test ??= find(/\b(test|pytest|vitest|jest|go test|cargo test|mix test|rspec)\b/i);
109
+ facts.lint ??= find(/\b(lint|eslint|biome|clippy|ruff|flake8|golangci-lint|fmt --check)\b/i);
110
+ facts.build ??= find(
111
+ /\b(build|compile|tsc|cargo build|go build|mvn .*package|gradle .*build)\b/i
112
+ );
113
+ }
71
114
  async function detectPackageManager2(root, declared) {
72
115
  if (declared) {
73
116
  const name = declared.split("@")[0];
74
117
  if (name) return name;
75
118
  }
76
- if (await pathExists(path39.join(root, "pnpm-lock.yaml"))) return "pnpm";
77
- if (await pathExists(path39.join(root, "bun.lockb"))) return "bun";
78
- if (await pathExists(path39.join(root, "bun.lock"))) return "bun";
79
- if (await pathExists(path39.join(root, "yarn.lock"))) return "yarn";
119
+ if (await pathExists(path4.join(root, "pnpm-lock.yaml"))) return "pnpm";
120
+ if (await pathExists(path4.join(root, "bun.lockb"))) return "bun";
121
+ if (await pathExists(path4.join(root, "bun.lock"))) return "bun";
122
+ if (await pathExists(path4.join(root, "yarn.lock"))) return "yarn";
80
123
  return "npm";
81
124
  }
82
125
  function hasUsableScript(scripts, name) {
@@ -94,10 +137,50 @@ function parseMakeTargets(makefile) {
94
137
  }
95
138
  return targets;
96
139
  }
140
+ async function scanSourceTree(root) {
141
+ const counts = /* @__PURE__ */ new Map();
142
+ const entryPoints = [];
143
+ const topDirs = [];
144
+ let fileCount = 0;
145
+ const MAX_FILES = 5e3;
146
+ const MAX_DEPTH = 6;
147
+ async function walk(dir, depth, rel) {
148
+ if (fileCount >= MAX_FILES || depth > MAX_DEPTH) return;
149
+ let entries;
150
+ try {
151
+ entries = await fsp5.readdir(dir, { withFileTypes: true });
152
+ } catch {
153
+ return;
154
+ }
155
+ for (const e of entries) {
156
+ if (fileCount >= MAX_FILES) return;
157
+ const name = e.name;
158
+ if (e.isDirectory()) {
159
+ if (SCAN_IGNORE_DIRS.has(name) || name.startsWith(".")) continue;
160
+ if (depth === 0) topDirs.push(name);
161
+ await walk(path4.join(dir, name), depth + 1, rel ? `${rel}/${name}` : name);
162
+ } else if (e.isFile()) {
163
+ fileCount++;
164
+ const ext = path4.extname(name).toLowerCase();
165
+ const lang = EXT_LANG[ext];
166
+ if (!lang) continue;
167
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
168
+ const base = name.slice(0, name.length - ext.length).toLowerCase();
169
+ if (entryPoints.length < 10 && ENTRY_BASENAMES.has(base)) {
170
+ entryPoints.push(rel ? `${rel}/${name}` : name);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ await walk(root, 0, "");
176
+ if (counts.size === 0) return void 0;
177
+ const languages = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([lang, n]) => `${lang} (${n})`);
178
+ return { languages, entryPoints, topDirs: topDirs.sort() };
179
+ }
97
180
  async function detectProjectFacts(root) {
98
181
  const facts = { hints: [] };
99
182
  try {
100
- const pkg = JSON.parse(await fsp5.readFile(path39.join(root, "package.json"), "utf8"));
183
+ const pkg = JSON.parse(await fsp5.readFile(path4.join(root, "package.json"), "utf8"));
101
184
  const scripts = pkg.scripts ?? {};
102
185
  const pm = await detectPackageManager2(root, pkg.packageManager);
103
186
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -111,14 +194,14 @@ async function detectProjectFacts(root) {
111
194
  } catch {
112
195
  }
113
196
  try {
114
- if (!await pathExists(path39.join(root, "pyproject.toml"))) throw new Error("not python");
197
+ if (!await pathExists(path4.join(root, "pyproject.toml"))) throw new Error("not python");
115
198
  facts.test ??= "pytest";
116
199
  facts.lint ??= "ruff check .";
117
200
  facts.hints.push("pyproject.toml");
118
201
  } catch {
119
202
  }
120
203
  try {
121
- if (!await pathExists(path39.join(root, "go.mod"))) throw new Error("not go");
204
+ if (!await pathExists(path4.join(root, "go.mod"))) throw new Error("not go");
122
205
  facts.build ??= "go build ./...";
123
206
  facts.test ??= "go test ./...";
124
207
  facts.run ??= "go run .";
@@ -126,7 +209,7 @@ async function detectProjectFacts(root) {
126
209
  } catch {
127
210
  }
128
211
  try {
129
- if (!await pathExists(path39.join(root, "Cargo.toml"))) throw new Error("not rust");
212
+ if (!await pathExists(path4.join(root, "Cargo.toml"))) throw new Error("not rust");
130
213
  facts.build ??= "cargo build";
131
214
  facts.test ??= "cargo test";
132
215
  facts.lint ??= "cargo clippy";
@@ -135,7 +218,7 @@ async function detectProjectFacts(root) {
135
218
  } catch {
136
219
  }
137
220
  try {
138
- const makefile = await fsp5.readFile(path39.join(root, "Makefile"), "utf8");
221
+ const makefile = await fsp5.readFile(path4.join(root, "Makefile"), "utf8");
139
222
  const targets = parseMakeTargets(makefile);
140
223
  facts.build ??= targets.has("build") ? "make build" : "make";
141
224
  if (targets.has("test")) facts.test ??= "make test";
@@ -145,6 +228,118 @@ async function detectProjectFacts(root) {
145
228
  facts.hints.push("Makefile");
146
229
  } catch {
147
230
  }
231
+ try {
232
+ const composer = JSON.parse(await fsp5.readFile(path4.join(root, "composer.json"), "utf8"));
233
+ const scripts = composer.scripts ?? {};
234
+ if ("test" in scripts) facts.test ??= "composer test";
235
+ if ("lint" in scripts) facts.lint ??= "composer lint";
236
+ facts.hints.push("composer.json");
237
+ } catch {
238
+ }
239
+ try {
240
+ if (!await pathExists(path4.join(root, "pom.xml"))) throw new Error("not maven");
241
+ facts.build ??= "mvn package";
242
+ facts.test ??= "mvn test";
243
+ facts.hints.push("pom.xml");
244
+ } catch {
245
+ }
246
+ try {
247
+ const hasGradle = await pathExists(path4.join(root, "build.gradle")) || await pathExists(path4.join(root, "build.gradle.kts"));
248
+ if (!hasGradle) throw new Error("not gradle");
249
+ const g = await pathExists(path4.join(root, "gradlew")) ? "./gradlew" : "gradle";
250
+ facts.build ??= `${g} build`;
251
+ facts.test ??= `${g} test`;
252
+ facts.hints.push("Gradle");
253
+ } catch {
254
+ }
255
+ try {
256
+ const hasDotnet = await pathExists(path4.join(root, "global.json")) || await hasRootFileWithSuffix(root, [".csproj", ".fsproj", ".sln"]);
257
+ if (!hasDotnet) throw new Error("not dotnet");
258
+ facts.build ??= "dotnet build";
259
+ facts.test ??= "dotnet test";
260
+ facts.run ??= "dotnet run";
261
+ facts.hints.push(".NET project");
262
+ } catch {
263
+ }
264
+ try {
265
+ if (!await pathExists(path4.join(root, "mix.exs"))) throw new Error("not elixir");
266
+ facts.build ??= "mix compile";
267
+ facts.test ??= "mix test";
268
+ facts.lint ??= "mix format --check-formatted";
269
+ facts.run ??= "mix run";
270
+ facts.hints.push("mix.exs");
271
+ } catch {
272
+ }
273
+ try {
274
+ if (!await pathExists(path4.join(root, "pubspec.yaml"))) throw new Error("not dart");
275
+ facts.test ??= "dart test";
276
+ facts.lint ??= "dart analyze";
277
+ facts.hints.push("pubspec.yaml");
278
+ } catch {
279
+ }
280
+ try {
281
+ const hasDeno = await pathExists(path4.join(root, "deno.json")) || await pathExists(path4.join(root, "deno.jsonc"));
282
+ if (!hasDeno) throw new Error("not deno");
283
+ facts.test ??= "deno test";
284
+ facts.lint ??= "deno lint";
285
+ facts.hints.push("deno.json");
286
+ } catch {
287
+ }
288
+ try {
289
+ if (!await pathExists(path4.join(root, "Package.swift"))) throw new Error("not swift");
290
+ facts.build ??= "swift build";
291
+ facts.test ??= "swift test";
292
+ facts.run ??= "swift run";
293
+ facts.hints.push("Package.swift");
294
+ } catch {
295
+ }
296
+ try {
297
+ if (!await pathExists(path4.join(root, "Gemfile"))) throw new Error("not ruby");
298
+ if (await pathExists(path4.join(root, "Rakefile"))) facts.test ??= "bundle exec rake test";
299
+ facts.hints.push("Gemfile");
300
+ } catch {
301
+ }
302
+ try {
303
+ if (!await pathExists(path4.join(root, "CMakeLists.txt"))) throw new Error("not cmake");
304
+ facts.build ??= "cmake -B build && cmake --build build";
305
+ facts.test ??= "ctest --test-dir build";
306
+ facts.hints.push("CMakeLists.txt");
307
+ } catch {
308
+ }
309
+ try {
310
+ const hasPip = await pathExists(path4.join(root, "requirements.txt")) || await pathExists(path4.join(root, "setup.py")) || await pathExists(path4.join(root, "setup.cfg"));
311
+ if (!hasPip) throw new Error("not pip");
312
+ facts.test ??= "pytest";
313
+ facts.hints.push("requirements.txt");
314
+ } catch {
315
+ }
316
+ if (!facts.build || !facts.test || !facts.lint) {
317
+ try {
318
+ const wfDir = path4.join(root, ".github", "workflows");
319
+ const wfNames = (await fsp5.readdir(wfDir)).filter((n) => /\.ya?ml$/i.test(n));
320
+ const commands = [];
321
+ for (const n of wfNames) {
322
+ commands.push(...parseCiRunCommands(await fsp5.readFile(path4.join(wfDir, n), "utf8")));
323
+ }
324
+ if (commands.length > 0) {
325
+ const before = { build: facts.build, test: facts.test, lint: facts.lint };
326
+ applyCiCommands(facts, commands);
327
+ if (facts.build !== before.build || facts.test !== before.test || facts.lint !== before.lint) {
328
+ facts.hints.push(".github/workflows");
329
+ }
330
+ }
331
+ } catch {
332
+ }
333
+ }
334
+ if (!facts.build && !facts.test && !facts.run && !facts.lint) {
335
+ const scan = await scanSourceTree(root);
336
+ if (scan) {
337
+ facts.languages = scan.languages;
338
+ if (scan.entryPoints.length > 0) facts.entryPoints = scan.entryPoints;
339
+ if (scan.topDirs.length > 0) facts.topDirs = scan.topDirs;
340
+ facts.hints.push(`source scan: ${scan.languages.join(", ")}`);
341
+ }
342
+ }
148
343
  return facts;
149
344
  }
150
345
  function renderAgentsTemplate(f) {
@@ -152,6 +347,16 @@ function renderAgentsTemplate(f) {
152
347
  const hints = f.hints.length > 0 ? `
153
348
 
154
349
  > Auto-detected: ${f.hints.join(", ")}` : "";
350
+ const runtime = f.languages?.length ? `_CLI, server, browser, worker, library, package?_ \u2014 detected: ${f.languages.join(", ")}` : "_CLI, server, browser, worker, library, package?_";
351
+ const keyFileRows = [];
352
+ for (const ep of f.entryPoints ?? [])
353
+ keyFileRows.push(`| \`${ep}\` | _Likely entry point (detected)_ |`);
354
+ for (const dir of f.topDirs ?? [])
355
+ keyFileRows.push(`| \`${dir}/\` | _Top-level directory (detected)_ |`);
356
+ const keyFiles = keyFileRows.length > 0 ? keyFileRows.join("\n") : `| _src/_ | _Main source entry point(s)_ |
357
+ | _tests/_ | _Test root or convention_ |
358
+ | _docs/_ | _Architecture, runbooks, design notes_ |
359
+ | _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |`;
155
360
  return `# AGENTS.md
156
361
 
157
362
  > **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
@@ -163,7 +368,7 @@ function renderAgentsTemplate(f) {
163
368
 
164
369
  - **Purpose:** _What does this project do and why does it exist?_
165
370
  - **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
166
- - **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
371
+ - **Runtime / deployment:** ${runtime}${hints}
167
372
 
168
373
  ## How to work safely
169
374
 
@@ -185,10 +390,7 @@ function renderAgentsTemplate(f) {
185
390
 
186
391
  | File / directory | Role |
187
392
  |---|---|
188
- | _src/_ | _Main source entry point(s)_ |
189
- | _tests/_ | _Test root or convention_ |
190
- | _docs/_ | _Architecture, runbooks, design notes_ |
191
- | _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
393
+ ${keyFiles}
192
394
 
193
395
  ## Architecture notes
194
396
 
@@ -247,8 +449,79 @@ function countToolResults(messages) {
247
449
  function estimateTokens(messages) {
248
450
  return estimateMessageTokens(messages);
249
451
  }
452
+ var EXT_LANG, SCAN_IGNORE_DIRS, ENTRY_BASENAMES;
250
453
  var init_helpers = __esm({
251
454
  "src/slash-commands/helpers.ts"() {
455
+ EXT_LANG = {
456
+ ".ts": "TypeScript",
457
+ ".tsx": "TypeScript",
458
+ ".mts": "TypeScript",
459
+ ".cts": "TypeScript",
460
+ ".js": "JavaScript",
461
+ ".jsx": "JavaScript",
462
+ ".mjs": "JavaScript",
463
+ ".cjs": "JavaScript",
464
+ ".py": "Python",
465
+ ".rb": "Ruby",
466
+ ".go": "Go",
467
+ ".rs": "Rust",
468
+ ".java": "Java",
469
+ ".kt": "Kotlin",
470
+ ".kts": "Kotlin",
471
+ ".c": "C",
472
+ ".h": "C",
473
+ ".cpp": "C++",
474
+ ".cc": "C++",
475
+ ".cxx": "C++",
476
+ ".hpp": "C++",
477
+ ".cs": "C#",
478
+ ".php": "PHP",
479
+ ".swift": "Swift",
480
+ ".scala": "Scala",
481
+ ".clj": "Clojure",
482
+ ".ex": "Elixir",
483
+ ".exs": "Elixir",
484
+ ".erl": "Erlang",
485
+ ".hs": "Haskell",
486
+ ".ml": "OCaml",
487
+ ".dart": "Dart",
488
+ ".lua": "Lua",
489
+ ".jl": "Julia",
490
+ ".sh": "Shell",
491
+ ".bash": "Shell",
492
+ ".ps1": "PowerShell",
493
+ ".pl": "Perl",
494
+ ".zig": "Zig",
495
+ ".nim": "Nim",
496
+ ".sql": "SQL",
497
+ ".vue": "Vue",
498
+ ".svelte": "Svelte"
499
+ };
500
+ SCAN_IGNORE_DIRS = /* @__PURE__ */ new Set([
501
+ ".git",
502
+ "node_modules",
503
+ "dist",
504
+ "build",
505
+ "out",
506
+ "target",
507
+ "vendor",
508
+ ".venv",
509
+ "venv",
510
+ "env",
511
+ "__pycache__",
512
+ ".next",
513
+ ".nuxt",
514
+ ".cache",
515
+ "coverage",
516
+ ".wrongstack",
517
+ ".idea",
518
+ ".vscode",
519
+ "obj",
520
+ ".gradle",
521
+ ".dart_tool",
522
+ "Pods"
523
+ ]);
524
+ ENTRY_BASENAMES = /* @__PURE__ */ new Set(["main", "index", "app", "cli", "server", "__main__"]);
252
525
  }
253
526
  });
254
527
  function normalizeKeys(cfg) {
@@ -269,11 +542,18 @@ function writeKeysBack(cfg, keys) {
269
542
  }
270
543
  cfg.apiKeys = keys;
271
544
  const active = keys.find((k) => k.label === cfg.activeKey) ?? expectDefined(keys[0]);
272
- cfg.apiKey = active.apiKey;
545
+ delete cfg.apiKey;
273
546
  if (!cfg.activeKey || !keys.some((k) => k.label === cfg.activeKey)) {
274
547
  cfg.activeKey = active.label;
275
548
  }
276
549
  }
550
+ function resolveActiveApiKey(cfg) {
551
+ if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
552
+ const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
553
+ return (active ?? cfg.apiKeys[0])?.apiKey;
554
+ }
555
+ return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
556
+ }
277
557
  function activeLabel(cfg, keys) {
278
558
  if (cfg.activeKey && keys.some((k) => k.label === cfg.activeKey)) return cfg.activeKey;
279
559
  return keys[0]?.label;
@@ -678,7 +958,7 @@ async function findSpec(store, idOrTitle) {
678
958
  async function gatherProjectContext2(projectRoot) {
679
959
  const parts = [];
680
960
  try {
681
- const pkgPath = path39.join(projectRoot, "package.json");
961
+ const pkgPath = path4.join(projectRoot, "package.json");
682
962
  const pkgRaw = await fsp5.readFile(pkgPath, "utf8");
683
963
  const pkg = JSON.parse(pkgRaw);
684
964
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -696,13 +976,13 @@ async function gatherProjectContext2(projectRoot) {
696
976
  } catch {
697
977
  }
698
978
  try {
699
- const tsconfigPath = path39.join(projectRoot, "tsconfig.json");
979
+ const tsconfigPath = path4.join(projectRoot, "tsconfig.json");
700
980
  await fsp5.access(tsconfigPath);
701
981
  parts.push("Language: TypeScript");
702
982
  } catch {
703
983
  }
704
984
  try {
705
- const srcDir = path39.join(projectRoot, "src");
985
+ const srcDir = path4.join(projectRoot, "src");
706
986
  const entries = await fsp5.readdir(srcDir, { withFileTypes: true });
707
987
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
708
988
  if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
@@ -2127,12 +2407,12 @@ __export(project_utils_exports, {
2127
2407
  touchProjectInManifest: () => touchProjectInManifest
2128
2408
  });
2129
2409
  function projectsJsonPath(globalConfigPath) {
2130
- const base = globalConfigPath ? path39.dirname(globalConfigPath) : wstackGlobalRoot();
2131
- return path39.join(base, "projects.json");
2410
+ const base = globalConfigPath ? path4.dirname(globalConfigPath) : wstackGlobalRoot();
2411
+ return path4.join(base, "projects.json");
2132
2412
  }
2133
2413
  function projectsDataDir(globalConfigPath) {
2134
- const base = globalConfigPath ? path39.dirname(globalConfigPath) : wstackGlobalRoot();
2135
- return path39.join(base, "projects");
2414
+ const base = globalConfigPath ? path4.dirname(globalConfigPath) : wstackGlobalRoot();
2415
+ return path4.join(base, "projects");
2136
2416
  }
2137
2417
  async function loadManifest(globalConfigPath) {
2138
2418
  const file = projectsJsonPath(globalConfigPath);
@@ -2146,12 +2426,12 @@ async function loadManifest(globalConfigPath) {
2146
2426
  }
2147
2427
  async function saveManifest(manifest, globalConfigPath) {
2148
2428
  const file = projectsJsonPath(globalConfigPath);
2149
- await fsp5.mkdir(path39.dirname(file), { recursive: true });
2429
+ await fsp5.mkdir(path4.dirname(file), { recursive: true });
2150
2430
  await fsp5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
2151
2431
  }
2152
2432
  function generateSlug(root) {
2153
- const base = path39.basename(root).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
2154
- const hash = createHash("sha256").update(path39.resolve(root)).digest("hex").slice(0, 6);
2433
+ const base = path4.basename(root).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
2434
+ const hash = createHash("sha256").update(path4.resolve(root)).digest("hex").slice(0, 6);
2155
2435
  return `${base}-${hash}`;
2156
2436
  }
2157
2437
  function findProject(manifest, query) {
@@ -2166,29 +2446,29 @@ function findProject(manifest, query) {
2166
2446
  return found;
2167
2447
  }
2168
2448
  async function ensureProjectDataDir(slug, globalConfigPath) {
2169
- const dir = path39.join(projectsDataDir(globalConfigPath), slug);
2449
+ const dir = path4.join(projectsDataDir(globalConfigPath), slug);
2170
2450
  await fsp5.mkdir(dir, { recursive: true });
2171
2451
  return dir;
2172
2452
  }
2173
2453
  async function touchProjectInManifest(opts) {
2174
- const root = path39.resolve(opts.projectRoot);
2454
+ const root = path4.resolve(opts.projectRoot);
2175
2455
  const file = projectsJsonPath(opts.globalConfigPath);
2176
2456
  let entry;
2177
2457
  await withFileLock(file, async () => {
2178
2458
  const manifest = await loadManifest(opts.globalConfigPath);
2179
2459
  const now = (/* @__PURE__ */ new Date()).toISOString();
2180
- entry = manifest.projects.find((p) => path39.resolve(p.root) === root);
2460
+ entry = manifest.projects.find((p) => path4.resolve(p.root) === root);
2181
2461
  if (entry) {
2182
2462
  entry.lastSeen = now;
2183
- if (opts.workingDir) entry.lastWorkingDir = path39.resolve(opts.workingDir);
2463
+ if (opts.workingDir) entry.lastWorkingDir = path4.resolve(opts.workingDir);
2184
2464
  } else {
2185
2465
  entry = {
2186
- name: opts.name ?? path39.basename(root),
2466
+ name: opts.name ?? path4.basename(root),
2187
2467
  root,
2188
2468
  slug: generateSlug(root),
2189
2469
  createdAt: now,
2190
2470
  lastSeen: now,
2191
- lastWorkingDir: opts.workingDir ? path39.resolve(opts.workingDir) : void 0
2471
+ lastWorkingDir: opts.workingDir ? path4.resolve(opts.workingDir) : void 0
2192
2472
  };
2193
2473
  manifest.projects.push(entry);
2194
2474
  }
@@ -2634,7 +2914,7 @@ __export(update_check_exports, {
2634
2914
  getUpdateNotification: () => getUpdateNotification
2635
2915
  });
2636
2916
  function cachePath(homeFn = defaultHomeDir2) {
2637
- return path39.join(homeFn(), ".wrongstack", "update-cache.json");
2917
+ return path4.join(homeFn(), ".wrongstack", "update-cache.json");
2638
2918
  }
2639
2919
  function currentVersion() {
2640
2920
  const req2 = createRequire(import.meta.url);
@@ -2671,7 +2951,7 @@ async function readCache(homeFn = defaultHomeDir2) {
2671
2951
  }
2672
2952
  async function writeCache(entry, homeFn = defaultHomeDir2) {
2673
2953
  try {
2674
- const dir = path39.dirname(cachePath(homeFn));
2954
+ const dir = path4.dirname(cachePath(homeFn));
2675
2955
  await fsp5.mkdir(dir, { recursive: true });
2676
2956
  await fsp5.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
2677
2957
  } catch {
@@ -2757,7 +3037,7 @@ function registerWebuiInstance(p, deps = {}) {
2757
3037
  wsPort: p.wsPort,
2758
3038
  host: p.host,
2759
3039
  projectRoot: p.projectRoot,
2760
- projectName: path39.basename(p.projectRoot) || p.projectRoot,
3040
+ projectName: path4.basename(p.projectRoot) || p.projectRoot,
2761
3041
  startedAt: p.startedAt,
2762
3042
  url: `http://${p.host}:${p.httpPort}`
2763
3043
  },
@@ -2768,7 +3048,7 @@ function registerWebuiInstance(p, deps = {}) {
2768
3048
  function announceWebuiReady(p) {
2769
3049
  const log = p.log ?? ((m) => console.log(m));
2770
3050
  const launch = p.openBrowserFn ?? openBrowser;
2771
- const openUrl = `http://${p.host}:${p.httpPort}`;
3051
+ const openUrl = p.wsToken ? `http://${p.host}:${p.httpPort}?token=${encodeURIComponent(p.wsToken)}` : `http://${p.host}:${p.httpPort}`;
2772
3052
  p.server.on("listening", () => {
2773
3053
  log(
2774
3054
  `
@@ -2821,7 +3101,7 @@ var init_lifecycle = __esm({
2821
3101
  }
2822
3102
  });
2823
3103
  function getVault(globalConfigPath) {
2824
- const keyFile = path39.join(path39.dirname(globalConfigPath ?? ""), ".key");
3104
+ const keyFile = path4.join(path4.dirname(globalConfigPath ?? ""), ".key");
2825
3105
  return new DefaultSecretVault({ keyFile });
2826
3106
  }
2827
3107
  async function loadSavedProviders(globalConfigPath) {
@@ -2855,7 +3135,7 @@ function resolveDistDir() {
2855
3135
  try {
2856
3136
  const requireFromHere = createRequire(import.meta.url);
2857
3137
  const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
2858
- return path39.resolve(path39.dirname(serverEntry), "..");
3138
+ return path4.resolve(path4.dirname(serverEntry), "..");
2859
3139
  } catch {
2860
3140
  return null;
2861
3141
  }
@@ -2869,7 +3149,9 @@ function startStaticServe(opts, deps = {}) {
2869
3149
  host: opts.host,
2870
3150
  distDir,
2871
3151
  wsPort: opts.wsPort,
2872
- globalRoot: opts.globalRoot
3152
+ globalRoot: opts.globalRoot,
3153
+ onFleetPing: opts.onFleetPing,
3154
+ apiToken: opts.apiToken
2873
3155
  });
2874
3156
  server.listen(opts.httpPort, opts.host);
2875
3157
  return { server, port: opts.httpPort };
@@ -3540,8 +3822,8 @@ function sendResult6(ctx, ws, success, message) {
3540
3822
  ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
3541
3823
  }
3542
3824
  async function handleProjectsList(ctx, ws) {
3543
- const projectsBase = ctx.opts.globalConfigPath ? path39.resolve(path39.dirname(ctx.opts.globalConfigPath)) : wstackGlobalRoot();
3544
- const manifestPath = path39.join(projectsBase, "projects.json");
3825
+ const projectsBase = ctx.opts.globalConfigPath ? path4.resolve(path4.dirname(ctx.opts.globalConfigPath)) : wstackGlobalRoot();
3826
+ const manifestPath = path4.join(projectsBase, "projects.json");
3545
3827
  try {
3546
3828
  const raw = await fsp5.readFile(manifestPath, "utf8");
3547
3829
  const manifest = JSON.parse(raw);
@@ -3554,22 +3836,22 @@ async function handleProjectsSelect(ctx, ws, payload) {
3554
3836
  const { opts } = ctx;
3555
3837
  const { root, name: projectName } = payload;
3556
3838
  try {
3557
- const resolved = path39.resolve(root);
3839
+ const resolved = path4.resolve(root);
3558
3840
  const stat7 = await fsp5.stat(resolved).catch(() => null);
3559
3841
  if (!stat7?.isDirectory()) {
3560
3842
  ctx.send(ws, {
3561
3843
  type: "projects.selected",
3562
3844
  payload: {
3563
3845
  root,
3564
- name: projectName ?? path39.basename(root),
3846
+ name: projectName ?? path4.basename(root),
3565
3847
  message: `Cannot switch: not a directory: ${resolved}`
3566
3848
  }
3567
3849
  });
3568
3850
  return;
3569
3851
  }
3570
3852
  const manifest = await loadManifest(opts.globalConfigPath);
3571
- const entry = manifest.projects.find((p) => path39.resolve(p.root) === resolved);
3572
- const displayName = projectName?.trim() || entry?.name || path39.basename(resolved);
3853
+ const entry = manifest.projects.find((p) => path4.resolve(p.root) === resolved);
3854
+ const displayName = projectName?.trim() || entry?.name || path4.basename(resolved);
3573
3855
  if (entry) {
3574
3856
  entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
3575
3857
  } else {
@@ -3615,8 +3897,8 @@ async function handleProjectsSelect(ctx, ws, payload) {
3615
3897
  });
3616
3898
  } catch {
3617
3899
  }
3618
- const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : wstackGlobalRoot();
3619
- const newSessionsDir = path39.join(resolveProjectDir(resolved, globalRoot), "sessions");
3900
+ const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : wstackGlobalRoot();
3901
+ const newSessionsDir = path4.join(resolveProjectDir(resolved, globalRoot), "sessions");
3620
3902
  await fsp5.mkdir(newSessionsDir, { recursive: true });
3621
3903
  const newStore = new DefaultSessionStore({ dir: newSessionsDir });
3622
3904
  opts.sessionStore = newStore;
@@ -3646,11 +3928,11 @@ async function handleProjectsSelect(ctx, ws, payload) {
3646
3928
  async function handleProjectsAdd(ctx, ws, payload) {
3647
3929
  const { root: addRoot, name: addName } = payload;
3648
3930
  try {
3649
- const resolved = path39.resolve(addRoot);
3931
+ const resolved = path4.resolve(addRoot);
3650
3932
  const stat7 = await fsp5.stat(resolved).catch(() => null);
3651
3933
  if (!stat7?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
3652
3934
  const manifest = await loadManifest(ctx.opts.globalConfigPath);
3653
- const existing = manifest.projects.find((p) => path39.resolve(p.root) === resolved);
3935
+ const existing = manifest.projects.find((p) => path4.resolve(p.root) === resolved);
3654
3936
  if (existing) {
3655
3937
  ctx.send(ws, {
3656
3938
  type: "projects.added",
@@ -3663,7 +3945,7 @@ async function handleProjectsAdd(ctx, ws, payload) {
3663
3945
  });
3664
3946
  return;
3665
3947
  }
3666
- const name = addName?.trim() || path39.basename(resolved);
3948
+ const name = addName?.trim() || path4.basename(resolved);
3667
3949
  const slug = projectSlug(resolved);
3668
3950
  await ensureProjectDataDir(slug, ctx.opts.globalConfigPath);
3669
3951
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3677,7 +3959,7 @@ async function handleProjectsAdd(ctx, ws, payload) {
3677
3959
  ctx.send(ws, {
3678
3960
  type: "projects.added",
3679
3961
  payload: {
3680
- name: path39.basename(addRoot),
3962
+ name: path4.basename(addRoot),
3681
3963
  root: addRoot,
3682
3964
  slug: "",
3683
3965
  message: toErrorMessage(err)
@@ -3688,8 +3970,8 @@ async function handleProjectsAdd(ctx, ws, payload) {
3688
3970
  async function handleWorkingDirSet(ctx, ws, newPath) {
3689
3971
  try {
3690
3972
  const wdRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
3691
- const resolved = path39.resolve(wdRoot, newPath);
3692
- if (!resolved.startsWith(wdRoot + path39.sep) && resolved !== wdRoot) {
3973
+ const resolved = path4.resolve(wdRoot, newPath);
3974
+ if (!resolved.startsWith(wdRoot + path4.sep) && resolved !== wdRoot) {
3693
3975
  sendResult6(ctx, ws, false, `Path must stay inside the project root: ${wdRoot}`);
3694
3976
  return;
3695
3977
  }
@@ -4026,13 +4308,13 @@ function sendResult8(ctx, ws, success, message) {
4026
4308
  }
4027
4309
  function storeFor(opts) {
4028
4310
  return opts.sessionStore ?? new DefaultSessionStore({
4029
- dir: path39.join(opts.projectRoot ?? opts.agent.ctx.projectRoot, ".wrongstack", "sessions")
4311
+ dir: path4.join(opts.projectRoot ?? opts.agent.ctx.projectRoot, ".wrongstack", "sessions")
4030
4312
  });
4031
4313
  }
4032
4314
  async function handleGoalGet(ctx, _ws) {
4033
4315
  const projectRoot = ctx.opts.projectRoot ?? ctx.opts.agent.ctx.projectRoot;
4034
4316
  try {
4035
- const goalPath = path39.join(projectRoot, ".wrongstack", "goal.json");
4317
+ const goalPath = path4.join(projectRoot, ".wrongstack", "goal.json");
4036
4318
  const raw = await fsp5.readFile(goalPath, "utf8");
4037
4319
  ctx.broadcast({ type: "goal.updated", payload: JSON.parse(raw) });
4038
4320
  } catch {
@@ -4108,7 +4390,7 @@ async function handleSessionNew(ctx, _ws) {
4108
4390
  function rewinderFor(opts) {
4109
4391
  const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot;
4110
4392
  return new DefaultSessionRewinder(
4111
- opts.sessionsDir ?? path39.join(projectRoot, ".wrongstack", "sessions"),
4393
+ opts.sessionsDir ?? path4.join(projectRoot, ".wrongstack", "sessions"),
4112
4394
  projectRoot
4113
4395
  );
4114
4396
  }
@@ -4442,14 +4724,14 @@ async function runWebUI(opts) {
4442
4724
  let customModeStoreP = null;
4443
4725
  const getCustomModeStore = () => {
4444
4726
  customModeStoreP ??= (async () => {
4445
- const dir = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : wstackGlobalRoot();
4727
+ const dir = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : wstackGlobalRoot();
4446
4728
  const store = createCustomModeStore(dir);
4447
4729
  await store.load();
4448
4730
  return store;
4449
4731
  })();
4450
4732
  return customModeStoreP;
4451
4733
  };
4452
- const autoPhaseStoreDir = opts.projectRoot ? path39.join(opts.projectRoot, ".wrongstack", "autophase") : path39.join(os.tmpdir(), ".wrongstack", "autophase");
4734
+ const autoPhaseStoreDir = opts.projectRoot ? path4.join(opts.projectRoot, ".wrongstack", "autophase") : path4.join(os.tmpdir(), ".wrongstack", "autophase");
4453
4735
  const autoPhaseHandler = new AutoPhaseWebSocketHandler(
4454
4736
  opts.agent,
4455
4737
  opts.agent.ctx,
@@ -4480,7 +4762,12 @@ async function runWebUI(opts) {
4480
4762
  "contextAutoCompact",
4481
4763
  "contextStrategy",
4482
4764
  "logLevel",
4483
- "auditLevel"
4765
+ "auditLevel",
4766
+ // Telegram plugin notification settings (parity with the standalone server).
4767
+ "tgConfigured",
4768
+ "tgSessionEnd",
4769
+ "tgDelegate",
4770
+ "tgLongToolMs"
4484
4771
  ];
4485
4772
  const prefSnapshot = () => {
4486
4773
  const snapshot = {};
@@ -4519,6 +4806,12 @@ async function runWebUI(opts) {
4519
4806
  meta["logLevel"] = cfg.log?.["level"] ?? "info";
4520
4807
  meta["auditLevel"] = cfg.session?.["auditLevel"] ?? "standard";
4521
4808
  meta["maxIterations"] = cfg.tools?.["maxIterations"] ?? 500;
4809
+ const tgExt = cfg.extensions?.["telegram"];
4810
+ meta["tgConfigured"] = typeof tgExt?.["botToken"] === "string" && tgExt["botToken"].length > 0;
4811
+ meta["tgSessionEnd"] = tgExt?.["notifyOnSessionEnd"] === true;
4812
+ meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
4813
+ const tgMs = tgExt?.["longToolThresholdMs"];
4814
+ meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
4522
4815
  } catch {
4523
4816
  }
4524
4817
  }
@@ -4540,7 +4833,7 @@ async function runWebUI(opts) {
4540
4833
  return;
4541
4834
  }
4542
4835
  const vault = new DefaultSecretVault({
4543
- keyFile: path39.join(path39.dirname(configPath2), ".key")
4836
+ keyFile: path4.join(path4.dirname(configPath2), ".key")
4544
4837
  });
4545
4838
  const decrypted = decryptConfigSecrets$1(parsed, vault);
4546
4839
  const autonomyCfg = decrypted.autonomy ?? {};
@@ -4613,6 +4906,16 @@ async function runWebUI(opts) {
4613
4906
  toolsCfg.maxIterations = payload["maxIterations"];
4614
4907
  decrypted.tools = toolsCfg;
4615
4908
  }
4909
+ const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
4910
+ if (tgTouched) {
4911
+ const ext = decrypted.extensions ?? {};
4912
+ const tg = ext["telegram"] ?? {};
4913
+ if (typeof payload["tgSessionEnd"] === "boolean") tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
4914
+ if (typeof payload["tgDelegate"] === "boolean") tg["notifyOnDelegate"] = payload["tgDelegate"];
4915
+ if (typeof payload["tgLongToolMs"] === "number") tg["longToolThresholdMs"] = payload["tgLongToolMs"];
4916
+ ext["telegram"] = tg;
4917
+ decrypted.extensions = ext;
4918
+ }
4616
4919
  const encrypted = encryptConfigSecrets$1(decrypted, vault);
4617
4920
  await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
4618
4921
  };
@@ -4634,7 +4937,6 @@ async function runWebUI(opts) {
4634
4937
  );
4635
4938
  }
4636
4939
  };
4637
- const authToken = crypto3.randomBytes(16).toString("hex");
4638
4940
  const sessionStartedAt = Date.now();
4639
4941
  async function buildSessionStartPayload(overrides, needsSetup = false) {
4640
4942
  let maxContext = 0;
@@ -4663,7 +4965,7 @@ async function runWebUI(opts) {
4663
4965
  model: opts.agent.ctx.model,
4664
4966
  provider: opts.agent.ctx.provider.id,
4665
4967
  mode: opts.modeId ?? "default",
4666
- projectName: opts.projectRoot ? path39.basename(opts.projectRoot) : void 0,
4968
+ projectName: opts.projectRoot ? path4.basename(opts.projectRoot) : void 0,
4667
4969
  // Frontend reads `projectRoot` from session.start (ws-handlers setEnv) —
4668
4970
  // omitting it left the store's projectRoot empty after a project switch.
4669
4971
  projectRoot: opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "",
@@ -4689,7 +4991,7 @@ async function runWebUI(opts) {
4689
4991
  const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
4690
4992
  const mailbox = new GlobalMailbox(projectDir, opts.events);
4691
4993
  webuiClientId = `webui@${crypto3.randomUUID().slice(0, 8)}`;
4692
- const projectName = opts.projectRoot ? path39.basename(opts.projectRoot) : "unknown";
4994
+ const projectName = opts.projectRoot ? path4.basename(opts.projectRoot) : "unknown";
4693
4995
  await mailbox.registerClient({
4694
4996
  clientId: webuiClientId,
4695
4997
  sessionId: opts.projectRoot,
@@ -4714,13 +5016,19 @@ async function runWebUI(opts) {
4714
5016
  }
4715
5017
  };
4716
5018
  registerWebuiClient();
5019
+ const wsToken = generateAuthToken();
4717
5020
  const wss = new WebSocketServer({ port, host, maxPayload: 1 * 1024 * 1024 });
4718
5021
  console.log(`[WebUI] WebSocket server starting on ws://${host}:${port}`);
5022
+ let fleetBroadcastCli = null;
4719
5023
  const httpServer = startStaticServe({
4720
5024
  host,
4721
5025
  httpPort,
4722
5026
  wsPort,
4723
- globalRoot: path39.dirname(opts.globalConfigPath ?? "")
5027
+ globalRoot: path4.dirname(opts.globalConfigPath ?? ""),
5028
+ onFleetPing: () => {
5029
+ void fleetBroadcastCli?.();
5030
+ },
5031
+ apiToken: wsToken
4724
5032
  });
4725
5033
  if (httpServer) {
4726
5034
  announceWebuiReady({
@@ -4728,14 +5036,15 @@ async function runWebUI(opts) {
4728
5036
  host,
4729
5037
  httpPort,
4730
5038
  wsPort,
4731
- open: !!opts.open
5039
+ open: !!opts.open,
5040
+ wsToken
4732
5041
  });
4733
5042
  } else {
4734
5043
  console.warn(
4735
5044
  `[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`). WS bridge still active on ws://${host}:${wsPort}.`
4736
5045
  );
4737
5046
  }
4738
- const registryBaseDir = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : void 0;
5047
+ const registryBaseDir = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
4739
5048
  if (opts.projectRoot) {
4740
5049
  registerWebuiInstance({
4741
5050
  pid: process.pid,
@@ -4770,9 +5079,13 @@ async function runWebUI(opts) {
4770
5079
  emitConcurrency();
4771
5080
  eventUnsubscribers.push(
4772
5081
  opts.events.on("iteration.started", (e) => {
5082
+ const maxIt = opts.agent.ctx.meta["maxIterations"];
4773
5083
  broadcast({
4774
5084
  type: "iteration.started",
4775
- payload: { index: e.index }
5085
+ payload: {
5086
+ index: e.index,
5087
+ ...typeof maxIt === "number" ? { maxIterations: maxIt } : {}
5088
+ }
4776
5089
  });
4777
5090
  })
4778
5091
  );
@@ -5033,6 +5346,18 @@ async function runWebUI(opts) {
5033
5346
  broadcast,
5034
5347
  log: (m) => console.log(m)
5035
5348
  };
5349
+ const skillsProjectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5350
+ const skillsCtx = {
5351
+ skillLoader: opts.skillLoader,
5352
+ skillInstaller: opts.skillLoader ? new SkillInstaller({
5353
+ manifestPath: path4.join(wstackGlobalRoot(), "installed-skills.json"),
5354
+ projectSkillsDir: path4.join(skillsProjectRoot, ".wrongstack", "skills"),
5355
+ globalSkillsDir: path4.join(wstackGlobalRoot(), "skills"),
5356
+ projectHash: skillsProjectRoot ? projectHash(skillsProjectRoot) : "",
5357
+ skillLoader: opts.skillLoader
5358
+ }) : void 0,
5359
+ projectRoot: skillsProjectRoot
5360
+ };
5036
5361
  const worklistCtx = {
5037
5362
  agent: opts.agent,
5038
5363
  sessionId: opts.session.id,
@@ -5097,20 +5422,22 @@ async function runWebUI(opts) {
5097
5422
  console.log(`[WebUI] WebSocket server running on ws://${host}:${port}`);
5098
5423
  setupEvents();
5099
5424
  opts.onListening?.({ httpPort, wsPort, host });
5100
- const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : void 0;
5425
+ const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : void 0;
5101
5426
  if (globalRoot) {
5102
- const statusInterval = setInterval(async () => {
5427
+ const broadcastSessions = async () => {
5103
5428
  try {
5104
5429
  const { SessionRegistry } = await import('@wrongstack/core');
5105
5430
  const registry = new SessionRegistry(globalRoot);
5106
5431
  const sessions = await registry.list();
5107
- const live = sessions.filter((s) => s.status !== "stale").map((s) => ({
5432
+ const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
5433
+ const live = sessions.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
5108
5434
  sessionId: s.sessionId,
5109
5435
  projectName: s.projectName,
5110
5436
  projectSlug: s.projectSlug,
5111
5437
  projectRoot: s.projectRoot,
5112
5438
  workingDir: s.workingDir,
5113
5439
  gitBranch: s.gitBranch,
5440
+ clientType: s.clientType,
5114
5441
  status: s.status,
5115
5442
  pid: s.pid,
5116
5443
  startedAt: s.startedAt,
@@ -5122,60 +5449,52 @@ async function runWebUI(opts) {
5122
5449
  currentTool: a.currentTool,
5123
5450
  iterations: a.iterations,
5124
5451
  toolCalls: a.toolCalls,
5452
+ costUsd: a.costUsd,
5453
+ tokensIn: a.tokensIn,
5454
+ tokensOut: a.tokensOut,
5455
+ ctxPct: a.ctxPct,
5456
+ model: a.model,
5457
+ partialText: a.partialText,
5125
5458
  lastActivityAt: a.lastActivityAt
5126
5459
  }))
5127
5460
  }));
5128
5461
  broadcast({ type: "sessions.status_update", payload: { sessions: live } });
5129
5462
  } catch {
5130
5463
  }
5131
- }, 5e3);
5464
+ };
5465
+ fleetBroadcastCli = broadcastSessions;
5466
+ const statusInterval = setInterval(() => void broadcastSessions(), 5e3);
5132
5467
  if (statusInterval.unref) statusInterval.unref();
5133
5468
  eventUnsubscribers.push(() => clearInterval(statusInterval));
5134
- }
5135
- });
5136
- wss.on("connection", async (ws, req2) => {
5137
- const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
5138
- const tokenMatches = (provided) => {
5139
- if (!provided) return false;
5140
- const a = Buffer.from(provided);
5141
- const b = Buffer.from(authToken);
5142
- return a.length === b.length && crypto3.timingSafeEqual(a, b);
5143
- };
5144
- try {
5145
- const url = new URL(req2.url ?? "/", `http://localhost:${port}`);
5146
- const token = url.searchParams.get("token");
5147
- const tokenOk = tokenMatches(token);
5148
- const hostHeader = (req2.headers.host ?? "").trim();
5149
- let hostOk = false;
5469
+ let regDebounce;
5150
5470
  try {
5151
- hostOk = !!hostHeader && isLoopback(new URL(`http://${hostHeader}`).hostname);
5471
+ const regWatcher = watch(globalRoot, { persistent: false }, (_event, filename) => {
5472
+ const name = filename ? String(filename) : "";
5473
+ if (!name.startsWith("session-registry.json") || name.endsWith(".lock")) return;
5474
+ if (regDebounce) clearTimeout(regDebounce);
5475
+ regDebounce = setTimeout(() => void broadcastSessions(), 150);
5476
+ });
5477
+ eventUnsubscribers.push(() => {
5478
+ if (regDebounce) clearTimeout(regDebounce);
5479
+ regWatcher.close();
5480
+ });
5152
5481
  } catch {
5153
- hostOk = false;
5154
- }
5155
- if (!hostOk) {
5156
- ws.close(4003, "Forbidden: non-loopback Host header");
5157
- return;
5158
- }
5159
- const origin = req2.headers.origin;
5160
- if (origin) {
5161
- try {
5162
- const { hostname } = new URL(origin);
5163
- if (!isLoopback(hostname) && !tokenOk) {
5164
- ws.close(4003, "Forbidden: non-loopback origin requires auth token");
5165
- return;
5166
- }
5167
- } catch {
5168
- ws.close(4003, "Forbidden: invalid origin");
5169
- return;
5170
- }
5171
- } else {
5172
- if (!tokenOk) {
5173
- ws.close(4003, "Forbidden: auth token required for non-browser clients");
5174
- return;
5175
- }
5176
5482
  }
5177
- } catch {
5178
- ws.close(4001, "Unauthorized: malformed request");
5483
+ void broadcastSessions();
5484
+ }
5485
+ });
5486
+ wss.on("connection", async (ws, req2) => {
5487
+ const ok = verifyClient({
5488
+ origin: req2.headers.origin,
5489
+ url: req2.url ?? "/",
5490
+ hostHeader: req2.headers.host,
5491
+ remoteAddress: req2.socket.remoteAddress,
5492
+ cookieHeader: req2.headers.cookie,
5493
+ wsHost: host,
5494
+ expectedToken: wsToken
5495
+ });
5496
+ if (!ok) {
5497
+ ws.close(4003, "Forbidden");
5179
5498
  return;
5180
5499
  }
5181
5500
  const client = { ws, sessionId: opts.session.id };
@@ -5500,10 +5819,84 @@ async function runWebUI(opts) {
5500
5819
  }
5501
5820
  return handleMemoryForget(ws, msg, opts.memoryStore);
5502
5821
  }
5822
+ // ── MCP operations — MCP servers are read directly from config file ──
5823
+ case "mcp.list": {
5824
+ const servers = [];
5825
+ if (opts.globalConfigPath) {
5826
+ try {
5827
+ const raw = await fsp5.readFile(opts.globalConfigPath, "utf8");
5828
+ const cfg = JSON.parse(raw);
5829
+ const mcpServers = cfg.mcpServers;
5830
+ if (mcpServers) {
5831
+ for (const [name, serverCfg] of Object.entries(mcpServers)) {
5832
+ servers.push({
5833
+ name,
5834
+ transport: serverCfg.transport ?? "stdio",
5835
+ status: "stopped",
5836
+ enabled: serverCfg.enabled ?? true,
5837
+ // Conditional spreads keep these absent (not explicitly
5838
+ // `undefined`) to satisfy exactOptionalPropertyTypes.
5839
+ ...serverCfg.description !== void 0 && { description: serverCfg.description },
5840
+ ...serverCfg.allowedTools !== void 0 && { tools: serverCfg.allowedTools }
5841
+ });
5842
+ }
5843
+ }
5844
+ } catch {
5845
+ }
5846
+ }
5847
+ send(ws, { type: "mcp.list", payload: { servers } });
5848
+ break;
5849
+ }
5850
+ case "mcp.add":
5851
+ case "mcp.remove":
5852
+ case "mcp.update":
5853
+ case "mcp.wake":
5854
+ case "mcp.sleep":
5855
+ case "mcp.discover":
5856
+ case "mcp.enable":
5857
+ case "mcp.disable":
5858
+ case "mcp.restart": {
5859
+ send(ws, {
5860
+ type: "mcp.operation_result",
5861
+ payload: {
5862
+ success: false,
5863
+ message: 'MCP management operations require the standalone WebUI server. Please run "wrongstack webui" instead of "wrongstack --webui".'
5864
+ }
5865
+ });
5866
+ break;
5867
+ }
5503
5868
  case "skills.list": {
5504
5869
  await handleSkillsList(introspectionCtx, ws);
5505
5870
  break;
5506
5871
  }
5872
+ case "skills.content": {
5873
+ await handleSkillsContent(ws, skillsCtx, msg);
5874
+ break;
5875
+ }
5876
+ case "skills.install": {
5877
+ await handleSkillsInstall(ws, skillsCtx, msg);
5878
+ break;
5879
+ }
5880
+ case "skills.uninstall": {
5881
+ await handleSkillsUninstall(ws, skillsCtx, msg);
5882
+ break;
5883
+ }
5884
+ case "skills.update": {
5885
+ await handleSkillsUpdate(ws, skillsCtx, msg);
5886
+ break;
5887
+ }
5888
+ case "skills.create": {
5889
+ await handleSkillsCreate(ws, skillsCtx, msg);
5890
+ break;
5891
+ }
5892
+ case "skills.edit": {
5893
+ await handleSkillsEdit(ws, skillsCtx, msg);
5894
+ break;
5895
+ }
5896
+ case "skills.export": {
5897
+ await handleSkillsExport(ws, skillsCtx);
5898
+ break;
5899
+ }
5507
5900
  case "modes.list": {
5508
5901
  await handleModesList(agentConfigCtx, ws);
5509
5902
  break;
@@ -5676,7 +6069,7 @@ async function runWebUI(opts) {
5676
6069
  // ── Mailbox operations — project-level inter-agent messaging ────
5677
6070
  case "mailbox.messages": {
5678
6071
  const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5679
- const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : "";
6072
+ const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
5680
6073
  if (!projectRoot || !globalRoot) {
5681
6074
  send(ws, {
5682
6075
  type: "mailbox.messages",
@@ -5725,7 +6118,7 @@ async function runWebUI(opts) {
5725
6118
  }
5726
6119
  case "mailbox.agents": {
5727
6120
  const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5728
- const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : "";
6121
+ const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
5729
6122
  if (!projectRoot || !globalRoot) {
5730
6123
  send(ws, {
5731
6124
  type: "mailbox.agents",
@@ -5768,7 +6161,7 @@ async function runWebUI(opts) {
5768
6161
  }
5769
6162
  case "mailbox.clear": {
5770
6163
  const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5771
- const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : "";
6164
+ const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
5772
6165
  if (!projectRoot || !globalRoot) {
5773
6166
  send(ws, { type: "mailbox.cleared", payload: { error: "No project root available" } });
5774
6167
  break;
@@ -5788,7 +6181,7 @@ async function runWebUI(opts) {
5788
6181
  }
5789
6182
  case "mailbox.purge": {
5790
6183
  const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5791
- const globalRoot = opts.globalConfigPath ? path39.dirname(opts.globalConfigPath) : "";
6184
+ const globalRoot = opts.globalConfigPath ? path4.dirname(opts.globalConfigPath) : "";
5792
6185
  if (!projectRoot || !globalRoot) {
5793
6186
  send(ws, { type: "mailbox.purged", payload: { error: "No project root available" } });
5794
6187
  break;
@@ -5809,32 +6202,7 @@ async function runWebUI(opts) {
5809
6202
  }
5810
6203
  case "git.info": {
5811
6204
  const projectRoot = opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
5812
- const cwd = projectRoot || void 0;
5813
- const { execFile: ef } = await import('child_process');
5814
- const git = (args) => new Promise((resolve11) => {
5815
- ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
5816
- resolve11(err ? "" : stdout.trim());
5817
- });
5818
- });
5819
- const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
5820
- git(["branch", "--show-current"]),
5821
- git(["diff", "--stat"]),
5822
- git(["status", "--porcelain"]),
5823
- git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"])
5824
- ]);
5825
- const branch = branchRaw || "(detached)";
5826
- const addMatch = /(\d+)\s+insertion/i.exec(diffRaw);
5827
- const delMatch = /(\d+)\s+deletion/i.exec(diffRaw);
5828
- const added = addMatch ? Number(addMatch[1]) : 0;
5829
- const deleted = delMatch ? Number(delMatch[1]) : 0;
5830
- const untracked = statusRaw.split("\n").filter((l) => l.startsWith("??")).length;
5831
- const [behindRaw, aheadRaw] = (upstreamRaw || "0 0").split(" ");
5832
- const behind = Number(behindRaw) || 0;
5833
- const ahead = Number(aheadRaw) || 0;
5834
- send(ws, {
5835
- type: "git.info",
5836
- payload: { branch, added, deleted, untracked, ahead, behind }
5837
- });
6205
+ await handleGitInfo(ws, projectRoot);
5838
6206
  break;
5839
6207
  }
5840
6208
  default: {
@@ -6449,7 +6817,7 @@ var ReadlineInputReader = class {
6449
6817
  history = [];
6450
6818
  pending = false;
6451
6819
  constructor(opts = {}) {
6452
- this.historyFile = opts.historyFile ?? path39.join(wstackGlobalRoot(), "history");
6820
+ this.historyFile = opts.historyFile ?? path4.join(wstackGlobalRoot(), "history");
6453
6821
  }
6454
6822
  async loadHistory() {
6455
6823
  try {
@@ -6461,7 +6829,7 @@ var ReadlineInputReader = class {
6461
6829
  }
6462
6830
  async saveHistory() {
6463
6831
  try {
6464
- await fsp5.mkdir(path39.dirname(this.historyFile), { recursive: true });
6832
+ await fsp5.mkdir(path4.dirname(this.historyFile), { recursive: true });
6465
6833
  await fsp5.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6466
6834
  } catch {
6467
6835
  }
@@ -6793,7 +7161,7 @@ function pickGroupIndex(opts) {
6793
7161
  if (Number.isFinite(parsed)) current = wrap(parsed);
6794
7162
  } catch {
6795
7163
  }
6796
- fs2.mkdirSync(path39.dirname(opts.cursorFile), { recursive: true });
7164
+ fs2.mkdirSync(path4.dirname(opts.cursorFile), { recursive: true });
6797
7165
  fs2.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
6798
7166
  return current;
6799
7167
  } catch {
@@ -6846,11 +7214,11 @@ function assertSafeToDelete(filename, parentDir) {
6846
7214
  throw new FsError({
6847
7215
  message: `Refusing to delete protected file: ${filename}`,
6848
7216
  code: ERROR_CODES.FS_DELETE_FAILED,
6849
- path: path39.join(parentDir, filename),
7217
+ path: path4.join(parentDir, filename),
6850
7218
  context: { reason: "protected_basename" }
6851
7219
  });
6852
7220
  }
6853
- if (filename !== path39.basename(filename)) {
7221
+ if (filename !== path4.basename(filename)) {
6854
7222
  throw new FsError({
6855
7223
  message: `Refusing to delete path with traversal: ${filename}`,
6856
7224
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -6862,11 +7230,11 @@ function assertSafeToDelete(filename, parentDir) {
6862
7230
  throw new FsError({
6863
7231
  message: `Refusing to delete unknown file: ${filename}`,
6864
7232
  code: ERROR_CODES.FS_DELETE_FAILED,
6865
- path: path39.join(parentDir, filename),
7233
+ path: path4.join(parentDir, filename),
6866
7234
  context: { reason: "unknown_file_pattern" }
6867
7235
  });
6868
7236
  }
6869
- const resolvedParent = path39.resolve(parentDir);
7237
+ const resolvedParent = path4.resolve(parentDir);
6870
7238
  if (!resolvedParent.endsWith(".wrongstack")) {
6871
7239
  throw new FsError({
6872
7240
  message: `Unexpected parent directory for bak prune: ${resolvedParent}`,
@@ -6877,8 +7245,8 @@ function assertSafeToDelete(filename, parentDir) {
6877
7245
  }
6878
7246
  }
6879
7247
  async function safeDelete(filePath) {
6880
- const dir = path39.dirname(filePath);
6881
- const filename = path39.basename(filePath);
7248
+ const dir = path4.dirname(filePath);
7249
+ const filename = path4.basename(filePath);
6882
7250
  try {
6883
7251
  assertSafeToDelete(filename, dir);
6884
7252
  await fsp5.unlink(filePath);
@@ -6923,16 +7291,16 @@ function diffSummary(oldCfg, newCfg) {
6923
7291
  }
6924
7292
  var defaultHomeDir = () => os__default.homedir();
6925
7293
  function historyDir(homeFn = defaultHomeDir) {
6926
- return path39.join(homeFn(), ".wrongstack", "config.history", "entries");
7294
+ return path4.join(homeFn(), ".wrongstack", "config.history", "entries");
6927
7295
  }
6928
7296
  function historyIndexPath(homeFn = defaultHomeDir) {
6929
- return path39.join(homeFn(), ".wrongstack", "config.history", "index.json");
7297
+ return path4.join(homeFn(), ".wrongstack", "config.history", "index.json");
6930
7298
  }
6931
7299
  function configPath(homeFn = defaultHomeDir) {
6932
- return path39.join(homeFn(), ".wrongstack", "config.json");
7300
+ return path4.join(homeFn(), ".wrongstack", "config.json");
6933
7301
  }
6934
7302
  function backupLastPath(homeFn = defaultHomeDir) {
6935
- return path39.join(homeFn(), ".wrongstack", "config.json.last");
7303
+ return path4.join(homeFn(), ".wrongstack", "config.json.last");
6936
7304
  }
6937
7305
  function entryId(ts) {
6938
7306
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -6990,7 +7358,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6990
7358
  }
6991
7359
  if (content !== void 0) {
6992
7360
  try {
6993
- const bakPath = path39.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
7361
+ const bakPath = path4.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
6994
7362
  await atomicWrite(bakPath, content);
6995
7363
  } catch (err) {
6996
7364
  writeErr(
@@ -6999,11 +7367,11 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6999
7367
  }
7000
7368
  }
7001
7369
  try {
7002
- const dir = path39.join(homeFn(), ".wrongstack");
7370
+ const dir = path4.join(homeFn(), ".wrongstack");
7003
7371
  const files = await fsp5.readdir(dir);
7004
7372
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
7005
7373
  for (const f of baks.slice(10)) {
7006
- await safeDelete(path39.join(dir, f));
7374
+ await safeDelete(path4.join(dir, f));
7007
7375
  }
7008
7376
  } catch (err) {
7009
7377
  writeErr(
@@ -7024,7 +7392,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
7024
7392
  };
7025
7393
  try {
7026
7394
  await fsp5.writeFile(
7027
- path39.join(historyDir(homeFn), `${id}.json`),
7395
+ path4.join(historyDir(homeFn), `${id}.json`),
7028
7396
  JSON.stringify(entry, null, 2),
7029
7397
  "utf8"
7030
7398
  );
@@ -7032,7 +7400,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
7032
7400
  throw new FsError({
7033
7401
  message: toErrorMessage(err),
7034
7402
  code: ERROR_CODES.FS_WRITE_FAILED,
7035
- path: path39.join(historyDir(homeFn), `${id}.json`),
7403
+ path: path4.join(historyDir(homeFn), `${id}.json`),
7036
7404
  cause: err
7037
7405
  });
7038
7406
  }
@@ -7047,7 +7415,7 @@ async function listHistory(homeFn = defaultHomeDir) {
7047
7415
  }
7048
7416
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
7049
7417
  try {
7050
- const raw = await fsp5.readFile(path39.join(historyDir(homeFn), `${id}.json`), "utf8");
7418
+ const raw = await fsp5.readFile(path4.join(historyDir(homeFn), `${id}.json`), "utf8");
7051
7419
  return JSON.parse(raw);
7052
7420
  } catch {
7053
7421
  return null;
@@ -7161,11 +7529,11 @@ async function buildPickableProviders(modelsRegistry, config) {
7161
7529
  var theme = { primary: color.amber };
7162
7530
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os__default.homedir()) {
7163
7531
  try {
7164
- const { atomicWrite: atomicWrite17 } = await import('@wrongstack/core');
7165
- const fs39 = await import('fs/promises');
7532
+ const { atomicWrite: atomicWrite16 } = await import('@wrongstack/core');
7533
+ const fs38 = await import('fs/promises');
7166
7534
  let existing = {};
7167
7535
  try {
7168
- const raw = await fs39.readFile(configPath2, "utf8");
7536
+ const raw = await fs38.readFile(configPath2, "utf8");
7169
7537
  existing = JSON.parse(raw);
7170
7538
  } catch {
7171
7539
  }
@@ -7184,7 +7552,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
7184
7552
  })
7185
7553
  );
7186
7554
  }
7187
- await atomicWrite17(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
7555
+ await atomicWrite16(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
7188
7556
  try {
7189
7557
  await appendHistory(
7190
7558
  oldCfg,
@@ -7506,7 +7874,8 @@ function buildAuthCommand(opts) {
7506
7874
  {
7507
7875
  encrypt: (v) => v,
7508
7876
  decrypt: (v) => v,
7509
- isEncrypted: () => false
7877
+ isEncrypted: () => false,
7878
+ keyVersion: 1
7510
7879
  }
7511
7880
  );
7512
7881
  } catch {
@@ -7847,7 +8216,7 @@ function formatPhaseList(graph) {
7847
8216
  }
7848
8217
  async function gatherProjectContext(projectRoot) {
7849
8218
  try {
7850
- const raw = await fsp5.readFile(path39.join(projectRoot, "package.json"), "utf8");
8219
+ const raw = await fsp5.readFile(path4.join(projectRoot, "package.json"), "utf8");
7851
8220
  const pkg = JSON.parse(raw);
7852
8221
  const parts = [
7853
8222
  `Project: ${String(pkg.name ?? "unknown")}`,
@@ -9273,17 +9642,17 @@ function diagnoseConfig(cfg, plugins = []) {
9273
9642
  function scanPlaintextSecrets(node, prefix, findings) {
9274
9643
  if (!isPlainObject(node)) return;
9275
9644
  for (const [key, value] of Object.entries(node)) {
9276
- const path40 = prefix ? `${prefix}.${key}` : key;
9645
+ const path39 = prefix ? `${prefix}.${key}` : key;
9277
9646
  if (typeof value === "string") {
9278
9647
  if (value.length > 0 && isSecretField$1(key) && !value.startsWith(ENC_PREFIX)) {
9279
9648
  findings.push({
9280
- path: path40,
9649
+ path: path39,
9281
9650
  problem: "looks like a plaintext secret (not vault-encrypted) \u2014 it will be encrypted on next boot",
9282
9651
  severity: "warning"
9283
9652
  });
9284
9653
  }
9285
9654
  } else if (isPlainObject(value)) {
9286
- scanPlaintextSecrets(value, path40, findings);
9655
+ scanPlaintextSecrets(value, path39, findings);
9287
9656
  }
9288
9657
  }
9289
9658
  }
@@ -9304,7 +9673,7 @@ function resolvePersistPath(deps) {
9304
9673
  return deps.globalConfigPath;
9305
9674
  }
9306
9675
  async function ensureProjectDir(filePath) {
9307
- const dir = path39.dirname(filePath);
9676
+ const dir = path4.dirname(filePath);
9308
9677
  try {
9309
9678
  await fsp5.mkdir(dir, { recursive: true });
9310
9679
  } catch {
@@ -9328,7 +9697,8 @@ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
9328
9697
  "session",
9329
9698
  "indexing",
9330
9699
  "tools",
9331
- "launch"
9700
+ "launch",
9701
+ "circuitBreaker"
9332
9702
  ]);
9333
9703
  function filterSafeForProject(cfg) {
9334
9704
  const out = {};
@@ -9508,8 +9878,8 @@ function buildDoctorCommand(opts) {
9508
9878
  return ` ${icon} ${color.cyan(f.path)} \u2014 ${f.problem}${fix}`;
9509
9879
  }
9510
9880
  async function findParsableBackup(file) {
9511
- const dir = path39.dirname(file);
9512
- const base = path39.basename(file);
9881
+ const dir = path4.dirname(file);
9882
+ const base = path4.basename(file);
9513
9883
  const candidates = [`${base}.last`];
9514
9884
  try {
9515
9885
  const siblings = await fsp5.readdir(dir);
@@ -9520,7 +9890,7 @@ function buildDoctorCommand(opts) {
9520
9890
  }
9521
9891
  for (const name of candidates) {
9522
9892
  try {
9523
- const raw = await fsp5.readFile(path39.join(dir, name), "utf8");
9893
+ const raw = await fsp5.readFile(path4.join(dir, name), "utf8");
9524
9894
  JSON.parse(raw);
9525
9895
  return { name, raw };
9526
9896
  } catch {
@@ -9637,7 +10007,7 @@ function buildDoctorCommand(opts) {
9637
10007
  await atomicWrite(target.file, JSON.stringify(report.fixed, null, 2));
9638
10008
  if (!target.isProject) {
9639
10009
  try {
9640
- const homeFn = () => path39.dirname(path39.dirname(target.file));
10010
+ const homeFn = () => path4.dirname(path4.dirname(target.file));
9641
10011
  await appendHistory(parsed, report.fixed, "config doctor auto-fix", homeFn);
9642
10012
  } catch {
9643
10013
  }
@@ -11817,8 +12187,8 @@ function buildInitCommand(opts) {
11817
12187
  description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
11818
12188
  async run(_args, ctx) {
11819
12189
  const root = ctx?.projectRoot ?? opts.projectRoot ?? process.cwd();
11820
- const dir = path39.join(root, ".wrongstack");
11821
- const file = path39.join(dir, "AGENTS.md");
12190
+ const dir = path4.join(root, ".wrongstack");
12191
+ const file = path4.join(dir, "AGENTS.md");
11822
12192
  const isFirstInit = !await fileExists(file);
11823
12193
  const detected = await detectProjectFacts(root);
11824
12194
  const body = renderAgentsTemplate(detected);
@@ -11826,7 +12196,7 @@ function buildInitCommand(opts) {
11826
12196
  await fsp5.writeFile(file, body, "utf8");
11827
12197
  let nodePkg = false;
11828
12198
  try {
11829
- await fsp5.access(path39.join(root, "package.json"));
12199
+ await fsp5.access(path4.join(root, "package.json"));
11830
12200
  nodePkg = true;
11831
12201
  } catch {
11832
12202
  }
@@ -12520,9 +12890,9 @@ function stateBadge(state) {
12520
12890
  return color.dim(state);
12521
12891
  }
12522
12892
  }
12523
- async function readConfig(path40) {
12893
+ async function readConfig(path39) {
12524
12894
  try {
12525
- return JSON.parse(await fsp5.readFile(path40, "utf8"));
12895
+ return JSON.parse(await fsp5.readFile(path39, "utf8"));
12526
12896
  } catch {
12527
12897
  return {};
12528
12898
  }
@@ -12530,11 +12900,11 @@ async function readConfig(path40) {
12530
12900
  function isMcpServerRecord(value) {
12531
12901
  return !!value && typeof value === "object" && !Array.isArray(value);
12532
12902
  }
12533
- async function writeConfig(path40, cfg) {
12903
+ async function writeConfig(path39, cfg) {
12534
12904
  const raw = JSON.stringify(cfg, null, 2);
12535
- const tmp = path40 + ".tmp";
12905
+ const tmp = path39 + ".tmp";
12536
12906
  await fsp5.writeFile(tmp, raw, "utf8");
12537
- await fsp5.rename(tmp, path40);
12907
+ await fsp5.rename(tmp, path39);
12538
12908
  }
12539
12909
 
12540
12910
  // src/slash-commands/mcp.ts
@@ -14653,6 +15023,73 @@ function formatSuggestions(suggestions) {
14653
15023
  return lines.join("\n");
14654
15024
  }
14655
15025
 
15026
+ // src/slash-commands/coordinator.ts
15027
+ function buildCoordinatorCommand(opts) {
15028
+ return {
15029
+ name: "coordinator",
15030
+ category: "Agent",
15031
+ description: "Start, stop, or inspect the AutonomousCoordinator \u2014 the fleet brain that auctions tasks and consults Brain for risky decisions.",
15032
+ help: [
15033
+ "Usage:",
15034
+ " /coordinator start <goal> Start the coordinator with a goal",
15035
+ " /coordinator stop Stop the running coordinator",
15036
+ " /coordinator status Show current coordinator status",
15037
+ "",
15038
+ "The AutonomousCoordinator runs alongside the agent loop and:",
15039
+ " \u2022 Maintains a shared knowledge graph of facts and decisions",
15040
+ " \u2022 Breaks work into a task DAG and auctions tasks to subagents",
15041
+ " \u2022 Consults the Brain for risky decisions",
15042
+ " \u2022 Uses ConsensusProtocol to vote on multi-agent changes",
15043
+ "",
15044
+ "It is separate from /autonomy eternal \u2014 both can run concurrently."
15045
+ ].join("\n"),
15046
+ async run(args) {
15047
+ const trimmed = args.trim();
15048
+ const [verbRaw, ...rest] = trimmed.split(/\s+/);
15049
+ const verb = (verbRaw ?? "").toLowerCase();
15050
+ if (verb === "start") {
15051
+ const goal = rest.join(" ").trim();
15052
+ if (!goal) {
15053
+ return { message: "Usage: /coordinator start <goal>\nA goal is required to start the coordinator." };
15054
+ }
15055
+ opts.onCoordinatorStart?.(goal);
15056
+ return {
15057
+ message: `AutonomousCoordinator started with goal: "${goal}"
15058
+ Use /coordinator status to monitor progress.`
15059
+ };
15060
+ }
15061
+ if (verb === "stop") {
15062
+ opts.onCoordinatorStop?.();
15063
+ return { message: "AutonomousCoordinator stop signal sent." };
15064
+ }
15065
+ if (verb === "status") {
15066
+ const canStart = opts.onCoordinatorStart != null;
15067
+ const canStop = opts.onCoordinatorStop != null;
15068
+ return {
15069
+ message: [
15070
+ `Coordinator wired: start=${canStart ? "yes" : "no"}, stop=${canStop ? "yes" : "no"}`,
15071
+ "Use F9 in the TUI for the full coordinator panel."
15072
+ ].join("\n")
15073
+ };
15074
+ }
15075
+ return {
15076
+ message: [
15077
+ "Usage:",
15078
+ " /coordinator start <goal> Start with a goal",
15079
+ " /coordinator stop Stop the coordinator",
15080
+ " /coordinator status Show status",
15081
+ "",
15082
+ "The coordinator is a fleet brain that:",
15083
+ " \u2022 Auctions tasks to subagents via TaskAuctioneer",
15084
+ " \u2022 Maintains a shared KnowledgeGraph",
15085
+ " \u2022 Consults Brain for risky decisions",
15086
+ " \u2022 Uses ConsensusProtocol for multi-agent votes"
15087
+ ].join("\n")
15088
+ };
15089
+ }
15090
+ };
15091
+ }
15092
+
14656
15093
  // src/slash-commands/mouse.ts
14657
15094
  function buildMouseCommand(_opts) {
14658
15095
  return {
@@ -14833,7 +15270,7 @@ async function listProjectsCommand(opts, ctx) {
14833
15270
  return { message: lines.join("\n") };
14834
15271
  }
14835
15272
  async function addProjectCommand(opts, ctx, targetPath, displayName) {
14836
- const resolved = path39.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), targetPath);
15273
+ const resolved = path4.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), targetPath);
14837
15274
  try {
14838
15275
  await fsp5.access(resolved);
14839
15276
  } catch {
@@ -14850,7 +15287,7 @@ async function addProjectCommand(opts, ctx, targetPath, displayName) {
14850
15287
  message: color.yellow(`Project already registered: "${existing.name}" (${existing.slug})`)
14851
15288
  };
14852
15289
  }
14853
- const name = displayName?.trim() || path39.basename(resolved);
15290
+ const name = displayName?.trim() || path4.basename(resolved);
14854
15291
  const slug = generateSlug(resolved);
14855
15292
  const now = (/* @__PURE__ */ new Date()).toISOString();
14856
15293
  await ensureProjectDataDir(slug, opts.paths?.globalConfig);
@@ -14903,7 +15340,7 @@ async function removeProjectCommand(opts, _ctx, slugOrName) {
14903
15340
  };
14904
15341
  }
14905
15342
  async function switchProjectCommand(opts, ctx, target, displayName) {
14906
- const resolved = path39.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), target);
15343
+ const resolved = path4.resolve(ctx?.projectRoot ?? ctx?.cwd ?? process.cwd(), target);
14907
15344
  try {
14908
15345
  await fsp5.access(resolved);
14909
15346
  } catch {
@@ -14917,8 +15354,8 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
14917
15354
  try {
14918
15355
  const req2 = createRequire(import.meta.url);
14919
15356
  const pkgPath = req2.resolve("@wrongstack/cli/package.json");
14920
- const pkgDir = path39.dirname(pkgPath);
14921
- cliPath = path39.join(pkgDir, "dist", "index.js");
15357
+ const pkgDir = path4.dirname(pkgPath);
15358
+ cliPath = path4.join(pkgDir, "dist", "index.js");
14922
15359
  await fsp5.access(cliPath);
14923
15360
  } catch {
14924
15361
  cliPath = process.argv[1] ?? "";
@@ -14935,13 +15372,13 @@ async function switchProjectCommand(opts, ctx, target, displayName) {
14935
15372
  if (existing) {
14936
15373
  existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
14937
15374
  } else {
14938
- const name = displayName?.trim() || path39.basename(resolved);
15375
+ const name = displayName?.trim() || path4.basename(resolved);
14939
15376
  const slug = generateSlug(resolved);
14940
15377
  manifest.projects.push({ name, root: resolved, slug, lastSeen: (/* @__PURE__ */ new Date()).toISOString() });
14941
15378
  await ensureProjectDataDir(slug, opts.paths?.globalConfig);
14942
15379
  }
14943
15380
  await saveManifest(manifest, opts.paths?.globalConfig);
14944
- const targetName = displayName?.trim() || path39.basename(resolved);
15381
+ const targetName = displayName?.trim() || path4.basename(resolved);
14945
15382
  const canSwitch = await confirmProjectSwitch(opts, targetName);
14946
15383
  if (!canSwitch) return { message: "" };
14947
15384
  const nodeExe = process.execPath;
@@ -15057,8 +15494,8 @@ async function spawnInProject(opts, _ctx, root, projectName) {
15057
15494
  try {
15058
15495
  const req2 = createRequire(import.meta.url);
15059
15496
  const pkgPath = req2.resolve("@wrongstack/cli/package.json");
15060
- const pkgDir = path39.dirname(pkgPath);
15061
- cliPath = path39.join(pkgDir, "dist", "index.js");
15497
+ const pkgDir = path4.dirname(pkgPath);
15498
+ cliPath = path4.join(pkgDir, "dist", "index.js");
15062
15499
  await fsp5.access(cliPath);
15063
15500
  } catch {
15064
15501
  cliPath = process.argv[1] ?? "";
@@ -15075,7 +15512,7 @@ async function spawnInProject(opts, _ctx, root, projectName) {
15075
15512
  if (existing) {
15076
15513
  existing.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
15077
15514
  } else {
15078
- const name = projectName || path39.basename(root);
15515
+ const name = projectName || path4.basename(root);
15079
15516
  const slug = generateSlug(root);
15080
15517
  manifest.projects.push({ name, root, slug, lastSeen: (/* @__PURE__ */ new Date()).toISOString() });
15081
15518
  await ensureProjectDataDir(slug, opts.paths?.globalConfig);
@@ -15107,8 +15544,8 @@ async function handleNewSession(_opts, _ctx) {
15107
15544
  try {
15108
15545
  const req2 = createRequire(import.meta.url);
15109
15546
  const pkgPath = req2.resolve("@wrongstack/cli/package.json");
15110
- const pkgDir = path39.dirname(pkgPath);
15111
- cliPath = path39.join(pkgDir, "dist", "index.js");
15547
+ const pkgDir = path4.dirname(pkgPath);
15548
+ cliPath = path4.join(pkgDir, "dist", "index.js");
15112
15549
  await fsp5.access(cliPath);
15113
15550
  } catch {
15114
15551
  cliPath = process.argv[1] ?? "";
@@ -15219,7 +15656,7 @@ function buildReviewCommand(opts) {
15219
15656
  for (const f of allChanged) {
15220
15657
  if (f.path.startsWith(".wrongstack/")) continue;
15221
15658
  try {
15222
- await fsp5.access(path39.join(cwd, f.path));
15659
+ await fsp5.access(path4.join(cwd, f.path));
15223
15660
  existing.push(f);
15224
15661
  } catch {
15225
15662
  }
@@ -15230,7 +15667,7 @@ function buildReviewCommand(opts) {
15230
15667
  const filesWithContent = [];
15231
15668
  for (const f of existing.slice(0, 30)) {
15232
15669
  try {
15233
- const content = await fsp5.readFile(path39.join(cwd, f.path), "utf8");
15670
+ const content = await fsp5.readFile(path4.join(cwd, f.path), "utf8");
15234
15671
  filesWithContent.push({ ...f, content });
15235
15672
  } catch {
15236
15673
  }
@@ -15264,10 +15701,13 @@ function buildSettingsCommand(opts) {
15264
15701
  " /settings hints on|off Show or suppress rotating launch hints",
15265
15702
  " /settings debug-stream on|off Raw SSE hex-dump to stderr for debugging",
15266
15703
  " /settings config-scope global|project Save settings globally or per-project",
15704
+ " /settings fs-access unrestricted|project File-tool access scope (project = confine to project root)",
15267
15705
  " /settings refine on|off Enable/disable prompt refinement",
15268
15706
  " /settings refine-delay <seconds> Countdown duration for refine preview",
15269
15707
  " /settings refine-language original|english Default language for refinement",
15270
15708
  " /settings semver-part patch|minor|major|auto Default part for /semver and the semver_bump tool",
15709
+ " /settings breaker on|off Enable/disable the process circuit breaker (gates bash/exec)",
15710
+ " /settings breaker-timeout <seconds> Auto kill/reset delay when the breaker trips (0 = manual)",
15271
15711
  " /settings defaults Show built-in default values",
15272
15712
  "",
15273
15713
  "Settings are persisted to ~/.wrongstack/config.json."
@@ -15279,10 +15719,14 @@ function buildSettingsCommand(opts) {
15279
15719
  const hints = opts.configStore.get().hints !== false;
15280
15720
  const debugStream = opts.configStore.get().debugStream === true;
15281
15721
  const configScope = opts.configStore.get().configScope ?? "global";
15722
+ const fsAccess = opts.configStore.get().tools?.restrictToProjectRoot === true ? "project" : "unrestricted";
15282
15723
  const enhanceEnabled = autonomy?.enhance ?? true;
15283
15724
  const enhanceDelay = autonomy?.enhanceDelayMs ?? 6e4;
15284
15725
  const enhanceLanguage = autonomy?.enhanceLanguage ?? "original";
15285
15726
  const semverPart = opts.configStore.get().extensions?.["semver-bump"]?.["defaultPart"] ?? "patch";
15727
+ const cb = opts.configStore.get().circuitBreaker;
15728
+ const breakerEnabled = cb?.enabled === true;
15729
+ const breakerTimeout = cb?.autoKillResetMs ?? 6e4;
15286
15730
  return [
15287
15731
  `${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
15288
15732
  "",
@@ -15291,10 +15735,12 @@ function buildSettingsCommand(opts) {
15291
15735
  ` launch hints: ${hints ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hints on|off")}`,
15292
15736
  ` debug stream: ${debugStream ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings debug-stream on|off")}`,
15293
15737
  ` config scope: ${color.cyan(configScope)} ${color.dim("change: /settings config-scope global|project")}`,
15738
+ ` filesystem access: ${color.cyan(fsAccess)} ${color.dim("change: /settings fs-access unrestricted|project")}`,
15294
15739
  ` refine: ${enhanceEnabled ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings refine on|off")}`,
15295
15740
  ` refine-delay: ${color.cyan(formatDelay(enhanceDelay))} ${color.dim("change: /settings refine-delay <seconds>")}`,
15296
15741
  ` refine-language: ${color.cyan(enhanceLanguage)} ${color.dim("change: /settings refine-language original|english")}`,
15297
15742
  ` semver default part: ${color.cyan(semverPart)} ${color.dim("change: /settings semver-part patch|minor|major|auto")}`,
15743
+ ` circuit breaker: ${breakerEnabled ? color.cyan("on") : color.dim("off")} (kill/reset ${breakerTimeout > 0 ? formatDelay(breakerTimeout) : color.dim("manual")}) ${color.dim("change: /settings breaker on|off")}`,
15298
15744
  "",
15299
15745
  color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
15300
15746
  ].join("\n");
@@ -15407,6 +15853,25 @@ function buildSettingsCommand(opts) {
15407
15853
  const label = raw === "project" ? `${color.cyan("project")} \u2014 settings saved to <project>/.wrongstack/config.json` : `${color.cyan("global")} \u2014 settings saved to ~/.wrongstack/config.json`;
15408
15854
  return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
15409
15855
  }
15856
+ if (sub === "fs-access") {
15857
+ const raw = (rest[0] ?? "").toLowerCase();
15858
+ if (!["unrestricted", "project"].includes(raw)) {
15859
+ return { message: `${color.amber("Usage:")} /settings fs-access unrestricted|project` };
15860
+ }
15861
+ const restrict = raw === "project";
15862
+ await persistConfigSetting(persistDeps, (cfg) => {
15863
+ const tools = cfg.tools ?? {};
15864
+ tools.restrictToProjectRoot = restrict;
15865
+ cfg.tools = tools;
15866
+ const features = cfg.features ?? {};
15867
+ features.allowOutsideProjectRoot = !restrict;
15868
+ cfg.features = features;
15869
+ });
15870
+ const label = restrict ? `${color.cyan("project")} \u2014 file tools confined to the project root` : `${color.cyan("unrestricted")} \u2014 file tools may access paths outside the project root`;
15871
+ return {
15872
+ message: `${color.green("\u2713")} filesystem access \u2192 ${label} ${color.dim("(restart or re-open the session to apply)")}`
15873
+ };
15874
+ }
15410
15875
  if (sub === "refine") {
15411
15876
  const raw = (rest[0] ?? "").toLowerCase();
15412
15877
  if (!["on", "off"].includes(raw)) {
@@ -15467,8 +15932,46 @@ function buildSettingsCommand(opts) {
15467
15932
  message: `${color.green("\u2713")} semver default part \u2192 ${color.bold(raw)} ${color.dim("saved to global config; used when /semver or semver_bump gets no explicit part")}`
15468
15933
  };
15469
15934
  }
15935
+ if (sub === "breaker") {
15936
+ const raw = (rest[0] ?? "").toLowerCase();
15937
+ if (!["on", "off"].includes(raw)) {
15938
+ return { message: `${color.amber("Usage:")} /settings breaker on|off` };
15939
+ }
15940
+ const on = raw === "on";
15941
+ await persistConfigSetting(persistDeps, (cfg) => {
15942
+ const cb = cfg.circuitBreaker;
15943
+ cfg.circuitBreaker = { ...cb ?? {}, enabled: on };
15944
+ });
15945
+ getProcessRegistry().setBreakerConfig({ enabled: on });
15946
+ return {
15947
+ message: `${color.green("\u2713")} circuit breaker \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim(on ? "bash/exec gated on repeated failures; trips arm the kill/reset countdown" : "bash/exec always proceed")}`
15948
+ };
15949
+ }
15950
+ if (sub === "breaker-timeout") {
15951
+ const raw = rest[0];
15952
+ if (raw === void 0) {
15953
+ return {
15954
+ message: `${color.amber("Usage:")} /settings breaker-timeout <seconds> ${color.dim("(0 = manual recovery only)")}`
15955
+ };
15956
+ }
15957
+ const seconds = Number.parseFloat(raw);
15958
+ if (Number.isNaN(seconds) || seconds < 0) {
15959
+ return {
15960
+ message: `${color.red("Invalid number")}: "${raw}". Enter seconds, e.g. /settings breaker-timeout 60`
15961
+ };
15962
+ }
15963
+ const ms = Math.round(seconds * 1e3);
15964
+ await persistConfigSetting(persistDeps, (cfg) => {
15965
+ const cb = cfg.circuitBreaker;
15966
+ cfg.circuitBreaker = { ...cb ?? {}, autoKillResetMs: ms };
15967
+ });
15968
+ getProcessRegistry().setBreakerConfig({ autoKillResetMs: ms });
15969
+ return {
15970
+ message: `${color.green("\u2713")} breaker kill/reset timeout \u2192 ${ms > 0 ? formatDelay(ms) : color.dim("manual")} ${color.dim(ms > 0 ? "statusline shows a countdown when the breaker trips" : "breaker trips require /kill reset")}`
15971
+ };
15972
+ }
15470
15973
  return {
15471
- message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "refine", "refine-delay", "refine-language", "semver-part", "defaults"], "settings")}`
15974
+ message: `${color.red("Unknown setting")} "${sub}". ${unknownSubcommand(sub, ["delay", "mode", "hints", "debug-stream", "config-scope", "fs-access", "refine", "refine-delay", "refine-language", "semver-part", "breaker", "breaker-timeout", "defaults"], "settings")}`
15472
15975
  };
15473
15976
  } catch (err) {
15474
15977
  return {
@@ -15601,7 +16104,7 @@ var DEFAULTS = {
15601
16104
  working_dir: true
15602
16105
  };
15603
16106
  function resolveConfigPath() {
15604
- return process.env[CONFIG_ENV] ?? path39.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
16107
+ return process.env[CONFIG_ENV] ?? path4.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
15605
16108
  }
15606
16109
  async function loadStatuslineConfig() {
15607
16110
  const p = resolveConfigPath();
@@ -15615,7 +16118,7 @@ async function loadStatuslineConfig() {
15615
16118
  async function saveStatuslineConfig(cfg) {
15616
16119
  const p = resolveConfigPath();
15617
16120
  try {
15618
- await fsp5.mkdir(path39.dirname(p), { recursive: true });
16121
+ await fsp5.mkdir(path4.dirname(p), { recursive: true });
15619
16122
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
15620
16123
  } catch (err) {
15621
16124
  throw new FsError({
@@ -15780,7 +16283,7 @@ function buildTasksCommand(_opts) {
15780
16283
  return {
15781
16284
  name: "tasks",
15782
16285
  category: "Inspect",
15783
- description: "Manage structured tasks with dependencies, types, and priorities: /tasks [show|add <title>|start|done|fail|status <id> <status>|promote <id>|planify <id>|clear]",
16286
+ description: 'Manage structured tasks with dependencies, types, and priorities: /tasks [show|add <title>|start|done|fail|status <id> <status>|promote <id>|planify <id>|clear]. Tasks are session-persistent (survive resume, isolated to this session). Use `scope: "project"` to share across sessions. For simpler per-turn todos use /todos. For multi-phase roadmaps use /plan.',
15784
16287
  help: [
15785
16288
  "Usage:",
15786
16289
  " /tasks Show task progress + list",
@@ -15797,7 +16300,8 @@ function buildTasksCommand(_opts) {
15797
16300
  " /tasks clear Remove all tasks",
15798
16301
  "",
15799
16302
  "Types: feature, bugfix, refactor, docs, test, chore",
15800
- "Priorities: critical, high, medium, low"
16303
+ "Priorities: critical, high, medium, low",
16304
+ 'Scope: tasks are session-isolated by default; use scope:"project" in the task tool to share across sessions'
15801
16305
  ].join("\n"),
15802
16306
  async run(args, ctx) {
15803
16307
  if (!ctx) {
@@ -15849,9 +16353,23 @@ ${formatPlan(updated)}`;
15849
16353
  return file;
15850
16354
  }
15851
16355
  const parts = restJoined.split(/\s+/);
15852
- const title = parts[0] ?? "";
15853
- const type = validateType(parts[1] ?? "") ?? "feature";
15854
- const priority = validatePriority(parts[2] ?? "") ?? "medium";
16356
+ let type = "feature";
16357
+ let priority = "medium";
16358
+ if (parts.length > 1) {
16359
+ const p = validatePriority(parts[parts.length - 1] ?? "");
16360
+ if (p) {
16361
+ priority = p;
16362
+ parts.pop();
16363
+ if (parts.length > 1) {
16364
+ const t = validateType(parts[parts.length - 1] ?? "");
16365
+ if (t) {
16366
+ type = t;
16367
+ parts.pop();
16368
+ }
16369
+ }
16370
+ }
16371
+ }
16372
+ const title = parts.join(" ");
15855
16373
  const now = (/* @__PURE__ */ new Date()).toISOString();
15856
16374
  file.tasks.push({
15857
16375
  id: `task_${randomUUID()}`,
@@ -16016,7 +16534,7 @@ ${formatTaskProgress(file.tasks)}`;
16016
16534
  "clear"
16017
16535
  ],
16018
16536
  "tasks"
16019
- );
16537
+ ) + "\n\nRelated: /plan (session-persistent roadmap) | /todos (per-turn list)";
16020
16538
  return file;
16021
16539
  }
16022
16540
  return file;
@@ -16027,13 +16545,13 @@ ${formatTaskProgress(file.tasks)}`;
16027
16545
  }
16028
16546
  async function discoverPackageFiles(projectRoot) {
16029
16547
  const files = [];
16030
- const rootPkg = path39.join(projectRoot, "package.json");
16548
+ const rootPkg = path4.join(projectRoot, "package.json");
16031
16549
  try {
16032
16550
  await fsp5.access(rootPkg);
16033
16551
  files.push(rootPkg);
16034
16552
  } catch {
16035
16553
  }
16036
- const workspaceFile = path39.join(projectRoot, "pnpm-workspace.yaml");
16554
+ const workspaceFile = path4.join(projectRoot, "pnpm-workspace.yaml");
16037
16555
  try {
16038
16556
  await fsp5.access(workspaceFile);
16039
16557
  const content = await fsp5.readFile(workspaceFile, "utf8");
@@ -16043,12 +16561,12 @@ async function discoverPackageFiles(projectRoot) {
16043
16561
  const globs = rawGlobs.split(/[\s,]+/).filter(Boolean).map((g) => g.replace(/['"]/g, ""));
16044
16562
  for (const g of globs) {
16045
16563
  const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
16046
- const dir = path39.join(projectRoot, dirPrefix);
16564
+ const dir = path4.join(projectRoot, dirPrefix);
16047
16565
  try {
16048
16566
  const entries = await fsp5.readdir(dir, { withFileTypes: true });
16049
16567
  for (const e of entries) {
16050
16568
  if (!e.isDirectory()) continue;
16051
- const subPkg = path39.join(dir, e.name, "package.json");
16569
+ const subPkg = path4.join(dir, e.name, "package.json");
16052
16570
  try {
16053
16571
  await fsp5.access(subPkg);
16054
16572
  files.push(subPkg);
@@ -16063,7 +16581,7 @@ async function discoverPackageFiles(projectRoot) {
16063
16581
  return files;
16064
16582
  }
16065
16583
  function buildTechStackTask(opts) {
16066
- const pkgList = opts.packageFiles.map((f) => ` - ${path39.relative(opts.projectRoot, f)}`).join("\n");
16584
+ const pkgList = opts.packageFiles.map((f) => ` - ${path4.relative(opts.projectRoot, f)}`).join("\n");
16067
16585
  const header = opts.isInit ? [
16068
16586
  "## Tech Stack Audit \u2014 First-Time Project Init",
16069
16587
  "",
@@ -16548,7 +17066,7 @@ function buildTodosCommand(opts) {
16548
17066
  return {
16549
17067
  name: "todos",
16550
17068
  category: "Inspect",
16551
- description: "Inspect or edit the live todo list: /todos [show|clear|add|done|remove|rm <id|index>]",
17069
+ description: "Inspect or edit the live todo list: /todos [show|clear|add|done|remove|rm <id|index>]. For multi-phase work use /plan (session-persistent roadmap). For structured typed work with priorities and dependencies use /tasks (session-persistent).",
16552
17070
  async run(args) {
16553
17071
  const ctx = opts.context;
16554
17072
  if (!ctx) return { message: "No active context." };
@@ -16602,7 +17120,7 @@ function buildTodosCommand(opts) {
16602
17120
  }
16603
17121
  default:
16604
17122
  return {
16605
- message: unknownSubcommand(cmd, ["show", "clear", "add", "done", "remove"], "todos")
17123
+ message: unknownSubcommand(cmd, ["show", "clear", "add", "done", "remove"], "todos") + "\n\nRelated: /plan (session-persistent roadmap) | /tasks (structured tasks with priorities)"
16606
17124
  };
16607
17125
  }
16608
17126
  }
@@ -16648,7 +17166,7 @@ function buildWorkingDirCommand(_opts) {
16648
17166
  }
16649
17167
  const trimmed = args.trim();
16650
17168
  if (!trimmed) {
16651
- const rel2 = path39.relative(ctx.projectRoot, ctx.workingDir) || ".";
17169
+ const rel2 = path4.relative(ctx.projectRoot, ctx.workingDir) || ".";
16652
17170
  return {
16653
17171
  message: [
16654
17172
  `Working directory: ${color.bold(ctx.workingDir)}`,
@@ -16657,10 +17175,10 @@ function buildWorkingDirCommand(_opts) {
16657
17175
  ].join("\n")
16658
17176
  };
16659
17177
  }
16660
- const resolved = path39.isAbsolute(trimmed) ? path39.resolve(trimmed) : path39.resolve(ctx.projectRoot, trimmed);
16661
- const root = path39.resolve(ctx.projectRoot);
16662
- const rel = path39.relative(root, resolved);
16663
- if (rel.startsWith("..") || path39.isAbsolute(rel)) {
17178
+ const resolved = path4.isAbsolute(trimmed) ? path4.resolve(trimmed) : path4.resolve(ctx.projectRoot, trimmed);
17179
+ const root = path4.resolve(ctx.projectRoot);
17180
+ const rel = path4.relative(root, resolved);
17181
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
16664
17182
  return {
16665
17183
  message: color.red(
16666
17184
  `Directory "${trimmed}" is outside the project root.
@@ -16685,8 +17203,8 @@ function buildWorkingDirCommand(_opts) {
16685
17203
  message: color.red(toErrorMessage(err))
16686
17204
  };
16687
17205
  }
16688
- const prevRel = path39.relative(ctx.projectRoot, previous) || ".";
16689
- const newRel = path39.relative(ctx.projectRoot, resolved) || ".";
17206
+ const prevRel = path4.relative(ctx.projectRoot, previous) || ".";
17207
+ const newRel = path4.relative(ctx.projectRoot, resolved) || ".";
16690
17208
  return {
16691
17209
  message: [
16692
17210
  color.green(` \u2713 ${prevRel} \u2192 ${color.bold(newRel)}`),
@@ -16840,6 +17358,7 @@ function buildBuiltinSlashCommands(opts) {
16840
17358
  buildMouseCommand(),
16841
17359
  buildAutonomyCommand(opts),
16842
17360
  buildGoalCommand(opts),
17361
+ buildCoordinatorCommand(opts),
16843
17362
  buildBrainCommand(opts),
16844
17363
  buildBtwCommand(opts),
16845
17364
  buildNextCommand(opts),
@@ -16886,13 +17405,13 @@ var MANIFESTS = [
16886
17405
  ];
16887
17406
  async function detectProjectKind(projectRoot) {
16888
17407
  try {
16889
- await fsp5.access(path39.join(projectRoot, ".wrongstack", "AGENTS.md"));
17408
+ await fsp5.access(path4.join(projectRoot, ".wrongstack", "AGENTS.md"));
16890
17409
  return "initialized";
16891
17410
  } catch {
16892
17411
  }
16893
17412
  for (const m of MANIFESTS) {
16894
17413
  try {
16895
- await fsp5.access(path39.join(projectRoot, m));
17414
+ await fsp5.access(path4.join(projectRoot, m));
16896
17415
  return "project";
16897
17416
  } catch {
16898
17417
  }
@@ -16900,8 +17419,8 @@ async function detectProjectKind(projectRoot) {
16900
17419
  return "empty";
16901
17420
  }
16902
17421
  async function scaffoldAgentsMd(projectRoot) {
16903
- const dir = path39.join(projectRoot, ".wrongstack");
16904
- const file = path39.join(dir, "AGENTS.md");
17422
+ const dir = path4.join(projectRoot, ".wrongstack");
17423
+ const file = path4.join(dir, "AGENTS.md");
16905
17424
  const facts = await detectProjectFacts(projectRoot);
16906
17425
  const body = renderAgentsTemplate(facts);
16907
17426
  await fsp5.mkdir(dir, { recursive: true });
@@ -16914,7 +17433,7 @@ async function runProjectCheck(opts) {
16914
17433
  if (kind === "initialized") {
16915
17434
  renderer.write(
16916
17435
  `
16917
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path39.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
17436
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path4.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
16918
17437
  `
16919
17438
  );
16920
17439
  return true;
@@ -16945,7 +17464,7 @@ async function runProjectCheck(opts) {
16945
17464
  }
16946
17465
  return true;
16947
17466
  }
16948
- const gitDir = path39.join(projectRoot, ".git");
17467
+ const gitDir = path4.join(projectRoot, ".git");
16949
17468
  let hasGit = false;
16950
17469
  try {
16951
17470
  await fsp5.access(gitDir);
@@ -17180,11 +17699,11 @@ async function countProjectFiles(projectRoot, threshold) {
17180
17699
  for (const e of entries) {
17181
17700
  if (SKIP_DIRS.has(e.name)) continue;
17182
17701
  if (count >= threshold) return;
17183
- const full = path39.join(dir, e.name);
17702
+ const full = path4.join(dir, e.name);
17184
17703
  if (e.isDirectory()) {
17185
17704
  await walk(full);
17186
17705
  } else if (e.isFile()) {
17187
- if (INDEXABLE_EXTS.has(path39.extname(e.name))) {
17706
+ if (INDEXABLE_EXTS.has(path4.extname(e.name))) {
17188
17707
  count++;
17189
17708
  }
17190
17709
  }
@@ -17525,14 +18044,14 @@ function summarize(value, name) {
17525
18044
  if (typeof v === "object" && v !== null) {
17526
18045
  const o = v;
17527
18046
  if (name === "edit") {
17528
- const path40 = typeof o["path"] === "string" ? o["path"] : "";
18047
+ const path39 = typeof o["path"] === "string" ? o["path"] : "";
17529
18048
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
17530
- return `${path40} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
18049
+ return `${path39} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
17531
18050
  }
17532
18051
  if (name === "write") {
17533
- const path40 = typeof o["path"] === "string" ? o["path"] : "";
18052
+ const path39 = typeof o["path"] === "string" ? o["path"] : "";
17534
18053
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
17535
- return bytes !== void 0 ? `${path40} ${bytes}B` : path40;
18054
+ return bytes !== void 0 ? `${path39} ${bytes}B` : path39;
17536
18055
  }
17537
18056
  if (typeof o["count"] === "number") {
17538
18057
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -17869,14 +18388,14 @@ var auditCmd = async (args, deps) => {
17869
18388
  return verify.ok ? 0 : 1;
17870
18389
  };
17871
18390
  async function listAudits(log, dir, deps) {
17872
- const fs39 = await import('fs/promises');
17873
- const path40 = await import('path');
18391
+ const fs38 = await import('fs/promises');
18392
+ const path39 = await import('path');
17874
18393
  const out = [];
17875
18394
  let foundRoot = true;
17876
18395
  const scan = async (scanDir, prefix, depth) => {
17877
18396
  let entries;
17878
18397
  try {
17879
- entries = await fs39.readdir(scanDir, { withFileTypes: true });
18398
+ entries = await fs38.readdir(scanDir, { withFileTypes: true });
17880
18399
  } catch {
17881
18400
  if (depth === 0) foundRoot = false;
17882
18401
  return;
@@ -17884,7 +18403,7 @@ async function listAudits(log, dir, deps) {
17884
18403
  for (const entry of entries) {
17885
18404
  if (entry.name.startsWith(".")) continue;
17886
18405
  if (entry.isDirectory()) {
17887
- if (depth === 0) await scan(path40.join(scanDir, entry.name), entry.name, depth + 1);
18406
+ if (depth === 0) await scan(path39.join(scanDir, entry.name), entry.name, depth + 1);
17888
18407
  continue;
17889
18408
  }
17890
18409
  if (!entry.isFile() || !entry.name.endsWith(".audit.jsonl")) continue;
@@ -18878,7 +19397,7 @@ async function resolveWstackEntry() {
18878
19397
  try {
18879
19398
  const req2 = createRequire(import.meta.url);
18880
19399
  const pkgPath = req2.resolve("@wrongstack/cli/package.json");
18881
- const entry = path39.join(path39.dirname(pkgPath), "dist", "index.js");
19400
+ const entry = path4.join(path4.dirname(pkgPath), "dist", "index.js");
18882
19401
  await fsp5.access(entry);
18883
19402
  return entry;
18884
19403
  } catch {
@@ -18893,7 +19412,7 @@ async function benchRun(_args, deps) {
18893
19412
  const outBase = flagStr(deps, "out") ?? "bench-results";
18894
19413
  let config;
18895
19414
  try {
18896
- config = await loadBenchConfig(path39.resolve(deps.cwd, modelsPath));
19415
+ config = await loadBenchConfig(path4.resolve(deps.cwd, modelsPath));
18897
19416
  } catch (err) {
18898
19417
  deps.renderer.writeError(toErrorMessage(err));
18899
19418
  return 1;
@@ -18904,8 +19423,8 @@ async function benchRun(_args, deps) {
18904
19423
  if (c > 0) config.concurrency = c;
18905
19424
  }
18906
19425
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18907
- const outDir = path39.resolve(deps.cwd, outBase, stamp);
18908
- const predictionsDir = path39.join(outDir, "predictions");
19426
+ const outDir = path4.resolve(deps.cwd, outBase, stamp);
19427
+ const predictionsDir = path4.join(outDir, "predictions");
18909
19428
  let suite;
18910
19429
  let grade;
18911
19430
  let isSwebench = false;
@@ -18917,14 +19436,14 @@ async function benchRun(_args, deps) {
18917
19436
  }
18918
19437
  const languagesRaw = flagStr(deps, "languages");
18919
19438
  const languages = languagesRaw ? languagesRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
18920
- suite = createPolyglotSuite({ polyglotDir: path39.resolve(deps.cwd, polyglotDir), languages });
19439
+ suite = createPolyglotSuite({ polyglotDir: path4.resolve(deps.cwd, polyglotDir), languages });
18921
19440
  grade = (a) => gradePolyglot(a);
18922
19441
  } else if (suiteId === "swebench") {
18923
19442
  isSwebench = true;
18924
19443
  const datasetDir = flagStr(deps, "dataset-dir");
18925
19444
  const docker = flagBool(deps, "docker");
18926
19445
  suite = createSwebenchSuite({
18927
- datasetDir: datasetDir ? path39.resolve(deps.cwd, datasetDir) : void 0,
19446
+ datasetDir: datasetDir ? path4.resolve(deps.cwd, datasetDir) : void 0,
18928
19447
  docker
18929
19448
  });
18930
19449
  grade = (a) => gradeSwebench({ ...a, predictionsDir });
@@ -18954,7 +19473,7 @@ async function benchRun(_args, deps) {
18954
19473
  }
18955
19474
  await writeJsonArtifacts(outDir, report);
18956
19475
  const md = renderMarkdownReport(report);
18957
- await fsp5.writeFile(path39.join(outDir, "report.md"), md, "utf8");
19476
+ await fsp5.writeFile(path4.join(outDir, "report.md"), md, "utf8");
18958
19477
  deps.renderer.write("\n" + md + "\n");
18959
19478
  if (isSwebench) {
18960
19479
  for (const cell of config.cells) {
@@ -18967,7 +19486,7 @@ async function benchRun(_args, deps) {
18967
19486
  "Grade with the official SWE-bench harness: python -m swebench.harness.run_evaluation --predictions_path <file> --run_id <id>"
18968
19487
  );
18969
19488
  }
18970
- deps.renderer.writeInfo(`Report written to ${path39.join(outDir, "report.md")}`);
19489
+ deps.renderer.writeInfo(`Report written to ${path4.join(outDir, "report.md")}`);
18971
19490
  return 0;
18972
19491
  }
18973
19492
  async function benchReport(args, deps) {
@@ -18976,7 +19495,7 @@ async function benchReport(args, deps) {
18976
19495
  deps.renderer.writeError("Usage: wstack bench report <run-directory>");
18977
19496
  return 1;
18978
19497
  }
18979
- const outDir = path39.resolve(deps.cwd, dir);
19498
+ const outDir = path4.resolve(deps.cwd, dir);
18980
19499
  let summary;
18981
19500
  try {
18982
19501
  summary = await readSummary(outDir);
@@ -18987,7 +19506,7 @@ async function benchReport(args, deps) {
18987
19506
  return 1;
18988
19507
  }
18989
19508
  const md = renderMarkdownReport(summary);
18990
- await fsp5.writeFile(path39.join(outDir, "report.md"), md, "utf8");
19509
+ await fsp5.writeFile(path4.join(outDir, "report.md"), md, "utf8");
18991
19510
  deps.renderer.write("\n" + md + "\n");
18992
19511
  return 0;
18993
19512
  }
@@ -19002,7 +19521,7 @@ async function benchList(_args, deps) {
19002
19521
  const modelsPath = flagStr(deps, "models");
19003
19522
  if (modelsPath) {
19004
19523
  try {
19005
- const config = await loadBenchConfig(path39.resolve(deps.cwd, modelsPath));
19524
+ const config = await loadBenchConfig(path4.resolve(deps.cwd, modelsPath));
19006
19525
  deps.renderer.write("\n" + color.bold("Model cells\n"));
19007
19526
  for (const cell of config.cells) {
19008
19527
  deps.renderer.write(
@@ -19057,14 +19576,14 @@ var doctorCmd = async (_args, deps) => {
19057
19576
  checks.push({
19058
19577
  name: "provider",
19059
19578
  status: "fail",
19060
- detail: "no provider configured \u2014 run `wstack init` or `wstack auth`"
19579
+ detail: "no provider configured \u2014 run `wstack auth` to set up"
19061
19580
  });
19062
19581
  else checks.push({ name: "provider", status: "ok", detail: cfg.provider });
19063
19582
  if (!cfg.model)
19064
19583
  checks.push({
19065
19584
  name: "model",
19066
19585
  status: "fail",
19067
- detail: "no model configured \u2014 run `wstack init`"
19586
+ detail: "no model configured \u2014 run `wstack auth` to configure"
19068
19587
  });
19069
19588
  else checks.push({ name: "model", status: "ok", detail: cfg.model });
19070
19589
  if (cfg.provider) {
@@ -19119,7 +19638,7 @@ var doctorCmd = async (_args, deps) => {
19119
19638
  }
19120
19639
  try {
19121
19640
  await fsp5.mkdir(deps.paths.projectSessions, { recursive: true });
19122
- const probe = path39.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
19641
+ const probe = path4.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
19123
19642
  await fsp5.writeFile(probe, "");
19124
19643
  await fsp5.unlink(probe);
19125
19644
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -19222,8 +19741,8 @@ var exportCmd = async (args, deps) => {
19222
19741
  return 1;
19223
19742
  }
19224
19743
  if (output) {
19225
- await fsp5.mkdir(path39.dirname(path39.resolve(deps.cwd, output)), { recursive: true });
19226
- await fsp5.writeFile(path39.resolve(deps.cwd, output), rendered, "utf8");
19744
+ await fsp5.mkdir(path4.dirname(path4.resolve(deps.cwd, output)), { recursive: true });
19745
+ await fsp5.writeFile(path4.resolve(deps.cwd, output), rendered, "utf8");
19227
19746
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
19228
19747
  `);
19229
19748
  } else {
@@ -19232,80 +19751,23 @@ var exportCmd = async (args, deps) => {
19232
19751
  }
19233
19752
  return 0;
19234
19753
  };
19235
-
19236
- // src/subcommands/handlers/init.ts
19237
- init_helpers();
19238
19754
  var initCmd = async (_args, deps) => {
19239
- deps.renderer.write(color.bold("WrongStack init\n"));
19240
- deps.renderer.writeInfo("Loading provider catalog from models.dev (cached locally)\u2026");
19241
- let providers;
19242
- try {
19243
- providers = await deps.modelsRegistry.listProviders();
19244
- } catch (err) {
19245
- deps.renderer.writeError(
19246
- `Failed to load provider catalog: ${err instanceof Error ? err.message : err}`
19247
- );
19248
- return 1;
19249
- }
19250
- const detected = providers.filter((p) => p.family !== "unsupported").filter((p) => p.envVars.some((v) => process.env[v]));
19251
- const ranked = detected.length > 0 ? detected : providers.filter((p) => ["anthropic", "openai", "google"].includes(p.id));
19252
- if (detected.length > 0)
19253
- deps.renderer.write(
19254
- `Detected API keys for: ${detected.map((p) => p.name).join(", ")}
19755
+ deps.renderer.write(color.bold("WrongStack init (deprecated)\n"));
19756
+ deps.renderer.write(
19757
+ `
19758
+ ${color.amber("\u26A0 This command is deprecated.")}
19759
+
19760
+ Use ${color.bold("wstack auth")} to set up providers, add API keys,
19761
+ and configure your default model in one interactive workflow.
19762
+
19763
+ ${color.dim("Examples:")}
19764
+ ${color.cyan("wstack auth")} Interactive setup menu
19765
+ ${color.cyan("wstack auth anthropic")} Add Anthropic API key directly
19766
+ ${color.cyan("wstack auth local")} Add local LLM (Ollama, vLLM, LM Studio)
19767
+
19768
+ Run ${color.bold("wstack auth --help")} for more options.
19255
19769
  `
19256
- );
19257
- const defaultId = ranked[0]?.id ?? "anthropic";
19258
- const providerAnswer = (await deps.reader.readLine(`Provider [${defaultId}]: `)).trim();
19259
- if (providerAnswer === "q") {
19260
- deps.renderer.write(color.dim("Cancelled.\n"));
19261
- return 0;
19262
- }
19263
- const providerId = providerAnswer || defaultId;
19264
- const provider = await deps.modelsRegistry.getProvider(providerId);
19265
- if (!provider) {
19266
- deps.renderer.writeError(`Provider "${providerId}" not found in models.dev catalog.`);
19267
- return 1;
19268
- }
19269
- if (provider.family === "unsupported") {
19270
- deps.renderer.writeError(
19271
- `Provider "${providerId}" uses ${provider.npm} which has no built-in transport. Install a plugin to enable it.`
19272
- );
19273
- return 1;
19274
- }
19275
- const suggestedModel = await deps.modelsRegistry.suggestModel(providerId) ?? "";
19276
- const modelHint = suggestedModel ? ` [${suggestedModel}]` : "";
19277
- const modelAnswer = (await deps.reader.readLine(`Model${modelHint}: `)).trim();
19278
- if (modelAnswer === "q") {
19279
- deps.renderer.write(color.dim("Cancelled.\n"));
19280
- return 0;
19281
- }
19282
- const modelId = modelAnswer || suggestedModel;
19283
- if (!modelId) {
19284
- deps.renderer.writeError("No model selected. Aborting.");
19285
- return 1;
19286
- }
19287
- const envHit = provider.envVars.map((v) => process.env[v]).find(Boolean);
19288
- let apiKey = "";
19289
- if (!envHit) {
19290
- apiKey = (await deps.reader.readLine(
19291
- `API key (stored in ${deps.paths.globalConfig}; empty = expect ${provider.envVars[0] ?? "env var"}): `
19292
- )).trim();
19293
- } else {
19294
- deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
19295
- }
19296
- await fsp5.mkdir(deps.paths.globalRoot, { recursive: true });
19297
- const config = { version: 1, provider: providerId, model: modelId };
19298
- if (apiKey) config.apiKey = apiKey;
19299
- const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
19300
- const encrypted = encryptConfigSecrets$1(config, vault);
19301
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
19302
- await fsp5.mkdir(path39.join(deps.projectRoot, ".wrongstack"), { recursive: true });
19303
- const agentsFile = path39.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
19304
- const projectFacts = await detectProjectFacts(deps.projectRoot);
19305
- await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
19306
- deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
19307
- deps.renderer.writeInfo(`Project state lives in ${deps.paths.projectDir}`);
19308
- deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
19770
+ );
19309
19771
  return 0;
19310
19772
  };
19311
19773
  var AllowAllPermissionPolicy = class extends AutoApprovePermissionPolicy {
@@ -19321,7 +19783,7 @@ function parseToolsFlag(flags) {
19321
19783
  );
19322
19784
  return set.size > 0 ? set : null;
19323
19785
  }
19324
- function makeServeContext(cwd, projectRoot, signal) {
19786
+ function makeServeContext(cwd, projectRoot, signal, restrictFsToRoot = true) {
19325
19787
  const provider = {
19326
19788
  id: "mcp-serve",
19327
19789
  capabilities: { maxContext: 0 },
@@ -19348,6 +19810,7 @@ function makeServeContext(cwd, projectRoot, signal) {
19348
19810
  tokenCounter,
19349
19811
  cwd,
19350
19812
  projectRoot,
19813
+ allowOutsideProjectRoot: !restrictFsToRoot,
19351
19814
  model: "mcp-serve",
19352
19815
  tools: []
19353
19816
  });
@@ -19373,7 +19836,12 @@ async function serveMcpStdio(deps) {
19373
19836
  registry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
19374
19837
  }
19375
19838
  const controller = new AbortController();
19376
- const ctx = makeServeContext(deps.cwd, deps.projectRoot, controller.signal);
19839
+ const ctx = makeServeContext(
19840
+ deps.cwd,
19841
+ deps.projectRoot,
19842
+ controller.signal,
19843
+ deps.config.tools?.restrictToProjectRoot ?? false
19844
+ );
19377
19845
  const permissionPolicy = yolo ? new AllowAllPermissionPolicy() : new AutoApprovePermissionPolicy();
19378
19846
  const executor = new ToolExecutor(registry, {
19379
19847
  permissionPolicy,
@@ -20663,7 +21131,7 @@ var usageCmd = async (_args, deps) => {
20663
21131
  return 0;
20664
21132
  };
20665
21133
  var projectsCmd = async (_args, deps) => {
20666
- const projectsRoot = path39.join(deps.paths.globalRoot, "projects");
21134
+ const projectsRoot = path4.join(deps.paths.globalRoot, "projects");
20667
21135
  try {
20668
21136
  const entries = await fsp5.readdir(projectsRoot);
20669
21137
  if (entries.length === 0) {
@@ -20673,7 +21141,7 @@ var projectsCmd = async (_args, deps) => {
20673
21141
  for (const hash of entries) {
20674
21142
  try {
20675
21143
  const meta = JSON.parse(
20676
- await fsp5.readFile(path39.join(projectsRoot, hash, "meta.json"), "utf8")
21144
+ await fsp5.readFile(path4.join(projectsRoot, hash, "meta.json"), "utf8")
20677
21145
  );
20678
21146
  deps.renderer.write(
20679
21147
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -21122,7 +21590,7 @@ function findSessionId(args) {
21122
21590
  var rewindCmd = async (args, deps) => {
21123
21591
  const flags = parseRewindFlags(args);
21124
21592
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
21125
- const sessionsDir = path39.join(wpaths.globalRoot, "sessions");
21593
+ const sessionsDir = path4.join(wpaths.globalRoot, "sessions");
21126
21594
  const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
21127
21595
  let sessionId = findSessionId(args);
21128
21596
  if (!sessionId) {
@@ -21267,7 +21735,7 @@ async function listFleetRuns(deps) {
21267
21735
  }
21268
21736
  const runs = [];
21269
21737
  for (const id of entries) {
21270
- const runDir = path39.join(deps.paths.projectSessions, id);
21738
+ const runDir = path4.join(deps.paths.projectSessions, id);
21271
21739
  let stat7;
21272
21740
  try {
21273
21741
  stat7 = await fsp5.stat(runDir);
@@ -21280,17 +21748,17 @@ async function listFleetRuns(deps) {
21280
21748
  let subagentCount = 0;
21281
21749
  let subagentsDir;
21282
21750
  try {
21283
- await fsp5.access(path39.join(runDir, "fleet.json"));
21751
+ await fsp5.access(path4.join(runDir, "fleet.json"));
21284
21752
  manifest = true;
21285
21753
  } catch {
21286
21754
  }
21287
21755
  try {
21288
- await fsp5.access(path39.join(runDir, "checkpoint.json"));
21756
+ await fsp5.access(path4.join(runDir, "checkpoint.json"));
21289
21757
  checkpoint = true;
21290
21758
  } catch {
21291
21759
  }
21292
21760
  try {
21293
- subagentsDir = path39.join(runDir, "subagents");
21761
+ subagentsDir = path4.join(runDir, "subagents");
21294
21762
  const files = await fsp5.readdir(subagentsDir);
21295
21763
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
21296
21764
  } catch {
@@ -21317,7 +21785,7 @@ async function listFleetRuns(deps) {
21317
21785
  return 0;
21318
21786
  }
21319
21787
  async function showFleetRun(runId, deps) {
21320
- const runDir = path39.join(deps.paths.projectSessions, runId);
21788
+ const runDir = path4.join(deps.paths.projectSessions, runId);
21321
21789
  let stat7;
21322
21790
  try {
21323
21791
  stat7 = await fsp5.stat(runDir);
@@ -21334,7 +21802,7 @@ async function showFleetRun(runId, deps) {
21334
21802
  deps.renderer.write(color.bold(`
21335
21803
  Fleet Run: ${runId}
21336
21804
  `) + "\n");
21337
- const manifestPath = path39.join(runDir, "fleet.json");
21805
+ const manifestPath = path4.join(runDir, "fleet.json");
21338
21806
  let manifestData = null;
21339
21807
  try {
21340
21808
  manifestData = await fsp5.readFile(manifestPath, "utf8");
@@ -21352,7 +21820,7 @@ Fleet Run: ${runId}
21352
21820
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
21353
21821
  `);
21354
21822
  }
21355
- const checkpointPath = path39.join(runDir, "checkpoint.json");
21823
+ const checkpointPath = path4.join(runDir, "checkpoint.json");
21356
21824
  let checkpointData = null;
21357
21825
  try {
21358
21826
  checkpointData = await fsp5.readFile(checkpointPath, "utf8");
@@ -21399,7 +21867,7 @@ Fleet Run: ${runId}
21399
21867
  } catch {
21400
21868
  }
21401
21869
  }
21402
- const subagentsDir = path39.join(runDir, "subagents");
21870
+ const subagentsDir = path4.join(runDir, "subagents");
21403
21871
  let subagentFiles = [];
21404
21872
  try {
21405
21873
  subagentFiles = await fsp5.readdir(subagentsDir);
@@ -21411,7 +21879,7 @@ Fleet Run: ${runId}
21411
21879
  Subagent transcripts (${subagentFiles.length}):
21412
21880
  `);
21413
21881
  for (const f of subagentFiles.sort()) {
21414
- const filePath = path39.join(subagentsDir, f);
21882
+ const filePath = path4.join(subagentsDir, f);
21415
21883
  let size;
21416
21884
  try {
21417
21885
  const s = await fsp5.stat(filePath);
@@ -21428,7 +21896,7 @@ Fleet Run: ${runId}
21428
21896
  ${color.dim("\u25CB")} No subagent transcripts
21429
21897
  `);
21430
21898
  }
21431
- const sharedDir = path39.join(runDir, "shared");
21899
+ const sharedDir = path4.join(runDir, "shared");
21432
21900
  try {
21433
21901
  const files = await fsp5.readdir(sharedDir);
21434
21902
  deps.renderer.write(`
@@ -21669,8 +22137,8 @@ var helpCmd = async (_args, deps) => {
21669
22137
  ' wstack --eternal "<mission>" Launch eternal-autonomy loop against a goal \u2014 Ctrl+C to stop',
21670
22138
  " wstack resume [<id>] Resume a session",
21671
22139
  " wstack sessions List recent sessions",
21672
- " wstack init Pick provider + model from models.dev",
21673
- " wstack auth Interactive key manager (list/add/update/delete)",
22140
+ " wstack auth Interactive setup + key manager (add/edit/delete)",
22141
+ " wstack auth <provider> Add API key for a provider",
21674
22142
  " wstack auth list Quick listing of saved providers and keys",
21675
22143
  " wstack auth status <id> Detailed view of one provider",
21676
22144
  " wstack auth remove <id> Delete a provider (asks for confirmation)",
@@ -21755,7 +22223,7 @@ function resolveBundledSkillsDir() {
21755
22223
  try {
21756
22224
  const req2 = createRequire(import.meta.url);
21757
22225
  const corePkg = req2.resolve("@wrongstack/core/package.json");
21758
- return path39.join(path39.dirname(corePkg), "skills");
22226
+ return path4.join(path4.dirname(corePkg), "skills");
21759
22227
  } catch {
21760
22228
  return void 0;
21761
22229
  }
@@ -21976,7 +22444,7 @@ async function boot(argv) {
21976
22444
  const created = await registerProjectAtBoot({ projectRoot, cwd, wpaths });
21977
22445
  if (created && isInteractiveTTY) {
21978
22446
  renderer.write(
21979
- color.dim(` \u2713 Registered "${path39.basename(projectRoot)}" in projects.json.
22447
+ color.dim(` \u2713 Registered "${path4.basename(projectRoot)}" in projects.json.
21980
22448
  `)
21981
22449
  );
21982
22450
  }
@@ -22056,7 +22524,7 @@ async function boot(argv) {
22056
22524
  } catch {
22057
22525
  }
22058
22526
  printLaunchHints(renderer, flags, {
22059
- cursorFile: path39.join(wpaths.cacheDir, "hint-cursor")
22527
+ cursorFile: path4.join(wpaths.cacheDir, "hint-cursor")
22060
22528
  });
22061
22529
  } else {
22062
22530
  const effectiveChoices = config.launch ? {
@@ -22094,7 +22562,7 @@ async function boot(argv) {
22094
22562
  }
22095
22563
  async function checkGitInCwd(opts) {
22096
22564
  const { cwd, renderer, reader } = opts;
22097
- const cwdGit = path39.join(cwd, ".git");
22565
+ const cwdGit = path4.join(cwd, ".git");
22098
22566
  let hasCwdGit = false;
22099
22567
  try {
22100
22568
  await fsp5.access(cwdGit);
@@ -22134,10 +22602,10 @@ async function checkGitInCwd(opts) {
22134
22602
  }
22135
22603
  }
22136
22604
  }
22137
- const parentDir = path39.dirname(cwd);
22605
+ const parentDir = path4.dirname(cwd);
22138
22606
  if (parentDir !== cwd) {
22139
22607
  try {
22140
- await fsp5.access(path39.join(parentDir, ".git"));
22608
+ await fsp5.access(path4.join(parentDir, ".git"));
22141
22609
  renderer.write(
22142
22610
  ` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
22143
22611
  `
@@ -22149,7 +22617,7 @@ async function checkGitInCwd(opts) {
22149
22617
  async function registerProjectAtBoot(opts) {
22150
22618
  const { projectRoot, cwd, wpaths } = opts;
22151
22619
  const manifest = await loadManifest(wpaths.globalConfig);
22152
- const existed = manifest.projects.some((p) => path39.resolve(p.root) === path39.resolve(projectRoot));
22620
+ const existed = manifest.projects.some((p) => path4.resolve(p.root) === path4.resolve(projectRoot));
22153
22621
  await touchProjectInManifest({
22154
22622
  projectRoot,
22155
22623
  globalConfigPath: wpaths.globalConfig,
@@ -22157,9 +22625,26 @@ async function registerProjectAtBoot(opts) {
22157
22625
  });
22158
22626
  return !existed;
22159
22627
  }
22628
+ function toolsForTier(tier, allTools) {
22629
+ switch (tier) {
22630
+ case "off":
22631
+ return allTools;
22632
+ case "minimal":
22633
+ case "light":
22634
+ return TIER1_TOOLS;
22635
+ case "medium":
22636
+ return [...TIER1_TOOLS, ...TIER2_TOOLS];
22637
+ case "aggressive": {
22638
+ const t2WithoutTask = TIER2_TOOLS.filter((t) => t.name !== "task");
22639
+ const t3WithoutSetCwd = TIER3_TOOLS.filter((t) => t.name !== "setWorkingDir");
22640
+ return [...TIER1_TOOLS, ...t2WithoutTask, ...t3WithoutSetCwd];
22641
+ }
22642
+ }
22643
+ }
22160
22644
  function registerBuiltinTools(deps) {
22645
+ const tier = normalizeTokenSavingTier(deps.config.features.tokenSavingMode);
22161
22646
  const allTools = builtinToolsPack.tools ?? [];
22162
- const toolsToRegister = deps.config.features.tokenSavingMode ? TIER1_TOOLS : allTools;
22647
+ const toolsToRegister = toolsForTier(tier, allTools);
22163
22648
  deps.toolRegistry.registerAllOrThrow([...toolsToRegister], builtinToolsPack.name);
22164
22649
  deps.toolRegistry.registerDefault(
22165
22650
  createContextManagerTool({ compactor: deps.compactor })
@@ -22350,7 +22835,7 @@ function resolveBundledSkillsDir2() {
22350
22835
  try {
22351
22836
  const req2 = createRequire(import.meta.url);
22352
22837
  const corePkg = req2.resolve("@wrongstack/core/package.json");
22353
- return path39.join(path39.dirname(corePkg), "skills");
22838
+ return path4.join(path4.dirname(corePkg), "skills");
22354
22839
  } catch {
22355
22840
  return void 0;
22356
22841
  }
@@ -22838,7 +23323,7 @@ async function runRepl(opts) {
22838
23323
  clientMailbox.registerClient({
22839
23324
  clientId,
22840
23325
  sessionId: replProjectRoot,
22841
- name: `REPL [${path39.basename(replProjectRoot)}]`,
23326
+ name: `REPL [${path4.basename(replProjectRoot)}]`,
22842
23327
  source: "repl",
22843
23328
  pid: process.pid
22844
23329
  }).then(() => {
@@ -23655,6 +24140,7 @@ function printBanner(renderer, projectName) {
23655
24140
  }
23656
24141
 
23657
24142
  // src/execution.ts
24143
+ init_provider_config_utils();
23658
24144
  init_utils();
23659
24145
  async function execute(deps) {
23660
24146
  const {
@@ -23874,8 +24360,8 @@ async function execute(deps) {
23874
24360
  result = await agent.run(query, { signal: ctrl.signal });
23875
24361
  } finally {
23876
24362
  process.off("SIGINT", onSigint);
23877
- const { getProcessRegistry: getProcessRegistry2 } = await import('@wrongstack/tools');
23878
- getProcessRegistry2().killAll();
24363
+ const { getProcessRegistry: getProcessRegistry3 } = await import('@wrongstack/tools');
24364
+ getProcessRegistry3().killAll();
23879
24365
  }
23880
24366
  const after = tokenCounter.total();
23881
24367
  const costAfter = tokenCounter.estimateCost().total;
@@ -23934,7 +24420,7 @@ async function execute(deps) {
23934
24420
  const { runTui } = await import('@wrongstack/tui');
23935
24421
  renderer.setSilent(true);
23936
24422
  const banneredFamily = savedProviderCfg?.family ?? resolvedProvider?.family;
23937
- const banneredKey = savedProviderCfg?.apiKey ?? config.apiKey ?? (resolvedProvider?.envVars ?? savedProviderCfg?.envVars ?? []).map((v) => process.env[v]).find((v) => !!v);
24423
+ const banneredKey = (savedProviderCfg ? resolveActiveApiKey(savedProviderCfg) : void 0) ?? config.apiKey ?? (resolvedProvider?.envVars ?? savedProviderCfg?.envVars ?? []).map((v) => process.env[v]).find((v) => !!v);
23938
24424
  const banneredKeyTail = banneredKey && banneredKey.length >= 3 ? banneredKey.slice(-3) : void 0;
23939
24425
  const autoPhaseHandlers = /* @__PURE__ */ new Map();
23940
24426
  const subscribeAutoPhase = (handler) => {
@@ -23987,7 +24473,7 @@ async function execute(deps) {
23987
24473
  const onDirectorReady = (dir) => {
23988
24474
  if (autonomousCoordinator) return;
23989
24475
  const transcript = context.session.transcriptPath;
23990
- const sessionDir = transcript ? path39.dirname(transcript) : wpaths.projectDir;
24476
+ const sessionDir = transcript ? path4.dirname(transcript) : wpaths.projectDir;
23991
24477
  const llmProvider = {
23992
24478
  decide: async (prompt) => {
23993
24479
  const sysPrompt = [
@@ -24041,8 +24527,12 @@ Reply with ONLY the JSON object.`
24041
24527
  mailbox,
24042
24528
  selfAgentId: `leader@${context.session.id ?? "unknown"}`,
24043
24529
  selfAgentName: "Leader",
24044
- llmProvider
24530
+ llmProvider,
24531
+ onCoordinatorEvent: (event) => {
24532
+ for (const fn of coordinatorEvents) fn(event);
24533
+ }
24045
24534
  });
24535
+ deps.onCoordinatorStop = () => autonomousCoordinator?.stop();
24046
24536
  };
24047
24537
  if (director) onDirectorReady(director);
24048
24538
  const offDirectorSpawned = events.onPattern("subagent.spawned", () => {
@@ -24135,7 +24625,7 @@ Reply with ONLY the JSON object.`
24135
24625
  featureMemory: cfg.features?.memory !== false,
24136
24626
  featureSkills: cfg.features?.skills !== false,
24137
24627
  featureModelsRegistry: cfg.features?.modelsRegistry !== false,
24138
- featureTokenSaving: cfg.features?.tokenSavingMode ?? false,
24628
+ featureTokenSaving: normalizeTokenSavingTier(cfg.features?.tokenSavingMode),
24139
24629
  allowOutsideProjectRoot: cfg.features?.allowOutsideProjectRoot ?? true,
24140
24630
  contextAutoCompact: cfg.context?.autoCompact !== false,
24141
24631
  contextStrategy: cfg.context?.strategy ?? "hybrid",
@@ -24143,6 +24633,7 @@ Reply with ONLY the JSON object.`
24143
24633
  auditLevel: cfg.session?.auditLevel ?? "standard",
24144
24634
  indexOnStart: cfg.indexing?.onSessionStart !== false,
24145
24635
  maxIterations: cfg.tools?.maxIterations ?? 500,
24636
+ restrictFsToRoot: cfg.tools?.restrictToProjectRoot ?? false,
24146
24637
  autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
24147
24638
  debugStream: cfg.debugStream ?? false,
24148
24639
  configScope: cfg.configScope ?? "global",
@@ -24150,7 +24641,9 @@ Reply with ONLY the JSON object.`
24150
24641
  enhanceEnabled: cfg.autonomy?.enhance ?? true,
24151
24642
  enhanceLanguage: cfg.autonomy?.enhanceLanguage === "english" ? "english" : "original",
24152
24643
  mouseMode: autonomy?.mouseMode ?? false,
24153
- autonomyNextPrompt: cfg.autonomy?.autonomyNextPrompt ?? "auto {{suggestion}}"
24644
+ autonomyNextPrompt: cfg.autonomy?.autonomyNextPrompt ?? "auto {{suggestion}}",
24645
+ breakerEnabled: cfg.circuitBreaker?.enabled === true,
24646
+ breakerAutoKillResetMs: cfg.circuitBreaker?.autoKillResetMs ?? 6e4
24154
24647
  };
24155
24648
  },
24156
24649
  async saveSettings(s) {
@@ -24179,7 +24672,7 @@ Reply with ONLY the JSON object.`
24179
24672
  a["autoProceedMaxIterations"] = s.autoProceedMaxIterations;
24180
24673
  }
24181
24674
  );
24182
- if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
24675
+ if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.featureTokenSaving !== void 0 || s.allowOutsideProjectRoot !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
24183
24676
  const configScope = s.configScope ?? configStore.get().configScope ?? "global";
24184
24677
  const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
24185
24678
  let raw;
@@ -24231,11 +24724,18 @@ Reply with ONLY the JSON object.`
24231
24724
  idx.onSessionStart = s.indexOnStart;
24232
24725
  decrypted.indexing = idx;
24233
24726
  }
24234
- if (s.maxIterations !== void 0) {
24727
+ if (s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0) {
24235
24728
  const tools = decrypted.tools ?? {};
24236
- tools.maxIterations = s.maxIterations;
24729
+ if (s.maxIterations !== void 0) tools.maxIterations = s.maxIterations;
24730
+ if (s.restrictFsToRoot !== void 0)
24731
+ tools.restrictToProjectRoot = s.restrictFsToRoot;
24237
24732
  decrypted.tools = tools;
24238
24733
  }
24734
+ if (s.restrictFsToRoot !== void 0) {
24735
+ const features = decrypted.features ?? {};
24736
+ features.allowOutsideProjectRoot = !s.restrictFsToRoot;
24737
+ decrypted.features = features;
24738
+ }
24239
24739
  if (s.debugStream !== void 0) {
24240
24740
  decrypted.debugStream = s.debugStream;
24241
24741
  const { setDebugStreamEnabled } = await import('@wrongstack/providers');
@@ -24257,7 +24757,7 @@ Reply with ONLY the JSON object.`
24257
24757
  const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
24258
24758
  const encrypted = encryptConfigSecrets(toWrite, noOpVault);
24259
24759
  if (targetPath !== wpaths.globalConfig) {
24260
- await fsp5.mkdir(path39.dirname(targetPath), { recursive: true });
24760
+ await fsp5.mkdir(path4.dirname(targetPath), { recursive: true });
24261
24761
  }
24262
24762
  await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
24263
24763
  configStore.update({
@@ -24267,7 +24767,7 @@ Reply with ONLY the JSON object.`
24267
24767
  ...s.logLevel !== void 0 ? { log: decrypted.log } : {},
24268
24768
  ...s.auditLevel !== void 0 ? { session: decrypted.session } : {},
24269
24769
  ...s.indexOnStart !== void 0 ? { indexing: decrypted.indexing } : {},
24270
- ...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {},
24770
+ ...s.maxIterations !== void 0 || s.restrictFsToRoot !== void 0 ? { tools: decrypted.tools } : {},
24271
24771
  ...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
24272
24772
  ...s.configScope !== void 0 ? { configScope: s.configScope } : {},
24273
24773
  ...s.enhanceDelayMs !== void 0 ? {
@@ -24329,6 +24829,18 @@ Reply with ONLY the JSON object.`
24329
24829
  coordinatorEvents.delete(fn);
24330
24830
  };
24331
24831
  },
24832
+ onCoordinatorStart: (goal) => {
24833
+ if (!autonomousCoordinator) {
24834
+ console.error("[coordinator] not ready \u2014 no director yet (spawn a subagent first)");
24835
+ return;
24836
+ }
24837
+ autonomousCoordinator.run({ goal: goal ?? "" }).catch((err) => {
24838
+ console.error("[coordinator] run() failed:", err);
24839
+ });
24840
+ },
24841
+ onCoordinatorStop: () => {
24842
+ autonomousCoordinator?.stop();
24843
+ },
24332
24844
  // /clear: signal the TUI to wipe entries and reset fleet/leader stats
24333
24845
  // AND bump the context chip version — so the display reflects a
24334
24846
  // completely fresh session after the backend has been cleared.
@@ -24346,7 +24858,7 @@ Reply with ONLY the JSON object.`
24346
24858
  agentsMonitorController,
24347
24859
  getLiveSessions: async () => {
24348
24860
  const { SessionRegistry } = await import('@wrongstack/core');
24349
- const globalRoot = path39.dirname(wpaths.globalConfig);
24861
+ const globalRoot = path4.dirname(wpaths.globalConfig);
24350
24862
  const registry = new SessionRegistry(globalRoot);
24351
24863
  const sessions = await registry.list();
24352
24864
  return sessions.filter((s) => s.status !== "stale").map((s) => ({
@@ -24475,7 +24987,7 @@ Reply with ONLY the JSON object.`
24475
24987
  if (!sessionStore) return null;
24476
24988
  try {
24477
24989
  const { SessionRegistry } = await import('@wrongstack/core');
24478
- const registry = new SessionRegistry(path39.dirname(wpaths.globalConfig));
24990
+ const registry = new SessionRegistry(path4.dirname(wpaths.globalConfig));
24479
24991
  const live = (await registry.list()).find(
24480
24992
  (s) => s.sessionId === sessionId && s.status !== "stale" && s.pid !== process.pid
24481
24993
  );
@@ -24613,7 +25125,7 @@ Reply with ONLY the JSON object.`
24613
25125
  if (slug === "new-session") {
24614
25126
  pendingProjectSwitch = {
24615
25127
  root: projectRoot,
24616
- name: path39.basename(projectRoot) || projectRoot
25128
+ name: path4.basename(projectRoot) || projectRoot
24617
25129
  };
24618
25130
  }
24619
25131
  return;
@@ -24667,7 +25179,7 @@ ${parts.join("\n")}
24667
25179
  },
24668
25180
  // `wrongstack quick` sets flags.quick — open the F3 agents monitor by default.
24669
25181
  initialAgentsMonitorOpen: !!flags.quick,
24670
- tokenSavingMode: config.features.tokenSavingMode,
25182
+ tokenSavingMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off",
24671
25183
  toolCount: agent.tools.list().length
24672
25184
  });
24673
25185
  if (code === PROJECT_SWITCH_EXIT_CODE && pendingProjectSwitch) {
@@ -24679,8 +25191,8 @@ ${parts.join("\n")}
24679
25191
  try {
24680
25192
  const req2 = createRequire8(import.meta.url);
24681
25193
  const pkgPath = req2.resolve("@wrongstack/cli/package.json");
24682
- const pkgDir = path39.dirname(pkgPath);
24683
- cliPath = path39.join(pkgDir, "dist", "index.js");
25194
+ const pkgDir = path4.dirname(pkgPath);
25195
+ cliPath = path4.join(pkgDir, "dist", "index.js");
24684
25196
  await fsp5.access(cliPath);
24685
25197
  } catch {
24686
25198
  cliPath = process.argv[1] ?? "";
@@ -24741,6 +25253,23 @@ ${parts.join("\n")}
24741
25253
  modeStore,
24742
25254
  modeId,
24743
25255
  needsSetup,
25256
+ // Print the "open this" banner only once the server is actually
25257
+ // listening, using the RESOLVED ports. The requested port
25258
+ // (flags.port) auto-advances past busy ports inside runWebUI, so a
25259
+ // banner printed up-front with flags.port lies whenever 3456/3457 are
25260
+ // taken (a second instance, leftover sockets). Bind is 127.0.0.1-only,
25261
+ // so the host must be the literal IPv4 loopback — `localhost` resolves
25262
+ // to `::1` first on Windows and never reaches the server.
25263
+ onListening: ({ httpPort: boundHttpPort }) => {
25264
+ renderer.writeInfo(
25265
+ color.green(
25266
+ ` \u2726 WebUI running \u2192 ${color.bold(`http://127.0.0.1:${boundHttpPort}`)}`
25267
+ )
25268
+ );
25269
+ renderer.writeInfo(
25270
+ color.dim(" Press Ctrl+C in this terminal to stop the WebUI server.\n")
25271
+ );
25272
+ },
24744
25273
  // Make autonomy.switch from the browser flip the CLI's real
24745
25274
  // autonomy mode — context.meta alone never reaches the run loop.
24746
25275
  onAutonomySwitch: (mode) => {
@@ -24749,12 +25278,6 @@ ${parts.join("\n")}
24749
25278
  }
24750
25279
  }
24751
25280
  });
24752
- renderer.writeInfo(
24753
- color.green(
24754
- ` \u2726 WebUI running \u2192 ${color.bold(`http://localhost:${Number.parseInt(String(flags.port ?? "3457"), 10)}`)}`
24755
- )
24756
- );
24757
- renderer.writeInfo(color.dim(" Press Ctrl+C in this terminal to stop the WebUI server.\n"));
24758
25281
  const webuiExit = new Promise((resolve11) => {
24759
25282
  const onSigint = () => {
24760
25283
  renderer.setSilent(false);
@@ -24789,7 +25312,7 @@ ${parts.join("\n")}
24789
25312
  supportsVision,
24790
25313
  attachments,
24791
25314
  effectiveMaxContext,
24792
- projectName: path39.basename(projectRoot) || void 0,
25315
+ projectName: path4.basename(projectRoot) || void 0,
24793
25316
  getAutonomy,
24794
25317
  onAutonomy,
24795
25318
  getNextPredict,
@@ -24827,6 +25350,7 @@ ${parts.join("\n")}
24827
25350
  }
24828
25351
  } finally {
24829
25352
  fleetStatusLine?.stop();
25353
+ deps.onCoordinatorStop?.();
24830
25354
  try {
24831
25355
  stats.render(renderer);
24832
25356
  } catch (_err) {
@@ -25011,7 +25535,7 @@ var MultiAgentHost = class _MultiAgentHost {
25011
25535
  doneCondition: { type: "all_tasks_done" },
25012
25536
  maxConcurrent: this.opts.maxConcurrent ?? 4
25013
25537
  };
25014
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path39.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
25538
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path4.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
25015
25539
  this.director = new Director({
25016
25540
  config: coordinatorConfig,
25017
25541
  manifestPath: this.opts.manifestPath,
@@ -25210,6 +25734,8 @@ var MultiAgentHost = class _MultiAgentHost {
25210
25734
  tokenCounter: this.deps.tokenCounter,
25211
25735
  cwd: subCwd,
25212
25736
  projectRoot: this.deps.projectRoot,
25737
+ // Subagents inherit the leader's filesystem-access scope.
25738
+ allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
25213
25739
  model: effModel,
25214
25740
  tools: this.filterTools(tools),
25215
25741
  // Distinct mailbox identity: without these, every subagent fell back
@@ -25568,16 +26094,16 @@ var MultiAgentHost = class _MultiAgentHost {
25568
26094
  if (this.director) return this.director;
25569
26095
  this.opts.directorMode = true;
25570
26096
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
25571
- this.opts.manifestPath = path39.join(this.opts.fleetRoot, "fleet.json");
26097
+ this.opts.manifestPath = path4.join(this.opts.fleetRoot, "fleet.json");
25572
26098
  }
25573
26099
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
25574
- this.opts.sharedScratchpadPath = path39.join(this.opts.fleetRoot, "shared");
26100
+ this.opts.sharedScratchpadPath = path4.join(this.opts.fleetRoot, "shared");
25575
26101
  }
25576
26102
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
25577
- this.opts.sessionsRoot = path39.join(this.opts.fleetRoot, "subagents");
26103
+ this.opts.sessionsRoot = path4.join(this.opts.fleetRoot, "subagents");
25578
26104
  }
25579
26105
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
25580
- this.opts.stateCheckpointPath = path39.join(this.opts.fleetRoot, "director-state.json");
26106
+ this.opts.stateCheckpointPath = path4.join(this.opts.fleetRoot, "director-state.json");
25581
26107
  }
25582
26108
  await this.ensureDirector();
25583
26109
  return this.director ?? null;
@@ -25706,11 +26232,11 @@ var SessionStats = class {
25706
26232
  if (tool.name === "bash") this.bashCommands++;
25707
26233
  else if (tool.name === "fetch") this.fetches++;
25708
26234
  if (!tool.ok) return;
25709
- const path40 = typeof input?.path === "string" ? input.path : void 0;
25710
- if (tool.name === "read" && path40) this.readPaths.add(path40);
25711
- else if (tool.name === "edit" && path40) this.editedPaths.add(path40);
25712
- else if (tool.name === "write" && path40) {
25713
- this.writtenPaths.add(path40);
26235
+ const path39 = typeof input?.path === "string" ? input.path : void 0;
26236
+ if (tool.name === "read" && path39) this.readPaths.add(path39);
26237
+ else if (tool.name === "edit" && path39) this.editedPaths.add(path39);
26238
+ else if (tool.name === "write" && path39) {
26239
+ this.writtenPaths.add(path39);
25714
26240
  const content = typeof input?.content === "string" ? input.content : "";
25715
26241
  this.bytesWritten += Buffer.byteLength(content, "utf8");
25716
26242
  }
@@ -26022,7 +26548,7 @@ async function setupCodebaseIndexing(deps) {
26022
26548
  if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
26023
26549
  const fp = payload.toolUse.input?.file_path;
26024
26550
  if (typeof fp === "string" && fp.length > 0) {
26025
- const abs = path39.resolve(payload.ctx.cwd, fp);
26551
+ const abs = path4.resolve(payload.ctx.cwd, fp);
26026
26552
  if (isIndexableFile(abs)) {
26027
26553
  enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
26028
26554
  }
@@ -26041,7 +26567,7 @@ async function setupCodebaseIndexing(deps) {
26041
26567
  if (!filename) return;
26042
26568
  const rel = filename.toString();
26043
26569
  if (isIgnored(rel)) return;
26044
- const abs = path39.resolve(projectRoot, rel);
26570
+ const abs = path4.resolve(projectRoot, rel);
26045
26571
  if (!isIndexableFile(abs)) return;
26046
26572
  enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
26047
26573
  });
@@ -26095,7 +26621,7 @@ function setupMetrics(params) {
26095
26621
  const dumpMetrics = () => {
26096
26622
  if (!metricsSink) return;
26097
26623
  try {
26098
- const out = path39.join(wpaths.projectSessions, "metrics.json");
26624
+ const out = path4.join(wpaths.projectSessions, "metrics.json");
26099
26625
  const snap = metricsSink.snapshot();
26100
26626
  writeFileSync(out, JSON.stringify(snap, null, 2));
26101
26627
  } catch {
@@ -26335,7 +26861,7 @@ async function setupSession(params) {
26335
26861
  );
26336
26862
  });
26337
26863
  const attachments = new DefaultAttachmentStore({
26338
- spoolDir: path39.join(wpaths.projectSessions, session?.id, "attachments")
26864
+ spoolDir: path4.join(wpaths.projectSessions, session?.id, "attachments")
26339
26865
  });
26340
26866
  const ctxSignal = new AbortController().signal;
26341
26867
  const traceId = randomBytes(16).toString("hex");
@@ -26347,11 +26873,15 @@ async function setupSession(params) {
26347
26873
  tokenCounter,
26348
26874
  cwd,
26349
26875
  projectRoot,
26876
+ // Filesystem-access scope: derived from features.allowOutsideProjectRoot,
26877
+ // falling back to the old tools.restrictToProjectRoot config key (inverted:
26878
+ // restrict=false in old config → allow=true in new). Togglable live via
26879
+ // `/settings` ("Allow outside project").
26880
+ allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
26350
26881
  model: config.model,
26351
26882
  agentId: "leader",
26352
26883
  agentName: "Leader Agent",
26353
- traceId,
26354
- allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? true
26884
+ traceId
26355
26885
  });
26356
26886
  context.meta["packageTrackerOpts"] = {
26357
26887
  storageDir: wpaths.projectDir,
@@ -26359,11 +26889,11 @@ async function setupSession(params) {
26359
26889
  };
26360
26890
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
26361
26891
  const queueStore = new QueueStore({
26362
- dir: path39.join(wpaths.projectSessions, session?.id),
26892
+ dir: path4.join(wpaths.projectSessions, session?.id),
26363
26893
  ...eventsBus ? { events: eventsBus } : {},
26364
26894
  ...traceId ? { traceId } : {}
26365
26895
  });
26366
- const todosCheckpointPath = path39.join(wpaths.projectSessions, `${session?.id}.todos.json`);
26896
+ const todosCheckpointPath = path4.join(wpaths.projectSessions, `${session?.id}.todos.json`);
26367
26897
  if (resumeId) {
26368
26898
  try {
26369
26899
  const restoredTodos = await loadTodosCheckpoint(
@@ -26387,15 +26917,15 @@ async function setupSession(params) {
26387
26917
  eventsBus,
26388
26918
  traceId
26389
26919
  );
26390
- const planPath = path39.join(wpaths.projectSessions, `${session?.id}.plan.json`);
26920
+ const planPath = path4.join(wpaths.projectSessions, `${session?.id}.plan.json`);
26391
26921
  context.state.setMeta("plan.path", planPath);
26392
- const taskPath = path39.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
26922
+ const taskPath = path4.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
26393
26923
  context.state.setMeta("task.path", taskPath);
26394
26924
  let dirState;
26395
26925
  if (resumeId) {
26396
26926
  try {
26397
- const fleetRoot = path39.join(wpaths.projectSessions, session?.id);
26398
- dirState = await loadDirectorState(path39.join(fleetRoot, "director-state.json"));
26927
+ const fleetRoot = path4.join(wpaths.projectSessions, session?.id);
26928
+ dirState = await loadDirectorState(path4.join(fleetRoot, "director-state.json"));
26399
26929
  if (dirState) {
26400
26930
  const tCounts = {};
26401
26931
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -26642,7 +27172,7 @@ async function main(argv) {
26642
27172
  projectGoal: wpaths.projectGoal,
26643
27173
  projectSessions: wpaths.projectSessions
26644
27174
  },
26645
- pathJoiner: { join: (a, b) => path39.join(a, b) },
27175
+ pathJoiner: { join: (a, b) => path4.join(a, b) },
26646
27176
  systemPromptBuilderToken: TOKENS.SystemPromptBuilder
26647
27177
  });
26648
27178
  const toolRegistry = new ToolRegistry();
@@ -26737,6 +27267,49 @@ async function main(argv) {
26737
27267
  writeErr(color.red(` \u2717 ${p.description}
26738
27268
  `));
26739
27269
  });
27270
+ const cliClientId = `cli@${randomBytes(4).toString("hex")}`;
27271
+ let cliToolCalls = 0;
27272
+ let cliInputTokens = 0;
27273
+ let cliOutputTokens = 0;
27274
+ let cliCacheTokens = 0;
27275
+ let cliCostUsd = 0;
27276
+ const emitClientStatus = () => {
27277
+ events.emit("client.status", {
27278
+ clientType: "cli",
27279
+ clientId: cliClientId,
27280
+ projectHash: wpaths.projectSlug,
27281
+ agentCount: 1,
27282
+ model: config.model,
27283
+ mode: activeMode?.id ?? "off",
27284
+ toolCalls: cliToolCalls,
27285
+ inputTokens: cliInputTokens,
27286
+ outputTokens: cliOutputTokens,
27287
+ cacheTokens: cliCacheTokens,
27288
+ costUsd: cliCostUsd,
27289
+ timestamp: Date.now(),
27290
+ projectSlug: wpaths.projectSlug
27291
+ });
27292
+ };
27293
+ evOn("tool.executed", () => {
27294
+ cliToolCalls++;
27295
+ emitClientStatus();
27296
+ });
27297
+ evOn("provider.response", (e) => {
27298
+ if (e.usage) {
27299
+ cliInputTokens = e.usage.input ?? cliInputTokens;
27300
+ cliOutputTokens = e.usage.output ?? cliOutputTokens;
27301
+ cliCacheTokens = (e.usage.cacheRead ?? 0) + (e.usage.cacheWrite ?? 0);
27302
+ }
27303
+ emitClientStatus();
27304
+ });
27305
+ evOn("token.accounted", (e) => {
27306
+ cliCostUsd = e.cost.total;
27307
+ emitClientStatus();
27308
+ });
27309
+ evOn("iteration.completed", () => {
27310
+ emitClientStatus();
27311
+ });
27312
+ emitClientStatus();
26740
27313
  const promptBuilder = container.resolve(TOKENS.SystemPromptBuilder);
26741
27314
  let onlineAgents = [];
26742
27315
  try {
@@ -26780,10 +27353,10 @@ async function main(argv) {
26780
27353
  memoryStore.withTraceId(sessResult.traceId);
26781
27354
  let tracker;
26782
27355
  try {
26783
- const { getSessionRegistry, AgentStatusTracker } = await import('@wrongstack/core');
27356
+ const { getSessionRegistry, AgentStatusTracker, FleetNotifier } = await import('@wrongstack/core');
26784
27357
  const registry = getSessionRegistry(wpaths.globalRoot);
26785
- const projectSlug2 = path39.basename(wpaths.projectDir);
26786
- const projectName = path39.basename(projectRoot);
27358
+ const projectSlug2 = path4.basename(wpaths.projectDir);
27359
+ const projectName = path4.basename(projectRoot);
26787
27360
  let gitBranch;
26788
27361
  try {
26789
27362
  const { execSync } = await import('child_process');
@@ -26803,13 +27376,22 @@ async function main(argv) {
26803
27376
  projectName,
26804
27377
  workingDir: context.workingDir,
26805
27378
  gitBranch,
27379
+ // The TUI and the REPL both boot through cli-main; `tuiOwnsScreen`
27380
+ // distinguishes the surface so the Fleet HQ map can label this session.
27381
+ clientType: tuiOwnsScreen ? "tui" : "cli",
26806
27382
  pid: process.pid,
26807
27383
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
26808
27384
  });
26809
- tracker = new AgentStatusTracker({ events, registry });
27385
+ const fleetNotifier = new FleetNotifier({
27386
+ baseDir: wpaths.globalRoot,
27387
+ projectRoot,
27388
+ selfPid: process.pid
27389
+ });
27390
+ tracker = new AgentStatusTracker({ events, registry, onUpdate: () => fleetNotifier.notify() });
26810
27391
  tracker.start();
26811
27392
  const cleanup = async () => {
26812
27393
  try {
27394
+ fleetNotifier.dispose();
26813
27395
  await registry.markClosing();
26814
27396
  tracker?.stop();
26815
27397
  } catch {
@@ -26882,7 +27464,7 @@ async function main(argv) {
26882
27464
  if (e.ok && (e.name === "write" || e.name === "edit" || e.name === "replace" || e.name === "patch")) {
26883
27465
  const filePath = e.input?.path;
26884
27466
  if (filePath) {
26885
- const projectDir = path39.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27467
+ const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
26886
27468
  void recordFileAction(
26887
27469
  { storageDir: projectDir, projectRoot },
26888
27470
  {
@@ -27012,7 +27594,7 @@ async function main(argv) {
27012
27594
  toolRegistry,
27013
27595
  events,
27014
27596
  log: logger,
27015
- lazyMode: config.features.tokenSavingMode
27597
+ lazyMode: normalizeTokenSavingTier(config.features.tokenSavingMode) !== "off"
27016
27598
  });
27017
27599
  if (config.features.mcp) {
27018
27600
  for (const cfg of Object.values(config.mcpServers ?? {})) {
@@ -27048,7 +27630,7 @@ async function main(argv) {
27048
27630
  let depWatcherDispose;
27049
27631
  if (dwCfg?.["enabled"] === true) {
27050
27632
  try {
27051
- const projectDir = path39.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27633
+ const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27052
27634
  const dwMailbox = new GlobalMailbox(projectDir, events);
27053
27635
  depWatcherDispose = attachDepWatcherBridge({
27054
27636
  events,
@@ -27147,12 +27729,12 @@ async function main(argv) {
27147
27729
  }
27148
27730
  }
27149
27731
  };
27150
- const fleetRoot = directorMode ? path39.join(wpaths.projectSessions, session.id) : void 0;
27151
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path39.join(expectDefined(fleetRoot), "fleet.json") : void 0;
27152
- const sharedScratchpadPath = directorMode ? path39.join(expectDefined(fleetRoot), "shared") : void 0;
27153
- const subagentSessionsRoot = directorMode ? path39.join(expectDefined(fleetRoot), "subagents") : void 0;
27154
- const stateCheckpointPath = directorMode ? path39.join(expectDefined(fleetRoot), "director-state.json") : void 0;
27155
- const fleetRootForPromotion = path39.join(wpaths.projectSessions, session.id);
27732
+ const fleetRoot = directorMode ? path4.join(wpaths.projectSessions, session.id) : void 0;
27733
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path4.join(expectDefined(fleetRoot), "fleet.json") : void 0;
27734
+ const sharedScratchpadPath = directorMode ? path4.join(expectDefined(fleetRoot), "shared") : void 0;
27735
+ const subagentSessionsRoot = directorMode ? path4.join(expectDefined(fleetRoot), "subagents") : void 0;
27736
+ const stateCheckpointPath = directorMode ? path4.join(expectDefined(fleetRoot), "director-state.json") : void 0;
27737
+ const fleetRootForPromotion = path4.join(wpaths.projectSessions, session.id);
27156
27738
  const brainSettings = {
27157
27739
  maxAutoRisk: "medium"
27158
27740
  };
@@ -27295,7 +27877,7 @@ async function main(argv) {
27295
27877
  let techStackConsumerDispose;
27296
27878
  if (dwCfg?.["enabled"] === true) {
27297
27879
  try {
27298
- const projectDir = path39.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27880
+ const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27299
27881
  const tsMailbox = new GlobalMailbox(projectDir, events);
27300
27882
  const fileAuthorOpts = {
27301
27883
  storageDir: projectDir,
@@ -27328,7 +27910,7 @@ async function main(argv) {
27328
27910
  let pkgOutdatedDispose;
27329
27911
  if (dwCfg?.["enabled"] === true) {
27330
27912
  try {
27331
- const projectDir = path39.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27913
+ const projectDir = path4.join(wpaths.globalRoot, "projects", wpaths.projectSlug);
27332
27914
  const pkgMailbox = new GlobalMailbox(projectDir, events);
27333
27915
  const pkgTrackerOpts = {
27334
27916
  storageDir: projectDir,
@@ -27702,7 +28284,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
27702
28284
  return director.spawn(cfg);
27703
28285
  },
27704
28286
  onFleetLog: async (subagentId, mode) => {
27705
- const subagentsRoot = path39.join(fleetRootForPromotion, "subagents");
28287
+ const subagentsRoot = path4.join(fleetRootForPromotion, "subagents");
27706
28288
  let runDirs;
27707
28289
  try {
27708
28290
  runDirs = await fsp5.readdir(subagentsRoot);
@@ -27711,7 +28293,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
27711
28293
  }
27712
28294
  const found = [];
27713
28295
  for (const runId of runDirs) {
27714
- const runDir = path39.join(subagentsRoot, runId);
28296
+ const runDir = path4.join(subagentsRoot, runId);
27715
28297
  let files;
27716
28298
  try {
27717
28299
  files = await fsp5.readdir(runDir);
@@ -27720,7 +28302,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
27720
28302
  }
27721
28303
  for (const f of files) {
27722
28304
  if (!f.endsWith(".jsonl")) continue;
27723
- const full = path39.join(runDir, f);
28305
+ const full = path4.join(runDir, f);
27724
28306
  try {
27725
28307
  const stat7 = await fsp5.stat(full);
27726
28308
  found.push({
@@ -27821,7 +28403,7 @@ ${color.dim("\u2500".repeat(40))}` : "";
27821
28403
  }
27822
28404
  const dir = await multiAgentHost.ensureDirector();
27823
28405
  if (!dir) return "Director is not available.";
27824
- const dirStatePath = path39.join(fleetRootForPromotion, "director-state.json");
28406
+ const dirStatePath = path4.join(fleetRootForPromotion, "director-state.json");
27825
28407
  const prior = await loadDirectorState(dirStatePath);
27826
28408
  if (!prior) {
27827
28409
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -27890,9 +28472,9 @@ ${color.dim("\u2500".repeat(40))}` : "";
27890
28472
  for (const tool of director2.tools(FLEET_ROSTER)) {
27891
28473
  toolRegistry.register(tool);
27892
28474
  }
27893
- const mp = path39.join(fleetRootForPromotion, "fleet.json");
27894
- const sp = path39.join(fleetRootForPromotion, "shared");
27895
- const ss = path39.join(fleetRootForPromotion, "subagents");
28475
+ const mp = path4.join(fleetRootForPromotion, "fleet.json");
28476
+ const sp = path4.join(fleetRootForPromotion, "shared");
28477
+ const ss = path4.join(fleetRootForPromotion, "subagents");
27896
28478
  const lines = [
27897
28479
  `${color.green("\u2713")} Promoted to director mode.`,
27898
28480
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -28261,6 +28843,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
28261
28843
  if (s.contextAutoCompact !== void 0) {
28262
28844
  autoCompactor?.setEnabled(s.contextAutoCompact);
28263
28845
  }
28846
+ if (s.restrictFsToRoot !== void 0) {
28847
+ context.allowOutsideProjectRoot = !s.restrictFsToRoot;
28848
+ config = patchConfig(config, {
28849
+ features: { ...config.features, allowOutsideProjectRoot: !s.restrictFsToRoot },
28850
+ tools: { ...config.tools, restrictToProjectRoot: s.restrictFsToRoot }
28851
+ });
28852
+ }
28264
28853
  } catch {
28265
28854
  }
28266
28855
  },