@wrongstack/core 0.308.6 → 0.309.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 (44) hide show
  1. package/dist/coordination/agents/index.js +1 -0
  2. package/dist/coordination/agents/role-skills.d.ts +1 -0
  3. package/dist/coordination/director/director-toolset.d.ts +2 -2
  4. package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
  5. package/dist/coordination/director-tools.d.ts +2 -0
  6. package/dist/coordination/director.d.ts +9 -0
  7. package/dist/coordination/explore-companion.d.ts +191 -0
  8. package/dist/coordination/fleet.d.ts +26 -0
  9. package/dist/coordination/index.d.ts +2 -1
  10. package/dist/coordination/index.js +1396 -370
  11. package/dist/coordination/mail-tools.d.ts +10 -6
  12. package/dist/coordination/mailbox-codecs.d.ts +31 -0
  13. package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
  14. package/dist/coordination/multi-agent-timeout.d.ts +11 -1
  15. package/dist/coordination/mutation-engine.d.ts +74 -0
  16. package/dist/coordination/subagent-budget.d.ts +54 -0
  17. package/dist/coordination/subagent-finish.d.ts +78 -0
  18. package/dist/core/index.js +19 -4
  19. package/dist/defaults/index.js +731 -52
  20. package/dist/execution/compaction-core.d.ts +1 -1
  21. package/dist/execution/compaction-elision.d.ts +0 -10
  22. package/dist/execution/index.js +269 -16
  23. package/dist/goal/index.js +54 -27
  24. package/dist/goal/phase-orchestrator.d.ts +7 -0
  25. package/dist/goal/types.d.ts +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +1280 -201
  28. package/dist/kernel/events/agent-events.d.ts +31 -2
  29. package/dist/models/index.js +11 -1
  30. package/dist/plugin/discovery.d.ts +73 -0
  31. package/dist/plugin/index.d.ts +2 -0
  32. package/dist/plugin/index.js +270 -29
  33. package/dist/plugin/loader.d.ts +5 -1
  34. package/dist/plugin/trust.d.ts +78 -0
  35. package/dist/tools/index.js +1 -0
  36. package/dist/types/config/mcp-features.d.ts +21 -0
  37. package/dist/types/config/skills-fleet-brain.d.ts +18 -0
  38. package/dist/types/index.d.ts +1 -1
  39. package/dist/types/index.js +14 -0
  40. package/dist/types/multi-agent.d.ts +15 -0
  41. package/dist/types/provider.d.ts +29 -1
  42. package/instructions/agents/chaos-monkey.md +57 -0
  43. package/instructions/agents/explore-companion.md +35 -0
  44. package/package.json +3 -3
@@ -285,6 +285,46 @@ function createMessage(type, from, payload, to) {
285
285
  };
286
286
  }
287
287
 
288
+ // src/core/btw.ts
289
+ var META_KEY = "_btwNotes";
290
+ var MAX_PENDING = 20;
291
+ function readQueue(ctx) {
292
+ const raw = ctx.meta[META_KEY];
293
+ return Array.isArray(raw) ? raw : [];
294
+ }
295
+ function setBtwNote(ctx, text) {
296
+ const trimmed = text.trim();
297
+ if (!trimmed) return readQueue(ctx).length;
298
+ const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
299
+ ctx.meta[META_KEY] = next;
300
+ return next.length;
301
+ }
302
+
303
+ // src/coordination/subagent-finish.ts
304
+ var SUBAGENT_FINISH_REQUESTED_EVENT = "subagent.finish_requested";
305
+ var DEFAULT_SUBAGENT_FINISH_GRACE_MS = 12e4;
306
+ function resolveGracefulFinish(config) {
307
+ const raw = config.gracefulFinish;
308
+ if (raw === void 0 || raw === false) return void 0;
309
+ if (raw === true) return { graceMs: DEFAULT_SUBAGENT_FINISH_GRACE_MS };
310
+ const graceMs = typeof raw.graceMs === "number" && Number.isFinite(raw.graceMs) && raw.graceMs > 0 ? Math.floor(raw.graceMs) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
311
+ return { graceMs };
312
+ }
313
+ function buildSubagentFinishNotice(input) {
314
+ const localTime = new Date(input.deadlineMs).toISOString();
315
+ const seconds = Math.max(1, Math.round(input.graceMs / 1e3));
316
+ const timeLeft = input.graceMs > 0 ? `You have roughly ${seconds} seconds (until ${localTime}) of legitimate working time left.` : `Your working-time window is already spent (deadline was ${localTime}) \u2014 finish now.`;
317
+ return [
318
+ "[SUBAGENT FINISH] The leader agent has finished its work.",
319
+ `Reason: ${input.reason}`,
320
+ timeLeft,
321
+ "Finish your task now, in this turn: complete the thought you are working on, stop",
322
+ "starting new tool calls unless one is strictly required to finish, and write your",
323
+ "final answer or report as your final output, then end your turn.",
324
+ "Do not restart the task and do not begin new work."
325
+ ].join("\n");
326
+ }
327
+
288
328
  // src/coordination/subagent-budget.ts
289
329
  var TIMEOUT_PREEMPT_FRACTION = 0.85;
290
330
  var DECISION_TIMEOUT_MS = 6e4;
@@ -342,6 +382,82 @@ var SubagentBudget = class _SubagentBudget {
342
382
  this.limits.idleTimeoutMs = ext.idleTimeoutMs;
343
383
  }
344
384
  }
385
+ /**
386
+ * Graceful-finish state (see coordination/subagent-finish.ts).
387
+ * `_finishNotified` guards the single in-band emission; `_grace` records a
388
+ * granted working-time extension past the original wall-clock deadline.
389
+ * They are separate because the two callers want different semantics:
390
+ * the watchdog grants grace at the deadline crossing (notify + extend),
391
+ * while an explicit leader-finished request only notifies — a subagent
392
+ * well inside its budget keeps its full legitimate working time and simply
393
+ * accelerates.
394
+ */
395
+ _finishNotified = false;
396
+ _grace = null;
397
+ /** True once the in-band finish notification has been emitted. */
398
+ get finishNotified() {
399
+ return this._finishNotified;
400
+ }
401
+ /** True once a grace window has been granted past the original deadline. */
402
+ get graceGranted() {
403
+ return this._grace !== null;
404
+ }
405
+ /**
406
+ * Notify the subagent in-band to finish its task in its own turn:
407
+ * `subagent.finish_requested` is emitted on the wired EventBus and the
408
+ * agent loop folds the notice into the conversation between tool batches.
409
+ * Nothing aborts — this is a notification, never an interrupt.
410
+ *
411
+ * `opts.graceMs` additionally extends the wall-clock ceiling by that window
412
+ * (used by the watchdog at a deadline crossing, so the model gets working
413
+ * time instead of a kill). Omit it to notify without touching the budget —
414
+ * the subagent keeps its existing time budget and just accelerates.
415
+ *
416
+ * Returns `true` when this call did something (emitted the notification
417
+ * and/or granted grace); `false` when there was nothing to do (already
418
+ * notified, grace already granted, no EventBus wired, budget not started).
419
+ */
420
+ notifyFinish(reason, opts, now = Date.now) {
421
+ if (!this._events) return false;
422
+ if (this.startTime === null) return false;
423
+ const shouldEmit = !this._finishNotified;
424
+ const rawGrace = opts?.graceMs;
425
+ const shouldGrant = rawGrace !== void 0 && this._grace === null;
426
+ if (!shouldEmit && !shouldGrant) return false;
427
+ let grantedGraceMs = 0;
428
+ let graceDeadlineMs;
429
+ if (shouldGrant && rawGrace !== void 0) {
430
+ grantedGraceMs = Number.isFinite(rawGrace) && rawGrace > 0 ? Math.floor(rawGrace) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
431
+ graceDeadlineMs = now() + grantedGraceMs;
432
+ this._grace = { deadlineMs: graceDeadlineMs, graceMs: grantedGraceMs };
433
+ this.patchLimits({ timeoutMs: graceDeadlineMs - this.startTime });
434
+ }
435
+ if (shouldEmit) {
436
+ this._finishNotified = true;
437
+ const effectiveDeadlineMs = graceDeadlineMs ?? (this.limits.timeoutMs !== void 0 ? this.startTime + this.limits.timeoutMs : now() + DEFAULT_SUBAGENT_FINISH_GRACE_MS);
438
+ const effectiveGraceMs = Math.max(0, effectiveDeadlineMs - now());
439
+ const subagentId = this._subagentId;
440
+ this._events.emit(SUBAGENT_FINISH_REQUESTED_EVENT, {
441
+ // Omitted entirely when the budget was built without an id — an
442
+ // empty string is an address that matches nothing.
443
+ ...subagentId !== void 0 ? { subagentId } : {},
444
+ reason,
445
+ deadlineMs: effectiveDeadlineMs,
446
+ graceMs: effectiveGraceMs,
447
+ notice: buildSubagentFinishNotice({
448
+ reason,
449
+ deadlineMs: effectiveDeadlineMs,
450
+ graceMs: effectiveGraceMs
451
+ })
452
+ });
453
+ }
454
+ return true;
455
+ }
456
+ /** Epoch ms by which the subagent should have produced its final output,
457
+ * once a grace window was granted. Undefined before that. */
458
+ get finishDeadlineMs() {
459
+ return this._grace?.deadlineMs;
460
+ }
345
461
  iterations = 0;
346
462
  toolCalls = 0;
347
463
  tokenInput = 0;
