@wrongstack/cli 0.5.6 → 0.5.7

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,7 +3,7 @@ import * as path21 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, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
6
+ import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
7
7
  import { createRequire } from 'module';
8
8
  import * as os6 from 'os';
9
9
  import os6__default from 'os';
@@ -7339,6 +7339,8 @@ async function execute(deps) {
7339
7339
  result = await agent.run(query, { signal: ctrl.signal });
7340
7340
  } finally {
7341
7341
  process.off("SIGINT", onSigint);
7342
+ const { getProcessRegistry } = await import('@wrongstack/tools');
7343
+ getProcessRegistry().killAll();
7342
7344
  }
7343
7345
  const after = tokenCounter.total();
7344
7346
  const costAfter = tokenCounter.estimateCost().total;
@@ -7537,22 +7539,18 @@ var MultiAgentHost = class {
7537
7539
  this.opts = opts;
7538
7540
  }
7539
7541
  deps;
7540
- coordinator;
7541
- /** Lazily built when `opts.directorMode` is set. Owns its own internal
7542
- * coordinator; the host's `coordinator` field still points at it so
7543
- * the rest of the methods don't need to branch. */
7544
7542
  director;
7543
+ /** Own FleetManager — created in buildDirector(), used for pending task
7544
+ * tracking so status() can show descriptions without host-side state. */
7545
+ fleetManager;
7545
7546
  /** Lazily built alongside the director — produces per-subagent JSONL
7546
- * writers under `<sessionsRoot>/<runId>/`. Null in non-director mode. */
7547
+ * writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
7547
7548
  sessionFactory;
7548
- pending = /* @__PURE__ */ new Map();
7549
- results = [];
7550
7549
  opts;
7551
7550
  /**
7552
- * Populated by `promoteToDirector` when it refuses to promote (typically
7553
- * because a non-director coordinator is already running). The delegate
7551
+ * Populated by `promoteToDirector` when it refuses to promote. The delegate
7554
7552
  * tool reads this through `getPromotionBlockReason` to render an
7555
- * actionable error instead of a generic "could not activate director".
7553
+ * actionable error instead of a generic "Director could not be activated".
7556
7554
  */
7557
7555
  promotionBlockReason = null;
7558
7556
  /**
@@ -7565,14 +7563,34 @@ var MultiAgentHost = class {
7565
7563
  * orchestration tools and `--director` becomes a no-op.
7566
7564
  */
7567
7565
  async ensureDirector() {
7566
+ if (this.director) return this.director;
7568
7567
  if (!this.opts.directorMode) return null;
7569
- await this.ensureCoordinator();
7568
+ await this.buildDirector();
7570
7569
  return this.director ?? null;
7571
7570
  }
