@wrongstack/cli 0.256.1 → 0.257.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
@@ -37,8 +37,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
37
37
  if (typeof require !== "undefined") return require.apply(this, arguments);
38
38
  throw Error('Dynamic require of "' + x + '" is not supported');
39
39
  });
40
- var __esm = (fn, res) => function __init() {
41
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
40
+ var __esm = (fn, res, err) => function __init() {
41
+ if (err) throw err[0];
42
+ try {
43
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
44
+ } catch (e) {
45
+ throw err = [e], e;
46
+ }
42
47
  };
43
48
  var __export = (target, all) => {
44
49
  for (var name in all)
@@ -5724,15 +5729,14 @@ function gitText(args, cwd) {
5724
5729
  reject(err);
5725
5730
  return;
5726
5731
  }
5727
- let out = "";
5728
- child.stdout?.on("data", (c) => {
5729
- if (out.length < MAX_CMD_OUTPUT) out += c.toString();
5730
- });
5731
- child.stderr?.on("data", (c) => {
5732
- if (out.length < MAX_CMD_OUTPUT) out += c.toString();
5733
- });
5734
- child.on("error", () => resolve10({ code: 1, out }));
5735
- child.on("close", (code) => resolve10({ code: code ?? 1, out: out.trim() }));
5732
+ const chunks = [];
5733
+ const emit = (c) => {
5734
+ if (chunks.join("").length < MAX_CMD_OUTPUT) chunks.push(c.toString());
5735
+ };
5736
+ child.stdout?.on("data", emit);
5737
+ child.stderr?.on("data", emit);
5738
+ child.on("error", () => resolve10({ code: 1, out: chunks.join("") }));
5739
+ child.on("close", (code) => resolve10({ code: code ?? 1, out: chunks.join("").trim() }));
5736
5740
  });
5737
5741
  }
5738
5742
  async function isGitRepo(cwd) {
@@ -5769,7 +5773,7 @@ function runCmd(cmd, args, cwd, shell = false) {
5769
5773
  }
5770
5774
  }
