@wrongstack/cli 0.7.0 → 0.7.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
@@ -3,11 +3,11 @@ import * as path23 from 'path';
3
3
  import { join } from 'path';
4
4
  import * as fsp2 from 'fs/promises';
5
5
  import { readdir, readFile } from 'fs/promises';
6
- import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
6
+ import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
7
7
  import { createRequire } from 'module';
8
8
  import * as os6 from 'os';
9
9
  import os6__default from 'os';
10
- import * as crypto from 'crypto';
10
+ import * as crypto2 from 'crypto';
11
11
  import { randomUUID } from 'crypto';
12
12
  import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
13
13
  import { WebSocketServer, WebSocket } from 'ws';
@@ -18,6 +18,9 @@ import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
18
18
  import * as readline from 'readline';
19
19
  import { spawn } from 'child_process';
20
20
  import { SkillInstaller } from '@wrongstack/core/skills';
21
+ import { WrongStackACPServer } from '@wrongstack/acp/agent';
22
+ import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
23
+ import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
21
24
  import { allServers } from '@wrongstack/core/infrastructure';
22
25
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
23
26
  import { ToolExecutor } from '@wrongstack/core/execution';
@@ -1244,12 +1247,12 @@ ${lines.join("\n")}`
1244
1247
  if (analysis.bottlenecks.length > 0) {
1245
1248
  lines.push("");
1246
1249
  lines.push(` \u{1F6AB} Bottlenecks (blocking most downstream):`);
1247
- analysis.bottlenecks.forEach((bt) => {
1250
+ for (const bt of analysis.bottlenecks) {
1248
1251
  const node = graph.nodes.get(bt.taskId);
1249
1252
  if (node) {
1250
1253
  lines.push(` \u2022 ${node.title} (blocks ${bt.blockedCount} task(s))`);
1251
1254
  }
1252
- });
1255
+ }
1253
1256
  }
1254
1257
  if (analysis.parallelGroups.length > 0) {
1255
1258
  lines.push("");
@@ -1262,12 +1265,12 @@ ${lines.join("\n")}`
1262
1265
  if (analysis.readyTasks.length > 0) {
1263
1266
  lines.push("");
1264
1267
  lines.push(` \u2705 Ready to start now:`);
1265
- analysis.readyTasks.forEach((taskId) => {
1268
+ for (const taskId of analysis.readyTasks) {
1266
1269
  const node = graph.nodes.get(taskId);
1267
1270
  if (node) {
1268
1271
  lines.push(` \u2022 ${node.title}`);
1269
1272
  }
1270
- });
1273
+ }
1271
1274
  }
1272
1275
  lines.push(`\u2570${"\u2500".repeat(55)}\u256F`);
1273
1276
  return { message: lines.join("\n") };
