@wrongstack/cli 0.2.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { color, DefaultPathResolver, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, InputBuilder, decryptConfigSecrets, encryptConfigSecrets, DefaultPluginAPI } from '@wrongstack/core';
2
+ import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, DefaultSessionStore, DefaultSkillLoader, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, InputBuilder, decryptConfigSecrets, encryptConfigSecrets, DefaultPluginAPI } from '@wrongstack/core';
3
3
  import * as fs3 from 'fs/promises';
4
4
  import { WebSocketServer, WebSocket } from 'ws';
5
5
  import { writeFileSync } from 'fs';
6
6
  import { createRequire } from 'module';
7
- import * as path13 from 'path';
7
+ import * as path14 from 'path';
8
8
  import { MCPRegistry } from '@wrongstack/mcp';
9
9
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
10
+ import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
10
11
  import { rememberTool, forgetTool } from '@wrongstack/tools';
11
- import { builtinTools } from '@wrongstack/tools/builtin';
12
+ import { builtinToolsPack } from '@wrongstack/tools/pack';
12
13
  import * as os3 from 'os';
13
14
  import * as readline from 'readline';
14
15
  import { randomUUID } from 'crypto';
16
+ import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
15
17
 
16
18
  var __defProp = Object.defineProperty;
17
19
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -568,6 +570,216 @@ var init_plugin_api_factory = __esm({
568
570
  "src/plugin-api-factory.ts"() {
569
571
  }
570
572
  });
573
+ async function setupProvider(params) {
574
+ const { config, modelsRegistry, logger } = params;
575
+ const savedProviderCfg = config.providers?.[config.provider];
576
+ let resolvedProvider = await modelsRegistry.getProvider(config.provider).catch(() => void 0);
577
+ if (!resolvedProvider && savedProviderCfg?.type && savedProviderCfg.type !== config.provider) {
578
+ resolvedProvider = await modelsRegistry.getProvider(savedProviderCfg.type).catch(() => void 0);
579
+ }
580
+ if (!resolvedProvider) {
581
+ if (!savedProviderCfg?.family) {
582
+ logger.warn(
583
+ `Provider "${config.provider}" not found in models.dev. Continuing with raw config.`
584
+ );
585
+ }
586
+ } else if (resolvedProvider.family === "unsupported" && !savedProviderCfg?.family) {
587
+ throw Object.assign(
588
+ new Error(
589
+ `Provider "${config.provider}" uses an unsupported wire family (${resolvedProvider.npm}). Install a plugin to enable it, or pick a different provider.`
590
+ ),
591
+ { code: "UNSUPPORTED_PROVIDER" }
592
+ );
593
+ }
594
+ const providerRegistry = new ProviderRegistry();
595
+ if (config.features.modelsRegistry) {
596
+ try {
597
+ const factories = await buildProviderFactoriesFromRegistry({
598
+ registry: modelsRegistry,
599
+ log: logger
600
+ });
601
+ for (const f of factories) providerRegistry.register(f);
602
+ } catch (err) {
603
+ throw new Error(
604
+ `Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
605
+ Try \`wstack models refresh\` once you have network access, or run with --no-features.`
606
+ );
607
+ }
608
+ }
609
+ const providerConfig = config.providers?.[config.provider] ?? {
610
+ type: config.provider,
611
+ apiKey: config.apiKey,
612
+ baseUrl: config.baseUrl
613
+ };
614
+ let provider;
615
+ try {
616
+ const cfgWithType = { ...providerConfig, type: config.provider };
617
+ if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
618
+ provider = providerRegistry.create(cfgWithType);
619
+ } else {
620
+ provider = makeProviderFromConfig(config.provider, cfgWithType);
621
+ }
622
+ } catch (err) {
623
+ throw new Error(
624
+ `Failed to create provider: ${err instanceof Error ? err.message : err}`
625
+ );
626
+ }
627
+ return { resolvedProvider, provider, providerRegistry };
628
+ }
629
+ async function setupSession(params) {
630
+ const { config, wpaths, projectRoot, cwd, sessionStore, systemPrompt, provider, tokenCounter, renderer, flags, onRecovery } = params;
631
+ let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
632
+ const recoveryLock = new RecoveryLock({ dir: wpaths.projectSessions, sessionStore });
633
+ if (!resumeId && !flags["no-recovery"]) {
634
+ const abandoned = await recoveryLock.checkAbandoned();
635
+ if (abandoned && abandoned.messageCount > 0) {
636
+ const choice = await onRecovery(abandoned, !!flags["recover"]);
637
+ if (choice === "resume") resumeId = abandoned.sessionId;
638
+ else if (choice === "delete") {
639
+ await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
640
+ await recoveryLock.clear();
641
+ } else await recoveryLock.clear();
642
+ } else if (abandoned) {
643
+ await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
644
+ await recoveryLock.clear();
645
+ }
646
+ }
647
+ let session;
648
+ let restoredMessages = [];
649
+ if (resumeId) {
650
+ try {
651
+ const resumed = await sessionStore.resume(resumeId);
652
+ session = resumed.writer;
653
+ restoredMessages = resumed.data.messages;
654
+ renderer.writeInfo(`Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`);
655
+ } catch (err) {
656
+ renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
657
+ throw Object.assign(new Error("RESUME_FAILED"), { exitCode: 2 });
658
+ }
659
+ } else {
660
+ session = await sessionStore.create({ id: "", title: "", model: config.model, provider: config.provider });
661
+ }
662
+ const sessionRef = { current: session };
663
+ await recoveryLock.write(session.id).catch(() => void 0);
664
+ const attachments = new DefaultAttachmentStore({ spoolDir: path14.join(wpaths.projectSessions, session.id, "attachments") });
665
+ const queueStore = new QueueStore({ dir: path14.join(wpaths.projectSessions, session.id) });
666
+ const ctxSignal = new AbortController().signal;
667
+ const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
668
+ if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
669
+ const todosCheckpointPath = path14.join(wpaths.projectSessions, `${session.id}.todos.json`);
670
+ if (resumeId) {
671
+ try {
672
+ const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
673
+ if (restoredTodos && restoredTodos.length > 0) {
674
+ context.state.replaceTodos(restoredTodos);
675
+ renderer.writeInfo(`Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`);
676
+ }
677
+ } catch {
678
+ }
679
+ }
680
+ const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
681
+ const planPath = path14.join(wpaths.projectSessions, `${session.id}.plan.json`);
682
+ context.state.setMeta("plan.path", planPath);
683
+ if (resumeId) {
684
+ try {
685
+ const fleetRoot = path14.join(wpaths.projectSessions, session.id);
686
+ const dirState = await loadDirectorState(path14.join(fleetRoot, "director-state.json"));
687
+ if (dirState) {
688
+ const tCounts = {};
689
+ for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
690
+ const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
691
+ renderer.writeInfo(`Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`);
692
+ }
693
+ } catch {
694
+ }
695
+ try {
696
+ const plan = await loadPlan(planPath);
697
+ if (plan && plan.items.length > 0) {
698
+ const open = plan.items.filter((p) => p.status !== "done").length;
699
+ const done = plan.items.length - open;
700
+ renderer.writeInfo(`Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`);
701
+ }
702
+ } catch {
703
+ }
704
+ }
705
+ return { session, sessionRef, context, restoredMessages, attachments, recoveryLock, queueStore, planPath, detachTodosCheckpoint };
706
+ }
707
+ function setupPipelines(params) {
708
+ const { events, logger } = params;
709
+ const pipelines = createDefaultPipelines();
710
+ const installBoundary = (p) => {
711
+ p.setErrorHandler((ev) => {
712
+ const fromPlugin = !!ev.owner && ev.owner !== "core";
713
+ logger.error(
714
+ `Pipeline middleware "${ev.middleware}" crashed (owner=${ev.owner ?? "unknown"}); ${fromPlugin ? "swallowed" : "rethrown"}`,
715
+ ev.err
716
+ );
717
+ events.emit("error", {
718
+ err: ev.err instanceof Error ? ev.err : new Error(String(ev.err)),
719
+ phase: `pipeline:${ev.middleware}`
720
+ });
721
+ return fromPlugin ? "swallow" : "rethrow";
722
+ });
723
+ };
724
+ installBoundary(pipelines.request);
725
+ installBoundary(pipelines.response);
726
+ installBoundary(pipelines.toolCall);
727
+ installBoundary(pipelines.userInput);
728
+ installBoundary(pipelines.assistantOutput);
729
+ installBoundary(pipelines.contextWindow);
730
+ return pipelines;
731
+ }
732
+ async function setupCompaction(params) {
733
+ const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
734
+ const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
735
+ const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
736
+ if (config.context.autoCompact !== false) {
737
+ const autoCompactor = new AutoCompactionMiddleware(
738
+ compactor,
739
+ effectiveMaxContext,
740
+ (ctx) => {
741
+ let total = 0;
742
+ for (const m of ctx.messages) {
743
+ if (typeof m.content === "string") {
744
+ total += Math.ceil(m.content.length / 4);
745
+ } else if (Array.isArray(m.content)) {
746
+ for (const b of m.content) {
747
+ if (b.type === "text") {
748
+ total += Math.ceil(b.text.length / 4);
749
+ } else if (b.type === "tool_use" || b.type === "tool_result") {
750
+ total += Math.ceil(JSON.stringify(b).length / 4);
751
+ }
752
+ }
753
+ }
754
+ }
755
+ return total;
756
+ },
757
+ {
758
+ warn: config.context.warnThreshold,
759
+ soft: config.context.softThreshold,
760
+ hard: config.context.hardThreshold
761
+ },
762
+ { aggressiveOn: "soft", failureMode: "throw_on_hard", events }
763
+ );
764
+ pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
765
+ }
766
+ return effectiveMaxContext;
767
+ }
768
+ function createAgent(params) {
769
+ return new Agent({
770
+ container: params.container,
771
+ tools: params.tools,
772
+ providers: params.providers,
773
+ events: params.events,
774
+ pipelines: params.pipelines,
775
+ context: params.context,
776
+ maxIterations: params.config.tools.maxIterations,
777
+ iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
778
+ executionStrategy: params.config.tools.defaultExecutionStrategy,
779
+ perIterationOutputCapBytes: params.config.tools.perIterationOutputCapBytes,
780
+ confirmAwaiter: params.confirmAwaiter
781
+ });
782
+ }
571
783
 
