@wrongstack/core 0.155.0 → 0.236.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/{agent-bridge-BbZU5TPN.d.ts → agent-bridge-Cimv7bK7.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-Bsueu0J2.d.ts → agent-subagent-runner-C658wj_c.d.ts} +9 -8
  3. package/dist/{brain-CS_B0vIE.d.ts → brain-sCZ3lCjq.d.ts} +26 -2
  4. package/dist/{compactor-BueGt7LG.d.ts → compactor-BRfg3QPd.d.ts} +1 -1
  5. package/dist/{config-BaVThgnT.d.ts → config-Koq6f3fs.d.ts} +2 -2
  6. package/dist/{context-C7G_MtLV.d.ts → context-CLz3z_E8.d.ts} +126 -2
  7. package/dist/coordination/index.d.ts +70 -13
  8. package/dist/coordination/index.js +1983 -145
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +26 -26
  11. package/dist/defaults/index.js +1105 -289
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +45 -16
  14. package/dist/execution/index.js +224 -53
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +86 -0
  17. package/dist/execution/prompt-enhancer.js +125 -0
  18. package/dist/execution/prompt-enhancer.js.map +1 -0
  19. package/dist/extension/index.d.ts +6 -6
  20. package/dist/extension/index.js +3 -1
  21. package/dist/extension/index.js.map +1 -1
  22. package/dist/{goal-preamble-CbV8pXLD.d.ts → goal-preamble-CnbzyVvl.d.ts} +19 -10
  23. package/dist/{index-CI1hRfPt.d.ts → index-BlMqh5GO.d.ts} +8 -8
  24. package/dist/{index-B5wz-GXm.d.ts → index-C2eSNPsB.d.ts} +7 -5
  25. package/dist/index.d.ts +438 -128
  26. package/dist/index.js +4974 -836
  27. package/dist/index.js.map +1 -1
  28. package/dist/infrastructure/index.d.ts +7 -7
  29. package/dist/infrastructure/index.js +61 -13
  30. package/dist/infrastructure/index.js.map +1 -1
  31. package/dist/kernel/index.d.ts +9 -9
  32. package/dist/kernel/index.js +7 -1
  33. package/dist/kernel/index.js.map +1 -1
  34. package/dist/{llm-selector-CP72f1lC.d.ts → llm-selector-D22R4AFz.d.ts} +2 -2
  35. package/dist/logger-DmmQhf4P.d.ts +65 -0
  36. package/dist/{mcp-servers-CPERR2De.d.ts → mcp-servers-DFbirBv6.d.ts} +3 -3
  37. package/dist/models/index.d.ts +5 -5
  38. package/dist/models/index.js +89 -9
  39. package/dist/models/index.js.map +1 -1
  40. package/dist/{models-registry-D90K9UnM.d.ts → models-registry-CnJRjTXc.d.ts} +1 -1
  41. package/dist/{multi-agent-coordinator-BSKSFNhv.d.ts → multi-agent-coordinator-60weDZoA.d.ts} +8 -8
  42. package/dist/{null-fleet-bus-CGOez8Le.d.ts → null-fleet-bus-1068dEnr.d.ts} +7 -7
  43. package/dist/observability/index.d.ts +2 -2
  44. package/dist/package-outdated-watcher-pzJ5w7y8.d.ts +560 -0
  45. package/dist/{parallel-eternal-engine-CYoTKjsz.d.ts → parallel-eternal-engine-DtG1fjc9.d.ts} +13 -9
  46. package/dist/{path-resolver-DuhlmPil.d.ts → path-resolver-CA1ULU0J.d.ts} +3 -3
  47. package/dist/{permission-B7nKnEvQ.d.ts → permission-DbWPbuoA.d.ts} +1 -1
  48. package/dist/{permission-policy-8-6zBmfA.d.ts → permission-policy-AOk0LVsV.d.ts} +2 -2
  49. package/dist/pipeline-DsmlwTXu.d.ts +493 -0
  50. package/dist/{plan-templates-DbH7lg-t.d.ts → plan-templates-DPABrDvy.d.ts} +18 -7
  51. package/dist/{provider-runner-Cocq0O9E.d.ts → provider-runner-D0HgUqwV.d.ts} +3 -3
  52. package/dist/{retry-policy-rutAfVeR.d.ts → retry-policy-BVnkbMET.d.ts} +1 -1
  53. package/dist/sdd/index.d.ts +8 -8
  54. package/dist/sdd/index.js +215 -79
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/{secret-vault-w8MbUe2Q.d.ts → secret-vault-CeVNiy_f.d.ts} +3 -2
  57. package/dist/security/index.d.ts +5 -4
  58. package/dist/security/index.js +155 -13
  59. package/dist/security/index.js.map +1 -1
  60. package/dist/{selector-4vDFZKt3.d.ts → selector-Cb4_9-hf.d.ts} +1 -1
  61. package/dist/{session-event-bridge-DWlvglC2.d.ts → session-event-bridge-BhtkkFFy.d.ts} +4 -2
  62. package/dist/{session-reader-BAtCxdaw.d.ts → session-reader-CCOssnBS.d.ts} +1 -1
  63. package/dist/skills/index.js +171 -21
  64. package/dist/skills/index.js.map +1 -1
  65. package/dist/storage/index.d.ts +150 -12
  66. package/dist/storage/index.js +1041 -214
  67. package/dist/storage/index.js.map +1 -1
  68. package/dist/types/index.d.ts +67 -20
  69. package/dist/types/index.js +557 -52
  70. package/dist/types/index.js.map +1 -1
  71. package/dist/utils/expect-defined.js +3 -1
  72. package/dist/utils/expect-defined.js.map +1 -1
  73. package/dist/utils/index.d.ts +16 -4
  74. package/dist/utils/index.js +40 -14
  75. package/dist/utils/index.js.map +1 -1
  76. package/dist/{wstack-paths-DD50Omgn.d.ts → wstack-paths-CJjEwPXn.d.ts} +14 -1
  77. package/package.json +7 -3
  78. package/skills/chimera/SKILL.md +105 -0
  79. package/skills/research-web/SKILL.md +342 -0
  80. package/dist/logger-B9J5puGM.d.ts +0 -32
  81. package/dist/pipeline-BG7UgbDc.d.ts +0 -239
@@ -1,7 +1,8 @@
1
- import { randomUUID, randomBytes } from 'crypto';
1
+ import { randomUUID, createHash, randomBytes } from 'crypto';
2
2
  import * as fsp6 from 'fs/promises';
3
3
  import * as path4 from 'path';
4
4
  import { isAbsolute, resolve } from 'path';
5
+ import * as os from 'os';
5
6
  import { hostname } from 'os';
6
7
  import { EventEmitter } from 'events';
7
8
 
@@ -54,12 +55,12 @@ var BrainDecisionQueue = class {
54
55
  options: request.options,
55
56
  rationale: "Decision escalated to human authority."
56
57
  };
