@wrongstack/cli 0.5.5 → 0.5.7

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 path20 from 'path';
2
+ import * as path21 from 'path';
3
3
  import { join } from 'path';
4
- import * as fs3 from 'fs/promises';
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, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, 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, 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';
7
7
  import { createRequire } from 'module';
8
8
  import * as os6 from 'os';
9
9
  import os6__default from 'os';
@@ -261,8 +261,8 @@ function buildSddCommand(opts) {
261
261
  async run(args) {
262
262
  const ctx = opts.context;
263
263
  const projectRoot = ctx?.projectRoot ?? process.cwd();
264
- const specsDir = path20.join(projectRoot, ".wrongstack", "specs");
265
- const graphsDir = path20.join(projectRoot, ".wrongstack", "task-graphs");
264
+ const specsDir = path21.join(projectRoot, ".wrongstack", "specs");
265
+ const graphsDir = path21.join(projectRoot, ".wrongstack", "task-graphs");
266
266
  const specStore = new SpecStore({ baseDir: specsDir });
267
267
  new TaskGraphStore({ baseDir: graphsDir });
268
268
  const versioning = new SpecVersioning();
@@ -278,9 +278,9 @@ function buildSddCommand(opts) {
278
278
  const forceFlag = rest.includes("--force") || rest.includes("-f");
279
279
  const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
280
280
  if (!sddState.getBuilder() && !forceFlag) {
281
- const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
281
+ const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
282
282
  try {
283
- await fs3.access(sessionPath);
283
+ await fsp2.access(sessionPath);
284
284
  const projectContext2 = await gatherProjectContext(projectRoot);
285
285
  const tempBuilder = new AISpecBuilder({
286
286
  store: specStore,
@@ -313,7 +313,7 @@ function buildSddCommand(opts) {
313
313
  projectContext,
314
314
  minQuestions: 2,
315
315
  maxQuestions: 10,
316
- sessionPath: path20.join(projectRoot, ".wrongstack", "sdd-session.json")
316
+ sessionPath: path21.join(projectRoot, ".wrongstack", "sdd-session.json")
317
317
  }));
318
318
  const builder = sddState.getBuilder();
319
319
  builder.startSession(title);
@@ -581,10 +581,10 @@ Start executing the tasks one by one.`
581
581
  };
582
582
  }
583
583
  case "cancel": {
584
- const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
584
+ const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
585
585
  let deletedFromDisk = false;
586
586
  try {
587
- await fs3.unlink(sessionPath);
587
+ await fsp2.unlink(sessionPath);
588
588
  deletedFromDisk = true;
589
589
  } catch {
590
590
  }
@@ -605,7 +605,7 @@ Start executing the tasks one by one.`
605
605
  if (sddState.getBuilder()) {
606
606
  return { message: "An SDD session is already active. Use /sdd cancel first." };
607
607
  }
608
- const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
608
+ const sessionPath = path21.join(projectRoot, ".wrongstack", "sdd-session.json");
609
609
  const projectContext = await gatherProjectContext(projectRoot);
610
610
  sddState.setBuilder(new AISpecBuilder({
611
611
  store: specStore,
@@ -831,8 +831,8 @@ function sddHelp() {
831
831
  async function gatherProjectContext(projectRoot) {
832
832
  const parts = [];
833
833
  try {
834
- const pkgPath = path20.join(projectRoot, "package.json");
835
- const pkgRaw = await fs3.readFile(pkgPath, "utf8");
834
+ const pkgPath = path21.join(projectRoot, "package.json");
835
+ const pkgRaw = await fsp2.readFile(pkgPath, "utf8");
836
836
  const pkg = JSON.parse(pkgRaw);
837
837
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
838
838
  parts.push(`Description: ${String(pkg.description ?? "none")}`);
@@ -847,14 +847,14 @@ async function gatherProjectContext(projectRoot) {
847
847
  } catch {
848
848
  }
849
849
  try {
850
- const tsconfigPath = path20.join(projectRoot, "tsconfig.json");
851
- await fs3.access(tsconfigPath);
850
+ const tsconfigPath = path21.join(projectRoot, "tsconfig.json");
851
+ await fsp2.access(tsconfigPath);
852
852
  parts.push("Language: TypeScript");
853
853
  } catch {
854
854
  }
855
855
  try {
856
- const srcDir = path20.join(projectRoot, "src");
857
- const entries = await fs3.readdir(srcDir, { withFileTypes: true });
856
+ const srcDir = path21.join(projectRoot, "src");
857
+ const entries = await fsp2.readdir(srcDir, { withFileTypes: true });
858
858
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
859
859
  if (dirs.length > 0) {
860
860
  parts.push(`Source structure: src/${dirs.join(", src/")}`);
@@ -976,7 +976,7 @@ __export(update_check_exports, {
976
976
  getUpdateNotification: () => getUpdateNotification
977
977
  });
978
978
  function cachePath(homeFn = defaultHomeDir2) {
979
- return path20.join(homeFn(), ".wrongstack", "update-cache.json");
979
+ return path21.join(homeFn(), ".wrongstack", "update-cache.json");
980
980
  }
981
981
  function currentVersion() {
982
982
  const req2 = createRequire(import.meta.url);
@@ -1003,7 +1003,7 @@ function isNewer(a, b) {
1003
1003
  }
1004
1004
  async function readCache(homeFn = defaultHomeDir2) {
1005
1005
  try {
1006
- const raw = await fs3.readFile(cachePath(homeFn), "utf8");
1006
+ const raw = await fsp2.readFile(cachePath(homeFn), "utf8");
1007
1007
  const entry = JSON.parse(raw);
1008
1008
  if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
1009
1009
  return entry;
@@ -1013,9 +1013,9 @@ async function readCache(homeFn = defaultHomeDir2) {
1013
1013
  }
1014
1014
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1015
1015
  try {
1016
- const dir = path20.dirname(cachePath(homeFn));
1017
- await fs3.mkdir(dir, { recursive: true });
1018
- await fs3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1016
+ const dir = path21.dirname(cachePath(homeFn));
1017
+ await fsp2.mkdir(dir, { recursive: true });
1018
+ await fsp2.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1019
1019
  } catch {
1020
1020
  }
1021
1021
  }
@@ -1573,7 +1573,7 @@ async function runWebUI(opts) {
1573
1573
  if (!opts.globalConfigPath) return {};
1574
1574
  let raw;
1575
1575
  try {
1576
- raw = await fs3.readFile(opts.globalConfigPath, "utf8");
1576
+ raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
1577
1577
  } catch {
1578
1578
  return {};
1579
1579
  }
@@ -1584,7 +1584,7 @@ async function runWebUI(opts) {
1584
1584
  return {};
1585
1585
  }
1586
1586
  if (!parsed.providers) return {};
1587
- const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
1587
+ const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
1588
1588
  const vault = new DefaultSecretVault$1({ keyFile });
1589
1589
  return decryptConfigSecrets$1(parsed.providers, vault);
1590
1590
  }
@@ -1592,7 +1592,7 @@ async function runWebUI(opts) {
1592
1592
  if (!opts.globalConfigPath) return;
1593
1593
  let raw;
1594
1594
  try {
1595
- raw = await fs3.readFile(opts.globalConfigPath, "utf8");
1595
+ raw = await fsp2.readFile(opts.globalConfigPath, "utf8");
1596
1596
  } catch {
1597
1597
  raw = "{}";
1598
1598
  }
@@ -1603,7 +1603,7 @@ async function runWebUI(opts) {
1603
1603
  parsed = {};
1604
1604
  }
1605
1605
  parsed.providers = providers;
1606
- const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
1606
+ const keyFile = path21.join(path21.dirname(opts.globalConfigPath), ".key");
1607
1607
  const vault = new DefaultSecretVault$1({ keyFile });
1608
1608
  const encrypted = encryptConfigSecrets(parsed, vault);
1609
1609
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -1681,7 +1681,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1681
1681
  "prompt",
1682
1682
  "metrics",
1683
1683
  "webui",
1684
- "no-check"
1684
+ "no-check",
1685
+ "director"
1685
1686
  ]);
1686
1687
  function parseArgs(argv) {
1687
1688
  const flags = {};
@@ -1787,7 +1788,7 @@ function parseSpawnFlags(input) {
1787
1788
  return { description: rest.trim(), opts };
1788
1789
  }
1789
1790
  async function bootConfig(flags) {
1790
- const cwd = typeof flags["cwd"] === "string" ? path20.resolve(flags["cwd"]) : process.cwd();
1791
+ const cwd = typeof flags["cwd"] === "string" ? path21.resolve(flags["cwd"]) : process.cwd();
1791
1792
  const pathResolver = new DefaultPathResolver(cwd);
1792
1793
  const projectRoot = pathResolver.projectRoot;
1793
1794
  const userHome = os6.homedir();
@@ -1838,13 +1839,13 @@ function flagsToConfigPatch(flags) {
1838
1839
  }
1839
1840
  async function ensureProjectMeta(paths, projectRoot) {
1840
1841
  try {
1841
- await fs3.mkdir(paths.projectDir, { recursive: true });
1842
+ await fsp2.mkdir(paths.projectDir, { recursive: true });
1842
1843
  const meta = {
1843
1844
  hash: paths.projectHash,
1844
1845
  root: projectRoot,
1845
1846
  lastSeen: (/* @__PURE__ */ new Date()).toISOString()
1846
1847
  };
1847
- await fs3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
1848
+ await fsp2.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
1848
1849
  } catch {
1849
1850
  }
1850
1851
  }
@@ -1854,11 +1855,11 @@ var ReadlineInputReader = class {
1854
1855
  history = [];
1855
1856
  pending = false;
1856
1857
  constructor(opts = {}) {
1857
- this.historyFile = opts.historyFile ?? path20.join(os6.homedir(), ".wrongstack", "history");
1858
+ this.historyFile = opts.historyFile ?? path21.join(os6.homedir(), ".wrongstack", "history");
1858
1859
  }
1859
1860
  async loadHistory() {
1860
1861
  try {
1861
- const raw = await fs3.readFile(this.historyFile, "utf8");
1862
+ const raw = await fsp2.readFile(this.historyFile, "utf8");
1862
1863
  this.history = raw.split("\n").filter(Boolean).slice(-1e3);
1863
1864
  } catch {
1864
1865
  this.history = [];
@@ -1866,8 +1867,8 @@ var ReadlineInputReader = class {
1866
1867
  }
1867
1868
  async saveHistory() {
1868
1869
  try {
1869
- await fs3.mkdir(path20.dirname(this.historyFile), { recursive: true });
1870
- await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1870
+ await fsp2.mkdir(path21.dirname(this.historyFile), { recursive: true });
1871
+ await fsp2.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1871
1872
  } catch {
1872
1873
  }
1873
1874
  }
@@ -2093,23 +2094,23 @@ function assertSafeToDelete(filename, parentDir) {
2093
2094
  if (PROTECTED_BASENAMES.has(filename)) {
2094
2095
  throw new Error(`Refusing to delete protected file: ${filename}`);
2095
2096
  }
2096
- if (filename !== path20.basename(filename)) {
2097
+ if (filename !== path21.basename(filename)) {
2097
2098
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
2098
2099
  }
2099
2100
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
2100
2101
  throw new Error(`Refusing to delete unknown file: ${filename}`);
2101
2102
  }
2102
- const resolvedParent = path20.resolve(parentDir);
2103
+ const resolvedParent = path21.resolve(parentDir);
2103
2104
  if (!resolvedParent.endsWith(".wrongstack")) {
2104
2105
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
2105
2106
  }
2106
2107
  }
2107
2108
  async function safeDelete(filePath) {
2108
- const dir = path20.dirname(filePath);
2109
- const filename = path20.basename(filePath);
2109
+ const dir = path21.dirname(filePath);
2110
+ const filename = path21.basename(filePath);
2110
2111
  try {
2111
2112
  assertSafeToDelete(filename, dir);
2112
- await fs3.unlink(filePath);
2113
+ await fsp2.unlink(filePath);
2113
2114
  } catch (err) {
2114
2115
  if (err instanceof Error && err.message.startsWith("Refusing")) {
2115
2116
  process.stderr.write(`[config-history] SAFETY: ${err.message}
@@ -2151,26 +2152,26 @@ function diffSummary(oldCfg, newCfg) {
2151
2152
  }
2152
2153
  var defaultHomeDir = () => os6__default.homedir();
2153
2154
  function historyDir(homeFn = defaultHomeDir) {
2154
- return path20.join(homeFn(), ".wrongstack", "config.history", "entries");
2155
+ return path21.join(homeFn(), ".wrongstack", "config.history", "entries");
2155
2156
  }
2156
2157
  function historyIndexPath(homeFn = defaultHomeDir) {
2157
- return path20.join(homeFn(), ".wrongstack", "config.history", "index.json");
2158
+ return path21.join(homeFn(), ".wrongstack", "config.history", "index.json");
2158
2159
  }
2159
2160
  function configPath(homeFn = defaultHomeDir) {
2160
- return path20.join(homeFn(), ".wrongstack", "config.json");
2161
+ return path21.join(homeFn(), ".wrongstack", "config.json");
2161
2162
  }
2162
2163
  function backupLastPath(homeFn = defaultHomeDir) {
2163
- return path20.join(homeFn(), ".wrongstack", "config.json.last");
2164
+ return path21.join(homeFn(), ".wrongstack", "config.json.last");
2164
2165
  }
2165
2166
  function entryId(ts) {
2166
2167
  return ts.replace(/[:.]/g, "-").slice(0, 19);
2167
2168
  }
2168
2169
  async function ensureHistoryDir(homeFn = defaultHomeDir) {
2169
- await fs3.mkdir(historyDir(homeFn), { recursive: true });
2170
+ await fsp2.mkdir(historyDir(homeFn), { recursive: true });
2170
2171
  }
2171
2172
  async function readIndex(homeFn = defaultHomeDir) {
2172
2173
  try {
2173
- const raw = await fs3.readFile(historyIndexPath(homeFn), "utf8");
2174
+ const raw = await fsp2.readFile(historyIndexPath(homeFn), "utf8");
2174
2175
  return JSON.parse(raw);
2175
2176
  } catch {
2176
2177
  return { version: 1, entries: [] };
@@ -2178,7 +2179,7 @@ async function readIndex(homeFn = defaultHomeDir) {
2178
2179
  }
2179
2180
  async function writeIndex(idx, homeFn = defaultHomeDir) {
2180
2181
  await ensureHistoryDir(homeFn);
2181
- await fs3.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
2182
+ await fsp2.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
2182
2183
  }
2183
2184
  async function backupCurrent(homeFn = defaultHomeDir) {
2184
2185
  const cfg = configPath(homeFn);
@@ -2186,7 +2187,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
2186
2187
  const ts = Date.now();
2187
2188
  let content;
2188
2189
  try {
2189
- content = await fs3.readFile(cfg, "utf8");
2190
+ content = await fsp2.readFile(cfg, "utf8");
2190
2191
  } catch {
2191
2192
  }
2192
2193
  if (content !== void 0) {
@@ -2197,17 +2198,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
2197
2198
  }
2198
2199
  if (content !== void 0) {
2199
2200
  try {
2200
- const bakPath = path20.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2201
+ const bakPath = path21.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
2201
2202
  await atomicWrite(bakPath, content);
2202
2203
  } catch {
2203
2204
  }
2204
2205
  }
2205
2206
  try {
2206
- const dir = path20.join(homeFn(), ".wrongstack");
2207
- const files = await fs3.readdir(dir);
2207
+ const dir = path21.join(homeFn(), ".wrongstack");
2208
+ const files = await fsp2.readdir(dir);
2208
2209
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
2209
2210
  for (const f of baks.slice(10)) {
2210
- await safeDelete(path20.join(dir, f));
2211
+ await safeDelete(path21.join(dir, f));
2211
2212
  }
2212
2213
  } catch {
2213
2214
  }
@@ -2223,8 +2224,8 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
2223
2224
  snapshotMasked: maskConfigSecrets(newCfg),
2224
2225
  diffSummary: diffSummary(oldCfg, newCfg)
2225
2226
  };
2226
- await fs3.writeFile(
2227
- path20.join(historyDir(homeFn), `${id}.json`),
2227
+ await fsp2.writeFile(
2228
+ path21.join(historyDir(homeFn), `${id}.json`),
2228
2229
  JSON.stringify(entry, null, 2),
2229
2230
  "utf8"
2230
2231
  );
@@ -2239,7 +2240,7 @@ async function listHistory(homeFn = defaultHomeDir) {
2239
2240
  }
2240
2241
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
2241
2242
  try {
2242
- const raw = await fs3.readFile(path20.join(historyDir(homeFn), `${id}.json`), "utf8");
2243
+ const raw = await fsp2.readFile(path21.join(historyDir(homeFn), `${id}.json`), "utf8");
2243
2244
  return JSON.parse(raw);
2244
2245
  } catch {
2245
2246
  return null;
@@ -2251,7 +2252,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
2251
2252
  await backupCurrent(homeFn);
2252
2253
  let oldCfg = {};
2253
2254
  try {
2254
- const raw = await fs3.readFile(configPath(homeFn), "utf8");
2255
+ const raw = await fsp2.readFile(configPath(homeFn), "utf8");
2255
2256
  oldCfg = JSON.parse(raw);
2256
2257
  } catch {
2257
2258
  }
@@ -2273,13 +2274,13 @@ async function restoreLast(homeFn = defaultHomeDir) {
2273
2274
  const cfg = configPath(homeFn);
2274
2275
  let oldCfg = {};
2275
2276
  try {
2276
- const raw = await fs3.readFile(cfg, "utf8");
2277
+ const raw = await fsp2.readFile(cfg, "utf8");
2277
2278
  oldCfg = JSON.parse(raw);
2278
2279
  } catch {
2279
2280
  }
2280
2281
  let lastCfg = {};
2281
2282
  try {
2282
- const raw = await fs3.readFile(last, "utf8");
2283
+ const raw = await fsp2.readFile(last, "utf8");
2283
2284
  lastCfg = JSON.parse(raw);
2284
2285
  } catch {
2285
2286
  return { ok: false, error: "No prior backup found" };
@@ -2557,7 +2558,7 @@ async function resolveModelSelection(answer, models, provider, _registry, render
2557
2558
  }
2558
2559
  async function pathExists(file) {
2559
2560
  try {
2560
- await fs3.access(file);
2561
+ await fsp2.access(file);
2561
2562
  return true;
2562
2563
  } catch {
2563
2564
  return false;
@@ -2568,10 +2569,10 @@ async function detectPackageManager(root, declared) {
2568
2569
  const name = declared.split("@")[0];
2569
2570
  if (name) return name;
2570
2571
  }
2571
- if (await pathExists(path20.join(root, "pnpm-lock.yaml"))) return "pnpm";
2572
- if (await pathExists(path20.join(root, "bun.lockb"))) return "bun";
2573
- if (await pathExists(path20.join(root, "bun.lock"))) return "bun";
2574
- if (await pathExists(path20.join(root, "yarn.lock"))) return "yarn";
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";
2575
2576
  return "npm";
2576
2577
  }
2577
2578
  function hasUsableScript(scripts, name) {
@@ -2592,7 +2593,7 @@ function parseMakeTargets(makefile) {
2592
2593
  async function detectProjectFacts(root) {
2593
2594
  const facts = { hints: [] };
2594
2595
  try {
2595
- const pkg = JSON.parse(await fs3.readFile(path20.join(root, "package.json"), "utf8"));
2596
+ const pkg = JSON.parse(await fsp2.readFile(path21.join(root, "package.json"), "utf8"));
2596
2597
  const scripts = pkg.scripts ?? {};
2597
2598
  const pm = await detectPackageManager(root, pkg.packageManager);
2598
2599
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2606,14 +2607,14 @@ async function detectProjectFacts(root) {
2606
2607
  } catch {
2607
2608
  }
2608
2609
  try {
2609
- if (!await pathExists(path20.join(root, "pyproject.toml"))) throw new Error("not python");
2610
+ if (!await pathExists(path21.join(root, "pyproject.toml"))) throw new Error("not python");
2610
2611
  facts.test ??= "pytest";
2611
2612
  facts.lint ??= "ruff check .";
2612
2613
  facts.hints.push("pyproject.toml");
2613
2614
  } catch {
2614
2615
  }
2615
2616
  try {
2616
- if (!await pathExists(path20.join(root, "go.mod"))) throw new Error("not go");
2617
+ if (!await pathExists(path21.join(root, "go.mod"))) throw new Error("not go");
2617
2618
  facts.build ??= "go build ./...";
2618
2619
  facts.test ??= "go test ./...";
2619
2620
  facts.run ??= "go run .";
@@ -2621,7 +2622,7 @@ async function detectProjectFacts(root) {
2621
2622
  } catch {
2622
2623
  }
2623
2624
  try {
2624
- if (!await pathExists(path20.join(root, "Cargo.toml"))) throw new Error("not rust");
2625
+ if (!await pathExists(path21.join(root, "Cargo.toml"))) throw new Error("not rust");
2625
2626
  facts.build ??= "cargo build";
2626
2627
  facts.test ??= "cargo test";
2627
2628
  facts.lint ??= "cargo clippy";
@@ -2630,7 +2631,7 @@ async function detectProjectFacts(root) {
2630
2631
  } catch {
2631
2632
  }
2632
2633
  try {
2633
- const makefile = await fs3.readFile(path20.join(root, "Makefile"), "utf8");
2634
+ const makefile = await fsp2.readFile(path21.join(root, "Makefile"), "utf8");
2634
2635
  const targets = parseMakeTargets(makefile);
2635
2636
  facts.build ??= targets.has("build") ? "make build" : "make";
2636
2637
  if (targets.has("test")) facts.test ??= "make test";
@@ -3266,10 +3267,10 @@ function buildInitCommand(opts) {
3266
3267
  description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
3267
3268
  async run(args, ctx) {
3268
3269
  const force = args.trim() === "--force";
3269
- const dir = path20.join(ctx.projectRoot, ".wrongstack");
3270
- const file = path20.join(dir, "AGENTS.md");
3270
+ const dir = path21.join(ctx.projectRoot, ".wrongstack");
3271
+ const file = path21.join(dir, "AGENTS.md");
3271
3272
  try {
3272
- await fs3.access(file);
3273
+ await fsp2.access(file);
3273
3274
  if (!force) {
3274
3275
  const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
3275
3276
  opts.renderer.writeWarning(msg2);
@@ -3279,8 +3280,8 @@ function buildInitCommand(opts) {
3279
3280
  }
3280
3281
  const detected = await detectProjectFacts(ctx.projectRoot);
3281
3282
  const body = renderAgentsTemplate(detected);
3282
- await fs3.mkdir(dir, { recursive: true });
3283
- await fs3.writeFile(file, body, "utf8");
3283
+ await fsp2.mkdir(dir, { recursive: true });
3284
+ await fsp2.writeFile(file, body, "utf8");
3284
3285
  if (detected.hints.length > 0) {
3285
3286
  const msg2 = `Wrote ${file}
3286
3287
  Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
@@ -3378,7 +3379,7 @@ function buildMetricsCommand(opts) {
3378
3379
  function buildPlanCommand(opts) {
3379
3380
  return {
3380
3381
  name: "plan",
3381
- description: "Strategic plan board: /plan [show|add <title>|done <id|#>|remove <id|#>|clear]",
3382
+ description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
3382
3383
  async run(args) {
3383
3384
  const planPath = opts.planPath;
3384
3385
  if (!planPath) return { message: "Plan storage is not configured for this session." };
@@ -3422,6 +3423,58 @@ ${formatPlan(updated)}` };
3422
3423
  await savePlan(planPath, updated);
3423
3424
  return { message: formatPlan(updated) };
3424
3425
  }
3426
+ case "promote": {
3427
+ if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
3428
+ const [target, ...subtasks] = restJoined.split(/\s+/);
3429
+ if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
3430
+ const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
3431
+ if (!derived) return { message: `No plan item matched "${target}".` };
3432
+ await savePlan(planPath, derived.plan);
3433
+ if (ctx) {
3434
+ ctx.state.replaceTodos(derived.todos);
3435
+ }
3436
+ return {
3437
+ message: `Promoted to ${derived.todos.length} todo(s):
3438
+ ${formatTodosList(derived.todos)}
3439
+
3440
+ ${formatPlan(derived.plan)}`
3441
+ };
3442
+ }
3443
+ case "derive": {
3444
+ if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
3445
+ const derived = deriveTodosFromPlanItem(plan, restJoined);
3446
+ if (!derived) return { message: `No plan item matched "${restJoined}".` };
3447
+ await savePlan(planPath, derived.plan);
3448
+ if (ctx) {
3449
+ ctx.state.replaceTodos(derived.todos);
3450
+ }
3451
+ return {
3452
+ message: `Derived ${derived.todos.length} todo(s):
3453
+ ${formatTodosList(derived.todos)}
3454
+
3455
+ ${formatPlan(derived.plan)}`
3456
+ };
3457
+ }
3458
+ case "template": {
3459
+ const subVerb = rest[0] ?? "";
3460
+ const subRest = rest.slice(1).join(" ").trim();
3461
+ if (subVerb === "" || subVerb === "list") {
3462
+ return { message: formatPlanTemplates() };
3463
+ }
3464
+ if (subVerb === "use") {
3465
+ if (!subRest) return { message: "Usage: /plan template use <template-name>" };
3466
+ const template = getPlanTemplate(subRest);
3467
+ if (!template) return { message: `Unknown template "${subRest}". Use /plan template list to see available templates.` };
3468
+ let updated = plan;
3469
+ for (const item of template.items) {
3470
+ ({ plan: updated } = addPlanItem(updated, item.title, item.details));
3471
+ }
3472
+ await savePlan(planPath, updated);
3473
+ return { message: `Applied template "${template.name}" (${template.items.length} items):
3474
+ ${formatPlan(updated)}` };
3475
+ }
3476
+ return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
3477
+ }
3425
3478
  case "clear": {
3426
3479
  const updated = clearPlan(plan);
3427
3480
  await savePlan(planPath, updated);
@@ -3429,7 +3482,7 @@ ${formatPlan(updated)}` };
3429
3482
  }
3430
3483
  default:
3431
3484
  return {
3432
- message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | clear`
3485
+ message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
3433
3486
  };
3434
3487
  }
3435
3488
  }
@@ -4107,11 +4160,11 @@ function getHelpMessage() {
4107
4160
  Run \`/security scan\` to start.`;
4108
4161
  }
4109
4162
  function makeInstaller(opts, projectRoot, global) {
4110
- const globalRoot = path20.join(os6.homedir(), ".wrongstack");
4163
+ const globalRoot = path21.join(os6.homedir(), ".wrongstack");
4111
4164
  return new SkillInstaller({
4112
- manifestPath: path20.join(globalRoot, "installed-skills.json"),
4113
- projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
4114
- globalSkillsDir: path20.join(globalRoot, "skills"),
4165
+ manifestPath: path21.join(globalRoot, "installed-skills.json"),
4166
+ projectSkillsDir: path21.join(projectRoot, ".wrongstack", "skills"),
4167
+ globalSkillsDir: path21.join(globalRoot, "skills"),
4115
4168
  projectHash: projectHash(projectRoot),
4116
4169
  skillLoader: opts.skillLoader
4117
4170
  });
@@ -4323,13 +4376,13 @@ var MANIFESTS = [
4323
4376
  ];
4324
4377
  async function detectProjectKind(projectRoot) {
4325
4378
  try {
4326
- await fs3.access(path20.join(projectRoot, ".wrongstack", "AGENTS.md"));
4379
+ await fsp2.access(path21.join(projectRoot, ".wrongstack", "AGENTS.md"));
4327
4380
  return "initialized";
4328
4381
  } catch {
4329
4382
  }
4330
4383
  for (const m of MANIFESTS) {
4331
4384
  try {
4332
- await fs3.access(path20.join(projectRoot, m));
4385
+ await fsp2.access(path21.join(projectRoot, m));
4333
4386
  return "project";
4334
4387
  } catch {
4335
4388
  }
@@ -4337,12 +4390,12 @@ async function detectProjectKind(projectRoot) {
4337
4390
  return "empty";
4338
4391
  }
4339
4392
  async function scaffoldAgentsMd(projectRoot) {
4340
- const dir = path20.join(projectRoot, ".wrongstack");
4341
- const file = path20.join(dir, "AGENTS.md");
4393
+ const dir = path21.join(projectRoot, ".wrongstack");
4394
+ const file = path21.join(dir, "AGENTS.md");
4342
4395
  const facts = await detectProjectFacts(projectRoot);
4343
4396
  const body = renderAgentsTemplate(facts);
4344
- await fs3.mkdir(dir, { recursive: true });
4345
- await fs3.writeFile(file, body, "utf8");
4397
+ await fsp2.mkdir(dir, { recursive: true });
4398
+ await fsp2.writeFile(file, body, "utf8");
4346
4399
  return file;
4347
4400
  }
4348
4401
  async function runProjectCheck(opts) {
@@ -4351,7 +4404,7 @@ async function runProjectCheck(opts) {
4351
4404
  if (kind === "initialized") {
4352
4405
  renderer.write(
4353
4406
  `
4354
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path20.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
4407
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path21.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
4355
4408
  `
4356
4409
  );
4357
4410
  return true;
@@ -4378,10 +4431,10 @@ async function runProjectCheck(opts) {
4378
4431
  }
4379
4432
  return true;
4380
4433
  }
4381
- const gitDir = path20.join(projectRoot, ".git");
4434
+ const gitDir = path21.join(projectRoot, ".git");
4382
4435
  let hasGit = false;
4383
4436
  try {
4384
- await fs3.access(gitDir);
4437
+ await fsp2.access(gitDir);
4385
4438
  hasGit = true;
4386
4439
  } catch {
4387
4440
  }
@@ -4679,14 +4732,14 @@ function summarize(value, name) {
4679
4732
  if (typeof v === "object" && v !== null) {
4680
4733
  const o = v;
4681
4734
  if (name === "edit") {
4682
- const path21 = typeof o["path"] === "string" ? o["path"] : "";
4735
+ const path22 = typeof o["path"] === "string" ? o["path"] : "";
4683
4736
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
4684
- return `${path21} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4737
+ return `${path22} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
4685
4738
  }
4686
4739
  if (name === "write") {
4687
- const path21 = typeof o["path"] === "string" ? o["path"] : "";
4740
+ const path22 = typeof o["path"] === "string" ? o["path"] : "";
4688
4741
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
4689
- return bytes !== void 0 ? `${path21} ${bytes}B` : path21;
4742
+ return bytes !== void 0 ? `${path22} ${bytes}B` : path22;
4690
4743
  }
4691
4744
  if (typeof o["count"] === "number") {
4692
4745
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -5282,7 +5335,7 @@ async function readKeyInput(deps, intent) {
5282
5335
  async function loadProviders(deps) {
5283
5336
  let raw;
5284
5337
  try {
5285
- raw = await fs3.readFile(deps.globalConfigPath, "utf8");
5338
+ raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
5286
5339
  } catch {
5287
5340
  return {};
5288
5341
  }
@@ -5298,7 +5351,7 @@ async function loadProviders(deps) {
5298
5351
  async function mutateProviders(deps, mutator) {
5299
5352
  let raw;
5300
5353
  try {
5301
- raw = await fs3.readFile(deps.globalConfigPath, "utf8");
5354
+ raw = await fsp2.readFile(deps.globalConfigPath, "utf8");
5302
5355
  } catch {
5303
5356
  raw = "{}";
5304
5357
  }
@@ -5498,7 +5551,7 @@ var doctorCmd = async (_args, deps) => {
5498
5551
  });
5499
5552
  }
5500
5553
  try {
5501
- await fs3.access(deps.paths.secretsKey);
5554
+ await fsp2.access(deps.paths.secretsKey);
5502
5555
  checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
5503
5556
  } catch {
5504
5557
  checks.push({
@@ -5508,10 +5561,10 @@ var doctorCmd = async (_args, deps) => {
5508
5561
  });
5509
5562
  }
5510
5563
  try {
5511
- await fs3.mkdir(deps.paths.projectSessions, { recursive: true });
5512
- const probe = path20.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
5513
- await fs3.writeFile(probe, "");
5514
- await fs3.unlink(probe);
5564
+ await fsp2.mkdir(deps.paths.projectSessions, { recursive: true });
5565
+ const probe = path21.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
5566
+ await fsp2.writeFile(probe, "");
5567
+ await fsp2.unlink(probe);
5515
5568
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
5516
5569
  } catch (err) {
5517
5570
  checks.push({
@@ -5612,8 +5665,8 @@ var exportCmd = async (args, deps) => {
5612
5665
  return 1;
5613
5666
  }
5614
5667
  if (output) {
5615
- await fs3.mkdir(path20.dirname(path20.resolve(deps.cwd, output)), { recursive: true });
5616
- await fs3.writeFile(path20.resolve(deps.cwd, output), rendered, "utf8");
5668
+ await fsp2.mkdir(path21.dirname(path21.resolve(deps.cwd, output)), { recursive: true });
5669
+ await fsp2.writeFile(path21.resolve(deps.cwd, output), rendered, "utf8");
5617
5670
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
5618
5671
  `);
5619
5672
  } else {
@@ -5670,17 +5723,17 @@ var initCmd = async (_args, deps) => {
5670
5723
  } else {
5671
5724
  deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
5672
5725
  }
5673
- await fs3.mkdir(deps.paths.globalRoot, { recursive: true });
5726
+ await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
5674
5727
  const config = { version: 1, provider: providerId, model: modelId };
5675
5728
  if (apiKey) config.apiKey = apiKey;
5676
- const keyFile = path20.join(path20.dirname(deps.paths.globalConfig), ".key");
5729
+ const keyFile = path21.join(path21.dirname(deps.paths.globalConfig), ".key");
5677
5730
  const vault = new DefaultSecretVault$1({ keyFile });
5678
5731
  const encrypted = encryptConfigSecrets(config, vault);
5679
5732
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
5680
- await fs3.mkdir(path20.join(deps.projectRoot, ".wrongstack"), { recursive: true });
5681
- const agentsFile = path20.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
5733
+ await fsp2.mkdir(path21.join(deps.projectRoot, ".wrongstack"), { recursive: true });
5734
+ const agentsFile = path21.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
5682
5735
  try {
5683
- await fs3.access(agentsFile);
5736
+ await fsp2.access(agentsFile);
5684
5737
  } catch {
5685
5738
  const detected2 = await detectProjectFacts(deps.projectRoot);
5686
5739
  await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
@@ -5756,7 +5809,7 @@ async function addMcpServer(args, deps) {
5756
5809
  serverCfg.enabled = enable;
5757
5810
  let existing = {};
5758
5811
  try {
5759
- existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
5812
+ existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
5760
5813
  } catch {
5761
5814
  }
5762
5815
  const mcpServers = existing.mcpServers ?? {};
@@ -5776,7 +5829,7 @@ async function addMcpServer(args, deps) {
5776
5829
  async function removeMcpServer(name, deps) {
5777
5830
  let existing = {};
5778
5831
  try {
5779
- existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
5832
+ existing = JSON.parse(await fsp2.readFile(deps.paths.globalConfig, "utf8"));
5780
5833
  } catch {
5781
5834
  deps.renderer.writeError("No config file found.\n");
5782
5835
  return 1;
@@ -5897,7 +5950,7 @@ function renderConfiguredPlugins(config) {
5897
5950
  }
5898
5951
  async function readConfig(file) {
5899
5952
  try {
5900
- return JSON.parse(await fs3.readFile(file, "utf8"));
5953
+ return JSON.parse(await fsp2.readFile(file, "utf8"));
5901
5954
  } catch {
5902
5955
  return {};
5903
5956
  }
@@ -5988,9 +6041,9 @@ var usageCmd = async (_args, deps) => {
5988
6041
  return 0;
5989
6042
  };
5990
6043
  var projectsCmd = async (_args, deps) => {
5991
- const projectsRoot = path20.join(deps.paths.globalRoot, "projects");
6044
+ const projectsRoot = path21.join(deps.paths.globalRoot, "projects");
5992
6045
  try {
5993
- const entries = await fs3.readdir(projectsRoot);
6046
+ const entries = await fsp2.readdir(projectsRoot);
5994
6047
  if (entries.length === 0) {
5995
6048
  deps.renderer.write("No projects tracked.\n");
5996
6049
  return 0;
@@ -5998,7 +6051,7 @@ var projectsCmd = async (_args, deps) => {
5998
6051
  for (const hash of entries) {
5999
6052
  try {
6000
6053
  const meta = JSON.parse(
6001
- await fs3.readFile(path20.join(projectsRoot, hash, "meta.json"), "utf8")
6054
+ await fsp2.readFile(path21.join(projectsRoot, hash, "meta.json"), "utf8")
6002
6055
  );
6003
6056
  deps.renderer.write(
6004
6057
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -6141,9 +6194,210 @@ function redactKeys(obj) {
6141
6194
  }
6142
6195
  return out;
6143
6196
  }
6197
+ var sessionsFleetCmd = async (args, deps) => {
6198
+ const runId = args.find((a) => !a.startsWith("-"));
6199
+ if (runId) {
6200
+ return showFleetRun(runId, deps);
6201
+ }
6202
+ return listFleetRuns(deps);
6203
+ };
6204
+ async function listFleetRuns(deps) {
6205
+ let entries = [];
6206
+ try {
6207
+ entries = await fsp2.readdir(deps.paths.projectSessions);
6208
+ } catch {
6209
+ deps.renderer.writeError(`Cannot read projectSessions: ${deps.paths.projectSessions}
6210
+ `);
6211
+ return 1;
6212
+ }
6213
+ const runs = [];
6214
+ for (const id of entries) {
6215
+ const runDir = path21.join(deps.paths.projectSessions, id);
6216
+ let stat3;
6217
+ try {
6218
+ stat3 = await fsp2.stat(runDir);
6219
+ } catch {
6220
+ continue;
6221
+ }
6222
+ if (!stat3.isDirectory()) continue;
6223
+ let manifest = false;
6224
+ let checkpoint = false;
6225
+ let subagentCount = 0;
6226
+ let subagentsDir;
6227
+ try {
6228
+ await fsp2.access(path21.join(runDir, "fleet.json"));
6229
+ manifest = true;
6230
+ } catch {
6231
+ }
6232
+ try {
6233
+ await fsp2.access(path21.join(runDir, "checkpoint.json"));
6234
+ checkpoint = true;
6235
+ } catch {
6236
+ }
6237
+ try {
6238
+ subagentsDir = path21.join(runDir, "subagents");
6239
+ const files = await fsp2.readdir(subagentsDir);
6240
+ subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
6241
+ } catch {
6242
+ }
6243
+ runs.push({ id, manifest, checkpoint, subagents: subagentCount });
6244
+ }
6245
+ if (runs.length === 0) {
6246
+ deps.renderer.write("No fleet runs found.\n");
6247
+ return 0;
6248
+ }
6249
+ deps.renderer.write(color.bold("\nFleet Runs\n") + "\n");
6250
+ for (const r of runs.sort((a, b) => b.id.localeCompare(a.id))) {
6251
+ const checkpointFlag = r.checkpoint ? color.green("\u2713") : color.dim("\u25CB");
6252
+ const manifestFlag = r.manifest ? color.green("\u2713") : color.dim("\u25CB");
6253
+ const subagentInfo = r.subagents > 0 ? color.dim(` ${r.subagents} subagent jsonl`) : "";
6254
+ deps.renderer.write(
6255
+ ` ${color.bold(r.id)} ${checkpointFlag} checkpoint ${manifestFlag} manifest${subagentInfo}
6256
+ `
6257
+ );
6258
+ }
6259
+ deps.renderer.write(
6260
+ `
6261
+ ${color.dim("Run `wrongstack sessions fleet <runId>` for details.")}
6262
+ `
6263
+ );
6264
+ return 0;
6265
+ }
6266
+ async function showFleetRun(runId, deps) {
6267
+ const runDir = path21.join(deps.paths.projectSessions, runId);
6268
+ let stat3;
6269
+ try {
6270
+ stat3 = await fsp2.stat(runDir);
6271
+ } catch {
6272
+ deps.renderer.writeError(`Fleet run not found: ${runId}
6273
+ `);
6274
+ return 1;
6275
+ }
6276
+ if (!stat3.isDirectory()) {
6277
+ deps.renderer.writeError(`Not a directory: ${runId}
6278
+ `);
6279
+ return 1;
6280
+ }
6281
+ deps.renderer.write(color.bold(`
6282
+ Fleet Run: ${runId}
6283
+ `) + "\n");
6284
+ const manifestPath = path21.join(runDir, "fleet.json");
6285
+ let manifestData = null;
6286
+ try {
6287
+ manifestData = await fsp2.readFile(manifestPath, "utf8");
6288
+ const manifest = JSON.parse(manifestData);
6289
+ const subagents = manifest.subagents ?? [];
6290
+ const tasks = manifest.tasks ?? [];
6291
+ const completed = tasks.filter((t) => t.status === "completed" || t.status === "failed" || t.status === "timeout" || t.status === "stopped");
6292
+ deps.renderer.write(
6293
+ ` ${color.green("\u2713")} fleet.json \u2014 ${subagents.length} subagent(s), ${completed.length}/${tasks.length} tasks done
6294
+ `
6295
+ );
6296
+ } catch {
6297
+ deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
6298
+ `);
6299
+ }
6300
+ const checkpointPath = path21.join(runDir, "checkpoint.json");
6301
+ let checkpointData = null;
6302
+ try {
6303
+ checkpointData = await fsp2.readFile(checkpointPath, "utf8");
6304
+ const snap = JSON.parse(checkpointData);
6305
+ const lockPath = `${checkpointPath}.lock`;
6306
+ let lockStatus = color.dim("\u25CB no lock");
6307
+ try {
6308
+ const lockRaw = await fsp2.readFile(lockPath, "utf8");
6309
+ const lock = JSON.parse(lockRaw);
6310
+ lockStatus = `${color.yellow("\u25B8")} lock held by pid ${lock.pid} on ${lock.hostname} (started ${lock.startedAt})`;
6311
+ } catch {
6312
+ lockStatus = color.green("\u2713 no lock (safe to resume)");
6313
+ }
6314
+ deps.renderer.write(
6315
+ ` ${color.green("\u2713")} checkpoint.json \u2014 updated ${snap.updatedAt}, ${snap.spawnCount} spawns, ${snap.tasks?.length ?? 0} tasks tracked
6316
+ ${lockStatus}
6317
+ `
6318
+ );
6319
+ } catch {
6320
+ deps.renderer.write(` ${color.dim("\u25CB")} checkpoint.json \u2014 not found
6321
+ `);
6322
+ }
6323
+ if (checkpointData) {
6324
+ try {
6325
+ const snap = JSON.parse(checkpointData);
6326
+ if (snap.subagents?.length) {
6327
+ deps.renderer.write("\n Subagents:\n");
6328
+ for (const s of snap.subagents) {
6329
+ deps.renderer.write(
6330
+ ` ${color.cyan(s.id)} ${s.name ? `${s.name} ` : ""}${s.provider ? `(${s.provider}/${s.model})` : ""} spawned ${s.spawnedAt}
6331
+ `
6332
+ );
6333
+ }
6334
+ }
6335
+ if (snap.tasks?.length) {
6336
+ deps.renderer.write("\n Tasks:\n");
6337
+ for (const t of snap.tasks) {
6338
+ deps.renderer.write(
6339
+ ` ${color.dim(t.taskId)} ${t.status} ${t.description ? t.description.slice(0, 50) : "(no description)"}
6340
+ `
6341
+ );
6342
+ }
6343
+ }
6344
+ } catch {
6345
+ }
6346
+ }
6347
+ const subagentsDir = path21.join(runDir, "subagents");
6348
+ let subagentFiles = [];
6349
+ try {
6350
+ subagentFiles = await fsp2.readdir(subagentsDir);
6351
+ subagentFiles = subagentFiles.filter((f) => f.endsWith(".jsonl"));
6352
+ } catch {
6353
+ }
6354
+ if (subagentFiles.length > 0) {
6355
+ deps.renderer.write(`
6356
+ Subagent transcripts (${subagentFiles.length}):
6357
+ `);
6358
+ for (const f of subagentFiles.sort()) {
6359
+ const filePath = path21.join(subagentsDir, f);
6360
+ let size;
6361
+ try {
6362
+ const s = await fsp2.stat(filePath);
6363
+ size = s.size;
6364
+ } catch {
6365
+ size = 0;
6366
+ }
6367
+ const sizeStr = size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)}MB` : `${(size / 1024).toFixed(0)}KB`;
6368
+ deps.renderer.write(` ${color.dim(f)} ${color.dim(sizeStr)}
6369
+ `);
6370
+ }
6371
+ } else {
6372
+ deps.renderer.write(`
6373
+ ${color.dim("\u25CB")} No subagent transcripts
6374
+ `);
6375
+ }
6376
+ const sharedDir = path21.join(runDir, "shared");
6377
+ try {
6378
+ const files = await fsp2.readdir(sharedDir);
6379
+ deps.renderer.write(`
6380
+ Shared scratchpad: ${files.length} file(s)
6381
+ `);
6382
+ } catch {
6383
+ deps.renderer.write(`
6384
+ ${color.dim("\u25CB")} No shared scratchpad
6385
+ `);
6386
+ }
6387
+ deps.renderer.write(
6388
+ `
6389
+ ${color.dim("Resume: wrongstack --resume " + runId)}
6390
+ `
6391
+ );
6392
+ return 0;
6393
+ }
6144
6394
 
6145
6395
  // src/subcommands/handlers/sessions-config.ts
6146
- var sessionsCmd = async (_args, deps) => {
6396
+ var sessionsCmd = async (args, deps) => {
6397
+ const sub = args[0];
6398
+ if (sub === "fleet") {
6399
+ return sessionsFleetCmd(args.slice(1), deps);
6400
+ }
6147
6401
  if (!deps.sessionStore) {
6148
6402
  deps.renderer.writeError("No session store available.");
6149
6403
  return 1;
@@ -6275,7 +6529,7 @@ function parseRewindFlags(args) {
6275
6529
  var rewindCmd = async (args, deps) => {
6276
6530
  const flags = parseRewindFlags(args);
6277
6531
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
6278
- const sessionsDir = path20.join(wpaths.globalRoot, "sessions");
6532
+ const sessionsDir = path21.join(wpaths.globalRoot, "sessions");
6279
6533
  const rewind = new DefaultSessionRewinder(sessionsDir);
6280
6534
  let sessionId = args.find((a) => !a.startsWith("--"));
6281
6535
  if (!sessionId) {
@@ -6485,22 +6739,22 @@ function fmtDuration(ms) {
6485
6739
  const remMin = m - h * 60;
6486
6740
  return `${h}h${remMin}m`;
6487
6741
  }
6488
- function fmtTaskResultLine(r, color32) {
6742
+ function fmtTaskResultLine(r, color33) {
6489
6743
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
6490
6744
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
6491
6745
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
6492
6746
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
6493
- const errKindChip = errKind ? color32.dim(` [${errKind}]`) : "";
6494
- const errSnip = errMsg || errKind ? `${errKindChip}${color32.dim(errTail)}` : "";
6747
+ const errKindChip = errKind ? color33.dim(` [${errKind}]`) : "";
6748
+ const errSnip = errMsg || errKind ? `${errKindChip}${color33.dim(errTail)}` : "";
6495
6749
  switch (r.status) {
6496
6750
  case "success":
6497
- return { mark: color32.green("\u2713"), stats, tail: "" };
6751
+ return { mark: color33.green("\u2713"), stats, tail: "" };
6498
6752
  case "timeout":
6499
- return { mark: color32.yellow("\u23F1"), stats: `${color32.yellow("timeout")} ${stats}`, tail: errSnip };
6753
+ return { mark: color33.yellow("\u23F1"), stats: `${color33.yellow("timeout")} ${stats}`, tail: errSnip };
6500
6754
  case "stopped":
6501
- return { mark: color32.dim("\u2298"), stats: `${color32.dim("stopped")} ${stats}`, tail: errSnip };
6755
+ return { mark: color33.dim("\u2298"), stats: `${color33.dim("stopped")} ${stats}`, tail: errSnip };
6502
6756
  case "failed":
6503
- return { mark: color32.red("\u2717"), stats: `${color32.red("failed")} ${stats}`, tail: errSnip };
6757
+ return { mark: color33.red("\u2717"), stats: `${color33.red("failed")} ${stats}`, tail: errSnip };
6504
6758
  }
6505
6759
  }
6506
6760
 
@@ -6510,7 +6764,7 @@ function resolveBundledSkillsDir() {
6510
6764
  try {
6511
6765
  const req2 = createRequire(import.meta.url);
6512
6766
  const corePkg = req2.resolve("@wrongstack/core/package.json");
6513
- return path20.join(path20.dirname(corePkg), "skills");
6767
+ return path21.join(path21.dirname(corePkg), "skills");
6514
6768
  } catch {
6515
6769
  return void 0;
6516
6770
  }
@@ -7085,6 +7339,8 @@ async function execute(deps) {
7085
7339
  result = await agent.run(query, { signal: ctrl.signal });
7086
7340
  } finally {
7087
7341
  process.off("SIGINT", onSigint);
7342
+ const { getProcessRegistry } = await import('@wrongstack/tools');
7343
+ getProcessRegistry().killAll();
7088
7344
  }
7089
7345
  const after = tokenCounter.total();
7090
7346
  const costAfter = tokenCounter.estimateCost().total;
@@ -7236,7 +7492,7 @@ async function execute(deps) {
7236
7492
  supportsVision,
7237
7493
  attachments,
7238
7494
  effectiveMaxContext,
7239
- projectName: path20.basename(projectRoot) || void 0,
7495
+ projectName: path21.basename(projectRoot) || void 0,
7240
7496
  getAutonomy,
7241
7497
  skillLoader
7242
7498
  });
@@ -7254,7 +7510,7 @@ async function execute(deps) {
7254
7510
  supportsVision,
7255
7511
  attachments,
7256
7512
  effectiveMaxContext,
7257
- projectName: path20.basename(projectRoot) || void 0,
7513
+ projectName: path21.basename(projectRoot) || void 0,
7258
7514
  getAutonomy,
7259
7515
  skillLoader
7260
7516
  });
@@ -7283,22 +7539,18 @@ var MultiAgentHost = class {
7283
7539
  this.opts = opts;
7284
7540
  }
7285
7541
  deps;
7286
- coordinator;
7287
- /** Lazily built when `opts.directorMode` is set. Owns its own internal
7288
- * coordinator; the host's `coordinator` field still points at it so
7289
- * the rest of the methods don't need to branch. */
7290
7542
  director;
7543
+ /** Own FleetManager — created in buildDirector(), used for pending task
7544
+ * tracking so status() can show descriptions without host-side state. */
7545
+ fleetManager;
7291
7546
  /** Lazily built alongside the director — produces per-subagent JSONL
7292
- * writers under `<sessionsRoot>/<runId>/`. Null in non-director mode. */
7547
+ * writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
7293
7548
  sessionFactory;
7294
- pending = /* @__PURE__ */ new Map();
7295
- results = [];
7296
7549
  opts;
7297
7550
  /**
7298
- * Populated by `promoteToDirector` when it refuses to promote (typically
7299
- * because a non-director coordinator is already running). The delegate
7551
+ * Populated by `promoteToDirector` when it refuses to promote. The delegate
7300
7552
  * tool reads this through `getPromotionBlockReason` to render an
7301
- * actionable error instead of a generic "could not activate director".
7553
+ * actionable error instead of a generic "Director could not be activated".
7302
7554
  */
7303
7555
  promotionBlockReason = null;
7304
7556
  /**
@@ -7311,14 +7563,34 @@ var MultiAgentHost = class {
7311
7563
  * orchestration tools and `--director` becomes a no-op.
7312
7564
  */
7313
7565
  async ensureDirector() {
7566
+ if (this.director) return this.director;
7314
7567
  if (!this.opts.directorMode) return null;
7315
- await this.ensureCoordinator();
7568
+ await this.buildDirector();
7316
7569
  return this.director ?? null;
7317
7570
  }
7318
- async ensureCoordinator() {
7319
- if (this.coordinator) return this.coordinator;
7571
+ /** Access the Director's internal coordinator. Returns the concrete
7572
+ * `DefaultMultiAgentCoordinator` so callers can use class-only surface
7573
+ * (`on`, `setRunner`) that isn't part of the `MultiAgentCoordinator`
7574
+ * interface. */
7575
+ getCoordinator() {
7576
+ return this.director.coordinator;
7577
+ }
7578
+ async buildDirector() {
7579
+ if (this.director) return;
7320
7580
  const config = this.deps.configStore.get();
7321
- if (this.opts.directorMode && this.opts.sessionsRoot && !this.sessionFactory) {
7581
+ const fleetManager = new FleetManager({
7582
+ manifestPath: this.opts.manifestPath,
7583
+ sessionsRoot: this.opts.sessionsRoot,
7584
+ directorRunId: this.opts.directorRunId,
7585
+ stateCheckpointPath: this.opts.stateCheckpointPath,
7586
+ sessionWriter: this.opts.sessionWriter,
7587
+ directorBudget: this.opts.directorBudget,
7588
+ manifestDebounceMs: 2e3,
7589
+ checkpointDebounceMs: this.opts.checkpointDebounceMs ?? 250,
7590
+ maxSpawnDepth: 5
7591
+ });
7592
+ this.fleetManager = fleetManager;
7593
+ if (this.opts.sessionsRoot && !this.sessionFactory) {
7322
7594
  this.sessionFactory = makeDirectorSessionFactory({
7323
7595
  sessionsRoot: this.opts.sessionsRoot,
7324
7596
  directorRunId: this.opts.directorRunId
@@ -7328,69 +7600,45 @@ var MultiAgentHost = class {
7328
7600
  coordinatorId: randomUUID(),
7329
7601
  doneCondition: { type: "all_tasks_done" },
7330
7602
  maxConcurrent: 8
7331
- // No defaultBudget. Caps land on a subagent ONLY when the
7332
- // orchestrator (delegate-tool / spawn_subagent) or the user
7333
- // (CLI flag) sets them explicitly. The prior defaults
7334
- // (1000 tools / 200 iter / 4h) silently killed long autonomous
7335
- // runs; for a "work until done" director we want no implicit
7336
- // ceilings. The orchestrator can still cap a single subagent
7337
- // by passing maxToolCalls/maxIterations through the spawn tool.
7338
7603
  };
7339
- if (this.opts.directorMode) {
7340
- this.director = new Director({
7341
- config: coordinatorConfig,
7342
- manifestPath: this.opts.manifestPath,
7343
- sharedScratchpadPath: this.opts.sharedScratchpadPath,
7344
- stateCheckpointPath: this.opts.stateCheckpointPath,
7345
- sessionWriter: this.opts.sessionWriter,
7346
- // Autonomy: allow nested directors a few levels deep. Default
7347
- // is 2 (root + one tier of workers), which trips the moment a
7348
- // worker tries to recurse into "let me delegate the parser
7349
- // analysis to a tighter specialist". 5 lets the director
7350
- // structure work as deeply as the task requires without us
7351
- // having to pass a flag every time.
7352
- maxSpawnDepth: 5
7353
- });
7354
- this.director.on("task.completed", ({ task, result }) => {
7355
- this.results.push(result);
7356
- this.pending.delete(task.id);
7357
- this.emitLifecycleCompleted(task.id, result);
7604
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
7605
+ this.director = new Director({
7606
+ config: coordinatorConfig,
7607
+ manifestPath: this.opts.manifestPath,
7608
+ sharedScratchpadPath: defaultScratchpad,
7609
+ stateCheckpointPath: this.opts.stateCheckpointPath,
7610
+ sessionWriter: this.opts.sessionWriter,
7611
+ directorBudget: this.opts.directorBudget,
7612
+ maxBudgetExtensions: this.opts.maxBudgetExtensions,
7613
+ checkpointDebounceMs: this.opts.checkpointDebounceMs,
7614
+ sessionsRoot: this.opts.sessionsRoot,
7615
+ directorRunId: this.opts.directorRunId,
7616
+ maxSpawnDepth: 5,
7617
+ fleetManager
7618
+ // pass so director.fleetManager is never undefined
7619
+ });
7620
+ this.director.on("task.completed", ({ task, result }) => {
7621
+ this.fleetManager?.removePendingTask(task.id);
7622
+ this.emitLifecycleCompleted(task.id, result);
7623
+ });
7624
+ this.director.fleet.filter("budget.threshold_reached", (e) => {
7625
+ const payload = e.payload;
7626
+ this.deps.events.emit("subagent.budget_warning", {
7627
+ subagentId: e.subagentId,
7628
+ kind: payload.kind,
7629
+ used: payload.used,
7630
+ limit: payload.limit
7358
7631
  });
7359
- this.director.fleet.filter("budget.threshold_reached", (e) => {
7360
- const payload = e.payload;
7361
- this.deps.events.emit("subagent.budget_warning", {
7362
- subagentId: e.subagentId,
7363
- kind: payload.kind,
7364
- used: payload.used,
7365
- limit: payload.limit
7366
- });
7632
+ });
7633
+ this.getCoordinator().on("task.assigned", ({ task, subagentId }) => {
7634
+ this.deps.events.emit("subagent.task_started", {
7635
+ subagentId,
7636
+ taskId: task.id,
7637
+ description: task.description
7367
7638
  });
7368
- this.coordinator = this.director.coordinator;
7369
- } else {
7370
- this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
7371
- this.coordinator.on(
7372
- "task.completed",
7373
- ({ task, result }) => {
7374
- this.results.push(result);
7375
- this.pending.delete(task.id);
7376
- this.emitLifecycleCompleted(task.id, result);
7377
- }
7378
- );
7379
- }
7380
- this.coordinator.on(
7381
- "task.assigned",
7382
- ({ task, subagentId }) => {
7383
- this.deps.events.emit("subagent.task_started", {
7384
- subagentId,
7385
- taskId: task.id,
7386
- description: task.description
7387
- });
7388
- }
7389
- );
7639
+ });
7390
7640
  const runner = this.buildSubagentRunner(config);
7391
- const innerCoord = this.opts.directorMode ? this.director.coordinator : this.coordinator;
7392
- innerCoord.setRunner(runner);
7393
- return this.coordinator;
7641
+ this.getCoordinator().setRunner(runner);
7394
7642
  }
7395
7643
  /**
7396
7644
  * Build the per-subagent runner: agent factory → runner. Extracted so
@@ -7472,7 +7720,7 @@ var MultiAgentHost = class {
7472
7720
  };
7473
7721
  return { agent, events, dispose };
7474
7722
  };
7475
- return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet });
7723
+ return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet ?? NULL_FLEET_BUS });
7476
7724
  }
7477
7725
  /**
7478
7726
  * Build a Provider for a subagent. When `overrideId` is supplied (from
@@ -7518,7 +7766,7 @@ var MultiAgentHost = class {
7518
7766
  * the full tool registry.
7519
7767
  */
7520
7768
  async spawn(description, opts) {
7521
- await this.ensureCoordinator();
7769
+ await this.buildDirector();
7522
7770
  const subagentConfig = {
7523
7771
  name: opts?.name ?? "adhoc",
7524
7772
  role: "general",
@@ -7526,35 +7774,11 @@ var MultiAgentHost = class {
7526
7774
  model: opts?.model,
7527
7775
  tools: opts?.tools
7528
7776
  };
7529
- const transcriptPath = this.sessionFactory ? path20.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
7530
- if (this.director) {
7531
- const subagentId = await this.director.spawn(subagentConfig);
7532
- const taskId2 = randomUUID();
7533
- this.pending.set(taskId2, { description, subagentId });
7534
- this.deps.events.emit("subagent.spawned", {
7535
- subagentId,
7536
- taskId: taskId2,
7537
- name: subagentConfig.name,
7538
- provider: opts?.provider,
7539
- model: opts?.model,
7540
- description,
7541
- transcriptPath
7542
- });
7543
- await this.director.assign({
7544
- id: taskId2,
7545
- description,
7546
- subagentId
7547
- // No maxToolCalls — same reasoning as the spawn config above.
7548
- // The director / orchestrator owns the budget decision.
7549
- });
7550
- return { subagentId, taskId: taskId2 };
7551
- }
7552
- const coord = this.coordinator;
7553
- const spawned = await coord.spawn(subagentConfig);
7554
- const taskId = randomUUID();
7555
- this.pending.set(taskId, { description, subagentId: spawned.subagentId });
7777
+ const transcriptPath = this.sessionFactory ? path21.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
7778
+ const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
7779
+ this.fleetManager?.addPendingTask(taskId, subagentId, description);
7556
7780
  this.deps.events.emit("subagent.spawned", {
7557
- subagentId: spawned.subagentId,
7781
+ subagentId,
7558
7782
  taskId,
7559
7783
  name: subagentConfig.name,
7560
7784
  provider: opts?.provider,
@@ -7562,13 +7786,22 @@ var MultiAgentHost = class {
7562
7786
  description,
7563
7787
  transcriptPath
7564
7788
  });
7565
- await coord.assign({
7566
- id: taskId,
7567
- description,
7568
- subagentId: spawned.subagentId
7569
- // No maxToolCalls see comment on the director branch above.
7570
- });
7571
- return { subagentId: spawned.subagentId, taskId };
7789
+ return { subagentId, taskId };
7790
+ }
7791
+ /**
7792
+ * Common spawn + assign logic shared by both director mode and raw
7793
+ * coordinator mode. Extracts the identical body from the two branches
7794
+ * in `spawn()` so future changes (e.g. adding a new field to both
7795
+ * paths) are made in one place.
7796
+ *
7797
+ * Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
7798
+ * and event emission — the helper only talks to the coordinator.
7799
+ */
7800
+ async _spawnAndAssign(subagentConfig) {
7801
+ const taskId = randomUUID();
7802
+ const subagentId = await this.director.spawn(subagentConfig);
7803
+ await this.director.assign({ id: taskId, description: "", subagentId });
7804
+ return { subagentId, taskId };
7572
7805
  }
7573
7806
  /**
7574
7807
  * Relay a `task.completed` notification (from either the Director or
@@ -7590,21 +7823,25 @@ var MultiAgentHost = class {
7590
7823
  });
7591
7824
  }
7592
7825
  status() {
7593
- const pending = Array.from(this.pending.entries()).map(([taskId, v]) => ({
7594
- taskId,
7595
- description: v.description,
7596
- subagentId: v.subagentId
7597
- }));
7826
+ const activeSubagentIds = /* @__PURE__ */ new Set();
7598
7827
  const live = [];
7599
- if (this.coordinator) {
7600
- const s = this.coordinator.getStatus();
7828
+ if (this.director) {
7829
+ const coord = this.getCoordinator();
7830
+ const s = coord.getStatus();
7601
7831
  for (const a of s.subagents) {
7832
+ if (a.status === "running" || a.status === "idle") {
7833
+ activeSubagentIds.add(a.id);
7834
+ }
7602
7835
  live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
7603
7836
  }
7604
7837
  }
7838
+ const fleetStatus = this.fleetManager?.getFleetStatus() ?? { pending: []};
7839
+ const pending = fleetStatus.pending.filter((p) => activeSubagentIds.has(p.subagentId));
7840
+ const completed = this.director ? this.director.completedResults() : [];
7841
+ const completedCount = completed.length;
7605
7842
  const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
7606
- const summary = !this.coordinator ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${this.results.length} completed.` : `${pending.length} pending, ${this.results.length} completed.`;
7607
- return { pending, completed: this.results, live, summary };
7843
+ const summary = !this.director ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${completedCount} completed.` : `${pending.length} pending, ${completedCount} completed.`;
7844
+ return { pending, completed, live, summary };
7608
7845
  }
7609
7846
  /**
7610
7847
  * Roll up per-subagent runtime cost from completed TaskResults. We don't
@@ -7617,8 +7854,9 @@ var MultiAgentHost = class {
7617
7854
  * the table renders the most interesting subagent at the top.
7618
7855
  */
7619
7856
  usage() {
7857
+ const completed = this.director ? this.director.completedResults() : [];
7620
7858
  const bySubagent = /* @__PURE__ */ new Map();
7621
- for (const r of this.results) {
7859
+ for (const r of completed) {
7622
7860
  const cur = bySubagent.get(r.subagentId) ?? {
7623
7861
  tasks: 0,
7624
7862
  iterations: 0,
@@ -7664,7 +7902,7 @@ var MultiAgentHost = class {
7664
7902
  */
7665
7903
  async manifest() {
7666
7904
  if (!this.director) return null;
7667
- return this.director.writeManifest();
7905
+ return await this.director.fleetManager?.writeManifest() ?? null;
7668
7906
  }
7669
7907
  /**
7670
7908
  * Promote a non-director session to director mode at runtime. Only
@@ -7677,25 +7915,18 @@ var MultiAgentHost = class {
7677
7915
  */
7678
7916
  async promoteToDirector() {
7679
7917
  if (this.director) return this.director;
7680
- if (this.coordinator) {
7681
- const status = this.coordinator.getStatus();
7682
- const running = status.subagents.filter((s) => s.status === "running").length;
7683
- const idle = status.subagents.filter((s) => s.status === "idle").length;
7684
- this.promotionBlockReason = `Cannot promote to director: a non-director coordinator is already in use (${running} running, ${idle} idle, ${status.pendingTasks} pending tasks). Stop the existing subagents with /fleet kill <id> or wait for them to finish, then retry \u2014 or restart wstack with --director to start in director mode.`;
7685
- return null;
7686
- }
7687
7918
  this.opts.directorMode = true;
7688
7919
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
7689
- this.opts.manifestPath = path20.join(this.opts.fleetRoot, "fleet.json");
7920
+ this.opts.manifestPath = path21.join(this.opts.fleetRoot, "fleet.json");
7690
7921
  }
7691
7922
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
7692
- this.opts.sharedScratchpadPath = path20.join(this.opts.fleetRoot, "shared");
7923
+ this.opts.sharedScratchpadPath = path21.join(this.opts.fleetRoot, "shared");
7693
7924
  }
7694
7925
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
7695
- this.opts.sessionsRoot = path20.join(this.opts.fleetRoot, "subagents");
7926
+ this.opts.sessionsRoot = path21.join(this.opts.fleetRoot, "subagents");
7696
7927
  }
7697
7928
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
7698
- this.opts.stateCheckpointPath = path20.join(this.opts.fleetRoot, "director-state.json");
7929
+ this.opts.stateCheckpointPath = path21.join(this.opts.fleetRoot, "director-state.json");
7699
7930
  }
7700
7931
  await this.ensureDirector();
7701
7932
  return this.director ?? null;
@@ -7725,13 +7956,13 @@ var MultiAgentHost = class {
7725
7956
  * called /fleet kill before any /spawn, and there's nothing to do.
7726
7957
  */
7727
7958
  async kill(subagentId) {
7728
- if (!this.coordinator) return false;
7729
- await this.coordinator.stop(subagentId);
7959
+ if (!this.director) return false;
7960
+ await this.getCoordinator().stop(subagentId);
7730
7961
  return true;
7731
7962
  }
7732
7963
  async stopAll() {
7733
- if (this.coordinator) {
7734
- await this.coordinator.stopAll();
7964
+ if (this.director) {
7965
+ await this.getCoordinator().stopAll();
7735
7966
  }
7736
7967
  }
7737
7968
  };
@@ -7816,11 +8047,11 @@ var SessionStats = class {
7816
8047
  if (e.name === "bash") this.bashCommands++;
7817
8048
  else if (e.name === "fetch") this.fetches++;
7818
8049
  if (!e.ok) return;
7819
- const path21 = typeof input?.path === "string" ? input.path : void 0;
7820
- if (e.name === "read" && path21) this.readPaths.add(path21);
7821
- else if (e.name === "edit" && path21) this.editedPaths.add(path21);
7822
- else if (e.name === "write" && path21) {
7823
- this.writtenPaths.add(path21);
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);
7824
8055
  const content = typeof input?.content === "string" ? input.content : "";
7825
8056
  this.bytesWritten += Buffer.byteLength(content, "utf8");
7826
8057
  }
@@ -8151,12 +8382,12 @@ async function setupSession(params) {
8151
8382
  }
8152
8383
  const sessionRef = { current: session };
8153
8384
  await recoveryLock.write(session.id).catch(() => void 0);
8154
- const attachments = new DefaultAttachmentStore({ spoolDir: path20.join(wpaths.projectSessions, session.id, "attachments") });
8155
- const queueStore = new QueueStore({ dir: path20.join(wpaths.projectSessions, session.id) });
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) });
8156
8387
  const ctxSignal = new AbortController().signal;
8157
8388
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
8158
8389
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
8159
- const todosCheckpointPath = path20.join(wpaths.projectSessions, `${session.id}.todos.json`);
8390
+ const todosCheckpointPath = path21.join(wpaths.projectSessions, `${session.id}.todos.json`);
8160
8391
  if (resumeId) {
8161
8392
  try {
8162
8393
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -8168,12 +8399,13 @@ async function setupSession(params) {
8168
8399
  }
8169
8400
  }
8170
8401
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
8171
- const planPath = path20.join(wpaths.projectSessions, `${session.id}.plan.json`);
8402
+ const planPath = path21.join(wpaths.projectSessions, `${session.id}.plan.json`);
8172
8403
  context.state.setMeta("plan.path", planPath);
8404
+ let dirState;
8173
8405
  if (resumeId) {
8174
8406
  try {
8175
- const fleetRoot = path20.join(wpaths.projectSessions, session.id);
8176
- const dirState = await loadDirectorState(path20.join(fleetRoot, "director-state.json"));
8407
+ const fleetRoot = path21.join(wpaths.projectSessions, session.id);
8408
+ dirState = await loadDirectorState(path21.join(fleetRoot, "director-state.json"));
8177
8409
  if (dirState) {
8178
8410
  const tCounts = {};
8179
8411
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -8192,7 +8424,7 @@ async function setupSession(params) {
8192
8424
  } catch {
8193
8425
  }
8194
8426
  }
8195
- return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint };
8427
+ return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint, priorFleetState: dirState ?? void 0 };
8196
8428
  }
8197
8429
 
8198
8430
  // src/index.ts
@@ -8200,7 +8432,7 @@ function resolveBundledSkillsDir2() {
8200
8432
  try {
8201
8433
  const req2 = createRequire(import.meta.url);
8202
8434
  const corePkg = req2.resolve("@wrongstack/core/package.json");
8203
- return path20.join(path20.dirname(corePkg), "skills");
8435
+ return path21.join(path21.dirname(corePkg), "skills");
8204
8436
  } catch {
8205
8437
  return void 0;
8206
8438
  }
@@ -8307,7 +8539,7 @@ async function main(argv) {
8307
8539
  modeId,
8308
8540
  modePrompt,
8309
8541
  modelCapabilities,
8310
- planPath: () => sessionRef.current ? path20.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
8542
+ planPath: () => sessionRef.current ? path21.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
8311
8543
  })
8312
8544
  );
8313
8545
  const toolRegistry = new ToolRegistry();
@@ -8335,7 +8567,7 @@ async function main(argv) {
8335
8567
  name: "session-store",
8336
8568
  check: async () => {
8337
8569
  try {
8338
- await fs3.access(wpaths.projectSessions);
8570
+ await fsp2.access(wpaths.projectSessions);
8339
8571
  return { status: "healthy" };
8340
8572
  } catch (e) {
8341
8573
  return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
@@ -8352,7 +8584,7 @@ async function main(argv) {
8352
8584
  const dumpMetrics = () => {
8353
8585
  if (!metricsSink) return;
8354
8586
  try {
8355
- const out = path20.join(wpaths.projectSessions, "metrics.json");
8587
+ const out = path21.join(wpaths.projectSessions, "metrics.json");
8356
8588
  const snap = metricsSink.snapshot();
8357
8589
  writeFileSync(out, JSON.stringify(snap, null, 2));
8358
8590
  } catch {
@@ -8469,6 +8701,7 @@ async function main(argv) {
8469
8701
  const queueStore = sessResult.queueStore;
8470
8702
  const planPath = sessResult.planPath;
8471
8703
  const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
8704
+ const priorFleetState = sessResult.priorFleetState;
8472
8705
  const stats = new SessionStats(events, tokenCounter);
8473
8706
  const errorRing = [];
8474
8707
  events.on("error", (e) => {
@@ -8568,15 +8801,15 @@ async function main(argv) {
8568
8801
  return err instanceof Error ? err.message : String(err);
8569
8802
  }
8570
8803
  };
8571
- const directorMode = flags["director"] === true;
8804
+ const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
8572
8805
  let director = null;
8573
8806
  let autonomyMode = "off";
8574
- const fleetRoot = directorMode ? path20.join(wpaths.projectSessions, session.id) : void 0;
8575
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path20.join(fleetRoot, "fleet.json") : void 0;
8576
- const sharedScratchpadPath = directorMode ? path20.join(fleetRoot, "shared") : void 0;
8577
- const subagentSessionsRoot = directorMode ? path20.join(fleetRoot, "subagents") : void 0;
8578
- const stateCheckpointPath = directorMode ? path20.join(fleetRoot, "director-state.json") : void 0;
8579
- const fleetRootForPromotion = path20.join(wpaths.projectSessions, session.id);
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);
8580
8813
  const multiAgentHost = new MultiAgentHost(
8581
8814
  {
8582
8815
  container,
@@ -8616,6 +8849,7 @@ async function main(argv) {
8616
8849
  if (directorMode) {
8617
8850
  director = await multiAgentHost.ensureDirector();
8618
8851
  if (director) {
8852
+ if (priorFleetState) director.setCheckpointState(priorFleetState);
8619
8853
  for (const tool of director.tools(FLEET_ROSTER)) {
8620
8854
  toolRegistry.register(tool);
8621
8855
  }
@@ -8759,32 +8993,32 @@ async function main(argv) {
8759
8993
  return `Unknown fleet action: ${action}`;
8760
8994
  },
8761
8995
  onFleetLog: async (subagentId, mode) => {
8762
- const subagentsRoot = path20.join(fleetRootForPromotion, "subagents");
8996
+ const subagentsRoot = path21.join(fleetRootForPromotion, "subagents");
8763
8997
  let runDirs;
8764
8998
  try {
8765
- runDirs = await fs3.readdir(subagentsRoot);
8999
+ runDirs = await fsp2.readdir(subagentsRoot);
8766
9000
  } catch {
8767
9001
  return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
8768
9002
  }
8769
9003
  const found = [];
8770
9004
  for (const runId of runDirs) {
8771
- const runDir = path20.join(subagentsRoot, runId);
9005
+ const runDir = path21.join(subagentsRoot, runId);
8772
9006
  let files;
8773
9007
  try {
8774
- files = await fs3.readdir(runDir);
9008
+ files = await fsp2.readdir(runDir);
8775
9009
  } catch {
8776
9010
  continue;
8777
9011
  }
8778
9012
  for (const f of files) {
8779
9013
  if (!f.endsWith(".jsonl")) continue;
8780
- const full = path20.join(runDir, f);
9014
+ const full = path21.join(runDir, f);
8781
9015
  try {
8782
- const stat2 = await fs3.stat(full);
9016
+ const stat3 = await fsp2.stat(full);
8783
9017
  found.push({
8784
9018
  runId,
8785
9019
  subagentId: f.replace(/\.jsonl$/, ""),
8786
9020
  file: full,
8787
- size: stat2.size
9021
+ size: stat3.size
8788
9022
  });
8789
9023
  } catch {
8790
9024
  }
@@ -8818,7 +9052,7 @@ async function main(argv) {
8818
9052
  ].join("\n");
8819
9053
  }
8820
9054
  const t = matches[0];
8821
- const raw = await fs3.readFile(t.file, "utf8");
9055
+ const raw = await fsp2.readFile(t.file, "utf8");
8822
9056
  if (mode === "raw") return raw;
8823
9057
  const lines = raw.split("\n").filter((l) => l.trim());
8824
9058
  const counts = {};
@@ -8874,7 +9108,7 @@ async function main(argv) {
8874
9108
  }
8875
9109
  const dir = await multiAgentHost.ensureDirector();
8876
9110
  if (!dir) return "Director is not available.";
8877
- const dirStatePath = path20.join(fleetRootForPromotion, "director-state.json");
9111
+ const dirStatePath = path21.join(fleetRootForPromotion, "director-state.json");
8878
9112
  const prior = await loadDirectorState(dirStatePath);
8879
9113
  if (!prior) {
8880
9114
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -8945,9 +9179,9 @@ async function main(argv) {
8945
9179
  for (const tool of director2.tools(FLEET_ROSTER)) {
8946
9180
  toolRegistry.register(tool);
8947
9181
  }
8948
- const mp = path20.join(fleetRootForPromotion, "fleet.json");
8949
- const sp = path20.join(fleetRootForPromotion, "shared");
8950
- const ss = path20.join(fleetRootForPromotion, "subagents");
9182
+ const mp = path21.join(fleetRootForPromotion, "fleet.json");
9183
+ const sp = path21.join(fleetRootForPromotion, "shared");
9184
+ const ss = path21.join(fleetRootForPromotion, "subagents");
8951
9185
  const lines = [
8952
9186
  `${color.green("\u2713")} Promoted to director mode.`,
8953
9187
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,