@wrongstack/core 0.1.10 → 0.3.1

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 (54) hide show
  1. package/dist/{agent-bridge-6KPqsFx6.d.ts → agent-bridge-C3DUGjSb.d.ts} +1 -1
  2. package/dist/{compactor-B4mQZXf2.d.ts → compactor-BUU6Zm_3.d.ts} +1 -1
  3. package/dist/{config-BU9f_5yH.d.ts → config-CKLYPkCi.d.ts} +1 -1
  4. package/dist/{context-BmM2xGUZ.d.ts → context-IovtuTf8.d.ts} +10 -0
  5. package/dist/coordination/index.d.ts +211 -13
  6. package/dist/coordination/index.js +964 -67
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +33 -18
  9. package/dist/defaults/index.js +1273 -42
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/{events-BMNaEFZl.d.ts → events-CNB9PALO.d.ts} +99 -1
  12. package/dist/execution/index.d.ts +12 -12
  13. package/dist/extension/index.d.ts +9 -0
  14. package/dist/extension/index.js +234 -0
  15. package/dist/extension/index.js.map +1 -0
  16. package/dist/index-BDb0cAMP.d.ts +806 -0
  17. package/dist/index.d.ts +112 -29
  18. package/dist/index.js +2036 -490
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +6 -6
  21. package/dist/kernel/index.d.ts +12 -9
  22. package/dist/kernel/index.js +73 -7
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/{mcp-servers-Dzgg4x1w.d.ts → mcp-servers-DR35ojJZ.d.ts} +3 -3
  25. package/dist/models/index.d.ts +2 -2
  26. package/dist/models/index.js +24 -1
  27. package/dist/models/index.js.map +1 -1
  28. package/dist/{multi-agent-fmkRHtof.d.ts → multi-agent-B9a6sflH.d.ts} +71 -3
  29. package/dist/observability/index.d.ts +2 -2
  30. package/dist/{path-resolver-DBjaoXFq.d.ts → path-resolver-Cl_q0u-R.d.ts} +2 -2
  31. package/dist/provider-runner-BXuADQqQ.d.ts +36 -0
  32. package/dist/sdd/index.d.ts +3 -3
  33. package/dist/{secret-scrubber-CicHLN4G.d.ts → secret-scrubber-CgG2tV2B.d.ts} +1 -1
  34. package/dist/{secret-scrubber-DF88luOe.d.ts → secret-scrubber-Cuy5afaQ.d.ts} +1 -1
  35. package/dist/security/index.d.ts +20 -4
  36. package/dist/security/index.js +37 -2
  37. package/dist/security/index.js.map +1 -1
  38. package/dist/{selector-BbJqiEP4.d.ts → selector-wT2fv9Fg.d.ts} +1 -1
  39. package/dist/{session-reader-Drq8RvJu.d.ts → session-reader-CcPi4BQ8.d.ts} +1 -1
  40. package/dist/{skill-DhfSizKv.d.ts → skill-C_7znCIC.d.ts} +2 -2
  41. package/dist/storage/index.d.ts +164 -6
  42. package/dist/storage/index.js +297 -2
  43. package/dist/storage/index.js.map +1 -1
  44. package/dist/{renderer-rk_1Swwc.d.ts → system-prompt-Dk1qm8ey.d.ts} +30 -2
  45. package/dist/{tool-executor-CpuJPYm9.d.ts → tool-executor-DKu4A6nB.d.ts} +5 -5
  46. package/dist/types/index.d.ts +16 -16
  47. package/dist/types/index.js +24 -1
  48. package/dist/types/index.js.map +1 -1
  49. package/dist/utils/index.d.ts +1 -1
  50. package/dist/utils/index.js +24 -1
  51. package/dist/utils/index.js.map +1 -1
  52. package/package.json +5 -1
  53. package/dist/plugin-DJk6LL8B.d.ts +0 -434
  54. package/dist/system-prompt-BC_8ypCG.d.ts +0 -23
