@wrongstack/core 0.32.0 → 0.51.3

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 (45) hide show
  1. package/dist/{agent-bridge-D_XcS2HL.d.ts → agent-bridge-CjbD-i7-.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DpZTLdBe.d.ts → agent-subagent-runner-DfvlBx5N.d.ts} +3 -3
  3. package/dist/{config-BUEGM4JP.d.ts → config-ZRCf7sTu.d.ts} +21 -1
  4. package/dist/coordination/index.d.ts +10 -10
  5. package/dist/coordination/index.js +3310 -3056
  6. package/dist/coordination/index.js.map +1 -1
  7. package/dist/defaults/index.d.ts +13 -13
  8. package/dist/defaults/index.js +1544 -1390
  9. package/dist/defaults/index.js.map +1 -1
  10. package/dist/{events-BrQiweXN.d.ts → events-Bt44ikPN.d.ts} +135 -1
  11. package/dist/execution/index.d.ts +35 -9
  12. package/dist/execution/index.js +61 -28
  13. package/dist/execution/index.js.map +1 -1
  14. package/dist/extension/index.d.ts +3 -3
  15. package/dist/{index-pXJdVLe0.d.ts → index-OzA1XjHL.d.ts} +35 -3
  16. package/dist/{index-ysfO_DlX.d.ts → index-mAWBdLyJ.d.ts} +2 -2
  17. package/dist/index.d.ts +221 -25
  18. package/dist/index.js +1670 -1017
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +4 -4
  21. package/dist/infrastructure/index.js +17 -3
  22. package/dist/infrastructure/index.js.map +1 -1
  23. package/dist/kernel/index.d.ts +4 -4
  24. package/dist/kernel/index.js +3 -1
  25. package/dist/kernel/index.js.map +1 -1
  26. package/dist/{mcp-servers-BzB3r7_c.d.ts → mcp-servers-DONdo-XM.d.ts} +1 -1
  27. package/dist/models/index.js +5 -2
  28. package/dist/models/index.js.map +1 -1
  29. package/dist/{multi-agent-C8Z1i__e.d.ts → multi-agent-Ba9Ni2hC.d.ts} +1 -1
  30. package/dist/{multi-agent-coordinator-DOXSgtom.d.ts → multi-agent-coordinator-BuKq0q89.d.ts} +2 -2
  31. package/dist/{null-fleet-bus-DLsUjOyB.d.ts → null-fleet-bus-C0xd73YP.d.ts} +169 -138
  32. package/dist/observability/index.d.ts +1 -1
  33. package/dist/{path-resolver-DumKAi0n.d.ts → path-resolver-nkmdiFgi.d.ts} +1 -1
  34. package/dist/{plan-templates-BZMi-VpU.d.ts → plan-templates-BmDdJ7UL.d.ts} +2 -2
  35. package/dist/{provider-runner-Dlv8Fvw9.d.ts → provider-runner-BGro2qQB.d.ts} +1 -1
  36. package/dist/sdd/index.d.ts +5 -5
  37. package/dist/storage/index.d.ts +3 -3
  38. package/dist/{tool-executor-BAi4WI2d.d.ts → tool-executor-p4tP9tGF.d.ts} +1 -1
  39. package/dist/types/index.d.ts +8 -8
  40. package/dist/types/index.js +22 -5
  41. package/dist/types/index.js.map +1 -1
  42. package/dist/utils/index.d.ts +107 -1
  43. package/dist/utils/index.js +53 -2
  44. package/dist/utils/index.js.map +1 -1
  45. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { readFile, readdir, stat, mkdir } from 'fs/promises';
5
5
  import * as path6 from 'path';
6
6
  import { join, extname, relative, isAbsolute, resolve, sep } from 'path';
7
7
  import * as fs2 from 'fs';
8
- import * as os5 from 'os';
8
+ import * as os6 from 'os';
9
9
  import { execFile, spawn } from 'child_process';
10
10
  import { promisify } from 'util';
11
11
  import { EventEmitter } from 'events';
@@ -93,7 +93,7 @@ async function renameWithRetry(from, to) {
93
93
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
94
94
  throw err;
95
95
  }
96
- await new Promise((resolve10) => setTimeout(resolve10, delays[i]));
96
+ await new Promise((resolve11) => setTimeout(resolve11, delays[i]));
97
97
  }
98
98
  }
99
99
  throw lastErr;
@@ -882,7 +882,9 @@ var TOKENS = {
882
882
  /** Replaces the entire provider call layer — retry, streaming, tracing. */
883
883
  ProviderRunner: t("ProviderRunner"),
884
884
  /** Optional git-worktree lifecycle manager (per-phase isolation in AutoPhase). */
885
- WorktreeManager: t("WorktreeManager")
885
+ WorktreeManager: t("WorktreeManager"),
886
+ /** Optional global Brain arbiter for policy/decision escalation. */
887
+ BrainArbiter: t("BrainArbiter")
886
888
  };
887
889
 
888
890
  // src/kernel/run-controller.ts
@@ -1295,11 +1297,62 @@ function deepMerge(a, b) {
1295
1297
  return out;
1296
1298
  }
1297
1299
 
1300
+ // src/utils/term.ts
1301
+ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
1302
+ var hasStdin = () => typeof process !== "undefined" && !!process.stdin;
1303
+ function isStdoutTTY() {
1304
+ return hasStdout() && Boolean(process.stdout.isTTY);
1305
+ }
1306
+ function isStdinTTY() {
1307
+ return hasStdin() && Boolean(process.stdin.isTTY);
1308
+ }
1309
+ function isInteractive() {
1310
+ return isStdinTTY() && isStdoutTTY();
1311
+ }
1312
+ function getTermSize() {
1313
+ if (!hasStdout()) return { rows: 24, cols: 80 };
1314
+ return {
1315
+ rows: process.stdout.rows ?? 24,
1316
+ cols: process.stdout.columns ?? 80
1317
+ };
1318
+ }
1319
+ function onResize(cb, stream = process.stdout) {
1320
+ if (!stream || typeof stream.on !== "function") return () => {
1321
+ };
1322
+ const handler = () => {
1323
+ cb({
1324
+ rows: stream.rows ?? 24,
1325
+ cols: stream.columns ?? 80
1326
+ });
1327
+ };
1328
+ stream.on("resize", handler);
1329
+ return () => {
1330
+ stream.off("resize", handler);
1331
+ };
1332
+ }
1333
+ function setRawMode(input, mode) {
1334
+ if (!input || input.isTTY !== true) return false;
1335
+ if (typeof input.setRawMode !== "function") return false;
1336
+ input.setRawMode(mode);
1337
+ return true;
1338
+ }
1339
+ function writeTo(s, stream) {
1340
+ if (!stream || typeof stream.write !== "function") return false;
1341
+ stream.write(s);
1342
+ return true;
1343
+ }
1344
+ function writeOut(s, stream = process.stdout) {
1345
+ return writeTo(s, stream);
1346
+ }
1347
+ function writeErr(s, stream = process.stderr) {
1348
+ return writeTo(s, stream);
1349
+ }
1350
+
1298
1351
  // src/utils/color.ts
1299
1352
  var isColorTty = () => {
1300
1353
  if (process.env.NO_COLOR) return false;
1301
1354
  if (process.env.FORCE_COLOR) return true;
1302
- return Boolean(process.stdout?.isTTY);
1355
+ return isStdoutTTY();
1303
1356
  };
1304
1357
  var COLOR = isColorTty();
1305
1358
  var wrap = (open5, close) => (s) => COLOR ? `\x1B[${open5}m${s}\x1B[${close}m` : s;
