@wrongstack/cli 0.41.0 → 0.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import * as path9 from 'path';
2
+ import * as path8 from 'path';
3
3
  import { join } from 'path';
4
4
  import * as fsp3 from 'fs/promises';
5
- import { color, writeErr, DefaultTaskStore, TaskTracker, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, renderTaskGraph, SpecVersioning, atomicWrite, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, resolveWstackPaths, DefaultSecretVault as DefaultSecretVault$1, migratePlaintextSecrets, DefaultConfigLoader, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, onResize, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError, ERROR_CODES } from '@wrongstack/core';
5
+ import { color, writeErr, DefaultTaskStore, TaskTracker, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, renderTaskGraph, SpecVersioning, DefaultSecretScrubber, atomicWrite, DefaultPathResolver, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, bootConfig as bootConfig$1, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, onResize, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError, ERROR_CODES } from '@wrongstack/core';
6
6
  import { createRequire } from 'module';
7
- import * as os3 from 'os';
8
- import os3__default from 'os';
7
+ import * as os2 from 'os';
8
+ import os2__default from 'os';
9
9
  import * as crypto2 from 'crypto';
10
10
  import { randomUUID } from 'crypto';
11
11
  import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
12
12
  import { WebSocketServer, WebSocket } from 'ws';
13
13
  import { MCPRegistry } from '@wrongstack/mcp';
14
- import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
14
+ import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
15
15
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
16
16
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
17
17
  import { fileURLToPath } from 'url';
