@wrongstack/cli 0.109.1 → 0.119.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,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
2
+ import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError } from '@wrongstack/core';
3
3
  import * as fsp4 from 'fs/promises';
4
4
  import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
5
- import * as path8 from 'path';
5
+ import * as path9 from 'path';
6
6
  import { join } from 'path';
7
7
  import { createRequire } from 'module';
8
8
  import * as os2 from 'os';
@@ -18,7 +18,7 @@ import { createDefaultContainer, routeImagesForModel, readClipboardImage } from
18
18
  import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
19
19
  import { fileURLToPath } from 'url';
20
20
  import * as readline from 'readline';
21
- import * as fs13 from 'fs';
21
+ import * as fs14 from 'fs';
22
22
  import { writeFileSync, existsSync, readFileSync } from 'fs';
23
23
  import { WrongStackACPServer } from '@wrongstack/acp/agent';
24
24
  import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
@@ -386,7 +386,7 @@ async function findSpec(store, idOrTitle) {
386
386
  async function gatherProjectContext2(projectRoot) {
387
387
  const parts = [];
388
388
  try {
389
- const pkgPath = path8.join(projectRoot, "package.json");
389
+ const pkgPath = path9.join(projectRoot, "package.json");
390
390
  const pkgRaw = await fsp4.readFile(pkgPath, "utf8");
391
391
  const pkg = JSON.parse(pkgRaw);
392
392
  parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
@@ -402,13 +402,13 @@ async function gatherProjectContext2(projectRoot) {
402
402
  } catch {
403
403
  }
404
404
  try {
405
- const tsconfigPath = path8.join(projectRoot, "tsconfig.json");
405
+ const tsconfigPath = path9.join(projectRoot, "tsconfig.json");
406
406
  await fsp4.access(tsconfigPath);
407
407
  parts.push("Language: TypeScript");
408
408
  } catch {
409
409
  }
410
410
  try {
411
- const srcDir = path8.join(projectRoot, "src");
411
+ const srcDir = path9.join(projectRoot, "src");
412
412
  const entries = await fsp4.readdir(srcDir, { withFileTypes: true });
413
413
  const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
414
414
  if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
@@ -1602,7 +1602,7 @@ __export(update_check_exports, {
1602
1602
  getUpdateNotification: () => getUpdateNotification
1603
1603
  });
1604
1604
  function cachePath(homeFn = defaultHomeDir2) {
1605
- return path8.join(homeFn(), ".wrongstack", "update-cache.json");
1605
+ return path9.join(homeFn(), ".wrongstack", "update-cache.json");
1606
1606
  }
1607
1607
  function currentVersion() {
1608
1608
  const req2 = createRequire(import.meta.url);
@@ -1639,7 +1639,7 @@ async function readCache(homeFn = defaultHomeDir2) {
1639
1639
  }
1640
1640
  async function writeCache(entry, homeFn = defaultHomeDir2) {
1641
1641
  try {
1642
- const dir = path8.dirname(cachePath(homeFn));
1642
+ const dir = path9.dirname(cachePath(homeFn));
1643
1643
  await fsp4.mkdir(dir, { recursive: true });
1644
1644
  await fsp4.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
1645
1645
  } catch {
@@ -1746,7 +1746,7 @@ async function runWebUI(opts) {
1746
1746
  try {
1747
1747
  const requireFromHere = createRequire(import.meta.url);
1748
1748
  const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
1749
- const distDir = path8.resolve(path8.dirname(serverEntry), "..");
1749
+ const distDir = path9.resolve(path9.dirname(serverEntry), "..");
1750
1750
  httpServer = createHttpServer({ host, distDir, wsPort });
1751
1751
  const openUrl = `http://${host}:${httpPort}`;
1752
1752
  httpServer?.listen(httpPort, host, () => {
@@ -1763,7 +1763,7 @@ async function runWebUI(opts) {
1763
1763
  `[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`): ${err instanceof Error ? err.message : String(err)}. WS bridge still active on ws://${host}:${wsPort}.`
1764
1764
  );
1765
1765
  }
1766
- const registryBaseDir = opts.globalConfigPath ? path8.dirname(opts.globalConfigPath) : void 0;
1766
+ const registryBaseDir = opts.globalConfigPath ? path9.dirname(opts.globalConfigPath) : void 0;
1767
1767
  if (opts.projectRoot) {
1768
1768
  void registerInstance(
1769
1769
  {
@@ -1772,7 +1772,7 @@ async function runWebUI(opts) {
1772
1772
  wsPort,
1773
1773
  host,
1774
1774
  projectRoot: opts.projectRoot,
1775
- projectName: path8.basename(opts.projectRoot) || opts.projectRoot,
1775
+ projectName: path9.basename(opts.projectRoot) || opts.projectRoot,
1776
1776
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1777
1777
  url: `http://${host}:${httpPort}`
1778
1778
  },
@@ -2400,7 +2400,7 @@ async function runWebUI(opts) {
2400
2400
  }
2401
2401
  }
2402
2402
  function getVault() {
2403
- const keyFile = path8.join(path8.dirname(opts.globalConfigPath ?? ""), ".key");
2403
+ const keyFile = path9.join(path9.dirname(opts.globalConfigPath ?? ""), ".key");
2404
2404
  return new DefaultSecretVault({ keyFile });
2405
2405
  }
2406
2406
  async function loadSavedProviders() {
@@ -2701,10 +2701,10 @@ async function detectPackageManager(root, declared) {
2701
2701
  const name = declared.split("@")[0];
2702
2702
  if (name) return name;
2703
2703
  }
2704
- if (await pathExists(path8.join(root, "pnpm-lock.yaml"))) return "pnpm";
2705
- if (await pathExists(path8.join(root, "bun.lockb"))) return "bun";
2706
- if (await pathExists(path8.join(root, "bun.lock"))) return "bun";
2707
- if (await pathExists(path8.join(root, "yarn.lock"))) return "yarn";
2704
+ if (await pathExists(path9.join(root, "pnpm-lock.yaml"))) return "pnpm";
2705
+ if (await pathExists(path9.join(root, "bun.lockb"))) return "bun";
2706
+ if (await pathExists(path9.join(root, "bun.lock"))) return "bun";
2707
+ if (await pathExists(path9.join(root, "yarn.lock"))) return "yarn";
2708
2708
  return "npm";
2709
2709
  }
2710
2710
  function hasUsableScript(scripts, name) {
@@ -2725,7 +2725,7 @@ function parseMakeTargets(makefile) {
2725
2725
  async function detectProjectFacts(root) {
2726
2726
  const facts = { hints: [] };
2727
2727
  try {
2728
- const pkg = JSON.parse(await fsp4.readFile(path8.join(root, "package.json"), "utf8"));
2728
+ const pkg = JSON.parse(await fsp4.readFile(path9.join(root, "package.json"), "utf8"));
2729
2729
  const scripts = pkg.scripts ?? {};
2730
2730
  const pm = await detectPackageManager(root, pkg.packageManager);
2731
2731
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -2739,14 +2739,14 @@ async function detectProjectFacts(root) {
2739
2739
  } catch {
2740
2740
  }
2741
2741
  try {
2742
- if (!await pathExists(path8.join(root, "pyproject.toml"))) throw new Error("not python");
2742
+ if (!await pathExists(path9.join(root, "pyproject.toml"))) throw new Error("not python");
2743
2743
  facts.test ??= "pytest";
2744
2744
  facts.lint ??= "ruff check .";
2745
2745
  facts.hints.push("pyproject.toml");
2746
2746
  } catch {
2747
2747
  }
2748
2748
  try {
2749
- if (!await pathExists(path8.join(root, "go.mod"))) throw new Error("not go");
2749
+ if (!await pathExists(path9.join(root, "go.mod"))) throw new Error("not go");
2750
2750
  facts.build ??= "go build ./...";
2751
2751
  facts.test ??= "go test ./...";
2752
2752
  facts.run ??= "go run .";
@@ -2754,7 +2754,7 @@ async function detectProjectFacts(root) {
2754
2754
  } catch {
2755
2755
  }
2756
2756
  try {
2757
- if (!await pathExists(path8.join(root, "Cargo.toml"))) throw new Error("not rust");
2757
+ if (!await pathExists(path9.join(root, "Cargo.toml"))) throw new Error("not rust");
2758
2758
  facts.build ??= "cargo build";
2759
2759
  facts.test ??= "cargo test";
2760
2760
  facts.lint ??= "cargo clippy";
@@ -2763,7 +2763,7 @@ async function detectProjectFacts(root) {
2763
2763
  } catch {
2764
2764
  }
2765
2765
  try {
2766
- const makefile = await fsp4.readFile(path8.join(root, "Makefile"), "utf8");
2766
+ const makefile = await fsp4.readFile(path9.join(root, "Makefile"), "utf8");
2767
2767
  const targets = parseMakeTargets(makefile);
2768
2768
  facts.build ??= targets.has("build") ? "make build" : "make";
2769
2769
  if (targets.has("test")) facts.test ??= "make test";
@@ -3071,7 +3071,7 @@ function formatPhaseList(graph) {
3071
3071
  }
3072
3072
  async function gatherProjectContext(projectRoot) {
3073
3073
  try {
3074
- const raw = await fsp4.readFile(path8.join(projectRoot, "package.json"), "utf8");
3074
+ const raw = await fsp4.readFile(path9.join(projectRoot, "package.json"), "utf8");
3075
3075
  const pkg = JSON.parse(raw);
3076
3076
  const parts = [
3077
3077
  `Project: ${String(pkg.name ?? "unknown")}`,
@@ -3993,14 +3993,59 @@ function buildStatsCommand(opts) {
3993
3993
  }
3994
3994
  };
3995
3995
  }
3996
+ function resolvePersistPath(deps) {
3997
+ const scope = deps.configStore.get().configScope;
3998
+ if (scope === "project" && deps.inProjectConfigPath) {
3999
+ return deps.inProjectConfigPath;
4000
+ }
4001
+ return deps.globalConfigPath;
4002
+ }
4003
+ async function ensureProjectDir(filePath) {
4004
+ const dir = path9.dirname(filePath);
4005
+ try {
4006
+ await fsp4.mkdir(dir, { recursive: true });
4007
+ } catch {
4008
+ }
4009
+ }
4010
+ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
4011
+ "provider",
4012
+ "model",
4013
+ "fallbackModels",
4014
+ "modelMatrix",
4015
+ "maxConcurrent",
4016
+ "autonomy",
4017
+ "hints",
4018
+ "nextPrediction",
4019
+ "debugStream",
4020
+ "configScope",
4021
+ "yolo",
4022
+ "features",
4023
+ "context",
4024
+ "log",
4025
+ "session",
4026
+ "indexing",
4027
+ "tools",
4028
+ "launch"
4029
+ ]);
4030
+ function filterSafeForProject(cfg) {
4031
+ const out = {};
4032
+ for (const [key, value] of Object.entries(cfg)) {
4033
+ if (PROJECT_SAFE_FIELDS.has(key)) {
4034
+ out[key] = value;
4035
+ }
4036
+ }
4037
+ return out;
4038
+ }
3996
4039
  async function persistAutonomySetting(deps, mutator) {
4040
+ const targetPath = resolvePersistPath(deps);
4041
+ await ensureProjectDir(targetPath);
3997
4042
  let raw;
3998
4043
  let fileExists = true;
3999
4044
  try {
4000
- raw = await fsp4.readFile(deps.globalConfigPath, "utf8");
4045
+ raw = await fsp4.readFile(targetPath, "utf8");
4001
4046
  } catch (err) {
4002
4047
  if (err.code !== "ENOENT") {
4003
- throw new Error(`Could not read ${deps.globalConfigPath}: ${err.message}`);
4048
+ throw new Error(`Could not read ${targetPath}: ${err.message}`);
4004
4049
  }
4005
4050
  fileExists = false;
4006
4051
  raw = "{}";
@@ -4010,7 +4055,7 @@ async function persistAutonomySetting(deps, mutator) {
4010
4055
  parsed = JSON.parse(raw);
4011
4056
  } catch (err) {
4012
4057
  if (fileExists) {
4013
- throw new Error(`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`);
4058
+ throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
4014
4059
  }
4015
4060
  parsed = {};
4016
4061
  }
@@ -4018,18 +4063,26 @@ async function persistAutonomySetting(deps, mutator) {
4018
4063
  const autonomy = decrypted.autonomy ?? {};
4019
4064
  mutator(autonomy);
4020
4065
  decrypted.autonomy = autonomy;
4021
- const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
4022
- await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
4066
+ const newScope = decrypted.configScope;
4067
+ const actualTarget = newScope === "project" && deps.inProjectConfigPath ? deps.inProjectConfigPath : newScope === "global" ? deps.globalConfigPath : targetPath;
4068
+ if (actualTarget !== targetPath) {
4069
+ await ensureProjectDir(actualTarget);
4070
+ }
4071
+ const toWrite = actualTarget === deps.globalConfigPath ? decrypted : filterSafeForProject(decrypted);
4072
+ const encrypted = encryptConfigSecrets$1(toWrite, deps.vault);
4073
+ await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
4023
4074
  deps.configStore.update({ autonomy: decrypted.autonomy });
4024
4075
  }
4025
4076
  async function persistConfigSetting(deps, mutator) {
4077
+ const targetPath = resolvePersistPath(deps);
4078
+ await ensureProjectDir(targetPath);
4026
4079
  let raw;
4027
4080
  let fileExists = true;
4028
4081
  try {
4029
- raw = await fsp4.readFile(deps.globalConfigPath, "utf8");
4082
+ raw = await fsp4.readFile(targetPath, "utf8");
4030
4083
  } catch (err) {
4031
4084
  if (err.code !== "ENOENT") {
4032
- throw new Error(`Could not read ${deps.globalConfigPath}: ${err.message}`);
4085
+ throw new Error(`Could not read ${targetPath}: ${err.message}`);
4033
4086
  }
4034
4087
  fileExists = false;
4035
4088
  raw = "{}";
@@ -4039,14 +4092,20 @@ async function persistConfigSetting(deps, mutator) {
4039
4092
  parsed = JSON.parse(raw);
4040
4093
  } catch (err) {
4041
4094
  if (fileExists) {
4042
- throw new Error(`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`);
4095
+ throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
4043
4096
  }
4044
4097
  parsed = {};
4045
4098
  }
4046
4099
  const decrypted = decryptConfigSecrets$1(parsed, deps.vault);
4047
4100
  mutator(decrypted);
4048
- const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
4049
- await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
4101
+ const newScope = decrypted.configScope;
4102
+ const actualTarget = newScope === "project" && deps.inProjectConfigPath ? deps.inProjectConfigPath : newScope === "global" ? deps.globalConfigPath : targetPath;
4103
+ if (actualTarget !== targetPath) {
4104
+ await ensureProjectDir(actualTarget);
4105
+ }
4106
+ const toWrite = actualTarget === deps.globalConfigPath ? decrypted : filterSafeForProject(decrypted);
4107
+ const encrypted = encryptConfigSecrets$1(toWrite, deps.vault);
4108
+ await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 384 });
4050
4109
  deps.configStore.update(decrypted);
4051
4110
  }
4052
4111
  async function persistTelegramConfig(deps, mutator) {
@@ -5690,8 +5749,8 @@ function buildInitCommand(opts) {
5690
5749
  category: "Config",
5691
5750
  description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
5692
5751
  async run(_args, ctx) {
5693
- const dir = path8.join(ctx.projectRoot, ".wrongstack");
5694
- const file = path8.join(dir, "AGENTS.md");
5752
+ const dir = path9.join(ctx.projectRoot, ".wrongstack");
5753
+ const file = path9.join(dir, "AGENTS.md");
5695
5754
  const detected = await detectProjectFacts(ctx.projectRoot);
5696
5755
  const body = renderAgentsTemplate(detected);
5697
5756
  await fsp4.mkdir(dir, { recursive: true });
@@ -5906,9 +5965,9 @@ function stateBadge(state) {
5906
5965
  return color.dim(state);
5907
5966
  }
5908
5967
  }
5909
- async function readConfig(path26) {
5968
+ async function readConfig(path27) {
5910
5969
  try {
5911
- return JSON.parse(await fsp4.readFile(path26, "utf8"));
5970
+ return JSON.parse(await fsp4.readFile(path27, "utf8"));
5912
5971
  } catch {
5913
5972
  return {};
5914
5973
  }
@@ -5916,11 +5975,11 @@ async function readConfig(path26) {
5916
5975
  function isMcpServerRecord(value) {
5917
5976
  return !!value && typeof value === "object" && !Array.isArray(value);
5918
5977
  }
5919
- async function writeConfig(path26, cfg) {
5978
+ async function writeConfig(path27, cfg) {
5920
5979
  const raw = JSON.stringify(cfg, null, 2);
5921
- const tmp = path26 + ".tmp";
5980
+ const tmp = path27 + ".tmp";
5922
5981
  await fsp4.writeFile(tmp, raw, "utf8");
5923
- await fsp4.rename(tmp, path26);
5982
+ await fsp4.rename(tmp, path27);
5924
5983
  }
5925
5984
 
5926
5985
  // src/slash-commands/mcp.ts
@@ -7134,7 +7193,7 @@ function buildSetModelCommand(opts) {
7134
7193
  for (const phase of MATRIX_PHASE_KEYS) {
7135
7194
  const agents = AGENTS_BY_PHASE[phase];
7136
7195
  if (agents && agents.length > 0) {
7137
- picks.push(agents[0].config.role);
7196
+ picks.push(agents[0]?.config.role);
7138
7197
  }
7139
7198
  }
7140
7199
  picks.push("security-scanner", "bug-hunter");
@@ -7379,6 +7438,130 @@ function buildSetModelCommand(opts) {
7379
7438
  }
7380
7439
  };
7381
7440
  }
7441
+ function fmtTokens(n) {
7442
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
7443
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
7444
+ return String(n);
7445
+ }
7446
+ function fmtPrice(pricePer1k) {
7447
+ if (pricePer1k === void 0 || pricePer1k <= 0) return color.dim("\u2014");
7448
+ return `$${pricePer1k.toFixed(2)}/M tok`;
7449
+ }
7450
+ function contextBar(maxContext) {
7451
+ const emoji = maxContext > 2e5 ? "\u{1F7E2}" : maxContext > 128e3 ? "\u{1F7E1}" : "\u{1F534}";
7452
+ return `${emoji} ${fmtTokens(maxContext)}`;
7453
+ }
7454
+ function buildModelCapsCommand(opts) {
7455
+ return {
7456
+ name: "modelcaps",
7457
+ category: "Config",
7458
+ description: "List available models with capacities (context window, max output, pricing).",
7459
+ help: [
7460
+ "Usage:",
7461
+ " /modelcaps List all available models grouped by provider",
7462
+ " /modelcaps <provider> Show models for one provider only",
7463
+ " /modelcaps <fragment> Filter models by id fragment (case-insensitive)",
7464
+ " /modelcaps summary Show agent-type \u2192 model mapping matrix",
7465
+ "",
7466
+ "Capacities shown: context window, max output tokens, input/output pricing.",
7467
+ "\u25CF = API key present \xB7 \u25CB = no key (model listed but not usable)."
7468
+ ].join("\n"),
7469
+ async run(args) {
7470
+ const trimmed = args.trim().toLowerCase();
7471
+ if (trimmed === "summary") {
7472
+ return {
7473
+ message: [
7474
+ `${color.bold("Agent-Type \u2192 Model Mapping")} ${color.dim("\u2014 use /setmodel")}`,
7475
+ "",
7476
+ `${color.dim("Run /setmodel to see the current model matrix and resolution chain.")}`,
7477
+ `${color.dim("Each agent role resolves its model via: role \u2192 phase \u2192 * \u2192 leader.")}`,
7478
+ "",
7479
+ `${color.dim("/setmodel \u2014 show leader + matrix + resolution summary")}`,
7480
+ `${color.dim("/setmodel resolve <role> \u2014 walk the resolution chain for one role")}`
7481
+ ].join("\n")
7482
+ };
7483
+ }
7484
+ const cachePath2 = opts.paths?.modelsCache;
7485
+ if (!cachePath2) {
7486
+ return { message: `${color.red("Models cache path not available")}.` };
7487
+ }
7488
+ let providers;
7489
+ try {
7490
+ const raw = await fsp4.readFile(cachePath2, "utf8");
7491
+ const parsed = JSON.parse(raw);
7492
+ const payload = parsed.payload ?? parsed;
7493
+ providers = Object.entries(payload).map(([id, p]) => ({
7494
+ id: p.id ?? id,
7495
+ name: p.name ?? id,
7496
+ family: p.npm ?? id,
7497
+ models: Object.values(p.models ?? {}).map((m) => ({
7498
+ id: m.id,
7499
+ name: m.name,
7500
+ capabilities: {
7501
+ contextWindow: m.limit?.context,
7502
+ maxOutputTokens: m.limit?.output
7503
+ },
7504
+ pricing: m.cost
7505
+ }))
7506
+ }));
7507
+ } catch {
7508
+ return {
7509
+ message: [
7510
+ `${color.amber("Models cache not available")}.`,
7511
+ `${color.dim(`Expected at: ${cachePath2}`)}`,
7512
+ "",
7513
+ `${color.dim("Run wstack sync-models or wait for the next auto-sync.")}`
7514
+ ].join("\n")
7515
+ };
7516
+ }
7517
+ const config = opts.configStore.get();
7518
+ const configProviders = config?.providers ?? {};
7519
+ function hasKey(providerId) {
7520
+ const pc = configProviders[providerId];
7521
+ if (!pc) return false;
7522
+ if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
7523
+ if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
7524
+ return false;
7525
+ }
7526
+ const lines = [
7527
+ `${color.bold("Available Models")} ${color.dim("\u2014 capacities + pricing")}`,
7528
+ ""
7529
+ ];
7530
+ let shown = 0;
7531
+ for (const prov of providers) {
7532
+ if (trimmed && !trimmed.includes("/") && !prov.id.toLowerCase().includes(trimmed) && !prov.name.toLowerCase().includes(trimmed)) {
7533
+ continue;
7534
+ }
7535
+ const keyed = hasKey(prov.id);
7536
+ const marker = keyed ? color.green("\u25CF") : color.dim("\u25CB");
7537
+ lines.push(` ${marker} ${color.bold(prov.id.padEnd(16))} ${color.dim(`(${prov.name})`)}`);
7538
+ const models = prov.models ?? [];
7539
+ if (models.length === 0) {
7540
+ lines.push(` ${color.dim("no models listed \u2014 any model id accepted")}`);
7541
+ }
7542
+ for (const m of models) {
7543
+ if (trimmed?.includes("/")) {
7544
+ const frag = trimmed.split("/").pop() ?? "";
7545
+ if (frag && !m.id.toLowerCase().includes(frag)) continue;
7546
+ }
7547
+ const cap = m.capabilities;
7548
+ const ctx = cap?.contextWindow ?? 0;
7549
+ const maxOut = cap?.maxOutputTokens ?? 0;
7550
+ lines.push(
7551
+ ` ${color.cyan(m.id)} ${contextBar(ctx)}` + (maxOut > 0 ? ` ${color.dim("out")} ${fmtTokens(maxOut)}` : "") + ` ${color.dim("in")} ${fmtPrice(m.pricing?.input)} ${color.dim("out")} ${fmtPrice(m.pricing?.output)}`
7552
+ );
7553
+ shown++;
7554
+ }
7555
+ lines.push("");
7556
+ }
7557
+ if (shown === 0) {
7558
+ lines.push(` ${color.dim("No models matched. Try /modelcaps without a filter.")}`);
7559
+ }
7560
+ lines.push(color.dim(`${shown} model(s). \u25CF = key present \xB7 \u25CB = no key. Use /modelcaps summary for agent-type mapping.`));
7561
+ return { message: lines.join("\n") };
7562
+ }
7563
+ };
7564
+ }
7382
7565
  var noOpVault4 = {
7383
7566
  encrypt: (v) => v,
7384
7567
  decrypt: (v) => v,
@@ -7396,6 +7579,8 @@ function buildSettingsCommand(opts) {
7396
7579
  " /settings delay <seconds> Auto-proceed delay in auto mode (0 disables)",
7397
7580
  " /settings mode <off|suggest|auto> Default autonomy mode at startup",
7398
7581
  " /settings hints on|off Show or suppress rotating launch hints",
7582
+ " /settings debug-stream on|off Raw SSE hex-dump to stderr for debugging",
7583
+ " /settings config-scope global|project Save settings globally or per-project",
7399
7584
  " /settings defaults Show built-in default values",
7400
7585
  "",
7401
7586
  "Settings are persisted to ~/.wrongstack/config.json."
@@ -7405,12 +7590,16 @@ function buildSettingsCommand(opts) {
7405
7590
  const delay = autonomy?.autoProceedDelayMs ?? 45e3;
7406
7591
  const mode = autonomy?.defaultMode ?? "off";
7407
7592
  const hints = opts.configStore.get().hints !== false;
7593
+ const debugStream = opts.configStore.get().debugStream === true;
7594
+ const configScope = opts.configStore.get().configScope ?? "global";
7408
7595
  return [
7409
7596
  `${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
7410
7597
  "",
7411
7598
  ` auto-proceed delay: ${color.cyan(formatDelay(delay))} ${color.dim("change: /settings delay <seconds>")}`,
7412
7599
  ` default autonomy mode: ${color.cyan(mode)} ${color.dim("change: /settings mode off|suggest|auto")}`,
7413
7600
  ` launch hints: ${hints ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings hints on|off")}`,
7601
+ ` debug stream: ${debugStream ? color.cyan("on") : color.dim("off")} ${color.dim("change: /settings debug-stream on|off")}`,
7602
+ ` config scope: ${color.cyan(configScope)} ${color.dim("change: /settings config-scope global|project")}`,
7414
7603
  "",
7415
7604
  color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
7416
7605
  ].join("\n");
@@ -7449,6 +7638,7 @@ function buildSettingsCommand(opts) {
7449
7638
  const persistDeps = {
7450
7639
  configStore: opts.configStore,
7451
7640
  globalConfigPath: opts.paths.globalConfig,
7641
+ inProjectConfigPath: opts.paths.inProjectConfig,
7452
7642
  vault: noOpVault4
7453
7643
  };
7454
7644
  try {
@@ -7493,8 +7683,32 @@ function buildSettingsCommand(opts) {
7493
7683
  });
7494
7684
  return { message: `${color.green("\u2713")} launch hints \u2192 ${on ? color.cyan("on") : color.dim("off")}` };
7495
7685
  }
7686
+ if (sub === "debug-stream") {
7687
+ const raw = (parts[1] ?? "").toLowerCase();
7688
+ if (!["on", "off"].includes(raw)) {
7689
+ return { message: `${color.amber("Usage:")} /settings debug-stream on|off` };
7690
+ }
7691
+ const on = raw === "on";
7692
+ const { setDebugStreamEnabled } = await import('@wrongstack/providers');
7693
+ setDebugStreamEnabled(on);
7694
+ await persistConfigSetting(persistDeps, (cfg) => {
7695
+ cfg.debugStream = on;
7696
+ });
7697
+ return { message: `${color.green("\u2713")} debug stream \u2192 ${on ? color.cyan("on") : color.dim("off")} ${color.dim("raw SSE hex-dump to stderr")}` };
7698
+ }
7699
+ if (sub === "config-scope") {
7700
+ const raw = (parts[1] ?? "").toLowerCase();
7701
+ if (!["global", "project"].includes(raw)) {
7702
+ return { message: `${color.amber("Usage:")} /settings config-scope global|project` };
7703
+ }
7704
+ await persistConfigSetting(persistDeps, (cfg) => {
7705
+ cfg.configScope = raw;
7706
+ });
7707
+ const label = raw === "project" ? `${color.cyan("project")} \u2014 settings saved to <project>/.wrongstack/config.json` : `${color.cyan("global")} \u2014 settings saved to ~/.wrongstack/config.json`;
7708
+ return { message: `${color.green("\u2713")} config scope \u2192 ${label}` };
7709
+ }
7496
7710
  return {
7497
- message: `${color.red("Unknown setting")} "${sub}". Try ${color.dim("/settings")}, ${color.dim("/settings delay <s>")}, ${color.dim("/settings mode <m>")}, or ${color.dim("/settings hints on|off")}.`
7711
+ message: `${color.red("Unknown setting")} "${sub}". Try ${color.dim("/settings")}, ${color.dim("/settings delay <s>")}, ${color.dim("/settings mode <m>")}, ${color.dim("/settings hints on|off")}, ${color.dim("/settings debug-stream on|off")}, or ${color.dim("/settings config-scope global|project")}.`
7498
7712
  };
7499
7713
  } catch (err) {
7500
7714
  return {
@@ -7745,7 +7959,7 @@ var DEFAULTS = {
7745
7959
  cost: true
7746
7960
  };
7747
7961
  function resolveConfigPath() {
7748
- return process.env[CONFIG_ENV] ?? path8.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
7962
+ return process.env[CONFIG_ENV] ?? path9.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
7749
7963
  }
7750
7964
  async function loadStatuslineConfig() {
7751
7965
  const p = resolveConfigPath();
@@ -7759,7 +7973,7 @@ async function loadStatuslineConfig() {
7759
7973
  async function saveStatuslineConfig(cfg) {
7760
7974
  const p = resolveConfigPath();
7761
7975
  try {
7762
- await fsp4.mkdir(path8.dirname(p), { recursive: true });
7976
+ await fsp4.mkdir(path9.dirname(p), { recursive: true });
7763
7977
  await atomicWrite(p, JSON.stringify(cfg, null, 2));
7764
7978
  } catch (err) {
7765
7979
  throw new FsError({
@@ -8276,6 +8490,7 @@ function buildBuiltinSlashCommands(opts) {
8276
8490
  buildSettingsCommand(opts),
8277
8491
  buildTelegramSetupCommand(opts),
8278
8492
  buildSetModelCommand(opts),
8493
+ buildModelCapsCommand(opts),
8279
8494
  buildModelsCommand(opts),
8280
8495
  buildCollabCommand(opts),
8281
8496
  buildStatuslineCommand({
@@ -8305,13 +8520,13 @@ var MANIFESTS = [
8305
8520
  ];
8306
8521
  async function detectProjectKind(projectRoot) {
8307
8522
  try {
8308
- await fsp4.access(path8.join(projectRoot, ".wrongstack", "AGENTS.md"));
8523
+ await fsp4.access(path9.join(projectRoot, ".wrongstack", "AGENTS.md"));
8309
8524
  return "initialized";
8310
8525
  } catch {
8311
8526
  }
8312
8527
  for (const m of MANIFESTS) {
8313
8528
  try {
8314
- await fsp4.access(path8.join(projectRoot, m));
8529
+ await fsp4.access(path9.join(projectRoot, m));
8315
8530
  return "project";
8316
8531
  } catch {
8317
8532
  }
@@ -8319,8 +8534,8 @@ async function detectProjectKind(projectRoot) {
8319
8534
  return "empty";
8320
8535
  }
8321
8536
  async function scaffoldAgentsMd(projectRoot) {
8322
- const dir = path8.join(projectRoot, ".wrongstack");
8323
- const file = path8.join(dir, "AGENTS.md");
8537
+ const dir = path9.join(projectRoot, ".wrongstack");
8538
+ const file = path9.join(dir, "AGENTS.md");
8324
8539
  const facts = await detectProjectFacts(projectRoot);
8325
8540
  const body = renderAgentsTemplate(facts);
8326
8541
  await fsp4.mkdir(dir, { recursive: true });
@@ -8333,7 +8548,7 @@ async function runProjectCheck(opts) {
8333
8548
  if (kind === "initialized") {
8334
8549
  renderer.write(
8335
8550
  `
8336
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path8.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
8551
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path9.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
8337
8552
  `
8338
8553
  );
8339
8554
  return true;
@@ -8364,7 +8579,7 @@ async function runProjectCheck(opts) {
8364
8579
  }
8365
8580
  return true;
8366
8581
  }
8367
- const gitDir = path8.join(projectRoot, ".git");
8582
+ const gitDir = path9.join(projectRoot, ".git");
8368
8583
  let hasGit = false;
8369
8584
  try {
8370
8585
  await fsp4.access(gitDir);
@@ -8558,7 +8773,7 @@ var ReadlineInputReader = class {
8558
8773
  history = [];
8559
8774
  pending = false;
8560
8775
  constructor(opts = {}) {
8561
- this.historyFile = opts.historyFile ?? path8.join(os2.homedir(), ".wrongstack", "history");
8776
+ this.historyFile = opts.historyFile ?? path9.join(os2.homedir(), ".wrongstack", "history");
8562
8777
  }
8563
8778
  async loadHistory() {
8564
8779
  try {
@@ -8570,7 +8785,7 @@ var ReadlineInputReader = class {
8570
8785
  }
8571
8786
  async saveHistory() {
8572
8787
  try {
8573
- await fsp4.mkdir(path8.dirname(this.historyFile), { recursive: true });
8788
+ await fsp4.mkdir(path9.dirname(this.historyFile), { recursive: true });
8574
8789
  await fsp4.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
8575
8790
  } catch {
8576
8791
  }
@@ -8859,20 +9074,20 @@ function assertSafeToDelete(filename, parentDir) {
8859
9074
  if (PROTECTED_BASENAMES.has(filename)) {
8860
9075
  throw new Error(`Refusing to delete protected file: ${filename}`);
8861
9076
  }
8862
- if (filename !== path8.basename(filename)) {
9077
+ if (filename !== path9.basename(filename)) {
8863
9078
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
8864
9079
  }
8865
9080
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
8866
9081
  throw new Error(`Refusing to delete unknown file: ${filename}`);
8867
9082
  }
8868
- const resolvedParent = path8.resolve(parentDir);
9083
+ const resolvedParent = path9.resolve(parentDir);
8869
9084
  if (!resolvedParent.endsWith(".wrongstack")) {
8870
9085
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
8871
9086
  }
8872
9087
  }
8873
9088
  async function safeDelete(filePath) {
8874
- const dir = path8.dirname(filePath);
8875
- const filename = path8.basename(filePath);
9089
+ const dir = path9.dirname(filePath);
9090
+ const filename = path9.basename(filePath);
8876
9091
  try {
8877
9092
  assertSafeToDelete(filename, dir);
8878
9093
  await fsp4.unlink(filePath);
@@ -8917,16 +9132,16 @@ function diffSummary(oldCfg, newCfg) {
8917
9132
  }
8918
9133
  var defaultHomeDir = () => os2__default.homedir();
8919
9134
  function historyDir(homeFn = defaultHomeDir) {
8920
- return path8.join(homeFn(), ".wrongstack", "config.history", "entries");
9135
+ return path9.join(homeFn(), ".wrongstack", "config.history", "entries");
8921
9136
  }
8922
9137
  function historyIndexPath(homeFn = defaultHomeDir) {
8923
- return path8.join(homeFn(), ".wrongstack", "config.history", "index.json");
9138
+ return path9.join(homeFn(), ".wrongstack", "config.history", "index.json");
8924
9139
  }
8925
9140
  function configPath(homeFn = defaultHomeDir) {
8926
- return path8.join(homeFn(), ".wrongstack", "config.json");
9141
+ return path9.join(homeFn(), ".wrongstack", "config.json");
8927
9142
  }
8928
9143
  function backupLastPath(homeFn = defaultHomeDir) {
8929
- return path8.join(homeFn(), ".wrongstack", "config.json.last");
9144
+ return path9.join(homeFn(), ".wrongstack", "config.json.last");
8930
9145
  }
8931
9146
  function entryId(ts) {
8932
9147
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -8981,17 +9196,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
8981
9196
  }
8982
9197
  if (content !== void 0) {
8983
9198
  try {
8984
- const bakPath = path8.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
9199
+ const bakPath = path9.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
8985
9200
  await atomicWrite(bakPath, content);
8986
9201
  } catch {
8987
9202
  }
8988
9203
  }
8989
9204
  try {
8990
- const dir = path8.join(homeFn(), ".wrongstack");
9205
+ const dir = path9.join(homeFn(), ".wrongstack");
8991
9206
  const files = await fsp4.readdir(dir);
8992
9207
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
8993
9208
  for (const f of baks.slice(10)) {
8994
- await safeDelete(path8.join(dir, f));
9209
+ await safeDelete(path9.join(dir, f));
8995
9210
  }
8996
9211
  } catch {
8997
9212
  }
@@ -9009,7 +9224,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
9009
9224
  };
9010
9225
  try {
9011
9226
  await fsp4.writeFile(
9012
- path8.join(historyDir(homeFn), `${id}.json`),
9227
+ path9.join(historyDir(homeFn), `${id}.json`),
9013
9228
  JSON.stringify(entry, null, 2),
9014
9229
  "utf8"
9015
9230
  );
@@ -9017,7 +9232,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
9017
9232
  throw new FsError({
9018
9233
  message: err instanceof Error ? err.message : String(err),
9019
9234
  code: ERROR_CODES.FS_WRITE_FAILED,
9020
- path: path8.join(historyDir(homeFn), `${id}.json`),
9235
+ path: path9.join(historyDir(homeFn), `${id}.json`),
9021
9236
  cause: err
9022
9237
  });
9023
9238
  }
@@ -9032,7 +9247,7 @@ async function listHistory(homeFn = defaultHomeDir) {
9032
9247
  }
9033
9248
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
9034
9249
  try {
9035
- const raw = await fsp4.readFile(path8.join(historyDir(homeFn), `${id}.json`), "utf8");
9250
+ const raw = await fsp4.readFile(path9.join(historyDir(homeFn), `${id}.json`), "utf8");
9036
9251
  return JSON.parse(raw);
9037
9252
  } catch {
9038
9253
  return null;
@@ -9098,10 +9313,10 @@ var theme = { primary: color.amber };
9098
9313
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
9099
9314
  try {
9100
9315
  const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
9101
- const fs27 = await import('fs/promises');
9316
+ const fs29 = await import('fs/promises');
9102
9317
  let existing = {};
9103
9318
  try {
9104
- const raw = await fs27.readFile(configPath2, "utf8");
9319
+ const raw = await fs29.readFile(configPath2, "utf8");
9105
9320
  existing = JSON.parse(raw);
9106
9321
  } catch {
9107
9322
  }
@@ -9437,12 +9652,12 @@ function pickGroupIndex(opts) {
9437
9652
  try {
9438
9653
  let current = 0;
9439
9654
  try {
9440
- const parsed = Number.parseInt(fs13.readFileSync(opts.cursorFile, "utf8").trim(), 10);
9655
+ const parsed = Number.parseInt(fs14.readFileSync(opts.cursorFile, "utf8").trim(), 10);
9441
9656
  if (Number.isFinite(parsed)) current = wrap(parsed);
9442
9657
  } catch {
9443
9658
  }
9444
- fs13.mkdirSync(path8.dirname(opts.cursorFile), { recursive: true });
9445
- fs13.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
9659
+ fs14.mkdirSync(path9.dirname(opts.cursorFile), { recursive: true });
9660
+ fs14.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
9446
9661
  return current;
9447
9662
  } catch {
9448
9663
  }
@@ -9778,14 +9993,14 @@ function summarize(value, name) {
9778
9993
  if (typeof v === "object" && v !== null) {
9779
9994
  const o = v;
9780
9995
  if (name === "edit") {
9781
- const path26 = typeof o["path"] === "string" ? o["path"] : "";
9996
+ const path27 = typeof o["path"] === "string" ? o["path"] : "";
9782
9997
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
9783
- return `${path26} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
9998
+ return `${path27} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
9784
9999
  }
9785
10000
  if (name === "write") {
9786
- const path26 = typeof o["path"] === "string" ? o["path"] : "";
10001
+ const path27 = typeof o["path"] === "string" ? o["path"] : "";
9787
10002
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
9788
- return bytes !== void 0 ? `${path26} ${bytes}B` : path26;
10003
+ return bytes !== void 0 ? `${path27} ${bytes}B` : path27;
9789
10004
  }
9790
10005
  if (typeof o["count"] === "number") {
9791
10006
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -10009,7 +10224,8 @@ ${color.bold(providerId)} ${cfg.family ? color.dim(`[${cfg.family}]`) : color.am
10009
10224
  renderer.write(color.dim(" (no keys saved)\n"));
10010
10225
  } else {
10011
10226
  for (let i = 0; i < keys.length; i++) {
10012
- renderKeyLine(renderer, keys[i], i + 1, active);
10227
+ const key = keys[i];
10228
+ if (key) renderKeyLine(renderer, key, i + 1, active);
10013
10229
  }
10014
10230
  }
10015
10231
  }
@@ -10101,7 +10317,7 @@ async function confirm(deps, question) {
10101
10317
  return answer === "y" || answer === "yes";
10102
10318
  }
10103
10319
  function suggestLabel(usedLabels) {
10104
- let candidate = "default";
10320
+ const candidate = "default";
10105
10321
  if (!usedLabels.has(candidate)) return candidate;
10106
10322
  let n = 2;
10107
10323
  while (usedLabels.has(`key${n}`)) n++;
@@ -10420,7 +10636,7 @@ ${color.amber("?")} ${providerId} > `
10420
10636
  if (!raw || raw === "b" || raw === "back" || raw === "q" || raw === "quit") {
10421
10637
  return;
10422
10638
  }
10423
- const [verb, argRaw] = raw.split(/\s+/, 2);
10639
+ const [verb = "", argRaw = ""] = raw.split(/\s+/, 2);
10424
10640
  const arg = argRaw ? Number.parseInt(argRaw, 10) : Number.NaN;
10425
10641
  const handled = await dispatchAction(verb, arg, providerId, keys, cfg, deps);
10426
10642
  if (handled === "exit") return;
@@ -11002,7 +11218,7 @@ var doctorCmd = async (_args, deps) => {
11002
11218
  }
11003
11219
  try {
11004
11220
  await fsp4.mkdir(deps.paths.projectSessions, { recursive: true });
11005
- const probe = path8.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
11221
+ const probe = path9.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
11006
11222
  await fsp4.writeFile(probe, "");
11007
11223
  await fsp4.unlink(probe);
11008
11224
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -11105,8 +11321,8 @@ var exportCmd = async (args, deps) => {
11105
11321
  return 1;
11106
11322
  }
11107
11323
  if (output) {
11108
- await fsp4.mkdir(path8.dirname(path8.resolve(deps.cwd, output)), { recursive: true });
11109
- await fsp4.writeFile(path8.resolve(deps.cwd, output), rendered, "utf8");
11324
+ await fsp4.mkdir(path9.dirname(path9.resolve(deps.cwd, output)), { recursive: true });
11325
+ await fsp4.writeFile(path9.resolve(deps.cwd, output), rendered, "utf8");
11110
11326
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
11111
11327
  `);
11112
11328
  } else {
@@ -11179,8 +11395,8 @@ var initCmd = async (_args, deps) => {
11179
11395
  const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
11180
11396
  const encrypted = encryptConfigSecrets(config, vault);
11181
11397
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
11182
- await fsp4.mkdir(path8.join(deps.projectRoot, ".wrongstack"), { recursive: true });
11183
- const agentsFile = path8.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
11398
+ await fsp4.mkdir(path9.join(deps.projectRoot, ".wrongstack"), { recursive: true });
11399
+ const agentsFile = path9.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
11184
11400
  const projectFacts = await detectProjectFacts(deps.projectRoot);
11185
11401
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
11186
11402
  deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
@@ -11637,7 +11853,7 @@ var usageCmd = async (_args, deps) => {
11637
11853
  return 0;
11638
11854
  };
11639
11855
  var projectsCmd = async (_args, deps) => {
11640
- const projectsRoot = path8.join(deps.paths.globalRoot, "projects");
11856
+ const projectsRoot = path9.join(deps.paths.globalRoot, "projects");
11641
11857
  try {
11642
11858
  const entries = await fsp4.readdir(projectsRoot);
11643
11859
  if (entries.length === 0) {
@@ -11647,7 +11863,7 @@ var projectsCmd = async (_args, deps) => {
11647
11863
  for (const hash of entries) {
11648
11864
  try {
11649
11865
  const meta = JSON.parse(
11650
- await fsp4.readFile(path8.join(projectsRoot, hash, "meta.json"), "utf8")
11866
+ await fsp4.readFile(path9.join(projectsRoot, hash, "meta.json"), "utf8")
11651
11867
  );
11652
11868
  deps.renderer.write(
11653
11869
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -12026,7 +12242,7 @@ async function listFleetRuns(deps) {
12026
12242
  }
12027
12243
  const runs = [];
12028
12244
  for (const id of entries) {
12029
- const runDir = path8.join(deps.paths.projectSessions, id);
12245
+ const runDir = path9.join(deps.paths.projectSessions, id);
12030
12246
  let stat4;
12031
12247
  try {
12032
12248
  stat4 = await fsp4.stat(runDir);
@@ -12039,17 +12255,17 @@ async function listFleetRuns(deps) {
12039
12255
  let subagentCount = 0;
12040
12256
  let subagentsDir;
12041
12257
  try {
12042
- await fsp4.access(path8.join(runDir, "fleet.json"));
12258
+ await fsp4.access(path9.join(runDir, "fleet.json"));
12043
12259
  manifest = true;
12044
12260
  } catch {
12045
12261
  }
12046
12262
  try {
12047
- await fsp4.access(path8.join(runDir, "checkpoint.json"));
12263
+ await fsp4.access(path9.join(runDir, "checkpoint.json"));
12048
12264
  checkpoint = true;
12049
12265
  } catch {
12050
12266
  }
12051
12267
  try {
12052
- subagentsDir = path8.join(runDir, "subagents");
12268
+ subagentsDir = path9.join(runDir, "subagents");
12053
12269
  const files = await fsp4.readdir(subagentsDir);
12054
12270
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
12055
12271
  } catch {
@@ -12078,7 +12294,7 @@ async function listFleetRuns(deps) {
12078
12294
  return 0;
12079
12295
  }
12080
12296
  async function showFleetRun(runId, deps) {
12081
- const runDir = path8.join(deps.paths.projectSessions, runId);
12297
+ const runDir = path9.join(deps.paths.projectSessions, runId);
12082
12298
  let stat4;
12083
12299
  try {
12084
12300
  stat4 = await fsp4.stat(runDir);
@@ -12095,7 +12311,7 @@ async function showFleetRun(runId, deps) {
12095
12311
  deps.renderer.write(color.bold(`
12096
12312
  Fleet Run: ${runId}
12097
12313
  `) + "\n");
12098
- const manifestPath = path8.join(runDir, "fleet.json");
12314
+ const manifestPath = path9.join(runDir, "fleet.json");
12099
12315
  let manifestData = null;
12100
12316
  try {
12101
12317
  manifestData = await fsp4.readFile(manifestPath, "utf8");
@@ -12111,7 +12327,7 @@ Fleet Run: ${runId}
12111
12327
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
12112
12328
  `);
12113
12329
  }
12114
- const checkpointPath = path8.join(runDir, "checkpoint.json");
12330
+ const checkpointPath = path9.join(runDir, "checkpoint.json");
12115
12331
  let checkpointData = null;
12116
12332
  try {
12117
12333
  checkpointData = await fsp4.readFile(checkpointPath, "utf8");
@@ -12158,7 +12374,7 @@ Fleet Run: ${runId}
12158
12374
  } catch {
12159
12375
  }
12160
12376
  }
12161
- const subagentsDir = path8.join(runDir, "subagents");
12377
+ const subagentsDir = path9.join(runDir, "subagents");
12162
12378
  let subagentFiles = [];
12163
12379
  try {
12164
12380
  subagentFiles = await fsp4.readdir(subagentsDir);
@@ -12170,7 +12386,7 @@ Fleet Run: ${runId}
12170
12386
  Subagent transcripts (${subagentFiles.length}):
12171
12387
  `);
12172
12388
  for (const f of subagentFiles.sort()) {
12173
- const filePath = path8.join(subagentsDir, f);
12389
+ const filePath = path9.join(subagentsDir, f);
12174
12390
  let size;
12175
12391
  try {
12176
12392
  const s = await fsp4.stat(filePath);
@@ -12187,7 +12403,7 @@ Fleet Run: ${runId}
12187
12403
  ${color.dim("\u25CB")} No subagent transcripts
12188
12404
  `);
12189
12405
  }
12190
- const sharedDir = path8.join(runDir, "shared");
12406
+ const sharedDir = path9.join(runDir, "shared");
12191
12407
  try {
12192
12408
  const files = await fsp4.readdir(sharedDir);
12193
12409
  deps.renderer.write(`
@@ -12354,7 +12570,7 @@ function findSessionId(args) {
12354
12570
  var rewindCmd = async (args, deps) => {
12355
12571
  const flags = parseRewindFlags(args);
12356
12572
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
12357
- const sessionsDir = path8.join(wpaths.globalRoot, "sessions");
12573
+ const sessionsDir = path9.join(wpaths.globalRoot, "sessions");
12358
12574
  const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
12359
12575
  let sessionId = findSessionId(args);
12360
12576
  if (!sessionId) {
@@ -12594,10 +12810,10 @@ var auditCmd = async (args, deps) => {
12594
12810
  return verify.ok ? 0 : 1;
12595
12811
  };
12596
12812
  async function listAudits(log, dir, deps) {
12597
- const fs27 = await import('fs/promises');
12813
+ const fs29 = await import('fs/promises');
12598
12814
  let entries;
12599
12815
  try {
12600
- entries = await fs27.readdir(dir);
12816
+ entries = await fs29.readdir(dir);
12601
12817
  } catch {
12602
12818
  deps.renderer.write(
12603
12819
  color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
@@ -12650,6 +12866,743 @@ var skillsCmd = async (_args, deps) => {
12650
12866
  );
12651
12867
  return 0;
12652
12868
  };
12869
+ var MODEL_PROFILES = [
12870
+ { provider: "anthropic", pattern: /claude-opus/i, family: "Claude Opus", strengths: ["reasoning", "planning"], bestFor: ["planning", "security", "debugging"], costTier: "premium", speedTier: "slow" },
12871
+ { provider: "anthropic", pattern: /claude-sonnet/i, family: "Claude Sonnet", strengths: ["coding", "balanced"], bestFor: ["coding", "general"], costTier: "standard", speedTier: "fast" },
12872
+ { provider: "anthropic", pattern: /claude-haiku/i, family: "Claude Haiku", strengths: ["speed"], bestFor: ["lightweight", "docs"], avoidFor: ["planning"], costTier: "budget", speedTier: "fast" },
12873
+ { provider: "openai", pattern: /gpt-5|o3|o4/i, family: "GPT-5/o3/o4", strengths: ["reasoning", "coding"], bestFor: ["planning", "coding", "debugging"], costTier: "premium", speedTier: "normal" },
12874
+ { provider: "openai", pattern: /gpt-4/i, family: "GPT-4", strengths: ["coding"], bestFor: ["coding", "docs"], costTier: "standard", speedTier: "fast" },
12875
+ { provider: "openai", pattern: /gpt-4o-mini/i, family: "GPT-4o Mini", strengths: ["speed"], bestFor: ["lightweight", "docs"], avoidFor: ["planning"], costTier: "budget", speedTier: "fast" },
12876
+ { provider: "google", pattern: /gemini-(?:2\.5|3)/i, family: "Gemini 2.5/3", strengths: ["context", "coding"], bestFor: ["coding", "data"], costTier: "standard", speedTier: "normal" },
12877
+ { provider: "google", pattern: /gemini.*flash/i, family: "Gemini Flash", strengths: ["speed"], bestFor: ["lightweight", "docs"], avoidFor: ["planning"], costTier: "budget", speedTier: "fast" },
12878
+ { provider: "deepseek", pattern: /deepseek/i, family: "DeepSeek", strengths: ["coding", "cost-effective"], bestFor: ["coding", "general"], costTier: "standard", speedTier: "normal" }
12879
+ ];
12880
+ function fmtTokens2(n) {
12881
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
12882
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
12883
+ return String(n);
12884
+ }
12885
+ function fmtMs(ms) {
12886
+ if (ms >= 1e3) return `${(ms / 1e3).toFixed(1)}s`;
12887
+ return `${ms}ms`;
12888
+ }
12889
+ function fmtPrice2(usdPer1M) {
12890
+ if (usdPer1M === void 0) return color.dim("?");
12891
+ if (usdPer1M >= 10) return `$${usdPer1M.toFixed(1)}`;
12892
+ return `$${usdPer1M.toFixed(2)}`;
12893
+ }
12894
+ function checkMark(ok) {
12895
+ return ok ? color.green("\u2713") : color.red("\u2717");
12896
+ }
12897
+ function costLabel(tier) {
12898
+ switch (tier) {
12899
+ case "premium":
12900
+ return color.red("$$$");
12901
+ case "standard":
12902
+ return color.amber("$$");
12903
+ case "budget":
12904
+ return color.green("$");
12905
+ default:
12906
+ return color.dim("?");
12907
+ }
12908
+ }
12909
+ function speedLabel(tier) {
12910
+ switch (tier) {
12911
+ case "fast":
12912
+ return color.green("\u26A1");
12913
+ case "normal":
12914
+ return color.amber("\u2192");
12915
+ case "slow":
12916
+ return color.red("\u{1F422}");
12917
+ default:
12918
+ return color.dim("?");
12919
+ }
12920
+ }
12921
+ function scoreBar(score, max) {
12922
+ const pct2 = Math.min(1, Math.max(0, score / max));
12923
+ const filled = Math.round(pct2 * 10);
12924
+ const bar = color.green("\u2588".repeat(filled)) + color.dim("\u2591".repeat(10 - filled));
12925
+ return `${bar} ${score}/${max}`;
12926
+ }
12927
+ var ROLE_CATEGORY = {
12928
+ "security-scanner": "security",
12929
+ "security-reviewer": "security",
12930
+ "bug-hunter": "debugging",
12931
+ debugger: "debugging",
12932
+ tracer: "debugging",
12933
+ planner: "planning",
12934
+ architect: "planning",
12935
+ "refactor-planner": "planning",
12936
+ test: "testing",
12937
+ e2e: "testing",
12938
+ document: "docs",
12939
+ simplifier: "docs",
12940
+ "code-reviewer": "review",
12941
+ critic: "review",
12942
+ executor: "coding",
12943
+ refactor: "refactoring",
12944
+ migration: "coding",
12945
+ frontend: "frontend",
12946
+ backend: "backend",
12947
+ api: "backend",
12948
+ auth: "backend",
12949
+ designer: "frontend",
12950
+ analyst: "data",
12951
+ data: "data",
12952
+ database: "data",
12953
+ explore: "planning",
12954
+ search: "planning",
12955
+ researcher: "planning"
12956
+ };
12957
+ function findProfile(pid, mid) {
12958
+ for (const p of MODEL_PROFILES) {
12959
+ if (p.provider === pid && p.pattern.test(mid)) return p;
12960
+ }
12961
+ return void 0;
12962
+ }
12963
+ function scoreModel(pid, mid, category, ctxWindow) {
12964
+ const profile = findProfile(pid, mid);
12965
+ let score = 50;
12966
+ if (profile) {
12967
+ if (profile.bestFor.includes(category)) score += 35;
12968
+ if (profile.avoidFor?.includes(category)) score -= 50;
12969
+ if (category === "planning" && profile.costTier === "premium") score += 15;
12970
+ if (profile.speedTier === "slow" && category === "planning") score += 10;
12971
+ if (profile.costTier === "budget" && category !== "planning" && category !== "security") score += 10;
12972
+ }
12973
+ if (ctxWindow > 2e5) score += 10;
12974
+ else if (ctxWindow > 1e5) score += 5;
12975
+ else if (ctxWindow > 32e3) score += 2;
12976
+ return { score, profile };
12977
+ }
12978
+ function rankModels(providers, hasKey, category, limit) {
12979
+ const candidates = [];
12980
+ for (const prov of providers) {
12981
+ if (!hasKey(prov.id)) continue;
12982
+ for (const m of prov.models ?? []) {
12983
+ const ctxWindow = m.capabilities?.contextWindow ?? 0;
12984
+ const { score, profile } = scoreModel(prov.id, m.id, category, ctxWindow);
12985
+ if (score > 0) {
12986
+ candidates.push({
12987
+ provider: prov.id,
12988
+ model: m.id,
12989
+ profile,
12990
+ score,
12991
+ ctxWindow,
12992
+ maxOutput: m.capabilities?.maxOutputTokens ?? 0,
12993
+ inputPrice: m.pricing?.input,
12994
+ outputPrice: m.pricing?.output
12995
+ });
12996
+ }
12997
+ }
12998
+ }
12999
+ candidates.sort((a, b) => b.score - a.score);
13000
+ return candidates.slice(0, limit);
13001
+ }
13002
+ var EVAL_TASKS = {
13003
+ coding: {
13004
+ label: "Code Generation",
13005
+ prompt: "Write a TypeScript function parseCSV(input: string): { headers: string[]; rows: string[][] } that handles quoted fields, escaped quotes, and empty lines. Return an error string on malformed input. Keep under 40 lines."
13006
+ },
13007
+ planning: {
13008
+ label: "Architecture Planning",
13009
+ prompt: "Design the folder structure and key interfaces for a monorepo CLI tool with slash commands, model routing, subagent spawning, and config persistence. List packages, their responsibilities, and the 5 most important TypeScript interfaces."
13010
+ },
13011
+ security: {
13012
+ label: "Vulnerability Detection",
13013
+ prompt: 'Review this code for security issues:\n```ts\napp.get("/api/user", (req, res) => {\n const id = req.query.id;\n const user = db.query("SELECT * FROM users WHERE id = " + id);\n res.json(user);\n});\n\napp.post("/api/run", (req, res) => {\n const { cmd } = req.body;\n exec("echo " + cmd, (err, stdout) => res.send(stdout));\n});\n```\nList every vulnerability, its severity (critical/high/medium), and the exact fix.'
13014
+ },
13015
+ debugging: {
13016
+ label: "Bug Diagnosis",
13017
+ prompt: 'This async function has 2 bugs. Find and fix both:\n```ts\nasync function processBatch(items: string[]) {\n const results = [];\n for (const item of items) {\n const result = await fetch("https://api.example.com/" + item);\n results.push(result);\n }\n return results.map(r => r.json());\n}\n```\nExplain what each bug is, why it fails, and write the corrected version.'
13018
+ },
13019
+ testing: {
13020
+ label: "Test Authoring",
13021
+ prompt: 'Write vitest test cases for this deepMerge function:\n```ts\nfunction deepMerge(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n const merged = { ...base };\n for (const [key, val] of Object.entries(overrides)) {\n if (val === null) { delete merged[key]; continue; }\n if (typeof val === "object" && !Array.isArray(val) && typeof merged[key] === "object" && !Array.isArray(merged[key])) {\n merged[key] = deepMerge(merged[key] as Record<string, unknown>, val as Record<string, unknown>);\n } else { merged[key] = val; }\n }\n return merged;\n}\n```\nCover: happy path, edge cases, and error conditions.'
13022
+ },
13023
+ docs: {
13024
+ label: "Documentation",
13025
+ prompt: "Write TSDoc comments for this RateLimiter interface. Include @param, @returns, @throws, and @example for each method:\n```ts\ninterface RateLimiter {\n tryAcquire(key: string, maxPerWindow: number, windowMs: number): Promise<boolean>;\n getRemaining(key: string): Promise<number>;\n reset(key: string): Promise<void>;\n}\n```"
13026
+ },
13027
+ review: {
13028
+ label: "Code Review",
13029
+ prompt: 'Review this PR change:\n```diff\n async function loadConfig(path: string) {\n- const raw = await fs.readFile(path, "utf8");\n- return JSON.parse(raw);\n+ const raw = await fs.readFile(path);\n+ const config = JSON.parse(raw);\n+ process.env.API_KEY = config.apiKey;\n+ return config;\n }\n```\nList issues by severity (blocking / should-fix / nit) and explain your reasoning.'
13030
+ },
13031
+ refactoring: {
13032
+ label: "Refactoring",
13033
+ prompt: 'Refactor this nested condition into a cleaner pattern:\n```ts\nfunction getDiscount(user: { type: string; years: number; coupon?: string }): number {\n if (user.type === "premium") {\n if (user.years > 5) {\n if (user.coupon === "BLACKFRIDAY") return 0.5;\n return 0.3;\n }\n return 0.2;\n }\n if (user.type === "standard") {\n if (user.years > 3) return 0.15;\n return 0.1;\n }\n return 0;\n}\n```\nShow your refactored code and explain why your approach is cleaner.'
13034
+ }
13035
+ };
13036
+ var EVAL_CATEGORIES = Object.keys(EVAL_TASKS);
13037
+ function roleCat(role) {
13038
+ return ROLE_CATEGORY[role] ?? "general";
13039
+ }
13040
+ function createProviderForId(providerId, cfg) {
13041
+ const savedCfg = cfg.providers?.[providerId];
13042
+ const resolvedProviderId = savedCfg?.type ?? providerId;
13043
+ const cfgWithType = {
13044
+ ...savedCfg ?? { type: providerId, apiKey: cfg.apiKey, baseUrl: cfg.baseUrl },
13045
+ type: resolvedProviderId
13046
+ };
13047
+ try {
13048
+ return makeProviderFromConfig(resolvedProviderId, cfgWithType);
13049
+ } catch {
13050
+ return void 0;
13051
+ }
13052
+ }
13053
+ async function rankResponses(provider, leaderModel, taskPrompt, responses) {
13054
+ const labelToIdx = /* @__PURE__ */ new Map();
13055
+ const responseBlock = responses.map((r, i) => {
13056
+ const label = String.fromCharCode(65 + i);
13057
+ labelToIdx.set(label, i);
13058
+ return `=== Response ${label} ===
13059
+ ${r.text.slice(0, 800)}`;
13060
+ }).join("\n\n");
13061
+ const rankingPrompt = `Rank these responses from BEST (1) to WORST.
13062
+
13063
+ TASK:
13064
+ ${taskPrompt.slice(0, 600)}
13065
+
13066
+ RESPONSES:
13067
+ ${responseBlock}
13068
+
13069
+ Output ONLY a ranked list, one per line:
13070
+ 1. Response X \u2014 brief reason
13071
+ 2. Response Y \u2014 brief reason`;
13072
+ try {
13073
+ const resp = await provider.complete(
13074
+ {
13075
+ model: leaderModel,
13076
+ system: [{ type: "text", text: "You are an expert evaluator. Rank responses concisely. Output ONLY the ranked list." }],
13077
+ messages: [{ role: "user", content: [{ type: "text", text: rankingPrompt }] }],
13078
+ maxTokens: 400
13079
+ },
13080
+ { signal: AbortSignal.timeout(3e4) }
13081
+ );
13082
+ const text = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
13083
+ const rankings = [];
13084
+ for (const line of text.split("\n")) {
13085
+ const m = line.match(/^\s*(\d+)[\.\)]\s*Response\s+([A-Z])/i);
13086
+ if (m) {
13087
+ const label = m[2].toUpperCase();
13088
+ const idx = labelToIdx.get(label);
13089
+ if (idx !== void 0 && !rankings.includes(idx)) {
13090
+ rankings.push(idx);
13091
+ }
13092
+ }
13093
+ }
13094
+ return rankings.length > 0 ? rankings : responses.map((_, i) => i);
13095
+ } catch {
13096
+ return responses.map((_, i) => i);
13097
+ }
13098
+ }
13099
+ async function readProviders(cachePath2) {
13100
+ if (!cachePath2) {
13101
+ return `${color.red("Models cache not available")}.`;
13102
+ }
13103
+ try {
13104
+ const raw = await fsp4.readFile(cachePath2, "utf8");
13105
+ const parsed = JSON.parse(raw);
13106
+ const payload = parsed.payload ?? parsed;
13107
+ return Object.entries(payload).map(([id, p]) => ({
13108
+ id: p.id ?? id,
13109
+ name: p.name ?? id,
13110
+ family: p.npm ?? id,
13111
+ models: Object.values(p.models ?? {}).map((m) => ({
13112
+ id: m.id,
13113
+ name: m.name,
13114
+ capabilities: {
13115
+ contextWindow: m.limit?.context,
13116
+ maxOutputTokens: m.limit?.output
13117
+ },
13118
+ pricing: m.cost
13119
+ }))
13120
+ }));
13121
+ } catch {
13122
+ return `${color.amber("Models cache not available")}. Run wstack sync-models.`;
13123
+ }
13124
+ }
13125
+ function checkHasKey(pid, config) {
13126
+ if (pid === config.provider && config.provider) return true;
13127
+ const pc = config.providers?.[pid];
13128
+ if (!pc) return false;
13129
+ if (typeof pc.apiKey === "string" && pc.apiKey.length > 0) return true;
13130
+ if (Array.isArray(pc.apiKeys) && pc.apiKeys.some((k) => k?.apiKey)) return true;
13131
+ return false;
13132
+ }
13133
+ var modeldiagCmd = async (args, deps) => {
13134
+ const sub = args[0]?.toLowerCase() || "full";
13135
+ const cacheResult = await readProviders(deps.paths.modelsCache);
13136
+ if (typeof cacheResult === "string") {
13137
+ deps.renderer.write(`${cacheResult}
13138
+ `);
13139
+ return cacheResult.includes(color.red("")) ? 1 : 0;
13140
+ }
13141
+ const providers = cacheResult;
13142
+ const config = deps.config;
13143
+ const modelMatrix = config.modelMatrix ?? {};
13144
+ function hasKey(pid) {
13145
+ return checkHasKey(pid, config);
13146
+ }
13147
+ function writeLine(line = "") {
13148
+ deps.renderer.write(`${line}
13149
+ `);
13150
+ }
13151
+ if (sub === "keys") {
13152
+ writeLine(`${color.bold("API Key Status")}`);
13153
+ writeLine();
13154
+ for (const prov of providers) {
13155
+ const k = hasKey(prov.id);
13156
+ writeLine(` ${checkMark(k)} ${color.bold(prov.id.padEnd(18))} ${color.dim(prov.name)}`);
13157
+ }
13158
+ writeLine();
13159
+ writeLine(`${color.dim(`Leader: ${config.provider}/${config.model}`)}`);
13160
+ return 0;
13161
+ }
13162
+ if (sub === "caps") {
13163
+ writeLine(`${color.bold("Model Capabilities")} ${color.dim("\u2014 matched to known profiles")}`);
13164
+ writeLine();
13165
+ for (const prov of providers) {
13166
+ if (!hasKey(prov.id)) continue;
13167
+ writeLine(` ${color.bold(prov.id)} ${color.dim(`(${prov.name})`)}`);
13168
+ const tiers = { premium: [], standard: [], budget: [], unknown: [] };
13169
+ for (const m of prov.models ?? []) {
13170
+ const profile = findProfile(prov.id, m.id);
13171
+ tiers[profile?.costTier ?? "unknown"].push(m);
13172
+ }
13173
+ for (const tier of ["premium", "standard", "budget", "unknown"]) {
13174
+ const tierModels = tiers[tier];
13175
+ if (tierModels.length === 0) continue;
13176
+ const label = tier === "unknown" ? color.dim("unmatched") : `${costLabel(tier)} ${tier}`;
13177
+ writeLine(` ${label}`);
13178
+ for (const m of tierModels) {
13179
+ const cap = m.capabilities;
13180
+ const ctx = cap?.contextWindow ?? 0;
13181
+ const maxOut = cap?.maxOutputTokens ?? 0;
13182
+ const profile = findProfile(prov.id, m.id);
13183
+ const family = profile ? `${speedLabel(profile.speedTier)} ${color.green(profile.family)}` : color.dim("no profile match");
13184
+ const pricing = m.pricing ? `${color.dim("in")}${fmtPrice2(m.pricing.input)} ${color.dim("out")}${fmtPrice2(m.pricing.output)}` : color.dim("pricing ?");
13185
+ writeLine(
13186
+ ` ${color.cyan(m.id.padEnd(34))}${ctx > 0 ? `ctx ${fmtTokens2(ctx).padEnd(6)}` : color.dim("ctx ? ")}${maxOut > 0 ? `out ${fmtTokens2(maxOut).padEnd(6)}` : " "}${family} ${pricing}`
13187
+ );
13188
+ }
13189
+ }
13190
+ writeLine();
13191
+ }
13192
+ writeLine(color.dim("Prices in USD per 1M tokens (input/output). ctx = context window, out = max output."));
13193
+ return 0;
13194
+ }
13195
+ async function renderSuggest() {
13196
+ writeLine();
13197
+ writeLine(`${color.bold("Agent \u2192 Model Suggestions")} ${color.amber("(heuristic \u2014 untested)")}`);
13198
+ writeLine(color.dim('These are profile-based best guesses. Test them with wstack modeldiag bench <role> "<prompt>".'));
13199
+ writeLine();
13200
+ const keyedProviders = providers.filter((p) => hasKey(p.id));
13201
+ if (keyedProviders.length === 0) {
13202
+ writeLine(` ${color.amber("No providers have API keys configured. Add keys with wstack auth.")}`);
13203
+ } else {
13204
+ const roles = [
13205
+ "security-scanner",
13206
+ "bug-hunter",
13207
+ "planner",
13208
+ "architect",
13209
+ "refactor-planner",
13210
+ "test",
13211
+ "document",
13212
+ "code-reviewer",
13213
+ "executor",
13214
+ "debugger"
13215
+ ];
13216
+ for (const role of roles) {
13217
+ if (modelMatrix[role]) {
13218
+ const entry = modelMatrix[role];
13219
+ const p = entry.provider ?? config.provider;
13220
+ writeLine(` ${color.dim(role.padEnd(20))} \u2192 ${color.cyan(`${p}/${entry.model}`)} ${color.dim("(user-configured)")}`);
13221
+ continue;
13222
+ }
13223
+ const cat = roleCat(role);
13224
+ const ranked = rankModels(providers, hasKey, cat, 3);
13225
+ if (ranked.length === 0) {
13226
+ writeLine(` ${color.dim(role.padEnd(20))} \u2192 ${color.dim("no candidates")}`);
13227
+ continue;
13228
+ }
13229
+ const best = ranked[0];
13230
+ const family = best.profile ? ` ${color.dim(`(${best.profile.family})`)}` : "";
13231
+ const bar = scoreBar(best.score, 110);
13232
+ writeLine(
13233
+ ` ${color.amber(role.padEnd(20))} \u2192 ${color.cyan(`${best.provider}/${best.model}`)}${family}`
13234
+ );
13235
+ writeLine(` ${" ".repeat(22)} ${bar} ${color.dim(cat)}`);
13236
+ if (ranked.length > 1 && ranked[1].score >= best.score - 15) {
13237
+ for (const alt of ranked.slice(1)) {
13238
+ const af = alt.profile ? ` (${alt.profile.family})` : "";
13239
+ writeLine(` ${" ".repeat(22)} ${color.dim(`${alt.provider}/${alt.model}${af} score ${alt.score}`)}`);
13240
+ }
13241
+ }
13242
+ }
13243
+ writeLine();
13244
+ writeLine(` ${color.bold("leader".padEnd(20))} \u2192 ${color.cyan(`${config.provider}/${config.model}`)}`);
13245
+ }
13246
+ }
13247
+ if (sub === "suggest") {
13248
+ await renderSuggest();
13249
+ writeLine();
13250
+ writeLine(color.dim("Pin a suggestion: wstack setmodel set <role> <provider>/<model>"));
13251
+ writeLine(color.dim('Test candidates: wstack modeldiag bench <role> "<test prompt>"'));
13252
+ return 0;
13253
+ }
13254
+ if (sub === "test") {
13255
+ writeLine(`${color.bold("Connectivity Test")}`);
13256
+ writeLine();
13257
+ const keyed = providers.filter((p) => hasKey(p.id));
13258
+ if (keyed.length === 0) {
13259
+ writeLine(` ${color.amber("No providers have API keys. Add keys with wstack auth.")}`);
13260
+ return 0;
13261
+ }
13262
+ for (const prov of keyed) {
13263
+ writeLine(` ${color.cyan("\u27F3")} ${prov.id}... ${color.dim("(capability scan, no API call)")}`);
13264
+ const profile = findProfile(prov.id, config.model ?? "");
13265
+ const firstModel = prov.models?.[0]?.id ?? config.model ?? "?";
13266
+ const cap = prov.models?.[0]?.capabilities;
13267
+ const ctx = cap?.contextWindow ?? 0;
13268
+ writeLine(` ${checkMark(true)} provider: ${prov.id}`);
13269
+ writeLine(` ${checkMark(ctx > 0)} context: ${ctx > 0 ? fmtTokens2(ctx) : "unknown"}`);
13270
+ writeLine(` ${checkMark(!!profile)} profile: ${profile?.family ?? "no match"}`);
13271
+ writeLine(` model: ${color.cyan(firstModel)}`);
13272
+ writeLine();
13273
+ }
13274
+ writeLine(color.dim("Full API connectivity test requires an active session (costs tokens)."));
13275
+ writeLine(color.dim('Use wstack modeldiag bench <role> "<prompt>" to test models with real API calls.'));
13276
+ return 0;
13277
+ }
13278
+ if (sub === "bench") {
13279
+ const benchArgs = args.slice(1);
13280
+ if (benchArgs.length < 2) {
13281
+ writeLine(`${color.amber("Usage:")} wstack modeldiag bench <role> "<test prompt>" [--providers=p1,p2]`);
13282
+ writeLine();
13283
+ writeLine(color.dim('Example: wstack modeldiag bench verify "Write a function that checks if a string is a palindrome"'));
13284
+ writeLine(color.dim("Tests the top 5 candidate models for the role with your prompt and reports results."));
13285
+ writeLine(color.dim("Add --providers=anthropic,google to test across multiple providers."));
13286
+ return 0;
13287
+ }
13288
+ const providersEqIdx = benchArgs.findIndex((a) => a.startsWith("--providers="));
13289
+ let providerFilter;
13290
+ if (providersEqIdx >= 0) {
13291
+ providerFilter = benchArgs[providersEqIdx].replace("--providers=", "").split(",").map((s) => s.trim()).filter(Boolean);
13292
+ benchArgs.splice(providersEqIdx, 1);
13293
+ }
13294
+ const benchRole = benchArgs[0];
13295
+ const benchPrompt = benchArgs.slice(1).join(" ");
13296
+ const cat = roleCat(benchRole);
13297
+ const candidates = rankModels(providers, hasKey, cat, 5);
13298
+ if (candidates.length === 0) {
13299
+ writeLine(`${color.amber("No candidate models found")} for role "${benchRole}" (category: ${cat}).`);
13300
+ return 0;
13301
+ }
13302
+ let targetCandidates;
13303
+ if (providerFilter && providerFilter.length > 0) {
13304
+ targetCandidates = candidates.filter((c) => providerFilter.includes(c.provider));
13305
+ if (targetCandidates.length === 0) {
13306
+ writeLine(`${color.amber("No candidates match the specified providers")}: ${providerFilter.join(", ")}`);
13307
+ writeLine(`Candidate providers: ${[...new Set(candidates.map((c) => c.provider))].join(", ")}`);
13308
+ return 0;
13309
+ }
13310
+ } else {
13311
+ targetCandidates = candidates;
13312
+ }
13313
+ writeLine(`${color.bold("Model Benchmark")} \u2014 ${color.amber(benchRole)} ${color.dim(`(category: ${cat})`)}`);
13314
+ writeLine(`${color.dim("Prompt:")} "${benchPrompt.slice(0, 120)}${benchPrompt.length > 120 ? "\u2026" : ""}"`);
13315
+ writeLine();
13316
+ writeLine(
13317
+ ` ${color.dim("# model".padEnd(52))} ${color.dim("score".padEnd(12))} ${color.dim("latency".padEnd(10))} ${color.dim("tokens".padEnd(14))} ${color.dim("first line")}`
13318
+ );
13319
+ writeLine(` ${color.dim("\u2500".repeat(108))}`);
13320
+ const providerInstances = /* @__PURE__ */ new Map();
13321
+ for (const pid of [...new Set(targetCandidates.map((c) => c.provider))]) {
13322
+ const prov = createProviderForId(pid, config);
13323
+ if (prov) providerInstances.set(pid, prov);
13324
+ }
13325
+ let idx = 0;
13326
+ for (const c of targetCandidates.slice(0, 20)) {
13327
+ idx++;
13328
+ const label = `${idx}`.padStart(2);
13329
+ const modelKey = `${c.provider}/${c.model}`;
13330
+ const prov = providerInstances.get(c.provider);
13331
+ if (!prov) {
13332
+ writeLine(
13333
+ ` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("NO PROVIDER")}`
13334
+ );
13335
+ continue;
13336
+ }
13337
+ try {
13338
+ const start = Date.now();
13339
+ const resp = await prov.complete(
13340
+ {
13341
+ model: c.model,
13342
+ messages: [{ role: "user", content: [{ type: "text", text: benchPrompt }] }],
13343
+ maxTokens: 256
13344
+ },
13345
+ { signal: AbortSignal.timeout(3e4) }
13346
+ );
13347
+ const latency = Date.now() - start;
13348
+ const firstText = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
13349
+ const firstLineClean = firstText.replace(/\n/g, " ").slice(0, 80) || color.dim("(empty)");
13350
+ const provColor = c.provider === config.provider ? color.green : color.cyan;
13351
+ const usage = resp.usage;
13352
+ writeLine(
13353
+ ` ${label} ${provColor(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.amber(fmtMs(latency).padEnd(8))} ${color.dim(`in${usage?.input ?? "?"}/out${usage?.output ?? "?"}`.padEnd(12))} ${firstLineClean}`
13354
+ );
13355
+ } catch (err) {
13356
+ const errMsg = err instanceof Error ? err.message : String(err);
13357
+ writeLine(
13358
+ ` ${label} ${color.red(modelKey.padEnd(50))} ${scoreBar(c.score, 110).slice(0, 11)} ${color.red("FAILED")} ${color.dim(errMsg.slice(0, 40))}`
13359
+ );
13360
+ }
13361
+ }
13362
+ const testedProviders = [...new Set(targetCandidates.map((c) => c.provider))];
13363
+ writeLine();
13364
+ writeLine(
13365
+ color.dim(`Tested ${idx} model(s) across ${testedProviders.length} provider(s): ${testedProviders.join(", ")}.`)
13366
+ );
13367
+ writeLine(color.dim("Pin the best: wstack setmodel set <role> <provider>/<model>"));
13368
+ return 0;
13369
+ }
13370
+ if (sub === "eval" || sub === "evall") {
13371
+ const evalArgs = args.slice(1);
13372
+ const providersEq = evalArgs.find((a) => a.startsWith("--providers="));
13373
+ const providerFilter = providersEq ? providersEq.replace("--providers=", "").split(",").map((s) => s.trim()).filter(Boolean) : void 0;
13374
+ const maxEq = evalArgs.find((a) => a.startsWith("--max="));
13375
+ const maxModels = maxEq ? Math.max(1, parseInt(maxEq.replace("--max=", ""), 10) || 2) : 2;
13376
+ const quick = evalArgs.includes("--quick");
13377
+ const modelsPerCat = quick ? 1 : maxModels;
13378
+ const roleFilter = evalArgs.find((a) => !a.startsWith("--"));
13379
+ const targetCategories = roleFilter ? EVAL_CATEGORIES.includes(roleCat(roleFilter)) ? [roleCat(roleFilter)] : [] : EVAL_CATEGORIES;
13380
+ if (targetCategories.length === 0 && roleFilter) {
13381
+ writeLine(`${color.amber("Unknown role/category")}: "${roleFilter}". Try: ${EVAL_CATEGORIES.join(", ")}`);
13382
+ return 1;
13383
+ }
13384
+ const keyedProviderIds2 = providers.filter((p) => hasKey(p.id)).map((p) => p.id);
13385
+ let targetProviderIds;
13386
+ if (providerFilter && providerFilter.length > 0) {
13387
+ const unknown = providerFilter.filter((pid) => !keyedProviderIds2.includes(pid));
13388
+ targetProviderIds = providerFilter.filter((pid) => keyedProviderIds2.includes(pid));
13389
+ if (targetProviderIds.length === 0) {
13390
+ const noKeyMsg = unknown.length > 0 ? `None of the specified providers (${unknown.join(", ")}) have API keys. Add keys with wstack auth.` : "None of the specified providers have API keys configured.";
13391
+ writeLine(`${color.amber(noKeyMsg)}`);
13392
+ return 0;
13393
+ }
13394
+ } else if (keyedProviderIds2.length === 0) {
13395
+ writeLine(`${color.amber("No providers have API keys. Add keys with wstack auth.")}`);
13396
+ return 0;
13397
+ } else if (keyedProviderIds2.length === 1) {
13398
+ targetProviderIds = keyedProviderIds2;
13399
+ } else {
13400
+ const providerList = keyedProviderIds2.map((pid, i) => {
13401
+ const info = providers.find((p) => p.id === pid);
13402
+ return ` ${color.cyan(String(i + 1))}) ${color.bold(pid.padEnd(16))} ${color.dim(info?.name ?? "")}`;
13403
+ }).join("\n");
13404
+ deps.renderer.write(`
13405
+ ${color.bold("Select providers to evaluate")}
13406
+
13407
+ ${providerList}
13408
+
13409
+ ${color.dim('Enter numbers or provider IDs (comma-separated, or "all"):')}
13410
+ `);
13411
+ const input = await deps.reader.readLine(" > ");
13412
+ const selected = input.trim().toLowerCase();
13413
+ if (selected === "" || selected === "all") {
13414
+ targetProviderIds = keyedProviderIds2;
13415
+ } else {
13416
+ targetProviderIds = [];
13417
+ for (const part of selected.split(",").map((s) => s.trim())) {
13418
+ const idx = parseInt(part, 10);
13419
+ if (idx >= 1 && idx <= keyedProviderIds2.length) {
13420
+ const pid = keyedProviderIds2[idx - 1];
13421
+ if (!targetProviderIds.includes(pid)) targetProviderIds.push(pid);
13422
+ } else if (keyedProviderIds2.includes(part)) {
13423
+ if (!targetProviderIds.includes(part)) targetProviderIds.push(part);
13424
+ }
13425
+ }
13426
+ }
13427
+ if (targetProviderIds.length === 0) {
13428
+ writeLine(color.dim("No providers selected."));
13429
+ return 0;
13430
+ }
13431
+ }
13432
+ const leaderModel = config.model ?? "unknown";
13433
+ const unknownProviders = providerFilter ? providerFilter.filter((pid) => !keyedProviderIds2.includes(pid)) : [];
13434
+ const warningLine = unknownProviders.length > 0 ? ` ${color.amber("\u26A0 skipped (no key):")} ${unknownProviders.join(", ")}
13435
+ ` : "";
13436
+ writeLine(`${color.bold("Model Competency Evaluation")}`);
13437
+ writeLine(color.dim(`Providers: ${targetProviderIds.join(", ")} | ${targetCategories.length} cats | ${modelsPerCat} model(s)/cat/provider`));
13438
+ writeLine(warningLine);
13439
+ writeLine(color.dim(`Leader (ranker): ${config.provider}/${leaderModel}`));
13440
+ writeLine();
13441
+ const collected = /* @__PURE__ */ new Map();
13442
+ let total = 0;
13443
+ let ok = 0;
13444
+ for (const pid of targetProviderIds) {
13445
+ const prov = createProviderForId(pid, config);
13446
+ if (!prov) {
13447
+ writeLine(color.dim(` \u2298 ${pid}: provider unavailable, skipping`));
13448
+ continue;
13449
+ }
13450
+ for (const cat of targetCategories) {
13451
+ const task = EVAL_TASKS[cat];
13452
+ if (!task) continue;
13453
+ const candidates = rankModels(providers, hasKey, cat, modelsPerCat).filter((c) => c.provider === pid);
13454
+ if (candidates.length === 0) continue;
13455
+ if (!collected.has(cat)) collected.set(cat, /* @__PURE__ */ new Map());
13456
+ for (const c of candidates) {
13457
+ total++;
13458
+ const modelKey = `${pid}/${c.model}`;
13459
+ try {
13460
+ const start = Date.now();
13461
+ const resp = await prov.complete(
13462
+ {
13463
+ model: c.model,
13464
+ system: [{ type: "text", text: "Be thorough and correct." }],
13465
+ messages: [{ role: "user", content: [{ type: "text", text: task.prompt }] }],
13466
+ maxTokens: 1024
13467
+ },
13468
+ { signal: AbortSignal.timeout(45e3) }
13469
+ );
13470
+ const respText = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
13471
+ const respUsage = resp.usage;
13472
+ collected.get(cat).set(modelKey, {
13473
+ model: modelKey,
13474
+ latency: Date.now() - start,
13475
+ tokens: (respUsage?.input ?? 0) + (respUsage?.output ?? 0),
13476
+ text: respText
13477
+ });
13478
+ ok++;
13479
+ } catch {
13480
+ collected.get(cat).set(modelKey, {
13481
+ model: modelKey,
13482
+ latency: -1,
13483
+ tokens: 0,
13484
+ text: ""
13485
+ });
13486
+ }
13487
+ }
13488
+ }
13489
+ }
13490
+ writeLine(`${color.dim(`Phase 1: ${ok}/${total} calls succeeded`)}`);
13491
+ writeLine();
13492
+ if (collected.size === 0) {
13493
+ writeLine(color.amber("No responses collected. Check provider configuration."));
13494
+ return 0;
13495
+ }
13496
+ const leaderProvider = createProviderForId(config.provider, config);
13497
+ if (leaderProvider) {
13498
+ writeLine(`${color.bold("Phase 2")} \u2014 ${color.dim("leader ranks responses")}`);
13499
+ writeLine();
13500
+ }
13501
+ const rankings = /* @__PURE__ */ new Map();
13502
+ for (const [cat, responses] of collected) {
13503
+ const valid = Array.from(responses.values()).filter((r) => r.latency >= 0);
13504
+ if (valid.length < 2) {
13505
+ if (valid.length === 1) {
13506
+ const m = valid[0].model;
13507
+ if (!rankings.has(m)) rankings.set(m, /* @__PURE__ */ new Map());
13508
+ rankings.get(m).set(cat, { rank: 1, total: 1 });
13509
+ }
13510
+ continue;
13511
+ }
13512
+ const task = EVAL_TASKS[cat];
13513
+ if (leaderProvider) {
13514
+ const ranked = await rankResponses(leaderProvider, leaderModel, task.prompt, valid);
13515
+ for (let i = 0; i < valid.length; i++) {
13516
+ const m = valid[ranked[i] ?? i].model;
13517
+ if (!rankings.has(m)) rankings.set(m, /* @__PURE__ */ new Map());
13518
+ rankings.get(m).set(cat, { rank: i + 1, total: valid.length });
13519
+ }
13520
+ } else {
13521
+ for (const r of valid) {
13522
+ if (!rankings.has(r.model)) rankings.set(r.model, /* @__PURE__ */ new Map());
13523
+ rankings.get(r.model).set(cat, { rank: 1, total: valid.length });
13524
+ }
13525
+ }
13526
+ }
13527
+ writeLine(`${color.bold("Competency Report")}`);
13528
+ writeLine();
13529
+ const allModels = [.../* @__PURE__ */ new Set([...rankings.keys()])].sort();
13530
+ const catList = [...collected.keys()];
13531
+ const modelColWidth = Math.max(24, ...allModels.map((m) => m.length)) + 2;
13532
+ const cw = 12;
13533
+ writeLine(
13534
+ ` ${color.dim("model".padEnd(modelColWidth))}` + catList.map((c) => color.dim((EVAL_TASKS[c]?.label ?? c).slice(0, cw).padEnd(cw + 2))).join("")
13535
+ );
13536
+ writeLine(` ${color.dim("\u2500".repeat(modelColWidth + catList.length * (cw + 2)))}`);
13537
+ for (const model of allModels) {
13538
+ const mr = rankings.get(model);
13539
+ const provFromModel = model.split("/")[0] ?? "";
13540
+ const modelColor = provFromModel === config.provider ? color.cyan : color.green;
13541
+ let row = ` ${modelColor(model.padEnd(modelColWidth))}`;
13542
+ for (const cat of catList) {
13543
+ const e = mr.get(cat);
13544
+ if (e) {
13545
+ const pct2 = Math.round((1 - (e.rank - 1) / Math.max(1, e.total - 1)) * 100);
13546
+ const pc = pct2 >= 80 ? color.green : pct2 >= 50 ? color.amber : color.red;
13547
+ row += `${pc(`#${e.rank} ${pct2}%`.padEnd(cw + 2))}`;
13548
+ } else {
13549
+ row += color.dim("\u2014".padEnd(cw + 2));
13550
+ }
13551
+ }
13552
+ writeLine(row);
13553
+ }
13554
+ writeLine();
13555
+ writeLine(color.dim("#1 100% = best in category. \u2014 = not tested."));
13556
+ writeLine();
13557
+ writeLine(color.dim("Pin: wstack setmodel set <role> <provider>/<model>"));
13558
+ writeLine(color.dim("Full: wstack modeldiag eval Providers: wstack modeldiag eval --providers=id1,id2"));
13559
+ writeLine(color.dim("Max: wstack modeldiag eval --max=3 Quick: wstack modeldiag eval --quick"));
13560
+ return 0;
13561
+ }
13562
+ writeLine(`${color.bold("API Key Status")}`);
13563
+ writeLine();
13564
+ for (const prov of providers) {
13565
+ const k = hasKey(prov.id);
13566
+ writeLine(` ${checkMark(k)} ${color.bold(prov.id.padEnd(18))} ${color.dim(prov.name)}`);
13567
+ }
13568
+ writeLine();
13569
+ writeLine(`${color.dim(`Leader: ${config.provider}/${config.model}`)}`);
13570
+ writeLine();
13571
+ writeLine(`${color.bold("Model Capabilities")} ${color.dim("\u2014 matched to known profiles")}`);
13572
+ writeLine();
13573
+ for (const prov of providers) {
13574
+ if (!hasKey(prov.id)) continue;
13575
+ writeLine(` ${color.bold(prov.id)} ${color.dim(`(${prov.name})`)}`);
13576
+ const tiers = { premium: [], standard: [], budget: [], unknown: [] };
13577
+ for (const m of prov.models ?? []) {
13578
+ const profile = findProfile(prov.id, m.id);
13579
+ tiers[profile?.costTier ?? "unknown"].push(m);
13580
+ }
13581
+ for (const tier of ["premium", "standard", "budget", "unknown"]) {
13582
+ const tierModels = tiers[tier];
13583
+ if (tierModels.length === 0) continue;
13584
+ const label = tier === "unknown" ? color.dim("unmatched") : `${costLabel(tier)} ${tier}`;
13585
+ writeLine(` ${label}`);
13586
+ for (const m of tierModels) {
13587
+ const cap = m.capabilities;
13588
+ const ctx = cap?.contextWindow ?? 0;
13589
+ const maxOut = cap?.maxOutputTokens ?? 0;
13590
+ const profile = findProfile(prov.id, m.id);
13591
+ const family = profile ? `${speedLabel(profile.speedTier)} ${color.green(profile.family)}` : color.dim("no profile match");
13592
+ const pricing = m.pricing ? `${color.dim("in")}${fmtPrice2(m.pricing.input)} ${color.dim("out")}${fmtPrice2(m.pricing.output)}` : color.dim("pricing ?");
13593
+ writeLine(
13594
+ ` ${color.cyan(m.id.padEnd(34))}${ctx > 0 ? `ctx ${fmtTokens2(ctx).padEnd(6)}` : color.dim("ctx ? ")}${maxOut > 0 ? `out ${fmtTokens2(maxOut).padEnd(6)}` : " "}${family} ${pricing}`
13595
+ );
13596
+ }
13597
+ }
13598
+ writeLine();
13599
+ }
13600
+ await renderSuggest();
13601
+ writeLine();
13602
+ writeLine(color.dim("Pin a suggestion: wstack setmodel set <role> <provider>/<model>"));
13603
+ writeLine(color.dim('Test candidates: wstack modeldiag bench <role> "<test prompt>"'));
13604
+ return 0;
13605
+ };
12653
13606
  var versionCmd = async (_args, deps) => {
12654
13607
  deps.renderer.write(
12655
13608
  `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os2.platform()})
@@ -12729,7 +13682,8 @@ var subcommands = {
12729
13682
  usage: usageCmd,
12730
13683
  version: versionCmd,
12731
13684
  help: helpCmd,
12732
- projects: projectsCmd
13685
+ projects: projectsCmd,
13686
+ modeldiag: modeldiagCmd
12733
13687
  };
12734
13688
 
12735
13689
  // src/utils.ts
@@ -12752,22 +13706,22 @@ function fmtDuration(ms) {
12752
13706
  const remMin = m - h * 60;
12753
13707
  return `${h}h${remMin}m`;
12754
13708
  }
12755
- function fmtTaskResultLine(r, color56) {
13709
+ function fmtTaskResultLine(r, color58) {
12756
13710
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
12757
13711
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
12758
13712
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
12759
13713
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
12760
- const errKindChip = errKind ? color56.dim(` [${errKind}]`) : "";
12761
- const errSnip = errMsg || errKind ? `${errKindChip}${color56.dim(errTail)}` : "";
13714
+ const errKindChip = errKind ? color58.dim(` [${errKind}]`) : "";
13715
+ const errSnip = errMsg || errKind ? `${errKindChip}${color58.dim(errTail)}` : "";
12762
13716
  switch (r.status) {
12763
13717
  case "success":
12764
- return { mark: color56.green("\u2713"), stats, tail: "" };
13718
+ return { mark: color58.green("\u2713"), stats, tail: "" };
12765
13719
  case "timeout":
12766
- return { mark: color56.yellow("\u23F1"), stats: `${color56.yellow("timeout")} ${stats}`, tail: errSnip };
13720
+ return { mark: color58.yellow("\u23F1"), stats: `${color58.yellow("timeout")} ${stats}`, tail: errSnip };
12767
13721
  case "stopped":
12768
- return { mark: color56.dim("\u2298"), stats: `${color56.dim("stopped")} ${stats}`, tail: errSnip };
13722
+ return { mark: color58.dim("\u2298"), stats: `${color58.dim("stopped")} ${stats}`, tail: errSnip };
12769
13723
  case "failed":
12770
- return { mark: color56.red("\u2717"), stats: `${color56.red("failed")} ${stats}`, tail: errSnip };
13724
+ return { mark: color58.red("\u2717"), stats: `${color58.red("failed")} ${stats}`, tail: errSnip };
12771
13725
  }
12772
13726
  }
12773
13727
 
@@ -12785,7 +13739,7 @@ function resolveBundledSkillsDir() {
12785
13739
  try {
12786
13740
  const req2 = createRequire(import.meta.url);
12787
13741
  const corePkg = req2.resolve("@wrongstack/core/package.json");
12788
- return path8.join(path8.dirname(corePkg), "skills");
13742
+ return path9.join(path9.dirname(corePkg), "skills");
12789
13743
  } catch {
12790
13744
  return void 0;
12791
13745
  }
@@ -13035,7 +13989,7 @@ async function boot(argv) {
13035
13989
  } catch {
13036
13990
  }
13037
13991
  printLaunchHints(renderer, flags, {
13038
- cursorFile: path8.join(wpaths.cacheDir, "hint-cursor")
13992
+ cursorFile: path9.join(wpaths.cacheDir, "hint-cursor")
13039
13993
  });
13040
13994
  }
13041
13995
  return {
@@ -13056,7 +14010,7 @@ async function boot(argv) {
13056
14010
  }
13057
14011
  async function checkGitInCwd(opts) {
13058
14012
  const { cwd, renderer, reader } = opts;
13059
- const cwdGit = path8.join(cwd, ".git");
14013
+ const cwdGit = path9.join(cwd, ".git");
13060
14014
  let hasCwdGit = false;
13061
14015
  try {
13062
14016
  await fsp4.access(cwdGit);
@@ -13097,10 +14051,10 @@ async function checkGitInCwd(opts) {
13097
14051
  }
13098
14052
  }
13099
14053
  }
13100
- const parentDir = path8.dirname(cwd);
14054
+ const parentDir = path9.dirname(cwd);
13101
14055
  if (parentDir !== cwd) {
13102
14056
  try {
13103
- await fsp4.access(path8.join(parentDir, ".git"));
14057
+ await fsp4.access(path9.join(parentDir, ".git"));
13104
14058
  renderer.write(
13105
14059
  ` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
13106
14060
  `
@@ -14296,7 +15250,10 @@ async function execute(deps) {
14296
15250
  logLevel: cfg.log?.level ?? "info",
14297
15251
  auditLevel: cfg.session?.auditLevel ?? "standard",
14298
15252
  indexOnStart: cfg.indexing?.onSessionStart !== false,
14299
- maxIterations: cfg.tools?.maxIterations ?? 500
15253
+ maxIterations: cfg.tools?.maxIterations ?? 500,
15254
+ debugStream: cfg.debugStream ?? false,
15255
+ configScope: cfg.configScope ?? "global",
15256
+ enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4
14300
15257
  };
14301
15258
  },
14302
15259
  async saveSettings(s) {
@@ -14305,6 +15262,7 @@ async function execute(deps) {
14305
15262
  {
14306
15263
  configStore,
14307
15264
  globalConfigPath: wpaths.globalConfig,
15265
+ inProjectConfigPath: wpaths.inProjectConfig,
14308
15266
  vault: { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false }
14309
15267
  },
14310
15268
  (autonomy) => {
@@ -14318,8 +15276,10 @@ async function execute(deps) {
14318
15276
  a["confirmExit"] = s.confirmExit ?? true;
14319
15277
  }
14320
15278
  );
14321
- if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0) {
14322
- const raw = await fsp4.readFile(wpaths.globalConfig, "utf8").catch(() => "{}");
15279
+ if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
15280
+ const configScope = s.configScope ?? (configStore.get().configScope ?? "global");
15281
+ const targetPath = configScope === "project" && wpaths.inProjectConfig ? wpaths.inProjectConfig : wpaths.globalConfig;
15282
+ const raw = await fsp4.readFile(targetPath, "utf8").catch(() => "{}");
14323
15283
  const parsed = JSON.parse(raw);
14324
15284
  const vault = { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false };
14325
15285
  const decrypted = decryptConfigSecrets$1(parsed, vault);
@@ -14361,8 +15321,26 @@ async function execute(deps) {
14361
15321
  tools.maxIterations = s.maxIterations;
14362
15322
  decrypted.tools = tools;
14363
15323
  }
14364
- const encrypted = encryptConfigSecrets$1(decrypted, vault);
14365
- await atomicWrite(wpaths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
15324
+ if (s.debugStream !== void 0) {
15325
+ decrypted.debugStream = s.debugStream;
15326
+ const { setDebugStreamEnabled } = await import('@wrongstack/providers');
15327
+ setDebugStreamEnabled(s.debugStream);
15328
+ }
15329
+ if (s.configScope !== void 0) {
15330
+ decrypted.configScope = s.configScope;
15331
+ }
15332
+ if (s.enhanceDelayMs !== void 0) {
15333
+ const autonomy = decrypted.autonomy ?? {};
15334
+ autonomy.enhanceDelayMs = s.enhanceDelayMs;
15335
+ decrypted.autonomy = autonomy;
15336
+ }
15337
+ const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
15338
+ const encrypted = encryptConfigSecrets$1(toWrite, vault);
15339
+ if (targetPath !== wpaths.globalConfig) {
15340
+ await fsp4.mkdir(path9.dirname(targetPath), { recursive: true }).catch(() => {
15341
+ });
15342
+ }
15343
+ await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
14366
15344
  configStore.update({
14367
15345
  ...s.nextPrediction !== void 0 ? { nextPrediction: s.nextPrediction } : {},
14368
15346
  ...s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 ? { features: decrypted.features } : {},
@@ -14370,7 +15348,10 @@ async function execute(deps) {
14370
15348
  ...s.logLevel !== void 0 ? { log: decrypted.log } : {},
14371
15349
  ...s.auditLevel !== void 0 ? { session: decrypted.session } : {},
14372
15350
  ...s.indexOnStart !== void 0 ? { indexing: decrypted.indexing } : {},
14373
- ...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {}
15351
+ ...s.maxIterations !== void 0 ? { tools: decrypted.tools } : {},
15352
+ ...s.debugStream !== void 0 ? { debugStream: s.debugStream } : {},
15353
+ ...s.configScope !== void 0 ? { configScope: s.configScope } : {},
15354
+ ...s.enhanceDelayMs !== void 0 ? { autonomy: { ...configStore.get().autonomy ?? {}, enhanceDelayMs: s.enhanceDelayMs } } : {}
14374
15355
  });
14375
15356
  }
14376
15357
  if (s.streamFleet !== void 0) {
@@ -14445,6 +15426,16 @@ async function execute(deps) {
14445
15426
  getModeLabel: () => {
14446
15427
  const metaMode = context.meta?.["mode"];
14447
15428
  return typeof metaMode === "string" ? metaMode : modeId ?? "default";
15429
+ },
15430
+ registerDebugStreamCallback: (cb) => {
15431
+ void import('@wrongstack/providers').then(
15432
+ ({ setDebugStreamCallback }) => setDebugStreamCallback(cb)
15433
+ );
15434
+ },
15435
+ restoreDebugStreamCallback: () => {
15436
+ void import('@wrongstack/providers').then(
15437
+ ({ setDebugStreamCallback, defaultDebugStreamCallback }) => setDebugStreamCallback(defaultDebugStreamCallback)
15438
+ );
14448
15439
  }
14449
15440
  });
14450
15441
  } finally {
@@ -14475,7 +15466,7 @@ async function execute(deps) {
14475
15466
  supportsVision,
14476
15467
  attachments,
14477
15468
  effectiveMaxContext,
14478
- projectName: path8.basename(projectRoot) || void 0,
15469
+ projectName: path9.basename(projectRoot) || void 0,
14479
15470
  projectRoot,
14480
15471
  getAutonomy,
14481
15472
  onAutonomy,
@@ -14503,7 +15494,7 @@ async function execute(deps) {
14503
15494
  supportsVision,
14504
15495
  attachments,
14505
15496
  effectiveMaxContext,
14506
- projectName: path8.basename(projectRoot) || void 0,
15497
+ projectName: path9.basename(projectRoot) || void 0,
14507
15498
  getAutonomy,
14508
15499
  onAutonomy,
14509
15500
  getNextPredict,
@@ -14635,7 +15626,7 @@ var MultiAgentHost = class {
14635
15626
  doneCondition: { type: "all_tasks_done" },
14636
15627
  maxConcurrent: this.opts.maxConcurrent ?? 4
14637
15628
  };
14638
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path8.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
15629
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path9.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
14639
15630
  this.director = new Director({
14640
15631
  config: coordinatorConfig,
14641
15632
  manifestPath: this.opts.manifestPath,
@@ -15104,16 +16095,16 @@ var MultiAgentHost = class {
15104
16095
  if (this.director) return this.director;
15105
16096
  this.opts.directorMode = true;
15106
16097
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
15107
- this.opts.manifestPath = path8.join(this.opts.fleetRoot, "fleet.json");
16098
+ this.opts.manifestPath = path9.join(this.opts.fleetRoot, "fleet.json");
15108
16099
  }
15109
16100
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
15110
- this.opts.sharedScratchpadPath = path8.join(this.opts.fleetRoot, "shared");
16101
+ this.opts.sharedScratchpadPath = path9.join(this.opts.fleetRoot, "shared");
15111
16102
  }
15112
16103
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
15113
- this.opts.sessionsRoot = path8.join(this.opts.fleetRoot, "subagents");
16104
+ this.opts.sessionsRoot = path9.join(this.opts.fleetRoot, "subagents");
15114
16105
  }
15115
16106
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
15116
- this.opts.stateCheckpointPath = path8.join(this.opts.fleetRoot, "director-state.json");
16107
+ this.opts.stateCheckpointPath = path9.join(this.opts.fleetRoot, "director-state.json");
15117
16108
  }
15118
16109
  await this.ensureDirector();
15119
16110
  return this.director ?? null;
@@ -15285,11 +16276,11 @@ var SessionStats = class {
15285
16276
  if (e.name === "bash") this.bashCommands++;
15286
16277
  else if (e.name === "fetch") this.fetches++;
15287
16278
  if (!e.ok) return;
15288
- const path26 = typeof input?.path === "string" ? input.path : void 0;
15289
- if (e.name === "read" && path26) this.readPaths.add(path26);
15290
- else if (e.name === "edit" && path26) this.editedPaths.add(path26);
15291
- else if (e.name === "write" && path26) {
15292
- this.writtenPaths.add(path26);
16279
+ const path27 = typeof input?.path === "string" ? input.path : void 0;
16280
+ if (e.name === "read" && path27) this.readPaths.add(path27);
16281
+ else if (e.name === "edit" && path27) this.editedPaths.add(path27);
16282
+ else if (e.name === "write" && path27) {
16283
+ this.writtenPaths.add(path27);
15293
16284
  const content = typeof input?.content === "string" ? input.content : "";
15294
16285
  this.bytesWritten += Buffer.byteLength(content, "utf8");
15295
16286
  }
@@ -15956,9 +16947,13 @@ function parseModelRef(ref) {
15956
16947
  }
15957
16948
  return { model: trimmed };
15958
16949
  }
15959
- function overloadStatus(err) {
16950
+ function shouldFallback(err) {
16951
+ if (err instanceof StreamHangError) {
16952
+ return 599;
16953
+ }
15960
16954
  if (!(err instanceof ProviderError)) return null;
15961
16955
  const s = err.status;
16956
+ if (s === 0) return s;
15962
16957
  if (s === 429 || s === 529 || s >= 500) return s;
15963
16958
  return null;
15964
16959
  }
@@ -15990,7 +16985,7 @@ function createFallbackModelExtension(deps) {
15990
16985
  const cfg = deps.getConfig();
15991
16986
  const chain = cfg.fallbackModels ?? [];
15992
16987
  for (const ref of chain) {
15993
- const status = overloadStatus(lastErr);
16988
+ const status = shouldFallback(lastErr);
15994
16989
  if (status === null) break;
15995
16990
  const parsed = parseModelRef(ref);
15996
16991
  if (!parsed.model) continue;
@@ -16107,7 +17102,7 @@ function setupMetrics(params) {
16107
17102
  const dumpMetrics = () => {
16108
17103
  if (!metricsSink) return;
16109
17104
  try {
16110
- const out = path8.join(wpaths.projectSessions, "metrics.json");
17105
+ const out = path9.join(wpaths.projectSessions, "metrics.json");
16111
17106
  const snap = metricsSink.snapshot();
16112
17107
  writeFileSync(out, JSON.stringify(snap, null, 2));
16113
17108
  } catch {
@@ -16182,7 +17177,7 @@ async function setupCodebaseIndexing(deps) {
16182
17177
  if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
16183
17178
  const fp = payload.toolUse.input?.file_path;
16184
17179
  if (typeof fp === "string" && fp.length > 0) {
16185
- const abs = path8.resolve(payload.ctx.cwd, fp);
17180
+ const abs = path9.resolve(payload.ctx.cwd, fp);
16186
17181
  if (isIndexableFile(abs)) {
16187
17182
  enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
16188
17183
  }
@@ -16197,11 +17192,11 @@ async function setupCodebaseIndexing(deps) {
16197
17192
  let watcher;
16198
17193
  if (idx.watchExternal) {
16199
17194
  try {
16200
- watcher = fs13.watch(projectRoot, { recursive: true }, (_event, filename) => {
17195
+ watcher = fs14.watch(projectRoot, { recursive: true }, (_event, filename) => {
16201
17196
  if (!filename) return;
16202
17197
  const rel = filename.toString();
16203
17198
  if (isIgnored(rel)) return;
16204
- const abs = path8.resolve(projectRoot, rel);
17199
+ const abs = path9.resolve(projectRoot, rel);
16205
17200
  if (!isIndexableFile(abs)) return;
16206
17201
  enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
16207
17202
  });
@@ -16490,9 +17485,9 @@ async function setupSession(params) {
16490
17485
  const sessionRef = { current: session };
16491
17486
  await recoveryLock.write(session?.id).catch(() => void 0);
16492
17487
  const attachments = new DefaultAttachmentStore({
16493
- spoolDir: path8.join(wpaths.projectSessions, session?.id, "attachments")
17488
+ spoolDir: path9.join(wpaths.projectSessions, session?.id, "attachments")
16494
17489
  });
16495
- const queueStore = new QueueStore({ dir: path8.join(wpaths.projectSessions, session?.id) });
17490
+ const queueStore = new QueueStore({ dir: path9.join(wpaths.projectSessions, session?.id) });
16496
17491
  const ctxSignal = new AbortController().signal;
16497
17492
  const context = new Context({
16498
17493
  systemPrompt,
@@ -16505,7 +17500,7 @@ async function setupSession(params) {
16505
17500
  model: config.model
16506
17501
  });
16507
17502
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
16508
- const todosCheckpointPath = path8.join(wpaths.projectSessions, `${session?.id}.todos.json`);
17503
+ const todosCheckpointPath = path9.join(wpaths.projectSessions, `${session?.id}.todos.json`);
16509
17504
  if (resumeId) {
16510
17505
  try {
16511
17506
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -16523,15 +17518,15 @@ async function setupSession(params) {
16523
17518
  todosCheckpointPath,
16524
17519
  session?.id
16525
17520
  );
16526
- const planPath = path8.join(wpaths.projectSessions, `${session?.id}.plan.json`);
17521
+ const planPath = path9.join(wpaths.projectSessions, `${session?.id}.plan.json`);
16527
17522
  context.state.setMeta("plan.path", planPath);
16528
- const taskPath = path8.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
17523
+ const taskPath = path9.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
16529
17524
  context.state.setMeta("task.path", taskPath);
16530
17525
  let dirState;
16531
17526
  if (resumeId) {
16532
17527
  try {
16533
- const fleetRoot = path8.join(wpaths.projectSessions, session?.id);
16534
- dirState = await loadDirectorState(path8.join(fleetRoot, "director-state.json"));
17528
+ const fleetRoot = path9.join(wpaths.projectSessions, session?.id);
17529
+ dirState = await loadDirectorState(path9.join(fleetRoot, "director-state.json"));
16535
17530
  if (dirState) {
16536
17531
  const tCounts = {};
16537
17532
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -16571,7 +17566,7 @@ function resolveBundledSkillsDir2() {
16571
17566
  try {
16572
17567
  const req2 = createRequire(import.meta.url);
16573
17568
  const corePkg = req2.resolve("@wrongstack/core/package.json");
16574
- return path8.join(path8.dirname(corePkg), "skills");
17569
+ return path9.join(path9.dirname(corePkg), "skills");
16575
17570
  } catch {
16576
17571
  return void 0;
16577
17572
  }
@@ -16682,6 +17677,9 @@ async function main(argv) {
16682
17677
  updateInfo
16683
17678
  } = ctx;
16684
17679
  updateInfo = await printUpdateNotice(updateInfo);
17680
+ const { setDebugStreamEnabled, setDebugStreamCallback, defaultDebugStreamCallback } = await import('@wrongstack/providers');
17681
+ if (config.debugStream) setDebugStreamEnabled(true);
17682
+ setDebugStreamCallback(defaultDebugStreamCallback);
16685
17683
  const pathResolver = new DefaultPathResolver(cwd);
16686
17684
  const events = new EventBus();
16687
17685
  events.setLogger(logger);
@@ -16768,7 +17766,7 @@ async function main(argv) {
16768
17766
  modeId,
16769
17767
  modePrompt,
16770
17768
  modelCapabilities,
16771
- planPath: () => sessionRef.current ? path8.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
17769
+ planPath: () => sessionRef.current ? path9.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
16772
17770
  contributors: [
16773
17771
  // Injects the ETERNAL AUTONOMY block when the user has activated
16774
17772
  // a long-running autonomy engine. Without this, the per-iteration
@@ -17158,12 +18156,12 @@ async function main(argv) {
17158
18156
  }
17159
18157
  }
17160
18158
  };
17161
- const fleetRoot = directorMode ? path8.join(wpaths.projectSessions, session.id) : void 0;
17162
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path8.join(expectDefined(fleetRoot), "fleet.json") : void 0;
17163
- const sharedScratchpadPath = directorMode ? path8.join(expectDefined(fleetRoot), "shared") : void 0;
17164
- const subagentSessionsRoot = directorMode ? path8.join(expectDefined(fleetRoot), "subagents") : void 0;
17165
- const stateCheckpointPath = directorMode ? path8.join(expectDefined(fleetRoot), "director-state.json") : void 0;
17166
- const fleetRootForPromotion = path8.join(wpaths.projectSessions, session.id);
18159
+ const fleetRoot = directorMode ? path9.join(wpaths.projectSessions, session.id) : void 0;
18160
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path9.join(expectDefined(fleetRoot), "fleet.json") : void 0;
18161
+ const sharedScratchpadPath = directorMode ? path9.join(expectDefined(fleetRoot), "shared") : void 0;
18162
+ const subagentSessionsRoot = directorMode ? path9.join(expectDefined(fleetRoot), "subagents") : void 0;
18163
+ const stateCheckpointPath = directorMode ? path9.join(expectDefined(fleetRoot), "director-state.json") : void 0;
18164
+ const fleetRootForPromotion = path9.join(wpaths.projectSessions, session.id);
17167
18165
  const brainQueue = new BrainDecisionQueue(events);
17168
18166
  const brain = new ObservableBrainArbiter(
17169
18167
  new HumanEscalatingBrainArbiter(new DefaultBrainArbiter(), brainQueue),
@@ -17301,6 +18299,13 @@ async function main(argv) {
17301
18299
  enhanceController,
17302
18300
  llmProvider: provider,
17303
18301
  llmModel: config.model,
18302
+ createProvider: (pid) => {
18303
+ try {
18304
+ return buildProviderForId(pid);
18305
+ } catch {
18306
+ return void 0;
18307
+ }
18308
+ },
17304
18309
  statuslineConfig: statuslineConfigDeps,
17305
18310
  statuslineHiddenItems: [...currentHiddenItems],
17306
18311
  setStatuslineHiddenItems,
@@ -17521,7 +18526,7 @@ async function main(argv) {
17521
18526
  return director.spawn(cfg);
17522
18527
  },
17523
18528
  onFleetLog: async (subagentId, mode) => {
17524
- const subagentsRoot = path8.join(fleetRootForPromotion, "subagents");
18529
+ const subagentsRoot = path9.join(fleetRootForPromotion, "subagents");
17525
18530
  let runDirs;
17526
18531
  try {
17527
18532
  runDirs = await fsp4.readdir(subagentsRoot);
@@ -17530,7 +18535,7 @@ async function main(argv) {
17530
18535
  }
17531
18536
  const found = [];
17532
18537
  for (const runId of runDirs) {
17533
- const runDir = path8.join(subagentsRoot, runId);
18538
+ const runDir = path9.join(subagentsRoot, runId);
17534
18539
  let files;
17535
18540
  try {
17536
18541
  files = await fsp4.readdir(runDir);
@@ -17539,7 +18544,7 @@ async function main(argv) {
17539
18544
  }
17540
18545
  for (const f of files) {
17541
18546
  if (!f.endsWith(".jsonl")) continue;
17542
- const full = path8.join(runDir, f);
18547
+ const full = path9.join(runDir, f);
17543
18548
  try {
17544
18549
  const stat4 = await fsp4.stat(full);
17545
18550
  found.push({
@@ -17640,7 +18645,7 @@ async function main(argv) {
17640
18645
  }
17641
18646
  const dir = await multiAgentHost.ensureDirector();
17642
18647
  if (!dir) return "Director is not available.";
17643
- const dirStatePath = path8.join(fleetRootForPromotion, "director-state.json");
18648
+ const dirStatePath = path9.join(fleetRootForPromotion, "director-state.json");
17644
18649
  const prior = await loadDirectorState(dirStatePath);
17645
18650
  if (!prior) {
17646
18651
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -17709,9 +18714,9 @@ async function main(argv) {
17709
18714
  for (const tool of director2.tools(FLEET_ROSTER)) {
17710
18715
  toolRegistry.register(tool);
17711
18716
  }
17712
- const mp = path8.join(fleetRootForPromotion, "fleet.json");
17713
- const sp = path8.join(fleetRootForPromotion, "shared");
17714
- const ss = path8.join(fleetRootForPromotion, "subagents");
18717
+ const mp = path9.join(fleetRootForPromotion, "fleet.json");
18718
+ const sp = path9.join(fleetRootForPromotion, "shared");
18719
+ const ss = path9.join(fleetRootForPromotion, "subagents");
17715
18720
  const lines = [
17716
18721
  `${color.green("\u2713")} Promoted to director mode.`,
17717
18722
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,