5771
5775
  return new Promise((resolve10, reject) => {
5772
- let out = "";
5776
+ const chunks = [];
5773
5777
  let child;
5774
5778
  try {
5775
5779
  child = spawn(cmd, args, {
@@ -5787,13 +5791,16 @@ function runCmd(cmd, args, cwd, shell = false) {
5787
5791
  return;
5788
5792
  }
5789
5793
  const append = (c) => {
5790
- out += c.toString();
5791
- if (out.length > MAX_CMD_OUTPUT) out = out.slice(-MAX_CMD_OUTPUT);
5794
+ chunks.push(c.toString());
5792
5795
  };
5793
5796
  child.stdout?.on("data", append);
5794
5797
  child.stderr?.on("data", append);
5795
- child.on("error", (e) => resolve10({ code: 1, out: `${out}${String(e)}` }));
5796
- child.on("close", (code) => resolve10({ code: code ?? 1, out: out.trim() }));
5798
+ child.on("error", (e) => resolve10({ code: 1, out: `${chunks.join("")}${String(e)}` }));
5799
+ child.on("close", (code) => {
5800
+ let out = chunks.join("");
5801
+ if (out.length > MAX_CMD_OUTPUT) out = out.slice(-MAX_CMD_OUTPUT);
5802
+ resolve10({ code: code ?? 1, out: out.trim() });
5803
+ });
5797
5804
  });
5798
5805
  }
5799
5806
  function detectPackageManager(root) {
@@ -8646,8 +8653,19 @@ function listRoles() {
8646
8653
  }
8647
8654
  var DEFAULT_TIMEOUT_MS = 6e4;
8648
8655
  var MAX_OUTPUT_LINES = 500;
8656
+ var WINDOWS_CMD_METACHARACTERS = /[;&|<>^$,(){}[\]!#%'"\\/`]/;
8657
+ function validateCommand(cmd) {
8658
+ if (process.platform !== "win32") return;
8659
+ if (WINDOWS_CMD_METACHARACTERS.test(cmd)) {
8660
+ throw new Error(
8661
+ `Command contains disallowed metacharacters for Windows: ${cmd.match(WINDOWS_CMD_METACHARACTERS)?.[0] ?? "?"}
8662
+ The following characters are not allowed: ; & | < > ^ $ , ( ) { } [ ] ! # % ' " \\ / \` * ?`
8663
+ );
8664
+ }
8665
+ }
8649
8666
  function runCommand(cmd, cwd, timeout) {
8650
8667
  return new Promise((resolve10) => {
8668
+ validateCommand(cmd);
8651
8669
  const opts = {
8652
8670
  cwd,
8653
8671
  timeout,
@@ -8728,6 +8746,7 @@ Examples:
8728
8746
  /dev git diff --stat`
8729
8747
  };
8730
8748
  }
8749
+ validateCommand(cmd);
8731
8750
  const cwd = opts.cwd;
8732
8751
  const startedAt = Date.now();
8733
8752
  opts.renderer.write(color.dim(`$ ${cmd}`));
@@ -10425,11 +10444,11 @@ function classifyError(input) {
10425
10444
  }
10426
10445
  function extractCode(s) {
10427
10446
  const ts = /\bTS\d+\b|\bCS\d+\b/.exec(s);
10428
- if (ts) return ts[0];
10447
+ if (ts !== null) return ts[0];
10429
10448
  const rust = /\bE\d{4,}\b/.exec(s);
10430
- if (rust) return rust[0];
10449
+ if (rust !== null) return rust[0];
10431
10450
  const c = /\bc\d+\b/i.exec(s);
10432
- if (c) return c[0];
10451
+ if (c !== null) return c[0];
10433
10452
  return void 0;
10434
10453
  }
10435
10454
  function needsSubagent(c) {
@@ -12155,8 +12174,20 @@ async function runAdd(name, enable, configured, configPath2, mcpRegistry, all) {
12155
12174
  }
12156
12175
  async function runRemove(name, configured, configPath2, mcpRegistry) {
12157
12176
  if (!configured[name]) return `Server "${name}" is not in config.`;
12158
- await mcpRegistry.stop(name).catch(() => {
12159
- });
12177
+ try {
12178
+ await mcpRegistry.stop(name);
12179
+ } catch (err) {
12180
+ console.error(
12181
+ JSON.stringify({
12182
+ level: "warn",
12183
+ event: "mcp.stop_failed_on_remove",
12184
+ server: name,
12185
+ message: err instanceof Error ? err.message : String(err),
12186
+ note: "config entry removed but server may still be running",
12187
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
12188
+ })
12189
+ );
12190
+ }
12160
12191
  const full = await readConfig(configPath2);
12161
12192
  const mcpServers = {
12162
12193
  ...full.mcpServers ?? {}
@@ -12195,8 +12226,20 @@ async function runEnable(name, configured, configPath2, mcpRegistry) {
12195
12226
  async function runDisable(name, configured, configPath2, mcpRegistry) {
12196
12227
  const cfg = configured[name];
12197
12228
  if (!cfg) return `Server "${name}" is not in config.`;
12198
- await mcpRegistry.stop(name).catch(() => {
12199
- });
12229
+ try {
12230
+ await mcpRegistry.stop(name);
12231
+ } catch (err) {
12232
+ console.error(
12233
+ JSON.stringify({
12234
+ level: "warn",
12235
+ event: "mcp.stop_failed_on_disable",
12236
+ server: name,
12237
+ message: err instanceof Error ? err.message : String(err),
12238
+ note: "config marked disabled but server may still be running",
12239
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
12240
+ })
12241
+ );
12242
+ }
12200
12243
  const full = await readConfig(configPath2);
12201
12244
  const mcpServers = {
12202
12245
  ...full.mcpServers ?? {}
@@ -21730,22 +21773,22 @@ function renderFleetLine(states, now, columns, version) {
21730
21773
  const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
21731
21774
  if (visible.length > max) {
21732
21775
  let count = 0;
21733
- let out = "";
21776
+ const out = [];
21734
21777
  let i = 0;
21735
21778
  while (i < line.length && count < max - 1) {
21736
21779
  if (line[i] === "\x1B") {
21737
21780
  const end = line.indexOf("m", i);
21738
21781
  if (end !== -1) {
21739
- out += line.slice(i, end + 1);
21782
+ out.push(line.slice(i, end + 1));
21740
21783
  i = end + 1;
21741
21784
  continue;
21742
21785
  }
21743
21786
  }
21744
- out += line[i];
21787
+ if (line[i] !== void 0) out.push(line[i]);
21745
21788
  count++;
21746
21789
  i++;
21747
21790
  }
21748
- line = out + "\u2026";
21791
+ line = out.join("") + "\u2026";
21749
21792
  }
21750
21793
  return line;
21751
21794
  }
@@ -21999,7 +22042,8 @@ init_sdd();
21999
22042
  init_utils();
22000
22043
  var DEFAULT_MAX_CONSECUTIVE_AUTO_PROCEED = 50;
22001
22044
  function parseSuggestionsFromOutput(finalText) {
22002
- const { texts } = parseNextSteps(finalText, false);
22045
+ const { texts, autoTexts } = parseNextSteps(finalText, false);
22046
+ if (autoTexts.length > 0) ;
22003
22047
  return texts.length > 0 ? texts : null;
22004
22048
  }
22005
22049
  async function runRepl(opts) {
@@ -22224,13 +22268,17 @@ ${lines.join("\n")}
22224
22268
  );
22225
22269
  }
22226
22270
  } else {
22227
- const top = suggestions[0] ?? "";
22271
+ const isYolo = opts.getYolo?.() ?? false;
22272
+ const autoSuggestions = opts.getAutoSuggestions?.() ?? [];
22273
+ const useAutoSuggestions = isYolo && autoSuggestions.length > 0;
22274
+ const top = useAutoSuggestions ? autoSuggestions[0] ?? "" : suggestions[0] ?? "";
22228
22275
  const delay = opts.autoProceedDelayMs ?? 1e3;
22229
22276
  const ctrl = new AbortController();
22230
22277
  activeCtrl = ctrl;
22231
22278
  try {
22232
22279
  autoIterCount++;
22233
- await runAutoProceed(opts, top, delay, ctrl);
22280
+ const promptToFeed = useAutoSuggestions && opts.autonomyNextPrompt ? opts.autonomyNextPrompt.replace("{{suggestion}}", top) : top;
22281
+ await runAutoProceed(opts, promptToFeed, delay, ctrl);
22234
22282
  } finally {
22235
22283
  activeCtrl = void 0;
22236
22284
  }
@@ -22909,6 +22957,8 @@ async function execute(deps) {
22909
22957
  getNextPredict,
22910
22958
  onSuggestionsParsed,
22911
22959
  getSuggestions: getSuggestions2,
22960
+ getAutoSuggestions,
22961
+ autonomyNextPrompt,
22912
22962
  autoProceedDelayMs,
22913
22963
  autoProceedMaxIterations,
22914
22964
  brain,
@@ -23269,7 +23319,8 @@ async function execute(deps) {
23269
23319
  enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4,
23270
23320
  enhanceEnabled: cfg.autonomy?.enhance ?? true,
23271
23321
  enhanceLanguage: cfg.autonomy?.enhanceLanguage === "english" ? "english" : "original",
23272
- mouseMode: autonomy?.mouseMode ?? false
23322
+ mouseMode: autonomy?.mouseMode ?? false,
23323
+ autonomyNextPrompt: cfg.autonomy?.autonomyNextPrompt ?? "auto {{suggestion}}"
23273
23324
  };
23274
23325
  },
23275
23326
  async saveSettings(s) {
@@ -23293,6 +23344,7 @@ async function execute(deps) {
23293
23344
  if (s.mouseMode !== void 0) a["mouseMode"] = s.mouseMode;
23294
23345
  if (s.enhanceEnabled !== void 0) a["enhance"] = s.enhanceEnabled;
23295
23346
  if (s.enhanceLanguage !== void 0) a["enhanceLanguage"] = s.enhanceLanguage;
23347
+ if (s.autonomyNextPrompt !== void 0) a["autonomyNextPrompt"] = s.autonomyNextPrompt;
23296
23348
  }
23297
23349
  );
23298
23350
  if (s.featureMcp !== void 0 || s.featurePlugins !== void 0 || s.featureMemory !== void 0 || s.featureSkills !== void 0 || s.featureModelsRegistry !== void 0 || s.contextAutoCompact !== void 0 || s.contextStrategy !== void 0 || s.logLevel !== void 0 || s.auditLevel !== void 0 || s.indexOnStart !== void 0 || s.maxIterations !== void 0 || s.nextPrediction !== void 0 || s.debugStream !== void 0 || s.configScope !== void 0 || s.enhanceDelayMs !== void 0) {
@@ -23437,6 +23489,8 @@ async function execute(deps) {
23437
23489
  onClearHistory: (dispatch) => {
23438
23490
  dispatch({ type: "clearHistory" });
23439
23491
  dispatch({ type: "resetContextChip" });
23492
+ dispatch({ type: "streamReset" });
23493
+ dispatch({ type: "toolStreamClear" });
23440
23494
  },
23441
23495
  fleetStreamController,
23442
23496
  interruptController,
@@ -23521,31 +23575,35 @@ async function execute(deps) {
23521
23575
  const metaMode = context.meta?.["mode"];
23522
23576
  return typeof metaMode === "string" ? metaMode : modeId ?? "default";
23523
23577
  },
23524
- registerDebugStreamCallback: (cb) => {
23525
- void import('@wrongstack/providers').then(({ setDebugStreamCallback }) => setDebugStreamCallback(cb)).catch(
23526
- (err) => console.error(
23578
+ registerDebugStreamCallback: async (cb) => {
23579
+ try {
23580
+ const { setDebugStreamCallback } = await import('@wrongstack/providers');
23581
+ setDebugStreamCallback(cb);
23582
+ } catch (err) {
23583
+ console.error(
23527
23584
  JSON.stringify({
23528
23585
  level: "error",
23529
23586
  event: "execution.debug_stream_register_failed",
23530
23587
  message: err instanceof Error ? err.message : String(err),
23531
23588
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
23532
23589
  })
23533
- )
23534
- );
23590
+ );
23591
+ }
23535
23592
  },
23536
- restoreDebugStreamCallback: () => {
23537
- void import('@wrongstack/providers').then(
23538
- ({ setDebugStreamCallback, defaultDebugStreamCallback }) => setDebugStreamCallback(defaultDebugStreamCallback)
23539
- ).catch(
23540
- (err) => console.error(
23593
+ restoreDebugStreamCallback: async () => {
23594
+ try {
23595
+ const { setDebugStreamCallback, defaultDebugStreamCallback } = await import('@wrongstack/providers');
23596
+ setDebugStreamCallback(defaultDebugStreamCallback);
23597
+ } catch (err) {
23598
+ console.error(
23541
23599
  JSON.stringify({
23542
23600
  level: "error",
23543
23601
  event: "execution.debug_stream_restore_failed",
23544
23602
  message: err instanceof Error ? err.message : String(err),
23545
23603
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
23546
23604
  })
23547
- )
23548
- );
23605
+ );
23606
+ }
23549
23607
  },
23550
23608
  restoredMessages,
23551
23609
  restoredToolCalls,
@@ -23600,12 +23658,14 @@ async function execute(deps) {
23600
23658
  if (oldWriter && oldWriter !== resumed.writer) {
23601
23659
  const endedUsage = tokenCounter.total();
23602
23660
  void (async () => {
23661
+ let appendOk = false;
23603
23662
  try {
23604
23663
  await oldWriter.append({
23605
23664
  type: "session_end",
23606
23665
  ts: (/* @__PURE__ */ new Date()).toISOString(),
23607
23666
  usage: endedUsage
23608
23667
  });
23668
+ appendOk = true;
23609
23669
  } catch (err) {
23610
23670
  console.error(
23611
23671
  JSON.stringify({
@@ -23616,32 +23676,50 @@ async function execute(deps) {
23616
23676
  })
23617
23677
  );
23618
23678
  }
23619
- try {
23620
- await oldWriter.close();
23621
- } catch (err) {
23622
- console.error(
23623
- JSON.stringify({
23624
- level: "error",
23625
- event: "execution.session_close_failed",
23626
- message: err instanceof Error ? err.message : String(err),
23627
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
23628
- })
23629
- );
23679
+ if (appendOk) {
23680
+ try {
23681
+ await oldWriter.close();
23682
+ } catch (err) {
23683
+ console.error(
23684
+ JSON.stringify({
23685
+ level: "error",
23686
+ event: "execution.session_close_failed",
23687
+ message: err instanceof Error ? err.message : String(err),
23688
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
23689
+ })
23690
+ );
23691
+ }
23630
23692
  }
23631
23693
  })();
23632
23694
  }
23633
23695
  agent.ctx.session = resumed.writer;
23634
23696
  tokenCounter.reset();
23635
- void recoveryLock.clear().then(() => recoveryLock.write(resumed.writer.id)).catch(
23636
- (err) => console.error(
23637
- JSON.stringify({
23638
- level: "error",
23639
- event: "execution.recovery_lock_update_failed",
23640
- message: err instanceof Error ? err.message : String(err),
23641
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
23642
- })
23643
- )
23644
- );
23697
+ void (async () => {
23698
+ try {
23699
+ await recoveryLock.clear();
23700
+ } catch (err) {
23701
+ console.error(
23702
+ JSON.stringify({
23703
+ level: "warn",
23704
+ event: "execution.recovery_lock_clear_failed",
23705
+ message: err instanceof Error ? err.message : String(err),
23706
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
23707
+ })
23708
+ );
23709
+ }
23710
+ try {
23711
+ await recoveryLock.write(resumed.writer.id);
23712
+ } catch (err) {
23713
+ console.error(
23714
+ JSON.stringify({
23715
+ level: "error",
23716
+ event: "execution.recovery_lock_update_failed",
23717
+ message: err instanceof Error ? err.message : String(err),
23718
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
23719
+ })
23720
+ );
23721
+ }
23722
+ })();
23645
23723
  const { replaySessionEvents } = await import('@wrongstack/tui');
23646
23724
  const entries = replaySessionEvents(
23647
23725
  resumed.data.events,
@@ -23870,6 +23948,9 @@ ${parts.join("\n")}
23870
23948
  getNextPredict,
23871
23949
  onSuggestionsParsed,
23872
23950
  getSuggestions: getSuggestions2,
23951
+ getAutoSuggestions,
23952
+ getYolo,
23953
+ autonomyNextPrompt,
23873
23954
  autoProceedDelayMs,
23874
23955
  onValidateAutoProceed,
23875
23956
  autoProceedMaxIterations,
@@ -23984,7 +24065,7 @@ function buildRoutingRunner(config, host) {
23984
24065
  }
23985
24066
 
23986
24067
  // src/fleet/host.ts
23987
- var MultiAgentHost = class {
24068
+ var MultiAgentHost = class _MultiAgentHost {
23988
24069
  constructor(deps, opts = {}) {
23989
24070
  this.deps = deps;
23990
24071
  this.opts = opts;
@@ -24013,6 +24094,19 @@ var MultiAgentHost = class {
24013
24094
  * actionable error instead of a generic "Director could not be activated".
24014
24095
  */
24015
24096
  promotionBlockReason = null;
24097
+ /** Guards `buildDirector` from overwriting a runner set by `spawnACP`. */
24098
+ directorRunnerSet = false;
24099
+ /** Event-bus off-handles registered in `buildDirector` — cleaned up in `dispose()`. */
24100
+ directorOffHandles = [];
24101
+ /** Coordinator task.assigned listener — cleaned up in `dispose()`. */
24102
+ coordinatorOffHandle = null;
24103
+ /** ACP runner cache — keyed by role/subagentId, reused across tasks to avoid
24104
+ * creating a new transport process on every ACP task dispatch. Stores the
24105
+ * pending promise so concurrent calls for the same subagentId share one spawn.
24106
+ * Bounded to 20 entries with LRU eviction to prevent unbounded memory growth. */
24107
+ acpRunnerCache = /* @__PURE__ */ new Map();
24108
+ acpRunnerAccessOrder = [];
24109
+ static ACP_CACHE_MAX = 20;
24016
24110
  /**
24017
24111
  * Force the lazy build path to run *now* and return the live Director,
24018
24112
  * or null when director mode is off. Used by the CLI to register the
@@ -24094,60 +24188,71 @@ var MultiAgentHost = class {
24094
24188
  this.fleetManager?.removePendingTask(task.id);
24095
24189
  this.emitLifecycleCompleted(task.id, result);
24096
24190
  });
24097
- this.director.fleet.filter("budget.threshold_reached", (e) => {
24098
- const payload = e.payload;
24099
- this.deps.events.emit("subagent.budget_warning", {
24100
- subagentId: e.subagentId,
24101
- kind: payload.kind,
24102
- used: payload.used,
24103
- limit: payload.limit
24104
- });
24105
- });
24106
- this.director.fleet.filter("budget.extended", (e) => {
24107
- const payload = e.payload;
24108
- this.deps.events.emit("subagent.budget_extended", {
24109
- subagentId: e.subagentId,
24110
- kind: payload.kind,
24111
- newLimit: payload.newLimit,
24112
- totalExtensions: payload.totalExtensions
24113
- });
24114
- });
24115
- this.director.fleet.filter("ctx.pct", (e) => {
24116
- const payload = e.payload;
24117
- this.deps.events.emit("subagent.ctx_pct", {
24118
- subagentId: e.subagentId,
24119
- load: payload.load,
24120
- tokens: payload.tokens,
24121
- maxContext: payload.maxContext
24122
- });
24123
- });
24124
- this.director.fleet.filter("subagent.spawned", (e) => {
24125
- const payload = e.payload;
24126
- this.deps.events.emit("subagent.spawned", {
24127
- subagentId: payload.subagentId,
24128
- taskId: payload.taskId,
24129
- name: payload.name,
24130
- provider: payload.provider,
24131
- model: payload.model
24132
- });
24133
- });
24134
- this.getCoordinator().on(
24135
- "task.assigned",
24136
- ({
24137
- task,
24138
- subagentId
24139
- }) => {
24140
- this.deps.events.emit("subagent.task_started", {
24141
- subagentId,
24142
- taskId: task.id,
24143
- description: task.description
24191
+ this.directorOffHandles.push(
24192
+ this.director.fleet.filter("budget.threshold_reached", (e) => {
24193
+ const payload = e.payload;
24194
+ this.deps.events.emit("subagent.budget_warning", {
24195
+ subagentId: e.subagentId,
24196
+ kind: payload.kind,
24197
+ used: payload.used,
24198
+ limit: payload.limit
24144
24199
  });
24145
- }
24200
+ })
24146
24201
  );
24202
+ this.directorOffHandles.push(
24203
+ this.director.fleet.filter("budget.extended", (e) => {
24204
+ const payload = e.payload;
24205
+ this.deps.events.emit("subagent.budget_extended", {
24206
+ subagentId: e.subagentId,
24207
+ kind: payload.kind,
24208
+ newLimit: payload.newLimit,
24209
+ totalExtensions: payload.totalExtensions
24210
+ });
24211
+ })
24212
+ );
24213
+ this.directorOffHandles.push(
24214
+ this.director.fleet.filter("ctx.pct", (e) => {
24215
+ const payload = e.payload;
24216
+ this.deps.events.emit("subagent.ctx_pct", {
24217
+ subagentId: e.subagentId,
24218
+ load: payload.load,
24219
+ tokens: payload.tokens,
24220
+ maxContext: payload.maxContext
24221
+ });
24222
+ })
24223
+ );
24224
+ this.directorOffHandles.push(
24225
+ this.director.fleet.filter("subagent.spawned", (e) => {
24226
+ const payload = e.payload;
24227
+ this.deps.events.emit("subagent.spawned", {
24228
+ subagentId: payload.subagentId,
24229
+ taskId: payload.taskId,
24230
+ name: payload.name,
24231
+ provider: payload.provider,
24232
+ model: payload.model
24233
+ });
24234
+ })
24235
+ );
24236
+ const coordinatorTaskAssignedHandler = ({
24237
+ task,
24238
+ subagentId
24239
+ }) => {
24240
+ this.deps.events.emit("subagent.task_started", {
24241
+ subagentId,
24242
+ taskId: task.id,
24243
+ description: task.description
24244
+ });
24245
+ };
24246
+ const coordinator = this.getCoordinator();
24247
+ coordinator.on("task.assigned", coordinatorTaskAssignedHandler);
24248
+ this.coordinatorOffHandle = () => coordinator.off("task.assigned", coordinatorTaskAssignedHandler);
24147
24249
  this.fleetEmitTool = makeFleetEmitTool(this.director);
24148
24250
  this.fleetStatusTool = makeFleetStatusTool(this.director);
24149
24251
  const runner = await this.buildSubagentRunner(config);
24150
- this.getCoordinator().setRunner(runner);
24252
+ if (!this.directorRunnerSet) {
24253
+ this.getCoordinator().setRunner(runner);
24254
+ this.directorRunnerSet = true;
24255
+ }
24151
24256
  }
24152
24257
  /**
24153
24258
  * Returns the FleetEmitTool for director-mode subagents, if the director
@@ -24349,9 +24454,23 @@ var MultiAgentHost = class {
24349
24454
  return buildRoutingRunner(config, this);
24350
24455
  }
24351
24456
  async buildACPRunner(subagentId) {
24457
+ const cached = this.acpRunnerCache.get(subagentId);
24458
+ if (cached) {
24459
+ const idx = this.acpRunnerAccessOrder.indexOf(subagentId);
24460
+ if (idx !== -1) this.acpRunnerAccessOrder.splice(idx, 1);
24461
+ this.acpRunnerAccessOrder.push(subagentId);
24462
+ return cached;
24463
+ }
24352
24464
  const cmd = ACP_AGENT_COMMANDS[subagentId];
24353
24465
  if (!cmd) throw new Error(`Unknown ACP agent: ${subagentId}`);
24354
- return makeACPSubagentRunner(cmd);
24466
+ while (this.acpRunnerAccessOrder.length >= _MultiAgentHost.ACP_CACHE_MAX) {
24467
+ const oldest = this.acpRunnerAccessOrder.shift();
24468
+ if (oldest) this.acpRunnerCache.delete(oldest);
24469
+ }
24470
+ const p = makeACPSubagentRunner(cmd);
24471
+ this.acpRunnerCache.set(subagentId, p);
24472
+ this.acpRunnerAccessOrder.push(subagentId);
24473
+ return p;
24355
24474
  }
24356
24475
  /**
24357
24476
  * Build a Provider for a subagent. When `overrideId` is supplied (from
@@ -24394,6 +24513,7 @@ var MultiAgentHost = class {
24394
24513
  const coordinator = this.getCoordinator();
24395
24514
  const acpRunner = await this.buildACPRunner(subagentId);
24396
24515
  coordinator.setRunner(acpRunner);
24516
+ this.directorRunnerSet = true;
24397
24517
  await coordinator.spawn({
24398
24518
  id: subagentId,
24399
24519
  name: subagentId,
@@ -24463,8 +24583,9 @@ var MultiAgentHost = class {
24463
24583
  */
24464
24584
  async spawnAndWait(description, opts) {
24465
24585
  const { taskId } = await this.spawn(description, opts);
24466
- if (!this.director) throw new Error("Director is not initialized");
24467
- const results = await this.director.awaitTasks([taskId]);
24586
+ const director = this.director;
24587
+ if (!director) throw new Error("Director is not initialized");
24588
+ const results = await director.awaitTasks([taskId]);
24468
24589
  const result = results[0];
24469
24590
  if (!result) throw new Error(`Task ${taskId} completed but no result returned`);
24470
24591
  return result;
@@ -24678,6 +24799,24 @@ var MultiAgentHost = class {
24678
24799
  this.getCoordinator().setMaxConcurrent(v);
24679
24800
  }
24680
24801
  }
24802
+ /**
24803
+ * Clean up all listeners and resources held by the host.
24804
+ * Unregisters all EventBus/FleetBus listeners registered in `buildDirector`
24805
+ * and stops the Director and its coordinator.
24806
+ *
24807
+ * Safe to call multiple times — subsequent calls are no-ops.
24808
+ */
24809
+ async dispose() {
24810
+ for (const off of this.directorOffHandles) {
24811
+ off();
24812
+ }
24813
+ this.directorOffHandles.length = 0;
24814
+ this.coordinatorOffHandle?.();
24815
+ this.coordinatorOffHandle = null;
24816
+ if (this.director) {
24817
+ await this.director.shutdown();
24818
+ }
24819
+ }
24681
24820
  };
24682
24821
 
24683
24822
  // src/session-stats.ts
@@ -27014,6 +27153,9 @@ Restart WrongStack to load or unload plugin code in this session.`;
27014
27153
  brainMonitor.stop();
27015
27154
  brainQueue.dispose();
27016
27155
  void mcpRegistry.stopAll();
27156
+ multiAgentHost.dispose().catch(
27157
+ (err) => logger.warn(`multiAgentHost.dispose() failed: ${err instanceof Error ? err.message : String(err)}`)
27158
+ );
27017
27159
  },
27018
27160
  onBeforeExit: async () => {
27019
27161
  const cwd2 = projectRoot;
@@ -27295,8 +27437,11 @@ Reply YES to auto-proceed, NO to wait for human input.`
27295
27437
  brain,
27296
27438
  brainSettings,
27297
27439
  getBrainLog: () => brainLog,
27298
- // Clean up SessionStats event listeners when the REPL exits.
27299
- onDestroy: () => stats.destroy(events)
27440
+ // Clean up SessionStats event listeners and all EventBus handlers when the REPL exits.
27441
+ onDestroy: () => {
27442
+ teardownHandlers.forEach((fn) => fn());
27443
+ stats.destroy(events);
27444
+ }
27300
27445
  });
27301
27446
  }
27302
27447
  var isMain = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}` || process.argv[1]?.endsWith("/cli/dist/index.js") || process.argv[1]?.endsWith("\\cli\\dist\\index.js");