572
784
  // src/arg-parser.ts
573
785
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -693,7 +905,7 @@ function parseSpawnFlags(input) {
693
905
  return { description: rest.trim(), opts };
694
906
  }
695
907
  async function bootConfig(flags) {
696
- const cwd = typeof flags["cwd"] === "string" ? path13.resolve(flags["cwd"]) : process.cwd();
908
+ const cwd = typeof flags["cwd"] === "string" ? path14.resolve(flags["cwd"]) : process.cwd();
697
909
  const pathResolver = new DefaultPathResolver(cwd);
698
910
  const projectRoot = pathResolver.projectRoot;
699
911
  const userHome = os3.homedir();
@@ -760,7 +972,7 @@ var ReadlineInputReader = class {
760
972
  history = [];
761
973
  pending = false;
762
974
  constructor(opts = {}) {
763
- this.historyFile = opts.historyFile ?? path13.join(os3.homedir(), ".wrongstack", "history");
975
+ this.historyFile = opts.historyFile ?? path14.join(os3.homedir(), ".wrongstack", "history");
764
976
  }
765
977
  async loadHistory() {
766
978
  try {
@@ -772,7 +984,7 @@ var ReadlineInputReader = class {
772
984
  }
773
985
  async saveHistory() {
774
986
  try {
775
- await fs3.mkdir(path13.dirname(this.historyFile), { recursive: true });
987
+ await fs3.mkdir(path14.dirname(this.historyFile), { recursive: true });
776
988
  await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
777
989
  } catch {
778
990
  }
@@ -1228,7 +1440,7 @@ async function saveToGlobalConfig(configPath, provider, model) {
1228
1440
  async function detectProjectFacts(root) {
1229
1441
  const facts = { hints: [] };
1230
1442
  try {
1231
- const pkg = JSON.parse(await fs3.readFile(path13.join(root, "package.json"), "utf8"));
1443
+ const pkg = JSON.parse(await fs3.readFile(path14.join(root, "package.json"), "utf8"));
1232
1444
  const scripts = pkg.scripts ?? {};
1233
1445
  const pm = (pkg.packageManager ?? "npm").split("@")[0] ?? "npm";
1234
1446
  if (scripts["build"]) facts.build = `${pm} run build`;
@@ -1240,28 +1452,28 @@ async function detectProjectFacts(root) {
1240
1452
  } catch {
1241
1453
  }
1242
1454
  try {
1243
- await fs3.access(path13.join(root, "pyproject.toml"));
1455
+ await fs3.access(path14.join(root, "pyproject.toml"));
1244
1456
  facts.test ??= "pytest";
1245
1457
  facts.lint ??= "ruff check .";
1246
1458
  facts.hints.push("pyproject.toml");
1247
1459
  } catch {
1248
1460
  }
1249
1461
  try {
1250
- await fs3.access(path13.join(root, "go.mod"));
1462
+ await fs3.access(path14.join(root, "go.mod"));
1251
1463
  facts.build ??= "go build ./...";
1252
1464
  facts.test ??= "go test ./...";
1253
1465
  facts.hints.push("go.mod");
1254
1466
  } catch {
1255
1467
  }
1256
1468
  try {
1257
- await fs3.access(path13.join(root, "Cargo.toml"));
1469
+ await fs3.access(path14.join(root, "Cargo.toml"));
1258
1470
  facts.build ??= "cargo build";
1259
1471
  facts.test ??= "cargo test";
1260
1472
  facts.hints.push("Cargo.toml");
1261
1473
  } catch {
1262
1474
  }
1263
1475
  try {
1264
- await fs3.access(path13.join(root, "Makefile"));
1476
+ await fs3.access(path14.join(root, "Makefile"));
1265
1477
  facts.build ??= "make";
1266
1478
  facts.test ??= "make test";
1267
1479
  facts.hints.push("Makefile");
@@ -1398,7 +1610,9 @@ function buildCompactCommand(opts) {
1398
1610
  }
1399
1611
  const aggressive = args.trim() === "aggressive";
1400
1612
  const report = await opts.compactor.compact(ctx, { aggressive });
1401
- const msg = `Compaction: ${report.before} \u2192 ${report.after} tokens (${report.reductions.map((r) => `${r.phase}: ${r.saved}`).join(", ")})`;
1613
+ const reductions = report.reductions.map((r) => `${r.phase}: ${r.saved}`).join(", ");
1614
+ const repaired = report.repaired ? `; repaired ${report.repaired.removedToolUses.length} tool_use, ${report.repaired.removedToolResults.length} tool_result, ${report.repaired.removedMessages} empty messages` : "";
1615
+ const msg = `Compaction: ${report.before} -> ${report.after} tokens (${reductions})${repaired}`;
1402
1616
  opts.renderer.writeInfo(msg);
1403
1617
  return { message: msg };
1404
1618
  }
@@ -1412,15 +1626,68 @@ function buildContextCommand(opts) {
1412
1626
  help: [
1413
1627
  "Usage:",
1414
1628
  " /context Show counts: messages, est. tokens, tool calls, todos, read files.",
1415
- " /context detail As above, plus model, cwd, projectRoot, and the file list."
1629
+ " /context detail As above, plus model, cwd, projectRoot, and the file list.",
1630
+ " /context repair Repair orphan tool_use/tool_result blocks after manual compaction.",
1631
+ " /context mode List context-window modes.",
1632
+ " /context mode <id> Switch context-window mode for this session."
1416
1633
  ].join("\n"),
1417
1634
  async run(args, ctx) {
1635
+ const trimmed = args.trim();
1636
+ if (trimmed === "mode" || trimmed === "modes") {
1637
+ const active = readPolicy(ctx)?.id ?? "balanced";
1638
+ const msg2 = `${color.bold("Context Window Modes")}
1639
+ ${formatContextWindowModeList(active)}`;
1640
+ opts.renderer.write(`${msg2}
1641
+ `);
1642
+ return { message: msg2 };
1643
+ }
1644
+ if (trimmed === "repair") {
1645
+ const before = ctx.messages.length;
1646
+ const repaired = repairToolUseAdjacency(ctx.messages);
1647
+ if (repaired.report.changed) {
1648
+ ctx.state.replaceMessages(repaired.messages);
1649
+ }
1650
+ const msg2 = repaired.report.changed ? [
1651
+ `${color.green("Context repaired")}`,
1652
+ ` messages: ${before} -> ${ctx.messages.length}`,
1653
+ ` tool_use: removed ${repaired.report.removedToolUses.length}`,
1654
+ ` tool_result: removed ${repaired.report.removedToolResults.length}`,
1655
+ ` empty msgs: removed ${repaired.report.removedMessages}`
1656
+ ].join("\n") : "Context repair: no orphan tool_use/tool_result blocks found.";
1657
+ opts.renderer.write(`${msg2}
1658
+ `);
1659
+ return { message: msg2 };
1660
+ }
1661
+ if (trimmed.startsWith("mode ")) {
1662
+ const id = trimmed.slice("mode ".length).trim();
1663
+ const mode = getContextWindowMode(id);
1664
+ if (!mode) {
1665
+ const msg3 = `Unknown context mode "${id}". Use /context mode to list modes.`;
1666
+ opts.renderer.write(`${color.red(msg3)}
1667
+ `);
1668
+ return { message: msg3 };
1669
+ }
1670
+ const policy2 = resolveContextWindowPolicy({}, mode.id);
1671
+ ctx.meta["contextWindowMode"] = policy2.id;
1672
+ ctx.meta["contextWindowPolicy"] = policy2;
1673
+ const msg2 = [
1674
+ `${color.green("Context mode set:")} ${policy2.id} (${policy2.name})`,
1675
+ ` thresholds: warn ${pct(policy2.thresholds.warn)}, soft ${pct(policy2.thresholds.soft)}, hard ${pct(policy2.thresholds.hard)}`,
1676
+ ` preserve: last ${policy2.preserveK} user/assistant messages`,
1677
+ ` elide: old tool results >= ${policy2.eliseThreshold.toLocaleString()} tokens`
1678
+ ].join("\n");
1679
+ opts.renderer.write(`${msg2}
1680
+ `);
1681
+ return { message: msg2 };
1682
+ }
1418
1683
  const messages = ctx.messages;
1419
- const detailed = args.trim() === "detail";
1684
+ const detailed = trimmed === "detail";
1685
+ const policy = readPolicy(ctx);
1420
1686
  const lines = [
1421
1687
  `${color.bold("Context Window")}`,
1422
1688
  ` messages: ${messages.length} total (${countTurnPairs(messages)} user+assistant pairs)`,
1423
- ` tokens (\u2248): ${estimateTokens(messages).toLocaleString()} (chars \xF7 4 estimate)`,
1689
+ ` tokens (est): ${estimateTokens(messages).toLocaleString()} (chars / 4 estimate)`,
1690
+ ` mode: ${policy ? `${policy.id} (${policy.name})` : "balanced"}`,
1424
1691
  ` system prompt: ${ctx.systemPrompt.length} block${ctx.systemPrompt.length !== 1 ? "s" : ""}`,
1425
1692
  ` tools: ${countToolUses(messages)} calls made, ${countToolResults(messages)} results in history`,
1426
1693
  ` read files: ${ctx.readFiles.size} files`,
@@ -1428,6 +1695,7 @@ function buildContextCommand(opts) {
1428
1695
  ];
1429
1696
  if (detailed) {
1430
1697
  lines.push(
1698
+ ` thresholds: warn ${pct(policy?.thresholds.warn ?? 0.6)}, soft ${pct(policy?.thresholds.soft ?? 0.75)}, hard ${pct(policy?.thresholds.hard ?? 0.9)}`,
1431
1699
  ` model: ${ctx.model}`,
1432
1700
  ` cwd: ${ctx.cwd}`,
1433
1701
  ` projectRoot: ${ctx.projectRoot}`,
@@ -1442,6 +1710,13 @@ function buildContextCommand(opts) {
1442
1710
  }
1443
1711
  };
1444
1712
  }
1713
+ function readPolicy(ctx) {
1714
+ const policy = ctx.meta?.["contextWindowPolicy"];
1715
+ return policy && typeof policy === "object" ? policy : null;
1716
+ }
1717
+ function pct(n) {
1718
+ return `${Math.round(n * 100)}%`;
1719
+ }
1445
1720
 
1446
1721
  // src/slash-commands/diag-stats.ts
1447
1722
  function buildDiagCommand(opts) {
@@ -1643,8 +1918,8 @@ function buildInitCommand(opts) {
1643
1918
  description: "Scaffold .wrongstack/AGENTS.md in the current project.",
1644
1919
  async run(args, ctx) {
1645
1920
  const force = args.trim() === "--force";
1646
- const dir = path13.join(ctx.projectRoot, ".wrongstack");
1647
- const file = path13.join(dir, "AGENTS.md");
1921
+ const dir = path14.join(ctx.projectRoot, ".wrongstack");
1922
+ const file = path14.join(dir, "AGENTS.md");
1648
1923
  try {
1649
1924
  await fs3.access(file);
1650
1925
  if (!force) {
@@ -2043,13 +2318,13 @@ var MANIFESTS = [
2043
2318
  ];
2044
2319
  async function detectProjectKind(projectRoot) {
2045
2320
  try {
2046
- await fs3.access(path13.join(projectRoot, ".wrongstack", "AGENTS.md"));
2321
+ await fs3.access(path14.join(projectRoot, ".wrongstack", "AGENTS.md"));
2047
2322
  return "initialized";
2048
2323
  } catch {
2049
2324
  }
2050
2325
  for (const m of MANIFESTS) {
2051
2326
  try {
2052
- await fs3.access(path13.join(projectRoot, m));
2327
+ await fs3.access(path14.join(projectRoot, m));
2053
2328
  return "project";
2054
2329
  } catch {
2055
2330
  }
@@ -2057,8 +2332,8 @@ async function detectProjectKind(projectRoot) {
2057
2332
  return "empty";
2058
2333
  }
2059
2334
  async function scaffoldAgentsMd(projectRoot) {
2060
- const dir = path13.join(projectRoot, ".wrongstack");
2061
- const file = path13.join(dir, "AGENTS.md");
2335
+ const dir = path14.join(projectRoot, ".wrongstack");
2336
+ const file = path14.join(dir, "AGENTS.md");
2062
2337
  const facts = await detectProjectFacts(projectRoot);
2063
2338
  const body = renderAgentsTemplate(facts);
2064
2339
  await fs3.mkdir(dir, { recursive: true });
@@ -2071,7 +2346,7 @@ async function runProjectCheck(opts) {
2071
2346
  if (kind === "initialized") {
2072
2347
  renderer.write(
2073
2348
  `
2074
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path13.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
2349
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path14.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
2075
2350
  `
2076
2351
  );
2077
2352
  return true;
@@ -2367,14 +2642,14 @@ function summarize(value, name) {
2367
2642
  if (typeof v === "object" && v !== null) {
2368
2643
  const o = v;
2369
2644
  if (name === "edit") {
2370
- const path14 = typeof o["path"] === "string" ? o["path"] : "";
2645
+ const path15 = typeof o["path"] === "string" ? o["path"] : "";
2371
2646
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
2372
- return `${path14} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
2647
+ return `${path15} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
2373
2648
  }
2374
2649
  if (name === "write") {
2375
- const path14 = typeof o["path"] === "string" ? o["path"] : "";
2650
+ const path15 = typeof o["path"] === "string" ? o["path"] : "";
2376
2651
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
2377
- return bytes !== void 0 ? `${path14} ${bytes}B` : path14;
2652
+ return bytes !== void 0 ? `${path15} ${bytes}B` : path15;
2378
2653
  }
2379
2654
  if (typeof o["count"] === "number") {
2380
2655
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -3137,7 +3412,7 @@ var doctorCmd = async (_args, deps) => {
3137
3412
  }
3138
3413
  try {
3139
3414
  await fs3.mkdir(deps.paths.projectSessions, { recursive: true });
3140
- const probe = path13.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
3415
+ const probe = path14.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
3141
3416
  await fs3.writeFile(probe, "");
3142
3417
  await fs3.unlink(probe);
3143
3418
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -3240,8 +3515,8 @@ var exportCmd = async (args, deps) => {
3240
3515
  return 1;
3241
3516
  }
3242
3517
  if (output) {
3243
- await fs3.mkdir(path13.dirname(path13.resolve(deps.cwd, output)), { recursive: true });
3244
- await fs3.writeFile(path13.resolve(deps.cwd, output), rendered, "utf8");
3518
+ await fs3.mkdir(path14.dirname(path14.resolve(deps.cwd, output)), { recursive: true });
3519
+ await fs3.writeFile(path14.resolve(deps.cwd, output), rendered, "utf8");
3245
3520
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
3246
3521
  `);
3247
3522
  } else {
@@ -3302,8 +3577,8 @@ var initCmd = async (_args, deps) => {
3302
3577
  const config = { version: 1, provider: providerId, model: modelId };
3303
3578
  if (apiKey) config.apiKey = apiKey;
3304
3579
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(config, null, 2));
3305
- await fs3.mkdir(path13.join(deps.projectRoot, ".wrongstack"), { recursive: true });
3306
- const agentsFile = path13.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
3580
+ await fs3.mkdir(path14.join(deps.projectRoot, ".wrongstack"), { recursive: true });
3581
+ const agentsFile = path14.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
3307
3582
  try {
3308
3583
  await fs3.access(agentsFile);
3309
3584
  } catch {
@@ -3317,86 +3592,7 @@ var initCmd = async (_args, deps) => {
3317
3592
  deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
3318
3593
  return 0;
3319
3594
  };
3320
- var BUILT_IN_MCP = {
3321
- filesystem: {
3322
- name: "filesystem",
3323
- transport: "stdio",
3324
- command: "npx",
3325
- args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
3326
- permission: "confirm",
3327
- description: "Read, write, and navigate the local filesystem"
3328
- },
3329
- github: {
3330
- name: "github",
3331
- transport: "stdio",
3332
- command: "npx",
3333
- args: ["-y", "@modelcontextprotocol/server-github"],
3334
- permission: "confirm",
3335
- description: "GitHub API \u2014 issues, PRs, repos, search"
3336
- },
3337
- context7: {
3338
- name: "context7",
3339
- transport: "streamable-http",
3340
- url: "https://server.context7.ai/mcp",
3341
- permission: "confirm",
3342
- description: "Codebase-aware documentation and Q&A"
3343
- },
3344
- "brave-search": {
3345
- name: "brave-search",
3346
- transport: "stdio",
3347
- command: "npx",
3348
- args: ["-y", "@modelcontextprotocol/server-brave-search"],
3349
- permission: "confirm",
3350
- description: "Web search (Brave)"
3351
- },
3352
- block: {
3353
- name: "block",
3354
- transport: "stdio",
3355
- command: "npx",
3356
- args: ["-y", "@modelcontextprotocol/server-block"],
3357
- permission: "confirm",
3358
- description: "Postgres database via SQL"
3359
- },
3360
- everart: {
3361
- name: "everart",
3362
- transport: "stdio",
3363
- command: "npx",
3364
- args: ["-y", "@modelcontextprotocol/server-everart"],
3365
- permission: "confirm",
3366
- description: "AI image generation"
3367
- },
3368
- slack: {
3369
- name: "slack",
3370
- transport: "stdio",
3371
- command: "npx",
3372
- args: ["-y", "@modelcontextprotocol/server-slack"],
3373
- permission: "confirm",
3374
- description: "Slack messaging & channels"
3375
- },
3376
- aws: {
3377
- name: "aws",
3378
- transport: "stdio",
3379
- command: "npx",
3380
- args: ["-y", "@modelcontextprotocol/server-aws"],
3381
- permission: "confirm",
3382
- description: "AWS \u2014 EC2, S3, Lambda, IAM"
3383
- },
3384
- "google-maps": {
3385
- name: "google-maps",
3386
- transport: "stdio",
3387
- command: "npx",
3388
- args: ["-y", "@modelcontextprotocol/server-google-maps"],
3389
- permission: "confirm",
3390
- description: "Google Maps \u2014 directions, geocoding, places"
3391
- },
3392
- sentinel: {
3393
- name: "sentinel",
3394
- transport: "streamable-http",
3395
- url: "https://mcp.sentinel.ai",
3396
- permission: "deny",
3397
- description: "Security vulnerability scanning"
3398
- }
3399
- };
3595
+ var BUILT_IN_MCP = allServers();
3400
3596
  var mcpCmd = async (args, deps) => {
3401
3597
  const sub = args[0];
3402
3598
  if (!sub || sub === "list") {
@@ -3459,7 +3655,7 @@ async function addMcpServer(args, deps) {
3459
3655
  return 1;
3460
3656
  }
3461
3657
  const serverCfg = { ...factory };
3462
- if (!enable) serverCfg.enabled = false;
3658
+ serverCfg.enabled = enable;
3463
3659
  let existing = {};
3464
3660
  try {
3465
3661
  existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
@@ -3531,7 +3727,7 @@ var usageCmd = async (_args, deps) => {
3531
3727
  return 0;
3532
3728
  };
3533
3729
  var projectsCmd = async (_args, deps) => {
3534
- const projectsRoot = path13.join(deps.paths.globalRoot, "projects");
3730
+ const projectsRoot = path14.join(deps.paths.globalRoot, "projects");
3535
3731
  try {
3536
3732
  const entries = await fs3.readdir(projectsRoot);
3537
3733
  if (entries.length === 0) {
@@ -3541,7 +3737,7 @@ var projectsCmd = async (_args, deps) => {
3541
3737
  for (const hash of entries) {
3542
3738
  try {
3543
3739
  const meta = JSON.parse(
3544
- await fs3.readFile(path13.join(projectsRoot, hash, "meta.json"), "utf8")
3740
+ await fs3.readFile(path14.join(projectsRoot, hash, "meta.json"), "utf8")
3545
3741
  );
3546
3742
  deps.renderer.write(
3547
3743
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -3839,7 +4035,7 @@ function resolveBundledSkillsDir() {
3839
4035
  try {
3840
4036
  const req2 = createRequire(import.meta.url);
3841
4037
  const corePkg = req2.resolve("@wrongstack/core/package.json");
3842
- return path13.join(path13.dirname(corePkg), "skills");
4038
+ return path14.join(path14.dirname(corePkg), "skills");
3843
4039
  } catch {
3844
4040
  return void 0;
3845
4041
  }
@@ -3876,7 +4072,10 @@ async function boot(argv) {
3876
4072
  bundledDir: resolveBundledSkillsDir()
3877
4073
  });
3878
4074
  const toolRegistryForSubcmd = new ToolRegistry();
3879
- for (const t of builtinTools) toolRegistryForSubcmd.register(t);
4075
+ toolRegistryForSubcmd.registerAllOrThrow(
4076
+ [...builtinToolsPack.tools ?? []],
4077
+ builtinToolsPack.name
4078
+ );
3880
4079
  const code = await subcommands[first](positional.slice(1), {
3881
4080
  config,
3882
4081
  renderer,
@@ -4005,6 +4204,10 @@ async function runRepl(opts) {
4005
4204
  continue;
4006
4205
  }
4007
4206
  interrupts = 0;
4207
+ if (trimmed === "/image" || trimmed === "/paste-image" || raw === "\x1Bv") {
4208
+ await pasteClipboardImage(builder, opts);
4209
+ continue;
4210
+ }
4008
4211
  if (trimmed.startsWith("/")) {
4009
4212
  try {
4010
4213
  const res = await opts.slashRegistry.dispatch(trimmed, opts.agent.ctx);
@@ -4029,7 +4232,23 @@ async function runRepl(opts) {
4029
4232
  const startedAt = Date.now();
4030
4233
  const before = opts.tokenCounter?.total();
4031
4234
  const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
4032
- const result = await opts.agent.run(blocks, { signal: runCtrl.signal });
4235
+ const routed = blocks.some((block) => block.type === "image") ? await routeImagesForModel(blocks, {
4236
+ supportsVision: opts.supportsVision ? await opts.supportsVision() : opts.agent.ctx.provider.capabilities.vision,
4237
+ adapters: opts.visionAdapters ?? [],
4238
+ ctx: opts.agent.ctx,
4239
+ signal: runCtrl.signal,
4240
+ providerId: opts.agent.ctx.provider.id,
4241
+ model: opts.agent.ctx.model
4242
+ }) : { blocks, route: "none", convertedImages: 0 };
4243
+ if (routed.route === "adapter") {
4244
+ opts.renderer.write(
4245
+ color.dim(
4246
+ ` \u21B3 image analyzed via ${routed.adapterName ?? "vision adapter"} (${routed.convertedImages} image${routed.convertedImages === 1 ? "" : "s"})
4247
+ `
4248
+ )
4249
+ );
4250
+ }
4251
+ const result = await opts.agent.run(routed.blocks, { signal: runCtrl.signal });
4033
4252
  if (result.status === "aborted") {
4034
4253
  opts.renderer.writeWarning("Aborted.");
4035
4254
  } else if (result.status === "failed") {
@@ -4068,6 +4287,23 @@ ${color.dim(
4068
4287
  });
4069
4288
  }
4070
4289
  }
4290
+ async function pasteClipboardImage(builder, opts) {
4291
+ try {
4292
+ const img = await readClipboardImage();
4293
+ if (!img) {
4294
+ opts.renderer.write(color.dim(" no image on clipboard\n"));
4295
+ return;
4296
+ }
4297
+ const placeholder = await builder.appendImage(img.base64, img.mediaType);
4298
+ const kb = (img.bytes / 1024).toFixed(0);
4299
+ opts.renderer.write(color.dim(` \u21B3 ${placeholder} (PNG ${kb}KB)
4300
+ `));
4301
+ } catch (err) {
4302
+ opts.renderer.writeError(
4303
+ `Clipboard image error: ${err instanceof Error ? err.message : String(err)}`
4304
+ );
4305
+ }
4306
+ }
4071
4307
  async function readPossiblyMultiline(opts) {
4072
4308
  const firstPrompt = theme2.primary("\u203A ");
4073
4309
  const contPrompt = color.dim("\xB7 ");
@@ -4093,9 +4329,9 @@ var FILLED = "\u2588";
4093
4329
  var EMPTY = "\u2591";
4094
4330
  function renderContextChip(used, max) {
4095
4331
  const ratio = Math.max(0, Math.min(1, used / max));
4096
- const pct = Math.round(ratio * 100);
4332
+ const pct2 = Math.round(ratio * 100);
4097
4333
  const bar = renderProgress(ratio, 6);
4098
- return `${bar} ${pct}% (${fmtTok(used)}/${fmtTok(max)})`;
4334
+ return `${bar} ${pct2}% (${fmtTok(used)}/${fmtTok(max)})`;
4099
4335
  }
4100
4336
  function renderProgress(ratio, width) {
4101
4337
  const clamped = Math.max(0, Math.min(1, ratio));
@@ -4139,6 +4375,7 @@ async function execute(deps) {
4139
4375
  queueStore,
4140
4376
  context,
4141
4377
  stats,
4378
+ detachTodosCheckpoint,
4142
4379
  savedProviderCfg,
4143
4380
  resolvedProvider,
4144
4381
  getPickableProviders,
@@ -4149,6 +4386,15 @@ async function execute(deps) {
4149
4386
  } = deps;
4150
4387
  let code = 0;
4151
4388
  try {
4389
+ const visionAdapters = () => createToolVisionAdapters(agent.tools);
4390
+ const supportsVision = async () => {
4391
+ try {
4392
+ const caps = await capabilitiesFor(modelsRegistry, context.provider.id, context.model);
4393
+ return caps.vision;
4394
+ } catch {
4395
+ return context.provider.capabilities.vision;
4396
+ }
4397
+ };
4152
4398
  const promptFlag = typeof flags["prompt"] === "string" ? flags["prompt"] : void 0;
4153
4399
  if (promptFlag) {
4154
4400
  positional.unshift(promptFlag);
@@ -4233,6 +4479,8 @@ async function execute(deps) {
4233
4479
  slashRegistry,
4234
4480
  attachments,
4235
4481
  tokenCounter,
4482
+ visionAdapters,
4483
+ supportsVision,
4236
4484
  model: context.model,
4237
4485
  banner: !flags["no-banner"],
4238
4486
  queueStore,
@@ -4284,9 +4532,11 @@ async function execute(deps) {
4284
4532
  reader,
4285
4533
  slashRegistry,
4286
4534
  tokenCounter,
4535
+ visionAdapters,
4536
+ supportsVision,
4287
4537
  attachments,
4288
4538
  effectiveMaxContext,
4289
- projectName: path13.basename(projectRoot) || void 0
4539
+ projectName: path14.basename(projectRoot) || void 0
4290
4540
  });
4291
4541
  await webuiPromise;
4292
4542
  } else {
@@ -4296,13 +4546,16 @@ async function execute(deps) {
4296
4546
  reader,
4297
4547
  slashRegistry,
4298
4548
  tokenCounter,
4549
+ visionAdapters,
4550
+ supportsVision,
4299
4551
  attachments,
4300
4552
  effectiveMaxContext,
4301
- projectName: path13.basename(projectRoot) || void 0
4553
+ projectName: path14.basename(projectRoot) || void 0
4302
4554
  });
4303
4555
  }
4304
4556
  } finally {
4305
4557
  stats.render(renderer);
4558
+ await Promise.resolve(detachTodosCheckpoint?.()).catch(() => void 0);
4306
4559
  await mcpRegistry.stopAll();
4307
4560
  await session.append({
4308
4561
  type: "session_end",
@@ -4555,7 +4808,7 @@ var MultiAgentHost = class {
4555
4808
  model: opts?.model,
4556
4809
  tools: opts?.tools
4557
4810
  };
4558
- const transcriptPath = this.sessionFactory ? path13.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
4811
+ const transcriptPath = this.sessionFactory ? path14.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
4559
4812
  if (this.director) {
4560
4813
  const subagentId = await this.director.spawn(subagentConfig);
4561
4814
  const taskId2 = randomUUID();
@@ -4707,16 +4960,16 @@ var MultiAgentHost = class {
4707
4960
  }
4708
4961
  this.opts.directorMode = true;
4709
4962
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
4710
- this.opts.manifestPath = path13.join(this.opts.fleetRoot, "fleet.json");
4963
+ this.opts.manifestPath = path14.join(this.opts.fleetRoot, "fleet.json");
4711
4964
  }
4712
4965
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
4713
- this.opts.sharedScratchpadPath = path13.join(this.opts.fleetRoot, "shared");
4966
+ this.opts.sharedScratchpadPath = path14.join(this.opts.fleetRoot, "shared");
4714
4967
  }
4715
4968
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
4716
- this.opts.sessionsRoot = path13.join(this.opts.fleetRoot, "subagents");
4969
+ this.opts.sessionsRoot = path14.join(this.opts.fleetRoot, "subagents");
4717
4970
  }
4718
4971
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
4719
- this.opts.stateCheckpointPath = path13.join(this.opts.fleetRoot, "director-state.json");
4972
+ this.opts.stateCheckpointPath = path14.join(this.opts.fleetRoot, "director-state.json");
4720
4973
  }
4721
4974
  await this.ensureDirector();
4722
4975
  return this.director ?? null;
@@ -4836,11 +5089,11 @@ var SessionStats = class {
4836
5089
  if (e.name === "bash") this.bashCommands++;
4837
5090
  else if (e.name === "fetch") this.fetches++;
4838
5091
  if (!e.ok) return;
4839
- const path14 = typeof input?.path === "string" ? input.path : void 0;
4840
- if (e.name === "read" && path14) this.readPaths.add(path14);
4841
- else if (e.name === "edit" && path14) this.editedPaths.add(path14);
4842
- else if (e.name === "write" && path14) {
4843
- this.writtenPaths.add(path14);
5092
+ const path15 = typeof input?.path === "string" ? input.path : void 0;
5093
+ if (e.name === "read" && path15) this.readPaths.add(path15);
5094
+ else if (e.name === "edit" && path15) this.editedPaths.add(path15);
5095
+ else if (e.name === "write" && path15) {
5096
+ this.writtenPaths.add(path15);
4844
5097
  const content = typeof input?.content === "string" ? input.content : "";
4845
5098
  this.bytesWritten += Buffer.byteLength(content, "utf8");
4846
5099
  }
@@ -4877,9 +5130,9 @@ var SessionStats = class {
4877
5130
  );
4878
5131
  const cache = this.tokenCounter.cacheStats();
4879
5132
  if (cache.readTokens > 0 || cache.writeTokens > 0) {
4880
- const pct = (cache.hitRatio * 100).toFixed(1);
5133
+ const pct2 = (cache.hitRatio * 100).toFixed(1);
4881
5134
  lines.push(
4882
- ` Prompt cache: ${pct}% hit ${color.dim(`(${fmtTok(cache.readTokens)} read / ${fmtTok(cache.writeTokens)} write)`)}`
5135
+ ` Prompt cache: ${pct2}% hit ${color.dim(`(${fmtTok(cache.readTokens)} read / ${fmtTok(cache.writeTokens)} write)`)}`
4883
5136
  );
4884
5137
  }
4885
5138
  if (cost.total > 0) {
@@ -5006,10 +5259,10 @@ var Spinner = class {
5006
5259
  };
5007
5260
  function renderContextChip2(ctx) {
5008
5261
  const ratio = Math.max(0, Math.min(1, ctx.used / ctx.max));
5009
- const pct = Math.round(ratio * 100);
5262
+ const pct2 = Math.round(ratio * 100);
5010
5263
  const chipColor = ratio >= 0.85 ? color.red : ratio >= 0.65 ? color.yellow : color.cyan;
5011
5264
  const bar = renderProgress2(ratio, 8);
5012
- return color.dim("ctx ") + chipColor(bar) + chipColor(` ${pct}%`) + color.dim(` (${fmtTok(ctx.used)}/${fmtTok(ctx.max)})`);
5265
+ return color.dim("ctx ") + chipColor(bar) + chipColor(` ${pct2}%`) + color.dim(` (${fmtTok(ctx.used)}/${fmtTok(ctx.max)})`);
5013
5266
  }
5014
5267
  function renderProgress2(ratio, width) {
5015
5268
  const clamped = Math.max(0, Math.min(1, ratio));
@@ -5023,7 +5276,7 @@ function resolveBundledSkillsDir2() {
5023
5276
  try {
5024
5277
  const req2 = createRequire(import.meta.url);
5025
5278
  const corePkg = req2.resolve("@wrongstack/core/package.json");
5026
- return path13.join(path13.dirname(corePkg), "skills");
5279
+ return path14.join(path14.dirname(corePkg), "skills");
5027
5280
  } catch {
5028
5281
  return void 0;
5029
5282
  }
@@ -5046,52 +5299,35 @@ async function main(argv) {
5046
5299
  logger
5047
5300
  } = ctx;
5048
5301
  const pathResolver = new DefaultPathResolver(cwd);
5049
- const savedProviderCfg = config.providers?.[config.provider];
5050
- let resolvedProvider = await modelsRegistry.getProvider(config.provider).catch(() => void 0);
5051
- if (!resolvedProvider && savedProviderCfg?.type && savedProviderCfg.type !== config.provider) {
5052
- resolvedProvider = await modelsRegistry.getProvider(savedProviderCfg.type).catch(() => void 0);
5053
- }
5054
- if (!resolvedProvider) {
5055
- if (!savedProviderCfg?.family) {
5056
- logger.warn(
5057
- `Provider "${config.provider}" not found in models.dev. Continuing with raw config.`
5058
- );
5059
- }
5060
- } else if (resolvedProvider.family === "unsupported" && !savedProviderCfg?.family) {
5061
- process.stderr.write(
5062
- `Provider "${config.provider}" uses an unsupported wire family (${resolvedProvider.npm}). Install a plugin to enable it, or pick a different provider.
5063
- `
5064
- );
5302
+ const container = createDefaultContainer({
5303
+ config,
5304
+ wpaths,
5305
+ logger,
5306
+ modelsRegistry,
5307
+ permission: { yolo: config.yolo, promptDelegate: makePromptDelegate(reader) },
5308
+ compactor: { preserveK: config.context.preserveK, eliseThreshold: config.context.eliseThreshold },
5309
+ bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
5310
+ });
5311
+ const configStore = container.resolve(TOKENS.ConfigStore);
5312
+ container.bind(TOKENS.PathResolver, () => pathResolver);
5313
+ container.bind(TOKENS.Renderer, () => renderer);
5314
+ container.bind(TOKENS.InputReader, () => reader);
5315
+ const modeStore = container.resolve(TOKENS.ModeStore);
5316
+ const activeMode = await modeStore.getActiveMode();
5317
+ let resolvedProvider;
5318
+ let providerRegistry;
5319
+ let provider;
5320
+ try {
5321
+ const result = await setupProvider({ config, modelsRegistry, logger });
5322
+ resolvedProvider = result.resolvedProvider;
5323
+ providerRegistry = result.providerRegistry;
5324
+ provider = result.provider;
5325
+ } catch (err) {
5326
+ process.stderr.write(`${err instanceof Error ? err.message : err}
5327
+ `);
5065
5328
  await reader.close();
5066
5329
  return 2;
5067
5330
  }
5068
- const container = new Container();
5069
- const configStore = new DefaultConfigStore(config);
5070
- container.bind(TOKENS.ConfigStore, () => configStore);
5071
- container.bind(TOKENS.Logger, () => logger);
5072
- container.bind(TOKENS.PathResolver, () => pathResolver);
5073
- container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());
5074
- container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());
5075
- container.bind(TOKENS.ErrorHandler, () => new DefaultErrorHandler());
5076
- container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
5077
- container.bind(
5078
- TOKENS.TokenCounter,
5079
- () => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider })
5080
- );
5081
- const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
5082
- container.bind(TOKENS.ModeStore, () => modeStore);
5083
- container.bind(
5084
- TOKENS.SessionStore,
5085
- () => new DefaultSessionStore({ dir: wpaths.projectSessions })
5086
- );
5087
- const memoryStore = new DefaultMemoryStore({ paths: wpaths });
5088
- container.bind(TOKENS.MemoryStore, () => memoryStore);
5089
- const skillLoader = new DefaultSkillLoader({
5090
- paths: wpaths,
5091
- bundledDir: config.features.skills ? resolveBundledSkillsDir2() : void 0
5092
- });
5093
- container.bind(TOKENS.SkillLoader, () => skillLoader);
5094
- const activeMode = await modeStore.getActiveMode();
5095
5331
  const modeId = activeMode?.id ?? "default";
5096
5332
  const modePrompt = activeMode?.prompt ?? "";
5097
5333
  const resolvedModel = await modelsRegistry.getModel(config.provider, config.model);
@@ -5101,6 +5337,8 @@ async function main(argv) {
5101
5337
  supportsVision: resolvedModel.capabilities.vision,
5102
5338
  supportsReasoning: resolvedModel.capabilities.reasoning
5103
5339
  } : void 0;
5340
+ const memoryStore = container.resolve(TOKENS.MemoryStore);
5341
+ const skillLoader = container.resolve(TOKENS.SkillLoader);
5104
5342
  const sessionRef = {};
5105
5343
  container.bind(
5106
5344
  TOKENS.SystemPromptBuilder,
@@ -5111,48 +5349,11 @@ async function main(argv) {
5111
5349
  modeId,
5112
5350
  modePrompt,
5113
5351
  modelCapabilities,
5114
- // Reads the ref each time returns undefined until the session
5115
- // is created, then resolves to the per-session plan JSON path.
5116
- planPath: () => sessionRef.current ? path13.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
5117
- })
5118
- );
5119
- container.bind(TOKENS.Renderer, () => renderer);
5120
- container.bind(TOKENS.InputReader, () => reader);
5121
- container.bind(
5122
- TOKENS.PermissionPolicy,
5123
- () => new DefaultPermissionPolicy({
5124
- trustFile: wpaths.projectTrust,
5125
- yolo: config.yolo,
5126
- promptDelegate: makePromptDelegate(reader)
5127
- })
5128
- );
5129
- container.bind(
5130
- TOKENS.Compactor,
5131
- () => new HybridCompactor({
5132
- preserveK: config.context.preserveK,
5133
- eliseThreshold: config.context.eliseThreshold
5352
+ planPath: () => sessionRef.current ? path14.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
5134
5353
  })
5135
5354
  );
5136
- const providerRegistry = new ProviderRegistry();
5137
- if (config.features.modelsRegistry) {
5138
- try {
5139
- const factories = await buildProviderFactoriesFromRegistry({
5140
- registry: modelsRegistry,
5141
- log: logger
5142
- });
5143
- for (const f of factories) providerRegistry.register(f);
5144
- } catch (err) {
5145
- process.stderr.write(
5146
- `Failed to load models.dev registry: ${err instanceof Error ? err.message : err}
5147
- Try \`wstack models refresh\` once you have network access, or run with --no-features.
5148
- `
5149
- );
5150
- await reader.close();
5151
- return 2;
5152
- }
5153
- }
5154
5355
  const toolRegistry = new ToolRegistry();
5155
- for (const t of builtinTools) toolRegistry.register(t);
5356
+ toolRegistry.registerAllOrThrow([...builtinToolsPack.tools ?? []], builtinToolsPack.name);
5156
5357
  toolRegistry.registerDefault(
5157
5358
  createContextManagerTool({ compactor: container.resolve(TOKENS.Compactor) })
5158
5359
  );
@@ -5193,7 +5394,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5193
5394
  const dumpMetrics = () => {
5194
5395
  if (!metricsSink) return;
5195
5396
  try {
5196
- const out = path13.join(wpaths.projectSessions, "metrics.json");
5397
+ const out = path14.join(wpaths.projectSessions, "metrics.json");
5197
5398
  const snap = metricsSink.snapshot();
5198
5399
  writeFileSync(out, JSON.stringify(snap, null, 2));
5199
5400
  } catch {
@@ -5278,27 +5479,6 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5278
5479
  process.stderr.write(color.red(` \u2717 ${p.description}
5279
5480
  `));
5280
5481
  });
5281
- const providerConfig = config.providers?.[config.provider] ?? {
5282
- type: config.provider,
5283
- apiKey: config.apiKey,
5284
- baseUrl: config.baseUrl
5285
- };
5286
- let provider;
5287
- try {
5288
- const cfgWithType = { ...providerConfig, type: config.provider };
5289
- if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
5290
- provider = providerRegistry.create(cfgWithType);
5291
- } else {
5292
- provider = makeProviderFromConfig(config.provider, cfgWithType);
5293
- }
5294
- } catch (err) {
5295
- process.stderr.write(
5296
- `Failed to create provider: ${err instanceof Error ? err.message : err}
5297
- `
5298
- );
5299
- await reader.close();
5300
- return 2;
5301
- }
5302
5482
  const promptBuilder = container.resolve(TOKENS.SystemPromptBuilder);
5303
5483
  const systemPrompt = await promptBuilder.build({
5304
5484
  cwd,
@@ -5308,59 +5488,29 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5308
5488
  model: config.model
5309
5489
  });
5310
5490
  const sessionStore = container.resolve(TOKENS.SessionStore);
5311
- let resumeId = typeof flags["resume"] === "string" ? flags["resume"] : void 0;
5312
- const recoveryLock = new RecoveryLock({
5313
- dir: wpaths.projectSessions,
5314
- sessionStore
5491
+ const tokenCounter = container.resolve(TOKENS.TokenCounter);
5492
+ const sessResult = await setupSession({
5493
+ config: { model: config.model, provider: config.provider },
5494
+ wpaths,
5495
+ projectRoot,
5496
+ cwd,
5497
+ sessionStore,
5498
+ systemPrompt,
5499
+ provider,
5500
+ tokenCounter,
5501
+ renderer,
5502
+ flags,
5503
+ onRecovery: (abandoned, autoRecover) => promptRecovery(reader, renderer, abandoned, autoRecover)
5315
5504
  });
5316
- if (!resumeId && !flags["no-recovery"]) {
5317
- const abandoned = await recoveryLock.checkAbandoned();
5318
- if (abandoned && abandoned.messageCount > 0) {
5319
- const choice = await promptRecovery(reader, renderer, abandoned, !!flags["recover"]);
5320
- if (choice === "resume") {
5321
- resumeId = abandoned.sessionId;
5322
- } else if (choice === "delete") {
5323
- await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
5324
- await recoveryLock.clear();
5325
- } else {
5326
- await recoveryLock.clear();
5327
- }
5328
- } else if (abandoned) {
5329
- await sessionStore.delete(abandoned.sessionId).catch(() => void 0);
5330
- await recoveryLock.clear();
5331
- }
5332
- }
5333
- let session;
5334
- let restoredMessages = [];
5335
- if (resumeId) {
5336
- try {
5337
- const resumed = await sessionStore.resume(resumeId);
5338
- session = resumed.writer;
5339
- restoredMessages = resumed.data.messages;
5340
- renderer.writeInfo(
5341
- `Resumed session ${resumed.data.metadata.id} \u2014 ${restoredMessages.length} messages, ${resumed.data.usage.input + resumed.data.usage.output} tokens used previously.`
5342
- );
5343
- } catch (err) {
5344
- renderer.writeError(`Resume failed: ${err instanceof Error ? err.message : String(err)}`);
5345
- return 2;
5346
- }
5347
- } else {
5348
- session = await sessionStore.create({
5349
- id: "",
5350
- title: "",
5351
- model: config.model,
5352
- provider: config.provider
5353
- });
5354
- }
5505
+ const session = sessResult.session;
5355
5506
  sessionRef.current = session;
5356
- await recoveryLock.write(session.id).catch(() => void 0);
5357
- const attachments = new DefaultAttachmentStore({
5358
- spoolDir: path13.join(wpaths.projectSessions, session.id, "attachments")
5359
- });
5360
- const queueStore = new QueueStore({
5361
- dir: path13.join(wpaths.projectSessions, session.id)
5362
- });
5363
- const tokenCounter = container.resolve(TOKENS.TokenCounter);
5507
+ sessResult.restoredMessages;
5508
+ const context = sessResult.context;
5509
+ const attachments = sessResult.attachments;
5510
+ const recoveryLock = sessResult.recoveryLock;
5511
+ const queueStore = sessResult.queueStore;
5512
+ const planPath = sessResult.planPath;
5513
+ const detachTodosCheckpoint = sessResult.detachTodosCheckpoint;
5364
5514
  const stats = new SessionStats(events, tokenCounter);
5365
5515
  const errorRing = [];
5366
5516
  events.on("error", (e) => {
@@ -5370,150 +5520,15 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5370
5520
  errorRing.push({ ts: (/* @__PURE__ */ new Date()).toISOString(), phase: e.phase, code, message });
5371
5521
  if (errorRing.length > 5) errorRing.shift();
5372
5522
  });
5373
- const ctxSignal = new AbortController().signal;
5374
- const context = new Context({
5375
- systemPrompt,
5376
- provider,
5377
- session,
5378
- signal: ctxSignal,
5379
- tokenCounter,
5380
- cwd,
5381
- projectRoot,
5382
- model: config.model
5383
- });
5384
- if (restoredMessages.length > 0) {
5385
- context.state.replaceMessages(restoredMessages);
5386
- }
5387
- const todosCheckpointPath = path13.join(wpaths.projectSessions, `${session.id}.todos.json`);
5388
- if (resumeId) {
5389
- try {
5390
- const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
5391
- if (restoredTodos && restoredTodos.length > 0) {
5392
- context.state.replaceTodos(restoredTodos);
5393
- renderer.writeInfo(
5394
- `Restored ${restoredTodos.length} todo${restoredTodos.length === 1 ? "" : "s"} from previous run.`
5395
- );
5396
- }
5397
- } catch {
5398
- }
5399
- }
5400
- attachTodosCheckpoint(
5401
- context.state,
5402
- todosCheckpointPath,
5403
- session.id
5404
- );
5405
- const planPath = path13.join(wpaths.projectSessions, `${session.id}.plan.json`);
5406
- context.state.setMeta("plan.path", planPath);
5407
- if (resumeId) {
5408
- try {
5409
- const fleetRoot2 = path13.join(wpaths.projectSessions, session.id);
5410
- const dirState = await loadDirectorState(path13.join(fleetRoot2, "director-state.json"));
5411
- if (dirState) {
5412
- const tCounts = {};
5413
- for (const t of dirState.tasks) {
5414
- tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
5415
- }
5416
- const summary = Object.entries(tCounts).map(([k, v]) => `${v} ${k}`).join(", ");
5417
- renderer.writeInfo(
5418
- `Prior fleet state: ${dirState.subagents.length} subagent${dirState.subagents.length === 1 ? "" : "s"}, tasks ${summary || "(none)"}.`
5419
- );
5420
- }
5421
- } catch {
5422
- }
5423
- try {
5424
- const plan = await loadPlan(planPath);
5425
- if (plan && plan.items.length > 0) {
5426
- const open = plan.items.filter((p) => p.status !== "done").length;
5427
- const done = plan.items.length - open;
5428
- renderer.writeInfo(
5429
- `Plan: ${plan.items.length} item${plan.items.length === 1 ? "" : "s"} (${open} open, ${done} done). Use /plan to review.`
5430
- );
5431
- }
5432
- } catch {
5433
- }
5434
- }
5435
- const pipelines = createDefaultPipelines();
5436
- const installBoundary = (p) => {
5437
- p.setErrorHandler((ev) => {
5438
- const fromPlugin = !!ev.owner && ev.owner !== "core";
5439
- logger.error(
5440
- `Pipeline middleware "${ev.middleware}" crashed (owner=${ev.owner ?? "unknown"}); ${fromPlugin ? "swallowed" : "rethrown"}`,
5441
- ev.err
5442
- );
5443
- events.emit("error", {
5444
- err: ev.err instanceof Error ? ev.err : new Error(String(ev.err)),
5445
- phase: `pipeline:${ev.middleware}`
5446
- });
5447
- return fromPlugin ? "swallow" : "rethrow";
5448
- });
5449
- };
5450
- installBoundary(pipelines.request);
5451
- installBoundary(pipelines.response);
5452
- installBoundary(pipelines.toolCall);
5453
- installBoundary(pipelines.userInput);
5454
- installBoundary(pipelines.assistantOutput);
5455
- installBoundary(pipelines.contextWindow);
5523
+ const pipelines = setupPipelines({ events, logger });
5456
5524
  const compactor = container.resolve(TOKENS.Compactor);
5457
- const resolvedCaps = await capabilitiesFor(modelsRegistry, config.provider, context.model).catch(
5458
- () => void 0
5459
- );
5460
- const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
5525
+ const effectiveMaxContext = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
5461
5526
  const updateSpinnerContext = () => {
5462
5527
  if (effectiveMaxContext > 0 && lastInputTokens > 0) {
5463
5528
  spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
5464
- } else {
5465
- spinner.setContext(void 0);
5466
- }
5529
+ } else spinner.setContext(void 0);
5467
5530
  };
5468
- if (config.context.autoCompact !== false) {
5469
- const autoCompactor = new AutoCompactionMiddleware(
5470
- compactor,
5471
- effectiveMaxContext,
5472
- (ctx2) => {
5473
- const msgs = ctx2.messages;
5474
- let total = 0;
5475
- for (const m of msgs) {
5476
- if (typeof m.content === "string") total += Math.ceil(m.content.length / 4);
5477
- else if (Array.isArray(m.content)) {
5478
- for (const b of m.content) {
5479
- if (b.type === "text") total += Math.ceil(b.text.length / 4);
5480
- else if (b.type === "tool_use" || b.type === "tool_result") {
5481
- total += Math.ceil(JSON.stringify(b).length / 4);
5482
- }
5483
- }
5484
- }
5485
- }
5486
- return total;
5487
- },
5488
- {
5489
- warn: config.context.warnThreshold,
5490
- soft: config.context.softThreshold,
5491
- hard: config.context.hardThreshold
5492
- },
5493
- {
5494
- aggressiveOn: "soft",
5495
- failureMode: "throw_on_hard",
5496
- events
5497
- }
5498
- );
5499
- pipelines.contextWindow.use({
5500
- name: "AutoCompaction",
5501
- handler: autoCompactor.handler()
5502
- });
5503
- }
5504
- const agent = new Agent({
5505
- container,
5506
- tools: toolRegistry,
5507
- providers: providerRegistry,
5508
- events,
5509
- pipelines,
5510
- context,
5511
- maxIterations: config.tools.maxIterations,
5512
- iterationTimeoutMs: config.tools.iterationTimeoutMs,
5513
- executionStrategy: config.tools.defaultExecutionStrategy,
5514
- perIterationOutputCapBytes: config.tools.perIterationOutputCapBytes,
5515
- confirmAwaiter: makeConfirmAwaiter(reader)
5516
- });
5531
+ const agent = createAgent({ container, tools: toolRegistry, providers: providerRegistry, events, pipelines, context, config, confirmAwaiter: makeConfirmAwaiter(reader) });
5517
5532
  const mcpRegistry = new MCPRegistry({ toolRegistry, events, log: logger });
5518
5533
  if (config.features.mcp) {
5519
5534
  for (const cfg of Object.values(config.mcpServers ?? {})) {
@@ -5554,7 +5569,14 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5554
5569
  slashCommandRegistry: slashRegistry,
5555
5570
  mcpRegistry,
5556
5571
  config,
5557
- log: logger
5572
+ log: logger,
5573
+ extensions: agent.extensions,
5574
+ sessionWriter: {
5575
+ transcriptPath: context.session.transcriptPath,
5576
+ append: (e) => context.session.append(e)
5577
+ },
5578
+ metricsSink,
5579
+ configStore
5558
5580
  })
5559
5581
  });
5560
5582
  }
@@ -5579,12 +5601,12 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5579
5601
  };
5580
5602
  const directorMode = flags["director"] === true;
5581
5603
  let director = null;
5582
- const fleetRoot = directorMode ? path13.join(wpaths.projectSessions, session.id) : void 0;
5583
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path13.join(fleetRoot, "fleet.json") : void 0;
5584
- const sharedScratchpadPath = directorMode ? path13.join(fleetRoot, "shared") : void 0;
5585
- const subagentSessionsRoot = directorMode ? path13.join(fleetRoot, "subagents") : void 0;
5586
- const stateCheckpointPath = directorMode ? path13.join(fleetRoot, "director-state.json") : void 0;
5587
- const fleetRootForPromotion = path13.join(wpaths.projectSessions, session.id);
5604
+ const fleetRoot = directorMode ? path14.join(wpaths.projectSessions, session.id) : void 0;
5605
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path14.join(fleetRoot, "fleet.json") : void 0;
5606
+ const sharedScratchpadPath = directorMode ? path14.join(fleetRoot, "shared") : void 0;
5607
+ const subagentSessionsRoot = directorMode ? path14.join(fleetRoot, "subagents") : void 0;
5608
+ const stateCheckpointPath = directorMode ? path14.join(fleetRoot, "director-state.json") : void 0;
5609
+ const fleetRootForPromotion = path14.join(wpaths.projectSessions, session.id);
5588
5610
  const multiAgentHost = new MultiAgentHost(
5589
5611
  {
5590
5612
  container,
@@ -5736,7 +5758,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5736
5758
  return `Unknown fleet action: ${action}`;
5737
5759
  },
5738
5760
  onFleetLog: async (subagentId, mode) => {
5739
- const subagentsRoot = path13.join(fleetRootForPromotion, "subagents");
5761
+ const subagentsRoot = path14.join(fleetRootForPromotion, "subagents");
5740
5762
  let runDirs;
5741
5763
  try {
5742
5764
  runDirs = await fs3.readdir(subagentsRoot);
@@ -5745,7 +5767,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5745
5767
  }
5746
5768
  const found = [];
5747
5769
  for (const runId of runDirs) {
5748
- const runDir = path13.join(subagentsRoot, runId);
5770
+ const runDir = path14.join(subagentsRoot, runId);
5749
5771
  let files;
5750
5772
  try {
5751
5773
  files = await fs3.readdir(runDir);
@@ -5754,7 +5776,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5754
5776
  }
5755
5777
  for (const f of files) {
5756
5778
  if (!f.endsWith(".jsonl")) continue;
5757
- const full = path13.join(runDir, f);
5779
+ const full = path14.join(runDir, f);
5758
5780
  try {
5759
5781
  const stat2 = await fs3.stat(full);
5760
5782
  found.push({
@@ -5851,7 +5873,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5851
5873
  }
5852
5874
  const dir = await multiAgentHost.ensureDirector();
5853
5875
  if (!dir) return "Director is not available.";
5854
- const dirStatePath = path13.join(fleetRootForPromotion, "director-state.json");
5876
+ const dirStatePath = path14.join(fleetRootForPromotion, "director-state.json");
5855
5877
  const prior = await loadDirectorState(dirStatePath);
5856
5878
  if (!prior) {
5857
5879
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -5922,9 +5944,9 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5922
5944
  for (const tool of director2.tools(FLEET_ROSTER)) {
5923
5945
  toolRegistry.register(tool);
5924
5946
  }
5925
- const mp = path13.join(fleetRootForPromotion, "fleet.json");
5926
- const sp = path13.join(fleetRootForPromotion, "shared");
5927
- const ss = path13.join(fleetRootForPromotion, "subagents");
5947
+ const mp = path14.join(fleetRootForPromotion, "fleet.json");
5948
+ const sp = path14.join(fleetRootForPromotion, "shared");
5949
+ const ss = path14.join(fleetRootForPromotion, "subagents");
5928
5950
  const lines = [
5929
5951
  `${color.green("\u2713")} Promoted to director mode.`,
5930
5952
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -5967,6 +5989,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5967
5989
  onStats: () => stats.format()
5968
5990
  });
5969
5991
  for (const cmd of slashCmds) slashRegistry.register(cmd);
5992
+ const savedProviderCfg = config.providers?.[config.provider];
5970
5993
  return execute({
5971
5994
  agent,
5972
5995
  events,
@@ -5988,6 +6011,7 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
5988
6011
  queueStore,
5989
6012
  context,
5990
6013
  stats,
6014
+ detachTodosCheckpoint,
5991
6015
  savedProviderCfg,
5992
6016
  resolvedProvider: resolvedProvider ?? void 0,
5993
6017
  getPickableProviders: () => buildPickableProviders(modelsRegistry, config),