@wrongstack/cli 0.5.7 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import * as path21 from 'path';
2
+ import * as path23 from 'path';
3
3
  import { join } from 'path';
4
4
  import * as fsp2 from 'fs/promises';
5
5
  import { readdir, readFile } from 'fs/promises';
6
- import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
6
+ import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, loadGoal, goalFilePath, summarizeUsage, emptyGoal, saveGoal, formatGoal, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1 } from '@wrongstack/core';
7
7
  import { createRequire } from 'module';
8
8
  import * as os6 from 'os';
9
9
  import os6__default from 'os';
@@ -11,15 +11,17 @@ import * as crypto from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
13
13
  import { WebSocketServer, WebSocket } from 'ws';
14
- import { writeFileSync } from 'fs';
15
14
  import { MCPRegistry } from '@wrongstack/mcp';
16
15
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
17
16
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
18
17
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
19
18
  import * as readline from 'readline';
20
19
  import { spawn } from 'child_process';
20
+ import { buildGoalPreamble } from '@wrongstack/tui';
21
21
  import { SkillInstaller } from '@wrongstack/core/skills';
22
22
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
23
+ import { ToolExecutor } from '@wrongstack/core/execution';
24
+ import { writeFileSync } from 'fs';
23
25
 
24
26
  var __defProp = Object.defineProperty;