package/dist/index.js CHANGED
@@ -395,6 +395,7 @@ var Pipeline = class {
395
395
  // src/kernel/events.ts
396
396
  var EventBus = class {
397
397
  listeners = /* @__PURE__ */ new Map();
398
+ wildcards = [];
398
399
  logger;
399
400
  setLogger(logger) {
400
401
  this.logger = logger;
@@ -421,24 +422,73 @@ var EventBus = class {
421
422
  this.off(event, wrapper);
422
423
  };
423
424
  }
425
+ /**
426
+ * Subscribe to all events whose name matches a glob-style prefix.
427
+ * `'tool.*'` matches `tool.started`, `tool.executed`, `tool.progress`, etc.
428
+ * `'*'` matches every event.
429
+ *
430
+ * The handler receives `(eventName, payload)` with the event name as a
431
+ * string and the payload as `unknown`. Use for logging, debugging, or
432
+ * metrics collection across a family of events.
433
+ *
434
+ * Returns an unsubscribe function.
435
+ */
436
+ onPattern(pattern, fn) {
437
+ const match = makePatternMatcher(pattern);
438
+ const entry = { match, fn };
439
+ this.wildcards.push(entry);
440
+ return () => {
441
+ const idx = this.wildcards.indexOf(entry);
442
+ if (idx >= 0) this.wildcards.splice(idx, 1);
443
+ };
444
+ }
445
+ /**
446
+ * Subscribe to all events whose name matches a RegExp.
447
+ * More flexible than `onPattern` — use when you need regex features
448
+ * (alternation, character classes, capture groups).
449
+ *
450
+ * Returns an unsubscribe function.
451
+ */
452
+ onRegex(regex, fn) {
453
+ const entry = { match: (e) => regex.test(e), fn };
454
+ this.wildcards.push(entry);
455
+ return () => {
456
+ const idx = this.wildcards.indexOf(entry);
457
+ if (idx >= 0) this.wildcards.splice(idx, 1);
458
+ };
459
+ }
424
460
  emit(event, payload) {
425
461
  const set = this.listeners.get(event);
426
- if (!set) return;
427
- for (const fn of set) {
428
- try {
429
- fn(payload);
430
- } catch (err) {
431
- this.logger?.error(`EventBus listener for "${event}" threw`, err);
462
+ if (set) {
463
+ for (const fn of set) {
464
+ try {
465
+ fn(payload);
466
+ } catch (err) {
467
+ this.logger?.error(`EventBus listener for "${event}" threw`, err);
468
+ }
469
+ }
470
+ }
471
+ if (this.wildcards.length > 0) {
472
+ const name = event;
473
+ for (const { match, fn } of this.wildcards) {
474
+ if (!match(name)) continue;
475
+ try {
476
+ fn(name, payload);
477
+ } catch (err) {
478
+ this.logger?.error(`EventBus wildcard listener for "${name}" threw`, err);
479
+ }
432
480
  }
433
481
  }
434
482
  }
435
483
  clear() {
436
484
  this.listeners.clear();
485
+ this.wildcards.length = 0;
437
486
  }
438
487
  /**
439
488
  * V2-D: introspection helper. Pass an `event` to count handlers for a
440
489
  * single key, or omit to get the total across every event. Used by the
441
490
  * leak-detection smoke test to flag handler accumulation across runs.
491
+ * Does NOT include wildcard listeners.
442
492
  */
443
493
  listenerCount(event) {
444
494
  if (event !== void 0) return this.listeners.get(event)?.size ?? 0;
@@ -446,7 +496,21 @@ var EventBus = class {
446
496
  for (const set of this.listeners.values()) total += set.size;
447
497
  return total;
448
498
  }
499
+ /**
500
+ * Number of wildcard listeners currently registered.
501
+ */
502
+ wildcardCount() {
503
+ return this.wildcards.length;
504
+ }
449
505
  };
506
+ function makePatternMatcher(pattern) {
507
+ if (pattern === "*") return () => true;
508
+ if (pattern.endsWith(".*")) {
509
+ const prefix = pattern.slice(0, -2);
510
+ return (e) => e.startsWith(`${prefix}.`);
511
+ }
512
+ return (e) => e === pattern;
513
+ }
450
514
 
451
515
  // src/kernel/tokens.ts
452
516
  var t = (name) => Symbol(name);
@@ -468,7 +532,9 @@ var TOKENS = {
468
532
  SystemPromptBuilder: t("SystemPromptBuilder"),
469
533
  SecretScrubber: t("SecretScrubber"),
470
534
  ModelsRegistry: t("ModelsRegistry"),
471
- ModeStore: t("ModeStore")
535
+ ModeStore: t("ModeStore"),
536
+ /** Replaces the entire provider call layer — retry, streaming, tracing. */
537
+ ProviderRunner: t("ProviderRunner")
472
538
  };
473
539
 
474
540
  // src/kernel/run-controller.ts
@@ -1539,7 +1605,7 @@ async function atomicWrite(targetPath, content, opts = {}) {
1539
1605
  if (mode !== void 0) {
1540
1606
  await fsp2.chmod(tmp, mode);
1541
1607
  }
1542
- await fsp2.rename(tmp, targetPath);
1608
+ await renameWithRetry(tmp, targetPath);
1543
1609
  } catch (err) {
1544
1610
  try {
1545
1611
  await fsp2.unlink(tmp);
@@ -1551,6 +1617,29 @@ async function atomicWrite(targetPath, content, opts = {}) {
1551
1617
  async function ensureDir(dir) {
1552
1618
  await fsp2.mkdir(dir, { recursive: true });
1553
1619
  }
1620
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
1621
+ async function renameWithRetry(from, to) {
1622
+ if (process.platform !== "win32") {
1623
+ await fsp2.rename(from, to);
1624
+ return;
1625
+ }
1626
+ const delays = [10, 25, 60, 120, 250];
1627
+ let lastErr;
1628
+ for (let i = 0; i <= delays.length; i++) {
1629
+ try {
1630
+ await fsp2.rename(from, to);
1631
+ return;
1632
+ } catch (err) {
1633
+ lastErr = err;
1634
+ const code = err?.code;
1635
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
1636
+ throw err;
1637
+ }
1638
+ await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
1639
+ }
1640
+ }
1641
+ throw lastErr;
1642
+ }
1554
1643
 
1555
1644
  // src/models/models-registry.ts
1556
1645
  var DEFAULT_URL = "https://models.dev/api.json";
@@ -3116,11 +3205,11 @@ function validateAgainstSchema(value, schema) {
3116
3205
  walk2(value, schema, "", errors);
3117
3206
  return { ok: errors.length === 0, errors };
3118
3207
  }
3119
- function walk2(value, schema, path17, errors) {
3208
+ function walk2(value, schema, path18, errors) {
3120
3209
  if (schema.enum !== void 0) {
3121
3210
  if (!schema.enum.some((e) => deepEqual(e, value))) {
3122
3211
  errors.push({
3123
- path: path17 || "<root>",
3212
+ path: path18 || "<root>",
3124
3213
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
3125
3214
  });
3126
3215
  return;
@@ -3129,7 +3218,7 @@ function walk2(value, schema, path17, errors) {
3129
3218
  if (typeof schema.type === "string") {
3130
3219
  if (!checkType(value, schema.type)) {
3131
3220
  errors.push({
3132
- path: path17 || "<root>",
3221
+ path: path18 || "<root>",
3133
3222
  message: `expected ${schema.type}, got ${describeType(value)}`
3134
3223
  });
3135
3224
  return;
@@ -3139,19 +3228,19 @@ function walk2(value, schema, path17, errors) {
3139
3228
  const obj = value;
3140
3229
  for (const req of schema.required ?? []) {
3141
3230
  if (!(req in obj)) {
3142
- errors.push({ path: joinPath(path17, req), message: "required property missing" });
3231
+ errors.push({ path: joinPath(path18, req), message: "required property missing" });
3143
3232
  }
3144
3233
  }
3145
3234
  if (schema.properties) {
3146
3235
  for (const [key, subSchema] of Object.entries(schema.properties)) {
3147
3236
  if (key in obj) {
3148
- walk2(obj[key], subSchema, joinPath(path17, key), errors);
3237
+ walk2(obj[key], subSchema, joinPath(path18, key), errors);
3149
3238
  }
3150
3239
  }
3151
3240
  }
3152
3241
  }
3153
3242
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
3154
- value.forEach((item, i) => walk2(item, schema.items, `${path17}[${i}]`, errors));
3243
+ value.forEach((item, i) => walk2(item, schema.items, `${path18}[${i}]`, errors));
3155
3244
  }
3156
3245
  }
3157
3246
  function checkType(value, type) {
@@ -3439,6 +3528,12 @@ var FileSessionWriter = class {
3439
3528
  tokenIn = 0;
3440
3529
  tokenOut = 0;
3441
3530
  filePath;
3531
+ /** Public accessor for the JSONL path — required by SessionWriter so
3532
+ * observability surfaces (`/fleet log`, FleetPanel) can locate the
3533
+ * transcript without recomputing the path from session metadata. */
3534
+ get transcriptPath() {
3535
+ return this.filePath || void 0;
3536
+ }
3442
3537
  initDone = false;
3443
3538
  resumed;
3444
3539
  appendFailCount = 0;
@@ -4368,6 +4463,272 @@ var SessionAnalyzer = class {
4368
4463
  return last - first;
4369
4464
  }
4370
4465
  };
4466
+ async function loadTodosCheckpoint(filePath) {
4467
+ let raw;
4468
+ try {
4469
+ raw = await fsp2.readFile(filePath, "utf8");
4470
+ } catch {
4471
+ return null;
4472
+ }
4473
+ try {
4474
+ const parsed = JSON.parse(raw);
4475
+ if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
4476
+ return parsed.todos.filter(
4477
+ (t2) => !!t2 && typeof t2.id === "string" && typeof t2.content === "string" && typeof t2.status === "string"
4478
+ );
4479
+ } catch {
4480
+ return null;
4481
+ }
4482
+ }
4483
+ async function saveTodosCheckpoint(filePath, sessionId, todos) {
4484
+ const payload = {
4485
+ version: 1,
4486
+ sessionId,
4487
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4488
+ todos: [...todos]
4489
+ };
4490
+ try {
4491
+ await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
4492
+ } catch (err) {
4493
+ console.warn(
4494
+ "[todos-checkpoint] save failed:",
4495
+ err instanceof Error ? err.message : String(err)
4496
+ );
4497
+ }
4498
+ }
4499
+ function attachTodosCheckpoint(state, filePath, sessionId) {
4500
+ let timer = null;
4501
+ let pending = null;
4502
+ const flush = () => {
4503
+ timer = null;
4504
+ if (pending) {
4505
+ void saveTodosCheckpoint(filePath, sessionId, pending);
4506
+ pending = null;
4507
+ }
4508
+ };
4509
+ const unsubscribe = state.onChange((change) => {
4510
+ if (change.kind !== "todos_replaced") return;
4511
+ pending = change.todos;
4512
+ if (timer) clearTimeout(timer);
4513
+ timer = setTimeout(flush, 150);
4514
+ });
4515
+ return () => {
4516
+ unsubscribe();
4517
+ if (timer) {
4518
+ clearTimeout(timer);
4519
+ flush();
4520
+ }
4521
+ };
4522
+ }
4523
+ async function loadPlan(filePath) {
4524
+ let raw;
4525
+ try {
4526
+ raw = await fsp2.readFile(filePath, "utf8");
4527
+ } catch {
4528
+ return null;
4529
+ }
4530
+ try {
4531
+ const parsed = JSON.parse(raw);
4532
+ if (parsed?.version !== 1 || !Array.isArray(parsed.items)) return null;
4533
+ return parsed;
4534
+ } catch {
4535
+ return null;
4536
+ }
4537
+ }
4538
+ async function savePlan(filePath, plan) {
4539
+ try {
4540
+ await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
4541
+ } catch (err) {
4542
+ console.warn(
4543
+ "[plan-store] save failed:",
4544
+ err instanceof Error ? err.message : String(err)
4545
+ );
4546
+ }
4547
+ }
4548
+ function emptyPlan(sessionId, title) {
4549
+ return {
4550
+ version: 1,
4551
+ sessionId,
4552
+ title,
4553
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4554
+ items: []
4555
+ };
4556
+ }
4557
+ function addPlanItem(plan, title, details) {
4558
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4559
+ const item = {
4560
+ id: `plan_${Date.now()}_${randomUUID().slice(0, 6)}`,
4561
+ title,
4562
+ details,
4563
+ status: "open",
4564
+ createdAt: now,
4565
+ updatedAt: now
4566
+ };
4567
+ return {
4568
+ plan: { ...plan, items: [...plan.items, item], updatedAt: now },
4569
+ item
4570
+ };
4571
+ }
4572
+ function removePlanItem(plan, idOrIndex) {
4573
+ const idx = matchIndex(plan, idOrIndex);
4574
+ if (idx === -1) return plan;
4575
+ return {
4576
+ ...plan,
4577
+ items: plan.items.filter((_, i) => i !== idx),
4578
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4579
+ };
4580
+ }
4581
+ function setPlanItemStatus(plan, idOrIndex, status) {
4582
+ const idx = matchIndex(plan, idOrIndex);
4583
+ if (idx === -1) return plan;
4584
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4585
+ const items = plan.items.map(
4586
+ (it, i) => i === idx ? { ...it, status, updatedAt: now } : it
4587
+ );
4588
+ return { ...plan, items, updatedAt: now };
4589
+ }
4590
+ function clearPlan(plan) {
4591
+ return { ...plan, items: [], updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
4592
+ }
4593
+ function formatPlan(plan) {
4594
+ if (plan.items.length === 0) return "Plan is empty.";
4595
+ const lines = [];
4596
+ if (plan.title) lines.push(`# ${plan.title}`);
4597
+ plan.items.forEach((it, i) => {
4598
+ const mark = it.status === "done" ? "[x]" : it.status === "in_progress" ? "[~]" : "[ ]";
4599
+ lines.push(`${i + 1}. ${mark} ${it.title}`);
4600
+ if (it.details) {
4601
+ for (const line of it.details.split("\n")) lines.push(` ${line}`);
4602
+ }
4603
+ });
4604
+ return lines.join("\n");
4605
+ }
4606
+ function matchIndex(plan, idOrIndex) {
4607
+ const asNum = Number.parseInt(idOrIndex, 10);
4608
+ if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= plan.items.length) return asNum - 1;
4609
+ const byId = plan.items.findIndex((it) => it.id === idOrIndex);
4610
+ if (byId !== -1) return byId;
4611
+ const lower = idOrIndex.toLowerCase();
4612
+ return plan.items.findIndex((it) => it.title.toLowerCase().includes(lower));
4613
+ }
4614
+ function attachPlanCheckpoint(_state, _filePath, _sessionId) {
4615
+ return () => void 0;
4616
+ }
4617
+ async function loadDirectorState(filePath) {
4618
+ let raw;
4619
+ try {
4620
+ raw = await fsp2.readFile(filePath, "utf8");
4621
+ } catch {
4622
+ return null;
4623
+ }
4624
+ try {
4625
+ const parsed = JSON.parse(raw);
4626
+ if (parsed?.version !== 1) return null;
4627
+ return parsed;
4628
+ } catch {
4629
+ return null;
4630
+ }
4631
+ }
4632
+ var DirectorStateCheckpoint = class {
4633
+ snapshot;
4634
+ filePath;
4635
+ timer = null;
4636
+ debounceMs;
4637
+ writing = false;
4638
+ rewriteRequested = false;
4639
+ constructor(filePath, init, debounceMs = 250) {
4640
+ this.filePath = filePath;
4641
+ this.debounceMs = debounceMs;
4642
+ this.snapshot = {
4643
+ version: 1,
4644
+ directorRunId: init.directorRunId,
4645
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4646
+ spawnCount: 0,
4647
+ maxSpawns: init.maxSpawns,
4648
+ spawnDepth: init.spawnDepth,
4649
+ maxSpawnDepth: init.maxSpawnDepth,
4650
+ subagents: [],
4651
+ tasks: []
4652
+ };
4653
+ }
4654
+ current() {
4655
+ return this.snapshot;
4656
+ }
4657
+ recordSpawn(sub, spawnCount) {
4658
+ this.snapshot = {
4659
+ ...this.snapshot,
4660
+ spawnCount,
4661
+ subagents: [...this.snapshot.subagents.filter((s) => s.id !== sub.id), sub]
4662
+ };
4663
+ this.bumpUpdatedAt();
4664
+ this.schedule();
4665
+ }
4666
+ recordTaskAssigned(task) {
4667
+ const exists = this.snapshot.tasks.some((t2) => t2.taskId === task.taskId);
4668
+ this.snapshot = {
4669
+ ...this.snapshot,
4670
+ tasks: exists ? this.snapshot.tasks.map((t2) => t2.taskId === task.taskId ? { ...t2, ...task } : t2) : [...this.snapshot.tasks, task]
4671
+ };
4672
+ this.bumpUpdatedAt();
4673
+ this.schedule();
4674
+ }
4675
+ recordTaskStatus(taskId, patch) {
4676
+ this.snapshot = {
4677
+ ...this.snapshot,
4678
+ tasks: this.snapshot.tasks.map(
4679
+ (t2) => t2.taskId === taskId ? { ...t2, ...patch } : t2
4680
+ )
4681
+ };
4682
+ this.bumpUpdatedAt();
4683
+ this.schedule();
4684
+ }
4685
+ setUsage(usage) {
4686
+ this.snapshot = { ...this.snapshot, usage };
4687
+ this.bumpUpdatedAt();
4688
+ this.schedule();
4689
+ }
4690
+ /** Force a synchronous flush — used by Director.shutdown(). */
4691
+ async flush() {
4692
+ if (this.timer) {
4693
+ clearTimeout(this.timer);
4694
+ this.timer = null;
4695
+ }
4696
+ await this.persist();
4697
+ }
4698
+ bumpUpdatedAt() {
4699
+ this.snapshot = { ...this.snapshot, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
4700
+ }
4701
+ schedule() {
4702
+ if (this.timer) return;
4703
+ this.timer = setTimeout(() => {
4704
+ this.timer = null;
4705
+ void this.persist();
4706
+ }, this.debounceMs);
4707
+ }
4708
+ async persist() {
4709
+ if (this.writing) {
4710
+ this.rewriteRequested = true;
4711
+ return;
4712
+ }
4713
+ this.writing = true;
4714
+ try {
4715
+ await atomicWrite(this.filePath, JSON.stringify(this.snapshot, null, 2), {
4716
+ mode: 384
4717
+ });
4718
+ } catch (err) {
4719
+ console.warn(
4720
+ "[director-state] checkpoint write failed:",
4721
+ err instanceof Error ? err.message : String(err)
4722
+ );
4723
+ } finally {
4724
+ this.writing = false;
4725
+ if (this.rewriteRequested) {
4726
+ this.rewriteRequested = false;
4727
+ this.schedule();
4728
+ }
4729
+ }
4730
+ }
4731
+ };
4371
4732
  var DefaultPermissionPolicy = class {
4372
4733
  policy = {};
4373
4734
  loaded = false;
@@ -4476,6 +4837,18 @@ var DefaultPermissionPolicy = class {
4476
4837
  return void 0;
4477
4838
  }
4478
4839
  };
4840
+ var AutoApprovePermissionPolicy = class {
4841
+ async evaluate(tool) {
4842
+ if (tool.permission === "deny") {
4843
+ return { permission: "deny", source: "default", reason: "tool default deny" };
4844
+ }
4845
+ return { permission: "auto", source: "yolo" };
4846
+ }
4847
+ async trust() {
4848
+ }
4849
+ async reload() {
4850
+ }
4851
+ };
4479
4852
  var DefaultSkillLoader = class {
4480
4853
  dirs;
4481
4854
  cache;
@@ -4605,103 +4978,399 @@ function parseDescription(raw) {
4605
4978
  return { trigger, scope };
4606
4979
  }
4607
4980
 
4608
- // src/execution/intelligent-compactor.ts
4609
- var IntelligentCompactor = class {
4610
- provider;
4611
- warnThreshold;
4612
- softThreshold;
4613
- hardThreshold;
4614
- maxContext;
4615
- preserveK;
4616
- eliseThreshold;
4617
- summarizerPrompt;
4618
- summarizerModel;
4619
- constructor(opts) {
4620
- this.provider = opts.provider;
4621
- this.warnThreshold = opts.warnThreshold ?? 0.6;
4622
- this.softThreshold = opts.softThreshold ?? 0.75;
4623
- this.hardThreshold = opts.hardThreshold ?? 0.9;
4624
- this.maxContext = opts.maxContext ?? 128e3;
4625
- this.preserveK = opts.preserveK ?? 4;
4626
- this.eliseThreshold = opts.eliseThreshold ?? 500;
4627
- this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of conversation messages, produce a concise but complete summary that preserves all factual information, decisions made, and any state changes (e.g. file edits, todo updates). Do not add commentary. Output only the summary.";
4628
- this.summarizerModel = opts.summarizerModel;
4629
- }
4630
- async compact(ctx, opts = {}) {
4631
- const beforeTokens = this.estimateTokens(ctx.messages);
4632
- const reductions = [];
4633
- const load = beforeTokens / this.maxContext;
4634
- const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
4635
- const saved1 = this.eliseOldToolResults(ctx);
4636
- if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
4637
- if (aggressive) {
4638
- const saved2 = await this.summarizeAncientTurns(ctx);
4639
- if (saved2 > 0) reductions.push({ phase: "summary", saved: saved2 });
4640
- } else if (load >= this.warnThreshold) {
4641
- const saved2 = this.lightweightCompact(ctx);
4642
- if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
4643
- }
4644
- const afterTokens = this.estimateTokens(ctx.messages);
4645
- return { before: beforeTokens, after: afterTokens, reductions };
4646
- }
4647
- async summarizeAncientTurns(ctx) {
4648
- const messages = ctx.messages;
4649
- const cutoff = Math.max(0, messages.length - this.preserveK * 2);
4650
- if (cutoff <= 2) return 0;
4651
- const boundary = this.findSafeBoundary(messages, 0, cutoff);
4652
- if (boundary <= 1) return 0;
4653
- const toSummarize = messages.slice(0, boundary);
4654
- const removedTokens = this.estimateTokens(toSummarize);
4655
- let summaryText;
4656
- try {
4657
- summaryText = await this.callSummarizer(toSummarize, ctx);
4658
- } catch {
4659
- summaryText = `[${toSummarize.length} earlier turns omitted \u2014 key decisions and file states preserved in context]`;
4660
- }
4661
- const summaryMsg = {
4662
- role: "system",
4663
- content: `[prior_turns_summary: ${summaryText}]`
4664
- };
4665
- const summaryTokens = this.estimateTokens([summaryMsg]);
4666
- const tail = ctx.messages.slice(boundary);
4667
- ctx.state.replaceMessages([summaryMsg, ...tail]);
4668
- return Math.max(0, removedTokens - summaryTokens);
4669
- }
4670
- findSafeBoundary(messages, from, to) {
4671
- for (let i = to; i >= from; i--) {
4672
- const m = messages[i];
4673
- if (!m) continue;
4674
- if (m.role === "user" && this.hasTextContent(m)) {
4675
- return this.findExchangeStart(messages, i);
4981
+ // src/core/streaming-response-builder.ts
4982
+ function buildResponse(state) {
4983
+ const content = [];
4984
+ for (const b of state.blockOrder) {
4985
+ if (b.kind === "text") {
4986
+ const txt = state.textBuffers[b.idx] ?? "";
4987
+ if (txt) content.push({ type: "text", text: txt });
4988
+ } else if (b.kind === "thinking") {
4989
+ const t2 = state.thinking[b.idx];
4990
+ if (!t2) continue;
4991
+ if (!t2.textBuf && !t2.signature) continue;
4992
+ const block = { type: "thinking", thinking: t2.textBuf };
4993
+ if (t2.signature) block.signature = t2.signature;
4994
+ if (t2.providerMeta && Object.keys(t2.providerMeta).length > 0) {
4995
+ block.providerMeta = t2.providerMeta;
4676
4996
  }
4677
- }
4678
- return -1;
4679
- }
4680
- findExchangeStart(messages, userIndex) {
4681
- for (let i = userIndex - 1; i >= 0; i--) {
4682
- const m = messages[i];
4683
- if (!m) continue;
4684
- if (m.role === "assistant") {
4685
- const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
4686
- if (!hasToolUse) {
4687
- return i + 1;
4997
+ content.push(block);
4998
+ } else {
4999
+ const tb = state.tools.get(b.id);
5000
+ if (tb) {
5001
+ const block = {
5002
+ type: "tool_use",
5003
+ id: b.id,
5004
+ name: tb.name,
5005
+ input: tb.input ?? {}
5006
+ };
5007
+ if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
5008
+ block.providerMeta = tb.providerMeta;
4688
5009
  }
4689
- } else if (m.role !== "user") ; else {
4690
- return i;
5010
+ content.push(block);
4691
5011
  }
4692
5012
  }
4693
- return 0;
4694
5013
  }
4695
- async callSummarizer(messages, ctx) {
4696
- const prompt = [
4697
- { type: "text", text: this.summarizerPrompt },
4698
- { type: "text", text: "\n\nConversation to summarize:\n" },
4699
- ...this.messagesToText(messages)
4700
- ];
4701
- const req = {
4702
- model: this.summarizerModel ?? ctx.model,
4703
- system: prompt,
4704
- messages: [],
5014
+ if (content.length === 0) content.push({ type: "text", text: "" });
5015
+ return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
5016
+ }
5017
+ function createStreamingState(model) {
5018
+ return {
5019
+ model,
5020
+ stopReason: "end_turn",
5021
+ usage: { input: 0, output: 0 },
5022
+ textBuffers: [],
5023
+ currentTextIndex: -1,
5024
+ tools: /* @__PURE__ */ new Map(),
5025
+ thinking: [],
5026
+ currentThinkingIndex: -1,
5027
+ blockOrder: []
5028
+ };
5029
+ }
5030
+ function handleMessageStart(state, model) {
5031
+ state.model = model;
5032
+ }
5033
+ function handleContentBlockStart(state, ev) {
5034
+ const kind = ev.kind ?? "text";
5035
+ if (kind === "text") {
5036
+ state.currentTextIndex = state.textBuffers.length;
5037
+ state.textBuffers.push("");
5038
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
5039
+ } else if (kind === "tool_use") {
5040
+ const id = ev.id ?? crypto.randomUUID();
5041
+ state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
5042
+ state.blockOrder.push({ kind: "tool", id });
5043
+ state.currentTextIndex = -1;
5044
+ } else if (kind === "thinking") {
5045
+ state.currentThinkingIndex = state.thinking.length;
5046
+ state.thinking.push({
5047
+ textBuf: "",
5048
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
5049
+ });
5050
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
5051
+ state.currentTextIndex = -1;
5052
+ }
5053
+ }
5054
+ function handleContentBlockStop(state, ev) {
5055
+ }
5056
+ function handleTextDelta(state, text) {
5057
+ if (state.currentTextIndex === -1) {
5058
+ state.currentTextIndex = state.textBuffers.length;
5059
+ state.textBuffers.push("");
5060
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
5061
+ }
5062
+ state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
5063
+ }
5064
+ function handleToolUseStart(state, ev) {
5065
+ state.currentTextIndex = -1;
5066
+ state.tools.set(ev.id, { name: ev.name, partial: "" });
5067
+ state.blockOrder.push({ kind: "tool", id: ev.id });
5068
+ }
5069
+ function handleToolUseInputDelta(state, ev) {
5070
+ const t2 = state.tools.get(ev.id);
5071
+ if (t2) t2.partial += ev.partial;
5072
+ }
5073
+ function safeJsonOrRaw(s) {
5074
+ if (!s) return {};
5075
+ try {
5076
+ return JSON.parse(s);
5077
+ } catch {
5078
+ return { _raw: s };
5079
+ }
5080
+ }
5081
+ function handleToolUseStop(state, ev) {
5082
+ const t2 = state.tools.get(ev.id);
5083
+ if (t2) {
5084
+ t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
5085
+ if (ev.providerMeta) t2.providerMeta = ev.providerMeta;
5086
+ }
5087
+ state.currentTextIndex = -1;
5088
+ }
5089
+ function handleThinkingStart(state, ev) {
5090
+ state.currentThinkingIndex = state.thinking.length;
5091
+ state.thinking.push({
5092
+ textBuf: "",
5093
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
5094
+ });
5095
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
5096
+ state.currentTextIndex = -1;
5097
+ }
5098
+ function handleThinkingDelta(state, text) {
5099
+ if (state.currentThinkingIndex === -1) {
5100
+ handleThinkingStart(state, {});
5101
+ }
5102
+ const t2 = state.thinking[state.currentThinkingIndex];
5103
+ if (t2) t2.textBuf += text;
5104
+ }
5105
+ function handleThinkingSignature(state, signature) {
5106
+ if (state.currentThinkingIndex === -1) {
5107
+ handleThinkingStart(state, {});
5108
+ }
5109
+ const t2 = state.thinking[state.currentThinkingIndex];
5110
+ if (t2) t2.signature = signature;
5111
+ }
5112
+ function handleThinkingStop(state) {
5113
+ state.currentThinkingIndex = -1;
5114
+ }
5115
+ function handleMessageStop(state, ev) {
5116
+ state.stopReason = ev.stopReason ?? "end_turn";
5117
+ state.usage = ev.usage ?? { input: 0, output: 0 };
5118
+ }
5119
+ async function streamProviderToResponse(provider, req, signal, ctx, events) {
5120
+ const state = createStreamingState(req.model);
5121
+ const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
5122
+ try {
5123
+ for (; ; ) {
5124
+ const next = await iter.next();
5125
+ if (next.done) break;
5126
+ const ev = next.value;
5127
+ switch (ev.type) {
5128
+ case "message_start":
5129
+ handleMessageStart(state, ev.model);
5130
+ break;
5131
+ case "content_block_start":
5132
+ handleContentBlockStart(state, ev);
5133
+ break;
5134
+ case "content_block_stop":
5135
+ handleContentBlockStop(state, ev);
5136
+ break;
5137
+ case "text_delta":
5138
+ handleTextDelta(state, ev.text);
5139
+ events.emit("provider.text_delta", { ctx, text: ev.text });
5140
+ break;
5141
+ case "tool_use_start":
5142
+ handleToolUseStart(state, ev);
5143
+ events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
5144
+ break;
5145
+ case "tool_use_input_delta":
5146
+ handleToolUseInputDelta(state, ev);
5147
+ break;
5148
+ case "tool_use_stop":
5149
+ handleToolUseStop(state, ev);
5150
+ events.emit("provider.tool_use_stop", { ctx, id: ev.id });
5151
+ break;
5152
+ case "thinking_start":
5153
+ handleThinkingStart(state, ev);
5154
+ break;
5155
+ case "thinking_delta":
5156
+ handleThinkingDelta(state, ev.text);
5157
+ events.emit("provider.thinking_delta", { ctx, text: ev.text });
5158
+ break;
5159
+ case "thinking_signature":
5160
+ handleThinkingSignature(state, ev.signature);
5161
+ break;
5162
+ case "thinking_stop":
5163
+ handleThinkingStop(state);
5164
+ break;
5165
+ case "message_stop":
5166
+ handleMessageStop(state, ev);
5167
+ break;
5168
+ }
5169
+ }
5170
+ } catch (err) {
5171
+ if (signal.aborted) {
5172
+ state.stopReason = "end_turn";
5173
+ return buildResponse(state);
5174
+ }
5175
+ throw err;
5176
+ } finally {
5177
+ try {
5178
+ let drainTimer = null;
5179
+ try {
5180
+ await Promise.race([
5181
+ Promise.resolve(iter.return?.()),
5182
+ new Promise((resolve4) => {
5183
+ drainTimer = setTimeout(resolve4, 500);
5184
+ })
5185
+ ]);
5186
+ } finally {
5187
+ if (drainTimer) clearTimeout(drainTimer);
5188
+ }
5189
+ } catch {
5190
+ }
5191
+ }
5192
+ return buildResponse(state);
5193
+ }
5194
+
5195
+ // src/core/provider-runner.ts
5196
+ async function runProviderWithRetry(opts) {
5197
+ const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
5198
+ let attempt = 0;
5199
+ for (; ; ) {
5200
+ const span = tracer?.startSpan("provider.complete", {
5201
+ "provider.id": provider.id,
5202
+ "provider.model": request.model,
5203
+ "provider.streaming": provider.capabilities.streaming,
5204
+ "provider.attempt": attempt
5205
+ });
5206
+ try {
5207
+ const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
5208
+ span?.setAttribute("provider.stopReason", res.stopReason);
5209
+ span?.setAttribute("provider.usage_in", res.usage.input);
5210
+ span?.setAttribute("provider.usage_out", res.usage.output);
5211
+ span?.end();
5212
+ return res;
5213
+ } catch (err) {
5214
+ if (err instanceof Error) span?.recordError(err);
5215
+ span?.end();
5216
+ if (signal.aborted) throw err;
5217
+ const isProviderErr = err instanceof ProviderError;
5218
+ const errAsErr = err instanceof Error ? err : new Error(String(err));
5219
+ const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
5220
+ const description = isProviderErr ? err.describe() : errAsErr.message;
5221
+ if (!canRetry) {
5222
+ if (isProviderErr) {
5223
+ events.emit("provider.error", {
5224
+ providerId: err.providerId,
5225
+ status: err.status,
5226
+ description,
5227
+ retryable: false
5228
+ });
5229
+ }
5230
+ throw err;
5231
+ }
5232
+ const delay = Math.round(retry.delayMs(attempt));
5233
+ const attemptNum = attempt + 1;
5234
+ logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
5235
+ if (isProviderErr) {
5236
+ events.emit("provider.retry", {
5237
+ providerId: err.providerId,
5238
+ attempt: attemptNum,
5239
+ delayMs: delay,
5240
+ status: err.status,
5241
+ description
5242
+ });
5243
+ }
5244
+ await new Promise((resolve4, reject) => {
5245
+ let settled = false;
5246
+ const onAbort = () => {
5247
+ if (settled) return;
5248
+ settled = true;
5249
+ clearTimeout(t2);
5250
+ reject(new Error("aborted"));
5251
+ };
5252
+ const t2 = setTimeout(() => {
5253
+ if (settled) return;
5254
+ settled = true;
5255
+ clearTimeout(t2);
5256
+ signal.removeEventListener("abort", onAbort);
5257
+ resolve4();
5258
+ }, delay);
5259
+ if (signal.aborted) {
5260
+ onAbort();
5261
+ return;
5262
+ }
5263
+ signal.addEventListener("abort", onAbort, { once: true });
5264
+ });
5265
+ attempt++;
5266
+ }
5267
+ }
5268
+ }
5269
+
5270
+ // src/execution/provider-runner-impl.ts
5271
+ var DefaultProviderRunner = class {
5272
+ async run(opts) {
5273
+ return runProviderWithRetry(opts);
5274
+ }
5275
+ };
5276
+
5277
+ // src/execution/intelligent-compactor.ts
5278
+ var IntelligentCompactor = class {
5279
+ provider;
5280
+ warnThreshold;
5281
+ softThreshold;
5282
+ hardThreshold;
5283
+ maxContext;
5284
+ preserveK;
5285
+ eliseThreshold;
5286
+ summarizerPrompt;
5287
+ summarizerModel;
5288
+ constructor(opts) {
5289
+ this.provider = opts.provider;
5290
+ this.warnThreshold = opts.warnThreshold ?? 0.6;
5291
+ this.softThreshold = opts.softThreshold ?? 0.75;
5292
+ this.hardThreshold = opts.hardThreshold ?? 0.9;
5293
+ this.maxContext = opts.maxContext ?? 128e3;
5294
+ this.preserveK = opts.preserveK ?? 4;
5295
+ this.eliseThreshold = opts.eliseThreshold ?? 500;
5296
+ this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of conversation messages, produce a concise but complete summary that preserves all factual information, decisions made, and any state changes (e.g. file edits, todo updates). Do not add commentary. Output only the summary.";
5297
+ this.summarizerModel = opts.summarizerModel;
5298
+ }
5299
+ async compact(ctx, opts = {}) {
5300
+ const beforeTokens = this.estimateTokens(ctx.messages);
5301
+ const reductions = [];
5302
+ const load = beforeTokens / this.maxContext;
5303
+ const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
5304
+ const saved1 = this.eliseOldToolResults(ctx);
5305
+ if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
5306
+ if (aggressive) {
5307
+ const saved2 = await this.summarizeAncientTurns(ctx);
5308
+ if (saved2 > 0) reductions.push({ phase: "summary", saved: saved2 });
5309
+ } else if (load >= this.warnThreshold) {
5310
+ const saved2 = this.lightweightCompact(ctx);
5311
+ if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
5312
+ }
5313
+ const afterTokens = this.estimateTokens(ctx.messages);
5314
+ return { before: beforeTokens, after: afterTokens, reductions };
5315
+ }
5316
+ async summarizeAncientTurns(ctx) {
5317
+ const messages = ctx.messages;
5318
+ const cutoff = Math.max(0, messages.length - this.preserveK * 2);
5319
+ if (cutoff <= 2) return 0;
5320
+ const boundary = this.findSafeBoundary(messages, 0, cutoff);
5321
+ if (boundary <= 1) return 0;
5322
+ const toSummarize = messages.slice(0, boundary);
5323
+ const removedTokens = this.estimateTokens(toSummarize);
5324
+ let summaryText;
5325
+ try {
5326
+ summaryText = await this.callSummarizer(toSummarize, ctx);
5327
+ } catch {
5328
+ summaryText = `[${toSummarize.length} earlier turns omitted \u2014 key decisions and file states preserved in context]`;
5329
+ }
5330
+ const summaryMsg = {
5331
+ role: "system",
5332
+ content: `[prior_turns_summary: ${summaryText}]`
5333
+ };
5334
+ const summaryTokens = this.estimateTokens([summaryMsg]);
5335
+ const tail = ctx.messages.slice(boundary);
5336
+ ctx.state.replaceMessages([summaryMsg, ...tail]);
5337
+ return Math.max(0, removedTokens - summaryTokens);
5338
+ }
5339
+ findSafeBoundary(messages, from, to) {
5340
+ for (let i = to; i >= from; i--) {
5341
+ const m = messages[i];
5342
+ if (!m) continue;
5343
+ if (m.role === "user" && this.hasTextContent(m)) {
5344
+ return this.findExchangeStart(messages, i);
5345
+ }
5346
+ }
5347
+ return -1;
5348
+ }
5349
+ findExchangeStart(messages, userIndex) {
5350
+ for (let i = userIndex - 1; i >= 0; i--) {
5351
+ const m = messages[i];
5352
+ if (!m) continue;
5353
+ if (m.role === "assistant") {
5354
+ const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
5355
+ if (!hasToolUse) {
5356
+ return i + 1;
5357
+ }
5358
+ } else if (m.role !== "user") ; else {
5359
+ return i;
5360
+ }
5361
+ }
5362
+ return 0;
5363
+ }
5364
+ async callSummarizer(messages, ctx) {
5365
+ const prompt = [
5366
+ { type: "text", text: this.summarizerPrompt },
5367
+ { type: "text", text: "\n\nConversation to summarize:\n" },
5368
+ ...this.messagesToText(messages)
5369
+ ];
5370
+ const req = {
5371
+ model: this.summarizerModel ?? ctx.model,
5372
+ system: prompt,
5373
+ messages: [],
4705
5374
  maxTokens: 1024
4706
5375
  };
4707
5376
  const ac = ctx.signal ? void 0 : new AbortController();
@@ -5516,6 +6185,10 @@ var FleetBus = class {
5516
6185
  "iteration.started",
5517
6186
  "iteration.completed",
5518
6187
  "provider.text_delta",
6188
+ // Subagent extended-thinking output. Forwarded so the FleetPanel /
6189
+ // /fleet log can surface "the planner is thinking…" instead of a
6190
+ // silent gap between iteration.started and the first text_delta.
6191
+ "provider.thinking_delta",
5519
6192
  "provider.response",
5520
6193
  "provider.retry",
5521
6194
  "provider.error",
@@ -5764,14 +6437,34 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5764
6437
  completedResults = [];
5765
6438
  totalIterations = 0;
5766
6439
  inFlight = 0;
6440
+ /**
6441
+ * Subagents currently being stopped. Set on entry to `stop()`, cleared
6442
+ * once `recordCompletion` lands the terminal TaskResult. Used by
6443
+ * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
6444
+ * to a subagent the caller has already asked to terminate — closes the
6445
+ * assign+terminate race where a fresh task could land on a worker that
6446
+ * was about to be killed.
6447
+ */
6448
+ terminating = /* @__PURE__ */ new Set();
5767
6449
  constructor(config, options = {}) {
5768
6450
  super();
5769
6451
  this.coordinatorId = config.coordinatorId;
5770
6452
  this.config = config;
5771
6453
  this.runner = options.runner;
5772
6454
  }
6455
+ /**
6456
+ * Replace the runner after construction. Used when the runner depends
6457
+ * on infrastructure (e.g. FleetBus) that isn't available until after
6458
+ * the coordinator's owning Director is built.
6459
+ */
6460
+ setRunner(runner) {
6461
+ this.runner = runner;
6462
+ }
5773
6463
  async spawn(subagent) {
5774
6464
  const id = subagent.id || randomUUID();
6465
+ if (this.subagents.has(id)) {
6466
+ throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
6467
+ }
5775
6468
  const context = {
5776
6469
  subagentId: id,
5777
6470
  tasks: [],
@@ -5815,6 +6508,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5815
6508
  async stop(subagentId) {
5816
6509
  const subagent = this.subagents.get(subagentId);
5817
6510
  if (!subagent) return;
6511
+ this.terminating.add(subagentId);
5818
6512
  subagent.abortController.abort();
5819
6513
  subagent.status = "stopped";
5820
6514
  subagent.currentTask = void 0;
@@ -5822,6 +6516,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5822
6516
  this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
5823
6517
  }
5824
6518
  async stopAll() {
6519
+ this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
5825
6520
  await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
5826
6521
  }
5827
6522
  getStatus() {
@@ -5855,7 +6550,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5855
6550
  tryDispatchNext() {
5856
6551
  while (this.canDispatch()) {
5857
6552
  const subagentId = this.findIdleSubagent();
5858
- if (!subagentId) return;
6553
+ if (!subagentId) {
6554
+ if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
6555
+ this.drainPendingAsAborted(
6556
+ "No live subagent available \u2014 all stopped or mid-termination"
6557
+ );
6558
+ }
6559
+ return;
6560
+ }
5859
6561
  const task = this.pendingTasks.shift();
5860
6562
  if (!task) return;
5861
6563
  this.runDispatched(subagentId, task).catch((err) => {
@@ -5863,7 +6565,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5863
6565
  subagentId,
5864
6566
  taskId: task.id,
5865
6567
  status: "failed",
5866
- error: err instanceof Error ? err.message : String(err),
6568
+ error: classifySubagentError(err),
5867
6569
  iterations: 0,
5868
6570
  toolCalls: 0,
5869
6571
  durationMs: 0
@@ -5877,13 +6579,76 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5877
6579
  }
5878
6580
  findIdleSubagent() {
5879
6581
  for (const [id, s] of this.subagents) {
5880
- if (s.status === "idle") return id;
6582
+ if (s.status === "idle" && !this.terminating.has(id)) return id;
5881
6583
  }
5882
6584
  return null;
5883
6585
  }
6586
+ /**
6587
+ * Returns true iff at least one spawned subagent could still
6588
+ * process a task. A "live" subagent is one that is not stopped
6589
+ * AND not mid-termination — `running` workers count because they
6590
+ * will eventually finish and become idle.
6591
+ *
6592
+ * When no subagent has ever been spawned, returns `true` so a
6593
+ * pre-spawn `assign()` simply queues (legacy behaviour). The
6594
+ * dead-end detection only fires after `stop()` has retired every
6595
+ * spawned worker.
6596
+ *
6597
+ * Used by `tryDispatchNext` to detect a dead-end pending queue.
6598
+ */
6599
+ hasLiveSubagent() {
6600
+ if (this.subagents.size === 0) return true;
6601
+ for (const [id, s] of this.subagents) {
6602
+ if (s.status !== "stopped" && !this.terminating.has(id)) return true;
6603
+ }
6604
+ return false;
6605
+ }
6606
+ /**
6607
+ * Drain every pending task with a synthetic `aborted_by_parent`
6608
+ * completion event. Same shape as the `stopAll()` drain — we go
6609
+ * around `recordCompletion` because pending tasks were never
6610
+ * counted in `inFlight` and routing them through would trip the
6611
+ * underflow guard on every task after the first.
6612
+ */
6613
+ drainPendingAsAborted(message) {
6614
+ const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
6615
+ for (const t2 of dropped) {
6616
+ const synthetic = {
6617
+ subagentId: t2.subagentId ?? "unassigned",
6618
+ taskId: t2.id,
6619
+ status: "stopped",
6620
+ error: {
6621
+ kind: "aborted_by_parent",
6622
+ message,
6623
+ retryable: false
6624
+ },
6625
+ iterations: 0,
6626
+ toolCalls: 0,
6627
+ durationMs: 0
6628
+ };
6629
+ this.completedResults.push(synthetic);
6630
+ this.emit("task.completed", { task: t2, result: synthetic });
6631
+ }
6632
+ }
5884
6633
  async runDispatched(subagentId, task) {
5885
6634
  const subagent = this.subagents.get(subagentId);
5886
6635
  if (!subagent) return;
6636
+ if (this.terminating.has(subagentId) || subagent.status === "stopped") {
6637
+ this.recordCompletion({
6638
+ subagentId,
6639
+ taskId: task.id,
6640
+ status: "stopped",
6641
+ error: {
6642
+ kind: "aborted_by_parent",
6643
+ message: "Subagent was terminated before task could start",
6644
+ retryable: false
6645
+ },
6646
+ iterations: 0,
6647
+ toolCalls: 0,
6648
+ durationMs: 0
6649
+ });
6650
+ return;
6651
+ }
5887
6652
  subagent.status = "running";
5888
6653
  subagent.currentTask = task.id;
5889
6654
  task.subagentId = subagentId;
@@ -5929,7 +6694,9 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5929
6694
  subagentId,
5930
6695
  taskId: task.id,
5931
6696
  status,
5932
- error: err instanceof Error ? err.message : String(err),
6697
+ error: classifySubagentError(err, {
6698
+ parentAborted: subagent.abortController.signal.aborted
6699
+ }),
5933
6700
  iterations: usage.iterations,
5934
6701
  toolCalls: usage.toolCalls,
5935
6702
  durationMs: Date.now() - startTime
@@ -5968,19 +6735,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5968
6735
  }
5969
6736
  const subagent = this.subagents.get(result.subagentId);
5970
6737
  if (subagent && subagent.status !== "stopped") {
5971
- const failed = result.status === "failed" || result.status === "timeout";
5972
- subagent.status = failed ? "error" : "idle";
6738
+ result.status === "failed" || result.status === "timeout";
6739
+ subagent.status = "idle";
5973
6740
  subagent.currentTask = void 0;
5974
6741
  if (subagent.abortController.signal.aborted) {
5975
6742
  subagent.abortController = new AbortController();
5976
6743
  }
5977
- if (subagent.status === "error") {
5978
- queueMicrotask(() => {
5979
- if (subagent.status === "error") subagent.status = "idle";
5980
- this.tryDispatchNext();
5981
- });
5982
- }
5983
6744
  }
6745
+ this.terminating.delete(result.subagentId);
5984
6746
  this.emit("task.completed", {
5985
6747
  task: subagent?.context.tasks.find((t2) => t2.id === result.taskId) ?? { id: result.taskId },
5986
6748
  result
@@ -6003,6 +6765,99 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
6003
6765
  return false;
6004
6766
  }
6005
6767
  };
6768
+ function classifySubagentError(err, hints = {}) {
6769
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
6770
+ const baseMessage = err instanceof Error ? err.message : String(err);
6771
+ if (err instanceof ProviderError) {
6772
+ return providerErrorToSubagentError(err, baseMessage, cause);
6773
+ }
6774
+ if (err instanceof BudgetExceededError) {
6775
+ const map = {
6776
+ iterations: "budget_iterations",
6777
+ tool_calls: "budget_tool_calls",
6778
+ tokens: "budget_tokens",
6779
+ cost: "budget_cost",
6780
+ timeout: "budget_timeout"
6781
+ };
6782
+ return {
6783
+ kind: map[err.kind],
6784
+ message: baseMessage,
6785
+ // Budgets are user-configured ceilings, not transient failures —
6786
+ // retrying with the same budget will hit the same ceiling. The
6787
+ // orchestrator must raise the budget or narrow the task first.
6788
+ retryable: false,
6789
+ cause
6790
+ };
6791
+ }
6792
+ if (hints.parentAborted) {
6793
+ return {
6794
+ kind: "aborted_by_parent",
6795
+ message: baseMessage,
6796
+ retryable: false,
6797
+ cause
6798
+ };
6799
+ }
6800
+ const lower = baseMessage.toLowerCase();
6801
+ if (/agent aborted$/i.test(baseMessage)) {
6802
+ return {
6803
+ kind: "aborted_by_parent",
6804
+ message: baseMessage,
6805
+ retryable: false,
6806
+ cause
6807
+ };
6808
+ }
6809
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
6810
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
6811
+ }
6812
+ if (/empty response$/i.test(baseMessage)) {
6813
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
6814
+ }
6815
+ if (/^tool failed: /i.test(baseMessage)) {
6816
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
6817
+ }
6818
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
6819
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
6820
+ }
6821
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
6822
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
6823
+ }
6824
+ return {
6825
+ kind: "unknown",
6826
+ message: baseMessage,
6827
+ retryable: false,
6828
+ cause
6829
+ };
6830
+ }
6831
+ function providerErrorToSubagentError(err, message, cause) {
6832
+ const status = err.status;
6833
+ if (status === 429 || err.body?.type === "rate_limit_error") {
6834
+ return {
6835
+ kind: "provider_rate_limit",
6836
+ message,
6837
+ retryable: true,
6838
+ // Conservative default: 5s. Provider-specific code can override
6839
+ // by emitting an error whose body carries an explicit hint.
6840
+ backoffMs: 5e3,
6841
+ cause
6842
+ };
6843
+ }
6844
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
6845
+ return { kind: "provider_auth", message, retryable: false, cause };
6846
+ }
6847
+ if (status === 408 || status === 0) {
6848
+ return { kind: "provider_timeout", message, retryable: true, cause };
6849
+ }
6850
+ if (status >= 500 && status < 600) {
6851
+ return {
6852
+ kind: "provider_5xx",
6853
+ message,
6854
+ retryable: true,
6855
+ backoffMs: 3e3,
6856
+ cause
6857
+ };
6858
+ }
6859
+ return { kind: "unknown", message, retryable: err.retryable, cause };
6860
+ }
6006
6861
 
6007
6862
  // src/coordination/director.ts
6008
6863
  var DirectorBudgetError = class extends Error {
@@ -6066,6 +6921,27 @@ var Director = class {
6066
6921
  spawnDepth;
6067
6922
  /** Live spawn counter for `maxSpawns` enforcement. */
6068
6923
  spawnCount = 0;
6924
+ /** Optional checkpoint mirror — writes the live task graph + roster to disk. */
6925
+ stateCheckpoint;
6926
+ /** Optional session writer for emitting task_* / agent_* lifecycle events. */
6927
+ sessionWriter;
6928
+ /** Debounce timer for periodic manifest writes. */
6929
+ manifestTimer = null;
6930
+ manifestDebounceMs;
6931
+ /** Resolves task descriptions back from `assign()` so completion events
6932
+ * can also carry a human-readable title. */
6933
+ taskDescriptions = /* @__PURE__ */ new Map();
6934
+ /** Snapshot of which subagent owns each task — drives state-checkpoint
6935
+ * status updates without re-walking the manifest. */
6936
+ taskOwners = /* @__PURE__ */ new Map();
6937
+ /**
6938
+ * Handle to the coordinator-side `task.completed` listener so we can
6939
+ * unsubscribe in `shutdown()`. Without this, repeated Director
6940
+ * construction (e.g. tests, hot reloads) accumulates listeners on a
6941
+ * cached coordinator and slowly drifts the EventEmitter past its
6942
+ * default cap.
6943
+ */
6944
+ taskCompletedListener = null;
6069
6945
  constructor(opts) {
6070
6946
  this.id = opts.config.coordinatorId || randomUUID();
6071
6947
  this.manifestPath = opts.manifestPath;
@@ -6076,6 +6952,14 @@ var Director = class {
6076
6952
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
6077
6953
  this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
6078
6954
  this.spawnDepth = opts.spawnDepth ?? 0;
6955
+ this.sessionWriter = opts.sessionWriter ?? null;
6956
+ this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
6957
+ this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
6958
+ directorRunId: this.id,
6959
+ maxSpawns: opts.maxSpawns,
6960
+ spawnDepth: this.spawnDepth,
6961
+ maxSpawnDepth: this.maxSpawnDepth
6962
+ }) : null;
6079
6963
  if (this.sharedScratchpadPath) {
6080
6964
  void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
6081
6965
  }
@@ -6094,7 +6978,7 @@ var Director = class {
6094
6978
  { ...opts.config, coordinatorId: this.id },
6095
6979
  { runner: opts.runner }
6096
6980
  );
6097
- this.coordinator.on("task.completed", (payload) => {
6981
+ this.taskCompletedListener = (payload) => {
6098
6982
  const r = payload.result;
6099
6983
  this.completed.set(r.taskId, r);
6100
6984
  const waiter = this.taskWaiters.get(r.taskId);
@@ -6102,7 +6986,54 @@ var Director = class {
6102
6986
  waiter.resolve(r);
6103
6987
  this.taskWaiters.delete(r.taskId);
6104
6988
  }
6105
- });
6989
+ const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
6990
+ const failed = r.status !== "success";
6991
+ const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
6992
+ this.stateCheckpoint?.recordTaskStatus(r.taskId, {
6993
+ status: failed ? r.status : "completed",
6994
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6995
+ iterations: r.iterations,
6996
+ toolCalls: r.toolCalls,
6997
+ durationMs: r.durationMs,
6998
+ error: errorString
6999
+ });
7000
+ this.stateCheckpoint?.setUsage(this.usage.snapshot());
7001
+ void this.appendSessionEvent(
7002
+ failed ? {
7003
+ type: "task_failed",
7004
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7005
+ taskId: r.taskId,
7006
+ title,
7007
+ error: errorString ?? r.status
7008
+ } : {
7009
+ type: "task_completed",
7010
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7011
+ taskId: r.taskId,
7012
+ title
7013
+ }
7014
+ );
7015
+ this.scheduleManifest();
7016
+ };
7017
+ this.coordinator.on("task.completed", this.taskCompletedListener);
7018
+ }
7019
+ /** Best-effort session-writer append. Swallows failures — the director
7020
+ * must not break a fleet run because the session JSONL handle closed. */
7021
+ async appendSessionEvent(event) {
7022
+ if (!this.sessionWriter) return;
7023
+ try {
7024
+ await this.sessionWriter.append(event);
7025
+ } catch {
7026
+ }
7027
+ }
7028
+ /** Debounced manifest writer. A burst of spawn/assign/complete events
7029
+ * collapses into one write. Set `manifestDebounceMs` to 0 to disable. */
7030
+ scheduleManifest() {
7031
+ if (!this.manifestPath || this.manifestDebounceMs <= 0) return;
7032
+ if (this.manifestTimer) return;
7033
+ this.manifestTimer = setTimeout(() => {
7034
+ this.manifestTimer = null;
7035
+ void this.writeManifest().catch(() => void 0);
7036
+ }, this.manifestDebounceMs);
6106
7037
  }
6107
7038
  /**
6108
7039
  * Spawn a subagent. Identical to the coordinator's `spawn()` but
@@ -6141,6 +7072,25 @@ var Director = class {
6141
7072
  model: config.model,
6142
7073
  taskIds: []
6143
7074
  });
7075
+ const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
7076
+ this.stateCheckpoint?.recordSpawn(
7077
+ {
7078
+ id: result.subagentId,
7079
+ name: config.name,
7080
+ role: config.role,
7081
+ provider: config.provider,
7082
+ model: config.model,
7083
+ spawnedAt
7084
+ },
7085
+ this.spawnCount
7086
+ );
7087
+ void this.appendSessionEvent({
7088
+ type: "agent_spawned",
7089
+ ts: spawnedAt,
7090
+ agentId: result.subagentId,
7091
+ role: config.role ?? config.name
7092
+ });
7093
+ this.scheduleManifest();
6144
7094
  return result.subagentId;
6145
7095
  }
6146
7096
  /**
@@ -6261,13 +7211,42 @@ var Director = class {
6261
7211
  * — calling shutdown twice is a no-op on the second invocation.
6262
7212
  */
6263
7213
  async shutdown() {
7214
+ if (this.manifestTimer) {
7215
+ clearTimeout(this.manifestTimer);
7216
+ this.manifestTimer = null;
7217
+ }
7218
+ if (this.taskCompletedListener) {
7219
+ this.coordinator.off("task.completed", this.taskCompletedListener);
7220
+ this.taskCompletedListener = null;
7221
+ }
6264
7222
  await this.coordinator.stopAll();
6265
7223
  for (const b of this.subagentBridges.values()) {
6266
- await b.stop().catch(() => void 0);
7224
+ await b.stop().catch((err) => this.logShutdownError("subagent_bridge_stop", err));
6267
7225
  }
6268
7226
  this.subagentBridges.clear();
6269
- await this.bridge.stop().catch(() => void 0);
6270
- if (this.manifestPath) await this.writeManifest().catch(() => void 0);
7227
+ await this.bridge.stop().catch((err) => this.logShutdownError("director_bridge_stop", err));
7228
+ if (this.manifestPath)
7229
+ await this.writeManifest().catch((err) => this.logShutdownError("manifest_write", err));
7230
+ if (this.stateCheckpoint) {
7231
+ this.stateCheckpoint.setUsage(this.usage.snapshot());
7232
+ await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
7233
+ }
7234
+ }
7235
+ /**
7236
+ * Funnel for shutdown-phase errors. We can't throw — `shutdown()` is
7237
+ * called from process-exit paths where an uncaught throw would lose
7238
+ * the manifest write that comes after. But we MUST NOT silently
7239
+ * swallow either — a persistent bridge-close failure would otherwise
7240
+ * mask a real bug. `process.emitWarning` is the right tier:
7241
+ * surfaces on stderr by default, lets the host plug a warning
7242
+ * listener for structured collection, and never affects exit code.
7243
+ */
7244
+ logShutdownError(phase, err) {
7245
+ const detail = err instanceof Error ? err.message : String(err);
7246
+ process.emitWarning(
7247
+ `Director shutdown phase "${phase}" failed: ${detail}`,
7248
+ "DirectorShutdownWarning"
7249
+ );
6271
7250
  }
6272
7251
  /**
6273
7252
  * Hand a task to the coordinator. Returns the assigned task id so
@@ -6281,6 +7260,23 @@ var Director = class {
6281
7260
  if (entry) entry.taskIds.push(taskWithId.id);
6282
7261
  }
6283
7262
  await this.coordinator.assign(taskWithId);
7263
+ this.taskDescriptions.set(taskWithId.id, taskWithId.description);
7264
+ if (taskWithId.subagentId) this.taskOwners.set(taskWithId.id, taskWithId.subagentId);
7265
+ const assignedAt = (/* @__PURE__ */ new Date()).toISOString();
7266
+ this.stateCheckpoint?.recordTaskAssigned({
7267
+ taskId: taskWithId.id,
7268
+ subagentId: taskWithId.subagentId,
7269
+ description: taskWithId.description,
7270
+ status: "running",
7271
+ assignedAt
7272
+ });
7273
+ void this.appendSessionEvent({
7274
+ type: "task_created",
7275
+ ts: assignedAt,
7276
+ taskId: taskWithId.id,
7277
+ title: taskWithId.description
7278
+ });
7279
+ this.scheduleManifest();
6284
7280
  return taskWithId.id;
6285
7281
  }
6286
7282
  /**
@@ -6340,6 +7336,23 @@ var Director = class {
6340
7336
  snapshot() {
6341
7337
  return this.usage.snapshot();
6342
7338
  }
7339
+ /**
7340
+ * Look up provider/model metadata for a spawned subagent. Returns
7341
+ * undefined when the subagent id is unknown (not yet spawned, or
7342
+ * already torn down). Callers — notably the TUI fleet panel — use
7343
+ * this to render human-readable provider/model tags next to each
7344
+ * subagent row without reaching into private state.
7345
+ */
7346
+ getSubagentMeta(id) {
7347
+ const usage = this.subagentMeta.get(id);
7348
+ const manifest = this.manifestEntries.get(id);
7349
+ if (!usage && !manifest) return void 0;
7350
+ return {
7351
+ provider: usage?.provider ?? manifest?.provider,
7352
+ model: usage?.model ?? manifest?.model,
7353
+ name: manifest?.name
7354
+ };
7355
+ }
6343
7356
  /**
6344
7357
  * Compose the leader/director-agent system prompt: fleet preamble +
6345
7358
  * (optional) roster summary + user base prompt. Pass the result to your
@@ -6639,12 +7652,260 @@ function makeFleetUsageTool(director) {
6639
7652
  }
6640
7653
  };
6641
7654
  }
7655
+ function createDelegateTool(opts) {
7656
+ const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
7657
+ const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
7658
+ const inputSchema = {
7659
+ type: "object",
7660
+ properties: {
7661
+ task: {
7662
+ type: "string",
7663
+ description: "What the subagent should do \u2014 natural language, complete sentence(s). The subagent has its own tool slice, its own LLM call, and returns when its task is done."
7664
+ },
7665
+ role: {
7666
+ type: "string",
7667
+ description: rosterIds.length > 0 ? `Roster role (preferred). One of: ${rosterIds.join(", ")}. Picks a pre-tuned config (prompt, budgets, tools) for that role.` : "No roster is configured \u2014 pass `name` instead.",
7668
+ enum: rosterIds.length > 0 ? rosterIds : void 0
7669
+ },
7670
+ name: {
7671
+ type: "string",
7672
+ description: "Display name for the subagent when not using a roster role. Required when `role` is omitted."
7673
+ },
7674
+ provider: {
7675
+ type: "string",
7676
+ description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the host provider when omitted.'
7677
+ },
7678
+ model: {
7679
+ type: "string",
7680
+ description: "Model id within the provider. Defaults to the host model when omitted."
7681
+ },
7682
+ systemPromptOverride: {
7683
+ type: "string",
7684
+ description: "Optional extra prompt text appended to the role baseline."
7685
+ },
7686
+ timeoutMs: {
7687
+ type: "number",
7688
+ description: `Wall-clock budget for this delegate in milliseconds. No hard cap \u2014 set as high as the task realistically needs (a monorepo audit can take hours, a single-file lint takes seconds). Default ${Math.round(defaultTimeoutMs / 1e3 / 60)} minutes.`
7689
+ },
7690
+ maxIterations: {
7691
+ type: "number",
7692
+ description: "Maximum LLM iterations the subagent may take. Unset = use the role/coordinator default. Raise this for tasks with many tool-think-tool cycles (deep code analysis, multi-file refactors)."
7693
+ },
7694
+ maxToolCalls: {
7695
+ type: "number",
7696
+ description: "Maximum number of tool invocations the subagent may make. Unset = use the role/coordinator default. Raise this for tasks that touch many files (large grep + read + report)."
7697
+ }
7698
+ },
7699
+ required: ["task"]
7700
+ };
7701
+ return {
7702
+ name: "delegate",
7703
+ description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and its own budget \u2014 use this when a task is self-contained, would otherwise blow up your context, or benefits from a specialized role (bug-hunter, security-scanner, refactor-planner, audit-log). YOU decide how big the budget is: pass `timeoutMs`, `maxIterations`, and `maxToolCalls` sized to the actual work. There is no hidden cap forcing a 3-minute / 80-iteration limit \u2014 if a monorepo audit needs 2 hours and 500 tool calls, ask for that. Call multiple delegates in parallel through the provider's parallel-tool-call surface to fan work out across roles.",
7704
+ usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster or pass `name` + `provider` + `model`. For non-trivial work, also pass `timeoutMs` (the wall-clock budget you actually need), `maxIterations`, and `maxToolCalls` \u2014 defaults are intentionally generous (4 hours) but the right values depend on scope. Returns the subagent's `TaskResult` \u2014 including the textual `result`, iteration count, tool count, and duration. Auto-promotes the host into director mode on first call.",
7705
+ permission: "auto",
7706
+ mutating: false,
7707
+ inputSchema,
7708
+ async execute(input) {
7709
+ const i = input ?? {};
7710
+ if (typeof i.task !== "string" || !i.task.trim()) {
7711
+ return { ok: false, error: "`task` is required." };
7712
+ }
7713
+ let director = await opts.host.ensureDirector();
7714
+ if (!director) {
7715
+ director = await opts.host.promoteToDirector();
7716
+ }
7717
+ if (!director) {
7718
+ const reason = opts.host.getPromotionBlockReason?.();
7719
+ return {
7720
+ ok: false,
7721
+ error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
7722
+ };
7723
+ }
7724
+ const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
7725
+ let cfg;
7726
+ if (i.role) {
7727
+ const base = opts.roster?.[i.role];
7728
+ if (!base) {
7729
+ return {
7730
+ ok: false,
7731
+ error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
7732
+ };
7733
+ }
7734
+ cfg = { ...base };
7735
+ if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
7736
+ if (i.provider) cfg.provider = i.provider;
7737
+ if (i.model) cfg.model = i.model;
7738
+ } else {
7739
+ if (!i.name) {
7740
+ return {
7741
+ ok: false,
7742
+ error: "Either `role` (from the roster) or `name` is required."
7743
+ };
7744
+ }
7745
+ cfg = {
7746
+ name: i.name,
7747
+ provider: i.provider,
7748
+ model: i.model,
7749
+ systemPromptOverride: i.systemPromptOverride
7750
+ };
7751
+ }
7752
+ if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
7753
+ cfg.maxIterations = i.maxIterations;
7754
+ }
7755
+ if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
7756
+ cfg.maxToolCalls = i.maxToolCalls;
7757
+ }
7758
+ const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
7759
+ const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
7760
+ if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
7761
+ cfg.timeoutMs = desiredSubTimeout;
7762
+ }
7763
+ try {
7764
+ const subagentId = await director.spawn(cfg);
7765
+ const taskId = await director.assign({
7766
+ id: "",
7767
+ description: i.task,
7768
+ subagentId
7769
+ });
7770
+ const result = await Promise.race([
7771
+ director.awaitTasks([taskId]).then((r) => r[0]),
7772
+ new Promise(
7773
+ (resolve4) => setTimeout(() => resolve4({ __timeout: true }), timeoutMs)
7774
+ )
7775
+ ]);
7776
+ if ("__timeout" in result) {
7777
+ const partial2 = await readSubagentPartial(opts, subagentId);
7778
+ return {
7779
+ ok: false,
7780
+ stopReason: "host_timeout",
7781
+ error: `Subagent did not finish within ${timeoutMs}ms.`,
7782
+ hint: "Reduce scope of the next delegate, raise timeoutMs, or use spawn_subagent + await_tasks for long-running work.",
7783
+ subagentId,
7784
+ taskId,
7785
+ partial: partial2
7786
+ };
7787
+ }
7788
+ const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
7789
+ const partial = result.status === "success" ? void 0 : await readSubagentPartial(opts, subagentId);
7790
+ const errorKind = result.error?.kind;
7791
+ const retryable = result.error?.retryable;
7792
+ const backoffMs = result.error?.backoffMs;
7793
+ return {
7794
+ ok: result.status === "success",
7795
+ status: result.status,
7796
+ stopReason: baseStopReason,
7797
+ errorKind,
7798
+ retryable,
7799
+ backoffMs,
7800
+ subagentId: result.subagentId,
7801
+ taskId: result.taskId,
7802
+ result: result.result,
7803
+ error: result.error,
7804
+ iterations: result.iterations,
7805
+ toolCalls: result.toolCalls,
7806
+ durationMs: result.durationMs,
7807
+ ...partial ? { partial } : {},
7808
+ ...hintForKind(errorKind, retryable, backoffMs) ? { hint: hintForKind(errorKind, retryable, backoffMs) } : {}
7809
+ };
7810
+ } catch (err) {
7811
+ return {
7812
+ ok: false,
7813
+ stopReason: "error",
7814
+ error: err instanceof Error ? err.message : String(err)
7815
+ };
7816
+ }
7817
+ }
7818
+ };
7819
+ }
7820
+ function hintForKind(kind, retryable, backoffMs) {
7821
+ if (!kind) return void 0;
7822
+ switch (kind) {
7823
+ case "provider_rate_limit":
7824
+ return `Provider rate-limited. Retry safe after ${backoffMs ?? 5e3}ms backoff. Consider a smaller model or fewer parallel delegates.`;
7825
+ case "provider_5xx":
7826
+ return `Provider server error. Retry safe after ${backoffMs ?? 3e3}ms backoff \u2014 usually transient.`;
7827
+ case "provider_timeout":
7828
+ return "Provider network timeout. Retry safe; reduce input size if it persists.";
7829
+ case "provider_auth":
7830
+ return "Provider rejected credentials. Cannot retry \u2014 fix the API key / config and re-invoke.";
7831
+ case "context_overflow":
7832
+ return "Subagent context exceeded the model limit. Narrow the task, use a larger-context model, or split into multiple delegates.";
7833
+ case "budget_iterations":
7834
+ case "budget_tool_calls":
7835
+ case "budget_tokens":
7836
+ case "budget_cost":
7837
+ return "Subagent exhausted its budget. Raise the matching `max*` field on the next delegate or narrow task scope.";
7838
+ case "budget_timeout":
7839
+ return "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
7840
+ case "aborted_by_parent":
7841
+ return "Subagent was aborted (user Ctrl+C, parent unwound, or sibling failure cascade). Not retryable until the abort condition is resolved.";
7842
+ case "empty_response":
7843
+ return "Subagent ended its turn with no text and no tool calls. Almost always a prompt / config issue \u2014 clarify the task or check the model.";
7844
+ case "tool_failed":
7845
+ return "A tool inside the subagent returned ok:false. Inspect `partial.lastAssistantText` for the agent reasoning, then retry with corrected inputs.";
7846
+ case "bridge_failed":
7847
+ return "Parent-child bridge transport failed. This is rare \u2014 restart the session and retry.";
7848
+ default:
7849
+ return retryable ? "Failure classified as retryable. Try again with the same input." : void 0;
7850
+ }
7851
+ }
7852
+ async function readSubagentPartial(opts, subagentId) {
7853
+ if (!opts.sessionsRoot) return void 0;
7854
+ const candidates = [];
7855
+ if (opts.directorRunId) {
7856
+ candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
7857
+ } else {
7858
+ try {
7859
+ const runDirs = await fsp2.readdir(opts.sessionsRoot);
7860
+ for (const r of runDirs) {
7861
+ candidates.push(path6.join(opts.sessionsRoot, r, `${subagentId}.jsonl`));
7862
+ }
7863
+ } catch {
7864
+ return void 0;
7865
+ }
7866
+ }
7867
+ for (const file of candidates) {
7868
+ let raw;
7869
+ try {
7870
+ raw = await fsp2.readFile(file, "utf8");
7871
+ } catch {
7872
+ continue;
7873
+ }
7874
+ const lines = raw.split("\n").filter((l) => l.trim());
7875
+ let lastAssistantText;
7876
+ let lastStopReason;
7877
+ let toolUses = 0;
7878
+ for (const line of lines) {
7879
+ try {
7880
+ const ev = JSON.parse(line);
7881
+ if (ev.type === "tool_use") toolUses += 1;
7882
+ if (ev.type === "llm_response") {
7883
+ if (typeof ev.stopReason === "string") lastStopReason = ev.stopReason;
7884
+ if (Array.isArray(ev.content)) {
7885
+ const txt = ev.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").trim();
7886
+ if (txt) lastAssistantText = txt;
7887
+ }
7888
+ }
7889
+ } catch {
7890
+ }
7891
+ }
7892
+ return {
7893
+ lastAssistantText,
7894
+ lastStopReason,
7895
+ toolUsesObserved: toolUses,
7896
+ events: lines.length
7897
+ };
7898
+ }
7899
+ return void 0;
7900
+ }
6642
7901
 
6643
7902
  // src/coordination/agent-subagent-runner.ts
6644
7903
  function makeAgentSubagentRunner(opts) {
6645
7904
  const format = opts.formatTaskInput ?? defaultFormatTaskInput;
6646
7905
  return async (task, ctx) => {
6647
- const { agent, events } = await opts.factory(ctx.config);
7906
+ const factoryResult = await opts.factory(ctx.config);
7907
+ const { agent, events } = factoryResult;
7908
+ const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
6648
7909
  const aborter = new AbortController();
6649
7910
  let budgetError = null;
6650
7911
  const onBudgetError = (err) => {
@@ -6658,13 +7919,19 @@ function makeAgentSubagentRunner(opts) {
6658
7919
  budgetError.message += ` (caused by: ${err.message})`;
6659
7920
  }
6660
7921
  };
7922
+ let lastToolFailed = null;
6661
7923
  const unsub = [];
6662
7924
  unsub.push(
6663
- events.on("tool.started", () => {
7925
+ events.on("tool.executed", (e) => {
6664
7926
  try {
6665
7927
  ctx.budget.recordToolCall();
6666
- } catch (e) {
6667
- onBudgetError(e);
7928
+ } catch (eb) {
7929
+ onBudgetError(eb);
7930
+ }
7931
+ if (e.ok === false) {
7932
+ lastToolFailed = e.name;
7933
+ } else if (e.ok === true) {
7934
+ lastToolFailed = null;
6668
7935
  }
6669
7936
  }),
6670
7937
  events.on("provider.response", (e) => {
@@ -6681,6 +7948,26 @@ function makeAgentSubagentRunner(opts) {
6681
7948
  } catch (e) {
6682
7949
  onBudgetError(e);
6683
7950
  }
7951
+ }),
7952
+ // D3: cooperative timeout enforcement DURING a long tool call.
7953
+ // The iteration-loop checkTimeout() only fires between agent
7954
+ // iterations — a single `bash sleep 3600` call would otherwise
7955
+ // park inside one tool execution while the timeout silently
7956
+ // passes, relying solely on the coordinator's hard Promise.race
7957
+ // to interrupt. Tools that emit `tool.progress` (bash chunks,
7958
+ // fetch byte progress, spawn-stream stdout) give us a heartbeat
7959
+ // we can hang the check on. When the budget trips here:
7960
+ // 1. onBudgetError sets budgetError + aborter.abort()
7961
+ // 2. aborter signal propagates to agent.run → tool executor
7962
+ // 3. tool's own signal listener kills the child process
7963
+ // Cheap: O(1) per progress event, and the budget short-circuits
7964
+ // when timeoutMs is unset (most subagents have one set anyway).
7965
+ events.on("tool.progress", () => {
7966
+ try {
7967
+ ctx.budget.checkTimeout();
7968
+ } catch (e) {
7969
+ onBudgetError(e);
7970
+ }
6684
7971
  })
6685
7972
  );
6686
7973
  const onParentAbort = () => aborter.abort();
@@ -6689,8 +7976,15 @@ function makeAgentSubagentRunner(opts) {
6689
7976
  try {
6690
7977
  result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
6691
7978
  } finally {
7979
+ detachFleet?.();
6692
7980
  ctx.signal.removeEventListener("abort", onParentAbort);
6693
7981
  for (const u of unsub) u();
7982
+ if (factoryResult.dispose) {
7983
+ try {
7984
+ await factoryResult.dispose();
7985
+ } catch {
7986
+ }
7987
+ }
6694
7988
  }
6695
7989
  if (budgetError) throw budgetError;
6696
7990
  if (result.status === "failed") {
@@ -6703,6 +7997,13 @@ function makeAgentSubagentRunner(opts) {
6703
7997
  throw new Error("agent exhausted iteration limit");
6704
7998
  }
6705
7999
  const usage = ctx.budget.usage();
8000
+ const finalText = (result.finalText ?? "").trim();
8001
+ if (finalText.length === 0 && usage.toolCalls === 0) {
8002
+ throw new Error("empty response");
8003
+ }
8004
+ if (finalText.length === 0 && lastToolFailed !== null) {
8005
+ throw new Error(`tool failed: ${lastToolFailed}`);
8006
+ }
6706
8007
  return {
6707
8008
  result: result.finalText,
6708
8009
  iterations: result.iterations,
@@ -6768,10 +8069,12 @@ Working rules:
6768
8069
  - Never fabricate numbers \u2014 read the actual logs first
6769
8070
  - Always include file:line references for errors
6770
8071
  - If sessionPath is missing, ask the director to provide it
6771
- - Report confidence level: high (>90% accuracy), medium, low`,
6772
- maxIterations: 50,
6773
- maxToolCalls: 200,
6774
- timeoutMs: 12e4
8072
+ - Report confidence level: high (>90% accuracy), medium, low`
8073
+ // No hardcoded budgets — the orchestrator (delegate tool or
8074
+ // spawn_subagent) decides per-task how much room a subagent gets.
8075
+ // A monorepo audit needs hours; a single-file lint check needs
8076
+ // seconds. Pinning a number here forces the orchestrator to fight
8077
+ // the role's default instead of just asking for what it needs.
6775
8078
  };
6776
8079
  var BUG_HUNTER_AGENT = {
6777
8080
  id: "bug-hunter",
@@ -6811,10 +8114,8 @@ Working rules:
6811
8114
  - Never scan node_modules \u2014 it's noise
6812
8115
  - Always include file:line for every finding
6813
8116
  - If >30% of findings are false positives, note the confidence level
6814
- - Ask director for clarification if paths are ambiguous`,
6815
- maxIterations: 80,
6816
- maxToolCalls: 300,
6817
- timeoutMs: 18e4
8117
+ - Ask director for clarification if paths are ambiguous`
8118
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
6818
8119
  };
6819
8120
  var REFACTOR_PLANNER_AGENT = {
6820
8121
  id: "refactor-planner",
@@ -6854,10 +8155,8 @@ Working rules:
6854
8155
  - Always include rollback strategy \u2014 every refactor can fail
6855
8156
  - Merge tasks that take <1h into a single phase
6856
8157
  - Respect team constraints (reviewer availability, parallelization)
6857
- - Never plan without analyzing the actual code first`,
6858
- maxIterations: 60,
6859
- maxToolCalls: 250,
6860
- timeoutMs: 15e4
8158
+ - Never plan without analyzing the actual code first`
8159
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
6861
8160
  };
6862
8161
  var SECURITY_SCANNER_AGENT = {
6863
8162
  id: "security-scanner",
@@ -6905,10 +8204,8 @@ Working rules:
6905
8204
  - Never scan node_modules \u2014 use npm audit instead
6906
8205
  - Always provide remediation steps, not just findings
6907
8206
  - Verify regex-based secrets before flagging (false positive risk)
6908
- - When in doubt, flag as medium rather than ignoring potential issues`,
6909
- maxIterations: 70,
6910
- maxToolCalls: 280,
6911
- timeoutMs: 16e4
8207
+ - When in doubt, flag as medium rather than ignoring potential issues`
8208
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
6912
8209
  };
6913
8210
  var FLEET_ROSTER = {
6914
8211
  "audit-log": AUDIT_LOG_AGENT,
@@ -8177,7 +9474,7 @@ async function startMetricsServer(opts) {
8177
9474
  const tls = opts.tls;
8178
9475
  const useHttps = !!(tls?.cert && tls?.key);
8179
9476
  const host = opts.host ?? "127.0.0.1";
8180
- const path17 = opts.path ?? "/metrics";
9477
+ const path18 = opts.path ?? "/metrics";
8181
9478
  const healthPath = opts.healthPath ?? "/healthz";
8182
9479
  const healthRegistry = opts.healthRegistry;
8183
9480
  const listener = (req, res) => {
@@ -8187,7 +9484,7 @@ async function startMetricsServer(opts) {
8187
9484
  return;
8188
9485
  }
8189
9486
  const url = req.url.split("?")[0];
8190
- if (url === path17) {
9487
+ if (url === path18) {
8191
9488
  let body;
8192
9489
  try {
8193
9490
  body = renderPrometheus(opts.sink.snapshot());
@@ -8251,7 +9548,7 @@ async function startMetricsServer(opts) {
8251
9548
  const protocol = useHttps ? "https" : "http";
8252
9549
  return {
8253
9550
  port: boundPort,
8254
- url: `${protocol}://${host}:${boundPort}${path17}`,
9551
+ url: `${protocol}://${host}:${boundPort}${path18}`,
8255
9552
  close: () => new Promise((resolve4, reject) => {
8256
9553
  server.close((err) => err ? reject(err) : resolve4());
8257
9554
  })
@@ -8797,336 +10094,248 @@ var allServers = () => ({
8797
10094
  sentinel: { ...sentinelServer(), enabled: false }
8798
10095
  });
8799
10096
 
8800
- // src/core/iteration-limit.ts
8801
- function requestLimitExtension(opts) {
8802
- const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
8803
- return new Promise((resolve4) => {
8804
- let resolved = false;
8805
- const timer = setTimeout(() => {
8806
- if (!resolved) {
8807
- resolved = true;
8808
- resolve4(0);
8809
- }
8810
- }, timeoutMs);
8811
- const deny = () => {
8812
- if (!resolved) {
8813
- resolved = true;
8814
- clearTimeout(timer);
8815
- resolve4(0);
8816
- }
10097
+ // src/extension/registry.ts
10098
+ var ExtensionRegistry = class {
10099
+ extensions = [];
10100
+ promptContributors = [];
10101
+ log;
10102
+ setLogger(log) {
10103
+ this.log = log;
10104
+ }
10105
+ /**
10106
+ * Register a system prompt contributor. Returns an unregister function.
10107
+ * Contributors are called on every system prompt build in registration
10108
+ * order. Their output blocks are inserted after the core environment
10109
+ * block, before the mode and plan blocks.
10110
+ */
10111
+ registerSystemPromptContributor(c) {
10112
+ this.promptContributors.push(c);
10113
+ return () => {
10114
+ const idx = this.promptContributors.indexOf(c);
10115
+ if (idx >= 0) this.promptContributors.splice(idx, 1);
8817
10116
  };
8818
- const grant = (extra) => {
8819
- if (!resolved) {
8820
- resolved = true;
8821
- clearTimeout(timer);
8822
- resolve4(Math.max(0, extra));
10117
+ }
10118
+ /**
10119
+ * Build all registered system prompt contributions.
10120
+ * Failures are caught and logged — one bad contributor doesn't
10121
+ * break the prompt assembly.
10122
+ */
10123
+ async buildSystemPromptContributions(ctx) {
10124
+ const blocks = [];
10125
+ for (const c of this.promptContributors) {
10126
+ try {
10127
+ const contributed = await c(ctx);
10128
+ blocks.push(...contributed);
10129
+ } catch (err) {
10130
+ this.log?.error("SystemPromptContributor failed", err);
8823
10131
  }
8824
- };
8825
- events.emit("iteration.limit_reached", {
8826
- currentIterations,
8827
- currentLimit,
8828
- grant,
8829
- deny
8830
- });
8831
- if (autoExtend) {
8832
- setImmediate(() => {
8833
- if (!resolved) {
8834
- resolved = true;
8835
- clearTimeout(timer);
8836
- resolve4(100);
8837
- }
10132
+ }
10133
+ return blocks;
10134
+ }
10135
+ /**
10136
+ * Returns the live array of contributors (readonly snapshot for
10137
+ * passing to DefaultSystemPromptBuilder at build time).
10138
+ */
10139
+ listSystemPromptContributors() {
10140
+ return this.promptContributors;
10141
+ }
10142
+ /**
10143
+ * Register an extension. Duplicate names are rejected.
10144
+ * Returns an unregister function.
10145
+ */
10146
+ register(ext) {
10147
+ if (this.extensions.some((e) => e.name === ext.name)) {
10148
+ throw new WrongStackError({
10149
+ message: `Extension "${ext.name}" already registered`,
10150
+ code: "REGISTRY_DUPLICATE",
10151
+ subsystem: "container",
10152
+ context: { extension: ext.name }
8838
10153
  });
8839
10154
  }
8840
- });
8841
- }
8842
-
8843
- // src/core/streaming-response-builder.ts
8844
- function buildResponse(state) {
8845
- const content = [];
8846
- for (const b of state.blockOrder) {
8847
- if (b.kind === "text") {
8848
- const txt = state.textBuffers[b.idx] ?? "";
8849
- if (txt) content.push({ type: "text", text: txt });
8850
- } else if (b.kind === "thinking") {
8851
- const t2 = state.thinking[b.idx];
8852
- if (!t2) continue;
8853
- if (!t2.textBuf && !t2.signature) continue;
8854
- const block = { type: "thinking", thinking: t2.textBuf };
8855
- if (t2.signature) block.signature = t2.signature;
8856
- if (t2.providerMeta && Object.keys(t2.providerMeta).length > 0) {
8857
- block.providerMeta = t2.providerMeta;
8858
- }
8859
- content.push(block);
8860
- } else {
8861
- const tb = state.tools.get(b.id);
8862
- if (tb) {
8863
- const block = {
8864
- type: "tool_use",
8865
- id: b.id,
8866
- name: tb.name,
8867
- input: tb.input ?? {}
8868
- };
8869
- if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
8870
- block.providerMeta = tb.providerMeta;
8871
- }
8872
- content.push(block);
10155
+ this.extensions.push(ext);
10156
+ return () => this.unregister(ext.name);
10157
+ }
10158
+ /**
10159
+ * Register an extension, silently replacing any previous registration
10160
+ * with the same name. Use this when overriding a default extension.
10161
+ */
10162
+ registerOrReplace(ext) {
10163
+ const idx = this.extensions.findIndex((e) => e.name === ext.name);
10164
+ if (idx >= 0) this.extensions.splice(idx, 1);
10165
+ return this.register(ext);
10166
+ }
10167
+ /**
10168
+ * Unregister an extension by name. Returns true if found.
10169
+ */
10170
+ unregister(name) {
10171
+ const idx = this.extensions.findIndex((e) => e.name === name);
10172
+ if (idx === -1) return false;
10173
+ this.extensions.splice(idx, 1);
10174
+ return true;
10175
+ }
10176
+ /**
10177
+ * List registered extension names in order.
10178
+ */
10179
+ list() {
10180
+ return this.extensions.map((e) => e.name);
10181
+ }
10182
+ /**
10183
+ * Check if an extension with the given name is registered.
10184
+ */
10185
+ has(name) {
10186
+ return this.extensions.some((e) => e.name === name);
10187
+ }
10188
+ /**
10189
+ * Remove all registered extensions and contributors.
10190
+ */
10191
+ clear() {
10192
+ this.extensions.length = 0;
10193
+ this.promptContributors.length = 0;
10194
+ }
10195
+ // ── Hook runners ─────────────────────────────────────────────────
10196
+ async runBeforeRun(...args) {
10197
+ for (const ext of this.extensions) {
10198
+ if (!ext.beforeRun) continue;
10199
+ try {
10200
+ await ext.beforeRun(...args);
10201
+ } catch (err) {
10202
+ this.log?.error(`Extension "${ext.name}" beforeRun hook failed`, err);
8873
10203
  }
8874
10204
  }
8875
10205
  }
8876
- if (content.length === 0) content.push({ type: "text", text: "" });
8877
- return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
8878
- }
8879
- function createStreamingState(model) {
8880
- return {
8881
- model,
8882
- stopReason: "end_turn",
8883
- usage: { input: 0, output: 0 },
8884
- textBuffers: [],
8885
- currentTextIndex: -1,
8886
- tools: /* @__PURE__ */ new Map(),
8887
- thinking: [],
8888
- currentThinkingIndex: -1,
8889
- blockOrder: []
8890
- };
8891
- }
8892
- function handleMessageStart(state, model) {
8893
- state.model = model;
8894
- }
8895
- function handleContentBlockStart(state, ev) {
8896
- const kind = ev.kind ?? "text";
8897
- if (kind === "text") {
8898
- state.currentTextIndex = state.textBuffers.length;
8899
- state.textBuffers.push("");
8900
- state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
8901
- } else if (kind === "tool_use") {
8902
- const id = ev.id ?? crypto.randomUUID();
8903
- state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
8904
- state.blockOrder.push({ kind: "tool", id });
8905
- state.currentTextIndex = -1;
8906
- } else if (kind === "thinking") {
8907
- state.currentThinkingIndex = state.thinking.length;
8908
- state.thinking.push({
8909
- textBuf: "",
8910
- ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
8911
- });
8912
- state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
8913
- state.currentTextIndex = -1;
8914
- }
8915
- }
8916
- function handleContentBlockStop(state, ev) {
8917
- }
8918
- function handleTextDelta(state, text) {
8919
- if (state.currentTextIndex === -1) {
8920
- state.currentTextIndex = state.textBuffers.length;
8921
- state.textBuffers.push("");
8922
- state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
10206
+ async runAfterRun(...args) {
10207
+ for (const ext of this.extensions) {
10208
+ if (!ext.afterRun) continue;
10209
+ try {
10210
+ await ext.afterRun(...args);
10211
+ } catch (err) {
10212
+ this.log?.error(`Extension "${ext.name}" afterRun hook failed`, err);
10213
+ }
10214
+ }
8923
10215
  }
8924
- state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
8925
- }
8926
- function handleToolUseStart(state, ev) {
8927
- state.currentTextIndex = -1;
8928
- state.tools.set(ev.id, { name: ev.name, partial: "" });
8929
- state.blockOrder.push({ kind: "tool", id: ev.id });
8930
- }
8931
- function handleToolUseInputDelta(state, ev) {
8932
- const t2 = state.tools.get(ev.id);
8933
- if (t2) t2.partial += ev.partial;
8934
- }
8935
- function safeJsonOrRaw(s) {
8936
- if (!s) return {};
8937
- try {
8938
- return JSON.parse(s);
8939
- } catch {
8940
- return { _raw: s };
10216
+ async runBeforeIteration(...args) {
10217
+ for (const ext of this.extensions) {
10218
+ if (!ext.beforeIteration) continue;
10219
+ try {
10220
+ await ext.beforeIteration(...args);
10221
+ } catch (err) {
10222
+ this.log?.error(`Extension "${ext.name}" beforeIteration hook failed`, err);
10223
+ }
10224
+ }
8941
10225
  }
8942
- }
8943
- function handleToolUseStop(state, ev) {
8944
- const t2 = state.tools.get(ev.id);
8945
- if (t2) {
8946
- t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
8947
- if (ev.providerMeta) t2.providerMeta = ev.providerMeta;
10226
+ async runAfterIteration(...args) {
10227
+ for (const ext of this.extensions) {
10228
+ if (!ext.afterIteration) continue;
10229
+ try {
10230
+ await ext.afterIteration(...args);
10231
+ } catch (err) {
10232
+ this.log?.error(`Extension "${ext.name}" afterIteration hook failed`, err);
10233
+ }
10234
+ }
8948
10235
  }
8949
- state.currentTextIndex = -1;
8950
- }
8951
- function handleThinkingStart(state, ev) {
8952
- state.currentThinkingIndex = state.thinking.length;
8953
- state.thinking.push({
8954
- textBuf: "",
8955
- ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
8956
- });
8957
- state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
8958
- state.currentTextIndex = -1;
8959
- }
8960
- function handleThinkingDelta(state, text) {
8961
- if (state.currentThinkingIndex === -1) {
8962
- handleThinkingStart(state, {});
10236
+ /**
10237
+ * Run onError hooks in order. The first hook that returns a non-void
10238
+ * result wins; subsequent hooks are skipped.
10239
+ */
10240
+ async runOnError(...args) {
10241
+ for (const ext of this.extensions) {
10242
+ if (!ext.onError) continue;
10243
+ try {
10244
+ const result = await ext.onError(...args);
10245
+ if (result) return result;
10246
+ } catch (err) {
10247
+ this.log?.error(`Extension "${ext.name}" onError hook failed`, err);
10248
+ }
10249
+ }
8963
10250
  }
8964
- const t2 = state.thinking[state.currentThinkingIndex];
8965
- if (t2) t2.textBuf += text;
8966
- }
8967
- function handleThinkingSignature(state, signature) {
8968
- if (state.currentThinkingIndex === -1) {
8969
- handleThinkingStart(state, {});
10251
+ /**
10252
+ * Build a composed provider runner. Extensions with `wrapProviderRunner`
10253
+ * form a middleware-style chain: the innermost extension wraps the
10254
+ * default runner, each subsequent wrapper wraps the previous.
10255
+ */
10256
+ wrapProviderRunner(inner) {
10257
+ const wrappers = this.extensions.filter((e) => e.wrapProviderRunner).map((e) => ({ name: e.name, wrap: e.wrapProviderRunner }));
10258
+ if (wrappers.length === 0) return inner;
10259
+ let composed = inner;
10260
+ for (let i = wrappers.length - 1; i >= 0; i--) {
10261
+ const wrapper = wrappers[i];
10262
+ const next = composed;
10263
+ composed = async (ctx, req) => {
10264
+ try {
10265
+ return await wrapper.wrap(ctx, req, next);
10266
+ } catch (err) {
10267
+ this.log?.error(`Extension "${wrapper.name}" wrapProviderRunner failed`, err);
10268
+ throw err;
10269
+ }
10270
+ };
10271
+ }
10272
+ return composed;
8970
10273
  }
8971
- const t2 = state.thinking[state.currentThinkingIndex];
8972
- if (t2) t2.signature = signature;
8973
- }
8974
- function handleThinkingStop(state) {
8975
- state.currentThinkingIndex = -1;
8976
- }
8977
- function handleMessageStop(state, ev) {
8978
- state.stopReason = ev.stopReason ?? "end_turn";
8979
- state.usage = ev.usage ?? { input: 0, output: 0 };
8980
- }
8981
- async function streamProviderToResponse(provider, req, signal, ctx, events) {
8982
- const state = createStreamingState(req.model);
8983
- const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
8984
- try {
8985
- for (; ; ) {
8986
- const next = await iter.next();
8987
- if (next.done) break;
8988
- const ev = next.value;
8989
- switch (ev.type) {
8990
- case "message_start":
8991
- handleMessageStart(state, ev.model);
8992
- break;
8993
- case "content_block_start":
8994
- handleContentBlockStart(state, ev);
8995
- break;
8996
- case "content_block_stop":
8997
- handleContentBlockStop(state, ev);
8998
- break;
8999
- case "text_delta":
9000
- handleTextDelta(state, ev.text);
9001
- events.emit("provider.text_delta", { ctx, text: ev.text });
9002
- break;
9003
- case "tool_use_start":
9004
- handleToolUseStart(state, ev);
9005
- events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
9006
- break;
9007
- case "tool_use_input_delta":
9008
- handleToolUseInputDelta(state, ev);
9009
- break;
9010
- case "tool_use_stop":
9011
- handleToolUseStop(state, ev);
9012
- events.emit("provider.tool_use_stop", { ctx, id: ev.id });
9013
- break;
9014
- case "thinking_start":
9015
- handleThinkingStart(state, ev);
9016
- break;
9017
- case "thinking_delta":
9018
- handleThinkingDelta(state, ev.text);
9019
- events.emit("provider.thinking_delta", { ctx, text: ev.text });
9020
- break;
9021
- case "thinking_signature":
9022
- handleThinkingSignature(state, ev.signature);
9023
- break;
9024
- case "thinking_stop":
9025
- handleThinkingStop(state);
9026
- break;
9027
- case "message_stop":
9028
- handleMessageStop(state, ev);
9029
- break;
10274
+ async runBeforeToolExecution(...args) {
10275
+ let toolUses = args[1];
10276
+ for (const ext of this.extensions) {
10277
+ if (!ext.beforeToolExecution) continue;
10278
+ try {
10279
+ toolUses = await ext.beforeToolExecution(args[0], toolUses);
10280
+ } catch (err) {
10281
+ this.log?.error(`Extension "${ext.name}" beforeToolExecution hook failed`, err);
9030
10282
  }
9031
10283
  }
9032
- } catch (err) {
9033
- if (signal.aborted) {
9034
- state.stopReason = "end_turn";
9035
- return buildResponse(state);
9036
- }
9037
- throw err;
9038
- } finally {
9039
- try {
9040
- let drainTimer = null;
10284
+ return toolUses;
10285
+ }
10286
+ async runAfterToolExecution(...args) {
10287
+ for (const ext of this.extensions) {
10288
+ if (!ext.afterToolExecution) continue;
9041
10289
  try {
9042
- await Promise.race([
9043
- Promise.resolve(iter.return?.()),
9044
- new Promise((resolve4) => {
9045
- drainTimer = setTimeout(resolve4, 500);
9046
- })
9047
- ]);
9048
- } finally {
9049
- if (drainTimer) clearTimeout(drainTimer);
10290
+ await ext.afterToolExecution(...args);
10291
+ } catch (err) {
10292
+ this.log?.error(`Extension "${ext.name}" afterToolExecution hook failed`, err);
9050
10293
  }
9051
- } catch {
9052
10294
  }
9053
10295
  }
9054
- return buildResponse(state);
9055
- }
10296
+ };
9056
10297
 
9057
- // src/core/provider-runner.ts
9058
- async function runProviderWithRetry(opts) {
9059
- const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
9060
- let attempt = 0;
9061
- for (; ; ) {
9062
- const span = tracer?.startSpan("provider.complete", {
9063
- "provider.id": provider.id,
9064
- "provider.model": request.model,
9065
- "provider.streaming": provider.capabilities.streaming,
9066
- "provider.attempt": attempt
9067
- });
9068
- try {
9069
- const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
9070
- span?.setAttribute("provider.stopReason", res.stopReason);
9071
- span?.setAttribute("provider.usage_in", res.usage.input);
9072
- span?.setAttribute("provider.usage_out", res.usage.output);
9073
- span?.end();
9074
- return res;
9075
- } catch (err) {
9076
- if (err instanceof Error) span?.recordError(err);
9077
- span?.end();
9078
- if (signal.aborted) throw err;
9079
- const isProviderErr = err instanceof ProviderError;
9080
- const errAsErr = err instanceof Error ? err : new Error(String(err));
9081
- const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
9082
- const description = isProviderErr ? err.describe() : errAsErr.message;
9083
- if (!canRetry) {
9084
- if (isProviderErr) {
9085
- events.emit("provider.error", {
9086
- providerId: err.providerId,
9087
- status: err.status,
9088
- description,
9089
- retryable: false
9090
- });
9091
- }
9092
- throw err;
10298
+ // src/core/iteration-limit.ts
10299
+ function requestLimitExtension(opts) {
10300
+ const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
10301
+ return new Promise((resolve4) => {
10302
+ let resolved = false;
10303
+ const timer = setTimeout(() => {
10304
+ if (!resolved) {
10305
+ resolved = true;
10306
+ resolve4(0);
9093
10307
  }
9094
- const delay = Math.round(retry.delayMs(attempt));
9095
- const attemptNum = attempt + 1;
9096
- logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
9097
- if (isProviderErr) {
9098
- events.emit("provider.retry", {
9099
- providerId: err.providerId,
9100
- attempt: attemptNum,
9101
- delayMs: delay,
9102
- status: err.status,
9103
- description
9104
- });
10308
+ }, timeoutMs);
10309
+ const deny = () => {
10310
+ if (!resolved) {
10311
+ resolved = true;
10312
+ clearTimeout(timer);
10313
+ resolve4(0);
9105
10314
  }
9106
- await new Promise((resolve4, reject) => {
9107
- let settled = false;
9108
- const onAbort = () => {
9109
- if (settled) return;
9110
- settled = true;
9111
- clearTimeout(t2);
9112
- reject(new Error("aborted"));
9113
- };
9114
- const t2 = setTimeout(() => {
9115
- if (settled) return;
9116
- settled = true;
9117
- clearTimeout(t2);
9118
- signal.removeEventListener("abort", onAbort);
9119
- resolve4();
9120
- }, delay);
9121
- if (signal.aborted) {
9122
- onAbort();
9123
- return;
10315
+ };
10316
+ const grant = (extra) => {
10317
+ if (!resolved) {
10318
+ resolved = true;
10319
+ clearTimeout(timer);
10320
+ resolve4(Math.max(0, extra));
10321
+ }
10322
+ };
10323
+ events.emit("iteration.limit_reached", {
10324
+ currentIterations,
10325
+ currentLimit,
10326
+ grant,
10327
+ deny
10328
+ });
10329
+ if (autoExtend) {
10330
+ setImmediate(() => {
10331
+ if (!resolved) {
10332
+ resolved = true;
10333
+ clearTimeout(timer);
10334
+ resolve4(100);
9124
10335
  }
9125
- signal.addEventListener("abort", onAbort, { once: true });
9126
10336
  });
9127
- attempt++;
9128
10337
  }
9129
- }
10338
+ });
9130
10339
  }
9131
10340
 
9132
10341
  // src/core/agent.ts
@@ -9163,6 +10372,7 @@ var Agent = class {
9163
10372
  toolExecutor;
9164
10373
  autoExtendLimit;
9165
10374
  tracer;
10375
+ extensions;
9166
10376
  constructor(init) {
9167
10377
  this.container = init.container;
9168
10378
  this.tools = init.tools;
@@ -9176,8 +10386,10 @@ var Agent = class {
9176
10386
  this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
9177
10387
  this.autoExtendLimit = init.autoExtendLimit ?? true;
9178
10388
  this.tracer = init.tracer;
10389
+ this.extensions = init.extensions ?? new ExtensionRegistry();
10390
+ this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
9179
10391
  this.toolExecutor = new ToolExecutor(this.tools, {
9180
- permissionPolicy: this.permission,
10392
+ permissionPolicy: init.permissionPolicy ?? this.permission,
9181
10393
  secretScrubber: this.scrubber,
9182
10394
  renderer: this.renderer,
9183
10395
  events: this.events,
@@ -9221,33 +10433,66 @@ var Agent = class {
9221
10433
  "agent.model": opts.model ?? this.ctx.model,
9222
10434
  "agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
9223
10435
  });
10436
+ const { blocks, text } = normalizeInput(userInput);
10437
+ const inputPayload = { content: blocks, text, ctx: this.ctx };
10438
+ await this.extensions.runBeforeRun(this.ctx, inputPayload);
9224
10439
  try {
9225
- const result = await this.runInner(userInput, opts, controller);
10440
+ const result = await this.runInner(inputPayload, opts, controller);
9226
10441
  span?.setAttribute("agent.status", result.status);
9227
10442
  span?.setAttribute("agent.iterations", result.iterations);
10443
+ await this.extensions.runAfterRun(this.ctx, result);
9228
10444
  return result;
9229
10445
  } catch (err) {
9230
10446
  const wse = err instanceof AgentError ? err : toWrongStackError(err);
9231
10447
  this.events.emit("error", { err: toError(err), phase: "agent" });
9232
10448
  if (err instanceof Error) span?.recordError(err);
9233
10449
  span?.setAttribute("agent.status", "failed");
9234
- return {
10450
+ const result = {
9235
10451
  status: signal.aborted ? "aborted" : "failed",
9236
10452
  iterations: 0,
9237
10453
  error: wse
9238
10454
  };
10455
+ await this.extensions.runAfterRun(this.ctx, result);
10456
+ return result;
9239
10457
  } finally {
9240
10458
  span?.end();
9241
10459
  await controller.dispose();
9242
10460
  }
9243
10461
  }
9244
- async runInner(userInput, opts, controller) {
9245
- await this.normalizeAndEmitUserInput(userInput);
10462
+ async runInner(inputPayload, opts, controller) {
10463
+ await this.pipelines.userInput.run(inputPayload);
10464
+ this.ctx.state.appendMessage({ role: "user", content: inputPayload.content });
10465
+ await this.ctx.session.append({
10466
+ type: "user_input",
10467
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
10468
+ content: inputPayload.content
10469
+ });
9246
10470
  let finalText = "";
9247
10471
  let iterations = 0;
9248
10472
  let effectiveLimit = opts.maxIterations ?? this.maxIterations;
9249
10473
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
9250
10474
  let recoveryRetries = 0;
10475
+ const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
10476
+ const baseRunner = diRunner ? (ctx, req) => diRunner.run({
10477
+ provider: ctx.provider,
10478
+ request: req,
10479
+ signal: controller.signal,
10480
+ ctx,
10481
+ events: this.events,
10482
+ retry: this.retry,
10483
+ logger: this.logger,
10484
+ tracer: this.tracer
10485
+ }) : async (ctx, req) => runProviderWithRetry({
10486
+ provider: ctx.provider,
10487
+ request: req,
10488
+ signal: controller.signal,
10489
+ ctx,
10490
+ events: this.events,
10491
+ retry: this.retry,
10492
+ logger: this.logger,
10493
+ tracer: this.tracer
10494
+ });
10495
+ const customRunner = this.extensions.wrapProviderRunner(baseRunner);
9251
10496
  for (let i = 0; ; i++) {
9252
10497
  iterations = i + 1;
9253
10498
  if (controller.signal.aborted) {
@@ -9263,26 +10508,39 @@ var Agent = class {
9263
10508
  if (limitCheck.exit) {
9264
10509
  return { ...limitCheck.exit, finalText };
9265
10510
  }
10511
+ await this.extensions.runBeforeIteration(this.ctx, i);
9266
10512
  this.events.emit("iteration.started", { ctx: this.ctx, index: i });
9267
10513
  const req = await this.buildAndRunRequestPipeline(opts);
9268
10514
  let res;
9269
10515
  try {
9270
- res = await runProviderWithRetry({
9271
- provider: this.ctx.provider,
9272
- request: req,
9273
- signal: controller.signal,
9274
- ctx: this.ctx,
9275
- events: this.events,
9276
- retry: this.retry,
9277
- logger: this.logger,
9278
- tracer: this.tracer
9279
- });
10516
+ res = await customRunner(this.ctx, req);
9280
10517
  recoveryRetries = 0;
9281
10518
  } catch (err) {
9282
10519
  if (controller.signal.aborted) {
9283
10520
  this.events.emit("error", { err: toError(err), phase: "provider" });
9284
10521
  return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
9285
10522
  }
10523
+ const extDecision = await this.extensions.runOnError(this.ctx, err, "provider", i);
10524
+ if (extDecision) {
10525
+ if (extDecision.action === "fail") {
10526
+ this.events.emit("error", { err: toError(err), phase: "provider" });
10527
+ return { status: "failed", iterations, error: toWrongStackError(err) };
10528
+ }
10529
+ if (extDecision.action === "continue") {
10530
+ await this.extensions.runAfterIteration(this.ctx, i);
10531
+ continue;
10532
+ }
10533
+ if (extDecision.action === "retry") {
10534
+ recoveryRetries++;
10535
+ if (recoveryRetries > 2) {
10536
+ this.events.emit("error", { err: toError(err), phase: "provider" });
10537
+ return { status: "failed", iterations, error: toWrongStackError(err) };
10538
+ }
10539
+ if (extDecision.model) this.ctx.model = extDecision.model;
10540
+ this.logger.info("Extension requested retry; retrying turn");
10541
+ continue;
10542
+ }
10543
+ }
9286
10544
  const recovered = await this.errorHandler.recover(err, this.ctx);
9287
10545
  if (!recovered || recovered.action === "fail") {
9288
10546
  this.events.emit("error", { err: toError(err), phase: "provider" });
@@ -9321,21 +10579,9 @@ var Agent = class {
9321
10579
  await this.executeTools(toolUses);
9322
10580
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
9323
10581
  await this.compactContextIfNeeded();
10582
+ await this.extensions.runAfterIteration(this.ctx, i);
9324
10583
  }
9325
10584
  }
9326
- /**
9327
- * Normalize user input and emit through userInput pipeline + session append.
9328
- */
9329
- async normalizeAndEmitUserInput(userInput) {
9330
- const { blocks, text } = normalizeInput(userInput);
9331
- await this.pipelines.userInput.run({ content: blocks, text, ctx: this.ctx });
9332
- this.ctx.state.appendMessage({ role: "user", content: blocks });
9333
- await this.ctx.session.append({
9334
- type: "user_input",
9335
- ts: (/* @__PURE__ */ new Date()).toISOString(),
9336
- content: blocks
9337
- });
9338
- }
9339
10585
  /**
9340
10586
  * Check if iteration limit has been reached and request extension if needed.
9341
10587
  * Returns the new effective limit (possibly extended) and a RunResult if
@@ -9419,6 +10665,7 @@ var Agent = class {
9419
10665
  * single tool.
9420
10666
  */
9421
10667
  async executeTools(toolUses) {
10668
+ toolUses = await this.extensions.runBeforeToolExecution(this.ctx, toolUses);
9422
10669
  const { outputs } = await this.toolExecutor.executeBatch(
9423
10670
  toolUses,
9424
10671
  this.ctx,
@@ -9504,6 +10751,7 @@ var Agent = class {
9504
10751
  role: "user",
9505
10752
  content: outputs.map((o) => o.result)
9506
10753
  });
10754
+ await this.extensions.runAfterToolExecution(this.ctx, outputs);
9507
10755
  }
9508
10756
  waitForConfirm(info) {
9509
10757
  return new Promise((resolve4) => {
@@ -9931,6 +11179,7 @@ var DefaultSystemPromptBuilder = class {
9931
11179
  const layer3 = await this.buildEnvironment(ctx);
9932
11180
  const layer4 = await this.buildMemoryAndSkills();
9933
11181
  const layer5 = await this.buildMode();
11182
+ const layer6 = ctx.subagent ? "" : await this.buildActivePlan();
9934
11183
  const blocks = [
9935
11184
  { type: "text", text: layer1 },
9936
11185
  { type: "text", text: layer2 },
@@ -9950,16 +11199,95 @@ var DefaultSystemPromptBuilder = class {
9950
11199
  cache_control: { type: "ephemeral" }
9951
11200
  });
9952
11201
  }
11202
+ if (layer6.trim()) {
11203
+ blocks.push({
11204
+ type: "text",
11205
+ text: layer6,
11206
+ cache_control: { type: "ephemeral" }
11207
+ });
11208
+ }
11209
+ if (this.opts.contributors && this.opts.contributors.length > 0) {
11210
+ for (const c of this.opts.contributors) {
11211
+ try {
11212
+ const contributed = await c(ctx);
11213
+ blocks.push(...contributed);
11214
+ } catch {
11215
+ }
11216
+ }
11217
+ }
9953
11218
  return blocks;
9954
11219
  }
11220
+ /**
11221
+ * Reads `<sessionId>.plan.json` (when configured) and produces a short
11222
+ * "Active plan" block listing open items so the model is anchored to
11223
+ * the strategic roadmap every turn. Reads on every `build()` so a
11224
+ * plan edit (via `/plan` or the `plan` tool) reflects on the next
11225
+ * turn without restarting the session.
11226
+ */
11227
+ async buildActivePlan() {
11228
+ const planPath = typeof this.opts.planPath === "function" ? this.opts.planPath() : this.opts.planPath;
11229
+ if (!planPath) return "";
11230
+ let raw;
11231
+ try {
11232
+ raw = await fsp2.readFile(planPath, "utf8");
11233
+ } catch {
11234
+ return "";
11235
+ }
11236
+ let parsed;
11237
+ try {
11238
+ parsed = JSON.parse(raw);
11239
+ } catch {
11240
+ return "";
11241
+ }
11242
+ if (!Array.isArray(parsed.items) || parsed.items.length === 0) return "";
11243
+ const open3 = parsed.items.filter((i) => i?.status !== "done");
11244
+ if (open3.length === 0) return "";
11245
+ const lines = ["## Active plan"];
11246
+ if (parsed.title) lines.push(`*${parsed.title}*`, "");
11247
+ parsed.items.forEach((it, idx) => {
11248
+ const mark = it?.status === "done" ? "[x]" : it?.status === "in_progress" ? "[~]" : "[ ]";
11249
+ lines.push(`${idx + 1}. ${mark} ${it?.title ?? "(untitled)"}`);
11250
+ });
11251
+ lines.push(
11252
+ "",
11253
+ "Use `/plan` (user) or the `plan` tool to update status as you progress. The roadmap survives session resume."
11254
+ );
11255
+ return lines.join("\n");
11256
+ }
9955
11257
  buildToolUsage(tools) {
9956
11258
  if (tools.length === 0) return "## Tool usage\n\nNo tools registered.";
9957
- const lines = ["## Tool usage"];
11259
+ const byCat = /* @__PURE__ */ new Map();
11260
+ const uncategorized = [];
9958
11261
  for (const t2 of tools) {
9959
- const hint = t2.usageHint ?? t2.description;
11262
+ if (t2.category) {
11263
+ let group = byCat.get(t2.category);
11264
+ if (!group) {
11265
+ group = [];
11266
+ byCat.set(t2.category, group);
11267
+ }
11268
+ group.push(t2);
11269
+ } else {
11270
+ uncategorized.push(t2);
11271
+ }
11272
+ }
11273
+ const lines = ["## Tool usage"];
11274
+ for (const [cat, catTools] of byCat) {
9960
11275
  lines.push(`
11276
+ ### ${cat}`);
11277
+ for (const t2 of catTools) {
11278
+ const hint = t2.usageHint ?? t2.description;
11279
+ const desc = hint.length > 80 ? `${hint.slice(0, 77)}...` : hint.trim();
11280
+ lines.push(`- **${t2.name}** \u2014 ${desc}`);
11281
+ }
11282
+ }
11283
+ if (uncategorized.length > 0) {
11284
+ if (byCat.size > 0) lines.push("");
11285
+ for (const t2 of uncategorized) {
11286
+ const hint = t2.usageHint ?? t2.description;
11287
+ lines.push(`
9961
11288
  ### ${t2.name}
9962
11289
  ${hint.trim()}`);
11290
+ }
9963
11291
  }
9964
11292
  lines.push(`
9965
11293
  ## Common patterns
@@ -9971,6 +11299,78 @@ ${hint.trim()}`);
9971
11299
  - **Batch ops:** Use \`replace\` with glob patterns for multi-file surgical changes
9972
11300
 
9973
11301
  When unsure about a file's current state, read it first rather than assuming.`);
11302
+ const hasDelegate = tools.some((t2) => t2.name === "delegate");
11303
+ if (hasDelegate) {
11304
+ const delegateTool = tools.find((t2) => t2.name === "delegate");
11305
+ const enumValues = (() => {
11306
+ const role = delegateTool?.inputSchema?.properties?.role?.enum;
11307
+ return Array.isArray(role) ? role.filter((r) => typeof r === "string") : [];
11308
+ })();
11309
+ const roleList = enumValues.length > 0 ? enumValues.join(", ") : "(no roster configured)";
11310
+ lines.push(`
11311
+ ## Delegation
11312
+
11313
+ You have a \`delegate\` tool that hands a discrete piece of work to a
11314
+ dedicated subagent (its own context, its own LLM call, its own budget
11315
+ cap) and waits for the result. Use it proactively when:
11316
+
11317
+ - **The task fans out naturally** \u2014 e.g. "audit these 5 files for
11318
+ security issues" splits cleanly into 5 parallel \`delegate\` calls,
11319
+ one per file or per role. Fire them through the provider's
11320
+ parallel-tool-call surface in the same turn.
11321
+ - **A specialized role exists** \u2014 the roster has tuned prompts and
11322
+ budgets for: ${roleList}. Reach for a role when the description
11323
+ matches your subtask; otherwise pass \`name\` + \`provider\` + \`model\`.
11324
+ - **A subtask would blow up your context** \u2014 long log analyses, large
11325
+ diff reviews, multi-file refactor plans. The subagent absorbs the
11326
+ reading cost and hands back a summary.
11327
+ - **You'd otherwise switch hats mid-turn** \u2014 instead of stopping a code
11328
+ fix to do a security pass, delegate the security pass.
11329
+
11330
+ ### Scope it tight \u2014 narrow tasks succeed, broad tasks time out
11331
+
11332
+ A subagent has a finite iteration / tool-call budget (typically 50\u201380
11333
+ iterations, 200\u2013300 tool calls). Tasks that mention "ALL files" or "the
11334
+ entire codebase" reliably exhaust that budget without producing a clean
11335
+ answer \u2014 the delegate returns with \`stopReason: budget_exhausted\` and
11336
+ no useful output.
11337
+
11338
+ - \u274C BAD: \`"Analyze ALL .ts files in src/ for bugs"\`
11339
+ - \u274C BAD: \`"Audit the codebase for security issues"\`
11340
+ - \u274C BAD: \`"Plan a refactor of the whole project"\`
11341
+ - \u2705 GOOD: \`"Audit src/auth/session.ts for null-deref bugs in the login flow"\`
11342
+ - \u2705 GOOD: \`"Check packages/core/src/storage/*.ts for unhandled promise rejections (~6 files)"\`
11343
+ - \u2705 GOOD: \`"Plan a phased refactor of the InMemoryBridge transport (3 files in coordination/)"\`
11344
+
11345
+ If you need fleet-wide coverage, **fan out**: list the target files
11346
+ yourself first (one quick \`glob\` call), then fire one \`delegate\` per
11347
+ chunk of \u22645\u201310 files in parallel.
11348
+
11349
+ ### Reading the result
11350
+
11351
+ \`delegate\` returns a structured object. Look at \`stopReason\`:
11352
+
11353
+ - \`end_turn\` \u2014 subagent finished cleanly, \`result\` has the answer.
11354
+ - \`budget_exhausted\` \u2014 task was too broad; \`partial.lastAssistantText\`
11355
+ has whatever it managed. Narrow the next try.
11356
+ - \`subagent_timeout\` / \`host_timeout\` \u2014 likewise partial; raise
11357
+ \`timeoutMs\` only if you have a reason to believe more time would help.
11358
+ - \`aborted\` \u2014 the user or another tool stopped this worker; don't retry
11359
+ silently.
11360
+ - \`error\` \u2014 infrastructure problem; surface it.
11361
+
11362
+ Stay in-process (no \`delegate\`) when:
11363
+ - The task is trivial or atomic.
11364
+ - The information needed is already in your context.
11365
+ - The user is mid-conversation and expects an immediate reply from you,
11366
+ not a research detour through a subagent.
11367
+
11368
+ \`delegate\` auto-promotes the host into director mode the first time
11369
+ it's called \u2014 you do not need to call any setup tool. For fine-grained
11370
+ control over a long-running fleet (spawn N workers, hand them tasks
11371
+ one by one, roll up results), use \`spawn_subagent\` + \`assign_task\` +
11372
+ \`await_tasks\` directly; \`delegate\` is the one-call shortcut.`);
11373
+ }
9974
11374
  const hasContextManager = tools.some((t2) => t2.name === "context_manager");
9975
11375
  if (hasContextManager) {
9976
11376
  const maxCtx = this.opts.modelCapabilities?.maxContextTokens ?? 128e3;
@@ -10159,6 +11559,21 @@ var ToolRegistry = class {
10159
11559
  this.tools.set(tool.name, { tool, owner });
10160
11560
  return true;
10161
11561
  }
11562
+ /**
11563
+ * Bulk-register multiple tools at once. Each tool that conflicts with an
11564
+ * existing registration is silently skipped — use `registerAllOrThrow`
11565
+ * if you want it to throw on conflicts.
11566
+ */
11567
+ registerAll(tools, owner = "core") {
11568
+ for (const tool of tools) this.tryRegister(tool, owner);
11569
+ }
11570
+ /**
11571
+ * Bulk-register and throw on the first conflict. Use when you need
11572
+ * strict registration (e.g. at boot time).
11573
+ */
11574
+ registerAllOrThrow(tools, owner = "core") {
11575
+ for (const tool of tools) this.register(tool, owner);
11576
+ }
10162
11577
  /**
10163
11578
  * Register a tool as a default. If the tool name is already registered,
10164
11579
  * this is a no-op — the existing registration (from core or another
@@ -10186,6 +11601,30 @@ var ToolRegistry = class {
10186
11601
  }
10187
11602
  this.tools.set(name, { tool, owner });
10188
11603
  }
11604
+ /**
11605
+ * Wrap (decorate) an existing tool. The wrapper receives the current
11606
+ * tool and must return a new tool — typically the same tool with a
11607
+ * wrapped `execute` or `executeStream`. Throws if the tool is not
11608
+ * registered.
11609
+ *
11610
+ * Multiple wraps stack: each wrapper gets the output of the previous.
11611
+ *
11612
+ * @example
11613
+ * registry.wrap('bash', (t) => ({ ...t, permission: 'confirm' }));
11614
+ */
11615
+ wrap(name, wrapper, owner = "core") {
11616
+ const entry = this.tools.get(name);
11617
+ if (!entry) {
11618
+ throw new WrongStackError({
11619
+ message: `Tool "${name}" not registered; cannot wrap`,
11620
+ code: "REGISTRY_NOT_FOUND",
11621
+ subsystem: "container",
11622
+ context: { tool: name }
11623
+ });
11624
+ }
11625
+ const wrapped = wrapper(entry.tool);
11626
+ this.tools.set(name, { tool: wrapped, owner: `${entry.owner}+${owner}` });
11627
+ }
10189
11628
  get(name) {
10190
11629
  return this.tools.get(name)?.tool;
10191
11630
  }
@@ -10195,6 +11634,24 @@ var ToolRegistry = class {
10195
11634
  list() {
10196
11635
  return Array.from(this.tools.values()).map((e) => e.tool);
10197
11636
  }
11637
+ /**
11638
+ * Group tools by their `category` field. Tools without a category
11639
+ * are placed under the key `""` (empty string). Returns a Map of
11640
+ * category → tools, sorted by registration order within each category.
11641
+ */
11642
+ listByCategory() {
11643
+ const map = /* @__PURE__ */ new Map();
11644
+ for (const { tool } of this.tools.values()) {
11645
+ const cat = tool.category ?? "";
11646
+ let group = map.get(cat);
11647
+ if (!group) {
11648
+ group = [];
11649
+ map.set(cat, group);
11650
+ }
11651
+ group.push(tool);
11652
+ }
11653
+ return map;
11654
+ }
10198
11655
  listWithOwner() {
10199
11656
  return Array.from(this.tools.values());
10200
11657
  }
@@ -10214,6 +11671,12 @@ var ProviderRegistry = class {
10214
11671
  register(f) {
10215
11672
  this.factories.set(f.type, f);
10216
11673
  }
11674
+ /**
11675
+ * Bulk-register multiple provider factories at once.
11676
+ */
11677
+ registerAll(factories) {
11678
+ for (const f of factories) this.register(f);
11679
+ }
10217
11680
  /**
10218
11681
  * Override an existing factory. Throws if no factory is registered
10219
11682
  * for the given type. Use this to safely replace a provider at runtime
@@ -10282,6 +11745,12 @@ var SlashCommandRegistry = class {
10282
11745
  }
10283
11746
  return this.cmds.delete(name);
10284
11747
  }
11748
+ /**
11749
+ * Bulk-register multiple slash commands at once.
11750
+ */
11751
+ registerAll(cmds, owner = "core") {
11752
+ for (const cmd of cmds) this.register(cmd, owner);
11753
+ }
10285
11754
  get(name) {
10286
11755
  return this.cmds.get(name)?.cmd;
10287
11756
  }
@@ -10354,15 +11823,23 @@ var DefaultPluginAPI = class {
10354
11823
  providers;
10355
11824
  mcp;
10356
11825
  slashCommands;
11826
+ extensions;
11827
+ session;
11828
+ metrics;
10357
11829
  config;
10358
11830
  log;
11831
+ configStore;
10359
11832
  pluginCleanupFns = [];
10360
11833
  constructor(init) {
10361
11834
  const owner = init.ownerName;
10362
11835
  this.container = init.container;
10363
11836
  this.events = init.events;
10364
11837
  this.config = init.config;
11838
+ this.configStore = init.configStore;
10365
11839
  this.log = init.log.child({ plugin: owner });
11840
+ this.extensions = init.extensions ?? new ExtensionRegistry();
11841
+ this.session = init.sessionWriter ?? noopSession;
11842
+ this.metrics = init.metricsSink ? scopedMetrics(init.metricsSink, owner) : noopMetrics;
10366
11843
  const pipelines = init.pipelines;
10367
11844
  const readonlyPipelines = {};
10368
11845
  for (const [key, pipeline] of Object.entries(pipelines)) {
@@ -10373,6 +11850,7 @@ var DefaultPluginAPI = class {
10373
11850
  this.tools = {
10374
11851
  register: (t2) => tr.register(t2, owner),
10375
11852
  unregister: (name) => tr.unregister(name),
11853
+ wrap: (name, wrapper) => tr.wrap(name, wrapper, owner),
10376
11854
  get: (name) => tr.get(name),
10377
11855
  list: () => tr.list()
10378
11856
  };
@@ -10396,6 +11874,19 @@ var DefaultPluginAPI = class {
10396
11874
  this.pluginCleanupFns.push(off);
10397
11875
  return off;
10398
11876
  }
11877
+ onPattern(pattern, handler) {
11878
+ const off = this.events.onPattern(pattern, handler);
11879
+ this.pluginCleanupFns.push(off);
11880
+ return off;
11881
+ }
11882
+ emitCustom(event, payload) {
11883
+ this.events.emit(event, payload);
11884
+ }
11885
+ onConfigChange(handler) {
11886
+ if (!this.configStore) return () => {
11887
+ };
11888
+ return this.configStore.watch(handler);
11889
+ }
10399
11890
  /** Called by the plugin loader when uninstalling the plugin. */
10400
11891
  drainCleanup() {
10401
11892
  for (const fn of this.pluginCleanupFns.splice(0)) {
@@ -10405,6 +11896,9 @@ var DefaultPluginAPI = class {
10405
11896
  }
10406
11897
  }
10407
11898
  }
11899
+ registerSystemPromptContributor(c) {
11900
+ return this.extensions.registerSystemPromptContributor(c);
11901
+ }
10408
11902
  };
10409
11903
  var noopMcp = {
10410
11904
  start: async () => void 0,
@@ -10425,6 +11919,32 @@ var noopSlashCommands = {
10425
11919
  return [];
10426
11920
  }
10427
11921
  };
11922
+ var noopSession = {
11923
+ append: async () => {
11924
+ }
11925
+ };
11926
+ var noopMetrics = {
11927
+ counter() {
11928
+ },
11929
+ histogram() {
11930
+ },
11931
+ gauge() {
11932
+ }
11933
+ };
11934
+ function scopedMetrics(sink, pluginName) {
11935
+ const prefix = `plugin.${pluginName}.`;
11936
+ return {
11937
+ counter(name, value, labels) {
11938
+ sink.counter(`${prefix}${name}`, value, labels);
11939
+ },
11940
+ histogram(name, value, labels) {
11941
+ sink.histogram(`${prefix}${name}`, value, labels);
11942
+ },
11943
+ gauge(name, value, labels) {
11944
+ sink.gauge(`${prefix}${name}`, value, labels);
11945
+ }
11946
+ };
11947
+ }
10428
11948
 
10429
11949
  // src/plugin/loader.ts
10430
11950
  var KERNEL_API_VERSION = "0.1.10";
@@ -10450,6 +11970,16 @@ function satisfies(range, version) {
10450
11970
  function normalizeDep(d) {
10451
11971
  return typeof d === "string" ? { name: d } : d;
10452
11972
  }
11973
+ function shallowMerge(defaults, overrides) {
11974
+ if (overrides === void 0 || overrides === null) return { ...defaults };
11975
+ if (typeof overrides !== "object") return { ...defaults };
11976
+ const ov = overrides;
11977
+ const out = { ...defaults };
11978
+ for (const key of Object.keys(ov)) {
11979
+ out[key] = ov[key];
11980
+ }
11981
+ return out;
11982
+ }
10453
11983
  function topoSort(plugins) {
10454
11984
  const map = /* @__PURE__ */ new Map();
10455
11985
  for (const p of plugins) map.set(p.name, p);
@@ -10545,6 +12075,11 @@ async function loadPlugins(plugins, opts) {
10545
12075
  failed.push({ plugin, err });
10546
12076
  continue;
10547
12077
  }
12078
+ if (plugin.defaultConfig && opts.pluginOptions) {
12079
+ const userOpts = opts.pluginOptions[plugin.name];
12080
+ const merged = shallowMerge(plugin.defaultConfig, userOpts);
12081
+ opts.pluginOptions[plugin.name] = merged;
12082
+ }
10548
12083
  if (plugin.configSchema && opts.pluginOptions) {
10549
12084
  const pluginOpts = opts.pluginOptions[plugin.name];
10550
12085
  if (pluginOpts !== void 0) {
@@ -10566,7 +12101,7 @@ async function loadPlugins(plugins, opts) {
10566
12101
  }
10567
12102
  try {
10568
12103
  const rawApi = opts.apiFactory(plugin);
10569
- const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log) : rawApi;
12104
+ const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log, opts.enforceCapabilities) : rawApi;
10570
12105
  await plugin.setup(api);
10571
12106
  loaded.push(plugin);
10572
12107
  opts.log.info(`Plugin "${plugin.name}" loaded`);
@@ -10590,10 +12125,18 @@ async function unloadPlugins(loadedPlugins, opts) {
10590
12125
  }
10591
12126
  }
10592
12127
  }
10593
- function wrapApiForCapabilityCheck(plugin, api, log) {
12128
+ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
10594
12129
  const caps = plugin.capabilities ?? {};
10595
- const warn = (subsystem, detail) => {
12130
+ const violate = (subsystem, detail) => {
10596
12131
  const msg = `Plugin "${plugin.name}" used ${subsystem} without declaring capabilities.${subsystem} \u2014 ${detail}`;
12132
+ if (enforce) {
12133
+ throw new PluginError({
12134
+ message: msg,
12135
+ code: "PLUGIN_LOAD_FAILED",
12136
+ pluginName: plugin.name,
12137
+ context: { subsystem, detail }
12138
+ });
12139
+ }
10597
12140
  if (typeof log.warn === "function") log.warn(msg);
10598
12141
  else log.error(msg);
10599
12142
  };
@@ -10601,7 +12144,7 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
10601
12144
  get(target, prop, receiver) {
10602
12145
  if (prop === "register") {
10603
12146
  return (t2) => {
10604
- warn("tools", `register(${t2?.name ?? "<unknown>"})`);
12147
+ violate("tools", `register(${t2?.name ?? "<unknown>"})`);
10605
12148
  return target.register(t2);
10606
12149
  };
10607
12150
  }
@@ -10612,7 +12155,7 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
10612
12155
  get(target, prop, receiver) {
10613
12156
  if (prop === "register") {
10614
12157
  return (f) => {
10615
- warn("providers", `register(${f?.type ?? "<unknown>"})`);
12158
+ violate("providers", `register(${f?.type ?? "<unknown>"})`);
10616
12159
  return target.register(f);
10617
12160
  };
10618
12161
  }
@@ -10623,7 +12166,10 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
10623
12166
  get(target, prop, receiver) {
10624
12167
  if (prop === "register") {
10625
12168
  return (c) => {
10626
- warn("slashCommands", `register(${c?.name ?? "<unknown>"})`);
12169
+ violate(
12170
+ "slashCommands",
12171
+ `register(${c?.name ?? "<unknown>"})`
12172
+ );
10627
12173
  return target.register(c);
10628
12174
  };
10629
12175
  }
@@ -10634,7 +12180,7 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
10634
12180
  get(target, prop, receiver) {
10635
12181
  if (prop === "start") {
10636
12182
  return (cfg) => {
10637
- warn("mcp", `start(${cfg?.name ?? "<unknown>"})`);
12183
+ violate("mcp", `start(${cfg?.name ?? "<unknown>"})`);
10638
12184
  return target.start(cfg);
10639
12185
  };
10640
12186
  }
@@ -10659,6 +12205,6 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
10659
12205
  });
10660
12206
  }
10661
12207
 
10662
- export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DoneConditionChecker, EventBus, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, allServers, asBlocks, asText, atomicWrite, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatTodosList, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadPlugins, loadProjectModes, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
12208
+ export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, emptyPlan, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatPlan, formatTodosList, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, removePlanItem, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
10663
12209
  //# sourceMappingURL=index.js.map
10664
12210
  //# sourceMappingURL=index.js.map