@@ -357,6 +473,10 @@ var SubagentBudget = class _SubagentBudget {
357
473
  lastActivityTime = null;
358
474
  _onThreshold;
359
475
  _sessionId;
476
+ /** Owning subagent id — used to address the graceful-finish event. */
477
+ _subagentId;
478
+ /** True when only the coordinator watchdog may enforce wall-clock limits. */
479
+ _wallClockWatchdogOwned;
360
480
  /**
361
481
  * Hard cap on how long `_negotiateExtension` waits for the coordinator to
362
482
  * respond before defaulting to 'stop'. Without this fallback an absent
@@ -428,6 +548,8 @@ var SubagentBudget = class _SubagentBudget {
428
548
  constructor(limits = {}, mode = "auto", options = {}) {
429
549
  this._mode = mode;
430
550
  this._sessionId = options.sessionId;
551
+ this._subagentId = options.subagentId;
552
+ this._wallClockWatchdogOwned = options.wallClockWatchdogOwned === true;
431
553
  this.limits = { ...limits };
432
554
  }
433
555
  currentSessionId() {
@@ -506,7 +628,7 @@ var SubagentBudget = class _SubagentBudget {
506
628
  if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
507
629
  exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
508
630
  }
509
- const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
631
+ const wallOwnedByWatchdog = this._wallClockWatchdogOwned || this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
510
632
  if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
511
633
  exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
512
634
  }
@@ -705,7 +827,7 @@ var SubagentBudget = class _SubagentBudget {
705
827
  if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
706
828
  const elapsed = Date.now() - this.startTime;
707
829
  const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
708
- const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
830
+ const wallTripped = this._wallClockWatchdogOwned || wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
709
831
  const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
710
832
  if (!wallTripped && !idleTripped) return;
711
833
  void this.checkLimits(elapsed);
@@ -1165,6 +1287,14 @@ function makeAgentSubagentRunner(opts) {
1165
1287
  );
1166
1288
  const onParentAbort = () => aborter.abort();
1167
1289
  ctx.signal.addEventListener("abort", onParentAbort);
1290
+ if (resolveGracefulFinish(ctx.config)) {
1291
+ unsub.push(
1292
+ events.on("subagent.finish_requested", (e) => {
1293
+ if (e.subagentId && e.subagentId !== ctx.subagentId) return;
1294
+ setBtwNote(agent.ctx, e.notice);
1295
+ })
1296
+ );
1297
+ }
1168
1298
  let result;
1169
1299
  try {
1170
1300
  result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
@@ -2862,6 +2992,7 @@ function inferRuntimeCapabilities(toolNames) {
2862
2992
  var skillSet = (...names) => names;
2863
2993
  var ROLE_SKILL_SETS = {
2864
2994
  explore: skillSet("research-web", "node-modern", "typescript-strict"),
2995
+ "explore-companion": skillSet("node-modern", "typescript-strict"),
2865
2996
  search: skillSet("bug-hunter", "typescript-strict", "research-web"),
2866
2997
  research: skillSet("research-web", "tech-stack", "security-scanner", "api-design"),
2867
2998
  analyst: skillSet("sdd", "api-design", "testing", "security-scanner"),
@@ -10389,6 +10520,42 @@ var SHADOW_AGENT = {
10389
10520
  ...defineAgent("shadow-agent", "Shadow"),
10390
10521
  skillNames: [...SHADOW_AGENT_SKILLS]
10391
10522
  };
10523
+ var EXPLORE_COMPANION_AGENT = {
10524
+ ...defineAgent("explore-companion", "Explore Companion"),
10525
+ tools: [...TOOLS.read, ...TOOLS.index],
10526
+ // Read-only, triple-enforced: allowlist has no write/bash, and the
10527
+ // disabled list blocks the escape hatches explicitly.
10528
+ disabledTools: [
10529
+ "write",
10530
+ "edit",
10531
+ "replace",
10532
+ "patch",
10533
+ "bash",
10534
+ "exec",
10535
+ "delegate",
10536
+ "spawn_subagent",
10537
+ "assign_task"
10538
+ ],
10539
+ skillNames: [...ROLE_SKILL_SETS["explore-companion"]],
10540
+ spawnBudgetExempt: true,
10541
+ // Findings travel via mailbox + submit_result, not the leader's stream.
10542
+ textStream: "silent",
10543
+ toolStream: "silent"
10544
+ };
10545
+ var CHAOS_MONKEY_AGENT = {
10546
+ ...defineAgent("chaos-monkey", "Chaos Monkey"),
10547
+ tools: [...TOOLS.build],
10548
+ skillNames: ["testing", "typescript-strict"],
10549
+ spawnBudgetExempt: true,
10550
+ // Follow fleet worktree policy (NOT 'required'): mutation targets are
10551
+ // often freshly written and uncommitted — a worktree spawned from HEAD
10552
+ // would not contain them and every mutant would drift. Callers pass
10553
+ // `worktree: 'off'` in the mutation_test input for uncommitted targets.
10554
+ worktree: "auto",
10555
+ // Report travels via submit_result + final text, not the leader's stream.
10556
+ textStream: "silent",
10557
+ toolStream: "silent"
10558
+ };
10392
10559
  var CRITIC_AGENT = defineAgent("critic", "Critic");
10393
10560
  var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
10394
10561
  function withDispatchMetadata(definition) {
@@ -10407,6 +10574,8 @@ var FLEET_ROSTER = {
10407
10574
  critic: CRITIC_AGENT,
10408
10575
  generic: GENERIC_AGENT,
10409
10576
  "shadow-agent": SHADOW_AGENT,
10577
+ "explore-companion": EXPLORE_COMPANION_AGENT,
10578
+ "chaos-monkey": CHAOS_MONKEY_AGENT,
10410
10579
  ...Object.fromEntries(
10411
10580
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
10412
10581
  )
@@ -10430,6 +10599,23 @@ var FLEET_ROSTER_BUDGETS = {
10430
10599
  maxTokens: 96e3,
10431
10600
  maxCostUsd: 0.5
10432
10601
  },
10602
+ "explore-companion": {
10603
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
10604
+ maxIterations: 3e3,
10605
+ maxToolCalls: 8e3,
10606
+ maxTokens: 96e3,
10607
+ maxCostUsd: 0.5
10608
+ },
10609
+ "chaos-monkey": {
10610
+ // A mutation pass is many short apply/run/restore cycles — per-mutant
10611
+ // work is tiny, but a large plan (25 mutants/file × N files) needs
10612
+ // headroom. Idle-based reaping covers a stalled pass.
10613
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
10614
+ maxIterations: 2e3,
10615
+ maxToolCalls: 6e3,
10616
+ maxTokens: 96e3,
10617
+ maxCostUsd: 0.5
10618
+ },
10433
10619
  ...Object.fromEntries(
10434
10620
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
10435
10621
  )
@@ -11168,89 +11354,648 @@ async function readSubagentPartial(opts, subagentId) {
11168
11354
  return void 0;
11169
11355
  }
11170
11356
 
11171
- // src/coordination/dep-watcher.ts
11172
- var DEPENDENCY_FILE_PATTERNS = [
11173
- "package.json",
11174
- "tsconfig.json",
11175
- "pnpm-lock.yaml",
11176
- "yarn.lock",
11177
- "package-lock.json",
11178
- "go.mod",
11179
- "go.sum",
11180
- "Cargo.toml",
11181
- "Cargo.lock",
11182
- "pyproject.toml",
11183
- "setup.py",
11184
- "setup.cfg",
11185
- "requirements.txt",
11186
- "Pipfile",
11187
- "Pipfile.lock",
11188
- "Gemfile",
11189
- "Gemfile.lock",
11190
- "composer.json",
11191
- "composer.lock",
11192
- "mix.exs",
11193
- "mix.lock",
11194
- "pom.xml",
11195
- "build.gradle",
11196
- "build.gradle.kts",
11197
- "settings.gradle",
11198
- "settings.gradle.kts",
11199
- "*.csproj",
11200
- "packages.config",
11201
- "pubspec.yaml",
11202
- "pubspec.lock",
11203
- "CMakeLists.txt",
11204
- "conanfile.txt",
11205
- "conanfile.py",
11206
- "vcpkg.json"
11207
- ];
11208
- function makeDependencyWatcherConfig(opts) {
11209
- const {
11210
- projectRoot,
11211
- mailbox,
11212
- targetAgent = "*",
11213
- watcherAgentId = "dep-watcher",
11214
- debounceMs = 3e3,
11215
- patterns = DEPENDENCY_FILE_PATTERNS
11216
- } = opts;
11217
- const watchPaths = [];
11218
- for (const p of patterns) {
11219
- if (p.includes("*")) {
11220
- continue;
11221
- }
11222
- watchPaths.push(`${projectRoot}/${p}`);
11357
+ // src/coordination/explore-companion.ts
11358
+ import { randomUUID as randomUUID6 } from "node:crypto";
11359
+
11360
+ // src/coordination/mailbox-type-properties.ts
11361
+ var MAILBOX_TYPE_PROPERTIES = {
11362
+ note: {
11363
+ category: "informational",
11364
+ expectsReply: false,
11365
+ requiresAction: false,
11366
+ backgroundEligible: false,
11367
+ outOfBand: false,
11368
+ renderPriority: 20,
11369
+ recipientObligation: "Read for context; no reply needed.",
11370
+ senderGuidance: "General-purpose FYI. Use when no more specific type applies."
11371
+ },
11372
+ ask: {
11373
+ category: "actionable",
11374
+ expectsReply: true,
11375
+ requiresAction: true,
11376
+ backgroundEligible: true,
11377
+ outOfBand: false,
11378
+ renderPriority: 10,
11379
+ recipientObligation: "Answer as soon as possible \u2014 the sender is waiting.",
11380
+ senderGuidance: "Blocking question. Only use when you need an answer to proceed."
11381
+ },
11382
+ assign: {
11383
+ category: "actionable",
11384
+ expectsReply: false,
11385
+ requiresAction: true,
11386
+ backgroundEligible: true,
11387
+ outOfBand: false,
11388
+ renderPriority: 10,
11389
+ recipientObligation: "Accept or decline; act on it when current operation allows.",
11390
+ senderGuidance: 'Task delegation. Must be directed to a specific recipient (not "*").'
11391
+ },
11392
+ steer: {
11393
+ category: "actionable",
11394
+ expectsReply: false,
11395
+ requiresAction: true,
11396
+ backgroundEligible: true,
11397
+ outOfBand: false,
11398
+ renderPriority: 0,
11399
+ // Always rendered first
11400
+ recipientObligation: "Pause current approach, adjust per instruction, then resume.",
11401
+ senderGuidance: "Mid-task direction change. The recipient is already working on something."
11402
+ },
11403
+ btw: {
11404
+ category: "informational",
11405
+ expectsReply: false,
11406
+ requiresAction: false,
11407
+ backgroundEligible: false,
11408
+ outOfBand: false,
11409
+ renderPriority: 30,
11410
+ recipientObligation: "Absorb the information and stay on current task; no reply needed.",
11411
+ senderGuidance: "Low-priority aside. Non-urgent info that can wait."
11412
+ },
11413
+ broadcast: {
11414
+ category: "routing",
11415
+ expectsReply: false,
11416
+ requiresAction: false,
11417
+ backgroundEligible: false,
11418
+ outOfBand: false,
11419
+ renderPriority: 20,
11420
+ recipientObligation: 'Read if addressed to you (direct recipient, alias, or "*").',
11421
+ senderGuidance: 'Multi-recipient envelope. Auto-selected when to is "*" or "@session".'
11422
+ },
11423
+ status: {
11424
+ category: "informational",
11425
+ expectsReply: false,
11426
+ requiresAction: false,
11427
+ backgroundEligible: false,
11428
+ outOfBand: false,
11429
+ renderPriority: 40,
11430
+ recipientObligation: "Use to avoid redundant work; never act on as a task or question.",
11431
+ senderGuidance: "Agent/system status update. Machine-generated, not for human-originated messages."
11432
+ },
11433
+ result: {
11434
+ category: "informational",
11435
+ expectsReply: false,
11436
+ requiresAction: false,
11437
+ backgroundEligible: true,
11438
+ outOfBand: false,
11439
+ renderPriority: 10,
11440
+ recipientObligation: "Factor into next decision; treat as evidence, not a new task.",
11441
+ senderGuidance: "Task completion notice. Share the outcome of finished work."
11442
+ },
11443
+ review: {
11444
+ category: "actionable",
11445
+ expectsReply: false,
11446
+ requiresAction: true,
11447
+ backgroundEligible: true,
11448
+ outOfBand: false,
11449
+ renderPriority: 10,
11450
+ recipientObligation: "Inspect when convenient; no immediate reply required.",
11451
+ senderGuidance: "Passive review request (code/doc/PR). No reply required."
11452
+ },
11453
+ control: {
11454
+ category: "control_signal",
11455
+ expectsReply: false,
11456
+ requiresAction: false,
11457
+ backgroundEligible: false,
11458
+ outOfBand: true,
11459
+ renderPriority: 999,
11460
+ // Never rendered
11461
+ recipientObligation: 'Handled by the agent loop, NOT folded into conversation. "interrupt" causes cooperative halt.',
11462
+ senderGuidance: "RESERVED for runtime use. Agents must NOT send control messages."
11223
11463
  }
11224
- watchPaths.push(projectRoot);
11225
- const unique = [...new Set(watchPaths)];
11226
- const isMultiRecipient = targetAgent === "*" || targetAgent.startsWith("@session:");
11227
- const globPatterns = patterns.filter((p) => p.includes("*"));
11228
- const plainPatterns = patterns.filter((p) => !p.includes("*"));
11229
- function matchesPattern(filePath) {
11230
- const basename6 = filePath.split("/").pop()?.split("\\").pop() ?? "";
11231
- if (plainPatterns.includes(basename6)) return true;
11232
- for (const gp of globPatterns) {
11233
- const regex = new RegExp(
11234
- "^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
11235
- );
11236
- if (regex.test(basename6)) return true;
11237
- }
11238
- return false;
11464
+ };
11465
+
11466
+ // src/coordination/mailbox-auth-types.ts
11467
+ var MAILBOX_CAPABILITY_IMPLICATIONS = {
11468
+ "mail.read.all": ["mail.read.self"],
11469
+ "mail.events.all": ["mail.events.self"],
11470
+ "mail.send.directive": ["mail.send.actionable", "mail.send.informational"],
11471
+ "mail.send.actionable": ["mail.send.informational"],
11472
+ // Leaf capabilities imply nothing further.
11473
+ "mail.send.informational": [],
11474
+ "mail.read.self": [],
11475
+ "mail.ack.self": [],
11476
+ "mail.events.self": [],
11477
+ "mail.presence.register.self": [],
11478
+ "mail.presence.heartbeat.self": [],
11479
+ "mail.presence.deregister.self": [],
11480
+ "mail.presence.read": [],
11481
+ "mail.retention.purge": [],
11482
+ "mail.retention.clear": [],
11483
+ "mail.admin.receipts": []
11484
+ };
11485
+ function expandMailboxCapabilities(caps) {
11486
+ const result = /* @__PURE__ */ new Set();
11487
+ const queue = [...caps];
11488
+ while (queue.length > 0) {
11489
+ const cap = queue.pop();
11490
+ if (result.has(cap)) continue;
11491
+ result.add(cap);
11492
+ const implied = MAILBOX_CAPABILITY_IMPLICATIONS[cap];
11493
+ if (implied) queue.push(...implied);
11239
11494
  }
11240
- const pending = /* @__PURE__ */ new Map();
11241
- return {
11242
- watchPaths: unique,
11243
- debounceMs,
11244
- dispose() {
11245
- for (const t of pending.values()) clearTimeout(t);
11246
- pending.clear();
11247
- },
11248
- async onChange(entry) {
11249
- if (entry.event === "delete") return;
11250
- if (!matchesPattern(entry.path)) return;
11251
- const key = entry.path;
11252
- const existing = pending.get(key);
11253
- if (existing) clearTimeout(existing);
11495
+ return result;
11496
+ }
11497
+ function hasMailboxCapability(actor, cap) {
11498
+ if (actor.capabilities.has(cap)) return true;
11499
+ const expanded = expandMailboxCapabilities(actor.capabilities);
11500
+ return expanded.has(cap);
11501
+ }
11502
+
11503
+ // src/coordination/mailbox-predicates.ts
11504
+ function mailboxIdentityBase(agentId) {
11505
+ return agentId.split(/[@#]/, 1)[0].trim().toLowerCase();
11506
+ }
11507
+ function isMailboxLeader(agentId, role) {
11508
+ return mailboxIdentityBase(agentId) === "leader" || role?.trim().toLowerCase() === "leader";
11509
+ }
11510
+ function isMailboxSenderInFamily(senderId, family) {
11511
+ const base = mailboxIdentityBase(senderId);
11512
+ const normalizedFamily = family.trim().toLowerCase();
11513
+ if (normalizedFamily.length === 0) return false;
11514
+ return base === normalizedFamily || base.startsWith(`${normalizedFamily}-`);
11515
+ }
11516
+ function isMailboxMessageVisibleTo(message, agentId, role) {
11517
+ return message.audience !== "leaders" || isMailboxLeader(agentId, role);
11518
+ }
11519
+ function validateSendType(type, to) {
11520
+ if (type === "control") {
11521
+ throw new TypeError('Type "control" is reserved for runtime use and cannot be set by agents');
11522
+ }
11523
+ const isMultiRecipient = to === "*" || to.startsWith("@session:");
11524
+ if (type === "assign" && isMultiRecipient) {
11525
+ throw new TypeError(
11526
+ `Type "assign" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
11527
+ );
11528
+ }
11529
+ if (type === "steer" && isMultiRecipient) {
11530
+ throw new TypeError(
11531
+ `Type "steer" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
11532
+ );
11533
+ }
11534
+ }
11535
+ var SESSION_RECIPIENT_PREFIX = "@session:";
11536
+ function sessionRecipient(sessionId) {
11537
+ const normalizedSessionId = sessionId.trim();
11538
+ if (!normalizedSessionId) {
11539
+ throw new TypeError('sessionId is required for the "@session" recipient');
11540
+ }
11541
+ return `${SESSION_RECIPIENT_PREFIX}${normalizedSessionId}`;
11542
+ }
11543
+ function normalizeRecipient(to, sessionId) {
11544
+ const trimmed = to.trim();
11545
+ const normalized = trimmed.toLowerCase();
11546
+ if (normalized === "all") return "*";
11547
+ if (normalized === "@session") return sessionRecipient(sessionId ?? "");
11548
+ return trimmed;
11549
+ }
11550
+ function isActionRequiredForActor(message, projection) {
11551
+ if (projection.legacyGlobalCompletion) return false;
11552
+ if (message.deletedAt !== void 0) return false;
11553
+ if (projection.completedByMe) return false;
11554
+ return MAILBOX_TYPE_PROPERTIES[message.type]?.requiresAction === true;
11555
+ }
11556
+
11557
+ // src/coordination/mailbox-session-sync.ts
11558
+ function isAffectedBySessionAffinity(message) {
11559
+ return message.sessionAffinity !== void 0;
11560
+ }
11561
+ async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
11562
+ if (!isAffectedBySessionAffinity(message)) return true;
11563
+ const affinity = message.sessionAffinity;
11564
+ if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
11565
+ return false;
11566
+ }
11567
+ if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
11568
+ return false;
11569
+ }
11570
+ if (!currentSessionId) {
11571
+ return ctx?.allowUnscoped === true;
11572
+ }
11573
+ if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
11574
+ if (affinity.sessionId !== currentSessionId) return false;
11575
+ return true;
11576
+ }
11577
+ if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
11578
+ try {
11579
+ const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
11580
+ if (resolved === currentSessionId) return true;
11581
+ if (resolved !== void 0) return false;
11582
+ } catch {
11583
+ }
11584
+ }
11585
+ if (ctx?.allowUnscoped === true) return true;
11586
+ return false;
11587
+ }
11588
+
11589
+ // src/coordination/explore-companion.ts
11590
+ var DEFAULT_EXPLORE_COMPANION_AGENT_ID = "explore-companion";
11591
+ var DEFAULT_PROBE_COOLDOWN_MS = 12e4;
11592
+ var DEFAULT_MAX_PENDING_PROBES = 8;
11593
+ var DEFAULT_MAILBOX_POLL_INTERVAL_MS = 5e3;
11594
+ var DEFAULT_EXPLORE_EDIT_TOOLS = [
11595
+ "edit",
11596
+ "write",
11597
+ "patch",
11598
+ "multi_edit",
11599
+ "multiedit",
11600
+ "str_replace"
11601
+ ];
11602
+ var DEFAULT_EXPLORE_SEARCH_TOOLS = [
11603
+ "search",
11604
+ "grep",
11605
+ "codebase-search"
11606
+ ];
11607
+ function buildProbeTaskText(probe) {
11608
+ const payload = { probe: probe.probe };
11609
+ if (probe.hint) payload.hint = probe.hint;
11610
+ if (probe.context) payload.context = probe.context;
11611
+ return JSON.stringify(payload, null, 2);
11612
+ }
11613
+ function extractedPath(input) {
11614
+ if (!input || typeof input !== "object") return void 0;
11615
+ const rec = input;
11616
+ const candidate = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : void 0;
11617
+ return candidate && candidate.length > 0 ? candidate : void 0;
11618
+ }
11619
+ function looksEmpty(e) {
11620
+ if (typeof e.outputLines === "number" && e.outputLines === 0) return true;
11621
+ const out = e.output ?? "";
11622
+ return /(?:^|\n)(?:no |0 )(?:matches|results|files? found|occurrences)/i.test(out) || /total\s*:\s*0\b/i.test(out);
11623
+ }
11624
+ function extractSubjectTokens(text) {
11625
+ const out = [];
11626
+ const fileRe = /([\w@./-]+\.(?:[cm]?[jt]sx?|json|md|py|go|rs|ya?ml))\b/g;
11627
+ for (const m of text.matchAll(fileRe)) {
11628
+ const value = m[1];
11629
+ if (value) out.push({ kind: "file", value });
11630
+ }
11631
+ const symRe = /\b[A-Z][A-Za-z0-9_]{2,}\b/g;
11632
+ for (const m of text.matchAll(symRe)) {
11633
+ out.push({ kind: "symbol", value: m[0] });
11634
+ }
11635
+ return out;
11636
+ }
11637
+ var ExploreCompanion = class {
11638
+ constructor(opts) {
11639
+ this.opts = opts;
11640
+ this.cfg = this.resolveConfig(opts);
11641
+ }
11642
+ opts;
11643
+ unsubscribers = [];
11644
+ /** Paths the leader has read (readSet) — feeds edit-unread + unfamiliar-read. */
11645
+ readSet = /* @__PURE__ */ new Set();
11646
+ /** subject → last probe time; cooldown gate. Survives detach/reconfigure. */
11647
+ probedAt = /* @__PURE__ */ new Map();
11648
+ /** todo id → last observed status, per leader agent. */
11649
+ todoSeen = /* @__PURE__ */ new Map();
11650
+ pending = [];
11651
+ inFlight = false;
11652
+ pollTimer;
11653
+ running = false;
11654
+ hostStarted = false;
11655
+ cfg;
11656
+ resolveConfig(opts) {
11657
+ const signals = opts.signals ?? {};
11658
+ return {
11659
+ enabled: opts.enabled ?? true,
11660
+ cooldownMs: opts.cooldownMs ?? DEFAULT_PROBE_COOLDOWN_MS,
11661
+ maxPending: opts.maxPending ?? DEFAULT_MAX_PENDING_PROBES,
11662
+ pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_MAILBOX_POLL_INTERVAL_MS,
11663
+ companionAgentId: opts.companionAgentId ?? DEFAULT_EXPLORE_COMPANION_AGENT_ID,
11664
+ signals: {
11665
+ editUnreadFile: signals.editUnreadFile ?? true,
11666
+ searchZeroHits: signals.searchZeroHits ?? true,
11667
+ unfamiliarRead: signals.unfamiliarRead ?? true,
11668
+ todoInProgress: signals.todoInProgress ?? true,
11669
+ errorSymbol: signals.errorSymbol ?? true,
11670
+ mailboxAsk: signals.mailboxAsk ?? true
11671
+ },
11672
+ fileEditTools: new Set(
11673
+ (opts.fileEditTools ?? DEFAULT_EXPLORE_EDIT_TOOLS).map((t) => t.toLowerCase())
11674
+ ),
11675
+ searchTools: new Set(
11676
+ (opts.searchTools ?? DEFAULT_EXPLORE_SEARCH_TOOLS).map((t) => t.toLowerCase())
11677
+ )
11678
+ };
11679
+ }
11680
+ /** Resolve the leader's own session id for event filtering. */
11681
+ resolveLeaderSessionId() {
11682
+ const sid = this.opts.leaderSessionId;
11683
+ return typeof sid === "function" ? sid() : sid;
11684
+ }
11685
+ /** Resolve the leader's agent id for todo diffing (optional signal). */
11686
+ resolveLeaderAgentId() {
11687
+ const aid = this.opts.leaderAgentId;
11688
+ if (!aid) return void 0;
11689
+ return typeof aid === "function" ? aid() : aid;
11690
+ }
11691
+ /** Re-apply tunables to a (possibly running) companion. */
11692
+ reconfigure(next) {
11693
+ const merged = { ...this.opts, ...next };
11694
+ const nextCfg = this.resolveConfig(merged);
11695
+ const changed = nextCfg.enabled !== this.cfg.enabled || nextCfg.cooldownMs !== this.cfg.cooldownMs || nextCfg.maxPending !== this.cfg.maxPending || nextCfg.pollIntervalMs !== this.cfg.pollIntervalMs || nextCfg.companionAgentId !== this.cfg.companionAgentId || Object.keys(nextCfg.signals).some(
11696
+ (k) => nextCfg.signals[k] !== this.cfg.signals[k]
11697
+ );
11698
+ this.cfg = nextCfg;
11699
+ if (!changed) return false;
11700
+ if (this.hostStarted) {
11701
+ this.detach();
11702
+ this.attach();
11703
+ }
11704
+ return true;
11705
+ }
11706
+ /** Begin watching. Idempotent; a disabled companion records intent only. */
11707
+ start() {
11708
+ this.hostStarted = true;
11709
+ this.attach();
11710
+ }
11711
+ /** Stop watching and drop the host's intent to watch. */
11712
+ stop() {
11713
+ this.hostStarted = false;
11714
+ this.detach();
11715
+ }
11716
+ /** True while the watchers are attached. */
11717
+ isRunning() {
11718
+ return this.running;
11719
+ }
11720
+ /** Number of probes queued but not yet dispatched (for status surfaces). */
11721
+ pendingCount() {
11722
+ return this.pending.length;
11723
+ }
11724
+ attach() {
11725
+ if (!this.cfg.enabled || this.running) return;
11726
+ this.running = true;
11727
+ this.unsubscribers.push(
11728
+ this.opts.events.on("tool.executed", (e) => {
11729
+ const lsid = this.resolveLeaderSessionId();
11730
+ if (lsid && e.sessionId && e.sessionId !== lsid) return;
11731
+ this.trackToolExecuted(e);
11732
+ })
11733
+ );
11734
+ if (this.cfg.signals.todoInProgress && this.resolveLeaderAgentId()) {
11735
+ this.unsubscribers.push(
11736
+ this.opts.events.on("session.agents_updated", (e) => {
11737
+ const lsid = this.resolveLeaderSessionId();
11738
+ if (lsid && e.sessionId && e.sessionId !== lsid) return;
11739
+ this.trackAgentTodos(e.agents);
11740
+ })
11741
+ );
11742
+ }
11743
+ if (this.cfg.signals.errorSymbol) {
11744
+ this.unsubscribers.push(
11745
+ this.opts.events.on("error", (e) => {
11746
+ const lsid = this.resolveLeaderSessionId();
11747
+ if (lsid && e.sessionId && e.sessionId !== lsid) return;
11748
+ this.trackError(e.err);
11749
+ })
11750
+ );
11751
+ }
11752
+ if (this.cfg.signals.mailboxAsk) {
11753
+ this.pollTimer = setInterval(() => {
11754
+ void this.pollMailbox();
11755
+ }, this.cfg.pollIntervalMs);
11756
+ this.pollTimer.unref?.();
11757
+ }
11758
+ }
11759
+ /** Tear down watchers without touching host intent. Cooldowns survive. */
11760
+ detach() {
11761
+ for (const unsub of this.unsubscribers.splice(0)) unsub();
11762
+ if (this.pollTimer) {
11763
+ clearInterval(this.pollTimer);
11764
+ this.pollTimer = void 0;
11765
+ }
11766
+ this.running = false;
11767
+ }
11768
+ // ── signal handlers ──────────────────────────────────────────────────────
11769
+ trackToolExecuted(e) {
11770
+ const tool = e.name.toLowerCase();
11771
+ const path42 = extractedPath(e.input);
11772
+ if (e.ok && this.cfg.signals.editUnreadFile && this.cfg.fileEditTools.has(tool) && path42) {
11773
+ if (!this.readSet.has(path42)) {
11774
+ this.engage({
11775
+ id: randomUUID6(),
11776
+ probe: `Map file ${path42}: role, exports, dependencies, and callers \u2014 the leader is about to edit it.`,
11777
+ hint: { file: path42 },
11778
+ context: `Leader edited ${path42} without reading it first.`,
11779
+ source: "edit_unread_file",
11780
+ subject: `file:${path42}`,
11781
+ createdAt: this.now()
11782
+ });
11783
+ }
11784
+ return;
11785
+ }
11786
+ if (e.ok && this.cfg.signals.unfamiliarRead && tool === "read" && path42) {
11787
+ if (!this.readSet.has(path42)) {
11788
+ this.readSet.add(path42);
11789
+ this.engage({
11790
+ id: randomUUID6(),
11791
+ probe: `Skeleton + callers + dependents of ${path42}: what it exports, who imports it, and how it fits the feature flow.`,
11792
+ hint: { file: path42 },
11793
+ context: `Leader read unfamiliar file ${path42}.`,
11794
+ source: "unfamiliar_read",
11795
+ subject: `file:${path42}`,
11796
+ createdAt: this.now()
11797
+ });
11798
+ }
11799
+ return;
11800
+ }
11801
+ if (e.ok && this.cfg.signals.searchZeroHits && this.cfg.searchTools.has(tool) && looksEmpty(e)) {
11802
+ const input = e.input ?? {};
11803
+ const query = typeof input["query"] === "string" ? input["query"] : typeof input["pattern"] === "string" ? input["pattern"] : "";
11804
+ this.engage({
11805
+ id: randomUUID6(),
11806
+ probe: query ? `Locate "${query}" \u2014 the leader's ${e.name} returned no hits. Try synonyms, a refreshed index, and lexical fallbacks.` : `The leader's ${e.name} returned no results. Find where the concept actually lives.`,
11807
+ hint: query ? { symbol: query } : void 0,
11808
+ context: `${e.name} for "${query}" returned zero results.`,
11809
+ source: "search_zero_hits",
11810
+ subject: `search:${query}`,
11811
+ createdAt: this.now()
11812
+ });
11813
+ }
11814
+ }
11815
+ trackAgentTodos(agents) {
11816
+ const leaderId = this.resolveLeaderAgentId();
11817
+ if (!leaderId) return;
11818
+ const leader = agents.find((a) => a.id === leaderId);
11819
+ if (!leader?.todos) return;
11820
+ for (const todo of leader.todos) {
11821
+ const prev = this.todoSeen.get(todo.id);
11822
+ if (prev !== "in_progress" && todo.status === "in_progress") {
11823
+ const mentions = extractSubjectTokens(todo.content);
11824
+ const first = mentions[0];
11825
+ this.engage({
11826
+ id: randomUUID6(),
11827
+ probe: `Pre-map the files/symbols behind this in-progress todo: "${todo.content.slice(0, 160)}".`,
11828
+ hint: first ? { [first.kind]: first.value } : void 0,
11829
+ context: `Todo "${todo.content.slice(0, 120)}" flipped to in_progress.`,
11830
+ source: "todo_in_progress",
11831
+ subject: `todo:${todo.id}`,
11832
+ createdAt: this.now()
11833
+ });
11834
+ }
11835
+ this.todoSeen.set(todo.id, todo.status);
11836
+ }
11837
+ }
11838
+ trackError(err) {
11839
+ const tokens = extractSubjectTokens(err.message);
11840
+ for (const token of tokens.slice(0, 2)) {
11841
+ this.engage({
11842
+ id: randomUUID6(),
11843
+ probe: `What is ${token.value}, where does it live, and who uses it? The leader hit an error naming it.`,
11844
+ hint: { [token.kind]: token.value },
11845
+ context: `Error: ${err.message.slice(0, 300)}`,
11846
+ source: "error_symbol",
11847
+ subject: `token:${token.value}`,
11848
+ createdAt: this.now()
11849
+ });
11850
+ }
11851
+ }
11852
+ async pollMailbox() {
11853
+ if (!this.cfg.enabled || !this.cfg.signals.mailboxAsk) return;
11854
+ try {
11855
+ const messages = await this.opts.mailbox.query({
11856
+ unreadBy: this.cfg.companionAgentId,
11857
+ limit: 20
11858
+ });
11859
+ const lsid = this.resolveLeaderSessionId();
11860
+ for (const msg of messages) {
11861
+ if (msg.type !== "ask" && msg.type !== "assign") continue;
11862
+ const fromLeader = isMailboxLeader(msg.from) || lsid != null && msg.senderSessionId === lsid;
11863
+ if (!fromLeader) continue;
11864
+ this.engage({
11865
+ id: randomUUID6(),
11866
+ probe: msg.body.trim().slice(0, 2e3) || msg.subject,
11867
+ context: `Direct ask from ${msg.from}: ${msg.subject}`,
11868
+ source: "mailbox_ask",
11869
+ subject: `mail:${msg.id}`,
11870
+ createdAt: this.now()
11871
+ });
11872
+ await this.opts.mailbox.ack({
11873
+ messageId: msg.id,
11874
+ readerId: this.cfg.companionAgentId,
11875
+ read: true,
11876
+ completed: true
11877
+ }).catch(() => {
11878
+ });
11879
+ }
11880
+ } catch {
11881
+ }
11882
+ }
11883
+ // ── engagement ───────────────────────────────────────────────────────────
11884
+ cooldownOk(subject) {
11885
+ const last = this.probedAt.get(subject);
11886
+ return last === void 0 || this.now() - last >= this.cfg.cooldownMs;
11887
+ }
11888
+ now() {
11889
+ return this.opts.now ? this.opts.now() : Date.now();
11890
+ }
11891
+ engage(probe) {
11892
+ if (!this.cfg.enabled) return;
11893
+ if (!this.cooldownOk(probe.subject)) return;
11894
+ this.probedAt.set(probe.subject, this.now());
11895
+ if (this.pending.length >= this.cfg.maxPending) {
11896
+ this.pending.shift();
11897
+ }
11898
+ this.pending.push(probe);
11899
+ void this.drain();
11900
+ }
11901
+ async drain() {
11902
+ if (this.inFlight) return;
11903
+ const probe = this.pending.shift();
11904
+ if (!probe) return;
11905
+ this.inFlight = true;
11906
+ try {
11907
+ await this.opts.onProbe(probe);
11908
+ } catch {
11909
+ } finally {
11910
+ this.inFlight = false;
11911
+ if (this.pending.length > 0) void this.drain();
11912
+ }
11913
+ }
11914
+ };
11915
+
11916
+ // src/coordination/dep-watcher.ts
11917
+ var DEPENDENCY_FILE_PATTERNS = [
11918
+ "package.json",
11919
+ "tsconfig.json",
11920
+ "pnpm-lock.yaml",
11921
+ "yarn.lock",
11922
+ "package-lock.json",
11923
+ "go.mod",
11924
+ "go.sum",
11925
+ "Cargo.toml",
11926
+ "Cargo.lock",
11927
+ "pyproject.toml",
11928
+ "setup.py",
11929
+ "setup.cfg",
11930
+ "requirements.txt",
11931
+ "Pipfile",
11932
+ "Pipfile.lock",
11933
+ "Gemfile",
11934
+ "Gemfile.lock",
11935
+ "composer.json",
11936
+ "composer.lock",
11937
+ "mix.exs",
11938
+ "mix.lock",
11939
+ "pom.xml",
11940
+ "build.gradle",
11941
+ "build.gradle.kts",
11942
+ "settings.gradle",
11943
+ "settings.gradle.kts",
11944
+ "*.csproj",
11945
+ "packages.config",
11946
+ "pubspec.yaml",
11947
+ "pubspec.lock",
11948
+ "CMakeLists.txt",
11949
+ "conanfile.txt",
11950
+ "conanfile.py",
11951
+ "vcpkg.json"
11952
+ ];
11953
+ function makeDependencyWatcherConfig(opts) {
11954
+ const {
11955
+ projectRoot,
11956
+ mailbox,
11957
+ targetAgent = "*",
11958
+ watcherAgentId = "dep-watcher",
11959
+ debounceMs = 3e3,
11960
+ patterns = DEPENDENCY_FILE_PATTERNS
11961
+ } = opts;
11962
+ const watchPaths = [];
11963
+ for (const p of patterns) {
11964
+ if (p.includes("*")) {
11965
+ continue;
11966
+ }
11967
+ watchPaths.push(`${projectRoot}/${p}`);
11968
+ }
11969
+ watchPaths.push(projectRoot);
11970
+ const unique = [...new Set(watchPaths)];
11971
+ const isMultiRecipient = targetAgent === "*" || targetAgent.startsWith("@session:");
11972
+ const globPatterns = patterns.filter((p) => p.includes("*"));
11973
+ const plainPatterns = patterns.filter((p) => !p.includes("*"));
11974
+ function matchesPattern(filePath) {
11975
+ const basename6 = filePath.split("/").pop()?.split("\\").pop() ?? "";
11976
+ if (plainPatterns.includes(basename6)) return true;
11977
+ for (const gp of globPatterns) {
11978
+ const regex = new RegExp(
11979
+ "^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
11980
+ );
11981
+ if (regex.test(basename6)) return true;
11982
+ }
11983
+ return false;
11984
+ }
11985
+ const pending = /* @__PURE__ */ new Map();
11986
+ return {
11987
+ watchPaths: unique,
11988
+ debounceMs,
11989
+ dispose() {
11990
+ for (const t of pending.values()) clearTimeout(t);
11991
+ pending.clear();
11992
+ },
11993
+ async onChange(entry) {
11994
+ if (entry.event === "delete") return;
11995
+ if (!matchesPattern(entry.path)) return;
11996
+ const key = entry.path;
11997
+ const existing = pending.get(key);
11998
+ if (existing) clearTimeout(existing);
11254
11999
  pending.set(
11255
12000
  key,
11256
12001
  setTimeout(async () => {
@@ -11323,7 +12068,7 @@ function attachDepWatcherBridge(opts) {
11323
12068
  }
11324
12069
 
11325
12070
  // src/coordination/director.ts
11326
- import { randomUUID as randomUUID14 } from "node:crypto";
12071
+ import { randomUUID as randomUUID16 } from "node:crypto";
11327
12072
  import * as fsp25 from "node:fs/promises";
11328
12073
 
11329
12074
  // src/core/instruction-template.ts
@@ -12128,7 +12873,7 @@ var FleetContextOverflowError = class extends Error {
12128
12873
  };
12129
12874
 
12130
12875
  // src/coordination/director/director-task-registry.ts
12131
- import { randomUUID as randomUUID6 } from "node:crypto";
12876
+ import { randomUUID as randomUUID7 } from "node:crypto";
12132
12877
  var DirectorTaskRegistry = class _DirectorTaskRegistry {
12133
12878
  constructor(deps) {
12134
12879
  this.deps = deps;
@@ -12163,7 +12908,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
12163
12908
  return { internal, consumedInBand: waiter !== void 0 || anyConsumed };
12164
12909
  }
12165
12910
  async assign(task) {
12166
- const taskWithId = task.id ? task : { ...task, id: randomUUID6() };
12911
+ const taskWithId = task.id ? task : { ...task, id: randomUUID7() };
12167
12912
  if (this.deps.isWorkComplete()) {
12168
12913
  const stopped = this.makeStoppedResult(
12169
12914
  taskWithId.id,
@@ -12183,7 +12928,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
12183
12928
  return taskWithId.id;
12184
12929
  }
12185
12930
  async assignInternal(task) {
12186
- const taskWithId = task.id ? task : { ...task, id: randomUUID6() };
12931
+ const taskWithId = task.id ? task : { ...task, id: randomUUID7() };
12187
12932
  this.internalTaskIds.add(taskWithId.id);
12188
12933
  try {
12189
12934
  await this.deps.coordinator.assign(taskWithId);
@@ -12398,7 +13143,7 @@ ${JSON.stringify(result.result, null, 2)}
12398
13143
  };
12399
13144
 
12400
13145
  // src/coordination/director-tools.ts
12401
- import { randomUUID as randomUUID10 } from "node:crypto";
13146
+ import { randomUUID as randomUUID12 } from "node:crypto";
12402
13147
  import {
12403
13148
  completeKanbanDispatch,
12404
13149
  failKanbanDispatch,
@@ -12411,7 +13156,7 @@ import {
12411
13156
  } from "@wrongstack/kanban";
12412
13157
 
12413
13158
  // src/coordination/director-input-helpers.ts
12414
- import { randomUUID as randomUUID7 } from "node:crypto";
13159
+ import { randomUUID as randomUUID8 } from "node:crypto";
12415
13160
  function stringArray2(value) {
12416
13161
  if (!Array.isArray(value)) return void 0;
12417
13162
  const strings = value.filter((v) => typeof v === "string" && v.trim().length > 0);
@@ -12425,7 +13170,7 @@ function normalizeWorktreeOverride(value) {
12425
13170
  function instantiateRosterConfig2(role, base) {
12426
13171
  return {
12427
13172
  ...base,
12428
- id: `${role}-${randomUUID7().slice(0, 8)}`
13173
+ id: `${role}-${randomUUID8().slice(0, 8)}`
12429
13174
  };
12430
13175
  }
12431
13176
 
@@ -12492,7 +13237,7 @@ function buildKanbanFleetTaskPrompt(board, task, lease) {
12492
13237
  const dependencyLines = (task.dependsOn ?? []).map((depId) => board.tasks.find((candidate) => candidate.id === depId)).filter((dep) => Boolean(dep)).map((dep) => `- ${dep.title} [${dep.status}] (${dep.id})`);
12493
13238
  const checks = task.successCriteria?.map((check) => `- ${check.description}`).join("\n");
12494
13239
  const metrics = task.goalMetrics?.map(
12495
- (metric) => `- ${metric.name}: ${metric.current ?? "n/a"}${metric.target !== void 0 ? ` / ${metric.target}` : ""}${metric.unit ? ` ${metric.unit}` : ""} [${metric.status}]`
13240
+ (metric) => `- ${metric.name}: ${metric.current ?? "n/a"}${metric.target !== void 0 ? ` / ${metric.direction === "at_most" ? "\u2264" : "\u2265"} ${metric.target}` : ""}${metric.unit ? ` ${metric.unit}` : ""} [${metric.status}]`
12496
13241
  ).join("\n");
12497
13242
  const chain = task.chain ? [
12498
13243
  `chainId: ${task.chain.chainId}`,
@@ -12766,7 +13511,7 @@ function makeLLMClassifier(complete2) {
12766
13511
  }
12767
13512
 
12768
13513
  // src/coordination/director-basic-tools.ts
12769
- import { randomUUID as randomUUID8 } from "node:crypto";
13514
+ import { randomUUID as randomUUID9 } from "node:crypto";
12770
13515
  function makeAssignTool(director) {
12771
13516
  const inputSchema = {
12772
13517
  type: "object",
@@ -12805,7 +13550,7 @@ function makeAssignTool(director) {
12805
13550
  };
12806
13551
  }
12807
13552
  const task = {
12808
- id: randomUUID8(),
13553
+ id: randomUUID9(),
12809
13554
  description: composeBoundedTaskDescription(i.description, boundary.boundary),
12810
13555
  subagentId: i.subagentId,
12811
13556
  maxToolCalls: i.maxToolCalls,
@@ -13225,7 +13970,7 @@ function makeWorkCompleteTool(director) {
13225
13970
  }
13226
13971
 
13227
13972
  // src/coordination/director-quality-gate-tool.ts
13228
- import { randomUUID as randomUUID9 } from "node:crypto";
13973
+ import { randomUUID as randomUUID10 } from "node:crypto";
13229
13974
  function makeQualityGateTool(director, roster) {
13230
13975
  return {
13231
13976
  name: "quality_gate",
@@ -13322,7 +14067,7 @@ function makeQualityGateTool(director, roster) {
13322
14067
  makeQualityGateSubagentConfig("verifier", roster, i.verifierWorktree ?? "auto")
13323
14068
  );
13324
14069
  const taskId = await director.assign({
13325
- id: randomUUID9(),
14070
+ id: randomUUID10(),
13326
14071
  subagentId,
13327
14072
  description: buildVerifierTask(i, {
13328
14073
  attempt,
@@ -13340,7 +14085,7 @@ function makeQualityGateTool(director, roster) {
13340
14085
  makeQualityGateSubagentConfig("reviewer", roster, i.reviewerWorktree ?? "off")
13341
14086
  );
13342
14087
  const taskId = await director.assign({
13343
- id: randomUUID9(),
14088
+ id: randomUUID10(),
13344
14089
  subagentId,
13345
14090
  description: buildReviewerTask(i, {
13346
14091
  attempt,
@@ -13368,7 +14113,7 @@ function makeQualityGateTool(director, roster) {
13368
14113
  };
13369
14114
  }
13370
14115
  const repairTaskId = await director.assign({
13371
- id: randomUUID9(),
14116
+ id: randomUUID10(),
13372
14117
  subagentId: i.repairSubagentId,
13373
14118
  description: buildRepairTask(i, attempts[attempts.length - 1], attempt),
13374
14119
  timeoutMs: i.timeoutMs
@@ -13598,6 +14343,413 @@ function excerpt(text, max) {
13598
14343
  ...(truncated)`;
13599
14344
  }
13600
14345
 
14346
+ // src/coordination/director-mutation-test-tool.ts
14347
+ import { randomUUID as randomUUID11 } from "node:crypto";
14348
+ import { readFileSync as readFileSync13 } from "node:fs";
14349
+ import { isAbsolute as isAbsolute3, join as join16 } from "node:path";
14350
+
14351
+ // src/coordination/mutation-engine.ts
14352
+ var TOKEN_PATTERNS = [
14353
+ {
14354
+ kind: "relax-boundary",
14355
+ // `>` not followed by `=` and not part of `=>` or `>>`; require code-ish
14356
+ // context on both sides so generic text (JSX, strings) is not touched.
14357
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>(?!=|>))/g,
14358
+ replace: () => ">="
14359
+ },
14360
+ {
14361
+ kind: "tighten-boundary",
14362
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>=)/g,
14363
+ replace: () => ">"
14364
+ },
14365
+ {
14366
+ kind: "arith-plus-to-minus",
14367
+ // `+` between operands (binary), not `++`, unary `+x`, or `+=`.
14368
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>\+(?!\+|=))/g,
14369
+ replace: () => "-"
14370
+ },
14371
+ {
14372
+ kind: "arith-minus-to-plus",
14373
+ // Binary `-` between operands, not `--`, `-=` or negative-number literal.
14374
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>-(?!-|=))/g,
14375
+ replace: () => "+"
14376
+ },
14377
+ {
14378
+ kind: "negate-boolean",
14379
+ // Standalone boolean literals used as values, not property names.
14380
+ regex: /(?<![.\w$])(?<op>true|false)(?![\w$])/g,
14381
+ replace: (m) => m === "true" ? "false" : "true"
14382
+ },
14383
+ {
14384
+ kind: "return-null",
14385
+ // `return <expr>;` where expr is not already null/undefined/void.
14386
+ regex: /(?<indent>\breturn\b)(?<expr>\s+[^;{}\n]+?)\s*;/g,
14387
+ replace: () => "return null;"
14388
+ }
14389
+ ];
14390
+ function planMutations(file, source, opts = {}) {
14391
+ const maxPerFile = opts.maxPerFile ?? 25;
14392
+ const out = [];
14393
+ const lines = source.split("\n");
14394
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
14395
+ const line = lines[lineIdx];
14396
+ const t = line.trim();
14397
+ if (t.startsWith("//") || t.startsWith("*") || t.startsWith("/*")) continue;
14398
+ for (const pattern of TOKEN_PATTERNS) {
14399
+ pattern.regex.lastIndex = 0;
14400
+ let m;
14401
+ while ((m = pattern.regex.exec(line)) !== null) {
14402
+ const token = m.groups?.["op"] ?? m[0];
14403
+ const tokenStart = m.index + m[0].indexOf(token);
14404
+ if (isMasked(line, tokenStart, token.length)) continue;
14405
+ const original = line.slice(tokenStart, tokenStart + token.length);
14406
+ const replacement = pattern.replace(token);
14407
+ if (replacement === original) continue;
14408
+ out.push({
14409
+ id: `${pattern.kind}#${lineIdx + 1}#${tokenStart + 1}`,
14410
+ kind: pattern.kind,
14411
+ file,
14412
+ line: lineIdx + 1,
14413
+ column: tokenStart + 1,
14414
+ original,
14415
+ replacement
14416
+ });
14417
+ }
14418
+ }
14419
+ if (out.length >= maxPerFile) break;
14420
+ }
14421
+ return out.slice(0, maxPerFile);
14422
+ }
14423
+ function isMasked(line, start, len) {
14424
+ let inSingle = false;
14425
+ let inDouble = false;
14426
+ for (let i = 0; i < start; i++) {
14427
+ const c = line[i];
14428
+ const prev = i > 0 ? line[i - 1] : void 0;
14429
+ if (c === "'" && prev !== "\\") inSingle = !inSingle;
14430
+ else if (c === '"' && prev !== "\\") inDouble = !inDouble;
14431
+ if (!inSingle && !inDouble && c === "/" && prev === "/") return true;
14432
+ }
14433
+ if (inSingle || inDouble) return true;
14434
+ const window = line.slice(start, start + len);
14435
+ return /['"]/.test(window);
14436
+ }
14437
+ function parseMutationReport(text) {
14438
+ const candidates = [];
14439
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/);
14440
+ if (fence?.[1]) candidates.push(fence[1].trim());
14441
+ const firstBrace = text.indexOf("{");
14442
+ if (firstBrace >= 0) candidates.push(extractBalancedObject(text, firstBrace));
14443
+ for (const candidate of candidates) {
14444
+ if (!candidate) continue;
14445
+ try {
14446
+ const parsed = JSON.parse(candidate);
14447
+ if (!Array.isArray(parsed.mutants)) continue;
14448
+ return {
14449
+ mutants: parsed.mutants.map(normalizeMutantEntry).filter((x) => Boolean(x)),
14450
+ summary: typeof parsed.summary === "string" ? parsed.summary : void 0
14451
+ };
14452
+ } catch {
14453
+ }
14454
+ }
14455
+ return void 0;
14456
+ }
14457
+ function extractBalancedObject(text, start) {
14458
+ let depth = 0;
14459
+ let inString = false;
14460
+ let escaped = false;
14461
+ for (let i = start; i < text.length; i++) {
14462
+ const c = text[i];
14463
+ if (escaped) {
14464
+ escaped = false;
14465
+ continue;
14466
+ }
14467
+ if (c === "\\") {
14468
+ escaped = true;
14469
+ continue;
14470
+ }
14471
+ if (c === '"') inString = !inString;
14472
+ if (inString) continue;
14473
+ if (c === "{") depth++;
14474
+ else if (c === "}") {
14475
+ depth--;
14476
+ if (depth === 0) return text.slice(start, i + 1);
14477
+ }
14478
+ }
14479
+ return text.slice(start);
14480
+ }
14481
+ function normalizeMutantEntry(value) {
14482
+ if (typeof value !== "object" || value === null) return void 0;
14483
+ const rec = value;
14484
+ const id = typeof rec["id"] === "string" ? rec["id"] : void 0;
14485
+ const status = rec["status"];
14486
+ if (!id || status !== "killed" && status !== "survived" && status !== "skipped") {
14487
+ return void 0;
14488
+ }
14489
+ return {
14490
+ id,
14491
+ file: typeof rec["file"] === "string" ? rec["file"] : "",
14492
+ line: typeof rec["line"] === "number" ? rec["line"] : 0,
14493
+ kind: typeof rec["kind"] === "string" ? rec["kind"] : "",
14494
+ status,
14495
+ evidence: typeof rec["evidence"] === "string" ? rec["evidence"] : void 0
14496
+ };
14497
+ }
14498
+
14499
+ // src/coordination/director-mutation-test-tool.ts
14500
+ var DEFAULT_MAX_PER_FILE = 10;
14501
+ var DEFAULT_MAX_STRENGTHEN_ATTEMPTS = 2;
14502
+ var CHAOS_ROLE = "chaos-monkey";
14503
+ function makeMutationTestTool(director, roster, opts = {}) {
14504
+ return {
14505
+ name: "mutation_test",
14506
+ description: "Chaos Monkey mutation testing: deterministically sabotage boundary conditions in the target code (> to >=, + to -, boolean flips, return null), re-run the tests per mutant, and report which mutants were killed. Surviving mutants mean the tests are weak \u2014 optionally loop a strengthen-tests repair until they die.",
14507
+ usageHint: "Use after writing new code AND its tests, before delivering. Pass targets (files) and testCommand. Provide repairSubagentId to auto-strengthen weak tests. Survivors that persist are reported as suspected-equivalent.",
14508
+ permission: "auto",
14509
+ mutating: false,
14510
+ capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
14511
+ inputSchema: {
14512
+ type: "object",
14513
+ properties: {
14514
+ targets: {
14515
+ type: "array",
14516
+ items: { type: "string" },
14517
+ description: "Project-relative (or absolute) source files to mutate. Keep to files changed by the current task."
14518
+ },
14519
+ testCommand: {
14520
+ type: "string",
14521
+ description: 'Exact command that runs the relevant tests, e.g. "pnpm exec vitest run packages/core/tests/coordination/mutation-engine.test.ts".'
14522
+ },
14523
+ cwd: { type: "string", description: "Working directory for the test command." },
14524
+ maxPerFile: {
14525
+ type: "number",
14526
+ minimum: 1,
14527
+ maximum: 25,
14528
+ description: "Mutant cap per file per pass. Default 10."
14529
+ },
14530
+ maxStrengthenAttempts: {
14531
+ type: "number",
14532
+ minimum: 0,
14533
+ maximum: 5,
14534
+ description: "Strengthen\u2192re-verify rounds. Default 2 when repairSubagentId is set, else 0."
14535
+ },
14536
+ repairSubagentId: {
14537
+ type: "string",
14538
+ description: "Subagent that owns the tests. When set and mutants survive, it receives a strengthen-tests task and the survivors are re-verified."
14539
+ },
14540
+ chaosWorktree: {
14541
+ anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
14542
+ description: "Worktree override for the chaos agent. Use 'off' when targets are uncommitted \u2014 a worktree from HEAD would not contain them."
14543
+ },
14544
+ timeoutMs: { type: "number", minimum: 1, description: "Per-task timeout for chaos/strengthen/rerun tasks." },
14545
+ reportOnly: {
14546
+ type: "boolean",
14547
+ description: "Skip the strengthen loop even when survivors exist. Default false."
14548
+ }
14549
+ },
14550
+ required: ["targets", "testCommand"],
14551
+ additionalProperties: false
14552
+ },
14553
+ async execute(input, ctx) {
14554
+ const i = normalizeMutationTestInput(input);
14555
+ const root = opts.projectRoot ?? ctx.projectRoot;
14556
+ const plan = buildPlan(i, root);
14557
+ if (plan.length === 0) {
14558
+ return {
14559
+ verdict: "inconclusive",
14560
+ passed: false,
14561
+ error: "No mutable sites found in the given targets (after comment/string filtering)."
14562
+ };
14563
+ }
14564
+ const chaosSubagentId = await director.spawn(
14565
+ makeChaosConfig(roster, i.chaosWorktree ?? "off")
14566
+ );
14567
+ const chaosTaskId = await director.assign({
14568
+ id: randomUUID11(),
14569
+ subagentId: chaosSubagentId,
14570
+ description: buildChaosTask(plan, i, 1, []),
14571
+ timeoutMs: i.timeoutMs
14572
+ });
14573
+ const [chaosResult] = await director.awaitTasks([chaosTaskId]);
14574
+ const pass1 = collectOutcomes(chaosResult, plan);
14575
+ const survivors = pass1.filter((m) => m.status === "survived");
14576
+ const maxAttempts = clamp(
14577
+ i.maxStrengthenAttempts ?? (i.repairSubagentId && !i.reportOnly ? DEFAULT_MAX_STRENGTHEN_ATTEMPTS : 0),
14578
+ 0,
14579
+ 5
14580
+ );
14581
+ const attempts = [];
14582
+ let current = survivors;
14583
+ while (current.length > 0 && attempts.length < maxAttempts && i.repairSubagentId) {
14584
+ const attemptNo = attempts.length + 1;
14585
+ const strengthenTaskId = await director.assign({
14586
+ id: randomUUID11(),
14587
+ subagentId: i.repairSubagentId,
14588
+ description: buildStrengthenTask(current, i, attemptNo),
14589
+ timeoutMs: i.timeoutMs
14590
+ });
14591
+ const [strengthenResult] = await director.awaitTasks([strengthenTaskId]);
14592
+ if (strengthenResult?.status !== "success") {
14593
+ attempts.push({
14594
+ attempt: attemptNo,
14595
+ survivorsBefore: current,
14596
+ strengthenResult: strengthenResult ? { taskId: strengthenResult.taskId, status: strengthenResult.status } : void 0,
14597
+ survivorsAfter: current,
14598
+ suspectedEquivalent: []
14599
+ });
14600
+ break;
14601
+ }
14602
+ const survivorPlan = plan.filter((p) => current.some((s) => s.id === p.id));
14603
+ const rerunSubagentId = await director.spawn(
14604
+ makeChaosConfig(roster, i.chaosWorktree ?? "off")
14605
+ );
14606
+ const rerunTaskId = await director.assign({
14607
+ id: randomUUID11(),
14608
+ subagentId: rerunSubagentId,
14609
+ description: buildChaosTask(survivorPlan, i, attemptNo + 1, current),
14610
+ timeoutMs: i.timeoutMs
14611
+ });
14612
+ const [rerunResult] = await director.awaitTasks([rerunTaskId]);
14613
+ const passN = collectOutcomes(rerunResult, survivorPlan);
14614
+ const stillSurviving = passN.filter((m) => m.status === "survived" || m.status === "skipped");
14615
+ attempts.push({
14616
+ attempt: attemptNo,
14617
+ survivorsBefore: current,
14618
+ strengthenResult: { taskId: strengthenResult.taskId, status: strengthenResult.status },
14619
+ rerunResult: { taskId: rerunTaskId, status: rerunResult?.status ?? "unknown" },
14620
+ survivorsAfter: stillSurviving,
14621
+ suspectedEquivalent: stillSurviving.filter((m) => current.some((c) => c.id === m.id)).map((m) => m.id)
14622
+ });
14623
+ current = stillSurviving.filter((m) => m.status === "survived");
14624
+ if (passN.every((m) => m.status === "skipped")) break;
14625
+ }
14626
+ const finalSurvivors = current;
14627
+ const verifiedCount = pass1.filter((m) => m.status !== "skipped").length;
14628
+ const skippedCount = pass1.filter((m) => m.status === "skipped").length;
14629
+ const score = plan.length === 0 ? 0 : pass1.filter((m) => m.status === "killed").length / plan.length;
14630
+ const verdict = verifiedCount === 0 ? "inconclusive" : finalSurvivors.length === 0 ? skippedCount > 0 ? "partial" : "pass" : score >= 0.8 ? "partial" : "fail";
14631
+ return {
14632
+ verdict,
14633
+ passed: verdict === "pass",
14634
+ mutationScore: Number.parseFloat(score.toFixed(3)),
14635
+ planned: plan.length,
14636
+ killed: pass1.filter((m) => m.status === "killed").length,
14637
+ survived: pass1.filter((m) => m.status === "survived").length,
14638
+ skipped: pass1.filter((m) => m.status === "skipped").length,
14639
+ finalSurvivors: finalSurvivors.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
14640
+ suspectedEquivalent: attempts.flatMap((a) => a.suspectedEquivalent),
14641
+ strengthenAttempts: attempts.length,
14642
+ attempts,
14643
+ chaosTaskId,
14644
+ nextAction: finalSurvivors.length === 0 ? "accept" : attempts.length >= maxAttempts && i.repairSubagentId ? "manual_review_survivors" : "strengthen_tests"
14645
+ };
14646
+ }
14647
+ };
14648
+ }
14649
+ function normalizeMutationTestInput(input) {
14650
+ const raw = input ?? {};
14651
+ const targets = stringArray2(raw["targets"]) ?? [];
14652
+ const testCommand = typeof raw["testCommand"] === "string" ? raw["testCommand"].trim() : "";
14653
+ return {
14654
+ targets: targets.filter(Boolean),
14655
+ testCommand,
14656
+ cwd: typeof raw["cwd"] === "string" && raw["cwd"].trim() ? raw["cwd"].trim() : void 0,
14657
+ maxPerFile: typeof raw["maxPerFile"] === "number" ? raw["maxPerFile"] : void 0,
14658
+ maxStrengthenAttempts: typeof raw["maxStrengthenAttempts"] === "number" ? raw["maxStrengthenAttempts"] : void 0,
14659
+ repairSubagentId: typeof raw["repairSubagentId"] === "string" && raw["repairSubagentId"].trim() ? raw["repairSubagentId"].trim() : void 0,
14660
+ chaosWorktree: raw["chaosWorktree"] ?? void 0,
14661
+ timeoutMs: typeof raw["timeoutMs"] === "number" ? raw["timeoutMs"] : void 0,
14662
+ reportOnly: raw["reportOnly"] === true
14663
+ };
14664
+ }
14665
+ function clamp(n, lo, hi) {
14666
+ return Math.min(hi, Math.max(lo, n));
14667
+ }
14668
+ function buildPlan(i, projectRoot) {
14669
+ const plan = [];
14670
+ for (const target of i.targets) {
14671
+ const abs = isAbsolute3(target) ? target : join16(projectRoot ?? process.cwd(), target);
14672
+ let source;
14673
+ try {
14674
+ source = readFileSync13(abs, "utf8");
14675
+ } catch {
14676
+ continue;
14677
+ }
14678
+ plan.push(...planMutations(target, source, { maxPerFile: i.maxPerFile ?? DEFAULT_MAX_PER_FILE }));
14679
+ }
14680
+ return plan;
14681
+ }
14682
+ function makeChaosConfig(roster, worktree) {
14683
+ const base = roster?.[CHAOS_ROLE] ?? getAgentDefinition(CHAOS_ROLE)?.config ?? { name: "Chaos Monkey", role: CHAOS_ROLE };
14684
+ return { ...instantiateRosterConfig2(CHAOS_ROLE, base), worktree };
14685
+ }
14686
+ function buildChaosTask(plan, i, pass, priorSurvivors) {
14687
+ const mutants = plan.map(
14688
+ (m) => `- ${m.id} | ${m.file}:${m.line}:${m.column} | ${m.kind} | "${m.original}" -> "${m.replacement}"`
14689
+ ).join("\n");
14690
+ const prior = priorSurvivors.length > 0 ? `
14691
+ These mutants survived a previous pass (pass ${pass - 1}) \u2014 re-verify them against the STRENGTHENED tests:
14692
+ ${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join("\n")}` : "";
14693
+ return [
14694
+ "Execute this deterministic mutation plan against the current checkout.",
14695
+ "",
14696
+ "For each mutant, in order:",
14697
+ "1. Apply ONLY that mutation at its exact (file, line, column).",
14698
+ `2. Run the test command: ${i.testCommand}${i.cwd ? ` (cwd: ${i.cwd})` : ""}`,
14699
+ "3. Record killed (tests failed \u2014 quote first failing assertion) or survived (suite green).",
14700
+ "4. Restore the file byte-for-byte before the next mutant.",
14701
+ "",
14702
+ "Mutants:",
14703
+ mutants,
14704
+ prior,
14705
+ "",
14706
+ "Rules: one mutation at a time; never stack; if the anchored token no longer matches, mark skipped with the drift as evidence; do not fix or refactor anything; stay inside the plan.",
14707
+ "Finish with submit_result, then repeat the same JSON as your final text."
14708
+ ].join("\n");
14709
+ }
14710
+ function buildStrengthenTask(survivors, i, attempt) {
14711
+ return [
14712
+ `Strengthen the tests so these SURVIVING mutants die (attempt ${attempt}).`,
14713
+ "",
14714
+ "Each survivor below was a deliberate sabotage of production code that the current suite did NOT catch:",
14715
+ ...survivors.map((s) => `- ${s.id} | ${s.file}:${s.line} | ${s.kind}${s.evidence ? ` | ${s.evidence}` : ""}`),
14716
+ "",
14717
+ `Test command that must fail under each mutant: ${i.testCommand}`,
14718
+ "",
14719
+ "For each survivor add or tighten exactly one assertion that pins the sabotaged boundary/behavior. Do not change production code. Do not weaken other tests. Run the suite green on clean code before finishing."
14720
+ ].join("\n");
14721
+ }
14722
+ function collectOutcomes(result, plan) {
14723
+ const fromText = parseTextOutcomes(result);
14724
+ if (fromText.length > 0) {
14725
+ const planned = new Set(plan.map((p) => p.id));
14726
+ const matched = fromText.filter((m) => planned.has(m.id));
14727
+ if (matched.length > 0) return matched;
14728
+ }
14729
+ return plan.map((p) => ({
14730
+ id: p.id,
14731
+ file: p.file,
14732
+ line: p.line,
14733
+ kind: p.kind,
14734
+ status: "skipped",
14735
+ evidence: result ? `chaos task ended ${result.status}` : "chaos task produced no result"
14736
+ }));
14737
+ }
14738
+ function parseTextOutcomes(result) {
14739
+ const text = typeof result?.result === "string" ? result.result : void 0;
14740
+ if (!text) return [];
14741
+ const parsed = parseMutationReport(text);
14742
+ if (!parsed) return [];
14743
+ return parsed.mutants.map((m) => ({
14744
+ id: m.id,
14745
+ file: m.file,
14746
+ line: m.line,
14747
+ kind: m.kind,
14748
+ status: m.status,
14749
+ evidence: m.evidence
14750
+ }));
14751
+ }
14752
+
13601
14753
  // src/coordination/director-tools.ts
13602
14754
  function makeSpawnTool(director, roster) {
13603
14755
  const dispatchCatalog = () => {
@@ -13890,7 +15042,7 @@ function makeKanbanQueueTool(director, roster) {
13890
15042
  try {
13891
15043
  const config = buildKanbanSubagentConfig(claim.task, i, roster, instantiateRosterConfig2);
13892
15044
  subagentId = await director.spawn(config);
13893
- const dispatchTaskId = randomUUID10();
15045
+ const dispatchTaskId = randomUUID12();
13894
15046
  const taskSpec = {
13895
15047
  id: dispatchTaskId,
13896
15048
  subagentId,
@@ -14166,6 +15318,7 @@ function buildDirectorToolset(director, roster) {
14166
15318
  makeAskResultTool(director),
14167
15319
  makeRollUpTool(director),
14168
15320
  makeQualityGateTool(director, roster),
15321
+ makeMutationTestTool(director, roster),
14169
15322
  makeTerminateTool(director),
14170
15323
  makeTerminateAllTool(director),
14171
15324
  makeFleetTool(director),
@@ -14252,7 +15405,7 @@ import * as fsp24 from "node:fs/promises";
14252
15405
  import * as path32 from "node:path";
14253
15406
 
14254
15407
  // src/storage/session-store.ts
14255
- import { randomUUID as randomUUID12 } from "node:crypto";
15408
+ import { randomUUID as randomUUID14 } from "node:crypto";
14256
15409
  import * as fsp23 from "node:fs/promises";
14257
15410
  import * as path31 from "node:path";
14258
15411
 
@@ -15976,7 +17129,7 @@ var FileSessionWriter = class _FileSessionWriter {
15976
17129
 
15977
17130
  // src/storage/session-checkpoint-cas.ts
15978
17131
  import { spawn as spawn3 } from "node:child_process";
15979
- import { createHash as createHash3, randomUUID as randomUUID11 } from "node:crypto";
17132
+ import { createHash as createHash3, randomUUID as randomUUID13 } from "node:crypto";
15980
17133
  import * as fsp10 from "node:fs/promises";
15981
17134
  import * as path23 from "node:path";
15982
17135
 
@@ -16234,7 +17387,7 @@ var SessionCheckpointCas = class {
16234
17387
  }
16235
17388
  const temp = path23.join(
16236
17389
  path23.dirname(target),
16237
- `.${path23.basename(target)}.${process.pid}.${randomUUID11()}.tmp`
17390
+ `.${path23.basename(target)}.${process.pid}.${randomUUID13()}.tmp`
16238
17391
  );
16239
17392
  let handle;
16240
17393
  try {
@@ -18030,7 +19183,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
18030
19183
  onAppend;
18031
19184
  onAppendBatch;
18032
19185
  catalogClient;
18033
- maintenanceHolderId = randomUUID12();
19186
+ maintenanceHolderId = randomUUID14();
18034
19187
  _loadCache = /* @__PURE__ */ new Map();
18035
19188
  loadCache = new SessionLoadCache(this._loadCache);
18036
19189
  _indexCache = null;
@@ -20237,7 +21390,7 @@ function hashStr(s) {
20237
21390
  }
20238
21391
 
20239
21392
  // src/coordination/multi-agent-coordinator.ts
20240
- import { randomUUID as randomUUID13 } from "node:crypto";
21393
+ import { randomUUID as randomUUID15 } from "node:crypto";
20241
21394
  import { EventEmitter as EventEmitter2 } from "node:events";
20242
21395
 
20243
21396
  // src/coordination/coordinator/error-classifier.ts
@@ -20328,7 +21481,8 @@ async function executeSubagentWithTimeout({
20328
21481
  budget,
20329
21482
  preemptFraction = TIMEOUT_PREEMPT_FRACTION,
20330
21483
  abortSubagent,
20331
- currentSessionId
21484
+ currentSessionId,
21485
+ gracefulFinish
20332
21486
  }) {
20333
21487
  const initialTimeoutMs = budget.limits.timeoutMs;
20334
21488
  const idleLimitMs = budget.limits.idleTimeoutMs;
@@ -20359,9 +21513,17 @@ async function executeSubagentWithTimeout({
20359
21513
  const scheduleNext = () => {
20360
21514
  const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
20361
21515
  const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
20362
- const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
20363
- const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
20364
- armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
21516
+ const idleRemaining = idleLimitMs === void 0 || gracefulFinish !== void 0 && initialTimeoutMs !== void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
21517
+ const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit || gracefulFinish !== void 0 ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
21518
+ const next = Math.min(wallRemaining, idleRemaining, preemptRemaining);
21519
+ if (!Number.isFinite(next)) {
21520
+ if (timer) {
21521
+ clearTimeout(timer);
21522
+ timer = null;
21523
+ }
21524
+ return;
21525
+ }
21526
+ armFor(Math.max(25, next));
20365
21527
  };
20366
21528
  const negotiateTimeout = async (used, limit) => {
20367
21529
  const handler = budget.onThreshold;
@@ -20410,6 +21572,10 @@ async function executeSubagentWithTimeout({
20410
21572
  const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
20411
21573
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
20412
21574
  if (idleExceeded && !wallExceeded) {
21575
+ if (gracefulFinish !== void 0 && initialTimeoutMs !== void 0) {
21576
+ scheduleNext();
21577
+ return;
21578
+ }
20413
21579
  const sessionId = currentSessionId();
20414
21580
  budget._events?.emit("budget.threshold_reached", {
20415
21581
  ...sessionId ? { sessionId } : {},
@@ -20426,7 +21592,7 @@ async function executeSubagentWithTimeout({
20426
21592
  reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
20427
21593
  return;
20428
21594
  }
20429
- if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
21595
+ if (wallLimit !== void 0 && !wallExceeded && gracefulFinish === void 0 && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
20430
21596
  const activityTs = Date.now() - budget.idleMs();
20431
21597
  if (activityTs <= lastGrantActivityTs) {
20432
21598
  preemptState = "locked" /* LOCKED */;
@@ -20460,6 +21626,22 @@ async function executeSubagentWithTimeout({
20460
21626
  return;
20461
21627
  }
20462
21628
  const limit = wallLimit ?? 0;
21629
+ if (gracefulFinish !== void 0) {
21630
+ if (!budget.graceGranted) {
21631
+ const reason = `wall-clock budget of ${Math.round(limit / 1e3)}s reached`;
21632
+ if (budget.notifyFinish(reason, { graceMs: gracefulFinish.graceMs })) {
21633
+ scheduleNext();
21634
+ return;
21635
+ }
21636
+ abortSubagent(ctx.subagentId);
21637
+ reject(new BudgetExceededError("timeout", limit, elapsed));
21638
+ return;
21639
+ } else {
21640
+ abortSubagent(ctx.subagentId);
21641
+ reject(new BudgetExceededError("timeout", limit, elapsed));
21642
+ return;
21643
+ }
21644
+ }
20463
21645
  if (!budget.onThreshold) {
20464
21646
  abortSubagent(ctx.subagentId);
20465
21647
  reject(new BudgetExceededError("timeout", limit, elapsed));
@@ -20607,7 +21789,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
20607
21789
  return { ...subagent, name: display };
20608
21790
  }
20609
21791
  async spawn(subagent) {
20610
- const id = subagent.id || randomUUID13();
21792
+ const id = subagent.id || randomUUID15();
20611
21793
  const cfg = this.withNickname(subagent, id);
20612
21794
  if (this.subagents.has(id)) {
20613
21795
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -20844,6 +22026,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
20844
22026
  completeTask(result) {
20845
22027
  this.recordCompletion(result);
20846
22028
  }
22029
+ /**
22030
+ * Ask every RUNNING subagent that opted into `gracefulFinish` to finish its
22031
+ * task in its own turn (see coordination/subagent-finish.ts). This is the
22032
+ * leader-side entry point for "the leader agent has finished": it delivers
22033
+ * an in-band notification between tool batches — never an interrupt, never
22034
+ * an abort. Each notified subagent keeps its existing time budget and
22035
+ * accelerates; the watchdog still bounds the maximum lifetime.
22036
+ *
22037
+ * Subagents without the policy opted in are deliberately untouched — their
22038
+ * lifecycle remains the legacy watchdog contract.
22039
+ *
22040
+ * Returns the number of subagents actually notified.
22041
+ */
22042
+ requestFinish(reason) {
22043
+ let notified = 0;
22044
+ for (const subagent of this.subagents.values()) {
22045
+ if (subagent.status !== "running") continue;
22046
+ if (!resolveGracefulFinish(subagent.config)) continue;
22047
+ const budget = subagent.activeBudget;
22048
+ if (!budget) continue;
22049
+ const usage = budget.usage();
22050
+ if (usage.iterations === 0 && usage.toolCalls === 0) continue;
22051
+ if (budget.notifyFinish(reason)) notified++;
22052
+ }
22053
+ return notified;
22054
+ }
20847
22055
  // --- internal dispatching ---------------------------------------------
20848
22056
  tryDispatchNext() {
20849
22057
  while (this.canDispatch()) {
@@ -21017,7 +22225,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
21017
22225
  idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
21018
22226
  },
21019
22227
  "auto",
21020
- { sessionId: () => this.currentSessionId() }
22228
+ {
22229
+ sessionId: () => this.currentSessionId(),
22230
+ subagentId,
22231
+ // Graceful-finish runs own wall-clock enforcement to the watchdog so
22232
+ // the notify-then-bound lifecycle cannot be raced by tool.progress
22233
+ // heartbeats calling checkTimeout() (see subagent-budget.ts).
22234
+ ...resolveGracefulFinish(subagent.config) ? { wallClockWatchdogOwned: true } : {}
22235
+ }
21021
22236
  );
21022
22237
  subagent.activeBudget = budget;
21023
22238
  if (!this.runner) {
@@ -21050,7 +22265,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
21050
22265
  task,
21051
22266
  runCtx,
21052
22267
  budget,
21053
- subagent.config.preemptFraction
22268
+ subagent.config.preemptFraction,
22269
+ resolveGracefulFinish(subagent.config)
21054
22270
  );
21055
22271
  result = {
21056
22272
  subagentId,
@@ -21080,13 +22296,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
21080
22296
  }
21081
22297
  this.recordCompletion(result);
21082
22298
  }
21083
- async executeWithTimeout(runner, task, ctx, budget, preemptFraction) {
22299
+ async executeWithTimeout(runner, task, ctx, budget, preemptFraction, gracefulFinish) {
21084
22300
  return executeSubagentWithTimeout({
21085
22301
  runner,
21086
22302
  task,
21087
22303
  ctx,
21088
22304
  budget,
21089
22305
  preemptFraction,
22306
+ gracefulFinish,
21090
22307
  abortSubagent: (subagentId) => this.subagents.get(subagentId)?.abortController.abort(),
21091
22308
  currentSessionId: () => this.currentSessionId()
21092
22309
  });
@@ -21492,7 +22709,7 @@ var Director = class _Director {
21492
22709
  sessionProvider;
21493
22710
  sessionModel;
21494
22711
  constructor(opts) {
21495
- this.id = opts.config.coordinatorId || randomUUID14();
22712
+ this.id = opts.config.coordinatorId || randomUUID16();
21496
22713
  this.manifestPath = opts.manifestPath;
21497
22714
  this.roster = opts.roster;
21498
22715
  this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -21699,6 +22916,17 @@ var Director = class _Director {
21699
22916
  isWorkComplete() {
21700
22917
  return this.workCompleteFlag;
21701
22918
  }
22919
+ /**
22920
+ * Ask every running background subagent that opted into `gracefulFinish`
22921
+ * to finish its task in its own turn. In-band notification between tool
22922
+ * batches — no interrupt, no abort; each subagent keeps its time budget and
22923
+ * accelerates. Session shutdown calls this before draining Chimera work so
22924
+ * the post-session reviewer is nudged to complete rather than killed.
22925
+ * Returns the number of subagents notified.
22926
+ */
22927
+ requestFinish(reason) {
22928
+ return this.coordinator.requestFinish(reason);
22929
+ }
21702
22930
  setLeaderBtwNote(note) {
21703
22931
  return this.btwNotes.add(note);
21704
22932
  }
@@ -21775,7 +23003,7 @@ var Director = class _Director {
21775
23003
  );
21776
23004
  }
21777
23005
  const msg = {
21778
- id: randomUUID14(),
23006
+ id: randomUUID16(),
21779
23007
  type: "task",
21780
23008
  from: this.id,
21781
23009
  to: subagentId,
@@ -22029,7 +23257,7 @@ var Director = class _Director {
22029
23257
  };
22030
23258
 
22031
23259
  // src/coordination/fleet-manager.ts
22032
- import { randomUUID as randomUUID15 } from "node:crypto";
23260
+ import { randomUUID as randomUUID17 } from "node:crypto";
22033
23261
  import * as fsp26 from "node:fs/promises";
22034
23262
  import * as path33 from "node:path";
22035
23263
  var FleetManager = class {
@@ -22095,7 +23323,7 @@ var FleetManager = class {
22095
23323
  maxContext;
22096
23324
  constructor(opts = {}) {
22097
23325
  this.manifestPath = opts.manifestPath;
22098
- this.directorRunId = opts.directorRunId ?? randomUUID15();
23326
+ this.directorRunId = opts.directorRunId ?? randomUUID17();
22099
23327
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
22100
23328
  this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
22101
23329
  this.spawnDepth = opts.spawnDepth ?? 0;
@@ -23004,235 +24232,6 @@ var MailboxProjectServerConnection = class {
23004
24232
  }
23005
24233
  };
23006
24234
 
23007
- // src/coordination/mailbox-type-properties.ts
23008
- var MAILBOX_TYPE_PROPERTIES = {
23009
- note: {
23010
- category: "informational",
23011
- expectsReply: false,
23012
- requiresAction: false,
23013
- backgroundEligible: false,
23014
- outOfBand: false,
23015
- renderPriority: 20,
23016
- recipientObligation: "Read for context; no reply needed.",
23017
- senderGuidance: "General-purpose FYI. Use when no more specific type applies."
23018
- },
23019
- ask: {
23020
- category: "actionable",
23021
- expectsReply: true,
23022
- requiresAction: true,
23023
- backgroundEligible: true,
23024
- outOfBand: false,
23025
- renderPriority: 10,
23026
- recipientObligation: "Answer as soon as possible \u2014 the sender is waiting.",
23027
- senderGuidance: "Blocking question. Only use when you need an answer to proceed."
23028
- },
23029
- assign: {
23030
- category: "actionable",
23031
- expectsReply: false,
23032
- requiresAction: true,
23033
- backgroundEligible: true,
23034
- outOfBand: false,
23035
- renderPriority: 10,
23036
- recipientObligation: "Accept or decline; act on it when current operation allows.",
23037
- senderGuidance: 'Task delegation. Must be directed to a specific recipient (not "*").'
23038
- },
23039
- steer: {
23040
- category: "actionable",
23041
- expectsReply: false,
23042
- requiresAction: true,
23043
- backgroundEligible: true,
23044
- outOfBand: false,
23045
- renderPriority: 0,
23046
- // Always rendered first
23047
- recipientObligation: "Pause current approach, adjust per instruction, then resume.",
23048
- senderGuidance: "Mid-task direction change. The recipient is already working on something."
23049
- },
23050
- btw: {
23051
- category: "informational",
23052
- expectsReply: false,
23053
- requiresAction: false,
23054
- backgroundEligible: false,
23055
- outOfBand: false,
23056
- renderPriority: 30,
23057
- recipientObligation: "Absorb the information and stay on current task; no reply needed.",
23058
- senderGuidance: "Low-priority aside. Non-urgent info that can wait."
23059
- },
23060
- broadcast: {
23061
- category: "routing",
23062
- expectsReply: false,
23063
- requiresAction: false,
23064
- backgroundEligible: false,
23065
- outOfBand: false,
23066
- renderPriority: 20,
23067
- recipientObligation: 'Read if addressed to you (direct recipient, alias, or "*").',
23068
- senderGuidance: 'Multi-recipient envelope. Auto-selected when to is "*" or "@session".'
23069
- },
23070
- status: {
23071
- category: "informational",
23072
- expectsReply: false,
23073
- requiresAction: false,
23074
- backgroundEligible: false,
23075
- outOfBand: false,
23076
- renderPriority: 40,
23077
- recipientObligation: "Use to avoid redundant work; never act on as a task or question.",
23078
- senderGuidance: "Agent/system status update. Machine-generated, not for human-originated messages."
23079
- },
23080
- result: {
23081
- category: "informational",
23082
- expectsReply: false,
23083
- requiresAction: false,
23084
- backgroundEligible: true,
23085
- outOfBand: false,
23086
- renderPriority: 10,
23087
- recipientObligation: "Factor into next decision; treat as evidence, not a new task.",
23088
- senderGuidance: "Task completion notice. Share the outcome of finished work."
23089
- },
23090
- review: {
23091
- category: "actionable",
23092
- expectsReply: false,
23093
- requiresAction: true,
23094
- backgroundEligible: true,
23095
- outOfBand: false,
23096
- renderPriority: 10,
23097
- recipientObligation: "Inspect when convenient; no immediate reply required.",
23098
- senderGuidance: "Passive review request (code/doc/PR). No reply required."
23099
- },
23100
- control: {
23101
- category: "control_signal",
23102
- expectsReply: false,
23103
- requiresAction: false,
23104
- backgroundEligible: false,
23105
- outOfBand: true,
23106
- renderPriority: 999,
23107
- // Never rendered
23108
- recipientObligation: 'Handled by the agent loop, NOT folded into conversation. "interrupt" causes cooperative halt.',
23109
- senderGuidance: "RESERVED for runtime use. Agents must NOT send control messages."
23110
- }
23111
- };
23112
-
23113
- // src/coordination/mailbox-auth-types.ts
23114
- var MAILBOX_CAPABILITY_IMPLICATIONS = {
23115
- "mail.read.all": ["mail.read.self"],
23116
- "mail.events.all": ["mail.events.self"],
23117
- "mail.send.directive": ["mail.send.actionable", "mail.send.informational"],
23118
- "mail.send.actionable": ["mail.send.informational"],
23119
- // Leaf capabilities imply nothing further.
23120
- "mail.send.informational": [],
23121
- "mail.read.self": [],
23122
- "mail.ack.self": [],
23123
- "mail.events.self": [],
23124
- "mail.presence.register.self": [],
23125
- "mail.presence.heartbeat.self": [],
23126
- "mail.presence.deregister.self": [],
23127
- "mail.presence.read": [],
23128
- "mail.retention.purge": [],
23129
- "mail.retention.clear": [],
23130
- "mail.admin.receipts": []
23131
- };
23132
- function expandMailboxCapabilities(caps) {
23133
- const result = /* @__PURE__ */ new Set();
23134
- const queue = [...caps];
23135
- while (queue.length > 0) {
23136
- const cap = queue.pop();
23137
- if (result.has(cap)) continue;
23138
- result.add(cap);
23139
- const implied = MAILBOX_CAPABILITY_IMPLICATIONS[cap];
23140
- if (implied) queue.push(...implied);
23141
- }
23142
- return result;
23143
- }
23144
- function hasMailboxCapability(actor, cap) {
23145
- if (actor.capabilities.has(cap)) return true;
23146
- const expanded = expandMailboxCapabilities(actor.capabilities);
23147
- return expanded.has(cap);
23148
- }
23149
-
23150
- // src/coordination/mailbox-predicates.ts
23151
- function mailboxIdentityBase(agentId) {
23152
- return agentId.split(/[@#]/, 1)[0].trim().toLowerCase();
23153
- }
23154
- function isMailboxLeader(agentId, role) {
23155
- return mailboxIdentityBase(agentId) === "leader" || role?.trim().toLowerCase() === "leader";
23156
- }
23157
- function isMailboxSenderInFamily(senderId, family) {
23158
- const base = mailboxIdentityBase(senderId);
23159
- const normalizedFamily = family.trim().toLowerCase();
23160
- if (normalizedFamily.length === 0) return false;
23161
- return base === normalizedFamily || base.startsWith(`${normalizedFamily}-`);
23162
- }
23163
- function isMailboxMessageVisibleTo(message, agentId, role) {
23164
- return message.audience !== "leaders" || isMailboxLeader(agentId, role);
23165
- }
23166
- function validateSendType(type, to) {
23167
- if (type === "control") {
23168
- throw new TypeError('Type "control" is reserved for runtime use and cannot be set by agents');
23169
- }
23170
- const isMultiRecipient = to === "*" || to.startsWith("@session:");
23171
- if (type === "assign" && isMultiRecipient) {
23172
- throw new TypeError(
23173
- `Type "assign" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
23174
- );
23175
- }
23176
- if (type === "steer" && isMultiRecipient) {
23177
- throw new TypeError(
23178
- `Type "steer" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
23179
- );
23180
- }
23181
- }
23182
- var SESSION_RECIPIENT_PREFIX = "@session:";
23183
- function sessionRecipient(sessionId) {
23184
- const normalizedSessionId = sessionId.trim();
23185
- if (!normalizedSessionId) {
23186
- throw new TypeError('sessionId is required for the "@session" recipient');
23187
- }
23188
- return `${SESSION_RECIPIENT_PREFIX}${normalizedSessionId}`;
23189
- }
23190
- function normalizeRecipient(to, sessionId) {
23191
- const trimmed = to.trim();
23192
- const normalized = trimmed.toLowerCase();
23193
- if (normalized === "all") return "*";
23194
- if (normalized === "@session") return sessionRecipient(sessionId ?? "");
23195
- return trimmed;
23196
- }
23197
- function isActionRequiredForActor(message, projection) {
23198
- if (projection.legacyGlobalCompletion) return false;
23199
- if (message.deletedAt !== void 0) return false;
23200
- if (projection.completedByMe) return false;
23201
- return MAILBOX_TYPE_PROPERTIES[message.type]?.requiresAction === true;
23202
- }
23203
-
23204
- // src/coordination/mailbox-session-sync.ts
23205
- function isAffectedBySessionAffinity(message) {
23206
- return message.sessionAffinity !== void 0;
23207
- }
23208
- async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
23209
- if (!isAffectedBySessionAffinity(message)) return true;
23210
- const affinity = message.sessionAffinity;
23211
- if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
23212
- return false;
23213
- }
23214
- if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
23215
- return false;
23216
- }
23217
- if (!currentSessionId) {
23218
- return ctx?.allowUnscoped === true;
23219
- }
23220
- if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
23221
- if (affinity.sessionId !== currentSessionId) return false;
23222
- return true;
23223
- }
23224
- if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
23225
- try {
23226
- const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
23227
- if (resolved === currentSessionId) return true;
23228
- if (resolved !== void 0) return false;
23229
- } catch {
23230
- }
23231
- }
23232
- if (ctx?.allowUnscoped === true) return true;
23233
- return false;
23234
- }
23235
-
23236
24235
  // src/coordination/mailbox-message-codec.ts
23237
24236
  function resolveSendType(type, to) {
23238
24237
  const normalizedTo = normalizeRecipient(to);
@@ -24129,7 +25128,7 @@ function makeFleetStatusTool(opts = {}) {
24129
25128
  }
24130
25129
 
24131
25130
  // src/coordination/fleet-supervisor.ts
24132
- import { randomUUID as randomUUID16 } from "node:crypto";
25131
+ import { randomUUID as randomUUID18 } from "node:crypto";
24133
25132
  var COLLAB_ID_PREFIXES2 = ["bug-hunter-", "refactor-planner-", "critic-"];
24134
25133
  var DEFAULTS = {
24135
25134
  intervalMs: 2e4,
@@ -24407,7 +25406,7 @@ var FleetSupervisor = class {
24407
25406
  */
24408
25407
  async decide(question, context, options, risk) {
24409
25408
  const request = {
24410
- id: `fleetsup-${randomUUID16()}`,
25409
+ id: `fleetsup-${randomUUID18()}`,
24411
25410
  sessionId: this.opts.sessionId?.(),
24412
25411
  source: "system",
24413
25412
  question,
@@ -24767,6 +25766,22 @@ var SEND_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
24767
25766
  // receiver trusts the sender-asserted `sessionId`; the boundary must
24768
25767
  // refuse the field entirely.
24769
25768
  ]);
25769
+ var SEND_FORBIDDEN_FIELDS = /* @__PURE__ */ new Set([
25770
+ "from",
25771
+ "sessionAffinity"
25772
+ ]);
25773
+ function filterMailboxSendPayload(input) {
25774
+ const payload = {};
25775
+ const stripped = [];
25776
+ for (const key of Object.keys(input)) {
25777
+ if (SEND_ALLOWED_FIELDS.has(key) || SEND_FORBIDDEN_FIELDS.has(key)) {
25778
+ payload[key] = input[key];
25779
+ } else {
25780
+ stripped.push(key);
25781
+ }
25782
+ }
25783
+ return { payload, stripped };
25784
+ }
24770
25785
  var ACK_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
24771
25786
  "messageId",
24772
25787
  "read",
@@ -25004,7 +26019,9 @@ function makeMailSendTool(opts = {}) {
25004
26019
  required: ["to", "subject", "body"]
25005
26020
  },
25006
26021
  async execute(input, ctx) {
25007
- const i = input ?? {};
26022
+ const { payload: i, stripped } = filterMailboxSendPayload(
26023
+ input ?? {}
26024
+ );
25008
26025
  const rawTo = i.to;
25009
26026
  const subject = i.subject;
25010
26027
  const body = i.body;
@@ -25027,15 +26044,13 @@ function makeMailSendTool(opts = {}) {
25027
26044
  recipientAliases: /* @__PURE__ */ new Set([codecIdentity.baseId]),
25028
26045
  sessionId: codecIdentity.sessionId
25029
26046
  };
26047
+ let parsed;
25030
26048
  try {
25031
- parseMailboxSendInput(i, codecActor);
26049
+ parsed = parseMailboxSendInput(i, codecActor);
25032
26050
  } catch (err) {
25033
26051
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
25034
26052
  }
25035
- const audience = i.audience;
25036
- if (audience !== void 0 && audience !== "all" && audience !== "leaders") {
25037
- return { ok: false, error: '"audience" must be "all" or "leaders".' };
25038
- }
26053
+ const audience = parsed.audience;
25039
26054
  const mb = resolveMailbox(ctx);
25040
26055
  const identity = await register(mb, ctx);
25041
26056
  const requestedTo = normalizeRecipient(rawTo, identity.sessionId);
@@ -25049,10 +26064,10 @@ function makeMailSendTool(opts = {}) {
25049
26064
  to: delivery.to,
25050
26065
  type: resolvedType,
25051
26066
  audience: delivery.audience,
25052
- subject,
25053
- body,
25054
- priority: i.priority ?? "normal",
25055
- replyTo: i.replyTo,
26067
+ subject: parsed.subject,
26068
+ body: parsed.body,
26069
+ priority: parsed.priority,
26070
+ replyTo: parsed.replyTo,
25056
26071
  senderSessionId: identity.sessionId
25057
26072
  });
25058
26073
  return {
@@ -25060,7 +26075,9 @@ function makeMailSendTool(opts = {}) {
25060
26075
  messageId: msg.id,
25061
26076
  from: identity.callerId,
25062
26077
  to: msg.to,
25063
- summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity.callerId}.`
26078
+ // Surfacing what was stripped keeps the send auditable without
26079
+ // re-introducing the clutter into the payload itself.
26080
+ ...stripped.length > 0 ? { strippedFields: stripped, summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity.callerId}. Ignored ${stripped.length} unrecognized field(s): ${stripped.join(", ")}.` } : { summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity.callerId}.` }
25064
26081
  };
25065
26082
  }
25066
26083
  };
@@ -28582,7 +29599,7 @@ function createAgentMonitorService(opts) {
28582
29599
  }
28583
29600
 
28584
29601
  // src/coordination/autonomous-brain.ts
28585
- import { randomUUID as randomUUID17 } from "node:crypto";
29602
+ import { randomUUID as randomUUID19 } from "node:crypto";
28586
29603
  var AutonomousBrain = class {
28587
29604
  graph;
28588
29605
  // Fleet bus for emitting decisions — null-safe, no-op if not provided
@@ -28688,7 +29705,7 @@ var AutonomousBrain = class {
28688
29705
  consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
28689
29706
  }));
28690
29707
  return this.decideAuto({
28691
- id: randomUUID17(),
29708
+ id: randomUUID19(),
28692
29709
  source,
28693
29710
  decisionType: "spawn",
28694
29711
  question: `Should we spawn a subagent for this task?`,
@@ -28731,7 +29748,7 @@ var AutonomousBrain = class {
28731
29748
  }
28732
29749
  ];
28733
29750
  return this.decideAuto({
28734
- id: randomUUID17(),
29751
+ id: randomUUID19(),
28735
29752
  source,
28736
29753
  decisionType: "approve_change",
28737
29754
  question: `Should we approve the change "${change.title}"?`,
@@ -28790,7 +29807,7 @@ var AutonomousBrain = class {
28790
29807
  consequence: "Break the task into smaller sub-tasks"
28791
29808
  });
28792
29809
  return this.decideAuto({
28793
- id: randomUUID17(),
29810
+ id: randomUUID19(),
28794
29811
  source,
28795
29812
  decisionType: "escalate_task",
28796
29813
  question: `Task failed: ${error.slice(0, 100)}. How should we proceed?`,
@@ -28924,10 +29941,10 @@ ${ctx.error}`);
28924
29941
  };
28925
29942
 
28926
29943
  // src/coordination/autonomous-coordinator.ts
28927
- import { randomUUID as randomUUID20 } from "node:crypto";
29944
+ import { randomUUID as randomUUID22 } from "node:crypto";
28928
29945
 
28929
29946
  // src/coordination/knowledge-graph.ts
28930
- import { randomUUID as randomUUID18 } from "node:crypto";
29947
+ import { randomUUID as randomUUID20 } from "node:crypto";
28931
29948
  import * as fsp28 from "node:fs/promises";
28932
29949
  import * as path41 from "node:path";
28933
29950
  var DEFAULT_MAX_NODES = 2e3;
@@ -28975,7 +29992,7 @@ var KnowledgeGraph = class _KnowledgeGraph {
28975
29992
  * Returns the node with its assigned id.
28976
29993
  */
28977
29994
  async add(node) {
28978
- const full = { id: randomUUID18(), ...node };
29995
+ const full = { id: randomUUID20(), ...node };
28979
29996
  this.nodes.set(full.id, full);
28980
29997
  this._trackSeq(full.id);
28981
29998
  this._addToIndex(full, this._indexKeys(full));
@@ -29104,8 +30121,8 @@ var KnowledgeGraph = class _KnowledgeGraph {
29104
30121
  if (this.subs.size >= MAX_SUBSCRIPTIONS) {
29105
30122
  throw new Error(`Knowledge graph subscription limit reached (${MAX_SUBSCRIPTIONS})`);
29106
30123
  }
29107
- const channel = randomUUID18();
29108
- const sub = { id: randomUUID18(), agentId, filter, channel };
30124
+ const channel = randomUUID20();
30125
+ const sub = { id: randomUUID20(), agentId, filter, channel };
29109
30126
  this.subs.set(channel, sub);
29110
30127
  this.pendingDeliveries.set(channel, []);
29111
30128
  return channel;
@@ -29586,7 +30603,7 @@ var TaskDAG = class {
29586
30603
  };
29587
30604
 
29588
30605
  // src/coordination/task-auctioneer.ts
29589
- import { randomUUID as randomUUID19 } from "node:crypto";
30606
+ import { randomUUID as randomUUID21 } from "node:crypto";
29590
30607
  function isTerminalGoalStatus(status) {
29591
30608
  return status === "done" || status === "failed";
29592
30609
  }
@@ -29709,7 +30726,7 @@ var TaskAuctioneer = class {
29709
30726
  const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
29710
30727
  if (score < this.minConfidence) return false;
29711
30728
  const bid = {
29712
- id: randomUUID19(),
30729
+ id: randomUUID21(),
29713
30730
  taskId,
29714
30731
  agentId: agent.agentId,
29715
30732
  agentName: agent.agentName,
@@ -30646,7 +31663,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
30646
31663
  break;
30647
31664
  }
30648
31665
  const decision = await this.brain.decideAuto({
30649
- id: randomUUID20(),
31666
+ id: randomUUID22(),
30650
31667
  source: "system",
30651
31668
  decisionType: "prioritize_goals",
30652
31669
  question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
@@ -31361,7 +32378,13 @@ export {
31361
32378
  DEFAULT_DIRECTOR_PREAMBLE,
31362
32379
  DEFAULT_DISPATCH_ROLE,
31363
32380
  DEFAULT_EAGER_SKILL_LIMIT,
32381
+ DEFAULT_EXPLORE_COMPANION_AGENT_ID,
32382
+ DEFAULT_EXPLORE_EDIT_TOOLS,
32383
+ DEFAULT_EXPLORE_SEARCH_TOOLS,
32384
+ DEFAULT_MAILBOX_POLL_INTERVAL_MS,
31364
32385
  DEFAULT_MAX_FLEET_SPAWNS,
32386
+ DEFAULT_MAX_PENDING_PROBES,
32387
+ DEFAULT_PROBE_COOLDOWN_MS,
31365
32388
  DEFAULT_QUALITY_CHECKS,
31366
32389
  DEFAULT_SUBAGENT_BASELINE,
31367
32390
  DELIVERY_AGENTS,
@@ -31375,6 +32398,7 @@ export {
31375
32398
  Director,
31376
32399
  DirectorAlertLevel,
31377
32400
  EscalationRoutingBrainArbiter,
32401
+ ExploreCompanion,
31378
32402
  FLEET_ROSTER,
31379
32403
  FLEET_ROSTER_BUDGETS,
31380
32404
  FLEET_ROSTER_WITHACP,
@@ -31453,6 +32477,7 @@ export {
31453
32477
  brainDecisionKey,
31454
32478
  buildConsolidationInstruction,
31455
32479
  buildDownAlert,
32480
+ buildProbeTaskText,
31456
32481
  buildProjectContextualizedPrompt,
31457
32482
  buildRecoveryAlert,
31458
32483
  buildSkillDistillInstruction,
@@ -31534,6 +32559,7 @@ export {
31534
32559
  makeMailInboxTool,
31535
32560
  makeMailSendTool,
31536
32561
  makeMailboxTool,
32562
+ makeMutationTestTool,
31537
32563
  makeQualityGateTool,
31538
32564
  makeRollUpTool,
31539
32565
  makeSpawnTool,