57
- const pending = new Promise((resolve2) => {
58
- const entry = { request, resolve: resolve2 };
58
+ const pending = new Promise((resolve3) => {
59
+ const entry = { request, resolve: resolve3 };
59
60
  if (this.opts.timeoutMs && this.opts.timeoutMs > 0) {
60
61
  entry.timer = setTimeout(() => {
61
62
  this.pending.delete(request.id);
62
- resolve2({ type: "deny", reason: "Brain human decision timed out." });
63
+ resolve3({ type: "deny", reason: "Brain human decision timed out." });
63
64
  }, this.opts.timeoutMs);
64
65
  }
65
66
  this.pending.set(request.id, entry);
@@ -191,6 +192,49 @@ async function atomicWrite(targetPath, content, opts = {}) {
191
192
  async function ensureDir(dir) {
192
193
  await fsp6.mkdir(dir, { recursive: true });
193
194
  }
195
+ async function withFileLock(targetPath, fn, opts = {}) {
196
+ const dir = path4.dirname(targetPath);
197
+ await fsp6.mkdir(dir, { recursive: true });
198
+ const lockPath = path4.join(dir, `.${path4.basename(targetPath)}.lock`);
199
+ const timeoutMs = opts.timeoutMs ?? 5e3;
200
+ const staleMs = opts.staleMs ?? 3e4;
201
+ const started = Date.now();
202
+ let handle;
203
+ for (; ; ) {
204
+ try {
205
+ handle = await fsp6.open(lockPath, "wx");
206
+ await handle.writeFile(`${process.pid}:${Date.now()}`);
207
+ break;
208
+ } catch (err) {
209
+ if (err.code !== "EEXIST") throw err;
210
+ try {
211
+ const stat5 = await fsp6.stat(lockPath);
212
+ if (Date.now() - stat5.mtimeMs > staleMs) {
213
+ await fsp6.unlink(lockPath);
214
+ continue;
215
+ }
216
+ } catch {
217
+ continue;
218
+ }
219
+ if (Date.now() - started >= timeoutMs) {
220
+ throw new Error(`Timed out waiting for file lock: ${targetPath}`);
221
+ }
222
+ await new Promise((resolve3) => setTimeout(resolve3, 25));
223
+ }
224
+ }
225
+ try {
226
+ return await fn();
227
+ } finally {
228
+ try {
229
+ await handle?.close();
230
+ } catch {
231
+ }
232
+ try {
233
+ await fsp6.unlink(lockPath);
234
+ } catch {
235
+ }
236
+ }
237
+ }
194
238
  var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
195
239
  async function renameWithRetry(from, to) {
196
240
  if (process.platform !== "win32") {
@@ -209,7 +253,7 @@ async function renameWithRetry(from, to) {
209
253
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
210
254
  throw err;
211
255
  }
212
- await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
256
+ await new Promise((resolve3) => setTimeout(resolve3, delays[i]));
213
257
  }
214
258
  }
215
259
  throw lastErr;
@@ -395,6 +439,62 @@ function safeParse(input, maxBytes = 5e6) {
395
439
  }
396
440
  }
397
441
 
442
+ // src/types/errors.ts
443
+ var ERROR_CODES = {
444
+ // Provider
445
+ PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
446
+ PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
447
+ PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
448
+ PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
449
+ PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
450
+ PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
451
+ // Agent
452
+ AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
453
+ AGENT_ABORTED: "AGENT_ABORTED",
454
+ AGENT_RUN_FAILED: "AGENT_RUN_FAILED"};
455
+ var WrongStackError = class extends Error {
456
+ code;
457
+ subsystem;
458
+ severity;
459
+ recoverable;
460
+ context;
461
+ constructor(opts) {
462
+ super(opts.message, { cause: opts.cause });
463
+ this.name = "WrongStackError";
464
+ this.code = opts.code;
465
+ this.subsystem = opts.subsystem;
466
+ this.severity = opts.severity ?? "error";
467
+ this.recoverable = opts.recoverable ?? false;
468
+ this.context = opts.context;
469
+ }
470
+ /**
471
+ * Render a one-line user-facing description.
472
+ * Subclasses should override for domain-specific formatting.
473
+ */
474
+ describe() {
475
+ const ctx = this.context ? ` ${formatContext(this.context)}` : "";
476
+ return `${this.code}: ${this.message}${ctx}`;
477
+ }
478
+ };
479
+ function formatContext(ctx) {
480
+ const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
481
+ return parts.length > 0 ? `[${parts.join(" ")}]` : "";
482
+ }
483
+ var AgentError = class extends WrongStackError {
484
+ constructor(opts) {
485
+ super({
486
+ message: opts.message,
487
+ code: opts.code,
488
+ subsystem: "agent",
489
+ severity: opts.code === ERROR_CODES.AGENT_ABORTED ? "warning" : "error",
490
+ recoverable: opts.recoverable ?? opts.code === ERROR_CODES.AGENT_ITERATION_LIMIT,
491
+ context: opts.context,
492
+ cause: opts.cause
493
+ });
494
+ this.name = "AgentError";
495
+ }
496
+ };
497
+
398
498
  // src/coordination/in-memory-transport.ts
399
499
  var InMemoryBridgeTransport = class {
400
500
  subs = /* @__PURE__ */ new Map();
@@ -483,28 +583,40 @@ var InMemoryAgentBridge = class {
483
583
  return () => this.subscriptions.delete(handler);
484
584
  }
485
585
  async request(msg, timeoutMs) {
486
- if (this.stopped) throw new Error("Bridge is stopped");
586
+ if (this.stopped) throw new AgentError({
587
+ message: "Bridge is stopped",
588
+ code: ERROR_CODES.AGENT_ABORTED
589
+ });
487
590
  const timeout = timeoutMs ?? this.timeoutMs;
488
591
  const correlationId = msg.id;
489
592
  if (this.inflightGuards.has(correlationId)) {
490
- throw new Error(
491
- `Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`
492
- );
593
+ throw new AgentError({
594
+ message: `Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`,
595
+ code: ERROR_CODES.AGENT_RUN_FAILED,
596
+ context: { correlationId }
597
+ });
493
598
  }
494
599
  this.inflightGuards.add(correlationId);
495
- return new Promise((resolve2, reject) => {
600
+ return new Promise((resolve3, reject) => {
496
601
  const timer = setTimeout(() => {
497
602
  this.inflightGuards.delete(correlationId);
498
603
  this.pendingRequests.delete(correlationId);
499
- reject(new Error(`Request ${correlationId} timed out after ${timeout}ms`));
604
+ reject(new AgentError({
605
+ message: `Request ${correlationId} timed out after ${timeout}ms`,
606
+ code: ERROR_CODES.AGENT_RUN_FAILED,
607
+ context: { correlationId, timeoutMs: timeout }
608
+ }));
500
609
  }, timeout);
501
610
  if (!this.inflightGuards.has(correlationId)) {
502
611
  clearTimeout(timer);
503
- reject(new Error("Bridge stopped"));
612
+ reject(new AgentError({
613
+ message: "Bridge stopped",
614
+ code: ERROR_CODES.AGENT_ABORTED
615
+ }));
504
616
  return;
505
617
  }
506
618
  this.pendingRequests.set(correlationId, {
507
- resolve: resolve2,
619
+ resolve: resolve3,
508
620
  reject,
509
621
  timer
510
622
  });
@@ -521,7 +633,10 @@ var InMemoryAgentBridge = class {
521
633
  this.stopped = true;
522
634
  for (const [, p] of this.pendingRequests) {
523
635
  clearTimeout(p.timer);
524
- p.reject(new Error("Bridge stopped"));
636
+ p.reject(new AgentError({
637
+ message: "Bridge stopped",
638
+ code: ERROR_CODES.AGENT_ABORTED
639
+ }));
525
640
  }
526
641
  this.pendingRequests.clear();
527
642
  this.inflightGuards.clear();
@@ -546,7 +661,9 @@ function createMessage(type, from, payload, to) {
546
661
  // src/utils/expect-defined.ts
547
662
  function expectDefined(value, label) {
548
663
  if (value === null || value === void 0) {
549
- throw new Error("Expected value to be defined");
664
+ const err = new Error("Expected value to be defined");
665
+ err.name = "ExpectDefinedError";
666
+ throw err;
550
667
  }
551
668
  return value;
552
669
  }
@@ -1306,7 +1423,23 @@ Bridge contract:
1306
1423
  subagents' context. Those are not yours to read.
1307
1424
  - Your final task output is what the Director sees. Be concise,
1308
1425
  structured, and self-contained \u2014 assume the Director will paste your
1309
- output into its own context.`;
1426
+ output into its own context.
1427
+
1428
+ Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` tools):
1429
+ - You are part of a project-wide fleet that may span other terminals and
1430
+ WebUIs. Your mailbox identity is \`<your-name>@<session-tag>\` (unique
1431
+ per session); mail addressed to you, to your bare name, or broadcast
1432
+ to \`*\` is injected into your conversation automatically before each
1433
+ step \u2014 read it once, it is marked read.
1434
+ - Broadcast milestones: when you complete a significant piece of work,
1435
+ \`mail_send to="*"\` a one-line summary so parallel agents don't collide
1436
+ with or duplicate it.
1437
+ - Hand off matching work: if another online agent's role fits a follow-up
1438
+ better (e.g. a reviewer while you just wrote code), \`mail_send\` it to
1439
+ their exact id instead of doing everything yourself. Discover ids with
1440
+ \`mailbox action=online\`.
1441
+ - Answer your mail: reply to the sender's exact \`from\` id. When done with
1442
+ an assigned task, post a \`result\` back to whoever assigned it.`;
1310
1443
  function composeDirectorPrompt(parts = {}) {
1311
1444
  const sections = [];
1312
1445
  const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -1380,11 +1513,11 @@ var HEAVY_BUDGET = {
1380
1513
  };
1381
1514
  var TOOLS = {
1382
1515
  /** Pure read/inspect — safe for analysis and review agents. */
1383
- read: ["read", "grep", "glob", "search", "tree"],
1516
+ read: ["read", "grep", "glob", "search", "tree", "mailbox"],
1384
1517
  /** Read + structured inspection (logs, diffs, json, dependency audit). */
1385
- inspect: ["read", "grep", "glob", "search", "tree", "json", "diff", "logs", "audit"],
1518
+ inspect: ["read", "grep", "glob", "search", "tree", "json", "diff", "logs", "audit", "mailbox"],
1386
1519
  /** Read + edit (no shell). For agents that write code/docs but don't run it. */
1387
- write: ["read", "grep", "glob", "search", "tree", "write", "edit", "replace", "patch"],
1520
+ write: ["read", "grep", "glob", "search", "tree", "write", "edit", "replace", "patch", "mailbox"],
1388
1521
  /** Full build loop: edit + run (lint/format/typecheck/test/bash). */
1389
1522
  build: [
1390
1523
  "read",
@@ -1401,16 +1534,17 @@ var TOOLS = {
1401
1534
  "lint",
1402
1535
  "format",
1403
1536
  "typecheck",
1404
- "test"
1537
+ "test",
1538
+ "mailbox"
1405
1539
  ],
1406
1540
  /** Version control. */
1407
1541
  vcs: ["read", "grep", "glob", "git", "diff"],
1408
1542
  /** Dependency management + CVE audit. */
1409
- deps: ["read", "grep", "glob", "install", "outdated", "audit", "json"],
1543
+ deps: ["read", "grep", "glob", "install", "outdated", "audit", "json", "mailbox"],
1410
1544
  /** Documentation authoring. */
1411
- docs: ["read", "grep", "glob", "search", "tree", "write", "edit", "document"],
1545
+ docs: ["read", "grep", "glob", "search", "tree", "write", "edit", "document", "mailbox"],
1412
1546
  /** Web research. */
1413
- research: ["read", "grep", "glob", "search", "fetch"]
1547
+ research: ["read", "grep", "glob", "search", "fetch", "mailbox"]
1414
1548
  };
1415
1549
 
1416
1550
  // src/coordination/agents/phase1-discovery.ts
@@ -3823,7 +3957,7 @@ Working rules:
3823
3957
  id: "tech-stack",
3824
3958
  name: "Tech Stack Validator",
3825
3959
  role: "tech-stack",
3826
- tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json"],
3960
+ tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json", "mailbox"],
3827
3961
  prompt: `You are the Tech Stack Validator \u2014 a single-shot validation agent that fires
3828
3962
  before any package, library, or framework choice is committed.
3829
3963
 
@@ -3831,6 +3965,16 @@ Your ONLY job: verify that a technology choice is current, real, and not obsolet
3831
3965
  You are the "this isn't code, this is 10-year-old technology" agent. Intervene
3832
3966
  hard when the LLM hallucinates a version number or suggests dead tech.
3833
3967
 
3968
+ ## Before you begin
3969
+
3970
+ Check the inter-agent mailbox for pending tasks. Other agents or the file-watcher
3971
+ may have left assign messages with dependency files to audit:
3972
+ - mailbox action=check
3973
+
3974
+ If you find an assign message, use the specified file path and packages.
3975
+ When done, post results back:
3976
+ - mailbox action=send to=<sender> type=result subject="Tech stack audit results" body="..."
3977
+
3834
3978
  ## Critical rules
3835
3979
 
3836
3980
  1. **Verify existence.** Search npm registry (fetch https://registry.npmjs.org/<pkg>/latest)
@@ -3889,11 +4033,11 @@ When APPROVED:
3889
4033
  **Install**: pnpm add <name>@^<major>.<minor>.0`
3890
4034
  },
3891
4035
  budget: {
3892
- timeoutMs: 6e4,
3893
- maxIterations: 5,
3894
- maxToolCalls: 20,
3895
- maxTokens: 4e4,
3896
- maxCostUsd: 0.1
4036
+ timeoutMs: 12e4,
4037
+ maxIterations: 10,
4038
+ maxToolCalls: 40,
4039
+ maxTokens: 6e4,
4040
+ maxCostUsd: 0.25
3897
4041
  },
3898
4042
  capability: {
3899
4043
  phase: "meta",
@@ -5025,12 +5169,12 @@ var SubagentBudget = class _SubagentBudget {
5025
5169
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
5026
5170
  return Promise.resolve("stop");
5027
5171
  }
5028
- return new Promise((resolve2) => {
5172
+ return new Promise((resolve3) => {
5029
5173
  let resolved = false;
5030
5174
  const respond = (d) => {
5031
5175
  if (resolved) return;
5032
5176
  resolved = true;
5033
- resolve2(d);
5177
+ resolve3(d);
5034
5178
  };
5035
5179
  const fallback = setTimeout(
5036
5180
  () => respond("stop"),
@@ -5151,44 +5295,6 @@ var SubagentBudget = class _SubagentBudget {
5151
5295
  }
5152
5296
  };
5153
5297
 
5154
- // src/types/errors.ts
5155
- var ERROR_CODES = {
5156
- // Provider
5157
- PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
5158
- PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
5159
- PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
5160
- PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
5161
- PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
5162
- PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR"};
5163
- var WrongStackError = class extends Error {
5164
- code;
5165
- subsystem;
5166
- severity;
5167
- recoverable;
5168
- context;
5169
- constructor(opts) {
5170
- super(opts.message, { cause: opts.cause });
5171
- this.name = "WrongStackError";
5172
- this.code = opts.code;
5173
- this.subsystem = opts.subsystem;
5174
- this.severity = opts.severity ?? "error";
5175
- this.recoverable = opts.recoverable ?? false;
5176
- this.context = opts.context;
5177
- }
5178
- /**
5179
- * Render a one-line user-facing description.
5180
- * Subclasses should override for domain-specific formatting.
5181
- */
5182
- describe() {
5183
- const ctx = this.context ? ` ${formatContext(this.context)}` : "";
5184
- return `${this.code}: ${this.message}${ctx}`;
5185
- }
5186
- };
5187
- function formatContext(ctx) {
5188
- const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
5189
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
5190
- }
5191
-
5192
5298
  // src/types/provider.ts
5193
5299
  var ProviderError = class extends WrongStackError {
5194
5300
  status;
@@ -5263,6 +5369,9 @@ function providerStatusToCode(status, type) {
5263
5369
 
5264
5370
  // src/coordination/coordinator/error-classifier.ts
5265
5371
  function classifySubagentError(err, hints = {}) {
5372
+ if (err instanceof AgentError && err.cause) {
5373
+ return classifySubagentError(err.cause, hints);
5374
+ }
5266
5375
  const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
5267
5376
  if (err instanceof ProviderError) {
5268
5377
  const baseMessage2 = err.describe();
@@ -5295,7 +5404,7 @@ function classifySubagentError(err, hints = {}) {
5295
5404
  if (/agent exhausted iteration limit$/i.test(baseMessage)) {
5296
5405
  return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
5297
5406
  }
5298
- if (/empty response$/i.test(baseMessage)) {
5407
+ if (/empty response/i.test(baseMessage)) {
5299
5408
  return { kind: "empty_response", message: baseMessage, retryable: false, cause };
5300
5409
  }
5301
5410
  if (/^tool failed: /i.test(baseMessage)) {
@@ -5980,7 +6089,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5980
6089
  taskIds.map((id) => {
5981
6090
  const cached = this.completedResults.find((r) => r.taskId === id);
5982
6091
  if (cached) return cached;
5983
- return new Promise((resolve2, reject) => {
6092
+ return new Promise((resolve3, reject) => {
5984
6093
  const timeout = setTimeout(() => {
5985
6094
  this.off("task.completed", handler);
5986
6095
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -5989,7 +6098,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5989
6098
  if (result.taskId === id) {
5990
6099
  clearTimeout(timeout);
5991
6100
  this.off("task.completed", handler);
5992
- resolve2(result);
6101
+ resolve3(result);
5993
6102
  }
5994
6103
  };
5995
6104
  this.on("task.completed", handler);
@@ -7362,11 +7471,11 @@ var Director = class _Director {
7362
7471
  if (cached) return cached;
7363
7472
  const existing = this.taskWaiters.get(id);
7364
7473
  if (existing) return existing.promise;
7365
- let resolve2;
7474
+ let resolve3;
7366
7475
  const promise = new Promise((res) => {
7367
- resolve2 = res;
7476
+ resolve3 = res;
7368
7477
  });
7369
- this.taskWaiters.set(id, { promise, resolve: resolve2 });
7478
+ this.taskWaiters.set(id, { promise, resolve: resolve3 });
7370
7479
  return promise;
7371
7480
  })
7372
7481
  );
@@ -7762,7 +7871,7 @@ function createDelegateTool(opts) {
7762
7871
  subagentId
7763
7872
  });
7764
7873
  const dir = director;
7765
- const result = await new Promise((resolve2) => {
7874
+ const result = await new Promise((resolve3) => {
7766
7875
  let settled = false;
7767
7876
  let timer;
7768
7877
  const finish = (value) => {
@@ -7772,7 +7881,7 @@ function createDelegateTool(opts) {
7772
7881
  offTool();
7773
7882
  offIter();
7774
7883
  offProgress();
7775
- resolve2(value);
7884
+ resolve3(value);
7776
7885
  };
7777
7886
  const arm = () => {
7778
7887
  if (timer) clearTimeout(timer);
@@ -8182,21 +8291,40 @@ function makeAgentSubagentRunner(opts) {
8182
8291
  if (budgetError) throw budgetError;
8183
8292
  }
8184
8293
  if (result.status === "failed") {
8185
- throw result.error instanceof Error ? result.error : new Error(String(result.error ?? "agent failed"));
8294
+ throw result.error instanceof AgentError ? result.error : new AgentError({
8295
+ message: result.error instanceof Error ? result.error.message : String(result.error ?? "agent failed"),
8296
+ code: ERROR_CODES.AGENT_RUN_FAILED,
8297
+ cause: result.error
8298
+ });
8186
8299
  }
8187
8300
  if (result.status === "aborted") {
8188
- throw new Error("agent aborted");
8301
+ throw new AgentError({
8302
+ message: "agent aborted",
8303
+ code: ERROR_CODES.AGENT_ABORTED
8304
+ });
8189
8305
  }
8190
8306
  if (result.status === "max_iterations") {
8191
- throw new Error("agent exhausted iteration limit");
8307
+ throw new AgentError({
8308
+ message: "agent exhausted iteration limit",
8309
+ code: ERROR_CODES.AGENT_ITERATION_LIMIT,
8310
+ recoverable: true
8311
+ });
8192
8312
  }
8193
8313
  const usage = ctx.budget.usage();
8194
8314
  const finalText = (result.finalText ?? "").trim();
8195
8315
  if (finalText.length === 0 && usage.toolCalls === 0) {
8196
- throw new Error("empty response");
8316
+ throw new AgentError({
8317
+ message: "empty response \u2014 agent produced no text and no tool calls",
8318
+ code: ERROR_CODES.AGENT_RUN_FAILED,
8319
+ context: { iterations: result.iterations }
8320
+ });
8197
8321
  }
8198
8322
  if (finalText.length === 0 && lastToolFailed !== null) {
8199
- throw new Error(`tool failed: ${lastToolFailed}`);
8323
+ throw new AgentError({
8324
+ message: `unrecovered tool failure: ${lastToolFailed} \u2014 agent ended turn without acknowledging the error`,
8325
+ code: ERROR_CODES.AGENT_RUN_FAILED,
8326
+ context: { tool: lastToolFailed, iterations: result.iterations }
8327
+ });
8200
8328
  }
8201
8329
  return {
8202
8330
  result: result.finalText,
@@ -8361,7 +8489,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
8361
8489
  onClose: (s) => this.appendToIndex(s)
8362
8490
  });
8363
8491
  } catch (err) {
8364
- await handle.close().catch((e) => console.warn(`[session-store] handle.close() failed: ${e}`));
8492
+ await handle.close().catch((e) => console.warn(JSON.stringify({
8493
+ level: "warn",
8494
+ event: "session_store.handle_close_failed",
8495
+ message: e instanceof Error ? e.message : String(e),
8496
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8497
+ })));
8365
8498
  throw err;
8366
8499
  }
8367
8500
  }
@@ -8388,11 +8521,25 @@ var DefaultSessionStore = class _DefaultSessionStore {
8388
8521
  provider: data.metadata.provider
8389
8522
  },
8390
8523
  this.events,
8391
- { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber, onClose: (s) => this.appendToIndex(s) }
8524
+ {
8525
+ resumed: true,
8526
+ // Shard directory (sessions/<date>/) — must match create() so the
8527
+ // .summary.json sidecar lands next to the JSONL instead of the
8528
+ // sessions root (where summaryFor() would never find it).
8529
+ dir: path4.dirname(file),
8530
+ filePath: file,
8531
+ secretScrubber: this.secretScrubber,
8532
+ onClose: (s) => this.appendToIndex(s)
8533
+ }
8392
8534
  );
8393
8535
  return { writer, data };
8394
8536
  } catch (err) {
8395
- await handle.close().catch((e) => console.warn(`[session-store] handle.close() failed: ${e}`));
8537
+ await handle.close().catch((e) => console.warn(JSON.stringify({
8538
+ level: "warn",
8539
+ event: "session_store.handle_close_failed",
8540
+ message: e instanceof Error ? e.message : String(e),
8541
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8542
+ })));
8396
8543
  throw err;
8397
8544
  }
8398
8545
  }
@@ -8412,7 +8559,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
8412
8559
  }
8413
8560
  const meta = this.metaFromEvents(id, events);
8414
8561
  const { messages, usage } = this.replay(events, id);
8415
- return { metadata: meta, events, messages, usage };
8562
+ const toolCallEnds = extractToolCallEnds(events);
8563
+ return { metadata: meta, events, messages, usage, toolCallEnds };
8416
8564
  }
8417
8565
  async list(limit = 20) {
8418
8566
  try {
@@ -8568,10 +8716,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
8568
8716
  const stat5 = await fsp6.stat(full);
8569
8717
  const summary = await this.summarize(id, stat5.mtime.toISOString());
8570
8718
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
8571
- console.warn(
8572
- `[session-store] Failed to write manifest for "${id}":`,
8573
- err instanceof Error ? err.message : String(err)
8574
- );
8719
+ console.warn(JSON.stringify({
8720
+ level: "warn",
8721
+ event: "session_store.manifest_write_failed",
8722
+ sessionId: id,
8723
+ message: err instanceof Error ? err.message : String(err),
8724
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8725
+ }));
8575
8726
  });
8576
8727
  return summary;
8577
8728
  }
@@ -8579,17 +8730,48 @@ var DefaultSessionStore = class _DefaultSessionStore {
8579
8730
  /**
8580
8731
  * Delete a session and all associated files: JSONL, summary, plan/todos
8581
8732
  * sidecars, and the session directory (fleet.json, shared/, subagents/).
8733
+ *
8734
+ * Individual file deletions are best-effort (logged as structured warnings),
8735
+ * but a tombstone is always written so readIndex() filters this session out.
8736
+ * If the session directory itself can't be removed, the error is surfaced
8737
+ * to the caller so prune() can report it.
8582
8738
  */
8583
8739
  async deleteSession(id) {
8584
- await fsp6.unlink(this.sessionPath(id, ".jsonl")).catch((err) => console.warn(`[session-store] delete .jsonl failed: ${err}`));
8585
- await fsp6.unlink(this.sessionPath(id, ".summary.json")).catch((err) => console.warn(`[session-store] delete .summary.json failed: ${err}`));
8740
+ const jsonlPath = this.sessionPath(id, ".jsonl");
8741
+ const summaryPath = this.sessionPath(id, ".summary.json");
8586
8742
  const shardDir = path4.dirname(path4.join(this.dir, id));
8587
8743
  const base = path4.basename(id);
8588
- for (const ext of [".plan.json", ".todos.json"]) {
8589
- await fsp6.unlink(path4.join(shardDir, `${base}${ext}`)).catch((err) => console.warn(`[session-store] delete ${ext} failed: ${err}`));
8590
- }
8591
8744
  const sessDir = path4.join(shardDir, base);
8592
- await fsp6.rm(sessDir, { recursive: true, force: true }).catch((err) => console.warn(`[session-store] delete session dir failed: ${err}`));
8745
+ const deletions = [
8746
+ fsp6.unlink(jsonlPath),
8747
+ fsp6.unlink(summaryPath),
8748
+ fsp6.unlink(path4.join(shardDir, `${base}.plan.json`)),
8749
+ fsp6.unlink(path4.join(shardDir, `${base}.todos.json`))
8750
+ ];
8751
+ const results = await Promise.allSettled(deletions);
8752
+ for (const r of results) {
8753
+ if (r.status === "rejected") {
8754
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
8755
+ if (r.reason?.code !== "ENOENT") {
8756
+ console.warn(JSON.stringify({
8757
+ level: "warn",
8758
+ event: "session_store.delete_failed",
8759
+ sessionId: id,
8760
+ message: msg,
8761
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8762
+ }));
8763
+ }
8764
+ }
8765
+ }
8766
+ await fsp6.rm(sessDir, { recursive: true, force: true }).catch((err) => {
8767
+ console.warn(JSON.stringify({
8768
+ level: "warn",
8769
+ event: "session_store.rmdir_failed",
8770
+ sessionId: id,
8771
+ message: err instanceof Error ? err.message : String(err),
8772
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8773
+ }));
8774
+ });
8593
8775
  await this.writeTombstone(id);
8594
8776
  }
8595
8777
  async delete(id) {
@@ -8605,24 +8787,33 @@ var DefaultSessionStore = class _DefaultSessionStore {
8605
8787
  activeSessionId = active.sessionId ?? null;
8606
8788
  } catch {
8607
8789
  }
8790
+ const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
8791
+ const pruneFile = async (dir, name, prefix) => {
8792
+ const jsonlPath = path4.join(dir, name);
8793
+ try {
8794
+ const stat5 = await fsp6.stat(jsonlPath);
8795
+ if (stat5.mtimeMs >= cutoff) return;
8796
+ } catch {
8797
+ return;
8798
+ }
8799
+ const base = name.replace(/\.jsonl$/, "");
8800
+ const id = prefix ? `${prefix}/${base}` : base;
8801
+ if (activeSessionId && id === activeSessionId) return;
8802
+ await this.deleteSession(id);
8803
+ deleted++;
8804
+ };
8608
8805
  const entries = await fsp6.readdir(this.dir, { withFileTypes: true }).catch(() => []);
8609
8806
  for (const entry of entries) {
8807
+ if (entry.isFile()) {
8808
+ if (isPrunableJsonl(entry.name)) await pruneFile(this.dir, entry.name, "");
8809
+ continue;
8810
+ }
8610
8811
  if (!entry.isDirectory()) continue;
8611
8812
  const dateDir = path4.join(this.dir, entry.name);
8612
8813
  const files = await fsp6.readdir(dateDir, { withFileTypes: true }).catch(() => []);
8613
8814
  for (const file of files) {
8614
- if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
8615
- const jsonlPath = path4.join(dateDir, file.name);
8616
- try {
8617
- const stat5 = await fsp6.stat(jsonlPath);
8618
- if (stat5.mtimeMs >= cutoff) continue;
8619
- } catch {
8620
- continue;
8621
- }
8622
- const id = `${entry.name}/${file.name.replace(/\.jsonl$/, "")}`;
8623
- if (activeSessionId && id === activeSessionId) continue;
8624
- await this.deleteSession(id);
8625
- deleted++;
8815
+ if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
8816
+ await pruneFile(dateDir, file.name, entry.name);
8626
8817
  }
8627
8818
  }
8628
8819
  if (deleted > 0) {
@@ -8711,7 +8902,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8711
8902
  }
8712
8903
  metaFromEvents(id, events) {
8713
8904
  const start = events.find((e) => e.type === "session_start");
8714
- const end = events.find((e) => e.type === "session_end");
8905
+ const end = events.findLast((e) => e.type === "session_end");
8715
8906
  return {
8716
8907
  id,
8717
8908
  startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
@@ -8728,9 +8919,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
8728
8919
  for (const e of events) {
8729
8920
  if (e.type === "user_input") {
8730
8921
  openToolUses.clear();
8731
- messages.push({ role: "user", content: e.content });
8922
+ messages.push({ role: "user", content: e.content, ts: e.ts });
8732
8923
  } else if (e.type === "llm_response") {
8733
- messages.push({ role: "assistant", content: e.content });
8924
+ messages.push({ role: "assistant", content: e.content, ts: e.ts });
8734
8925
  for (const b of e.content) {
8735
8926
  if (b.type === "tool_use") openToolUses.add(b.id);
8736
8927
  }
@@ -8749,25 +8940,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
8749
8940
  continue;
8750
8941
  }
8751
8942
  openToolUses.delete(e.id);
8752
- const content = [
8753
- {
8754
- type: "tool_result",
8755
- tool_use_id: e.id,
8756
- content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
8757
- is_error: e.isError
8758
- }
8759
- ];
8943
+ const resultBlock = {
8944
+ type: "tool_result",
8945
+ tool_use_id: e.id,
8946
+ content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
8947
+ is_error: e.isError
8948
+ };
8760
8949
  const last = messages[messages.length - 1];
8761
- if (last && last.role === "user") {
8762
- if (Array.isArray(last.content)) {
8763
- last.content.push(...content);
8764
- } else if (typeof last.content === "string") {
8765
- last.content = [{ type: "text", text: last.content }, ...content];
8766
- } else {
8767
- messages.push({ role: "user", content });
8768
- }
8950
+ const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
8951
+ if (lastIsToolResultUser && Array.isArray(last.content)) {
8952
+ last.content.push(resultBlock);
8769
8953
  } else {
8770
- messages.push({ role: "user", content });
8954
+ messages.push({ role: "user", content: [resultBlock], ts: e.ts });
8771
8955
  }
8772
8956
  }
8773
8957
  }
@@ -8787,7 +8971,24 @@ var DefaultSessionStore = class _DefaultSessionStore {
8787
8971
  return { messages: repaired.messages, usage };
8788
8972
  }
8789
8973
  };
8790
- var FileSessionWriter = class {
8974
+ function extractToolCallEnds(events) {
8975
+ const result = [];
8976
+ for (const e of events) {
8977
+ if (e.type === "tool_call_end") {
8978
+ result.push({
8979
+ name: e.name,
8980
+ id: e.id,
8981
+ durationMs: e.durationMs,
8982
+ ok: e.ok ?? false,
8983
+ outputBytes: e.outputBytes,
8984
+ outputTokens: e.outputTokens,
8985
+ outputLines: e.outputLines
8986
+ });
8987
+ }
8988
+ }
8989
+ return result;
8990
+ }
8991
+ var FileSessionWriter = class _FileSessionWriter {
8791
8992
  constructor(id, handle, startedAt, meta, events, opts = {}) {
8792
8993
  this.id = id;
8793
8994
  this.handle = handle;
@@ -8814,7 +9015,7 @@ var FileSessionWriter = class {
8814
9015
  meta;
8815
9016
  events;
8816
9017
  closed = false;
8817
- closing = false;
9018
+ closePromise = null;
8818
9019
  manifestFile;
8819
9020
  summary;
8820
9021
  tokenIn = 0;
@@ -8823,12 +9024,51 @@ var FileSessionWriter = class {
8823
9024
  get transcriptPath() {
8824
9025
  return this.filePath || void 0;
8825
9026
  }
8826
- initDone = false;
9027
+ /**
9028
+ * Lazy session_start/session_resumed init, shared by all appenders.
9029
+ * A single promise (not a boolean) so a second append racing the first
9030
+ * can't push its event into the buffer BEFORE the first append's event —
9031
+ * every appender awaits the same init and resumes in FIFO call order.
9032
+ */
9033
+ initPromise = null;
9034
+ ensureInit() {
9035
+ if (!this.initPromise) this.initPromise = this.writeSessionStartLazy();
9036
+ return this.initPromise;
9037
+ }
8827
9038
  resumed;
8828
9039
  appendFailCount = 0;
8829
9040
  lastAppendWarnAt = 0;
8830
9041
  secretScrubber;
8831
9042
  onCloseCb;
9043
+ // ── Write buffer — batches events to reduce per-event disk I/O ─────────
9044
+ //
9045
+ // Every append() pushes the scrubbed event into an in-memory buffer instead
9046
+ // of calling handle.appendFile() synchronously. The buffer flushes to disk
9047
+ // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
9048
+ // This cuts the number of disk writes by ~95% without changing the on-disk
9049
+ // format — the JSONL is still one JSON object per line.
9050
+ writeBuffer = [];
9051
+ flushTimer = null;
9052
+ static FLUSH_INTERVAL_MS = 500;
9053
+ static FLUSH_SIZE = 50;
9054
+ // ── Write serialization ─────────────────────────────────────────────────
9055
+ //
9056
+ // All disk writes are funneled through a FIFO promise chain. Without it,
9057
+ // a timer-driven flush racing an explicit flush()/close() issues two
9058
+ // concurrent appendFile() calls on the shared O_APPEND handle — the kernel
9059
+ // may complete them out of order (chronology breaks) or, for large
9060
+ // batches, interleave partial writes (torn JSONL lines). The chain keeps
9061
+ // exactly one write in flight; failures don't break the chain.
9062
+ writeChain = Promise.resolve();
9063
+ /** Enqueue a write on the FIFO chain. Resolves/rejects with that write. */
9064
+ enqueueWrite(data) {
9065
+ const write = this.writeChain.then(() => this.handle.appendFile(data, "utf8"));
9066
+ this.writeChain = write.then(
9067
+ () => void 0,
9068
+ () => void 0
9069
+ );
9070
+ return write;
9071
+ }
8832
9072
  // ── Enriched summary tracking ──────────────────────────────────────────
8833
9073
  iterationCount = 0;
8834
9074
  toolCallCount = 0;
@@ -8878,31 +9118,91 @@ var FileSessionWriter = class {
8878
9118
  })}
8879
9119
  `;
8880
9120
  try {
8881
- if (this.filePath) {
8882
- await fsp6.writeFile(this.filePath, record, { flag: "a", mode: 384 });
8883
- }
9121
+ await this.enqueueWrite(record);
8884
9122
  } catch {
8885
9123
  }
8886
9124
  }
8887
9125
  async append(event) {
8888
9126
  if (this.closed) return;
8889
- if (!this.initDone) {
8890
- this.initDone = true;
8891
- await this.writeSessionStartLazy();
8892
- }
9127
+ await this.ensureInit();
8893
9128
  const scrubbed = this.scrubEvent(event);
8894
9129
  this.observeForSummary(scrubbed);
9130
+ this.writeBuffer.push(scrubbed);
9131
+ if (this.writeBuffer.length >= _FileSessionWriter.FLUSH_SIZE) {
9132
+ if (this.flushTimer) {
9133
+ clearTimeout(this.flushTimer);
9134
+ this.flushTimer = null;
9135
+ }
9136
+ await this.flushBuffer();
9137
+ } else {
9138
+ this.scheduleFlush();
9139
+ }
9140
+ }
9141
+ async appendBatch(events) {
9142
+ if (this.closed || events.length === 0) return;
9143
+ await this.ensureInit();
9144
+ for (const event of events) {
9145
+ const scrubbed = this.scrubEvent(event);
9146
+ this.observeForSummary(scrubbed);
9147
+ this.writeBuffer.push(scrubbed);
9148
+ }
9149
+ if (this.writeBuffer.length >= _FileSessionWriter.FLUSH_SIZE) {
9150
+ if (this.flushTimer) {
9151
+ clearTimeout(this.flushTimer);
9152
+ this.flushTimer = null;
9153
+ }
9154
+ await this.flushBuffer();
9155
+ } else {
9156
+ this.scheduleFlush();
9157
+ }
9158
+ }
9159
+ /**
9160
+ * Flush buffered events to disk immediately. Critical events
9161
+ * (user_input, llm_response) call this so they survive SIGKILL/crash
9162
+ * instead of sitting in the in-memory buffer for up to 500ms.
9163
+ *
9164
+ * Idempotent — cancels any pending timer and writes whatever has
9165
+ * accumulated in the buffer. Safe to call even when the buffer
9166
+ * is empty (no-op).
9167
+ */
9168
+ async flush() {
9169
+ if (this.flushTimer) {
9170
+ clearTimeout(this.flushTimer);
9171
+ this.flushTimer = null;
9172
+ }
9173
+ await this.flushBuffer();
9174
+ }
9175
+ /** Schedule a deferred flush. No-op if a timer is already pending. */
9176
+ scheduleFlush() {
9177
+ if (this.flushTimer) return;
9178
+ this.flushTimer = setTimeout(() => {
9179
+ this.flushTimer = null;
9180
+ this.flushBuffer().catch(() => {
9181
+ });
9182
+ }, _FileSessionWriter.FLUSH_INTERVAL_MS);
9183
+ }
9184
+ /**
9185
+ * Flush all buffered events to disk as a single appendFile call.
9186
+ * Errors use the same throttled-warning pattern the old per-event
9187
+ * append path used — one warning every 5s with a suppressed count.
9188
+ * On failure the buffer is cleared (events are best-effort, same as
9189
+ * the old per-event path where a failed write was silently dropped).
9190
+ */
9191
+ async flushBuffer() {
9192
+ if (this.writeBuffer.length === 0) return;
9193
+ const eventCount = this.writeBuffer.length;
9194
+ const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
9195
+ this.writeBuffer = [];
8895
9196
  try {
8896
- await this.handle.appendFile(`${JSON.stringify(scrubbed)}
8897
- `, "utf8");
9197
+ await this.enqueueWrite(batch);
8898
9198
  } catch (err) {
8899
- this.appendFailCount++;
9199
+ this.appendFailCount += eventCount;
8900
9200
  const now = Date.now();
8901
9201
  if (now - this.lastAppendWarnAt > 5e3) {
8902
9202
  const suppressed = this.appendFailCount - 1;
8903
9203
  const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
8904
9204
  console.warn(
8905
- "[session] append failed:",
9205
+ "[session] flush failed:",
8906
9206
  err instanceof Error ? err.message : String(err),
8907
9207
  tail
8908
9208
  );
@@ -8912,6 +9212,11 @@ var FileSessionWriter = class {
8912
9212
  }
8913
9213
  }
8914
9214
  observeForSummary(event) {
9215
+ if (event.type === "llm_response") {
9216
+ for (const block of event.content) {
9217
+ if (block.type === "tool_use") this.openToolUses.add(block.id);
9218
+ }
9219
+ }
8915
9220
  if (event.type === "tool_use") {
8916
9221
  this.openToolUses.add(event.id);
8917
9222
  } else if (event.type === "tool_call_start") {
@@ -8945,9 +9250,18 @@ var FileSessionWriter = class {
8945
9250
  }
8946
9251
  }
8947
9252
  async close() {
8948
- if (this.closing) return;
8949
- this.closing = true;
9253
+ if (this.closePromise) return this.closePromise;
9254
+ this.closePromise = this.doClose();
9255
+ return this.closePromise;
9256
+ }
9257
+ async doClose() {
8950
9258
  this.closed = true;
9259
+ if (this.flushTimer) {
9260
+ clearTimeout(this.flushTimer);
9261
+ this.flushTimer = null;
9262
+ }
9263
+ await this.flushBuffer();
9264
+ await this.writeChain;
8951
9265
  this.summary = {
8952
9266
  ...this.summary,
8953
9267
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9003,6 +9317,12 @@ var FileSessionWriter = class {
9003
9317
  }
9004
9318
  async truncateToCheckpoint(targetPromptIndex) {
9005
9319
  if (!this.filePath) return 0;
9320
+ if (this.flushTimer) {
9321
+ clearTimeout(this.flushTimer);
9322
+ this.flushTimer = null;
9323
+ }
9324
+ await this.flushBuffer();
9325
+ await this.writeChain;
9006
9326
  const raw = await fsp6.readFile(this.filePath, "utf8");
9007
9327
  const lines = raw.split("\n");
9008
9328
  const kept = [];
@@ -9065,6 +9385,12 @@ var FileSessionWriter = class {
9065
9385
  }
9066
9386
  async clearSession() {
9067
9387
  if (!this.filePath) return;
9388
+ if (this.flushTimer) {
9389
+ clearTimeout(this.flushTimer);
9390
+ this.flushTimer = null;
9391
+ }
9392
+ this.writeBuffer = [];
9393
+ await this.writeChain;
9068
9394
  const record = `${JSON.stringify({
9069
9395
  type: "session_start",
9070
9396
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -9563,6 +9889,1518 @@ var FleetManager = class {
9563
9889
  }
9564
9890
  };
9565
9891
 
9566
- export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, formatHumanPrompt, getAgentDefinition, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, rosterSummaryFromConfigs, scoreAgents };
9892
+ // src/coordination/mailbox-types.ts
9893
+ function normalizeRecipient(to) {
9894
+ return to.trim().toLowerCase() === "all" ? "*" : to.trim();
9895
+ }
9896
+
9897
+ // src/coordination/mailbox.ts
9898
+ var MAILBOX_FILE = "_mailbox.jsonl";
9899
+ var LINE_SEPARATOR = "\n";
9900
+ var DefaultMailbox = class {
9901
+ filePath;
9902
+ constructor(sessionDir) {
9903
+ this.filePath = path4.join(sessionDir, MAILBOX_FILE);
9904
+ }
9905
+ get mailboxPath() {
9906
+ return this.filePath;
9907
+ }
9908
+ // ── Send ──────────────────────────────────────────────────────────────
9909
+ async send(input) {
9910
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9911
+ const msg = {
9912
+ id: randomUUID(),
9913
+ from: input.from,
9914
+ // "all" is an accepted spelling of the broadcast address — canonical
9915
+ // form on disk is '*' so every query/checker matches it.
9916
+ to: normalizeRecipient(input.to),
9917
+ type: input.type,
9918
+ subject: input.subject,
9919
+ body: input.body,
9920
+ priority: input.priority ?? "normal",
9921
+ readBy: {},
9922
+ completed: false,
9923
+ timestamp: now,
9924
+ replyTo: input.replyTo,
9925
+ taskContext: input.taskContext
9926
+ };
9927
+ const line = JSON.stringify(msg) + LINE_SEPARATOR;
9928
+ await fsp6.mkdir(path4.dirname(this.filePath), { recursive: true });
9929
+ await withFileLock(this.filePath, async () => {
9930
+ await fsp6.appendFile(this.filePath, line, "utf8");
9931
+ });
9932
+ return msg;
9933
+ }
9934
+ // ── Query ─────────────────────────────────────────────────────────────
9935
+ async query(q) {
9936
+ const all = await this._readAll();
9937
+ const limit = q.limit ?? 50;
9938
+ let filtered = all;
9939
+ if (q.to !== void 0) {
9940
+ filtered = filtered.filter((m) => m.to === q.to || m.to === "*");
9941
+ }
9942
+ if (q.from !== void 0) {
9943
+ filtered = filtered.filter((m) => m.from === q.from);
9944
+ }
9945
+ if (q.unreadBy !== void 0) {
9946
+ filtered = filtered.filter((m) => !(q.unreadBy in m.readBy));
9947
+ }
9948
+ if (q.incompleteOnly) {
9949
+ filtered = filtered.filter((m) => !m.completed);
9950
+ }
9951
+ if (q.type !== void 0) {
9952
+ filtered = filtered.filter((m) => m.type === q.type);
9953
+ }
9954
+ if (q.minPriority !== void 0) {
9955
+ const order = { low: 0, normal: 1, high: 2 };
9956
+ const min = order[q.minPriority];
9957
+ filtered = filtered.filter((m) => (order[m.priority] ?? 1) >= min);
9958
+ }
9959
+ if (q.since !== void 0) {
9960
+ const since = q.since;
9961
+ filtered = filtered.filter((m) => m.timestamp > since);
9962
+ }
9963
+ filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
9964
+ return filtered.slice(0, limit);
9965
+ }
9966
+ // ── Ack ───────────────────────────────────────────────────────────────
9967
+ async ack(input) {
9968
+ let result = null;
9969
+ await withFileLock(this.filePath, async () => {
9970
+ const all = await this._readAll();
9971
+ const idx = all.findIndex((m) => m.id === input.messageId);
9972
+ if (idx === -1) return;
9973
+ const msg = all[idx];
9974
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9975
+ if (input.read !== false) {
9976
+ msg.readBy[input.readerId] = now;
9977
+ }
9978
+ if (input.completed) {
9979
+ msg.completed = true;
9980
+ msg.completedBy = input.readerId;
9981
+ msg.completedAt = now;
9982
+ }
9983
+ if (input.outcome !== void 0) {
9984
+ msg.outcome = input.outcome;
9985
+ }
9986
+ const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
9987
+ await fsp6.writeFile(this.filePath, serialized, "utf8");
9988
+ result = msg;
9989
+ });
9990
+ return result;
9991
+ }
9992
+ // ── Agent statuses ────────────────────────────────────────────────────
9993
+ async getAgentStatuses() {
9994
+ const all = await this._readAll();
9995
+ const latest = /* @__PURE__ */ new Map();
9996
+ for (const m of all) {
9997
+ if (m.type !== "status") continue;
9998
+ const existing = latest.get(m.from);
9999
+ if (existing && m.timestamp <= existing.lastActivityAt) continue;
10000
+ latest.set(m.from, {
10001
+ agentId: m.from,
10002
+ name: m.taskContext?.agentName ?? m.from,
10003
+ role: m.taskContext?.agentRole,
10004
+ sessionId: m.senderSessionId ?? "?",
10005
+ status: m.taskContext?.status ?? "idle",
10006
+ currentTool: void 0,
10007
+ currentTask: m.subject,
10008
+ iterations: 0,
10009
+ toolCalls: 0,
10010
+ lastActivityAt: m.timestamp,
10011
+ lastSeenAt: m.timestamp,
10012
+ online: true,
10013
+ pid: 0,
10014
+ source: void 0
10015
+ });
10016
+ }
10017
+ return Array.from(latest.values()).sort(
10018
+ (a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt)
10019
+ );
10020
+ }
10021
+ // ── Stubs for cross-session features (not applicable per-session) ─────
10022
+ async getOnlineAgents() {
10023
+ return this.getAgentStatuses();
10024
+ }
10025
+ async registerAgent(_input) {
10026
+ }
10027
+ async heartbeat(_input) {
10028
+ }
10029
+ async unreadCount(forAgentId) {
10030
+ const all = await this._readAll();
10031
+ return all.filter(
10032
+ (m) => (m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed
10033
+ ).length;
10034
+ }
10035
+ async close() {
10036
+ }
10037
+ async clearAll() {
10038
+ await withFileLock(this.filePath, async () => {
10039
+ await fsp6.writeFile(this.filePath, "", "utf8");
10040
+ });
10041
+ }
10042
+ // ── Internal ──────────────────────────────────────────────────────────
10043
+ async _readAll() {
10044
+ try {
10045
+ const raw = await fsp6.readFile(this.filePath, "utf8");
10046
+ const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
10047
+ const messages = [];
10048
+ for (const line of lines) {
10049
+ try {
10050
+ const parsed = JSON.parse(line);
10051
+ if (!parsed["readBy"]) {
10052
+ const readBy = {};
10053
+ if (parsed["read"] && parsed["readAt"]) {
10054
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
10055
+ }
10056
+ parsed["readBy"] = readBy;
10057
+ delete parsed["read"];
10058
+ delete parsed["readAt"];
10059
+ }
10060
+ messages.push(parsed);
10061
+ } catch {
10062
+ }
10063
+ }
10064
+ return messages;
10065
+ } catch (err) {
10066
+ if (err.code === "ENOENT") return [];
10067
+ throw err;
10068
+ }
10069
+ }
10070
+ };
10071
+ var BrainMonitor = class {
10072
+ constructor(opts) {
10073
+ this.opts = opts;
10074
+ this.toolFailureStreak = opts.toolFailureStreak ?? 3;
10075
+ this.errorStormCount = opts.errorStormCount ?? 4;
10076
+ this.errorStormWindowMs = opts.errorStormWindowMs ?? 6e4;
10077
+ this.cooldownMs = opts.cooldownMs ?? 12e4;
10078
+ }
10079
+ opts;
10080
+ failStreaks = /* @__PURE__ */ new Map();
10081
+ errorTimestamps = [];
10082
+ lastEngagedAt = /* @__PURE__ */ new Map();
10083
+ unsubscribers = [];
10084
+ engaging = false;
10085
+ toolFailureStreak;
10086
+ errorStormCount;
10087
+ errorStormWindowMs;
10088
+ cooldownMs;
10089
+ start() {
10090
+ this.unsubscribers.push(
10091
+ this.opts.events.on("tool.executed", (e) => {
10092
+ if (e.ok) {
10093
+ this.failStreaks.delete(e.name);
10094
+ return;
10095
+ }
10096
+ const streak = (this.failStreaks.get(e.name) ?? 0) + 1;
10097
+ this.failStreaks.set(e.name, streak);
10098
+ if (streak >= this.toolFailureStreak) {
10099
+ this.failStreaks.delete(e.name);
10100
+ void this.engage("tool_failure_streak", {
10101
+ question: `The tool "${e.name}" has failed ${streak} times in a row. Should the agent be steered to a different approach?`,
10102
+ context: [
10103
+ `Tool: ${e.name}`,
10104
+ `Consecutive failures: ${streak}`,
10105
+ e.output ? `Last output (truncated): ${String(e.output).slice(0, 400)}` : ""
10106
+ ].filter(Boolean).join("\n")
10107
+ });
10108
+ }
10109
+ })
10110
+ );
10111
+ this.unsubscribers.push(
10112
+ this.opts.events.on("error", (e) => {
10113
+ const now = Date.now();
10114
+ this.errorTimestamps.push(now);
10115
+ this.errorTimestamps = this.errorTimestamps.filter(
10116
+ (t) => now - t <= this.errorStormWindowMs
10117
+ );
10118
+ if (this.errorTimestamps.length >= this.errorStormCount) {
10119
+ const count = this.errorTimestamps.length;
10120
+ this.errorTimestamps = [];
10121
+ const message = e.err instanceof Error ? e.err.message : String(e.err);
10122
+ void this.engage("error_storm", {
10123
+ question: `${count} errors occurred within ${Math.round(this.errorStormWindowMs / 1e3)}s (phase: ${e.phase}). Should the agent be steered before more work is wasted?`,
10124
+ context: `Latest error: ${message.slice(0, 400)}`
10125
+ });
10126
+ }
10127
+ })
10128
+ );
10129
+ }
10130
+ stop() {
10131
+ for (const off of this.unsubscribers) off();
10132
+ this.unsubscribers.length = 0;
10133
+ this.failStreaks.clear();
10134
+ this.errorTimestamps = [];
10135
+ }
10136
+ async engage(kind, input) {
10137
+ const last = this.lastEngagedAt.get(kind) ?? 0;
10138
+ if (this.engaging || Date.now() - last < this.cooldownMs) return;
10139
+ this.engaging = true;
10140
+ this.lastEngagedAt.set(kind, Date.now());
10141
+ try {
10142
+ const request = {
10143
+ id: `brainmon-${randomUUID()}`,
10144
+ source: "system",
10145
+ question: input.question,
10146
+ context: input.context,
10147
+ options: [
10148
+ {
10149
+ id: "steer",
10150
+ label: "Steer the agent with corrective guidance",
10151
+ consequence: "A steer message is injected before its next step.",
10152
+ risk: "low"
10153
+ },
10154
+ {
10155
+ id: "continue",
10156
+ label: "Let the agent continue unaided",
10157
+ risk: "low"
10158
+ }
10159
+ ],
10160
+ risk: "medium",
10161
+ // Without an LLM layer the policy brain resolves this fallback to
10162
+ // "continue" — the monitor observes but never interferes.
10163
+ fallback: "continue"
10164
+ };
10165
+ const decision = await this.opts.brain.decide(request);
10166
+ const intervened = await this.maybeIntervene(kind, request, decision);
10167
+ this.opts.events.emit("brain.intervention", {
10168
+ kind,
10169
+ request,
10170
+ decision,
10171
+ intervened,
10172
+ at: Date.now()
10173
+ });
10174
+ } catch {
10175
+ } finally {
10176
+ this.engaging = false;
10177
+ }
10178
+ }
10179
+ async maybeIntervene(kind, request, decision) {
10180
+ if (decision.type !== "answer") return false;
10181
+ const choseSteer = decision.optionId === "steer";
10182
+ const freeTextGuidance = !decision.optionId && !/^continue\b/i.test(decision.text.trim()) && decision.text.trim().length > 0;
10183
+ if (!choseSteer && !freeTextGuidance) return false;
10184
+ const guidance = decision.rationale?.trim() || decision.text.trim();
10185
+ try {
10186
+ await this.opts.intervene({
10187
+ subject: `Brain intervention: ${kind.replace(/_/g, " ")}`,
10188
+ body: [
10189
+ `The Brain engaged after detecting: ${request.question}`,
10190
+ "",
10191
+ `Guidance: ${guidance}`,
10192
+ "",
10193
+ "Adjust your approach accordingly \u2014 do not simply retry the same action."
10194
+ ].join("\n")
10195
+ });
10196
+ return true;
10197
+ } catch {
10198
+ return false;
10199
+ }
10200
+ }
10201
+ };
10202
+ function projectSlug(absRoot) {
10203
+ const base = slugify(path4.basename(absRoot));
10204
+ const hash = createHash("sha256").update(path4.resolve(absRoot)).digest("hex").slice(0, 6);
10205
+ return `${base}-${hash}`;
10206
+ }
10207
+ function slugify(name) {
10208
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
10209
+ }
10210
+ function wstackGlobalRoot() {
10211
+ const fromEnv = process.env["WRONGSTACK_HOME"];
10212
+ if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
10213
+ return path4.join(os.homedir(), ".wrongstack");
10214
+ }
10215
+
10216
+ // src/coordination/global-mailbox.ts
10217
+ var MAILBOX_FILE2 = "_mailbox.jsonl";
10218
+ var AGENT_STALE_MS = 6e4;
10219
+ var HEARTBEAT_THROTTLE_MS = 5e3;
10220
+ var REGISTRY_CACHE_TTL_MS = 2e3;
10221
+ var LINE_SEPARATOR2 = "\n";
10222
+ function resolveProjectDir(projectRoot, globalRoot) {
10223
+ return path4.join(globalRoot, "projects", projectSlug(projectRoot));
10224
+ }
10225
+ var GlobalMailbox = class {
10226
+ /** Path to the JSONL message file. */
10227
+ messagePath;
10228
+ /** Path to the JSON agent registry file. */
10229
+ registryPath;
10230
+ /** Optional event bus for emitting agent registration/heartbeat events. */
10231
+ _events;
10232
+ /**
10233
+ * Local cache of the agent registry to avoid re-reading on every call.
10234
+ * Time-bounded: the registry file is shared ACROSS PROCESSES (that's the
10235
+ * whole point of GlobalMailbox), so a cache served forever would never see
10236
+ * agents registered by other sessions. Writers always bypass it.
10237
+ */
10238
+ _registryCache = null;
10239
+ /** When the registry cache was last refreshed from disk (epoch ms). */
10240
+ _registryCacheAt = 0;
10241
+ /** Last time each local agent sent a heartbeat (throttle). */
10242
+ _lastHeartbeat = /* @__PURE__ */ new Map();
10243
+ /**
10244
+ * @param projectDir — `~/.wrongstack/projects/<slug>/`
10245
+ * @param events — optional EventBus for real-time TUI/WebUI notifications
10246
+ */
10247
+ constructor(projectDir, events) {
10248
+ this.messagePath = path4.join(projectDir, MAILBOX_FILE2);
10249
+ this.registryPath = path4.join(projectDir, "_mailbox.registry.json");
10250
+ this._events = events;
10251
+ }
10252
+ // ── Messages ────────────────────────────────────────────────────────────
10253
+ async send(input) {
10254
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10255
+ const msg = {
10256
+ id: randomUUID(),
10257
+ from: input.from,
10258
+ // "all" is an accepted spelling of the broadcast address — canonical
10259
+ // form on disk is '*' so every query/checker matches it.
10260
+ to: normalizeRecipient(input.to),
10261
+ type: input.type,
10262
+ subject: input.subject,
10263
+ body: input.body,
10264
+ priority: input.priority ?? "normal",
10265
+ readBy: {},
10266
+ completed: false,
10267
+ timestamp: now,
10268
+ replyTo: input.replyTo,
10269
+ taskContext: input.taskContext
10270
+ };
10271
+ const line = JSON.stringify(msg) + LINE_SEPARATOR2;
10272
+ await fsp6.mkdir(path4.dirname(this.messagePath), { recursive: true });
10273
+ await withFileLock(this.messagePath, async () => {
10274
+ await fsp6.appendFile(this.messagePath, line, "utf8");
10275
+ });
10276
+ return msg;
10277
+ }
10278
+ async query(q) {
10279
+ const all = await this._readMessages();
10280
+ const limit = q.limit ?? 50;
10281
+ let filtered = all;
10282
+ if (q.to !== void 0) {
10283
+ filtered = filtered.filter((m) => m.to === q.to || m.to === "*");
10284
+ }
10285
+ if (q.from !== void 0) {
10286
+ filtered = filtered.filter((m) => m.from === q.from);
10287
+ }
10288
+ if (q.unreadBy !== void 0) {
10289
+ filtered = filtered.filter((m) => !(q.unreadBy in m.readBy));
10290
+ }
10291
+ if (q.incompleteOnly) {
10292
+ filtered = filtered.filter((m) => !m.completed);
10293
+ }
10294
+ if (q.type !== void 0) {
10295
+ filtered = filtered.filter((m) => m.type === q.type);
10296
+ }
10297
+ if (q.minPriority !== void 0) {
10298
+ const order = { low: 0, normal: 1, high: 2 };
10299
+ const min = order[q.minPriority];
10300
+ filtered = filtered.filter((m) => (order[m.priority] ?? 1) >= min);
10301
+ }
10302
+ if (q.since !== void 0) {
10303
+ const since = q.since;
10304
+ filtered = filtered.filter((m) => m.timestamp > since);
10305
+ }
10306
+ filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
10307
+ return filtered.slice(0, limit);
10308
+ }
10309
+ async ack(input) {
10310
+ let result = null;
10311
+ await withFileLock(this.messagePath, async () => {
10312
+ const all = await this._readMessages();
10313
+ const idx = all.findIndex((m) => m.id === input.messageId);
10314
+ if (idx === -1) return;
10315
+ const msg = all[idx];
10316
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10317
+ if (input.read !== false) {
10318
+ msg.readBy[input.readerId] = now;
10319
+ }
10320
+ if (input.completed) {
10321
+ msg.completed = true;
10322
+ msg.completedBy = input.readerId;
10323
+ msg.completedAt = now;
10324
+ }
10325
+ if (input.outcome !== void 0) {
10326
+ msg.outcome = input.outcome;
10327
+ }
10328
+ const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
10329
+ await fsp6.writeFile(this.messagePath, serialized, "utf8");
10330
+ result = msg;
10331
+ });
10332
+ return result;
10333
+ }
10334
+ async unreadCount(forAgentId) {
10335
+ const all = await this._readMessages();
10336
+ return all.filter(
10337
+ (m) => (m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed
10338
+ ).length;
10339
+ }
10340
+ // ── Agent registry ──────────────────────────────────────────────────────
10341
+ async registerAgent(input) {
10342
+ await this._ensureRegistry();
10343
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10344
+ const agent = {
10345
+ agentId: input.agentId,
10346
+ sessionId: input.sessionId,
10347
+ name: input.name,
10348
+ role: input.role,
10349
+ status: "idle",
10350
+ currentTool: void 0,
10351
+ currentTask: void 0,
10352
+ iterations: 0,
10353
+ toolCalls: 0,
10354
+ registeredAt: now,
10355
+ lastSeenAt: now,
10356
+ pid: input.pid,
10357
+ source: input.source
10358
+ };
10359
+ await withFileLock(this.registryPath, async () => {
10360
+ const registry = await this._readRegistry({ fresh: true });
10361
+ this._pruneStaleInPlace(registry);
10362
+ registry.set(input.agentId, agent);
10363
+ this._registryCache = registry;
10364
+ this._registryCacheAt = Date.now();
10365
+ await this._writeRegistry(registry);
10366
+ });
10367
+ this._events?.emitCustom("mailbox.agent_registered", {
10368
+ agentId: input.agentId,
10369
+ sessionId: input.sessionId,
10370
+ name: input.name,
10371
+ role: input.role,
10372
+ source: input.source
10373
+ });
10374
+ }
10375
+ async heartbeat(input) {
10376
+ const last = this._lastHeartbeat.get(input.agentId) ?? 0;
10377
+ const now = Date.now();
10378
+ if (now - last < HEARTBEAT_THROTTLE_MS) return;
10379
+ this._lastHeartbeat.set(input.agentId, now);
10380
+ await this._ensureRegistry();
10381
+ await withFileLock(this.registryPath, async () => {
10382
+ const registry = await this._readRegistry({ fresh: true });
10383
+ this._pruneStaleInPlace(registry);
10384
+ const agent = registry.get(input.agentId);
10385
+ if (agent) {
10386
+ const iso = (/* @__PURE__ */ new Date()).toISOString();
10387
+ agent.lastSeenAt = iso;
10388
+ if (input.status !== void 0) agent.status = input.status;
10389
+ if (input.currentTool !== void 0) agent.currentTool = input.currentTool;
10390
+ if (input.currentTask !== void 0) agent.currentTask = input.currentTask;
10391
+ if (input.iterations !== void 0) agent.iterations = input.iterations;
10392
+ if (input.toolCalls !== void 0) agent.toolCalls = input.toolCalls;
10393
+ }
10394
+ this._registryCache = registry;
10395
+ this._registryCacheAt = Date.now();
10396
+ await this._writeRegistry(registry);
10397
+ });
10398
+ this._events?.emitCustom("mailbox.agent_heartbeat", {
10399
+ agentId: input.agentId,
10400
+ status: input.status,
10401
+ currentTool: input.currentTool,
10402
+ currentTask: input.currentTask
10403
+ });
10404
+ }
10405
+ async getAgentStatuses() {
10406
+ await this._ensureRegistry();
10407
+ const registry = await this._readRegistry();
10408
+ this._pruneStaleInPlace(registry);
10409
+ const now = Date.now();
10410
+ return Array.from(registry.values()).map((a) => ({
10411
+ agentId: a.agentId,
10412
+ name: a.name,
10413
+ role: a.role,
10414
+ sessionId: a.sessionId,
10415
+ status: a.status,
10416
+ currentTool: a.currentTool,
10417
+ currentTask: a.currentTask,
10418
+ iterations: a.iterations,
10419
+ toolCalls: a.toolCalls,
10420
+ lastActivityAt: a.lastSeenAt,
10421
+ lastSeenAt: a.lastSeenAt,
10422
+ online: now - new Date(a.lastSeenAt).getTime() < AGENT_STALE_MS,
10423
+ pid: a.pid,
10424
+ source: a.source
10425
+ })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
10426
+ }
10427
+ async getOnlineAgents() {
10428
+ const all = await this.getAgentStatuses();
10429
+ return all.filter((a) => a.online);
10430
+ }
10431
+ // ── Lifecycle ───────────────────────────────────────────────────────────
10432
+ async close() {
10433
+ this._registryCache = null;
10434
+ }
10435
+ async clearAll() {
10436
+ await withFileLock(this.messagePath, async () => {
10437
+ await fsp6.writeFile(this.messagePath, "", "utf8");
10438
+ });
10439
+ }
10440
+ // ── Internal ────────────────────────────────────────────────────────────
10441
+ async _readMessages() {
10442
+ try {
10443
+ const raw = await fsp6.readFile(this.messagePath, "utf8");
10444
+ const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
10445
+ const messages = [];
10446
+ for (const line of lines) {
10447
+ try {
10448
+ const parsed = JSON.parse(line);
10449
+ if (!parsed["readBy"]) {
10450
+ const readBy = {};
10451
+ if (parsed["read"] && parsed["readAt"]) {
10452
+ readBy[parsed["to"]] = parsed["readAt"];
10453
+ }
10454
+ parsed["readBy"] = readBy;
10455
+ delete parsed["read"];
10456
+ delete parsed["readAt"];
10457
+ }
10458
+ messages.push(parsed);
10459
+ } catch {
10460
+ }
10461
+ }
10462
+ return messages;
10463
+ } catch (err) {
10464
+ if (err.code === "ENOENT") return [];
10465
+ throw err;
10466
+ }
10467
+ }
10468
+ async _ensureRegistry() {
10469
+ await fsp6.mkdir(path4.dirname(this.registryPath), { recursive: true });
10470
+ }
10471
+ async _readRegistry(opts) {
10472
+ if (!opts?.fresh && this._registryCache && Date.now() - this._registryCacheAt < REGISTRY_CACHE_TTL_MS) {
10473
+ return new Map(this._registryCache);
10474
+ }
10475
+ try {
10476
+ const raw = await fsp6.readFile(this.registryPath, "utf8");
10477
+ const data = JSON.parse(raw);
10478
+ const map = /* @__PURE__ */ new Map();
10479
+ for (const [id, agent] of Object.entries(data)) {
10480
+ map.set(id, agent);
10481
+ }
10482
+ this._registryCache = map;
10483
+ this._registryCacheAt = Date.now();
10484
+ return new Map(map);
10485
+ } catch (err) {
10486
+ if (err.code === "ENOENT") {
10487
+ const empty = /* @__PURE__ */ new Map();
10488
+ this._registryCache = empty;
10489
+ this._registryCacheAt = Date.now();
10490
+ return empty;
10491
+ }
10492
+ throw err;
10493
+ }
10494
+ }
10495
+ _pruneStaleInPlace(registry) {
10496
+ const cutoff = Date.now() - AGENT_STALE_MS;
10497
+ for (const agent of registry.values()) {
10498
+ if (new Date(agent.lastSeenAt).getTime() < cutoff) {
10499
+ agent.status = "idle";
10500
+ }
10501
+ }
10502
+ }
10503
+ async _writeRegistry(registry) {
10504
+ const obj = {};
10505
+ for (const [id, agent] of registry) {
10506
+ obj[id] = agent;
10507
+ }
10508
+ const tmp = `${this.registryPath}.${randomUUID().slice(0, 8)}.tmp`;
10509
+ await fsp6.writeFile(tmp, JSON.stringify(obj, null, 2), "utf8");
10510
+ await fsp6.rename(tmp, this.registryPath);
10511
+ }
10512
+ };
10513
+ function defaultResolveProjectDir(ctx) {
10514
+ return resolveProjectDir(ctx.projectRoot, wstackGlobalRoot());
10515
+ }
10516
+ function mailboxSessionTag(sessionId) {
10517
+ return createHash("sha256").update(sessionId).digest("hex").slice(0, 8);
10518
+ }
10519
+ function resolveMailboxIdentity(ctx, fallbackBase = "leader") {
10520
+ const fieldId = ctx.agentId && ctx.agentId !== "unknown" ? ctx.agentId : void 0;
10521
+ const baseId = ctx.meta["agentId"] ?? fieldId ?? fallbackBase;
10522
+ const sessionId = ctx.meta["sessionId"] ?? ctx.session?.id ?? "default";
10523
+ const callerId = ctx.meta["globalAgentId"] ?? `${baseId}@${mailboxSessionTag(sessionId)}`;
10524
+ const fieldName = ctx.agentName && ctx.agentName !== "Unknown Agent" ? ctx.agentName : void 0;
10525
+ const name = ctx.meta["agentName"] ?? fieldName ?? baseId;
10526
+ const role = ctx.meta["agentRole"];
10527
+ return { baseId, callerId, name, role, sessionId };
10528
+ }
10529
+ function makeMailboxTool(opts = {}) {
10530
+ const resolveMailbox = opts.resolveMailbox ?? ((ctx) => {
10531
+ const dir = opts.projectDir ?? defaultResolveProjectDir(ctx);
10532
+ return new GlobalMailbox(dir, opts.events);
10533
+ });
10534
+ const agentId = opts.agentId ?? "leader";
10535
+ const sessionId = opts.sessionId ?? "default";
10536
+ const shortHint = "Sub-commands: check (unread), send (to/broadcast), ack (read/complete), query (filter), status (all agents), online (active only), unread (count).";
10537
+ return {
10538
+ name: "mailbox",
10539
+ description: "Inter-agent mailbox with cross-session support. Send messages, check for incoming messages, acknowledge with read receipts, query by criteria, see online agents.",
10540
+ usageHint: shortHint,
10541
+ category: "coordination",
10542
+ permission: "auto",
10543
+ mutating: true,
10544
+ inputSchema: {
10545
+ type: "object",
10546
+ properties: {
10547
+ action: {
10548
+ type: "string",
10549
+ enum: ["check", "send", "ack", "query", "status", "online", "unread"],
10550
+ description: "Which mailbox operation to perform."
10551
+ },
10552
+ to: { type: "string", description: "Recipient agent id, or '*' / 'all' for broadcast." },
10553
+ type: { type: "string", enum: ["note", "ask", "assign", "steer", "btw", "broadcast", "status", "result"], description: "Message type." },
10554
+ subject: { type: "string", description: "Short subject line." },
10555
+ body: { type: "string", description: "Full message content." },
10556
+ priority: { type: "string", enum: ["low", "normal", "high"] },
10557
+ replyTo: { type: "string", description: "Reply to a specific message id." },
10558
+ messageId: { type: "string", description: "Message id to acknowledge. Required for 'ack'." },
10559
+ read: { type: "boolean", description: "Mark as read (adds read receipt)." },
10560
+ completed: { type: "boolean", description: "Mark as completed." },
10561
+ outcome: { type: "string", description: "Outcome summary when marking complete." },
10562
+ unreadBy: { type: "string", description: "Filter messages unread by this agent. Used by 'check'." },
10563
+ incompleteOnly: { type: "boolean", description: "Only incomplete messages." },
10564
+ from: { type: "string", description: "Filter by sender." },
10565
+ minPriority: { type: "string", enum: ["low", "normal", "high"] },
10566
+ since: { type: "string", description: "ISO8601 timestamp \u2014 only messages after this." },
10567
+ limit: { type: "number", description: "Max messages to return." }
10568
+ },
10569
+ required: ["action"]
10570
+ },
10571
+ async execute(input, ctx) {
10572
+ const mb = resolveMailbox(ctx);
10573
+ const i = input ?? {};
10574
+ const action = i.action;
10575
+ const identity = resolveMailboxIdentity(ctx, agentId);
10576
+ const baseCallerId = identity.baseId;
10577
+ const callerId = identity.callerId;
10578
+ const callerSessionId = ctx.meta["sessionId"] ?? (ctx.session?.id ?? sessionId);
10579
+ try {
10580
+ await mb.registerAgent({
10581
+ agentId: callerId,
10582
+ sessionId: callerSessionId,
10583
+ name: identity.name,
10584
+ role: identity.role,
10585
+ pid: process.pid,
10586
+ source: ctx.meta["source"] ?? "cli"
10587
+ });
10588
+ } catch {
10589
+ }
10590
+ try {
10591
+ await mb.heartbeat({ agentId: callerId });
10592
+ } catch {
10593
+ }
10594
+ switch (action) {
10595
+ case "check":
10596
+ return executeCheck(mb, callerId, [baseCallerId], i);
10597
+ case "send":
10598
+ return executeSend(mb, callerId, callerSessionId, i);
10599
+ case "ack":
10600
+ return executeAck(mb, callerId, i);
10601
+ case "query":
10602
+ return executeQuery(mb, i);
10603
+ case "status":
10604
+ return executeStatus(mb);
10605
+ case "online":
10606
+ return executeOnline(mb);
10607
+ case "unread":
10608
+ return executeUnread(mb, callerId, [baseCallerId]);
10609
+ default:
10610
+ return { ok: false, error: `Unknown action: "${action}". Use check, send, ack, query, status, online, or unread.` };
10611
+ }
10612
+ }
10613
+ };
10614
+ }
10615
+ async function executeCheck(mb, agentId, aliases, i) {
10616
+ const limit = i.limit ?? 20;
10617
+ const targets = [agentId, ...aliases.filter((al) => al && al !== agentId)];
10618
+ const batches = await Promise.all(
10619
+ targets.map(
10620
+ (to) => mb.query({ to, unreadBy: agentId, limit, minPriority: "low" }).catch(() => [])
10621
+ )
10622
+ );
10623
+ const seen = /* @__PURE__ */ new Set();
10624
+ const messages = batches.flat().filter((m) => {
10625
+ if (seen.has(m.id)) return false;
10626
+ seen.add(m.id);
10627
+ return true;
10628
+ });
10629
+ const acked = await Promise.all(
10630
+ messages.map(async (m) => {
10631
+ const updated = await mb.ack({ messageId: m.id, readerId: agentId, read: true }).catch(() => null);
10632
+ return updated ?? m;
10633
+ })
10634
+ );
10635
+ return {
10636
+ ok: true,
10637
+ count: acked.length,
10638
+ messages: acked.map((m) => formatMessage(m, agentId)),
10639
+ summary: acked.length === 0 ? "No unread messages." : `${acked.length} unread message(s).`
10640
+ };
10641
+ }
10642
+ async function executeSend(mb, agentId, _sessionId, i) {
10643
+ const to = i.to;
10644
+ const tp = i.type;
10645
+ const subject = i.subject;
10646
+ const body = i.body;
10647
+ if (!to) return { ok: false, error: '"to" is required.' };
10648
+ if (!tp) return { ok: false, error: '"type" is required.' };
10649
+ if (!subject) return { ok: false, error: '"subject" is required.' };
10650
+ if (body === void 0 || body === null) return { ok: false, error: '"body" is required.' };
10651
+ const msg = await mb.send({
10652
+ from: agentId,
10653
+ to,
10654
+ type: tp,
10655
+ subject,
10656
+ body,
10657
+ priority: i.priority ?? "normal",
10658
+ replyTo: i.replyTo
10659
+ });
10660
+ return {
10661
+ ok: true,
10662
+ messageId: msg.id,
10663
+ to: msg.to,
10664
+ type: msg.type,
10665
+ timestamp: msg.timestamp,
10666
+ summary: `Message sent to ${msg.to === "*" ? "all agents" : msg.to}. Id: ${msg.id}`
10667
+ };
10668
+ }
10669
+ async function executeAck(mb, agentId, i) {
10670
+ const messageId = i.messageId;
10671
+ if (!messageId) return { ok: false, error: '"messageId" is required.' };
10672
+ const updated = await mb.ack({
10673
+ messageId,
10674
+ readerId: agentId,
10675
+ read: i.read,
10676
+ completed: i.completed,
10677
+ outcome: i.outcome
10678
+ });
10679
+ if (!updated) return { ok: false, error: `Message "${messageId}" not found.` };
10680
+ return {
10681
+ ok: true,
10682
+ messageId: updated.id,
10683
+ readBy: Object.keys(updated.readBy),
10684
+ readByCount: Object.keys(updated.readBy).length,
10685
+ completed: updated.completed,
10686
+ completedBy: updated.completedBy,
10687
+ outcome: updated.outcome,
10688
+ summary: `Message ${messageId} acknowledged. Read by ${Object.keys(updated.readBy).length} agent(s), Completed: ${updated.completed}.`
10689
+ };
10690
+ }
10691
+ async function executeQuery(mb, i) {
10692
+ const limit = i.limit ?? 50;
10693
+ const messages = await mb.query({
10694
+ to: i.to,
10695
+ from: i.from,
10696
+ unreadBy: i.unreadBy,
10697
+ incompleteOnly: i.incompleteOnly,
10698
+ type: i.type,
10699
+ minPriority: i.minPriority,
10700
+ since: i.since,
10701
+ limit
10702
+ });
10703
+ return { ok: true, count: messages.length, messages, summary: `${messages.length} message(s).` };
10704
+ }
10705
+ async function executeStatus(mb) {
10706
+ const agents = await mb.getAgentStatuses();
10707
+ return {
10708
+ ok: true,
10709
+ count: agents.length,
10710
+ agents: agents.map((a) => ({
10711
+ agentId: a.agentId,
10712
+ name: a.name,
10713
+ role: a.role,
10714
+ sessionId: a.sessionId,
10715
+ status: a.status,
10716
+ currentTool: a.currentTool,
10717
+ currentTask: a.currentTask,
10718
+ iterations: a.iterations,
10719
+ toolCalls: a.toolCalls,
10720
+ lastSeenAt: a.lastSeenAt,
10721
+ online: a.online,
10722
+ pid: a.pid,
10723
+ source: a.source
10724
+ })),
10725
+ summary: `${agents.filter((a) => a.online).length} online, ${agents.length} total.`
10726
+ };
10727
+ }
10728
+ async function executeOnline(mb) {
10729
+ const agents = await mb.getOnlineAgents();
10730
+ return {
10731
+ ok: true,
10732
+ count: agents.length,
10733
+ agents: agents.map((a) => ({
10734
+ agentId: a.agentId,
10735
+ name: a.name,
10736
+ role: a.role,
10737
+ sessionId: a.sessionId,
10738
+ status: a.status,
10739
+ currentTool: a.currentTool,
10740
+ currentTask: a.currentTask,
10741
+ lastSeenAt: a.lastSeenAt,
10742
+ source: a.source
10743
+ })),
10744
+ summary: `${agents.length} online agent(s).`
10745
+ };
10746
+ }
10747
+ async function executeUnread(mb, agentId, aliases = []) {
10748
+ const targets = [agentId, ...aliases.filter((al) => al && al !== agentId)];
10749
+ const batches = await Promise.all(
10750
+ targets.map((to) => mb.query({ to, unreadBy: agentId, limit: 200 }).catch(() => []))
10751
+ );
10752
+ const ids = new Set(batches.flat().map((m) => m.id));
10753
+ return { ok: true, count: ids.size, summary: `${ids.size} unread message(s) for you.` };
10754
+ }
10755
+ function formatMessage(m, readerId) {
10756
+ const maxBody = 2e3;
10757
+ const truncated = m.body.length > maxBody ? `${m.body.slice(0, maxBody)}\u2026 [truncated]` : m.body;
10758
+ return {
10759
+ id: m.id,
10760
+ from: m.from,
10761
+ to: m.to,
10762
+ type: m.type,
10763
+ subject: m.subject,
10764
+ body: truncated,
10765
+ priority: m.priority,
10766
+ readByMe: readerId in m.readBy,
10767
+ readByCount: Object.keys(m.readBy).length,
10768
+ readBy: m.readBy,
10769
+ completed: m.completed,
10770
+ completedBy: m.completedBy,
10771
+ outcome: m.outcome,
10772
+ timestamp: m.timestamp,
10773
+ replyTo: m.replyTo,
10774
+ senderSessionId: m.senderSessionId
10775
+ };
10776
+ }
10777
+
10778
+ // src/coordination/mail-tools.ts
10779
+ function makeResolver(opts) {
10780
+ return opts.resolveMailbox ?? ((ctx) => new GlobalMailbox(opts.projectDir ?? defaultResolveProjectDir(ctx), opts.events));
10781
+ }
10782
+ async function register(mb, ctx) {
10783
+ const identity = resolveMailboxIdentity(ctx);
10784
+ try {
10785
+ await mb.registerAgent({
10786
+ agentId: identity.callerId,
10787
+ sessionId: identity.sessionId,
10788
+ name: identity.name,
10789
+ role: identity.role,
10790
+ pid: process.pid,
10791
+ source: ctx.meta["source"] ?? "cli"
10792
+ });
10793
+ await mb.heartbeat({ agentId: identity.callerId });
10794
+ } catch {
10795
+ }
10796
+ return identity;
10797
+ }
10798
+ function makeMailSendTool(opts = {}) {
10799
+ const resolveMailbox = makeResolver(opts);
10800
+ return {
10801
+ name: "mail_send",
10802
+ description: 'Send a mail to other agents working on this project (other terminals, TUIs, WebUIs). Use it to hand off work ("can you review src/auth.ts?"), ask questions, or announce what you just did. to="*" broadcasts to everyone; to="leader" reaches every leader process; an exact id like "leader@a1b2c3d4" reaches one agent. Recipients see your mail automatically before their next step.',
10803
+ usageHint: 'mail_send to="*" subject="auth refactor done" body="touched src/auth/*, please review"',
10804
+ category: "coordination",
10805
+ permission: "auto",
10806
+ mutating: true,
10807
+ inputSchema: {
10808
+ type: "object",
10809
+ properties: {
10810
+ to: {
10811
+ type: "string",
10812
+ description: 'Recipient: exact agent id ("leader@a1b2c3d4"), base alias ("leader"), or "*" / "all" for everyone.'
10813
+ },
10814
+ subject: { type: "string", description: "Short subject line." },
10815
+ body: { type: "string", description: "The message." },
10816
+ type: {
10817
+ type: "string",
10818
+ enum: ["note", "ask", "assign", "steer", "btw", "broadcast", "status", "result"],
10819
+ description: 'Message intent. Default: "broadcast" when to="*", otherwise "note".'
10820
+ },
10821
+ priority: { type: "string", enum: ["low", "normal", "high"] },
10822
+ replyTo: { type: "string", description: "Message id this replies to." }
10823
+ },
10824
+ required: ["to", "subject", "body"]
10825
+ },
10826
+ async execute(input, ctx) {
10827
+ const i = input ?? {};
10828
+ const rawTo = i.to;
10829
+ const subject = i.subject;
10830
+ const body = i.body;
10831
+ if (!rawTo || !subject || body === void 0 || body === null) {
10832
+ return { ok: false, error: '"to", "subject" and "body" are required.' };
10833
+ }
10834
+ const to = normalizeRecipient(rawTo);
10835
+ const mb = resolveMailbox(ctx);
10836
+ const identity = await register(mb, ctx);
10837
+ const type = i.type ?? (to === "*" ? "broadcast" : "note");
10838
+ const msg = await mb.send({
10839
+ from: identity.callerId,
10840
+ to,
10841
+ type,
10842
+ subject,
10843
+ body,
10844
+ priority: i.priority ?? "normal",
10845
+ replyTo: i.replyTo
10846
+ });
10847
+ return {
10848
+ ok: true,
10849
+ messageId: msg.id,
10850
+ from: identity.callerId,
10851
+ to: msg.to,
10852
+ summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity.callerId}.`
10853
+ };
10854
+ }
10855
+ };
10856
+ }
10857
+ function makeMailInboxTool(opts = {}) {
10858
+ const resolveMailbox = makeResolver(opts);
10859
+ return {
10860
+ name: "mail_inbox",
10861
+ description: 'Read your unread mail from other agents on this project and mark it read. Covers mail addressed to you directly, to your base name (e.g. "leader"), and broadcasts ("*"). Urgent steer/btw mail is already injected automatically \u2014 use this to catch up on notes, questions, handoffs and results, or after a long stretch of tool work.',
10862
+ usageHint: "mail_inbox (optionally: limit=10, markRead=false to peek)",
10863
+ category: "coordination",
10864
+ permission: "auto",
10865
+ mutating: false,
10866
+ inputSchema: {
10867
+ type: "object",
10868
+ properties: {
10869
+ limit: { type: "number", description: "Max messages to return (default 20)." },
10870
+ markRead: {
10871
+ type: "boolean",
10872
+ description: "Add a read receipt for each returned message (default true)."
10873
+ }
10874
+ }
10875
+ },
10876
+ async execute(input, ctx) {
10877
+ const i = input ?? {};
10878
+ const limit = i.limit ?? 20;
10879
+ const markRead = i.markRead ?? true;
10880
+ const mb = resolveMailbox(ctx);
10881
+ const identity = await register(mb, ctx);
10882
+ const targets = [identity.callerId];
10883
+ if (identity.baseId !== identity.callerId) targets.push(identity.baseId);
10884
+ const batches = await Promise.all(
10885
+ targets.map(
10886
+ (to) => mb.query({ to, unreadBy: identity.callerId, limit }).catch(() => [])
10887
+ )
10888
+ );
10889
+ const seen = /* @__PURE__ */ new Set();
10890
+ const messages = batches.flat().filter((m) => {
10891
+ if (seen.has(m.id) || m.from === identity.callerId) return false;
10892
+ seen.add(m.id);
10893
+ return true;
10894
+ }).slice(0, limit);
10895
+ if (markRead) {
10896
+ await Promise.all(
10897
+ messages.map(
10898
+ (m) => mb.ack({ messageId: m.id, readerId: identity.callerId, read: true }).catch(() => null)
10899
+ )
10900
+ );
10901
+ }
10902
+ return {
10903
+ ok: true,
10904
+ you: identity.callerId,
10905
+ count: messages.length,
10906
+ messages: messages.map((m) => ({
10907
+ id: m.id,
10908
+ from: m.from,
10909
+ to: m.to,
10910
+ type: m.type,
10911
+ subject: m.subject,
10912
+ body: m.body.length > 2e3 ? `${m.body.slice(0, 2e3)}\u2026 [truncated]` : m.body,
10913
+ timestamp: m.timestamp,
10914
+ replyTo: m.replyTo
10915
+ })),
10916
+ summary: messages.length === 0 ? "Inbox empty." : `${messages.length} unread message(s)${markRead ? " (marked read)" : ""}. Reply with mail_send using the sender id.`
10917
+ };
10918
+ }
10919
+ };
10920
+ }
10921
+
10922
+ // src/coordination/dep-watcher.ts
10923
+ var DEPENDENCY_FILE_PATTERNS = [
10924
+ "package.json",
10925
+ "tsconfig.json",
10926
+ "pnpm-lock.yaml",
10927
+ "yarn.lock",
10928
+ "package-lock.json",
10929
+ "go.mod",
10930
+ "go.sum",
10931
+ "Cargo.toml",
10932
+ "Cargo.lock",
10933
+ "pyproject.toml",
10934
+ "setup.py",
10935
+ "setup.cfg",
10936
+ "requirements.txt",
10937
+ "Pipfile",
10938
+ "Pipfile.lock",
10939
+ "Gemfile",
10940
+ "Gemfile.lock",
10941
+ "composer.json",
10942
+ "composer.lock",
10943
+ "mix.exs",
10944
+ "mix.lock",
10945
+ "pom.xml",
10946
+ "build.gradle",
10947
+ "build.gradle.kts",
10948
+ "settings.gradle",
10949
+ "settings.gradle.kts",
10950
+ "*.csproj",
10951
+ "packages.config",
10952
+ "pubspec.yaml",
10953
+ "pubspec.lock",
10954
+ "CMakeLists.txt",
10955
+ "conanfile.txt",
10956
+ "conanfile.py",
10957
+ "vcpkg.json"
10958
+ ];
10959
+ function makeDependencyWatcherConfig(opts) {
10960
+ const {
10961
+ projectRoot,
10962
+ mailbox,
10963
+ targetAgent = "*",
10964
+ watcherAgentId = "dep-watcher",
10965
+ debounceMs = 3e3,
10966
+ patterns = DEPENDENCY_FILE_PATTERNS
10967
+ } = opts;
10968
+ const watchPaths = [];
10969
+ for (const p of patterns) {
10970
+ if (p.includes("*")) {
10971
+ continue;
10972
+ }
10973
+ watchPaths.push(`${projectRoot}/${p}`);
10974
+ }
10975
+ watchPaths.push(projectRoot);
10976
+ const unique = [...new Set(watchPaths)];
10977
+ const globPatterns = patterns.filter((p) => p.includes("*"));
10978
+ const plainPatterns = patterns.filter((p) => !p.includes("*"));
10979
+ function matchesPattern(filePath) {
10980
+ const basename5 = filePath.split("/").pop()?.split("\\").pop() ?? "";
10981
+ if (plainPatterns.includes(basename5)) return true;
10982
+ for (const gp of globPatterns) {
10983
+ const regex = new RegExp(
10984
+ "^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
10985
+ );
10986
+ if (regex.test(basename5)) return true;
10987
+ }
10988
+ return false;
10989
+ }
10990
+ const pending = /* @__PURE__ */ new Map();
10991
+ return {
10992
+ watchPaths: unique,
10993
+ debounceMs,
10994
+ async onChange(entry) {
10995
+ if (entry.event === "delete") return;
10996
+ if (!matchesPattern(entry.path)) return;
10997
+ const key = entry.path;
10998
+ const existing = pending.get(key);
10999
+ if (existing) clearTimeout(existing);
11000
+ pending.set(
11001
+ key,
11002
+ setTimeout(async () => {
11003
+ pending.delete(key);
11004
+ try {
11005
+ const fileName = entry.path.split("/").pop()?.split("\\").pop() ?? entry.path;
11006
+ await mailbox.send({
11007
+ from: watcherAgentId,
11008
+ to: targetAgent,
11009
+ type: "assign",
11010
+ subject: `Dependency file changed: ${fileName}`,
11011
+ body: [
11012
+ `File: ${entry.path}`,
11013
+ `Event: ${entry.event}`,
11014
+ `Timestamp: ${entry.timestamp}`,
11015
+ "",
11016
+ `Action: Run a tech-stack audit on the changed dependency file.`,
11017
+ `Validate any new packages, check versions, flag deprecated or prehistoric packages.`,
11018
+ `Report findings back via mailbox (type: result).`
11019
+ ].join("\n"),
11020
+ priority: "high",
11021
+ taskContext: {
11022
+ agentRole: "tech-stack",
11023
+ status: "pending"
11024
+ }
11025
+ });
11026
+ } catch {
11027
+ }
11028
+ }, debounceMs)
11029
+ );
11030
+ }
11031
+ };
11032
+ }
11033
+
11034
+ // src/coordination/dep-watcher-bridge.ts
11035
+ function attachDepWatcherBridge(opts) {
11036
+ const {
11037
+ events,
11038
+ mailbox,
11039
+ projectRoot,
11040
+ targetAgent = "tech-stack",
11041
+ watcherAgentId = "dep-watcher",
11042
+ debounceMs = 3e3
11043
+ } = opts;
11044
+ const cfg = makeDependencyWatcherConfig({
11045
+ projectRoot,
11046
+ mailbox,
11047
+ targetAgent,
11048
+ watcherAgentId,
11049
+ debounceMs
11050
+ });
11051
+ const unsub = events.onPattern("file-watcher:changed", (_eventName, rawPayload) => {
11052
+ const payload = rawPayload;
11053
+ if (!payload?.path) return;
11054
+ void cfg.onChange({
11055
+ path: payload.path,
11056
+ event: payload.event ?? "change",
11057
+ timestamp: payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
11058
+ }).catch(() => {
11059
+ });
11060
+ });
11061
+ return () => {
11062
+ unsub();
11063
+ };
11064
+ }
11065
+
11066
+ // src/coordination/mailbox-hooks.ts
11067
+ function createMailboxHooks(opts) {
11068
+ const { mailbox, agentId, notifyNewMail = true, heartbeat = true } = opts;
11069
+ let lastUnreadCount = -1;
11070
+ return {
11071
+ /**
11072
+ * Call before each tool execution. Checks mailbox and emits events.
11073
+ * @param events — EventBus-like object with emit method.
11074
+ */
11075
+ async beforeTool(events) {
11076
+ try {
11077
+ const count = await mailbox.unreadCount(agentId);
11078
+ if (notifyNewMail && count !== lastUnreadCount) {
11079
+ lastUnreadCount = count;
11080
+ events.emit("mailbox.unread_count", { agentId, count });
11081
+ }
11082
+ } catch {
11083
+ }
11084
+ },
11085
+ /**
11086
+ * Call after each tool execution. Updates heartbeat and optionally
11087
+ * current tool status.
11088
+ */
11089
+ async afterTool(toolName) {
11090
+ if (!heartbeat) return;
11091
+ try {
11092
+ await mailbox.heartbeat({
11093
+ agentId,
11094
+ status: "running",
11095
+ currentTool: toolName
11096
+ });
11097
+ } catch {
11098
+ }
11099
+ },
11100
+ /** Reset the cached unread count (e.g., after the agent checks manually). */
11101
+ reset() {
11102
+ lastUnreadCount = -1;
11103
+ }
11104
+ };
11105
+ }
11106
+ var DEFAULT_MAX_ENTRIES = 1e4;
11107
+ var LOG_FILENAME = "package-authors.json";
11108
+ function logPath(storageDir) {
11109
+ return path4.join(storageDir, LOG_FILENAME);
11110
+ }
11111
+ async function loadLog(storageDir, projectRoot) {
11112
+ try {
11113
+ const raw = await fsp6.readFile(logPath(storageDir), "utf-8");
11114
+ const parsed = JSON.parse(raw);
11115
+ if (!parsed.entries || !Array.isArray(parsed.entries)) {
11116
+ return { projectRoot, entries: [] };
11117
+ }
11118
+ return parsed;
11119
+ } catch (err) {
11120
+ if (err.code === "ENOENT") {
11121
+ return { projectRoot, entries: [] };
11122
+ }
11123
+ throw err;
11124
+ }
11125
+ }
11126
+ async function saveLog(storageDir, log) {
11127
+ await fsp6.mkdir(storageDir, { recursive: true });
11128
+ const tmp = `${logPath(storageDir)}.tmp.${Date.now()}`;
11129
+ await fsp6.writeFile(tmp, JSON.stringify(log, null, 2) + "\n", "utf-8");
11130
+ await fsp6.rename(tmp, logPath(storageDir));
11131
+ }
11132
+ function detectEcosystem(manifestPath) {
11133
+ const name = path4.basename(manifestPath).toLowerCase();
11134
+ if (name === "package.json") return "npm";
11135
+ if (name === "go.mod") return "go";
11136
+ if (name === "cargo.toml") return "cargo";
11137
+ if (name === "pyproject.toml" || name === "requirements.txt" || name === "pipfile" || name === "pipfile.lock") return "pip";
11138
+ if (name === "gemfile" || name === "gemfile.lock") return "gem";
11139
+ if (name === "composer.json" || name === "composer.lock") return "composer";
11140
+ if (name.endsWith(".csproj") || name === "packages.config") return "nuget";
11141
+ if (name === "mix.exs" || name === "mix.lock") return "elixir";
11142
+ if (name === "pom.xml" || name.startsWith("build.gradle")) return "maven";
11143
+ if (name === "pubspec.yaml" || name === "pubspec.lock") return "dart";
11144
+ if (name === "vcpkg.json") return "vcpkg";
11145
+ if (name === "conanfile.txt" || name === "conanfile.py") return "conan";
11146
+ if (name === "cmakeLists.txt") return "cmake";
11147
+ return "unknown";
11148
+ }
11149
+ async function recordPackageAction(opts, entry) {
11150
+ const { storageDir, projectRoot, maxEntries = DEFAULT_MAX_ENTRIES } = opts;
11151
+ const log = await loadLog(storageDir, projectRoot);
11152
+ log.entries.push({
11153
+ ...entry,
11154
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11155
+ });
11156
+ if (log.entries.length > maxEntries) {
11157
+ const keep = Math.floor(maxEntries * 0.8);
11158
+ log.entries = log.entries.slice(-keep);
11159
+ log.lastCompactedAt = (/* @__PURE__ */ new Date()).toISOString();
11160
+ }
11161
+ await saveLog(storageDir, log);
11162
+ }
11163
+ async function getPackageAuthor(opts, manifestPath, packageName) {
11164
+ const log = await loadLog(opts.storageDir, opts.projectRoot);
11165
+ const normalizedManifest = manifestPath.replace(/\\/g, "/");
11166
+ for (let i = log.entries.length - 1; i >= 0; i--) {
11167
+ const e = log.entries[i];
11168
+ if (e && e.manifestPath.replace(/\\/g, "/") === normalizedManifest && e.packageName === packageName) {
11169
+ return e;
11170
+ }
11171
+ }
11172
+ return void 0;
11173
+ }
11174
+ async function getManifestPackages(opts, manifestPath) {
11175
+ const log = await loadLog(opts.storageDir, opts.projectRoot);
11176
+ const normalizedManifest = manifestPath.replace(/\\/g, "/");
11177
+ return log.entries.filter(
11178
+ (e) => e.manifestPath.replace(/\\/g, "/") === normalizedManifest
11179
+ );
11180
+ }
11181
+ async function getPackagesByAgent(opts, agentId) {
11182
+ const log = await loadLog(opts.storageDir, opts.projectRoot);
11183
+ const map = /* @__PURE__ */ new Map();
11184
+ for (const e of log.entries) {
11185
+ if (e.agentId === agentId) {
11186
+ const key = `${e.manifestPath}|${e.packageName}`;
11187
+ map.set(key, e);
11188
+ }
11189
+ }
11190
+ return map;
11191
+ }
11192
+ async function updatePackageOutdatedStatus(opts, manifestPath, packageName, outdated, latestVersion) {
11193
+ const { storageDir, projectRoot } = opts;
11194
+ const log = await loadLog(storageDir, projectRoot);
11195
+ log.entries.push({
11196
+ manifestPath,
11197
+ packageName,
11198
+ versionSpec: "",
11199
+ ecosystem: detectEcosystem(manifestPath),
11200
+ agentId: "outdated-checker",
11201
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11202
+ outdated,
11203
+ latestVersion
11204
+ });
11205
+ await saveLog(storageDir, log);
11206
+ }
11207
+ async function getFullPackageLog(opts) {
11208
+ return loadLog(opts.storageDir, opts.projectRoot);
11209
+ }
11210
+
11211
+ // src/coordination/package-outdated-watcher.ts
11212
+ function parseOutdatedPackages(body) {
11213
+ const results = [];
11214
+ const tableRows = body.matchAll(
11215
+ /^\|\s*([^-][^|]*?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|/gm
11216
+ );
11217
+ for (const rowMatch of tableRows) {
11218
+ const cols = rowMatch[0].split("|").map((c) => c.trim()).filter(Boolean);
11219
+ if (cols.length >= 5 && cols[0] && cols[0] !== "Package") {
11220
+ results.push({
11221
+ name: cols[0] ?? "",
11222
+ currentVersion: cols[1] ?? "",
11223
+ latestVersion: cols[2] ?? "",
11224
+ wantedVersion: cols[3] ?? "",
11225
+ manifestPath: cols[4] ?? "",
11226
+ ecosystem: detectEcosystem2(cols[4] ?? "")
11227
+ });
11228
+ }
11229
+ }
11230
+ if (results.length === 0) {
11231
+ const kvMatches = body.matchAll(
11232
+ /(?:package|name)[\s:=]+([\w@/-]+).*?(?:current|version)[\s:=]+([\d.]+).*?latest[\s:=]+([\d.]+)/gi
11233
+ );
11234
+ for (const m of kvMatches) {
11235
+ results.push({
11236
+ name: m[1] ?? "",
11237
+ currentVersion: m[2] ?? "",
11238
+ latestVersion: m[3] ?? "",
11239
+ wantedVersion: m[2] ?? "",
11240
+ manifestPath: "",
11241
+ ecosystem: "unknown"
11242
+ });
11243
+ }
11244
+ }
11245
+ return results;
11246
+ }
11247
+ function detectEcosystem2(manifestPath) {
11248
+ const name = manifestPath.split("/").pop()?.split("\\").pop() ?? manifestPath;
11249
+ if (name === "package.json") return "npm";
11250
+ if (name === "go.mod") return "go";
11251
+ if (name === "cargo.toml") return "cargo";
11252
+ if (name === "pyproject.toml" || name === "requirements.txt") return "pip";
11253
+ if (name === "gemfile" || name === "gemfile.lock") return "gem";
11254
+ if (name === "composer.json" || name === "composer.lock") return "composer";
11255
+ if (name.endsWith(".csproj") || name === "packages.config") return "nuget";
11256
+ if (name === "mix.exs" || name === "mix.lock") return "elixir";
11257
+ if (name === "pom.xml" || name.startsWith("build.gradle")) return "maven";
11258
+ if (name === "pubspec.yaml" || name === "pubspec.lock") return "dart";
11259
+ return "unknown";
11260
+ }
11261
+ function startPackageOutdatedWatcher(opts) {
11262
+ const {
11263
+ mailbox,
11264
+ packageTrackerOpts,
11265
+ pollIntervalMs = 60 * 60 * 1e3,
11266
+ watcherAgentId = "pkg-outdated-watcher",
11267
+ onNotify,
11268
+ onLog,
11269
+ onError
11270
+ } = opts;
11271
+ const log = (msg) => onLog?.(msg);
11272
+ const handleError = (err) => onError?.(err);
11273
+ const state = {
11274
+ running: true,
11275
+ timer: null,
11276
+ processedIds: /* @__PURE__ */ new Set()
11277
+ };
11278
+ async function pollOnce() {
11279
+ if (!state.running) return;
11280
+ try {
11281
+ const messages = await mailbox.query({
11282
+ to: watcherAgentId,
11283
+ type: "result",
11284
+ unreadBy: watcherAgentId,
11285
+ limit: 10
11286
+ });
11287
+ for (const msg of messages) {
11288
+ if (state.processedIds.has(msg.id)) continue;
11289
+ state.processedIds.add(msg.id);
11290
+ await mailbox.ack({
11291
+ messageId: msg.id,
11292
+ readerId: watcherAgentId,
11293
+ read: true
11294
+ });
11295
+ await processResultMessage(msg);
11296
+ }
11297
+ } catch (err) {
11298
+ handleError(err);
11299
+ }
11300
+ }
11301
+ async function processResultMessage(msg) {
11302
+ const entries = parseOutdatedPackages(msg.body ?? "");
11303
+ if (entries.length === 0) {
11304
+ log(`[pkg-outdated-watcher] No outdated packages found in message ${msg.id}`);
11305
+ return;
11306
+ }
11307
+ log(`[pkg-outdated-watcher] Processing ${entries.length} outdated package(s) from ${msg.from}`);
11308
+ for (const entry of entries) {
11309
+ try {
11310
+ const author = await getPackageAuthor(
11311
+ packageTrackerOpts,
11312
+ entry.manifestPath,
11313
+ entry.name
11314
+ );
11315
+ const notifyTarget = author?.agentId ?? "*";
11316
+ const notifyBody = buildNotifyBody(entry, author?.agentName);
11317
+ const notifyMsg = {
11318
+ from: watcherAgentId,
11319
+ to: notifyTarget,
11320
+ subject: `Outdated package: ${entry.name}@${entry.currentVersion} \u2192 ${entry.latestVersion}`,
11321
+ body: notifyBody,
11322
+ priority: "high"
11323
+ };
11324
+ await onNotify(notifyMsg);
11325
+ log(
11326
+ `[pkg-outdated-watcher] Notified ${notifyTarget} about outdated ${entry.name} (${entry.currentVersion} \u2192 ${entry.latestVersion}) in ${entry.manifestPath}`
11327
+ );
11328
+ } catch (err) {
11329
+ handleError(err);
11330
+ log(`[pkg-outdated-watcher] Failed to notify for ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
11331
+ }
11332
+ }
11333
+ }
11334
+ state.timer = setInterval(() => {
11335
+ void pollOnce();
11336
+ }, pollIntervalMs);
11337
+ void pollOnce();
11338
+ return () => {
11339
+ state.running = false;
11340
+ if (state.timer) {
11341
+ clearInterval(state.timer);
11342
+ state.timer = null;
11343
+ }
11344
+ };
11345
+ }
11346
+ function buildNotifyBody(entry, authorName) {
11347
+ const lines = [
11348
+ `The package **${entry.name}** is outdated.`,
11349
+ "",
11350
+ `| Field | Value |`,
11351
+ `|-------|-------|`,
11352
+ `| Package | ${entry.name} |`,
11353
+ `| Installed | ${entry.currentVersion} |`,
11354
+ `| Latest | ${entry.latestVersion} |`,
11355
+ `| Wanted | ${entry.wantedVersion} |`,
11356
+ `| Manifest | ${entry.manifestPath} |`,
11357
+ `| Ecosystem | ${entry.ecosystem} |`,
11358
+ ""
11359
+ ];
11360
+ if (authorName) {
11361
+ lines.push(
11362
+ `You added this package${authorName !== "unknown" ? ` (as ${authorName})` : ""}. Consider updating it with the install tool.`
11363
+ );
11364
+ } else {
11365
+ lines.push(
11366
+ `This package appears to have been added by an agent no longer on record. Consider reviewing and updating it.`
11367
+ );
11368
+ }
11369
+ lines.push(
11370
+ "",
11371
+ `Update with:`,
11372
+ `\`\`\``,
11373
+ `${getUpdateCommand(entry)}`,
11374
+ `\`\`\``
11375
+ );
11376
+ return lines.join("\n");
11377
+ }
11378
+ function getUpdateCommand(entry) {
11379
+ switch (entry.ecosystem) {
11380
+ case "npm":
11381
+ return `pnpm add ${entry.name}@latest # or: pnpm update ${entry.name}`;
11382
+ case "cargo":
11383
+ return `cargo update ${entry.name}`;
11384
+ case "go":
11385
+ return `go get ${entry.name}@latest`;
11386
+ case "pip":
11387
+ return `pip install --upgrade ${entry.name}`;
11388
+ case "gem":
11389
+ return `gem install ${entry.name}`;
11390
+ case "composer":
11391
+ return `composer require ${entry.name}:^${entry.latestVersion} --update-with-dependencies`;
11392
+ case "nuget":
11393
+ return `dotnet add package ${entry.name}`;
11394
+ case "maven":
11395
+ return `# Update the <version> in pom.xml or run:
11396
+ mvn versions:use-latest-versions`;
11397
+ case "dart":
11398
+ return `dart pub upgrade ${entry.name}`;
11399
+ default:
11400
+ return `# Update ${entry.name} to ${entry.latestVersion} using your package manager`;
11401
+ }
11402
+ }
11403
+
11404
+ export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GlobalMailbox, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, formatHumanPrompt, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, normalizeRecipient, recordPackageAction, resolveMailboxIdentity, resolveProjectDir, rosterSummaryFromConfigs, scoreAgents, startPackageOutdatedWatcher, updatePackageOutdatedStatus };
9567
11405
  //# sourceMappingURL=index.js.map
9568
11406
  //# sourceMappingURL=index.js.map