@@ -1468,7 +1471,10 @@ var init_sdd = __esm({
1468
1471
  return Date.now() - this.phaseStartTime;
1469
1472
  }
1470
1473
  getVersioning() {
1471
- return this.versioning ?? (this.versioning = new SpecVersioning());
1474
+ if (this.versioning === null) {
1475
+ this.versioning = new SpecVersioning();
1476
+ }
1477
+ return this.versioning;
1472
1478
  }
1473
1479
  clearTaskState() {
1474
1480
  this.taskStore = null;
@@ -1554,7 +1560,7 @@ function currentVersion() {
1554
1560
  return "dev";
1555
1561
  }
1556
1562
  function isNewer(a, b) {
1557
- const parse = (v) => v.replace(/^v/i, "").split(".").map((p) => parseInt(p, 10) || 0);
1563
+ const parse = (v) => v.replace(/^v/i, "").split(".").map((p) => Number.parseInt(p, 10) || 0);
1558
1564
  const [ap, bp] = [parse(a), parse(b)];
1559
1565
  for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
1560
1566
  const ai = ap[i] ?? 0;
@@ -1663,7 +1669,7 @@ async function runWebUI(opts) {
1663
1669
  const port = opts.port ?? 3457;
1664
1670
  const clients = /* @__PURE__ */ new Map();
1665
1671
  let abortController = null;
1666
- const authToken = crypto.randomBytes(16).toString("hex");
1672
+ const authToken = crypto2.randomBytes(16).toString("hex");
1667
1673
  const wss = new WebSocketServer({ port, host: "127.0.0.1", maxPayload: 1 * 1024 * 1024 });
1668
1674
  console.log(`[WebUI] WebSocket server starting on ws://127.0.0.1:${port}`);
1669
1675
  const eventUnsubscribers = [];
@@ -2244,6 +2250,35 @@ ${diff}`;
2244
2250
  }
2245
2251
  return "chore: update";
2246
2252
  }
2253
+ function makeProviderClassifier(provider, model) {
2254
+ return makeLLMClassifier(async (prompt) => {
2255
+ const ctrl = new AbortController();
2256
+ const timeout = setTimeout(() => ctrl.abort(), 15e3);
2257
+ try {
2258
+ const resp = await provider.complete(
2259
+ {
2260
+ model,
2261
+ system: [
2262
+ {
2263
+ type: "text",
2264
+ text: 'You are an agent router. Choose the single best agent for the task. Reply with ONLY a compact JSON object {"role":"...","reason":"..."}.'
2265
+ }
2266
+ ],
2267
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
2268
+ maxTokens: 120,
2269
+ temperature: 0
2270
+ },
2271
+ { signal: ctrl.signal }
2272
+ );
2273
+ const content = resp.content;
2274
+ return Array.isArray(content) ? content[0]?.text ?? "" : "";
2275
+ } catch {
2276
+ return "";
2277
+ } finally {
2278
+ clearTimeout(timeout);
2279
+ }
2280
+ });
2281
+ }
2247
2282
 
2248
2283
  // src/arg-parser.ts
2249
2284
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -3511,8 +3546,12 @@ async function runGit(args, cwd) {
3511
3546
  });
3512
3547
  let stdout = "";
3513
3548
  let stderr = "";
3514
- child.stdout?.on("data", (d) => stdout += d);
3515
- child.stderr?.on("data", (d) => stderr += d);
3549
+ child.stdout?.on("data", (d) => {
3550
+ stdout += d;
3551
+ });
3552
+ child.stderr?.on("data", (d) => {
3553
+ stderr += d;
3554
+ });
3516
3555
  child.on("error", (err) => {
3517
3556
  reject(new WrongStackError({
3518
3557
  message: `Failed to run git: ${err.message}`,
@@ -3846,6 +3885,17 @@ function buildStatsCommand(opts) {
3846
3885
  }
3847
3886
  };
3848
3887
  }
3888
+ var PHASE_ORDER = [
3889
+ { phase: "discovery", label: "1 \xB7 Discovery" },
3890
+ { phase: "planning", label: "2 \xB7 Planning" },
3891
+ { phase: "build", label: "3 \xB7 Build" },
3892
+ { phase: "verify", label: "4 \xB7 Verify" },
3893
+ { phase: "review", label: "5 \xB7 Review" },
3894
+ { phase: "domain", label: "6 \xB7 Domain" },
3895
+ { phase: "knowledge", label: "7 \xB7 Knowledge" },
3896
+ { phase: "delivery", label: "8 \xB7 Delivery & Ops" },
3897
+ { phase: "meta", label: "9 \xB7 Meta" }
3898
+ ];
3849
3899
  function buildFleetCommand(opts) {
3850
3900
  return {
3851
3901
  name: "fleet",
@@ -3854,12 +3904,15 @@ function buildFleetCommand(opts) {
3854
3904
  "Usage:",
3855
3905
  " /fleet Show fleet status (default)",
3856
3906
  " /fleet status Same as /fleet (verbose status)",
3907
+ " /fleet list List the agent roster grouped by phase",
3908
+ " /fleet dispatch <task> Route a task to the best agent and spawn it",
3857
3909
  " /fleet spawn <role> [count] Spawn N subagents of a role (default 1)",
3858
3910
  " /fleet terminate <subagentId> Stop a specific subagent by id",
3859
3911
  " /fleet kill Stop all running subagents",
3860
3912
  " /fleet usage Token and cost breakdown across the fleet",
3861
3913
  " /fleet journal Show recent journal entries from /goal journal",
3862
3914
  "",
3915
+ "In the TUI, press Ctrl+F to open the graphical fleet monitor.",
3863
3916
  "Works during /autonomy parallel mode and standalone director sessions."
3864
3917
  ].join("\n"),
3865
3918
  async run(args) {
@@ -3893,8 +3946,9 @@ function buildFleetCommand(opts) {
3893
3946
  const id = sa.id?.padEnd(36) ?? "".padEnd(36);
3894
3947
  const name = (sa.name ?? "worker").padEnd(16);
3895
3948
  const statusColor = sa.status === "running" ? color.green(sa.status.padEnd(10)) : sa.status === "idle" ? color.dim(sa.status.padEnd(10)) : color.dim(sa.status.padEnd(10));
3949
+ const ext = sa.extensions && sa.extensions > 0 ? `${color.yellow(`\u26A1\xD7${sa.extensions}`)} ` : "";
3896
3950
  const task = sa.currentTask ?? color.dim("\u2014");
3897
- lines.push(` ${id} ${name} ${statusColor} ${task}`);
3951
+ lines.push(` ${id} ${name} ${statusColor} ${ext}${task}`);
3898
3952
  }
3899
3953
  }
3900
3954
  const msg3 = lines.join("\n");
@@ -4000,29 +4054,28 @@ function buildFleetCommand(opts) {
4000
4054
  if (cmd === "terminate" || cmd === "stop") {
4001
4055
  const targetId = subargs[0];
4002
4056
  if (!targetId) {
4003
- const msg2 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
4004
- opts.renderer.writeWarning(msg2);
4005
- return { message: msg2 };
4057
+ const msg3 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
4058
+ opts.renderer.writeWarning(msg3);
4059
+ return { message: msg3 };
4006
4060
  }
4007
4061
  if (!opts.onFleetTerminate) {
4008
- const msg2 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
4009
- opts.renderer.writeWarning(msg2);
4010
- return { message: msg2 };
4062
+ const msg3 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
4063
+ opts.renderer.writeWarning(msg3);
4064
+ return { message: msg3 };
4011
4065
  }
4012
4066
  const ok = opts.onFleetTerminate(targetId);
4013
4067
  if (ok) {
4014
- const msg2 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
4015
- opts.renderer.write(msg2);
4016
- return { message: msg2 };
4017
- } else {
4018
- const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
4019
- opts.renderer.writeWarning(msg2);
4020
- return { message: msg2 };
4068
+ const msg3 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
4069
+ opts.renderer.write(msg3);
4070
+ return { message: msg3 };
4021
4071
  }
4072
+ const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
4073
+ opts.renderer.writeWarning(msg2);
4074
+ return { message: msg2 };
4022
4075
  }
4023
4076
  if (cmd === "spawn" || cmd === "add") {
4024
4077
  const role = subargs[0] ?? "worker";
4025
- const count = Math.min(16, Math.max(1, parseInt(subargs[1] ?? "1", 10) || 1));
4078
+ const count = Math.min(16, Math.max(1, Number.parseInt(subargs[1] ?? "1", 10) || 1));
4026
4079
  if (!opts.onFleetSpawn) {
4027
4080
  const msg3 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
4028
4081
  opts.renderer.writeWarning(msg3);
@@ -4048,11 +4101,64 @@ function buildFleetCommand(opts) {
4048
4101
  }
4049
4102
  return { message: msg2 };
4050
4103
  }
4104
+ if (cmd === "list" || cmd === "roster" || cmd === "agents") {
4105
+ const lines = [`${color.bold("Agent Roster")} ${color.dim("(spawn with /fleet spawn <role>)")}`];
4106
+ for (const { phase, label } of PHASE_ORDER) {
4107
+ const defs = AGENTS_BY_PHASE[phase];
4108
+ if (!defs || defs.length === 0) continue;
4109
+ lines.push("");
4110
+ lines.push(color.cyan(` Phase ${label}`));
4111
+ for (const def of defs) {
4112
+ const role = (def.config.role ?? "").padEnd(18);
4113
+ lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
4114
+ }
4115
+ }
4116
+ const msg2 = lines.join("\n");
4117
+ opts.renderer.write(msg2);
4118
+ return { message: msg2 };
4119
+ }
4120
+ if (cmd === "dispatch" || cmd === "route") {
4121
+ const task = subargs.join(" ").trim();
4122
+ if (!task) {
4123
+ const msg3 = `Usage: /fleet dispatch <task description> \u2014 routes the task to the best agent.`;
4124
+ opts.renderer.writeWarning(msg3);
4125
+ return { message: msg3 };
4126
+ }
4127
+ const decision = await dispatchAgent(task, { classifier: opts.onDispatchClassify });
4128
+ const pct2 = Math.round(decision.confidence * 100);
4129
+ const lines = [];
4130
+ lines.push(
4131
+ `${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`
4132
+ );
4133
+ lines.push(` ${color.dim(decision.definition.capability.summary)}`);
4134
+ lines.push(` ${color.dim("why:")} ${decision.reason}`);
4135
+ if (decision.alternatives.length > 0) {
4136
+ const alts = decision.alternatives.slice(0, 3).map((a) => a.role).join(", ");
4137
+ lines.push(` ${color.dim("alternatives:")} ${alts}`);
4138
+ }
4139
+ if (opts.onFleetSpawn) {
4140
+ try {
4141
+ const id = await opts.onFleetSpawn(decision.role);
4142
+ lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
4143
+ } catch (err) {
4144
+ lines.push(
4145
+ ` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
4146
+ );
4147
+ }
4148
+ } else {
4149
+ lines.push(` ${color.dim("(no fleet active \u2014 run /autonomy parallel or --director to spawn)")}`);
4150
+ }
4151
+ const msg2 = lines.join("\n");
4152
+ opts.renderer.write(msg2);
4153
+ return { message: msg2 };
4154
+ }
4051
4155
  if (cmd === "help" || cmd === "?") {
4052
4156
  const msg2 = [
4053
4157
  `${color.bold("Fleet Commands")}`,
4054
4158
  ` ${color.dim("/fleet")} Show fleet status (default)`,
4055
4159
  ` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
4160
+ ` ${color.dim("/fleet list")} List the agent roster grouped by phase`,
4161
+ ` ${color.dim("/fleet dispatch <task>")} Route a task to the best agent and spawn it`,
4056
4162
  ` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
4057
4163
  ` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
4058
4164
  ` ${color.dim("/fleet kill")} Stop all running subagents`,
@@ -4062,7 +4168,7 @@ function buildFleetCommand(opts) {
4062
4168
  opts.renderer.write(msg2);
4063
4169
  return { message: msg2 };
4064
4170
  }
4065
- const valid = ["status", "usage", "spawn", "terminate", "kill", "retry", "journal"];
4171
+ const valid = ["status", "list", "dispatch", "usage", "spawn", "terminate", "kill", "retry", "journal"];
4066
4172
  const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
4067
4173
  opts.renderer.writeWarning(msg);
4068
4174
  return { message: msg };
@@ -5471,8 +5577,8 @@ ${list}
5471
5577
 
5472
5578
  Use \`/security report <number>\` to view a specific report.` };
5473
5579
  }
5474
- const index = parseInt(reportId, 10) - 1;
5475
- if (!isNaN(index) && reports[index]) {
5580
+ const index = Number.parseInt(reportId, 10) - 1;
5581
+ if (!Number.isNaN(index) && reports[index]) {
5476
5582
  const content = await readFile(join(reportsDir, reports[index]), "utf-8");
5477
5583
  return { message: `# Security Report
5478
5584
 
@@ -7147,6 +7253,162 @@ function summarize(value, name) {
7147
7253
  }
7148
7254
  return "";
7149
7255
  }
7256
+ var acpCmd = async (args, deps) => {
7257
+ const sub = args[0];
7258
+ if (!sub || sub === "server" || sub === "serve") {
7259
+ return runACPServer(deps);
7260
+ }
7261
+ if (sub === "help") {
7262
+ deps.renderer.write(`wstack acp \u2014 ACP (Agent Client Protocol) integration
7263
+
7264
+ Usage:
7265
+ wstack acp Start WrongStack as an ACP server (blocks)
7266
+ wstack acp server Same as above
7267
+ wstack acp list List available ACP agents
7268
+ wstack acp spawn <id> <task>
7269
+ Spawn an ACP agent as a subagent and wait for result
7270
+ wstack acp help Show this help
7271
+
7272
+ ACP Mode:
7273
+ When run as \`wstack acp\`. WrongStack acts as an ACP-compatible agent.
7274
+ ACP clients (Zed, JetBrains, VS Code) spawn it as a subprocess and
7275
+ communicate over stdio JSON-RPC.
7276
+ Press Ctrl+C to stop.
7277
+
7278
+ spawn:
7279
+ Spawns a named ACP agent (cline, gemini-cli, copilot, openhands, goose)
7280
+ with the given task and waits for its result.
7281
+ Example: wstack acp spawn cline "fix the login bug"
7282
+ `);
7283
+ return 0;
7284
+ }
7285
+ if (sub === "list") {
7286
+ return listACPAgents(deps);
7287
+ }
7288
+ if (sub === "spawn") {
7289
+ return spawnACPAgent(args.slice(1), deps);
7290
+ }
7291
+ deps.renderer.writeError(`Unknown acp subcommand: ${sub}
7292
+ `);
7293
+ deps.renderer.write("Run `wstack acp help` for usage.\n");
7294
+ return 1;
7295
+ };
7296
+ async function runACPServer(deps) {
7297
+ const toolRegistry = deps.toolRegistry;
7298
+ const tools = toolRegistry?.list() ?? [];
7299
+ deps.renderer.writeInfo("Starting WrongStack ACP server...\n");
7300
+ deps.renderer.writeInfo(`Exposing ${tools.length} tool(s) via ACP protocol.
7301
+ `);
7302
+ deps.renderer.writeInfo("Waiting for ACP client connection on stdin/stdout...\n");
7303
+ deps.renderer.writeInfo("Press Ctrl+C to stop.\n");
7304
+ const server = new WrongStackACPServer({ tools });
7305
+ const shutdown = () => {
7306
+ deps.renderer.writeWarning("\nShutting down ACP server...");
7307
+ server.stop();
7308
+ process.exit(0);
7309
+ };
7310
+ process.on("SIGINT", shutdown);
7311
+ process.on("SIGTERM", shutdown);
7312
+ try {
7313
+ await server.start();
7314
+ } catch (err) {
7315
+ deps.renderer.writeError(`ACP server error: ${err instanceof Error ? err.message : String(err)}
7316
+ `);
7317
+ return 1;
7318
+ }
7319
+ return 0;
7320
+ }
7321
+ function listACPAgents(deps) {
7322
+ deps.renderer.write("Available ACP agents:\n\n");
7323
+ for (const a of ACP_AGENTS) {
7324
+ const id = a.id ?? a.role;
7325
+ const name = a.name ?? a.role ?? "";
7326
+ const desc = a.prompt?.slice(0, 50) ?? "";
7327
+ deps.renderer.write(` ${id.padEnd(16)} ${name.padEnd(20)} ${desc}\u2026
7328
+ `);
7329
+ }
7330
+ deps.renderer.write("\nUse `wstack acp spawn <agent> <task>` to delegate a task.\n");
7331
+ return 0;
7332
+ }
7333
+ async function spawnACPAgent(args, deps) {
7334
+ const [subagentId, ...taskParts] = args;
7335
+ if (!subagentId) {
7336
+ deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
7337
+ deps.renderer.write("Run `wstack acp list` to see available agents.\n");
7338
+ return 1;
7339
+ }
7340
+ const task = taskParts.join(" ");
7341
+ if (!task) {
7342
+ deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
7343
+ deps.renderer.write("Task description is required.\n");
7344
+ return 1;
7345
+ }
7346
+ const cmd = ACP_AGENT_COMMANDS[subagentId];
7347
+ if (!cmd) {
7348
+ deps.renderer.writeError(`Unknown ACP agent: ${subagentId}
7349
+ `);
7350
+ deps.renderer.write("Run `wstack acp list` to see available agents.\n");
7351
+ return 1;
7352
+ }
7353
+ deps.renderer.writeInfo(`Spawning ACP agent '${subagentId}'\u2026
7354
+ `);
7355
+ const cleanup = () => {
7356
+ if (stop) {
7357
+ try {
7358
+ stop();
7359
+ } catch {
7360
+ }
7361
+ }
7362
+ };
7363
+ process.on("SIGINT", cleanup);
7364
+ process.on("SIGTERM", cleanup);
7365
+ let stop = null;
7366
+ try {
7367
+ const { runner, stop: runStop } = await makeACPSubagentRunnerWithStop(cmd);
7368
+ stop = runStop;
7369
+ const taskId = `acp-${crypto.randomUUID()}`;
7370
+ const budget = new SubagentBudget({
7371
+ timeoutMs: 5 * 60 * 1e3,
7372
+ maxIterations: 2e3,
7373
+ maxToolCalls: 5e3
7374
+ });
7375
+ const ctx = {
7376
+ subagentId,
7377
+ config: {
7378
+ id: subagentId,
7379
+ name: cmd.role ?? subagentId,
7380
+ role: subagentId,
7381
+ provider: "acp",
7382
+ prompt: ""
7383
+ },
7384
+ budget,
7385
+ signal: new AbortController().signal,
7386
+ bridge: null
7387
+ };
7388
+ budget.start();
7389
+ deps.renderer.writeInfo("Running task\u2026\n");
7390
+ const result = await runner(
7391
+ { id: taskId, description: task },
7392
+ ctx
7393
+ );
7394
+ deps.renderer.write("\n--- Result ---\n");
7395
+ deps.renderer.write(String(result.result ?? "no result"));
7396
+ deps.renderer.write("\n---------------\n");
7397
+ deps.renderer.writeInfo(
7398
+ `Done. iterations=${result.iterations} toolCalls=${result.toolCalls}
7399
+ `
7400
+ );
7401
+ return 0;
7402
+ } catch (err) {
7403
+ deps.renderer.writeError(`ACP agent error: ${err instanceof Error ? err.message : String(err)}
7404
+ `);
7405
+ return 1;
7406
+ } finally {
7407
+ cleanup();
7408
+ process.off("SIGINT", cleanup);
7409
+ process.off("SIGTERM", cleanup);
7410
+ }
7411
+ }
7150
7412
 
7151
7413
  // src/auth-menu.ts
7152
7414
  init_provider_config_utils();
@@ -9015,8 +9277,8 @@ var rewindCmd = async (args, deps) => {
9015
9277
  deps.renderer.write("Rewinding to session start...\n");
9016
9278
  result = await rewind.rewindToStart(sessionId);
9017
9279
  } else if (flags.last) {
9018
- const n = parseInt(flags.last, 10);
9019
- if (isNaN(n) || n < 1) {
9280
+ const n = Number.parseInt(flags.last, 10);
9281
+ if (Number.isNaN(n) || n < 1) {
9020
9282
  deps.renderer.writeError("--last requires a positive number");
9021
9283
  return 1;
9022
9284
  }
@@ -9024,8 +9286,8 @@ var rewindCmd = async (args, deps) => {
9024
9286
  `);
9025
9287
  result = await rewind.rewindLastN(sessionId, n);
9026
9288
  } else if (flags.to) {
9027
- const idx = parseInt(flags.to, 10);
9028
- if (isNaN(idx) || idx < 0) {
9289
+ const idx = Number.parseInt(flags.to, 10);
9290
+ if (Number.isNaN(idx) || idx < 0) {
9029
9291
  deps.renderer.writeError("--to requires a non-negative number");
9030
9292
  return 1;
9031
9293
  }
@@ -9146,6 +9408,7 @@ var helpCmd = async (_args, deps) => {
9146
9408
 
9147
9409
  // src/subcommands/index.ts
9148
9410
  var subcommands = {
9411
+ acp: acpCmd,
9149
9412
  init: initCmd,
9150
9413
  auth: authCmd,
9151
9414
  update: updateCmd,
@@ -9188,22 +9451,22 @@ function fmtDuration(ms) {
9188
9451
  const remMin = m - h * 60;
9189
9452
  return `${h}h${remMin}m`;
9190
9453
  }
9191
- function fmtTaskResultLine(r, color38) {
9454
+ function fmtTaskResultLine(r, color39) {
9192
9455
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
9193
9456
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
9194
9457
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
9195
9458
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
9196
- const errKindChip = errKind ? color38.dim(` [${errKind}]`) : "";
9197
- const errSnip = errMsg || errKind ? `${errKindChip}${color38.dim(errTail)}` : "";
9459
+ const errKindChip = errKind ? color39.dim(` [${errKind}]`) : "";
9460
+ const errSnip = errMsg || errKind ? `${errKindChip}${color39.dim(errTail)}` : "";
9198
9461
  switch (r.status) {
9199
9462
  case "success":
9200
- return { mark: color38.green("\u2713"), stats, tail: "" };
9463
+ return { mark: color39.green("\u2713"), stats, tail: "" };
9201
9464
  case "timeout":
9202
- return { mark: color38.yellow("\u23F1"), stats: `${color38.yellow("timeout")} ${stats}`, tail: errSnip };
9465
+ return { mark: color39.yellow("\u23F1"), stats: `${color39.yellow("timeout")} ${stats}`, tail: errSnip };
9203
9466
  case "stopped":
9204
- return { mark: color38.dim("\u2298"), stats: `${color38.dim("stopped")} ${stats}`, tail: errSnip };
9467
+ return { mark: color39.dim("\u2298"), stats: `${color39.dim("stopped")} ${stats}`, tail: errSnip };
9205
9468
  case "failed":
9206
- return { mark: color38.red("\u2717"), stats: `${color38.red("failed")} ${stats}`, tail: errSnip };
9469
+ return { mark: color39.red("\u2717"), stats: `${color39.red("failed")} ${stats}`, tail: errSnip };
9207
9470
  }
9208
9471
  }
9209
9472
 
@@ -9362,6 +9625,214 @@ async function boot(argv) {
9362
9625
  updateInfo
9363
9626
  };
9364
9627
  }
9628
+ function fmtElapsed(ms) {
9629
+ const s = Math.floor(ms / 1e3);
9630
+ if (s < 60) return `${s}s`;
9631
+ const m = Math.floor(s / 60);
9632
+ const rem = s % 60;
9633
+ if (m < 60) return `${m}m${rem.toString().padStart(2, "0")}s`;
9634
+ const h = Math.floor(m / 60);
9635
+ return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
9636
+ }
9637
+ function renderFleetLine(states, now, columns, version) {
9638
+ const all = [...states.values()];
9639
+ if (all.length === 0) return "";
9640
+ const running = all.filter((a) => a.status === "running");
9641
+ const done = all.filter((a) => a.status === "done").length;
9642
+ const failed = all.filter((a) => a.status === "failed").length;
9643
+ const versionChip = version ? `${color.bold("WS")}${color.dim(` v${version}`)} ${color.dim("\u2502")} ` : "";
9644
+ const counts = versionChip + `${color.cyan("\u27F3 fleet")} ${color.yellow(`\u25B6${running.length}`)} ${color.green(`\u2713${done}`)}` + (failed > 0 ? ` ${color.red(`\u2717${failed}`)}` : "");
9645
+ const shown = running.sort((a, b) => a.startedAt - b.startedAt).slice(0, 4).map((a) => {
9646
+ const elapsed = fmtElapsed(Math.max(0, now - a.startedAt));
9647
+ const tool = a.lastTool ? ` ${color.dim(a.lastTool)}` : "";
9648
+ const ext = a.extensions && a.extensions > 0 ? ` ${color.yellow(`\u26A1${a.extensions}`)}` : "";
9649
+ return `${color.bold(a.name)} ${color.yellow("\u25B6")} ${color.dim(elapsed)} ${color.dim(`L${a.iterations}`)} ${color.dim(`${a.toolCalls}t`)}${tool}${ext}`;
9650
+ });
9651
+ let line = shown.length > 0 ? `${counts} ${color.dim("\u2502")} ${shown.join(color.dim(" \xB7 "))}` : counts;
9652
+ const max = Math.max(20, columns - 1);
9653
+ const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
9654
+ if (visible.length > max) {
9655
+ let count = 0;
9656
+ let out = "";
9657
+ let i = 0;
9658
+ while (i < line.length && count < max - 1) {
9659
+ if (line[i] === "\x1B") {
9660
+ const end = line.indexOf("m", i);
9661
+ if (end !== -1) {
9662
+ out += line.slice(i, end + 1);
9663
+ i = end + 1;
9664
+ continue;
9665
+ }
9666
+ }
9667
+ out += line[i];
9668
+ count++;
9669
+ i++;
9670
+ }
9671
+ line = out + "\u2026";
9672
+ }
9673
+ return line;
9674
+ }
9675
+ var FleetStatusLine = class {
9676
+ events;
9677
+ out;
9678
+ throttleMs;
9679
+ now;
9680
+ version;
9681
+ states = /* @__PURE__ */ new Map();
9682
+ unsubs = [];
9683
+ active = false;
9684
+ rows = 0;
9685
+ repaintTimer = null;
9686
+ tickTimer = null;
9687
+ lastPaint = 0;
9688
+ onResize = () => {
9689
+ if (this.active) {
9690
+ this.rows = this.out.rows ?? 24;
9691
+ this.out.write(`\x1B[1;${this.rows - 1}r`);
9692
+ this.paint(true);
9693
+ }
9694
+ };
9695
+ constructor(opts) {
9696
+ this.events = opts.events;
9697
+ this.out = opts.out ?? process.stdout;
9698
+ this.throttleMs = opts.throttleMs ?? 150;
9699
+ this.now = opts.now ?? Date.now;
9700
+ this.version = opts.version;
9701
+ }
9702
+ /** Subscribe to host fleet events. No terminal output until a subagent spawns. */
9703
+ start() {
9704
+ const ensure = (id, name) => {
9705
+ let s = this.states.get(id);
9706
+ if (!s) {
9707
+ s = {
9708
+ id,
9709
+ name: name ?? id,
9710
+ status: "running",
9711
+ iterations: 0,
9712
+ toolCalls: 0,
9713
+ startedAt: this.now()
9714
+ };
9715
+ this.states.set(id, s);
9716
+ } else if (name && s.name === s.id) {
9717
+ s.name = name;
9718
+ }
9719
+ return s;
9720
+ };
9721
+ this.unsubs.push(
9722
+ this.events.on("subagent.spawned", (e) => {
9723
+ ensure(e.subagentId, e.name);
9724
+ this.activate();
9725
+ this.schedulePaint();
9726
+ }),
9727
+ this.events.on("subagent.task_started", (e) => {
9728
+ const s = ensure(e.subagentId);
9729
+ s.status = "running";
9730
+ this.activate();
9731
+ this.schedulePaint();
9732
+ }),
9733
+ this.events.on("subagent.tool_executed", (e) => {
9734
+ const s = ensure(e.subagentId);
9735
+ s.toolCalls++;
9736
+ s.lastTool = e.name;
9737
+ this.activate();
9738
+ this.schedulePaint();
9739
+ }),
9740
+ this.events.on("subagent.iteration_summary", (e) => {
9741
+ const s = ensure(e.subagentId);
9742
+ s.iterations = e.iteration;
9743
+ if (typeof e.toolCalls === "number") s.toolCalls = e.toolCalls;
9744
+ if (e.currentTool) s.lastTool = e.currentTool;
9745
+ this.schedulePaint();
9746
+ }),
9747
+ this.events.on("subagent.budget_extended", (e) => {
9748
+ const s = ensure(e.subagentId);
9749
+ s.extensions = e.totalExtensions;
9750
+ this.activate();
9751
+ this.schedulePaint();
9752
+ }),
9753
+ this.events.on("subagent.task_completed", (e) => {
9754
+ const s = ensure(e.subagentId);
9755
+ s.status = e.status === "success" ? "done" : "failed";
9756
+ s.iterations = e.iterations;
9757
+ s.toolCalls = e.toolCalls;
9758
+ s.endedAt = this.now();
9759
+ this.schedulePaint();
9760
+ if (![...this.states.values()].some((a) => a.status === "running")) {
9761
+ setTimeout(() => {
9762
+ if (![...this.states.values()].some((a) => a.status === "running")) {
9763
+ this.deactivate();
9764
+ }
9765
+ }, 800);
9766
+ }
9767
+ })
9768
+ );
9769
+ }
9770
+ /** Unsubscribe and restore the terminal. Idempotent. */
9771
+ stop() {
9772
+ for (const u of this.unsubs.splice(0)) u();
9773
+ this.deactivate();
9774
+ }
9775
+ isTty() {
9776
+ return !!this.out.isTTY;
9777
+ }
9778
+ activate() {
9779
+ if (this.active || !this.isTty()) return;
9780
+ this.active = true;
9781
+ this.rows = this.out.rows ?? 24;
9782
+ this.out.write("\n");
9783
+ this.out.write(`\x1B[1;${this.rows - 1}r`);
9784
+ this.out.write(`\x1B[${this.rows - 1};1H`);
9785
+ this.out.on("resize", this.onResize);
9786
+ this.tickTimer = setInterval(() => this.paint(true), 1e3);
9787
+ if (this.tickTimer.unref) this.tickTimer.unref();
9788
+ this.paint(true);
9789
+ }
9790
+ deactivate() {
9791
+ if (!this.active) return;
9792
+ this.active = false;
9793
+ if (this.tickTimer) {
9794
+ clearInterval(this.tickTimer);
9795
+ this.tickTimer = null;
9796
+ }
9797
+ if (this.repaintTimer) {
9798
+ clearTimeout(this.repaintTimer);
9799
+ this.repaintTimer = null;
9800
+ }
9801
+ this.out.off("resize", this.onResize);
9802
+ if (this.isTty()) {
9803
+ this.out.write("\x1B7");
9804
+ this.out.write(`\x1B[${this.rows};1H`);
9805
+ this.out.write("\x1B[2K");
9806
+ this.out.write("\x1B8");
9807
+ this.out.write("\x1B[r");
9808
+ }
9809
+ }
9810
+ schedulePaint() {
9811
+ if (!this.active) return;
9812
+ const since = this.now() - this.lastPaint;
9813
+ if (since >= this.throttleMs) {
9814
+ this.paint(false);
9815
+ return;
9816
+ }
9817
+ if (this.repaintTimer) return;
9818
+ this.repaintTimer = setTimeout(() => {
9819
+ this.repaintTimer = null;
9820
+ this.paint(false);
9821
+ }, this.throttleMs - since);
9822
+ if (this.repaintTimer.unref) this.repaintTimer.unref();
9823
+ }
9824
+ paint(force) {
9825
+ if (!this.active || !this.isTty()) return;
9826
+ if (!force) this.lastPaint = this.now();
9827
+ else this.lastPaint = this.now();
9828
+ const line = renderFleetLine(this.states, this.now(), this.out.columns ?? 80, this.version);
9829
+ this.out.write("\x1B7");
9830
+ this.out.write(`\x1B[${this.rows};1H`);
9831
+ this.out.write("\x1B[2K");
9832
+ this.out.write(line);
9833
+ this.out.write("\x1B8");
9834
+ }
9835
+ };
9365
9836
 
9366
9837
  // src/repl.ts
9367
9838
  init_sdd();
@@ -9959,6 +10430,7 @@ async function execute(deps) {
9959
10430
  skillLoader
9960
10431
  } = deps;
9961
10432
  let code = 0;
10433
+ let fleetStatusLine = null;
9962
10434
  try {
9963
10435
  const visionAdapters = () => createToolVisionAdapters(agent.tools);
9964
10436
  const supportsVision = async () => {
@@ -9978,6 +10450,11 @@ async function execute(deps) {
9978
10450
  if ((goalFlag || askFlag) && positional.length === 0 && !promptFlag) {
9979
10451
  flags.tui = true;
9980
10452
  }
10453
+ const enteringTui = !(positional.length > 0 || promptFlag) && !!flags.tui && flags["no-tui"] !== true;
10454
+ if (!enteringTui) {
10455
+ fleetStatusLine = new FleetStatusLine({ events, version: CLI_VERSION });
10456
+ fleetStatusLine.start();
10457
+ }
9981
10458
  if (positional.length > 0 || promptFlag) {
9982
10459
  const query = positional.join(" ");
9983
10460
  const ctrl = new AbortController();
@@ -10189,6 +10666,7 @@ async function execute(deps) {
10189
10666
  });
10190
10667
  }
10191
10668
  } finally {
10669
+ fleetStatusLine?.stop();
10192
10670
  try {
10193
10671
  stats.render(renderer);
10194
10672
  } catch (err) {
@@ -10248,6 +10726,13 @@ var MultiAgentHost = class {
10248
10726
  getCoordinator() {
10249
10727
  return this.director.coordinator;
10250
10728
  }
10729
+ /** Public accessor for the Director — used by buildRoutingRunner. */
10730
+ getDirector() {
10731
+ return this.director;
10732
+ }
10733
+ async ensureCoordinator(config) {
10734
+ await this.buildDirector();
10735
+ }
10251
10736
  async buildDirector() {
10252
10737
  if (this.director) return;
10253
10738
  const config = this.deps.configStore.get();
@@ -10303,22 +10788,41 @@ var MultiAgentHost = class {
10303
10788
  limit: payload.limit
10304
10789
  });
10305
10790
  });
10306
- this.getCoordinator().on("task.assigned", ({ task, subagentId }) => {
10307
- this.deps.events.emit("subagent.task_started", {
10308
- subagentId,
10309
- taskId: task.id,
10310
- description: task.description
10791
+ this.director.fleet.filter("budget.extended", (e) => {
10792
+ const payload = e.payload;
10793
+ this.deps.events.emit("subagent.budget_extended", {
10794
+ subagentId: e.subagentId,
10795
+ kind: payload.kind,
10796
+ newLimit: payload.newLimit,
10797
+ totalExtensions: payload.totalExtensions
10311
10798
  });
10312
10799
  });
10313
- const runner = this.buildSubagentRunner(config);
10800
+ this.getCoordinator().on(
10801
+ "task.assigned",
10802
+ ({
10803
+ task,
10804
+ subagentId
10805
+ }) => {
10806
+ this.deps.events.emit("subagent.task_started", {
10807
+ subagentId,
10808
+ taskId: task.id,
10809
+ description: task.description
10810
+ });
10811
+ }
10812
+ );
10813
+ const runner = await this.buildSubagentRunner(config);
10314
10814
  this.getCoordinator().setRunner(runner);
10315
10815
  }
10316
10816
  /**
10317
- * Build the per-subagent runner: agent factory runner. Extracted so
10318
- * ensureCoordinator stays focused on orchestration setup.
10817
+ * Build a per-role subagent factory: given a SubagentConfig, construct a
10818
+ * fresh, isolated Agent with the role's filtered tools and (when the config
10819
+ * carries one) the role's persona as an appended system-prompt block. Public
10820
+ * so the autonomy-parallel engine can reuse the exact same agent-construction
10821
+ * path the director/spawn flow uses — each parallel slot then runs as a real,
10822
+ * specialized, concurrency-safe agent instead of sharing the leader's Context.
10319
10823
  */
10320
- buildSubagentRunner(config) {
10321
- const factory = async (subCfg) => {
10824
+ makeSubagentFactory(config) {
10825
+ return async (subCfg) => {
10322
10826
  const events = new EventBus();
10323
10827
  const provider = await this.buildSubagentProvider(config, subCfg.provider);
10324
10828
  const baseSystem = await this.deps.systemPromptBuilder.build({
@@ -10333,6 +10837,9 @@ var MultiAgentHost = class {
10333
10837
  // meaningless to a single delegated subtask.
10334
10838
  subagent: true
10335
10839
  });
10840
+ if (subCfg.systemPromptOverride) {
10841
+ baseSystem.push({ type: "text", text: subCfg.systemPromptOverride });
10842
+ }
10336
10843
  let subSession;
10337
10844
  if (this.sessionFactory) {
10338
10845
  const subagentName = subCfg.id ?? subCfg.name ?? `sub_${randomUUID().slice(0, 8)}`;
@@ -10411,7 +10918,22 @@ var MultiAgentHost = class {
10411
10918
  };
10412
10919
  return { agent, events, dispose };
10413
10920
  };
10414
- return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet ?? NULL_FLEET_BUS });
10921
+ }
10922
+ /**
10923
+ * Build the per-subagent runner.
10924
+ *
10925
+ * ACP agents (provider: 'acp') get their own runner via
10926
+ * makeACPSubagentRunner — they run external processes and don't go
10927
+ * through the Agent factory. Regular agents use the standard
10928
+ * makeAgentSubagentRunner path.
10929
+ */
10930
+ async buildSubagentRunner(config) {
10931
+ return buildRoutingRunner(config, this);
10932
+ }
10933
+ async buildACPRunner(subagentId) {
10934
+ const cmd = ACP_AGENT_COMMANDS[subagentId];
10935
+ if (!cmd) throw new Error(`Unknown ACP agent: ${subagentId}`);
10936
+ return makeACPSubagentRunner(cmd);
10415
10937
  }
10416
10938
  /**
10417
10939
  * Build a Provider for a subagent. When `overrideId` is supplied (from
@@ -10433,6 +10955,24 @@ var MultiAgentHost = class {
10433
10955
  type: providerId
10434
10956
  });
10435
10957
  }
10958
+ async spawnACP(subagentId, task, config) {
10959
+ const taskId = randomUUID();
10960
+ await this.ensureCoordinator(config);
10961
+ const coordinator = this.getCoordinator();
10962
+ const acpRunner = await this.buildACPRunner(subagentId);
10963
+ coordinator.setRunner(acpRunner);
10964
+ await coordinator.spawn({
10965
+ id: subagentId,
10966
+ name: subagentId,
10967
+ role: subagentId,
10968
+ provider: "acp"
10969
+ });
10970
+ await coordinator.assign({
10971
+ id: taskId,
10972
+ description: task
10973
+ });
10974
+ return taskId;
10975
+ }
10436
10976
  /** Returns a tool slice for the subagent — full set unless restricted. */
10437
10977
  filterTools(allow) {
10438
10978
  const all = this.deps.toolRegistry.list();
@@ -10442,7 +10982,7 @@ var MultiAgentHost = class {
10442
10982
  }
10443
10983
  subagentToolRegistry(allow) {
10444
10984
  if (!allow || allow.length === 0) return this.deps.toolRegistry;
10445
- const sub = this.deps.toolRegistry.clone();
10985
+ const sub = new ToolRegistry();
10446
10986
  for (const t of this.filterTools(allow)) sub.register(t);
10447
10987
  return sub;
10448
10988
  }
@@ -10687,6 +11227,20 @@ var MultiAgentHost = class {
10687
11227
  }
10688
11228
  }
10689
11229
  };
11230
+ function buildRoutingRunner(config, host) {
11231
+ const standardRunner = makeAgentSubagentRunner({
11232
+ factory: host.makeSubagentFactory(config),
11233
+ fleetBus: host.getDirector()?.fleet ?? NULL_FLEET_BUS
11234
+ });
11235
+ return async (task, ctx) => {
11236
+ const subCfg = ctx.config;
11237
+ if (subCfg.provider === "acp") {
11238
+ const cacheKey = subCfg.role ?? subCfg.name ?? subCfg.id;
11239
+ return host.buildACPRunner(cacheKey).then((r) => r(task, ctx));
11240
+ }
11241
+ return standardRunner(task, ctx);
11242
+ };
11243
+ }
10690
11244
  function makePromptDelegate(reader) {
10691
11245
  return async (tool, input, suggestedPattern) => {
10692
11246
  process.stdout.write("\x07");
@@ -12160,7 +12714,11 @@ Restart WrongStack to load or unload plugin code in this session.`;
12160
12714
  projectRoot,
12161
12715
  compactor: container.resolve(TOKENS.Compactor),
12162
12716
  maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
12163
- onIteration: broadcastEternalIteration
12717
+ onIteration: broadcastEternalIteration,
12718
+ // Real per-role factory: each dispatched slot runs as a fresh,
12719
+ // isolated agent with the role's filtered tools + persona prompt
12720
+ // (instead of sharing the leader agent's Context).
12721
+ subagentFactory: multiAgentHost.makeSubagentFactory(config)
12164
12722
  });
12165
12723
  }
12166
12724
  void parallelEngine.prime?.();
@@ -12190,7 +12748,9 @@ Restart WrongStack to load or unload plugin code in this session.`;
12190
12748
  const statusResult = await new Promise((resolve4, reject) => {
12191
12749
  const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
12192
12750
  let stdout = "";
12193
- child.stdout?.on("data", (d) => stdout += d);
12751
+ child.stdout?.on("data", (d) => {
12752
+ stdout += d;
12753
+ });
12194
12754
  child.on("error", reject);
12195
12755
  child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
12196
12756
  });
@@ -12236,7 +12796,11 @@ Restart WrongStack to load or unload plugin code in this session.`;
12236
12796
  provider: context.provider,
12237
12797
  model: context.model
12238
12798
  });
12239
- }
12799
+ },
12800
+ onDispatchClassify: makeProviderClassifier(
12801
+ context.provider,
12802
+ context.model
12803
+ )
12240
12804
  });
12241
12805
  for (const cmd of slashCmds) slashRegistry.register(cmd);
12242
12806
  const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";