@@ -1403,10 +1456,10 @@ var DefaultLogger = class _DefaultLogger {
1403
1456
  if (r <= LEVEL_RANK.warn || this.level === "debug" || this.level === "trace") {
1404
1457
  const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
1405
1458
  if (ctx !== void 0) {
1406
- process.stderr.write(`${head} ${formatCtx(ctx)}
1459
+ writeErr(`${head} ${formatCtx(ctx)}
1407
1460
  `);
1408
1461
  } else {
1409
- process.stderr.write(`${head}
1462
+ writeErr(`${head}
1410
1463
  `);
1411
1464
  }
1412
1465
  }
@@ -2439,12 +2492,12 @@ var DefaultModelsRegistry = class {
2439
2492
  */
2440
2493
  async loadOverlay(opts = {}) {
2441
2494
  if (this.overlayPayload && !opts.force) return this.overlayPayload;
2442
- if (this.overlay) {
2495
+ if (hasEntries(this.overlay)) {
2443
2496
  this.overlayPayload = this.overlay;
2444
2497
  return this.overlayPayload;
2445
2498
  }
2446
2499
  const fetched = await this.loadOverlayFromUrl(opts);
2447
- if (fetched) {
2500
+ if (hasEntries(fetched)) {
2448
2501
  this.overlayPayload = fetched;
2449
2502
  return fetched;
2450
2503
  }
@@ -2571,6 +2624,9 @@ var DefaultModelsRegistry = class {
2571
2624
  return path6.resolve(this.cacheFile);
2572
2625
  }
2573
2626
  };
2627
+ function hasEntries(payload) {
2628
+ return payload !== void 0 && Object.keys(payload).length > 0;
2629
+ }
2574
2630
 
2575
2631
  // src/types/mode.ts
2576
2632
  var DEFAULT_MODES = [
@@ -2978,7 +3034,7 @@ var InMemoryAgentBridge = class {
2978
3034
  );
2979
3035
  }
2980
3036
  this.inflightGuards.add(correlationId);
2981
- return new Promise((resolve10, reject) => {
3037
+ return new Promise((resolve11, reject) => {
2982
3038
  const timer = setTimeout(() => {
2983
3039
  this.inflightGuards.delete(correlationId);
2984
3040
  this.pendingRequests.delete(correlationId);
@@ -2990,7 +3046,7 @@ var InMemoryAgentBridge = class {
2990
3046
  return;
2991
3047
  }
2992
3048
  this.pendingRequests.set(correlationId, {
2993
- resolve: resolve10,
3049
+ resolve: resolve11,
2994
3050
  reject,
2995
3051
  timer
2996
3052
  });
@@ -3183,11 +3239,11 @@ function validateAgainstSchema(value, schema) {
3183
3239
  walk2(value, schema, "", errors);
3184
3240
  return { ok: errors.length === 0, errors };
3185
3241
  }
3186
- function walk2(value, schema, path35, errors) {
3242
+ function walk2(value, schema, path36, errors) {
3187
3243
  if (schema.enum !== void 0) {
3188
3244
  if (!schema.enum.some((e) => deepEqual(e, value))) {
3189
3245
  errors.push({
3190
- path: path35 || "<root>",
3246
+ path: path36 || "<root>",
3191
3247
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
3192
3248
  });
3193
3249
  return;
@@ -3196,7 +3252,7 @@ function walk2(value, schema, path35, errors) {
3196
3252
  if (typeof schema.type === "string") {
3197
3253
  if (!checkType(value, schema.type)) {
3198
3254
  errors.push({
3199
- path: path35 || "<root>",
3255
+ path: path36 || "<root>",
3200
3256
  message: `expected ${schema.type}, got ${describeType(value)}`
3201
3257
  });
3202
3258
  return;
@@ -3206,19 +3262,19 @@ function walk2(value, schema, path35, errors) {
3206
3262
  const obj = value;
3207
3263
  for (const req of schema.required ?? []) {
3208
3264
  if (!(req in obj)) {
3209
- errors.push({ path: joinPath(path35, req), message: "required property missing" });
3265
+ errors.push({ path: joinPath(path36, req), message: "required property missing" });
3210
3266
  }
3211
3267
  }
3212
3268
  if (schema.properties) {
3213
3269
  for (const [key, subSchema] of Object.entries(schema.properties)) {
3214
3270
  if (key in obj) {
3215
- walk2(obj[key], subSchema, joinPath(path35, key), errors);
3271
+ walk2(obj[key], subSchema, joinPath(path36, key), errors);
3216
3272
  }
3217
3273
  }
3218
3274
  }
3219
3275
  }
3220
3276
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
3221
- value.forEach((item, i) => walk2(item, schema.items, `${path35}[${i}]`, errors));
3277
+ value.forEach((item, i) => walk2(item, schema.items, `${path36}[${i}]`, errors));
3222
3278
  }
3223
3279
  }
3224
3280
  function checkType(value, type) {
@@ -4366,7 +4422,7 @@ function projectHash(absRoot) {
4366
4422
  return createHash("sha256").update(path6.resolve(absRoot)).digest("hex").slice(0, 12);
4367
4423
  }
4368
4424
  function resolveWstackPaths(opts) {
4369
- const home = opts.userHome ?? os5.homedir();
4425
+ const home = opts.userHome ?? os6.homedir();
4370
4426
  const globalRoot = opts.globalRoot ?? path6.join(home, ".wrongstack");
4371
4427
  const hash = projectHash(opts.projectRoot);
4372
4428
  const projectDir = path6.join(globalRoot, "projects", hash);
@@ -5487,9 +5543,9 @@ var DefaultMemoryStore = class {
5487
5543
  async readAll() {
5488
5544
  const parts = [];
5489
5545
  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}`);
5546
+ const writeErr2 = this.writeErrors.get(scope);
5547
+ if (writeErr2) {
5548
+ parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
5493
5549
  }
5494
5550
  const body = await this.read(scope);
5495
5551
  if (body.trim()) parts.push(`## ${labelOf(scope)}
@@ -6041,7 +6097,7 @@ var RecoveryLock = class {
6041
6097
  constructor(opts) {
6042
6098
  this.file = path6.join(opts.dir, LOCK_FILE);
6043
6099
  this.pid = opts.pid ?? process.pid;
6044
- this.hostname = opts.hostname ?? os5.hostname();
6100
+ this.hostname = opts.hostname ?? os6.hostname();
6045
6101
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
6046
6102
  this.sessionStore = opts.sessionStore;
6047
6103
  this.probe = opts.isPidAlive ?? defaultIsPidAlive;
@@ -7478,8 +7534,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
7478
7534
  try {
7479
7535
  await Promise.race([
7480
7536
  Promise.resolve(iter.return?.()),
7481
- new Promise((resolve10) => {
7482
- drainTimer = setTimeout(resolve10, 500);
7537
+ new Promise((resolve11) => {
7538
+ drainTimer = setTimeout(resolve11, 500);
7483
7539
  })
7484
7540
  ]);
7485
7541
  } finally {
@@ -7540,7 +7596,7 @@ async function runProviderWithRetry(opts) {
7540
7596
  description
7541
7597
  });
7542
7598
  }
7543
- await new Promise((resolve10, reject) => {
7599
+ await new Promise((resolve11, reject) => {
7544
7600
  let settled = false;
7545
7601
  const onAbort = () => {
7546
7602
  if (settled) return;
@@ -7553,7 +7609,7 @@ async function runProviderWithRetry(opts) {
7553
7609
  settled = true;
7554
7610
  clearTimeout(t2);
7555
7611
  signal.removeEventListener("abort", onAbort);
7556
- resolve10();
7612
+ resolve11();
7557
7613
  }, delay);
7558
7614
  if (signal.aborted) {
7559
7615
  onAbort();
@@ -8560,7 +8616,7 @@ init_atomic_write();
8560
8616
  var MAX_JOURNAL_ENTRIES = 500;
8561
8617
  function goalFilePath(projectRoot) {
8562
8618
  const hash = createHash("sha256").update(path6.resolve(projectRoot)).digest("hex").slice(0, 12);
8563
- return path6.join(os5.homedir(), ".wrongstack", "projects", hash, "goal.json");
8619
+ return path6.join(os6.homedir(), ".wrongstack", "projects", hash, "goal.json");
8564
8620
  }
8565
8621
  async function loadGoal(filePath) {
8566
8622
  let raw;
@@ -9232,7 +9288,7 @@ ${recentJournal}` : "No prior iterations.",
9232
9288
  }
9233
9289
  };
9234
9290
  function sleep(ms) {
9235
- return new Promise((resolve10) => setTimeout(resolve10, ms));
9291
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
9236
9292
  }
9237
9293
 
9238
9294
  // src/coordination/subagent-budget.ts
@@ -9448,12 +9504,12 @@ var SubagentBudget = class _SubagentBudget {
9448
9504
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
9449
9505
  return Promise.resolve("stop");
9450
9506
  }
9451
- return new Promise((resolve10) => {
9507
+ return new Promise((resolve11) => {
9452
9508
  let resolved = false;
9453
9509
  const respond = (d) => {
9454
9510
  if (resolved) return;
9455
9511
  resolved = true;
9456
- resolve10(d);
9512
+ resolve11(d);
9457
9513
  };
9458
9514
  const fallback = setTimeout(
9459
9515
  () => respond("stop"),
@@ -12680,7 +12736,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
12680
12736
  taskIds.map((id) => {
12681
12737
  const cached = this.completedResults.find((r) => r.taskId === id);
12682
12738
  if (cached) return cached;
12683
- return new Promise((resolve10, reject) => {
12739
+ return new Promise((resolve11, reject) => {
12684
12740
  const timeout = setTimeout(() => {
12685
12741
  this.off("task.completed", handler);
12686
12742
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -12689,7 +12745,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
12689
12745
  if (result.taskId === id) {
12690
12746
  clearTimeout(timeout);
12691
12747
  this.off("task.completed", handler);
12692
- resolve10(result);
12748
+ resolve11(result);
12693
12749
  }
12694
12750
  };
12695
12751
  this.on("task.completed", handler);
@@ -13192,7 +13248,7 @@ function providerErrorToSubagentError(err, message, cause) {
13192
13248
 
13193
13249
  // src/execution/parallel-eternal-engine.ts
13194
13250
  function sleep2(ms) {
13195
- return new Promise((resolve10) => setTimeout(resolve10, ms));
13251
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
13196
13252
  }
13197
13253
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
13198
13254
  var ParallelEternalEngine = class {
@@ -13258,8 +13314,14 @@ var ParallelEternalEngine = class {
13258
13314
  await this.runOneIteration();
13259
13315
  } catch (err) {
13260
13316
  this.consecutiveFailures++;
13261
- this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
13262
- await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
13317
+ this.opts.onError?.(
13318
+ err instanceof Error ? err : new Error(String(err)),
13319
+ this.consecutiveFailures
13320
+ );
13321
+ await this.appendFailure(
13322
+ "engine error",
13323
+ err instanceof Error ? err.message : String(err)
13324
+ );
13263
13325
  }
13264
13326
  if (this.stopRequested) break;
13265
13327
  await sleep2(2e3);
@@ -13275,14 +13337,19 @@ var ParallelEternalEngine = class {
13275
13337
  * Called by the REPL in its main loop (REPL drives, engine is stateless per tick).
13276
13338
  */
13277
13339
  async runOneIteration() {
13340
+ const emit = (stage) => {
13341
+ this.opts.onStage?.(stage);
13342
+ };
13278
13343
  this.iterations++;
13279
13344
  const goal = await loadGoal(this.goalPath);
13280
13345
  if (!goal) {
13281
13346
  this.stopRequested = true;
13347
+ emit({ phase: "stopped" });
13282
13348
  return false;
13283
13349
  }
13284
13350
  if (goal.goalState !== "active") {
13285
13351
  this.stopRequested = true;
13352
+ emit({ phase: "stopped" });
13286
13353
  return false;
13287
13354
  }
13288
13355
  if (!this.coordinator) {
@@ -13295,10 +13362,13 @@ var ParallelEternalEngine = class {
13295
13362
  const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
13296
13363
  this.coordinator.setRunner?.(runner);
13297
13364
  }
13365
+ emit({ phase: "decompose" });
13298
13366
  const tasks = await this.decomposeGoal(goal);
13299
13367
  if (!tasks || tasks.length === 0) {
13368
+ emit({ phase: "sleep", ms: 2e3 });
13300
13369
  return false;
13301
13370
  }
13371
+ emit({ phase: "fanout", slots: Math.min(this.slots, tasks.length) });
13302
13372
  const fanOut = await this.fanOut(goal, tasks);
13303
13373
  this.iterationsSinceCompact++;
13304
13374
  const successCount = fanOut.results.filter((r) => r.status === "success").length;
@@ -13315,12 +13385,20 @@ var ParallelEternalEngine = class {
13315
13385
  status,
13316
13386
  note
13317
13387
  });
13388
+ emit({
13389
+ phase: "aggregate",
13390
+ successCount,
13391
+ total: fanOut.results.length,
13392
+ goalComplete: fanOut.goalComplete
13393
+ });
13318
13394
  if (fanOut.goalComplete) {
13319
13395
  this.stopRequested = true;
13320
13396
  this.state = "stopped";
13397
+ emit({ phase: "stopped" });
13321
13398
  return true;
13322
13399
  }
13323
13400
  await this.maybeCompact();
13401
+ emit({ phase: "sleep", ms: 2e3 });
13324
13402
  return fanOut.allSuccessful;
13325
13403
  }
13326
13404
  // -------------------------------------------------------------------------
@@ -13334,7 +13412,9 @@ var ParallelEternalEngine = class {
13334
13412
  (t2) => dispatchAgent(t2, { classifier: this.dispatchClassifier }).catch(() => null)
13335
13413
  )
13336
13414
  ) : [];
13337
- const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
13415
+ const recentJournal = goal.journal.slice(-5).map(
13416
+ (e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`
13417
+ ).join("\n");
13338
13418
  const directivePreamble = [
13339
13419
  "\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 parallel task slot \u2550\u2550\u2550",
13340
13420
  "",
@@ -13377,35 +13457,44 @@ ${personaLine}Task: ${task}
13377
13457
  role: route?.role ?? "generic",
13378
13458
  method: route?.method ?? "none"
13379
13459
  });
13380
- spawnPromises.push((async () => {
13381
- try {
13382
- await coordinator.spawn(
13383
- route ? {
13384
- id: subagentId,
13385
- name: route.definition.config.name,
13386
- role: route.role,
13387
- tools: route.definition.config.tools,
13388
- systemPromptOverride: route.definition.config.prompt,
13389
- timeoutMs: this.timeoutMs
13390
- } : {
13391
- id: subagentId,
13392
- name: `slot-${subagentId.slice(-6)}`,
13393
- // Let the coordinator apply its default budget (roster or generic).
13394
- // Hardcoding low limits here defeats the x10 budget improvement.
13395
- timeoutMs: this.timeoutMs
13396
- }
13397
- );
13398
- subagentIds.push(subagentId);
13399
- taskIds.push(taskId);
13400
- await coordinator.assign(spec);
13401
- } catch {
13402
- }
13403
- })());
13460
+ spawnPromises.push(
13461
+ (async () => {
13462
+ try {
13463
+ await coordinator.spawn(
13464
+ route ? {
13465
+ id: subagentId,
13466
+ name: route.definition.config.name,
13467
+ role: route.role,
13468
+ tools: route.definition.config.tools,
13469
+ systemPromptOverride: route.definition.config.prompt,
13470
+ timeoutMs: this.timeoutMs
13471
+ } : {
13472
+ id: subagentId,
13473
+ name: `slot-${subagentId.slice(-6)}`,
13474
+ // Let the coordinator apply its default budget (roster or generic).
13475
+ // Hardcoding low limits here defeats the x10 budget improvement.
13476
+ timeoutMs: this.timeoutMs
13477
+ }
13478
+ );
13479
+ subagentIds.push(subagentId);
13480
+ taskIds.push(taskId);
13481
+ await coordinator.assign(spec);
13482
+ } catch {
13483
+ }
13484
+ })()
13485
+ );
13404
13486
  }
13405
13487
  await Promise.all(spawnPromises);
13406
13488
  if (taskIds.length === 0) {
13407
- return { results: [], allSuccessful: false, goalComplete: false, partialOutput: "", routes: routeInfo };
13489
+ return {
13490
+ results: [],
13491
+ allSuccessful: false,
13492
+ goalComplete: false,
13493
+ partialOutput: "",
13494
+ routes: routeInfo
13495
+ };
13408
13496
  }
13497
+ this.opts.onStage?.({ phase: "await", taskIds: [...taskIds] });
13409
13498
  let results = [];
13410
13499
  try {
13411
13500
  const ctrl = new AbortController();
@@ -13686,357 +13775,631 @@ function buildGoalPreamble(goal) {
13686
13775
  "BEGIN.]"
13687
13776
  ].join("\n");
13688
13777
  }
13689
-
13690
- // src/coordination/director.ts
13691
13778
  init_atomic_write();
13692
-
13693
- // src/coordination/large-answer-store.ts
13694
- var LargeAnswerStore = class {
13779
+ var DEFAULT_MAX_TARGET_FILES = 30;
13780
+ var CollabSession = class extends EventEmitter {
13781
+ sessionId;
13782
+ options;
13783
+ snapshot;
13784
+ director;
13785
+ fleetBus;
13786
+ subagentIds = /* @__PURE__ */ new Map();
13787
+ // role → subagentId
13788
+ bugs = /* @__PURE__ */ new Map();
13789
+ plans = /* @__PURE__ */ new Map();
13790
+ evaluations = /* @__PURE__ */ new Map();
13791
+ disposers = new Array();
13792
+ settled = false;
13793
+ timeoutMs;
13794
+ cancelled = false;
13795
+ alerts = [];
13796
+ /** Tracks tool call counts per subagent for progress-based timeout decisions. */
13797
+ progressBySubagent = /* @__PURE__ */ new Map();
13798
+ /** Last tool call count when a timeout warning was handled. */
13799
+ lastTimeoutProgress = /* @__PURE__ */ new Map();
13800
+ /** Session-level timeout timer handle (cleared on cancel or natural completion). */
13801
+ _timeoutTimer;
13802
+ constructor(director, fleetBus, options) {
13803
+ super();
13804
+ this.sessionId = randomUUID();
13805
+ this.options = options;
13806
+ this.director = director;
13807
+ this.fleetBus = fleetBus;
13808
+ this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
13809
+ if (options.prebuiltSnapshot) {
13810
+ this.snapshot = options.prebuiltSnapshot;
13811
+ } else {
13812
+ this.snapshot = {
13813
+ id: this.sessionId,
13814
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13815
+ files: []
13816
+ };
13817
+ }
13818
+ }
13819
+ get id() {
13820
+ return this.sessionId;
13821
+ }
13822
+ getSessionAlerts() {
13823
+ return [...this.alerts];
13824
+ }
13825
+ isCancelled() {
13826
+ return this.cancelled;
13827
+ }
13695
13828
  /**
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.
13829
+ * Snapshot of role subagentId map. The Director calls coordinator.stop()
13830
+ * for each agent when cancelling the session, using this map to enumerate
13831
+ * all three collab agents.
13699
13832
  */
13700
- sizeThreshold;
13701
- store = /* @__PURE__ */ new Map();
13702
- constructor(sizeThreshold = 2e3) {
13703
- this.sizeThreshold = sizeThreshold;
13833
+ getSubagentIds() {
13834
+ return new Map(this.subagentIds);
13704
13835
  }
13705
13836
  /**
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).
13837
+ * Returns the effective file limit for this session.
13838
+ * Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
13708
13839
  */
13709
- storeAnswer(value) {
13710
- if (value === void 0 || value === null) {
13711
- return { summary: String(value), inline: true };
13840
+ effectiveFileLimit() {
13841
+ if (this.options.maxTargetFiles !== void 0) {
13842
+ return this.options.maxTargetFiles;
13712
13843
  }
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 };
13844
+ if (this.options.contextWindow !== void 0) {
13845
+ return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
13717
13846
  }
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
- };
13847
+ return DEFAULT_MAX_TARGET_FILES;
13730
13848
  }
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;
13849
+ async buildSnapshot() {
13850
+ if (this.snapshot.files.length > 0) return this.snapshot;
13851
+ const allFiles = [];
13852
+ for (const pattern of this.options.targetPaths) {
13853
+ const expanded = await expandGlob(pattern);
13854
+ allFiles.push(...expanded);
13855
+ }
13856
+ const limit = this.effectiveFileLimit();
13857
+ if (allFiles.length > limit) {
13858
+ const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
13859
+ throw new Error(
13860
+ `[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.`
13861
+ );
13862
+ }
13863
+ for (const filePath of allFiles) {
13864
+ try {
13865
+ const content = await fsp3.readFile(filePath, "utf8");
13866
+ const ext = filePath.split(".").pop() ?? "";
13867
+ const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
13868
+ this.snapshot.files.push({ path: filePath, content, language });
13869
+ } catch {
13870
+ this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
13871
+ }
13872
+ }
13873
+ return this.snapshot;
13737
13874
  }
13738
13875
  /**
13739
- * Check if a key exists in the store.
13876
+ * Cancel the session. Emits director.cancel_collab on the FleetBus so all
13877
+ * collab agents finish early. The session-level timeout timer is also cleared.
13878
+ * Safe to call multiple times (idempotent after first call).
13740
13879
  */
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;
13880
+ cancel(reason = "Director cancelled collab session") {
13881
+ if (this.settled) return;
13882
+ this.cancelled = true;
13883
+ if (this._timeoutTimer) {
13884
+ clearTimeout(this._timeoutTimer);
13885
+ this._timeoutTimer = void 0;
13886
+ }
13887
+ this.fleetBus.emit({
13888
+ subagentId: this.director.id,
13889
+ ts: Date.now(),
13890
+ type: "director.cancel_collab",
13891
+ payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
13892
+ });
13893
+ this.fleetBus.emit({
13894
+ subagentId: this.director.id,
13895
+ ts: Date.now(),
13896
+ type: "collab.cancelled",
13897
+ payload: { sessionId: this.sessionId, reason }
13898
+ });
13753
13899
  }
13754
- /** Clear all stored entries. Call at the end of a director run. */
13755
- clear() {
13756
- this.store.clear();
13900
+ async start() {
13901
+ if (this.settled) throw new Error("session already settled");
13902
+ this.settled = true;
13903
+ await this.buildSnapshot();
13904
+ this.wireFleetBus();
13905
+ const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
13906
+ this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
13907
+ this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
13908
+ this.spawnAgent("critic", this.buildCriticTask())
13909
+ ]);
13910
+ this.subagentIds.set("bug-hunter", bugHunterId);
13911
+ this.subagentIds.set("refactor-planner", refactorPlannerId);
13912
+ this.subagentIds.set("critic", criticId);
13913
+ const timeout = new Promise((_, reject) => {
13914
+ this._timeoutTimer = setTimeout(() => {
13915
+ this.cancel("Session-level timeout reached");
13916
+ reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
13917
+ }, this.timeoutMs);
13918
+ });
13919
+ let results = null;
13920
+ try {
13921
+ results = await Promise.race([
13922
+ Promise.all([
13923
+ this.director.awaitTasks([bugHunterId]),
13924
+ this.director.awaitTasks([refactorPlannerId]),
13925
+ this.director.awaitTasks([criticId])
13926
+ ]),
13927
+ timeout
13928
+ ]);
13929
+ } catch (err) {
13930
+ if (this._timeoutTimer) {
13931
+ clearTimeout(this._timeoutTimer);
13932
+ this._timeoutTimer = void 0;
13933
+ }
13934
+ this.cleanup();
13935
+ const error = err instanceof Error ? err : new Error(String(err));
13936
+ this.emit("session.error", error);
13937
+ throw error;
13938
+ }
13939
+ for (const result of results.flat()) {
13940
+ await this.parseAndEmit(result);
13941
+ }
13942
+ const report = this.assembleReport();
13943
+ this.cleanup();
13944
+ this.emit("session.done", report);
13945
+ return report;
13757
13946
  }
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);
13947
+ async parseAndEmit(result) {
13948
+ if (result.status !== "success" || result.result == null) return;
13949
+ const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
13950
+ for (const obj of this.extractJsonObjects(text)) {
13951
+ const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
13952
+ if (!type) continue;
13953
+ this.fleetBus.emit({
13954
+ subagentId: result.subagentId,
13955
+ taskId: result.taskId,
13956
+ ts: Date.now(),
13957
+ type,
13958
+ payload: obj
13959
+ });
13960
+ }
13763
13961
  }
13764
- return (h >>> 0).toString(36);
13765
- }
13962
+ extractJsonObjects(text) {
13963
+ const objects = [];
13964
+ let depth = 0;
13965
+ let start = -1;
13966
+ let inString = false;
13967
+ let escaped = false;
13968
+ for (let i = 0; i < text.length; i++) {
13969
+ const ch = text[i];
13970
+ if (inString) {
13971
+ if (escaped) escaped = false;
13972
+ else if (ch === "\\") escaped = true;
13973
+ else if (ch === '"') inString = false;
13974
+ continue;
13975
+ }
13976
+ if (ch === '"') {
13977
+ inString = true;
13978
+ } else if (ch === "{") {
13979
+ if (depth === 0) start = i;
13980
+ depth++;
13981
+ } else if (ch === "}" && depth > 0) {
13982
+ depth--;
13983
+ if (depth === 0 && start >= 0) {
13984
+ const candidate = text.slice(start, i + 1);
13985
+ try {
13986
+ const parsed = JSON.parse(candidate);
13987
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13988
+ objects.push(parsed);
13989
+ }
13990
+ } catch {
13991
+ }
13992
+ start = -1;
13993
+ }
13994
+ }
13995
+ }
13996
+ return objects;
13997
+ }
13998
+ budgetForRole(role) {
13999
+ if (this.options.budgetOverrides?.[role]) {
14000
+ return this.options.budgetOverrides[role];
14001
+ }
14002
+ const defaults = {
14003
+ "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
14004
+ "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
14005
+ "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
14006
+ };
14007
+ return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
14008
+ }
14009
+ async spawnAgent(role, taskBrief) {
14010
+ const budget = this.budgetForRole(role);
14011
+ const cfg = {
14012
+ id: `${role}-${this.sessionId}`,
14013
+ name: role,
14014
+ role,
14015
+ tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
14016
+ maxIterations: budget.maxIterations,
14017
+ maxToolCalls: budget.maxToolCalls,
14018
+ timeoutMs: budget.timeoutMs
14019
+ };
14020
+ const subagentId = await this.director.spawn(cfg);
14021
+ await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
14022
+ return subagentId;
14023
+ }
14024
+ buildBugHunterTask() {
14025
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14026
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14027
+ ${f.content}`).join("\n\n");
14028
+ return `You are BugHunter. Scan the following files for bugs and code smells.
13766
14029
 
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.
14030
+ Target files:
14031
+ ${fileContents}
13771
14032
 
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
14033
+ For each bug found, emit it using the fleet_emit tool immediately:
14034
+ { "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
13781
14035
 
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.
14036
+ After scanning all files, write your full markdown bug report to:
14037
+ ${scratchpad}/bug-hunter-report-${this.sessionId}.md
13803
14038
 
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());
14039
+ Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
13849
14040
  }
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}`);
14041
+ buildRefactorPlannerTask() {
14042
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14043
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
14044
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14045
+ ${f.content}`).join("\n\n");
14046
+ return `You are RefactorPlanner. Plan refactorings for the following files.
14047
+
14048
+ Target files:
14049
+ ${fileContents}
14050
+
14051
+ Read the BugHunter report at: ${bugHunterReportPath}
14052
+
14053
+ For each bug you can address, emit a refactor plan using fleet_emit:
14054
+ { "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>" } } }
14055
+
14056
+ Also write your full markdown plan to:
14057
+ ${scratchpad}/refactor-plan-${this.sessionId}.md
14058
+
14059
+ Emit each plan immediately. Do not wait until planning is complete.`;
13859
14060
  }
13860
- return lines.join("\n");
13861
- }
14061
+ buildCriticTask() {
14062
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
14063
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
14064
+ const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
14065
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
14066
+ ${f.content}`).join("\n\n");
14067
+ return `You are Critic. Evaluate bug findings and refactor plans.
13862
14068
 
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
- };
14069
+ Target files:
14070
+ ${fileContents}
14071
+
14072
+ Read the BugHunter report at: ${bugHunterReportPath}
14073
+ Read the RefactorPlanner report at: ${refactorPlanPath}
14074
+
14075
+ For each bug and refactor plan, emit your evaluation using fleet_emit:
14076
+ { "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>" }] } } }
14077
+
14078
+ After all evaluations, write your markdown report to:
14079
+ ${scratchpad}/critic-report-${this.sessionId}.md
14080
+
14081
+ Emit each evaluation immediately. Do not wait until you have read all reports.`;
13886
14082
  }
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);
13893
- }
13894
- set.add(handler);
13895
- return () => {
13896
- set.delete(handler);
13897
- };
14083
+ wireFleetBus() {
14084
+ const dTool = this.fleetBus.filter("tool.executed", (e) => {
14085
+ this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
14086
+ });
14087
+ this.disposers.push(dTool);
14088
+ const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
14089
+ const payload = e.payload;
14090
+ const role = this.roleFromSubagentId(e.subagentId);
14091
+ if (!role) return;
14092
+ const btwNotes = this.director.getLeaderBtwNotes();
14093
+ const alert = {
14094
+ sessionId: this.sessionId,
14095
+ subagentId: e.subagentId,
14096
+ role,
14097
+ level: "warning" /* WARNING */,
14098
+ message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
14099
+ budgetKind: payload.kind,
14100
+ elapsedMs: payload.timeoutMs,
14101
+ limit: payload.limit,
14102
+ btwNotes
14103
+ };
14104
+ this.alerts.push(alert);
14105
+ this.fleetBus.emit({
14106
+ subagentId: e.subagentId,
14107
+ ts: Date.now(),
14108
+ type: "collab.warning",
14109
+ payload: alert
14110
+ });
14111
+ const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
14112
+ if (decision === "cancel") {
14113
+ this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
14114
+ return;
14115
+ }
14116
+ if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
14117
+ const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
14118
+ const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
14119
+ if (progress <= lastProgress) {
14120
+ payload.deny();
14121
+ return;
14122
+ }
14123
+ this.lastTimeoutProgress.set(e.subagentId, progress);
14124
+ const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
14125
+ setImmediate(() => {
14126
+ payload.extend({ timeoutMs: newLimit });
14127
+ });
14128
+ return;
14129
+ }
14130
+ if (decision === "extend") {
14131
+ setImmediate(() => {
14132
+ const base = Math.max(payload.limit, payload.used);
14133
+ const extra = {};
14134
+ switch (payload.kind) {
14135
+ case "iterations":
14136
+ extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
14137
+ break;
14138
+ case "tool_calls":
14139
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
14140
+ break;
14141
+ case "tokens":
14142
+ extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
14143
+ break;
14144
+ case "cost":
14145
+ extra.maxCostUsd = Math.min(base * 1.5, 100);
14146
+ break;
14147
+ }
14148
+ payload.extend(extra);
14149
+ });
14150
+ return;
14151
+ }
14152
+ if (payload.kind !== "timeout") {
14153
+ setImmediate(() => {
14154
+ const base = Math.max(payload.limit, payload.used);
14155
+ const extra = {};
14156
+ switch (payload.kind) {
14157
+ case "iterations":
14158
+ extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
14159
+ break;
14160
+ case "tool_calls":
14161
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
14162
+ break;
14163
+ case "tokens":
14164
+ extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
14165
+ break;
14166
+ case "cost":
14167
+ extra.maxCostUsd = Math.min(base * 1.25, 100);
14168
+ break;
14169
+ }
14170
+ payload.extend(extra);
14171
+ });
14172
+ }
14173
+ });
14174
+ this.disposers.push(dBudget);
14175
+ const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
14176
+ const payload = e.payload;
14177
+ if (payload.sessionId !== this.sessionId) return;
14178
+ this.cancelled = true;
14179
+ if (this._timeoutTimer) {
14180
+ clearTimeout(this._timeoutTimer);
14181
+ this._timeoutTimer = void 0;
14182
+ }
14183
+ this.fleetBus.emit({
14184
+ subagentId: this.director.id,
14185
+ ts: Date.now(),
14186
+ type: "collab.cancelled",
14187
+ payload: { sessionId: this.sessionId, reason: payload.reason }
14188
+ });
14189
+ });
14190
+ this.disposers.push(dCancel);
14191
+ const d1 = this.fleetBus.filter("bug.found", (e) => {
14192
+ const payload = e.payload;
14193
+ if (payload?.finding) {
14194
+ this.bugs.set(payload.finding.id, payload.finding);
14195
+ this.emit("bug.found", payload);
14196
+ }
14197
+ });
14198
+ this.disposers.push(d1);
14199
+ const d2 = this.fleetBus.filter("refactor.plan", (e) => {
14200
+ const payload = e.payload;
14201
+ if (payload?.plan) {
14202
+ this.plans.set(payload.plan.id, payload.plan);
14203
+ this.emit("refactor.plan", payload);
14204
+ }
14205
+ });
14206
+ this.disposers.push(d2);
14207
+ const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
14208
+ const payload = e.payload;
14209
+ if (payload?.evaluation) {
14210
+ this.evaluations.set(payload.evaluation.id, payload.evaluation);
14211
+ this.emit("critic.evaluation", payload);
14212
+ }
14213
+ });
14214
+ this.disposers.push(d3);
13898
14215
  }
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);
14216
+ roleFromSubagentId(subagentId) {
14217
+ for (const [role, id] of this.subagentIds) {
14218
+ if (id === subagentId) return role;
13905
14219
  }
13906
- set.add(handler);
13907
- return () => {
13908
- set.delete(handler);
13909
- };
14220
+ const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
14221
+ return match?.[1] ?? null;
13910
14222
  }
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);
14223
+ assembleReport() {
14224
+ const bugList = Array.from(this.bugs.values());
14225
+ const planList = Array.from(this.plans.values());
14226
+ const evalList = Array.from(this.evaluations.values());
14227
+ let disposition = "completed";
14228
+ if (this.cancelled) disposition = "cancelled";
14229
+ const verdictOrder = {
14230
+ approve: 0,
14231
+ needs_revision: 1,
14232
+ reject: 2
14233
+ };
14234
+ const overallVerdict = evalList.reduce(
14235
+ (worst, eval_) => {
14236
+ const w = verdictOrder[worst];
14237
+ const c = verdictOrder[eval_.verdict];
14238
+ return c > w ? eval_.verdict : worst;
14239
+ },
14240
+ "approve"
14241
+ );
14242
+ const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
14243
+ return {
14244
+ sessionId: this.sessionId,
14245
+ startedAt: this.snapshot.createdAt,
14246
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
14247
+ targetPaths: this.options.targetPaths,
14248
+ disposition,
14249
+ bugs: bugList,
14250
+ refactorPlans: planList,
14251
+ evaluations: evalList,
14252
+ alerts: [...this.alerts],
14253
+ overallVerdict,
14254
+ summary
13916
14255
  };
13917
14256
  }
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
- }
14257
+ buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
14258
+ const lines = [
14259
+ `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
14260
+ "",
14261
+ `**Target:** ${this.options.targetPaths.join(", ")}`,
14262
+ `**Disposition:** ${disposition.toUpperCase()}`,
14263
+ `**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
14264
+ ""
14265
+ ];
14266
+ if (this.alerts.length > 0) {
14267
+ lines.push("### Alerts", "");
14268
+ for (const alert of this.alerts) {
14269
+ lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
13926
14270
  }
13927
- const byType = this.byType.get(event.type);
13928
- if (byType)
13929
- for (const h of byType) {
13930
- try {
13931
- h(event);
13932
- } catch {
14271
+ lines.push("");
14272
+ }
14273
+ if (bugs.length > 0) {
14274
+ lines.push("### Bugs Found", "");
14275
+ for (const b of bugs) {
14276
+ lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
14277
+ }
14278
+ lines.push("");
14279
+ }
14280
+ if (plans.length > 0) {
14281
+ lines.push("### Refactor Plans", "");
14282
+ for (const p of plans) {
14283
+ lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
14284
+ for (const phase of p.phases) {
14285
+ lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
13933
14286
  }
13934
14287
  }
13935
- for (const h of this.any) {
13936
- try {
13937
- h(event);
13938
- } catch {
14288
+ lines.push("");
14289
+ }
14290
+ if (evals.length > 0) {
14291
+ lines.push("### Critic Evaluations", "");
14292
+ for (const e of evals) {
14293
+ lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
14294
+ for (const c of e.concerns) {
14295
+ if (c.severity === "blocking") lines.push(` - ${c.description}`);
14296
+ }
13939
14297
  }
14298
+ lines.push("");
13940
14299
  }
14300
+ return lines.join("\n");
13941
14301
  }
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)));
14302
+ cleanup() {
14303
+ for (const dispose of this.disposers) dispose();
14304
+ this.disposers.length = 0;
13950
14305
  }
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;
14306
+ };
14307
+
14308
+ // src/coordination/director-prompts.ts
14309
+ var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
14310
+ subagents by spawning them, assigning tasks, awaiting completions, and
14311
+ rolling up their outputs into your next decision.
14312
+
14313
+ Core fleet tools available to you:
14314
+ - spawn_subagent \u2014 create a worker with a chosen provider / model / role
14315
+ - assign_task \u2014 hand a piece of work to a specific subagent
14316
+ - await_tasks \u2014 block until named task ids complete (parallel-safe)
14317
+ - ask_subagent \u2014 synchronously query a running subagent via the bridge
14318
+ - roll_up \u2014 aggregate finished tasks into a markdown/json summary
14319
+ - terminate_subagent \u2014 abort a stuck worker (use sparingly)
14320
+ - fleet_status \u2014 snapshot of all subagents and pending tasks
14321
+ - fleet_usage \u2014 token + cost breakdown per subagent and total
14322
+
14323
+ Working rules:
14324
+ 1. Decompose first. Before spawning, decide which sub-tasks are
14325
+ independent and can run in parallel. Sequential work doesn't need a
14326
+ subagent \u2014 do it yourself.
14327
+ 2. Match worker to job. Cheap/fast model for triage, capable model for
14328
+ synthesis. Different providers per sibling is allowed and encouraged.
14329
+ 3. Always pair an assign with an await. Don't fire-and-forget; you owe
14330
+ the user a single coherent answer at the end.
14331
+ 4. Roll up before deciding. After await_tasks resolves, call roll_up so
14332
+ the results are folded back into your context in a compact form.
14333
+ 5. Budget is real. Check fleet_usage periodically. If a subagent is
14334
+ thrashing, terminate it rather than letting cost climb silently.
14335
+ 6. Never claim a subagent's work as your own without verifying it. If a
14336
+ result looks wrong, ask_subagent for clarification before passing it
14337
+ to the user.
14338
+ 7. Wind down when satisfied. When the results are good enough, call
14339
+ work_complete \u2014 no new subagents will spawn and queued tasks complete
14340
+ as aborted. Running subagents finish naturally. Call terminate_subagent
14341
+ only for ones you need to stop immediately.`;
14342
+ var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
14343
+ a specific slice of a larger plan \u2014 do that slice well and report back.
14344
+
14345
+ Bridge contract:
14346
+ - You have a parent (the Director). You may call \`request\` on the
14347
+ parent bridge to ask a clarifying question. Use this sparingly; the
14348
+ parent is also working.
14349
+ - You MAY NOT request the parent's system prompt, tool list, or other
14350
+ subagents' context. Those are not yours to read.
14351
+ - Your final task output is what the Director sees. Be concise,
14352
+ structured, and self-contained \u2014 assume the Director will paste your
14353
+ output into its own context.`;
14354
+ function composeDirectorPrompt(parts = {}) {
14355
+ const sections = [];
14356
+ const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
14357
+ if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
14358
+ if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
14359
+ sections.push(`Available roles you can spawn:
14360
+ ${parts.rosterSummary.trim()}`);
13971
14361
  }
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;
14362
+ if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
14363
+ sections.push(parts.basePrompt.trim());
13976
14364
  }
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
- };
14365
+ return sections.join("\n\n");
14366
+ }
14367
+ function composeSubagentPrompt(parts = {}) {
14368
+ const sections = [];
14369
+ const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
14370
+ if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
14371
+ if (parts.role && parts.role.trim().length > 0) {
14372
+ sections.push(`Role:
14373
+ ${parts.role.trim()}`);
13985
14374
  }
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()
14003
- };
14004
- this.perSubagent.set(subagentId, snap);
14005
- }
14006
- return snap;
14375
+ if (parts.task && parts.task.trim().length > 0) {
14376
+ sections.push(`Task:
14377
+ ${parts.task.trim()}`);
14007
14378
  }
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;
14379
+ if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
14380
+ sections.push(
14381
+ `Shared notes:
14382
+ A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
14383
+ - Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
14384
+ - Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
14385
+ - Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
14386
+ );
14028
14387
  }
14029
- onToolExecuted(e) {
14030
- const snap = this.ensure(e.subagentId);
14031
- snap.toolCalls += 1;
14032
- snap.lastEventAt = e.ts;
14388
+ if (parts.override && parts.override.trim().length > 0) {
14389
+ sections.push(parts.override.trim());
14033
14390
  }
14034
- onIterationStarted(e) {
14035
- const snap = this.ensure(e.subagentId);
14036
- snap.iterations += 1;
14037
- snap.lastEventAt = e.ts;
14391
+ return sections.join("\n\n");
14392
+ }
14393
+ function rosterSummaryFromConfigs(roster) {
14394
+ const lines = [];
14395
+ for (const [roleId, cfg] of Object.entries(roster)) {
14396
+ const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
14397
+ const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
14398
+ const tail = headline ? ` \u2014 ${headline}` : "";
14399
+ lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
14038
14400
  }
14039
- };
14401
+ return lines.join("\n");
14402
+ }
14040
14403
  function makeSpawnTool(director, roster) {
14041
14404
  const inputSchema = {
14042
14405
  type: "object",
@@ -14470,534 +14833,291 @@ function makeWorkCompleteTool(director) {
14470
14833
  }
14471
14834
  };
14472
14835
  }
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
- }
14836
+
14837
+ // src/coordination/fleet-bus.ts
14838
+ var FleetBus = class {
14839
+ byId = /* @__PURE__ */ new Map();
14840
+ byType = /* @__PURE__ */ new Map();
14841
+ any = /* @__PURE__ */ new Set();
14530
14842
  /**
14531
- * Returns the effective file limit for this session.
14532
- * Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
14843
+ * Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
14844
+ * `onPattern('*')`) to forward all events with subagent attribution, so
14845
+ * new kernel event types are automatically forwarded without any manual
14846
+ * registration. `subagent.*` events are excluded because they originate
14847
+ * from MultiAgentHost on the parent bus, not the subagent's own bus.
14848
+ *
14849
+ * Returns a disposer that detaches every subscription; call on
14850
+ * subagent teardown so the listeners don't outlive the run.
14533
14851
  */
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;
14876
- }
14877
- this.fleetBus.emit({
14878
- subagentId: this.director.id,
14879
- ts: Date.now(),
14880
- type: "collab.cancelled",
14881
- payload: { sessionId: this.sessionId, reason: payload.reason }
14882
- });
14883
- });
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);
14890
- }
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);
14898
- }
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);
14906
- }
14852
+ attach(subagentId, bus, taskId) {
14853
+ const off = bus.onAny((type, payload) => {
14854
+ if (type.startsWith("subagent.")) return;
14855
+ this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
14907
14856
  });
14908
- this.disposers.push(d3);
14857
+ return () => {
14858
+ off();
14859
+ };
14909
14860
  }
14910
- roleFromSubagentId(subagentId) {
14911
- for (const [role, id] of this.subagentIds) {
14912
- if (id === subagentId) return role;
14861
+ /** Subscribe to every event from one subagent. */
14862
+ subscribe(subagentId, handler) {
14863
+ let set = this.byId.get(subagentId);
14864
+ if (!set) {
14865
+ set = /* @__PURE__ */ new Set();
14866
+ this.byId.set(subagentId, set);
14913
14867
  }
14914
- const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
14915
- return match?.[1] ?? null;
14868
+ set.add(handler);
14869
+ return () => {
14870
+ set.delete(handler);
14871
+ };
14916
14872
  }
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
14873
+ /** Subscribe to one event type across all subagents. */
14874
+ filter(type, handler) {
14875
+ let set = this.byType.get(type);
14876
+ if (!set) {
14877
+ set = /* @__PURE__ */ new Set();
14878
+ this.byType.set(type, set);
14879
+ }
14880
+ set.add(handler);
14881
+ return () => {
14882
+ set.delete(handler);
14927
14883
  };
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
14884
+ }
14885
+ /** Subscribe to literally everything. The fleet roll-up uses this. */
14886
+ onAny(handler) {
14887
+ this.any.add(handler);
14888
+ return () => {
14889
+ this.any.delete(handler);
14949
14890
  };
14950
14891
  }
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}]`);
14892
+ emit(event) {
14893
+ const byId = this.byId.get(event.subagentId);
14894
+ if (byId)
14895
+ for (const h of byId) {
14896
+ try {
14897
+ h(event);
14898
+ } catch {
14980
14899
  }
14981
14900
  }
14982
- lines.push("");
14983
- }
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}`);
14901
+ const byType = this.byType.get(event.type);
14902
+ if (byType)
14903
+ for (const h of byType) {
14904
+ try {
14905
+ h(event);
14906
+ } catch {
14990
14907
  }
14991
14908
  }
14992
- lines.push("");
14909
+ for (const h of this.any) {
14910
+ try {
14911
+ h(event);
14912
+ } catch {
14913
+ }
14993
14914
  }
14994
- return lines.join("\n");
14995
14915
  }
14996
- cleanup() {
14997
- for (const dispose of this.disposers) dispose();
14998
- this.disposers.length = 0;
14916
+ };
14917
+ var FleetUsageAggregator = class {
14918
+ constructor(bus, priceLookup, metaLookup) {
14919
+ this.priceLookup = priceLookup;
14920
+ this.metaLookup = metaLookup;
14921
+ this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
14922
+ this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
14923
+ this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
14924
+ }
14925
+ priceLookup;
14926
+ metaLookup;
14927
+ perSubagent = /* @__PURE__ */ new Map();
14928
+ total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
14929
+ unsub = new Array();
14930
+ /**
14931
+ * Remove a terminated subagent's data from the aggregator and subtract its
14932
+ * contribution from the running totals. Call this when a subagent is removed
14933
+ * from the fleet so the aggregator doesn't accumulate unbounded data for
14934
+ * entities that will never emit events again.
14935
+ */
14936
+ removeSubagent(subagentId) {
14937
+ const snap = this.perSubagent.get(subagentId);
14938
+ if (!snap) return;
14939
+ this.perSubagent.delete(subagentId);
14940
+ this.total.input -= snap.input;
14941
+ this.total.output -= snap.output;
14942
+ this.total.cacheRead -= snap.cacheRead;
14943
+ this.total.cacheWrite -= snap.cacheWrite;
14944
+ this.total.cost -= snap.cost;
14945
+ }
14946
+ /** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
14947
+ dispose() {
14948
+ for (const off of this.unsub) off();
14949
+ this.unsub.length = 0;
14950
+ }
14951
+ /** Live snapshot — safe to call from a tool's execute() body. */
14952
+ snapshot() {
14953
+ return {
14954
+ total: { ...this.total },
14955
+ perSubagent: Object.fromEntries(
14956
+ Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
14957
+ )
14958
+ };
14959
+ }
14960
+ ensure(subagentId) {
14961
+ let snap = this.perSubagent.get(subagentId);
14962
+ if (!snap) {
14963
+ const meta = this.metaLookup?.(subagentId);
14964
+ snap = {
14965
+ subagentId,
14966
+ provider: meta?.provider,
14967
+ model: meta?.model,
14968
+ input: 0,
14969
+ output: 0,
14970
+ cacheRead: 0,
14971
+ cacheWrite: 0,
14972
+ cost: 0,
14973
+ toolCalls: 0,
14974
+ iterations: 0,
14975
+ startedAt: Date.now(),
14976
+ lastEventAt: Date.now()
14977
+ };
14978
+ this.perSubagent.set(subagentId, snap);
14979
+ }
14980
+ return snap;
14981
+ }
14982
+ onProviderResponse(e) {
14983
+ const snap = this.ensure(e.subagentId);
14984
+ const p = e.payload;
14985
+ const usage = p?.usage;
14986
+ if (!usage) return;
14987
+ snap.input += usage.input ?? 0;
14988
+ snap.output += usage.output ?? 0;
14989
+ snap.cacheRead += usage.cacheRead ?? 0;
14990
+ snap.cacheWrite += usage.cacheWrite ?? 0;
14991
+ this.total.input += usage.input ?? 0;
14992
+ this.total.output += usage.output ?? 0;
14993
+ this.total.cacheRead += usage.cacheRead ?? 0;
14994
+ this.total.cacheWrite += usage.cacheWrite ?? 0;
14995
+ const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
14996
+ if (price) {
14997
+ 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);
14998
+ snap.cost += delta;
14999
+ this.total.cost += delta;
15000
+ }
15001
+ snap.lastEventAt = e.ts;
15002
+ }
15003
+ onToolExecuted(e) {
15004
+ const snap = this.ensure(e.subagentId);
15005
+ snap.toolCalls += 1;
15006
+ snap.lastEventAt = e.ts;
15007
+ }
15008
+ onIterationStarted(e) {
15009
+ const snap = this.ensure(e.subagentId);
15010
+ snap.iterations += 1;
15011
+ snap.lastEventAt = e.ts;
15012
+ }
15013
+ };
15014
+
15015
+ // src/coordination/large-answer-store.ts
15016
+ var LargeAnswerStore = class {
15017
+ /**
15018
+ * Responses above this size (in characters) are stored out-of-context.
15019
+ * Below this, the full answer is returned inline (no overhead).
15020
+ * Default: 2000 chars ≈ 400-600 tokens.
15021
+ */
15022
+ sizeThreshold;
15023
+ store = /* @__PURE__ */ new Map();
15024
+ constructor(sizeThreshold = 2e3) {
15025
+ this.sizeThreshold = sizeThreshold;
15026
+ }
15027
+ /**
15028
+ * Store a value, returning a summary + key for inline use.
15029
+ * If the value is below sizeThreshold, returns it as-is (no store entry).
15030
+ */
15031
+ storeAnswer(value) {
15032
+ if (value === void 0 || value === null) {
15033
+ return { summary: String(value), inline: true };
15034
+ }
15035
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
15036
+ const size = serialized.length;
15037
+ if (size <= this.sizeThreshold) {
15038
+ return { summary: serialized.slice(0, 500), inline: true };
15039
+ }
15040
+ const key = `a-${hashStr(serialized)}`;
15041
+ this.store.set(key, {
15042
+ key,
15043
+ value,
15044
+ size,
15045
+ storedAt: Date.now()
15046
+ });
15047
+ return {
15048
+ key,
15049
+ summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
15050
+ inline: false
15051
+ };
15052
+ }
15053
+ /**
15054
+ * Retrieve a previously stored answer by its key.
15055
+ * Returns undefined if the key is unknown or the store was cleared.
15056
+ */
15057
+ retrieveAnswer(key) {
15058
+ return this.store.get(key)?.value;
15059
+ }
15060
+ /**
15061
+ * Check if a key exists in the store.
15062
+ */
15063
+ hasAnswer(key) {
15064
+ return this.store.has(key);
15065
+ }
15066
+ /** Number of stored entries. */
15067
+ get size() {
15068
+ return this.store.size;
15069
+ }
15070
+ /** Total characters stored. */
15071
+ get totalChars() {
15072
+ let total = 0;
15073
+ for (const e of this.store.values()) total += e.size;
15074
+ return total;
15075
+ }
15076
+ /** Clear all stored entries. Call at the end of a director run. */
15077
+ clear() {
15078
+ this.store.clear();
14999
15079
  }
15000
15080
  };
15081
+ function hashStr(s) {
15082
+ let h = 5381;
15083
+ for (let i = 0; i < s.length; i++) {
15084
+ h = h * 33 ^ s.charCodeAt(i);
15085
+ }
15086
+ return (h >>> 0).toString(36);
15087
+ }
15088
+
15089
+ // src/coordination/model-matrix.ts
15090
+ var MATRIX_PHASE_KEYS = Object.keys(AGENTS_BY_PHASE);
15091
+ var ROLE_TO_PHASE = (() => {
15092
+ const map = {};
15093
+ for (const [phase, defs] of Object.entries(AGENTS_BY_PHASE)) {
15094
+ for (const def of defs) {
15095
+ const role = def.config.role;
15096
+ if (role) map[role] = phase;
15097
+ }
15098
+ }
15099
+ return map;
15100
+ })();
15101
+ function phaseForRole(role) {
15102
+ return role ? ROLE_TO_PHASE[role] : void 0;
15103
+ }
15104
+ function resolveModelMatrix(matrix, role) {
15105
+ if (!matrix) return void 0;
15106
+ if (role && matrix[role]) return matrix[role];
15107
+ const phase = phaseForRole(role);
15108
+ if (phase && matrix[phase]) return matrix[phase];
15109
+ if (matrix["*"]) return matrix["*"];
15110
+ return void 0;
15111
+ }
15112
+ function matrixKeyKind(key) {
15113
+ if (key === "*") return "default";
15114
+ if (key in AGENT_CATALOG) return "role";
15115
+ if (MATRIX_PHASE_KEYS.includes(key)) return "phase";
15116
+ return "unknown";
15117
+ }
15118
+ function isValidMatrixKey(key) {
15119
+ return matrixKeyKind(key) !== "unknown";
15120
+ }
15001
15121
 
15002
15122
  // src/coordination/director.ts
15003
15123
  var FleetSpawnBudgetError = class extends Error {
@@ -15076,6 +15196,12 @@ var Director = class _Director {
15076
15196
  getLeaderContextPressure() {
15077
15197
  return this.leaderContextPressure;
15078
15198
  }
15199
+ resolveMaxContext() {
15200
+ const resolved = typeof this.maxContext === "function" ? this.maxContext() : this.maxContext;
15201
+ return resolved && resolved > 0 ? resolved : 128e3;
15202
+ }
15203
+ /** Optional Brain arbiter for director-level policy decisions. */
15204
+ brain;
15079
15205
  /**
15080
15206
  * Optional fleet-level policy container. When provided the Director
15081
15207
  * delegates spawn budgeting, manifest entries, and checkpointing to it
@@ -15167,8 +15293,11 @@ var Director = class _Director {
15167
15293
  leaderContextPressure = 0;
15168
15294
  /** Maximum context load fraction before spawn is refused. */
15169
15295
  maxLeaderContextLoad;
15170
- /** Provider's max context window in tokens. */
15296
+ /** Provider's max context window in tokens, or a live resolver for runtime model switches. */
15171
15297
  maxContext;
15298
+ /** Per-task model matrix (static record or live getter); resolved
15299
+ * per-spawn when no explicit model is set. */
15300
+ modelMatrix;
15172
15301
  /**
15173
15302
  * When set by `workComplete()`, the director stops dispatching new tasks
15174
15303
  * and terminates all running subagents. Used when the director's LLM decides
@@ -15184,6 +15313,7 @@ var Director = class _Director {
15184
15313
  largeAnswerStore;
15185
15314
  constructor(opts) {
15186
15315
  this.id = opts.config.coordinatorId || randomUUID();
15316
+ this.brain = opts.brain;
15187
15317
  this.manifestPath = opts.manifestPath;
15188
15318
  this.roster = opts.roster;
15189
15319
  this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -15199,20 +15329,23 @@ var Director = class _Director {
15199
15329
  this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
15200
15330
  this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
15201
15331
  this.maxContext = opts.maxContext ?? 128e3;
15332
+ this.modelMatrix = opts.modelMatrix;
15202
15333
  this.sessionsRoot = opts.sessionsRoot;
15203
15334
  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;
15335
+ this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
15336
+ opts.stateCheckpointPath,
15337
+ {
15338
+ directorRunId: this.id,
15339
+ maxSpawns: opts.maxSpawns,
15340
+ spawnDepth: this.spawnDepth,
15341
+ maxSpawnDepth: this.maxSpawnDepth,
15342
+ directorBudget: opts.directorBudget
15343
+ },
15344
+ opts.checkpointDebounceMs ?? 250
15345
+ ) : null;
15211
15346
  this.fleetManager = opts.fleetManager;
15212
15347
  if (this.sharedScratchpadPath) {
15213
- void fsp3.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
15214
- (err) => this.logShutdownError("shared_scratchpad_mkdir", err)
15215
- );
15348
+ void fsp3.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
15216
15349
  }
15217
15350
  this.transport = new InMemoryBridgeTransport();
15218
15351
  this.bridge = new InMemoryAgentBridge(
@@ -15325,33 +15458,81 @@ var Director = class _Director {
15325
15458
  return;
15326
15459
  }
15327
15460
  }
15328
- extendCounts.set(guardKey, prior + 1);
15329
- setImmediate(() => {
15330
- const extra = {};
15331
- const base = Math.max(payload.limit, payload.used);
15332
- const grow = (ceiling) => Math.min(Math.ceil(base * 1.5), ceiling);
15333
- let newLimit = base;
15334
- switch (payload.kind) {
15335
- case "iterations":
15336
- newLimit = grow(5e4);
15337
- extra.maxIterations = newLimit;
15338
- break;
15339
- case "tool_calls":
15340
- newLimit = grow(1e5);
15341
- extra.maxToolCalls = newLimit;
15342
- break;
15343
- case "tokens":
15344
- newLimit = grow(5e6);
15345
- extra.maxTokens = newLimit;
15346
- break;
15347
- case "cost":
15348
- newLimit = Math.min(base * 1.5, 100);
15349
- extra.maxCostUsd = newLimit;
15350
- break;
15351
- }
15352
- this.recordExtension(e.subagentId, e.taskId, payload.kind, newLimit);
15353
- payload.extend(extra);
15354
- });
15461
+ const grantExtension = () => {
15462
+ setImmediate(() => {
15463
+ const extra = {};
15464
+ const base = Math.max(payload.limit, payload.used);
15465
+ const grow = (ceiling) => Math.min(Math.ceil(base * 1.5), ceiling);
15466
+ let newLimit = base;
15467
+ switch (payload.kind) {
15468
+ case "iterations":
15469
+ newLimit = grow(5e4);
15470
+ extra.maxIterations = newLimit;
15471
+ break;
15472
+ case "tool_calls":
15473
+ newLimit = grow(1e5);
15474
+ extra.maxToolCalls = newLimit;
15475
+ break;
15476
+ case "tokens":
15477
+ newLimit = grow(5e6);
15478
+ extra.maxTokens = newLimit;
15479
+ break;
15480
+ case "cost":
15481
+ newLimit = Math.min(base * 1.5, 100);
15482
+ extra.maxCostUsd = newLimit;
15483
+ break;
15484
+ }
15485
+ extendCounts.set(guardKey, prior + 1);
15486
+ this.recordExtension(e.subagentId, e.taskId, payload.kind, newLimit);
15487
+ payload.extend(extra);
15488
+ });
15489
+ };
15490
+ if (this.brain) {
15491
+ void this.brain.decide({
15492
+ id: `director-budget-${e.subagentId}-${payload.kind}`,
15493
+ source: "director",
15494
+ question: `Should the director extend the ${payload.kind} budget for subagent ${e.subagentId}?`,
15495
+ context: [
15496
+ e.taskId ? `Task id: ${e.taskId}` : void 0,
15497
+ `Used: ${payload.used}`,
15498
+ `Limit: ${payload.limit}`,
15499
+ `Prior extensions for this kind: ${prior}`
15500
+ ].filter(Boolean).join("\n"),
15501
+ risk: payload.kind === "cost" ? "high" : "medium",
15502
+ fallback: "continue",
15503
+ options: [
15504
+ {
15505
+ id: "extend",
15506
+ label: "Grant the director default budget extension",
15507
+ consequence: "The subagent continues with a larger per-kind budget.",
15508
+ risk: payload.kind === "cost" ? "high" : "medium",
15509
+ recommended: true
15510
+ },
15511
+ {
15512
+ id: "stop",
15513
+ label: "Stop this subagent at the current budget limit",
15514
+ consequence: "The current task will fail or stop due to budget pressure.",
15515
+ risk: "low"
15516
+ }
15517
+ ]
15518
+ }).then((decision) => {
15519
+ if (decision.type === "deny") {
15520
+ payload.deny();
15521
+ return;
15522
+ }
15523
+ if (decision.type === "ask_human") {
15524
+ payload.deny();
15525
+ return;
15526
+ }
15527
+ if (decision.optionId === "stop" || /\bstop\b/i.test(decision.text)) {
15528
+ payload.deny();
15529
+ return;
15530
+ }
15531
+ grantExtension();
15532
+ }).catch(() => payload.deny());
15533
+ return;
15534
+ }
15535
+ grantExtension();
15355
15536
  });
15356
15537
  this.largeAnswerStore = new LargeAnswerStore(2e3);
15357
15538
  }
@@ -15390,7 +15571,12 @@ var Director = class _Director {
15390
15571
  */
15391
15572
  workComplete() {
15392
15573
  this.workCompleteFlag = true;
15393
- this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
15574
+ this.fleet.emit({
15575
+ subagentId: this.id,
15576
+ ts: Date.now(),
15577
+ type: "director.work_complete",
15578
+ payload: {}
15579
+ });
15394
15580
  }
15395
15581
  /** Returns true if `workComplete()` has been called on this director. */
15396
15582
  isWorkComplete() {
@@ -15522,13 +15708,25 @@ var Director = class _Director {
15522
15708
  "workComplete() has been called \u2014 director closed further spawning"
15523
15709
  );
15524
15710
  }
15711
+ if (!config.model && this.modelMatrix) {
15712
+ const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
15713
+ const entry = resolveModelMatrix(matrix, config.role);
15714
+ if (entry) {
15715
+ config.model = entry.model;
15716
+ if (entry.provider) config.provider = entry.provider;
15717
+ }
15718
+ }
15525
15719
  if (this.fleetManager) {
15526
15720
  const rejection = this.fleetManager.canSpawn(config);
15527
15721
  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);
15722
+ if (rejection.kind === "max_spawn_depth")
15723
+ throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
15724
+ if (rejection.kind === "max_spawns")
15725
+ throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
15726
+ if (rejection.kind === "max_cost_usd")
15727
+ throw new FleetCostCapError(rejection.limit, rejection.observed);
15728
+ if (rejection.kind === "max_context_load")
15729
+ throw new FleetContextOverflowError(rejection.limit, rejection.observed);
15532
15730
  }
15533
15731
  } else {
15534
15732
  if (this.spawnDepth >= this.maxSpawnDepth) {
@@ -15544,7 +15742,8 @@ var Director = class _Director {
15544
15742
  }
15545
15743
  }
15546
15744
  if (this.maxLeaderContextLoad < 1) {
15547
- const threshold = this.maxContext * this.maxLeaderContextLoad;
15745
+ const maxContext = this.resolveMaxContext();
15746
+ const threshold = maxContext * this.maxLeaderContextLoad;
15548
15747
  if (this.leaderContextPressure >= threshold) {
15549
15748
  throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
15550
15749
  }
@@ -15558,7 +15757,9 @@ var Director = class _Director {
15558
15757
  this.fleetManager.assignNicknameAndRecord(config);
15559
15758
  } else {
15560
15759
  config.name = assignNickname(role, this._usedNicknames);
15561
- this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
15760
+ this._usedNicknames.add(
15761
+ config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
15762
+ );
15562
15763
  }
15563
15764
  }
15564
15765
  result = await this.coordinator.spawn(config);
@@ -15793,7 +15994,11 @@ var Director = class _Director {
15793
15994
  subagentId: taskWithId.subagentId ?? "unassigned",
15794
15995
  taskId: taskWithId.id,
15795
15996
  status: "stopped",
15796
- error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
15997
+ error: {
15998
+ kind: "aborted_by_parent",
15999
+ message: "Director called workComplete() \u2014 no further tasks will run",
16000
+ retryable: false
16001
+ },
15797
16002
  iterations: 0,
15798
16003
  toolCalls: 0,
15799
16004
  durationMs: 0
@@ -15848,11 +16053,11 @@ var Director = class _Director {
15848
16053
  if (cached) return cached;
15849
16054
  const existing = this.taskWaiters.get(id);
15850
16055
  if (existing) return existing.promise;
15851
- let resolve10;
16056
+ let resolve11;
15852
16057
  const promise = new Promise((res) => {
15853
- resolve10 = res;
16058
+ resolve11 = res;
15854
16059
  });
15855
- this.taskWaiters.set(id, { promise, resolve: resolve10 });
16060
+ this.taskWaiters.set(id, { promise, resolve: resolve11 });
15856
16061
  return promise;
15857
16062
  })
15858
16063
  );
@@ -16237,7 +16442,7 @@ function createDelegateTool(opts) {
16237
16442
  subagentId
16238
16443
  });
16239
16444
  const dir = director;
16240
- const result = await new Promise((resolve10) => {
16445
+ const result = await new Promise((resolve11) => {
16241
16446
  let settled = false;
16242
16447
  let timer;
16243
16448
  const finish = (value) => {
@@ -16247,7 +16452,7 @@ function createDelegateTool(opts) {
16247
16452
  offTool();
16248
16453
  offIter();
16249
16454
  offProgress();
16250
- resolve10(value);
16455
+ resolve11(value);
16251
16456
  };
16252
16457
  const arm = () => {
16253
16458
  if (timer) clearTimeout(timer);
@@ -17877,9 +18082,9 @@ var AISpecBuilder = class {
17877
18082
  if (!this.sessionPath) return;
17878
18083
  try {
17879
18084
  const fsp20 = await import('fs/promises');
17880
- const path35 = await import('path');
18085
+ const path36 = await import('path');
17881
18086
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
17882
- await fsp20.mkdir(path35.dirname(this.sessionPath), { recursive: true });
18087
+ await fsp20.mkdir(path36.dirname(this.sessionPath), { recursive: true });
17883
18088
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
17884
18089
  } catch {
17885
18090
  }
@@ -18589,15 +18794,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
18589
18794
  maxId = id;
18590
18795
  }
18591
18796
  }
18592
- const path35 = [];
18797
+ const path36 = [];
18593
18798
  let current = maxId;
18594
18799
  const visited = /* @__PURE__ */ new Set();
18595
18800
  while (current && !visited.has(current)) {
18596
18801
  visited.add(current);
18597
- path35.unshift(current);
18802
+ path36.unshift(current);
18598
18803
  current = prev.get(current) ?? null;
18599
18804
  }
18600
- return path35;
18805
+ return path36;
18601
18806
  }
18602
18807
  function computeParallelGroups(graph, blockedByMap) {
18603
18808
  const groups = [];
@@ -19390,9 +19595,9 @@ var DefaultHealthRegistry = class {
19390
19595
  }
19391
19596
  async runOne(check) {
19392
19597
  let timer = null;
19393
- const timeout = new Promise((resolve10) => {
19598
+ const timeout = new Promise((resolve11) => {
19394
19599
  timer = setTimeout(
19395
- () => resolve10({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
19600
+ () => resolve11({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
19396
19601
  this.timeoutMs
19397
19602
  );
19398
19603
  });
@@ -19575,7 +19780,7 @@ async function startMetricsServer(opts) {
19575
19780
  const tls = opts.tls;
19576
19781
  const useHttps = !!(tls?.cert && tls?.key);
19577
19782
  const host = opts.host ?? "127.0.0.1";
19578
- const path35 = opts.path ?? "/metrics";
19783
+ const path36 = opts.path ?? "/metrics";
19579
19784
  const healthPath = opts.healthPath ?? "/healthz";
19580
19785
  const healthRegistry = opts.healthRegistry;
19581
19786
  const listener = (req, res) => {
@@ -19585,7 +19790,7 @@ async function startMetricsServer(opts) {
19585
19790
  return;
19586
19791
  }
19587
19792
  const url = req.url.split("?")[0];
19588
- if (url === path35) {
19793
+ if (url === path36) {
19589
19794
  let body;
19590
19795
  try {
19591
19796
  body = renderPrometheus(opts.sink.snapshot());
@@ -19631,14 +19836,14 @@ async function startMetricsServer(opts) {
19631
19836
  const { createServer } = await import('http');
19632
19837
  server = createServer(listener);
19633
19838
  }
19634
- await new Promise((resolve10, reject) => {
19839
+ await new Promise((resolve11, reject) => {
19635
19840
  const onError = (err) => {
19636
19841
  server.off("listening", onListening);
19637
19842
  reject(err);
19638
19843
  };
19639
19844
  const onListening = () => {
19640
19845
  server.off("error", onError);
19641
- resolve10();
19846
+ resolve11();
19642
19847
  };
19643
19848
  server.once("error", onError);
19644
19849
  server.once("listening", onListening);
@@ -19649,9 +19854,9 @@ async function startMetricsServer(opts) {
19649
19854
  const protocol = useHttps ? "https" : "http";
19650
19855
  return {
19651
19856
  port: boundPort,
19652
- url: `${protocol}://${host}:${boundPort}${path35}`,
19653
- close: () => new Promise((resolve10, reject) => {
19654
- server.close((err) => err ? reject(err) : resolve10());
19857
+ url: `${protocol}://${host}:${boundPort}${path36}`,
19858
+ close: () => new Promise((resolve11, reject) => {
19859
+ server.close((err) => err ? reject(err) : resolve11());
19655
19860
  })
19656
19861
  };
19657
19862
  }
@@ -20351,7 +20556,7 @@ async function downloadGitHubTarball(parsed) {
20351
20556
  `Tarball too large (${(Number.parseInt(contentLength, 10) / 1024 / 1024).toFixed(1)}MB). Max: ${MAX_TARBALL_SIZE / 1024 / 1024}MB`
20352
20557
  );
20353
20558
  }
20354
- const tempDir = await fsp3.mkdtemp(path6.join(os5.tmpdir(), "wskill-"));
20559
+ const tempDir = await fsp3.mkdtemp(path6.join(os6.tmpdir(), "wskill-"));
20355
20560
  try {
20356
20561
  if (!response.body) {
20357
20562
  throw new Error("Empty response body from GitHub API");
@@ -22851,8 +23056,8 @@ var ReportGenerator = class {
22851
23056
  try {
22852
23057
  await stat(this.options.outputDir);
22853
23058
  } catch {
22854
- const { mkdir: mkdir15 } = await import('fs/promises');
22855
- await mkdir15(this.options.outputDir, { recursive: true });
23059
+ const { mkdir: mkdir16 } = await import('fs/promises');
23060
+ await mkdir16(this.options.outputDir, { recursive: true });
22856
23061
  }
22857
23062
  }
22858
23063
  generateMarkdown(result) {
@@ -22983,22 +23188,24 @@ var ReportGenerator = class {
22983
23188
  medium: "#ca8a04",
22984
23189
  low: "#16a34a"
22985
23190
  };
22986
- const rows = result.findings.map(
22987
- (f) => `
23191
+ const rows = result.findings.map((f) => {
23192
+ const sevColor = severityColors[f.severity] ?? "#000000";
23193
+ const loc = `${escapeHtml(f.file)}${f.line ? `:${escapeHtml(String(f.line))}` : ""}`;
23194
+ return `
22988
23195
  <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>
23196
+ <td style="color: ${escapeHtml(sevColor)}; font-weight: bold;">${escapeHtml(f.severity.toUpperCase())}</td>
23197
+ <td>${escapeHtml(f.category)}</td>
23198
+ <td>${escapeHtml(f.title)}</td>
23199
+ <td><code>${loc}</code></td>
23200
+ <td>${escapeHtml(f.remediation)}</td>
22994
23201
  </tr>
22995
- `
22996
- ).join("");
23202
+ `;
23203
+ }).join("");
22997
23204
  return `
22998
23205
  <!DOCTYPE html>
22999
23206
  <html>
23000
23207
  <head>
23001
- <title>Security Scan Report - ${new Date(result.timestamp).toLocaleDateString()}</title>
23208
+ <title>Security Scan Report - ${escapeHtml(new Date(result.timestamp).toLocaleDateString())}</title>
23002
23209
  <style>
23003
23210
  body { font-family: system-ui, sans-serif; margin: 2rem; }
23004
23211
  table { border-collapse: collapse; width: 100%; }
@@ -23009,9 +23216,9 @@ var ReportGenerator = class {
23009
23216
  </head>
23010
23217
  <body>
23011
23218
  <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>
23219
+ <p><strong>Generated:</strong> ${escapeHtml(new Date(result.timestamp).toLocaleString())}</p>
23220
+ <p><strong>Project:</strong> ${escapeHtml(result.projectRoot)}</p>
23221
+ <p><strong>Tech Stack:</strong> ${escapeHtml(result.techStack.stack)} (${escapeHtml(result.techStack.packageManager)})</p>
23015
23222
 
23016
23223
  <h2>Summary</h2>
23017
23224
  <ul>
@@ -23037,6 +23244,9 @@ var ReportGenerator = class {
23037
23244
  `.trim();
23038
23245
  }
23039
23246
  };
23247
+ function escapeHtml(value) {
23248
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
23249
+ }
23040
23250
  var defaultReportGenerator = new ReportGenerator();
23041
23251
 
23042
23252
  // src/security-scanner/gitignore-updater.ts
@@ -23127,7 +23337,7 @@ var SecurityScannerOrchestrator = class {
23127
23337
  const delay = Math.round(policy.delayMs(attempt));
23128
23338
  const status = isProviderErr ? err.status : 0;
23129
23339
  console.warn(`[SecurityScanner] retry ${attempt + 1} after ${delay}ms (status=${status}) \u2014 ${errAsErr.message}`);
23130
- await new Promise((resolve10) => setTimeout(resolve10, delay));
23340
+ await new Promise((resolve11) => setTimeout(resolve11, delay));
23131
23341
  return this.completeWithRetry(provider, request, abortController, attempt + 1);
23132
23342
  }
23133
23343
  }
@@ -23872,7 +24082,7 @@ var FleetManager = class {
23872
24082
  leaderContextPressure = 0;
23873
24083
  /** Maximum context load fraction before spawn is refused. */
23874
24084
  maxLeaderContextLoad;
23875
- /** Provider's max context window in tokens. */
24085
+ /** Provider's max context window in tokens, or a live resolver for runtime model switches. */
23876
24086
  maxContext;
23877
24087
  constructor(opts = {}) {
23878
24088
  this.manifestPath = opts.manifestPath;
@@ -23952,7 +24162,8 @@ var FleetManager = class {
23952
24162
  }
23953
24163
  }
23954
24164
  if (this.maxLeaderContextLoad < 1) {
23955
- const threshold = this.maxContext * this.maxLeaderContextLoad;
24165
+ const maxContext = this.resolveMaxContext();
24166
+ const threshold = maxContext * this.maxLeaderContextLoad;
23956
24167
  if (this.leaderContextPressure >= threshold) {
23957
24168
  return {
23958
24169
  kind: "max_context_load",
@@ -23966,6 +24177,10 @@ var FleetManager = class {
23966
24177
  setLeaderContextPressure(tokens) {
23967
24178
  this.leaderContextPressure = tokens;
23968
24179
  }
24180
+ resolveMaxContext() {
24181
+ const resolved = typeof this.maxContext === "function" ? this.maxContext() : this.maxContext;
24182
+ return resolved && resolved > 0 ? resolved : 128e3;
24183
+ }
23969
24184
  /**
23970
24185
  * Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
23971
24186
  * record it so the same name is never reused, then record the spawn.
@@ -24698,12 +24913,12 @@ function makeContinueToNextIterationTool() {
24698
24913
  // src/core/iteration-limit.ts
24699
24914
  function requestLimitExtension(opts) {
24700
24915
  const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
24701
- return new Promise((resolve10) => {
24916
+ return new Promise((resolve11) => {
24702
24917
  let resolved = false;
24703
24918
  const timerFired = () => {
24704
24919
  if (!resolved) {
24705
24920
  resolved = true;
24706
- resolve10(0);
24921
+ resolve11(0);
24707
24922
  }
24708
24923
  };
24709
24924
  const timer = setTimeout(timerFired, timeoutMs);
@@ -24712,14 +24927,14 @@ function requestLimitExtension(opts) {
24712
24927
  if (!resolved) {
24713
24928
  resolved = true;
24714
24929
  clearTimeout(timer);
24715
- resolve10(0);
24930
+ resolve11(0);
24716
24931
  }
24717
24932
  };
24718
24933
  const grant = (extra) => {
24719
24934
  if (!resolved) {
24720
24935
  resolved = true;
24721
24936
  clearTimeout(timer);
24722
- resolve10(Math.max(0, extra));
24937
+ resolve11(Math.max(0, extra));
24723
24938
  }
24724
24939
  };
24725
24940
  events.emit("iteration.limit_reached", {
@@ -24733,7 +24948,7 @@ function requestLimitExtension(opts) {
24733
24948
  if (!resolved) {
24734
24949
  resolved = true;
24735
24950
  clearTimeout(timer);
24736
- resolve10(100);
24951
+ resolve11(100);
24737
24952
  }
24738
24953
  });
24739
24954
  }
@@ -25325,13 +25540,13 @@ var Agent = class {
25325
25540
  }
25326
25541
  }
25327
25542
  waitForConfirm(info) {
25328
- return new Promise((resolve10) => {
25543
+ return new Promise((resolve11) => {
25329
25544
  this.events.emit("tool.confirm_needed", {
25330
25545
  tool: info.tool,
25331
25546
  input: info.input,
25332
25547
  toolUseId: info.toolUseId,
25333
25548
  suggestedPattern: info.suggestedPattern,
25334
- resolve: resolve10
25549
+ resolve: resolve11
25335
25550
  });
25336
25551
  });
25337
25552
  }
@@ -25405,6 +25620,85 @@ function sizeSignals(toolName, content) {
25405
25620
  }
25406
25621
  return { outputBytes, outputTokens, outputLines };
25407
25622
  }
25623
+ async function bootConfig(options = {}) {
25624
+ const { flags = {}, appLabel = "wstack", loadSyncConfig = true } = options;
25625
+ const cwd = typeof flags["cwd"] === "string" ? path6.resolve(flags["cwd"]) : process.cwd();
25626
+ const pathResolver = new DefaultPathResolver(cwd);
25627
+ const projectRoot = pathResolver.projectRoot;
25628
+ const userHome = os6.homedir();
25629
+ const wpaths = resolveWstackPaths({ projectRoot, userHome });
25630
+ await fsp3.mkdir(wpaths.globalRoot, { recursive: true });
25631
+ await fsp3.mkdir(wpaths.projectDir, { recursive: true });
25632
+ await fsp3.mkdir(wpaths.projectSessions, { recursive: true });
25633
+ await writeProjectMeta(wpaths, projectRoot);
25634
+ const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
25635
+ for (const file of [wpaths.globalConfig, wpaths.projectLocalConfig]) {
25636
+ try {
25637
+ const { migrated } = await migratePlaintextSecrets(file, vault);
25638
+ if (migrated > 0) {
25639
+ writeErr(`[${appLabel}] Encrypted ${migrated} plaintext secret(s) in ${file}
25640
+ `);
25641
+ }
25642
+ } catch {
25643
+ }
25644
+ }
25645
+ const configLoader = new DefaultConfigLoader({ paths: wpaths, vault });
25646
+ let config = await configLoader.load({ cliFlags: flagsToConfigPatch(flags) });
25647
+ if (loadSyncConfig) {
25648
+ const syncConfig = await configLoader.loadSyncConfig();
25649
+ if (syncConfig) {
25650
+ config = Object.freeze({ ...config, sync: syncConfig });
25651
+ }
25652
+ }
25653
+ const logger = new DefaultLogger({ level: config.log?.level ?? "info", file: wpaths.logFile });
25654
+ return {
25655
+ cwd,
25656
+ projectRoot,
25657
+ userHome,
25658
+ wpaths,
25659
+ pathResolver,
25660
+ config,
25661
+ vault,
25662
+ logger,
25663
+ globalConfigPath: wpaths.globalConfig
25664
+ };
25665
+ }
25666
+ function flagsToConfigPatch(flags) {
25667
+ const patch = {};
25668
+ if (typeof flags["provider"] === "string") patch.provider = flags["provider"];
25669
+ if (typeof flags["model"] === "string") patch.model = flags["model"];
25670
+ if (typeof flags["cwd"] === "string") patch.cwd = flags["cwd"];
25671
+ if (typeof flags["log-level"] === "string") {
25672
+ patch.log = { level: flags["log-level"] };
25673
+ } else if (flags["verbose"]) {
25674
+ patch.log = { level: "debug" };
25675
+ } else if (flags["trace"]) {
25676
+ patch.log = { level: "trace" };
25677
+ }
25678
+ if (flags["yolo"]) patch.yolo = true;
25679
+ if (flags["no-features"]) {
25680
+ patch.features = {
25681
+ mcp: false,
25682
+ plugins: false,
25683
+ memory: false,
25684
+ modelsRegistry: false,
25685
+ skills: false
25686
+ };
25687
+ }
25688
+ return patch;
25689
+ }
25690
+ async function writeProjectMeta(paths, projectRoot) {
25691
+ try {
25692
+ await fsp3.mkdir(paths.projectDir, { recursive: true });
25693
+ const meta = {
25694
+ hash: paths.projectHash,
25695
+ root: projectRoot,
25696
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString()
25697
+ };
25698
+ await fsp3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
25699
+ } catch {
25700
+ }
25701
+ }
25408
25702
 
25409
25703
  // src/core/conversation-state.ts
25410
25704
  var ConversationState = class {
@@ -25704,8 +25998,8 @@ var InputBuilder = class {
25704
25998
  async registerFile(input) {
25705
25999
  const ref = await this.store.add({ ...input, kind: "file" });
25706
26000
  this.refs.push(ref);
25707
- const path35 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
25708
- return `[file:${path35}]`;
26001
+ const path36 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
26002
+ return `[file:${path36}]`;
25709
26003
  }
25710
26004
  /**
25711
26005
  * Whether `appendPaste(text)` would collapse the text to a placeholder
@@ -26036,7 +26330,7 @@ summarize it, and let the tool result hold only the summary.`);
26036
26330
  const cached = this.envCacheByRoot.get(ctx.projectRoot);
26037
26331
  if (cached) return cached;
26038
26332
  const today = this.opts.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
26039
- const platform2 = `${os5.platform()} ${os5.release()}`;
26333
+ const platform2 = `${os6.platform()} ${os6.release()}`;
26040
26334
  const shell = process.env.SHELL ?? process.env.ComSpec ?? "unknown";
26041
26335
  const node = process.version;
26042
26336
  const isGit = await this.dirExists(path6.join(ctx.projectRoot, ".git"));
@@ -26105,12 +26399,12 @@ ${mem}`);
26105
26399
  }
26106
26400
  }
26107
26401
  async gitStatus(root) {
26108
- return new Promise((resolve10) => {
26402
+ return new Promise((resolve11) => {
26109
26403
  let settled = false;
26110
26404
  const finish = (s) => {
26111
26405
  if (settled) return;
26112
26406
  settled = true;
26113
- resolve10(s);
26407
+ resolve11(s);
26114
26408
  };
26115
26409
  let proc;
26116
26410
  const timer = setTimeout(() => {
@@ -27048,6 +27342,7 @@ var PhaseOrchestrator = class {
27048
27342
  maxConcurrentPhases: opts.maxConcurrentPhases ?? 1,
27049
27343
  maxConcurrentTasks: opts.maxConcurrentTasks ?? 2,
27050
27344
  maxRetries: opts.maxRetries ?? 2,
27345
+ maxVerifyAttempts: opts.maxVerifyAttempts ?? 2,
27051
27346
  autonomous: opts.autonomous ?? true,
27052
27347
  phaseDelayMs: opts.phaseDelayMs ?? 0,
27053
27348
  stopOnFailure: opts.stopOnFailure ?? true,
@@ -27066,11 +27361,15 @@ var PhaseOrchestrator = class {
27066
27361
  this.graph.updatedAt = Date.now();
27067
27362
  let readyPhases = this.getReadyPhases();
27068
27363
  while (readyPhases.length > 0 && !this.stopped) {
27364
+ await this.waitWhilePaused();
27365
+ if (this.stopped) break;
27069
27366
  const batch = readyPhases.slice(0, this.opts.maxConcurrentPhases);
27070
27367
  await Promise.all(batch.map((p) => this.startPhase(p)));
27071
27368
  if (this.opts.phaseDelayMs > 0) {
27072
27369
  await this.delay(this.opts.phaseDelayMs);
27073
27370
  }
27371
+ await this.waitWhilePaused();
27372
+ if (this.stopped) break;
27074
27373
  readyPhases = this.getReadyPhases().filter(
27075
27374
  (p) => !this.runningPhases.has(p.id) && p.status !== "completed" && p.status !== "failed"
27076
27375
  );
@@ -27094,7 +27393,10 @@ var PhaseOrchestrator = class {
27094
27393
  resume() {
27095
27394
  this.paused = false;
27096
27395
  this.tick().catch((err) => {
27097
- console.error("[phase-orchestrator] tick failed:", err instanceof Error ? err.message : String(err));
27396
+ console.error(
27397
+ "[phase-orchestrator] tick failed:",
27398
+ err instanceof Error ? err.message : String(err)
27399
+ );
27098
27400
  });
27099
27401
  }
27100
27402
  /** Tamamen durdur — aktif fazlar da durdurulur */
@@ -27175,33 +27477,30 @@ var PhaseOrchestrator = class {
27175
27477
  failed: failedTasks
27176
27478
  });
27177
27479
  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);
27480
+ await this.failPhaseAfterTasks(phase, `${failedTasks} task(s) failed`);
27481
+ return;
27204
27482
  }
27483
+ const verdict = await this.runVerifyGate(phase);
27484
+ if (!verdict.ok) {
27485
+ await this.failPhaseAfterTasks(
27486
+ phase,
27487
+ `verification failed${verdict.output ? `: ${this.truncate(verdict.output)}` : ""}`
27488
+ );
27489
+ return;
27490
+ }
27491
+ this.updatePhaseStatus(phase, "completed");
27492
+ phase.completedAt = Date.now();
27493
+ phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
27494
+ this.runningPhases.delete(phase.id);
27495
+ this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
27496
+ this.graph.completedPhaseIds.push(phase.id);
27497
+ this.emit("phase.completed", {
27498
+ phaseId: phase.id,
27499
+ name: phase.name,
27500
+ durationMs: phase.actualDurationMs
27501
+ });
27502
+ this.ctx.onPhaseComplete?.(phase);
27503
+ await this.commitAndEnqueueMerge(phase);
27205
27504
  } catch (error) {
27206
27505
  this.updatePhaseStatus(phase, "failed");
27207
27506
  phase.completedAt = Date.now();
@@ -27218,6 +27517,69 @@ var PhaseOrchestrator = class {
27218
27517
  await this.keepWorktreeForReview(phase);
27219
27518
  }
27220
27519
  }
27520
+ // ─── Verification gate ──────────────────────────────────────────────────────
27521
+ /**
27522
+ * Run the verification gate for a phase whose tasks all succeeded. Verifies in
27523
+ * the phase's worktree; on failure, runs the repair pass and re-verifies, up to
27524
+ * `maxVerifyAttempts` repairs. Returns the final verdict. When no `verifyPhase`
27525
+ * callback is wired the gate is a no-op and always passes.
27526
+ */
27527
+ async runVerifyGate(phase) {
27528
+ if (!this.ctx.verifyPhase) return { ok: true };
27529
+ const env = this.worktreeEnv(phase);
27530
+ for (let attempt = 0; attempt <= this.opts.maxVerifyAttempts; attempt++) {
27531
+ if (this.stopped) return { ok: false, output: "stopped before verification completed" };
27532
+ this.emit("phase.verifying", { phaseId: phase.id, name: phase.name, attempt });
27533
+ let verdict;
27534
+ try {
27535
+ verdict = await this.ctx.verifyPhase(phase, env);
27536
+ } catch (err) {
27537
+ verdict = { ok: false, output: err instanceof Error ? err.message : String(err) };
27538
+ }
27539
+ if (verdict.ok) return { ok: true };
27540
+ this.emit("phase.verifyFailed", {
27541
+ phaseId: phase.id,
27542
+ name: phase.name,
27543
+ attempt,
27544
+ error: verdict.output
27545
+ });
27546
+ if (attempt >= this.opts.maxVerifyAttempts || !this.ctx.repairPhase || this.stopped) {
27547
+ return { ok: false, output: verdict.output };
27548
+ }
27549
+ this.emit("phase.repairing", { phaseId: phase.id, name: phase.name, attempt: attempt + 1 });
27550
+ try {
27551
+ await this.ctx.repairPhase(
27552
+ phase,
27553
+ verdict.output ?? "verification failed",
27554
+ attempt + 1,
27555
+ env
27556
+ );
27557
+ } catch {
27558
+ }
27559
+ }
27560
+ return { ok: false };
27561
+ }
27562
+ /** Worktree env (cwd/branch) for a phase, or undefined if it runs on the shared tree. */
27563
+ worktreeEnv(phase) {
27564
+ const handle = this.phaseWorktrees.get(phase.id);
27565
+ return handle ? { cwd: handle.dir, branch: handle.branch } : void 0;
27566
+ }
27567
+ /** Shared failure bookkeeping for a phase whose tasks ran but the phase failed. */
27568
+ async failPhaseAfterTasks(phase, error) {
27569
+ this.updatePhaseStatus(phase, "failed");
27570
+ phase.completedAt = Date.now();
27571
+ phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
27572
+ this.runningPhases.delete(phase.id);
27573
+ this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
27574
+ this.emit("phase.failed", { phaseId: phase.id, name: phase.name, error });
27575
+ this.ctx.onPhaseFail?.(phase, new Error(error));
27576
+ await this.keepWorktreeForReview(phase);
27577
+ }
27578
+ /** Trim long verifier output so it fits cleanly in an event/error message. */
27579
+ truncate(text, max = 500) {
27580
+ const t2 = text.trim();
27581
+ return t2.length <= max ? t2 : `${t2.slice(0, max)}\u2026 (+${t2.length - max} chars)`;
27582
+ }
27221
27583
  // ─── Worktree integration ───────────────────────────────────────────────────
27222
27584
  /**
27223
27585
  * Commit the phase's worktree changes, then enqueue the merge back into the
@@ -27240,13 +27602,40 @@ var PhaseOrchestrator = class {
27240
27602
  })();
27241
27603
  this.phaseMergePromise.set(phase.id, merged);
27242
27604
  }
27243
- /** Squash-merge one phase. Conflicts mark the worktree needs-review (run continues). */
27605
+ /**
27606
+ * Squash-merge one phase. When a `resolveConflict` callback is wired, a merge
27607
+ * conflict is handed to it (a resolver subagent) before giving up; only if
27608
+ * that fails does the worktree fall to needs-review and the run continues.
27609
+ */
27244
27610
  async mergeOne(phase, handle) {
27245
27611
  if (!this.worktrees) return;
27246
27612
  try {
27247
- const result = await this.worktrees.merge(handle, { squash: true });
27613
+ const resolve11 = this.ctx.resolveConflict ? async (info) => {
27614
+ const shouldResolve = await this.shouldAttemptConflictResolution(phase, info);
27615
+ if (!shouldResolve) return false;
27616
+ this.emit("phase.conflictResolving", {
27617
+ phaseId: phase.id,
27618
+ name: phase.name,
27619
+ files: info.conflictFiles
27620
+ });
27621
+ return this.ctx.resolveConflict(phase, info);
27622
+ } : void 0;
27623
+ const result = await this.worktrees.merge(handle, { squash: true, resolve: resolve11 });
27624
+ if (result.resolved) {
27625
+ this.emit("phase.conflictResolved", { phaseId: phase.id, name: phase.name });
27626
+ }
27627
+ this.setIntegrationMetadata(phase, result.ok ? "merged" : "needs_review", {
27628
+ branch: handle.branch,
27629
+ worktreeDir: handle.dir,
27630
+ conflictFiles: result.conflictFiles
27631
+ });
27248
27632
  await this.worktrees.release(handle, { keep: !result.ok });
27249
27633
  } catch (err) {
27634
+ this.setIntegrationMetadata(phase, "merge_failed", {
27635
+ branch: handle.branch,
27636
+ worktreeDir: handle.dir,
27637
+ error: err instanceof Error ? err.message : String(err)
27638
+ });
27250
27639
  this.emit("phase.failed", {
27251
27640
  phaseId: phase.id,
27252
27641
  name: phase.name,
@@ -27254,6 +27643,56 @@ var PhaseOrchestrator = class {
27254
27643
  });
27255
27644
  }
27256
27645
  }
27646
+ async shouldAttemptConflictResolution(phase, info) {
27647
+ if (!this.ctx.brain) return true;
27648
+ const decision = await this.ctx.brain.decide({
27649
+ id: `autophase-conflict-${phase.id}`,
27650
+ source: "autophase",
27651
+ question: `Should AutoPhase try to resolve merge conflicts for phase "${phase.name}" automatically?`,
27652
+ context: [
27653
+ `Phase id: ${phase.id}`,
27654
+ `Conflicted files: ${info.conflictFiles.join(", ") || "(unknown)"}`,
27655
+ `Base working tree: ${info.cwd}`
27656
+ ].join("\n"),
27657
+ risk: "high",
27658
+ fallback: "ask_human",
27659
+ options: [
27660
+ {
27661
+ id: "resolve",
27662
+ label: "Try the configured conflict resolver",
27663
+ consequence: "A resolver agent may edit conflicted files in the base working tree.",
27664
+ risk: "medium"
27665
+ },
27666
+ {
27667
+ id: "review",
27668
+ label: "Keep the worktree for human review",
27669
+ consequence: "No automatic conflict resolution is attempted.",
27670
+ risk: "low",
27671
+ recommended: true
27672
+ }
27673
+ ]
27674
+ });
27675
+ phase.metadata = {
27676
+ ...phase.metadata,
27677
+ brainConflictDecision: decision.type,
27678
+ brainConflictDecisionAt: Date.now()
27679
+ };
27680
+ if (decision.type !== "answer") return false;
27681
+ return decision.optionId === "resolve" || /\bresolve\b/i.test(decision.text);
27682
+ }
27683
+ setIntegrationMetadata(phase, status, details = {}) {
27684
+ phase.metadata = {
27685
+ ...phase.metadata,
27686
+ integrationStatus: status,
27687
+ integrationBranch: details.branch,
27688
+ integrationWorktreeDir: details.worktreeDir,
27689
+ integrationConflictFiles: details.conflictFiles,
27690
+ integrationError: details.error,
27691
+ integrationUpdatedAt: Date.now()
27692
+ };
27693
+ phase.updatedAt = Date.now();
27694
+ this.graph.updatedAt = Date.now();
27695
+ }
27257
27696
  /** A failed phase keeps its worktree on disk for inspection (no merge). */
27258
27697
  async keepWorktreeForReview(phase) {
27259
27698
  const handle = this.phaseWorktrees.get(phase.id);
@@ -27262,6 +27701,10 @@ var PhaseOrchestrator = class {
27262
27701
  await this.worktrees.commitAll(handle, `autophase(${phase.name}) [failed]: ${phase.id}`);
27263
27702
  } catch {
27264
27703
  }
27704
+ this.setIntegrationMetadata(phase, "not_merged_failed_phase", {
27705
+ branch: handle.branch,
27706
+ worktreeDir: handle.dir
27707
+ });
27265
27708
  await this.worktrees.release(handle, { keep: true }).catch(() => {
27266
27709
  });
27267
27710
  }
@@ -27308,7 +27751,11 @@ var PhaseOrchestrator = class {
27308
27751
  const currentRetries = this.taskRetryCounts.get(taskKey) ?? 0;
27309
27752
  if (currentRetries < this.opts.maxRetries) {
27310
27753
  this.taskRetryCounts.set(taskKey, currentRetries + 1);
27311
- tracker.updateNodeStatus(task.id, "pending", `Retry ${currentRetries + 1}/${this.opts.maxRetries}`);
27754
+ tracker.updateNodeStatus(
27755
+ task.id,
27756
+ "pending",
27757
+ `Retry ${currentRetries + 1}/${this.opts.maxRetries}`
27758
+ );
27312
27759
  this.emit("phase.taskRetrying", {
27313
27760
  phaseId: phase.id,
27314
27761
  taskId: task.id,
@@ -27317,7 +27764,11 @@ var PhaseOrchestrator = class {
27317
27764
  maxRetries: this.opts.maxRetries
27318
27765
  });
27319
27766
  } else {
27320
- tracker.updateNodeStatus(task.id, "failed", error instanceof Error ? error.message : String(error));
27767
+ tracker.updateNodeStatus(
27768
+ task.id,
27769
+ "failed",
27770
+ error instanceof Error ? error.message : String(error)
27771
+ );
27321
27772
  this.emit("phase.taskFailed", {
27322
27773
  phaseId: phase.id,
27323
27774
  taskId: task.id,
@@ -27513,8 +27964,13 @@ var PhaseOrchestrator = class {
27513
27964
  }
27514
27965
  };
27515
27966
  }
27967
+ async waitWhilePaused() {
27968
+ while (this.paused && !this.stopped) {
27969
+ await this.delay(100);
27970
+ }
27971
+ }
27516
27972
  delay(ms) {
27517
- return new Promise((resolve10) => setTimeout(resolve10, ms));
27973
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
27518
27974
  }
27519
27975
  };
27520
27976
 
@@ -27558,6 +28014,10 @@ var AutoPhaseRunner = class {
27558
28014
  this.graph = await builder.build();
27559
28015
  const ctx = {
27560
28016
  executeTask: this.opts.executeTask,
28017
+ verifyPhase: this.opts.verifyPhase,
28018
+ repairPhase: this.opts.repairPhase,
28019
+ resolveConflict: this.opts.resolveConflict,
28020
+ brain: this.opts.brain,
27561
28021
  onPhaseComplete: (phase) => {
27562
28022
  this.opts.onPhaseComplete?.(phase);
27563
28023
  },
@@ -27574,10 +28034,12 @@ var AutoPhaseRunner = class {
27574
28034
  maxConcurrentPhases: this.opts.maxConcurrentPhases,
27575
28035
  maxConcurrentTasks: this.opts.maxConcurrentTasks,
27576
28036
  maxRetries: this.opts.maxRetries,
28037
+ maxVerifyAttempts: this.opts.maxVerifyAttempts,
27577
28038
  autonomous: this.opts.autonomous,
27578
28039
  phaseDelayMs: this.opts.phaseDelayMs,
27579
28040
  stopOnFailure: this.opts.stopOnFailure,
27580
- events: this.opts.events
28041
+ events: this.opts.events,
28042
+ worktrees: this.opts.worktrees
27581
28043
  });
27582
28044
  if (this.opts.onProgress) {
27583
28045
  this.progressInterval = setInterval(() => {
@@ -28268,6 +28730,10 @@ var WorktreeManager = class {
28268
28730
  ${merged.stderr}`);
28269
28731
  const fromIndex = await this.unmergedFiles();
28270
28732
  const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
28733
+ if (opts.resolve) {
28734
+ const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
28735
+ if (finalized) return finalized;
28736
+ }
28271
28737
  await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
28272
28738
  handle.conflictFiles = conflictFiles;
28273
28739
  this.setStatus(handle, "needs-review", { lastError: merged.stderr });
@@ -28298,6 +28764,52 @@ ${merged.stderr}`);
28298
28764
  });
28299
28765
  return { ok: true };
28300
28766
  }
28767
+ /**
28768
+ * Run the caller-supplied resolver against a conflicted squash-merge, then
28769
+ * commit if it cleared every marker. Returns a successful `MergeResult` on a
28770
+ * clean resolution, or `null` to signal the caller should fall back to the
28771
+ * abort path. Never leaves the base tree committed-but-dirty: a partial or
28772
+ * failed resolution returns `null` and the caller hard-resets.
28773
+ */
28774
+ async tryResolveConflict(handle, conflictFiles, opts) {
28775
+ let resolved = false;
28776
+ try {
28777
+ resolved = await opts.resolve({ conflictFiles, cwd: this.projectRoot });
28778
+ } catch {
28779
+ resolved = false;
28780
+ }
28781
+ if (!resolved) return null;
28782
+ await this.runGit(["add", "-A"], this.projectRoot);
28783
+ if (await this.hasConflictMarkers()) return null;
28784
+ const idArgs = await this.identityArgs(this.projectRoot);
28785
+ const msg = opts.message ?? `merge ${handle.branch} (squash, conflict resolved)`;
28786
+ const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
28787
+ if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
28788
+ return null;
28789
+ }
28790
+ handle.conflictFiles = conflictFiles;
28791
+ this.setStatus(handle, "merged");
28792
+ this.emit("worktree.merged", {
28793
+ handleId: handle.id,
28794
+ ownerId: handle.ownerId,
28795
+ branch: handle.branch,
28796
+ baseBranch: handle.baseBranch,
28797
+ squash: true
28798
+ });
28799
+ return { ok: true, resolved: true, conflictFiles };
28800
+ }
28801
+ /**
28802
+ * True when staged content still carries conflict markers. `git diff --cached
28803
+ * --check` exits nonzero and prints a "leftover conflict marker" line for each
28804
+ * survivor; whitespace-only errors (also flagged by --check) are ignored so a
28805
+ * clean resolution with unrelated whitespace is not rejected.
28806
+ */
28807
+ async hasConflictMarkers() {
28808
+ const check = await this.runGit(["diff", "--cached", "--check"], this.projectRoot);
28809
+ if (check.code === 0) return false;
28810
+ return /conflict marker/i.test(`${check.stdout}
28811
+ ${check.stderr}`);
28812
+ }
28301
28813
  /**
28302
28814
  * Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
28303
28815
  * are left on disk for inspection.
@@ -28446,6 +28958,147 @@ function assertSafePath(dir, projectRoot) {
28446
28958
  }
28447
28959
  }
28448
28960
 
28961
+ // src/coordination/brain.ts
28962
+ var ObservableBrainArbiter = class {
28963
+ constructor(inner, events) {
28964
+ this.inner = inner;
28965
+ this.events = events;
28966
+ }
28967
+ inner;
28968
+ events;
28969
+ async decide(request) {
28970
+ this.events.emit("brain.decision_requested", { request, at: Date.now() });
28971
+ const decision = await this.inner.decide(request);
28972
+ const event = decision.type === "ask_human" ? "brain.decision_ask_human" : decision.type === "deny" ? "brain.decision_denied" : "brain.decision_answered";
28973
+ this.events.emit(event, { request, decision, at: Date.now() });
28974
+ return decision;
28975
+ }
28976
+ };
28977
+ var BrainDecisionQueue = class {
28978
+ constructor(events, opts = {}) {
28979
+ this.events = events;
28980
+ this.opts = opts;
28981
+ this.offAnswer = this.events.on("brain.human_answered", (answer) => {
28982
+ const pending = this.pending.get(answer.id);
28983
+ if (!pending) return;
28984
+ this.pending.delete(answer.id);
28985
+ if (pending.timer) clearTimeout(pending.timer);
28986
+ if (answer.deny) {
28987
+ pending.resolve({ type: "deny", reason: answer.text ?? "Denied by human." });
28988
+ return;
28989
+ }
28990
+ const option = pending.request.options?.find((o) => o.id === answer.optionId);
28991
+ pending.resolve({
28992
+ type: "answer",
28993
+ optionId: answer.optionId,
28994
+ text: answer.text ?? option?.label ?? answer.optionId ?? "Human answered.",
28995
+ rationale: "Human answered a Brain escalation prompt."
28996
+ });
28997
+ });
28998
+ }
28999
+ events;
29000
+ opts;
29001
+ pending = /* @__PURE__ */ new Map();
29002
+ offAnswer;
29003
+ async requestHumanDecision(request) {
29004
+ const ask = {
29005
+ type: "ask_human",
29006
+ prompt: formatHumanPrompt(request),
29007
+ options: request.options,
29008
+ rationale: "Decision escalated to human authority."
29009
+ };
29010
+ const pending = new Promise((resolve11) => {
29011
+ const entry = { request, resolve: resolve11 };
29012
+ if (this.opts.timeoutMs && this.opts.timeoutMs > 0) {
29013
+ entry.timer = setTimeout(() => {
29014
+ this.pending.delete(request.id);
29015
+ resolve11({ type: "deny", reason: "Brain human decision timed out." });
29016
+ }, this.opts.timeoutMs);
29017
+ }
29018
+ this.pending.set(request.id, entry);
29019
+ });
29020
+ this.events.emit("brain.decision_ask_human", { request, decision: ask, at: Date.now() });
29021
+ return pending;
29022
+ }
29023
+ dispose() {
29024
+ this.offAnswer();
29025
+ for (const [id, pending] of this.pending) {
29026
+ if (pending.timer) clearTimeout(pending.timer);
29027
+ pending.resolve({ type: "deny", reason: "Brain decision queue disposed." });
29028
+ this.pending.delete(id);
29029
+ }
29030
+ }
29031
+ };
29032
+ var HumanEscalatingBrainArbiter = class {
29033
+ constructor(inner, queue) {
29034
+ this.inner = inner;
29035
+ this.queue = queue;
29036
+ }
29037
+ inner;
29038
+ queue;
29039
+ async decide(request) {
29040
+ const decision = await this.inner.decide(request);
29041
+ if (decision.type !== "ask_human") return decision;
29042
+ return this.queue.requestHumanDecision(request);
29043
+ }
29044
+ };
29045
+ var DefaultBrainArbiter = class {
29046
+ allowLowRiskAutoAnswer;
29047
+ constructor(opts = {}) {
29048
+ this.allowLowRiskAutoAnswer = opts.allowLowRiskAutoAnswer ?? true;
29049
+ }
29050
+ async decide(request) {
29051
+ const recommended = request.options?.find((option) => option.recommended);
29052
+ if (this.allowLowRiskAutoAnswer && request.risk === "low" && recommended) {
29053
+ return {
29054
+ type: "answer",
29055
+ optionId: recommended.id,
29056
+ text: recommended.label,
29057
+ rationale: "Low-risk request with an explicit recommended option."
29058
+ };
29059
+ }
29060
+ switch (request.fallback) {
29061
+ case "deny":
29062
+ return {
29063
+ type: "deny",
29064
+ reason: `Brain could not safely decide: ${request.question}`
29065
+ };
29066
+ case "continue":
29067
+ return {
29068
+ type: "answer",
29069
+ text: "Continue with the caller default.",
29070
+ rationale: "No safe Brain decision was available; request fallback is continue."
29071
+ };
29072
+ case "ask_human":
29073
+ return {
29074
+ type: "ask_human",
29075
+ prompt: formatHumanPrompt(request),
29076
+ options: request.options,
29077
+ rationale: "Decision requires human authority or lacks a safe automatic option."
29078
+ };
29079
+ }
29080
+ }
29081
+ };
29082
+ function formatHumanPrompt(request) {
29083
+ const lines = [
29084
+ `Brain requires human decision for ${request.source}:`,
29085
+ `Question: ${request.question}`
29086
+ ];
29087
+ if (request.context?.trim()) {
29088
+ lines.push("", "Context:", request.context.trim());
29089
+ }
29090
+ if (request.options?.length) {
29091
+ lines.push("", "Options:");
29092
+ for (const option of request.options) {
29093
+ const risk = option.risk ? ` [risk: ${option.risk}]` : "";
29094
+ const consequence = option.consequence ? ` \u2014 ${option.consequence}` : "";
29095
+ lines.push(`- ${option.id}: ${option.label}${risk}${consequence}`);
29096
+ }
29097
+ }
29098
+ lines.push("", `Risk: ${request.risk}`);
29099
+ return lines.join("\n");
29100
+ }
29101
+
28449
29102
  // src/coordination/collab-bus.ts
28450
29103
  var CollaborationBus = class {
28451
29104
  pausePromise = null;
@@ -28474,8 +29127,8 @@ var CollaborationBus = class {
28474
29127
  if (this.isPaused()) return false;
28475
29128
  this.pausedAtMs = Date.now();
28476
29129
  this.pausedBy = byParticipant;
28477
- this.pausePromise = new Promise((resolve10) => {
28478
- this.pauseResolve = resolve10;
29130
+ this.pausePromise = new Promise((resolve11) => {
29131
+ this.pauseResolve = resolve11;
28479
29132
  });
28480
29133
  return true;
28481
29134
  }
@@ -28511,8 +29164,8 @@ var CollaborationBus = class {
28511
29164
  return true;
28512
29165
  }
28513
29166
  let timer;
28514
- const timeoutPromise = new Promise((resolve10) => {
28515
- timer = setTimeout(() => resolve10("timeout"), timeoutMs);
29167
+ const timeoutPromise = new Promise((resolve11) => {
29168
+ timer = setTimeout(() => resolve11("timeout"), timeoutMs);
28516
29169
  });
28517
29170
  const resumedPromise = this.pausePromise.then(() => "resumed");
28518
29171
  const winner = await Promise.race([resumedPromise, timeoutPromise]);
@@ -29003,7 +29656,7 @@ function createGitPlugin() {
29003
29656
  }
29004
29657
  async function runGit(args, cwd) {
29005
29658
  try {
29006
- return await new Promise((resolve10, reject) => {
29659
+ return await new Promise((resolve11, reject) => {
29007
29660
  const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
29008
29661
  let stdout = "";
29009
29662
  let stderr = "";
@@ -29024,7 +29677,7 @@ async function runGit(args, cwd) {
29024
29677
  })
29025
29678
  );
29026
29679
  });
29027
- child.on("close", (code) => resolve10({ stdout, stderr, code: code ?? 0 }));
29680
+ child.on("close", (code) => resolve11({ stdout, stderr, code: code ?? 0 }));
29028
29681
  });
29029
29682
  } catch (err) {
29030
29683
  if (err instanceof WrongStackError) throw err;
@@ -29349,7 +30002,7 @@ function createSkillsPlugin(opts) {
29349
30002
  };
29350
30003
  }
29351
30004
  function makeInstaller(skillLoader, projectRoot) {
29352
- const globalRoot = path6.join(os5.homedir(), ".wrongstack");
30005
+ const globalRoot = path6.join(os6.homedir(), ".wrongstack");
29353
30006
  return new SkillInstaller({
29354
30007
  manifestPath: path6.join(globalRoot, "installed-skills.json"),
29355
30008
  projectSkillsDir: path6.join(projectRoot, ".wrongstack", "skills"),
@@ -29700,6 +30353,6 @@ ${formatPlan(updated)}`
29700
30353
  };
29701
30354
  }
29702
30355
 
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 };
30356
+ 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, BrainDecisionQueue, 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, DefaultBrainArbiter, 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, HumanEscalatingBrainArbiter, 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, ObservableBrainArbiter, 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, bootConfig, 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, flagsToConfigPatch, formatContextWindowModeList, formatGoal, formatHumanPrompt, 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
30357
  //# sourceMappingURL=index.js.map
29705
30358
  //# sourceMappingURL=index.js.map