7572
- async ensureCoordinator() {
7573
- if (this.coordinator) return this.coordinator;
7571
+ /** Access the Director's internal coordinator. Returns the concrete
7572
+ * `DefaultMultiAgentCoordinator` so callers can use class-only surface
7573
+ * (`on`, `setRunner`) that isn't part of the `MultiAgentCoordinator`
7574
+ * interface. */
7575
+ getCoordinator() {
7576
+ return this.director.coordinator;
7577
+ }
7578
+ async buildDirector() {
7579
+ if (this.director) return;
7574
7580
  const config = this.deps.configStore.get();
7575
- if (this.opts.directorMode && this.opts.sessionsRoot && !this.sessionFactory) {
7581
+ const fleetManager = new FleetManager({
7582
+ manifestPath: this.opts.manifestPath,
7583
+ sessionsRoot: this.opts.sessionsRoot,
7584
+ directorRunId: this.opts.directorRunId,
7585
+ stateCheckpointPath: this.opts.stateCheckpointPath,
7586
+ sessionWriter: this.opts.sessionWriter,
7587
+ directorBudget: this.opts.directorBudget,
7588
+ manifestDebounceMs: 2e3,
7589
+ checkpointDebounceMs: this.opts.checkpointDebounceMs ?? 250,
7590
+ maxSpawnDepth: 5
7591
+ });
7592
+ this.fleetManager = fleetManager;
7593
+ if (this.opts.sessionsRoot && !this.sessionFactory) {
7576
7594
  this.sessionFactory = makeDirectorSessionFactory({
7577
7595
  sessionsRoot: this.opts.sessionsRoot,
7578
7596
  directorRunId: this.opts.directorRunId
@@ -7582,75 +7600,45 @@ var MultiAgentHost = class {
7582
7600
  coordinatorId: randomUUID(),
7583
7601
  doneCondition: { type: "all_tasks_done" },
7584
7602
  maxConcurrent: 8
7585
- // No defaultBudget. Caps land on a subagent ONLY when the
7586
- // orchestrator (delegate-tool / spawn_subagent) or the user
7587
- // (CLI flag) sets them explicitly. The prior defaults
7588
- // (1000 tools / 200 iter / 4h) silently killed long autonomous
7589
- // runs; for a "work until done" director we want no implicit
7590
- // ceilings. The orchestrator can still cap a single subagent
7591
- // by passing maxToolCalls/maxIterations through the spawn tool.
7592
7603
  };
7593
- if (this.opts.directorMode) {
7594
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
7595
- this.director = new Director({
7596
- config: coordinatorConfig,
7597
- manifestPath: this.opts.manifestPath,
7598
- sharedScratchpadPath: defaultScratchpad,
7599
- stateCheckpointPath: this.opts.stateCheckpointPath,
7600
- sessionWriter: this.opts.sessionWriter,
7601
- directorBudget: this.opts.directorBudget,
7602
- maxBudgetExtensions: this.opts.maxBudgetExtensions,
7603
- checkpointDebounceMs: this.opts.checkpointDebounceMs,
7604
- sessionsRoot: this.opts.sessionsRoot,
7605
- directorRunId: this.opts.directorRunId,
7606
- // Autonomy: allow nested directors a few levels deep. Default
7607
- // is 2 (root + one tier of workers), which trips the moment a
7608
- // worker tries to recurse into "let me delegate the parser
7609
- // analysis to a tighter specialist". 5 lets the director
7610
- // structure work as deeply as the task requires without us
7611
- // having to pass a flag every time.
7612
- maxSpawnDepth: 5
7613
- });
7614
- this.director.on("task.completed", ({ task, result }) => {
7615
- this.results.push(result);
7616
- this.pending.delete(task.id);
7617
- this.emitLifecycleCompleted(task.id, result);
7604
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path21.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
7605
+ this.director = new Director({
7606
+ config: coordinatorConfig,
7607
+ manifestPath: this.opts.manifestPath,
7608
+ sharedScratchpadPath: defaultScratchpad,
7609
+ stateCheckpointPath: this.opts.stateCheckpointPath,
7610
+ sessionWriter: this.opts.sessionWriter,
7611
+ directorBudget: this.opts.directorBudget,
7612
+ maxBudgetExtensions: this.opts.maxBudgetExtensions,
7613
+ checkpointDebounceMs: this.opts.checkpointDebounceMs,
7614
+ sessionsRoot: this.opts.sessionsRoot,
7615
+ directorRunId: this.opts.directorRunId,
7616
+ maxSpawnDepth: 5,
7617
+ fleetManager
7618
+ // pass so director.fleetManager is never undefined
7619
+ });
7620
+ this.director.on("task.completed", ({ task, result }) => {
7621
+ this.fleetManager?.removePendingTask(task.id);
7622
+ this.emitLifecycleCompleted(task.id, result);
7623
+ });
7624
+ this.director.fleet.filter("budget.threshold_reached", (e) => {
7625
+ const payload = e.payload;
7626
+ this.deps.events.emit("subagent.budget_warning", {
7627
+ subagentId: e.subagentId,
7628
+ kind: payload.kind,
7629
+ used: payload.used,
7630
+ limit: payload.limit
7618
7631
  });
7619
- this.director.fleet.filter("budget.threshold_reached", (e) => {
7620
- const payload = e.payload;
7621
- this.deps.events.emit("subagent.budget_warning", {
7622
- subagentId: e.subagentId,
7623
- kind: payload.kind,
7624
- used: payload.used,
7625
- limit: payload.limit
7626
- });
7632
+ });
7633
+ this.getCoordinator().on("task.assigned", ({ task, subagentId }) => {
7634
+ this.deps.events.emit("subagent.task_started", {
7635
+ subagentId,
7636
+ taskId: task.id,
7637
+ description: task.description
7627
7638
  });
7628
- this.coordinator = this.director.coordinator;
7629
- } else {
7630
- this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
7631
- this.coordinator.on(
7632
- "task.completed",
7633
- ({ task, result }) => {
7634
- this.results.push(result);
7635
- this.pending.delete(task.id);
7636
- this.emitLifecycleCompleted(task.id, result);
7637
- }
7638
- );
7639
- }
7640
- this.coordinator.on(
7641
- "task.assigned",
7642
- ({ task, subagentId }) => {
7643
- this.deps.events.emit("subagent.task_started", {
7644
- subagentId,
7645
- taskId: task.id,
7646
- description: task.description
7647
- });
7648
- }
7649
- );
7639
+ });
7650
7640
  const runner = this.buildSubagentRunner(config);
7651
- const innerCoord = this.opts.directorMode ? this.director.coordinator : this.coordinator;
7652
- innerCoord.setRunner(runner);
7653
- return this.coordinator;
7641
+ this.getCoordinator().setRunner(runner);
7654
7642
  }
7655
7643
  /**
7656
7644
  * Build the per-subagent runner: agent factory → runner. Extracted so
@@ -7732,7 +7720,7 @@ var MultiAgentHost = class {
7732
7720
  };
7733
7721
  return { agent, events, dispose };
7734
7722
  };
7735
- return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet });
7723
+ return makeAgentSubagentRunner({ factory, fleetBus: this.director?.fleet ?? NULL_FLEET_BUS });
7736
7724
  }
7737
7725
  /**
7738
7726
  * Build a Provider for a subagent. When `overrideId` is supplied (from
@@ -7778,7 +7766,7 @@ var MultiAgentHost = class {
7778
7766
  * the full tool registry.
7779
7767
  */
7780
7768
  async spawn(description, opts) {
7781
- await this.ensureCoordinator();
7769
+ await this.buildDirector();
7782
7770
  const subagentConfig = {
7783
7771
  name: opts?.name ?? "adhoc",
7784
7772
  role: "general",
@@ -7787,34 +7775,10 @@ var MultiAgentHost = class {
7787
7775
  tools: opts?.tools
7788
7776
  };
7789
7777
  const transcriptPath = this.sessionFactory ? path21.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
7790
- if (this.director) {
7791
- const subagentId = await this.director.spawn(subagentConfig);
7792
- const taskId2 = randomUUID();
7793
- this.pending.set(taskId2, { description, subagentId });
7794
- this.deps.events.emit("subagent.spawned", {
7795
- subagentId,
7796
- taskId: taskId2,
7797
- name: subagentConfig.name,
7798
- provider: opts?.provider,
7799
- model: opts?.model,
7800
- description,
7801
- transcriptPath
7802
- });
7803
- await this.director.assign({
7804
- id: taskId2,
7805
- description,
7806
- subagentId
7807
- // No maxToolCalls — same reasoning as the spawn config above.
7808
- // The director / orchestrator owns the budget decision.
7809
- });
7810
- return { subagentId, taskId: taskId2 };
7811
- }
7812
- const coord = this.coordinator;
7813
- const spawned = await coord.spawn(subagentConfig);
7814
- const taskId = randomUUID();
7815
- this.pending.set(taskId, { description, subagentId: spawned.subagentId });
7778
+ const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
7779
+ this.fleetManager?.addPendingTask(taskId, subagentId, description);
7816
7780
  this.deps.events.emit("subagent.spawned", {
7817
- subagentId: spawned.subagentId,
7781
+ subagentId,
7818
7782
  taskId,
7819
7783
  name: subagentConfig.name,
7820
7784
  provider: opts?.provider,
@@ -7822,13 +7786,22 @@ var MultiAgentHost = class {
7822
7786
  description,
7823
7787
  transcriptPath
7824
7788
  });
7825
- await coord.assign({
7826
- id: taskId,
7827
- description,
7828
- subagentId: spawned.subagentId
7829
- // No maxToolCalls see comment on the director branch above.
7830
- });
7831
- return { subagentId: spawned.subagentId, taskId };
7789
+ return { subagentId, taskId };
7790
+ }
7791
+ /**
7792
+ * Common spawn + assign logic shared by both director mode and raw
7793
+ * coordinator mode. Extracts the identical body from the two branches
7794
+ * in `spawn()` so future changes (e.g. adding a new field to both
7795
+ * paths) are made in one place.
7796
+ *
7797
+ * Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
7798
+ * and event emission — the helper only talks to the coordinator.
7799
+ */
7800
+ async _spawnAndAssign(subagentConfig) {
7801
+ const taskId = randomUUID();
7802
+ const subagentId = await this.director.spawn(subagentConfig);
7803
+ await this.director.assign({ id: taskId, description: "", subagentId });
7804
+ return { subagentId, taskId };
7832
7805
  }
7833
7806
  /**
7834
7807
  * Relay a `task.completed` notification (from either the Director or
@@ -7851,29 +7824,24 @@ var MultiAgentHost = class {
7851
7824
  }
7852
7825
  status() {
7853
7826
  const activeSubagentIds = /* @__PURE__ */ new Set();
7854
- if (this.coordinator) {
7855
- const s = this.coordinator.getStatus();
7827
+ const live = [];
7828
+ if (this.director) {
7829
+ const coord = this.getCoordinator();
7830
+ const s = coord.getStatus();
7856
7831
  for (const a of s.subagents) {
7857
7832
  if (a.status === "running" || a.status === "idle") {
7858
7833
  activeSubagentIds.add(a.id);
7859
7834
  }
7860
- }
7861
- }
7862
- const pending = Array.from(this.pending.entries()).filter(([, v]) => activeSubagentIds.has(v.subagentId)).map(([taskId, v]) => ({
7863
- taskId,
7864
- description: v.description,
7865
- subagentId: v.subagentId
7866
- }));
7867
- const live = [];
7868
- if (this.coordinator) {
7869
- const s = this.coordinator.getStatus();
7870
- for (const a of s.subagents) {
7871
7835
  live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
7872
7836
  }
7873
7837
  }
7838
+ const fleetStatus = this.fleetManager?.getFleetStatus() ?? { pending: []};
7839
+ const pending = fleetStatus.pending.filter((p) => activeSubagentIds.has(p.subagentId));
7840
+ const completed = this.director ? this.director.completedResults() : [];
7841
+ const completedCount = completed.length;
7874
7842
  const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
7875
- const summary = !this.coordinator ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${this.results.length} completed.` : `${pending.length} pending, ${this.results.length} completed.`;
7876
- return { pending, completed: this.results, live, summary };
7843
+ const summary = !this.director ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${completedCount} completed.` : `${pending.length} pending, ${completedCount} completed.`;
7844
+ return { pending, completed, live, summary };
7877
7845
  }
7878
7846
  /**
7879
7847
  * Roll up per-subagent runtime cost from completed TaskResults. We don't
@@ -7886,8 +7854,9 @@ var MultiAgentHost = class {
7886
7854
  * the table renders the most interesting subagent at the top.
7887
7855
  */
7888
7856
  usage() {
7857
+ const completed = this.director ? this.director.completedResults() : [];
7889
7858
  const bySubagent = /* @__PURE__ */ new Map();
7890
- for (const r of this.results) {
7859
+ for (const r of completed) {
7891
7860
  const cur = bySubagent.get(r.subagentId) ?? {
7892
7861
  tasks: 0,
7893
7862
  iterations: 0,
@@ -7933,7 +7902,7 @@ var MultiAgentHost = class {
7933
7902
  */
7934
7903
  async manifest() {
7935
7904
  if (!this.director) return null;
7936
- return this.director.writeManifest();
7905
+ return await this.director.fleetManager?.writeManifest() ?? null;
7937
7906
  }
7938
7907
  /**
7939
7908
  * Promote a non-director session to director mode at runtime. Only
@@ -7946,13 +7915,6 @@ var MultiAgentHost = class {
7946
7915
  */
7947
7916
  async promoteToDirector() {
7948
7917
  if (this.director) return this.director;
7949
- if (this.coordinator) {
7950
- const status = this.coordinator.getStatus();
7951
- const running = status.subagents.filter((s) => s.status === "running").length;
7952
- const idle = status.subagents.filter((s) => s.status === "idle").length;
7953
- this.promotionBlockReason = `Cannot promote to director: a non-director coordinator is already in use (${running} running, ${idle} idle, ${status.pendingTasks} pending tasks). Stop the existing subagents with /fleet kill <id> or wait for them to finish, then retry \u2014 or restart wstack with --director to start in director mode.`;
7954
- return null;
7955
- }
7956
7918
  this.opts.directorMode = true;
7957
7919
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
7958
7920
  this.opts.manifestPath = path21.join(this.opts.fleetRoot, "fleet.json");
@@ -7994,13 +7956,13 @@ var MultiAgentHost = class {
7994
7956
  * called /fleet kill before any /spawn, and there's nothing to do.
7995
7957
  */
7996
7958
  async kill(subagentId) {
7997
- if (!this.coordinator) return false;
7998
- await this.coordinator.stop(subagentId);
7959
+ if (!this.director) return false;
7960
+ await this.getCoordinator().stop(subagentId);
7999
7961
  return true;
8000
7962
  }
8001
7963
  async stopAll() {
8002
- if (this.coordinator) {
8003
- await this.coordinator.stopAll();
7964
+ if (this.director) {
7965
+ await this.getCoordinator().stopAll();
8004
7966
  }
8005
7967
  }
8006
7968
  };