@@ -1395,7 +1395,7 @@ function sddHelp() {
1395
1395
  async function gatherProjectContext2(projectRoot) {
1396
1396
  const parts = [];
1397
1397
  try {
1398
- const pkgPath = path9.join(projectRoot, "package.json");
1398
+ const pkgPath = path8.join(projectRoot, "package.json");
1399
1399
  const pkgRaw = await fsp3.readFile(pkgPath, "utf8");
1400
1400
  const pkg = JSON.parse(pkgRaw);
1401
1401
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -1411,13 +1411,13 @@ async function gatherProjectContext2(projectRoot) {
1411
1411
  } catch {
1412
1412
  }
1413
1413
  try {
1414
- const tsconfigPath = path9.join(projectRoot, "tsconfig.json");
1414
+ const tsconfigPath = path8.join(projectRoot, "tsconfig.json");
1415
1415
  await fsp3.access(tsconfigPath);
1416
1416
  parts.push("Language: TypeScript");
1417
1417
  } catch {
1418
1418
  }
1419
1419
  try {
1420
- const srcDir = path9.join(projectRoot, "src");
1420
+ const srcDir = path8.join(projectRoot, "src");
1421
1421
  const entries = await fsp3.readdir(srcDir, { withFileTypes: true });
1422
1422
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1423
1423
  if (dirs.length > 0) {
@@ -1573,7 +1573,7 @@ __export(update_check_exports, {
1573
1573
  getUpdateNotification: () => getUpdateNotification
1574
1574
  });
1575
1575
  function cachePath(homeFn = defaultHomeDir2) {
1576
- return path9.join(homeFn(), ".wrongstack", "update-cache.json");
1576
+ return path8.join(homeFn(), ".wrongstack", "update-cache.json");
1577
1577
  }
1578
1578
  function currentVersion() {
1579
1579
  const req2 = createRequire(import.meta.url);
@@ -1610,7 +1610,7 @@ async function readCache(homeFn = defaultHomeDir2) {
1610
1610
  }
1611
1611
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1612
1612
  try {
1613
- const dir = path9.dirname(cachePath(homeFn));
1613
+ const dir = path8.dirname(cachePath(homeFn));
1614
1614
  await fsp3.mkdir(dir, { recursive: true });
1615
1615
  await fsp3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1616
1616
  } catch {
@@ -1683,7 +1683,7 @@ async function getUpdateNotification(signal, homeFn) {
1683
1683
  var defaultHomeDir2, CACHE_TTL_MS;
1684
1684
  var init_update_check = __esm({
1685
1685
  "src/update-check.ts"() {
1686
- defaultHomeDir2 = () => os3.homedir();
1686
+ defaultHomeDir2 = () => os2.homedir();
1687
1687
  CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1688
1688
  }
1689
1689
  });
@@ -1696,6 +1696,7 @@ __export(webui_server_exports, {
1696
1696
  async function runWebUI(opts) {
1697
1697
  const port = opts.port ?? 3457;
1698
1698
  const clients = /* @__PURE__ */ new Map();
1699
+ const secretScrubber = new DefaultSecretScrubber();
1699
1700
  let abortController = null;
1700
1701
  const authToken = crypto2.randomBytes(16).toString("hex");
1701
1702
  const wss = new WebSocketServer({ port, host: "127.0.0.1", maxPayload: 1 * 1024 * 1024 });
@@ -1735,7 +1736,7 @@ async function runWebUI(opts) {
1735
1736
  payload: {
1736
1737
  id: e.id,
1737
1738
  name: e.name,
1738
- input: e.input,
1739
+ input: secretScrubber.scrubObject(e.input),
1739
1740
  messageId: `tool_${e.id}`
1740
1741
  }
1741
1742
  });
@@ -1764,8 +1765,8 @@ async function runWebUI(opts) {
1764
1765
  name: e.name,
1765
1766
  durationMs: e.durationMs,
1766
1767
  ok: e.ok,
1767
- input: e.input,
1768
- output: e.output
1768
+ input: secretScrubber.scrubObject(e.input),
1769
+ output: secretScrubber.scrubObject(e.output)
1769
1770
  }
1770
1771
  });
1771
1772
  })
@@ -1813,7 +1814,7 @@ async function runWebUI(opts) {
1813
1814
  );
1814
1815
  }
1815
1816
  }
1816
- return new Promise((resolve4) => {
1817
+ return new Promise((resolve3) => {
1817
1818
  wss.on("listening", () => {
1818
1819
  console.log(`[WebUI] WebSocket server running on ws://127.0.0.1:${port}`);
1819
1820
  setupEvents();
@@ -1914,7 +1915,7 @@ async function runWebUI(opts) {
1914
1915
  clients.clear();
1915
1916
  wss.close(() => {
1916
1917
  console.log("[WebUI] Server stopped");
1917
- resolve4();
1918
+ resolve3();
1918
1919
  });
1919
1920
  }
1920
1921
  process.on("SIGINT", shutdown);
@@ -2231,7 +2232,7 @@ async function runWebUI(opts) {
2231
2232
  return {};
2232
2233
  }
2233
2234
  if (!parsed.providers) return {};
2234
- const keyFile = path9.join(path9.dirname(opts.globalConfigPath), ".key");
2235
+ const keyFile = path8.join(path8.dirname(opts.globalConfigPath), ".key");
2235
2236
  const vault = new DefaultSecretVault({ keyFile });
2236
2237
  return decryptConfigSecrets(parsed.providers, vault);
2237
2238
  }
@@ -2264,7 +2265,7 @@ async function runWebUI(opts) {
2264
2265
  parsed = {};
2265
2266
  }
2266
2267
  parsed.providers = providers;
2267
- const keyFile = path9.join(path9.dirname(opts.globalConfigPath), ".key");
2268
+ const keyFile = path8.join(path8.dirname(opts.globalConfigPath), ".key");
2268
2269
  const vault = new DefaultSecretVault({ keyFile });
2269
2270
  const encrypted = encryptConfigSecrets(parsed, vault);
2270
2271
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -2338,6 +2339,44 @@ function makeProviderClassifier(provider, model) {
2338
2339
  }
2339
2340
  });
2340
2341
  }
2342
+ async function resolveRuntimeMaxContext(input) {
2343
+ const explicitContext = positiveNumber(input.config.context?.effectiveMaxContext);
2344
+ if (explicitContext) return explicitContext;
2345
+ const providerConfig = input.runtimeProviderConfig ?? input.config.providers?.[input.providerId];
2346
+ const providerOverride = positiveNumber(readConfiguredMaxContext(providerConfig));
2347
+ if (providerOverride) return providerOverride;
2348
+ const topLevelBaseUrlApplies = input.providerId === input.config.provider;
2349
+ const hasCustomBaseUrl = Boolean(
2350
+ providerConfig?.baseUrl || topLevelBaseUrlApplies && input.config.baseUrl
2351
+ );
2352
+ if (input.modelsRegistry && !hasCustomBaseUrl) {
2353
+ const mergedModels = mergeCustomModelDefs(
2354
+ providerConfig?.customModels,
2355
+ input.config.models
2356
+ );
2357
+ const caps = await capabilitiesFor(
2358
+ input.modelsRegistry,
2359
+ input.providerId,
2360
+ input.modelId,
2361
+ mergedModels
2362
+ ).catch(() => void 0);
2363
+ const catalogMax = positiveNumber(caps?.maxContext);
2364
+ if (catalogMax) return catalogMax;
2365
+ const directModel = await input.modelsRegistry.getModel(input.providerId, input.modelId).catch(() => void 0);
2366
+ const directMax = positiveNumber(directModel?.capabilities.maxContext);
2367
+ if (directMax) return directMax;
2368
+ }
2369
+ return positiveNumber(input.provider.capabilities.maxContext) ?? 0;
2370
+ }
2371
+ function readConfiguredMaxContext(providerConfig) {
2372
+ if (!providerConfig || typeof providerConfig !== "object") return void 0;
2373
+ const capabilities = providerConfig.capabilities;
2374
+ if (!capabilities || typeof capabilities !== "object") return void 0;
2375
+ return capabilities.maxContext;
2376
+ }
2377
+ function positiveNumber(value) {
2378
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
2379
+ }
2341
2380
 
2342
2381
  // src/arg-parser.ts
2343
2382
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -2361,6 +2400,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
2361
2400
  "metrics",
2362
2401
  "webui",
2363
2402
  "no-check",
2403
+ "no-models-refresh",
2364
2404
  "director",
2365
2405
  "no-director",
2366
2406
  "no-autonomy",
@@ -2485,10 +2525,10 @@ async function detectPackageManager(root, declared) {
2485
2525
  const name = declared.split("@")[0];
2486
2526
  if (name) return name;
2487
2527
  }
2488
- if (await pathExists(path9.join(root, "pnpm-lock.yaml"))) return "pnpm";
2489
- if (await pathExists(path9.join(root, "bun.lockb"))) return "bun";
2490
- if (await pathExists(path9.join(root, "bun.lock"))) return "bun";
2491
- if (await pathExists(path9.join(root, "yarn.lock"))) return "yarn";
2528
+ if (await pathExists(path8.join(root, "pnpm-lock.yaml"))) return "pnpm";
2529
+ if (await pathExists(path8.join(root, "bun.lockb"))) return "bun";
2530
+ if (await pathExists(path8.join(root, "bun.lock"))) return "bun";
2531
+ if (await pathExists(path8.join(root, "yarn.lock"))) return "yarn";
2492
2532
  return "npm";
2493
2533
  }
2494
2534
  function hasUsableScript(scripts, name) {
@@ -2509,7 +2549,7 @@ function parseMakeTargets(makefile) {
2509
2549
  async function detectProjectFacts(root) {
2510
2550
  const facts = { hints: [] };
2511
2551
  try {
2512
- const pkg = JSON.parse(await fsp3.readFile(path9.join(root, "package.json"), "utf8"));
2552
+ const pkg = JSON.parse(await fsp3.readFile(path8.join(root, "package.json"), "utf8"));
2513
2553
  const scripts = pkg.scripts ?? {};
2514
2554
  const pm = await detectPackageManager(root, pkg.packageManager);
2515
2555
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2523,14 +2563,14 @@ async function detectProjectFacts(root) {
2523
2563
  } catch {
2524
2564
  }
2525
2565
  try {
2526
- if (!await pathExists(path9.join(root, "pyproject.toml"))) throw new Error("not python");
2566
+ if (!await pathExists(path8.join(root, "pyproject.toml"))) throw new Error("not python");
2527
2567
  facts.test ??= "pytest";
2528
2568
  facts.lint ??= "ruff check .";
2529
2569
  facts.hints.push("pyproject.toml");
2530
2570
  } catch {
2531
2571
  }
2532
2572
  try {
2533
- if (!await pathExists(path9.join(root, "go.mod"))) throw new Error("not go");
2573
+ if (!await pathExists(path8.join(root, "go.mod"))) throw new Error("not go");
2534
2574
  facts.build ??= "go build ./...";
2535
2575
  facts.test ??= "go test ./...";
2536
2576
  facts.run ??= "go run .";
@@ -2538,7 +2578,7 @@ async function detectProjectFacts(root) {
2538
2578
  } catch {
2539
2579
  }
2540
2580
  try {
2541
- if (!await pathExists(path9.join(root, "Cargo.toml"))) throw new Error("not rust");
2581
+ if (!await pathExists(path8.join(root, "Cargo.toml"))) throw new Error("not rust");
2542
2582
  facts.build ??= "cargo build";
2543
2583
  facts.test ??= "cargo test";
2544
2584
  facts.lint ??= "cargo clippy";
@@ -2547,7 +2587,7 @@ async function detectProjectFacts(root) {
2547
2587
  } catch {
2548
2588
  }
2549
2589
  try {
2550
- const makefile = await fsp3.readFile(path9.join(root, "Makefile"), "utf8");
2590
+ const makefile = await fsp3.readFile(path8.join(root, "Makefile"), "utf8");
2551
2591
  const targets = parseMakeTargets(makefile);
2552
2592
  facts.build ??= targets.has("build") ? "make build" : "make";
2553
2593
  if (targets.has("test")) facts.test ??= "make test";
@@ -2729,8 +2769,16 @@ function buildAutonomyCommand(opts) {
2729
2769
  const goal = await loadGoal(goalFilePath(opts.projectRoot));
2730
2770
  if (goal) {
2731
2771
  const u = summarizeUsage(goal);
2732
- lines.push(color.dim(` Goal: ${goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal}`));
2733
- lines.push(color.dim(` Engine state: ${goal.engineState} \xB7 iterations: ${goal.iterations} \xB7 journal: ${goal.journal.length}`));
2772
+ lines.push(
2773
+ color.dim(
2774
+ ` Goal: ${goal.goal.length > 80 ? `${goal.goal.slice(0, 77)}\u2026` : goal.goal}`
2775
+ )
2776
+ );
2777
+ lines.push(
2778
+ color.dim(
2779
+ ` Engine state: ${goal.engineState} \xB7 iterations: ${goal.iterations} \xB7 journal: ${goal.journal.length}`
2780
+ )
2781
+ );
2734
2782
  if (u.iterationsWithUsage > 0) {
2735
2783
  lines.push(
2736
2784
  color.dim(
@@ -2741,7 +2789,9 @@ function buildAutonomyCommand(opts) {
2741
2789
  const recent = goal.journal.slice(-10);
2742
2790
  const failed = recent.filter((e) => e.status === "failure").length;
2743
2791
  if (failed > 0) {
2744
- lines.push(color.amber(` Recent failures: ${failed} of last ${recent.length} iterations`));
2792
+ lines.push(
2793
+ color.amber(` Recent failures: ${failed} of last ${recent.length} iterations`)
2794
+ );
2745
2795
  }
2746
2796
  }
2747
2797
  } catch {
@@ -2775,7 +2825,7 @@ function buildAutonomyCommand(opts) {
2775
2825
  }
2776
2826
  } catch {
2777
2827
  }
2778
- const msg2 = `${color.amber("Eternal/parallel mode stop requested.")} The current iteration will finish, then the loop exits.${summaryLine}`;
2828
+ const msg2 = `${color.amber("Eternal/parallel mode stop requested.")} In-flight eternal work is cancelled; parallel fan-out stops after the current tick cleans up.${summaryLine}`;
2779
2829
  opts.renderer.write(msg2);
2780
2830
  return { message: msg2 };
2781
2831
  }
@@ -2792,7 +2842,7 @@ function buildAutonomyCommand(opts) {
2792
2842
  newMode = "eternal-parallel";
2793
2843
  } else if (arg === "toggle" || arg === "cycle") {
2794
2844
  const current = opts.onAutonomy() ?? "off";
2795
- const cycle = ["off", "suggest", "auto", "eternal"];
2845
+ const cycle = ["off", "suggest", "auto", "eternal", "eternal-parallel"];
2796
2846
  newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
2797
2847
  } else {
2798
2848
  const msg2 = `Unknown argument: ${arg}. Use /autonomy on, off, suggest, eternal, parallel, stop, or toggle.`;
@@ -2907,7 +2957,7 @@ function formatPhaseList(graph) {
2907
2957
  }
2908
2958
  async function gatherProjectContext(projectRoot) {
2909
2959
  try {
2910
- const raw = await fsp3.readFile(path9.join(projectRoot, "package.json"), "utf8");
2960
+ const raw = await fsp3.readFile(path8.join(projectRoot, "package.json"), "utf8");
2911
2961
  const pkg = JSON.parse(raw);
2912
2962
  const parts = [
2913
2963
  `Project: ${String(pkg.name ?? "unknown")}`,
@@ -4771,8 +4821,8 @@ function buildInitCommand(opts) {
4771
4821
  name: "init",
4772
4822
  description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
4773
4823
  async run(_args, ctx) {
4774
- const dir = path9.join(ctx.projectRoot, ".wrongstack");
4775
- const file = path9.join(dir, "AGENTS.md");
4824
+ const dir = path8.join(ctx.projectRoot, ".wrongstack");
4825
+ const file = path8.join(dir, "AGENTS.md");
4776
4826
  const detected = await detectProjectFacts(ctx.projectRoot);
4777
4827
  const body = renderAgentsTemplate(detected);
4778
4828
  await fsp3.mkdir(dir, { recursive: true });
@@ -4963,18 +5013,18 @@ function stateBadge(state) {
4963
5013
  return color.dim(state);
4964
5014
  }
4965
5015
  }
4966
- async function readConfig(path26) {
5016
+ async function readConfig(path25) {
4967
5017
  try {
4968
- return JSON.parse(await fsp3.readFile(path26, "utf8"));
5018
+ return JSON.parse(await fsp3.readFile(path25, "utf8"));
4969
5019
  } catch {
4970
5020
  return {};
4971
5021
  }
4972
5022
  }
4973
- async function writeConfig(path26, cfg) {
5023
+ async function writeConfig(path25, cfg) {
4974
5024
  const raw = JSON.stringify(cfg, null, 2);
4975
- const tmp = path26 + ".tmp";
5025
+ const tmp = path25 + ".tmp";
4976
5026
  await fsp3.writeFile(tmp, raw, "utf8");
4977
- await fsp3.rename(tmp, path26);
5027
+ await fsp3.rename(tmp, path25);
4978
5028
  }
4979
5029
 
4980
5030
  // src/slash-commands/mcp.ts
@@ -5157,6 +5207,224 @@ ${targetMode.description}`
5157
5207
  }
5158
5208
  };
5159
5209
  }
5210
+ var noOpVault = {
5211
+ encrypt: (v) => v,
5212
+ decrypt: (v) => v,
5213
+ isEncrypted: () => false
5214
+ };
5215
+ async function patchGlobalConfig(globalConfigPath, mutate) {
5216
+ let raw = "{}";
5217
+ let fileExists = true;
5218
+ try {
5219
+ raw = await fsp3.readFile(globalConfigPath, "utf8");
5220
+ } catch (err) {
5221
+ if (err.code !== "ENOENT") throw err;
5222
+ fileExists = false;
5223
+ }
5224
+ let parsed;
5225
+ try {
5226
+ parsed = JSON.parse(raw);
5227
+ } catch (err) {
5228
+ if (fileExists) {
5229
+ throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
5230
+ }
5231
+ parsed = {};
5232
+ }
5233
+ const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
5234
+ mutate(decrypted);
5235
+ const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
5236
+ await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
5237
+ return decrypted;
5238
+ }
5239
+ function fmtModel(id, def) {
5240
+ const parts = [];
5241
+ if (def.provider) parts.push(`${color.dim("provider:")} ${color.cyan(def.provider)}`);
5242
+ if (def.name) parts.push(`${color.dim("name:")} ${def.name}`);
5243
+ const caps = def.capabilities;
5244
+ if (caps) {
5245
+ if (caps.maxContext) parts.push(`${color.dim("maxContext:")} ${color.yellow(String(caps.maxContext))}`);
5246
+ const flags = [];
5247
+ if (caps.tools) flags.push("tools");
5248
+ if (caps.vision) flags.push("vision");
5249
+ if (caps.reasoning) flags.push("reasoning");
5250
+ if (caps.streaming) flags.push("streaming");
5251
+ if (caps.jsonMode) flags.push("json");
5252
+ if (flags.length) parts.push(`${color.dim("caps:")} ${flags.join(", ")}`);
5253
+ }
5254
+ if (def.maxOutput) parts.push(`${color.dim("maxOutput:")} ${color.yellow(String(def.maxOutput))}`);
5255
+ return ` ${color.amber(id)} ${parts.join(" ")}`;
5256
+ }
5257
+ function safeAt(arr, idx) {
5258
+ const v = arr[idx];
5259
+ if (v === void 0) throw new Error(`Missing value at position ${idx}`);
5260
+ return v;
5261
+ }
5262
+ function parseFlags(tokens) {
5263
+ let modelId = "";
5264
+ const caps = {};
5265
+ let provider;
5266
+ let name;
5267
+ let maxOutput;
5268
+ let i = 0;
5269
+ while (i < tokens.length) {
5270
+ const t = tokens[i];
5271
+ if (t.startsWith("--")) {
5272
+ const key = t.slice(2);
5273
+ switch (key) {
5274
+ case "provider":
5275
+ provider = safeAt(tokens, ++i);
5276
+ break;
5277
+ case "name":
5278
+ name = safeAt(tokens, ++i);
5279
+ break;
5280
+ case "max-context":
5281
+ caps.maxContext = Number(safeAt(tokens, ++i));
5282
+ break;
5283
+ case "max-output":
5284
+ maxOutput = Number(safeAt(tokens, ++i));
5285
+ break;
5286
+ case "tools":
5287
+ caps.tools = true;
5288
+ break;
5289
+ case "vision":
5290
+ caps.vision = true;
5291
+ break;
5292
+ case "streaming":
5293
+ caps.streaming = true;
5294
+ break;
5295
+ case "reasoning":
5296
+ caps.reasoning = true;
5297
+ break;
5298
+ case "json-mode":
5299
+ caps.jsonMode = true;
5300
+ break;
5301
+ default:
5302
+ return { modelId: "", error: `Unknown flag: --${key}` };
5303
+ }
5304
+ } else if (!t.startsWith("-") && !modelId) {
5305
+ modelId = t;
5306
+ }
5307
+ i++;
5308
+ }
5309
+ if (!modelId) return { modelId: "", error: "missing model id" };
5310
+ const hasCaps = Object.keys(caps).length > 0;
5311
+ const def = {};
5312
+ if (provider !== void 0) def.provider = provider;
5313
+ if (name !== void 0) def.name = name;
5314
+ if (maxOutput !== void 0) def.maxOutput = maxOutput;
5315
+ if (hasCaps) def.capabilities = caps;
5316
+ return { modelId, def: Object.keys(def).length ? def : void 0 };
5317
+ }
5318
+ function buildModelsCommand(opts) {
5319
+ const help = [
5320
+ "Usage:",
5321
+ " /models List custom model definitions",
5322
+ " /models add <id> [flags] Add or update a custom model",
5323
+ " /models remove <id> Remove a custom model",
5324
+ "",
5325
+ "Flags for add:",
5326
+ " --provider <id> Owning provider",
5327
+ ' --name "Display" Display name',
5328
+ " --max-context <N> Context window override",
5329
+ " --max-output <N> Max output tokens",
5330
+ " --tools Tool-capable",
5331
+ " --vision Vision-capable",
5332
+ " --streaming Streaming support",
5333
+ " --reasoning Reasoning support",
5334
+ " --json-mode JSON mode support",
5335
+ "",
5336
+ "Persisted to ~/.wrongstack/config.json."
5337
+ ].join("\n");
5338
+ return {
5339
+ name: "models",
5340
+ description: "Manage custom model definitions.",
5341
+ help,
5342
+ async run(args) {
5343
+ const parts = args.trim().split(/\s+/).filter(Boolean);
5344
+ const sub = (parts[0] ?? "").toLowerCase();
5345
+ if (sub === "help" || sub === "--help") return { message: help };
5346
+ if (!opts.configStore || !opts.paths) {
5347
+ return { message: `${color.red("Error")} config store not available.` };
5348
+ }
5349
+ const config = opts.configStore.get();
5350
+ const globalConfigPath = opts.paths.globalConfig;
5351
+ if (!sub) {
5352
+ const models = config.models ?? {};
5353
+ const ids = Object.keys(models);
5354
+ if (ids.length === 0) {
5355
+ return {
5356
+ message: [
5357
+ `${color.bold("Custom Models")} ${color.dim("(none defined)")}`,
5358
+ "",
5359
+ color.dim(" Add one: /models add <id> --max-context 128000 --tools")
5360
+ ].join("\n")
5361
+ };
5362
+ }
5363
+ return {
5364
+ message: [
5365
+ `${color.bold("Custom Models")} ${color.dim(`(${ids.length})`)}`,
5366
+ ...ids.sort().map((id) => fmtModel(id, models[id]))
5367
+ ].join("\n")
5368
+ };
5369
+ }
5370
+ try {
5371
+ if (sub === "add") {
5372
+ const { modelId, def, error } = parseFlags(parts.slice(1));
5373
+ if (error) {
5374
+ return { message: `${color.red("Error")}: ${error}. ${color.dim("/models help")}` };
5375
+ }
5376
+ if (!def && !error) {
5377
+ return { message: `${color.amber("Usage:")} /models add <id> [--max-context N] [--tools] ... ${color.dim("/models help")}` };
5378
+ }
5379
+ const existingModels = config.models ?? {};
5380
+ const existed = modelId in existingModels;
5381
+ const decrypted = await patchGlobalConfig(globalConfigPath, (cfg) => {
5382
+ const models = { ...cfg.models ?? {} };
5383
+ models[modelId] = {
5384
+ ...models[modelId],
5385
+ ...def,
5386
+ capabilities: {
5387
+ ...models[modelId]?.capabilities,
5388
+ ...def?.capabilities
5389
+ }
5390
+ };
5391
+ cfg.models = models;
5392
+ });
5393
+ opts.configStore.update({
5394
+ models: decrypted.models
5395
+ });
5396
+ return { message: `${color.green("\u2713")} ${color.amber(modelId)} ${existed ? "updated" : "added"}.` };
5397
+ }
5398
+ if (sub === "remove" || sub === "rm") {
5399
+ const modelId = parts[1];
5400
+ if (!modelId) {
5401
+ return { message: `${color.amber("Usage:")} /models remove <id>` };
5402
+ }
5403
+ const existing = config.models ?? {};
5404
+ if (!(modelId in existing)) {
5405
+ return { message: `${color.amber("Not found")}: custom model "${modelId}" is not defined.` };
5406
+ }
5407
+ const decrypted = await patchGlobalConfig(globalConfigPath, (cfg) => {
5408
+ const models = { ...cfg.models ?? {} };
5409
+ delete models[modelId];
5410
+ cfg.models = models;
5411
+ });
5412
+ opts.configStore.update({
5413
+ models: decrypted.models
5414
+ });
5415
+ return { message: `${color.green("\u2713")} removed ${color.amber(modelId)}` };
5416
+ }
5417
+ return {
5418
+ message: `${color.red("Unknown subcommand")} "${sub}". Try ${color.dim("/models")}, ${color.dim("/models add")}, or ${color.dim("/models help")}.`
5419
+ };
5420
+ } catch (err) {
5421
+ return {
5422
+ message: `${color.red("models error")}: ${err instanceof Error ? err.message : String(err)}`
5423
+ };
5424
+ }
5425
+ }
5426
+ };
5427
+ }
5160
5428
  function buildNextCommand(opts) {
5161
5429
  return {
5162
5430
  name: "next",
@@ -5414,7 +5682,7 @@ function summariseEvent(ev) {
5414
5682
  return color.dim("\u2026");
5415
5683
  }
5416
5684
  }
5417
- var noOpVault = {
5685
+ var noOpVault2 = {
5418
5686
  encrypt: (v) => v,
5419
5687
  decrypt: (v) => v,
5420
5688
  isEncrypted: () => false
@@ -5449,7 +5717,7 @@ function parseTarget(tokens) {
5449
5717
  function fmtEntry(e) {
5450
5718
  return e.provider ? `${e.provider}/${e.model}` : `${e.model} ${color.dim("(leader provider)")}`;
5451
5719
  }
5452
- async function patchGlobalConfig(globalConfigPath, mutate) {
5720
+ async function patchGlobalConfig2(globalConfigPath, mutate) {
5453
5721
  let raw = "{}";
5454
5722
  let fileExists = true;
5455
5723
  try {
@@ -5466,9 +5734,9 @@ async function patchGlobalConfig(globalConfigPath, mutate) {
5466
5734
  throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
5467
5735
  parsed = {};
5468
5736
  }
5469
- const decrypted = decryptConfigSecrets$1(parsed, noOpVault);
5737
+ const decrypted = decryptConfigSecrets$1(parsed, noOpVault2);
5470
5738
  mutate(decrypted);
5471
- const encrypted = encryptConfigSecrets$1(decrypted, noOpVault);
5739
+ const encrypted = encryptConfigSecrets$1(decrypted, noOpVault2);
5472
5740
  await atomicWrite(globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
5473
5741
  return decrypted;
5474
5742
  }
@@ -5559,7 +5827,7 @@ function buildSetModelCommand(opts) {
5559
5827
  message: `${color.red("Provider not available")}: "${provider}". Keyed: ${keyed.join(", ") || "(none)"}. ${color.dim("/setmodel list")}`
5560
5828
  };
5561
5829
  }
5562
- const decrypted = await patchGlobalConfig(globalConfigPath, (cfg) => {
5830
+ const decrypted = await patchGlobalConfig2(globalConfigPath, (cfg) => {
5563
5831
  cfg.provider = provider;
5564
5832
  cfg.model = model;
5565
5833
  });
@@ -5590,7 +5858,7 @@ function buildSetModelCommand(opts) {
5590
5858
  message: `${color.red("Provider not available")}: "${parsed.provider}". Keyed: ${keyed.join(", ") || "(none)"}.`
5591
5859
  };
5592
5860
  }
5593
- const decrypted = await patchGlobalConfig(globalConfigPath, (cfg) => {
5861
+ const decrypted = await patchGlobalConfig2(globalConfigPath, (cfg) => {
5594
5862
  const matrix = { ...cfg.modelMatrix ?? {} };
5595
5863
  matrix[key] = parsed.provider ? { provider: parsed.provider, model: parsed.model } : { model: parsed.model };
5596
5864
  cfg.modelMatrix = matrix;
@@ -5607,7 +5875,7 @@ function buildSetModelCommand(opts) {
5607
5875
  if (!(key in existing)) {
5608
5876
  return { message: `${color.amber("No matrix entry")} for "${key}".` };
5609
5877
  }
5610
- const decrypted = await patchGlobalConfig(globalConfigPath, (cfg) => {
5878
+ const decrypted = await patchGlobalConfig2(globalConfigPath, (cfg) => {
5611
5879
  const matrix = { ...cfg.modelMatrix ?? {} };
5612
5880
  delete matrix[key];
5613
5881
  cfg.modelMatrix = matrix;
@@ -5659,7 +5927,7 @@ async function persistAutonomySetting(deps, mutator) {
5659
5927
  }
5660
5928
 
5661
5929
  // src/slash-commands/settings.ts
5662
- var noOpVault2 = {
5930
+ var noOpVault3 = {
5663
5931
  encrypt: (v) => v,
5664
5932
  decrypt: (v) => v,
5665
5933
  isEncrypted: () => false
@@ -5724,7 +5992,7 @@ function buildSettingsCommand(opts) {
5724
5992
  const persistDeps = {
5725
5993
  configStore: opts.configStore,
5726
5994
  globalConfigPath: opts.paths.globalConfig,
5727
- vault: noOpVault2
5995
+ vault: noOpVault3
5728
5996
  };
5729
5997
  try {
5730
5998
  if (sub === "delay") {
@@ -5854,7 +6122,7 @@ var DEFAULTS = {
5854
6122
  cost: true
5855
6123
  };
5856
6124
  function resolveConfigPath() {
5857
- return process.env[CONFIG_ENV] ?? path9.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
6125
+ return process.env[CONFIG_ENV] ?? path8.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
5858
6126
  }
5859
6127
  async function loadStatuslineConfig() {
5860
6128
  const p = resolveConfigPath();
@@ -5868,7 +6136,7 @@ async function loadStatuslineConfig() {
5868
6136
  async function saveStatuslineConfig(cfg) {
5869
6137
  const p = resolveConfigPath();
5870
6138
  try {
5871
- await fsp3.mkdir(path9.dirname(p), { recursive: true });
6139
+ await fsp3.mkdir(path8.dirname(p), { recursive: true });
5872
6140
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
5873
6141
  } catch (err) {
5874
6142
  throw new FsError({
@@ -6148,6 +6416,7 @@ function buildBuiltinSlashCommands(opts) {
6148
6416
  buildWorktreeCommand(opts),
6149
6417
  buildSettingsCommand(opts),
6150
6418
  buildSetModelCommand(opts),
6419
+ buildModelsCommand(opts),
6151
6420
  buildCollabCommand(opts),
6152
6421
  buildStatuslineCommand({
6153
6422
  cwd: opts.cwd,
@@ -6176,13 +6445,13 @@ var MANIFESTS = [
6176
6445
  ];
6177
6446
  async function detectProjectKind(projectRoot) {
6178
6447
  try {
6179
- await fsp3.access(path9.join(projectRoot, ".wrongstack", "AGENTS.md"));
6448
+ await fsp3.access(path8.join(projectRoot, ".wrongstack", "AGENTS.md"));
6180
6449
  return "initialized";
6181
6450
  } catch {
6182
6451
  }
6183
6452
  for (const m of MANIFESTS) {
6184
6453
  try {
6185
- await fsp3.access(path9.join(projectRoot, m));
6454
+ await fsp3.access(path8.join(projectRoot, m));
6186
6455
  return "project";
6187
6456
  } catch {
6188
6457
  }
@@ -6190,8 +6459,8 @@ async function detectProjectKind(projectRoot) {
6190
6459
  return "empty";
6191
6460
  }
6192
6461
  async function scaffoldAgentsMd(projectRoot) {
6193
- const dir = path9.join(projectRoot, ".wrongstack");
6194
- const file = path9.join(dir, "AGENTS.md");
6462
+ const dir = path8.join(projectRoot, ".wrongstack");
6463
+ const file = path8.join(dir, "AGENTS.md");
6195
6464
  const facts = await detectProjectFacts(projectRoot);
6196
6465
  const body = renderAgentsTemplate(facts);
6197
6466
  await fsp3.mkdir(dir, { recursive: true });
@@ -6204,7 +6473,7 @@ async function runProjectCheck(opts) {
6204
6473
  if (kind === "initialized") {
6205
6474
  renderer.write(
6206
6475
  `
6207
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path9.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6476
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path8.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6208
6477
  `
6209
6478
  );
6210
6479
  return true;
@@ -6235,7 +6504,7 @@ async function runProjectCheck(opts) {
6235
6504
  }
6236
6505
  return true;
6237
6506
  }
6238
- const gitDir = path9.join(projectRoot, ".git");
6507
+ const gitDir = path8.join(projectRoot, ".git");
6239
6508
  let hasGit = false;
6240
6509
  try {
6241
6510
  await fsp3.access(gitDir);
@@ -6258,10 +6527,10 @@ async function runProjectCheck(opts) {
6258
6527
  if (answer2 === "y" || answer2 === "yes") {
6259
6528
  try {
6260
6529
  const { spawn: spawn3 } = await import('child_process');
6261
- await new Promise((resolve4, reject) => {
6530
+ await new Promise((resolve3, reject) => {
6262
6531
  const child = spawn3("git", ["init"], { cwd: projectRoot });
6263
6532
  child.on("error", reject);
6264
- child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
6533
+ child.on("close", (code) => code === 0 ? resolve3() : reject(new Error(`git init failed with ${code}`)));
6265
6534
  });
6266
6535
  renderer.write(` ${color.green("\u2713")} Git repository initialized
6267
6536
  `);
@@ -6360,78 +6629,21 @@ async function runLaunchPrompts(opts) {
6360
6629
  return { mode, yolo, director, autonomy };
6361
6630
  }
6362
6631
  async function bootConfig(flags) {
6363
- const cwd = typeof flags["cwd"] === "string" ? path9.resolve(flags["cwd"]) : process.cwd();
6364
- const pathResolver = new DefaultPathResolver(cwd);
6365
- const projectRoot = pathResolver.projectRoot;
6366
- const userHome = os3.homedir();
6367
- const wpaths = resolveWstackPaths({ projectRoot, userHome });
6368
- await ensureProjectMeta(wpaths, projectRoot);
6369
- const vault = new DefaultSecretVault$1({ keyFile: wpaths.secretsKey });
6370
- for (const file of [wpaths.globalConfig, wpaths.projectLocalConfig]) {
6371
- try {
6372
- const { migrated } = await migratePlaintextSecrets(file, vault);
6373
- if (migrated > 0) {
6374
- writeErr(`[wstack] Encrypted ${migrated} plaintext secret(s) in ${file}
6375
- `);
6376
- }
6377
- } catch {
6378
- }
6379
- }
6380
- const configLoader = new DefaultConfigLoader({ paths: wpaths, vault });
6381
- let config = await configLoader.load({ cliFlags: flagsToConfigPatch(flags) });
6382
- const syncConfig = await configLoader.loadSyncConfig();
6383
- if (syncConfig) {
6384
- config = Object.freeze({ ...config, sync: syncConfig });
6385
- }
6632
+ const opts = { flags, appLabel: "wstack" };
6633
+ const { cwd, projectRoot, userHome, wpaths, pathResolver, config, vault } = await bootConfig$1(opts);
6386
6634
  return {
6387
6635
  paths: { cwd, projectRoot, userHome, wpaths, pathResolver },
6388
6636
  config,
6389
6637
  vault
6390
6638
  };
6391
6639
  }
6392
- function flagsToConfigPatch(flags) {
6393
- const patch = {};
6394
- if (typeof flags["provider"] === "string") patch.provider = flags["provider"];
6395
- if (typeof flags["model"] === "string") patch.model = flags["model"];
6396
- if (typeof flags["cwd"] === "string") patch.cwd = flags["cwd"];
6397
- if (typeof flags["log-level"] === "string") {
6398
- patch.log = { level: flags["log-level"] };
6399
- } else if (flags["verbose"]) {
6400
- patch.log = { level: "debug" };
6401
- } else if (flags["trace"]) {
6402
- patch.log = { level: "trace" };
6403
- }
6404
- if (flags["yolo"]) patch.yolo = true;
6405
- if (flags["no-features"]) {
6406
- patch.features = {
6407
- mcp: false,
6408
- plugins: false,
6409
- memory: false,
6410
- modelsRegistry: false,
6411
- skills: false
6412
- };
6413
- }
6414
- return patch;
6415
- }
6416
- async function ensureProjectMeta(paths, projectRoot) {
6417
- try {
6418
- await fsp3.mkdir(paths.projectDir, { recursive: true });
6419
- const meta = {
6420
- hash: paths.projectHash,
6421
- root: projectRoot,
6422
- lastSeen: (/* @__PURE__ */ new Date()).toISOString()
6423
- };
6424
- await fsp3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
6425
- } catch {
6426
- }
6427
- }
6428
6640
  var ReadlineInputReader = class {
6429
6641
  rl;
6430
6642
  historyFile;
6431
6643
  history = [];
6432
6644
  pending = false;
6433
6645
  constructor(opts = {}) {
6434
- this.historyFile = opts.historyFile ?? path9.join(os3.homedir(), ".wrongstack", "history");
6646
+ this.historyFile = opts.historyFile ?? path8.join(os2.homedir(), ".wrongstack", "history");
6435
6647
  }
6436
6648
  async loadHistory() {
6437
6649
  try {
@@ -6443,7 +6655,7 @@ var ReadlineInputReader = class {
6443
6655
  }
6444
6656
  async saveHistory() {
6445
6657
  try {
6446
- await fsp3.mkdir(path9.dirname(this.historyFile), { recursive: true });
6658
+ await fsp3.mkdir(path8.dirname(this.historyFile), { recursive: true });
6447
6659
  await fsp3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6448
6660
  } catch {
6449
6661
  }
@@ -6462,7 +6674,7 @@ var ReadlineInputReader = class {
6462
6674
  async readLine(prompt) {
6463
6675
  if (this.history.length === 0) await this.loadHistory();
6464
6676
  while (this.pending) {
6465
- await new Promise((resolve4) => setTimeout(resolve4, 50));
6677
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
6466
6678
  }
6467
6679
  this.pending = true;
6468
6680
  try {
@@ -6472,15 +6684,15 @@ var ReadlineInputReader = class {
6472
6684
  this.rl = void 0;
6473
6685
  }
6474
6686
  const fresh = this.ensure();
6475
- return new Promise((resolve4) => {
6687
+ return new Promise((resolve3) => {
6476
6688
  fresh.question(prompt ?? "> ", (line) => {
6477
6689
  if (line.trim()) {
6478
6690
  this.history.push(line);
6479
6691
  void this.saveHistory();
6480
6692
  }
6481
- resolve4(line);
6693
+ resolve3(line);
6482
6694
  });
6483
- fresh.once("close", () => resolve4(""));
6695
+ fresh.once("close", () => resolve3(""));
6484
6696
  }).then((result) => {
6485
6697
  this.rl?.close();
6486
6698
  this.rl = void 0;
@@ -6492,7 +6704,7 @@ var ReadlineInputReader = class {
6492
6704
  }
6493
6705
  async readKey(prompt, options) {
6494
6706
  writeOut(prompt);
6495
- return new Promise((resolve4) => {
6707
+ return new Promise((resolve3) => {
6496
6708
  const stdin = process.stdin;
6497
6709
  const wasRaw = stdin.isRaw;
6498
6710
  const wasPaused = stdin.isPaused();
@@ -6503,7 +6715,7 @@ var ReadlineInputReader = class {
6503
6715
  if (key === "") {
6504
6716
  cleanup();
6505
6717
  writeOut("\n");
6506
- resolve4("");
6718
+ resolve3("");
6507
6719
  return;
6508
6720
  }
6509
6721
  const opt = options.find(
@@ -6513,12 +6725,12 @@ var ReadlineInputReader = class {
6513
6725
  cleanup();
6514
6726
  writeOut(`${opt.key}
6515
6727
  `);
6516
- resolve4(opt.value);
6728
+ resolve3(opt.value);
6517
6729
  }
6518
6730
  };
6519
6731
  const onClose = () => {
6520
6732
  cleanup();
6521
- resolve4("");
6733
+ resolve3("");
6522
6734
  };
6523
6735
  const cleanup = () => {
6524
6736
  stdin.off("data", onData);
@@ -6546,7 +6758,7 @@ var ReadlineInputReader = class {
6546
6758
  this.rl?.close();
6547
6759
  this.rl = void 0;
6548
6760
  writeOut(prompt);
6549
- return new Promise((resolve4) => {
6761
+ return new Promise((resolve3) => {
6550
6762
  let buf = "";
6551
6763
  const wasRaw = stdin.isRaw;
6552
6764
  setRawMode(stdin, true);
@@ -6564,7 +6776,7 @@ var ReadlineInputReader = class {
6564
6776
  cleanup();
6565
6777
  writeOut(` ${dim(`[${buf.length} chars]`)}
6566
6778
  `);
6567
- resolve4(buf);
6779
+ resolve3(buf);
6568
6780
  return;
6569
6781
  }
6570
6782
  if (ch === "") {
@@ -6661,7 +6873,7 @@ async function buildPickableProviders(modelsRegistry, config) {
6661
6873
  }
6662
6874
  return out;
6663
6875
  }
6664
- var defaultUidFn = () => os3__default.userInfo().uid;
6876
+ var defaultUidFn = () => os2__default.userInfo().uid;
6665
6877
  async function getFileUid(filePath) {
6666
6878
  try {
6667
6879
  const stat4 = await fsp3.stat(filePath);
@@ -6671,7 +6883,7 @@ async function getFileUid(filePath) {
6671
6883
  }
6672
6884
  }
6673
6885
  async function checkConfigOwnership(homeFn, uidFn = defaultUidFn) {
6674
- if (os3__default.platform() === "win32") return true;
6886
+ if (os2__default.platform() === "win32") return true;
6675
6887
  const cfg = configPath(homeFn);
6676
6888
  const fileUid = await getFileUid(cfg);
6677
6889
  if (fileUid === void 0) return true;
@@ -6688,20 +6900,20 @@ function assertSafeToDelete(filename, parentDir) {
6688
6900
  if (PROTECTED_BASENAMES.has(filename)) {
6689
6901
  throw new Error(`Refusing to delete protected file: ${filename}`);
6690
6902
  }
6691
- if (filename !== path9.basename(filename)) {
6903
+ if (filename !== path8.basename(filename)) {
6692
6904
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
6693
6905
  }
6694
6906
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
6695
6907
  throw new Error(`Refusing to delete unknown file: ${filename}`);
6696
6908
  }
6697
- const resolvedParent = path9.resolve(parentDir);
6909
+ const resolvedParent = path8.resolve(parentDir);
6698
6910
  if (!resolvedParent.endsWith(".wrongstack")) {
6699
6911
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
6700
6912
  }
6701
6913
  }
6702
6914
  async function safeDelete(filePath) {
6703
- const dir = path9.dirname(filePath);
6704
- const filename = path9.basename(filePath);
6915
+ const dir = path8.dirname(filePath);
6916
+ const filename = path8.basename(filePath);
6705
6917
  try {
6706
6918
  assertSafeToDelete(filename, dir);
6707
6919
  await fsp3.unlink(filePath);
@@ -6744,18 +6956,18 @@ function diffSummary(oldCfg, newCfg) {
6744
6956
  }
6745
6957
  return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
6746
6958
  }
6747
- var defaultHomeDir = () => os3__default.homedir();
6959
+ var defaultHomeDir = () => os2__default.homedir();
6748
6960
  function historyDir(homeFn = defaultHomeDir) {
6749
- return path9.join(homeFn(), ".wrongstack", "config.history", "entries");
6961
+ return path8.join(homeFn(), ".wrongstack", "config.history", "entries");
6750
6962
  }
6751
6963
  function historyIndexPath(homeFn = defaultHomeDir) {
6752
- return path9.join(homeFn(), ".wrongstack", "config.history", "index.json");
6964
+ return path8.join(homeFn(), ".wrongstack", "config.history", "index.json");
6753
6965
  }
6754
6966
  function configPath(homeFn = defaultHomeDir) {
6755
- return path9.join(homeFn(), ".wrongstack", "config.json");
6967
+ return path8.join(homeFn(), ".wrongstack", "config.json");
6756
6968
  }
6757
6969
  function backupLastPath(homeFn = defaultHomeDir) {
6758
- return path9.join(homeFn(), ".wrongstack", "config.json.last");
6970
+ return path8.join(homeFn(), ".wrongstack", "config.json.last");
6759
6971
  }
6760
6972
  function entryId(ts) {
6761
6973
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -6810,17 +7022,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6810
7022
  }
6811
7023
  if (content !== void 0) {
6812
7024
  try {
6813
- const bakPath = path9.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
7025
+ const bakPath = path8.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
6814
7026
  await atomicWrite(bakPath, content);
6815
7027
  } catch {
6816
7028
  }
6817
7029
  }
6818
7030
  try {
6819
- const dir = path9.join(homeFn(), ".wrongstack");
7031
+ const dir = path8.join(homeFn(), ".wrongstack");
6820
7032
  const files = await fsp3.readdir(dir);
6821
7033
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
6822
7034
  for (const f of baks.slice(10)) {
6823
- await safeDelete(path9.join(dir, f));
7035
+ await safeDelete(path8.join(dir, f));
6824
7036
  }
6825
7037
  } catch {
6826
7038
  }
@@ -6838,7 +7050,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6838
7050
  };
6839
7051
  try {
6840
7052
  await fsp3.writeFile(
6841
- path9.join(historyDir(homeFn), `${id}.json`),
7053
+ path8.join(historyDir(homeFn), `${id}.json`),
6842
7054
  JSON.stringify(entry, null, 2),
6843
7055
  "utf8"
6844
7056
  );
@@ -6846,7 +7058,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6846
7058
  throw new FsError({
6847
7059
  message: err instanceof Error ? err.message : String(err),
6848
7060
  code: ERROR_CODES.FS_WRITE_FAILED,
6849
- path: path9.join(historyDir(homeFn), `${id}.json`),
7061
+ path: path8.join(historyDir(homeFn), `${id}.json`),
6850
7062
  cause: err
6851
7063
  });
6852
7064
  }
@@ -6861,7 +7073,7 @@ async function listHistory(homeFn = defaultHomeDir) {
6861
7073
  }
6862
7074
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
6863
7075
  try {
6864
- const raw = await fsp3.readFile(path9.join(historyDir(homeFn), `${id}.json`), "utf8");
7076
+ const raw = await fsp3.readFile(path8.join(historyDir(homeFn), `${id}.json`), "utf8");
6865
7077
  return JSON.parse(raw);
6866
7078
  } catch {
6867
7079
  return null;
@@ -6926,11 +7138,11 @@ async function restoreLast(homeFn = defaultHomeDir) {
6926
7138
  var theme = { primary: color.amber };
6927
7139
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
6928
7140
  try {
6929
- const { atomicWrite: atomicWrite10 } = await import('@wrongstack/core');
6930
- const fs23 = await import('fs/promises');
7141
+ const { atomicWrite: atomicWrite12 } = await import('@wrongstack/core');
7142
+ const fs24 = await import('fs/promises');
6931
7143
  let existing = {};
6932
7144
  try {
6933
- const raw = await fs23.readFile(configPath2, "utf8");
7145
+ const raw = await fs24.readFile(configPath2, "utf8");
6934
7146
  existing = JSON.parse(raw);
6935
7147
  } catch {
6936
7148
  }
@@ -6938,7 +7150,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
6938
7150
  existing.provider = provider;
6939
7151
  existing.model = model;
6940
7152
  await backupCurrent(homeFn);
6941
- await atomicWrite10(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
7153
+ await atomicWrite12(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
6942
7154
  try {
6943
7155
  await appendHistory(
6944
7156
  oldCfg,
@@ -7265,7 +7477,7 @@ function pickGroupIndex(opts) {
7265
7477
  if (Number.isFinite(parsed)) current = wrap(parsed);
7266
7478
  } catch {
7267
7479
  }
7268
- fs11.mkdirSync(path9.dirname(opts.cursorFile), { recursive: true });
7480
+ fs11.mkdirSync(path8.dirname(opts.cursorFile), { recursive: true });
7269
7481
  fs11.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
7270
7482
  return current;
7271
7483
  } catch {
@@ -7601,14 +7813,14 @@ function summarize(value, name) {
7601
7813
  if (typeof v === "object" && v !== null) {
7602
7814
  const o = v;
7603
7815
  if (name === "edit") {
7604
- const path26 = typeof o["path"] === "string" ? o["path"] : "";
7816
+ const path25 = typeof o["path"] === "string" ? o["path"] : "";
7605
7817
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
7606
- return `${path26} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7818
+ return `${path25} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7607
7819
  }
7608
7820
  if (name === "write") {
7609
- const path26 = typeof o["path"] === "string" ? o["path"] : "";
7821
+ const path25 = typeof o["path"] === "string" ? o["path"] : "";
7610
7822
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
7611
- return bytes !== void 0 ? `${path26} ${bytes}B` : path26;
7823
+ return bytes !== void 0 ? `${path25} ${bytes}B` : path25;
7612
7824
  }
7613
7825
  if (typeof o["count"] === "number") {
7614
7826
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -8468,7 +8680,7 @@ var updateCmd = async (args, deps) => {
8468
8680
  deps.renderer.write(`Updating wrongstack from v${info.current} to v${info.latest}...
8469
8681
  `);
8470
8682
  try {
8471
- const result = await new Promise((resolve4, reject) => {
8683
+ const result = await new Promise((resolve3, reject) => {
8472
8684
  const child = spawn("npm", ["install", "-g", "wrongstack@latest"], {
8473
8685
  cwd,
8474
8686
  stdio: "pipe"
@@ -8478,7 +8690,7 @@ var updateCmd = async (args, deps) => {
8478
8690
  _stderr += d;
8479
8691
  });
8480
8692
  child.on("error", reject);
8481
- child.on("close", (code) => resolve4({ code: code ?? 0 }));
8693
+ child.on("close", (code) => resolve3({ code: code ?? 0 }));
8482
8694
  });
8483
8695
  if (result.code === 0) {
8484
8696
  deps.renderer.write(`
@@ -8539,7 +8751,7 @@ var diagCmd = async (_args, deps) => {
8539
8751
  ` modelsCache: ${deps.paths.modelsCache}`,
8540
8752
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
8541
8753
  ` node: ${process.version}`,
8542
- ` os: ${os3.platform()} ${os3.release()}`,
8754
+ ` os: ${os2.platform()} ${os2.release()}`,
8543
8755
  ` provider: ${cfg.provider ?? "<unset>"}`,
8544
8756
  ` model: ${cfg.model ?? "<unset>"}`,
8545
8757
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -8618,7 +8830,7 @@ var doctorCmd = async (_args, deps) => {
8618
8830
  }
8619
8831
  try {
8620
8832
  await fsp3.mkdir(deps.paths.projectSessions, { recursive: true });
8621
- const probe = path9.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8833
+ const probe = path8.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8622
8834
  await fsp3.writeFile(probe, "");
8623
8835
  await fsp3.unlink(probe);
8624
8836
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -8721,8 +8933,8 @@ var exportCmd = async (args, deps) => {
8721
8933
  return 1;
8722
8934
  }
8723
8935
  if (output) {
8724
- await fsp3.mkdir(path9.dirname(path9.resolve(deps.cwd, output)), { recursive: true });
8725
- await fsp3.writeFile(path9.resolve(deps.cwd, output), rendered, "utf8");
8936
+ await fsp3.mkdir(path8.dirname(path8.resolve(deps.cwd, output)), { recursive: true });
8937
+ await fsp3.writeFile(path8.resolve(deps.cwd, output), rendered, "utf8");
8726
8938
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
8727
8939
  `);
8728
8940
  } else {
@@ -8795,8 +9007,8 @@ var initCmd = async (_args, deps) => {
8795
9007
  const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
8796
9008
  const encrypted = encryptConfigSecrets(config, vault);
8797
9009
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
8798
- await fsp3.mkdir(path9.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8799
- const agentsFile = path9.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
9010
+ await fsp3.mkdir(path8.join(deps.projectRoot, ".wrongstack"), { recursive: true });
9011
+ const agentsFile = path8.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8800
9012
  const projectFacts = await detectProjectFacts(deps.projectRoot);
8801
9013
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
8802
9014
  deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
@@ -9102,7 +9314,7 @@ var usageCmd = async (_args, deps) => {
9102
9314
  return 0;
9103
9315
  };
9104
9316
  var projectsCmd = async (_args, deps) => {
9105
- const projectsRoot = path9.join(deps.paths.globalRoot, "projects");
9317
+ const projectsRoot = path8.join(deps.paths.globalRoot, "projects");
9106
9318
  try {
9107
9319
  const entries = await fsp3.readdir(projectsRoot);
9108
9320
  if (entries.length === 0) {
@@ -9112,7 +9324,7 @@ var projectsCmd = async (_args, deps) => {
9112
9324
  for (const hash of entries) {
9113
9325
  try {
9114
9326
  const meta = JSON.parse(
9115
- await fsp3.readFile(path9.join(projectsRoot, hash, "meta.json"), "utf8")
9327
+ await fsp3.readFile(path8.join(projectsRoot, hash, "meta.json"), "utf8")
9116
9328
  );
9117
9329
  deps.renderer.write(
9118
9330
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -9173,8 +9385,49 @@ ${color.dim(`Current: ${deps.config.provider ?? "<unset>"} / ${deps.config.model
9173
9385
  return 1;
9174
9386
  }
9175
9387
  };
9388
+ function parseFlags2(args) {
9389
+ const flags = {};
9390
+ for (let i = 0; i < args.length; i++) {
9391
+ const a = args[i];
9392
+ if (a.startsWith("--")) {
9393
+ const eq = a.indexOf("=");
9394
+ if (eq !== -1) {
9395
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
9396
+ } else {
9397
+ const name = a.slice(2);
9398
+ if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
9399
+ flags[name] = args[++i] ?? "";
9400
+ } else {
9401
+ flags[name] = true;
9402
+ }
9403
+ }
9404
+ }
9405
+ }
9406
+ return flags;
9407
+ }
9408
+ function positionals(args) {
9409
+ const out = [];
9410
+ for (let i = 0; i < args.length; i++) {
9411
+ const a = args[i];
9412
+ if (a.startsWith("--")) {
9413
+ const eq = a.indexOf("=");
9414
+ if (eq === -1) {
9415
+ if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
9416
+ i++;
9417
+ }
9418
+ }
9419
+ continue;
9420
+ }
9421
+ out.push(a);
9422
+ }
9423
+ return out;
9424
+ }
9425
+ var DEFAULT_PER_PAGE = 15;
9176
9426
  var modelsCmd = async (args, deps) => {
9177
9427
  const sub = args[0];
9428
+ if (sub === "add") return modelsAdd(args.slice(1), deps);
9429
+ if (sub === "remove") return modelsRemove(args.slice(1), deps);
9430
+ if (sub === "list") return modelsList(args.slice(1), deps);
9178
9431
  if (sub === "refresh") {
9179
9432
  deps.renderer.writeInfo("Refreshing models.dev cache\u2026");
9180
9433
  try {
@@ -9188,9 +9441,13 @@ var modelsCmd = async (args, deps) => {
9188
9441
  return 1;
9189
9442
  }
9190
9443
  }
9191
- const providerId = sub ?? deps.config.provider;
9444
+ const flags = parseFlags2(args);
9445
+ const search = typeof flags["search"] === "string" ? flags["search"].toLowerCase() : "";
9446
+ const perPage = Number(flags["per-page"]) > 0 ? Number(flags["per-page"]) : DEFAULT_PER_PAGE;
9447
+ const page = Math.max(1, Number(flags["page"]) || 1);
9448
+ const providerId = sub ?? deps.config.provider ?? "";
9192
9449
  if (!providerId) {
9193
- deps.renderer.writeError("Usage: wstack models <provider> | refresh");
9450
+ deps.renderer.writeError("Usage: wstack models <provider> [--search <term>] [--page N] [--per-page N]");
9194
9451
  return 1;
9195
9452
  }
9196
9453
  let lookupId = providerId;
@@ -9214,34 +9471,209 @@ var modelsCmd = async (args, deps) => {
9214
9471
  `));
9215
9472
  const userModels = deps.config.providers?.[providerId]?.models;
9216
9473
  const catalogById = new Map(provider.models.map((m) => [m.id, m]));
9217
- const sorted = userModels && userModels.length > 0 ? userModels.map((id) => catalogById.get(id) ?? { id, name: id }) : [...provider.models].sort(
9474
+ const allSorted = userModels && userModels.length > 0 ? userModels.map((id) => catalogById.get(id) ?? { id, name: id }) : [...provider.models].sort(
9218
9475
  (a, b) => (b.release_date ?? "").localeCompare(a.release_date ?? "")
9219
9476
  );
9220
9477
  if (userModels && userModels.length > 0)
9221
9478
  deps.renderer.write(color.dim(`(${userModels.length} model(s) from your saved config)
9222
9479
  `));
9223
- for (const m of sorted) {
9224
- const caps = [];
9225
- if ("tool_call" in m && m.tool_call) caps.push("tools");
9226
- if ("reasoning" in m && m.reasoning) caps.push("reasoning");
9227
- if ("modalities" in m && m.modalities?.input?.includes("image")) caps.push("vision");
9228
- const ctx = "limit" in m && m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k` : "?";
9229
- const cost = "cost" in m && m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
9230
- deps.renderer.write(
9231
- ` ${m.id.padEnd(40)} ${color.dim(ctx.padStart(6))} ${color.dim(cost.padEnd(14))} ${color.dim(caps.join(","))}
9480
+ const filtered = search ? allSorted.filter((m) => m.id.toLowerCase().includes(search)) : allSorted;
9481
+ const total = filtered.length;
9482
+ const totalPages = Math.max(1, Math.ceil(total / perPage));
9483
+ const actualPage = Math.min(page, totalPages);
9484
+ const start = (actualPage - 1) * perPage;
9485
+ const pageItems = filtered.slice(start, start + perPage);
9486
+ const end = Math.min(start + pageItems.length, total);
9487
+ const pageHint = totalPages > 1 ? color.cyan(`[page ${actualPage}/${totalPages}]`) : "";
9488
+ const searchHint = search ? color.yellow(` (filtered: "${search}" \u2014 ${total} match${total === 1 ? "" : "es"})`) : color.dim(` (${total} model${total === 1 ? "" : "s"})`);
9489
+ deps.renderer.write(`${pageHint}${searchHint}
9490
+ `);
9491
+ if (pageItems.length === 0) {
9492
+ deps.renderer.write(color.dim("(no models match)\n"));
9493
+ } else {
9494
+ if (start > 0)
9495
+ deps.renderer.write(color.dim(` ${String.fromCharCode(8593)} ${start} above
9496
+ `));
9497
+ for (const m of pageItems) {
9498
+ const caps = [];
9499
+ if ("tool_call" in m && m.tool_call) caps.push("tools");
9500
+ if ("reasoning" in m && m.reasoning) caps.push("reasoning");
9501
+ if ("modalities" in m && m.modalities?.input?.includes("image")) caps.push("vision");
9502
+ const ctx = "limit" in m && m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k` : "?";
9503
+ const cost = "cost" in m && m.cost?.input !== void 0 ? `${m.cost.input}/${m.cost.output ?? "?"}` : "";
9504
+ deps.renderer.write(
9505
+ ` ${m.id.padEnd(40)} ${color.dim(ctx.padStart(6))} ${color.dim(cost.padEnd(14))} ${color.dim(caps.join(","))}
9232
9506
  `
9233
- );
9507
+ );
9508
+ }
9509
+ if (end < total)
9510
+ deps.renderer.write(color.dim(` ${String.fromCharCode(8595)} ${total - end} below
9511
+ `));
9512
+ }
9513
+ const navLines = [];
9514
+ if (totalPages > 1) {
9515
+ if (actualPage > 1) navLines.push(`--page ${actualPage - 1} (prev)`);
9516
+ if (actualPage < totalPages) navLines.push(`--page ${actualPage + 1} (next)`);
9234
9517
  }
9518
+ navLines.push("--search <term> (filter)");
9519
+ deps.renderer.write(color.dim(`
9520
+ ${navLines.join(" \xB7 ")}
9521
+ `));
9235
9522
  const age = await deps.modelsRegistry.ageSeconds();
9236
9523
  deps.renderer.write(
9237
9524
  color.dim(
9238
- `
9239
- Cache age: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never fetched"}. Run \`wstack models refresh\` to update.
9525
+ `Cache age: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never fetched"}. Run \`wstack models refresh\` to update.
9240
9526
  `
9241
9527
  )
9242
9528
  );
9243
9529
  return 0;
9244
9530
  };
9531
+ async function mutateModelsConfig(deps, mutator) {
9532
+ const vault = deps.vault;
9533
+ const configPath2 = deps.paths.globalConfig;
9534
+ let fileExists = true;
9535
+ let raw;
9536
+ try {
9537
+ raw = await fsp3.readFile(configPath2, "utf8");
9538
+ } catch (err) {
9539
+ if (err.code !== "ENOENT") throw err;
9540
+ fileExists = false;
9541
+ raw = "{}";
9542
+ }
9543
+ let parsed;
9544
+ try {
9545
+ parsed = JSON.parse(raw);
9546
+ } catch (err) {
9547
+ if (fileExists) {
9548
+ throw new Error(
9549
+ `Refusing to overwrite corrupt config at ${configPath2} (${err.message}).`
9550
+ );
9551
+ }
9552
+ parsed = {};
9553
+ }
9554
+ const decrypted = decryptConfigSecrets$1(parsed, vault);
9555
+ const models = decrypted.models ?? {};
9556
+ mutator(models);
9557
+ decrypted.models = models;
9558
+ const encrypted = encryptConfigSecrets$1(decrypted, vault);
9559
+ await atomicWrite(configPath2, JSON.stringify(encrypted, null, 2), { mode: 384 });
9560
+ }
9561
+ function parseSizeFlag(raw) {
9562
+ if (!raw) return void 0;
9563
+ const s = raw.trim().toLowerCase();
9564
+ const match = /^(\d+(?:\.\d+)?)\s*(k|m|b)?$/.exec(s);
9565
+ if (!match) return void 0;
9566
+ const num = Number.parseFloat(match[1]);
9567
+ const unit = match[2];
9568
+ if (unit === "b") return Math.round(num * 1e9);
9569
+ if (unit === "m") return Math.round(num * 1e6);
9570
+ if (unit === "k") return Math.round(num * 1e3);
9571
+ return Math.round(num);
9572
+ }
9573
+ function parseBoolFlag(flags, key) {
9574
+ if (flags[key] === true || flags[key] === "true") return true;
9575
+ if (flags[`no-${key}`] !== void 0) return false;
9576
+ return void 0;
9577
+ }
9578
+ async function modelsAdd(args, deps) {
9579
+ const flags = parseFlags2(args);
9580
+ const pos = positionals(args);
9581
+ const modelId = pos[0];
9582
+ if (!modelId) {
9583
+ deps.renderer.writeError(
9584
+ "Usage: wstack models add <modelId> [--provider <id>] [--name <name>] [--max-context <N>] [--max-output <N>] [--tools] [--no-tools] [--vision] [--no-vision] [--reasoning] [--streaming] [--no-streaming] [--json-mode]"
9585
+ );
9586
+ return 1;
9587
+ }
9588
+ const existing = deps.config.models?.[modelId];
9589
+ if (existing) {
9590
+ deps.renderer.writeWarning(
9591
+ `Model "${modelId}" already defined. Overwriting.`
9592
+ );
9593
+ }
9594
+ const capabilities = {};
9595
+ const toolsVal = parseBoolFlag(flags, "tools");
9596
+ if (toolsVal !== void 0) capabilities.tools = toolsVal;
9597
+ const visionVal = parseBoolFlag(flags, "vision");
9598
+ if (visionVal !== void 0) capabilities.vision = visionVal;
9599
+ const streamingVal = parseBoolFlag(flags, "streaming");
9600
+ if (streamingVal !== void 0) capabilities.streaming = streamingVal;
9601
+ const reasoningVal = parseBoolFlag(flags, "reasoning");
9602
+ if (reasoningVal !== void 0) capabilities.reasoning = reasoningVal;
9603
+ const jsonModeVal = parseBoolFlag(flags, "json-mode");
9604
+ if (jsonModeVal !== void 0) capabilities.jsonMode = jsonModeVal;
9605
+ const maxContextRaw = typeof flags["max-context"] === "string" ? flags["max-context"] : void 0;
9606
+ const maxContext = parseSizeFlag(maxContextRaw);
9607
+ if (maxContext !== void 0) capabilities.maxContext = maxContext;
9608
+ const def = {};
9609
+ const nameFlag = typeof flags["name"] === "string" ? flags["name"] : void 0;
9610
+ const providerFlag = typeof flags["provider"] === "string" ? flags["provider"] : void 0;
9611
+ if (nameFlag) def.name = nameFlag;
9612
+ if (providerFlag) def.provider = providerFlag;
9613
+ if (Object.keys(capabilities).length > 0) def.capabilities = capabilities;
9614
+ const maxOutputRaw = typeof flags["max-output"] === "string" ? flags["max-output"] : void 0;
9615
+ const maxOutput = parseSizeFlag(maxOutputRaw);
9616
+ if (maxOutput !== void 0) def.maxOutput = maxOutput;
9617
+ await mutateModelsConfig(deps, (models) => {
9618
+ models[modelId] = def;
9619
+ });
9620
+ deps.renderer.writeInfo(`Custom model "${modelId}" ${existing ? "updated" : "added"}.`);
9621
+ const capLines = [];
9622
+ if (def.capabilities) {
9623
+ for (const [k, v] of Object.entries(def.capabilities)) {
9624
+ capLines.push(` ${k}: ${v}`);
9625
+ }
9626
+ }
9627
+ if (def.maxOutput !== void 0) capLines.push(` maxOutput: ${def.maxOutput}`);
9628
+ if (capLines.length > 0) {
9629
+ deps.renderer.write(color.dim(capLines.join("\n") + "\n"));
9630
+ }
9631
+ return 0;
9632
+ }
9633
+ async function modelsRemove(args, deps) {
9634
+ const modelId = args[0];
9635
+ if (!modelId) {
9636
+ deps.renderer.writeError("Usage: wstack models remove <modelId>");
9637
+ return 1;
9638
+ }
9639
+ const existing = deps.config.models?.[modelId];
9640
+ if (!existing) {
9641
+ deps.renderer.writeError(`No custom model "${modelId}" found.`);
9642
+ return 1;
9643
+ }
9644
+ await mutateModelsConfig(deps, (models) => {
9645
+ delete models[modelId];
9646
+ });
9647
+ deps.renderer.writeInfo(`Removed custom model "${modelId}".`);
9648
+ return 0;
9649
+ }
9650
+ async function modelsList(_args, deps) {
9651
+ const models = deps.config.models ?? {};
9652
+ const entries = Object.entries(models);
9653
+ if (entries.length === 0) {
9654
+ deps.renderer.write(color.dim("No custom models defined.\n"));
9655
+ deps.renderer.write(color.dim("Use `wstack models add <modelId> --max-context 128k --tools`\n"));
9656
+ return 0;
9657
+ }
9658
+ deps.renderer.write(color.bold("Custom models\n"));
9659
+ for (const [id, def] of entries.sort(([a], [b]) => a.localeCompare(b))) {
9660
+ const label = def.name ?? id;
9661
+ const provider = def.provider ? ` ${color.dim(`(${def.provider})`)}` : "";
9662
+ deps.renderer.write(` ${color.bold(label)}${provider}
9663
+ `);
9664
+ if (def.capabilities) {
9665
+ for (const [k, v] of Object.entries(def.capabilities)) {
9666
+ deps.renderer.write(` ${color.dim(`${k}:`)} ${v}
9667
+ `);
9668
+ }
9669
+ }
9670
+ if (def.maxOutput !== void 0) {
9671
+ deps.renderer.write(` ${color.dim("maxOutput:")} ${def.maxOutput}
9672
+ `);
9673
+ }
9674
+ }
9675
+ return 0;
9676
+ }
9245
9677
  function redactKeys(obj) {
9246
9678
  if (!obj || typeof obj !== "object") return obj;
9247
9679
  if (Array.isArray(obj)) return obj.map(redactKeys);
@@ -9271,7 +9703,7 @@ async function listFleetRuns(deps) {
9271
9703
  }
9272
9704
  const runs = [];
9273
9705
  for (const id of entries) {
9274
- const runDir = path9.join(deps.paths.projectSessions, id);
9706
+ const runDir = path8.join(deps.paths.projectSessions, id);
9275
9707
  let stat4;
9276
9708
  try {
9277
9709
  stat4 = await fsp3.stat(runDir);
@@ -9284,17 +9716,17 @@ async function listFleetRuns(deps) {
9284
9716
  let subagentCount = 0;
9285
9717
  let subagentsDir;
9286
9718
  try {
9287
- await fsp3.access(path9.join(runDir, "fleet.json"));
9719
+ await fsp3.access(path8.join(runDir, "fleet.json"));
9288
9720
  manifest = true;
9289
9721
  } catch {
9290
9722
  }
9291
9723
  try {
9292
- await fsp3.access(path9.join(runDir, "checkpoint.json"));
9724
+ await fsp3.access(path8.join(runDir, "checkpoint.json"));
9293
9725
  checkpoint = true;
9294
9726
  } catch {
9295
9727
  }
9296
9728
  try {
9297
- subagentsDir = path9.join(runDir, "subagents");
9729
+ subagentsDir = path8.join(runDir, "subagents");
9298
9730
  const files = await fsp3.readdir(subagentsDir);
9299
9731
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
9300
9732
  } catch {
@@ -9323,7 +9755,7 @@ async function listFleetRuns(deps) {
9323
9755
  return 0;
9324
9756
  }
9325
9757
  async function showFleetRun(runId, deps) {
9326
- const runDir = path9.join(deps.paths.projectSessions, runId);
9758
+ const runDir = path8.join(deps.paths.projectSessions, runId);
9327
9759
  let stat4;
9328
9760
  try {
9329
9761
  stat4 = await fsp3.stat(runDir);
@@ -9340,7 +9772,7 @@ async function showFleetRun(runId, deps) {
9340
9772
  deps.renderer.write(color.bold(`
9341
9773
  Fleet Run: ${runId}
9342
9774
  `) + "\n");
9343
- const manifestPath = path9.join(runDir, "fleet.json");
9775
+ const manifestPath = path8.join(runDir, "fleet.json");
9344
9776
  let manifestData = null;
9345
9777
  try {
9346
9778
  manifestData = await fsp3.readFile(manifestPath, "utf8");
@@ -9356,7 +9788,7 @@ Fleet Run: ${runId}
9356
9788
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
9357
9789
  `);
9358
9790
  }
9359
- const checkpointPath = path9.join(runDir, "checkpoint.json");
9791
+ const checkpointPath = path8.join(runDir, "checkpoint.json");
9360
9792
  let checkpointData = null;
9361
9793
  try {
9362
9794
  checkpointData = await fsp3.readFile(checkpointPath, "utf8");
@@ -9403,7 +9835,7 @@ Fleet Run: ${runId}
9403
9835
  } catch {
9404
9836
  }
9405
9837
  }
9406
- const subagentsDir = path9.join(runDir, "subagents");
9838
+ const subagentsDir = path8.join(runDir, "subagents");
9407
9839
  let subagentFiles = [];
9408
9840
  try {
9409
9841
  subagentFiles = await fsp3.readdir(subagentsDir);
@@ -9415,7 +9847,7 @@ Fleet Run: ${runId}
9415
9847
  Subagent transcripts (${subagentFiles.length}):
9416
9848
  `);
9417
9849
  for (const f of subagentFiles.sort()) {
9418
- const filePath = path9.join(subagentsDir, f);
9850
+ const filePath = path8.join(subagentsDir, f);
9419
9851
  let size;
9420
9852
  try {
9421
9853
  const s = await fsp3.stat(filePath);
@@ -9432,7 +9864,7 @@ Fleet Run: ${runId}
9432
9864
  ${color.dim("\u25CB")} No subagent transcripts
9433
9865
  `);
9434
9866
  }
9435
- const sharedDir = path9.join(runDir, "shared");
9867
+ const sharedDir = path8.join(runDir, "shared");
9436
9868
  try {
9437
9869
  const files = await fsp3.readdir(sharedDir);
9438
9870
  deps.renderer.write(`
@@ -9599,7 +10031,7 @@ function findSessionId(args) {
9599
10031
  var rewindCmd = async (args, deps) => {
9600
10032
  const flags = parseRewindFlags(args);
9601
10033
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
9602
- const sessionsDir = path9.join(wpaths.globalRoot, "sessions");
10034
+ const sessionsDir = path8.join(wpaths.globalRoot, "sessions");
9603
10035
  const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
9604
10036
  let sessionId = findSessionId(args);
9605
10037
  if (!sessionId) {
@@ -9834,10 +10266,10 @@ var auditCmd = async (args, deps) => {
9834
10266
  return verify.ok ? 0 : 1;
9835
10267
  };
9836
10268
  async function listAudits(log, dir, deps) {
9837
- const fs23 = await import('fs/promises');
10269
+ const fs24 = await import('fs/promises');
9838
10270
  let entries;
9839
10271
  try {
9840
- entries = await fs23.readdir(dir);
10272
+ entries = await fs24.readdir(dir);
9841
10273
  } catch {
9842
10274
  deps.renderer.write(
9843
10275
  color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
@@ -9892,7 +10324,7 @@ var skillsCmd = async (_args, deps) => {
9892
10324
  };
9893
10325
  var versionCmd = async (_args, deps) => {
9894
10326
  deps.renderer.write(
9895
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os3.platform()})
10327
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os2.platform()})
9896
10328
  `
9897
10329
  );
9898
10330
  return 0;
@@ -9914,6 +10346,9 @@ var helpCmd = async (_args, deps) => {
9914
10346
  " wstack providers [--all] List providers from models.dev",
9915
10347
  " wstack models [<provider>] List models",
9916
10348
  " wstack models refresh Force-refresh cache",
10349
+ " wstack models add <mid> Add/override custom model (--max-context, --tools, --vision, \u2026)",
10350
+ " wstack models remove <mid> Remove a custom model",
10351
+ " wstack models list List all custom models",
9917
10352
  " wstack mcp [list] List MCP servers",
9918
10353
  " wstack plugin [list|status|official|install|add|remove|enable|disable] Manage plugins",
9919
10354
  " wstack projects List tracked projects",
@@ -9974,22 +10409,22 @@ function fmtDuration(ms) {
9974
10409
  const remMin = m - h * 60;
9975
10410
  return `${h}h${remMin}m`;
9976
10411
  }
9977
- function fmtTaskResultLine(r, color45) {
10412
+ function fmtTaskResultLine(r, color46) {
9978
10413
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
9979
10414
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
9980
10415
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
9981
10416
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
9982
- const errKindChip = errKind ? color45.dim(` [${errKind}]`) : "";
9983
- const errSnip = errMsg || errKind ? `${errKindChip}${color45.dim(errTail)}` : "";
10417
+ const errKindChip = errKind ? color46.dim(` [${errKind}]`) : "";
10418
+ const errSnip = errMsg || errKind ? `${errKindChip}${color46.dim(errTail)}` : "";
9984
10419
  switch (r.status) {
9985
10420
  case "success":
9986
- return { mark: color45.green("\u2713"), stats, tail: "" };
10421
+ return { mark: color46.green("\u2713"), stats, tail: "" };
9987
10422
  case "timeout":
9988
- return { mark: color45.yellow("\u23F1"), stats: `${color45.yellow("timeout")} ${stats}`, tail: errSnip };
10423
+ return { mark: color46.yellow("\u23F1"), stats: `${color46.yellow("timeout")} ${stats}`, tail: errSnip };
9989
10424
  case "stopped":
9990
- return { mark: color45.dim("\u2298"), stats: `${color45.dim("stopped")} ${stats}`, tail: errSnip };
10425
+ return { mark: color46.dim("\u2298"), stats: `${color46.dim("stopped")} ${stats}`, tail: errSnip };
9991
10426
  case "failed":
9992
- return { mark: color45.red("\u2717"), stats: `${color45.red("failed")} ${stats}`, tail: errSnip };
10427
+ return { mark: color46.red("\u2717"), stats: `${color46.red("failed")} ${stats}`, tail: errSnip };
9993
10428
  }
9994
10429
  }
9995
10430
 
@@ -10007,7 +10442,7 @@ function resolveBundledSkillsDir() {
10007
10442
  try {
10008
10443
  const req2 = createRequire(import.meta.url);
10009
10444
  const corePkg = req2.resolve("@wrongstack/core/package.json");
10010
- return path9.join(path9.dirname(corePkg), "skills");
10445
+ return path8.join(path8.dirname(corePkg), "skills");
10011
10446
  } catch {
10012
10447
  return void 0;
10013
10448
  }
@@ -10038,7 +10473,12 @@ async function boot(argv) {
10038
10473
  const reader = new ReadlineInputReader({ historyFile: wpaths.historyFile });
10039
10474
  const modelsRegistry = new DefaultModelsRegistry({
10040
10475
  cacheFile: wpaths.modelsCache,
10041
- ttlSeconds: 24 * 3600,
10476
+ // Force a refresh attempt once per CLI process. Model metadata changes faster
10477
+ // than releases (new model ids, corrected context windows), and stale cache
10478
+ // here directly affects runtime behavior like context bars and compaction.
10479
+ // If the network is unavailable, DefaultModelsRegistry still falls back to
10480
+ // stale cache or the bundled overlay instead of failing startup.
10481
+ ttlSeconds: 0,
10042
10482
  // Curated overlay merged on top of models.dev: fetched from GitHub raw for
10043
10483
  // freshness, with the bundled file as the offline floor.
10044
10484
  overlayUrl: GITHUB_PROVIDERS_OVERLAY_URL,
@@ -10052,6 +10492,15 @@ async function boot(argv) {
10052
10492
  }).catch(() => {
10053
10493
  });
10054
10494
  }
10495
+ if (!flags["no-models-refresh"]) {
10496
+ try {
10497
+ await modelsRegistry.refresh();
10498
+ logger.info("models.dev catalog refreshed");
10499
+ } catch (err) {
10500
+ const msg = err instanceof Error ? err.message : String(err);
10501
+ logger.warn(`models.dev refresh failed (${msg}); using cached catalog`);
10502
+ }
10503
+ }
10055
10504
  const first = positional[0];
10056
10505
  if (first && subcommands[first]) {
10057
10506
  const container = createDefaultContainer({
@@ -10143,7 +10592,8 @@ async function boot(argv) {
10143
10592
  else if (flags["no-director"] === true) directorPinned = false;
10144
10593
  let autonomyPinned;
10145
10594
  if (flags["no-autonomy"] === true) autonomyPinned = "off";
10146
- else if (flags["eternal"] === true) autonomyPinned = "off";
10595
+ else if (flags["eternal"] === true)
10596
+ autonomyPinned = "off";
10147
10597
  else if (typeof flags["autonomy"] === "string") {
10148
10598
  const v = flags["autonomy"].toLowerCase();
10149
10599
  autonomyPinned = v === "off" || v === "no" || v === "false" ? "off" : "auto";
@@ -10178,7 +10628,7 @@ async function boot(argv) {
10178
10628
  if (choices.director) flags["director"] = true;
10179
10629
  flags["autonomy"] = choices.autonomy;
10180
10630
  printLaunchHints(renderer, flags, {
10181
- cursorFile: path9.join(wpaths.cacheDir, "hint-cursor")
10631
+ cursorFile: path8.join(wpaths.cacheDir, "hint-cursor")
10182
10632
  });
10183
10633
  }
10184
10634
  return {
@@ -10556,7 +11006,7 @@ async function runRepl(opts) {
10556
11006
  `[eternal] ${err instanceof Error ? err.message : String(err)}`
10557
11007
  );
10558
11008
  }
10559
- await new Promise((resolve4) => setTimeout(resolve4, 250));
11009
+ await new Promise((resolve3) => setTimeout(resolve3, 250));
10560
11010
  continue;
10561
11011
  }
10562
11012
  } else if (opts.getAutonomy?.() === "eternal-parallel") {
@@ -10602,7 +11052,7 @@ async function runRepl(opts) {
10602
11052
  `[parallel] ${err instanceof Error ? err.message : String(err)}`
10603
11053
  );
10604
11054
  }
10605
- await new Promise((resolve4) => setTimeout(resolve4, 250));
11055
+ await new Promise((resolve3) => setTimeout(resolve3, 250));
10606
11056
  continue;
10607
11057
  }
10608
11058
  }
@@ -11152,7 +11602,17 @@ async function execute(deps) {
11152
11602
  const visionAdapters = () => createToolVisionAdapters(agent.tools);
11153
11603
  const supportsVision = async () => {
11154
11604
  try {
11155
- const caps = await capabilitiesFor(modelsRegistry, context.provider.id, context.model);
11605
+ const providerConfig = config.providers?.[context.provider.id];
11606
+ const mergedModels = mergeCustomModelDefs(
11607
+ providerConfig?.customModels,
11608
+ config.models
11609
+ );
11610
+ const caps = await capabilitiesFor(
11611
+ modelsRegistry,
11612
+ context.provider.id,
11613
+ context.model,
11614
+ mergedModels
11615
+ );
11156
11616
  return caps.vision;
11157
11617
  } catch {
11158
11618
  return context.provider.capabilities.vision;
@@ -11256,6 +11716,11 @@ async function execute(deps) {
11256
11716
  "phase.taskCompleted",
11257
11717
  "phase.taskFailed",
11258
11718
  "phase.taskRetrying",
11719
+ "phase.verifying",
11720
+ "phase.verifyFailed",
11721
+ "phase.repairing",
11722
+ "phase.conflictResolving",
11723
+ "phase.conflictResolved",
11259
11724
  "autonomous.tick",
11260
11725
  "graph.completed",
11261
11726
  "graph.failed",
@@ -11383,10 +11848,18 @@ async function execute(deps) {
11383
11848
  return getActiveSDDContext2();
11384
11849
  },
11385
11850
  onSDDOutput: async (output) => {
11386
- const { trySaveSpecFromAIOutput: trySaveSpecFromAIOutput2, trySaveImplementationPlan: trySaveImplementationPlan2, trySaveTasksFromAIOutput: trySaveTasksFromAIOutput2, autoDetectTaskCompletion: autoDetectTaskCompletion2, getTaskProgress: getTaskProgress2, getActiveSDDPhase: getActiveSDDPhase2 } = (init_sdd(), __toCommonJS(sdd_exports));
11851
+ const {
11852
+ trySaveSpecFromAIOutput: trySaveSpecFromAIOutput2,
11853
+ trySaveImplementationPlan: trySaveImplementationPlan2,
11854
+ trySaveTasksFromAIOutput: trySaveTasksFromAIOutput2,
11855
+ autoDetectTaskCompletion: autoDetectTaskCompletion2,
11856
+ getTaskProgress: getTaskProgress2,
11857
+ getActiveSDDPhase: getActiveSDDPhase2
11858
+ } = (init_sdd(), __toCommonJS(sdd_exports));
11387
11859
  const messages = [];
11388
11860
  const specSaved = await trySaveSpecFromAIOutput2(output);
11389
- if (specSaved) messages.push("\u2713 Spec detected and saved! Use /sdd approve to continue.");
11861
+ if (specSaved)
11862
+ messages.push("\u2713 Spec detected and saved! Use /sdd approve to continue.");
11390
11863
  const planSaved = trySaveImplementationPlan2(output);
11391
11864
  if (planSaved) messages.push("\u2713 Implementation plan saved!");
11392
11865
  const tasksSaved = await trySaveTasksFromAIOutput2(output);
@@ -11401,7 +11874,9 @@ async function execute(deps) {
11401
11874
  if (autoCompleted > 0) {
11402
11875
  const progress = getTaskProgress2();
11403
11876
  if (progress) {
11404
- messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`);
11877
+ messages.push(
11878
+ `\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percentComplete}%)`
11879
+ );
11405
11880
  }
11406
11881
  }
11407
11882
  }
@@ -11433,7 +11908,7 @@ async function execute(deps) {
11433
11908
  supportsVision,
11434
11909
  attachments,
11435
11910
  effectiveMaxContext,
11436
- projectName: path9.basename(projectRoot) || void 0,
11911
+ projectName: path8.basename(projectRoot) || void 0,
11437
11912
  projectRoot,
11438
11913
  getAutonomy,
11439
11914
  onAutonomy,
@@ -11461,7 +11936,7 @@ async function execute(deps) {
11461
11936
  supportsVision,
11462
11937
  attachments,
11463
11938
  effectiveMaxContext,
11464
- projectName: path9.basename(projectRoot) || void 0,
11939
+ projectName: path8.basename(projectRoot) || void 0,
11465
11940
  getAutonomy,
11466
11941
  onAutonomy,
11467
11942
  getNextPredict,
@@ -11562,7 +12037,8 @@ var MultiAgentHost = class {
11562
12037
  directorBudget: this.opts.directorBudget,
11563
12038
  manifestDebounceMs: 2e3,
11564
12039
  checkpointDebounceMs: this.opts.checkpointDebounceMs ?? 250,
11565
- maxSpawnDepth: 5
12040
+ maxSpawnDepth: 5,
12041
+ maxContext: this.opts.getLeaderMaxContext
11566
12042
  });
11567
12043
  this.fleetManager = fleetManager;
11568
12044
  if (this.opts.sessionsRoot && !this.sessionFactory) {
@@ -11576,7 +12052,7 @@ var MultiAgentHost = class {
11576
12052
  doneCondition: { type: "all_tasks_done" },
11577
12053
  maxConcurrent: this.opts.maxConcurrent ?? 4
11578
12054
  };
11579
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path9.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
12055
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path8.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
11580
12056
  this.director = new Director({
11581
12057
  config: coordinatorConfig,
11582
12058
  manifestPath: this.opts.manifestPath,
@@ -11589,11 +12065,13 @@ var MultiAgentHost = class {
11589
12065
  sessionsRoot: this.opts.sessionsRoot,
11590
12066
  directorRunId: this.opts.directorRunId,
11591
12067
  maxSpawnDepth: 5,
12068
+ maxContext: this.opts.getLeaderMaxContext,
11592
12069
  // Live getter (not a snapshot) so a mid-session `/setmodel` takes
11593
12070
  // effect on the next spawn — the director is built lazily + once.
11594
12071
  modelMatrix: () => this.deps.configStore.get().modelMatrix,
11595
- fleetManager
12072
+ fleetManager,
11596
12073
  // pass so director.fleetManager is never undefined
12074
+ brain: this.opts.brain
11597
12075
  });
11598
12076
  this.director.on("task.completed", ({ task, result }) => {
11599
12077
  this.fleetManager?.removePendingTask(task.id);
@@ -11779,9 +12257,18 @@ var MultiAgentHost = class {
11779
12257
  subagentId: subCfg.id ?? subCfg.name ?? "subagent"
11780
12258
  });
11781
12259
  });
12260
+ const offCtxBridge = events.on("ctx.pct", (e) => {
12261
+ hostEvents.emit("subagent.ctx_pct", {
12262
+ subagentId: subCfg.id ?? subCfg.name ?? "subagent",
12263
+ load: e.load,
12264
+ tokens: e.tokens,
12265
+ maxContext: e.maxContext
12266
+ });
12267
+ });
11782
12268
  const dispose = async () => {
11783
12269
  offToolBridge();
11784
12270
  offSummaryBridge();
12271
+ offCtxBridge();
11785
12272
  try {
11786
12273
  await subSession.close?.();
11787
12274
  } catch {
@@ -11815,22 +12302,28 @@ var MultiAgentHost = class {
11815
12302
  * use the leader and the calling code can decide to error later).
11816
12303
  */
11817
12304
  async buildSubagentProvider(config, overrideId, model) {
11818
- const providerId = overrideId && config.providers?.[overrideId] ? overrideId : config.provider;
12305
+ const requestedProviderId = overrideId ?? config.provider;
12306
+ const providerId = requestedProviderId === config.provider || config.providers?.[requestedProviderId] !== void 0 || this.deps.providerRegistry.has(requestedProviderId) ? requestedProviderId : config.provider;
11819
12307
  const newCfg = config.providers?.[providerId] ?? {
11820
12308
  type: providerId,
11821
12309
  apiKey: config.apiKey,
11822
12310
  baseUrl: config.baseUrl
11823
12311
  };
11824
- const provider = makeProviderFromConfig(providerId, {
12312
+ const cfgWithType = {
11825
12313
  ...newCfg,
11826
12314
  type: providerId
11827
- });
12315
+ };
12316
+ const provider = this.deps.providerRegistry.has(providerId) ? this.deps.providerRegistry.create(cfgWithType) : makeProviderFromConfig(providerId, cfgWithType);
11828
12317
  if (this.deps.modelsRegistry) {
11829
12318
  const resolvedModel = model ?? config.model;
11830
- const caps = await capabilitiesFor(this.deps.modelsRegistry, providerId, resolvedModel).catch(
11831
- () => void 0
11832
- );
11833
- const mc = caps?.maxContext ?? config.context?.effectiveMaxContext ?? provider.capabilities.maxContext;
12319
+ const mc = await resolveRuntimeMaxContext({
12320
+ modelsRegistry: this.deps.modelsRegistry,
12321
+ config,
12322
+ provider,
12323
+ runtimeProviderConfig: cfgWithType,
12324
+ providerId,
12325
+ modelId: resolvedModel
12326
+ });
11834
12327
  if (mc && mc > 0) provider.capabilities.maxContext = mc;
11835
12328
  }
11836
12329
  return provider;
@@ -12025,16 +12518,16 @@ var MultiAgentHost = class {
12025
12518
  if (this.director) return this.director;
12026
12519
  this.opts.directorMode = true;
12027
12520
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
12028
- this.opts.manifestPath = path9.join(this.opts.fleetRoot, "fleet.json");
12521
+ this.opts.manifestPath = path8.join(this.opts.fleetRoot, "fleet.json");
12029
12522
  }
12030
12523
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
12031
- this.opts.sharedScratchpadPath = path9.join(this.opts.fleetRoot, "shared");
12524
+ this.opts.sharedScratchpadPath = path8.join(this.opts.fleetRoot, "shared");
12032
12525
  }
12033
12526
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
12034
- this.opts.sessionsRoot = path9.join(this.opts.fleetRoot, "subagents");
12527
+ this.opts.sessionsRoot = path8.join(this.opts.fleetRoot, "subagents");
12035
12528
  }
12036
12529
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
12037
- this.opts.stateCheckpointPath = path9.join(this.opts.fleetRoot, "director-state.json");
12530
+ this.opts.stateCheckpointPath = path8.join(this.opts.fleetRoot, "director-state.json");
12038
12531
  }
12039
12532
  await this.ensureDirector();
12040
12533
  return this.director ?? null;
@@ -12220,11 +12713,11 @@ var SessionStats = class {
12220
12713
  if (e.name === "bash") this.bashCommands++;
12221
12714
  else if (e.name === "fetch") this.fetches++;
12222
12715
  if (!e.ok) return;
12223
- const path26 = typeof input?.path === "string" ? input.path : void 0;
12224
- if (e.name === "read" && path26) this.readPaths.add(path26);
12225
- else if (e.name === "edit" && path26) this.editedPaths.add(path26);
12226
- else if (e.name === "write" && path26) {
12227
- this.writtenPaths.add(path26);
12716
+ const path25 = typeof input?.path === "string" ? input.path : void 0;
12717
+ if (e.name === "read" && path25) this.readPaths.add(path25);
12718
+ else if (e.name === "edit" && path25) this.editedPaths.add(path25);
12719
+ else if (e.name === "write" && path25) {
12720
+ this.writtenPaths.add(path25);
12228
12721
  const content = typeof input?.content === "string" ? input.content : "";
12229
12722
  this.bytesWritten += Buffer.byteLength(content, "utf8");
12230
12723
  }
@@ -12330,7 +12823,7 @@ function samplePaths(set) {
12330
12823
  }
12331
12824
  var WORKTREE_PHASE_CONCURRENCY = 4;
12332
12825
  function gitText(args, cwd) {
12333
- return new Promise((resolve4) => {
12826
+ return new Promise((resolve3) => {
12334
12827
  let out = "";
12335
12828
  const child = spawn("git", args, {
12336
12829
  cwd,
@@ -12343,8 +12836,8 @@ function gitText(args, cwd) {
12343
12836
  child.stderr?.on("data", (c) => {
12344
12837
  out += c.toString();
12345
12838
  });
12346
- child.on("error", () => resolve4({ code: 1, out }));
12347
- child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
12839
+ child.on("error", () => resolve3({ code: 1, out }));
12840
+ child.on("close", (code) => resolve3({ code: code ?? 1, out: out.trim() }));
12348
12841
  });
12349
12842
  }
12350
12843
  async function isGitRepo(cwd) {
@@ -12352,7 +12845,7 @@ async function isGitRepo(cwd) {
12352
12845
  return code === 0 && out.trim() === "true";
12353
12846
  }
12354
12847
  function runCmd(cmd, args, cwd, shell = false) {
12355
- return new Promise((resolve4) => {
12848
+ return new Promise((resolve3) => {
12356
12849
  let out = "";
12357
12850
  const child = spawn(cmd, args, {
12358
12851
  cwd,
@@ -12366,8 +12859,8 @@ function runCmd(cmd, args, cwd, shell = false) {
12366
12859
  child.stderr?.on("data", (c) => {
12367
12860
  out += c.toString();
12368
12861
  });
12369
- child.on("error", (e) => resolve4({ code: 1, out: `${out}${String(e)}` }));
12370
- child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
12862
+ child.on("error", (e) => resolve3({ code: 1, out: `${out}${String(e)}` }));
12863
+ child.on("close", (code) => resolve3({ code: code ?? 1, out: out.trim() }));
12371
12864
  });
12372
12865
  }
12373
12866
  function detectPackageManager2(root) {
@@ -12572,6 +13065,7 @@ ${res.out}` };
12572
13065
  return false;
12573
13066
  }
12574
13067
  } : void 0,
13068
+ brain: deps.brain,
12575
13069
  onPhaseComplete: (phase) => {
12576
13070
  log(`\u2705 Phase completed: ${phase.name}`);
12577
13071
  void persist(graph);
@@ -12593,11 +13087,20 @@ ${res.out}` };
12593
13087
  });
12594
13088
  const onUntyped = deps.events.on;
12595
13089
  const offUntyped = deps.events.off;
13090
+ const finalizeActiveRun = () => {
13091
+ if (active?.graph.id !== graph.id) return;
13092
+ active.unsubscribe();
13093
+ active = null;
13094
+ };
12596
13095
  const onDone = () => {
12597
13096
  log(`\u{1F389} AutoPhase complete: ${graph.title}`);
12598
13097
  void persist(graph);
13098
+ finalizeActiveRun();
13099
+ };
13100
+ const onFailed = () => {
13101
+ void persist(graph);
13102
+ finalizeActiveRun();
12599
13103
  };
12600
- const onFailed = () => void persist(graph);
12601
13104
  onUntyped("graph.completed", onDone);
12602
13105
  onUntyped("graph.failed", onFailed);
12603
13106
  const unsubscribe = () => {
@@ -12607,6 +13110,8 @@ ${res.out}` };
12607
13110
  active = { graph, orchestrator, abort, unsubscribe };
12608
13111
  void orchestrator.start().catch((err) => {
12609
13112
  log(`\u{1F4A5} AutoPhase aborted: ${err instanceof Error ? err.message : String(err)}`);
13113
+ void persist(graph);
13114
+ finalizeActiveRun();
12610
13115
  });
12611
13116
  return { ok: true, graph };
12612
13117
  },
@@ -12781,11 +13286,27 @@ function setupPipelines(params) {
12781
13286
  return pipelines;
12782
13287
  }
12783
13288
  async function setupCompaction(params) {
12784
- const { compactor, events, modelsRegistry, context, config, provider, pipelines, fullConfig, sessionWriter, sessionBridge: providedBridge } = params;
12785
- const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
12786
- const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
13289
+ const {
13290
+ compactor,
13291
+ events,
13292
+ modelsRegistry,
13293
+ context,
13294
+ config,
13295
+ provider,
13296
+ pipelines,
13297
+ fullConfig,
13298
+ sessionWriter,
13299
+ sessionBridge: providedBridge
13300
+ } = params;
13301
+ const effectiveMaxContext = await resolveRuntimeMaxContext({
13302
+ modelsRegistry,
13303
+ config,
13304
+ provider,
13305
+ providerId: config.provider ?? provider.id,
13306
+ modelId: config.model ?? context.model
13307
+ });
12787
13308
  let autoCompactor;
12788
- if (config.context.autoCompact !== false) {
13309
+ if (config.context.autoCompact !== false && effectiveMaxContext > 0) {
12789
13310
  const auditLevel = resolveAuditLevel(fullConfig ?? config);
12790
13311
  const sessionBridge = providedBridge ?? createSessionEventBridge(sessionWriter, auditLevel);
12791
13312
  autoCompactor = new AutoCompactionMiddleware(
@@ -12872,7 +13393,7 @@ function setupMetrics(params) {
12872
13393
  const dumpMetrics = () => {
12873
13394
  if (!metricsSink) return;
12874
13395
  try {
12875
- const out = path9.join(wpaths.projectSessions, "metrics.json");
13396
+ const out = path8.join(wpaths.projectSessions, "metrics.json");
12876
13397
  const snap = metricsSink.snapshot();
12877
13398
  writeFileSync(out, JSON.stringify(snap, null, 2));
12878
13399
  } catch {
@@ -13131,12 +13652,12 @@ async function setupSession(params) {
13131
13652
  }
13132
13653
  const sessionRef = { current: session };
13133
13654
  await recoveryLock.write(session.id).catch(() => void 0);
13134
- const attachments = new DefaultAttachmentStore({ spoolDir: path9.join(wpaths.projectSessions, session.id, "attachments") });
13135
- const queueStore = new QueueStore({ dir: path9.join(wpaths.projectSessions, session.id) });
13655
+ const attachments = new DefaultAttachmentStore({ spoolDir: path8.join(wpaths.projectSessions, session.id, "attachments") });
13656
+ const queueStore = new QueueStore({ dir: path8.join(wpaths.projectSessions, session.id) });
13136
13657
  const ctxSignal = new AbortController().signal;
13137
13658
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
13138
13659
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
13139
- const todosCheckpointPath = path9.join(wpaths.projectSessions, `${session.id}.todos.json`);
13660
+ const todosCheckpointPath = path8.join(wpaths.projectSessions, `${session.id}.todos.json`);
13140
13661
  if (resumeId) {
13141
13662
  try {
13142
13663
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -13148,13 +13669,13 @@ async function setupSession(params) {
13148
13669
  }
13149
13670
  }
13150
13671
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
13151
- const planPath = path9.join(wpaths.projectSessions, `${session.id}.plan.json`);
13672
+ const planPath = path8.join(wpaths.projectSessions, `${session.id}.plan.json`);
13152
13673
  context.state.setMeta("plan.path", planPath);
13153
13674
  let dirState;
13154
13675
  if (resumeId) {
13155
13676
  try {
13156
- const fleetRoot = path9.join(wpaths.projectSessions, session.id);
13157
- dirState = await loadDirectorState(path9.join(fleetRoot, "director-state.json"));
13677
+ const fleetRoot = path8.join(wpaths.projectSessions, session.id);
13678
+ dirState = await loadDirectorState(path8.join(fleetRoot, "director-state.json"));
13158
13679
  if (dirState) {
13159
13680
  const tCounts = {};
13160
13681
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -13179,7 +13700,7 @@ function resolveBundledSkillsDir2() {
13179
13700
  try {
13180
13701
  const req2 = createRequire(import.meta.url);
13181
13702
  const corePkg = req2.resolve("@wrongstack/core/package.json");
13182
- return path9.join(path9.dirname(corePkg), "skills");
13703
+ return path8.join(path8.dirname(corePkg), "skills");
13183
13704
  } catch {
13184
13705
  return void 0;
13185
13706
  }
@@ -13304,7 +13825,6 @@ var resolveSessionLoggingConfig = (cfg) => ({
13304
13825
  }
13305
13826
  }
13306
13827
  });
13307
- var stageListeners = /* @__PURE__ */ new Set();
13308
13828
  async function main(argv) {
13309
13829
  const ctx = await boot(argv);
13310
13830
  if (typeof ctx === "number") return ctx;
@@ -13334,7 +13854,10 @@ async function main(argv) {
13334
13854
  forceAllYolo: flags["force-all-yolo"] === true,
13335
13855
  promptDelegate: makePromptDelegate(reader)
13336
13856
  },
13337
- compactor: { preserveK: config.context.preserveK, eliseThreshold: config.context.eliseThreshold },
13857
+ compactor: {
13858
+ preserveK: config.context.preserveK,
13859
+ eliseThreshold: config.context.eliseThreshold
13860
+ },
13338
13861
  bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
13339
13862
  });
13340
13863
  const replayFlag = flags["replay"];
@@ -13349,9 +13872,7 @@ async function main(argv) {
13349
13872
  mode,
13350
13873
  logger
13351
13874
  });
13352
- logger.info(
13353
- `replay: ProviderRunner bound in '${mode}' mode for session ${sessionId}`
13354
- );
13875
+ logger.info(`replay: ProviderRunner bound in '${mode}' mode for session ${sessionId}`);
13355
13876
  }
13356
13877
  const configStore = container.resolve(TOKENS.ConfigStore);
13357
13878
  container.bind(TOKENS.PathResolver, () => pathResolver);
@@ -13375,12 +13896,20 @@ async function main(argv) {
13375
13896
  }
13376
13897
  const modeId = activeMode?.id ?? "default";
13377
13898
  const modePrompt = activeMode?.prompt ?? "";
13378
- const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
13379
- const modelCapabilities = resolvedModel?.capabilities ? {
13380
- maxContextTokens: resolvedModel.capabilities.maxContext,
13381
- supportsTools: resolvedModel.capabilities.tools,
13382
- supportsVision: resolvedModel.capabilities.vision,
13383
- supportsReasoning: resolvedModel.capabilities.reasoning
13899
+ const [resolvedCaps, resolvedModel] = await Promise.all([
13900
+ capabilitiesFor(
13901
+ modelsRegistry,
13902
+ provider.id,
13903
+ config.model,
13904
+ mergeCustomModelDefs(config.providers?.[provider.id]?.customModels, config.models)
13905
+ ).catch(() => void 0),
13906
+ modelsRegistry.getModel(config.provider, config.model).catch(() => void 0)
13907
+ ]);
13908
+ const modelCapabilities = resolvedCaps ? {
13909
+ maxContextTokens: resolvedCaps.maxContext,
13910
+ supportsTools: resolvedCaps.tools,
13911
+ supportsVision: resolvedCaps.vision,
13912
+ supportsReasoning: resolvedModel?.capabilities.reasoning ?? false
13384
13913
  } : void 0;
13385
13914
  const memoryStore = container.resolve(TOKENS.MemoryStore);
13386
13915
  const skillLoader = container.resolve(TOKENS.SkillLoader);
@@ -13396,15 +13925,15 @@ async function main(argv) {
13396
13925
  modeId,
13397
13926
  modePrompt,
13398
13927
  modelCapabilities,
13399
- planPath: () => sessionRef.current ? path9.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
13928
+ planPath: () => sessionRef.current ? path8.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
13400
13929
  contributors: [
13401
13930
  // Injects the ETERNAL AUTONOMY block when the user has activated
13402
- // `/autonomy eternal`. Without this, the per-iteration directive
13403
- // is the only place the model sees the rules — compaction can
13404
- // drop it and the model forgets it's in autonomy mode.
13931
+ // a long-running autonomy engine. Without this, the per-iteration
13932
+ // directive is the only place the model sees the rules — compaction
13933
+ // can drop it and the model forgets it's in autonomy mode.
13405
13934
  makeAutonomyPromptContributor({
13406
13935
  goalPath: goalPathForPrompt,
13407
- enabled: () => autonomyModeRef.current === "eternal"
13936
+ enabled: () => autonomyModeRef.current === "eternal" || autonomyModeRef.current === "eternal-parallel"
13408
13937
  })
13409
13938
  ]
13410
13939
  })
@@ -13421,7 +13950,13 @@ async function main(argv) {
13421
13950
  const events = new EventBus();
13422
13951
  events.setLogger(logger);
13423
13952
  const { metricsSink, healthRegistry } = (() => {
13424
- const ms = setupMetrics({ flags, wpaths, events, logger, config: { provider: config.provider, model: config.model } });
13953
+ const ms = setupMetrics({
13954
+ flags,
13955
+ wpaths,
13956
+ events,
13957
+ logger,
13958
+ config: { provider: config.provider, model: config.model }
13959
+ });
13425
13960
  return ms;
13426
13961
  })();
13427
13962
  const spinner = new Spinner();
@@ -13587,7 +14122,7 @@ async function main(argv) {
13587
14122
  });
13588
14123
  const pipelines = setupPipelines({ events, logger });
13589
14124
  const compactor = container.resolve(TOKENS.Compactor);
13590
- const { effectiveMaxContext, autoCompactor } = await setupCompaction({
14125
+ const compactionSetup = await setupCompaction({
13591
14126
  compactor,
13592
14127
  events,
13593
14128
  modelsRegistry,
@@ -13599,18 +14134,41 @@ async function main(argv) {
13599
14134
  sessionBridge
13600
14135
  // share the same bridge for consistent audit logging (compaction + errors + future)
13601
14136
  });
13602
- const refreshMaxContext = async (providerId, modelId) => {
13603
- if (!autoCompactor) return;
13604
- const cap = await capabilitiesFor(modelsRegistry, providerId, modelId).catch(() => void 0);
13605
- const mc = cap?.maxContext ?? config.context.effectiveMaxContext ?? 2e5;
13606
- autoCompactor.setMaxContext(mc);
14137
+ let effectiveMaxContext = compactionSetup.effectiveMaxContext;
14138
+ context.provider.capabilities.maxContext = effectiveMaxContext;
14139
+ const { autoCompactor } = compactionSetup;
14140
+ const refreshMaxContext = async (providerId, modelId, runtimeProviderConfig) => {
14141
+ const mc = await resolveRuntimeMaxContext({
14142
+ modelsRegistry,
14143
+ config,
14144
+ provider: context.provider,
14145
+ runtimeProviderConfig,
14146
+ providerId,
14147
+ modelId
14148
+ });
14149
+ effectiveMaxContext = mc;
14150
+ context.provider.capabilities.maxContext = effectiveMaxContext;
14151
+ if (effectiveMaxContext > 0) {
14152
+ autoCompactor?.setMaxContext(effectiveMaxContext);
14153
+ }
14154
+ events.emit("ctx.max_context", { providerId, modelId, maxContext: effectiveMaxContext });
14155
+ updateSpinnerContext();
13607
14156
  };
13608
14157
  const updateSpinnerContext = () => {
13609
14158
  if (effectiveMaxContext > 0 && lastInputTokens > 0) {
13610
14159
  spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
13611
14160
  } else spinner.setContext(void 0);
13612
14161
  };
13613
- const agent = createAgent({ container, tools: toolRegistry, providers: providerRegistry, events, pipelines, context, config, confirmAwaiter: makeConfirmAwaiter(reader) });
14162
+ const agent = createAgent({
14163
+ container,
14164
+ tools: toolRegistry,
14165
+ providers: providerRegistry,
14166
+ events,
14167
+ pipelines,
14168
+ context,
14169
+ config,
14170
+ confirmAwaiter: makeConfirmAwaiter(reader)
14171
+ });
13614
14172
  const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger });
13615
14173
  if (config.features.mcp) {
13616
14174
  for (const cfg of Object.values(config.mcpServers ?? {})) {
@@ -13655,7 +14213,7 @@ async function main(argv) {
13655
14213
  context.model = modelId;
13656
14214
  config = patchConfig(config, { provider: providerId, model: modelId });
13657
14215
  configStore.update({ provider: providerId, model: modelId });
13658
- void refreshMaxContext(resolvedProviderId, modelId);
14216
+ void refreshMaxContext(resolvedProviderId, modelId, cfgWithType);
13659
14217
  return null;
13660
14218
  } catch (err) {
13661
14219
  return err instanceof Error ? err.message : String(err);
@@ -13684,12 +14242,26 @@ async function main(argv) {
13684
14242
  }
13685
14243
  }
13686
14244
  };
13687
- const fleetRoot = directorMode ? path9.join(wpaths.projectSessions, session.id) : void 0;
13688
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path9.join(fleetRoot, "fleet.json") : void 0;
13689
- const sharedScratchpadPath = directorMode ? path9.join(fleetRoot, "shared") : void 0;
13690
- const subagentSessionsRoot = directorMode ? path9.join(fleetRoot, "subagents") : void 0;
13691
- const stateCheckpointPath = directorMode ? path9.join(fleetRoot, "director-state.json") : void 0;
13692
- const fleetRootForPromotion = path9.join(wpaths.projectSessions, session.id);
14245
+ const stageListeners = /* @__PURE__ */ new Set();
14246
+ const broadcastAutonomyStage = (stage) => {
14247
+ for (const fn of stageListeners) {
14248
+ try {
14249
+ fn(stage);
14250
+ } catch {
14251
+ }
14252
+ }
14253
+ };
14254
+ const fleetRoot = directorMode ? path8.join(wpaths.projectSessions, session.id) : void 0;
14255
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path8.join(fleetRoot, "fleet.json") : void 0;
14256
+ const sharedScratchpadPath = directorMode ? path8.join(fleetRoot, "shared") : void 0;
14257
+ const subagentSessionsRoot = directorMode ? path8.join(fleetRoot, "subagents") : void 0;
14258
+ const stateCheckpointPath = directorMode ? path8.join(fleetRoot, "director-state.json") : void 0;
14259
+ const fleetRootForPromotion = path8.join(wpaths.projectSessions, session.id);
14260
+ const brainQueue = new BrainDecisionQueue(events);
14261
+ const brain = new ObservableBrainArbiter(
14262
+ new HumanEscalatingBrainArbiter(new DefaultBrainArbiter(), brainQueue),
14263
+ events
14264
+ );
13693
14265
  const multiAgentHost = new MultiAgentHost(
13694
14266
  {
13695
14267
  container,
@@ -13714,7 +14286,9 @@ async function main(argv) {
13714
14286
  fleetRoot: fleetRootForPromotion,
13715
14287
  stateCheckpointPath,
13716
14288
  sessionWriter: session,
13717
- maxConcurrent
14289
+ maxConcurrent,
14290
+ getLeaderMaxContext: () => effectiveMaxContext,
14291
+ brain
13718
14292
  }
13719
14293
  );
13720
14294
  toolRegistry.register(
@@ -13785,6 +14359,7 @@ async function main(argv) {
13785
14359
  events,
13786
14360
  storeDir: wpaths.projectAutophase,
13787
14361
  projectRoot,
14362
+ brain,
13788
14363
  log: (line) => renderer.write(`${line}
13789
14364
  `)
13790
14365
  });
@@ -13881,7 +14456,9 @@ async function main(argv) {
13881
14456
  for (const a of s.live) {
13882
14457
  if (a.status === "running" || a.status === "idle") {
13883
14458
  const task = a.task ? ` \u2014 ${a.task.slice(0, 60)}` : "";
13884
- lines.push(` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`);
14459
+ lines.push(
14460
+ ` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`
14461
+ );
13885
14462
  }
13886
14463
  }
13887
14464
  for (const p of s.pending) {
@@ -14015,7 +14592,8 @@ async function main(argv) {
14015
14592
  }
14016
14593
  },
14017
14594
  onFleetSpawn: async (role) => {
14018
- if (!director) throw new Error("No director active \u2014 start with --director or use /autonomy parallel.");
14595
+ if (!director)
14596
+ throw new Error("No director active \u2014 start with --director or use /autonomy parallel.");
14019
14597
  const cfg = FLEET_ROSTER[role] ?? {
14020
14598
  id: `manual-${Date.now()}`,
14021
14599
  name: role,
@@ -14025,7 +14603,7 @@ async function main(argv) {
14025
14603
  return director.spawn(cfg);
14026
14604
  },
14027
14605
  onFleetLog: async (subagentId, mode) => {
14028
- const subagentsRoot = path9.join(fleetRootForPromotion, "subagents");
14606
+ const subagentsRoot = path8.join(fleetRootForPromotion, "subagents");
14029
14607
  let runDirs;
14030
14608
  try {
14031
14609
  runDirs = await fsp3.readdir(subagentsRoot);
@@ -14034,7 +14612,7 @@ async function main(argv) {
14034
14612
  }
14035
14613
  const found = [];
14036
14614
  for (const runId of runDirs) {
14037
- const runDir = path9.join(subagentsRoot, runId);
14615
+ const runDir = path8.join(subagentsRoot, runId);
14038
14616
  let files;
14039
14617
  try {
14040
14618
  files = await fsp3.readdir(runDir);
@@ -14043,7 +14621,7 @@ async function main(argv) {
14043
14621
  }
14044
14622
  for (const f of files) {
14045
14623
  if (!f.endsWith(".jsonl")) continue;
14046
- const full = path9.join(runDir, f);
14624
+ const full = path8.join(runDir, f);
14047
14625
  try {
14048
14626
  const stat4 = await fsp3.stat(full);
14049
14627
  found.push({
@@ -14068,7 +14646,9 @@ async function main(argv) {
14068
14646
  ` ${color.cyan(t2.subagentId.padEnd(18))} ${color.dim(t2.runId.slice(0, 18))} ${color.dim(`${(t2.size / 1024).toFixed(1)} KB`)}`
14069
14647
  );
14070
14648
  }
14071
- lines2.push("Use `/fleet log <subagentId>` for a summary, or append `raw` for the full JSONL.");
14649
+ lines2.push(
14650
+ "Use `/fleet log <subagentId>` for a summary, or append `raw` for the full JSONL."
14651
+ );
14072
14652
  return lines2.join("\n");
14073
14653
  }
14074
14654
  const matches = found.filter(
@@ -14097,7 +14677,9 @@ async function main(argv) {
14097
14677
  const ev = JSON.parse(line);
14098
14678
  counts[ev.type] = (counts[ev.type] ?? 0) + 1;
14099
14679
  if (ev.type === "user_input" && !firstUser) {
14100
- const txt = typeof ev.content === "string" ? ev.content : Array.isArray(ev.content) ? ev.content.filter((b) => b.type === "text").map((b) => b.text).join(" ") : "";
14680
+ const txt = typeof ev.content === "string" ? ev.content : Array.isArray(ev.content) ? ev.content.filter(
14681
+ (b) => b.type === "text"
14682
+ ).map((b) => b.text).join(" ") : "";
14101
14683
  firstUser = txt.slice(0, 120);
14102
14684
  }
14103
14685
  if (ev.type === "llm_response") {
@@ -14140,7 +14722,7 @@ async function main(argv) {
14140
14722
  }
14141
14723
  const dir = await multiAgentHost.ensureDirector();
14142
14724
  if (!dir) return "Director is not available.";
14143
- const dirStatePath = path9.join(fleetRootForPromotion, "director-state.json");
14725
+ const dirStatePath = path8.join(fleetRootForPromotion, "director-state.json");
14144
14726
  const prior = await loadDirectorState(dirStatePath);
14145
14727
  if (!prior) {
14146
14728
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -14165,9 +14747,7 @@ async function main(argv) {
14165
14747
  lines.push("Run `/fleet retry <taskId>` or `/fleet retry all` to re-assign.");
14166
14748
  return lines.join("\n");
14167
14749
  }
14168
- const targets = taskId === "all" ? interrupted : interrupted.filter(
14169
- (t) => t.taskId === taskId || t.taskId.startsWith(taskId)
14170
- );
14750
+ const targets = taskId === "all" ? interrupted : interrupted.filter((t) => t.taskId === taskId || t.taskId.startsWith(taskId));
14171
14751
  if (targets.length === 0) {
14172
14752
  return `No interrupted task matched "${taskId}".`;
14173
14753
  }
@@ -14211,9 +14791,9 @@ async function main(argv) {
14211
14791
  for (const tool of director2.tools(FLEET_ROSTER)) {
14212
14792
  toolRegistry.register(tool);
14213
14793
  }
14214
- const mp = path9.join(fleetRootForPromotion, "fleet.json");
14215
- const sp = path9.join(fleetRootForPromotion, "shared");
14216
- const ss = path9.join(fleetRootForPromotion, "subagents");
14794
+ const mp = path8.join(fleetRootForPromotion, "fleet.json");
14795
+ const sp = path8.join(fleetRootForPromotion, "shared");
14796
+ const ss = path8.join(fleetRootForPromotion, "subagents");
14217
14797
  const lines = [
14218
14798
  `${color.green("\u2713")} Promoted to director mode.`,
14219
14799
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -14284,17 +14864,19 @@ Restart WrongStack to load or unload plugin code in this session.`;
14284
14864
  const effectiveMode = mode ?? "eternal";
14285
14865
  if (effectiveMode === "eternal-parallel") {
14286
14866
  if (!parallelEngine) {
14287
- parallelEngine = new ParallelEternalEngine({
14867
+ const parallelOptions = {
14288
14868
  agent,
14289
14869
  projectRoot,
14290
14870
  compactor: container.resolve(TOKENS.Compactor),
14291
14871
  maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
14292
14872
  onIteration: broadcastEternalIteration,
14873
+ onStage: broadcastAutonomyStage,
14293
14874
  // Real per-role factory: each dispatched slot runs as a fresh,
14294
14875
  // isolated agent with the role's filtered tools + persona prompt
14295
14876
  // (instead of sharing the leader agent's Context).
14296
14877
  subagentFactory: multiAgentHost.makeSubagentFactory(config)
14297
- });
14878
+ };
14879
+ parallelEngine = new ParallelEternalEngine(parallelOptions);
14298
14880
  }
14299
14881
  void parallelEngine.prime?.();
14300
14882
  } else {
@@ -14304,7 +14886,8 @@ Restart WrongStack to load or unload plugin code in this session.`;
14304
14886
  projectRoot,
14305
14887
  compactor: container.resolve(TOKENS.Compactor),
14306
14888
  maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
14307
- onIteration: broadcastEternalIteration
14889
+ onIteration: broadcastEternalIteration,
14890
+ onStage: broadcastAutonomyStage
14308
14891
  });
14309
14892
  }
14310
14893
  void eternalEngine.prime();
@@ -14315,20 +14898,26 @@ Restart WrongStack to load or unload plugin code in this session.`;
14315
14898
  parallelEngine?.stop();
14316
14899
  },
14317
14900
  onExit: () => {
14901
+ brainQueue.dispose();
14318
14902
  void mcpRegistry.stopAll();
14319
14903
  },
14320
14904
  onBeforeExit: async () => {
14321
14905
  const { spawn: spawn3 } = await import('child_process');
14322
14906
  const cwd2 = projectRoot;
14323
- const statusResult = await new Promise((resolve4, reject) => {
14324
- const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
14325
- let stdout = "";
14326
- child.stdout?.on("data", (d) => {
14327
- stdout += d;
14328
- });
14329
- child.on("error", reject);
14330
- child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
14331
- });
14907
+ const statusResult = await new Promise(
14908
+ (resolve3, reject) => {
14909
+ const child = spawn3("git", ["status", "--porcelain"], {
14910
+ cwd: cwd2,
14911
+ stdio: ["ignore", "pipe", "pipe"]
14912
+ });
14913
+ let stdout = "";
14914
+ child.stdout?.on("data", (d) => {
14915
+ stdout += d;
14916
+ });
14917
+ child.on("error", reject);
14918
+ child.on("close", (code) => resolve3({ stdout, code: code ?? 0 }));
14919
+ }
14920
+ );
14332
14921
  if (statusResult.stdout.trim().length > 0) {
14333
14922
  const lines = statusResult.stdout.split("\n").filter(Boolean);
14334
14923
  return {
@@ -14405,8 +14994,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
14405
14994
  parallelSlots: opts?.parallelSlots,
14406
14995
  subagentFactory,
14407
14996
  onProgress: (p) => {
14408
- renderer.write(` \u2591 wave ${p.wave + 1} \xB7 ${p.completed}/${p.total} tasks \xB7 ${p.percent}% done
14409
- `);
14997
+ renderer.write(
14998
+ ` \u2591 wave ${p.wave + 1} \xB7 ${p.completed}/${p.total} tasks \xB7 ${p.percent}% done
14999
+ `
15000
+ );
14410
15001
  }
14411
15002
  });
14412
15003
  globalThis.__sddParallelRun = run;