@wrongstack/cli 0.1.7 → 0.1.8

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { color, DefaultLogger, DefaultModelsRegistry, 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, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader, makeAgentSubagentRunner, DefaultMultiAgentCoordinator, decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core';
2
+ import { color, DefaultLogger, DefaultModelsRegistry, 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, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader, makeAgentSubagentRunner, Director, DefaultMultiAgentCoordinator, decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core';
3
3
  import { WebSocketServer, WebSocket } from 'ws';
4
4
  import * as fs6 from 'fs/promises';
5
5
  import { writeFileSync } from 'fs';
@@ -1046,6 +1046,7 @@ function buildBuiltinSlashCommands(opts) {
1046
1046
  statsCommand(opts),
1047
1047
  spawnCommand(opts),
1048
1048
  agentsCommand(opts),
1049
+ fleetCommand(opts),
1049
1050
  metricsCommand(opts),
1050
1051
  healthCommand(opts),
1051
1052
  memoryCommand(opts),
@@ -1673,18 +1674,46 @@ function statusIcon(status) {
1673
1674
  if (status === "degraded") return color.yellow("\u25CF");
1674
1675
  return color.red("\u25CF");
1675
1676
  }
1677
+ function parseSpawnFlags(input) {
1678
+ const opts = {};
1679
+ let rest = input;
1680
+ const consume = (re) => {
1681
+ const m = rest.match(re);
1682
+ if (m) {
1683
+ rest = rest.slice(m[0].length).replace(/^\s+/, "");
1684
+ return m;
1685
+ }
1686
+ return null;
1687
+ };
1688
+ while (rest.length > 0) {
1689
+ let m;
1690
+ if (m = consume(/^--provider=(\S+)\s*/)) opts.provider = m[1];
1691
+ else if (m = consume(/^--model=(\S+)\s*/)) opts.model = m[1];
1692
+ else if (m = consume(/^--name=("([^"]+)"|(\S+))\s*/)) opts.name = m[2] ?? m[3];
1693
+ else if (m = consume(/^--tools=(\S+)\s*/)) opts.tools = m[1].split(",").map((t) => t.trim()).filter(Boolean);
1694
+ else if (m = consume(/^-p\s+(\S+)\s*/)) opts.provider = m[1];
1695
+ else if (m = consume(/^-m\s+(\S+)\s*/)) opts.model = m[1];
1696
+ else if (m = consume(/^-n\s+("([^"]+)"|(\S+))\s*/)) opts.name = m[2] ?? m[3];
1697
+ else break;
1698
+ }
1699
+ return { description: rest.trim(), opts };
1700
+ }
1676
1701
  function spawnCommand(opts) {
1677
1702
  return {
1678
1703
  name: "spawn",
1679
- description: "Spawn an isolated subagent to handle a task. Usage: /spawn <task description>",
1704
+ description: "Spawn an isolated subagent to handle a task. Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>",
1680
1705
  async run(args) {
1681
- const description = args.trim();
1682
- if (!description) return { message: "Usage: /spawn <task description>" };
1706
+ const { description, opts: parsed } = parseSpawnFlags(args.trim());
1707
+ if (!description) {
1708
+ return {
1709
+ message: "Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>"
1710
+ };
1711
+ }
1683
1712
  if (!opts.onSpawn) {
1684
1713
  return { message: "Multi-agent is not enabled in this session." };
1685
1714
  }
1686
1715
  try {
1687
- const summary = await opts.onSpawn(description);
1716
+ const summary = Object.keys(parsed).length > 0 ? await opts.onSpawn(description, parsed) : await opts.onSpawn(description);
1688
1717
  return { message: summary };
1689
1718
  } catch (err) {
1690
1719
  return {
@@ -1706,6 +1735,63 @@ function agentsCommand(opts) {
1706
1735
  }
1707
1736
  };
1708
1737
  }
1738
+ function fleetCommand(opts) {
1739
+ return {
1740
+ name: "fleet",
1741
+ description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|help]",
1742
+ help: [
1743
+ "Usage:",
1744
+ " /fleet Show fleet status (alias for /fleet status).",
1745
+ " /fleet status Pending + completed subagent task table.",
1746
+ " /fleet usage Per-subagent runtime cost \u2014 iterations, tool calls, duration.",
1747
+ " /fleet kill <id> Terminate a running subagent by id (or prefix).",
1748
+ " /fleet manifest Print the director manifest (only with --director).",
1749
+ " /fleet help Show this help.",
1750
+ "",
1751
+ "Subagent ids are returned by /spawn and listed in /fleet status."
1752
+ ].join("\n"),
1753
+ async run(args) {
1754
+ if (!opts.onFleet) {
1755
+ return { message: "Multi-agent is not enabled in this session." };
1756
+ }
1757
+ const trimmed = args.trim();
1758
+ const [verb, ...rest] = trimmed.length === 0 ? ["status"] : trimmed.split(/\s+/);
1759
+ const target = rest.join(" ").trim() || void 0;
1760
+ switch (verb) {
1761
+ case "status":
1762
+ case "usage":
1763
+ case "manifest": {
1764
+ const out = await opts.onFleet(verb, void 0);
1765
+ return { message: out };
1766
+ }
1767
+ case "kill": {
1768
+ if (!target) {
1769
+ return { message: "Usage: /fleet kill <subagent-id>" };
1770
+ }
1771
+ const out = await opts.onFleet("kill", target);
1772
+ return { message: out };
1773
+ }
1774
+ case "help":
1775
+ case "?":
1776
+ return {
1777
+ message: [
1778
+ "/fleet \u2014 inspect or control the subagent fleet",
1779
+ "",
1780
+ " /fleet \u2192 status (default)",
1781
+ " /fleet status pending + completed tasks per subagent",
1782
+ " /fleet usage iterations, tool calls, duration roll-up",
1783
+ " /fleet kill <id> terminate a subagent",
1784
+ " /fleet manifest director manifest (requires --director)"
1785
+ ].join("\n")
1786
+ };
1787
+ default:
1788
+ return {
1789
+ message: `Unknown subcommand "${verb}". Try: status | usage | kill <id> | manifest | help`
1790
+ };
1791
+ }
1792
+ }
1793
+ };
1794
+ }
1709
1795
 
1710
1796
  // src/pre-launch.ts
1711
1797
  var MANIFESTS = [
@@ -2474,25 +2560,31 @@ async function ensureProjectMeta(paths, projectRoot) {
2474
2560
  }
2475
2561
  }
2476
2562
  var MultiAgentHost = class {
2477
- constructor(deps) {
2563
+ constructor(deps, opts = {}) {
2478
2564
  this.deps = deps;
2565
+ this.opts = opts;
2479
2566
  }
2480
2567
  deps;
2481
2568
  coordinator;
2569
+ /** Lazily built when `opts.directorMode` is set. Owns its own internal
2570
+ * coordinator; the host's `coordinator` field still points at it so
2571
+ * the rest of the methods don't need to branch. */
2572
+ director;
2482
2573
  pending = /* @__PURE__ */ new Map();
2483
2574
  results = [];
2575
+ opts;
2484
2576
  async ensureCoordinator() {
2485
2577
  if (this.coordinator) return this.coordinator;
2486
2578
  const config = this.deps.configStore.get();
2487
2579
  const factory = async (subCfg) => {
2488
2580
  const events = new EventBus();
2489
- const provider = await this.buildSubagentProvider(config);
2581
+ const provider = await this.buildSubagentProvider(config, subCfg.provider);
2490
2582
  const baseSystem = await this.deps.systemPromptBuilder.build({
2491
2583
  cwd: this.deps.cwd,
2492
2584
  projectRoot: this.deps.projectRoot,
2493
2585
  tools: this.filterTools(subCfg.tools),
2494
2586
  model: subCfg.model ?? config.model,
2495
- provider: config.provider
2587
+ provider: subCfg.provider ?? config.provider
2496
2588
  });
2497
2589
  const parentSession = this.deps.session;
2498
2590
  const subSession = {
@@ -2526,15 +2618,26 @@ var MultiAgentHost = class {
2526
2618
  return { agent, events };
2527
2619
  };
2528
2620
  const runner = makeAgentSubagentRunner({ factory });
2529
- this.coordinator = new DefaultMultiAgentCoordinator(
2530
- {
2531
- coordinatorId: randomUUID(),
2532
- doneCondition: { type: "all_tasks_done" },
2533
- maxConcurrent: 2,
2534
- defaultBudget: { maxToolCalls: 20, maxIterations: 20, timeoutMs: 12e4 }
2535
- },
2536
- { runner }
2537
- );
2621
+ const coordinatorConfig = {
2622
+ coordinatorId: randomUUID(),
2623
+ doneCondition: { type: "all_tasks_done" },
2624
+ maxConcurrent: 2,
2625
+ defaultBudget: { maxToolCalls: 20, maxIterations: 20, timeoutMs: 12e4 }
2626
+ };
2627
+ if (this.opts.directorMode) {
2628
+ this.director = new Director({
2629
+ config: coordinatorConfig,
2630
+ runner,
2631
+ manifestPath: this.opts.manifestPath
2632
+ });
2633
+ this.director.on("task.completed", ({ task, result }) => {
2634
+ this.results.push(result);
2635
+ this.pending.delete(task.id);
2636
+ });
2637
+ this.coordinator = this.director.coordinator;
2638
+ return this.coordinator;
2639
+ }
2640
+ this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, { runner });
2538
2641
  this.coordinator.on(
2539
2642
  "task.completed",
2540
2643
  ({ task, result }) => {
@@ -2544,15 +2647,24 @@ var MultiAgentHost = class {
2544
2647
  );
2545
2648
  return this.coordinator;
2546
2649
  }
2547
- async buildSubagentProvider(config) {
2548
- const newCfg = config.providers?.[config.provider] ?? {
2549
- type: config.provider,
2650
+ /**
2651
+ * Build a Provider for a subagent. When `overrideId` is supplied (from
2652
+ * `SubagentConfig.provider`), looks that provider up in
2653
+ * `config.providers` and constructs it with its own apiKey/baseUrl.
2654
+ * Falls back to the leader's provider when `overrideId` is absent or
2655
+ * not configured (so a typo doesn't crash the whole run — we just
2656
+ * use the leader and the calling code can decide to error later).
2657
+ */
2658
+ async buildSubagentProvider(config, overrideId) {
2659
+ const providerId = overrideId && config.providers?.[overrideId] ? overrideId : config.provider;
2660
+ const newCfg = config.providers?.[providerId] ?? {
2661
+ type: providerId,
2550
2662
  apiKey: config.apiKey,
2551
2663
  baseUrl: config.baseUrl
2552
2664
  };
2553
- return makeProviderFromConfig(config.provider, {
2665
+ return makeProviderFromConfig(providerId, {
2554
2666
  ...newCfg,
2555
- type: config.provider
2667
+ type: providerId
2556
2668
  });
2557
2669
  }
2558
2670
  /** Returns a tool slice for the subagent — full set unless restricted. */
@@ -2569,15 +2681,40 @@ var MultiAgentHost = class {
2569
2681
  for (const t of this.filterTools(allow)) sub.register(t);
2570
2682
  return sub;
2571
2683
  }
2572
- /** Spawn a fresh subagent and assign a single task. Returns task id. */
2573
- async spawn(description) {
2574
- const coord = await this.ensureCoordinator();
2575
- const spawned = await coord.spawn({
2576
- name: "adhoc",
2684
+ /**
2685
+ * Spawn a fresh subagent and assign a single task. Returns task id.
2686
+ *
2687
+ * Optional `opts` lets the caller (a `/spawn` slash command or the
2688
+ * future director surface) override the subagent's provider, model,
2689
+ * and tool slice on a per-spawn basis. Without options, the legacy
2690
+ * behavior holds: the subagent uses the leader's provider/model and
2691
+ * the full tool registry.
2692
+ */
2693
+ async spawn(description, opts) {
2694
+ await this.ensureCoordinator();
2695
+ const subagentConfig = {
2696
+ name: opts?.name ?? "adhoc",
2577
2697
  role: "general",
2578
2698
  maxToolCalls: 20,
2579
- maxIterations: 20
2580
- });
2699
+ maxIterations: 20,
2700
+ provider: opts?.provider,
2701
+ model: opts?.model,
2702
+ tools: opts?.tools
2703
+ };
2704
+ if (this.director) {
2705
+ const subagentId = await this.director.spawn(subagentConfig);
2706
+ const taskId2 = randomUUID();
2707
+ this.pending.set(taskId2, { description, subagentId });
2708
+ await this.director.assign({
2709
+ id: taskId2,
2710
+ description,
2711
+ subagentId,
2712
+ maxToolCalls: 20
2713
+ });
2714
+ return { subagentId, taskId: taskId2 };
2715
+ }
2716
+ const coord = this.coordinator;
2717
+ const spawned = await coord.spawn(subagentConfig);
2581
2718
  const taskId = randomUUID();
2582
2719
  this.pending.set(taskId, { description, subagentId: spawned.subagentId });
2583
2720
  await coord.assign({
@@ -2597,6 +2734,79 @@ var MultiAgentHost = class {
2597
2734
  const summary = !this.coordinator ? "No subagents have been spawned." : `${pending.length} pending, ${this.results.length} completed.`;
2598
2735
  return { pending, completed: this.results, summary };
2599
2736
  }
2737
+ /**
2738
+ * Roll up per-subagent runtime cost from completed TaskResults. We don't
2739
+ * yet have FleetUsageAggregator wired into the simple MultiAgentHost
2740
+ * path (that lives on `Director`), so this aggregates iterations / tool
2741
+ * calls / duration which we *do* have — enough to spot a thrashing
2742
+ * worker without paying for a heavier orchestrator on every /spawn.
2743
+ *
2744
+ * Returns rows sorted by total duration descending (slowest first) so
2745
+ * the table renders the most interesting subagent at the top.
2746
+ */
2747
+ usage() {
2748
+ const bySubagent = /* @__PURE__ */ new Map();
2749
+ for (const r of this.results) {
2750
+ const cur = bySubagent.get(r.subagentId) ?? { tasks: 0, iterations: 0, toolCalls: 0, durationMs: 0, lastStatus: "unknown" };
2751
+ cur.tasks += 1;
2752
+ cur.iterations += r.iterations;
2753
+ cur.toolCalls += r.toolCalls;
2754
+ cur.durationMs += r.durationMs;
2755
+ cur.lastStatus = r.status;
2756
+ bySubagent.set(r.subagentId, cur);
2757
+ }
2758
+ const rows = Array.from(bySubagent.entries()).map(([subagentId, v]) => ({
2759
+ subagentId,
2760
+ tasks: v.tasks,
2761
+ iterations: v.iterations,
2762
+ toolCalls: v.toolCalls,
2763
+ durationMs: v.durationMs,
2764
+ status: v.lastStatus
2765
+ })).sort((a, b) => b.durationMs - a.durationMs);
2766
+ const totals = rows.reduce(
2767
+ (acc, r) => ({
2768
+ tasks: acc.tasks + r.tasks,
2769
+ iterations: acc.iterations + r.iterations,
2770
+ toolCalls: acc.toolCalls + r.toolCalls,
2771
+ durationMs: acc.durationMs + r.durationMs
2772
+ }),
2773
+ { tasks: 0, iterations: 0, toolCalls: 0, durationMs: 0 }
2774
+ );
2775
+ return { rows, totals };
2776
+ }
2777
+ /**
2778
+ * Force the director to write its manifest to disk and return the path,
2779
+ * or `null` when director mode is off (the simple coordinator path has
2780
+ * no manifest). Callers should fall back to a friendly user message
2781
+ * when `null` is returned — e.g. `/fleet manifest` does this already.
2782
+ *
2783
+ * The returned string is the absolute path of the manifest file. The
2784
+ * file contents are JSON; readers can `JSON.parse(fs.readFileSync(...))`
2785
+ * to consume.
2786
+ */
2787
+ async manifest() {
2788
+ if (!this.director) return null;
2789
+ return this.director.writeManifest();
2790
+ }
2791
+ /**
2792
+ * True when this host is running in director mode. Surfaces the mode
2793
+ * to slash commands and tests without exposing the underlying Director
2794
+ * (which would let callers bypass the host's coordination layer).
2795
+ */
2796
+ isDirectorMode() {
2797
+ return !!this.director;
2798
+ }
2799
+ /**
2800
+ * Terminate a single subagent. Returns true when the subagent existed
2801
+ * (regardless of whether stop() succeeded or it was already idle),
2802
+ * false when no coordinator has been created yet — meaning the user
2803
+ * called /fleet kill before any /spawn, and there's nothing to do.
2804
+ */
2805
+ async kill(subagentId) {
2806
+ if (!this.coordinator) return false;
2807
+ await this.coordinator.stop(subagentId);
2808
+ return true;
2809
+ }
2600
2810
  async stopAll() {
2601
2811
  if (this.coordinator) {
2602
2812
  await this.coordinator.stopAll();
@@ -3933,7 +4143,8 @@ async function helpCmd(_args, deps) {
3933
4143
  " wstack version Print version",
3934
4144
  "",
3935
4145
  "Global flags:",
3936
- " --provider, --model, --cwd, --log-level, --yolo, --verbose, --trace, --config"
4146
+ " --provider, --model, --cwd, --log-level, --yolo, --verbose, --trace, --config",
4147
+ " --director Run with Director-backed orchestration (writes fleet manifest)"
3937
4148
  ];
3938
4149
  deps.renderer.write(lines.join("\n") + "\n");
3939
4150
  return 0;
@@ -4677,6 +4888,8 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
4677
4888
  return err instanceof Error ? err.message : String(err);
4678
4889
  }
4679
4890
  };
4891
+ const directorMode = flags["director"] === true;
4892
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path5.join(wpaths.projectSessions, session.id, "fleet.json") : void 0;
4680
4893
  const multiAgentHost = new MultiAgentHost({
4681
4894
  container,
4682
4895
  toolRegistry,
@@ -4688,7 +4901,10 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
4688
4901
  tokenCounter,
4689
4902
  projectRoot,
4690
4903
  cwd
4691
- });
4904
+ }, { directorMode, manifestPath });
4905
+ if (directorMode) {
4906
+ renderer.writeInfo(`Director mode enabled. Fleet manifest \u2192 ${manifestPath}`);
4907
+ }
4692
4908
  const slashCmds = buildBuiltinSlashCommands({
4693
4909
  registry: slashRegistry,
4694
4910
  toolRegistry,
@@ -4701,9 +4917,14 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
4701
4917
  context,
4702
4918
  metricsSink,
4703
4919
  healthRegistry,
4704
- onSpawn: async (description) => {
4705
- const { subagentId, taskId } = await multiAgentHost.spawn(description);
4706
- return `Spawned subagent ${subagentId} for task ${taskId}. Use /agents to track progress.`;
4920
+ onSpawn: async (description, spawnOpts) => {
4921
+ const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
4922
+ const tags = [];
4923
+ if (spawnOpts?.provider) tags.push(spawnOpts.provider);
4924
+ if (spawnOpts?.model) tags.push(spawnOpts.model);
4925
+ if (spawnOpts?.name) tags.push(`"${spawnOpts.name}"`);
4926
+ const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
4927
+ return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
4707
4928
  },
4708
4929
  onAgents: () => {
4709
4930
  const s = multiAgentHost.status();
@@ -4718,6 +4939,60 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
4718
4939
  }
4719
4940
  return lines.join("\n");
4720
4941
  },
4942
+ onFleet: async (action, target) => {
4943
+ if (action === "status") {
4944
+ const s = multiAgentHost.status();
4945
+ const lines = [color.bold("Fleet status"), ` ${s.summary}`];
4946
+ if (s.pending.length > 0) {
4947
+ lines.push("", color.dim(" Pending"));
4948
+ for (const p of s.pending) {
4949
+ lines.push(` ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`);
4950
+ }
4951
+ }
4952
+ if (s.completed.length > 0) {
4953
+ lines.push("", color.dim(" Completed"));
4954
+ for (const r of s.completed) {
4955
+ const mark = r.status === "success" ? color.green("\u2713") : color.red("\u2717");
4956
+ lines.push(` ${mark} ${r.taskId.slice(0, 8)} \u2192 ${r.subagentId.slice(0, 8)} \xB7 ${r.iterations}it ${r.toolCalls}tc ${r.durationMs}ms`);
4957
+ }
4958
+ }
4959
+ return lines.join("\n");
4960
+ }
4961
+ if (action === "usage") {
4962
+ const u = multiAgentHost.usage();
4963
+ if (u.rows.length === 0) return "No completed subagent tasks yet.";
4964
+ const lines = [
4965
+ color.bold("Fleet usage"),
4966
+ color.dim(" subagent tasks iter tools ms status")
4967
+ ];
4968
+ for (const r of u.rows) {
4969
+ lines.push(
4970
+ ` ${r.subagentId.slice(0, 14).padEnd(14)} ${String(r.tasks).padStart(5)} ${String(r.iterations).padStart(4)} ${String(r.toolCalls).padStart(5)} ${String(r.durationMs).padStart(5)} ${r.status}`
4971
+ );
4972
+ }
4973
+ lines.push(
4974
+ color.dim(" \u2500".repeat(28)),
4975
+ ` ${"TOTAL".padEnd(14)} ${String(u.totals.tasks).padStart(5)} ${String(u.totals.iterations).padStart(4)} ${String(u.totals.toolCalls).padStart(5)} ${String(u.totals.durationMs).padStart(5)}`
4976
+ );
4977
+ return lines.join("\n");
4978
+ }
4979
+ if (action === "kill") {
4980
+ if (!target) return "Usage: /fleet kill <subagent-id>";
4981
+ const ok = await multiAgentHost.kill(target);
4982
+ return ok ? `Sent stop signal to ${target}.` : "No coordinator is running yet \u2014 nothing to kill.";
4983
+ }
4984
+ if (action === "manifest") {
4985
+ if (!multiAgentHost.isDirectorMode()) {
4986
+ return "Manifest is only available when the run was started with --director.";
4987
+ }
4988
+ const p = await multiAgentHost.manifest();
4989
+ if (!p) {
4990
+ return "Director is active but no subagents have been spawned \u2014 nothing to record yet.";
4991
+ }
4992
+ return `Manifest written \u2192 ${p}`;
4993
+ }
4994
+ return `Unknown fleet action: ${action}`;
4995
+ },
4721
4996
  onExit: () => {
4722
4997
  void mcpRegistry.stopAll();
4723
4998
  },