@wrongstack/core 0.32.0 → 0.41.0

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.
Files changed (33) hide show
  1. package/dist/{agent-subagent-runner-DpZTLdBe.d.ts → agent-subagent-runner-C66vi4Gq.d.ts} +1 -1
  2. package/dist/{config-BUEGM4JP.d.ts → config-ZRCf7sTu.d.ts} +21 -1
  3. package/dist/coordination/index.d.ts +7 -7
  4. package/dist/coordination/index.js +3028 -2976
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/defaults/index.d.ts +8 -8
  7. package/dist/defaults/index.js +1166 -1104
  8. package/dist/defaults/index.js.map +1 -1
  9. package/dist/execution/index.d.ts +5 -5
  10. package/dist/extension/index.d.ts +2 -2
  11. package/dist/{index-ysfO_DlX.d.ts → index-6_csX32J.d.ts} +1 -1
  12. package/dist/{index-pXJdVLe0.d.ts → index-DkVgH3wC.d.ts} +31 -1
  13. package/dist/index.d.ts +135 -15
  14. package/dist/index.js +1467 -1214
  15. package/dist/index.js.map +1 -1
  16. package/dist/infrastructure/index.d.ts +2 -2
  17. package/dist/infrastructure/index.js +17 -3
  18. package/dist/infrastructure/index.js.map +1 -1
  19. package/dist/kernel/index.d.ts +2 -2
  20. package/dist/{mcp-servers-BzB3r7_c.d.ts → mcp-servers-DONdo-XM.d.ts} +1 -1
  21. package/dist/{multi-agent-coordinator-DOXSgtom.d.ts → multi-agent-coordinator-BUsjiRWl.d.ts} +1 -1
  22. package/dist/{null-fleet-bus-DLsUjOyB.d.ts → null-fleet-bus-FvgHnZah.d.ts} +150 -131
  23. package/dist/{plan-templates-BZMi-VpU.d.ts → plan-templates-DYCeRCDN.d.ts} +1 -1
  24. package/dist/sdd/index.d.ts +3 -3
  25. package/dist/storage/index.d.ts +2 -2
  26. package/dist/{tool-executor-BAi4WI2d.d.ts → tool-executor-BpK-SWtJ.d.ts} +1 -1
  27. package/dist/types/index.d.ts +3 -3
  28. package/dist/types/index.js +17 -3
  29. package/dist/types/index.js.map +1 -1
  30. package/dist/utils/index.d.ts +107 -1
  31. package/dist/utils/index.js +53 -2
  32. package/dist/utils/index.js.map +1 -1
  33. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1295,11 +1295,62 @@ function deepMerge(a, b) {
1295
1295
  return out;
1296
1296
  }
1297
1297
 
1298
+ // src/utils/term.ts
1299
+ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
1300
+ var hasStdin = () => typeof process !== "undefined" && !!process.stdin;
1301
+ function isStdoutTTY() {
1302
+ return hasStdout() && Boolean(process.stdout.isTTY);
1303
+ }
1304
+ function isStdinTTY() {
1305
+ return hasStdin() && Boolean(process.stdin.isTTY);
1306
+ }
1307
+ function isInteractive() {
1308
+ return isStdinTTY() && isStdoutTTY();
1309
+ }
1310
+ function getTermSize() {
1311
+ if (!hasStdout()) return { rows: 24, cols: 80 };
1312
+ return {
1313
+ rows: process.stdout.rows ?? 24,
1314
+ cols: process.stdout.columns ?? 80
1315
+ };
1316
+ }
1317
+ function onResize(cb, stream = process.stdout) {
1318
+ if (!stream || typeof stream.on !== "function") return () => {
1319
+ };
1320
+ const handler = () => {
1321
+ cb({
1322
+ rows: stream.rows ?? 24,
1323
+ cols: stream.columns ?? 80
1324
+ });
1325
+ };
1326
+ stream.on("resize", handler);
1327
+ return () => {
1328
+ stream.off("resize", handler);
1329
+ };
1330
+ }
1331
+ function setRawMode(input, mode) {
1332
+ if (!input || input.isTTY !== true) return false;
1333
+ if (typeof input.setRawMode !== "function") return false;
1334
+ input.setRawMode(mode);
1335
+ return true;
1336
+ }
1337
+ function writeTo(s, stream) {
1338
+ if (!stream || typeof stream.write !== "function") return false;
1339
+ stream.write(s);
1340
+ return true;
1341
+ }
1342
+ function writeOut(s, stream = process.stdout) {
1343
+ return writeTo(s, stream);
1344
+ }
1345
+ function writeErr(s, stream = process.stderr) {
1346
+ return writeTo(s, stream);
1347
+ }
1348
+
1298
1349
  // src/utils/color.ts
1299
1350
  var isColorTty = () => {
1300
1351
  if (process.env.NO_COLOR) return false;
1301
1352
  if (process.env.FORCE_COLOR) return true;
1302
- return Boolean(process.stdout?.isTTY);
1353
+ return isStdoutTTY();
1303
1354
  };
1304
1355
  var COLOR = isColorTty();
1305
1356
  var wrap = (open5, close) => (s) => COLOR ? `\x1B[${open5}m${s}\x1B[${close}m` : s;