25
27
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -261,8 +263,8 @@ function buildSddCommand(opts) {
261
263
  async run(args) {
262
264
  const ctx = opts.context;
263
265
  const projectRoot = ctx?.projectRoot ?? process.cwd();
264
- const specsDir = path21.join(projectRoot, ".wrongstack", "specs");
265
- const graphsDir = path21.join(projectRoot, ".wrongstack", "task-graphs");
266
+ const specsDir = path23.join(projectRoot, ".wrongstack", "specs");
267
+ const graphsDir = path23.join(projectRoot, ".wrongstack", "task-graphs");
266
268
  const specStore = new SpecStore({ baseDir: specsDir });
267
269
  new TaskGraphStore({ baseDir: graphsDir });
268
270
  const versioning = new SpecVersioning();
@@ -278,7 +280,7 @@ function buildSddCommand(opts) {
278
280
  const forceFlag = rest.includes("--force") || rest.includes("-f");
279
281
  const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
280
282
  if (!sddState.getBuilder() && !forceFlag) {
281
- const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
283
+ const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
282
284
  try {
283
285
  await fsp2.access(sessionPath);
284
286
  const projectContext2 = await gatherProjectContext(projectRoot);
@@ -313,7 +315,7 @@ function buildSddCommand(opts) {
313
315
  projectContext,
314
316
  minQuestions: 2,
315
317
  maxQuestions: 10,
316
- sessionPath: path21.join(projectRoot, ".wrongstack", "sdd-session.json")
318
+ sessionPath: path23.join(projectRoot, ".wrongstack", "sdd-session.json")
317
319
  }));
318
320
  const builder = sddState.getBuilder();
319
321
  builder.startSession(title);
@@ -581,7 +583,7 @@ Start executing the tasks one by one.`
581
583
  };
582
584
  }
583
585
  case "cancel": {
584
- const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
586
+ const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
585
587
  let deletedFromDisk = false;
586
588
  try {
587
589
  await fsp2.unlink(sessionPath);
@@ -605,7 +607,7 @@ Start executing the tasks one by one.`
605
607
  if (sddState.getBuilder()) {
606
608
  return { message: "An SDD session is already active. Use /sdd cancel first." };
607
609
  }
608
- const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
610
+ const sessionPath = path23.join(projectRoot, ".wrongstack", "sdd-session.json");
609
611
  const projectContext = await gatherProjectContext(projectRoot);
610
612
  sddState.setBuilder(new AISpecBuilder({
611
613
  store: specStore,
@@ -831,7 +833,7 @@ function sddHelp() {
831
833
  async function gatherProjectContext(projectRoot) {
832
834
  const parts = [];
833
835
  try {
834
- const pkgPath = path21.join(projectRoot, "package.json");
836
+ const pkgPath = path23.join(projectRoot, "package.json");
835
837
  const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
836
838
  const pkg = JSON.parse(pkgRaw);
837
839
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -847,13 +849,13 @@ async function gatherProjectContext(projectRoot) {
847
849
  } catch {
848
850
  }
849
851
  try {
850
- const tsconfigPath = path21.join(projectRoot, "tsconfig.json");
852
+ const tsconfigPath = path23.join(projectRoot, "tsconfig.json");
851
853
  await fsp2.access(tsconfigPath);
852
854
  parts.push("Language: TypeScript");
853
855
  } catch {
854
856
  }
855
857
  try {
856
- const srcDir = path21.join(projectRoot, "src");
858
+ const srcDir = path23.join(projectRoot, "src");
857
859
  const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
858
860
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
859
861
  if (dirs.length > 0) {
@@ -976,7 +978,7 @@ __export(update_check_exports, {
976
978
  getUpdateNotification: () => getUpdateNotification
977
979
  });
978
980
  function cachePath(homeFn = defaultHomeDir2) {
979
- return path21.join(homeFn(), ".wrongstack", "update-cache.json");
981
+ return path23.join(homeFn(), ".wrongstack", "update-cache.json");
980
982
  }
981
983
  function currentVersion() {
982
984
  const req2 = createRequire(import.meta.url);
@@ -1013,7 +1015,7 @@ async function readCache(homeFn = defaultHomeDir2) {
1013
1015
  }
1014
1016
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1015
1017
  try {
1016
- const dir = path21.dirname(cachePath(homeFn));
1018
+ const dir = path23.dirname(cachePath(homeFn));
1017
1019
  await fsp2.mkdir(dir, { recursive: true });
1018
1020
  await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1019
1021
  } catch {
@@ -1196,6 +1198,25 @@ async function runWebUI(opts) {
1196
1198
  });
1197
1199
  })
1198
1200
  );
1201
+ if (opts.subscribeEternalIteration) {
1202
+ eventUnsubscribers.push(
1203
+ opts.subscribeEternalIteration((entry) => {
1204
+ broadcast({
1205
+ type: "eternal.iteration",
1206
+ payload: {
1207
+ iteration: entry.iteration,
1208
+ at: entry.at,
1209
+ source: entry.source,
1210
+ task: entry.task,
1211
+ status: entry.status,
1212
+ note: entry.note,
1213
+ tokens: entry.tokens,
1214
+ costUsd: entry.costUsd
1215
+ }
1216
+ });
1217
+ })
1218
+ );
1219
+ }
1199
1220
  }
1200
1221
  return new Promise((resolve4) => {
1201
1222
  wss.on("listening", () => {
@@ -1584,26 +1605,40 @@ async function runWebUI(opts) {
1584
1605
  return {};
1585
1606
  }
1586
1607
  if (!parsed.providers) return {};
1587
- const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
1608
+ const keyFile = path23.join(path23.dirname(opts.globalConfigPath), ".key");
1588
1609
  const vault = new DefaultSecretVault$1({ keyFile });
1589
1610
  return decryptConfigSecrets$1(parsed.providers, vault);
1590
1611
  }
1591
1612
  async function saveProviders(providers) {
1592
1613
  if (!opts.globalConfigPath) return;
1593
1614
  let raw;
1615
+ let fileExists = true;
1594
1616
  try {
1595
1617
  raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
1596
- } catch {
1618
+ } catch (err) {
1619
+ if (err.code !== "ENOENT") {
1620
+ throw new Error(
1621
+ `Refusing to mutate ${opts.globalConfigPath}: ${err.message}`,
1622
+ { cause: err }
1623
+ );
1624
+ }
1625
+ fileExists = false;
1597
1626
  raw = "{}";
1598
1627
  }
1599
1628
  let parsed;
1600
1629
  try {
1601
1630
  parsed = JSON.parse(raw);
1602
- } catch {
1631
+ } catch (err) {
1632
+ if (fileExists) {
1633
+ throw new Error(
1634
+ `Refusing to overwrite corrupt config at ${opts.globalConfigPath} (${err.message}). Fix or move the file aside before retrying.`,
1635
+ { cause: err }
1636
+ );
1637
+ }
1603
1638
  parsed = {};
1604
1639
  }
1605
1640
  parsed.providers = providers;
1606
- const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
1641
+ const keyFile = path23.join(path23.dirname(opts.globalConfigPath), ".key");
1607
1642
  const vault = new DefaultSecretVault$1({ keyFile });
1608
1643
  const encrypted = encryptConfigSecrets(parsed, vault);
1609
1644
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -1618,19 +1653,6 @@ var init_webui_server = __esm({
1618
1653
  }
1619
1654
  });
1620
1655
 
1621
- // src/plugin-api-factory.ts
1622
- var plugin_api_factory_exports = {};
1623
- __export(plugin_api_factory_exports, {
1624
- default: () => createApi
1625
- });
1626
- function createApi(ownerName, base) {
1627
- return new DefaultPluginAPI({ ownerName, ...base });
1628
- }
1629
- var init_plugin_api_factory = __esm({
1630
- "src/plugin-api-factory.ts"() {
1631
- }
1632
- });
1633
-
1634
1656
  // src/slash-commands/commit-llm.ts
1635
1657
  async function generateCommitMessageWithLLM(diff, opts) {
1636
1658
  const systemPrompt = "You are a helpful assistant that generates concise, conventional-commit-formatted git commit messages. Analyze the provided diff and output ONLY the commit message (no explanation, no quotes). Format: <type>(<scope>): <short description> \u2014 <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, ci, build, temp. If the diff contains multiple unrelated changes, pick the most important one. Keep the description under 72 characters. Example: feat(cli): add /commit LLM integration";
@@ -1788,7 +1810,7 @@ function parseSpawnFlags(input) {
1788
1810
  return { description: rest.trim(), opts };
1789
1811
  }
1790
1812
  async function bootConfig(flags) {
1791
- const cwd = typeof flags["cwd"] === "string" ? path21.resolve(flags["cwd"]) : process.cwd();
1813
+ const cwd = typeof flags["cwd"] === "string" ? path23.resolve(flags["cwd"]) : process.cwd();
1792
1814
  const pathResolver = new DefaultPathResolver(cwd);
1793
1815
  const projectRoot = pathResolver.projectRoot;
1794
1816
  const userHome = os6.homedir();
@@ -1855,7 +1877,7 @@ var ReadlineInputReader = class {
1855
1877
  history = [];
1856
1878
  pending = false;
1857
1879
  constructor(opts = {}) {
1858
- this.historyFile = opts.historyFile ?? path21.join(os6.homedir(), ".wrongstack", "history");
1880
+ this.historyFile = opts.historyFile ?? path23.join(os6.homedir(), ".wrongstack", "history");
1859
1881
  }
1860
1882
  async loadHistory() {
1861
1883
  try {
@@ -1867,7 +1889,7 @@ var ReadlineInputReader = class {
1867
1889
  }
1868
1890
  async saveHistory() {
1869
1891
  try {
1870
- await fsp2.mkdir(path21.dirname(this.historyFile), { recursive: true });
1892
+ await fsp2.mkdir(path23.dirname(this.historyFile), { recursive: true });
1871
1893
  await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1872
1894
  } catch {
1873
1895
  }
@@ -2094,20 +2116,20 @@ function assertSafeToDelete(filename, parentDir) {
2094
2116
  if (PROTECTED_BASENAMES.has(filename)) {
2095
2117
  throw new Error(`Refusing to delete protected file: ${filename}`);
2096
2118
  }
2097
- if (filename !== path21.basename(filename)) {
2119
+ if (filename !== path23.basename(filename)) {
2098
2120
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
2099
2121
  }
2100
2122
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
2101
2123
  throw new Error(`Refusing to delete unknown file: ${filename}`);
2102
2124
  }
2103
- const resolvedParent = path21.resolve(parentDir);
2125
+ const resolvedParent = path23.resolve(parentDir);
2104
2126
  if (!resolvedParent.endsWith(".wrongstack")) {
2105
2127
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
2106
2128
  }
2107
2129
  }
2108
2130
  async function safeDelete(filePath) {
2109
- const dir = path21.dirname(filePath);
2110
- const filename = path21.basename(filePath);
2131
+ const dir = path23.dirname(filePath);
2132
+ const filename = path23.basename(filePath);
2111
2133
  try {
2112
2134
  assertSafeToDelete(filename, dir);
2113
2135
  await fsp2.unlink(filePath);
@@ -2152,16 +2174,16 @@ function diffSummary(oldCfg, newCfg) {
2152
2174
  }
2153
2175
  var defaultHomeDir = () => os6__default.homedir();
2154
2176
  function historyDir(homeFn = defaultHomeDir) {
2155
- return path21.join(homeFn(), ".wrongstack", "config.history", "entries");
2177
+ return path23.join(homeFn(), ".wrongstack", "config.history", "entries");
2156
2178
  }
2157
2179
  function historyIndexPath(homeFn = defaultHomeDir) {
2158
- return path21.join(homeFn(), ".wrongstack", "config.history", "index.json");
2180
+ return path23.join(homeFn(), ".wrongstack", "config.history", "index.json");
2159
2181
  }
2160
2182
  function configPath(homeFn = defaultHomeDir) {
2161
- return path21.join(homeFn(), ".wrongstack", "config.json");
2183
+ return path23.join(homeFn(), ".wrongstack", "config.json");
2162
2184
  }
2163
2185
  function backupLastPath(homeFn = defaultHomeDir) {
2164
- return path21.join(homeFn(), ".wrongstack", "config.json.last");
2186
+ return path23.join(homeFn(), ".wrongstack", "config.json.last");
2165
2187
  }
2166
2188
  function entryId(ts) {
2167
2189
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -2179,7 +2201,7 @@ async function readIndex(homeFn = defaultHomeDir) {
2179
2201
  }
2180
2202
  async function writeIndex(idx, homeFn = defaultHomeDir) {
2181
2203
  await ensureHistoryDir(homeFn);
2182
- await fsp2.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
2204
+ await atomicWrite(historyIndexPath(homeFn), JSON.stringify(idx, null, 2));
2183
2205
  }
2184
2206
  async function backupCurrent(homeFn = defaultHomeDir) {
2185
2207
  const cfg = configPath(homeFn);
@@ -2198,17 +2220,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
2198
2220
  }
2199
2221
  if (content !== void 0) {
2200
2222
  try {
2201
- const bakPath = path21.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2223
+ const bakPath = path23.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2202
2224
  await atomicWrite(bakPath, content);
2203
2225
  } catch {
2204
2226
  }
2205
2227
  }
2206
2228
  try {
2207
- const dir = path21.join(homeFn(), ".wrongstack");
2229
+ const dir = path23.join(homeFn(), ".wrongstack");
2208
2230
  const files = await fsp2.readdir(dir);
2209
2231
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
2210
2232
  for (const f of baks.slice(10)) {
2211
- await safeDelete(path21.join(dir, f));
2233
+ await safeDelete(path23.join(dir, f));
2212
2234
  }
2213
2235
  } catch {
2214
2236
  }
@@ -2225,7 +2247,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
2225
2247
  diffSummary: diffSummary(oldCfg, newCfg)
2226
2248
  };
2227
2249
  await fsp2.writeFile(
2228
- path21.join(historyDir(homeFn), `${id}.json`),
2250
+ path23.join(historyDir(homeFn), `${id}.json`),
2229
2251
  JSON.stringify(entry, null, 2),
2230
2252
  "utf8"
2231
2253
  );
@@ -2240,7 +2262,7 @@ async function listHistory(homeFn = defaultHomeDir) {
2240
2262
  }
2241
2263
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
2242
2264
  try {
2243
- const raw = await fsp2.readFile(path21.join(historyDir(homeFn), `${id}.json`), "utf8");
2265
+ const raw = await fsp2.readFile(path23.join(historyDir(homeFn), `${id}.json`), "utf8");
2244
2266
  return JSON.parse(raw);
2245
2267
  } catch {
2246
2268
  return null;
@@ -2299,11 +2321,11 @@ async function restoreLast(homeFn = defaultHomeDir) {
2299
2321
  var theme = { primary: color.amber };
2300
2322
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
2301
2323
  try {
2302
- const { atomicWrite: atomicWrite7 } = await import('@wrongstack/core');
2303
- const fs17 = await import('fs/promises');
2324
+ const { atomicWrite: atomicWrite8 } = await import('@wrongstack/core');
2325
+ const fs19 = await import('fs/promises');
2304
2326
  let existing = {};
2305
2327
  try {
2306
- const raw = await fs17.readFile(configPath2, "utf8");
2328
+ const raw = await fs19.readFile(configPath2, "utf8");
2307
2329
  existing = JSON.parse(raw);
2308
2330
  } catch {
2309
2331
  }
@@ -2311,7 +2333,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
2311
2333
  existing.provider = provider;
2312
2334
  existing.model = model;
2313
2335
  await backupCurrent(homeFn);
2314
- await atomicWrite7(configPath2, JSON.stringify(existing, null, 2));
2336
+ await atomicWrite8(configPath2, JSON.stringify(existing, null, 2));
2315
2337
  try {
2316
2338
  await appendHistory(
2317
2339
  oldCfg,
@@ -2440,8 +2462,12 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
2440
2462
  const defaultHint = defaultIdx !== void 0 && defaultProvider ? ` ${color.dim(`[Enter = ${defaultProvider}]`)}` : "";
2441
2463
  const providerAnswer = (await reader.readLine(
2442
2464
  `
2443
- ${color.amber("?")} Select provider (1-${ordered.length})${defaultHint}: `
2465
+ ${color.amber("?")} Select provider (1-${ordered.length})${defaultHint} ${color.dim("[q to quit]")}: `
2444
2466
  )).trim();
2467
+ if (providerAnswer.toLowerCase() === "q") {
2468
+ renderer.write(color.dim("Cancelled.\n"));
2469
+ return void 0;
2470
+ }
2445
2471
  if (!providerAnswer) {
2446
2472
  if (defaultIdx !== void 0) {
2447
2473
  const def = ordered[defaultIdx - 1];
@@ -2503,15 +2529,23 @@ async function pickModel(provider, registry, renderer, reader, defaultModel) {
2503
2529
  if (offset < models.length) {
2504
2530
  const more = (await reader.readLine(
2505
2531
  `
2506
- ${color.amber("?")} Showing ${Math.min(offset, models.length)}/${models.length} \u2014 Enter number or ${color.dim("Enter")} for more: `
2532
+ ${color.amber("?")} Showing ${Math.min(offset, models.length)}/${models.length} \u2014 Enter number, ${color.dim("Enter")} for more, or ${color.dim("q")} to quit: `
2507
2533
  )).trim();
2534
+ if (more.toLowerCase() === "q") {
2535
+ renderer.write(color.dim("Cancelled.\n"));
2536
+ return void 0;
2537
+ }
2508
2538
  if (!more) continue;
2509
2539
  return resolveModelSelection(more, models, provider, registry, renderer);
2510
2540
  }
2511
2541
  }
2512
2542
  const defaultHint = defaultIdxInModels >= 0 && defaultModel ? ` ${color.dim(`[Enter = ${defaultModel}]`)}` : "";
2513
2543
  const answer = (await reader.readLine(`
2514
- ${color.amber("?")} Select model (1-${models.length})${defaultHint}: `)).trim();
2544
+ ${color.amber("?")} Select model (1-${models.length})${defaultHint} ${color.dim("[q to quit]")}: `)).trim();
2545
+ if (answer.toLowerCase() === "q") {
2546
+ renderer.write(color.dim("Cancelled.\n"));
2547
+ return void 0;
2548
+ }
2515
2549
  if (!answer) {
2516
2550
  if (defaultIdxInModels >= 0 && defaultModel) {
2517
2551
  renderer.write(
@@ -2569,10 +2603,10 @@ async function detectPackageManager(root, declared) {
2569
2603
  const name = declared.split("@")[0];
2570
2604
  if (name) return name;
2571
2605
  }
2572
- if (await pathExists(path21.join(root, "pnpm-lock.yaml"))) return "pnpm";
2573
- if (await pathExists(path21.join(root, "bun.lockb"))) return "bun";
2574
- if (await pathExists(path21.join(root, "bun.lock"))) return "bun";
2575
- if (await pathExists(path21.join(root, "yarn.lock"))) return "yarn";
2606
+ if (await pathExists(path23.join(root, "pnpm-lock.yaml"))) return "pnpm";
2607
+ if (await pathExists(path23.join(root, "bun.lockb"))) return "bun";
2608
+ if (await pathExists(path23.join(root, "bun.lock"))) return "bun";
2609
+ if (await pathExists(path23.join(root, "yarn.lock"))) return "yarn";
2576
2610
  return "npm";
2577
2611
  }
2578
2612
  function hasUsableScript(scripts, name) {
@@ -2593,7 +2627,7 @@ function parseMakeTargets(makefile) {
2593
2627
  async function detectProjectFacts(root) {
2594
2628
  const facts = { hints: [] };
2595
2629
  try {
2596
- const pkg = JSON.parse(await fsp2.readFile(path21.join(root, "package.json"), "utf8"));
2630
+ const pkg = JSON.parse(await fsp2.readFile(path23.join(root, "package.json"), "utf8"));
2597
2631
  const scripts = pkg.scripts ?? {};
2598
2632
  const pm = await detectPackageManager(root, pkg.packageManager);
2599
2633
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2607,14 +2641,14 @@ async function detectProjectFacts(root) {
2607
2641
  } catch {
2608
2642
  }
2609
2643
  try {
2610
- if (!await pathExists(path21.join(root, "pyproject.toml"))) throw new Error("not python");
2644
+ if (!await pathExists(path23.join(root, "pyproject.toml"))) throw new Error("not python");
2611
2645
  facts.test ??= "pytest";
2612
2646
  facts.lint ??= "ruff check .";
2613
2647
  facts.hints.push("pyproject.toml");
2614
2648
  } catch {
2615
2649
  }
2616
2650
  try {
2617
- if (!await pathExists(path21.join(root, "go.mod"))) throw new Error("not go");
2651
+ if (!await pathExists(path23.join(root, "go.mod"))) throw new Error("not go");
2618
2652
  facts.build ??= "go build ./...";
2619
2653
  facts.test ??= "go test ./...";
2620
2654
  facts.run ??= "go run .";
@@ -2622,7 +2656,7 @@ async function detectProjectFacts(root) {
2622
2656
  } catch {
2623
2657
  }
2624
2658
  try {
2625
- if (!await pathExists(path21.join(root, "Cargo.toml"))) throw new Error("not rust");
2659
+ if (!await pathExists(path23.join(root, "Cargo.toml"))) throw new Error("not rust");
2626
2660
  facts.build ??= "cargo build";
2627
2661
  facts.test ??= "cargo test";
2628
2662
  facts.lint ??= "cargo clippy";
@@ -2631,7 +2665,7 @@ async function detectProjectFacts(root) {
2631
2665
  } catch {
2632
2666
  }
2633
2667
  try {
2634
- const makefile = await fsp2.readFile(path21.join(root, "Makefile"), "utf8");
2668
+ const makefile = await fsp2.readFile(path23.join(root, "Makefile"), "utf8");
2635
2669
  const targets = parseMakeTargets(makefile);
2636
2670
  facts.build ??= targets.has("build") ? "make build" : "make";
2637
2671
  if (targets.has("test")) facts.test ??= "make test";
@@ -3267,8 +3301,8 @@ function buildInitCommand(opts) {
3267
3301
  description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
3268
3302
  async run(args, ctx) {
3269
3303
  const force = args.trim() === "--force";
3270
- const dir = path21.join(ctx.projectRoot, ".wrongstack");
3271
- const file = path21.join(dir, "AGENTS.md");
3304
+ const dir = path23.join(ctx.projectRoot, ".wrongstack");
3305
+ const file = path23.join(dir, "AGENTS.md");
3272
3306
  try {
3273
3307
  await fsp2.access(file);
3274
3308
  if (!force) {
@@ -3769,21 +3803,24 @@ function buildAutonomyCommand(opts) {
3769
3803
  description: "Toggle or query autonomy mode (self-driving agent).",
3770
3804
  help: [
3771
3805
  "Usage:",
3772
- " /autonomy Show current autonomy status",
3773
- " /autonomy off Disabled \u2014 agent stops after each turn (default)",
3774
- " /autonomy suggest Show next-step suggestions after each turn",
3775
- " /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
3776
- " /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 off",
3806
+ " /autonomy Show current autonomy status",
3807
+ " /autonomy off Disabled \u2014 agent stops after each turn (default)",
3808
+ " /autonomy suggest Show next-step suggestions after each turn",
3809
+ " /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
3810
+ " /autonomy eternal Sittin-sene mode \u2014 runs forever against /goal",
3811
+ " /autonomy stop Stop eternal mode (no-op for other modes)",
3812
+ " /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 eternal \u2192 off",
3777
3813
  "",
3778
3814
  "Modes:",
3779
3815
  " off \u2014 Normal interactive mode. Agent stops and waits.",
3780
3816
  " suggest \u2014 After each turn, agent suggests next steps. You pick.",
3781
3817
  " auto \u2014 After each turn, agent picks the best next step and continues.",
3782
3818
  " Runs indefinitely until you press Esc or Ctrl+C.",
3819
+ " eternal \u2014 Goal-driven sense/decide/execute/reflect loop. Requires /goal.",
3820
+ " Force-enables YOLO. Runs until /autonomy stop or Ctrl+C twice.",
3783
3821
  "",
3784
- "In auto mode the agent works autonomously. Press Esc to redirect,",
3785
- "Ctrl+C to stop. The agent suggests context-aware next steps based on",
3786
- "the conversation history."
3822
+ "In auto/eternal modes the agent works autonomously. Press Esc to redirect,",
3823
+ "Ctrl+C to stop the active iteration. /autonomy stop ends the eternal loop."
3787
3824
  ].join("\n"),
3788
3825
  async run(args) {
3789
3826
  const arg = args.trim().toLowerCase();
@@ -3792,14 +3829,64 @@ function buildAutonomyCommand(opts) {
3792
3829
  opts.renderer.writeWarning(msg2);
3793
3830
  return { message: msg2 };
3794
3831
  }
3795
- if (!arg) {
3832
+ if (!arg || arg === "status") {
3796
3833
  const current = opts.onAutonomy();
3797
3834
  const labels2 = {
3798
3835
  off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
3799
3836
  suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
3800
- auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`
3837
+ auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`,
3838
+ eternal: `${color.red("ETERNAL")} ${color.dim("(sittin-sene \u2014 goal-driven, YOLO, until /autonomy stop)")}`
3801
3839
  };
3802
- const msg2 = `Autonomy mode: ${labels2[current]}`;
3840
+ const lines = [`Autonomy mode: ${labels2[current]}`];
3841
+ try {
3842
+ const goal = await loadGoal(goalFilePath(opts.projectRoot));
3843
+ if (goal) {
3844
+ const u = summarizeUsage(goal);
3845
+ lines.push(color.dim(` Goal: ${goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal}`));
3846
+ lines.push(color.dim(` Engine state: ${goal.engineState} \xB7 iterations: ${goal.iterations} \xB7 journal: ${goal.journal.length}`));
3847
+ if (u.iterationsWithUsage > 0) {
3848
+ lines.push(
3849
+ color.dim(
3850
+ ` Spent: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens`
3851
+ )
3852
+ );
3853
+ }
3854
+ const recent = goal.journal.slice(-10);
3855
+ const failed = recent.filter((e) => e.status === "failure").length;
3856
+ if (failed > 0) {
3857
+ lines.push(color.amber(` Recent failures: ${failed} of last ${recent.length} iterations`));
3858
+ }
3859
+ }
3860
+ } catch {
3861
+ }
3862
+ const msg2 = lines.join("\n");
3863
+ opts.renderer.write(msg2);
3864
+ return { message: msg2 };
3865
+ }
3866
+ if (arg === "stop" || arg === "halt" || arg === "kill") {
3867
+ if (!opts.onEternalStop) {
3868
+ const msg3 = "No eternal-mode controller wired in this session.";
3869
+ opts.renderer.writeWarning(msg3);
3870
+ return { message: msg3 };
3871
+ }
3872
+ opts.onEternalStop();
3873
+ opts.onAutonomy("off");
3874
+ let summaryLine = "";
3875
+ try {
3876
+ const goal = await loadGoal(goalFilePath(opts.projectRoot));
3877
+ if (goal) {
3878
+ const u = summarizeUsage(goal);
3879
+ if (u.iterationsWithUsage > 0) {
3880
+ summaryLine = "\n" + color.dim(
3881
+ ` Spent so far: $${u.totalCostUsd.toFixed(4)} \xB7 ${u.totalInputTokens} in / ${u.totalOutputTokens} out tokens \xB7 ${goal.iterations} total iterations.`
3882
+ );
3883
+ } else if (goal.iterations > 0) {
3884
+ summaryLine = "\n" + color.dim(` Total iterations: ${goal.iterations}.`);
3885
+ }
3886
+ }
3887
+ } catch {
3888
+ }
3889
+ const msg2 = `${color.amber("Eternal mode stop requested.")} The current iteration will finish, then the loop exits.${summaryLine}`;
3803
3890
  opts.renderer.write(msg2);
3804
3891
  return { message: msg2 };
3805
3892
  }
@@ -3810,20 +3897,47 @@ function buildAutonomyCommand(opts) {
3810
3897
  newMode = "off";
3811
3898
  } else if (arg === "suggest" || arg === "suggestions") {
3812
3899
  newMode = "suggest";
3900
+ } else if (arg === "eternal" || arg === "forever" || arg === "infinite" || arg === "sittinsene") {
3901
+ newMode = "eternal";
3813
3902
  } else if (arg === "toggle" || arg === "cycle") {
3814
3903
  const current = opts.onAutonomy() ?? "off";
3815
- const cycle = ["off", "suggest", "auto"];
3904
+ const cycle = ["off", "suggest", "auto", "eternal"];
3816
3905
  newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
3817
3906
  } else {
3818
- const msg2 = `Unknown argument: ${arg}. Use /autonomy on, /autonomy off, /autonomy suggest, or /autonomy toggle.`;
3907
+ const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, stop, or toggle.`;
3819
3908
  opts.renderer.writeWarning(msg2);
3820
3909
  return { message: msg2 };
3821
3910
  }
3911
+ if (newMode === "eternal") {
3912
+ const goal = await loadGoal(goalFilePath(opts.projectRoot));
3913
+ if (!goal) {
3914
+ const msg3 = `${color.red("Eternal mode requires a goal.")} Run \`/goal set <mission>\` first.`;
3915
+ opts.renderer.writeWarning(msg3);
3916
+ return { message: msg3 };
3917
+ }
3918
+ if (!opts.onEternalStart) {
3919
+ const msg3 = "Eternal mode controller is not wired in this session.";
3920
+ opts.renderer.writeWarning(msg3);
3921
+ return { message: msg3 };
3922
+ }
3923
+ if (opts.onYolo) opts.onYolo(true);
3924
+ opts.onAutonomy(newMode);
3925
+ opts.onEternalStart();
3926
+ const msg2 = `${color.red("Autonomy mode: ETERNAL")} \u2014 engine launching against goal: ${color.bold(goal.goal)}
3927
+ ${color.dim("YOLO forced ON. Use /autonomy stop to end. Journal at /goal journal.")}`;
3928
+ opts.renderer.write(msg2);
3929
+ return { message: msg2 };
3930
+ }
3931
+ const previous = opts.onAutonomy();
3932
+ if (previous === "eternal" && opts.onEternalStop) {
3933
+ opts.onEternalStop();
3934
+ }
3822
3935
  opts.onAutonomy(newMode);
3823
3936
  const labels = {
3824
3937
  off: `${color.green("OFF")} \u2014 agent stops after each turn`,
3825
3938
  suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
3826
- auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`
3939
+ auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`,
3940
+ eternal: `${color.red("ETERNAL")} \u2014 goal-driven sittin-sene loop`
3827
3941
  };
3828
3942
  const msg = `Autonomy mode: ${labels[newMode]}`;
3829
3943
  opts.renderer.write(msg);
@@ -3831,6 +3945,124 @@ function buildAutonomyCommand(opts) {
3831
3945
  }
3832
3946
  };
3833
3947
  }
3948
+ var KNOWN_VERBS = /* @__PURE__ */ new Set([
3949
+ "",
3950
+ "show",
3951
+ "status",
3952
+ "set",
3953
+ "new",
3954
+ "clear",
3955
+ "reset",
3956
+ "journal",
3957
+ "log"
3958
+ ]);
3959
+ function buildGoalCommand(opts) {
3960
+ return {
3961
+ name: "goal",
3962
+ description: "Set, inspect, or clear the long-running autonomous mission used by /autonomy eternal.",
3963
+ help: [
3964
+ "Usage:",
3965
+ " /goal Show current goal + recent journal",
3966
+ " /goal set <text> Set a new goal (overwrites previous)",
3967
+ " /goal clear Clear the goal (stops eternal mode if running)",
3968
+ " /goal status Same as /goal (alias)",
3969
+ " /goal journal [N] Show last N journal entries (default 25)",
3970
+ "",
3971
+ "Goals live in <projectRoot>/.wrongstack/goal.json and persist across sessions.",
3972
+ "A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
3973
+ "every iteration to decide what to do next."
3974
+ ].join("\n"),
3975
+ async run(args) {
3976
+ const trimmed = args.trim();
3977
+ const [verbRaw, ...rest] = trimmed.split(/\s+/);
3978
+ const verb = (verbRaw ?? "").toLowerCase();
3979
+ const restJoined = rest.join(" ").trim();
3980
+ const goalPath = goalFilePath(opts.projectRoot);
3981
+ const verbForDispatch = verb && !KNOWN_VERBS.has(verb) ? "set" : verb;
3982
+ const setText = verbForDispatch === "set" && !KNOWN_VERBS.has(verb) ? trimmed : restJoined;
3983
+ switch (verbForDispatch) {
3984
+ case "":
3985
+ case "show":
3986
+ case "status": {
3987
+ const current = await loadGoal(goalPath);
3988
+ if (!current) {
3989
+ const msg2 = "No goal set. Use `/goal set <mission text>` to create one.";
3990
+ opts.renderer.write(msg2);
3991
+ return { message: msg2 };
3992
+ }
3993
+ const msg = formatGoal(current);
3994
+ opts.renderer.write(msg);
3995
+ return { message: msg };
3996
+ }
3997
+ case "set":
3998
+ case "new": {
3999
+ if (!setText) {
4000
+ const msg2 = "Usage: /goal set <mission text>";
4001
+ opts.renderer.writeWarning(msg2);
4002
+ return { message: msg2 };
4003
+ }
4004
+ const existing = await loadGoal(goalPath);
4005
+ const next = existing ? { ...existing, goal: setText, setAt: (/* @__PURE__ */ new Date()).toISOString(), lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() } : emptyGoal(setText);
4006
+ await saveGoal(goalPath, next);
4007
+ const shortGoal = setText.length > 80 ? `${setText.slice(0, 80)}\u2026` : setText;
4008
+ const msg = `\u{1F3AF} ${color.green("Goal locked:")} ${shortGoal}
4009
+ ${color.dim(`Stored in ${goalPath} \u2014 Esc / /steer to redirect, Ctrl+C to stop.`)}`;
4010
+ opts.renderer.write(msg);
4011
+ return { message: msg, runText: buildGoalPreamble(setText) };
4012
+ }
4013
+ case "clear":
4014
+ case "reset": {
4015
+ const existing = await loadGoal(goalPath);
4016
+ if (!existing) {
4017
+ const msg2 = "No goal to clear.";
4018
+ opts.renderer.write(msg2);
4019
+ return { message: msg2 };
4020
+ }
4021
+ const { unlink: unlink4 } = await import('fs/promises');
4022
+ try {
4023
+ await unlink4(goalPath);
4024
+ } catch {
4025
+ }
4026
+ if (opts.onEternalStop) opts.onEternalStop();
4027
+ const msg = `${color.amber("Goal cleared.")} Eternal mode will stop on next cycle.`;
4028
+ opts.renderer.write(msg);
4029
+ return { message: msg };
4030
+ }
4031
+ case "journal":
4032
+ case "log": {
4033
+ const current = await loadGoal(goalPath);
4034
+ if (!current) {
4035
+ const msg2 = "No goal set.";
4036
+ opts.renderer.write(msg2);
4037
+ return { message: msg2 };
4038
+ }
4039
+ const n = restJoined ? Math.max(1, Number.parseInt(restJoined, 10) || 25) : 25;
4040
+ if (current.journal.length === 0) {
4041
+ const msg2 = "Journal is empty.";
4042
+ opts.renderer.write(msg2);
4043
+ return { message: msg2 };
4044
+ }
4045
+ const tail = current.journal.slice(-n);
4046
+ const lines = tail.map((e) => {
4047
+ const mark = e.status === "success" ? color.green("\u2713") : e.status === "failure" ? color.red("\u2717") : e.status === "aborted" ? color.amber("\u2298") : color.dim("\xB7");
4048
+ const note = e.note ? color.dim(` \u2014 ${e.note}`) : "";
4049
+ return `${color.dim(`#${e.iteration}`)} ${mark} ${color.dim(`[${e.source}]`)} ${e.task}${note}`;
4050
+ });
4051
+ const header = `Journal (last ${tail.length} of ${current.journal.length}):`;
4052
+ const msg = `${header}
4053
+ ${lines.join("\n")}`;
4054
+ opts.renderer.write(msg);
4055
+ return { message: msg };
4056
+ }
4057
+ default: {
4058
+ const msg = `Unknown subcommand "${verb}". Try: show | set <text> | clear | journal [N]`;
4059
+ opts.renderer.writeWarning(msg);
4060
+ return { message: msg };
4061
+ }
4062
+ }
4063
+ }
4064
+ };
4065
+ }
3834
4066
 
3835
4067
  // src/slash-commands/mode.ts
3836
4068
  function buildModeCommand(opts) {
@@ -3940,6 +4172,15 @@ ${lines.join("\n\n")}
3940
4172
  }
3941
4173
  };
3942
4174
  }
4175
+ function getProviderFromContext(ctx, opts) {
4176
+ if (opts.llmProvider && typeof opts.llmProvider.complete === "function") {
4177
+ return { provider: opts.llmProvider, model: opts.llmModel };
4178
+ }
4179
+ if (ctx.provider && typeof ctx.provider.complete === "function") {
4180
+ return { provider: ctx.provider, model: ctx.model };
4181
+ }
4182
+ return null;
4183
+ }
3943
4184
  function buildSecurityCommand(opts) {
3944
4185
  return {
3945
4186
  name: "security",
@@ -3991,11 +4232,11 @@ async function handleScan(args, ctx, opts) {
3991
4232
  const options = parseArgs2(args);
3992
4233
  const projectRoot = ctx.projectRoot || opts.projectRoot;
3993
4234
  try {
3994
- const orchestratorContext = ctx.provider ? ctx : { provider: opts.llmProvider, model: opts.llmModel };
3995
- if (!orchestratorContext.provider) {
4235
+ const providerInfo = getProviderFromContext(ctx, opts);
4236
+ if (!providerInfo) {
3996
4237
  return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
3997
4238
  }
3998
- const result = await defaultOrchestrator.run(orchestratorContext, {
4239
+ const result = await defaultOrchestrator.run(providerInfo, {
3999
4240
  projectRoot,
4000
4241
  scanOptions: {
4001
4242
  depth: options.depth || "standard",
@@ -4044,11 +4285,11 @@ async function handleScan(args, ctx, opts) {
4044
4285
  async function handleAudit(ctx, opts) {
4045
4286
  const projectRoot = ctx.projectRoot || opts.projectRoot;
4046
4287
  try {
4047
- const orchestratorContext = ctx.provider ? ctx : { provider: opts.llmProvider, model: opts.llmModel };
4048
- if (!orchestratorContext.provider) {
4288
+ const providerInfo = getProviderFromContext(ctx, opts);
4289
+ if (!providerInfo) {
4049
4290
  return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
4050
4291
  }
4051
- const result = await defaultOrchestrator.run(orchestratorContext, {
4292
+ const result = await defaultOrchestrator.run(providerInfo, {
4052
4293
  projectRoot,
4053
4294
  reportOptions: { format: "markdown" }
4054
4295
  });
@@ -4159,12 +4400,111 @@ function getHelpMessage() {
4159
4400
 
4160
4401
  Run \`/security scan\` to start.`;
4161
4402
  }
4403
+ var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
4404
+ var DEFAULTS = {
4405
+ todos: true,
4406
+ plan: true,
4407
+ fleet: true,
4408
+ git: true,
4409
+ elapsed: true,
4410
+ context: true,
4411
+ cost: true
4412
+ };
4413
+ function resolveConfigPath() {
4414
+ return process.env[CONFIG_ENV] ?? path23.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
4415
+ }
4416
+ async function loadStatuslineConfig() {
4417
+ const p = resolveConfigPath();
4418
+ try {
4419
+ const raw = await fsp2.readFile(p, "utf8");
4420
+ return { ...DEFAULTS, ...JSON.parse(raw) };
4421
+ } catch {
4422
+ return { ...DEFAULTS };
4423
+ }
4424
+ }
4425
+ async function saveStatuslineConfig(cfg) {
4426
+ const p = resolveConfigPath();
4427
+ await fsp2.mkdir(path23.dirname(p), { recursive: true });
4428
+ await atomicWrite(p, JSON.stringify(cfg, null, 2));
4429
+ }
4430
+ function buildStatuslineCommand(deps) {
4431
+ return {
4432
+ name: "statusline",
4433
+ aliases: ["sl"],
4434
+ description: "Customize status bar chips: /statusline [item] [on|off] or /statusline reset",
4435
+ help: [
4436
+ "Usage: /statusline [item] [on|off]",
4437
+ " /statusline \u2014 show current config",
4438
+ " /statusline <item> on \u2014 enable a chip",
4439
+ " /statusline <item> off \u2014 disable a chip",
4440
+ " /statusline reset \u2014 restore defaults",
4441
+ "",
4442
+ "Available items: todos, plan, fleet, git, elapsed, context, cost",
4443
+ "Persistent across sessions (saved to ~/.wrongstack/statusline.json)."
4444
+ ].join("\n"),
4445
+ async run(args) {
4446
+ const cfg = await deps.getConfig();
4447
+ const trimmed = args.trim();
4448
+ const parts = trimmed.split(/\s+/);
4449
+ const [item, action] = parts;
4450
+ if (!item) {
4451
+ const lines = ["StatusBar chips:"];
4452
+ const items = [
4453
+ "todos",
4454
+ "plan",
4455
+ "fleet",
4456
+ "git",
4457
+ "elapsed",
4458
+ "context",
4459
+ "cost"
4460
+ ];
4461
+ for (const k of items) {
4462
+ const val = cfg[k];
4463
+ if (val === void 0) continue;
4464
+ lines.push(` ${val ? "\u25CF" : "\u25CB"} ${k}`);
4465
+ }
4466
+ return { message: lines.join("\n") };
4467
+ }
4468
+ if (item === "reset") {
4469
+ await deps.setConfig({ ...DEFAULTS });
4470
+ deps.setHiddenItems([]);
4471
+ return { message: "StatusBar config reset to defaults." };
4472
+ }
4473
+ const validItems = [
4474
+ "todos",
4475
+ "plan",
4476
+ "fleet",
4477
+ "git",
4478
+ "elapsed",
4479
+ "context",
4480
+ "cost"
4481
+ ];
4482
+ if (!validItems.includes(item)) {
4483
+ return {
4484
+ message: `Unknown item "${item}". Available: ${validItems.join(", ")}. Usage: /statusline <item> on|off`
4485
+ };
4486
+ }
4487
+ const onOff = action?.toLowerCase();
4488
+ if (!onOff || onOff !== "on" && onOff !== "off") {
4489
+ return { message: `Usage: /statusline ${item} on|off` };
4490
+ }
4491
+ const next = { ...cfg, [item]: onOff === "on" };
4492
+ await deps.setConfig(next);
4493
+ if (onOff === "off") {
4494
+ deps.setHiddenItems([...deps.hiddenItems, item]);
4495
+ } else {
4496
+ deps.setHiddenItems(deps.hiddenItems.filter((i) => i !== item));
4497
+ }
4498
+ return { message: `statusline ${item}: ${onOff}` };
4499
+ }
4500
+ };
4501
+ }
4162
4502
  function makeInstaller(opts, projectRoot, global) {
4163
- const globalRoot = path21.join(os6.homedir(), ".wrongstack");
4503
+ const globalRoot = path23.join(os6.homedir(), ".wrongstack");
4164
4504
  return new SkillInstaller({
4165
- manifestPath: path21.join(globalRoot, "installed-skills.json"),
4166
- projectSkillsDir: path21.join(projectRoot, ".wrongstack", "skills"),
4167
- globalSkillsDir: path21.join(globalRoot, "skills"),
4505
+ manifestPath: path23.join(globalRoot, "installed-skills.json"),
4506
+ projectSkillsDir: path23.join(projectRoot, ".wrongstack", "skills"),
4507
+ globalSkillsDir: path23.join(globalRoot, "skills"),
4168
4508
  projectHash: projectHash(projectRoot),
4169
4509
  skillLoader: opts.skillLoader
4170
4510
  });
@@ -4352,12 +4692,22 @@ function buildBuiltinSlashCommands(opts) {
4352
4692
  buildLoadCommand(opts),
4353
4693
  buildYoloCommand(opts),
4354
4694
  buildAutonomyCommand(opts),
4695
+ buildGoalCommand(opts),
4355
4696
  buildModeCommand(opts),
4356
4697
  buildExitCommand(opts),
4357
4698
  buildCommitCommand(),
4358
4699
  buildGitcheckCommand(),
4359
4700
  buildPushCommand(),
4360
- buildSecurityCommand(opts)
4701
+ buildSecurityCommand(opts),
4702
+ buildStatuslineCommand({
4703
+ cwd: opts.cwd,
4704
+ hiddenItems: opts.statuslineHiddenItems ?? [],
4705
+ setHiddenItems: opts.setStatuslineHiddenItems ?? (() => {
4706
+ }),
4707
+ getConfig: opts.statuslineConfig?.get ?? (async () => ({})),
4708
+ setConfig: opts.statuslineConfig?.set ?? (async () => {
4709
+ })
4710
+ })
4361
4711
  ];
4362
4712
  }
4363
4713
 
@@ -4376,13 +4726,13 @@ var MANIFESTS = [
4376
4726
  ];
4377
4727
  async function detectProjectKind(projectRoot) {
4378
4728
  try {
4379
- await fsp2.access(path21.join(projectRoot, ".wrongstack", "AGENTS.md"));
4729
+ await fsp2.access(path23.join(projectRoot, ".wrongstack", "AGENTS.md"));
4380
4730
  return "initialized";
4381
4731
  } catch {
4382
4732
  }
4383
4733
  for (const m of MANIFESTS) {
4384
4734
  try {
4385
- await fsp2.access(path21.join(projectRoot, m));
4735
+ await fsp2.access(path23.join(projectRoot, m));
4386
4736
  return "project";
4387
4737
  } catch {
4388
4738
  }
@@ -4390,8 +4740,8 @@ async function detectProjectKind(projectRoot) {
4390
4740
  return "empty";
4391
4741
  }
4392
4742
  async function scaffoldAgentsMd(projectRoot) {
4393
- const dir = path21.join(projectRoot, ".wrongstack");
4394
- const file = path21.join(dir, "AGENTS.md");
4743
+ const dir = path23.join(projectRoot, ".wrongstack");
4744
+ const file = path23.join(dir, "AGENTS.md");
4395
4745
  const facts = await detectProjectFacts(projectRoot);
4396
4746
  const body = renderAgentsTemplate(facts);
4397
4747
  await fsp2.mkdir(dir, { recursive: true });
@@ -4404,7 +4754,7 @@ async function runProjectCheck(opts) {
4404
4754
  if (kind === "initialized") {
4405
4755
  renderer.write(
4406
4756
  `
4407
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path21.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
4757
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path23.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
4408
4758
  `
4409
4759
  );
4410
4760
  return true;
@@ -4416,8 +4766,12 @@ async function runProjectCheck(opts) {
4416
4766
  `
4417
4767
  );
4418
4768
  const answer2 = (await reader.readLine(
4419
- ` ${color.amber("?")} Scaffold ${color.bold("AGENTS.md")} now? ${color.dim("[y/N]")} `
4769
+ ` ${color.amber("?")} Scaffold ${color.bold("AGENTS.md")} now? ${color.dim("[y/N/q]")} `
4420
4770
  )).trim().toLowerCase();
4771
+ if (answer2 === "q") {
4772
+ renderer.write(color.dim(" Cancelled.\n"));
4773
+ return false;
4774
+ }
4421
4775
  if (answer2 === "y" || answer2 === "yes") {
4422
4776
  try {
4423
4777
  const file = await scaffoldAgentsMd(projectRoot);
@@ -4431,7 +4785,7 @@ async function runProjectCheck(opts) {
4431
4785
  }
4432
4786
  return true;
4433
4787
  }
4434
- const gitDir = path21.join(projectRoot, ".git");
4788
+ const gitDir = path23.join(projectRoot, ".git");
4435
4789
  let hasGit = false;
4436
4790
  try {
4437
4791
  await fsp2.access(gitDir);
@@ -4445,8 +4799,12 @@ async function runProjectCheck(opts) {
4445
4799
  `
4446
4800
  );
4447
4801
  const answer2 = (await reader.readLine(
4448
- ` ${color.amber("?")} No git repo found. ${color.bold("Initialize git?")} ${color.dim("[y/N]")} `
4802
+ ` ${color.amber("?")} No git repo found. ${color.bold("Initialize git?")} ${color.dim("[y/N/q]")} `
4449
4803
  )).trim().toLowerCase();
4804
+ if (answer2 === "q") {
4805
+ renderer.write(color.dim(" Cancelled.\n"));
4806
+ return false;
4807
+ }
4450
4808
  if (answer2 === "y" || answer2 === "yes") {
4451
4809
  try {
4452
4810
  const { spawn: spawn3 } = await import('child_process');
@@ -4468,8 +4826,8 @@ async function runProjectCheck(opts) {
4468
4826
  `
4469
4827
  );
4470
4828
  }
4471
- const answer = (await reader.readLine(` ${color.amber("?")} Continue anyway? ${color.dim("[Y/n]")} `)).trim().toLowerCase();
4472
- if (answer === "n" || answer === "no") {
4829
+ const answer = (await reader.readLine(` ${color.amber("?")} Continue anyway? ${color.dim("[Y/n/q]")} `)).trim().toLowerCase();
4830
+ if (answer === "q" || answer === "n" || answer === "no") {
4473
4831
  renderer.write(color.dim(" Cancelled.\n"));
4474
4832
  return false;
4475
4833
  }
@@ -4483,8 +4841,12 @@ async function runLaunchPrompts(opts) {
4483
4841
  } else {
4484
4842
  const answer = (await reader.readLine(
4485
4843
  `
4486
- ${color.amber("?")} Interactive mode: ${color.bold("T")}UI / ${color.bold("R")}EPL ${color.dim("[T/r]")} `
4844
+ ${color.amber("?")} Interactive mode: ${color.bold("T")}UI / ${color.bold("R")}EPL ${color.dim("[T/r/q]")} `
4487
4845
  )).trim().toLowerCase();
4846
+ if (answer === "q") {
4847
+ renderer.write(color.dim(" Goodbye!\n"));
4848
+ process.exit(0);
4849
+ }
4488
4850
  mode = answer === "r" || answer === "repl" ? "repl" : "tui";
4489
4851
  }
4490
4852
  let yolo;
@@ -4492,8 +4854,12 @@ async function runLaunchPrompts(opts) {
4492
4854
  yolo = yoloPinned;
4493
4855
  } else {
4494
4856
  const answer = (await reader.readLine(
4495
- ` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n]")} `
4857
+ ` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n/q]")} `
4496
4858
  )).trim().toLowerCase();
4859
+ if (answer === "q") {
4860
+ renderer.write(color.dim(" Goodbye!\n"));
4861
+ process.exit(0);
4862
+ }
4497
4863
  yolo = answer !== "n" && answer !== "no";
4498
4864
  }
4499
4865
  renderer.write(
@@ -4732,14 +5098,14 @@ function summarize(value, name) {
4732
5098
  if (typeof v === "object" && v !== null) {
4733
5099
  const o = v;
4734
5100
  if (name === "edit") {
4735
- const path22 = typeof o["path"] === "string" ? o["path"] : "";
5101
+ const path24 = typeof o["path"] === "string" ? o["path"] : "";
4736
5102
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
4737
- return `${path22} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
5103
+ return `${path24} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4738
5104
  }
4739
5105
  if (name === "write") {
4740
- const path22 = typeof o["path"] === "string" ? o["path"] : "";
5106
+ const path24 = typeof o["path"] === "string" ? o["path"] : "";
4741
5107
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
4742
- return bytes !== void 0 ? `${path22} ${bytes}B` : path22;
5108
+ return bytes !== void 0 ? `${path24} ${bytes}B` : path24;
4743
5109
  }
4744
5110
  if (typeof o["count"] === "number") {
4745
5111
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -4889,10 +5255,12 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
4889
5255
  deps.renderer.write(` ${color.bold("x")} Remove this provider entirely
4890
5256
  `);
4891
5257
  deps.renderer.write(` ${color.bold("b")} Back
5258
+ `);
5259
+ deps.renderer.write(` ${color.bold("q")} Quit
4892
5260
  `);
4893
5261
  const raw = (await deps.reader.readLine(`
4894
5262
  ${color.amber("?")} ${providerId} > `)).trim();
4895
- if (!raw || raw === "b" || raw === "back") return;
5263
+ if (!raw || raw === "b" || raw === "back" || raw === "q" || raw === "quit") return;
4896
5264
  const [verb, argRaw] = raw.split(/\s+/, 2);
4897
5265
  const arg = argRaw ? Number.parseInt(argRaw, 10) : Number.NaN;
4898
5266
  if (verb === "a" || verb === "add") {
@@ -4901,8 +5269,9 @@ ${color.amber("?")} ${providerId} > `)).trim();
4901
5269
  }
4902
5270
  if (verb === "x" || verb === "remove") {
4903
5271
  const confirm = (await deps.reader.readLine(
4904
- ` ${color.amber("?")} Remove provider "${providerId}" and ${keys.length} key(s)? ${color.dim("[y/N]")} `
5272
+ ` ${color.amber("?")} Remove provider "${providerId}" and ${keys.length} key(s)? ${color.dim("[y/N/q]")} `
4905
5273
  )).trim().toLowerCase();
5274
+ if (confirm === "q") continue;
4906
5275
  if (confirm === "y" || confirm === "yes") {
4907
5276
  await mutateProviders(deps, (all) => {
4908
5277
  delete all[providerId];
@@ -4940,8 +5309,9 @@ ${color.amber("?")} ${providerId} > `)).trim();
4940
5309
  }
4941
5310
  const target = keys[arg - 1];
4942
5311
  const confirm = (await deps.reader.readLine(
4943
- ` ${color.amber("?")} Delete key "${target.label}" (${maskedKey(target.apiKey)})? ${color.dim("[y/N]")} `
5312
+ ` ${color.amber("?")} Delete key "${target.label}" (${maskedKey(target.apiKey)})? ${color.dim("[y/N/q]")} `
4944
5313
  )).trim().toLowerCase();
5314
+ if (confirm === "q") continue;
4945
5315
  if (confirm !== "y" && confirm !== "yes") continue;
4946
5316
  await mutateProviders(deps, (all) => {
4947
5317
  const p = all[providerId];
@@ -5038,8 +5408,8 @@ async function addForNewProvider(deps) {
5038
5408
  deps.renderer.writeWarning("Catalog unavailable \u2014 falling back to manual entry.");
5039
5409
  }
5040
5410
  if (catalog.length === 0) {
5041
- const pid = (await deps.reader.readLine(` ${color.amber("?")} Provider id: `)).trim();
5042
- if (!pid) return;
5411
+ const pid = (await deps.reader.readLine(` ${color.amber("?")} Provider id ${color.dim("[q to quit]")}: `)).trim();
5412
+ if (!pid || pid === "q") return;
5043
5413
  const fam = (await deps.reader.readLine(
5044
5414
  ` ${color.amber("?")} Family (anthropic/openai/openai-compatible/google): `
5045
5415
  )).trim();
@@ -5059,8 +5429,9 @@ async function addForNewProvider(deps) {
5059
5429
  )
5060
5430
  );
5061
5431
  const filterRaw = (await deps.reader.readLine(
5062
- ` ${color.amber("?")} Filter ${color.dim('(substring of id/name, "s" for unsaved-only, empty = all)')}: `
5432
+ ` ${color.amber("?")} Filter ${color.dim('(substring, "s" for unsaved-only, q to quit)')}: `
5063
5433
  )).trim();
5434
+ if (filterRaw === "q") return;
5064
5435
  const filterLc = filterRaw.toLowerCase();
5065
5436
  const showUnsavedOnly = filterLc === "s" || filterLc === "unsaved";
5066
5437
  const matches = (p) => {
@@ -5114,9 +5485,9 @@ async function addForNewProvider(deps) {
5114
5485
  `);
5115
5486
  const answer = (await deps.reader.readLine(
5116
5487
  `
5117
- ${color.amber("?")} Pick (1-${ordered.length}) or type provider id: `
5488
+ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id ${color.dim("[q to quit]")}: `
5118
5489
  )).trim();
5119
- if (!answer) return;
5490
+ if (!answer || answer === "q") return;
5120
5491
  let chosen;
5121
5492
  const num = Number.parseInt(answer, 10);
5122
5493
  if (!Number.isNaN(num) && num >= 1 && num <= ordered.length) {
@@ -5133,7 +5504,8 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id: `
5133
5504
  Defaults from models.dev \u2014 press Enter to keep, or type a new value.
5134
5505
  `)
5135
5506
  );
5136
- const famRaw = (await deps.reader.readLine(` ${color.amber("?")} Family ${color.dim(`[${chosen.family}]`)}: `)).trim();
5507
+ const famRaw = (await deps.reader.readLine(` ${color.amber("?")} Family ${color.dim(`[${chosen.family}]`)} ${color.dim("(q to quit)")}: `)).trim();
5508
+ if (famRaw === "q") return;
5137
5509
  let family = chosen.family;
5138
5510
  if (famRaw) {
5139
5511
  if (!["anthropic", "openai", "openai-compatible", "google"].includes(famRaw)) {
@@ -5145,8 +5517,9 @@ ${color.amber("?")} Pick (1-${ordered.length}) or type provider id: `
5145
5517
  family = famRaw;
5146
5518
  }
5147
5519
  const baseRaw = (await deps.reader.readLine(
5148
- ` ${color.amber("?")} Base URL ${color.dim(`[${chosen.apiBase ?? "unset"}]`)}: `
5520
+ ` ${color.amber("?")} Base URL ${color.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color.dim("(q to quit)")}: `
5149
5521
  )).trim();
5522
+ if (baseRaw === "q") return;
5150
5523
  const baseUrl = baseRaw || chosen.apiBase;
5151
5524
  const providersNow = await loadProviders(deps);
5152
5525
  let suggestedAlias = chosen.id;
@@ -5191,17 +5564,18 @@ ${color.bold("Custom provider")} ${color.dim("\u2014 for local models or proxies
5191
5564
  `
5192
5565
  );
5193
5566
  const type = (await deps.reader.readLine(
5194
- ` ${color.amber("?")} Provider id ${color.dim('(e.g. "local-llama", "my-proxy")')}: `
5567
+ ` ${color.amber("?")} Provider id ${color.dim('(e.g. "local-llama", "my-proxy", q to quit)')}: `
5195
5568
  )).trim();
5196
- if (!type) return;
5569
+ if (!type || type === "q") return;
5197
5570
  const existing = (await loadProviders(deps))[type];
5198
5571
  if (existing) {
5199
5572
  deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
5200
5573
  return;
5201
5574
  }
5202
5575
  const familyRaw = (await deps.reader.readLine(
5203
- ` ${color.amber("?")} Wire family ${color.dim("(anthropic | openai | openai-compatible | google)")}: `
5576
+ ` ${color.amber("?")} Wire family ${color.dim("(anthropic | openai | openai-compatible | google)")} ${color.dim("(q to quit)")}: `
5204
5577
  )).trim();
5578
+ if (familyRaw === "q") return;
5205
5579
  if (!["anthropic", "openai", "openai-compatible", "google"].includes(familyRaw)) {
5206
5580
  deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
5207
5581
  return;
@@ -5336,13 +5710,21 @@ async function loadProviders(deps) {
5336
5710
  let raw;
5337
5711
  try {
5338
5712
  raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
5339
- } catch {
5713
+ } catch (err) {
5714
+ if (err.code !== "ENOENT") {
5715
+ deps.renderer.writeWarning(
5716
+ `Could not read ${deps.globalConfigPath}: ${err.message}. Treating as empty.`
5717
+ );
5718
+ }
5340
5719
  return {};
5341
5720
  }
5342
5721
  let parsed = {};
5343
5722
  try {
5344
5723
  parsed = JSON.parse(raw);
5345
- } catch {
5724
+ } catch (err) {
5725
+ deps.renderer.writeWarning(
5726
+ `Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`
5727
+ );
5346
5728
  return {};
5347
5729
  }
5348
5730
  const decrypted = decryptConfigSecrets(parsed, deps.vault);
@@ -5350,15 +5732,29 @@ async function loadProviders(deps) {
5350
5732
  }
5351
5733
  async function mutateProviders(deps, mutator) {
5352
5734
  let raw;
5735
+ let fileExists = true;
5353
5736
  try {
5354
5737
  raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
5355
- } catch {
5738
+ } catch (err) {
5739
+ if (err.code !== "ENOENT") {
5740
+ throw new Error(
5741
+ `Refusing to mutate ${deps.globalConfigPath}: ${err.message}`,
5742
+ { cause: err }
5743
+ );
5744
+ }
5745
+ fileExists = false;
5356
5746
  raw = "{}";
5357
5747
  }
5358
5748
  let parsed;
5359
5749
  try {
5360
5750
  parsed = JSON.parse(raw);
5361
- } catch {
5751
+ } catch (err) {
5752
+ if (fileExists) {
5753
+ throw new Error(
5754
+ `Refusing to overwrite corrupt config at ${deps.globalConfigPath} (${err.message}). Fix or move the file aside before retrying.`,
5755
+ { cause: err }
5756
+ );
5757
+ }
5362
5758
  parsed = {};
5363
5759
  }
5364
5760
  const decrypted = decryptConfigSecrets(parsed, deps.vault);
@@ -5562,7 +5958,7 @@ var doctorCmd = async (_args, deps) => {
5562
5958
  }
5563
5959
  try {
5564
5960
  await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
5565
- const probe = path21.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
5961
+ const probe = path23.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
5566
5962
  await fsp2.writeFile(probe, "");
5567
5963
  await fsp2.unlink(probe);
5568
5964
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -5665,8 +6061,8 @@ var exportCmd = async (args, deps) => {
5665
6061
  return 1;
5666
6062
  }
5667
6063
  if (output) {
5668
- await fsp2.mkdir(path21.dirname(path21.resolve(deps.cwd, output)), { recursive: true });
5669
- await fsp2.writeFile(path21.resolve(deps.cwd, output), rendered, "utf8");
6064
+ await fsp2.mkdir(path23.dirname(path23.resolve(deps.cwd, output)), { recursive: true });
6065
+ await fsp2.writeFile(path23.resolve(deps.cwd, output), rendered, "utf8");
5670
6066
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
5671
6067
  `);
5672
6068
  } else {
@@ -5695,7 +6091,12 @@ var initCmd = async (_args, deps) => {
5695
6091
  `
5696
6092
  );
5697
6093
  const defaultId = ranked[0]?.id ?? "anthropic";
5698
- const providerId = (await deps.reader.readLine(`Provider [${defaultId}]: `)).trim() || defaultId;
6094
+ const providerAnswer = (await deps.reader.readLine(`Provider [${defaultId}]: `)).trim();
6095
+ if (providerAnswer === "q") {
6096
+ deps.renderer.write(color.dim("Cancelled.\n"));
6097
+ return 0;
6098
+ }
6099
+ const providerId = providerAnswer || defaultId;
5699
6100
  const provider = await deps.modelsRegistry.getProvider(providerId);
5700
6101
  if (!provider) {
5701
6102
  deps.renderer.writeError(`Provider "${providerId}" not found in models.dev catalog.`);
@@ -5709,7 +6110,12 @@ var initCmd = async (_args, deps) => {
5709
6110
  }
5710
6111
  const suggestedModel = await deps.modelsRegistry.suggestModel(providerId) ?? "";
5711
6112
  const modelHint = suggestedModel ? ` [${suggestedModel}]` : "";
5712
- const modelId = (await deps.reader.readLine(`Model${modelHint}: `)).trim() || suggestedModel;
6113
+ const modelAnswer = (await deps.reader.readLine(`Model${modelHint}: `)).trim();
6114
+ if (modelAnswer === "q") {
6115
+ deps.renderer.write(color.dim("Cancelled.\n"));
6116
+ return 0;
6117
+ }
6118
+ const modelId = modelAnswer || suggestedModel;
5713
6119
  if (!modelId) {
5714
6120
  deps.renderer.writeError("No model selected. Aborting.");
5715
6121
  return 1;
@@ -5726,12 +6132,12 @@ var initCmd = async (_args, deps) => {
5726
6132
  await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
5727
6133
  const config = { version: 1, provider: providerId, model: modelId };
5728
6134
  if (apiKey) config.apiKey = apiKey;
5729
- const keyFile = path21.join(path21.dirname(deps.paths.globalConfig), ".key");
6135
+ const keyFile = path23.join(path23.dirname(deps.paths.globalConfig), ".key");
5730
6136
  const vault = new DefaultSecretVault$1({ keyFile });
5731
6137
  const encrypted = encryptConfigSecrets(config, vault);
5732
6138
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
5733
- await fsp2.mkdir(path21.join(deps.projectRoot, ".wrongstack"), { recursive: true });
5734
- const agentsFile = path21.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
6139
+ await fsp2.mkdir(path23.join(deps.projectRoot, ".wrongstack"), { recursive: true });
6140
+ const agentsFile = path23.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
5735
6141
  try {
5736
6142
  await fsp2.access(agentsFile);
5737
6143
  } catch {
@@ -6041,7 +6447,7 @@ var usageCmd = async (_args, deps) => {
6041
6447
  return 0;
6042
6448
  };
6043
6449
  var projectsCmd = async (_args, deps) => {
6044
- const projectsRoot = path21.join(deps.paths.globalRoot, "projects");
6450
+ const projectsRoot = path23.join(deps.paths.globalRoot, "projects");
6045
6451
  try {
6046
6452
  const entries = await fsp2.readdir(projectsRoot);
6047
6453
  if (entries.length === 0) {
@@ -6051,7 +6457,7 @@ var projectsCmd = async (_args, deps) => {
6051
6457
  for (const hash of entries) {
6052
6458
  try {
6053
6459
  const meta = JSON.parse(
6054
- await fsp2.readFile(path21.join(projectsRoot, hash, "meta.json"), "utf8")
6460
+ await fsp2.readFile(path23.join(projectsRoot, hash, "meta.json"), "utf8")
6055
6461
  );
6056
6462
  deps.renderer.write(
6057
6463
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -6212,7 +6618,7 @@ async function listFleetRuns(deps) {
6212
6618
  }
6213
6619
  const runs = [];
6214
6620
  for (const id of entries) {
6215
- const runDir = path21.join(deps.paths.projectSessions, id);
6621
+ const runDir = path23.join(deps.paths.projectSessions, id);
6216
6622
  let stat3;
6217
6623
  try {
6218
6624
  stat3 = await fsp2.stat(runDir);
@@ -6225,17 +6631,17 @@ async function listFleetRuns(deps) {
6225
6631
  let subagentCount = 0;
6226
6632
  let subagentsDir;
6227
6633
  try {
6228
- await fsp2.access(path21.join(runDir, "fleet.json"));
6634
+ await fsp2.access(path23.join(runDir, "fleet.json"));
6229
6635
  manifest = true;
6230
6636
  } catch {
6231
6637
  }
6232
6638
  try {
6233
- await fsp2.access(path21.join(runDir, "checkpoint.json"));
6639
+ await fsp2.access(path23.join(runDir, "checkpoint.json"));
6234
6640
  checkpoint = true;
6235
6641
  } catch {
6236
6642
  }
6237
6643
  try {
6238
- subagentsDir = path21.join(runDir, "subagents");
6644
+ subagentsDir = path23.join(runDir, "subagents");
6239
6645
  const files = await fsp2.readdir(subagentsDir);
6240
6646
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
6241
6647
  } catch {
@@ -6264,7 +6670,7 @@ async function listFleetRuns(deps) {
6264
6670
  return 0;
6265
6671
  }
6266
6672
  async function showFleetRun(runId, deps) {
6267
- const runDir = path21.join(deps.paths.projectSessions, runId);
6673
+ const runDir = path23.join(deps.paths.projectSessions, runId);
6268
6674
  let stat3;
6269
6675
  try {
6270
6676
  stat3 = await fsp2.stat(runDir);
@@ -6281,7 +6687,7 @@ async function showFleetRun(runId, deps) {
6281
6687
  deps.renderer.write(color.bold(`
6282
6688
  Fleet Run: ${runId}
6283
6689
  `) + "\n");
6284
- const manifestPath = path21.join(runDir, "fleet.json");
6690
+ const manifestPath = path23.join(runDir, "fleet.json");
6285
6691
  let manifestData = null;
6286
6692
  try {
6287
6693
  manifestData = await fsp2.readFile(manifestPath, "utf8");
@@ -6297,7 +6703,7 @@ Fleet Run: ${runId}
6297
6703
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
6298
6704
  `);
6299
6705
  }
6300
- const checkpointPath = path21.join(runDir, "checkpoint.json");
6706
+ const checkpointPath = path23.join(runDir, "checkpoint.json");
6301
6707
  let checkpointData = null;
6302
6708
  try {
6303
6709
  checkpointData = await fsp2.readFile(checkpointPath, "utf8");
@@ -6344,7 +6750,7 @@ Fleet Run: ${runId}
6344
6750
  } catch {
6345
6751
  }
6346
6752
  }
6347
- const subagentsDir = path21.join(runDir, "subagents");
6753
+ const subagentsDir = path23.join(runDir, "subagents");
6348
6754
  let subagentFiles = [];
6349
6755
  try {
6350
6756
  subagentFiles = await fsp2.readdir(subagentsDir);
@@ -6356,7 +6762,7 @@ Fleet Run: ${runId}
6356
6762
  Subagent transcripts (${subagentFiles.length}):
6357
6763
  `);
6358
6764
  for (const f of subagentFiles.sort()) {
6359
- const filePath = path21.join(subagentsDir, f);
6765
+ const filePath = path23.join(subagentsDir, f);
6360
6766
  let size;
6361
6767
  try {
6362
6768
  const s = await fsp2.stat(filePath);
@@ -6373,7 +6779,7 @@ Fleet Run: ${runId}
6373
6779
  ${color.dim("\u25CB")} No subagent transcripts
6374
6780
  `);
6375
6781
  }
6376
- const sharedDir = path21.join(runDir, "shared");
6782
+ const sharedDir = path23.join(runDir, "shared");
6377
6783
  try {
6378
6784
  const files = await fsp2.readdir(sharedDir);
6379
6785
  deps.renderer.write(`
@@ -6529,7 +6935,7 @@ function parseRewindFlags(args) {
6529
6935
  var rewindCmd = async (args, deps) => {
6530
6936
  const flags = parseRewindFlags(args);
6531
6937
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
6532
- const sessionsDir = path21.join(wpaths.globalRoot, "sessions");
6938
+ const sessionsDir = path23.join(wpaths.globalRoot, "sessions");
6533
6939
  const rewind = new DefaultSessionRewinder(sessionsDir);
6534
6940
  let sessionId = args.find((a) => !a.startsWith("--"));
6535
6941
  if (!sessionId) {
@@ -6672,6 +7078,7 @@ var helpCmd = async (_args, deps) => {
6672
7078
  "",
6673
7079
  " wstack Start REPL",
6674
7080
  ' wstack "<task>" Run task and exit',
7081
+ ' wstack --eternal "<mission>" Launch eternal-autonomy loop against a goal \u2014 Ctrl+C to stop',
6675
7082
  " wstack resume [<id>] Resume a session",
6676
7083
  " wstack sessions List recent sessions",
6677
7084
  " wstack init Pick provider + model from models.dev",
@@ -6739,22 +7146,22 @@ function fmtDuration(ms) {
6739
7146
  const remMin = m - h * 60;
6740
7147
  return `${h}h${remMin}m`;
6741
7148
  }
6742
- function fmtTaskResultLine(r, color33) {
7149
+ function fmtTaskResultLine(r, color34) {
6743
7150
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
6744
7151
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
6745
7152
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
6746
7153
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
6747
- const errKindChip = errKind ? color33.dim(` [${errKind}]`) : "";
6748
- const errSnip = errMsg || errKind ? `${errKindChip}${color33.dim(errTail)}` : "";
7154
+ const errKindChip = errKind ? color34.dim(` [${errKind}]`) : "";
7155
+ const errSnip = errMsg || errKind ? `${errKindChip}${color34.dim(errTail)}` : "";
6749
7156
  switch (r.status) {
6750
7157
  case "success":
6751
- return { mark: color33.green("\u2713"), stats, tail: "" };
7158
+ return { mark: color34.green("\u2713"), stats, tail: "" };
6752
7159
  case "timeout":
6753
- return { mark: color33.yellow("\u23F1"), stats: `${color33.yellow("timeout")} ${stats}`, tail: errSnip };
7160
+ return { mark: color34.yellow("\u23F1"), stats: `${color34.yellow("timeout")} ${stats}`, tail: errSnip };
6754
7161
  case "stopped":
6755
- return { mark: color33.dim("\u2298"), stats: `${color33.dim("stopped")} ${stats}`, tail: errSnip };
7162
+ return { mark: color34.dim("\u2298"), stats: `${color34.dim("stopped")} ${stats}`, tail: errSnip };
6756
7163
  case "failed":
6757
- return { mark: color33.red("\u2717"), stats: `${color33.red("failed")} ${stats}`, tail: errSnip };
7164
+ return { mark: color34.red("\u2717"), stats: `${color34.red("failed")} ${stats}`, tail: errSnip };
6758
7165
  }
6759
7166
  }
6760
7167
 
@@ -6764,7 +7171,7 @@ function resolveBundledSkillsDir() {
6764
7171
  try {
6765
7172
  const req2 = createRequire(import.meta.url);
6766
7173
  const corePkg = req2.resolve("@wrongstack/core/package.json");
6767
- return path21.join(path21.dirname(corePkg), "skills");
7174
+ return path23.join(path23.dirname(corePkg), "skills");
6768
7175
  } catch {
6769
7176
  return void 0;
6770
7177
  }
@@ -6917,6 +7324,7 @@ async function boot(argv) {
6917
7324
  init_sdd();
6918
7325
  async function runRepl(opts) {
6919
7326
  if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
7327
+ await renderGoalBanner(opts);
6920
7328
  let activeCtrl;
6921
7329
  let interrupts = 0;
6922
7330
  const onSigint = () => {
@@ -6925,6 +7333,12 @@ async function runRepl(opts) {
6925
7333
  opts.renderer.writeWarning("Exiting.");
6926
7334
  process.exit(130);
6927
7335
  }
7336
+ const engine = opts.getEternalEngine?.();
7337
+ if (engine && opts.getAutonomy?.() === "eternal") {
7338
+ engine.stop();
7339
+ opts.renderer.writeWarning("Eternal mode stop requested. Press Ctrl+C again to exit.");
7340
+ return;
7341
+ }
6928
7342
  if (activeCtrl) {
6929
7343
  activeCtrl.abort();
6930
7344
  opts.renderer.writeWarning("Iteration cancelled. Press Ctrl+C again to exit.");
@@ -6936,6 +7350,42 @@ async function runRepl(opts) {
6936
7350
  const builder = new InputBuilder({ store: opts.attachments });
6937
7351
  try {
6938
7352
  for (; ; ) {
7353
+ if (opts.getAutonomy?.() === "eternal") {
7354
+ const engine = opts.getEternalEngine?.();
7355
+ if (!engine) {
7356
+ opts.renderer.writeWarning("Eternal mode set but no engine wired \u2014 falling back to off.");
7357
+ } else {
7358
+ const beforeGoal = await loadGoalSafe(opts);
7359
+ const beforeIter = beforeGoal?.iterations ?? 0;
7360
+ opts.renderer.write(
7361
+ color.dim(`
7362
+ \u21B3 [eternal #${beforeIter + 1}] running iteration\u2026
7363
+ `)
7364
+ );
7365
+ interrupts = 0;
7366
+ try {
7367
+ const ok = await engine.runOneIteration();
7368
+ const afterGoal = await loadGoalSafe(opts);
7369
+ const last = afterGoal?.journal[afterGoal.journal.length - 1];
7370
+ if (!ok && !last) {
7371
+ opts.renderer.write(color.dim(" \u21B3 [eternal] iteration produced no progress.\n"));
7372
+ } else if (last) {
7373
+ const mark = last.status === "success" ? color.green("\u2713") : last.status === "failure" ? color.red("\u2717") : color.amber("\u2298");
7374
+ const tail = last.note ? color.dim(` \u2014 ${last.note.slice(0, 80)}`) : "";
7375
+ opts.renderer.write(
7376
+ ` ${mark} ${color.dim(`#${last.iteration}`)} ${color.dim(`[${last.source}]`)} ${last.task}${tail}
7377
+ `
7378
+ );
7379
+ }
7380
+ } catch (err) {
7381
+ opts.renderer.writeError(
7382
+ `[eternal] ${err instanceof Error ? err.message : String(err)}`
7383
+ );
7384
+ }
7385
+ await new Promise((resolve4) => setTimeout(resolve4, 250));
7386
+ continue;
7387
+ }
7388
+ }
6939
7389
  let raw;
6940
7390
  try {
6941
7391
  raw = await readPossiblyMultiline(opts);
@@ -6948,6 +7398,10 @@ async function runRepl(opts) {
6948
7398
  continue;
6949
7399
  }
6950
7400
  interrupts = 0;
7401
+ if (trimmed === "q") {
7402
+ opts.renderer.write(color.dim(" Goodbye!\n"));
7403
+ break;
7404
+ }
6951
7405
  if (trimmed === "/image" || trimmed === "/paste-image" || raw === "\x1Bv") {
6952
7406
  await pasteClipboardImage(builder, opts);
6953
7407
  continue;
@@ -7219,6 +7673,48 @@ async function pasteClipboardImage(builder, opts) {
7219
7673
  );
7220
7674
  }
7221
7675
  }
7676
+ async function loadGoalSafe(opts) {
7677
+ if (!opts.projectRoot) return null;
7678
+ try {
7679
+ return await loadGoal(goalFilePath(opts.projectRoot));
7680
+ } catch {
7681
+ return null;
7682
+ }
7683
+ }
7684
+ async function renderGoalBanner(opts) {
7685
+ const goal = await loadGoalSafe(opts);
7686
+ if (!goal) return;
7687
+ const summary = goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal;
7688
+ opts.renderer.write(
7689
+ color.dim("Goal: ") + color.bold(summary) + color.dim(` (iter ${goal.iterations})`) + "\n"
7690
+ );
7691
+ if (goal.engineState === "running") {
7692
+ opts.renderer.write(
7693
+ color.amber(" \u21BA Eternal engine was running when last session ended.") + "\n"
7694
+ );
7695
+ try {
7696
+ const answer = (await opts.reader.readLine(color.dim(" Resume eternal mode? [y/N] "))).trim().toLowerCase();
7697
+ if (answer === "y" || answer === "yes") {
7698
+ try {
7699
+ await opts.slashRegistry.dispatch("/autonomy eternal", opts.agent.ctx);
7700
+ } catch (err) {
7701
+ opts.renderer.writeError(
7702
+ `Auto-resume failed: ${err instanceof Error ? err.message : String(err)}`
7703
+ );
7704
+ }
7705
+ } else {
7706
+ opts.renderer.write(
7707
+ color.dim(" Not resuming. Use `/autonomy eternal` later to continue.") + "\n"
7708
+ );
7709
+ }
7710
+ } catch {
7711
+ opts.renderer.write(
7712
+ color.dim(" Use `/autonomy eternal` to resume.") + "\n"
7713
+ );
7714
+ }
7715
+ }
7716
+ opts.renderer.write("\n");
7717
+ }
7222
7718
  async function readPossiblyMultiline(opts) {
7223
7719
  const firstPrompt = theme2.primary("\u203A ");
7224
7720
  const contPrompt = color.dim("\xB7 ");
@@ -7266,7 +7762,7 @@ function printBanner(renderer, projectName) {
7266
7762
  if (projectName && projectName.length > 0) {
7267
7763
  lines.push(color.dim("Project: ") + theme2.bold(projectName));
7268
7764
  }
7269
- lines.push(color.dim("Type /help for commands, /exit to quit."), "");
7765
+ lines.push(color.dim("Type /help for commands, /exit or q to quit."), "");
7270
7766
  renderer.write(`${lines.join("\n")}
7271
7767
  `);
7272
7768
  }
@@ -7302,8 +7798,12 @@ async function execute(deps) {
7302
7798
  director,
7303
7799
  fleetRoster,
7304
7800
  fleetStreamController,
7801
+ statuslineHiddenItems,
7802
+ setStatuslineHiddenItems,
7305
7803
  getYolo,
7306
7804
  getAutonomy,
7805
+ getEternalEngine,
7806
+ subscribeEternalIteration,
7307
7807
  skillLoader
7308
7808
  } = deps;
7309
7809
  let code = 0;
@@ -7411,6 +7911,9 @@ async function execute(deps) {
7411
7911
  queueStore,
7412
7912
  yolo: !!config.yolo,
7413
7913
  getYolo,
7914
+ getAutonomy,
7915
+ getEternalEngine,
7916
+ subscribeEternalIteration,
7414
7917
  appVersion: CLI_VERSION,
7415
7918
  provider: config.provider,
7416
7919
  family: banneredFamily,
@@ -7436,6 +7939,8 @@ async function execute(deps) {
7436
7939
  dispatch({ type: "resetContextChip" });
7437
7940
  },
7438
7941
  fleetStreamController,
7942
+ statuslineHiddenItems,
7943
+ setStatuslineHiddenItems,
7439
7944
  initialGoal: goalFlag,
7440
7945
  initialAsk: askFlag,
7441
7946
  getSDDContext: () => {
@@ -7479,7 +7984,8 @@ async function execute(deps) {
7479
7984
  session,
7480
7985
  port: Number.parseInt(String(flags.port ?? "3457"), 10),
7481
7986
  modelsRegistry,
7482
- globalConfigPath: wpaths.globalConfig
7987
+ globalConfigPath: wpaths.globalConfig,
7988
+ subscribeEternalIteration
7483
7989
  });
7484
7990
  try {
7485
7991
  code = await runRepl({
@@ -7492,8 +7998,10 @@ async function execute(deps) {
7492
7998
  supportsVision,
7493
7999
  attachments,
7494
8000
  effectiveMaxContext,
7495
- projectName: path21.basename(projectRoot) || void 0,
8001
+ projectName: path23.basename(projectRoot) || void 0,
8002
+ projectRoot,
7496
8003
  getAutonomy,
8004
+ getEternalEngine,
7497
8005
  skillLoader
7498
8006
  });
7499
8007
  } finally {
@@ -7510,7 +8018,7 @@ async function execute(deps) {
7510
8018
  supportsVision,
7511
8019
  attachments,
7512
8020
  effectiveMaxContext,
7513
- projectName: path21.basename(projectRoot) || void 0,
8021
+ projectName: path23.basename(projectRoot) || void 0,
7514
8022
  getAutonomy,
7515
8023
  skillLoader
7516
8024
  });
@@ -7601,7 +8109,7 @@ var MultiAgentHost = class {
7601
8109
  doneCondition: { type: "all_tasks_done" },
7602
8110
  maxConcurrent: 8
7603
8111
  };
7604
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
8112
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path23.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
7605
8113
  this.director = new Director({
7606
8114
  config: coordinatorConfig,
7607
8115
  manifestPath: this.opts.manifestPath,
@@ -7687,6 +8195,16 @@ var MultiAgentHost = class {
7687
8195
  model: subCfg.model ?? config.model,
7688
8196
  tools: this.filterTools(subCfg.tools)
7689
8197
  });
8198
+ const toolExecutor = new ToolExecutor(this.subagentToolRegistry(subCfg.tools), {
8199
+ permissionPolicy: new AutoApprovePermissionPolicy(),
8200
+ secretScrubber: this.deps.secretScrubber,
8201
+ renderer: this.deps.renderer,
8202
+ events,
8203
+ confirmAwaiter: void 0,
8204
+ iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
8205
+ perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
8206
+ tracer: void 0
8207
+ });
7690
8208
  const agent = new Agent({
7691
8209
  container: this.deps.container,
7692
8210
  tools: this.subagentToolRegistry(subCfg.tools),
@@ -7698,7 +8216,8 @@ var MultiAgentHost = class {
7698
8216
  // run under a director, not the user. Auto-approve everything
7699
8217
  // (except tool-level hard denies); the user already authorized
7700
8218
  // the work when they invoked the leader.
7701
- permissionPolicy: new AutoApprovePermissionPolicy()
8219
+ permissionPolicy: new AutoApprovePermissionPolicy(),
8220
+ toolExecutor
7702
8221
  });
7703
8222
  const hostEvents = this.deps.events;
7704
8223
  const offToolBridge = events.on("tool.executed", (e) => {
@@ -7774,7 +8293,7 @@ var MultiAgentHost = class {
7774
8293
  model: opts?.model,
7775
8294
  tools: opts?.tools
7776
8295
  };
7777
- const transcriptPath = this.sessionFactory ? path21.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
8296
+ const transcriptPath = this.sessionFactory ? path23.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
7778
8297
  const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
7779
8298
  this.fleetManager?.addPendingTask(taskId, subagentId, description);
7780
8299
  this.deps.events.emit("subagent.spawned", {
@@ -7917,16 +8436,16 @@ var MultiAgentHost = class {
7917
8436
  if (this.director) return this.director;
7918
8437
  this.opts.directorMode = true;
7919
8438
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
7920
- this.opts.manifestPath = path21.join(this.opts.fleetRoot, "fleet.json");
8439
+ this.opts.manifestPath = path23.join(this.opts.fleetRoot, "fleet.json");
7921
8440
  }
7922
8441
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
7923
- this.opts.sharedScratchpadPath = path21.join(this.opts.fleetRoot, "shared");
8442
+ this.opts.sharedScratchpadPath = path23.join(this.opts.fleetRoot, "shared");
7924
8443
  }
7925
8444
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
7926
- this.opts.sessionsRoot = path21.join(this.opts.fleetRoot, "subagents");
8445
+ this.opts.sessionsRoot = path23.join(this.opts.fleetRoot, "subagents");
7927
8446
  }
7928
8447
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
7929
- this.opts.stateCheckpointPath = path21.join(this.opts.fleetRoot, "director-state.json");
8448
+ this.opts.stateCheckpointPath = path23.join(this.opts.fleetRoot, "director-state.json");
7930
8449
  }
7931
8450
  await this.ensureDirector();
7932
8451
  return this.director ?? null;
@@ -8047,11 +8566,11 @@ var SessionStats = class {
8047
8566
  if (e.name === "bash") this.bashCommands++;
8048
8567
  else if (e.name === "fetch") this.fetches++;
8049
8568
  if (!e.ok) return;
8050
- const path22 = typeof input?.path === "string" ? input.path : void 0;
8051
- if (e.name === "read" && path22) this.readPaths.add(path22);
8052
- else if (e.name === "edit" && path22) this.editedPaths.add(path22);
8053
- else if (e.name === "write" && path22) {
8054
- this.writtenPaths.add(path22);
8569
+ const path24 = typeof input?.path === "string" ? input.path : void 0;
8570
+ if (e.name === "read" && path24) this.readPaths.add(path24);
8571
+ else if (e.name === "edit" && path24) this.editedPaths.add(path24);
8572
+ else if (e.name === "write" && path24) {
8573
+ this.writtenPaths.add(path24);
8055
8574
  const content = typeof input?.content === "string" ? input.content : "";
8056
8575
  this.bytesWritten += Buffer.byteLength(content, "utf8");
8057
8576
  }
@@ -8277,6 +8796,19 @@ async function setupCompaction(params) {
8277
8796
  return { effectiveMaxContext, autoCompactor };
8278
8797
  }
8279
8798
  function createAgent(params) {
8799
+ const secretScrubber = params.container.resolve(TOKENS.SecretScrubber);
8800
+ const renderer = params.container.has(TOKENS.Renderer) ? params.container.resolve(TOKENS.Renderer) : void 0;
8801
+ params.container.resolve(TOKENS.Logger);
8802
+ const toolExecutor = new ToolExecutor(params.tools, {
8803
+ permissionPolicy: params.permissionPolicy ?? params.container.resolve(TOKENS.PermissionPolicy),
8804
+ secretScrubber,
8805
+ renderer,
8806
+ events: params.events,
8807
+ confirmAwaiter: params.confirmAwaiter,
8808
+ iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
8809
+ perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
8810
+ tracer: params.tracer
8811
+ });
8280
8812
  return new Agent({
8281
8813
  container: params.container,
8282
8814
  tools: params.tools,
@@ -8288,8 +8820,143 @@ function createAgent(params) {
8288
8820
  iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
8289
8821
  executionStrategy: params.config.tools.defaultExecutionStrategy,
8290
8822
  perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
8291
- confirmAwaiter: params.confirmAwaiter
8823
+ confirmAwaiter: params.confirmAwaiter,
8824
+ toolExecutor,
8825
+ tracer: params.tracer
8826
+ });
8827
+ }
8828
+ function setupMetrics(params) {
8829
+ const { flags, wpaths, events, logger, config } = params;
8830
+ let metricsSink;
8831
+ let healthRegistry;
8832
+ let metricsServerHandle;
8833
+ const metricsPortFlag = flags["metrics-port"];
8834
+ const metricsPort = typeof metricsPortFlag === "string" && metricsPortFlag.length > 0 ? Number.parseInt(metricsPortFlag, 10) : void 0;
8835
+ if (metricsPort !== void 0 && !flags.metrics) flags.metrics = true;
8836
+ if (!flags.metrics) return { metricsSink, healthRegistry, metricsServerHandle };
8837
+ metricsSink = new InMemoryMetricsSink();
8838
+ wireMetricsToEvents(events, metricsSink);
8839
+ healthRegistry = new DefaultHealthRegistry();
8840
+ healthRegistry.register({
8841
+ name: "session-store",
8842
+ check: async () => {
8843
+ try {
8844
+ await fsp2.access(wpaths.projectSessions);
8845
+ return { status: "healthy" };
8846
+ } catch (e) {
8847
+ return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
8848
+ }
8849
+ }
8292
8850
  });
8851
+ healthRegistry.register({
8852
+ name: "provider",
8853
+ check: async () => ({
8854
+ status: "healthy",
8855
+ data: { id: config.provider, model: config.model }
8856
+ })
8857
+ });
8858
+ const dumpMetrics = () => {
8859
+ if (!metricsSink) return;
8860
+ try {
8861
+ const out = path23.join(wpaths.projectSessions, "metrics.json");
8862
+ const snap = metricsSink.snapshot();
8863
+ writeFileSync(out, JSON.stringify(snap, null, 2));
8864
+ } catch {
8865
+ }
8866
+ };
8867
+ process.on("exit", dumpMetrics);
8868
+ if (metricsPort !== void 0 && Number.isFinite(metricsPort)) {
8869
+ try {
8870
+ metricsServerHandle = startMetricsServer({
8871
+ port: metricsPort,
8872
+ host: process.env["METRICS_HOST"] ?? "127.0.0.1",
8873
+ sink: metricsSink,
8874
+ healthRegistry
8875
+ });
8876
+ logger.info(
8877
+ `metrics endpoint listening on ${metricsServerHandle.url} (healthz on same port)`
8878
+ );
8879
+ process.on("exit", () => {
8880
+ void metricsServerHandle?.close().catch(() => {
8881
+ });
8882
+ });
8883
+ } catch (err) {
8884
+ logger.warn(
8885
+ `metrics endpoint failed to start: ${err instanceof Error ? err.message : String(err)}`
8886
+ );
8887
+ }
8888
+ }
8889
+ return { metricsSink, healthRegistry, metricsServerHandle };
8890
+ }
8891
+ function createApi(ownerName, base) {
8892
+ return new DefaultPluginAPI({ ownerName, ...base });
8893
+ }
8894
+
8895
+ // src/wiring/plugins.ts
8896
+ async function setupPlugins(params) {
8897
+ const {
8898
+ config,
8899
+ container,
8900
+ events,
8901
+ toolRegistry,
8902
+ providerRegistry,
8903
+ slashCommandRegistry,
8904
+ mcpRegistry,
8905
+ log,
8906
+ agent,
8907
+ sessionWriter,
8908
+ metricsSink,
8909
+ configStore,
8910
+ pipelines
8911
+ } = params;
8912
+ if (!config.features.plugins || !config.plugins || config.plugins.length === 0) return;
8913
+ const resolvedPlugins = [];
8914
+ for (const p of config.plugins) {
8915
+ if (typeof p === "object" && p.enabled === false) continue;
8916
+ const spec = typeof p === "string" ? p : p.name;
8917
+ try {
8918
+ const mod = await import(spec);
8919
+ if (mod.default) resolvedPlugins.push(mod.default);
8920
+ } catch (err) {
8921
+ log.warn(`Plugin "${spec}" failed to load`, err);
8922
+ }
8923
+ }
8924
+ if (resolvedPlugins.length === 0) return;
8925
+ const pluginOptions = buildPluginOptions(config);
8926
+ const pluginConfig = Object.keys(pluginOptions).length > 0 ? patchConfig(config, { extensions: pluginOptions }) : config;
8927
+ await loadPlugins(resolvedPlugins, {
8928
+ log,
8929
+ pluginOptions,
8930
+ apiFactory: (plugin) => createApi(plugin.name, {
8931
+ container,
8932
+ events,
8933
+ pipelines,
8934
+ toolRegistry,
8935
+ providerRegistry,
8936
+ slashCommandRegistry,
8937
+ mcpRegistry,
8938
+ config: pluginConfig,
8939
+ log,
8940
+ extensions: agent.extensions,
8941
+ sessionWriter: {
8942
+ transcriptPath: sessionWriter.transcriptPath,
8943
+ append: (e) => sessionWriter.append(e)
8944
+ },
8945
+ metricsSink,
8946
+ configStore
8947
+ })
8948
+ });
8949
+ }
8950
+ function buildPluginOptions(config) {
8951
+ const options = {};
8952
+ for (const entry of config.plugins ?? []) {
8953
+ if (typeof entry !== "object") continue;
8954
+ if (entry.options) options[entry.name] = { ...entry.options };
8955
+ }
8956
+ for (const [name, value] of Object.entries(config.extensions ?? {})) {
8957
+ options[name] = { ...options[name] ?? {}, ...value };
8958
+ }
8959
+ return options;
8293
8960
  }
8294
8961
  async function setupProvider(params) {
8295
8962
  const { config, modelsRegistry, logger } = params;
@@ -8382,12 +9049,12 @@ async function setupSession(params) {
8382
9049
  }
8383
9050
  const sessionRef = { current: session };
8384
9051
  await recoveryLock.write(session.id).catch(() => void 0);
8385
- const attachments = new DefaultAttachmentStore({ spoolDir: path21.join(wpaths.projectSessions, session.id, "attachments") });
8386
- const queueStore = new QueueStore({ dir: path21.join(wpaths.projectSessions, session.id) });
9052
+ const attachments = new DefaultAttachmentStore({ spoolDir: path23.join(wpaths.projectSessions, session.id, "attachments") });
9053
+ const queueStore = new QueueStore({ dir: path23.join(wpaths.projectSessions, session.id) });
8387
9054
  const ctxSignal = new AbortController().signal;
8388
9055
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
8389
9056
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
8390
- const todosCheckpointPath = path21.join(wpaths.projectSessions, `${session.id}.todos.json`);
9057
+ const todosCheckpointPath = path23.join(wpaths.projectSessions, `${session.id}.todos.json`);
8391
9058
  if (resumeId) {
8392
9059
  try {
8393
9060
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -8399,13 +9066,13 @@ async function setupSession(params) {
8399
9066
  }
8400
9067
  }
8401
9068
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
8402
- const planPath = path21.join(wpaths.projectSessions, `${session.id}.plan.json`);
9069
+ const planPath = path23.join(wpaths.projectSessions, `${session.id}.plan.json`);
8403
9070
  context.state.setMeta("plan.path", planPath);
8404
9071
  let dirState;
8405
9072
  if (resumeId) {
8406
9073
  try {
8407
- const fleetRoot = path21.join(wpaths.projectSessions, session.id);
8408
- dirState = await loadDirectorState(path21.join(fleetRoot, "director-state.json"));
9074
+ const fleetRoot = path23.join(wpaths.projectSessions, session.id);
9075
+ dirState = await loadDirectorState(path23.join(fleetRoot, "director-state.json"));
8409
9076
  if (dirState) {
8410
9077
  const tCounts = {};
8411
9078
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -8432,22 +9099,11 @@ function resolveBundledSkillsDir2() {
8432
9099
  try {
8433
9100
  const req2 = createRequire(import.meta.url);
8434
9101
  const corePkg = req2.resolve("@wrongstack/core/package.json");
8435
- return path21.join(path21.dirname(corePkg), "skills");
9102
+ return path23.join(path23.dirname(corePkg), "skills");
8436
9103
  } catch {
8437
9104
  return void 0;
8438
9105
  }
8439
9106
  }
8440
- function buildPluginOptions(config) {
8441
- const options = {};
8442
- for (const entry of config.plugins ?? []) {
8443
- if (typeof entry !== "object") continue;
8444
- if (entry.options) options[entry.name] = { ...entry.options };
8445
- }
8446
- for (const [name, value] of Object.entries(config.extensions ?? {})) {
8447
- options[name] = { ...options[name] ?? {}, ...value };
8448
- }
8449
- return options;
8450
- }
8451
9107
  async function main(argv) {
8452
9108
  const ctx = await boot(argv);
8453
9109
  if (typeof ctx === "number") return ctx;
@@ -8539,7 +9195,7 @@ async function main(argv) {
8539
9195
  modeId,
8540
9196
  modePrompt,
8541
9197
  modelCapabilities,
8542
- planPath: () => sessionRef.current ? path21.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
9198
+ planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
8543
9199
  })
8544
9200
  );
8545
9201
  const toolRegistry = new ToolRegistry();
@@ -8553,72 +9209,10 @@ async function main(argv) {
8553
9209
  }
8554
9210
  const events = new EventBus();
8555
9211
  events.setLogger(logger);
8556
- let metricsSink;
8557
- let healthRegistry;
8558
- let metricsServerHandle;
8559
- const metricsPortFlag = flags["metrics-port"];
8560
- const metricsPort = typeof metricsPortFlag === "string" && metricsPortFlag.length > 0 ? Number.parseInt(metricsPortFlag, 10) : void 0;
8561
- if (metricsPort !== void 0 && !flags.metrics) flags.metrics = true;
8562
- if (flags.metrics) {
8563
- metricsSink = new InMemoryMetricsSink();
8564
- wireMetricsToEvents(events, metricsSink);
8565
- healthRegistry = new DefaultHealthRegistry();
8566
- healthRegistry.register({
8567
- name: "session-store",
8568
- check: async () => {
8569
- try {
8570
- await fsp2.access(wpaths.projectSessions);
8571
- return { status: "healthy" };
8572
- } catch (e) {
8573
- return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
8574
- }
8575
- }
8576
- });
8577
- healthRegistry.register({
8578
- name: "provider",
8579
- check: async () => ({
8580
- status: "healthy",
8581
- data: { id: config.provider, model: config.model }
8582
- })
8583
- });
8584
- const dumpMetrics = () => {
8585
- if (!metricsSink) return;
8586
- try {
8587
- const out = path21.join(wpaths.projectSessions, "metrics.json");
8588
- const snap = metricsSink.snapshot();
8589
- writeFileSync(out, JSON.stringify(snap, null, 2));
8590
- } catch {
8591
- }
8592
- };
8593
- process.on("exit", dumpMetrics);
8594
- process.on("SIGINT", () => {
8595
- dumpMetrics();
8596
- process.exit(130);
8597
- });
8598
- if (metricsPort !== void 0 && Number.isFinite(metricsPort)) {
8599
- try {
8600
- metricsServerHandle = await startMetricsServer({
8601
- port: metricsPort,
8602
- host: process.env.METRICS_HOST ?? "127.0.0.1",
8603
- sink: metricsSink,
8604
- // V2-C: mount /healthz on the same listener so k8s probes can
8605
- // hit one endpoint per pod for both observability and liveness.
8606
- healthRegistry
8607
- });
8608
- logger.info(
8609
- `metrics endpoint listening on ${metricsServerHandle.url} (healthz on same port)`
8610
- );
8611
- process.on("exit", () => {
8612
- void metricsServerHandle?.close().catch(() => {
8613
- });
8614
- });
8615
- } catch (err) {
8616
- logger.warn(
8617
- `metrics endpoint failed to start: ${err instanceof Error ? err.message : String(err)}`
8618
- );
8619
- }
8620
- }
8621
- }
9212
+ const { metricsSink, healthRegistry, metricsServerHandle } = (() => {
9213
+ const ms = setupMetrics({ flags, wpaths, events, logger, config: { provider: config.provider, model: config.model } });
9214
+ return ms;
9215
+ })();
8622
9216
  const spinner = new Spinner();
8623
9217
  let lastInputTokens = 0;
8624
9218
  events.on("provider.response", (e) => {
@@ -8737,49 +9331,21 @@ async function main(argv) {
8737
9331
  }
8738
9332
  }
8739
9333
  const slashRegistry = new SlashCommandRegistry();
8740
- if (config.features.plugins && config.plugins && config.plugins.length > 0) {
8741
- const resolvedPlugins = [];
8742
- for (const p of config.plugins) {
8743
- if (typeof p === "object" && p.enabled === false) continue;
8744
- const spec = typeof p === "string" ? p : p.name;
8745
- try {
8746
- const mod = await import(spec);
8747
- if (mod.default) resolvedPlugins.push(mod.default);
8748
- } catch (err) {
8749
- logger.warn(`Plugin "${spec}" failed to load`, err);
8750
- }
8751
- }
8752
- if (resolvedPlugins.length > 0) {
8753
- const { default: createApi2 } = await Promise.resolve().then(() => (init_plugin_api_factory(), plugin_api_factory_exports));
8754
- const pluginOptions = buildPluginOptions(config);
8755
- const pluginConfig = Object.keys(pluginOptions).length > 0 ? patchConfig(config, { extensions: pluginOptions }) : config;
8756
- await loadPlugins(resolvedPlugins, {
8757
- log: logger,
8758
- // Each plugin's `configSchema` is validated against merged
8759
- // options from `plugins[].options` and `extensions[name]`.
8760
- // The merged view is also exposed as `api.config.extensions`.
8761
- pluginOptions,
8762
- apiFactory: (plugin) => createApi2(plugin.name, {
8763
- container,
8764
- events,
8765
- pipelines,
8766
- toolRegistry,
8767
- providerRegistry,
8768
- slashCommandRegistry: slashRegistry,
8769
- mcpRegistry,
8770
- config: pluginConfig,
8771
- log: logger,
8772
- extensions: agent.extensions,
8773
- sessionWriter: {
8774
- transcriptPath: context.session.transcriptPath,
8775
- append: (e) => context.session.append(e)
8776
- },
8777
- metricsSink,
8778
- configStore
8779
- })
8780
- });
8781
- }
8782
- }
9334
+ await setupPlugins({
9335
+ config,
9336
+ container,
9337
+ events,
9338
+ pipelines,
9339
+ toolRegistry,
9340
+ providerRegistry,
9341
+ slashCommandRegistry: slashRegistry,
9342
+ mcpRegistry,
9343
+ log: logger,
9344
+ agent,
9345
+ sessionWriter: context.session,
9346
+ metricsSink,
9347
+ configStore
9348
+ });
8783
9349
  const switchProviderAndModel = (providerId, modelId) => {
8784
9350
  try {
8785
9351
  const savedCfg = config.providers?.[providerId];
@@ -8804,12 +9370,22 @@ async function main(argv) {
8804
9370
  const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
8805
9371
  let director = null;
8806
9372
  let autonomyMode = "off";
8807
- const fleetRoot = directorMode ? path21.join(wpaths.projectSessions, session.id) : void 0;
8808
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path21.join(fleetRoot, "fleet.json") : void 0;
8809
- const sharedScratchpadPath = directorMode ? path21.join(fleetRoot, "shared") : void 0;
8810
- const subagentSessionsRoot = directorMode ? path21.join(fleetRoot, "subagents") : void 0;
8811
- const stateCheckpointPath = directorMode ? path21.join(fleetRoot, "director-state.json") : void 0;
8812
- const fleetRootForPromotion = path21.join(wpaths.projectSessions, session.id);
9373
+ let eternalEngine = null;
9374
+ const eternalListeners = /* @__PURE__ */ new Set();
9375
+ const broadcastEternalIteration = (entry) => {
9376
+ for (const fn of eternalListeners) {
9377
+ try {
9378
+ fn(entry);
9379
+ } catch {
9380
+ }
9381
+ }
9382
+ };
9383
+ const fleetRoot = directorMode ? path23.join(wpaths.projectSessions, session.id) : void 0;
9384
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path23.join(fleetRoot, "fleet.json") : void 0;
9385
+ const sharedScratchpadPath = directorMode ? path23.join(fleetRoot, "shared") : void 0;
9386
+ const subagentSessionsRoot = directorMode ? path23.join(fleetRoot, "subagents") : void 0;
9387
+ const stateCheckpointPath = directorMode ? path23.join(fleetRoot, "director-state.json") : void 0;
9388
+ const fleetRootForPromotion = path23.join(wpaths.projectSessions, session.id);
8813
9389
  const multiAgentHost = new MultiAgentHost(
8814
9390
  {
8815
9391
  container,
@@ -8821,7 +9397,8 @@ async function main(argv) {
8821
9397
  session,
8822
9398
  tokenCounter,
8823
9399
  projectRoot,
8824
- cwd
9400
+ cwd,
9401
+ secretScrubber: container.resolve(TOKENS.SecretScrubber)
8825
9402
  },
8826
9403
  {
8827
9404
  directorMode,
@@ -8868,6 +9445,20 @@ async function main(argv) {
8868
9445
  this.enabled = enabled;
8869
9446
  }
8870
9447
  };
9448
+ const statuslineConfigDeps = {
9449
+ get: () => loadStatuslineConfig(),
9450
+ set: (cfg) => saveStatuslineConfig(cfg)
9451
+ };
9452
+ const hiddenItemsFromConfig = await loadStatuslineConfig();
9453
+ const hiddenItemsList = [];
9454
+ const ALL_ITEMS = ["todos", "plan", "fleet", "git", "elapsed", "context", "cost"];
9455
+ for (const k of ALL_ITEMS) {
9456
+ if (!hiddenItemsFromConfig[k]) hiddenItemsList.push(k);
9457
+ }
9458
+ const statuslineHiddenItems = hiddenItemsList;
9459
+ [...statuslineHiddenItems];
9460
+ const setStatuslineHiddenItems = (items) => {
9461
+ };
8871
9462
  const slashCmds = buildBuiltinSlashCommands({
8872
9463
  registry: slashRegistry,
8873
9464
  toolRegistry,
@@ -8887,6 +9478,7 @@ async function main(argv) {
8887
9478
  fleetStreamController,
8888
9479
  llmProvider: provider,
8889
9480
  llmModel: config.model,
9481
+ statuslineConfig: statuslineConfigDeps,
8890
9482
  onSpawn: async (description, spawnOpts) => {
8891
9483
  const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
8892
9484
  const tags = [];
@@ -8993,7 +9585,7 @@ async function main(argv) {
8993
9585
  return `Unknown fleet action: ${action}`;
8994
9586
  },
8995
9587
  onFleetLog: async (subagentId, mode) => {
8996
- const subagentsRoot = path21.join(fleetRootForPromotion, "subagents");
9588
+ const subagentsRoot = path23.join(fleetRootForPromotion, "subagents");
8997
9589
  let runDirs;
8998
9590
  try {
8999
9591
  runDirs = await fsp2.readdir(subagentsRoot);
@@ -9002,7 +9594,7 @@ async function main(argv) {
9002
9594
  }
9003
9595
  const found = [];
9004
9596
  for (const runId of runDirs) {
9005
- const runDir = path21.join(subagentsRoot, runId);
9597
+ const runDir = path23.join(subagentsRoot, runId);
9006
9598
  let files;
9007
9599
  try {
9008
9600
  files = await fsp2.readdir(runDir);
@@ -9011,7 +9603,7 @@ async function main(argv) {
9011
9603
  }
9012
9604
  for (const f of files) {
9013
9605
  if (!f.endsWith(".jsonl")) continue;
9014
- const full = path21.join(runDir, f);
9606
+ const full = path23.join(runDir, f);
9015
9607
  try {
9016
9608
  const stat3 = await fsp2.stat(full);
9017
9609
  found.push({
@@ -9108,7 +9700,7 @@ async function main(argv) {
9108
9700
  }
9109
9701
  const dir = await multiAgentHost.ensureDirector();
9110
9702
  if (!dir) return "Director is not available.";
9111
- const dirStatePath = path21.join(fleetRootForPromotion, "director-state.json");
9703
+ const dirStatePath = path23.join(fleetRootForPromotion, "director-state.json");
9112
9704
  const prior = await loadDirectorState(dirStatePath);
9113
9705
  if (!prior) {
9114
9706
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -9179,9 +9771,9 @@ async function main(argv) {
9179
9771
  for (const tool of director2.tools(FLEET_ROSTER)) {
9180
9772
  toolRegistry.register(tool);
9181
9773
  }
9182
- const mp = path21.join(fleetRootForPromotion, "fleet.json");
9183
- const sp = path21.join(fleetRootForPromotion, "shared");
9184
- const ss = path21.join(fleetRootForPromotion, "subagents");
9774
+ const mp = path23.join(fleetRootForPromotion, "fleet.json");
9775
+ const sp = path23.join(fleetRootForPromotion, "shared");
9776
+ const ss = path23.join(fleetRootForPromotion, "subagents");
9185
9777
  const lines = [
9186
9778
  `${color.green("\u2713")} Promoted to director mode.`,
9187
9779
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -9224,6 +9816,26 @@ Restart WrongStack to load or unload plugin code in this session.`;
9224
9816
  }
9225
9817
  return autonomyMode;
9226
9818
  },
9819
+ onEternalStart: () => {
9820
+ if (!eternalEngine) {
9821
+ eternalEngine = new EternalAutonomyEngine({
9822
+ agent,
9823
+ projectRoot,
9824
+ // Wire the same compactor the manual /compact command uses so
9825
+ // multi-day eternal loops don't overflow the provider's context.
9826
+ // effectiveMaxContext is set up earlier with a model-specific
9827
+ // value; pass it through so aggressive-mode compact triggers
9828
+ // before the next iteration would actually overflow.
9829
+ compactor: container.resolve(TOKENS.Compactor),
9830
+ maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
9831
+ onIteration: broadcastEternalIteration
9832
+ });
9833
+ }
9834
+ void eternalEngine.prime();
9835
+ },
9836
+ onEternalStop: () => {
9837
+ eternalEngine?.stop();
9838
+ },
9227
9839
  onExit: () => {
9228
9840
  void mcpRegistry.stopAll();
9229
9841
  },
@@ -9281,6 +9893,29 @@ Restart WrongStack to load or unload plugin code in this session.`;
9281
9893
  }
9282
9894
  });
9283
9895
  for (const cmd of slashCmds) slashRegistry.register(cmd);
9896
+ const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
9897
+ if (eternalFlag.length > 0) {
9898
+ const { saveGoal: saveGoal2, emptyGoal: emptyGoal2, goalFilePath: goalFilePath4, loadGoal: loadGoal4 } = await import('@wrongstack/core');
9899
+ const goalPath = goalFilePath4(projectRoot);
9900
+ const prior = await loadGoal4(goalPath);
9901
+ const next = prior ? { ...prior, goal: eternalFlag, setAt: (/* @__PURE__ */ new Date()).toISOString(), lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() } : emptyGoal2(eternalFlag);
9902
+ await saveGoal2(goalPath, next);
9903
+ const policy = container.resolve(TOKENS.PermissionPolicy);
9904
+ policy.setYolo(true);
9905
+ config = patchConfig(config, { yolo: true });
9906
+ eternalEngine = new EternalAutonomyEngine({
9907
+ agent,
9908
+ projectRoot,
9909
+ compactor: container.resolve(TOKENS.Compactor),
9910
+ maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
9911
+ onIteration: broadcastEternalIteration
9912
+ });
9913
+ await eternalEngine.prime();
9914
+ autonomyMode = "eternal";
9915
+ renderer.write(
9916
+ color.red("Eternal mode launching from --eternal flag.") + color.dim(` Goal: ${eternalFlag.slice(0, 80)}${eternalFlag.length > 80 ? "\u2026" : ""}`) + "\n"
9917
+ );
9918
+ }
9284
9919
  const savedProviderCfg = config.providers?.[config.provider];
9285
9920
  return execute({
9286
9921
  agent,
@@ -9311,11 +9946,18 @@ Restart WrongStack to load or unload plugin code in this session.`;
9311
9946
  director: director ?? null,
9312
9947
  fleetRoster: FLEET_ROSTER,
9313
9948
  fleetStreamController,
9949
+ statuslineHiddenItems,
9950
+ setStatuslineHiddenItems,
9314
9951
  getYolo: () => {
9315
9952
  const policy = container.resolve(TOKENS.PermissionPolicy);
9316
9953
  return policy.getYolo();
9317
9954
  },
9318
9955
  getAutonomy: () => autonomyMode,
9956
+ getEternalEngine: () => eternalEngine,
9957
+ subscribeEternalIteration: (fn) => {
9958
+ eternalListeners.add(fn);
9959
+ return () => eternalListeners.delete(fn);
9960
+ },
9319
9961
  skillLoader: config.features.skills ? skillLoader : void 0
9320
9962
  });
9321
9963
  }