@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
package/dist/index.js CHANGED
@@ -1171,7 +1171,7 @@ __export(review_report_store_exports, {
1171
1171
  REPORT_STORE_FILE: () => REPORT_STORE_FILE,
1172
1172
  resolveReportStorePath: () => resolveReportStorePath
1173
1173
  });
1174
- import { randomUUID as randomUUID30 } from "node:crypto";
1174
+ import { randomUUID as randomUUID32 } from "node:crypto";
1175
1175
  import * as fsp35 from "node:fs/promises";
1176
1176
  import * as path76 from "node:path";
1177
1177
  function resolveReportStorePath(projectDir) {
@@ -1238,7 +1238,7 @@ var init_review_report_store = __esm({
1238
1238
  ...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
1239
1239
  };
1240
1240
  const createdEvent = {
1241
- id: randomUUID30(),
1241
+ id: randomUUID32(),
1242
1242
  reportId: report.id,
1243
1243
  eventType: "created",
1244
1244
  fromLifecycle: null,
@@ -1261,7 +1261,7 @@ var init_review_report_store = __esm({
1261
1261
  const from = this._materialize(entry).lifecycle;
1262
1262
  validateReportTransition(from, to);
1263
1263
  const event = {
1264
- id: randomUUID30(),
1264
+ id: randomUUID32(),
1265
1265
  reportId,
1266
1266
  eventType: reportEventTypeFor(to),
1267
1267
  fromLifecycle: from,
@@ -1304,7 +1304,7 @@ var init_review_report_store = __esm({
1304
1304
  if (!entry) throw new Error(`Review report not found: ${reportId}`);
1305
1305
  const materialized = this._materialize(entry);
1306
1306
  const event = {
1307
- id: randomUUID30(),
1307
+ id: randomUUID32(),
1308
1308
  reportId,
1309
1309
  eventType: "note_added",
1310
1310
  fromLifecycle: materialized.lifecycle,
@@ -1527,7 +1527,7 @@ __export(review_finding_store_exports, {
1527
1527
  JsonlFindingStore: () => JsonlFindingStore,
1528
1528
  resolveFindingStorePath: () => resolveFindingStorePath
1529
1529
  });
1530
- import { randomUUID as randomUUID40 } from "node:crypto";
1530
+ import { randomUUID as randomUUID42 } from "node:crypto";
1531
1531
  import * as fsp48 from "node:fs/promises";
1532
1532
  import * as path105 from "node:path";
1533
1533
  function resolveFindingStorePath(projectDir) {
@@ -1616,7 +1616,7 @@ var init_review_finding_store = __esm({
1616
1616
  validateTransition(from, to);
1617
1617
  validateResolution(to, opts?.outcome);
1618
1618
  const event = {
1619
- id: randomUUID40(),
1619
+ id: randomUUID42(),
1620
1620
  findingId,
1621
1621
  eventType: to === "resolved" ? "resolved" : to === "ignored" ? "ignored" : this._eventTypeFor(to),
1622
1622
  fromStatus: from,
@@ -1800,7 +1800,7 @@ var init_review_finding_store = __esm({
1800
1800
  }
1801
1801
  _makeEvent(findingId, eventType, fromStatus, toStatus, context) {
1802
1802
  return {
1803
- id: randomUUID40(),
1803
+ id: randomUUID42(),
1804
1804
  findingId,
1805
1805
  eventType,
1806
1806
  fromStatus,
@@ -19762,9 +19762,68 @@ function resourceId3(kind, value) {
19762
19762
  return `${kind}_${createHash17("sha256").update(value).digest("hex").slice(0, 24)}`;
19763
19763
  }
19764
19764
 
19765
+ // src/core/btw.ts
19766
+ var META_KEY2 = "_btwNotes";
19767
+ var MAX_PENDING = 20;
19768
+ function readQueue(ctx) {
19769
+ const raw = ctx.meta[META_KEY2];
19770
+ return Array.isArray(raw) ? raw : [];
19771
+ }
19772
+ function setBtwNote(ctx, text2) {
19773
+ const trimmed = text2.trim();
19774
+ if (!trimmed) return readQueue(ctx).length;
19775
+ const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
19776
+ ctx.meta[META_KEY2] = next;
19777
+ return next.length;
19778
+ }
19779
+ function pendingBtwCount(ctx) {
19780
+ return readQueue(ctx).length;
19781
+ }
19782
+ function consumeBtwNotes(ctx) {
19783
+ const notes = readQueue(ctx);
19784
+ if (notes.length > 0) delete ctx.meta[META_KEY2];
19785
+ return notes;
19786
+ }
19787
+ function buildBtwBlock(notes) {
19788
+ const body = notes.map((n) => `- ${n}`).join("\n");
19789
+ return [
19790
+ "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
19791
+ "your current task; do not restart from scratch unless it contradicts the",
19792
+ "goal:",
19793
+ "",
19794
+ body,
19795
+ "]"
19796
+ ].join("\n");
19797
+ }
19798
+
19765
19799
  // src/coordination/agent-subagent-runner.ts
19766
19800
  init_errors();
19767
19801
 
19802
+ // src/coordination/subagent-finish.ts
19803
+ var SUBAGENT_FINISH_REQUESTED_EVENT = "subagent.finish_requested";
19804
+ var DEFAULT_SUBAGENT_FINISH_GRACE_MS = 12e4;
19805
+ function resolveGracefulFinish(config) {
19806
+ const raw = config.gracefulFinish;
19807
+ if (raw === void 0 || raw === false) return void 0;
19808
+ if (raw === true) return { graceMs: DEFAULT_SUBAGENT_FINISH_GRACE_MS };
19809
+ const graceMs = typeof raw.graceMs === "number" && Number.isFinite(raw.graceMs) && raw.graceMs > 0 ? Math.floor(raw.graceMs) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
19810
+ return { graceMs };
19811
+ }
19812
+ function buildSubagentFinishNotice(input) {
19813
+ const localTime = new Date(input.deadlineMs).toISOString();
19814
+ const seconds = Math.max(1, Math.round(input.graceMs / 1e3));
19815
+ 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.`;
19816
+ return [
19817
+ "[SUBAGENT FINISH] The leader agent has finished its work.",
19818
+ `Reason: ${input.reason}`,
19819
+ timeLeft,
19820
+ "Finish your task now, in this turn: complete the thought you are working on, stop",
19821
+ "starting new tool calls unless one is strictly required to finish, and write your",
19822
+ "final answer or report as your final output, then end your turn.",
19823
+ "Do not restart the task and do not begin new work."
19824
+ ].join("\n");
19825
+ }
19826
+
19768
19827
  // src/coordination/subagent-budget.ts
19769
19828
  var TIMEOUT_PREEMPT_FRACTION = 0.85;
19770
19829
  var DECISION_TIMEOUT_MS = 6e4;
@@ -19822,6 +19881,82 @@ var SubagentBudget = class _SubagentBudget {
19822
19881
  this.limits.idleTimeoutMs = ext.idleTimeoutMs;
19823
19882
  }
19824
19883
  }
19884
+ /**
19885
+ * Graceful-finish state (see coordination/subagent-finish.ts).
19886
+ * `_finishNotified` guards the single in-band emission; `_grace` records a
19887
+ * granted working-time extension past the original wall-clock deadline.
19888
+ * They are separate because the two callers want different semantics:
19889
+ * the watchdog grants grace at the deadline crossing (notify + extend),
19890
+ * while an explicit leader-finished request only notifies — a subagent
19891
+ * well inside its budget keeps its full legitimate working time and simply
19892
+ * accelerates.
19893
+ */
19894
+ _finishNotified = false;
19895
+ _grace = null;
19896
+ /** True once the in-band finish notification has been emitted. */
19897
+ get finishNotified() {
19898
+ return this._finishNotified;
19899
+ }
19900
+ /** True once a grace window has been granted past the original deadline. */
19901
+ get graceGranted() {
19902
+ return this._grace !== null;
19903
+ }
19904
+ /**
19905
+ * Notify the subagent in-band to finish its task in its own turn:
19906
+ * `subagent.finish_requested` is emitted on the wired EventBus and the
19907
+ * agent loop folds the notice into the conversation between tool batches.
19908
+ * Nothing aborts — this is a notification, never an interrupt.
19909
+ *
19910
+ * `opts.graceMs` additionally extends the wall-clock ceiling by that window
19911
+ * (used by the watchdog at a deadline crossing, so the model gets working
19912
+ * time instead of a kill). Omit it to notify without touching the budget —
19913
+ * the subagent keeps its existing time budget and just accelerates.
19914
+ *
19915
+ * Returns `true` when this call did something (emitted the notification
19916
+ * and/or granted grace); `false` when there was nothing to do (already
19917
+ * notified, grace already granted, no EventBus wired, budget not started).
19918
+ */
19919
+ notifyFinish(reason, opts, now = Date.now) {
19920
+ if (!this._events) return false;
19921
+ if (this.startTime === null) return false;
19922
+ const shouldEmit = !this._finishNotified;
19923
+ const rawGrace = opts?.graceMs;
19924
+ const shouldGrant = rawGrace !== void 0 && this._grace === null;
19925
+ if (!shouldEmit && !shouldGrant) return false;
19926
+ let grantedGraceMs = 0;
19927
+ let graceDeadlineMs;
19928
+ if (shouldGrant && rawGrace !== void 0) {
19929
+ grantedGraceMs = Number.isFinite(rawGrace) && rawGrace > 0 ? Math.floor(rawGrace) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
19930
+ graceDeadlineMs = now() + grantedGraceMs;
19931
+ this._grace = { deadlineMs: graceDeadlineMs, graceMs: grantedGraceMs };
19932
+ this.patchLimits({ timeoutMs: graceDeadlineMs - this.startTime });
19933
+ }
19934
+ if (shouldEmit) {
19935
+ this._finishNotified = true;
19936
+ const effectiveDeadlineMs = graceDeadlineMs ?? (this.limits.timeoutMs !== void 0 ? this.startTime + this.limits.timeoutMs : now() + DEFAULT_SUBAGENT_FINISH_GRACE_MS);
19937
+ const effectiveGraceMs = Math.max(0, effectiveDeadlineMs - now());
19938
+ const subagentId = this._subagentId;
19939
+ this._events.emit(SUBAGENT_FINISH_REQUESTED_EVENT, {
19940
+ // Omitted entirely when the budget was built without an id — an
19941
+ // empty string is an address that matches nothing.
19942
+ ...subagentId !== void 0 ? { subagentId } : {},
19943
+ reason,
19944
+ deadlineMs: effectiveDeadlineMs,
19945
+ graceMs: effectiveGraceMs,
19946
+ notice: buildSubagentFinishNotice({
19947
+ reason,
19948
+ deadlineMs: effectiveDeadlineMs,
19949
+ graceMs: effectiveGraceMs
19950
+ })
19951
+ });
19952
+ }
19953
+ return true;
19954
+ }
19955
+ /** Epoch ms by which the subagent should have produced its final output,
19956
+ * once a grace window was granted. Undefined before that. */
19957
+ get finishDeadlineMs() {
19958
+ return this._grace?.deadlineMs;
19959
+ }
19825
19960
  iterations = 0;
19826
19961
  toolCalls = 0;
19827
19962
  tokenInput = 0;
@@ -19837,6 +19972,10 @@ var SubagentBudget = class _SubagentBudget {
19837
19972
  lastActivityTime = null;
19838
19973
  _onThreshold;
19839
19974
  _sessionId;
19975
+ /** Owning subagent id — used to address the graceful-finish event. */
19976
+ _subagentId;
19977
+ /** True when only the coordinator watchdog may enforce wall-clock limits. */
19978
+ _wallClockWatchdogOwned;
19840
19979
  /**
19841
19980
  * Hard cap on how long `_negotiateExtension` waits for the coordinator to
19842
19981
  * respond before defaulting to 'stop'. Without this fallback an absent
@@ -19908,6 +20047,8 @@ var SubagentBudget = class _SubagentBudget {
19908
20047
  constructor(limits = {}, mode = "auto", options = {}) {
19909
20048
  this._mode = mode;
19910
20049
  this._sessionId = options.sessionId;
20050
+ this._subagentId = options.subagentId;
20051
+ this._wallClockWatchdogOwned = options.wallClockWatchdogOwned === true;
19911
20052
  this.limits = { ...limits };
19912
20053
  }
19913
20054
  currentSessionId() {
@@ -19986,7 +20127,7 @@ var SubagentBudget = class _SubagentBudget {
19986
20127
  if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
19987
20128
  exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
19988
20129
  }
19989
- const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
20130
+ const wallOwnedByWatchdog = this._wallClockWatchdogOwned || this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
19990
20131
  if (this.limits.timeoutMs !== void 0 && elapsedMs2 > this.limits.timeoutMs && !wallOwnedByWatchdog) {
19991
20132
  exceeded.push({ kind: "timeout", used: elapsedMs2, limit: this.limits.timeoutMs });
19992
20133
  }
@@ -20185,7 +20326,7 @@ var SubagentBudget = class _SubagentBudget {
20185
20326
  if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
20186
20327
  const elapsed2 = Date.now() - this.startTime;
20187
20328
  const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
20188
- const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed2 > timeoutMs;
20329
+ const wallTripped = this._wallClockWatchdogOwned || wallSkipped ? false : timeoutMs !== void 0 && elapsed2 > timeoutMs;
20189
20330
  const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
20190
20331
  if (!wallTripped && !idleTripped) return;
20191
20332
  void this.checkLimits(elapsed2);
@@ -20666,6 +20807,14 @@ function makeAgentSubagentRunner(opts) {
20666
20807
  );
20667
20808
  const onParentAbort = () => aborter.abort();
20668
20809
  ctx.signal.addEventListener("abort", onParentAbort);
20810
+ if (resolveGracefulFinish(ctx.config)) {
20811
+ unsub.push(
20812
+ events.on("subagent.finish_requested", (e) => {
20813
+ if (e.subagentId && e.subagentId !== ctx.subagentId) return;
20814
+ setBtwNote(agent.ctx, e.notice);
20815
+ })
20816
+ );
20817
+ }
20669
20818
  let result;
20670
20819
  try {
20671
20820
  result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
@@ -21958,6 +22107,7 @@ function createProjectAgent(input, projectRoot) {
21958
22107
  var skillSet = (...names) => names;
21959
22108
  var ROLE_SKILL_SETS = {
21960
22109
  explore: skillSet("research-web", "node-modern", "typescript-strict"),
22110
+ "explore-companion": skillSet("node-modern", "typescript-strict"),
21961
22111
  search: skillSet("bug-hunter", "typescript-strict", "research-web"),
21962
22112
  research: skillSet("research-web", "tech-stack", "security-scanner", "api-design"),
21963
22113
  analyst: skillSet("sdd", "api-design", "testing", "security-scanner"),
@@ -29006,7 +29156,7 @@ function attachDepWatcherBridge(opts) {
29006
29156
  }
29007
29157
 
29008
29158
  // src/coordination/director.ts
29009
- import { randomUUID as randomUUID19 } from "node:crypto";
29159
+ import { randomUUID as randomUUID20 } from "node:crypto";
29010
29160
  import * as fsp30 from "node:fs/promises";
29011
29161
 
29012
29162
  // src/storage/director-state.ts
@@ -30901,7 +31051,7 @@ ${JSON.stringify(result.result, null, 2)}
30901
31051
  };
30902
31052
 
30903
31053
  // src/coordination/director-tools.ts
30904
- import { randomUUID as randomUUID14 } from "node:crypto";
31054
+ import { randomUUID as randomUUID15 } from "node:crypto";
30905
31055
  import {
30906
31056
  completeKanbanDispatch,
30907
31057
  failKanbanDispatch,
@@ -30996,7 +31146,7 @@ function buildKanbanFleetTaskPrompt(board, task, lease) {
30996
31146
  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})`);
30997
31147
  const checks = task.successCriteria?.map((check) => `- ${check.description}`).join("\n");
30998
31148
  const metrics = task.goalMetrics?.map(
30999
- (metric) => `- ${metric.name}: ${metric.current ?? "n/a"}${metric.target !== void 0 ? ` / ${metric.target}` : ""}${metric.unit ? ` ${metric.unit}` : ""} [${metric.status}]`
31149
+ (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}]`
31000
31150
  ).join("\n");
31001
31151
  const chain = task.chain ? [
31002
31152
  `chainId: ${task.chain.chainId}`,
@@ -32135,6 +32285,413 @@ function excerpt(text2, max) {
32135
32285
  ...(truncated)`;
32136
32286
  }
32137
32287
 
32288
+ // src/coordination/director-mutation-test-tool.ts
32289
+ import { randomUUID as randomUUID14 } from "node:crypto";
32290
+ import { readFileSync as readFileSync19 } from "node:fs";
32291
+ import { isAbsolute as isAbsolute9, join as join40 } from "node:path";
32292
+
32293
+ // src/coordination/mutation-engine.ts
32294
+ var TOKEN_PATTERNS = [
32295
+ {
32296
+ kind: "relax-boundary",
32297
+ // `>` not followed by `=` and not part of `=>` or `>>`; require code-ish
32298
+ // context on both sides so generic text (JSX, strings) is not touched.
32299
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>(?!=|>))/g,
32300
+ replace: () => ">="
32301
+ },
32302
+ {
32303
+ kind: "tighten-boundary",
32304
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>=)/g,
32305
+ replace: () => ">"
32306
+ },
32307
+ {
32308
+ kind: "arith-plus-to-minus",
32309
+ // `+` between operands (binary), not `++`, unary `+x`, or `+=`.
32310
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>\+(?!\+|=))/g,
32311
+ replace: () => "-"
32312
+ },
32313
+ {
32314
+ kind: "arith-minus-to-plus",
32315
+ // Binary `-` between operands, not `--`, `-=` or negative-number literal.
32316
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>-(?!-|=))/g,
32317
+ replace: () => "+"
32318
+ },
32319
+ {
32320
+ kind: "negate-boolean",
32321
+ // Standalone boolean literals used as values, not property names.
32322
+ regex: /(?<![.\w$])(?<op>true|false)(?![\w$])/g,
32323
+ replace: (m) => m === "true" ? "false" : "true"
32324
+ },
32325
+ {
32326
+ kind: "return-null",
32327
+ // `return <expr>;` where expr is not already null/undefined/void.
32328
+ regex: /(?<indent>\breturn\b)(?<expr>\s+[^;{}\n]+?)\s*;/g,
32329
+ replace: () => "return null;"
32330
+ }
32331
+ ];
32332
+ function planMutations(file, source, opts = {}) {
32333
+ const maxPerFile = opts.maxPerFile ?? 25;
32334
+ const out = [];
32335
+ const lines = source.split("\n");
32336
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
32337
+ const line = lines[lineIdx];
32338
+ const t2 = line.trim();
32339
+ if (t2.startsWith("//") || t2.startsWith("*") || t2.startsWith("/*")) continue;
32340
+ for (const pattern of TOKEN_PATTERNS) {
32341
+ pattern.regex.lastIndex = 0;
32342
+ let m;
32343
+ while ((m = pattern.regex.exec(line)) !== null) {
32344
+ const token = m.groups?.["op"] ?? m[0];
32345
+ const tokenStart = m.index + m[0].indexOf(token);
32346
+ if (isMasked(line, tokenStart, token.length)) continue;
32347
+ const original = line.slice(tokenStart, tokenStart + token.length);
32348
+ const replacement = pattern.replace(token);
32349
+ if (replacement === original) continue;
32350
+ out.push({
32351
+ id: `${pattern.kind}#${lineIdx + 1}#${tokenStart + 1}`,
32352
+ kind: pattern.kind,
32353
+ file,
32354
+ line: lineIdx + 1,
32355
+ column: tokenStart + 1,
32356
+ original,
32357
+ replacement
32358
+ });
32359
+ }
32360
+ }
32361
+ if (out.length >= maxPerFile) break;
32362
+ }
32363
+ return out.slice(0, maxPerFile);
32364
+ }
32365
+ function isMasked(line, start, len) {
32366
+ let inSingle = false;
32367
+ let inDouble = false;
32368
+ for (let i = 0; i < start; i++) {
32369
+ const c = line[i];
32370
+ const prev = i > 0 ? line[i - 1] : void 0;
32371
+ if (c === "'" && prev !== "\\") inSingle = !inSingle;
32372
+ else if (c === '"' && prev !== "\\") inDouble = !inDouble;
32373
+ if (!inSingle && !inDouble && c === "/" && prev === "/") return true;
32374
+ }
32375
+ if (inSingle || inDouble) return true;
32376
+ const window = line.slice(start, start + len);
32377
+ return /['"]/.test(window);
32378
+ }
32379
+ function parseMutationReport(text2) {
32380
+ const candidates = [];
32381
+ const fence = text2.match(/```(?:json)?\s*([\s\S]*?)```/);
32382
+ if (fence?.[1]) candidates.push(fence[1].trim());
32383
+ const firstBrace = text2.indexOf("{");
32384
+ if (firstBrace >= 0) candidates.push(extractBalancedObject(text2, firstBrace));
32385
+ for (const candidate of candidates) {
32386
+ if (!candidate) continue;
32387
+ try {
32388
+ const parsed = JSON.parse(candidate);
32389
+ if (!Array.isArray(parsed.mutants)) continue;
32390
+ return {
32391
+ mutants: parsed.mutants.map(normalizeMutantEntry).filter((x) => Boolean(x)),
32392
+ summary: typeof parsed.summary === "string" ? parsed.summary : void 0
32393
+ };
32394
+ } catch {
32395
+ }
32396
+ }
32397
+ return void 0;
32398
+ }
32399
+ function extractBalancedObject(text2, start) {
32400
+ let depth = 0;
32401
+ let inString = false;
32402
+ let escaped = false;
32403
+ for (let i = start; i < text2.length; i++) {
32404
+ const c = text2[i];
32405
+ if (escaped) {
32406
+ escaped = false;
32407
+ continue;
32408
+ }
32409
+ if (c === "\\") {
32410
+ escaped = true;
32411
+ continue;
32412
+ }
32413
+ if (c === '"') inString = !inString;
32414
+ if (inString) continue;
32415
+ if (c === "{") depth++;
32416
+ else if (c === "}") {
32417
+ depth--;
32418
+ if (depth === 0) return text2.slice(start, i + 1);
32419
+ }
32420
+ }
32421
+ return text2.slice(start);
32422
+ }
32423
+ function normalizeMutantEntry(value) {
32424
+ if (typeof value !== "object" || value === null) return void 0;
32425
+ const rec = value;
32426
+ const id = typeof rec["id"] === "string" ? rec["id"] : void 0;
32427
+ const status = rec["status"];
32428
+ if (!id || status !== "killed" && status !== "survived" && status !== "skipped") {
32429
+ return void 0;
32430
+ }
32431
+ return {
32432
+ id,
32433
+ file: typeof rec["file"] === "string" ? rec["file"] : "",
32434
+ line: typeof rec["line"] === "number" ? rec["line"] : 0,
32435
+ kind: typeof rec["kind"] === "string" ? rec["kind"] : "",
32436
+ status,
32437
+ evidence: typeof rec["evidence"] === "string" ? rec["evidence"] : void 0
32438
+ };
32439
+ }
32440
+
32441
+ // src/coordination/director-mutation-test-tool.ts
32442
+ var DEFAULT_MAX_PER_FILE = 10;
32443
+ var DEFAULT_MAX_STRENGTHEN_ATTEMPTS = 2;
32444
+ var CHAOS_ROLE = "chaos-monkey";
32445
+ function makeMutationTestTool(director, roster, opts = {}) {
32446
+ return {
32447
+ name: "mutation_test",
32448
+ 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.",
32449
+ 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.",
32450
+ permission: "auto",
32451
+ mutating: false,
32452
+ capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
32453
+ inputSchema: {
32454
+ type: "object",
32455
+ properties: {
32456
+ targets: {
32457
+ type: "array",
32458
+ items: { type: "string" },
32459
+ description: "Project-relative (or absolute) source files to mutate. Keep to files changed by the current task."
32460
+ },
32461
+ testCommand: {
32462
+ type: "string",
32463
+ description: 'Exact command that runs the relevant tests, e.g. "pnpm exec vitest run packages/core/tests/coordination/mutation-engine.test.ts".'
32464
+ },
32465
+ cwd: { type: "string", description: "Working directory for the test command." },
32466
+ maxPerFile: {
32467
+ type: "number",
32468
+ minimum: 1,
32469
+ maximum: 25,
32470
+ description: "Mutant cap per file per pass. Default 10."
32471
+ },
32472
+ maxStrengthenAttempts: {
32473
+ type: "number",
32474
+ minimum: 0,
32475
+ maximum: 5,
32476
+ description: "Strengthen\u2192re-verify rounds. Default 2 when repairSubagentId is set, else 0."
32477
+ },
32478
+ repairSubagentId: {
32479
+ type: "string",
32480
+ description: "Subagent that owns the tests. When set and mutants survive, it receives a strengthen-tests task and the survivors are re-verified."
32481
+ },
32482
+ chaosWorktree: {
32483
+ anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
32484
+ description: "Worktree override for the chaos agent. Use 'off' when targets are uncommitted \u2014 a worktree from HEAD would not contain them."
32485
+ },
32486
+ timeoutMs: { type: "number", minimum: 1, description: "Per-task timeout for chaos/strengthen/rerun tasks." },
32487
+ reportOnly: {
32488
+ type: "boolean",
32489
+ description: "Skip the strengthen loop even when survivors exist. Default false."
32490
+ }
32491
+ },
32492
+ required: ["targets", "testCommand"],
32493
+ additionalProperties: false
32494
+ },
32495
+ async execute(input, ctx) {
32496
+ const i = normalizeMutationTestInput(input);
32497
+ const root = opts.projectRoot ?? ctx.projectRoot;
32498
+ const plan = buildPlan(i, root);
32499
+ if (plan.length === 0) {
32500
+ return {
32501
+ verdict: "inconclusive",
32502
+ passed: false,
32503
+ error: "No mutable sites found in the given targets (after comment/string filtering)."
32504
+ };
32505
+ }
32506
+ const chaosSubagentId = await director.spawn(
32507
+ makeChaosConfig(roster, i.chaosWorktree ?? "off")
32508
+ );
32509
+ const chaosTaskId = await director.assign({
32510
+ id: randomUUID14(),
32511
+ subagentId: chaosSubagentId,
32512
+ description: buildChaosTask(plan, i, 1, []),
32513
+ timeoutMs: i.timeoutMs
32514
+ });
32515
+ const [chaosResult] = await director.awaitTasks([chaosTaskId]);
32516
+ const pass1 = collectOutcomes(chaosResult, plan);
32517
+ const survivors = pass1.filter((m) => m.status === "survived");
32518
+ const maxAttempts = clamp(
32519
+ i.maxStrengthenAttempts ?? (i.repairSubagentId && !i.reportOnly ? DEFAULT_MAX_STRENGTHEN_ATTEMPTS : 0),
32520
+ 0,
32521
+ 5
32522
+ );
32523
+ const attempts = [];
32524
+ let current = survivors;
32525
+ while (current.length > 0 && attempts.length < maxAttempts && i.repairSubagentId) {
32526
+ const attemptNo = attempts.length + 1;
32527
+ const strengthenTaskId = await director.assign({
32528
+ id: randomUUID14(),
32529
+ subagentId: i.repairSubagentId,
32530
+ description: buildStrengthenTask(current, i, attemptNo),
32531
+ timeoutMs: i.timeoutMs
32532
+ });
32533
+ const [strengthenResult] = await director.awaitTasks([strengthenTaskId]);
32534
+ if (strengthenResult?.status !== "success") {
32535
+ attempts.push({
32536
+ attempt: attemptNo,
32537
+ survivorsBefore: current,
32538
+ strengthenResult: strengthenResult ? { taskId: strengthenResult.taskId, status: strengthenResult.status } : void 0,
32539
+ survivorsAfter: current,
32540
+ suspectedEquivalent: []
32541
+ });
32542
+ break;
32543
+ }
32544
+ const survivorPlan = plan.filter((p) => current.some((s) => s.id === p.id));
32545
+ const rerunSubagentId = await director.spawn(
32546
+ makeChaosConfig(roster, i.chaosWorktree ?? "off")
32547
+ );
32548
+ const rerunTaskId = await director.assign({
32549
+ id: randomUUID14(),
32550
+ subagentId: rerunSubagentId,
32551
+ description: buildChaosTask(survivorPlan, i, attemptNo + 1, current),
32552
+ timeoutMs: i.timeoutMs
32553
+ });
32554
+ const [rerunResult] = await director.awaitTasks([rerunTaskId]);
32555
+ const passN = collectOutcomes(rerunResult, survivorPlan);
32556
+ const stillSurviving = passN.filter((m) => m.status === "survived" || m.status === "skipped");
32557
+ attempts.push({
32558
+ attempt: attemptNo,
32559
+ survivorsBefore: current,
32560
+ strengthenResult: { taskId: strengthenResult.taskId, status: strengthenResult.status },
32561
+ rerunResult: { taskId: rerunTaskId, status: rerunResult?.status ?? "unknown" },
32562
+ survivorsAfter: stillSurviving,
32563
+ suspectedEquivalent: stillSurviving.filter((m) => current.some((c) => c.id === m.id)).map((m) => m.id)
32564
+ });
32565
+ current = stillSurviving.filter((m) => m.status === "survived");
32566
+ if (passN.every((m) => m.status === "skipped")) break;
32567
+ }
32568
+ const finalSurvivors = current;
32569
+ const verifiedCount = pass1.filter((m) => m.status !== "skipped").length;
32570
+ const skippedCount = pass1.filter((m) => m.status === "skipped").length;
32571
+ const score = plan.length === 0 ? 0 : pass1.filter((m) => m.status === "killed").length / plan.length;
32572
+ const verdict = verifiedCount === 0 ? "inconclusive" : finalSurvivors.length === 0 ? skippedCount > 0 ? "partial" : "pass" : score >= 0.8 ? "partial" : "fail";
32573
+ return {
32574
+ verdict,
32575
+ passed: verdict === "pass",
32576
+ mutationScore: Number.parseFloat(score.toFixed(3)),
32577
+ planned: plan.length,
32578
+ killed: pass1.filter((m) => m.status === "killed").length,
32579
+ survived: pass1.filter((m) => m.status === "survived").length,
32580
+ skipped: pass1.filter((m) => m.status === "skipped").length,
32581
+ finalSurvivors: finalSurvivors.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
32582
+ suspectedEquivalent: attempts.flatMap((a) => a.suspectedEquivalent),
32583
+ strengthenAttempts: attempts.length,
32584
+ attempts,
32585
+ chaosTaskId,
32586
+ nextAction: finalSurvivors.length === 0 ? "accept" : attempts.length >= maxAttempts && i.repairSubagentId ? "manual_review_survivors" : "strengthen_tests"
32587
+ };
32588
+ }
32589
+ };
32590
+ }
32591
+ function normalizeMutationTestInput(input) {
32592
+ const raw = input ?? {};
32593
+ const targets = stringArray2(raw["targets"]) ?? [];
32594
+ const testCommand = typeof raw["testCommand"] === "string" ? raw["testCommand"].trim() : "";
32595
+ return {
32596
+ targets: targets.filter(Boolean),
32597
+ testCommand,
32598
+ cwd: typeof raw["cwd"] === "string" && raw["cwd"].trim() ? raw["cwd"].trim() : void 0,
32599
+ maxPerFile: typeof raw["maxPerFile"] === "number" ? raw["maxPerFile"] : void 0,
32600
+ maxStrengthenAttempts: typeof raw["maxStrengthenAttempts"] === "number" ? raw["maxStrengthenAttempts"] : void 0,
32601
+ repairSubagentId: typeof raw["repairSubagentId"] === "string" && raw["repairSubagentId"].trim() ? raw["repairSubagentId"].trim() : void 0,
32602
+ chaosWorktree: raw["chaosWorktree"] ?? void 0,
32603
+ timeoutMs: typeof raw["timeoutMs"] === "number" ? raw["timeoutMs"] : void 0,
32604
+ reportOnly: raw["reportOnly"] === true
32605
+ };
32606
+ }
32607
+ function clamp(n, lo, hi) {
32608
+ return Math.min(hi, Math.max(lo, n));
32609
+ }
32610
+ function buildPlan(i, projectRoot) {
32611
+ const plan = [];
32612
+ for (const target of i.targets) {
32613
+ const abs = isAbsolute9(target) ? target : join40(projectRoot ?? process.cwd(), target);
32614
+ let source;
32615
+ try {
32616
+ source = readFileSync19(abs, "utf8");
32617
+ } catch {
32618
+ continue;
32619
+ }
32620
+ plan.push(...planMutations(target, source, { maxPerFile: i.maxPerFile ?? DEFAULT_MAX_PER_FILE }));
32621
+ }
32622
+ return plan;
32623
+ }
32624
+ function makeChaosConfig(roster, worktree) {
32625
+ const base = roster?.[CHAOS_ROLE] ?? getAgentDefinition(CHAOS_ROLE)?.config ?? { name: "Chaos Monkey", role: CHAOS_ROLE };
32626
+ return { ...instantiateRosterConfig(CHAOS_ROLE, base), worktree };
32627
+ }
32628
+ function buildChaosTask(plan, i, pass, priorSurvivors) {
32629
+ const mutants = plan.map(
32630
+ (m) => `- ${m.id} | ${m.file}:${m.line}:${m.column} | ${m.kind} | "${m.original}" -> "${m.replacement}"`
32631
+ ).join("\n");
32632
+ const prior = priorSurvivors.length > 0 ? `
32633
+ These mutants survived a previous pass (pass ${pass - 1}) \u2014 re-verify them against the STRENGTHENED tests:
32634
+ ${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join("\n")}` : "";
32635
+ return [
32636
+ "Execute this deterministic mutation plan against the current checkout.",
32637
+ "",
32638
+ "For each mutant, in order:",
32639
+ "1. Apply ONLY that mutation at its exact (file, line, column).",
32640
+ `2. Run the test command: ${i.testCommand}${i.cwd ? ` (cwd: ${i.cwd})` : ""}`,
32641
+ "3. Record killed (tests failed \u2014 quote first failing assertion) or survived (suite green).",
32642
+ "4. Restore the file byte-for-byte before the next mutant.",
32643
+ "",
32644
+ "Mutants:",
32645
+ mutants,
32646
+ prior,
32647
+ "",
32648
+ "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.",
32649
+ "Finish with submit_result, then repeat the same JSON as your final text."
32650
+ ].join("\n");
32651
+ }
32652
+ function buildStrengthenTask(survivors, i, attempt) {
32653
+ return [
32654
+ `Strengthen the tests so these SURVIVING mutants die (attempt ${attempt}).`,
32655
+ "",
32656
+ "Each survivor below was a deliberate sabotage of production code that the current suite did NOT catch:",
32657
+ ...survivors.map((s) => `- ${s.id} | ${s.file}:${s.line} | ${s.kind}${s.evidence ? ` | ${s.evidence}` : ""}`),
32658
+ "",
32659
+ `Test command that must fail under each mutant: ${i.testCommand}`,
32660
+ "",
32661
+ "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."
32662
+ ].join("\n");
32663
+ }
32664
+ function collectOutcomes(result, plan) {
32665
+ const fromText = parseTextOutcomes(result);
32666
+ if (fromText.length > 0) {
32667
+ const planned = new Set(plan.map((p) => p.id));
32668
+ const matched = fromText.filter((m) => planned.has(m.id));
32669
+ if (matched.length > 0) return matched;
32670
+ }
32671
+ return plan.map((p) => ({
32672
+ id: p.id,
32673
+ file: p.file,
32674
+ line: p.line,
32675
+ kind: p.kind,
32676
+ status: "skipped",
32677
+ evidence: result ? `chaos task ended ${result.status}` : "chaos task produced no result"
32678
+ }));
32679
+ }
32680
+ function parseTextOutcomes(result) {
32681
+ const text2 = typeof result?.result === "string" ? result.result : void 0;
32682
+ if (!text2) return [];
32683
+ const parsed = parseMutationReport(text2);
32684
+ if (!parsed) return [];
32685
+ return parsed.mutants.map((m) => ({
32686
+ id: m.id,
32687
+ file: m.file,
32688
+ line: m.line,
32689
+ kind: m.kind,
32690
+ status: m.status,
32691
+ evidence: m.evidence
32692
+ }));
32693
+ }
32694
+
32138
32695
  // src/coordination/director-tools.ts
32139
32696
  function makeSpawnTool(director, roster) {
32140
32697
  const dispatchCatalog = () => {
@@ -32427,7 +32984,7 @@ function makeKanbanQueueTool(director, roster) {
32427
32984
  try {
32428
32985
  const config = buildKanbanSubagentConfig(claim.task, i, roster, instantiateRosterConfig);
32429
32986
  subagentId = await director.spawn(config);
32430
- const dispatchTaskId = randomUUID14();
32987
+ const dispatchTaskId = randomUUID15();
32431
32988
  const taskSpec = {
32432
32989
  id: dispatchTaskId,
32433
32990
  subagentId,
@@ -32703,6 +33260,7 @@ function buildDirectorToolset(director, roster) {
32703
33260
  makeAskResultTool(director),
32704
33261
  makeRollUpTool(director),
32705
33262
  makeQualityGateTool(director, roster),
33263
+ makeMutationTestTool(director, roster),
32706
33264
  makeTerminateTool(director),
32707
33265
  makeTerminateAllTool(director),
32708
33266
  makeFleetTool(director),
@@ -32789,7 +33347,7 @@ import * as fsp29 from "node:fs/promises";
32789
33347
  import * as path63 from "node:path";
32790
33348
 
32791
33349
  // src/storage/session-store.ts
32792
- import { randomUUID as randomUUID16 } from "node:crypto";
33350
+ import { randomUUID as randomUUID17 } from "node:crypto";
32793
33351
  import * as fsp28 from "node:fs/promises";
32794
33352
  import * as path62 from "node:path";
32795
33353
  init_client();
@@ -33960,7 +34518,7 @@ var FileSessionWriter = class _FileSessionWriter {
33960
34518
  // src/storage/session-checkpoint-cas.ts
33961
34519
  init_atomic_write();
33962
34520
  import { spawn as spawn5 } from "node:child_process";
33963
- import { createHash as createHash18, randomUUID as randomUUID15 } from "node:crypto";
34521
+ import { createHash as createHash18, randomUUID as randomUUID16 } from "node:crypto";
33964
34522
  import * as fsp15 from "node:fs/promises";
33965
34523
  import * as path54 from "node:path";
33966
34524
  init_error();
@@ -34198,7 +34756,7 @@ var SessionCheckpointCas = class {
34198
34756
  }
34199
34757
  const temp = path54.join(
34200
34758
  path54.dirname(target),
34201
- `.${path54.basename(target)}.${process.pid}.${randomUUID15()}.tmp`
34759
+ `.${path54.basename(target)}.${process.pid}.${randomUUID16()}.tmp`
34202
34760
  );
34203
34761
  let handle;
34204
34762
  try {
@@ -36006,7 +36564,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
36006
36564
  onAppend;
36007
36565
  onAppendBatch;
36008
36566
  catalogClient;
36009
- maintenanceHolderId = randomUUID16();
36567
+ maintenanceHolderId = randomUUID17();
36010
36568
  _loadCache = /* @__PURE__ */ new Map();
36011
36569
  loadCache = new SessionLoadCache(this._loadCache);
36012
36570
  _indexCache = null;
@@ -36698,7 +37256,7 @@ async function readDirectorSubagentSession(args) {
36698
37256
  }
36699
37257
 
36700
37258
  // src/core/fallback-model.ts
36701
- import { randomUUID as randomUUID17 } from "node:crypto";
37259
+ import { randomUUID as randomUUID18 } from "node:crypto";
36702
37260
 
36703
37261
  // src/types/provider.ts
36704
37262
  init_errors();
@@ -36708,6 +37266,18 @@ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not eno
36708
37266
  var ROUTE_SCOPED_QUOTA_RE = /\b(?:for|on)\s+(?:(?:this|the)\s+)?(?:route|model)\b|\b(?:route|model)(?:[-_\s]+[\w.-]+)?[-_\s]*(?:quota|limit)\b|\b(?:quota|limit).{0,24}\b(?:for|on)\s+(?:(?:this|the)\s+)?(?:route|model)\b/i;
36709
37267
 
36710
37268
  // src/types/provider.ts
37269
+ var REASONING_EFFORT_LEVELS = [
37270
+ "none",
37271
+ "minimal",
37272
+ "low",
37273
+ "medium",
37274
+ "high",
37275
+ "xhigh",
37276
+ "max"
37277
+ ];
37278
+ function isReasoningEffort(value) {
37279
+ return typeof value === "string" && REASONING_EFFORT_LEVELS.includes(value);
37280
+ }
36711
37281
  function effectiveInputTokens(usage) {
36712
37282
  return usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
36713
37283
  }
@@ -37730,7 +38300,7 @@ function createFallbackModelExtension(deps) {
37730
38300
  let gateRequestId;
37731
38301
  const configuredGateSeconds = cfg.fallbackGateSeconds ?? deps.fallbackGateSeconds;
37732
38302
  if (configuredGateSeconds !== 0 && deps.fallbackGate && usableChain.length > 0) {
37733
- gateRequestId = randomUUID17();
38303
+ gateRequestId = randomUUID18();
37734
38304
  const autoSwitchSeconds = Math.max(1, configuredGateSeconds ?? 7);
37735
38305
  const gateCandidates = usableChain.map((e) => ({
37736
38306
  providerId: e.providerId,
@@ -38702,7 +39272,7 @@ function hashStr(s) {
38702
39272
  }
38703
39273
 
38704
39274
  // src/coordination/multi-agent-coordinator.ts
38705
- import { randomUUID as randomUUID18 } from "node:crypto";
39275
+ import { randomUUID as randomUUID19 } from "node:crypto";
38706
39276
  import { EventEmitter as EventEmitter2 } from "node:events";
38707
39277
 
38708
39278
  // src/coordination/coordinator/error-classifier.ts
@@ -38805,6 +39375,42 @@ var SHADOW_AGENT = {
38805
39375
  ...defineAgent("shadow-agent", "Shadow"),
38806
39376
  skillNames: [...SHADOW_AGENT_SKILLS]
38807
39377
  };
39378
+ var EXPLORE_COMPANION_AGENT = {
39379
+ ...defineAgent("explore-companion", "Explore Companion"),
39380
+ tools: [...TOOLS.read, ...TOOLS.index],
39381
+ // Read-only, triple-enforced: allowlist has no write/bash, and the
39382
+ // disabled list blocks the escape hatches explicitly.
39383
+ disabledTools: [
39384
+ "write",
39385
+ "edit",
39386
+ "replace",
39387
+ "patch",
39388
+ "bash",
39389
+ "exec",
39390
+ "delegate",
39391
+ "spawn_subagent",
39392
+ "assign_task"
39393
+ ],
39394
+ skillNames: [...ROLE_SKILL_SETS["explore-companion"]],
39395
+ spawnBudgetExempt: true,
39396
+ // Findings travel via mailbox + submit_result, not the leader's stream.
39397
+ textStream: "silent",
39398
+ toolStream: "silent"
39399
+ };
39400
+ var CHAOS_MONKEY_AGENT = {
39401
+ ...defineAgent("chaos-monkey", "Chaos Monkey"),
39402
+ tools: [...TOOLS.build],
39403
+ skillNames: ["testing", "typescript-strict"],
39404
+ spawnBudgetExempt: true,
39405
+ // Follow fleet worktree policy (NOT 'required'): mutation targets are
39406
+ // often freshly written and uncommitted — a worktree spawned from HEAD
39407
+ // would not contain them and every mutant would drift. Callers pass
39408
+ // `worktree: 'off'` in the mutation_test input for uncommitted targets.
39409
+ worktree: "auto",
39410
+ // Report travels via submit_result + final text, not the leader's stream.
39411
+ textStream: "silent",
39412
+ toolStream: "silent"
39413
+ };
38808
39414
  var CRITIC_AGENT = defineAgent("critic", "Critic");
38809
39415
  var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
38810
39416
  function withDispatchMetadata(definition) {
@@ -38823,6 +39429,8 @@ var FLEET_ROSTER = {
38823
39429
  critic: CRITIC_AGENT,
38824
39430
  generic: GENERIC_AGENT,
38825
39431
  "shadow-agent": SHADOW_AGENT,
39432
+ "explore-companion": EXPLORE_COMPANION_AGENT,
39433
+ "chaos-monkey": CHAOS_MONKEY_AGENT,
38826
39434
  ...Object.fromEntries(
38827
39435
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
38828
39436
  )
@@ -38846,6 +39454,23 @@ var FLEET_ROSTER_BUDGETS = {
38846
39454
  maxTokens: 96e3,
38847
39455
  maxCostUsd: 0.5
38848
39456
  },
39457
+ "explore-companion": {
39458
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
39459
+ maxIterations: 3e3,
39460
+ maxToolCalls: 8e3,
39461
+ maxTokens: 96e3,
39462
+ maxCostUsd: 0.5
39463
+ },
39464
+ "chaos-monkey": {
39465
+ // A mutation pass is many short apply/run/restore cycles — per-mutant
39466
+ // work is tiny, but a large plan (25 mutants/file × N files) needs
39467
+ // headroom. Idle-based reaping covers a stalled pass.
39468
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
39469
+ maxIterations: 2e3,
39470
+ maxToolCalls: 6e3,
39471
+ maxTokens: 96e3,
39472
+ maxCostUsd: 0.5
39473
+ },
38849
39474
  ...Object.fromEntries(
38850
39475
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
38851
39476
  )
@@ -38908,7 +39533,8 @@ async function executeSubagentWithTimeout({
38908
39533
  budget,
38909
39534
  preemptFraction = TIMEOUT_PREEMPT_FRACTION,
38910
39535
  abortSubagent,
38911
- currentSessionId
39536
+ currentSessionId,
39537
+ gracefulFinish
38912
39538
  }) {
38913
39539
  const initialTimeoutMs = budget.limits.timeoutMs;
38914
39540
  const idleLimitMs = budget.limits.idleTimeoutMs;
@@ -38939,9 +39565,17 @@ async function executeSubagentWithTimeout({
38939
39565
  const scheduleNext = () => {
38940
39566
  const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
38941
39567
  const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
38942
- const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
38943
- const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
38944
- armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
39568
+ const idleRemaining = idleLimitMs === void 0 || gracefulFinish !== void 0 && initialTimeoutMs !== void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
39569
+ const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit || gracefulFinish !== void 0 ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
39570
+ const next = Math.min(wallRemaining, idleRemaining, preemptRemaining);
39571
+ if (!Number.isFinite(next)) {
39572
+ if (timer) {
39573
+ clearTimeout(timer);
39574
+ timer = null;
39575
+ }
39576
+ return;
39577
+ }
39578
+ armFor(Math.max(25, next));
38945
39579
  };
38946
39580
  const negotiateTimeout = async (used, limit) => {
38947
39581
  const handler = budget.onThreshold;
@@ -38990,6 +39624,10 @@ async function executeSubagentWithTimeout({
38990
39624
  const wallExceeded = wallLimit !== void 0 && elapsed2 >= wallLimit;
38991
39625
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
38992
39626
  if (idleExceeded && !wallExceeded) {
39627
+ if (gracefulFinish !== void 0 && initialTimeoutMs !== void 0) {
39628
+ scheduleNext();
39629
+ return;
39630
+ }
38993
39631
  const sessionId = currentSessionId();
38994
39632
  budget._events?.emit("budget.threshold_reached", {
38995
39633
  ...sessionId ? { sessionId } : {},
@@ -39006,7 +39644,7 @@ async function executeSubagentWithTimeout({
39006
39644
  reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
39007
39645
  return;
39008
39646
  }
39009
- if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed2 >= wallLimit * preemptFraction) {
39647
+ if (wallLimit !== void 0 && !wallExceeded && gracefulFinish === void 0 && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed2 >= wallLimit * preemptFraction) {
39010
39648
  const activityTs = Date.now() - budget.idleMs();
39011
39649
  if (activityTs <= lastGrantActivityTs) {
39012
39650
  preemptState = "locked" /* LOCKED */;
@@ -39040,6 +39678,22 @@ async function executeSubagentWithTimeout({
39040
39678
  return;
39041
39679
  }
39042
39680
  const limit = wallLimit ?? 0;
39681
+ if (gracefulFinish !== void 0) {
39682
+ if (!budget.graceGranted) {
39683
+ const reason = `wall-clock budget of ${Math.round(limit / 1e3)}s reached`;
39684
+ if (budget.notifyFinish(reason, { graceMs: gracefulFinish.graceMs })) {
39685
+ scheduleNext();
39686
+ return;
39687
+ }
39688
+ abortSubagent(ctx.subagentId);
39689
+ reject(new BudgetExceededError("timeout", limit, elapsed2));
39690
+ return;
39691
+ } else {
39692
+ abortSubagent(ctx.subagentId);
39693
+ reject(new BudgetExceededError("timeout", limit, elapsed2));
39694
+ return;
39695
+ }
39696
+ }
39043
39697
  if (!budget.onThreshold) {
39044
39698
  abortSubagent(ctx.subagentId);
39045
39699
  reject(new BudgetExceededError("timeout", limit, elapsed2));
@@ -39187,7 +39841,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39187
39841
  return { ...subagent, name: display };
39188
39842
  }
39189
39843
  async spawn(subagent) {
39190
- const id = subagent.id || randomUUID18();
39844
+ const id = subagent.id || randomUUID19();
39191
39845
  const cfg = this.withNickname(subagent, id);
39192
39846
  if (this.subagents.has(id)) {
39193
39847
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -39424,6 +40078,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39424
40078
  completeTask(result) {
39425
40079
  this.recordCompletion(result);
39426
40080
  }
40081
+ /**
40082
+ * Ask every RUNNING subagent that opted into `gracefulFinish` to finish its
40083
+ * task in its own turn (see coordination/subagent-finish.ts). This is the
40084
+ * leader-side entry point for "the leader agent has finished": it delivers
40085
+ * an in-band notification between tool batches — never an interrupt, never
40086
+ * an abort. Each notified subagent keeps its existing time budget and
40087
+ * accelerates; the watchdog still bounds the maximum lifetime.
40088
+ *
40089
+ * Subagents without the policy opted in are deliberately untouched — their
40090
+ * lifecycle remains the legacy watchdog contract.
40091
+ *
40092
+ * Returns the number of subagents actually notified.
40093
+ */
40094
+ requestFinish(reason) {
40095
+ let notified = 0;
40096
+ for (const subagent of this.subagents.values()) {
40097
+ if (subagent.status !== "running") continue;
40098
+ if (!resolveGracefulFinish(subagent.config)) continue;
40099
+ const budget = subagent.activeBudget;
40100
+ if (!budget) continue;
40101
+ const usage = budget.usage();
40102
+ if (usage.iterations === 0 && usage.toolCalls === 0) continue;
40103
+ if (budget.notifyFinish(reason)) notified++;
40104
+ }
40105
+ return notified;
40106
+ }
39427
40107
  // --- internal dispatching ---------------------------------------------
39428
40108
  tryDispatchNext() {
39429
40109
  while (this.canDispatch()) {
@@ -39597,7 +40277,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39597
40277
  idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
39598
40278
  },
39599
40279
  "auto",
39600
- { sessionId: () => this.currentSessionId() }
40280
+ {
40281
+ sessionId: () => this.currentSessionId(),
40282
+ subagentId,
40283
+ // Graceful-finish runs own wall-clock enforcement to the watchdog so
40284
+ // the notify-then-bound lifecycle cannot be raced by tool.progress
40285
+ // heartbeats calling checkTimeout() (see subagent-budget.ts).
40286
+ ...resolveGracefulFinish(subagent.config) ? { wallClockWatchdogOwned: true } : {}
40287
+ }
39601
40288
  );
39602
40289
  subagent.activeBudget = budget;
39603
40290
  if (!this.runner) {
@@ -39630,7 +40317,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39630
40317
  task,
39631
40318
  runCtx,
39632
40319
  budget,
39633
- subagent.config.preemptFraction
40320
+ subagent.config.preemptFraction,
40321
+ resolveGracefulFinish(subagent.config)
39634
40322
  );
39635
40323
  result = {
39636
40324
  subagentId,
@@ -39660,13 +40348,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39660
40348
  }
39661
40349
  this.recordCompletion(result);
39662
40350
  }
39663
- async executeWithTimeout(runner, task, ctx, budget, preemptFraction) {
40351
+ async executeWithTimeout(runner, task, ctx, budget, preemptFraction, gracefulFinish) {
39664
40352
  return executeSubagentWithTimeout({
39665
40353
  runner,
39666
40354
  task,
39667
40355
  ctx,
39668
40356
  budget,
39669
40357
  preemptFraction,
40358
+ gracefulFinish,
39670
40359
  abortSubagent: (subagentId) => this.subagents.get(subagentId)?.abortController.abort(),
39671
40360
  currentSessionId: () => this.currentSessionId()
39672
40361
  });
@@ -40073,7 +40762,7 @@ var Director = class _Director {
40073
40762
  sessionProvider;
40074
40763
  sessionModel;
40075
40764
  constructor(opts) {
40076
- this.id = opts.config.coordinatorId || randomUUID19();
40765
+ this.id = opts.config.coordinatorId || randomUUID20();
40077
40766
  this.manifestPath = opts.manifestPath;
40078
40767
  this.roster = opts.roster;
40079
40768
  this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -40280,6 +40969,17 @@ var Director = class _Director {
40280
40969
  isWorkComplete() {
40281
40970
  return this.workCompleteFlag;
40282
40971
  }
40972
+ /**
40973
+ * Ask every running background subagent that opted into `gracefulFinish`
40974
+ * to finish its task in its own turn. In-band notification between tool
40975
+ * batches — no interrupt, no abort; each subagent keeps its time budget and
40976
+ * accelerates. Session shutdown calls this before draining Chimera work so
40977
+ * the post-session reviewer is nudged to complete rather than killed.
40978
+ * Returns the number of subagents notified.
40979
+ */
40980
+ requestFinish(reason) {
40981
+ return this.coordinator.requestFinish(reason);
40982
+ }
40283
40983
  setLeaderBtwNote(note) {
40284
40984
  return this.btwNotes.add(note);
40285
40985
  }
@@ -40356,7 +41056,7 @@ var Director = class _Director {
40356
41056
  );
40357
41057
  }
40358
41058
  const msg = {
40359
- id: randomUUID19(),
41059
+ id: randomUUID20(),
40360
41060
  type: "task",
40361
41061
  from: this.id,
40362
41062
  to: subagentId,
@@ -40610,7 +41310,7 @@ var Director = class _Director {
40610
41310
  };
40611
41311
 
40612
41312
  // src/coordination/fleet-manager.ts
40613
- import { randomUUID as randomUUID20 } from "node:crypto";
41313
+ import { randomUUID as randomUUID21 } from "node:crypto";
40614
41314
  import * as fsp31 from "node:fs/promises";
40615
41315
  import * as path64 from "node:path";
40616
41316
  init_atomic_write();
@@ -40678,7 +41378,7 @@ var FleetManager = class {
40678
41378
  maxContext;
40679
41379
  constructor(opts = {}) {
40680
41380
  this.manifestPath = opts.manifestPath;
40681
- this.directorRunId = opts.directorRunId ?? randomUUID20();
41381
+ this.directorRunId = opts.directorRunId ?? randomUUID21();
40682
41382
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
40683
41383
  this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
40684
41384
  this.spawnDepth = opts.spawnDepth ?? 0;
@@ -42730,7 +43430,7 @@ function makeFleetStatusTool(opts = {}) {
42730
43430
  }
42731
43431
 
42732
43432
  // src/coordination/fleet-supervisor.ts
42733
- import { randomUUID as randomUUID21 } from "node:crypto";
43433
+ import { randomUUID as randomUUID22 } from "node:crypto";
42734
43434
  var COLLAB_ID_PREFIXES2 = ["bug-hunter-", "refactor-planner-", "critic-"];
42735
43435
  var DEFAULTS = {
42736
43436
  intervalMs: 2e4,
@@ -43008,7 +43708,7 @@ var FleetSupervisor = class {
43008
43708
  */
43009
43709
  async decide(question, context, options, risk) {
43010
43710
  const request = {
43011
- id: `fleetsup-${randomUUID21()}`,
43711
+ id: `fleetsup-${randomUUID22()}`,
43012
43712
  sessionId: this.opts.sessionId?.(),
43013
43713
  source: "system",
43014
43714
  question,
@@ -43404,7 +44104,7 @@ function attachAutoExtend(events, policy = {}) {
43404
44104
  }
43405
44105
 
43406
44106
  // src/coordination/delegate-tool.ts
43407
- import { randomUUID as randomUUID22 } from "node:crypto";
44107
+ import { randomUUID as randomUUID23 } from "node:crypto";
43408
44108
  import * as fsp32 from "node:fs/promises";
43409
44109
  import * as path69 from "node:path";
43410
44110
  init_error();
@@ -43611,7 +44311,7 @@ function createDelegateTool(opts) {
43611
44311
  }
43612
44312
  const description = delegatedTask;
43613
44313
  const taskId = await dir.assign({
43614
- id: randomUUID22(),
44314
+ id: randomUUID23(),
43615
44315
  description,
43616
44316
  subagentId
43617
44317
  });
@@ -43832,7 +44532,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
43832
44532
  function freshHandoffConfig(cfg, role, handoffCount) {
43833
44533
  return {
43834
44534
  ...cfg,
43835
- id: role ? `${role}-${randomUUID22().slice(0, 8)}` : `${cfg.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "subagent"}-handoff-${handoffCount}-${randomUUID22().slice(0, 6)}`
44535
+ id: role ? `${role}-${randomUUID23().slice(0, 8)}` : `${cfg.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "subagent"}-handoff-${handoffCount}-${randomUUID23().slice(0, 6)}`
43836
44536
  };
43837
44537
  }
43838
44538
  function continuationFor(result, partial, config) {
@@ -43892,7 +44592,7 @@ function instantiateRosterConfig2(role, base, requestedTimeoutMs, defaultTimeout
43892
44592
  timeoutMs: requestedTimeoutMs === void 0 ? rosterTimeoutMs ?? defaultTimeoutMs : void 0,
43893
44593
  // Give each spawn a fresh id so parallel or repeated delegates
43894
44594
  // can use the same role safely.
43895
- id: `${role}-${randomUUID22().slice(0, 8)}`
44595
+ id: `${role}-${randomUUID23().slice(0, 8)}`
43896
44596
  };
43897
44597
  }
43898
44598
  function hintForKind(kind, retryable, backoffMs, partial) {
@@ -44017,6 +44717,334 @@ async function readSubagentPartial(opts, subagentId) {
44017
44717
  return void 0;
44018
44718
  }
44019
44719
 
44720
+ // src/coordination/explore-companion.ts
44721
+ import { randomUUID as randomUUID24 } from "node:crypto";
44722
+ var DEFAULT_EXPLORE_COMPANION_AGENT_ID = "explore-companion";
44723
+ var DEFAULT_PROBE_COOLDOWN_MS = 12e4;
44724
+ var DEFAULT_MAX_PENDING_PROBES = 8;
44725
+ var DEFAULT_MAILBOX_POLL_INTERVAL_MS = 5e3;
44726
+ var DEFAULT_EXPLORE_EDIT_TOOLS = [
44727
+ "edit",
44728
+ "write",
44729
+ "patch",
44730
+ "multi_edit",
44731
+ "multiedit",
44732
+ "str_replace"
44733
+ ];
44734
+ var DEFAULT_EXPLORE_SEARCH_TOOLS = [
44735
+ "search",
44736
+ "grep",
44737
+ "codebase-search"
44738
+ ];
44739
+ function buildProbeTaskText(probe2) {
44740
+ const payload = { probe: probe2.probe };
44741
+ if (probe2.hint) payload.hint = probe2.hint;
44742
+ if (probe2.context) payload.context = probe2.context;
44743
+ return JSON.stringify(payload, null, 2);
44744
+ }
44745
+ function extractedPath(input) {
44746
+ if (!input || typeof input !== "object") return void 0;
44747
+ const rec = input;
44748
+ const candidate = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : void 0;
44749
+ return candidate && candidate.length > 0 ? candidate : void 0;
44750
+ }
44751
+ function looksEmpty(e) {
44752
+ if (typeof e.outputLines === "number" && e.outputLines === 0) return true;
44753
+ const out = e.output ?? "";
44754
+ return /(?:^|\n)(?:no |0 )(?:matches|results|files? found|occurrences)/i.test(out) || /total\s*:\s*0\b/i.test(out);
44755
+ }
44756
+ function extractSubjectTokens(text2) {
44757
+ const out = [];
44758
+ const fileRe = /([\w@./-]+\.(?:[cm]?[jt]sx?|json|md|py|go|rs|ya?ml))\b/g;
44759
+ for (const m of text2.matchAll(fileRe)) {
44760
+ const value = m[1];
44761
+ if (value) out.push({ kind: "file", value });
44762
+ }
44763
+ const symRe = /\b[A-Z][A-Za-z0-9_]{2,}\b/g;
44764
+ for (const m of text2.matchAll(symRe)) {
44765
+ out.push({ kind: "symbol", value: m[0] });
44766
+ }
44767
+ return out;
44768
+ }
44769
+ var ExploreCompanion = class {
44770
+ constructor(opts) {
44771
+ this.opts = opts;
44772
+ this.cfg = this.resolveConfig(opts);
44773
+ }
44774
+ opts;
44775
+ unsubscribers = [];
44776
+ /** Paths the leader has read (readSet) — feeds edit-unread + unfamiliar-read. */
44777
+ readSet = /* @__PURE__ */ new Set();
44778
+ /** subject → last probe time; cooldown gate. Survives detach/reconfigure. */
44779
+ probedAt = /* @__PURE__ */ new Map();
44780
+ /** todo id → last observed status, per leader agent. */
44781
+ todoSeen = /* @__PURE__ */ new Map();
44782
+ pending = [];
44783
+ inFlight = false;
44784
+ pollTimer;
44785
+ running = false;
44786
+ hostStarted = false;
44787
+ cfg;
44788
+ resolveConfig(opts) {
44789
+ const signals = opts.signals ?? {};
44790
+ return {
44791
+ enabled: opts.enabled ?? true,
44792
+ cooldownMs: opts.cooldownMs ?? DEFAULT_PROBE_COOLDOWN_MS,
44793
+ maxPending: opts.maxPending ?? DEFAULT_MAX_PENDING_PROBES,
44794
+ pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_MAILBOX_POLL_INTERVAL_MS,
44795
+ companionAgentId: opts.companionAgentId ?? DEFAULT_EXPLORE_COMPANION_AGENT_ID,
44796
+ signals: {
44797
+ editUnreadFile: signals.editUnreadFile ?? true,
44798
+ searchZeroHits: signals.searchZeroHits ?? true,
44799
+ unfamiliarRead: signals.unfamiliarRead ?? true,
44800
+ todoInProgress: signals.todoInProgress ?? true,
44801
+ errorSymbol: signals.errorSymbol ?? true,
44802
+ mailboxAsk: signals.mailboxAsk ?? true
44803
+ },
44804
+ fileEditTools: new Set(
44805
+ (opts.fileEditTools ?? DEFAULT_EXPLORE_EDIT_TOOLS).map((t2) => t2.toLowerCase())
44806
+ ),
44807
+ searchTools: new Set(
44808
+ (opts.searchTools ?? DEFAULT_EXPLORE_SEARCH_TOOLS).map((t2) => t2.toLowerCase())
44809
+ )
44810
+ };
44811
+ }
44812
+ /** Resolve the leader's own session id for event filtering. */
44813
+ resolveLeaderSessionId() {
44814
+ const sid = this.opts.leaderSessionId;
44815
+ return typeof sid === "function" ? sid() : sid;
44816
+ }
44817
+ /** Resolve the leader's agent id for todo diffing (optional signal). */
44818
+ resolveLeaderAgentId() {
44819
+ const aid = this.opts.leaderAgentId;
44820
+ if (!aid) return void 0;
44821
+ return typeof aid === "function" ? aid() : aid;
44822
+ }
44823
+ /** Re-apply tunables to a (possibly running) companion. */
44824
+ reconfigure(next) {
44825
+ const merged = { ...this.opts, ...next };
44826
+ const nextCfg = this.resolveConfig(merged);
44827
+ 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(
44828
+ (k) => nextCfg.signals[k] !== this.cfg.signals[k]
44829
+ );
44830
+ this.cfg = nextCfg;
44831
+ if (!changed) return false;
44832
+ if (this.hostStarted) {
44833
+ this.detach();
44834
+ this.attach();
44835
+ }
44836
+ return true;
44837
+ }
44838
+ /** Begin watching. Idempotent; a disabled companion records intent only. */
44839
+ start() {
44840
+ this.hostStarted = true;
44841
+ this.attach();
44842
+ }
44843
+ /** Stop watching and drop the host's intent to watch. */
44844
+ stop() {
44845
+ this.hostStarted = false;
44846
+ this.detach();
44847
+ }
44848
+ /** True while the watchers are attached. */
44849
+ isRunning() {
44850
+ return this.running;
44851
+ }
44852
+ /** Number of probes queued but not yet dispatched (for status surfaces). */
44853
+ pendingCount() {
44854
+ return this.pending.length;
44855
+ }
44856
+ attach() {
44857
+ if (!this.cfg.enabled || this.running) return;
44858
+ this.running = true;
44859
+ this.unsubscribers.push(
44860
+ this.opts.events.on("tool.executed", (e) => {
44861
+ const lsid = this.resolveLeaderSessionId();
44862
+ if (lsid && e.sessionId && e.sessionId !== lsid) return;
44863
+ this.trackToolExecuted(e);
44864
+ })
44865
+ );
44866
+ if (this.cfg.signals.todoInProgress && this.resolveLeaderAgentId()) {
44867
+ this.unsubscribers.push(
44868
+ this.opts.events.on("session.agents_updated", (e) => {
44869
+ const lsid = this.resolveLeaderSessionId();
44870
+ if (lsid && e.sessionId && e.sessionId !== lsid) return;
44871
+ this.trackAgentTodos(e.agents);
44872
+ })
44873
+ );
44874
+ }
44875
+ if (this.cfg.signals.errorSymbol) {
44876
+ this.unsubscribers.push(
44877
+ this.opts.events.on("error", (e) => {
44878
+ const lsid = this.resolveLeaderSessionId();
44879
+ if (lsid && e.sessionId && e.sessionId !== lsid) return;
44880
+ this.trackError(e.err);
44881
+ })
44882
+ );
44883
+ }
44884
+ if (this.cfg.signals.mailboxAsk) {
44885
+ this.pollTimer = setInterval(() => {
44886
+ void this.pollMailbox();
44887
+ }, this.cfg.pollIntervalMs);
44888
+ this.pollTimer.unref?.();
44889
+ }
44890
+ }
44891
+ /** Tear down watchers without touching host intent. Cooldowns survive. */
44892
+ detach() {
44893
+ for (const unsub of this.unsubscribers.splice(0)) unsub();
44894
+ if (this.pollTimer) {
44895
+ clearInterval(this.pollTimer);
44896
+ this.pollTimer = void 0;
44897
+ }
44898
+ this.running = false;
44899
+ }
44900
+ // ── signal handlers ──────────────────────────────────────────────────────
44901
+ trackToolExecuted(e) {
44902
+ const tool = e.name.toLowerCase();
44903
+ const path131 = extractedPath(e.input);
44904
+ if (e.ok && this.cfg.signals.editUnreadFile && this.cfg.fileEditTools.has(tool) && path131) {
44905
+ if (!this.readSet.has(path131)) {
44906
+ this.engage({
44907
+ id: randomUUID24(),
44908
+ probe: `Map file ${path131}: role, exports, dependencies, and callers \u2014 the leader is about to edit it.`,
44909
+ hint: { file: path131 },
44910
+ context: `Leader edited ${path131} without reading it first.`,
44911
+ source: "edit_unread_file",
44912
+ subject: `file:${path131}`,
44913
+ createdAt: this.now()
44914
+ });
44915
+ }
44916
+ return;
44917
+ }
44918
+ if (e.ok && this.cfg.signals.unfamiliarRead && tool === "read" && path131) {
44919
+ if (!this.readSet.has(path131)) {
44920
+ this.readSet.add(path131);
44921
+ this.engage({
44922
+ id: randomUUID24(),
44923
+ probe: `Skeleton + callers + dependents of ${path131}: what it exports, who imports it, and how it fits the feature flow.`,
44924
+ hint: { file: path131 },
44925
+ context: `Leader read unfamiliar file ${path131}.`,
44926
+ source: "unfamiliar_read",
44927
+ subject: `file:${path131}`,
44928
+ createdAt: this.now()
44929
+ });
44930
+ }
44931
+ return;
44932
+ }
44933
+ if (e.ok && this.cfg.signals.searchZeroHits && this.cfg.searchTools.has(tool) && looksEmpty(e)) {
44934
+ const input = e.input ?? {};
44935
+ const query = typeof input["query"] === "string" ? input["query"] : typeof input["pattern"] === "string" ? input["pattern"] : "";
44936
+ this.engage({
44937
+ id: randomUUID24(),
44938
+ 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.`,
44939
+ hint: query ? { symbol: query } : void 0,
44940
+ context: `${e.name} for "${query}" returned zero results.`,
44941
+ source: "search_zero_hits",
44942
+ subject: `search:${query}`,
44943
+ createdAt: this.now()
44944
+ });
44945
+ }
44946
+ }
44947
+ trackAgentTodos(agents) {
44948
+ const leaderId = this.resolveLeaderAgentId();
44949
+ if (!leaderId) return;
44950
+ const leader = agents.find((a) => a.id === leaderId);
44951
+ if (!leader?.todos) return;
44952
+ for (const todo of leader.todos) {
44953
+ const prev = this.todoSeen.get(todo.id);
44954
+ if (prev !== "in_progress" && todo.status === "in_progress") {
44955
+ const mentions = extractSubjectTokens(todo.content);
44956
+ const first = mentions[0];
44957
+ this.engage({
44958
+ id: randomUUID24(),
44959
+ probe: `Pre-map the files/symbols behind this in-progress todo: "${todo.content.slice(0, 160)}".`,
44960
+ hint: first ? { [first.kind]: first.value } : void 0,
44961
+ context: `Todo "${todo.content.slice(0, 120)}" flipped to in_progress.`,
44962
+ source: "todo_in_progress",
44963
+ subject: `todo:${todo.id}`,
44964
+ createdAt: this.now()
44965
+ });
44966
+ }
44967
+ this.todoSeen.set(todo.id, todo.status);
44968
+ }
44969
+ }
44970
+ trackError(err) {
44971
+ const tokens = extractSubjectTokens(err.message);
44972
+ for (const token of tokens.slice(0, 2)) {
44973
+ this.engage({
44974
+ id: randomUUID24(),
44975
+ probe: `What is ${token.value}, where does it live, and who uses it? The leader hit an error naming it.`,
44976
+ hint: { [token.kind]: token.value },
44977
+ context: `Error: ${err.message.slice(0, 300)}`,
44978
+ source: "error_symbol",
44979
+ subject: `token:${token.value}`,
44980
+ createdAt: this.now()
44981
+ });
44982
+ }
44983
+ }
44984
+ async pollMailbox() {
44985
+ if (!this.cfg.enabled || !this.cfg.signals.mailboxAsk) return;
44986
+ try {
44987
+ const messages = await this.opts.mailbox.query({
44988
+ unreadBy: this.cfg.companionAgentId,
44989
+ limit: 20
44990
+ });
44991
+ const lsid = this.resolveLeaderSessionId();
44992
+ for (const msg of messages) {
44993
+ if (msg.type !== "ask" && msg.type !== "assign") continue;
44994
+ const fromLeader = isMailboxLeader(msg.from) || lsid != null && msg.senderSessionId === lsid;
44995
+ if (!fromLeader) continue;
44996
+ this.engage({
44997
+ id: randomUUID24(),
44998
+ probe: msg.body.trim().slice(0, 2e3) || msg.subject,
44999
+ context: `Direct ask from ${msg.from}: ${msg.subject}`,
45000
+ source: "mailbox_ask",
45001
+ subject: `mail:${msg.id}`,
45002
+ createdAt: this.now()
45003
+ });
45004
+ await this.opts.mailbox.ack({
45005
+ messageId: msg.id,
45006
+ readerId: this.cfg.companionAgentId,
45007
+ read: true,
45008
+ completed: true
45009
+ }).catch(() => {
45010
+ });
45011
+ }
45012
+ } catch {
45013
+ }
45014
+ }
45015
+ // ── engagement ───────────────────────────────────────────────────────────
45016
+ cooldownOk(subject2) {
45017
+ const last = this.probedAt.get(subject2);
45018
+ return last === void 0 || this.now() - last >= this.cfg.cooldownMs;
45019
+ }
45020
+ now() {
45021
+ return this.opts.now ? this.opts.now() : Date.now();
45022
+ }
45023
+ engage(probe2) {
45024
+ if (!this.cfg.enabled) return;
45025
+ if (!this.cooldownOk(probe2.subject)) return;
45026
+ this.probedAt.set(probe2.subject, this.now());
45027
+ if (this.pending.length >= this.cfg.maxPending) {
45028
+ this.pending.shift();
45029
+ }
45030
+ this.pending.push(probe2);
45031
+ void this.drain();
45032
+ }
45033
+ async drain() {
45034
+ if (this.inFlight) return;
45035
+ const probe2 = this.pending.shift();
45036
+ if (!probe2) return;
45037
+ this.inFlight = true;
45038
+ try {
45039
+ await this.opts.onProbe(probe2);
45040
+ } catch {
45041
+ } finally {
45042
+ this.inFlight = false;
45043
+ if (this.pending.length > 0) void this.drain();
45044
+ }
45045
+ }
45046
+ };
45047
+
44020
45048
  // src/coordination/mailbox-codecs.ts
44021
45049
  var MailboxValidationError = class extends Error {
44022
45050
  code;
@@ -44048,6 +45076,22 @@ var SEND_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
44048
45076
  // receiver trusts the sender-asserted `sessionId`; the boundary must
44049
45077
  // refuse the field entirely.
44050
45078
  ]);
45079
+ var SEND_FORBIDDEN_FIELDS = /* @__PURE__ */ new Set([
45080
+ "from",
45081
+ "sessionAffinity"
45082
+ ]);
45083
+ function filterMailboxSendPayload(input) {
45084
+ const payload = {};
45085
+ const stripped = [];
45086
+ for (const key of Object.keys(input)) {
45087
+ if (SEND_ALLOWED_FIELDS.has(key) || SEND_FORBIDDEN_FIELDS.has(key)) {
45088
+ payload[key] = input[key];
45089
+ } else {
45090
+ stripped.push(key);
45091
+ }
45092
+ }
45093
+ return { payload, stripped };
45094
+ }
44051
45095
  var ACK_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
44052
45096
  "messageId",
44053
45097
  "read",
@@ -44285,7 +45329,9 @@ function makeMailSendTool(opts = {}) {
44285
45329
  required: ["to", "subject", "body"]
44286
45330
  },
44287
45331
  async execute(input, ctx) {
44288
- const i = input ?? {};
45332
+ const { payload: i, stripped } = filterMailboxSendPayload(
45333
+ input ?? {}
45334
+ );
44289
45335
  const rawTo = i.to;
44290
45336
  const subject2 = i.subject;
44291
45337
  const body = i.body;
@@ -44308,15 +45354,13 @@ function makeMailSendTool(opts = {}) {
44308
45354
  recipientAliases: /* @__PURE__ */ new Set([codecIdentity.baseId]),
44309
45355
  sessionId: codecIdentity.sessionId
44310
45356
  };
45357
+ let parsed;
44311
45358
  try {
44312
- parseMailboxSendInput(i, codecActor);
45359
+ parsed = parseMailboxSendInput(i, codecActor);
44313
45360
  } catch (err) {
44314
45361
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
44315
45362
  }
44316
- const audience = i.audience;
44317
- if (audience !== void 0 && audience !== "all" && audience !== "leaders") {
44318
- return { ok: false, error: '"audience" must be "all" or "leaders".' };
44319
- }
45363
+ const audience = parsed.audience;
44320
45364
  const mb = resolveMailbox(ctx);
44321
45365
  const identity2 = await register(mb, ctx);
44322
45366
  const requestedTo = normalizeRecipient(rawTo, identity2.sessionId);
@@ -44330,10 +45374,10 @@ function makeMailSendTool(opts = {}) {
44330
45374
  to: delivery.to,
44331
45375
  type: resolvedType,
44332
45376
  audience: delivery.audience,
44333
- subject: subject2,
44334
- body,
44335
- priority: i.priority ?? "normal",
44336
- replyTo: i.replyTo,
45377
+ subject: parsed.subject,
45378
+ body: parsed.body,
45379
+ priority: parsed.priority,
45380
+ replyTo: parsed.replyTo,
44337
45381
  senderSessionId: identity2.sessionId
44338
45382
  });
44339
45383
  return {
@@ -44341,7 +45385,9 @@ function makeMailSendTool(opts = {}) {
44341
45385
  messageId: msg.id,
44342
45386
  from: identity2.callerId,
44343
45387
  to: msg.to,
44344
- summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity2.callerId}.`
45388
+ // Surfacing what was stripped keeps the send auditable without
45389
+ // re-introducing the clutter into the payload itself.
45390
+ ...stripped.length > 0 ? { strippedFields: stripped, summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity2.callerId}. Ignored ${stripped.length} unrecognized field(s): ${stripped.join(", ")}.` } : { summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity2.callerId}.` }
44345
45391
  };
44346
45392
  }
44347
45393
  };
@@ -47867,7 +48913,7 @@ function createAgentMonitorService(opts) {
47867
48913
  }
47868
48914
 
47869
48915
  // src/coordination/autonomous-brain.ts
47870
- import { randomUUID as randomUUID23 } from "node:crypto";
48916
+ import { randomUUID as randomUUID25 } from "node:crypto";
47871
48917
  var AutonomousBrain = class {
47872
48918
  graph;
47873
48919
  // Fleet bus for emitting decisions — null-safe, no-op if not provided
@@ -47973,7 +49019,7 @@ var AutonomousBrain = class {
47973
49019
  consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
47974
49020
  }));
47975
49021
  return this.decideAuto({
47976
- id: randomUUID23(),
49022
+ id: randomUUID25(),
47977
49023
  source,
47978
49024
  decisionType: "spawn",
47979
49025
  question: `Should we spawn a subagent for this task?`,
@@ -48016,7 +49062,7 @@ var AutonomousBrain = class {
48016
49062
  }
48017
49063
  ];
48018
49064
  return this.decideAuto({
48019
- id: randomUUID23(),
49065
+ id: randomUUID25(),
48020
49066
  source,
48021
49067
  decisionType: "approve_change",
48022
49068
  question: `Should we approve the change "${change.title}"?`,
@@ -48075,7 +49121,7 @@ var AutonomousBrain = class {
48075
49121
  consequence: "Break the task into smaller sub-tasks"
48076
49122
  });
48077
49123
  return this.decideAuto({
48078
- id: randomUUID23(),
49124
+ id: randomUUID25(),
48079
49125
  source,
48080
49126
  decisionType: "escalate_task",
48081
49127
  question: `Task failed: ${error2.slice(0, 100)}. How should we proceed?`,
@@ -48209,12 +49255,12 @@ ${ctx.error}`);
48209
49255
  };
48210
49256
 
48211
49257
  // src/coordination/autonomous-coordinator.ts
48212
- import { randomUUID as randomUUID26 } from "node:crypto";
49258
+ import { randomUUID as randomUUID28 } from "node:crypto";
48213
49259
 
48214
49260
  // src/coordination/knowledge-graph.ts
48215
49261
  init_file_permissions();
48216
49262
  init_atomic_write();
48217
- import { randomUUID as randomUUID24 } from "node:crypto";
49263
+ import { randomUUID as randomUUID26 } from "node:crypto";
48218
49264
  import * as fsp34 from "node:fs/promises";
48219
49265
  import * as path73 from "node:path";
48220
49266
  var DEFAULT_MAX_NODES = 2e3;
@@ -48262,7 +49308,7 @@ var KnowledgeGraph = class _KnowledgeGraph {
48262
49308
  * Returns the node with its assigned id.
48263
49309
  */
48264
49310
  async add(node) {
48265
- const full = { id: randomUUID24(), ...node };
49311
+ const full = { id: randomUUID26(), ...node };
48266
49312
  this.nodes.set(full.id, full);
48267
49313
  this._trackSeq(full.id);
48268
49314
  this._addToIndex(full, this._indexKeys(full));
@@ -48391,8 +49437,8 @@ var KnowledgeGraph = class _KnowledgeGraph {
48391
49437
  if (this.subs.size >= MAX_SUBSCRIPTIONS) {
48392
49438
  throw new Error(`Knowledge graph subscription limit reached (${MAX_SUBSCRIPTIONS})`);
48393
49439
  }
48394
- const channel2 = randomUUID24();
48395
- const sub = { id: randomUUID24(), agentId, filter, channel: channel2 };
49440
+ const channel2 = randomUUID26();
49441
+ const sub = { id: randomUUID26(), agentId, filter, channel: channel2 };
48396
49442
  this.subs.set(channel2, sub);
48397
49443
  this.pendingDeliveries.set(channel2, []);
48398
49444
  return channel2;
@@ -48873,7 +49919,7 @@ var TaskDAG = class {
48873
49919
  };
48874
49920
 
48875
49921
  // src/coordination/task-auctioneer.ts
48876
- import { randomUUID as randomUUID25 } from "node:crypto";
49922
+ import { randomUUID as randomUUID27 } from "node:crypto";
48877
49923
  function isTerminalGoalStatus(status) {
48878
49924
  return status === "done" || status === "failed";
48879
49925
  }
@@ -48996,7 +50042,7 @@ var TaskAuctioneer = class {
48996
50042
  const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
48997
50043
  if (score < this.minConfidence) return false;
48998
50044
  const bid = {
48999
- id: randomUUID25(),
50045
+ id: randomUUID27(),
49000
50046
  taskId,
49001
50047
  agentId: agent.agentId,
49002
50048
  agentName: agent.agentName,
@@ -49933,7 +50979,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
49933
50979
  break;
49934
50980
  }
49935
50981
  const decision = await this.brain.decideAuto({
49936
- id: randomUUID26(),
50982
+ id: randomUUID28(),
49937
50983
  source: "system",
49938
50984
  decisionType: "prioritize_goals",
49939
50985
  question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
@@ -50848,7 +51894,7 @@ var TOKENS = {
50848
51894
  init_errors();
50849
51895
 
50850
51896
  // src/hq/factory.ts
50851
- import { createHash as createHash24, randomUUID as randomUUID29 } from "node:crypto";
51897
+ import { createHash as createHash24, randomUUID as randomUUID31 } from "node:crypto";
50852
51898
  import * as fs30 from "node:fs";
50853
51899
  import { hostname as hostname3 } from "node:os";
50854
51900
  import { basename as basename15 } from "node:path";
@@ -50856,13 +51902,13 @@ import { basename as basename15 } from "node:path";
50856
51902
  // src/hq/auth-store.ts
50857
51903
  init_file_permissions();
50858
51904
  init_atomic_write();
50859
- import { createHash as createHash23, randomBytes as randomBytes4, randomUUID as randomUUID27, scrypt, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
51905
+ import { createHash as createHash23, randomBytes as randomBytes4, randomUUID as randomUUID29, scrypt, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
50860
51906
  import * as syncFs from "node:fs";
50861
51907
  import * as fs29 from "node:fs/promises";
50862
51908
  import * as path75 from "node:path";
50863
51909
 
50864
51910
  // src/hq/auth-audit.ts
50865
- import { appendFileSync as appendFileSync2, readFileSync as readFileSync20, mkdirSync as mkdirSync9 } from "node:fs";
51911
+ import { appendFileSync as appendFileSync2, readFileSync as readFileSync21, mkdirSync as mkdirSync9 } from "node:fs";
50866
51912
  import * as path74 from "node:path";
50867
51913
  function hqAuthAuditPath(dataDir) {
50868
51914
  return path74.join(dataDir, "auth-audit.jsonl");
@@ -50871,7 +51917,7 @@ function readHqAuthAuditTail(dataDir, maxEntries = 50) {
50871
51917
  const filePath = hqAuthAuditPath(dataDir);
50872
51918
  let content;
50873
51919
  try {
50874
- content = readFileSync20(filePath, "utf8");
51920
+ content = readFileSync21(filePath, "utf8");
50875
51921
  } catch {
50876
51922
  return [];
50877
51923
  }
@@ -51183,8 +52229,8 @@ function mintHqToken(labelOrOptions) {
51183
52229
  const at = opts.now ?? Date.now();
51184
52230
  const createdAtIso = new Date(at).toISOString();
51185
52231
  return {
51186
- id: randomUUID27(),
51187
- token: randomUUID27().replace(/-/g, "") + randomUUID27().replace(/-/g, ""),
52232
+ id: randomUUID29(),
52233
+ token: randomUUID29().replace(/-/g, "") + randomUUID29().replace(/-/g, ""),
51188
52234
  createdAt: createdAtIso,
51189
52235
  ...opts.label ? { label: opts.label } : {},
51190
52236
  ...opts.ttlMs !== void 0 && Number.isFinite(opts.ttlMs) && opts.ttlMs > 0 ? { expiresAt: new Date(at + opts.ttlMs).toISOString() } : {}
@@ -51244,7 +52290,7 @@ function watchHqAuthFile(dataDir, onChange, opts = {}) {
51244
52290
  }
51245
52291
 
51246
52292
  // src/hq/publisher.ts
51247
- import { randomUUID as randomUUID28 } from "node:crypto";
52293
+ import { randomUUID as randomUUID30 } from "node:crypto";
51248
52294
  import * as v82 from "node:v8";
51249
52295
 
51250
52296
  // src/hq/protocol/governance.ts
@@ -52318,7 +53364,7 @@ var HqPublisher = class {
52318
53364
  this.options = options;
52319
53365
  this.socketFactory = options.socketFactory ?? defaultSocketFactory;
52320
53366
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
52321
- this.idFactory = options.idFactory ?? randomUUID28;
53367
+ this.idFactory = options.idFactory ?? randomUUID30;
52322
53368
  this.capabilities = options.capabilities ?? [
52323
53369
  "telemetry.publish",
52324
53370
  "mailbox.summary",
@@ -52902,7 +53948,7 @@ function createHqPublisherFromEnv(options) {
52902
53948
  const projectAlias = config.projectAlias?.trim() || void 0;
52903
53949
  const projectName = projectAlias ?? options.projectName ?? (basename15(options.projectRoot) || "unknown");
52904
53950
  const client = {
52905
- clientId: `${machineId}:${options.clientKind}:${process.pid}:${randomUUID29().slice(0, 8)}`,
53951
+ clientId: `${machineId}:${options.clientKind}:${process.pid}:${randomUUID31().slice(0, 8)}`,
52906
53952
  kind: options.clientKind,
52907
53953
  machineId,
52908
53954
  ...host ? { hostname: host } : {},
@@ -53171,40 +54217,6 @@ async function injectPendingMailboxMessages(checkMailbox2, foldFn, a, deliveryMo
53171
54217
  return interruptMsg ? { interrupt: true, interruptReason: interruptMsg.body || interruptMsg.subject || "operator interrupt" } : { interrupt: false };
53172
54218
  }
53173
54219
 
53174
- // src/core/btw.ts
53175
- var META_KEY2 = "_btwNotes";
53176
- var MAX_PENDING = 20;
53177
- function readQueue(ctx) {
53178
- const raw = ctx.meta[META_KEY2];
53179
- return Array.isArray(raw) ? raw : [];
53180
- }
53181
- function setBtwNote(ctx, text2) {
53182
- const trimmed = text2.trim();
53183
- if (!trimmed) return readQueue(ctx).length;
53184
- const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
53185
- ctx.meta[META_KEY2] = next;
53186
- return next.length;
53187
- }
53188
- function pendingBtwCount(ctx) {
53189
- return readQueue(ctx).length;
53190
- }
53191
- function consumeBtwNotes(ctx) {
53192
- const notes = readQueue(ctx);
53193
- if (notes.length > 0) delete ctx.meta[META_KEY2];
53194
- return notes;
53195
- }
53196
- function buildBtwBlock(notes) {
53197
- const body = notes.map((n) => `- ${n}`).join("\n");
53198
- return [
53199
- "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
53200
- "your current task; do not restart from scratch unless it contradicts the",
53201
- "goal:",
53202
- "",
53203
- body,
53204
- "]"
53205
- ].join("\n");
53206
- }
53207
-
53208
54220
  // src/core/fleet-pulse.ts
53209
54221
  var DEFAULT_MAX_AGENTS = 15;
53210
54222
  var DEFAULT_MAX_CHARS = 900;
@@ -53212,9 +54224,17 @@ var TASK_SNIPPET_CHARS2 = 60;
53212
54224
  function fleetPulseSignature(statuses) {
53213
54225
  return statuses.map((s) => `${s.agentId}|${s.status}|${s.currentTask ?? ""}`).sort().join("\n");
53214
54226
  }
53215
- function peerLine(s) {
54227
+ function visibleLineKey(s) {
54228
+ const role = s.role && s.role !== s.name ? s.role : "";
54229
+ const task = s.currentTask && s.currentTask.length > TASK_SNIPPET_CHARS2 ? `${s.currentTask.slice(0, TASK_SNIPPET_CHARS2)}\u2026` : s.currentTask ?? "";
54230
+ const tool = s.currentTool || "";
54231
+ const toolCalls = s.toolCalls > 0 ? String(s.toolCalls) : "";
54232
+ return [s.name, role, s.status, task, tool, toolCalls].join("\0");
54233
+ }
54234
+ function peerLine(s, count = 1) {
53216
54235
  const role = s.role && s.role !== s.name ? ` (${s.role})` : "";
53217
- const parts = [`\u2022 ${s.name}${role} \u2014 ${s.status}`];
54236
+ const grouped = count > 1 ? ` \xD7${count}` : "";
54237
+ const parts = [`\u2022 ${s.name}${role}${grouped} \u2014 ${s.status}`];
53218
54238
  if (s.currentTask) {
53219
54239
  const task = s.currentTask.length > TASK_SNIPPET_CHARS2 ? `${s.currentTask.slice(0, TASK_SNIPPET_CHARS2)}\u2026` : s.currentTask;
53220
54240
  parts.push(`"${task}"`);
@@ -53230,13 +54250,20 @@ function buildFleetPulseBlock(statuses, opts) {
53230
54250
  if (peers.length === 0) return null;
53231
54251
  const order = { running: 0, streaming: 0, waiting_user: 1, idle: 2, error: 3, offline: 4 };
53232
54252
  const sorted = [...peers].sort(
53233
- (x, y) => (order[x.status] ?? 5) - (order[y.status] ?? 5) || x.name.localeCompare(y.name)
54253
+ (x, y) => (order[x.status] ?? 5) - (order[y.status] ?? 5) || visibleLineKey(x).localeCompare(visibleLineKey(y))
53234
54254
  );
53235
54255
  const shown = sorted.slice(0, maxAgents);
53236
54256
  const hidden = sorted.length - shown.length;
53237
54257
  const parts = [];
53238
54258
  parts.push(`[FLEET PULSE] ${peers.length} peer${peers.length === 1 ? "" : "s"} online:`);
53239
- for (const s of shown) parts.push(peerLine(s));
54259
+ for (let i = 0; i < shown.length; ) {
54260
+ let run = 1;
54261
+ while (i + run < shown.length && visibleLineKey(shown[i]) === visibleLineKey(shown[i + run])) {
54262
+ run++;
54263
+ }
54264
+ parts.push(peerLine(shown[i], run));
54265
+ i += run;
54266
+ }
53240
54267
  if (hidden > 0) parts.push(`\u2026 +${hidden} more`);
53241
54268
  parts.push(
53242
54269
  "[END FLEET PULSE] (FYI \u2014 coordinate via mail_send; avoid duplicating peers' work)"
@@ -54187,7 +55214,7 @@ async function getPromptJournalEntries(projectRoot, filter = {}) {
54187
55214
  init_errors();
54188
55215
 
54189
55216
  // src/core/streaming-response-builder.ts
54190
- import { randomUUID as randomUUID31 } from "node:crypto";
55217
+ import { randomUUID as randomUUID33 } from "node:crypto";
54191
55218
  var STREAM_DRAIN_TIMEOUT_MS = 500;
54192
55219
  function buildResponse(state) {
54193
55220
  const content = [];
@@ -54247,7 +55274,7 @@ function handleContentBlockStart(state, ev) {
54247
55274
  state.textBuffers.push("");
54248
55275
  state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
54249
55276
  } else if (kind === "tool_use") {
54250
- const id = ev.id ?? randomUUID31();
55277
+ const id = ev.id ?? randomUUID33();
54251
55278
  state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
54252
55279
  state.blockOrder.push({ kind: "tool", id });
54253
55280
  state.currentTextIndex = -1;
@@ -54482,7 +55509,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
54482
55509
 
54483
55510
  // src/observability/network-telemetry.ts
54484
55511
  import { AsyncLocalStorage } from "node:async_hooks";
54485
- import { createHash as createHash25, randomUUID as randomUUID32 } from "node:crypto";
55512
+ import { createHash as createHash25, randomUUID as randomUUID34 } from "node:crypto";
54486
55513
  import { channel } from "node:diagnostics_channel";
54487
55514
  var storage = new AsyncLocalStorage();
54488
55515
  var requests = /* @__PURE__ */ new WeakMap();
@@ -54519,7 +55546,7 @@ function subscribe() {
54519
55546
  const requestBytes = numberField2(request, "contentLength");
54520
55547
  const state = {
54521
55548
  ...context,
54522
- requestId: randomUUID32(),
55549
+ requestId: randomUUID34(),
54523
55550
  ...target,
54524
55551
  ...requestBytes !== void 0 ? { requestBytes } : {},
54525
55552
  startedAt,
@@ -54654,7 +55681,7 @@ function hash3(value) {
54654
55681
  }
54655
55682
 
54656
55683
  // src/core/provider-runner.ts
54657
- import { randomUUID as randomUUID33 } from "node:crypto";
55684
+ import { randomUUID as randomUUID35 } from "node:crypto";
54658
55685
  function scrubProviderBody(body) {
54659
55686
  if (!body) return void 0;
54660
55687
  return {
@@ -54676,11 +55703,11 @@ function providerLogCtx(p, r) {
54676
55703
  }
54677
55704
  async function runProviderWithRetry(opts) {
54678
55705
  const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
54679
- const logicalRequestId = randomUUID33();
55706
+ const logicalRequestId = randomUUID35();
54680
55707
  const promptManifest = createChroniclePromptManifest(request);
54681
55708
  let attempt = 0;
54682
55709
  for (; ; ) {
54683
- const attemptId = randomUUID33();
55710
+ const attemptId = randomUUID35();
54684
55711
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
54685
55712
  const startedNs = process.hrtime.bigint();
54686
55713
  const correlation = {
@@ -59349,7 +60376,7 @@ function _resetDesignRulesCache() {
59349
60376
  }
59350
60377
 
59351
60378
  // src/execution/design-color.ts
59352
- function clamp(n, lo, hi) {
60379
+ function clamp2(n, lo, hi) {
59353
60380
  return n < lo ? lo : n > hi ? hi : n;
59354
60381
  }
59355
60382
  function parseOklch(value) {
@@ -59365,9 +60392,9 @@ function parseOklch(value) {
59365
60392
  let a = 1;
59366
60393
  if (alphaPart !== void 0) {
59367
60394
  const av = parseComponent(alphaPart.trim(), true);
59368
- if (av !== null) a = clamp(av, 0, 1);
60395
+ if (av !== null) a = clamp2(av, 0, 1);
59369
60396
  }
59370
- return [clamp(L, 0, 1), Math.max(0, C), H, a];
60397
+ return [clamp2(L, 0, 1), Math.max(0, C), H, a];
59371
60398
  }
59372
60399
  function parseComponent(s, percentIsFraction) {
59373
60400
  s = s.trim();
@@ -59386,7 +60413,7 @@ function parseAngle(s) {
59386
60413
  }
59387
60414
  function linearToSrgb(c) {
59388
60415
  const v = c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
59389
- return clamp(v, 0, 1);
60416
+ return clamp2(v, 0, 1);
59390
60417
  }
59391
60418
  function toHex2(n) {
59392
60419
  return Math.round(n * 255).toString(16).padStart(2, "0");
@@ -62133,7 +63160,7 @@ ${summaryText}` : summaryText;
62133
63160
 
62134
63161
  // src/execution/parallel-eternal-engine.ts
62135
63162
  init_error();
62136
- import { randomUUID as randomUUID34 } from "node:crypto";
63163
+ import { randomUUID as randomUUID36 } from "node:crypto";
62137
63164
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
62138
63165
  var ParallelEternalEngine = class {
62139
63166
  constructor(opts) {
@@ -62210,7 +63237,7 @@ var ParallelEternalEngine = class {
62210
63237
  this.state = "running";
62211
63238
  await this.persistState("running");
62212
63239
  const config = {
62213
- coordinatorId: `parallel-${randomUUID34().slice(0, 8)}`,
63240
+ coordinatorId: `parallel-${randomUUID36().slice(0, 8)}`,
62214
63241
  maxConcurrent: this.slots,
62215
63242
  doneCondition: { type: "all_tasks_done" }
62216
63243
  };
@@ -62264,7 +63291,7 @@ var ParallelEternalEngine = class {
62264
63291
  }
62265
63292
  if (!this.coordinator) {
62266
63293
  const config = {
62267
- coordinatorId: `parallel-${randomUUID34().slice(0, 8)}`,
63294
+ coordinatorId: `parallel-${randomUUID36().slice(0, 8)}`,
62268
63295
  maxConcurrent: this.slots,
62269
63296
  doneCondition: { type: "all_tasks_done" }
62270
63297
  };
@@ -62350,7 +63377,7 @@ ${recentJournal}` : "No prior iterations.",
62350
63377
  const task = expectDefined(tasks[i]);
62351
63378
  const route = routes[i] ?? null;
62352
63379
  const subagentId = `parallel-${this.iterations}-${i}`;
62353
- const taskId = randomUUID34();
63380
+ const taskId = randomUUID36();
62354
63381
  const personaLine = route ? `Acting agent: ${route.definition.config.name} \u2014 ${route.definition.capability.summary}
62355
63382
  ` : "";
62356
63383
  const spec = {
@@ -63978,7 +65005,7 @@ function readPolicy(ctx) {
63978
65005
  }
63979
65006
 
63980
65007
  // src/execution/tool-executor.ts
63981
- import { randomUUID as randomUUID37 } from "node:crypto";
65008
+ import { randomUUID as randomUUID39 } from "node:crypto";
63982
65009
  import * as fs38 from "node:fs/promises";
63983
65010
  import * as path85 from "node:path";
63984
65011
  init_errors();
@@ -64000,7 +65027,7 @@ var ToolErrorCategory = /* @__PURE__ */ ((ToolErrorCategory2) => {
64000
65027
  })(ToolErrorCategory || {});
64001
65028
 
64002
65029
  // src/execution/tool-executor-support.ts
64003
- import { createHash as createHash29, randomUUID as randomUUID35 } from "node:crypto";
65030
+ import { createHash as createHash29, randomUUID as randomUUID37 } from "node:crypto";
64004
65031
  import * as fs37 from "node:fs/promises";
64005
65032
  import * as path83 from "node:path";
64006
65033
  init_errors();
@@ -64132,7 +65159,7 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
64132
65159
  await fs37.mkdir(dir, { recursive: true });
64133
65160
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
64134
65161
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
64135
- const filePath = path83.join(dir, `${stamp}-${safeTool}-${randomUUID35()}.log`);
65162
+ const filePath = path83.join(dir, `${stamp}-${safeTool}-${randomUUID37()}.log`);
64136
65163
  await fs37.writeFile(filePath, content, "utf8");
64137
65164
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
64138
65165
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
@@ -64682,7 +65709,7 @@ ${errorDetails}`,
64682
65709
  }
64683
65710
 
64684
65711
  // src/execution/tool-executor-runner.ts
64685
- import { randomUUID as randomUUID36 } from "node:crypto";
65712
+ import { randomUUID as randomUUID38 } from "node:crypto";
64686
65713
 
64687
65714
  // src/observability/process-telemetry.ts
64688
65715
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -64906,7 +65933,7 @@ async function runToolWithTimeout(tool, input, parentSignal, ctx, opts, config,
64906
65933
  progressTailChars: config.progressTailChars,
64907
65934
  progressHeadChars: config.progressHeadChars
64908
65935
  }) : (async () => tool.execute(input, ctx, { signal: combined }))();
64909
- const telemetryToolCallId = toolUseId ?? `nested-${randomUUID36()}`;
65936
+ const telemetryToolCallId = toolUseId ?? `nested-${randomUUID38()}`;
64910
65937
  const toolPromise = opts.events ? runWithNetworkTelemetry(
64911
65938
  {
64912
65939
  events: opts.events,
@@ -65299,7 +66326,7 @@ ${post.additionalContext}`;
65299
66326
  const bridge = async (toolName, input) => {
65300
66327
  const nestedUse = {
65301
66328
  type: "tool_use",
65302
- id: `nested-${randomUUID37()}`,
66329
+ id: `nested-${randomUUID39()}`,
65303
66330
  name: toolName,
65304
66331
  input
65305
66332
  };
@@ -65904,13 +66931,13 @@ import * as fs39 from "node:fs/promises";
65904
66931
  import * as path87 from "node:path";
65905
66932
 
65906
66933
  // src/types/mode-prompts.ts
65907
- import { readFileSync as readFileSync23, statSync as statSync9 } from "node:fs";
66934
+ import { readFileSync as readFileSync24, statSync as statSync9 } from "node:fs";
65908
66935
  import * as path86 from "node:path";
65909
66936
  import { fileURLToPath as fileURLToPath9 } from "node:url";
65910
66937
  function modePrompt(id) {
65911
66938
  for (const dir of modePromptDirCandidates()) {
65912
66939
  try {
65913
- return readFileSync23(path86.join(dir, `${id}.md`), "utf8").trimEnd();
66940
+ return readFileSync24(path86.join(dir, `${id}.md`), "utf8").trimEnd();
65914
66941
  } catch {
65915
66942
  }
65916
66943
  }
@@ -66591,7 +67618,17 @@ function normalizeModelsDevModel(model) {
66591
67618
  const reasoningConfig = {
66592
67619
  default: disableSupported ? "enabled" : "always_on",
66593
67620
  disableSupported,
66594
- effortSupported: effortLevels.length > 0,
67621
+ // Tri-state (see ReasoningConfig.effortSupported):
67622
+ // options present → documented answer (true when effort values exist;
67623
+ // an explicitly EMPTY array is a documented "no
67624
+ // effort control", not an absent field).
67625
+ // field ABSENT → the model is known to reason but its vocabulary is
67626
+ // undocumented → `undefined`, so the resolver forwards
67627
+ // the request and each wire adapter applies its own
67628
+ // transport gating. Sending `false` here would make
67629
+ // the resolver claim "does not support effort" — an
67630
+ // assertion the catalog never made.
67631
+ ...raw === void 0 ? {} : { effortSupported: effortLevels.length > 0 },
66595
67632
  effortLevels,
66596
67633
  preserveThinking: model.interleaved ? "always_on" : "unsupported"
66597
67634
  };
@@ -67180,9 +68217,9 @@ async function startMetricsServer(opts) {
67180
68217
  let server;
67181
68218
  if (useHttps && tls) {
67182
68219
  const { createServer } = await import("node:https");
67183
- const { readFileSync: readFileSync25 } = await import("node:fs");
68220
+ const { readFileSync: readFileSync26 } = await import("node:fs");
67184
68221
  server = createServer(
67185
- { cert: readFileSync25(tls.cert), key: readFileSync25(tls.key) },
68222
+ { cert: readFileSync26(tls.cert), key: readFileSync26(tls.key) },
67186
68223
  listener
67187
68224
  );
67188
68225
  } else {
@@ -69148,7 +70185,7 @@ function deepFreeze(obj) {
69148
70185
  init_errors();
69149
70186
  init_atomic_write();
69150
70187
  init_error();
69151
- import { randomUUID as randomUUID38 } from "node:crypto";
70188
+ import { randomUUID as randomUUID40 } from "node:crypto";
69152
70189
  import * as fsp38 from "node:fs/promises";
69153
70190
  function assertPlanMutationInvariants(previous, updated) {
69154
70191
  const ids = updated.items.map((item) => item.id);
@@ -69270,7 +70307,7 @@ function emptyPlan(sessionId, title) {
69270
70307
  function addPlanItem(plan, title, details) {
69271
70308
  const now = (/* @__PURE__ */ new Date()).toISOString();
69272
70309
  const item = {
69273
- id: `plan_${Date.now()}_${randomUUID38().slice(0, 6)}`,
70310
+ id: `plan_${Date.now()}_${randomUUID40().slice(0, 6)}`,
69274
70311
  title,
69275
70312
  details,
69276
70313
  status: "open",
@@ -69342,7 +70379,7 @@ function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
69342
70379
  if (subtasks && subtasks.length > 0) {
69343
70380
  for (const st of subtasks) {
69344
70381
  todos.push({
69345
- id: `todo_${Date.now()}_${randomUUID38().slice(0, 6)}`,
70382
+ id: `todo_${Date.now()}_${randomUUID40().slice(0, 6)}`,
69346
70383
  content: st,
69347
70384
  status: "pending",
69348
70385
  promotedFromPlan: item.id
@@ -73339,7 +74376,7 @@ function resolveReasoningForRequest(settings, rc, warnings) {
73339
74376
  const cfg = settings.reasoning;
73340
74377
  if (!cfg) return void 0;
73341
74378
  const capKnown = rc !== void 0;
73342
- const supportsReasoning = rc ? rc.default !== "disabled" || rc.disableSupported || rc.effortSupported : false;
74379
+ const supportsReasoning = rc ? rc.default !== "disabled" || rc.disableSupported || rc.effortSupported !== false : false;
73343
74380
  const out = {};
73344
74381
  if (cfg.mode === "off") {
73345
74382
  if (capKnown && rc?.disableSupported) {
@@ -73361,14 +74398,17 @@ function resolveReasoningForRequest(settings, rc, warnings) {
73361
74398
  }
73362
74399
  const effort = cfg.effort;
73363
74400
  if (effort !== void 0) {
73364
- if (capKnown && rc?.effortSupported && rc.effortLevels.includes(effort)) {
73365
- out.effort = effort;
73366
- } else if (capKnown && rc?.effortSupported) {
74401
+ if (!capKnown) {
74402
+ } else if (rc?.effortSupported === false) {
74403
+ warnings.push(
74404
+ `reasoning effort "${effort}" requested, but this model does not support effort control; the setting was omitted.`
74405
+ );
74406
+ } else if (rc?.effortSupported === true && rc.effortLevels.length > 0 && !rc.effortLevels.includes(effort)) {
73367
74407
  warnings.push(
73368
74408
  `reasoning effort "${effort}" not supported by this model (supported: ${rc.effortLevels.join(", ")}); the setting was omitted.`
73369
74409
  );
73370
- } else if (capKnown) {
73371
- warnings.push(`reasoning effort "${effort}" requested, but this model does not support effort; the setting was omitted.`);
74410
+ } else {
74411
+ out.effort = effort;
73372
74412
  }
73373
74413
  }
73374
74414
  if (cfg.preserve !== void 0) {
@@ -75244,6 +76284,13 @@ var PhaseOrchestrator = class {
75244
76284
  events;
75245
76285
  stopped = false;
75246
76286
  paused = false;
76287
+ /**
76288
+ * Run-wide abort source. stop() aborts it; every in-flight
76289
+ * ctx.executeTask call observes the abort through its per-task signal
76290
+ * (composed from this controller and the task's own timeout controller).
76291
+ * Recreated by start() so a stopped orchestrator can be reused.
76292
+ */
76293
+ stopController = new AbortController();
75247
76294
  runningPhases = /* @__PURE__ */ new Set();
75248
76295
  tickInterval = null;
75249
76296
  trackerCache = /* @__PURE__ */ new Map();
@@ -75286,6 +76333,7 @@ var PhaseOrchestrator = class {
75286
76333
  async start() {
75287
76334
  this.stopped = false;
75288
76335
  this.paused = false;
76336
+ this.stopController = new AbortController();
75289
76337
  this.normalizeForResume();
75290
76338
  this.graph.startedAt = Date.now();
75291
76339
  this.graph.updatedAt = Date.now();
@@ -75345,6 +76393,7 @@ var PhaseOrchestrator = class {
75345
76393
  /** Stop completely, including active phases. */
75346
76394
  stop() {
75347
76395
  this.stopped = true;
76396
+ this.stopController.abort();
75348
76397
  if (this.tickInterval) {
75349
76398
  clearInterval(this.tickInterval);
75350
76399
  this.tickInterval = null;
@@ -75421,6 +76470,7 @@ var PhaseOrchestrator = class {
75421
76470
  return;
75422
76471
  }
75423
76472
  await this.executePhaseTasks(phase);
76473
+ if (this.stopped) return;
75424
76474
  const failedTasks = this.getFailedTaskCount(phase);
75425
76475
  const completedTasks = this.getCompletedTaskCount(phase);
75426
76476
  this.emit("phase.allTasksDone", {
@@ -75580,34 +76630,47 @@ var PhaseOrchestrator = class {
75580
76630
  agentName: task.assignee
75581
76631
  });
75582
76632
  const handle = this.phaseWorktrees.get(phase.id);
75583
- const taskPromise = this.ctx.executeTask(task, phase.id, {
75584
- cwd: handle?.dir,
75585
- branch: handle?.branch
75586
- });
75587
- if (this.opts.taskTimeoutMs > 0) {
75588
- const timeoutMs = this.opts.taskTimeoutMs;
75589
- const timedOut = /* @__PURE__ */ Symbol("timed_out");
75590
- const result = await Promise.race([
75591
- taskPromise,
75592
- new Promise((resolve57) => {
75593
- const timer = setTimeout(() => resolve57(timedOut), timeoutMs);
75594
- taskPromise.then(() => clearTimeout(timer)).catch(() => clearTimeout(timer));
75595
- })
75596
- ]);
75597
- if (result === timedOut) {
75598
- this.emit("phase.taskTimedOut", {
75599
- phaseId: phase.id,
75600
- taskId: task.id,
75601
- taskTitle: task.title,
75602
- timeoutMs
75603
- });
75604
- throw new Error(
75605
- `Task "${task.title}" (${task.id}) exceeded timeout of ${timeoutMs} ms`
75606
- );
75607
- }
75608
- return result;
75609
- }
75610
- return taskPromise;
76633
+ const timeoutController = this.opts.taskTimeoutMs > 0 ? new AbortController() : void 0;
76634
+ const signal = timeoutController ? AbortSignal.any([this.stopController.signal, timeoutController.signal]) : this.stopController.signal;
76635
+ const taskPromise = this.ctx.executeTask(
76636
+ task,
76637
+ phase.id,
76638
+ { cwd: handle?.dir, branch: handle?.branch },
76639
+ signal
76640
+ );
76641
+ if (!timeoutController) return taskPromise;
76642
+ const timeoutMs = this.opts.taskTimeoutMs;
76643
+ const timedOut = /* @__PURE__ */ Symbol("timed_out");
76644
+ const result = await Promise.race([
76645
+ taskPromise,
76646
+ new Promise((resolve57) => {
76647
+ const timer = setTimeout(() => {
76648
+ timeoutController.abort();
76649
+ resolve57(timedOut);
76650
+ }, timeoutMs);
76651
+ taskPromise.then(() => clearTimeout(timer)).catch(() => clearTimeout(timer));
76652
+ })
76653
+ ]);
76654
+ if (result !== timedOut) return result;
76655
+ this.emit("phase.taskTimedOut", {
76656
+ phaseId: phase.id,
76657
+ taskId: task.id,
76658
+ taskTitle: task.title,
76659
+ timeoutMs
76660
+ });
76661
+ const settled = taskPromise.then(
76662
+ () => void 0,
76663
+ () => void 0
76664
+ );
76665
+ const grace = new Promise((resolve57) => {
76666
+ const timer = setTimeout(resolve57, 5e3);
76667
+ timer.unref?.();
76668
+ void settled.then(() => clearTimeout(timer));
76669
+ });
76670
+ await Promise.race([settled, grace]);
76671
+ throw new Error(
76672
+ `Task "${task.title}" (${task.id}) exceeded timeout of ${timeoutMs} ms`
76673
+ );
75611
76674
  }
75612
76675
  markTaskCompleted(phase, task) {
75613
76676
  const tracker = this.getTrackerForPhase(phase);
@@ -75622,6 +76685,10 @@ var PhaseOrchestrator = class {
75622
76685
  const tracker = this.getTrackerForPhase(phase);
75623
76686
  const taskKey = `${phase.id}:${task.id}`;
75624
76687
  const currentRetries = this.taskRetryCounts.get(taskKey) ?? 0;
76688
+ if (this.stopped) {
76689
+ tracker.updateNodeStatus(task.id, "pending", "Stopped before completion");
76690
+ return;
76691
+ }
75625
76692
  if (currentRetries < this.opts.maxRetries) {
75626
76693
  this.taskRetryCounts.set(taskKey, currentRetries + 1);
75627
76694
  tracker.updateNodeStatus(
@@ -80934,7 +82001,8 @@ async function loadPlugins(plugins, opts) {
80934
82001
  plugin,
80935
82002
  resolution.options
80936
82003
  );
80937
- const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log, opts.enforceCapabilities) : rawApi;
82004
+ const enforceForPlugin = typeof opts.enforceCapabilities === "function" ? opts.enforceCapabilities(plugin) : opts.enforceCapabilities ?? false;
82005
+ const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log, enforceForPlugin) : rawApi;
80938
82006
  registration = {
80939
82007
  plugin,
80940
82008
  api,
@@ -81765,7 +82833,7 @@ async function snapshotChangedFiles(cwd) {
81765
82833
  }
81766
82834
 
81767
82835
  // src/plugins/review-claim-registry.ts
81768
- import { createHash as createHash33, randomUUID as randomUUID39 } from "node:crypto";
82836
+ import { createHash as createHash33, randomUUID as randomUUID41 } from "node:crypto";
81769
82837
  import * as fsp46 from "node:fs/promises";
81770
82838
  import { hostname as hostname5 } from "node:os";
81771
82839
  import * as path103 from "node:path";
@@ -81828,7 +82896,7 @@ async function breakStaleLock(lockPath) {
81828
82896
  return false;
81829
82897
  }
81830
82898
  async function breakLockAtomically(lockPath) {
81831
- const tombstone = `${lockPath}.stale-${randomUUID39()}.tmp`;
82899
+ const tombstone = `${lockPath}.stale-${randomUUID41()}.tmp`;
81832
82900
  try {
81833
82901
  await fsp46.rename(lockPath, tombstone);
81834
82902
  } catch {
@@ -81888,7 +82956,7 @@ async function withStoreLock(storeDir, fn, waitMs = LOCK_WAIT_MS) {
81888
82956
  }
81889
82957
  }
81890
82958
  var MAX_LEDGER_LINES = 1e4;
81891
- var HOST_SID = randomUUID39();
82959
+ var HOST_SID = randomUUID41();
81892
82960
  var claimsByEventBus = /* @__PURE__ */ new WeakMap();
81893
82961
  var startedReviews = /* @__PURE__ */ new WeakMap();
81894
82962
  var pendingStartedReviews = /* @__PURE__ */ new WeakMap();
@@ -81969,7 +83037,7 @@ async function compactLedger(storeDir, active) {
81969
83037
  );
81970
83038
  }
81971
83039
  }
81972
- const tmp = `${claimsFilePath(storeDir)}.tmp-${randomUUID39()}`;
83040
+ const tmp = `${claimsFilePath(storeDir)}.tmp-${randomUUID41()}`;
81973
83041
  await fsp46.writeFile(tmp, lines.length > 0 ? `${lines.join("\n")}
81974
83042
  ` : "", "utf8");
81975
83043
  let replaced = false;
@@ -82898,7 +83966,7 @@ init_review_finding_store();
82898
83966
 
82899
83967
  // src/plugins/review-finding-parser.ts
82900
83968
  init_review_finding_types();
82901
- import { randomUUID as randomUUID41 } from "node:crypto";
83969
+ import { randomUUID as randomUUID43 } from "node:crypto";
82902
83970
  var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
82903
83971
  var CATEGORIES = /* @__PURE__ */ new Set([
82904
83972
  "bug",
@@ -82970,7 +84038,7 @@ function parseChimeraReviewReport(reportText, context = {}) {
82970
84038
  }
82971
84039
  const structured = extractStructuredFindingsBlock(reportText);
82972
84040
  if (structured) {
82973
- const reportId2 = context.reportId ?? randomUUID41();
84041
+ const reportId2 = context.reportId ?? randomUUID43();
82974
84042
  const findings2 = structured.findings.map(
82975
84043
  (item) => buildFindingFromStructuredItem(item, { ...context, reportId: reportId2 })
82976
84044
  );
@@ -82982,7 +84050,7 @@ function parseChimeraReviewReport(reportText, context = {}) {
82982
84050
  };
82983
84051
  }
82984
84052
  const findings = [];
82985
- const reportId = context.reportId ?? randomUUID41();
84053
+ const reportId = context.reportId ?? randomUUID43();
82986
84054
  let unparseableCount = 0;
82987
84055
  let durationSeconds;
82988
84056
  let currentSeverity = null;
@@ -83083,7 +84151,7 @@ function parseFindingSegment(segment, severity, context) {
83083
84151
  const fullDesc = suggestions.length > 0 ? cleanDesc + "\n" + suggestions.map((s) => " \u2192 " + s).join("\n") : cleanDesc;
83084
84152
  const suggestedFix = suggestions.length > 0 ? suggestions.join("\n") : void 0;
83085
84153
  return {
83086
- id: randomUUID41(),
84154
+ id: randomUUID43(),
83087
84155
  fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
83088
84156
  severity,
83089
84157
  source: normalizeFindingSource(context.reviewType),
@@ -83094,7 +84162,7 @@ function parseFindingSegment(segment, severity, context) {
83094
84162
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
83095
84163
  status: "active",
83096
84164
  originReport: {
83097
- reportId: context.reportId ?? randomUUID41(),
84165
+ reportId: context.reportId ?? randomUUID43(),
83098
84166
  sessionId: context.sessionId ?? "",
83099
84167
  agentId: context.agentId ?? "",
83100
84168
  reviewerModel: context.reviewerModel ?? ""
@@ -83118,7 +84186,7 @@ function buildFindingFromStructuredItem(item, context) {
83118
84186
  const title = item.title;
83119
84187
  const description = item.description ?? title;
83120
84188
  return {
83121
- id: randomUUID41(),
84189
+ id: randomUUID43(),
83122
84190
  fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
83123
84191
  severity: item.severity,
83124
84192
  source: normalizeFindingSource(context.reviewType),
@@ -83131,7 +84199,7 @@ function buildFindingFromStructuredItem(item, context) {
83131
84199
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
83132
84200
  status: "active",
83133
84201
  originReport: {
83134
- reportId: context.reportId ?? randomUUID41(),
84202
+ reportId: context.reportId ?? randomUUID43(),
83135
84203
  sessionId: context.sessionId ?? "",
83136
84204
  agentId: context.agentId ?? "",
83137
84205
  reviewerModel: context.reviewerModel ?? ""
@@ -88782,7 +89850,7 @@ var ReplayProviderRunner = class {
88782
89850
  };
88783
89851
 
88784
89852
  // src/session-catalog/store.ts
88785
- import { randomBytes as randomBytes8, randomUUID as randomUUID42 } from "node:crypto";
89853
+ import { randomBytes as randomBytes8, randomUUID as randomUUID44 } from "node:crypto";
88786
89854
  import * as fs59 from "node:fs";
88787
89855
  import * as path119 from "node:path";
88788
89856
  init_atomic_write();
@@ -89133,7 +90201,7 @@ var SessionCatalogStore = class {
89133
90201
  if (!Number.isSafeInteger(entry.pid) || entry.pid <= 0)
89134
90202
  throw new TypeError("Invalid owner pid");
89135
90203
  const now = Date.now();
89136
- const leaseId = randomUUID42();
90204
+ const leaseId = randomUUID44();
89137
90205
  const leaseSecret = randomBytes8(32).toString("hex");
89138
90206
  const expiresAt = now + boundedMs(leaseMs, SESSION_CATALOG_DEFAULT_LEASE_MS, MAX_LEASE_MS);
89139
90207
  this.db.prepare(`INSERT INTO session_leases(
@@ -89205,7 +90273,7 @@ var SessionCatalogStore = class {
89205
90273
  const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
89206
90274
  if (!catalog && !fs59.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
89207
90275
  throw new Error(`Session not found: ${targetSessionId}`);
89208
- const reservationId = randomUUID42();
90276
+ const reservationId = randomUUID44();
89209
90277
  const now = Date.now();
89210
90278
  const expiresAt = now + boundedMs(reservationMs, SESSION_CATALOG_DEFAULT_RESERVATION_MS, MAX_RESERVATION_MS);
89211
90279
  try {
@@ -89501,7 +90569,7 @@ var SessionCatalogStore = class {
89501
90569
  "SELECT 1 AS yes FROM resume_reservations WHERE target_session_id=? AND expires_at>?"
89502
90570
  ).get(sessionId, Date.now());
89503
90571
  if (reservation) throw conflict(`Session ${sessionId} is reserved for resume`);
89504
- const leaseId = randomUUID42();
90572
+ const leaseId = randomUUID44();
89505
90573
  const now = Date.now();
89506
90574
  const expiresAt = now + boundedMs(leaseMs, 6e4, MAX_MAINTENANCE_MS);
89507
90575
  try {
@@ -90459,7 +91527,7 @@ import * as fs62 from "node:fs/promises";
90459
91527
  import * as path122 from "node:path";
90460
91528
 
90461
91529
  // src/session-registry-atomic-file.ts
90462
- import { randomUUID as randomUUID43 } from "node:crypto";
91530
+ import { randomUUID as randomUUID45 } from "node:crypto";
90463
91531
  import * as fs61 from "node:fs/promises";
90464
91532
  import { hostname as hostname6 } from "node:os";
90465
91533
  import * as path121 from "node:path";
@@ -90532,7 +91600,7 @@ async function breakStaleLockVerified2(lockPath, verify) {
90532
91600
  return await breakLockAtomically2(lockPath) === true;
90533
91601
  }
90534
91602
  async function breakLockAtomically2(lockPath) {
90535
- const tombstone = `${lockPath}.stale-${randomUUID43()}.tmp`;
91603
+ const tombstone = `${lockPath}.stale-${randomUUID45()}.tmp`;
90536
91604
  try {
90537
91605
  await fs61.rename(lockPath, tombstone);
90538
91606
  } catch {
@@ -90544,7 +91612,7 @@ async function breakLockAtomically2(lockPath) {
90544
91612
  async function writeAtomicFile(filePath, registry2) {
90545
91613
  const tmp = path121.join(
90546
91614
  path121.dirname(filePath),
90547
- `.${path121.basename(filePath)}.${randomUUID43().slice(0, 8)}.tmp`
91615
+ `.${path121.basename(filePath)}.${randomUUID45().slice(0, 8)}.tmp`
90548
91616
  );
90549
91617
  let tmpPersisted = false;
90550
91618
  try {
@@ -91107,7 +92175,7 @@ var SessionRegistry = class {
91107
92175
  init_errors();
91108
92176
  init_atomic_write();
91109
92177
  init_error();
91110
- import { randomUUID as randomUUID44 } from "node:crypto";
92178
+ import { randomUUID as randomUUID46 } from "node:crypto";
91111
92179
  import * as fs63 from "node:fs/promises";
91112
92180
  var FILE_VERSION = 1;
91113
92181
  var MAX_TEXT_LENGTH = 2e3;
@@ -91205,7 +92273,7 @@ var AnnotationsStore = class {
91205
92273
  });
91206
92274
  }
91207
92275
  const annotation = {
91208
- id: randomUUID44(),
92276
+ id: randomUUID46(),
91209
92277
  sessionId: input.sessionId,
91210
92278
  atEventIndex: input.atEventIndex,
91211
92279
  authorId: input.authorId,
@@ -91558,7 +92626,7 @@ var InputHistoryStore = class {
91558
92626
 
91559
92627
  // src/storage/memory-backend.ts
91560
92628
  init_file_permissions();
91561
- import { randomUUID as randomUUID45 } from "node:crypto";
92629
+ import { randomUUID as randomUUID47 } from "node:crypto";
91562
92630
  import * as fs65 from "node:fs/promises";
91563
92631
  import * as path124 from "node:path";
91564
92632
 
@@ -91786,7 +92854,7 @@ var FileMemoryBackend = class {
91786
92854
  }
91787
92855
  async remember(scope, entry, filePath) {
91788
92856
  const file = this.resolveFile(filePath, scope);
91789
- const id = `mem_${Date.now()}_${randomUUID45().slice(0, 8)}`;
92857
+ const id = `mem_${Date.now()}_${randomUUID47().slice(0, 8)}`;
91790
92858
  const meta = formatMetadata(entry);
91791
92859
  const line = `- [${entry.ts}] ${id}${meta} ${entry.text.replace(/\n/g, " ")}
91792
92860
  `;
@@ -93800,7 +94868,7 @@ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
93800
94868
  init_file_permissions();
93801
94869
  init_atomic_write();
93802
94870
  init_error();
93803
- import { createHash as createHash39, randomUUID as randomUUID46 } from "node:crypto";
94871
+ import { createHash as createHash39, randomUUID as randomUUID48 } from "node:crypto";
93804
94872
  import * as fs70 from "node:fs/promises";
93805
94873
  var GENESIS_PREV = "0".repeat(64);
93806
94874
  var DEFAULT_FSYNC_EVERY = 100;
@@ -93845,7 +94913,7 @@ var ToolAuditLog = class {
93845
94913
  const tip = await this._resolveChainTip(input.sessionId, fp);
93846
94914
  const prevHash = tip.prevHash;
93847
94915
  const index = tip.nextIndex;
93848
- const id = randomUUID46();
94916
+ const id = randomUUID48();
93849
94917
  const ts = (/* @__PURE__ */ new Date()).toISOString();
93850
94918
  const content = {
93851
94919
  id,
@@ -96386,7 +97454,7 @@ var DEFAULT_SPEC_TEMPLATE = {
96386
97454
  // src/worktree/worktree-manager.ts
96387
97455
  init_error();
96388
97456
  import { mkdir as mkdir35, readFile as readFile73 } from "node:fs/promises";
96389
- import { join as join102, resolve as resolve56, sep as sep9 } from "node:path";
97457
+ import { join as join103, resolve as resolve56, sep as sep9 } from "node:path";
96390
97458
 
96391
97459
  // src/worktree/worktree-git.ts
96392
97460
  import { spawn as spawn13 } from "node:child_process";
@@ -96618,7 +97686,7 @@ var WorktreeManager = class {
96618
97686
  }
96619
97687
  const slug = this.makeSlug(opts.slugHint ?? ownerId);
96620
97688
  const branch = `wstack/ap/${slug}`;
96621
- const dir = join102(this.worktreesRoot(), slug);
97689
+ const dir = join103(this.worktreesRoot(), slug);
96622
97690
  const absDir = resolve56(dir);
96623
97691
  const absRoot = resolve56(this.projectRoot);
96624
97692
  if (!absDir.startsWith(absRoot + sep9)) {
@@ -97009,7 +98077,7 @@ ${merged.stderr}`);
97009
98077
  const startMarker = /^(?:<{7,}(?: |$)|\|{7,}(?: |$))/m;
97010
98078
  for (const rel of paths) {
97011
98079
  try {
97012
- const content = (await readFile73(join102(this.projectRoot, rel), "utf8")).replace(/\r/g, "");
98080
+ const content = (await readFile73(join103(this.projectRoot, rel), "utf8")).replace(/\r/g, "");
97013
98081
  const lines = content.split("\n");
97014
98082
  let seenStart = false;
97015
98083
  for (const line of lines) {
@@ -97053,7 +98121,7 @@ ${merged.stderr}`);
97053
98121
  }
97054
98122
  // ── internals ────────────────────────────────────────────────────────────
97055
98123
  worktreesRoot() {
97056
- return join102(this.projectRoot, ".wrongstack", "worktrees");
98124
+ return join103(this.projectRoot, ".wrongstack", "worktrees");
97057
98125
  }
97058
98126
  async detectBaseBranch() {
97059
98127
  const head = await this.runGit(["rev-parse", "--abbrev-ref", "HEAD"], this.projectRoot);
@@ -97240,11 +98308,17 @@ export {
97240
98308
  DEFAULT_DIRECTOR_PREAMBLE,
97241
98309
  DEFAULT_DISPATCH_ROLE,
97242
98310
  DEFAULT_EAGER_SKILL_LIMIT,
98311
+ DEFAULT_EXPLORE_COMPANION_AGENT_ID,
98312
+ DEFAULT_EXPLORE_EDIT_TOOLS,
98313
+ DEFAULT_EXPLORE_SEARCH_TOOLS,
97243
98314
  DEFAULT_FILE_EDIT_TOOLS,
97244
98315
  DEFAULT_HQ_REDACTION_POLICY,
98316
+ DEFAULT_MAILBOX_POLL_INTERVAL_MS,
97245
98317
  DEFAULT_MAX_FLEET_SPAWNS,
97246
98318
  DEFAULT_MAX_ITERATIONS,
98319
+ DEFAULT_MAX_PENDING_PROBES,
97247
98320
  DEFAULT_MODES,
98321
+ DEFAULT_PROBE_COOLDOWN_MS,
97248
98322
  DEFAULT_QUALITY_CHECKS,
97249
98323
  DEFAULT_REFINER_RETRY_FEEDBACK,
97250
98324
  DEFAULT_SESSION_LOGGING_CONFIG,
@@ -97304,6 +98378,7 @@ export {
97304
98378
  EscalationRoutingBrainArbiter,
97305
98379
  EternalAutonomyEngine,
97306
98380
  EventBus,
98381
+ ExploreCompanion,
97307
98382
  ExtensionRegistry,
97308
98383
  FALLBACK_CHAIN_MANAGE_TOOL_NAME,
97309
98384
  FALLBACK_PROFILE_MANAGE_TOOL_NAME,
@@ -97464,6 +98539,7 @@ export {
97464
98539
  QUEUE_MAX_ITEMS,
97465
98540
  QUEUE_MAX_ITEM_BYTES,
97466
98541
  QueueStore,
98542
+ REASONING_EFFORT_LEVELS,
97467
98543
  REFACTOR_PLANNER_AGENT,
97468
98544
  REPORT_STORE_FILE,
97469
98545
  REVIEW_AGENTS,
@@ -97604,6 +98680,7 @@ export {
97604
98680
  buildNamespacePayloads,
97605
98681
  buildOtlpMetricsRequest,
97606
98682
  buildOtlpTracesRequest,
98683
+ buildProbeTaskText,
97607
98684
  buildProjectContextualizedPrompt,
97608
98685
  buildQueuedMessagesBlock,
97609
98686
  buildRecoveryAlert,
@@ -97926,6 +99003,7 @@ export {
97926
99003
  isPrivateIPv6,
97927
99004
  isProjectId,
97928
99005
  isProvenDirective,
99006
+ isReasoningEffort,
97929
99007
  isRetryableKind,
97930
99008
  isSafePathSegment,
97931
99009
  isSddError,
@@ -98000,6 +99078,7 @@ export {
98000
99078
  makeMailInboxTool,
98001
99079
  makeMailSendTool,
98002
99080
  makeMailboxTool,
99081
+ makeMutationTestTool,
98003
99082
  makeQualityGateTool,
98004
99083
  makeRollUpTool,
98005
99084
  makeSpawnTool,