@@ -1403,10 +1454,10 @@ var DefaultLogger = class _DefaultLogger {
1403
1454
  if (r <= LEVEL_RANK.warn || this.level === "debug" || this.level === "trace") {
1404
1455
  const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
1405
1456
  if (ctx !== void 0) {
1406
- process.stderr.write(`${head} ${formatCtx(ctx)}
1457
+ writeErr(`${head} ${formatCtx(ctx)}
1407
1458
  `);
1408
1459
  } else {
1409
- process.stderr.write(`${head}
1460
+ writeErr(`${head}
1410
1461
  `);
1411
1462
  }
1412
1463
  }
@@ -5487,9 +5538,9 @@ var DefaultMemoryStore = class {
5487
5538
  async readAll() {
5488
5539
  const parts = [];
5489
5540
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
5490
- const writeErr = this.writeErrors.get(scope);
5491
- if (writeErr) {
5492
- parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
5541
+ const writeErr2 = this.writeErrors.get(scope);
5542
+ if (writeErr2) {
5543
+ parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
5493
5544
  }
5494
5545
  const body = await this.read(scope);
5495
5546
  if (body.trim()) parts.push(`## ${labelOf(scope)}
@@ -13686,637 +13737,911 @@ function buildGoalPreamble(goal) {
13686
13737
  "BEGIN.]"
13687
13738
  ].join("\n");
13688
13739
  }
13689
-
13690
- // src/coordination/director.ts
13691
13740
  init_atomic_write();
13692
-
13693
- // src/coordination/large-answer-store.ts
13694
- var LargeAnswerStore = class {
13741
+ var DEFAULT_MAX_TARGET_FILES = 30;
13742
+ var CollabSession = class extends EventEmitter {
13743
+ sessionId;
13744
+ options;
13745
+ snapshot;
13746
+ director;
13747
+ fleetBus;
13748
+ subagentIds = /* @__PURE__ */ new Map();
13749
+ // role → subagentId
13750
+ bugs = /* @__PURE__ */ new Map();
13751
+ plans = /* @__PURE__ */ new Map();
13752
+ evaluations = /* @__PURE__ */ new Map();
13753
+ disposers = new Array();
13754
+ settled = false;
13755
+ timeoutMs;
13756
+ cancelled = false;
13757
+ alerts = [];
13758
+ /** Tracks tool call counts per subagent for progress-based timeout decisions. */
13759
+ progressBySubagent = /* @__PURE__ */ new Map();
13760
+ /** Last tool call count when a timeout warning was handled. */
13761
+ lastTimeoutProgress = /* @__PURE__ */ new Map();
13762
+ /** Session-level timeout timer handle (cleared on cancel or natural completion). */
13763
+ _timeoutTimer;
13764
+ constructor(director, fleetBus, options) {
13765
+ super();
13766
+ this.sessionId = randomUUID();
13767
+ this.options = options;
13768
+ this.director = director;
13769
+ this.fleetBus = fleetBus;
13770
+ this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
13771
+ if (options.prebuiltSnapshot) {
13772
+ this.snapshot = options.prebuiltSnapshot;
13773
+ } else {
13774
+ this.snapshot = {
13775
+ id: this.sessionId,
13776
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13777
+ files: []
13778
+ };
13779
+ }
13780
+ }
13781
+ get id() {
13782
+ return this.sessionId;
13783
+ }
13784
+ getSessionAlerts() {
13785
+ return [...this.alerts];
13786
+ }
13787
+ isCancelled() {
13788
+ return this.cancelled;
13789
+ }
13695
13790
  /**
13696
- * Responses above this size (in characters) are stored out-of-context.
13697
- * Below this, the full answer is returned inline (no overhead).
13698
- * Default: 2000 chars ≈ 400-600 tokens.
13791
+ * Snapshot of role subagentId map. The Director calls coordinator.stop()
13792
+ * for each agent when cancelling the session, using this map to enumerate
13793
+ * all three collab agents.
13699
13794
  */
13700
- sizeThreshold;
13701
- store = /* @__PURE__ */ new Map();
13702
- constructor(sizeThreshold = 2e3) {
13703
- this.sizeThreshold = sizeThreshold;
13795
+ getSubagentIds() {
13796
+ return new Map(this.subagentIds);
13704
13797
  }
13705
13798
  /**
13706
- * Store a value, returning a summary + key for inline use.
13707
- * If the value is below sizeThreshold, returns it as-is (no store entry).
13799
+ * Returns the effective file limit for this session.
13800
+ * Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
13708
13801
  */
13709
- storeAnswer(value) {
13710
- if (value === void 0 || value === null) {
13711
- return { summary: String(value), inline: true };
13802
+ effectiveFileLimit() {
13803
+ if (this.options.maxTargetFiles !== void 0) {
13804
+ return this.options.maxTargetFiles;
13712
13805
  }
13713
- const serialized = typeof value === "string" ? value : JSON.stringify(value);
13714
- const size = serialized.length;
13715
- if (size <= this.sizeThreshold) {
13716
- return { summary: serialized.slice(0, 500), inline: true };
13806
+ if (this.options.contextWindow !== void 0) {
13807
+ return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
13717
13808
  }
13718
- const key = `a-${hashStr(serialized)}`;
13719
- this.store.set(key, {
13720
- key,
13721
- value,
13722
- size,
13723
- storedAt: Date.now()
13724
- });
13725
- return {
13726
- key,
13727
- summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
13728
- inline: false
13729
- };
13809
+ return DEFAULT_MAX_TARGET_FILES;
13730
13810
  }
13731
- /**
13732
- * Retrieve a previously stored answer by its key.
13733
- * Returns undefined if the key is unknown or the store was cleared.
13734
- */
13735
- retrieveAnswer(key) {
13736
- return this.store.get(key)?.value;
13811
+ async buildSnapshot() {
13812
+ if (this.snapshot.files.length > 0) return this.snapshot;
13813
+ const allFiles = [];
13814
+ for (const pattern of this.options.targetPaths) {
13815
+ const expanded = await expandGlob(pattern);
13816
+ allFiles.push(...expanded);
13817
+ }
13818
+ const limit = this.effectiveFileLimit();
13819
+ if (allFiles.length > limit) {
13820
+ const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
13821
+ throw new Error(
13822
+ `[collab_debug] Target has ${allFiles.length} files, which exceeds the limit (${hint}). Narrow the target or pass maxTargetFiles / contextWindow to override. For large codebases, run package-by-package or module-by-module sessions instead of targeting the entire repo.`
13823
+ );
13824
+ }
13825
+ for (const filePath of allFiles) {
13826
+ try {
13827
+ const content = await fsp3.readFile(filePath, "utf8");
13828
+ const ext = filePath.split(".").pop() ?? "";
13829
+ const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
13830
+ this.snapshot.files.push({ path: filePath, content, language });
13831
+ } catch {
13832
+ this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
13833
+ }
13834
+ }
13835
+ return this.snapshot;
13737
13836
  }
13738
13837
  /**
13739
- * Check if a key exists in the store.
13838
+ * Cancel the session. Emits director.cancel_collab on the FleetBus so all
13839
+ * collab agents finish early. The session-level timeout timer is also cleared.
13840
+ * Safe to call multiple times (idempotent after first call).
13740
13841
  */
13741
- hasAnswer(key) {
13742
- return this.store.has(key);
13743
- }
13744
- /** Number of stored entries. */
13745
- get size() {
13746
- return this.store.size;
13747
- }
13748
- /** Total characters stored. */
13749
- get totalChars() {
13750
- let total = 0;
13751
- for (const e of this.store.values()) total += e.size;
13752
- return total;
13842
+ cancel(reason = "Director cancelled collab session") {
13843
+ if (this.settled) return;
13844
+ this.cancelled = true;
13845
+ if (this._timeoutTimer) {
13846
+ clearTimeout(this._timeoutTimer);
13847
+ this._timeoutTimer = void 0;
13848
+ }
13849
+ this.fleetBus.emit({
13850
+ subagentId: this.director.id,
13851
+ ts: Date.now(),
13852
+ type: "director.cancel_collab",
13853
+ payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
13854
+ });
13855
+ this.fleetBus.emit({
13856
+ subagentId: this.director.id,
13857
+ ts: Date.now(),
13858
+ type: "collab.cancelled",
13859
+ payload: { sessionId: this.sessionId, reason }
13860
+ });
13753
13861
  }
13754
- /** Clear all stored entries. Call at the end of a director run. */
13755
- clear() {
13756
- this.store.clear();
13862
+ async start() {
13863
+ if (this.settled) throw new Error("session already settled");
13864
+ this.settled = true;
13865
+ await this.buildSnapshot();
13866
+ this.wireFleetBus();
13867
+ const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
13868
+ this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
13869
+ this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
13870
+ this.spawnAgent("critic", this.buildCriticTask())
13871
+ ]);
13872
+ this.subagentIds.set("bug-hunter", bugHunterId);
13873
+ this.subagentIds.set("refactor-planner", refactorPlannerId);
13874
+ this.subagentIds.set("critic", criticId);
13875
+ const timeout = new Promise((_, reject) => {
13876
+ this._timeoutTimer = setTimeout(() => {
13877
+ this.cancel("Session-level timeout reached");
13878
+ reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
13879
+ }, this.timeoutMs);
13880
+ });
13881
+ let results = null;
13882
+ try {
13883
+ results = await Promise.race([
13884
+ Promise.all([
13885
+ this.director.awaitTasks([bugHunterId]),
13886
+ this.director.awaitTasks([refactorPlannerId]),
13887
+ this.director.awaitTasks([criticId])
13888
+ ]),
13889
+ timeout
13890
+ ]);
13891
+ } catch (err) {
13892
+ if (this._timeoutTimer) {
13893
+ clearTimeout(this._timeoutTimer);
13894
+ this._timeoutTimer = void 0;
13895
+ }
13896
+ this.cleanup();
13897
+ const error = err instanceof Error ? err : new Error(String(err));
13898
+ this.emit("session.error", error);
13899
+ throw error;
13900
+ }
13901
+ for (const result of results.flat()) {
13902
+ await this.parseAndEmit(result);
13903
+ }
13904
+ const report = this.assembleReport();
13905
+ this.cleanup();
13906
+ this.emit("session.done", report);
13907
+ return report;
13757
13908
  }
13758
- };
13759
- function hashStr(s) {
13760
- let h = 5381;
13761
- for (let i = 0; i < s.length; i++) {
13762
- h = h * 33 ^ s.charCodeAt(i);
13909
+ async parseAndEmit(result) {
13910
+ if (result.status !== "success" || result.result == null) return;
13911
+ const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
13912
+ for (const obj of this.extractJsonObjects(text)) {
13913
+ const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
13914
+ if (!type) continue;
13915
+ this.fleetBus.emit({
13916
+ subagentId: result.subagentId,
13917
+ taskId: result.taskId,
13918
+ ts: Date.now(),
13919
+ type,
13920
+ payload: obj
13921
+ });
13922
+ }
13763
13923
  }
13764
- return (h >>> 0).toString(36);
13765
- }
13766
-
13767
- // src/coordination/director-prompts.ts
13768
- var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
13769
- subagents by spawning them, assigning tasks, awaiting completions, and
13770
- rolling up their outputs into your next decision.
13771
-
13772
- Core fleet tools available to you:
13773
- - spawn_subagent \u2014 create a worker with a chosen provider / model / role
13774
- - assign_task \u2014 hand a piece of work to a specific subagent
13775
- - await_tasks \u2014 block until named task ids complete (parallel-safe)
13776
- - ask_subagent \u2014 synchronously query a running subagent via the bridge
13777
- - roll_up \u2014 aggregate finished tasks into a markdown/json summary
13778
- - terminate_subagent \u2014 abort a stuck worker (use sparingly)
13779
- - fleet_status \u2014 snapshot of all subagents and pending tasks
13780
- - fleet_usage \u2014 token + cost breakdown per subagent and total
13781
-
13782
- Working rules:
13783
- 1. Decompose first. Before spawning, decide which sub-tasks are
13784
- independent and can run in parallel. Sequential work doesn't need a
13785
- subagent \u2014 do it yourself.
13786
- 2. Match worker to job. Cheap/fast model for triage, capable model for
13787
- synthesis. Different providers per sibling is allowed and encouraged.
13788
- 3. Always pair an assign with an await. Don't fire-and-forget; you owe
13789
- the user a single coherent answer at the end.
13790
- 4. Roll up before deciding. After await_tasks resolves, call roll_up so
13791
- the results are folded back into your context in a compact form.
13792
- 5. Budget is real. Check fleet_usage periodically. If a subagent is
13793
- thrashing, terminate it rather than letting cost climb silently.
13794
- 6. Never claim a subagent's work as your own without verifying it. If a
13795
- result looks wrong, ask_subagent for clarification before passing it
13796
- to the user.
13797
- 7. Wind down when satisfied. When the results are good enough, call
13798
- work_complete \u2014 no new subagents will spawn and queued tasks complete
13799
- as aborted. Running subagents finish naturally. Call terminate_subagent
13800
- only for ones you need to stop immediately.`;
13801
- var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
13802
- a specific slice of a larger plan \u2014 do that slice well and report back.
13803
-
13804
- Bridge contract:
13805
- - You have a parent (the Director). You may call \`request\` on the
13806
- parent bridge to ask a clarifying question. Use this sparingly; the
13807
- parent is also working.
13808
- - You MAY NOT request the parent's system prompt, tool list, or other
13809
- subagents' context. Those are not yours to read.
13810
- - Your final task output is what the Director sees. Be concise,
13811
- structured, and self-contained \u2014 assume the Director will paste your
13812
- output into its own context.`;
13813
- function composeDirectorPrompt(parts = {}) {
13814
- const sections = [];
13815
- const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
13816
- if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
13817
- if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
13818
- sections.push(`Available roles you can spawn:
13819
- ${parts.rosterSummary.trim()}`);
13820
- }
13821
- if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
13822
- sections.push(parts.basePrompt.trim());
13823
- }
13824
- return sections.join("\n\n");
13825
- }
13826
- function composeSubagentPrompt(parts = {}) {
13827
- const sections = [];
13828
- const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
13829
- if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
13830
- if (parts.role && parts.role.trim().length > 0) {
13831
- sections.push(`Role:
13832
- ${parts.role.trim()}`);
13833
- }
13834
- if (parts.task && parts.task.trim().length > 0) {
13835
- sections.push(`Task:
13836
- ${parts.task.trim()}`);
13837
- }
13838
- if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
13839
- sections.push(
13840
- `Shared notes:
13841
- A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
13842
- - Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
13843
- - Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
13844
- - Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
13845
- );
13846
- }
13847
- if (parts.override && parts.override.trim().length > 0) {
13848
- sections.push(parts.override.trim());
13849
- }
13850
- return sections.join("\n\n");
13851
- }
13852
- function rosterSummaryFromConfigs(roster) {
13853
- const lines = [];
13854
- for (const [roleId, cfg] of Object.entries(roster)) {
13855
- const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
13856
- const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
13857
- const tail = headline ? ` \u2014 ${headline}` : "";
13858
- lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
13859
- }
13860
- return lines.join("\n");
13861
- }
13862
-
13863
- // src/coordination/fleet-bus.ts
13864
- var FleetBus = class {
13865
- byId = /* @__PURE__ */ new Map();
13866
- byType = /* @__PURE__ */ new Map();
13867
- any = /* @__PURE__ */ new Set();
13868
- /**
13869
- * Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
13870
- * `onPattern('*')`) to forward all events with subagent attribution, so
13871
- * new kernel event types are automatically forwarded without any manual
13872
- * registration. `subagent.*` events are excluded because they originate
13873
- * from MultiAgentHost on the parent bus, not the subagent's own bus.
13874
- *
13875
- * Returns a disposer that detaches every subscription; call on
13876
- * subagent teardown so the listeners don't outlive the run.
13877
- */
13878
- attach(subagentId, bus, taskId) {
13879
- const off = bus.onAny((type, payload) => {
13880
- if (type.startsWith("subagent.")) return;
13881
- this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
13882
- });
13883
- return () => {
13884
- off();
13885
- };
13886
- }
13887
- /** Subscribe to every event from one subagent. */
13888
- subscribe(subagentId, handler) {
13889
- let set = this.byId.get(subagentId);
13890
- if (!set) {
13891
- set = /* @__PURE__ */ new Set();
13892
- this.byId.set(subagentId, set);
13924
+ extractJsonObjects(text) {
13925
+ const objects = [];
13926
+ let depth = 0;
13927
+ let start = -1;
13928
+ let inString = false;
13929
+ let escaped = false;
13930
+ for (let i = 0; i < text.length; i++) {
13931
+ const ch = text[i];
13932
+ if (inString) {
13933
+ if (escaped) escaped = false;
13934
+ else if (ch === "\\") escaped = true;
13935
+ else if (ch === '"') inString = false;
13936
+ continue;
13937
+ }
13938
+ if (ch === '"') {
13939
+ inString = true;
13940
+ } else if (ch === "{") {
13941
+ if (depth === 0) start = i;
13942
+ depth++;
13943
+ } else if (ch === "}" && depth > 0) {
13944
+ depth--;
13945
+ if (depth === 0 && start >= 0) {
13946
+ const candidate = text.slice(start, i + 1);
13947
+ try {
13948
+ const parsed = JSON.parse(candidate);
13949
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13950
+ objects.push(parsed);
13951
+ }
13952
+ } catch {
13953
+ }
13954
+ start = -1;
13955
+ }
13956
+ }
13893
13957
  }
13894
- set.add(handler);
13895
- return () => {
13896
- set.delete(handler);
13897
- };
13958
+ return objects;
13898
13959
  }
13899
- /** Subscribe to one event type across all subagents. */
13900
- filter(type, handler) {
13901
- let set = this.byType.get(type);
13902
- if (!set) {
13903
- set = /* @__PURE__ */ new Set();
13904
- this.byType.set(type, set);
13960
+ budgetForRole(role) {
13961
+ if (this.options.budgetOverrides?.[role]) {
13962
+ return this.options.budgetOverrides[role];
13905
13963
  }
13906
- set.add(handler);
13907
- return () => {
13908
- set.delete(handler);
13964
+ const defaults = {
13965
+ "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
13966
+ "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
13967
+ "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
13909
13968
  };
13969
+ return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
13910
13970
  }
13911
- /** Subscribe to literally everything. The fleet roll-up uses this. */
13912
- onAny(handler) {
13913
- this.any.add(handler);
13914
- return () => {
13915
- this.any.delete(handler);
13971
+ async spawnAgent(role, taskBrief) {
13972
+ const budget = this.budgetForRole(role);
13973
+ const cfg = {
13974
+ id: `${role}-${this.sessionId}`,
13975
+ name: role,
13976
+ role,
13977
+ tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
13978
+ maxIterations: budget.maxIterations,
13979
+ maxToolCalls: budget.maxToolCalls,
13980
+ timeoutMs: budget.timeoutMs
13916
13981
  };
13982
+ const subagentId = await this.director.spawn(cfg);
13983
+ await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
13984
+ return subagentId;
13917
13985
  }
13918
- emit(event) {
13919
- const byId = this.byId.get(event.subagentId);
13920
- if (byId)
13921
- for (const h of byId) {
13922
- try {
13923
- h(event);
13924
- } catch {
13925
- }
13926
- }
13927
- const byType = this.byType.get(event.type);
13928
- if (byType)
13929
- for (const h of byType) {
13930
- try {
13931
- h(event);
13932
- } catch {
13933
- }
13934
- }
13935
- for (const h of this.any) {
13936
- try {
13937
- h(event);
13938
- } catch {
13939
- }
13940
- }
13941
- }
13942
- };
13943
- var FleetUsageAggregator = class {
13944
- constructor(bus, priceLookup, metaLookup) {
13945
- this.priceLookup = priceLookup;
13946
- this.metaLookup = metaLookup;
13947
- this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
13948
- this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
13949
- this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
13986
+ buildBugHunterTask() {
13987
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
13988
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
13989
+ ${f.content}`).join("\n\n");
13990
+ return `You are BugHunter. Scan the following files for bugs and code smells.
13991
+
13992
+ Target files:
13993
+ ${fileContents}
13994
+
13995
+ For each bug found, emit it using the fleet_emit tool immediately:
13996
+ { "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
13997
+
13998
+ After scanning all files, write your full markdown bug report to:
13999
+ ${scratchpad}/bug-hunter-report-${this.sessionId}.md
14000
+
14001
+ Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
13950
14002
  }
13951
- priceLookup;
13952
- metaLookup;
13953
- perSubagent = /* @__PURE__ */ new Map();
13954
- total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
13955
- unsub = new Array();
13956
- /**
13957
- * Remove a terminated subagent's data from the aggregator and subtract its
13958
- * contribution from the running totals. Call this when a subagent is removed
13959
- * from the fleet so the aggregator doesn't accumulate unbounded data for
13960
- * entities that will never emit events again.
13961
- */
13962
- removeSubagent(subagentId) {
13963
- const snap = this.perSubagent.get(subagentId);
13964
- if (!snap) return;
13965
- this.perSubagent.delete(subagentId);
13966
- this.total.input -= snap.input;
13967
- this.total.output -= snap.output;
13968
- this.total.cacheRead -= snap.cacheRead;
13969
- this.total.cacheWrite -= snap.cacheWrite;
13970
- this.total.cost -= snap.cost;
13971
- }
13972
- /** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
13973
- dispose() {
13974
- for (const off of this.unsub) off();
13975
- this.unsub.length = 0;
14003
+ buildRefactorPlannerTask() {
14004
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14005
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
14006
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14007
+ ${f.content}`).join("\n\n");
14008
+ return `You are RefactorPlanner. Plan refactorings for the following files.
14009
+
14010
+ Target files:
14011
+ ${fileContents}
14012
+
14013
+ Read the BugHunter report at: ${bugHunterReportPath}
14014
+
14015
+ For each bug you can address, emit a refactor plan using fleet_emit:
14016
+ { "type": "refactor.plan", "payload": { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [{ "number": 1, "title": "<phase>", "tasks": ["<task>"], "risk": "<low|medium|high>" }], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } } }
14017
+
14018
+ Also write your full markdown plan to:
14019
+ ${scratchpad}/refactor-plan-${this.sessionId}.md
14020
+
14021
+ Emit each plan immediately. Do not wait until planning is complete.`;
13976
14022
  }
13977
- /** Live snapshot — safe to call from a tool's execute() body. */
13978
- snapshot() {
13979
- return {
13980
- total: { ...this.total },
13981
- perSubagent: Object.fromEntries(
13982
- Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
13983
- )
13984
- };
14023
+ buildCriticTask() {
14024
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14025
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
14026
+ const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
14027
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14028
+ ${f.content}`).join("\n\n");
14029
+ return `You are Critic. Evaluate bug findings and refactor plans.
14030
+
14031
+ Target files:
14032
+ ${fileContents}
14033
+
14034
+ Read the BugHunter report at: ${bugHunterReportPath}
14035
+ Read the RefactorPlanner report at: ${refactorPlanPath}
14036
+
14037
+ For each bug and refactor plan, emit your evaluation using fleet_emit:
14038
+ { "type": "critic.evaluation", "payload": { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": ["<strength>"], "weaknesses": ["<weakness>"], "concerns": [{ "description": "<concern>", "severity": "<blocking|advisory>" }] } } }
14039
+
14040
+ After all evaluations, write your markdown report to:
14041
+ ${scratchpad}/critic-report-${this.sessionId}.md
14042
+
14043
+ Emit each evaluation immediately. Do not wait until you have read all reports.`;
13985
14044
  }
13986
- ensure(subagentId) {
13987
- let snap = this.perSubagent.get(subagentId);
13988
- if (!snap) {
13989
- const meta = this.metaLookup?.(subagentId);
13990
- snap = {
13991
- subagentId,
13992
- provider: meta?.provider,
13993
- model: meta?.model,
13994
- input: 0,
13995
- output: 0,
13996
- cacheRead: 0,
13997
- cacheWrite: 0,
13998
- cost: 0,
13999
- toolCalls: 0,
14000
- iterations: 0,
14001
- startedAt: Date.now(),
14002
- lastEventAt: Date.now()
14045
+ wireFleetBus() {
14046
+ const dTool = this.fleetBus.filter("tool.executed", (e) => {
14047
+ this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
14048
+ });
14049
+ this.disposers.push(dTool);
14050
+ const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
14051
+ const payload = e.payload;
14052
+ const role = this.roleFromSubagentId(e.subagentId);
14053
+ if (!role) return;
14054
+ const btwNotes = this.director.getLeaderBtwNotes();
14055
+ const alert = {
14056
+ sessionId: this.sessionId,
14057
+ subagentId: e.subagentId,
14058
+ role,
14059
+ level: "warning" /* WARNING */,
14060
+ message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
14061
+ budgetKind: payload.kind,
14062
+ elapsedMs: payload.timeoutMs,
14063
+ limit: payload.limit,
14064
+ btwNotes
14003
14065
  };
14004
- this.perSubagent.set(subagentId, snap);
14005
- }
14006
- return snap;
14007
- }
14008
- onProviderResponse(e) {
14009
- const snap = this.ensure(e.subagentId);
14010
- const p = e.payload;
14011
- const usage = p?.usage;
14012
- if (!usage) return;
14013
- snap.input += usage.input ?? 0;
14014
- snap.output += usage.output ?? 0;
14015
- snap.cacheRead += usage.cacheRead ?? 0;
14016
- snap.cacheWrite += usage.cacheWrite ?? 0;
14017
- this.total.input += usage.input ?? 0;
14018
- this.total.output += usage.output ?? 0;
14019
- this.total.cacheRead += usage.cacheRead ?? 0;
14020
- this.total.cacheWrite += usage.cacheWrite ?? 0;
14021
- const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
14022
- if (price) {
14023
- const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
14024
- snap.cost += delta;
14025
- this.total.cost += delta;
14026
- }
14027
- snap.lastEventAt = e.ts;
14028
- }
14029
- onToolExecuted(e) {
14030
- const snap = this.ensure(e.subagentId);
14031
- snap.toolCalls += 1;
14032
- snap.lastEventAt = e.ts;
14033
- }
14034
- onIterationStarted(e) {
14035
- const snap = this.ensure(e.subagentId);
14036
- snap.iterations += 1;
14037
- snap.lastEventAt = e.ts;
14038
- }
14039
- };
14040
- function makeSpawnTool(director, roster) {
14041
- const inputSchema = {
14042
- type: "object",
14043
- properties: {
14044
- role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
14045
- description: { type: "string", description: "Free-form task description. When `role` is not set, the director uses the smart dispatcher to route this to the best-matching catalog agent. Use this when you don't know the exact role name." },
14046
- name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
14047
- provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
14048
- model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
14049
- systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
14050
- maxIterations: { type: "number", minimum: 1 },
14051
- maxToolCalls: { type: "number", minimum: 1 },
14052
- maxCostUsd: { type: "number", minimum: 0 },
14053
- timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
14054
- idleTimeoutMs: { type: "number", minimum: 1, description: "Idle timeout in ms: reap the subagent after this long with no activity. Resets on every iteration/tool call. Default is role/coordinator-specific." },
14055
- maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
14056
- },
14057
- required: []
14058
- };
14059
- return {
14060
- name: "spawn_subagent",
14061
- description: "Create a new subagent under this director. Returns the subagent id.",
14062
- usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
14063
- permission: "auto",
14064
- mutating: false,
14065
- inputSchema,
14066
- async execute(input) {
14067
- const i = input ?? {};
14068
- const role = typeof i.role === "string" ? i.role : void 0;
14069
- const description = typeof i.description === "string" ? i.description : void 0;
14070
- let cfg;
14071
- if (role && roster) {
14072
- const base = roster[role];
14073
- if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
14074
- cfg = instantiateRosterConfig(role, base);
14075
- } else if (description && !role) {
14076
- const dispatchResult = await dispatchAgent(description, {
14077
- classifier: director.dispatchClassifier,
14078
- catalog: roster
14079
- });
14080
- const dispatchRole = dispatchResult.role;
14081
- if (roster?.[dispatchRole]) {
14082
- cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
14083
- } else {
14084
- const def = dispatchResult.definition;
14085
- cfg = {
14086
- name: def.config.name ?? dispatchRole,
14087
- role: dispatchRole,
14088
- provider: def.config.provider,
14089
- model: def.config.model
14090
- };
14091
- }
14066
+ this.alerts.push(alert);
14067
+ this.fleetBus.emit({
14068
+ subagentId: e.subagentId,
14069
+ ts: Date.now(),
14070
+ type: "collab.warning",
14071
+ payload: alert
14072
+ });
14073
+ const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
14074
+ if (decision === "cancel") {
14075
+ this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
14076
+ return;
14092
14077
  }
14093
- cfg ??= { name: i.name ?? "subagent" };
14094
- if (typeof i.name === "string") cfg.name = i.name;
14095
- if (typeof i.provider === "string") cfg.provider = i.provider;
14096
- if (typeof i.model === "string") cfg.model = i.model;
14097
- if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
14098
- if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
14099
- if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
14100
- if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
14101
- if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
14102
- if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
14103
- if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
14104
- try {
14105
- const subagentId = await director.spawn(cfg);
14106
- return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
14107
- } catch (err) {
14108
- if (err instanceof FleetSpawnBudgetError) {
14109
- return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
14110
- }
14111
- if (err instanceof FleetCostCapError) {
14112
- return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
14078
+ if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
14079
+ const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
14080
+ const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
14081
+ if (progress <= lastProgress) {
14082
+ payload.deny();
14083
+ return;
14113
14084
  }
14114
- return { error: err instanceof Error ? err.message : String(err) };
14085
+ this.lastTimeoutProgress.set(e.subagentId, progress);
14086
+ const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
14087
+ setImmediate(() => {
14088
+ payload.extend({ timeoutMs: newLimit });
14089
+ });
14090
+ return;
14115
14091
  }
14092
+ if (decision === "extend") {
14093
+ setImmediate(() => {
14094
+ const base = Math.max(payload.limit, payload.used);
14095
+ const extra = {};
14096
+ switch (payload.kind) {
14097
+ case "iterations":
14098
+ extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
14099
+ break;
14100
+ case "tool_calls":
14101
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
14102
+ break;
14103
+ case "tokens":
14104
+ extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
14105
+ break;
14106
+ case "cost":
14107
+ extra.maxCostUsd = Math.min(base * 1.5, 100);
14108
+ break;
14109
+ }
14110
+ payload.extend(extra);
14111
+ });
14112
+ return;
14113
+ }
14114
+ if (payload.kind !== "timeout") {
14115
+ setImmediate(() => {
14116
+ const base = Math.max(payload.limit, payload.used);
14117
+ const extra = {};
14118
+ switch (payload.kind) {
14119
+ case "iterations":
14120
+ extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
14121
+ break;
14122
+ case "tool_calls":
14123
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
14124
+ break;
14125
+ case "tokens":
14126
+ extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
14127
+ break;
14128
+ case "cost":
14129
+ extra.maxCostUsd = Math.min(base * 1.25, 100);
14130
+ break;
14131
+ }
14132
+ payload.extend(extra);
14133
+ });
14134
+ }
14135
+ });
14136
+ this.disposers.push(dBudget);
14137
+ const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
14138
+ const payload = e.payload;
14139
+ if (payload.sessionId !== this.sessionId) return;
14140
+ this.cancelled = true;
14141
+ if (this._timeoutTimer) {
14142
+ clearTimeout(this._timeoutTimer);
14143
+ this._timeoutTimer = void 0;
14144
+ }
14145
+ this.fleetBus.emit({
14146
+ subagentId: this.director.id,
14147
+ ts: Date.now(),
14148
+ type: "collab.cancelled",
14149
+ payload: { sessionId: this.sessionId, reason: payload.reason }
14150
+ });
14151
+ });
14152
+ this.disposers.push(dCancel);
14153
+ const d1 = this.fleetBus.filter("bug.found", (e) => {
14154
+ const payload = e.payload;
14155
+ if (payload?.finding) {
14156
+ this.bugs.set(payload.finding.id, payload.finding);
14157
+ this.emit("bug.found", payload);
14158
+ }
14159
+ });
14160
+ this.disposers.push(d1);
14161
+ const d2 = this.fleetBus.filter("refactor.plan", (e) => {
14162
+ const payload = e.payload;
14163
+ if (payload?.plan) {
14164
+ this.plans.set(payload.plan.id, payload.plan);
14165
+ this.emit("refactor.plan", payload);
14166
+ }
14167
+ });
14168
+ this.disposers.push(d2);
14169
+ const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
14170
+ const payload = e.payload;
14171
+ if (payload?.evaluation) {
14172
+ this.evaluations.set(payload.evaluation.id, payload.evaluation);
14173
+ this.emit("critic.evaluation", payload);
14174
+ }
14175
+ });
14176
+ this.disposers.push(d3);
14177
+ }
14178
+ roleFromSubagentId(subagentId) {
14179
+ for (const [role, id] of this.subagentIds) {
14180
+ if (id === subagentId) return role;
14116
14181
  }
14117
- };
14118
- }
14119
- function instantiateRosterConfig(role, base) {
14120
- return {
14121
- ...base,
14122
- // Roster entries are templates. A director may spawn several
14123
- // workers with the same role, so never reuse the template id.
14124
- id: `${role}-${randomUUID().slice(0, 8)}`
14125
- };
14126
- }
14127
- function makeAssignTool(director) {
14128
- const inputSchema = {
14129
- type: "object",
14130
- properties: {
14131
- subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
14132
- description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
14133
- maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
14134
- timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
14135
- },
14136
- required: ["subagentId", "description"]
14137
- };
14138
- return {
14139
- name: "assign_task",
14140
- description: "Hand a task to a previously spawned subagent. Returns the task id.",
14141
- permission: "auto",
14142
- mutating: false,
14143
- inputSchema,
14144
- async execute(input) {
14145
- const i = input;
14146
- const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
14147
- const taskId = await director.assign(task);
14148
- return { taskId, subagentId: i.subagentId };
14149
- }
14150
- };
14151
- }
14152
- function makeAwaitTasksTool(director) {
14153
- return {
14154
- name: "await_tasks",
14155
- description: "Block until every named task completes. Returns the array of TaskResult.",
14156
- permission: "auto",
14157
- mutating: false,
14158
- inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
14159
- async execute(input) {
14160
- const i = input;
14161
- const results = await director.awaitTasks(i.taskIds);
14162
- return { results };
14163
- }
14164
- };
14165
- }
14166
- function makeAskTool(director) {
14167
- return {
14168
- name: "ask_subagent",
14169
- description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
14170
- permission: "auto",
14171
- mutating: false,
14172
- inputSchema: {
14173
- type: "object",
14174
- properties: {
14175
- subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
14176
- question: { type: "string", minLength: 1, description: "The question or instruction." },
14177
- timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
14182
+ const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
14183
+ return match?.[1] ?? null;
14184
+ }
14185
+ assembleReport() {
14186
+ const bugList = Array.from(this.bugs.values());
14187
+ const planList = Array.from(this.plans.values());
14188
+ const evalList = Array.from(this.evaluations.values());
14189
+ let disposition = "completed";
14190
+ if (this.cancelled) disposition = "cancelled";
14191
+ const verdictOrder = {
14192
+ approve: 0,
14193
+ needs_revision: 1,
14194
+ reject: 2
14195
+ };
14196
+ const overallVerdict = evalList.reduce(
14197
+ (worst, eval_) => {
14198
+ const w = verdictOrder[worst];
14199
+ const c = verdictOrder[eval_.verdict];
14200
+ return c > w ? eval_.verdict : worst;
14178
14201
  },
14179
- required: ["subagentId", "question"]
14180
- },
14181
- async execute(input) {
14182
- const i = input;
14183
- try {
14184
- const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
14185
- const stored = director.largeAnswerStore.storeAnswer(answer);
14186
- if (stored.inline) {
14187
- return { ok: true, answer: stored.summary };
14202
+ "approve"
14203
+ );
14204
+ const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
14205
+ return {
14206
+ sessionId: this.sessionId,
14207
+ startedAt: this.snapshot.createdAt,
14208
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
14209
+ targetPaths: this.options.targetPaths,
14210
+ disposition,
14211
+ bugs: bugList,
14212
+ refactorPlans: planList,
14213
+ evaluations: evalList,
14214
+ alerts: [...this.alerts],
14215
+ overallVerdict,
14216
+ summary
14217
+ };
14218
+ }
14219
+ buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
14220
+ const lines = [
14221
+ `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
14222
+ "",
14223
+ `**Target:** ${this.options.targetPaths.join(", ")}`,
14224
+ `**Disposition:** ${disposition.toUpperCase()}`,
14225
+ `**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
14226
+ ""
14227
+ ];
14228
+ if (this.alerts.length > 0) {
14229
+ lines.push("### Alerts", "");
14230
+ for (const alert of this.alerts) {
14231
+ lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
14232
+ }
14233
+ lines.push("");
14234
+ }
14235
+ if (bugs.length > 0) {
14236
+ lines.push("### Bugs Found", "");
14237
+ for (const b of bugs) {
14238
+ lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
14239
+ }
14240
+ lines.push("");
14241
+ }
14242
+ if (plans.length > 0) {
14243
+ lines.push("### Refactor Plans", "");
14244
+ for (const p of plans) {
14245
+ lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
14246
+ for (const phase of p.phases) {
14247
+ lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
14188
14248
  }
14189
- return {
14190
- ok: true,
14191
- answer: stored.summary,
14192
- _answerKey: stored.key,
14193
- _hint: "Response was large and stored. Use ask_result with the key to retrieve it."
14194
- };
14195
- } catch (err) {
14196
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
14197
14249
  }
14250
+ lines.push("");
14198
14251
  }
14199
- };
14200
- }
14201
- function makeAskResultTool(director) {
14202
- return {
14203
- name: "ask_result",
14204
- description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
14205
- permission: "auto",
14206
- mutating: false,
14207
- inputSchema: {
14208
- type: "object",
14209
- properties: {
14210
- key: {
14211
- type: "string",
14212
- minLength: 1,
14213
- description: "The `_answerKey` returned by `ask_subagent` for a large response."
14252
+ if (evals.length > 0) {
14253
+ lines.push("### Critic Evaluations", "");
14254
+ for (const e of evals) {
14255
+ lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
14256
+ for (const c of e.concerns) {
14257
+ if (c.severity === "blocking") lines.push(` - ${c.description}`);
14214
14258
  }
14215
- },
14216
- required: ["key"]
14217
- },
14218
- async execute(input) {
14219
- const i = input;
14220
- const value = director.largeAnswerStore.retrieveAnswer(i.key);
14221
- if (value === void 0) {
14222
- return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
14223
14259
  }
14224
- return { ok: true, value };
14260
+ lines.push("");
14225
14261
  }
14226
- };
14227
- }
14228
- function makeRollUpTool(director) {
14229
- return {
14230
- name: "roll_up",
14231
- description: "Aggregate completed task results into a single formatted summary.",
14232
- permission: "auto",
14233
- mutating: false,
14234
- inputSchema: {
14235
- type: "object",
14236
- properties: {
14237
- taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
14238
- style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
14239
- },
14240
- required: ["taskIds"]
14241
- },
14242
- async execute(input) {
14243
- const i = input;
14244
- const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
14245
- return { summary, count: i.taskIds.length };
14246
- }
14247
- };
14262
+ return lines.join("\n");
14263
+ }
14264
+ cleanup() {
14265
+ for (const dispose of this.disposers) dispose();
14266
+ this.disposers.length = 0;
14267
+ }
14268
+ };
14269
+
14270
+ // src/coordination/director-prompts.ts
14271
+ var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
14272
+ subagents by spawning them, assigning tasks, awaiting completions, and
14273
+ rolling up their outputs into your next decision.
14274
+
14275
+ Core fleet tools available to you:
14276
+ - spawn_subagent \u2014 create a worker with a chosen provider / model / role
14277
+ - assign_task \u2014 hand a piece of work to a specific subagent
14278
+ - await_tasks \u2014 block until named task ids complete (parallel-safe)
14279
+ - ask_subagent \u2014 synchronously query a running subagent via the bridge
14280
+ - roll_up \u2014 aggregate finished tasks into a markdown/json summary
14281
+ - terminate_subagent \u2014 abort a stuck worker (use sparingly)
14282
+ - fleet_status \u2014 snapshot of all subagents and pending tasks
14283
+ - fleet_usage \u2014 token + cost breakdown per subagent and total
14284
+
14285
+ Working rules:
14286
+ 1. Decompose first. Before spawning, decide which sub-tasks are
14287
+ independent and can run in parallel. Sequential work doesn't need a
14288
+ subagent \u2014 do it yourself.
14289
+ 2. Match worker to job. Cheap/fast model for triage, capable model for
14290
+ synthesis. Different providers per sibling is allowed and encouraged.
14291
+ 3. Always pair an assign with an await. Don't fire-and-forget; you owe
14292
+ the user a single coherent answer at the end.
14293
+ 4. Roll up before deciding. After await_tasks resolves, call roll_up so
14294
+ the results are folded back into your context in a compact form.
14295
+ 5. Budget is real. Check fleet_usage periodically. If a subagent is
14296
+ thrashing, terminate it rather than letting cost climb silently.
14297
+ 6. Never claim a subagent's work as your own without verifying it. If a
14298
+ result looks wrong, ask_subagent for clarification before passing it
14299
+ to the user.
14300
+ 7. Wind down when satisfied. When the results are good enough, call
14301
+ work_complete \u2014 no new subagents will spawn and queued tasks complete
14302
+ as aborted. Running subagents finish naturally. Call terminate_subagent
14303
+ only for ones you need to stop immediately.`;
14304
+ var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
14305
+ a specific slice of a larger plan \u2014 do that slice well and report back.
14306
+
14307
+ Bridge contract:
14308
+ - You have a parent (the Director). You may call \`request\` on the
14309
+ parent bridge to ask a clarifying question. Use this sparingly; the
14310
+ parent is also working.
14311
+ - You MAY NOT request the parent's system prompt, tool list, or other
14312
+ subagents' context. Those are not yours to read.
14313
+ - Your final task output is what the Director sees. Be concise,
14314
+ structured, and self-contained \u2014 assume the Director will paste your
14315
+ output into its own context.`;
14316
+ function composeDirectorPrompt(parts = {}) {
14317
+ const sections = [];
14318
+ const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
14319
+ if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
14320
+ if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
14321
+ sections.push(`Available roles you can spawn:
14322
+ ${parts.rosterSummary.trim()}`);
14323
+ }
14324
+ if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
14325
+ sections.push(parts.basePrompt.trim());
14326
+ }
14327
+ return sections.join("\n\n");
14248
14328
  }
14249
- function makeTerminateTool(director) {
14329
+ function composeSubagentPrompt(parts = {}) {
14330
+ const sections = [];
14331
+ const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
14332
+ if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
14333
+ if (parts.role && parts.role.trim().length > 0) {
14334
+ sections.push(`Role:
14335
+ ${parts.role.trim()}`);
14336
+ }
14337
+ if (parts.task && parts.task.trim().length > 0) {
14338
+ sections.push(`Task:
14339
+ ${parts.task.trim()}`);
14340
+ }
14341
+ if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
14342
+ sections.push(
14343
+ `Shared notes:
14344
+ A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
14345
+ - Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
14346
+ - Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
14347
+ - Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
14348
+ );
14349
+ }
14350
+ if (parts.override && parts.override.trim().length > 0) {
14351
+ sections.push(parts.override.trim());
14352
+ }
14353
+ return sections.join("\n\n");
14354
+ }
14355
+ function rosterSummaryFromConfigs(roster) {
14356
+ const lines = [];
14357
+ for (const [roleId, cfg] of Object.entries(roster)) {
14358
+ const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
14359
+ const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
14360
+ const tail = headline ? ` \u2014 ${headline}` : "";
14361
+ lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
14362
+ }
14363
+ return lines.join("\n");
14364
+ }
14365
+ function makeSpawnTool(director, roster) {
14366
+ const inputSchema = {
14367
+ type: "object",
14368
+ properties: {
14369
+ role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
14370
+ description: { type: "string", description: "Free-form task description. When `role` is not set, the director uses the smart dispatcher to route this to the best-matching catalog agent. Use this when you don't know the exact role name." },
14371
+ name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
14372
+ provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
14373
+ model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
14374
+ systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
14375
+ maxIterations: { type: "number", minimum: 1 },
14376
+ maxToolCalls: { type: "number", minimum: 1 },
14377
+ maxCostUsd: { type: "number", minimum: 0 },
14378
+ timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
14379
+ idleTimeoutMs: { type: "number", minimum: 1, description: "Idle timeout in ms: reap the subagent after this long with no activity. Resets on every iteration/tool call. Default is role/coordinator-specific." },
14380
+ maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
14381
+ },
14382
+ required: []
14383
+ };
14250
14384
  return {
14251
- name: "terminate_subagent",
14252
- description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
14385
+ name: "spawn_subagent",
14386
+ description: "Create a new subagent under this director. Returns the subagent id.",
14387
+ usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
14253
14388
  permission: "auto",
14254
- mutating: true,
14255
- inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
14389
+ mutating: false,
14390
+ inputSchema,
14256
14391
  async execute(input) {
14257
- const i = input;
14258
- await director.terminate(i.subagentId);
14259
- return { ok: true };
14392
+ const i = input ?? {};
14393
+ const role = typeof i.role === "string" ? i.role : void 0;
14394
+ const description = typeof i.description === "string" ? i.description : void 0;
14395
+ let cfg;
14396
+ if (role && roster) {
14397
+ const base = roster[role];
14398
+ if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
14399
+ cfg = instantiateRosterConfig(role, base);
14400
+ } else if (description && !role) {
14401
+ const dispatchResult = await dispatchAgent(description, {
14402
+ classifier: director.dispatchClassifier,
14403
+ catalog: roster
14404
+ });
14405
+ const dispatchRole = dispatchResult.role;
14406
+ if (roster?.[dispatchRole]) {
14407
+ cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
14408
+ } else {
14409
+ const def = dispatchResult.definition;
14410
+ cfg = {
14411
+ name: def.config.name ?? dispatchRole,
14412
+ role: dispatchRole,
14413
+ provider: def.config.provider,
14414
+ model: def.config.model
14415
+ };
14416
+ }
14417
+ }
14418
+ cfg ??= { name: i.name ?? "subagent" };
14419
+ if (typeof i.name === "string") cfg.name = i.name;
14420
+ if (typeof i.provider === "string") cfg.provider = i.provider;
14421
+ if (typeof i.model === "string") cfg.model = i.model;
14422
+ if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
14423
+ if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
14424
+ if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
14425
+ if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
14426
+ if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
14427
+ if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
14428
+ if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
14429
+ try {
14430
+ const subagentId = await director.spawn(cfg);
14431
+ return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
14432
+ } catch (err) {
14433
+ if (err instanceof FleetSpawnBudgetError) {
14434
+ return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
14435
+ }
14436
+ if (err instanceof FleetCostCapError) {
14437
+ return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
14438
+ }
14439
+ return { error: err instanceof Error ? err.message : String(err) };
14440
+ }
14260
14441
  }
14261
14442
  };
14262
14443
  }
14263
- function makeTerminateAllTool(director) {
14444
+ function instantiateRosterConfig(role, base) {
14264
14445
  return {
14265
- name: "terminate_all",
14266
- description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
14267
- permission: "auto",
14268
- mutating: true,
14269
- inputSchema: { type: "object", properties: {}, required: [] },
14270
- async execute() {
14271
- await director.terminateAll();
14272
- return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
14273
- }
14446
+ ...base,
14447
+ // Roster entries are templates. A director may spawn several
14448
+ // workers with the same role, so never reuse the template id.
14449
+ id: `${role}-${randomUUID().slice(0, 8)}`
14274
14450
  };
14275
14451
  }
14276
- function makeFleetStatusTool(director) {
14452
+ function makeAssignTool(director) {
14453
+ const inputSchema = {
14454
+ type: "object",
14455
+ properties: {
14456
+ subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
14457
+ description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
14458
+ maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
14459
+ timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
14460
+ },
14461
+ required: ["subagentId", "description"]
14462
+ };
14277
14463
  return {
14278
- name: "fleet_status",
14279
- description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
14464
+ name: "assign_task",
14465
+ description: "Hand a task to a previously spawned subagent. Returns the task id.",
14280
14466
  permission: "auto",
14281
14467
  mutating: false,
14282
- inputSchema: { type: "object", properties: {}, required: [] },
14283
- async execute() {
14284
- const base = director.status();
14285
- const fm = director.fleetManager;
14286
- const stats = fm?.getFleetStats();
14287
- const fleetStatus = fm?.getFleetStatus();
14288
- return {
14289
- subagents: base.subagents,
14290
- coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
14291
- pending: fleetStatus?.pending ?? [],
14292
- usage: fm?.snapshot()
14293
- };
14468
+ inputSchema,
14469
+ async execute(input) {
14470
+ const i = input;
14471
+ const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
14472
+ const taskId = await director.assign(task);
14473
+ return { taskId, subagentId: i.subagentId };
14294
14474
  }
14295
14475
  };
14296
14476
  }
14297
- function makeFleetUsageTool(director) {
14477
+ function makeAwaitTasksTool(director) {
14298
14478
  return {
14299
- name: "fleet_usage",
14300
- description: "Token + cost breakdown across the fleet, per-subagent and totals.",
14479
+ name: "await_tasks",
14480
+ description: "Block until every named task completes. Returns the array of TaskResult.",
14301
14481
  permission: "auto",
14302
14482
  mutating: false,
14303
- inputSchema: { type: "object", properties: {}, required: [] },
14304
- async execute() {
14305
- return director.snapshot();
14483
+ inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
14484
+ async execute(input) {
14485
+ const i = input;
14486
+ const results = await director.awaitTasks(i.taskIds);
14487
+ return { results };
14306
14488
  }
14307
14489
  };
14308
14490
  }
14309
- function makeFleetSessionTool(director) {
14491
+ function makeAskTool(director) {
14310
14492
  return {
14311
- name: "fleet_session",
14312
- description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
14493
+ name: "ask_subagent",
14494
+ description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
14313
14495
  permission: "auto",
14314
14496
  mutating: false,
14315
14497
  inputSchema: {
14316
14498
  type: "object",
14317
14499
  properties: {
14318
- subagentId: { type: "string", description: "Subagent id to read the transcript of." },
14319
- tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
14500
+ subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
14501
+ question: { type: "string", minLength: 1, description: "The question or instruction." },
14502
+ timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
14503
+ },
14504
+ required: ["subagentId", "question"]
14505
+ },
14506
+ async execute(input) {
14507
+ const i = input;
14508
+ try {
14509
+ const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
14510
+ const stored = director.largeAnswerStore.storeAnswer(answer);
14511
+ if (stored.inline) {
14512
+ return { ok: true, answer: stored.summary };
14513
+ }
14514
+ return {
14515
+ ok: true,
14516
+ answer: stored.summary,
14517
+ _answerKey: stored.key,
14518
+ _hint: "Response was large and stored. Use ask_result with the key to retrieve it."
14519
+ };
14520
+ } catch (err) {
14521
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
14522
+ }
14523
+ }
14524
+ };
14525
+ }
14526
+ function makeAskResultTool(director) {
14527
+ return {
14528
+ name: "ask_result",
14529
+ description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
14530
+ permission: "auto",
14531
+ mutating: false,
14532
+ inputSchema: {
14533
+ type: "object",
14534
+ properties: {
14535
+ key: {
14536
+ type: "string",
14537
+ minLength: 1,
14538
+ description: "The `_answerKey` returned by `ask_subagent` for a large response."
14539
+ }
14540
+ },
14541
+ required: ["key"]
14542
+ },
14543
+ async execute(input) {
14544
+ const i = input;
14545
+ const value = director.largeAnswerStore.retrieveAnswer(i.key);
14546
+ if (value === void 0) {
14547
+ return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
14548
+ }
14549
+ return { ok: true, value };
14550
+ }
14551
+ };
14552
+ }
14553
+ function makeRollUpTool(director) {
14554
+ return {
14555
+ name: "roll_up",
14556
+ description: "Aggregate completed task results into a single formatted summary.",
14557
+ permission: "auto",
14558
+ mutating: false,
14559
+ inputSchema: {
14560
+ type: "object",
14561
+ properties: {
14562
+ taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
14563
+ style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
14564
+ },
14565
+ required: ["taskIds"]
14566
+ },
14567
+ async execute(input) {
14568
+ const i = input;
14569
+ const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
14570
+ return { summary, count: i.taskIds.length };
14571
+ }
14572
+ };
14573
+ }
14574
+ function makeTerminateTool(director) {
14575
+ return {
14576
+ name: "terminate_subagent",
14577
+ description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
14578
+ permission: "auto",
14579
+ mutating: true,
14580
+ inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
14581
+ async execute(input) {
14582
+ const i = input;
14583
+ await director.terminate(i.subagentId);
14584
+ return { ok: true };
14585
+ }
14586
+ };
14587
+ }
14588
+ function makeTerminateAllTool(director) {
14589
+ return {
14590
+ name: "terminate_all",
14591
+ description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
14592
+ permission: "auto",
14593
+ mutating: true,
14594
+ inputSchema: { type: "object", properties: {}, required: [] },
14595
+ async execute() {
14596
+ await director.terminateAll();
14597
+ return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
14598
+ }
14599
+ };
14600
+ }
14601
+ function makeFleetStatusTool(director) {
14602
+ return {
14603
+ name: "fleet_status",
14604
+ description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
14605
+ permission: "auto",
14606
+ mutating: false,
14607
+ inputSchema: { type: "object", properties: {}, required: [] },
14608
+ async execute() {
14609
+ const base = director.status();
14610
+ const fm = director.fleetManager;
14611
+ const stats = fm?.getFleetStats();
14612
+ const fleetStatus = fm?.getFleetStatus();
14613
+ return {
14614
+ subagents: base.subagents,
14615
+ coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
14616
+ pending: fleetStatus?.pending ?? [],
14617
+ usage: fm?.snapshot()
14618
+ };
14619
+ }
14620
+ };
14621
+ }
14622
+ function makeFleetUsageTool(director) {
14623
+ return {
14624
+ name: "fleet_usage",
14625
+ description: "Token + cost breakdown across the fleet, per-subagent and totals.",
14626
+ permission: "auto",
14627
+ mutating: false,
14628
+ inputSchema: { type: "object", properties: {}, required: [] },
14629
+ async execute() {
14630
+ return director.snapshot();
14631
+ }
14632
+ };
14633
+ }
14634
+ function makeFleetSessionTool(director) {
14635
+ return {
14636
+ name: "fleet_session",
14637
+ description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
14638
+ permission: "auto",
14639
+ mutating: false,
14640
+ inputSchema: {
14641
+ type: "object",
14642
+ properties: {
14643
+ subagentId: { type: "string", description: "Subagent id to read the transcript of." },
14644
+ tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
14320
14645
  },
14321
14646
  required: ["subagentId"]
14322
14647
  },
@@ -14403,601 +14728,358 @@ function makeCollabDebugTool(director) {
14403
14728
  targetPaths: i.targetPaths,
14404
14729
  timeoutMs: i.timeoutMs,
14405
14730
  maxTargetFiles: i.maxTargetFiles,
14406
- contextWindow: i.contextWindow
14407
- };
14408
- try {
14409
- const report = await director.spawnCollab(options);
14410
- return {
14411
- sessionId: report.sessionId,
14412
- overallVerdict: report.overallVerdict,
14413
- bugCount: report.bugs.length,
14414
- planCount: report.refactorPlans.length,
14415
- evaluationCount: report.evaluations.length,
14416
- summary: report.summary,
14417
- bugs: report.bugs,
14418
- refactorPlans: report.refactorPlans,
14419
- evaluations: report.evaluations
14420
- };
14421
- } catch (err) {
14422
- const msg = err instanceof Error ? err.message : String(err);
14423
- return { error: "collab_debug failed: " + msg };
14424
- }
14425
- }
14426
- };
14427
- }
14428
- function makeFleetEmitTool(director) {
14429
- return {
14430
- name: "fleet_emit",
14431
- description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
14432
- permission: "auto",
14433
- mutating: false,
14434
- inputSchema: {
14435
- type: "object",
14436
- properties: {
14437
- type: {
14438
- type: "string",
14439
- description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
14440
- },
14441
- payload: {
14442
- type: "object",
14443
- description: "Event payload. Structure depends on event type. Use null if no payload."
14444
- }
14445
- },
14446
- required: ["type"]
14447
- },
14448
- async execute(input) {
14449
- const i = input;
14450
- director.fleet.emit({
14451
- subagentId: director.id,
14452
- ts: Date.now(),
14453
- type: i.type,
14454
- payload: i.payload ?? {}
14455
- });
14456
- return { ok: true, event: i.type };
14457
- }
14458
- };
14459
- }
14460
- function makeWorkCompleteTool(director) {
14461
- return {
14462
- name: "work_complete",
14463
- description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
14464
- permission: "auto",
14465
- mutating: false,
14466
- inputSchema: { type: "object", properties: {}, required: [] },
14467
- async execute() {
14468
- director.workComplete();
14469
- return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
14470
- }
14471
- };
14472
- }
14473
- var DEFAULT_MAX_TARGET_FILES = 30;
14474
- var CollabSession = class extends EventEmitter {
14475
- sessionId;
14476
- options;
14477
- snapshot;
14478
- director;
14479
- fleetBus;
14480
- subagentIds = /* @__PURE__ */ new Map();
14481
- // role → subagentId
14482
- bugs = /* @__PURE__ */ new Map();
14483
- plans = /* @__PURE__ */ new Map();
14484
- evaluations = /* @__PURE__ */ new Map();
14485
- disposers = new Array();
14486
- settled = false;
14487
- timeoutMs;
14488
- cancelled = false;
14489
- alerts = [];
14490
- /** Tracks tool call counts per subagent for progress-based timeout decisions. */
14491
- progressBySubagent = /* @__PURE__ */ new Map();
14492
- /** Last tool call count when a timeout warning was handled. */
14493
- lastTimeoutProgress = /* @__PURE__ */ new Map();
14494
- /** Session-level timeout timer handle (cleared on cancel or natural completion). */
14495
- _timeoutTimer;
14496
- constructor(director, fleetBus, options) {
14497
- super();
14498
- this.sessionId = randomUUID();
14499
- this.options = options;
14500
- this.director = director;
14501
- this.fleetBus = fleetBus;
14502
- this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
14503
- if (options.prebuiltSnapshot) {
14504
- this.snapshot = options.prebuiltSnapshot;
14505
- } else {
14506
- this.snapshot = {
14507
- id: this.sessionId,
14508
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
14509
- files: []
14510
- };
14511
- }
14512
- }
14513
- get id() {
14514
- return this.sessionId;
14515
- }
14516
- getSessionAlerts() {
14517
- return [...this.alerts];
14518
- }
14519
- isCancelled() {
14520
- return this.cancelled;
14521
- }
14522
- /**
14523
- * Snapshot of role → subagentId map. The Director calls coordinator.stop()
14524
- * for each agent when cancelling the session, using this map to enumerate
14525
- * all three collab agents.
14526
- */
14527
- getSubagentIds() {
14528
- return new Map(this.subagentIds);
14529
- }
14530
- /**
14531
- * Returns the effective file limit for this session.
14532
- * Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
14533
- */
14534
- effectiveFileLimit() {
14535
- if (this.options.maxTargetFiles !== void 0) {
14536
- return this.options.maxTargetFiles;
14537
- }
14538
- if (this.options.contextWindow !== void 0) {
14539
- return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
14540
- }
14541
- return DEFAULT_MAX_TARGET_FILES;
14542
- }
14543
- async buildSnapshot() {
14544
- if (this.snapshot.files.length > 0) return this.snapshot;
14545
- const allFiles = [];
14546
- for (const pattern of this.options.targetPaths) {
14547
- const expanded = await expandGlob(pattern);
14548
- allFiles.push(...expanded);
14549
- }
14550
- const limit = this.effectiveFileLimit();
14551
- if (allFiles.length > limit) {
14552
- const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
14553
- throw new Error(
14554
- `[collab_debug] Target has ${allFiles.length} files, which exceeds the limit (${hint}). Narrow the target or pass maxTargetFiles / contextWindow to override. For large codebases, run package-by-package or module-by-module sessions instead of targeting the entire repo.`
14555
- );
14556
- }
14557
- for (const filePath of allFiles) {
14558
- try {
14559
- const content = await fsp3.readFile(filePath, "utf8");
14560
- const ext = filePath.split(".").pop() ?? "";
14561
- const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
14562
- this.snapshot.files.push({ path: filePath, content, language });
14563
- } catch {
14564
- this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
14565
- }
14566
- }
14567
- return this.snapshot;
14568
- }
14569
- /**
14570
- * Cancel the session. Emits director.cancel_collab on the FleetBus so all
14571
- * collab agents finish early. The session-level timeout timer is also cleared.
14572
- * Safe to call multiple times (idempotent after first call).
14573
- */
14574
- cancel(reason = "Director cancelled collab session") {
14575
- if (this.settled) return;
14576
- this.cancelled = true;
14577
- if (this._timeoutTimer) {
14578
- clearTimeout(this._timeoutTimer);
14579
- this._timeoutTimer = void 0;
14580
- }
14581
- this.fleetBus.emit({
14582
- subagentId: this.director.id,
14583
- ts: Date.now(),
14584
- type: "director.cancel_collab",
14585
- payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
14586
- });
14587
- this.fleetBus.emit({
14588
- subagentId: this.director.id,
14589
- ts: Date.now(),
14590
- type: "collab.cancelled",
14591
- payload: { sessionId: this.sessionId, reason }
14592
- });
14593
- }
14594
- async start() {
14595
- if (this.settled) throw new Error("session already settled");
14596
- this.settled = true;
14597
- await this.buildSnapshot();
14598
- this.wireFleetBus();
14599
- const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
14600
- this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
14601
- this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
14602
- this.spawnAgent("critic", this.buildCriticTask())
14603
- ]);
14604
- this.subagentIds.set("bug-hunter", bugHunterId);
14605
- this.subagentIds.set("refactor-planner", refactorPlannerId);
14606
- this.subagentIds.set("critic", criticId);
14607
- const timeout = new Promise((_, reject) => {
14608
- this._timeoutTimer = setTimeout(() => {
14609
- this.cancel("Session-level timeout reached");
14610
- reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
14611
- }, this.timeoutMs);
14612
- });
14613
- let results = null;
14614
- try {
14615
- results = await Promise.race([
14616
- Promise.all([
14617
- this.director.awaitTasks([bugHunterId]),
14618
- this.director.awaitTasks([refactorPlannerId]),
14619
- this.director.awaitTasks([criticId])
14620
- ]),
14621
- timeout
14622
- ]);
14623
- } catch (err) {
14624
- if (this._timeoutTimer) {
14625
- clearTimeout(this._timeoutTimer);
14626
- this._timeoutTimer = void 0;
14627
- }
14628
- this.cleanup();
14629
- const error = err instanceof Error ? err : new Error(String(err));
14630
- this.emit("session.error", error);
14631
- throw error;
14632
- }
14633
- for (const result of results.flat()) {
14634
- await this.parseAndEmit(result);
14635
- }
14636
- const report = this.assembleReport();
14637
- this.cleanup();
14638
- this.emit("session.done", report);
14639
- return report;
14640
- }
14641
- async parseAndEmit(result) {
14642
- if (result.status !== "success" || result.result == null) return;
14643
- const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
14644
- for (const obj of this.extractJsonObjects(text)) {
14645
- const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
14646
- if (!type) continue;
14647
- this.fleetBus.emit({
14648
- subagentId: result.subagentId,
14649
- taskId: result.taskId,
14650
- ts: Date.now(),
14651
- type,
14652
- payload: obj
14653
- });
14654
- }
14655
- }
14656
- extractJsonObjects(text) {
14657
- const objects = [];
14658
- let depth = 0;
14659
- let start = -1;
14660
- let inString = false;
14661
- let escaped = false;
14662
- for (let i = 0; i < text.length; i++) {
14663
- const ch = text[i];
14664
- if (inString) {
14665
- if (escaped) escaped = false;
14666
- else if (ch === "\\") escaped = true;
14667
- else if (ch === '"') inString = false;
14668
- continue;
14669
- }
14670
- if (ch === '"') {
14671
- inString = true;
14672
- } else if (ch === "{") {
14673
- if (depth === 0) start = i;
14674
- depth++;
14675
- } else if (ch === "}" && depth > 0) {
14676
- depth--;
14677
- if (depth === 0 && start >= 0) {
14678
- const candidate = text.slice(start, i + 1);
14679
- try {
14680
- const parsed = JSON.parse(candidate);
14681
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
14682
- objects.push(parsed);
14683
- }
14684
- } catch {
14685
- }
14686
- start = -1;
14687
- }
14688
- }
14689
- }
14690
- return objects;
14691
- }
14692
- budgetForRole(role) {
14693
- if (this.options.budgetOverrides?.[role]) {
14694
- return this.options.budgetOverrides[role];
14695
- }
14696
- const defaults = {
14697
- "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
14698
- "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
14699
- "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
14700
- };
14701
- return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
14702
- }
14703
- async spawnAgent(role, taskBrief) {
14704
- const budget = this.budgetForRole(role);
14705
- const cfg = {
14706
- id: `${role}-${this.sessionId}`,
14707
- name: role,
14708
- role,
14709
- tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
14710
- maxIterations: budget.maxIterations,
14711
- maxToolCalls: budget.maxToolCalls,
14712
- timeoutMs: budget.timeoutMs
14713
- };
14714
- const subagentId = await this.director.spawn(cfg);
14715
- await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
14716
- return subagentId;
14717
- }
14718
- buildBugHunterTask() {
14719
- const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14720
- const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14721
- ${f.content}`).join("\n\n");
14722
- return `You are BugHunter. Scan the following files for bugs and code smells.
14723
-
14724
- Target files:
14725
- ${fileContents}
14726
-
14727
- For each bug found, emit it using the fleet_emit tool immediately:
14728
- { "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
14729
-
14730
- After scanning all files, write your full markdown bug report to:
14731
- ${scratchpad}/bug-hunter-report-${this.sessionId}.md
14732
-
14733
- Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
14734
- }
14735
- buildRefactorPlannerTask() {
14736
- const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14737
- const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
14738
- const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14739
- ${f.content}`).join("\n\n");
14740
- return `You are RefactorPlanner. Plan refactorings for the following files.
14741
-
14742
- Target files:
14743
- ${fileContents}
14744
-
14745
- Read the BugHunter report at: ${bugHunterReportPath}
14746
-
14747
- For each bug you can address, emit a refactor plan using fleet_emit:
14748
- { "type": "refactor.plan", "payload": { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [{ "number": 1, "title": "<phase>", "tasks": ["<task>"], "risk": "<low|medium|high>" }], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } } }
14749
-
14750
- Also write your full markdown plan to:
14751
- ${scratchpad}/refactor-plan-${this.sessionId}.md
14752
-
14753
- Emit each plan immediately. Do not wait until planning is complete.`;
14754
- }
14755
- buildCriticTask() {
14756
- const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14757
- const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
14758
- const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
14759
- const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14760
- ${f.content}`).join("\n\n");
14761
- return `You are Critic. Evaluate bug findings and refactor plans.
14762
-
14763
- Target files:
14764
- ${fileContents}
14765
-
14766
- Read the BugHunter report at: ${bugHunterReportPath}
14767
- Read the RefactorPlanner report at: ${refactorPlanPath}
14768
-
14769
- For each bug and refactor plan, emit your evaluation using fleet_emit:
14770
- { "type": "critic.evaluation", "payload": { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": ["<strength>"], "weaknesses": ["<weakness>"], "concerns": [{ "description": "<concern>", "severity": "<blocking|advisory>" }] } } }
14771
-
14772
- After all evaluations, write your markdown report to:
14773
- ${scratchpad}/critic-report-${this.sessionId}.md
14774
-
14775
- Emit each evaluation immediately. Do not wait until you have read all reports.`;
14776
- }
14777
- wireFleetBus() {
14778
- const dTool = this.fleetBus.filter("tool.executed", (e) => {
14779
- this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
14780
- });
14781
- this.disposers.push(dTool);
14782
- const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
14783
- const payload = e.payload;
14784
- const role = this.roleFromSubagentId(e.subagentId);
14785
- if (!role) return;
14786
- const btwNotes = this.director.getLeaderBtwNotes();
14787
- const alert = {
14788
- sessionId: this.sessionId,
14789
- subagentId: e.subagentId,
14790
- role,
14791
- level: "warning" /* WARNING */,
14792
- message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
14793
- budgetKind: payload.kind,
14794
- elapsedMs: payload.timeoutMs,
14795
- limit: payload.limit,
14796
- btwNotes
14797
- };
14798
- this.alerts.push(alert);
14799
- this.fleetBus.emit({
14800
- subagentId: e.subagentId,
14801
- ts: Date.now(),
14802
- type: "collab.warning",
14803
- payload: alert
14804
- });
14805
- const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
14806
- if (decision === "cancel") {
14807
- this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
14808
- return;
14809
- }
14810
- if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
14811
- const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
14812
- const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
14813
- if (progress <= lastProgress) {
14814
- payload.deny();
14815
- return;
14816
- }
14817
- this.lastTimeoutProgress.set(e.subagentId, progress);
14818
- const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
14819
- setImmediate(() => {
14820
- payload.extend({ timeoutMs: newLimit });
14821
- });
14822
- return;
14823
- }
14824
- if (decision === "extend") {
14825
- setImmediate(() => {
14826
- const base = Math.max(payload.limit, payload.used);
14827
- const extra = {};
14828
- switch (payload.kind) {
14829
- case "iterations":
14830
- extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
14831
- break;
14832
- case "tool_calls":
14833
- extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
14834
- break;
14835
- case "tokens":
14836
- extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
14837
- break;
14838
- case "cost":
14839
- extra.maxCostUsd = Math.min(base * 1.5, 100);
14840
- break;
14841
- }
14842
- payload.extend(extra);
14843
- });
14844
- return;
14845
- }
14846
- if (payload.kind !== "timeout") {
14847
- setImmediate(() => {
14848
- const base = Math.max(payload.limit, payload.used);
14849
- const extra = {};
14850
- switch (payload.kind) {
14851
- case "iterations":
14852
- extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
14853
- break;
14854
- case "tool_calls":
14855
- extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
14856
- break;
14857
- case "tokens":
14858
- extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
14859
- break;
14860
- case "cost":
14861
- extra.maxCostUsd = Math.min(base * 1.25, 100);
14862
- break;
14863
- }
14864
- payload.extend(extra);
14865
- });
14866
- }
14867
- });
14868
- this.disposers.push(dBudget);
14869
- const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
14870
- const payload = e.payload;
14871
- if (payload.sessionId !== this.sessionId) return;
14872
- this.cancelled = true;
14873
- if (this._timeoutTimer) {
14874
- clearTimeout(this._timeoutTimer);
14875
- this._timeoutTimer = void 0;
14731
+ contextWindow: i.contextWindow
14732
+ };
14733
+ try {
14734
+ const report = await director.spawnCollab(options);
14735
+ return {
14736
+ sessionId: report.sessionId,
14737
+ overallVerdict: report.overallVerdict,
14738
+ bugCount: report.bugs.length,
14739
+ planCount: report.refactorPlans.length,
14740
+ evaluationCount: report.evaluations.length,
14741
+ summary: report.summary,
14742
+ bugs: report.bugs,
14743
+ refactorPlans: report.refactorPlans,
14744
+ evaluations: report.evaluations
14745
+ };
14746
+ } catch (err) {
14747
+ const msg = err instanceof Error ? err.message : String(err);
14748
+ return { error: "collab_debug failed: " + msg };
14876
14749
  }
14877
- this.fleetBus.emit({
14878
- subagentId: this.director.id,
14750
+ }
14751
+ };
14752
+ }
14753
+ function makeFleetEmitTool(director) {
14754
+ return {
14755
+ name: "fleet_emit",
14756
+ description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
14757
+ permission: "auto",
14758
+ mutating: false,
14759
+ inputSchema: {
14760
+ type: "object",
14761
+ properties: {
14762
+ type: {
14763
+ type: "string",
14764
+ description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
14765
+ },
14766
+ payload: {
14767
+ type: "object",
14768
+ description: "Event payload. Structure depends on event type. Use null if no payload."
14769
+ }
14770
+ },
14771
+ required: ["type"]
14772
+ },
14773
+ async execute(input) {
14774
+ const i = input;
14775
+ director.fleet.emit({
14776
+ subagentId: director.id,
14879
14777
  ts: Date.now(),
14880
- type: "collab.cancelled",
14881
- payload: { sessionId: this.sessionId, reason: payload.reason }
14778
+ type: i.type,
14779
+ payload: i.payload ?? {}
14882
14780
  });
14781
+ return { ok: true, event: i.type };
14782
+ }
14783
+ };
14784
+ }
14785
+ function makeWorkCompleteTool(director) {
14786
+ return {
14787
+ name: "work_complete",
14788
+ description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
14789
+ permission: "auto",
14790
+ mutating: false,
14791
+ inputSchema: { type: "object", properties: {}, required: [] },
14792
+ async execute() {
14793
+ director.workComplete();
14794
+ return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
14795
+ }
14796
+ };
14797
+ }
14798
+
14799
+ // src/coordination/fleet-bus.ts
14800
+ var FleetBus = class {
14801
+ byId = /* @__PURE__ */ new Map();
14802
+ byType = /* @__PURE__ */ new Map();
14803
+ any = /* @__PURE__ */ new Set();
14804
+ /**
14805
+ * Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
14806
+ * `onPattern('*')`) to forward all events with subagent attribution, so
14807
+ * new kernel event types are automatically forwarded without any manual
14808
+ * registration. `subagent.*` events are excluded because they originate
14809
+ * from MultiAgentHost on the parent bus, not the subagent's own bus.
14810
+ *
14811
+ * Returns a disposer that detaches every subscription; call on
14812
+ * subagent teardown so the listeners don't outlive the run.
14813
+ */
14814
+ attach(subagentId, bus, taskId) {
14815
+ const off = bus.onAny((type, payload) => {
14816
+ if (type.startsWith("subagent.")) return;
14817
+ this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
14883
14818
  });
14884
- this.disposers.push(dCancel);
14885
- const d1 = this.fleetBus.filter("bug.found", (e) => {
14886
- const payload = e.payload;
14887
- if (payload?.finding) {
14888
- this.bugs.set(payload.finding.id, payload.finding);
14889
- this.emit("bug.found", payload);
14819
+ return () => {
14820
+ off();
14821
+ };
14822
+ }
14823
+ /** Subscribe to every event from one subagent. */
14824
+ subscribe(subagentId, handler) {
14825
+ let set = this.byId.get(subagentId);
14826
+ if (!set) {
14827
+ set = /* @__PURE__ */ new Set();
14828
+ this.byId.set(subagentId, set);
14829
+ }
14830
+ set.add(handler);
14831
+ return () => {
14832
+ set.delete(handler);
14833
+ };
14834
+ }
14835
+ /** Subscribe to one event type across all subagents. */
14836
+ filter(type, handler) {
14837
+ let set = this.byType.get(type);
14838
+ if (!set) {
14839
+ set = /* @__PURE__ */ new Set();
14840
+ this.byType.set(type, set);
14841
+ }
14842
+ set.add(handler);
14843
+ return () => {
14844
+ set.delete(handler);
14845
+ };
14846
+ }
14847
+ /** Subscribe to literally everything. The fleet roll-up uses this. */
14848
+ onAny(handler) {
14849
+ this.any.add(handler);
14850
+ return () => {
14851
+ this.any.delete(handler);
14852
+ };
14853
+ }
14854
+ emit(event) {
14855
+ const byId = this.byId.get(event.subagentId);
14856
+ if (byId)
14857
+ for (const h of byId) {
14858
+ try {
14859
+ h(event);
14860
+ } catch {
14861
+ }
14890
14862
  }
14891
- });
14892
- this.disposers.push(d1);
14893
- const d2 = this.fleetBus.filter("refactor.plan", (e) => {
14894
- const payload = e.payload;
14895
- if (payload?.plan) {
14896
- this.plans.set(payload.plan.id, payload.plan);
14897
- this.emit("refactor.plan", payload);
14863
+ const byType = this.byType.get(event.type);
14864
+ if (byType)
14865
+ for (const h of byType) {
14866
+ try {
14867
+ h(event);
14868
+ } catch {
14869
+ }
14898
14870
  }
14899
- });
14900
- this.disposers.push(d2);
14901
- const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
14902
- const payload = e.payload;
14903
- if (payload?.evaluation) {
14904
- this.evaluations.set(payload.evaluation.id, payload.evaluation);
14905
- this.emit("critic.evaluation", payload);
14871
+ for (const h of this.any) {
14872
+ try {
14873
+ h(event);
14874
+ } catch {
14906
14875
  }
14907
- });
14908
- this.disposers.push(d3);
14876
+ }
14877
+ }
14878
+ };
14879
+ var FleetUsageAggregator = class {
14880
+ constructor(bus, priceLookup, metaLookup) {
14881
+ this.priceLookup = priceLookup;
14882
+ this.metaLookup = metaLookup;
14883
+ this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
14884
+ this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
14885
+ this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
14886
+ }
14887
+ priceLookup;
14888
+ metaLookup;
14889
+ perSubagent = /* @__PURE__ */ new Map();
14890
+ total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
14891
+ unsub = new Array();
14892
+ /**
14893
+ * Remove a terminated subagent's data from the aggregator and subtract its
14894
+ * contribution from the running totals. Call this when a subagent is removed
14895
+ * from the fleet so the aggregator doesn't accumulate unbounded data for
14896
+ * entities that will never emit events again.
14897
+ */
14898
+ removeSubagent(subagentId) {
14899
+ const snap = this.perSubagent.get(subagentId);
14900
+ if (!snap) return;
14901
+ this.perSubagent.delete(subagentId);
14902
+ this.total.input -= snap.input;
14903
+ this.total.output -= snap.output;
14904
+ this.total.cacheRead -= snap.cacheRead;
14905
+ this.total.cacheWrite -= snap.cacheWrite;
14906
+ this.total.cost -= snap.cost;
14907
+ }
14908
+ /** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
14909
+ dispose() {
14910
+ for (const off of this.unsub) off();
14911
+ this.unsub.length = 0;
14912
+ }
14913
+ /** Live snapshot — safe to call from a tool's execute() body. */
14914
+ snapshot() {
14915
+ return {
14916
+ total: { ...this.total },
14917
+ perSubagent: Object.fromEntries(
14918
+ Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
14919
+ )
14920
+ };
14921
+ }
14922
+ ensure(subagentId) {
14923
+ let snap = this.perSubagent.get(subagentId);
14924
+ if (!snap) {
14925
+ const meta = this.metaLookup?.(subagentId);
14926
+ snap = {
14927
+ subagentId,
14928
+ provider: meta?.provider,
14929
+ model: meta?.model,
14930
+ input: 0,
14931
+ output: 0,
14932
+ cacheRead: 0,
14933
+ cacheWrite: 0,
14934
+ cost: 0,
14935
+ toolCalls: 0,
14936
+ iterations: 0,
14937
+ startedAt: Date.now(),
14938
+ lastEventAt: Date.now()
14939
+ };
14940
+ this.perSubagent.set(subagentId, snap);
14941
+ }
14942
+ return snap;
14943
+ }
14944
+ onProviderResponse(e) {
14945
+ const snap = this.ensure(e.subagentId);
14946
+ const p = e.payload;
14947
+ const usage = p?.usage;
14948
+ if (!usage) return;
14949
+ snap.input += usage.input ?? 0;
14950
+ snap.output += usage.output ?? 0;
14951
+ snap.cacheRead += usage.cacheRead ?? 0;
14952
+ snap.cacheWrite += usage.cacheWrite ?? 0;
14953
+ this.total.input += usage.input ?? 0;
14954
+ this.total.output += usage.output ?? 0;
14955
+ this.total.cacheRead += usage.cacheRead ?? 0;
14956
+ this.total.cacheWrite += usage.cacheWrite ?? 0;
14957
+ const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
14958
+ if (price) {
14959
+ const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
14960
+ snap.cost += delta;
14961
+ this.total.cost += delta;
14962
+ }
14963
+ snap.lastEventAt = e.ts;
14964
+ }
14965
+ onToolExecuted(e) {
14966
+ const snap = this.ensure(e.subagentId);
14967
+ snap.toolCalls += 1;
14968
+ snap.lastEventAt = e.ts;
14909
14969
  }
14910
- roleFromSubagentId(subagentId) {
14911
- for (const [role, id] of this.subagentIds) {
14912
- if (id === subagentId) return role;
14913
- }
14914
- const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
14915
- return match?.[1] ?? null;
14970
+ onIterationStarted(e) {
14971
+ const snap = this.ensure(e.subagentId);
14972
+ snap.iterations += 1;
14973
+ snap.lastEventAt = e.ts;
14916
14974
  }
14917
- assembleReport() {
14918
- const bugList = Array.from(this.bugs.values());
14919
- const planList = Array.from(this.plans.values());
14920
- const evalList = Array.from(this.evaluations.values());
14921
- let disposition = "completed";
14922
- if (this.cancelled) disposition = "cancelled";
14923
- const verdictOrder = {
14924
- approve: 0,
14925
- needs_revision: 1,
14926
- reject: 2
14927
- };
14928
- const overallVerdict = evalList.reduce(
14929
- (worst, eval_) => {
14930
- const w = verdictOrder[worst];
14931
- const c = verdictOrder[eval_.verdict];
14932
- return c > w ? eval_.verdict : worst;
14933
- },
14934
- "approve"
14935
- );
14936
- const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
14937
- return {
14938
- sessionId: this.sessionId,
14939
- startedAt: this.snapshot.createdAt,
14940
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
14941
- targetPaths: this.options.targetPaths,
14942
- disposition,
14943
- bugs: bugList,
14944
- refactorPlans: planList,
14945
- evaluations: evalList,
14946
- alerts: [...this.alerts],
14947
- overallVerdict,
14948
- summary
14949
- };
14975
+ };
14976
+
14977
+ // src/coordination/large-answer-store.ts
14978
+ var LargeAnswerStore = class {
14979
+ /**
14980
+ * Responses above this size (in characters) are stored out-of-context.
14981
+ * Below this, the full answer is returned inline (no overhead).
14982
+ * Default: 2000 chars ≈ 400-600 tokens.
14983
+ */
14984
+ sizeThreshold;
14985
+ store = /* @__PURE__ */ new Map();
14986
+ constructor(sizeThreshold = 2e3) {
14987
+ this.sizeThreshold = sizeThreshold;
14950
14988
  }
14951
- buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
14952
- const lines = [
14953
- `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
14954
- "",
14955
- `**Target:** ${this.options.targetPaths.join(", ")}`,
14956
- `**Disposition:** ${disposition.toUpperCase()}`,
14957
- `**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
14958
- ""
14959
- ];
14960
- if (this.alerts.length > 0) {
14961
- lines.push("### Alerts", "");
14962
- for (const alert of this.alerts) {
14963
- lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
14964
- }
14965
- lines.push("");
14966
- }
14967
- if (bugs.length > 0) {
14968
- lines.push("### Bugs Found", "");
14969
- for (const b of bugs) {
14970
- lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
14971
- }
14972
- lines.push("");
14973
- }
14974
- if (plans.length > 0) {
14975
- lines.push("### Refactor Plans", "");
14976
- for (const p of plans) {
14977
- lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
14978
- for (const phase of p.phases) {
14979
- lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
14980
- }
14981
- }
14982
- lines.push("");
14989
+ /**
14990
+ * Store a value, returning a summary + key for inline use.
14991
+ * If the value is below sizeThreshold, returns it as-is (no store entry).
14992
+ */
14993
+ storeAnswer(value) {
14994
+ if (value === void 0 || value === null) {
14995
+ return { summary: String(value), inline: true };
14983
14996
  }
14984
- if (evals.length > 0) {
14985
- lines.push("### Critic Evaluations", "");
14986
- for (const e of evals) {
14987
- lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
14988
- for (const c of e.concerns) {
14989
- if (c.severity === "blocking") lines.push(` - ${c.description}`);
14990
- }
14991
- }
14992
- lines.push("");
14997
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
14998
+ const size = serialized.length;
14999
+ if (size <= this.sizeThreshold) {
15000
+ return { summary: serialized.slice(0, 500), inline: true };
14993
15001
  }
14994
- return lines.join("\n");
15002
+ const key = `a-${hashStr(serialized)}`;
15003
+ this.store.set(key, {
15004
+ key,
15005
+ value,
15006
+ size,
15007
+ storedAt: Date.now()
15008
+ });
15009
+ return {
15010
+ key,
15011
+ summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
15012
+ inline: false
15013
+ };
14995
15014
  }
14996
- cleanup() {
14997
- for (const dispose of this.disposers) dispose();
14998
- this.disposers.length = 0;
15015
+ /**
15016
+ * Retrieve a previously stored answer by its key.
15017
+ * Returns undefined if the key is unknown or the store was cleared.
15018
+ */
15019
+ retrieveAnswer(key) {
15020
+ return this.store.get(key)?.value;
15021
+ }
15022
+ /**
15023
+ * Check if a key exists in the store.
15024
+ */
15025
+ hasAnswer(key) {
15026
+ return this.store.has(key);
15027
+ }
15028
+ /** Number of stored entries. */
15029
+ get size() {
15030
+ return this.store.size;
15031
+ }
15032
+ /** Total characters stored. */
15033
+ get totalChars() {
15034
+ let total = 0;
15035
+ for (const e of this.store.values()) total += e.size;
15036
+ return total;
15037
+ }
15038
+ /** Clear all stored entries. Call at the end of a director run. */
15039
+ clear() {
15040
+ this.store.clear();
14999
15041
  }
15000
15042
  };
15043
+ function hashStr(s) {
15044
+ let h = 5381;
15045
+ for (let i = 0; i < s.length; i++) {
15046
+ h = h * 33 ^ s.charCodeAt(i);
15047
+ }
15048
+ return (h >>> 0).toString(36);
15049
+ }
15050
+
15051
+ // src/coordination/model-matrix.ts
15052
+ var MATRIX_PHASE_KEYS = Object.keys(AGENTS_BY_PHASE);
15053
+ var ROLE_TO_PHASE = (() => {
15054
+ const map = {};
15055
+ for (const [phase, defs] of Object.entries(AGENTS_BY_PHASE)) {
15056
+ for (const def of defs) {
15057
+ const role = def.config.role;
15058
+ if (role) map[role] = phase;
15059
+ }
15060
+ }
15061
+ return map;
15062
+ })();
15063
+ function phaseForRole(role) {
15064
+ return role ? ROLE_TO_PHASE[role] : void 0;
15065
+ }
15066
+ function resolveModelMatrix(matrix, role) {
15067
+ if (!matrix) return void 0;
15068
+ if (role && matrix[role]) return matrix[role];
15069
+ const phase = phaseForRole(role);
15070
+ if (phase && matrix[phase]) return matrix[phase];
15071
+ if (matrix["*"]) return matrix["*"];
15072
+ return void 0;
15073
+ }
15074
+ function matrixKeyKind(key) {
15075
+ if (key === "*") return "default";
15076
+ if (key in AGENT_CATALOG) return "role";
15077
+ if (MATRIX_PHASE_KEYS.includes(key)) return "phase";
15078
+ return "unknown";
15079
+ }
15080
+ function isValidMatrixKey(key) {
15081
+ return matrixKeyKind(key) !== "unknown";
15082
+ }
15001
15083
 
15002
15084
  // src/coordination/director.ts
15003
15085
  var FleetSpawnBudgetError = class extends Error {
@@ -15169,6 +15251,9 @@ var Director = class _Director {
15169
15251
  maxLeaderContextLoad;
15170
15252
  /** Provider's max context window in tokens. */
15171
15253
  maxContext;
15254
+ /** Per-task model matrix (static record or live getter); resolved
15255
+ * per-spawn when no explicit model is set. */
15256
+ modelMatrix;
15172
15257
  /**
15173
15258
  * When set by `workComplete()`, the director stops dispatching new tasks
15174
15259
  * and terminates all running subagents. Used when the director's LLM decides
@@ -15199,20 +15284,23 @@ var Director = class _Director {
15199
15284
  this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
15200
15285
  this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
15201
15286
  this.maxContext = opts.maxContext ?? 128e3;
15287
+ this.modelMatrix = opts.modelMatrix;
15202
15288
  this.sessionsRoot = opts.sessionsRoot;
15203
15289
  this.directorRunId = opts.directorRunId ?? this.id;
15204
- this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
15205
- directorRunId: this.id,
15206
- maxSpawns: opts.maxSpawns,
15207
- spawnDepth: this.spawnDepth,
15208
- maxSpawnDepth: this.maxSpawnDepth,
15209
- directorBudget: opts.directorBudget
15210
- }, opts.checkpointDebounceMs ?? 250) : null;
15290
+ this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
15291
+ opts.stateCheckpointPath,
15292
+ {
15293
+ directorRunId: this.id,
15294
+ maxSpawns: opts.maxSpawns,
15295
+ spawnDepth: this.spawnDepth,
15296
+ maxSpawnDepth: this.maxSpawnDepth,
15297
+ directorBudget: opts.directorBudget
15298
+ },
15299
+ opts.checkpointDebounceMs ?? 250
15300
+ ) : null;
15211
15301
  this.fleetManager = opts.fleetManager;
15212
15302
  if (this.sharedScratchpadPath) {
15213
- void fsp3.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
15214
- (err) => this.logShutdownError("shared_scratchpad_mkdir", err)
15215
- );
15303
+ void fsp3.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
15216
15304
  }
15217
15305
  this.transport = new InMemoryBridgeTransport();
15218
15306
  this.bridge = new InMemoryAgentBridge(
@@ -15390,7 +15478,12 @@ var Director = class _Director {
15390
15478
  */
15391
15479
  workComplete() {
15392
15480
  this.workCompleteFlag = true;
15393
- this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
15481
+ this.fleet.emit({
15482
+ subagentId: this.id,
15483
+ ts: Date.now(),
15484
+ type: "director.work_complete",
15485
+ payload: {}
15486
+ });
15394
15487
  }
15395
15488
  /** Returns true if `workComplete()` has been called on this director. */
15396
15489
  isWorkComplete() {
@@ -15522,13 +15615,25 @@ var Director = class _Director {
15522
15615
  "workComplete() has been called \u2014 director closed further spawning"
15523
15616
  );
15524
15617
  }
15618
+ if (!config.model && this.modelMatrix) {
15619
+ const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
15620
+ const entry = resolveModelMatrix(matrix, config.role);
15621
+ if (entry) {
15622
+ config.model = entry.model;
15623
+ if (entry.provider) config.provider = entry.provider;
15624
+ }
15625
+ }
15525
15626
  if (this.fleetManager) {
15526
15627
  const rejection = this.fleetManager.canSpawn(config);
15527
15628
  if (rejection) {
15528
- if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
15529
- if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
15530
- if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
15531
- if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
15629
+ if (rejection.kind === "max_spawn_depth")
15630
+ throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
15631
+ if (rejection.kind === "max_spawns")
15632
+ throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
15633
+ if (rejection.kind === "max_cost_usd")
15634
+ throw new FleetCostCapError(rejection.limit, rejection.observed);
15635
+ if (rejection.kind === "max_context_load")
15636
+ throw new FleetContextOverflowError(rejection.limit, rejection.observed);
15532
15637
  }
15533
15638
  } else {
15534
15639
  if (this.spawnDepth >= this.maxSpawnDepth) {
@@ -15558,7 +15663,9 @@ var Director = class _Director {
15558
15663
  this.fleetManager.assignNicknameAndRecord(config);
15559
15664
  } else {
15560
15665
  config.name = assignNickname(role, this._usedNicknames);
15561
- this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
15666
+ this._usedNicknames.add(
15667
+ config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
15668
+ );
15562
15669
  }
15563
15670
  }
15564
15671
  result = await this.coordinator.spawn(config);
@@ -15793,7 +15900,11 @@ var Director = class _Director {
15793
15900
  subagentId: taskWithId.subagentId ?? "unassigned",
15794
15901
  taskId: taskWithId.id,
15795
15902
  status: "stopped",
15796
- error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
15903
+ error: {
15904
+ kind: "aborted_by_parent",
15905
+ message: "Director called workComplete() \u2014 no further tasks will run",
15906
+ retryable: false
15907
+ },
15797
15908
  iterations: 0,
15798
15909
  toolCalls: 0,
15799
15910
  durationMs: 0
@@ -22983,22 +23094,24 @@ var ReportGenerator = class {
22983
23094
  medium: "#ca8a04",
22984
23095
  low: "#16a34a"
22985
23096
  };
22986
- const rows = result.findings.map(
22987
- (f) => `
23097
+ const rows = result.findings.map((f) => {
23098
+ const sevColor = severityColors[f.severity] ?? "#000000";
23099
+ const loc = `${escapeHtml(f.file)}${f.line ? `:${escapeHtml(String(f.line))}` : ""}`;
23100
+ return `
22988
23101
  <tr>
22989
- <td style="color: ${severityColors[f.severity]}; font-weight: bold;">${f.severity.toUpperCase()}</td>
22990
- <td>${f.category}</td>
22991
- <td>${f.title}</td>
22992
- <td><code>${f.file}${f.line ? `:${f.line}` : ""}</code></td>
22993
- <td>${f.remediation}</td>
23102
+ <td style="color: ${escapeHtml(sevColor)}; font-weight: bold;">${escapeHtml(f.severity.toUpperCase())}</td>
23103
+ <td>${escapeHtml(f.category)}</td>
23104
+ <td>${escapeHtml(f.title)}</td>
23105
+ <td><code>${loc}</code></td>
23106
+ <td>${escapeHtml(f.remediation)}</td>
22994
23107
  </tr>
22995
- `
22996
- ).join("");
23108
+ `;
23109
+ }).join("");
22997
23110
  return `
22998
23111
  <!DOCTYPE html>
22999
23112
  <html>
23000
23113
  <head>
23001
- <title>Security Scan Report - ${new Date(result.timestamp).toLocaleDateString()}</title>
23114
+ <title>Security Scan Report - ${escapeHtml(new Date(result.timestamp).toLocaleDateString())}</title>
23002
23115
  <style>
23003
23116
  body { font-family: system-ui, sans-serif; margin: 2rem; }
23004
23117
  table { border-collapse: collapse; width: 100%; }
@@ -23009,9 +23122,9 @@ var ReportGenerator = class {
23009
23122
  </head>
23010
23123
  <body>
23011
23124
  <h1>Security Scan Report</h1>
23012
- <p><strong>Generated:</strong> ${new Date(result.timestamp).toLocaleString()}</p>
23013
- <p><strong>Project:</strong> ${result.projectRoot}</p>
23014
- <p><strong>Tech Stack:</strong> ${result.techStack.stack} (${result.techStack.packageManager})</p>
23125
+ <p><strong>Generated:</strong> ${escapeHtml(new Date(result.timestamp).toLocaleString())}</p>
23126
+ <p><strong>Project:</strong> ${escapeHtml(result.projectRoot)}</p>
23127
+ <p><strong>Tech Stack:</strong> ${escapeHtml(result.techStack.stack)} (${escapeHtml(result.techStack.packageManager)})</p>
23015
23128
 
23016
23129
  <h2>Summary</h2>
23017
23130
  <ul>
@@ -23037,6 +23150,9 @@ var ReportGenerator = class {
23037
23150
  `.trim();
23038
23151
  }
23039
23152
  };
23153
+ function escapeHtml(value) {
23154
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
23155
+ }
23040
23156
  var defaultReportGenerator = new ReportGenerator();
23041
23157
 
23042
23158
  // src/security-scanner/gitignore-updater.ts
@@ -27048,6 +27164,7 @@ var PhaseOrchestrator = class {
27048
27164
  maxConcurrentPhases: opts.maxConcurrentPhases ?? 1,
27049
27165
  maxConcurrentTasks: opts.maxConcurrentTasks ?? 2,
27050
27166
  maxRetries: opts.maxRetries ?? 2,
27167
+ maxVerifyAttempts: opts.maxVerifyAttempts ?? 2,
27051
27168
  autonomous: opts.autonomous ?? true,
27052
27169
  phaseDelayMs: opts.phaseDelayMs ?? 0,
27053
27170
  stopOnFailure: opts.stopOnFailure ?? true,
@@ -27094,7 +27211,10 @@ var PhaseOrchestrator = class {
27094
27211
  resume() {
27095
27212
  this.paused = false;
27096
27213
  this.tick().catch((err) => {
27097
- console.error("[phase-orchestrator] tick failed:", err instanceof Error ? err.message : String(err));
27214
+ console.error(
27215
+ "[phase-orchestrator] tick failed:",
27216
+ err instanceof Error ? err.message : String(err)
27217
+ );
27098
27218
  });
27099
27219
  }
27100
27220
  /** Tamamen durdur — aktif fazlar da durdurulur */
@@ -27175,33 +27295,30 @@ var PhaseOrchestrator = class {
27175
27295
  failed: failedTasks
27176
27296
  });
27177
27297
  if (failedTasks > 0 && this.opts.stopOnFailure) {
27178
- this.updatePhaseStatus(phase, "failed");
27179
- phase.completedAt = Date.now();
27180
- phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
27181
- this.runningPhases.delete(phase.id);
27182
- this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
27183
- this.emit("phase.failed", {
27184
- phaseId: phase.id,
27185
- name: phase.name,
27186
- error: `${failedTasks} task(s) failed`
27187
- });
27188
- this.ctx.onPhaseFail?.(phase, new Error(`${failedTasks} task(s) failed`));
27189
- await this.keepWorktreeForReview(phase);
27190
- } else {
27191
- this.updatePhaseStatus(phase, "completed");
27192
- phase.completedAt = Date.now();
27193
- phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
27194
- this.runningPhases.delete(phase.id);
27195
- this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
27196
- this.graph.completedPhaseIds.push(phase.id);
27197
- this.emit("phase.completed", {
27198
- phaseId: phase.id,
27199
- name: phase.name,
27200
- durationMs: phase.actualDurationMs
27201
- });
27202
- this.ctx.onPhaseComplete?.(phase);
27203
- await this.commitAndEnqueueMerge(phase);
27298
+ await this.failPhaseAfterTasks(phase, `${failedTasks} task(s) failed`);
27299
+ return;
27300
+ }
27301
+ const verdict = await this.runVerifyGate(phase);
27302
+ if (!verdict.ok) {
27303
+ await this.failPhaseAfterTasks(
27304
+ phase,
27305
+ `verification failed${verdict.output ? `: ${this.truncate(verdict.output)}` : ""}`
27306
+ );
27307
+ return;
27204
27308
  }
27309
+ this.updatePhaseStatus(phase, "completed");
27310
+ phase.completedAt = Date.now();
27311
+ phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
27312
+ this.runningPhases.delete(phase.id);
27313
+ this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
27314
+ this.graph.completedPhaseIds.push(phase.id);
27315
+ this.emit("phase.completed", {
27316
+ phaseId: phase.id,
27317
+ name: phase.name,
27318
+ durationMs: phase.actualDurationMs
27319
+ });
27320
+ this.ctx.onPhaseComplete?.(phase);
27321
+ await this.commitAndEnqueueMerge(phase);
27205
27322
  } catch (error) {
27206
27323
  this.updatePhaseStatus(phase, "failed");
27207
27324
  phase.completedAt = Date.now();
@@ -27218,6 +27335,69 @@ var PhaseOrchestrator = class {
27218
27335
  await this.keepWorktreeForReview(phase);
27219
27336
  }
27220
27337
  }
27338
+ // ─── Verification gate ──────────────────────────────────────────────────────
27339
+ /**
27340
+ * Run the verification gate for a phase whose tasks all succeeded. Verifies in
27341
+ * the phase's worktree; on failure, runs the repair pass and re-verifies, up to
27342
+ * `maxVerifyAttempts` repairs. Returns the final verdict. When no `verifyPhase`
27343
+ * callback is wired the gate is a no-op and always passes.
27344
+ */
27345
+ async runVerifyGate(phase) {
27346
+ if (!this.ctx.verifyPhase) return { ok: true };
27347
+ const env = this.worktreeEnv(phase);
27348
+ for (let attempt = 0; attempt <= this.opts.maxVerifyAttempts; attempt++) {
27349
+ if (this.stopped) return { ok: false, output: "stopped before verification completed" };
27350
+ this.emit("phase.verifying", { phaseId: phase.id, name: phase.name, attempt });
27351
+ let verdict;
27352
+ try {
27353
+ verdict = await this.ctx.verifyPhase(phase, env);
27354
+ } catch (err) {
27355
+ verdict = { ok: false, output: err instanceof Error ? err.message : String(err) };
27356
+ }
27357
+ if (verdict.ok) return { ok: true };
27358
+ this.emit("phase.verifyFailed", {
27359
+ phaseId: phase.id,
27360
+ name: phase.name,
27361
+ attempt,
27362
+ error: verdict.output
27363
+ });
27364
+ if (attempt >= this.opts.maxVerifyAttempts || !this.ctx.repairPhase || this.stopped) {
27365
+ return { ok: false, output: verdict.output };
27366
+ }
27367
+ this.emit("phase.repairing", { phaseId: phase.id, name: phase.name, attempt: attempt + 1 });
27368
+ try {
27369
+ await this.ctx.repairPhase(
27370
+ phase,
27371
+ verdict.output ?? "verification failed",
27372
+ attempt + 1,
27373
+ env
27374
+ );
27375
+ } catch {
27376
+ }
27377
+ }
27378
+ return { ok: false };
27379
+ }
27380
+ /** Worktree env (cwd/branch) for a phase, or undefined if it runs on the shared tree. */
27381
+ worktreeEnv(phase) {
27382
+ const handle = this.phaseWorktrees.get(phase.id);
27383
+ return handle ? { cwd: handle.dir, branch: handle.branch } : void 0;
27384
+ }
27385
+ /** Shared failure bookkeeping for a phase whose tasks ran but the phase failed. */
27386
+ async failPhaseAfterTasks(phase, error) {
27387
+ this.updatePhaseStatus(phase, "failed");
27388
+ phase.completedAt = Date.now();
27389
+ phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
27390
+ this.runningPhases.delete(phase.id);
27391
+ this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
27392
+ this.emit("phase.failed", { phaseId: phase.id, name: phase.name, error });
27393
+ this.ctx.onPhaseFail?.(phase, new Error(error));
27394
+ await this.keepWorktreeForReview(phase);
27395
+ }
27396
+ /** Trim long verifier output so it fits cleanly in an event/error message. */
27397
+ truncate(text, max = 500) {
27398
+ const t2 = text.trim();
27399
+ return t2.length <= max ? t2 : `${t2.slice(0, max)}\u2026 (+${t2.length - max} chars)`;
27400
+ }
27221
27401
  // ─── Worktree integration ───────────────────────────────────────────────────
27222
27402
  /**
27223
27403
  * Commit the phase's worktree changes, then enqueue the merge back into the
@@ -27240,11 +27420,26 @@ var PhaseOrchestrator = class {
27240
27420
  })();
27241
27421
  this.phaseMergePromise.set(phase.id, merged);
27242
27422
  }
27243
- /** Squash-merge one phase. Conflicts mark the worktree needs-review (run continues). */
27423
+ /**
27424
+ * Squash-merge one phase. When a `resolveConflict` callback is wired, a merge
27425
+ * conflict is handed to it (a resolver subagent) before giving up; only if
27426
+ * that fails does the worktree fall to needs-review and the run continues.
27427
+ */
27244
27428
  async mergeOne(phase, handle) {
27245
27429
  if (!this.worktrees) return;
27246
27430
  try {
27247
- const result = await this.worktrees.merge(handle, { squash: true });
27431
+ const resolve10 = this.ctx.resolveConflict ? async (info) => {
27432
+ this.emit("phase.conflictResolving", {
27433
+ phaseId: phase.id,
27434
+ name: phase.name,
27435
+ files: info.conflictFiles
27436
+ });
27437
+ return this.ctx.resolveConflict(phase, info);
27438
+ } : void 0;
27439
+ const result = await this.worktrees.merge(handle, { squash: true, resolve: resolve10 });
27440
+ if (result.resolved) {
27441
+ this.emit("phase.conflictResolved", { phaseId: phase.id, name: phase.name });
27442
+ }
27248
27443
  await this.worktrees.release(handle, { keep: !result.ok });
27249
27444
  } catch (err) {
27250
27445
  this.emit("phase.failed", {
@@ -27308,7 +27503,11 @@ var PhaseOrchestrator = class {
27308
27503
  const currentRetries = this.taskRetryCounts.get(taskKey) ?? 0;
27309
27504
  if (currentRetries < this.opts.maxRetries) {
27310
27505
  this.taskRetryCounts.set(taskKey, currentRetries + 1);
27311
- tracker.updateNodeStatus(task.id, "pending", `Retry ${currentRetries + 1}/${this.opts.maxRetries}`);
27506
+ tracker.updateNodeStatus(
27507
+ task.id,
27508
+ "pending",
27509
+ `Retry ${currentRetries + 1}/${this.opts.maxRetries}`
27510
+ );
27312
27511
  this.emit("phase.taskRetrying", {
27313
27512
  phaseId: phase.id,
27314
27513
  taskId: task.id,
@@ -27317,7 +27516,11 @@ var PhaseOrchestrator = class {
27317
27516
  maxRetries: this.opts.maxRetries
27318
27517
  });
27319
27518
  } else {
27320
- tracker.updateNodeStatus(task.id, "failed", error instanceof Error ? error.message : String(error));
27519
+ tracker.updateNodeStatus(
27520
+ task.id,
27521
+ "failed",
27522
+ error instanceof Error ? error.message : String(error)
27523
+ );
27321
27524
  this.emit("phase.taskFailed", {
27322
27525
  phaseId: phase.id,
27323
27526
  taskId: task.id,
@@ -28268,6 +28471,10 @@ var WorktreeManager = class {
28268
28471
  ${merged.stderr}`);
28269
28472
  const fromIndex = await this.unmergedFiles();
28270
28473
  const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
28474
+ if (opts.resolve) {
28475
+ const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
28476
+ if (finalized) return finalized;
28477
+ }
28271
28478
  await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
28272
28479
  handle.conflictFiles = conflictFiles;
28273
28480
  this.setStatus(handle, "needs-review", { lastError: merged.stderr });
@@ -28298,6 +28505,52 @@ ${merged.stderr}`);
28298
28505
  });
28299
28506
  return { ok: true };
28300
28507
  }
28508
+ /**
28509
+ * Run the caller-supplied resolver against a conflicted squash-merge, then
28510
+ * commit if it cleared every marker. Returns a successful `MergeResult` on a
28511
+ * clean resolution, or `null` to signal the caller should fall back to the
28512
+ * abort path. Never leaves the base tree committed-but-dirty: a partial or
28513
+ * failed resolution returns `null` and the caller hard-resets.
28514
+ */
28515
+ async tryResolveConflict(handle, conflictFiles, opts) {
28516
+ let resolved = false;
28517
+ try {
28518
+ resolved = await opts.resolve({ conflictFiles, cwd: this.projectRoot });
28519
+ } catch {
28520
+ resolved = false;
28521
+ }
28522
+ if (!resolved) return null;
28523
+ await this.runGit(["add", "-A"], this.projectRoot);
28524
+ if (await this.hasConflictMarkers()) return null;
28525
+ const idArgs = await this.identityArgs(this.projectRoot);
28526
+ const msg = opts.message ?? `merge ${handle.branch} (squash, conflict resolved)`;
28527
+ const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
28528
+ if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
28529
+ return null;
28530
+ }
28531
+ handle.conflictFiles = conflictFiles;
28532
+ this.setStatus(handle, "merged");
28533
+ this.emit("worktree.merged", {
28534
+ handleId: handle.id,
28535
+ ownerId: handle.ownerId,
28536
+ branch: handle.branch,
28537
+ baseBranch: handle.baseBranch,
28538
+ squash: true
28539
+ });
28540
+ return { ok: true, resolved: true, conflictFiles };
28541
+ }
28542
+ /**
28543
+ * True when staged content still carries conflict markers. `git diff --cached
28544
+ * --check` exits nonzero and prints a "leftover conflict marker" line for each
28545
+ * survivor; whitespace-only errors (also flagged by --check) are ignored so a
28546
+ * clean resolution with unrelated whitespace is not rejected.
28547
+ */
28548
+ async hasConflictMarkers() {
28549
+ const check = await this.runGit(["diff", "--cached", "--check"], this.projectRoot);
28550
+ if (check.code === 0) return false;
28551
+ return /conflict marker/i.test(`${check.stdout}
28552
+ ${check.stderr}`);
28553
+ }
28301
28554
  /**
28302
28555
  * Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
28303
28556
  * are left on disk for inspection.
@@ -29700,6 +29953,6 @@ ${formatPlan(updated)}`
29700
29953
  };
29701
29954
  }
29702
29955
 
29703
- export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, CheckpointManager, CloudSync, CollaborationBus, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionRecovery, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, hashRequest, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, matchAny, matchGlob, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, recordActualUsage, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resetCalibration, resolveAuditLevel, resolveContextWindowPolicy, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, runProviderWithRetry, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
29956
+ export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, CheckpointManager, CloudSync, CollaborationBus, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionRecovery, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getPlanTemplate, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hashRequest, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isPluginError, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, matchAny, matchGlob, matrixKeyKind, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, onResize, parseContinueDirective, parseSkillRef, pendingBtwCount, phaseForRole, projectHash, recordActualUsage, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resetCalibration, resolveAuditLevel, resolveContextWindowPolicy, resolveModelMatrix, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, runProviderWithRetry, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, setRawMode, slackServer, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, writeErr, writeOut, zaiVisionServer };
29704
29957
  //# sourceMappingURL=index.js.map
29705
29958
  //# sourceMappingURL=index.js.map