@wrongstack/core 0.308.7 → 0.309.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/coordination/director/director-toolset.d.ts +2 -2
  2. package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
  3. package/dist/coordination/director-tools.d.ts +2 -0
  4. package/dist/coordination/director.d.ts +16 -0
  5. package/dist/coordination/explore-companion.d.ts +9 -6
  6. package/dist/coordination/fleet.d.ts +12 -0
  7. package/dist/coordination/index.d.ts +1 -1
  8. package/dist/coordination/index.js +990 -61
  9. package/dist/coordination/mail-tools.d.ts +10 -6
  10. package/dist/coordination/mailbox-codecs.d.ts +31 -0
  11. package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
  12. package/dist/coordination/multi-agent-timeout.d.ts +11 -1
  13. package/dist/coordination/mutation-engine.d.ts +76 -0
  14. package/dist/coordination/subagent-budget.d.ts +54 -0
  15. package/dist/coordination/subagent-finish.d.ts +78 -0
  16. package/dist/core/index.js +58 -13
  17. package/dist/defaults/index.js +1132 -106
  18. package/dist/execution/index.js +260 -20
  19. package/dist/hq/index.js +45 -5
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1456 -263
  22. package/dist/infrastructure/index.js +22 -3
  23. package/dist/kernel/events/agent-events.d.ts +31 -2
  24. package/dist/models/index.js +11 -1
  25. package/dist/observability/index.js +1 -1
  26. package/dist/plugin/index.js +113 -12
  27. package/dist/prompts/index.js +360 -3
  28. package/dist/security/auto-approve-policy.d.ts +2 -2
  29. package/dist/security/index.js +177 -50
  30. package/dist/security/permission-helpers.d.ts +11 -0
  31. package/dist/security/permission-policy.d.ts +10 -1
  32. package/dist/security/yolo-risk.d.ts +17 -0
  33. package/dist/session-catalog/index.js +32 -2
  34. package/dist/session-catalog/project-server.js +32 -2
  35. package/dist/skills/index.js +39 -6
  36. package/dist/storage/index.js +44 -3
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.js +14 -0
  39. package/dist/types/multi-agent.d.ts +15 -0
  40. package/dist/types/provider.d.ts +29 -1
  41. package/dist/types/tool.d.ts +15 -0
  42. package/dist/utils/index.d.ts +1 -0
  43. package/dist/utils/index.js +54 -4
  44. package/dist/utils/terminal-sanitize.d.ts +41 -0
  45. package/dist/utils/tool-subject.d.ts +1 -1
  46. package/instructions/agents/chaos-monkey.md +61 -0
  47. package/package.json +4 -4
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 randomUUID31 } 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: randomUUID31(),
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: randomUUID31(),
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: randomUUID31(),
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 randomUUID41 } 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: randomUUID41(),
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: randomUUID41(),
1803
+ id: randomUUID42(),
1804
1804
  findingId,
1805
1805
  eventType,
1806
1806
  fromStatus,
@@ -2513,7 +2513,7 @@ function walk(node, vault, transform) {
2513
2513
  }
2514
2514
  return out;
2515
2515
  }
2516
- var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
2516
+ var SECRET_KEY_PATTERN = /(?:api[-_]?key|auth[-_]?token|authorization|proxy-authorization|cookie|bearer|secret|password|passwd|pwd|refresh[-_]?token|session[-_]?key|access[_-]?token|private[_-]?key|token\b)/i;
2517
2517
  var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
2518
2518
  function isSecretField(name) {
2519
2519
  const lc = name.toLowerCase();
@@ -2684,6 +2684,14 @@ function keyFileNeedsHardening(keyFile, opts) {
2684
2684
  }
2685
2685
  return false;
2686
2686
  }
2687
+ function mkdirSecretDirSync(dir) {
2688
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
2689
+ if (process.platform === "win32") return;
2690
+ try {
2691
+ fs2.chmodSync(dir, 448);
2692
+ } catch {
2693
+ }
2694
+ }
2687
2695
  function writeKeyFileAtomicSync(keyFile, content) {
2688
2696
  const tmp = `${keyFile}.${randomBytes(4).toString("hex")}.tmp`;
2689
2697
  const fd = fs2.openSync(tmp, "w", 384);
@@ -2831,7 +2839,7 @@ var DefaultSecretVault = class {
2831
2839
  const oldVersion = this._keyVersion;
2832
2840
  const newKey = randomBytes(KEY_BYTES);
2833
2841
  const newVersion = oldVersion + 1;
2834
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
2842
+ mkdirSecretDirSync(path3.dirname(this.keyFile));
2835
2843
  const passphrase = getVaultPassphrase();
2836
2844
  if (passphrase) {
2837
2845
  writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
@@ -2915,7 +2923,7 @@ var DefaultSecretVault = class {
2915
2923
  } catch (err) {
2916
2924
  if (err.code !== "ENOENT") throw err;
2917
2925
  }
2918
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
2926
+ mkdirSecretDirSync(path3.dirname(this.keyFile));
2919
2927
  const key = randomBytes(KEY_BYTES);
2920
2928
  const passphrase = getVaultPassphrase();
2921
2929
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
@@ -4069,6 +4077,17 @@ var IN_PROJECT_DENIED_PATHS = [
4069
4077
  // See discover-mailbox-bridge.ts:findWorkspaceCliEntry.
4070
4078
  path: "features.mailboxBridge",
4071
4079
  reason: "Enables the mailbox bridge, whose CLI-entry resolution walks up from the project root \u2014 a repo-supplied packages/cli/dist/index.js would be spawned on WebUI boot."
4080
+ },
4081
+ {
4082
+ // `plugins` is already denied above, so a repo cannot ADD a plugin. This
4083
+ // closes the other half: a repo could previously ship
4084
+ // `{"features":{"pluginsTrust":false}}` and switch off the integrity gate
4085
+ // for plugins the user had ALREADY installed globally — disarming the
4086
+ // trust-on-first-use pin that exists to catch a supply-chain update
4087
+ // rewriting a plugin's entry file. Same operator-owned class as the
4088
+ // switches above.
4089
+ path: "features.pluginsTrust",
4090
+ reason: "Disables the plugin trust-on-first-use integrity gate, re-trusting changed code in already-installed global plugins."
4072
4091
  }
4073
4092
  ];
4074
4093
  function deleteNestedPath(target, path131) {
@@ -14860,6 +14879,42 @@ function formatTaskList(tasks) {
14860
14879
  return lines.join("\n");
14861
14880
  }
14862
14881
 
14882
+ // src/utils/terminal-sanitize.ts
14883
+ var ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
14884
+ var ANSI_OSC_RE = /\x1b\][\s\S]*?(?:\x07|\x1b\\)/g;
14885
+ var ANSI_CONTROL_STRING_RE = /\x1b[P^_X][\s\S]*?\x1b\\/g;
14886
+ var ANSI_ESCAPE_RE = /\x1b[ -/]*[@-~]/g;
14887
+ var BIDI_AND_ZERO_WIDTH_RE = /[​-‏‪-‮⁦-⁩]/g;
14888
+ function sanitizeTerminalText(value, tabWidth = 2) {
14889
+ const tab = " ".repeat(Math.max(1, Math.min(8, Math.floor(tabWidth))));
14890
+ const withoutEscapes = value.replace(ANSI_OSC_RE, "").replace(ANSI_CONTROL_STRING_RE, "").replace(ANSI_RE, "").replace(ANSI_ESCAPE_RE, "").replace(BIDI_AND_ZERO_WIDTH_RE, "").replace(/\t/g, tab).replace(/\r/g, "");
14891
+ let safe = "";
14892
+ for (const char of withoutEscapes) {
14893
+ const code = char.codePointAt(0) ?? 0;
14894
+ if (char === "\n" || code >= 32 && code !== 127 && !(code >= 128 && code <= 159)) {
14895
+ safe += char;
14896
+ }
14897
+ }
14898
+ return safe;
14899
+ }
14900
+ function sanitizeTerminalPreview(value, opts = {}) {
14901
+ const maxLines = opts.maxLines ?? 40;
14902
+ const maxChars = opts.maxChars ?? 8e3;
14903
+ const safe = sanitizeTerminalText(value, opts.tabWidth);
14904
+ let truncated = false;
14905
+ let clipped = safe;
14906
+ if (clipped.length > maxChars) {
14907
+ clipped = clipped.slice(0, maxChars);
14908
+ truncated = true;
14909
+ }
14910
+ const lines = clipped.split("\n");
14911
+ if (lines.length > maxLines) {
14912
+ clipped = lines.slice(0, maxLines).join("\n");
14913
+ truncated = true;
14914
+ }
14915
+ return { text: clipped, truncated };
14916
+ }
14917
+
14863
14918
  // src/utils/tool-description-mode.ts
14864
14919
  var DEFAULT_TOOL_DESCRIPTION_MODE = "extend";
14865
14920
  var ORIGINAL_TOOL_DESCRIPTION = /* @__PURE__ */ Symbol.for("wrongstack.tool.originalDescription");
@@ -15763,9 +15818,21 @@ function renderCommandLine(command, args) {
15763
15818
  });
15764
15819
  return [command, ...rendered].join(" ");
15765
15820
  }
15766
- function subjectForToolInput(toolName, input, subjectKey) {
15821
+ function renderSubjectFields(obj, fields) {
15822
+ const parts = [];
15823
+ for (const field of fields) {
15824
+ const value = obj[field];
15825
+ if (value === void 0 || value === null || value === "" || value === false) continue;
15826
+ const str = String(value);
15827
+ parts.push(`${field}=${/\s/.test(str) ? `"${str.replace(/"/g, '\\"')}"` : str}`);
15828
+ }
15829
+ return parts.join(" ");
15830
+ }
15831
+ function subjectForToolInput(toolName, input, subjectKey, subjectFields) {
15767
15832
  if (!input || typeof input !== "object") return void 0;
15768
15833
  const obj = input;
15834
+ const extra = subjectFields && subjectFields.length > 0 ? renderSubjectFields(obj, subjectFields) : "";
15835
+ const withExtra = (base) => extra ? `${base} ${extra}` : base;
15769
15836
  if (subjectKey) {
15770
15837
  const value = obj[subjectKey];
15771
15838
  if (Array.isArray(value)) {
@@ -15781,9 +15848,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
15781
15848
  if (subjectKey === "command") {
15782
15849
  const rendered = renderCommandLine(value, obj["args"]);
15783
15850
  if (value === "commit" && obj["dry_run"] === true) {
15784
- return `${escapeGlobSubject(rendered)}:dry-run`;
15851
+ return `${escapeGlobSubject(withExtra(rendered))}:dry-run`;
15785
15852
  }
15786
- return escapeGlobSubject(rendered);
15853
+ return escapeGlobSubject(withExtra(rendered));
15787
15854
  }
15788
15855
  if (subjectKey === "directory" && obj["dry_run"] === true) {
15789
15856
  return `${escapeGlobSubject(value)}:dry-run`;
@@ -15813,7 +15880,7 @@ function subjectForToolInput(toolName, input, subjectKey) {
15813
15880
  }
15814
15881
 
15815
15882
  // src/utils/win32-cmd.ts
15816
- var WIN32_CMD_META = /[&|<>"\r\n\0]/;
15883
+ var WIN32_CMD_META = /[&|<>"%\r\n\0]/;
15817
15884
  function buildWin32CmdShimInvocation(command, args = []) {
15818
15885
  assertSafeWin32CmdArgs([command, ...args]);
15819
15886
  const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
@@ -19762,9 +19829,68 @@ function resourceId3(kind, value) {
19762
19829
  return `${kind}_${createHash17("sha256").update(value).digest("hex").slice(0, 24)}`;
19763
19830
  }
19764
19831
 
19832
+ // src/core/btw.ts
19833
+ var META_KEY2 = "_btwNotes";
19834
+ var MAX_PENDING = 20;
19835
+ function readQueue(ctx) {
19836
+ const raw = ctx.meta[META_KEY2];
19837
+ return Array.isArray(raw) ? raw : [];
19838
+ }
19839
+ function setBtwNote(ctx, text2) {
19840
+ const trimmed = text2.trim();
19841
+ if (!trimmed) return readQueue(ctx).length;
19842
+ const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
19843
+ ctx.meta[META_KEY2] = next;
19844
+ return next.length;
19845
+ }
19846
+ function pendingBtwCount(ctx) {
19847
+ return readQueue(ctx).length;
19848
+ }
19849
+ function consumeBtwNotes(ctx) {
19850
+ const notes = readQueue(ctx);
19851
+ if (notes.length > 0) delete ctx.meta[META_KEY2];
19852
+ return notes;
19853
+ }
19854
+ function buildBtwBlock(notes) {
19855
+ const body = notes.map((n) => `- ${n}`).join("\n");
19856
+ return [
19857
+ "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
19858
+ "your current task; do not restart from scratch unless it contradicts the",
19859
+ "goal:",
19860
+ "",
19861
+ body,
19862
+ "]"
19863
+ ].join("\n");
19864
+ }
19865
+
19765
19866
  // src/coordination/agent-subagent-runner.ts
19766
19867
  init_errors();
19767
19868
 
19869
+ // src/coordination/subagent-finish.ts
19870
+ var SUBAGENT_FINISH_REQUESTED_EVENT = "subagent.finish_requested";
19871
+ var DEFAULT_SUBAGENT_FINISH_GRACE_MS = 12e4;
19872
+ function resolveGracefulFinish(config) {
19873
+ const raw = config.gracefulFinish;
19874
+ if (raw === void 0 || raw === false) return void 0;
19875
+ if (raw === true) return { graceMs: DEFAULT_SUBAGENT_FINISH_GRACE_MS };
19876
+ const graceMs = typeof raw.graceMs === "number" && Number.isFinite(raw.graceMs) && raw.graceMs > 0 ? Math.floor(raw.graceMs) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
19877
+ return { graceMs };
19878
+ }
19879
+ function buildSubagentFinishNotice(input) {
19880
+ const localTime = new Date(input.deadlineMs).toISOString();
19881
+ const seconds = Math.max(1, Math.round(input.graceMs / 1e3));
19882
+ 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.`;
19883
+ return [
19884
+ "[SUBAGENT FINISH] The leader agent has finished its work.",
19885
+ `Reason: ${input.reason}`,
19886
+ timeLeft,
19887
+ "Finish your task now, in this turn: complete the thought you are working on, stop",
19888
+ "starting new tool calls unless one is strictly required to finish, and write your",
19889
+ "final answer or report as your final output, then end your turn.",
19890
+ "Do not restart the task and do not begin new work."
19891
+ ].join("\n");
19892
+ }
19893
+
19768
19894
  // src/coordination/subagent-budget.ts
19769
19895
  var TIMEOUT_PREEMPT_FRACTION = 0.85;
19770
19896
  var DECISION_TIMEOUT_MS = 6e4;
@@ -19822,6 +19948,82 @@ var SubagentBudget = class _SubagentBudget {
19822
19948
  this.limits.idleTimeoutMs = ext.idleTimeoutMs;
19823
19949
  }
19824
19950
  }
19951
+ /**
19952
+ * Graceful-finish state (see coordination/subagent-finish.ts).
19953
+ * `_finishNotified` guards the single in-band emission; `_grace` records a
19954
+ * granted working-time extension past the original wall-clock deadline.
19955
+ * They are separate because the two callers want different semantics:
19956
+ * the watchdog grants grace at the deadline crossing (notify + extend),
19957
+ * while an explicit leader-finished request only notifies — a subagent
19958
+ * well inside its budget keeps its full legitimate working time and simply
19959
+ * accelerates.
19960
+ */
19961
+ _finishNotified = false;
19962
+ _grace = null;
19963
+ /** True once the in-band finish notification has been emitted. */
19964
+ get finishNotified() {
19965
+ return this._finishNotified;
19966
+ }
19967
+ /** True once a grace window has been granted past the original deadline. */
19968
+ get graceGranted() {
19969
+ return this._grace !== null;
19970
+ }
19971
+ /**
19972
+ * Notify the subagent in-band to finish its task in its own turn:
19973
+ * `subagent.finish_requested` is emitted on the wired EventBus and the
19974
+ * agent loop folds the notice into the conversation between tool batches.
19975
+ * Nothing aborts — this is a notification, never an interrupt.
19976
+ *
19977
+ * `opts.graceMs` additionally extends the wall-clock ceiling by that window
19978
+ * (used by the watchdog at a deadline crossing, so the model gets working
19979
+ * time instead of a kill). Omit it to notify without touching the budget —
19980
+ * the subagent keeps its existing time budget and just accelerates.
19981
+ *
19982
+ * Returns `true` when this call did something (emitted the notification
19983
+ * and/or granted grace); `false` when there was nothing to do (already
19984
+ * notified, grace already granted, no EventBus wired, budget not started).
19985
+ */
19986
+ notifyFinish(reason, opts, now = Date.now) {
19987
+ if (!this._events) return false;
19988
+ if (this.startTime === null) return false;
19989
+ const shouldEmit = !this._finishNotified;
19990
+ const rawGrace = opts?.graceMs;
19991
+ const shouldGrant = rawGrace !== void 0 && this._grace === null;
19992
+ if (!shouldEmit && !shouldGrant) return false;
19993
+ let grantedGraceMs = 0;
19994
+ let graceDeadlineMs;
19995
+ if (shouldGrant && rawGrace !== void 0) {
19996
+ grantedGraceMs = Number.isFinite(rawGrace) && rawGrace > 0 ? Math.floor(rawGrace) : DEFAULT_SUBAGENT_FINISH_GRACE_MS;
19997
+ graceDeadlineMs = now() + grantedGraceMs;
19998
+ this._grace = { deadlineMs: graceDeadlineMs, graceMs: grantedGraceMs };
19999
+ this.patchLimits({ timeoutMs: graceDeadlineMs - this.startTime });
20000
+ }
20001
+ if (shouldEmit) {
20002
+ this._finishNotified = true;
20003
+ const effectiveDeadlineMs = graceDeadlineMs ?? (this.limits.timeoutMs !== void 0 ? this.startTime + this.limits.timeoutMs : now() + DEFAULT_SUBAGENT_FINISH_GRACE_MS);
20004
+ const effectiveGraceMs = Math.max(0, effectiveDeadlineMs - now());
20005
+ const subagentId = this._subagentId;
20006
+ this._events.emit(SUBAGENT_FINISH_REQUESTED_EVENT, {
20007
+ // Omitted entirely when the budget was built without an id — an
20008
+ // empty string is an address that matches nothing.
20009
+ ...subagentId !== void 0 ? { subagentId } : {},
20010
+ reason,
20011
+ deadlineMs: effectiveDeadlineMs,
20012
+ graceMs: effectiveGraceMs,
20013
+ notice: buildSubagentFinishNotice({
20014
+ reason,
20015
+ deadlineMs: effectiveDeadlineMs,
20016
+ graceMs: effectiveGraceMs
20017
+ })
20018
+ });
20019
+ }
20020
+ return true;
20021
+ }
20022
+ /** Epoch ms by which the subagent should have produced its final output,
20023
+ * once a grace window was granted. Undefined before that. */
20024
+ get finishDeadlineMs() {
20025
+ return this._grace?.deadlineMs;
20026
+ }
19825
20027
  iterations = 0;
19826
20028
  toolCalls = 0;
19827
20029
  tokenInput = 0;
@@ -19837,6 +20039,10 @@ var SubagentBudget = class _SubagentBudget {
19837
20039
  lastActivityTime = null;
19838
20040
  _onThreshold;
19839
20041
  _sessionId;
20042
+ /** Owning subagent id — used to address the graceful-finish event. */
20043
+ _subagentId;
20044
+ /** True when only the coordinator watchdog may enforce wall-clock limits. */
20045
+ _wallClockWatchdogOwned;
19840
20046
  /**
19841
20047
  * Hard cap on how long `_negotiateExtension` waits for the coordinator to
19842
20048
  * respond before defaulting to 'stop'. Without this fallback an absent
@@ -19908,6 +20114,8 @@ var SubagentBudget = class _SubagentBudget {
19908
20114
  constructor(limits = {}, mode = "auto", options = {}) {
19909
20115
  this._mode = mode;
19910
20116
  this._sessionId = options.sessionId;
20117
+ this._subagentId = options.subagentId;
20118
+ this._wallClockWatchdogOwned = options.wallClockWatchdogOwned === true;
19911
20119
  this.limits = { ...limits };
19912
20120
  }
19913
20121
  currentSessionId() {
@@ -19986,7 +20194,7 @@ var SubagentBudget = class _SubagentBudget {
19986
20194
  if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
19987
20195
  exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
19988
20196
  }
19989
- const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
20197
+ const wallOwnedByWatchdog = this._wallClockWatchdogOwned || this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
19990
20198
  if (this.limits.timeoutMs !== void 0 && elapsedMs2 > this.limits.timeoutMs && !wallOwnedByWatchdog) {
19991
20199
  exceeded.push({ kind: "timeout", used: elapsedMs2, limit: this.limits.timeoutMs });
19992
20200
  }
@@ -20185,7 +20393,7 @@ var SubagentBudget = class _SubagentBudget {
20185
20393
  if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
20186
20394
  const elapsed2 = Date.now() - this.startTime;
20187
20395
  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;
20396
+ const wallTripped = this._wallClockWatchdogOwned || wallSkipped ? false : timeoutMs !== void 0 && elapsed2 > timeoutMs;
20189
20397
  const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
20190
20398
  if (!wallTripped && !idleTripped) return;
20191
20399
  void this.checkLimits(elapsed2);
@@ -20666,6 +20874,14 @@ function makeAgentSubagentRunner(opts) {
20666
20874
  );
20667
20875
  const onParentAbort = () => aborter.abort();
20668
20876
  ctx.signal.addEventListener("abort", onParentAbort);
20877
+ if (resolveGracefulFinish(ctx.config)) {
20878
+ unsub.push(
20879
+ events.on("subagent.finish_requested", (e) => {
20880
+ if (e.subagentId && e.subagentId !== ctx.subagentId) return;
20881
+ setBtwNote(agent.ctx, e.notice);
20882
+ })
20883
+ );
20884
+ }
20669
20885
  let result;
20670
20886
  try {
20671
20887
  result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
@@ -27827,6 +28043,14 @@ var PATTERNS = [
27827
28043
  anchor: "sk-ant-"
27828
28044
  },
27829
28045
  { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g, anchor: "sk-" },
28046
+ {
28047
+ // `xai` is a first-class provider in this codebase, but its key shape was
28048
+ // absent here — so the one credential format WrongStack itself hands users
28049
+ // was the one the scrubber could not recognize (audit 2026-08-20).
28050
+ type: "xai_key",
28051
+ regex: /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
28052
+ anchor: "xai-"
28053
+ },
27830
28054
  { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
27831
28055
  { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g, anchor: "github_pat_" },
27832
28056
  { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
@@ -27917,8 +28141,8 @@ var PATTERNS = [
27917
28141
  // replacement so the separator between adjacent secrets is preserved
27918
28142
  // rather than collapsed. Capture groups are therefore: 1=leading
27919
28143
  // delimiter, 2=key name, 3=value.
27920
- regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
27921
- anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
28144
+ regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD|PASSPHRASE))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
28145
+ anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD", "PASSPHRASE"]
27922
28146
  },
27923
28147
  {
27924
28148
  type: "json_credential_key",
@@ -28035,6 +28259,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
28035
28259
  var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
28036
28260
  var SCRUB_CHUNK_BYTES = 64 * 1024;
28037
28261
  var SCRUB_OVERLAP_BYTES = 1024;
28262
+ var PEM_PRIVATE_KEY_BEGIN_RE = /-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----/;
28263
+ var PEM_END_MARKER = "-----END";
28264
+ var MAX_PEM_BLOCK_BYTES = 64 * 1024;
28265
+ var PEM_END_LINE_TOLERANCE = 64;
28266
+ function extendChunkBoundaryPastPem(text2, chunkStart, proposedEnd) {
28267
+ const head = text2.slice(chunkStart, proposedEnd);
28268
+ const lastBegin = head.lastIndexOf("-----BEGIN ");
28269
+ if (lastBegin === -1) return proposedEnd;
28270
+ const fromBegin = text2.slice(chunkStart + lastBegin);
28271
+ const marker = PEM_PRIVATE_KEY_BEGIN_RE.exec(fromBegin);
28272
+ if (!marker || marker.index !== 0) return proposedEnd;
28273
+ const bodyStart = marker[0].length;
28274
+ const cap = Math.min(text2.length, chunkStart + lastBegin + MAX_PEM_BLOCK_BYTES);
28275
+ const closeIdx = fromBegin.indexOf(PEM_END_MARKER, bodyStart);
28276
+ if (closeIdx === -1 || chunkStart + lastBegin + closeIdx >= cap + PEM_END_LINE_TOLERANCE) {
28277
+ return proposedEnd;
28278
+ }
28279
+ const lineEnd = fromBegin.indexOf("\n", closeIdx);
28280
+ const end = lineEnd === -1 ? text2.length : chunkStart + lastBegin + lineEnd + 1;
28281
+ return Math.max(proposedEnd, end);
28282
+ }
28038
28283
  var PATTERN_ANCHORS = [
28039
28284
  ...new Set(
28040
28285
  PATTERNS.flatMap(
@@ -28071,6 +28316,7 @@ var DefaultSecretScrubber = class {
28071
28316
  }
28072
28317
  }
28073
28318
  end = safe === -1 ? end : safe + 1;
28319
+ end = extendChunkBoundaryPastPem(text2, i, end);
28074
28320
  }
28075
28321
  out.push(this.scrubOne(text2.slice(i, end)));
28076
28322
  i = end;
@@ -29007,7 +29253,7 @@ function attachDepWatcherBridge(opts) {
29007
29253
  }
29008
29254
 
29009
29255
  // src/coordination/director.ts
29010
- import { randomUUID as randomUUID19 } from "node:crypto";
29256
+ import { randomUUID as randomUUID20 } from "node:crypto";
29011
29257
  import * as fsp30 from "node:fs/promises";
29012
29258
 
29013
29259
  // src/storage/director-state.ts
@@ -30902,7 +31148,7 @@ ${JSON.stringify(result.result, null, 2)}
30902
31148
  };
30903
31149
 
30904
31150
  // src/coordination/director-tools.ts
30905
- import { randomUUID as randomUUID14 } from "node:crypto";
31151
+ import { randomUUID as randomUUID15 } from "node:crypto";
30906
31152
  import {
30907
31153
  completeKanbanDispatch,
30908
31154
  failKanbanDispatch,
@@ -32136,6 +32382,626 @@ function excerpt(text2, max) {
32136
32382
  ...(truncated)`;
32137
32383
  }
32138
32384
 
32385
+ // src/coordination/director-mutation-test-tool.ts
32386
+ import { randomUUID as randomUUID14 } from "node:crypto";
32387
+ import { readFileSync as readFileSync19 } from "node:fs";
32388
+ import { isAbsolute as isAbsolute9, join as join40 } from "node:path";
32389
+
32390
+ // src/coordination/mutation-engine.ts
32391
+ var TOKEN_PATTERNS = [
32392
+ {
32393
+ kind: "relax-boundary",
32394
+ // `>` not followed by `=` and not part of `=>` or `>>`; require code-ish
32395
+ // context on both sides so generic text (JSX, strings) is not touched.
32396
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>(?!=|>))/g,
32397
+ replace: () => ">="
32398
+ },
32399
+ {
32400
+ kind: "tighten-boundary",
32401
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>>=)/g,
32402
+ replace: () => ">"
32403
+ },
32404
+ {
32405
+ kind: "arith-plus-to-minus",
32406
+ // `+` between operands (binary), not `++`, unary `+x`, or `+=`.
32407
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>\+(?!\+|=))/g,
32408
+ replace: () => "-"
32409
+ },
32410
+ {
32411
+ kind: "arith-minus-to-plus",
32412
+ // Binary `-` between operands, not `--`, `-=` or negative-number literal.
32413
+ regex: /(?<=[\w\)\]\}'"`])\s*\x20?(?<op>-(?!-|=))/g,
32414
+ replace: () => "+"
32415
+ },
32416
+ {
32417
+ kind: "negate-boolean",
32418
+ // Standalone boolean literals used as values, not property names.
32419
+ regex: /(?<![.\w$])(?<op>true|false)(?![\w$])/g,
32420
+ replace: (m) => m === "true" ? "false" : "true"
32421
+ },
32422
+ {
32423
+ kind: "return-null",
32424
+ // `return <expr>;` where expr is not already null/undefined/void.
32425
+ regex: /(?<indent>\breturn\b)(?<expr>\s+[^;{}\n]+?)\s*;/g,
32426
+ replace: () => "return null;",
32427
+ endpointsInCode: true
32428
+ }
32429
+ ];
32430
+ function planMutations(file, source, opts = {}) {
32431
+ const maxPerFile = opts.maxPerFile ?? 25;
32432
+ const out = [];
32433
+ const lines = source.split("\n");
32434
+ const masks = computeLineMasks(source);
32435
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
32436
+ const line = lines[lineIdx];
32437
+ const t2 = line.trim();
32438
+ if (t2.startsWith("//")) continue;
32439
+ const codeRanges = masks[lineIdx];
32440
+ const inCode = (start) => codeRanges.some(([s, e]) => start >= s && start < e);
32441
+ for (const pattern of TOKEN_PATTERNS) {
32442
+ pattern.regex.lastIndex = 0;
32443
+ let m;
32444
+ while ((m = pattern.regex.exec(line)) !== null) {
32445
+ const token = m.groups?.["op"] ?? m[0];
32446
+ const tokenStart = m.index + m[0].indexOf(token);
32447
+ if (!inCode(tokenStart)) continue;
32448
+ if (pattern.endpointsInCode && !inCode(tokenStart + token.length - 1)) continue;
32449
+ const original = line.slice(tokenStart, tokenStart + token.length);
32450
+ const replacement = pattern.replace(token);
32451
+ if (replacement === original) continue;
32452
+ out.push({
32453
+ id: `${pattern.kind}#${lineIdx + 1}#${tokenStart + 1}`,
32454
+ kind: pattern.kind,
32455
+ file,
32456
+ line: lineIdx + 1,
32457
+ column: tokenStart + 1,
32458
+ original,
32459
+ replacement
32460
+ });
32461
+ }
32462
+ }
32463
+ if (out.length >= maxPerFile) break;
32464
+ }
32465
+ return out.slice(0, maxPerFile);
32466
+ }
32467
+ function computeLineMasks(source) {
32468
+ const lines = source.split("\n");
32469
+ const masks = lines.map(() => []);
32470
+ const stack = [{ kind: "code", depth: 0, parens: [] }];
32471
+ let inBlockComment = false;
32472
+ let lastToken = null;
32473
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
32474
+ const line = lines[lineIdx];
32475
+ const ranges = masks[lineIdx];
32476
+ let runStart = null;
32477
+ const closeRun = (end) => {
32478
+ if (runStart !== null && end > runStart) ranges.push([runStart, end]);
32479
+ runStart = null;
32480
+ };
32481
+ let i = 0;
32482
+ if (inBlockComment) {
32483
+ const close = line.indexOf("*/");
32484
+ if (close === -1) continue;
32485
+ inBlockComment = false;
32486
+ i = close + 2;
32487
+ }
32488
+ while (i < line.length) {
32489
+ const top = stack[stack.length - 1];
32490
+ const c = line[i];
32491
+ if (top.kind === "template") {
32492
+ if (c === "\\") {
32493
+ i += 2;
32494
+ continue;
32495
+ }
32496
+ if (c === "`") {
32497
+ stack.pop();
32498
+ lastToken = "`";
32499
+ i++;
32500
+ continue;
32501
+ }
32502
+ if (c === "$" && line[i + 1] === "{") {
32503
+ stack.push({ kind: "code", depth: 0, parens: [] });
32504
+ lastToken = "${";
32505
+ i += 2;
32506
+ continue;
32507
+ }
32508
+ i++;
32509
+ continue;
32510
+ }
32511
+ if (/[\w$]/.test(c)) {
32512
+ let j = i + 1;
32513
+ while (j < line.length && /[\w$]/.test(line[j])) j++;
32514
+ lastToken = line.slice(i, j);
32515
+ if (runStart === null) runStart = i;
32516
+ i = j;
32517
+ continue;
32518
+ }
32519
+ if (c === "'" || c === '"') {
32520
+ closeRun(i);
32521
+ i++;
32522
+ while (i < line.length && line[i] !== c) {
32523
+ if (line[i] === "\\") i++;
32524
+ i++;
32525
+ }
32526
+ i++;
32527
+ lastToken = c;
32528
+ continue;
32529
+ }
32530
+ if (c === "`") {
32531
+ closeRun(i);
32532
+ stack.push({ kind: "template", depth: 0, parens: [] });
32533
+ i++;
32534
+ continue;
32535
+ }
32536
+ if (c === "/" && line[i + 1] === "/") {
32537
+ closeRun(i);
32538
+ break;
32539
+ }
32540
+ if (c === "/" && line[i + 1] === "*") {
32541
+ closeRun(i);
32542
+ const close = line.indexOf("*/", i + 2);
32543
+ if (close === -1) {
32544
+ inBlockComment = true;
32545
+ break;
32546
+ }
32547
+ i = close + 2;
32548
+ continue;
32549
+ }
32550
+ if (c === "/") {
32551
+ if (!tokenCanEndOperand(lastToken)) {
32552
+ closeRun(i);
32553
+ const next = skipRegexLiteral(line, i);
32554
+ lastToken = next > i + 1 ? "regex" : "/";
32555
+ i = next;
32556
+ continue;
32557
+ }
32558
+ }
32559
+ if (c === "(") {
32560
+ top.parens.push(CONTROL_KEYWORDS.has(lastToken ?? "") ? "control" : "expr");
32561
+ lastToken = c;
32562
+ } else if (c === ")") {
32563
+ const kind = top.parens.pop() ?? "expr";
32564
+ lastToken = kind === "control" ? "control-paren-close" : ")";
32565
+ } else if (c === "{") {
32566
+ top.depth++;
32567
+ lastToken = c;
32568
+ } else if (c === "}") {
32569
+ if (top.depth > 0) {
32570
+ top.depth--;
32571
+ lastToken = c;
32572
+ } else if (stack.length > 1) {
32573
+ closeRun(i);
32574
+ stack.pop();
32575
+ i++;
32576
+ continue;
32577
+ } else {
32578
+ lastToken = c;
32579
+ }
32580
+ } else if (c !== " " && c !== " " && c !== "\r") {
32581
+ lastToken = c;
32582
+ }
32583
+ if (runStart === null) runStart = i;
32584
+ i++;
32585
+ }
32586
+ closeRun(line.length);
32587
+ }
32588
+ return masks;
32589
+ }
32590
+ var KEYWORDS_BEFORE_REGEX = /* @__PURE__ */ new Set([
32591
+ "return",
32592
+ "typeof",
32593
+ "instanceof",
32594
+ "in",
32595
+ "of",
32596
+ "new",
32597
+ "delete",
32598
+ "void",
32599
+ "throw",
32600
+ "case",
32601
+ "do",
32602
+ "else",
32603
+ "yield",
32604
+ "await"
32605
+ ]);
32606
+ var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "catch", "with", "await"]);
32607
+ function tokenCanEndOperand(token) {
32608
+ if (token === null) return false;
32609
+ if (/^[\w$]+$/.test(token)) return !KEYWORDS_BEFORE_REGEX.has(token);
32610
+ return token === ")" || token === "]" || token === "." || token === '"' || token === "'" || token === "`";
32611
+ }
32612
+ function skipRegexLiteral(line, start) {
32613
+ let i = start + 1;
32614
+ let inClass = false;
32615
+ while (i < line.length) {
32616
+ const ch = line[i];
32617
+ if (ch === "\\") {
32618
+ i += 2;
32619
+ continue;
32620
+ }
32621
+ if (inClass) {
32622
+ if (ch === "]") inClass = false;
32623
+ i++;
32624
+ continue;
32625
+ }
32626
+ if (ch === "[") {
32627
+ inClass = true;
32628
+ i++;
32629
+ continue;
32630
+ }
32631
+ if (ch === "/") {
32632
+ i++;
32633
+ break;
32634
+ }
32635
+ if (ch === "\n" || ch === "\r") return line.length;
32636
+ i++;
32637
+ }
32638
+ while (i < line.length && /[a-z]/.test(line[i])) i++;
32639
+ return i;
32640
+ }
32641
+ function parseMutationReport(text2) {
32642
+ const candidates = [];
32643
+ const fence = text2.match(/```(?:json)?\s*([\s\S]*?)```/);
32644
+ if (fence?.[1]) candidates.push(fence[1].trim());
32645
+ const firstBrace = text2.indexOf("{");
32646
+ if (firstBrace >= 0) candidates.push(extractBalancedObject(text2, firstBrace));
32647
+ for (const candidate of candidates) {
32648
+ if (!candidate) continue;
32649
+ try {
32650
+ const parsed = JSON.parse(candidate);
32651
+ if (!Array.isArray(parsed.mutants)) continue;
32652
+ return {
32653
+ mutants: parsed.mutants.map(normalizeMutantEntry).filter((x) => Boolean(x)),
32654
+ summary: typeof parsed.summary === "string" ? parsed.summary : void 0
32655
+ };
32656
+ } catch {
32657
+ }
32658
+ }
32659
+ return void 0;
32660
+ }
32661
+ function extractBalancedObject(text2, start) {
32662
+ let depth = 0;
32663
+ let inString = false;
32664
+ let escaped = false;
32665
+ for (let i = start; i < text2.length; i++) {
32666
+ const c = text2[i];
32667
+ if (escaped) {
32668
+ escaped = false;
32669
+ continue;
32670
+ }
32671
+ if (c === "\\") {
32672
+ escaped = true;
32673
+ continue;
32674
+ }
32675
+ if (c === '"') inString = !inString;
32676
+ if (inString) continue;
32677
+ if (c === "{") depth++;
32678
+ else if (c === "}") {
32679
+ depth--;
32680
+ if (depth === 0) return text2.slice(start, i + 1);
32681
+ }
32682
+ }
32683
+ return text2.slice(start);
32684
+ }
32685
+ function normalizeMutantEntry(value) {
32686
+ if (typeof value !== "object" || value === null) return void 0;
32687
+ const rec = value;
32688
+ const id = typeof rec["id"] === "string" ? rec["id"] : void 0;
32689
+ const status = rec["status"];
32690
+ if (!id || status !== "killed" && status !== "survived" && status !== "skipped" && status !== "killed-by-hang") {
32691
+ return void 0;
32692
+ }
32693
+ return {
32694
+ id,
32695
+ file: typeof rec["file"] === "string" ? rec["file"] : "",
32696
+ line: typeof rec["line"] === "number" ? rec["line"] : 0,
32697
+ kind: typeof rec["kind"] === "string" ? rec["kind"] : "",
32698
+ status,
32699
+ evidence: typeof rec["evidence"] === "string" ? rec["evidence"] : void 0
32700
+ };
32701
+ }
32702
+
32703
+ // src/coordination/director-mutation-test-tool.ts
32704
+ var DEFAULT_MAX_PER_FILE = 10;
32705
+ var DEFAULT_MAX_STRENGTHEN_ATTEMPTS = 2;
32706
+ var CHAOS_ROLE = "chaos-monkey";
32707
+ function makeMutationTestTool(director, roster, opts = {}) {
32708
+ return {
32709
+ name: "mutation_test",
32710
+ 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.",
32711
+ 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.",
32712
+ permission: "auto",
32713
+ mutating: false,
32714
+ capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
32715
+ inputSchema: {
32716
+ type: "object",
32717
+ properties: {
32718
+ targets: {
32719
+ type: "array",
32720
+ items: { type: "string" },
32721
+ description: "Project-relative (or absolute) source files to mutate. Keep to files changed by the current task."
32722
+ },
32723
+ testCommand: {
32724
+ type: "string",
32725
+ description: 'Exact command that runs the relevant tests, e.g. "pnpm exec vitest run packages/core/tests/coordination/mutation-engine.test.ts".'
32726
+ },
32727
+ cwd: { type: "string", description: "Working directory for the test command." },
32728
+ maxPerFile: {
32729
+ type: "number",
32730
+ minimum: 1,
32731
+ maximum: 25,
32732
+ description: "Mutant cap per file per pass. Default 10."
32733
+ },
32734
+ maxStrengthenAttempts: {
32735
+ type: "number",
32736
+ minimum: 0,
32737
+ maximum: 5,
32738
+ description: "Strengthen\u2192re-verify rounds. Default 2 when repairSubagentId is set, else 0."
32739
+ },
32740
+ repairSubagentId: {
32741
+ type: "string",
32742
+ description: "Subagent that owns the tests. When set and mutants survive, it receives a strengthen-tests task and the survivors are re-verified."
32743
+ },
32744
+ chaosWorktree: {
32745
+ anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
32746
+ description: "Worktree override for the chaos agent. Defaults to the roster policy for chaos-monkey ('off'), because mutation targets are usually freshly written and uncommitted \u2014 a worktree from HEAD would not contain them and every mutant would drift to skipped. Only pass 'auto' or 'required' when the targets are committed."
32747
+ },
32748
+ timeoutMs: { type: "number", minimum: 1, description: "Per-task timeout for chaos/strengthen/rerun tasks." },
32749
+ reportOnly: {
32750
+ type: "boolean",
32751
+ description: "Skip the strengthen loop even when survivors exist. Default false."
32752
+ }
32753
+ },
32754
+ required: ["targets", "testCommand"],
32755
+ additionalProperties: false
32756
+ },
32757
+ async execute(input, ctx) {
32758
+ const i = normalizeMutationTestInput(input);
32759
+ const root = opts.projectRoot ?? ctx.projectRoot;
32760
+ const plan = buildPlan(i, root);
32761
+ if (plan.length === 0) {
32762
+ return {
32763
+ verdict: "inconclusive",
32764
+ passed: false,
32765
+ error: "No mutable sites found in the given targets (after comment/string filtering)."
32766
+ };
32767
+ }
32768
+ const chaosBase = roster?.[CHAOS_ROLE];
32769
+ if (!chaosBase) {
32770
+ return {
32771
+ verdict: "inconclusive",
32772
+ passed: false,
32773
+ error: "chaos-monkey role missing from the roster \u2014 refusing to spawn a saboteur without its prompt/tools contract. Build the toolset with a roster that includes 'chaos-monkey' (FLEET_ROSTER does)."
32774
+ };
32775
+ }
32776
+ const chaosSubagentId = await director.spawn(
32777
+ makeChaosConfig(chaosBase, i.chaosWorktree ?? chaosBase.worktree ?? "off")
32778
+ );
32779
+ const chaosTaskId = await director.assign({
32780
+ id: randomUUID14(),
32781
+ subagentId: chaosSubagentId,
32782
+ description: buildChaosTask(plan, i, 1, []),
32783
+ timeoutMs: i.timeoutMs
32784
+ });
32785
+ const [chaosResult] = await director.awaitTasks([chaosTaskId]);
32786
+ const pass1 = collectOutcomes(chaosResult, plan);
32787
+ const survivors = pass1.filter((m) => m.status === "survived");
32788
+ const maxAttempts = clamp(
32789
+ i.maxStrengthenAttempts ?? (i.repairSubagentId && !i.reportOnly ? DEFAULT_MAX_STRENGTHEN_ATTEMPTS : 0),
32790
+ 0,
32791
+ 5
32792
+ );
32793
+ const attempts = [];
32794
+ let current = survivors;
32795
+ let rerunUnknowns = [];
32796
+ while (current.length > 0 && attempts.length < maxAttempts && i.repairSubagentId) {
32797
+ const attemptNo = attempts.length + 1;
32798
+ const strengthenTaskId = await director.assign({
32799
+ id: randomUUID14(),
32800
+ subagentId: i.repairSubagentId,
32801
+ description: buildStrengthenTask(current, i, attemptNo),
32802
+ timeoutMs: i.timeoutMs
32803
+ });
32804
+ const [strengthenResult] = await director.awaitTasks([strengthenTaskId]);
32805
+ if (strengthenResult?.status !== "success") {
32806
+ attempts.push({
32807
+ attempt: attemptNo,
32808
+ survivorsBefore: current,
32809
+ strengthenResult: strengthenResult ? { taskId: strengthenResult.taskId, status: strengthenResult.status } : void 0,
32810
+ survivorsAfter: current,
32811
+ suspectedEquivalent: []
32812
+ });
32813
+ break;
32814
+ }
32815
+ const survivorPlan = plan.filter((p) => current.some((s) => s.id === p.id));
32816
+ const rerunSubagentId = await director.spawn(
32817
+ makeChaosConfig(chaosBase, i.chaosWorktree ?? chaosBase.worktree ?? "off")
32818
+ );
32819
+ const rerunTaskId = await director.assign({
32820
+ id: randomUUID14(),
32821
+ subagentId: rerunSubagentId,
32822
+ description: buildChaosTask(survivorPlan, i, attemptNo + 1, current),
32823
+ timeoutMs: i.timeoutMs
32824
+ });
32825
+ const [rerunResult] = await director.awaitTasks([rerunTaskId]);
32826
+ const passN = collectOutcomes(rerunResult, survivorPlan);
32827
+ const stillSurviving = passN.filter((m) => !isKill(m.status));
32828
+ rerunUnknowns = passN.filter((m) => m.status === "skipped");
32829
+ attempts.push({
32830
+ attempt: attemptNo,
32831
+ survivorsBefore: current,
32832
+ strengthenResult: { taskId: strengthenResult.taskId, status: strengthenResult.status },
32833
+ rerunResult: { taskId: rerunTaskId, status: rerunResult?.status ?? "unknown" },
32834
+ survivorsAfter: stillSurviving,
32835
+ suspectedEquivalent: stillSurviving.filter((m) => m.status === "survived" && current.some((c) => c.id === m.id)).map((m) => m.id)
32836
+ });
32837
+ current = stillSurviving;
32838
+ }
32839
+ const finalSurvivors = current.filter((m) => m.status === "survived");
32840
+ const verifiedCount = pass1.filter((m) => m.status !== "skipped").length;
32841
+ const skippedCount = pass1.filter((m) => m.status === "skipped").length;
32842
+ const rerunUnknownCount = rerunUnknowns.length;
32843
+ const score = plan.length === 0 ? 0 : pass1.filter((m) => isKill(m.status)).length / plan.length;
32844
+ const verdict = verifiedCount === 0 ? "inconclusive" : finalSurvivors.length === 0 ? skippedCount > 0 || rerunUnknownCount > 0 ? "partial" : "pass" : score >= 0.8 ? "partial" : "fail";
32845
+ return {
32846
+ verdict,
32847
+ passed: verdict === "pass",
32848
+ mutationScore: Number.parseFloat(score.toFixed(3)),
32849
+ planned: plan.length,
32850
+ killed: pass1.filter((m) => isKill(m.status)).length,
32851
+ // Breakout of `killed`: how many kills were detected by the test
32852
+ // command hanging rather than by a failing assertion. A subset of
32853
+ // `killed`, surfaced so a director can distinguish a hang-heavy
32854
+ // suite (mutants breaking termination, not assertions) from an
32855
+ // assertion-strong one. hangHeavy = killedByHang === killed.
32856
+ killedByHang: pass1.filter((m) => m.status === "killed-by-hang").length,
32857
+ survived: pass1.filter((m) => m.status === "survived").length,
32858
+ skipped: pass1.filter((m) => m.status === "skipped").length,
32859
+ finalSurvivors: finalSurvivors.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
32860
+ suspectedEquivalent: attempts.flatMap((a) => a.suspectedEquivalent),
32861
+ strengthenAttempts: attempts.length,
32862
+ attempts,
32863
+ chaosTaskId,
32864
+ // Unverified leftovers from the strengthen loop: surfaced so the
32865
+ // caller can see WHICH mutants lack kill evidence, and counted by
32866
+ // the verdict gate above.
32867
+ unverifiedFromRerun: rerunUnknowns.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
32868
+ nextAction: finalSurvivors.length === 0 && rerunUnknownCount === 0 && skippedCount === 0 ? "accept" : attempts.length >= maxAttempts && i.repairSubagentId ? "manual_review_survivors" : "strengthen_tests"
32869
+ };
32870
+ }
32871
+ };
32872
+ }
32873
+ function normalizeMutationTestInput(input) {
32874
+ const raw = input ?? {};
32875
+ const targets = stringArray2(raw["targets"]) ?? [];
32876
+ const testCommand = typeof raw["testCommand"] === "string" ? raw["testCommand"].trim() : "";
32877
+ return {
32878
+ targets: targets.filter(Boolean),
32879
+ testCommand,
32880
+ cwd: typeof raw["cwd"] === "string" && raw["cwd"].trim() ? raw["cwd"].trim() : void 0,
32881
+ maxPerFile: typeof raw["maxPerFile"] === "number" ? raw["maxPerFile"] : void 0,
32882
+ maxStrengthenAttempts: typeof raw["maxStrengthenAttempts"] === "number" ? raw["maxStrengthenAttempts"] : void 0,
32883
+ repairSubagentId: typeof raw["repairSubagentId"] === "string" && raw["repairSubagentId"].trim() ? raw["repairSubagentId"].trim() : void 0,
32884
+ chaosWorktree: normalizeWorktreeOverride(raw["chaosWorktree"]),
32885
+ timeoutMs: typeof raw["timeoutMs"] === "number" ? raw["timeoutMs"] : void 0,
32886
+ reportOnly: raw["reportOnly"] === true
32887
+ };
32888
+ }
32889
+ function clamp(n, lo, hi) {
32890
+ return Math.min(hi, Math.max(lo, n));
32891
+ }
32892
+ function buildPlan(i, projectRoot) {
32893
+ const plan = [];
32894
+ for (const target of i.targets) {
32895
+ const abs = isAbsolute9(target) ? target : join40(projectRoot ?? process.cwd(), target);
32896
+ let source;
32897
+ try {
32898
+ source = readFileSync19(abs, "utf8");
32899
+ } catch {
32900
+ continue;
32901
+ }
32902
+ plan.push(...planMutations(target, source, { maxPerFile: i.maxPerFile ?? DEFAULT_MAX_PER_FILE }));
32903
+ }
32904
+ return plan;
32905
+ }
32906
+ function makeChaosConfig(base, worktree) {
32907
+ return { ...instantiateRosterConfig(CHAOS_ROLE, base), worktree };
32908
+ }
32909
+ function buildChaosTask(plan, i, pass, priorSurvivors) {
32910
+ const mutants = plan.map(
32911
+ (m) => `- ${m.id} | ${m.file}:${m.line}:${m.column} | ${m.kind} | "${m.original}" -> "${m.replacement}"`
32912
+ ).join("\n");
32913
+ const prior = priorSurvivors.length > 0 ? `
32914
+ These mutants survived a previous pass (pass ${pass - 1}) \u2014 re-verify them against the STRENGTHENED tests:
32915
+ ${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join("\n")}` : "";
32916
+ return [
32917
+ "Execute this deterministic mutation plan against the current checkout.",
32918
+ "",
32919
+ "For each mutant, in order:",
32920
+ "1. Apply ONLY that mutation at its exact (file, line, column).",
32921
+ `2. Run the test command: ${i.testCommand}${i.cwd ? ` (cwd: ${i.cwd})` : ""}`,
32922
+ "3. Record killed (tests failed \u2014 quote first failing assertion), survived (suite green), or killed-by-hang (the test command timed out or was aborted \u2014 the mutation broke the suite by non-termination; record the timeout as evidence, do NOT report it as survived).",
32923
+ "4. Restore the file byte-for-byte before the next mutant.",
32924
+ "",
32925
+ "Mutants:",
32926
+ mutants,
32927
+ prior,
32928
+ "",
32929
+ "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.",
32930
+ "Finish with submit_result, then repeat the same JSON as your final text."
32931
+ ].join("\n");
32932
+ }
32933
+ function buildStrengthenTask(survivors, i, attempt) {
32934
+ const confirmed = survivors.filter((s) => s.status === "survived");
32935
+ const unverified = survivors.filter((s) => s.status === "skipped");
32936
+ const row = (s) => `- ${s.id} | ${s.file}:${s.line} | ${s.kind}${s.evidence ? ` | ${s.evidence}` : ""}`;
32937
+ return [
32938
+ `Strengthen the tests so the mutants below die (attempt ${attempt}).`,
32939
+ "",
32940
+ ...confirmed.length > 0 ? [
32941
+ "CONFIRMED SURVIVORS \u2014 each was a deliberate sabotage of production code that the current suite did NOT catch:",
32942
+ ...confirmed.map(row),
32943
+ ""
32944
+ ] : [],
32945
+ ...unverified.length > 0 ? [
32946
+ "UNVERIFIED \u2014 these mutations were never actually re-tested (the re-verify pass skipped or did not report them). Do NOT assume the suite misses them: first apply each mutation, run the tests, and confirm it really survives; if the tests already fail, report that instead of writing new assertions.",
32947
+ ...unverified.map(row),
32948
+ ""
32949
+ ] : [],
32950
+ `Test command that must fail under each CONFIRMED mutant: ${i.testCommand}`,
32951
+ "",
32952
+ "For each CONFIRMED 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."
32953
+ ].join("\n");
32954
+ }
32955
+ function collectOutcomes(result, plan) {
32956
+ const fromText = parseTextOutcomes(result);
32957
+ if (fromText.length > 0) {
32958
+ const remaining = [...plan];
32959
+ const matched = [];
32960
+ for (const m of fromText) {
32961
+ const idx = remaining.findIndex((p) => p.id === m.id);
32962
+ if (idx === -1) continue;
32963
+ remaining.splice(idx, 1);
32964
+ matched.push(m);
32965
+ }
32966
+ if (matched.length > 0) {
32967
+ const missing = remaining.map((p) => ({
32968
+ id: p.id,
32969
+ file: p.file,
32970
+ line: p.line,
32971
+ kind: p.kind,
32972
+ status: "skipped",
32973
+ evidence: "not reported by chaos task"
32974
+ }));
32975
+ return [...matched, ...missing];
32976
+ }
32977
+ }
32978
+ return plan.map((p) => ({
32979
+ id: p.id,
32980
+ file: p.file,
32981
+ line: p.line,
32982
+ kind: p.kind,
32983
+ status: "skipped",
32984
+ evidence: result ? `chaos task ended ${result.status}` : "chaos task produced no result"
32985
+ }));
32986
+ }
32987
+ function isKill(status) {
32988
+ return status === "killed" || status === "killed-by-hang";
32989
+ }
32990
+ function parseTextOutcomes(result) {
32991
+ const text2 = typeof result?.result === "string" ? result.result : void 0;
32992
+ if (!text2) return [];
32993
+ const parsed = parseMutationReport(text2);
32994
+ if (!parsed) return [];
32995
+ return parsed.mutants.map((m) => ({
32996
+ id: m.id,
32997
+ file: m.file,
32998
+ line: m.line,
32999
+ kind: m.kind,
33000
+ status: m.status,
33001
+ evidence: m.evidence
33002
+ }));
33003
+ }
33004
+
32139
33005
  // src/coordination/director-tools.ts
32140
33006
  function makeSpawnTool(director, roster) {
32141
33007
  const dispatchCatalog = () => {
@@ -32428,7 +33294,7 @@ function makeKanbanQueueTool(director, roster) {
32428
33294
  try {
32429
33295
  const config = buildKanbanSubagentConfig(claim.task, i, roster, instantiateRosterConfig);
32430
33296
  subagentId = await director.spawn(config);
32431
- const dispatchTaskId = randomUUID14();
33297
+ const dispatchTaskId = randomUUID15();
32432
33298
  const taskSpec = {
32433
33299
  id: dispatchTaskId,
32434
33300
  subagentId,
@@ -32704,6 +33570,7 @@ function buildDirectorToolset(director, roster) {
32704
33570
  makeAskResultTool(director),
32705
33571
  makeRollUpTool(director),
32706
33572
  makeQualityGateTool(director, roster),
33573
+ makeMutationTestTool(director, roster),
32707
33574
  makeTerminateTool(director),
32708
33575
  makeTerminateAllTool(director),
32709
33576
  makeFleetTool(director),
@@ -32790,7 +33657,7 @@ import * as fsp29 from "node:fs/promises";
32790
33657
  import * as path63 from "node:path";
32791
33658
 
32792
33659
  // src/storage/session-store.ts
32793
- import { randomUUID as randomUUID16 } from "node:crypto";
33660
+ import { randomUUID as randomUUID17 } from "node:crypto";
32794
33661
  import * as fsp28 from "node:fs/promises";
32795
33662
  import * as path62 from "node:path";
32796
33663
  init_client();
@@ -33961,7 +34828,7 @@ var FileSessionWriter = class _FileSessionWriter {
33961
34828
  // src/storage/session-checkpoint-cas.ts
33962
34829
  init_atomic_write();
33963
34830
  import { spawn as spawn5 } from "node:child_process";
33964
- import { createHash as createHash18, randomUUID as randomUUID15 } from "node:crypto";
34831
+ import { createHash as createHash18, randomUUID as randomUUID16 } from "node:crypto";
33965
34832
  import * as fsp15 from "node:fs/promises";
33966
34833
  import * as path54 from "node:path";
33967
34834
  init_error();
@@ -34199,7 +35066,7 @@ var SessionCheckpointCas = class {
34199
35066
  }
34200
35067
  const temp = path54.join(
34201
35068
  path54.dirname(target),
34202
- `.${path54.basename(target)}.${process.pid}.${randomUUID15()}.tmp`
35069
+ `.${path54.basename(target)}.${process.pid}.${randomUUID16()}.tmp`
34203
35070
  );
34204
35071
  let handle;
34205
35072
  try {
@@ -36007,7 +36874,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
36007
36874
  onAppend;
36008
36875
  onAppendBatch;
36009
36876
  catalogClient;
36010
- maintenanceHolderId = randomUUID16();
36877
+ maintenanceHolderId = randomUUID17();
36011
36878
  _loadCache = /* @__PURE__ */ new Map();
36012
36879
  loadCache = new SessionLoadCache(this._loadCache);
36013
36880
  _indexCache = null;
@@ -36699,7 +37566,7 @@ async function readDirectorSubagentSession(args) {
36699
37566
  }
36700
37567
 
36701
37568
  // src/core/fallback-model.ts
36702
- import { randomUUID as randomUUID17 } from "node:crypto";
37569
+ import { randomUUID as randomUUID18 } from "node:crypto";
36703
37570
 
36704
37571
  // src/types/provider.ts
36705
37572
  init_errors();
@@ -36709,6 +37576,18 @@ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not eno
36709
37576
  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;
36710
37577
 
36711
37578
  // src/types/provider.ts
37579
+ var REASONING_EFFORT_LEVELS = [
37580
+ "none",
37581
+ "minimal",
37582
+ "low",
37583
+ "medium",
37584
+ "high",
37585
+ "xhigh",
37586
+ "max"
37587
+ ];
37588
+ function isReasoningEffort(value) {
37589
+ return typeof value === "string" && REASONING_EFFORT_LEVELS.includes(value);
37590
+ }
36712
37591
  function effectiveInputTokens(usage) {
36713
37592
  return usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
36714
37593
  }
@@ -37731,7 +38610,7 @@ function createFallbackModelExtension(deps) {
37731
38610
  let gateRequestId;
37732
38611
  const configuredGateSeconds = cfg.fallbackGateSeconds ?? deps.fallbackGateSeconds;
37733
38612
  if (configuredGateSeconds !== 0 && deps.fallbackGate && usableChain.length > 0) {
37734
- gateRequestId = randomUUID17();
38613
+ gateRequestId = randomUUID18();
37735
38614
  const autoSwitchSeconds = Math.max(1, configuredGateSeconds ?? 7);
37736
38615
  const gateCandidates = usableChain.map((e) => ({
37737
38616
  providerId: e.providerId,
@@ -38703,7 +39582,7 @@ function hashStr(s) {
38703
39582
  }
38704
39583
 
38705
39584
  // src/coordination/multi-agent-coordinator.ts
38706
- import { randomUUID as randomUUID18 } from "node:crypto";
39585
+ import { randomUUID as randomUUID19 } from "node:crypto";
38707
39586
  import { EventEmitter as EventEmitter2 } from "node:events";
38708
39587
 
38709
39588
  // src/coordination/coordinator/error-classifier.ts
@@ -38828,6 +39707,22 @@ var EXPLORE_COMPANION_AGENT = {
38828
39707
  textStream: "silent",
38829
39708
  toolStream: "silent"
38830
39709
  };
39710
+ var CHAOS_MONKEY_AGENT = {
39711
+ ...defineAgent("chaos-monkey", "Chaos Monkey"),
39712
+ tools: [...TOOLS.build],
39713
+ skillNames: ["testing", "typescript-strict"],
39714
+ spawnBudgetExempt: true,
39715
+ // Run in the live checkout: mutation targets are usually freshly
39716
+ // written and uncommitted — a worktree spawned from HEAD would not
39717
+ // contain them and every mutant would drift. The mutation_test tool
39718
+ // honors this value as its default; callers can still override per
39719
+ // call via its `chaosWorktree` input when targets are committed and
39720
+ // isolation is wanted.
39721
+ worktree: "off",
39722
+ // Report travels via submit_result + final text, not the leader's stream.
39723
+ textStream: "silent",
39724
+ toolStream: "silent"
39725
+ };
38831
39726
  var CRITIC_AGENT = defineAgent("critic", "Critic");
38832
39727
  var GENERIC_AGENT = defineAgent("generic", "Generic Project Agent");
38833
39728
  function withDispatchMetadata(definition) {
@@ -38847,6 +39742,7 @@ var FLEET_ROSTER = {
38847
39742
  generic: GENERIC_AGENT,
38848
39743
  "shadow-agent": SHADOW_AGENT,
38849
39744
  "explore-companion": EXPLORE_COMPANION_AGENT,
39745
+ "chaos-monkey": CHAOS_MONKEY_AGENT,
38850
39746
  ...Object.fromEntries(
38851
39747
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, withDispatchMetadata(d)])
38852
39748
  )
@@ -38877,6 +39773,16 @@ var FLEET_ROSTER_BUDGETS = {
38877
39773
  maxTokens: 96e3,
38878
39774
  maxCostUsd: 0.5
38879
39775
  },
39776
+ "chaos-monkey": {
39777
+ // A mutation pass is many short apply/run/restore cycles — per-mutant
39778
+ // work is tiny, but a large plan (25 mutants/file × N files) needs
39779
+ // headroom. Idle-based reaping covers a stalled pass.
39780
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
39781
+ maxIterations: 2e3,
39782
+ maxToolCalls: 6e3,
39783
+ maxTokens: 96e3,
39784
+ maxCostUsd: 0.5
39785
+ },
38880
39786
  ...Object.fromEntries(
38881
39787
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
38882
39788
  )
@@ -38939,7 +39845,8 @@ async function executeSubagentWithTimeout({
38939
39845
  budget,
38940
39846
  preemptFraction = TIMEOUT_PREEMPT_FRACTION,
38941
39847
  abortSubagent,
38942
- currentSessionId
39848
+ currentSessionId,
39849
+ gracefulFinish
38943
39850
  }) {
38944
39851
  const initialTimeoutMs = budget.limits.timeoutMs;
38945
39852
  const idleLimitMs = budget.limits.idleTimeoutMs;
@@ -38970,9 +39877,17 @@ async function executeSubagentWithTimeout({
38970
39877
  const scheduleNext = () => {
38971
39878
  const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
38972
39879
  const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
38973
- const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
38974
- const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
38975
- armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
39880
+ const idleRemaining = idleLimitMs === void 0 || gracefulFinish !== void 0 && initialTimeoutMs !== void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
39881
+ const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit || gracefulFinish !== void 0 ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
39882
+ const next = Math.min(wallRemaining, idleRemaining, preemptRemaining);
39883
+ if (!Number.isFinite(next)) {
39884
+ if (timer) {
39885
+ clearTimeout(timer);
39886
+ timer = null;
39887
+ }
39888
+ return;
39889
+ }
39890
+ armFor(Math.max(25, next));
38976
39891
  };
38977
39892
  const negotiateTimeout = async (used, limit) => {
38978
39893
  const handler = budget.onThreshold;
@@ -39021,6 +39936,10 @@ async function executeSubagentWithTimeout({
39021
39936
  const wallExceeded = wallLimit !== void 0 && elapsed2 >= wallLimit;
39022
39937
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
39023
39938
  if (idleExceeded && !wallExceeded) {
39939
+ if (gracefulFinish !== void 0 && initialTimeoutMs !== void 0) {
39940
+ scheduleNext();
39941
+ return;
39942
+ }
39024
39943
  const sessionId = currentSessionId();
39025
39944
  budget._events?.emit("budget.threshold_reached", {
39026
39945
  ...sessionId ? { sessionId } : {},
@@ -39037,7 +39956,7 @@ async function executeSubagentWithTimeout({
39037
39956
  reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
39038
39957
  return;
39039
39958
  }
39040
- if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed2 >= wallLimit * preemptFraction) {
39959
+ if (wallLimit !== void 0 && !wallExceeded && gracefulFinish === void 0 && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed2 >= wallLimit * preemptFraction) {
39041
39960
  const activityTs = Date.now() - budget.idleMs();
39042
39961
  if (activityTs <= lastGrantActivityTs) {
39043
39962
  preemptState = "locked" /* LOCKED */;
@@ -39071,6 +39990,22 @@ async function executeSubagentWithTimeout({
39071
39990
  return;
39072
39991
  }
39073
39992
  const limit = wallLimit ?? 0;
39993
+ if (gracefulFinish !== void 0) {
39994
+ if (!budget.graceGranted) {
39995
+ const reason = `wall-clock budget of ${Math.round(limit / 1e3)}s reached`;
39996
+ if (budget.notifyFinish(reason, { graceMs: gracefulFinish.graceMs })) {
39997
+ scheduleNext();
39998
+ return;
39999
+ }
40000
+ abortSubagent(ctx.subagentId);
40001
+ reject(new BudgetExceededError("timeout", limit, elapsed2));
40002
+ return;
40003
+ } else {
40004
+ abortSubagent(ctx.subagentId);
40005
+ reject(new BudgetExceededError("timeout", limit, elapsed2));
40006
+ return;
40007
+ }
40008
+ }
39074
40009
  if (!budget.onThreshold) {
39075
40010
  abortSubagent(ctx.subagentId);
39076
40011
  reject(new BudgetExceededError("timeout", limit, elapsed2));
@@ -39218,7 +40153,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39218
40153
  return { ...subagent, name: display };
39219
40154
  }
39220
40155
  async spawn(subagent) {
39221
- const id = subagent.id || randomUUID18();
40156
+ const id = subagent.id || randomUUID19();
39222
40157
  const cfg = this.withNickname(subagent, id);
39223
40158
  if (this.subagents.has(id)) {
39224
40159
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -39455,6 +40390,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39455
40390
  completeTask(result) {
39456
40391
  this.recordCompletion(result);
39457
40392
  }
40393
+ /**
40394
+ * Ask every RUNNING subagent that opted into `gracefulFinish` to finish its
40395
+ * task in its own turn (see coordination/subagent-finish.ts). This is the
40396
+ * leader-side entry point for "the leader agent has finished": it delivers
40397
+ * an in-band notification between tool batches — never an interrupt, never
40398
+ * an abort. Each notified subagent keeps its existing time budget and
40399
+ * accelerates; the watchdog still bounds the maximum lifetime.
40400
+ *
40401
+ * Subagents without the policy opted in are deliberately untouched — their
40402
+ * lifecycle remains the legacy watchdog contract.
40403
+ *
40404
+ * Returns the number of subagents actually notified.
40405
+ */
40406
+ requestFinish(reason) {
40407
+ let notified = 0;
40408
+ for (const subagent of this.subagents.values()) {
40409
+ if (subagent.status !== "running") continue;
40410
+ if (!resolveGracefulFinish(subagent.config)) continue;
40411
+ const budget = subagent.activeBudget;
40412
+ if (!budget) continue;
40413
+ const usage = budget.usage();
40414
+ if (usage.iterations === 0 && usage.toolCalls === 0) continue;
40415
+ if (budget.notifyFinish(reason)) notified++;
40416
+ }
40417
+ return notified;
40418
+ }
39458
40419
  // --- internal dispatching ---------------------------------------------
39459
40420
  tryDispatchNext() {
39460
40421
  while (this.canDispatch()) {
@@ -39628,7 +40589,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39628
40589
  idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
39629
40590
  },
39630
40591
  "auto",
39631
- { sessionId: () => this.currentSessionId() }
40592
+ {
40593
+ sessionId: () => this.currentSessionId(),
40594
+ subagentId,
40595
+ // Graceful-finish runs own wall-clock enforcement to the watchdog so
40596
+ // the notify-then-bound lifecycle cannot be raced by tool.progress
40597
+ // heartbeats calling checkTimeout() (see subagent-budget.ts).
40598
+ ...resolveGracefulFinish(subagent.config) ? { wallClockWatchdogOwned: true } : {}
40599
+ }
39632
40600
  );
39633
40601
  subagent.activeBudget = budget;
39634
40602
  if (!this.runner) {
@@ -39661,7 +40629,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39661
40629
  task,
39662
40630
  runCtx,
39663
40631
  budget,
39664
- subagent.config.preemptFraction
40632
+ subagent.config.preemptFraction,
40633
+ resolveGracefulFinish(subagent.config)
39665
40634
  );
39666
40635
  result = {
39667
40636
  subagentId,
@@ -39691,13 +40660,14 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39691
40660
  }
39692
40661
  this.recordCompletion(result);
39693
40662
  }
39694
- async executeWithTimeout(runner, task, ctx, budget, preemptFraction) {
40663
+ async executeWithTimeout(runner, task, ctx, budget, preemptFraction, gracefulFinish) {
39695
40664
  return executeSubagentWithTimeout({
39696
40665
  runner,
39697
40666
  task,
39698
40667
  ctx,
39699
40668
  budget,
39700
40669
  preemptFraction,
40670
+ gracefulFinish,
39701
40671
  abortSubagent: (subagentId) => this.subagents.get(subagentId)?.abortController.abort(),
39702
40672
  currentSessionId: () => this.currentSessionId()
39703
40673
  });
@@ -40007,6 +40977,7 @@ function worktreeOwnerLabel(task, config) {
40007
40977
  }
40008
40978
 
40009
40979
  // src/coordination/director.ts
40980
+ var BUSY_REARM_FLOOR_MS = 1e3;
40010
40981
  var Director = class _Director {
40011
40982
  /* eslint-disable-next-line @typescript-eslint/no-unused-vars — just a cast helper */
40012
40983
  static _asManifestEntry(v) {
@@ -40072,6 +41043,13 @@ var Director = class _Director {
40072
41043
  subagentIdleTimeoutMs;
40073
41044
  retireSubagentOnTaskComplete;
40074
41045
  subagentIdleTimers = /* @__PURE__ */ new Map();
41046
+ /**
41047
+ * Effective idle window per subagent (spawn-time `idleTimeoutMs` override
41048
+ * or the Director-wide default; undefined = no window). Internal-task
41049
+ * completion re-arms with THIS value, not the Director-wide default, so
41050
+ * a subagent-configured window survives its first internal probe.
41051
+ */
41052
+ subagentIdleDelayMs = /* @__PURE__ */ new Map();
40075
41053
  sharedScratchpadPath;
40076
41054
  maxSpawns;
40077
41055
  maxSpawnDepth;
@@ -40104,7 +41082,7 @@ var Director = class _Director {
40104
41082
  sessionProvider;
40105
41083
  sessionModel;
40106
41084
  constructor(opts) {
40107
- this.id = opts.config.coordinatorId || randomUUID19();
41085
+ this.id = opts.config.coordinatorId || randomUUID20();
40108
41086
  this.manifestPath = opts.manifestPath;
40109
41087
  this.roster = opts.roster;
40110
41088
  this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -40228,7 +41206,13 @@ var Director = class _Director {
40228
41206
  handleTaskCompleted(payload) {
40229
41207
  const r = payload.result;
40230
41208
  const settled = this.tasks.settle(r);
40231
- if (settled.internal) return;
41209
+ if (settled.internal) {
41210
+ this.armSubagentIdleRetirement(
41211
+ r.subagentId,
41212
+ this.subagentIdleDelayMs.get(r.subagentId) ?? this.subagentIdleTimeoutMs
41213
+ );
41214
+ return;
41215
+ }
40232
41216
  const title = this.tasks.descriptionFor(r.taskId, payload.task.description ?? r.taskId);
40233
41217
  if (!settled.consumedInBand && this.taskResultNotifier) {
40234
41218
  const resultText = typeof r.result === "string" ? r.result : r.result !== void 0 ? safeStringify(r.result) : void 0;
@@ -40293,7 +41277,7 @@ var Director = class _Director {
40293
41277
  }
40294
41278
  this.armSubagentIdleRetirement(
40295
41279
  r.subagentId,
40296
- this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleTimeoutMs
41280
+ this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleDelayMs.get(r.subagentId) ?? this.subagentIdleTimeoutMs
40297
41281
  );
40298
41282
  }
40299
41283
  extensionsFor(subagentId) {
@@ -40311,6 +41295,17 @@ var Director = class _Director {
40311
41295
  isWorkComplete() {
40312
41296
  return this.workCompleteFlag;
40313
41297
  }
41298
+ /**
41299
+ * Ask every running background subagent that opted into `gracefulFinish`
41300
+ * to finish its task in its own turn. In-band notification between tool
41301
+ * batches — no interrupt, no abort; each subagent keeps its time budget and
41302
+ * accelerates. Session shutdown calls this before draining Chimera work so
41303
+ * the post-session reviewer is nudged to complete rather than killed.
41304
+ * Returns the number of subagents notified.
41305
+ */
41306
+ requestFinish(reason) {
41307
+ return this.coordinator.requestFinish(reason);
41308
+ }
40314
41309
  setLeaderBtwNote(note) {
40315
41310
  return this.btwNotes.add(note);
40316
41311
  }
@@ -40368,6 +41363,7 @@ var Director = class _Director {
40368
41363
  this.resolveSpawnModel(config);
40369
41364
  const subagentId = await spawn6(this, config, priceLookup);
40370
41365
  const perSubagentIdleMs = typeof config.idleTimeoutMs === "number" && Number.isFinite(config.idleTimeoutMs) && config.idleTimeoutMs >= 0 ? config.idleTimeoutMs : this.subagentIdleTimeoutMs;
41366
+ this.subagentIdleDelayMs.set(subagentId, perSubagentIdleMs);
40371
41367
  this.armSubagentIdleRetirement(subagentId, perSubagentIdleMs);
40372
41368
  return subagentId;
40373
41369
  }
@@ -40387,7 +41383,7 @@ var Director = class _Director {
40387
41383
  );
40388
41384
  }
40389
41385
  const msg = {
40390
- id: randomUUID19(),
41386
+ id: randomUUID20(),
40391
41387
  type: "task",
40392
41388
  from: this.id,
40393
41389
  to: subagentId,
@@ -40421,6 +41417,7 @@ var Director = class _Director {
40421
41417
  this.budgetPolicy.dispose();
40422
41418
  for (const timer of this.subagentIdleTimers.values()) clearTimeout(timer);
40423
41419
  this.subagentIdleTimers.clear();
41420
+ this.subagentIdleDelayMs.clear();
40424
41421
  await this.coordinator.stopAll();
40425
41422
  this.tasks.resolveWaitersOnShutdown();
40426
41423
  for (const b of this.subagentBridges.values()) {
@@ -40479,6 +41476,7 @@ var Director = class _Director {
40479
41476
  }
40480
41477
  async remove(subagentId) {
40481
41478
  this.clearSubagentIdleRetirement(subagentId);
41479
+ this.subagentIdleDelayMs.delete(subagentId);
40482
41480
  void this.appendSessionEvent({
40483
41481
  type: "agent_stopped",
40484
41482
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -40531,9 +41529,13 @@ var Director = class _Director {
40531
41529
  const timer = setTimeout(() => {
40532
41530
  this.subagentIdleTimers.delete(subagentId);
40533
41531
  const entry = this.coordinator.getStatus().subagents.find((a) => a.id === subagentId);
40534
- if (entry?.status !== "idle") return;
41532
+ if (entry === void 0) return;
41533
+ if (entry.status !== "idle") {
41534
+ this.armSubagentIdleRetirement(subagentId, Math.max(delayMs, BUSY_REARM_FLOOR_MS));
41535
+ return;
41536
+ }
40535
41537
  if (this.coordinator.listPendingTasks().some((task) => task.subagentId === subagentId)) {
40536
- this.armSubagentIdleRetirement(subagentId, this.subagentIdleTimeoutMs);
41538
+ this.armSubagentIdleRetirement(subagentId, Math.max(delayMs, BUSY_REARM_FLOOR_MS));
40537
41539
  return;
40538
41540
  }
40539
41541
  void this.remove(subagentId).catch(
@@ -40641,7 +41643,7 @@ var Director = class _Director {
40641
41643
  };
40642
41644
 
40643
41645
  // src/coordination/fleet-manager.ts
40644
- import { randomUUID as randomUUID20 } from "node:crypto";
41646
+ import { randomUUID as randomUUID21 } from "node:crypto";
40645
41647
  import * as fsp31 from "node:fs/promises";
40646
41648
  import * as path64 from "node:path";
40647
41649
  init_atomic_write();
@@ -40709,7 +41711,7 @@ var FleetManager = class {
40709
41711
  maxContext;
40710
41712
  constructor(opts = {}) {
40711
41713
  this.manifestPath = opts.manifestPath;
40712
- this.directorRunId = opts.directorRunId ?? randomUUID20();
41714
+ this.directorRunId = opts.directorRunId ?? randomUUID21();
40713
41715
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
40714
41716
  this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
40715
41717
  this.spawnDepth = opts.spawnDepth ?? 0;
@@ -42761,7 +43763,7 @@ function makeFleetStatusTool(opts = {}) {
42761
43763
  }
42762
43764
 
42763
43765
  // src/coordination/fleet-supervisor.ts
42764
- import { randomUUID as randomUUID21 } from "node:crypto";
43766
+ import { randomUUID as randomUUID22 } from "node:crypto";
42765
43767
  var COLLAB_ID_PREFIXES2 = ["bug-hunter-", "refactor-planner-", "critic-"];
42766
43768
  var DEFAULTS = {
42767
43769
  intervalMs: 2e4,
@@ -43039,7 +44041,7 @@ var FleetSupervisor = class {
43039
44041
  */
43040
44042
  async decide(question, context, options, risk) {
43041
44043
  const request = {
43042
- id: `fleetsup-${randomUUID21()}`,
44044
+ id: `fleetsup-${randomUUID22()}`,
43043
44045
  sessionId: this.opts.sessionId?.(),
43044
44046
  source: "system",
43045
44047
  question,
@@ -43435,7 +44437,7 @@ function attachAutoExtend(events, policy = {}) {
43435
44437
  }
43436
44438
 
43437
44439
  // src/coordination/delegate-tool.ts
43438
- import { randomUUID as randomUUID22 } from "node:crypto";
44440
+ import { randomUUID as randomUUID23 } from "node:crypto";
43439
44441
  import * as fsp32 from "node:fs/promises";
43440
44442
  import * as path69 from "node:path";
43441
44443
  init_error();
@@ -43642,7 +44644,7 @@ function createDelegateTool(opts) {
43642
44644
  }
43643
44645
  const description = delegatedTask;
43644
44646
  const taskId = await dir.assign({
43645
- id: randomUUID22(),
44647
+ id: randomUUID23(),
43646
44648
  description,
43647
44649
  subagentId
43648
44650
  });
@@ -43863,7 +44865,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
43863
44865
  function freshHandoffConfig(cfg, role, handoffCount) {
43864
44866
  return {
43865
44867
  ...cfg,
43866
- id: role ? `${role}-${randomUUID22().slice(0, 8)}` : `${cfg.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "subagent"}-handoff-${handoffCount}-${randomUUID22().slice(0, 6)}`
44868
+ id: role ? `${role}-${randomUUID23().slice(0, 8)}` : `${cfg.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "subagent"}-handoff-${handoffCount}-${randomUUID23().slice(0, 6)}`
43867
44869
  };
43868
44870
  }
43869
44871
  function continuationFor(result, partial, config) {
@@ -43923,7 +44925,7 @@ function instantiateRosterConfig2(role, base, requestedTimeoutMs, defaultTimeout
43923
44925
  timeoutMs: requestedTimeoutMs === void 0 ? rosterTimeoutMs ?? defaultTimeoutMs : void 0,
43924
44926
  // Give each spawn a fresh id so parallel or repeated delegates
43925
44927
  // can use the same role safely.
43926
- id: `${role}-${randomUUID22().slice(0, 8)}`
44928
+ id: `${role}-${randomUUID23().slice(0, 8)}`
43927
44929
  };
43928
44930
  }
43929
44931
  function hintForKind(kind, retryable, backoffMs, partial) {
@@ -44049,7 +45051,7 @@ async function readSubagentPartial(opts, subagentId) {
44049
45051
  }
44050
45052
 
44051
45053
  // src/coordination/explore-companion.ts
44052
- import { randomUUID as randomUUID23 } from "node:crypto";
45054
+ import { randomUUID as randomUUID24 } from "node:crypto";
44053
45055
  var DEFAULT_EXPLORE_COMPANION_AGENT_ID = "explore-companion";
44054
45056
  var DEFAULT_PROBE_COOLDOWN_MS = 12e4;
44055
45057
  var DEFAULT_MAX_PENDING_PROBES = 8;
@@ -44189,16 +45191,14 @@ var ExploreCompanion = class {
44189
45191
  this.running = true;
44190
45192
  this.unsubscribers.push(
44191
45193
  this.opts.events.on("tool.executed", (e) => {
44192
- const lsid = this.resolveLeaderSessionId();
44193
- if (lsid && e.sessionId && e.sessionId !== lsid) return;
45194
+ if (e.sessionId !== this.resolveLeaderSessionId()) return;
44194
45195
  this.trackToolExecuted(e);
44195
45196
  })
44196
45197
  );
44197
45198
  if (this.cfg.signals.todoInProgress && this.resolveLeaderAgentId()) {
44198
45199
  this.unsubscribers.push(
44199
45200
  this.opts.events.on("session.agents_updated", (e) => {
44200
- const lsid = this.resolveLeaderSessionId();
44201
- if (lsid && e.sessionId && e.sessionId !== lsid) return;
45201
+ if (e.sessionId !== this.resolveLeaderSessionId()) return;
44202
45202
  this.trackAgentTodos(e.agents);
44203
45203
  })
44204
45204
  );
@@ -44206,8 +45206,7 @@ var ExploreCompanion = class {
44206
45206
  if (this.cfg.signals.errorSymbol) {
44207
45207
  this.unsubscribers.push(
44208
45208
  this.opts.events.on("error", (e) => {
44209
- const lsid = this.resolveLeaderSessionId();
44210
- if (lsid && e.sessionId && e.sessionId !== lsid) return;
45209
+ if (e.sessionId !== this.resolveLeaderSessionId()) return;
44211
45210
  this.trackError(e.err);
44212
45211
  })
44213
45212
  );
@@ -44235,7 +45234,7 @@ var ExploreCompanion = class {
44235
45234
  if (e.ok && this.cfg.signals.editUnreadFile && this.cfg.fileEditTools.has(tool) && path131) {
44236
45235
  if (!this.readSet.has(path131)) {
44237
45236
  this.engage({
44238
- id: randomUUID23(),
45237
+ id: randomUUID24(),
44239
45238
  probe: `Map file ${path131}: role, exports, dependencies, and callers \u2014 the leader is about to edit it.`,
44240
45239
  hint: { file: path131 },
44241
45240
  context: `Leader edited ${path131} without reading it first.`,
@@ -44250,7 +45249,7 @@ var ExploreCompanion = class {
44250
45249
  if (!this.readSet.has(path131)) {
44251
45250
  this.readSet.add(path131);
44252
45251
  this.engage({
44253
- id: randomUUID23(),
45252
+ id: randomUUID24(),
44254
45253
  probe: `Skeleton + callers + dependents of ${path131}: what it exports, who imports it, and how it fits the feature flow.`,
44255
45254
  hint: { file: path131 },
44256
45255
  context: `Leader read unfamiliar file ${path131}.`,
@@ -44265,7 +45264,7 @@ var ExploreCompanion = class {
44265
45264
  const input = e.input ?? {};
44266
45265
  const query = typeof input["query"] === "string" ? input["query"] : typeof input["pattern"] === "string" ? input["pattern"] : "";
44267
45266
  this.engage({
44268
- id: randomUUID23(),
45267
+ id: randomUUID24(),
44269
45268
  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.`,
44270
45269
  hint: query ? { symbol: query } : void 0,
44271
45270
  context: `${e.name} for "${query}" returned zero results.`,
@@ -44286,7 +45285,7 @@ var ExploreCompanion = class {
44286
45285
  const mentions = extractSubjectTokens(todo.content);
44287
45286
  const first = mentions[0];
44288
45287
  this.engage({
44289
- id: randomUUID23(),
45288
+ id: randomUUID24(),
44290
45289
  probe: `Pre-map the files/symbols behind this in-progress todo: "${todo.content.slice(0, 160)}".`,
44291
45290
  hint: first ? { [first.kind]: first.value } : void 0,
44292
45291
  context: `Todo "${todo.content.slice(0, 120)}" flipped to in_progress.`,
@@ -44302,7 +45301,7 @@ var ExploreCompanion = class {
44302
45301
  const tokens = extractSubjectTokens(err.message);
44303
45302
  for (const token of tokens.slice(0, 2)) {
44304
45303
  this.engage({
44305
- id: randomUUID23(),
45304
+ id: randomUUID24(),
44306
45305
  probe: `What is ${token.value}, where does it live, and who uses it? The leader hit an error naming it.`,
44307
45306
  hint: { [token.kind]: token.value },
44308
45307
  context: `Error: ${err.message.slice(0, 300)}`,
@@ -44320,12 +45319,21 @@ var ExploreCompanion = class {
44320
45319
  limit: 20
44321
45320
  });
44322
45321
  const lsid = this.resolveLeaderSessionId();
45322
+ const selfRecipients = new Set(
45323
+ [
45324
+ this.cfg.companionAgentId,
45325
+ mailboxIdentityBase(this.cfg.companionAgentId),
45326
+ ...lsid != null ? [sessionRecipient(lsid)] : []
45327
+ ].map((r) => r.toLowerCase())
45328
+ );
44323
45329
  for (const msg of messages) {
44324
45330
  if (msg.type !== "ask" && msg.type !== "assign") continue;
44325
- const fromLeader = isMailboxLeader(msg.from) || lsid != null && msg.senderSessionId === lsid;
45331
+ const to = msg.to.trim().toLowerCase();
45332
+ if (to !== "*" && !selfRecipients.has(to)) continue;
45333
+ const fromLeader = msg.senderSessionId === void 0 && isMailboxLeader(msg.from) || lsid != null && msg.senderSessionId === lsid;
44326
45334
  if (!fromLeader) continue;
44327
45335
  this.engage({
44328
- id: randomUUID23(),
45336
+ id: randomUUID24(),
44329
45337
  probe: msg.body.trim().slice(0, 2e3) || msg.subject,
44330
45338
  context: `Direct ask from ${msg.from}: ${msg.subject}`,
44331
45339
  source: "mailbox_ask",
@@ -44407,6 +45415,22 @@ var SEND_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
44407
45415
  // receiver trusts the sender-asserted `sessionId`; the boundary must
44408
45416
  // refuse the field entirely.
44409
45417
  ]);
45418
+ var SEND_FORBIDDEN_FIELDS = /* @__PURE__ */ new Set([
45419
+ "from",
45420
+ "sessionAffinity"
45421
+ ]);
45422
+ function filterMailboxSendPayload(input) {
45423
+ const payload = {};
45424
+ const stripped = [];
45425
+ for (const key of Object.keys(input)) {
45426
+ if (SEND_ALLOWED_FIELDS.has(key) || SEND_FORBIDDEN_FIELDS.has(key)) {
45427
+ payload[key] = input[key];
45428
+ } else {
45429
+ stripped.push(key);
45430
+ }
45431
+ }
45432
+ return { payload, stripped };
45433
+ }
44410
45434
  var ACK_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
44411
45435
  "messageId",
44412
45436
  "read",
@@ -44644,7 +45668,9 @@ function makeMailSendTool(opts = {}) {
44644
45668
  required: ["to", "subject", "body"]
44645
45669
  },
44646
45670
  async execute(input, ctx) {
44647
- const i = input ?? {};
45671
+ const { payload: i, stripped } = filterMailboxSendPayload(
45672
+ input ?? {}
45673
+ );
44648
45674
  const rawTo = i.to;
44649
45675
  const subject2 = i.subject;
44650
45676
  const body = i.body;
@@ -44667,15 +45693,13 @@ function makeMailSendTool(opts = {}) {
44667
45693
  recipientAliases: /* @__PURE__ */ new Set([codecIdentity.baseId]),
44668
45694
  sessionId: codecIdentity.sessionId
44669
45695
  };
45696
+ let parsed;
44670
45697
  try {
44671
- parseMailboxSendInput(i, codecActor);
45698
+ parsed = parseMailboxSendInput(i, codecActor);
44672
45699
  } catch (err) {
44673
45700
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
44674
45701
  }
44675
- const audience = i.audience;
44676
- if (audience !== void 0 && audience !== "all" && audience !== "leaders") {
44677
- return { ok: false, error: '"audience" must be "all" or "leaders".' };
44678
- }
45702
+ const audience = parsed.audience;
44679
45703
  const mb = resolveMailbox(ctx);
44680
45704
  const identity2 = await register(mb, ctx);
44681
45705
  const requestedTo = normalizeRecipient(rawTo, identity2.sessionId);
@@ -44689,10 +45713,10 @@ function makeMailSendTool(opts = {}) {
44689
45713
  to: delivery.to,
44690
45714
  type: resolvedType,
44691
45715
  audience: delivery.audience,
44692
- subject: subject2,
44693
- body,
44694
- priority: i.priority ?? "normal",
44695
- replyTo: i.replyTo,
45716
+ subject: parsed.subject,
45717
+ body: parsed.body,
45718
+ priority: parsed.priority,
45719
+ replyTo: parsed.replyTo,
44696
45720
  senderSessionId: identity2.sessionId
44697
45721
  });
44698
45722
  return {
@@ -44700,7 +45724,9 @@ function makeMailSendTool(opts = {}) {
44700
45724
  messageId: msg.id,
44701
45725
  from: identity2.callerId,
44702
45726
  to: msg.to,
44703
- summary: `Mail sent to ${msg.to === "*" ? "all agents" : msg.to} as ${identity2.callerId}.`
45727
+ // Surfacing what was stripped keeps the send auditable without
45728
+ // re-introducing the clutter into the payload itself.
45729
+ ...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}.` }
44704
45730
  };
44705
45731
  }
44706
45732
  };
@@ -48226,7 +49252,7 @@ function createAgentMonitorService(opts) {
48226
49252
  }
48227
49253
 
48228
49254
  // src/coordination/autonomous-brain.ts
48229
- import { randomUUID as randomUUID24 } from "node:crypto";
49255
+ import { randomUUID as randomUUID25 } from "node:crypto";
48230
49256
  var AutonomousBrain = class {
48231
49257
  graph;
48232
49258
  // Fleet bus for emitting decisions — null-safe, no-op if not provided
@@ -48332,7 +49358,7 @@ var AutonomousBrain = class {
48332
49358
  consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
48333
49359
  }));
48334
49360
  return this.decideAuto({
48335
- id: randomUUID24(),
49361
+ id: randomUUID25(),
48336
49362
  source,
48337
49363
  decisionType: "spawn",
48338
49364
  question: `Should we spawn a subagent for this task?`,
@@ -48375,7 +49401,7 @@ var AutonomousBrain = class {
48375
49401
  }
48376
49402
  ];
48377
49403
  return this.decideAuto({
48378
- id: randomUUID24(),
49404
+ id: randomUUID25(),
48379
49405
  source,
48380
49406
  decisionType: "approve_change",
48381
49407
  question: `Should we approve the change "${change.title}"?`,
@@ -48434,7 +49460,7 @@ var AutonomousBrain = class {
48434
49460
  consequence: "Break the task into smaller sub-tasks"
48435
49461
  });
48436
49462
  return this.decideAuto({
48437
- id: randomUUID24(),
49463
+ id: randomUUID25(),
48438
49464
  source,
48439
49465
  decisionType: "escalate_task",
48440
49466
  question: `Task failed: ${error2.slice(0, 100)}. How should we proceed?`,
@@ -48568,12 +49594,12 @@ ${ctx.error}`);
48568
49594
  };
48569
49595
 
48570
49596
  // src/coordination/autonomous-coordinator.ts
48571
- import { randomUUID as randomUUID27 } from "node:crypto";
49597
+ import { randomUUID as randomUUID28 } from "node:crypto";
48572
49598
 
48573
49599
  // src/coordination/knowledge-graph.ts
48574
49600
  init_file_permissions();
48575
49601
  init_atomic_write();
48576
- import { randomUUID as randomUUID25 } from "node:crypto";
49602
+ import { randomUUID as randomUUID26 } from "node:crypto";
48577
49603
  import * as fsp34 from "node:fs/promises";
48578
49604
  import * as path73 from "node:path";
48579
49605
  var DEFAULT_MAX_NODES = 2e3;
@@ -48621,7 +49647,7 @@ var KnowledgeGraph = class _KnowledgeGraph {
48621
49647
  * Returns the node with its assigned id.
48622
49648
  */
48623
49649
  async add(node) {
48624
- const full = { id: randomUUID25(), ...node };
49650
+ const full = { id: randomUUID26(), ...node };
48625
49651
  this.nodes.set(full.id, full);
48626
49652
  this._trackSeq(full.id);
48627
49653
  this._addToIndex(full, this._indexKeys(full));
@@ -48750,8 +49776,8 @@ var KnowledgeGraph = class _KnowledgeGraph {
48750
49776
  if (this.subs.size >= MAX_SUBSCRIPTIONS) {
48751
49777
  throw new Error(`Knowledge graph subscription limit reached (${MAX_SUBSCRIPTIONS})`);
48752
49778
  }
48753
- const channel2 = randomUUID25();
48754
- const sub = { id: randomUUID25(), agentId, filter, channel: channel2 };
49779
+ const channel2 = randomUUID26();
49780
+ const sub = { id: randomUUID26(), agentId, filter, channel: channel2 };
48755
49781
  this.subs.set(channel2, sub);
48756
49782
  this.pendingDeliveries.set(channel2, []);
48757
49783
  return channel2;
@@ -49232,7 +50258,7 @@ var TaskDAG = class {
49232
50258
  };
49233
50259
 
49234
50260
  // src/coordination/task-auctioneer.ts
49235
- import { randomUUID as randomUUID26 } from "node:crypto";
50261
+ import { randomUUID as randomUUID27 } from "node:crypto";
49236
50262
  function isTerminalGoalStatus(status) {
49237
50263
  return status === "done" || status === "failed";
49238
50264
  }
@@ -49355,7 +50381,7 @@ var TaskAuctioneer = class {
49355
50381
  const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
49356
50382
  if (score < this.minConfidence) return false;
49357
50383
  const bid = {
49358
- id: randomUUID26(),
50384
+ id: randomUUID27(),
49359
50385
  taskId,
49360
50386
  agentId: agent.agentId,
49361
50387
  agentName: agent.agentName,
@@ -50292,7 +51318,7 @@ var AutonomousCoordinator = class _AutonomousCoordinator {
50292
51318
  break;
50293
51319
  }
50294
51320
  const decision = await this.brain.decideAuto({
50295
- id: randomUUID27(),
51321
+ id: randomUUID28(),
50296
51322
  source: "system",
50297
51323
  decisionType: "prioritize_goals",
50298
51324
  question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
@@ -51207,7 +52233,7 @@ var TOKENS = {
51207
52233
  init_errors();
51208
52234
 
51209
52235
  // src/hq/factory.ts
51210
- import { createHash as createHash24, randomUUID as randomUUID30 } from "node:crypto";
52236
+ import { createHash as createHash24, randomUUID as randomUUID31 } from "node:crypto";
51211
52237
  import * as fs30 from "node:fs";
51212
52238
  import { hostname as hostname3 } from "node:os";
51213
52239
  import { basename as basename15 } from "node:path";
@@ -51215,13 +52241,13 @@ import { basename as basename15 } from "node:path";
51215
52241
  // src/hq/auth-store.ts
51216
52242
  init_file_permissions();
51217
52243
  init_atomic_write();
51218
- import { createHash as createHash23, randomBytes as randomBytes4, randomUUID as randomUUID28, scrypt, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
52244
+ import { createHash as createHash23, randomBytes as randomBytes4, randomUUID as randomUUID29, scrypt, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
51219
52245
  import * as syncFs from "node:fs";
51220
52246
  import * as fs29 from "node:fs/promises";
51221
52247
  import * as path75 from "node:path";
51222
52248
 
51223
52249
  // src/hq/auth-audit.ts
51224
- import { appendFileSync as appendFileSync2, readFileSync as readFileSync20, mkdirSync as mkdirSync9 } from "node:fs";
52250
+ import { appendFileSync as appendFileSync2, readFileSync as readFileSync21, mkdirSync as mkdirSync9 } from "node:fs";
51225
52251
  import * as path74 from "node:path";
51226
52252
  function hqAuthAuditPath(dataDir) {
51227
52253
  return path74.join(dataDir, "auth-audit.jsonl");
@@ -51230,7 +52256,7 @@ function readHqAuthAuditTail(dataDir, maxEntries = 50) {
51230
52256
  const filePath = hqAuthAuditPath(dataDir);
51231
52257
  let content;
51232
52258
  try {
51233
- content = readFileSync20(filePath, "utf8");
52259
+ content = readFileSync21(filePath, "utf8");
51234
52260
  } catch {
51235
52261
  return [];
51236
52262
  }
@@ -51542,8 +52568,8 @@ function mintHqToken(labelOrOptions) {
51542
52568
  const at = opts.now ?? Date.now();
51543
52569
  const createdAtIso = new Date(at).toISOString();
51544
52570
  return {
51545
- id: randomUUID28(),
51546
- token: randomUUID28().replace(/-/g, "") + randomUUID28().replace(/-/g, ""),
52571
+ id: randomUUID29(),
52572
+ token: randomUUID29().replace(/-/g, "") + randomUUID29().replace(/-/g, ""),
51547
52573
  createdAt: createdAtIso,
51548
52574
  ...opts.label ? { label: opts.label } : {},
51549
52575
  ...opts.ttlMs !== void 0 && Number.isFinite(opts.ttlMs) && opts.ttlMs > 0 ? { expiresAt: new Date(at + opts.ttlMs).toISOString() } : {}
@@ -51603,7 +52629,7 @@ function watchHqAuthFile(dataDir, onChange, opts = {}) {
51603
52629
  }
51604
52630
 
51605
52631
  // src/hq/publisher.ts
51606
- import { randomUUID as randomUUID29 } from "node:crypto";
52632
+ import { randomUUID as randomUUID30 } from "node:crypto";
51607
52633
  import * as v82 from "node:v8";
51608
52634
 
51609
52635
  // src/hq/protocol/governance.ts
@@ -52677,7 +53703,7 @@ var HqPublisher = class {
52677
53703
  this.options = options;
52678
53704
  this.socketFactory = options.socketFactory ?? defaultSocketFactory;
52679
53705
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
52680
- this.idFactory = options.idFactory ?? randomUUID29;
53706
+ this.idFactory = options.idFactory ?? randomUUID30;
52681
53707
  this.capabilities = options.capabilities ?? [
52682
53708
  "telemetry.publish",
52683
53709
  "mailbox.summary",
@@ -53261,7 +54287,7 @@ function createHqPublisherFromEnv(options) {
53261
54287
  const projectAlias = config.projectAlias?.trim() || void 0;
53262
54288
  const projectName = projectAlias ?? options.projectName ?? (basename15(options.projectRoot) || "unknown");
53263
54289
  const client = {
53264
- clientId: `${machineId}:${options.clientKind}:${process.pid}:${randomUUID30().slice(0, 8)}`,
54290
+ clientId: `${machineId}:${options.clientKind}:${process.pid}:${randomUUID31().slice(0, 8)}`,
53265
54291
  kind: options.clientKind,
53266
54292
  machineId,
53267
54293
  ...host ? { hostname: host } : {},
@@ -53530,40 +54556,6 @@ async function injectPendingMailboxMessages(checkMailbox2, foldFn, a, deliveryMo
53530
54556
  return interruptMsg ? { interrupt: true, interruptReason: interruptMsg.body || interruptMsg.subject || "operator interrupt" } : { interrupt: false };
53531
54557
  }
53532
54558
 
53533
- // src/core/btw.ts
53534
- var META_KEY2 = "_btwNotes";
53535
- var MAX_PENDING = 20;
53536
- function readQueue(ctx) {
53537
- const raw = ctx.meta[META_KEY2];
53538
- return Array.isArray(raw) ? raw : [];
53539
- }
53540
- function setBtwNote(ctx, text2) {
53541
- const trimmed = text2.trim();
53542
- if (!trimmed) return readQueue(ctx).length;
53543
- const next = [...readQueue(ctx), trimmed].slice(-MAX_PENDING);
53544
- ctx.meta[META_KEY2] = next;
53545
- return next.length;
53546
- }
53547
- function pendingBtwCount(ctx) {
53548
- return readQueue(ctx).length;
53549
- }
53550
- function consumeBtwNotes(ctx) {
53551
- const notes = readQueue(ctx);
53552
- if (notes.length > 0) delete ctx.meta[META_KEY2];
53553
- return notes;
53554
- }
53555
- function buildBtwBlock(notes) {
53556
- const body = notes.map((n) => `- ${n}`).join("\n");
53557
- return [
53558
- "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
53559
- "your current task; do not restart from scratch unless it contradicts the",
53560
- "goal:",
53561
- "",
53562
- body,
53563
- "]"
53564
- ].join("\n");
53565
- }
53566
-
53567
54559
  // src/core/fleet-pulse.ts
53568
54560
  var DEFAULT_MAX_AGENTS = 15;
53569
54561
  var DEFAULT_MAX_CHARS = 900;
@@ -53571,9 +54563,17 @@ var TASK_SNIPPET_CHARS2 = 60;
53571
54563
  function fleetPulseSignature(statuses) {
53572
54564
  return statuses.map((s) => `${s.agentId}|${s.status}|${s.currentTask ?? ""}`).sort().join("\n");
53573
54565
  }
53574
- function peerLine(s) {
54566
+ function visibleLineKey(s) {
54567
+ const role = s.role && s.role !== s.name ? s.role : "";
54568
+ const task = s.currentTask && s.currentTask.length > TASK_SNIPPET_CHARS2 ? `${s.currentTask.slice(0, TASK_SNIPPET_CHARS2)}\u2026` : s.currentTask ?? "";
54569
+ const tool = s.currentTool || "";
54570
+ const toolCalls = s.toolCalls > 0 ? String(s.toolCalls) : "";
54571
+ return [s.name, role, s.status, task, tool, toolCalls].join("\0");
54572
+ }
54573
+ function peerLine(s, count = 1) {
53575
54574
  const role = s.role && s.role !== s.name ? ` (${s.role})` : "";
53576
- const parts = [`\u2022 ${s.name}${role} \u2014 ${s.status}`];
54575
+ const grouped = count > 1 ? ` \xD7${count}` : "";
54576
+ const parts = [`\u2022 ${s.name}${role}${grouped} \u2014 ${s.status}`];
53577
54577
  if (s.currentTask) {
53578
54578
  const task = s.currentTask.length > TASK_SNIPPET_CHARS2 ? `${s.currentTask.slice(0, TASK_SNIPPET_CHARS2)}\u2026` : s.currentTask;
53579
54579
  parts.push(`"${task}"`);
@@ -53589,13 +54589,20 @@ function buildFleetPulseBlock(statuses, opts) {
53589
54589
  if (peers.length === 0) return null;
53590
54590
  const order = { running: 0, streaming: 0, waiting_user: 1, idle: 2, error: 3, offline: 4 };
53591
54591
  const sorted = [...peers].sort(
53592
- (x, y) => (order[x.status] ?? 5) - (order[y.status] ?? 5) || x.name.localeCompare(y.name)
54592
+ (x, y) => (order[x.status] ?? 5) - (order[y.status] ?? 5) || visibleLineKey(x).localeCompare(visibleLineKey(y))
53593
54593
  );
53594
54594
  const shown = sorted.slice(0, maxAgents);
53595
54595
  const hidden = sorted.length - shown.length;
53596
54596
  const parts = [];
53597
54597
  parts.push(`[FLEET PULSE] ${peers.length} peer${peers.length === 1 ? "" : "s"} online:`);
53598
- for (const s of shown) parts.push(peerLine(s));
54598
+ for (let i = 0; i < shown.length; ) {
54599
+ let run = 1;
54600
+ while (i + run < shown.length && visibleLineKey(shown[i]) === visibleLineKey(shown[i + run])) {
54601
+ run++;
54602
+ }
54603
+ parts.push(peerLine(shown[i], run));
54604
+ i += run;
54605
+ }
53599
54606
  if (hidden > 0) parts.push(`\u2026 +${hidden} more`);
53600
54607
  parts.push(
53601
54608
  "[END FLEET PULSE] (FYI \u2014 coordinate via mail_send; avoid duplicating peers' work)"
@@ -54277,6 +55284,7 @@ function requestLimitExtension(opts) {
54277
55284
  // src/prompts/prompt-journal.ts
54278
55285
  import * as fs31 from "node:fs/promises";
54279
55286
  import * as path77 from "node:path";
55287
+ var defaultScrubber2 = new DefaultSecretScrubber();
54280
55288
  var PROMPT_JOURNAL_RAW_MARKER = "promptJournal.raw";
54281
55289
  async function ensureGitignore(projectRoot) {
54282
55290
  const gitignorePath = path77.join(projectRoot, ".gitignore");
@@ -54305,7 +55313,9 @@ async function recordPromptJournalEntry(opts) {
54305
55313
  const monthStr = dateStr.slice(0, 7);
54306
55314
  const sessionId = opts.sessionId && opts.sessionId.trim() ? opts.sessionId.trim() : "general";
54307
55315
  const id = `pmt_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
54308
- const content = opts.content ?? "";
55316
+ const content = defaultScrubber2.scrub(opts.content ?? "");
55317
+ const rawContent = typeof opts.rawContent === "string" && opts.rawContent.length > 0 ? defaultScrubber2.scrub(opts.rawContent) : opts.rawContent;
55318
+ const decisionReason = typeof opts.decisionReason === "string" && opts.decisionReason.length > 0 ? defaultScrubber2.scrub(opts.decisionReason) : opts.decisionReason;
54309
55319
  const lines = content.split("\n");
54310
55320
  const characterCount = content.length;
54311
55321
  const lineCount2 = lines.length;
@@ -54318,7 +55328,7 @@ async function recordPromptJournalEntry(opts) {
54318
55328
  role: opts.role ?? (opts.category === "system_prompt" ? "system" : "user"),
54319
55329
  category: opts.category,
54320
55330
  content,
54321
- rawContent: opts.rawContent,
55331
+ rawContent,
54322
55332
  metadata: {
54323
55333
  model: opts.model,
54324
55334
  provider: opts.provider,
@@ -54329,7 +55339,7 @@ async function recordPromptJournalEntry(opts) {
54329
55339
  activeTools: opts.activeTools,
54330
55340
  contextFiles: opts.contextFiles,
54331
55341
  durationMs: opts.durationMs,
54332
- decisionReason: opts.decisionReason,
55342
+ decisionReason,
54333
55343
  tags: opts.tags
54334
55344
  }
54335
55345
  };
@@ -54546,7 +55556,7 @@ async function getPromptJournalEntries(projectRoot, filter = {}) {
54546
55556
  init_errors();
54547
55557
 
54548
55558
  // src/core/streaming-response-builder.ts
54549
- import { randomUUID as randomUUID32 } from "node:crypto";
55559
+ import { randomUUID as randomUUID33 } from "node:crypto";
54550
55560
  var STREAM_DRAIN_TIMEOUT_MS = 500;
54551
55561
  function buildResponse(state) {
54552
55562
  const content = [];
@@ -54606,7 +55616,7 @@ function handleContentBlockStart(state, ev) {
54606
55616
  state.textBuffers.push("");
54607
55617
  state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
54608
55618
  } else if (kind === "tool_use") {
54609
- const id = ev.id ?? randomUUID32();
55619
+ const id = ev.id ?? randomUUID33();
54610
55620
  state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
54611
55621
  state.blockOrder.push({ kind: "tool", id });
54612
55622
  state.currentTextIndex = -1;
@@ -54841,7 +55851,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
54841
55851
 
54842
55852
  // src/observability/network-telemetry.ts
54843
55853
  import { AsyncLocalStorage } from "node:async_hooks";
54844
- import { createHash as createHash25, randomUUID as randomUUID33 } from "node:crypto";
55854
+ import { createHash as createHash25, randomUUID as randomUUID34 } from "node:crypto";
54845
55855
  import { channel } from "node:diagnostics_channel";
54846
55856
  var storage = new AsyncLocalStorage();
54847
55857
  var requests = /* @__PURE__ */ new WeakMap();
@@ -54878,7 +55888,7 @@ function subscribe() {
54878
55888
  const requestBytes = numberField2(request, "contentLength");
54879
55889
  const state = {
54880
55890
  ...context,
54881
- requestId: randomUUID33(),
55891
+ requestId: randomUUID34(),
54882
55892
  ...target,
54883
55893
  ...requestBytes !== void 0 ? { requestBytes } : {},
54884
55894
  startedAt,
@@ -55013,7 +56023,7 @@ function hash3(value) {
55013
56023
  }
55014
56024
 
55015
56025
  // src/core/provider-runner.ts
55016
- import { randomUUID as randomUUID34 } from "node:crypto";
56026
+ import { randomUUID as randomUUID35 } from "node:crypto";
55017
56027
  function scrubProviderBody(body) {
55018
56028
  if (!body) return void 0;
55019
56029
  return {
@@ -55035,11 +56045,11 @@ function providerLogCtx(p, r) {
55035
56045
  }
55036
56046
  async function runProviderWithRetry(opts) {
55037
56047
  const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
55038
- const logicalRequestId = randomUUID34();
56048
+ const logicalRequestId = randomUUID35();
55039
56049
  const promptManifest = createChroniclePromptManifest(request);
55040
56050
  let attempt = 0;
55041
56051
  for (; ; ) {
55042
- const attemptId = randomUUID34();
56052
+ const attemptId = randomUUID35();
55043
56053
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
55044
56054
  const startedNs = process.hrtime.bigint();
55045
56055
  const correlation = {
@@ -55892,10 +56902,7 @@ function createAgentToolHandler(a) {
55892
56902
  } catch {
55893
56903
  }
55894
56904
  }
55895
- if (decision === "yes") {
55896
- const p = a.permission;
55897
- p.allowOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
55898
- } else if (decision === "no") {
56905
+ if (decision === "no") {
55899
56906
  const p = a.permission;
55900
56907
  p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
55901
56908
  }
@@ -59708,7 +60715,7 @@ function _resetDesignRulesCache() {
59708
60715
  }
59709
60716
 
59710
60717
  // src/execution/design-color.ts
59711
- function clamp(n, lo, hi) {
60718
+ function clamp2(n, lo, hi) {
59712
60719
  return n < lo ? lo : n > hi ? hi : n;
59713
60720
  }
59714
60721
  function parseOklch(value) {
@@ -59724,9 +60731,9 @@ function parseOklch(value) {
59724
60731
  let a = 1;
59725
60732
  if (alphaPart !== void 0) {
59726
60733
  const av = parseComponent(alphaPart.trim(), true);
59727
- if (av !== null) a = clamp(av, 0, 1);
60734
+ if (av !== null) a = clamp2(av, 0, 1);
59728
60735
  }
59729
- return [clamp(L, 0, 1), Math.max(0, C), H, a];
60736
+ return [clamp2(L, 0, 1), Math.max(0, C), H, a];
59730
60737
  }
59731
60738
  function parseComponent(s, percentIsFraction) {
59732
60739
  s = s.trim();
@@ -59745,7 +60752,7 @@ function parseAngle(s) {
59745
60752
  }
59746
60753
  function linearToSrgb(c) {
59747
60754
  const v = c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
59748
- return clamp(v, 0, 1);
60755
+ return clamp2(v, 0, 1);
59749
60756
  }
59750
60757
  function toHex2(n) {
59751
60758
  return Math.round(n * 255).toString(16).padStart(2, "0");
@@ -62492,7 +63499,7 @@ ${summaryText}` : summaryText;
62492
63499
 
62493
63500
  // src/execution/parallel-eternal-engine.ts
62494
63501
  init_error();
62495
- import { randomUUID as randomUUID35 } from "node:crypto";
63502
+ import { randomUUID as randomUUID36 } from "node:crypto";
62496
63503
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
62497
63504
  var ParallelEternalEngine = class {
62498
63505
  constructor(opts) {
@@ -62569,7 +63576,7 @@ var ParallelEternalEngine = class {
62569
63576
  this.state = "running";
62570
63577
  await this.persistState("running");
62571
63578
  const config = {
62572
- coordinatorId: `parallel-${randomUUID35().slice(0, 8)}`,
63579
+ coordinatorId: `parallel-${randomUUID36().slice(0, 8)}`,
62573
63580
  maxConcurrent: this.slots,
62574
63581
  doneCondition: { type: "all_tasks_done" }
62575
63582
  };
@@ -62623,7 +63630,7 @@ var ParallelEternalEngine = class {
62623
63630
  }
62624
63631
  if (!this.coordinator) {
62625
63632
  const config = {
62626
- coordinatorId: `parallel-${randomUUID35().slice(0, 8)}`,
63633
+ coordinatorId: `parallel-${randomUUID36().slice(0, 8)}`,
62627
63634
  maxConcurrent: this.slots,
62628
63635
  doneCondition: { type: "all_tasks_done" }
62629
63636
  };
@@ -62709,7 +63716,7 @@ ${recentJournal}` : "No prior iterations.",
62709
63716
  const task = expectDefined(tasks[i]);
62710
63717
  const route = routes[i] ?? null;
62711
63718
  const subagentId = `parallel-${this.iterations}-${i}`;
62712
- const taskId = randomUUID35();
63719
+ const taskId = randomUUID36();
62713
63720
  const personaLine = route ? `Acting agent: ${route.definition.config.name} \u2014 ${route.definition.capability.summary}
62714
63721
  ` : "";
62715
63722
  const spec = {
@@ -63659,6 +64666,10 @@ var DefaultSkillLoader = class {
63659
64666
  );
63660
64667
  for (const e of entries) {
63661
64668
  if (!await entryIsDirectory(dir, e)) continue;
64669
+ if (!isValidSkillNameFormat(e.name)) {
64670
+ this.skipped.push({ dir, entry: e.name, reason: "invalid-name-format" });
64671
+ continue;
64672
+ }
63662
64673
  const skillFile = path80.join(dir, e.name, "SKILL.md");
63663
64674
  let raw;
63664
64675
  try {
@@ -64337,7 +65348,7 @@ function readPolicy(ctx) {
64337
65348
  }
64338
65349
 
64339
65350
  // src/execution/tool-executor.ts
64340
- import { randomUUID as randomUUID38 } from "node:crypto";
65351
+ import { randomUUID as randomUUID39 } from "node:crypto";
64341
65352
  import * as fs38 from "node:fs/promises";
64342
65353
  import * as path85 from "node:path";
64343
65354
  init_errors();
@@ -64359,7 +65370,7 @@ var ToolErrorCategory = /* @__PURE__ */ ((ToolErrorCategory2) => {
64359
65370
  })(ToolErrorCategory || {});
64360
65371
 
64361
65372
  // src/execution/tool-executor-support.ts
64362
- import { createHash as createHash29, randomUUID as randomUUID36 } from "node:crypto";
65373
+ import { createHash as createHash29, randomUUID as randomUUID37 } from "node:crypto";
64363
65374
  import * as fs37 from "node:fs/promises";
64364
65375
  import * as path83 from "node:path";
64365
65376
  init_errors();
@@ -64491,7 +65502,7 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
64491
65502
  await fs37.mkdir(dir, { recursive: true });
64492
65503
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
64493
65504
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
64494
- const filePath = path83.join(dir, `${stamp}-${safeTool}-${randomUUID36()}.log`);
65505
+ const filePath = path83.join(dir, `${stamp}-${safeTool}-${randomUUID37()}.log`);
64495
65506
  await fs37.writeFile(filePath, content, "utf8");
64496
65507
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
64497
65508
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
@@ -65041,7 +66052,7 @@ ${errorDetails}`,
65041
66052
  }
65042
66053
 
65043
66054
  // src/execution/tool-executor-runner.ts
65044
- import { randomUUID as randomUUID37 } from "node:crypto";
66055
+ import { randomUUID as randomUUID38 } from "node:crypto";
65045
66056
 
65046
66057
  // src/observability/process-telemetry.ts
65047
66058
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -65063,7 +66074,7 @@ var SENSITIVE_FLAG_PATTERNS = [
65063
66074
  // redaction function (false positive = cosmetic noise; false negative = leak).
65064
66075
  /(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
65065
66076
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
65066
- /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
66077
+ /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|PASSPHRASE)\s*[=:]\s*[^\s,]+/gi,
65067
66078
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
65068
66079
  // when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
65069
66080
  // every such flag in the command line is redacted, not just the first.
@@ -65265,7 +66276,7 @@ async function runToolWithTimeout(tool, input, parentSignal, ctx, opts, config,
65265
66276
  progressTailChars: config.progressTailChars,
65266
66277
  progressHeadChars: config.progressHeadChars
65267
66278
  }) : (async () => tool.execute(input, ctx, { signal: combined }))();
65268
- const telemetryToolCallId = toolUseId ?? `nested-${randomUUID37()}`;
66279
+ const telemetryToolCallId = toolUseId ?? `nested-${randomUUID38()}`;
65269
66280
  const toolPromise = opts.events ? runWithNetworkTelemetry(
65270
66281
  {
65271
66282
  events: opts.events,
@@ -65438,7 +66449,7 @@ var ToolExecutor = class _ToolExecutor {
65438
66449
  return { result, tool, durationMs: Date.now() - start };
65439
66450
  }
65440
66451
  if (effectivePermission === "confirm") {
65441
- const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
66452
+ const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey, tool.subjectFields) ?? tool.name;
65442
66453
  if (this.opts.confirmAwaiter) {
65443
66454
  const awaiter = this.opts.confirmAwaiter;
65444
66455
  const choice = await new Promise(
@@ -65658,7 +66669,7 @@ ${post.additionalContext}`;
65658
66669
  const bridge = async (toolName, input) => {
65659
66670
  const nestedUse = {
65660
66671
  type: "tool_use",
65661
- id: `nested-${randomUUID38()}`,
66672
+ id: `nested-${randomUUID39()}`,
65662
66673
  name: toolName,
65663
66674
  input
65664
66675
  };
@@ -66263,13 +67274,13 @@ import * as fs39 from "node:fs/promises";
66263
67274
  import * as path87 from "node:path";
66264
67275
 
66265
67276
  // src/types/mode-prompts.ts
66266
- import { readFileSync as readFileSync23, statSync as statSync9 } from "node:fs";
67277
+ import { readFileSync as readFileSync24, statSync as statSync9 } from "node:fs";
66267
67278
  import * as path86 from "node:path";
66268
67279
  import { fileURLToPath as fileURLToPath9 } from "node:url";
66269
67280
  function modePrompt(id) {
66270
67281
  for (const dir of modePromptDirCandidates()) {
66271
67282
  try {
66272
- return readFileSync23(path86.join(dir, `${id}.md`), "utf8").trimEnd();
67283
+ return readFileSync24(path86.join(dir, `${id}.md`), "utf8").trimEnd();
66273
67284
  } catch {
66274
67285
  }
66275
67286
  }
@@ -66950,7 +67961,17 @@ function normalizeModelsDevModel(model) {
66950
67961
  const reasoningConfig = {
66951
67962
  default: disableSupported ? "enabled" : "always_on",
66952
67963
  disableSupported,
66953
- effortSupported: effortLevels.length > 0,
67964
+ // Tri-state (see ReasoningConfig.effortSupported):
67965
+ // options present → documented answer (true when effort values exist;
67966
+ // an explicitly EMPTY array is a documented "no
67967
+ // effort control", not an absent field).
67968
+ // field ABSENT → the model is known to reason but its vocabulary is
67969
+ // undocumented → `undefined`, so the resolver forwards
67970
+ // the request and each wire adapter applies its own
67971
+ // transport gating. Sending `false` here would make
67972
+ // the resolver claim "does not support effort" — an
67973
+ // assertion the catalog never made.
67974
+ ...raw === void 0 ? {} : { effortSupported: effortLevels.length > 0 },
66954
67975
  effortLevels,
66955
67976
  preserveThinking: model.interleaved ? "always_on" : "unsupported"
66956
67977
  };
@@ -67539,9 +68560,9 @@ async function startMetricsServer(opts) {
67539
68560
  let server;
67540
68561
  if (useHttps && tls) {
67541
68562
  const { createServer } = await import("node:https");
67542
- const { readFileSync: readFileSync25 } = await import("node:fs");
68563
+ const { readFileSync: readFileSync26 } = await import("node:fs");
67543
68564
  server = createServer(
67544
- { cert: readFileSync25(tls.cert), key: readFileSync25(tls.key) },
68565
+ { cert: readFileSync26(tls.cert), key: readFileSync26(tls.key) },
67545
68566
  listener
67546
68567
  );
67547
68568
  } else {
@@ -67965,7 +68986,9 @@ function hasRecursiveForceDelete(command, projectRoot) {
67965
68986
  if (token === "rd" || token === "rmdir") {
67966
68987
  const args = commandSegment(tokens, i + 1).map((arg) => arg.toLowerCase());
67967
68988
  if (args.includes("/s")) {
67968
- const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
68989
+ const targets = args.filter(
68990
+ (arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
68991
+ );
67969
68992
  if (targets.length === 0) return true;
67970
68993
  if (targets.some(isCatastrophicDeleteTarget)) return true;
67971
68994
  if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
@@ -68034,7 +69057,8 @@ function hasFindExec(command) {
68034
69057
  function isCatastrophicDeleteTarget(rawTarget) {
68035
69058
  const t2 = rawTarget.replace(/^['"]|['"]$/g, "").trim();
68036
69059
  if (!t2) return false;
68037
- if (t2 === "*" || t2 === "." || t2 === "./" || t2 === ".\\" || t2 === "./*" || t2 === ".\\*") return true;
69060
+ if (t2 === "*" || t2 === "." || t2 === "./" || t2 === ".\\" || t2 === "./*" || t2 === ".\\*")
69061
+ return true;
68038
69062
  const s = t2.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
68039
69063
  if (s === "") return true;
68040
69064
  if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
@@ -68074,12 +69098,16 @@ function hasCatastrophicDelete(command) {
68074
69098
  const args = tokens.slice(i + 1);
68075
69099
  const recursive = args.some((arg) => arg.toLowerCase() === "/s");
68076
69100
  if (!recursive) continue;
68077
- const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
69101
+ const targets = args.filter(
69102
+ (arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
69103
+ );
68078
69104
  if (targets.some(isCatastrophicDeleteTarget)) return true;
68079
69105
  }
68080
69106
  if (token === "del" || token === "erase") {
68081
69107
  const args = tokens.slice(i + 1);
68082
- const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
69108
+ const targets = args.filter(
69109
+ (arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
69110
+ );
68083
69111
  if (targets.some(isCatastrophicDeleteTarget)) return true;
68084
69112
  }
68085
69113
  }
@@ -68140,6 +69168,47 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
68140
69168
  if (HIGH_IMPACT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
68141
69169
  return false;
68142
69170
  }
69171
+ var WELL_KNOWN_CREDENTIAL_ENV_VARS = /* @__PURE__ */ new Set([
69172
+ "ANTHROPIC_API_KEY",
69173
+ "ANTHROPIC_AUTH_TOKEN",
69174
+ "OPENAI_API_KEY",
69175
+ "AZURE_OPENAI_API_KEY",
69176
+ "GEMINI_API_KEY",
69177
+ "GOOGLE_API_KEY",
69178
+ "GOOGLE_APPLICATION_CREDENTIALS",
69179
+ "GOOGLE_GENERATIVE_AI_API_KEY",
69180
+ "GROQ_API_KEY",
69181
+ "MISTRAL_API_KEY",
69182
+ "COHERE_API_KEY",
69183
+ "DEEPSEEK_API_KEY",
69184
+ "XAI_API_KEY",
69185
+ "OPENROUTER_API_KEY",
69186
+ "PERPLEXITY_API_KEY",
69187
+ "TOGETHER_API_KEY",
69188
+ "FIREWORKS_API_KEY",
69189
+ "HUGGINGFACE_API_KEY",
69190
+ "HF_TOKEN",
69191
+ "GITHUB_TOKEN",
69192
+ "GH_TOKEN",
69193
+ "NPM_TOKEN",
69194
+ "AWS_ACCESS_KEY_ID",
69195
+ "AWS_SECRET_ACCESS_KEY",
69196
+ "AWS_SESSION_TOKEN",
69197
+ "AZURE_CLIENT_SECRET",
69198
+ "GITLAB_TOKEN",
69199
+ "SLACK_TOKEN",
69200
+ "STRIPE_SECRET_KEY",
69201
+ "TELEGRAM_BOT_TOKEN",
69202
+ "WRONGSTACK_VAULT_PASSPHRASE"
69203
+ ]);
69204
+ function attachesWellKnownCredential(input) {
69205
+ if (!input || typeof input !== "object") return false;
69206
+ const envVars = input["envVars"];
69207
+ if (!Array.isArray(envVars)) return false;
69208
+ return envVars.some(
69209
+ (name) => typeof name === "string" && WELL_KNOWN_CREDENTIAL_ENV_VARS.has(name.toUpperCase())
69210
+ );
69211
+ }
68143
69212
 
68144
69213
  // src/security/permission-helpers.ts
68145
69214
  function matchesTrust(patterns, subject2) {
@@ -68156,7 +69225,7 @@ function hasShellSubject(tool) {
68156
69225
  ]);
68157
69226
  }
68158
69227
  function alwaysAllowUnavailableReason(tool, input) {
68159
- const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey);
69228
+ const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
68160
69229
  if (subject2 !== void 0) return void 0;
68161
69230
  return `"always allow" needs a subject to remember, and ${tool.name} calls do not carry one (no subjectKey, and no path/url/name input). Recording it would store a rule that can never match. Approve this call, or set a trust rule for ${tool.name} explicitly.`;
68162
69231
  }
@@ -68216,8 +69285,18 @@ var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trus
68216
69285
  function unescapeGlobSubject(value) {
68217
69286
  return value.replace(/\\([*?[\]])/g, "$1");
68218
69287
  }
69288
+ function stripAdsSuffix(forwardSlashPath) {
69289
+ const cut = forwardSlashPath.lastIndexOf("/");
69290
+ const dir = cut === -1 ? "" : forwardSlashPath.slice(0, cut + 1);
69291
+ const base = cut === -1 ? forwardSlashPath : forwardSlashPath.slice(cut + 1);
69292
+ const colon = base.indexOf(":");
69293
+ if (colon === -1 || cut === -1 && colon === 1 && base.length <= 2) return forwardSlashPath;
69294
+ return dir + base.slice(0, colon);
69295
+ }
68219
69296
  function normalizeForCompare(value) {
68220
- const forward = unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "");
69297
+ const forward = stripAdsSuffix(
69298
+ unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "")
69299
+ );
68221
69300
  return process.platform === "win32" ? forward.toLowerCase() : forward;
68222
69301
  }
68223
69302
  function realpathOfNearestExisting(p) {
@@ -68254,7 +69333,7 @@ function isProtectedAgentStatePath(absPath) {
68254
69333
  return AGENT_STATE_SENSITIVE_BASENAMES.test(path90.basename(normalizeForCompare(absPath)));
68255
69334
  }
68256
69335
  function pathLooksSensitive(rawPath) {
68257
- const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
69336
+ const normalized = stripAdsSuffix(stripShellQuotes(rawPath).replace(/\\/g, "/"));
68258
69337
  if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
68259
69338
  return isProtectedAgentStatePath(normalized);
68260
69339
  }
@@ -68278,10 +69357,24 @@ function shellCommandReadsSensitivePath(command) {
68278
69357
  }
68279
69358
  return false;
68280
69359
  }
69360
+ function isSensitiveReadCall(tool, input) {
69361
+ const isReadTool = hasCapability(tool, ToolCapabilities.FS_READ) || tool.name === "read" || tool.name === "grep" || tool.name === "glob" || tool.name === "tree";
69362
+ if (isReadTool && inputPathLooksSensitive(input)) return true;
69363
+ const hasShellCap = hasCapability(tool, [
69364
+ ToolCapabilities.SHELL_ARBITRARY,
69365
+ ToolCapabilities.SHELL_RESTRICTED,
69366
+ ToolCapabilities.SHELL_EXEC
69367
+ ]);
69368
+ if (!hasShellCap && tool.name !== "bash" && tool.name !== "shell" && tool.name !== "exec") {
69369
+ return false;
69370
+ }
69371
+ const command = shellCommandLineFromInput(input);
69372
+ return command ? shellCommandReadsSensitivePath(command) : false;
69373
+ }
68281
69374
 
68282
69375
  // src/security/permission-explain.ts
68283
69376
  function explainPermissionTrace(state, tool, input, ctx) {
68284
- const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey);
69377
+ const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
68285
69378
  const steps = [];
68286
69379
  let winnerIndex = -1;
68287
69380
  const add = (rule, matched, decision, source, detail) => {
@@ -68502,13 +69595,7 @@ function explainPermissionTrace(state, tool, input, ctx) {
68502
69595
  }
68503
69596
  };
68504
69597
  }
68505
- add(
68506
- "yolo",
68507
- true,
68508
- "auto",
68509
- "yolo",
68510
- "YOLO mode is active \u2014 auto-approving every non-denied call"
68511
- );
69598
+ add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
68512
69599
  winnerIndex = steps.length - 1;
68513
69600
  return {
68514
69601
  toolName: tool.name,
@@ -68780,7 +69867,14 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
68780
69867
  static isMcpTool(name) {
68781
69868
  return name.startsWith("mcp__");
68782
69869
  }
68783
- async evaluate(tool) {
69870
+ async evaluate(tool, input) {
69871
+ if (input !== void 0 && isSensitiveReadCall(tool, input)) {
69872
+ return {
69873
+ permission: "deny",
69874
+ source: "subagent_guard",
69875
+ reason: "subagents may not read credential-bearing paths \u2014 the leader must perform this read so the user can approve it"
69876
+ };
69877
+ }
68784
69878
  const caps = tool.capabilities ?? [];
68785
69879
  const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
68786
69880
  const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
@@ -68807,8 +69901,8 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
68807
69901
  }
68808
69902
  allowOnce() {
68809
69903
  }
68810
- async explain(tool) {
68811
- const decision = await this.evaluate(tool);
69904
+ async explain(tool, input) {
69905
+ const decision = await this.evaluate(tool, input);
68812
69906
  return {
68813
69907
  toolName: tool.name,
68814
69908
  subject: null,
@@ -68858,6 +69952,17 @@ function fsWriteTargetPaths(input) {
68858
69952
  }
68859
69953
  return out;
68860
69954
  }
69955
+ function mergeTrustEntries(exact, wildcard) {
69956
+ if (!exact) return wildcard;
69957
+ if (!wildcard) return exact;
69958
+ const deny = [...wildcard.deny ?? [], ...exact.deny ?? []];
69959
+ const merged = {
69960
+ ...wildcard,
69961
+ ...exact
69962
+ };
69963
+ if (deny.length > 0) merged.deny = [...new Set(deny)];
69964
+ return merged;
69965
+ }
68861
69966
  var DefaultPermissionPolicy = class {
68862
69967
  policy = {};
68863
69968
  loaded = false;
@@ -68896,6 +70001,7 @@ var DefaultPermissionPolicy = class {
68896
70001
  yoloBlockedAsDestructive(tool, input, ctx) {
68897
70002
  if (!this.yolo || this.yoloDestructive) return false;
68898
70003
  if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
70004
+ if (attachesWellKnownCredential(input)) return true;
68899
70005
  const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
68900
70006
  if (!isShellSurface) return false;
68901
70007
  const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
@@ -68989,8 +70095,8 @@ var DefaultPermissionPolicy = class {
68989
70095
  };
68990
70096
  }
68991
70097
  const namespaceEntry = this.findNamespaceEntry(tool.name);
68992
- const entry = this.policy[tool.name] ?? namespaceEntry;
68993
- const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey);
70098
+ const entry = mergeTrustEntries(this.policy[tool.name], namespaceEntry);
70099
+ const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
68994
70100
  const cacheKey = `${tool.name}::${subject2 ?? tool.name}`;
68995
70101
  const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
68996
70102
  if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
@@ -69007,15 +70113,6 @@ var DefaultPermissionPolicy = class {
69007
70113
  this._evalCache.set(evalKey, decision);
69008
70114
  return decision;
69009
70115
  }
69010
- if (this.sessionAllowed.has(cacheKey)) {
69011
- this.sessionAllowed.delete(cacheKey);
69012
- const decision = {
69013
- permission: "auto",
69014
- source: "trust",
69015
- reason: "session one-shot allow (user pressed yes)"
69016
- };
69017
- return decision;
69018
- }
69019
70116
  if (entry?.deny && subject2 && matchesTrust(entry.deny, subject2)) {
69020
70117
  this._logDeny(tool.name, subject2, "matched deny pattern");
69021
70118
  const decision = {
@@ -69026,6 +70123,15 @@ var DefaultPermissionPolicy = class {
69026
70123
  this._evalCache.set(evalKey, decision);
69027
70124
  return decision;
69028
70125
  }
70126
+ if (this.sessionAllowed.has(cacheKey)) {
70127
+ this.sessionAllowed.delete(cacheKey);
70128
+ const decision = {
70129
+ permission: "auto",
70130
+ source: "trust",
70131
+ reason: "session one-shot allow (user pressed yes)"
70132
+ };
70133
+ return decision;
70134
+ }
69029
70135
  if (tool.permission === "deny") {
69030
70136
  this._logDeny(tool.name, subject2, "tool default deny");
69031
70137
  const decision = {
@@ -69036,6 +70142,7 @@ var DefaultPermissionPolicy = class {
69036
70142
  this._evalCache.set(evalKey, decision);
69037
70143
  return decision;
69038
70144
  }
70145
+ const denyUnevaluated = Boolean(entry?.deny?.length) && subject2 === void 0;
69039
70146
  const allowMatches = hasShellSubject(tool) ? matchesCommandTrust : matchesTrust;
69040
70147
  if (entry?.allow && subject2 && allowMatches(entry.allow, subject2)) {
69041
70148
  const decision = {
@@ -69046,7 +70153,7 @@ var DefaultPermissionPolicy = class {
69046
70153
  this._evalCache.set(evalKey, decision);
69047
70154
  return decision;
69048
70155
  }
69049
- if (entry?.auto) {
70156
+ if (entry?.auto && !denyUnevaluated) {
69050
70157
  const decision = { permission: "auto", source: "trust" };
69051
70158
  this._evalCache.set(evalKey, decision);
69052
70159
  return decision;
@@ -69146,19 +70253,10 @@ var DefaultPermissionPolicy = class {
69146
70253
  }
69147
70254
  return { permission: "confirm", source: "default" };
69148
70255
  }
70256
+ // Delegates to the shared helper so the subagent policy applies the exact
70257
+ // same rule — see `isSensitiveReadCall` in ./permission-helpers.ts.
69149
70258
  isSensitiveReadCall(tool, input) {
69150
- const isReadTool = hasCapability(tool, ToolCapabilities.FS_READ) || tool.name === "read" || tool.name === "grep" || tool.name === "glob" || tool.name === "tree";
69151
- if (isReadTool && inputPathLooksSensitive(input)) return true;
69152
- const hasShellCap = hasCapability(tool, [
69153
- ToolCapabilities.SHELL_ARBITRARY,
69154
- ToolCapabilities.SHELL_RESTRICTED,
69155
- ToolCapabilities.SHELL_EXEC
69156
- ]);
69157
- if (!hasShellCap && tool.name !== "bash" && tool.name !== "shell" && tool.name !== "exec") {
69158
- return false;
69159
- }
69160
- const command = shellCommandLineFromInput(input);
69161
- return command ? shellCommandReadsSensitivePath(command) : false;
70259
+ return isSensitiveReadCall(tool, input);
69162
70260
  }
69163
70261
  async trust(rule) {
69164
70262
  if (!this.loaded) await this.reload();
@@ -69507,7 +70605,7 @@ function deepFreeze(obj) {
69507
70605
  init_errors();
69508
70606
  init_atomic_write();
69509
70607
  init_error();
69510
- import { randomUUID as randomUUID39 } from "node:crypto";
70608
+ import { randomUUID as randomUUID40 } from "node:crypto";
69511
70609
  import * as fsp38 from "node:fs/promises";
69512
70610
  function assertPlanMutationInvariants(previous, updated) {
69513
70611
  const ids = updated.items.map((item) => item.id);
@@ -69629,7 +70727,7 @@ function emptyPlan(sessionId, title) {
69629
70727
  function addPlanItem(plan, title, details) {
69630
70728
  const now = (/* @__PURE__ */ new Date()).toISOString();
69631
70729
  const item = {
69632
- id: `plan_${Date.now()}_${randomUUID39().slice(0, 6)}`,
70730
+ id: `plan_${Date.now()}_${randomUUID40().slice(0, 6)}`,
69633
70731
  title,
69634
70732
  details,
69635
70733
  status: "open",
@@ -69701,7 +70799,7 @@ function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
69701
70799
  if (subtasks && subtasks.length > 0) {
69702
70800
  for (const st of subtasks) {
69703
70801
  todos.push({
69704
- id: `todo_${Date.now()}_${randomUUID39().slice(0, 6)}`,
70802
+ id: `todo_${Date.now()}_${randomUUID40().slice(0, 6)}`,
69705
70803
  content: st,
69706
70804
  status: "pending",
69707
70805
  promotedFromPlan: item.id
@@ -73698,7 +74796,7 @@ function resolveReasoningForRequest(settings, rc, warnings) {
73698
74796
  const cfg = settings.reasoning;
73699
74797
  if (!cfg) return void 0;
73700
74798
  const capKnown = rc !== void 0;
73701
- const supportsReasoning = rc ? rc.default !== "disabled" || rc.disableSupported || rc.effortSupported : false;
74799
+ const supportsReasoning = rc ? rc.default !== "disabled" || rc.disableSupported || rc.effortSupported !== false : false;
73702
74800
  const out = {};
73703
74801
  if (cfg.mode === "off") {
73704
74802
  if (capKnown && rc?.disableSupported) {
@@ -73720,14 +74818,17 @@ function resolveReasoningForRequest(settings, rc, warnings) {
73720
74818
  }
73721
74819
  const effort = cfg.effort;
73722
74820
  if (effort !== void 0) {
73723
- if (capKnown && rc?.effortSupported && rc.effortLevels.includes(effort)) {
73724
- out.effort = effort;
73725
- } else if (capKnown && rc?.effortSupported) {
74821
+ if (!capKnown) {
74822
+ } else if (rc?.effortSupported === false) {
74823
+ warnings.push(
74824
+ `reasoning effort "${effort}" requested, but this model does not support effort control; the setting was omitted.`
74825
+ );
74826
+ } else if (rc?.effortSupported === true && rc.effortLevels.length > 0 && !rc.effortLevels.includes(effort)) {
73726
74827
  warnings.push(
73727
74828
  `reasoning effort "${effort}" not supported by this model (supported: ${rc.effortLevels.join(", ")}); the setting was omitted.`
73728
74829
  );
73729
- } else if (capKnown) {
73730
- warnings.push(`reasoning effort "${effort}" requested, but this model does not support effort; the setting was omitted.`);
74830
+ } else {
74831
+ out.effort = effort;
73731
74832
  }
73732
74833
  }
73733
74834
  if (cfg.preserve !== void 0) {
@@ -78189,6 +79290,15 @@ function deriveSessionStatus(agents) {
78189
79290
  (a) => a.status === "running" || a.status === "streaming" || a.status === "waiting_user"
78190
79291
  ) ? "active" : "idle";
78191
79292
  }
79293
+ function downgradeStaleAgentStatuses(agents, nowMs) {
79294
+ const cutoff = nowMs - HQ_STALE_SNAPSHOT_WINDOW_MS;
79295
+ return agents.map((agent) => {
79296
+ if (agent.status !== "running" && agent.status !== "streaming") return agent;
79297
+ const lastActivityAt = Date.parse(agent.lastActivityAt);
79298
+ if (!Number.isFinite(lastActivityAt) || lastActivityAt >= cutoff) return agent;
79299
+ return { ...agent, status: "idle" };
79300
+ });
79301
+ }
78192
79302
  function startSessionTelemetryBridge(opts) {
78193
79303
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
78194
79304
  const publisher = opts.publisher;
@@ -78209,6 +79319,7 @@ function startSessionTelemetryBridge(opts) {
78209
79319
  let lastPublishedAtMs = Date.now();
78210
79320
  let disposed = false;
78211
79321
  function buildSnapshot() {
79322
+ const effectiveAgents = downgradeStaleAgentStatuses(agents, Date.parse(now()));
78212
79323
  return {
78213
79324
  sessionId: opts.sessionId,
78214
79325
  clientKind: identity2.kind,
@@ -78216,11 +79327,11 @@ function startSessionTelemetryBridge(opts) {
78216
79327
  projectId: project2.projectId,
78217
79328
  projectName: opts.projectName ?? project2.projectName,
78218
79329
  projectRoot: opts.projectRoot,
78219
- status: deriveSessionStatus(agents),
79330
+ status: deriveSessionStatus(effectiveAgents),
78220
79331
  startedAt,
78221
79332
  lastActivityAt,
78222
- agentCount: agents.length,
78223
- agents,
79333
+ agentCount: effectiveAgents.length,
79334
+ agents: effectiveAgents,
78224
79335
  ...identity2.hostname !== void 0 ? { hostname: identity2.hostname } : {},
78225
79336
  ...identity2.pid !== void 0 ? { pid: identity2.pid } : {},
78226
79337
  ...opts.gitBranch !== void 0 ? { gitBranch: opts.gitBranch } : {}
@@ -81601,18 +82712,88 @@ var DefaultPluginAPI = class {
81601
82712
  list: () => tr.list()
81602
82713
  };
81603
82714
  const pr = init.providerRegistry;
82715
+ const providerTypesIOwn = /* @__PURE__ */ new Set();
82716
+ const assertCanMutateProvider = (type, op) => {
82717
+ if (isOfficial) return;
82718
+ if (providerTypesIOwn.has(type)) return;
82719
+ if (!pr.has(type)) return;
82720
+ throw new Error(
82721
+ `Plugin "${owner}" may not ${op} provider "${type}" \u2014 it was not registered by this plugin. Replacing an existing provider would route prompts and credentials through plugin code.`
82722
+ );
82723
+ };
81604
82724
  this.providers = {
81605
- register: (f) => pr.register(f),
81606
- unregister: (type) => pr.unregister(type),
82725
+ register: (f) => {
82726
+ assertCanMutateProvider(f.type, "replace");
82727
+ pr.register(f);
82728
+ providerTypesIOwn.add(f.type);
82729
+ },
82730
+ unregister: (type) => {
82731
+ assertCanMutateProvider(type, "unregister");
82732
+ providerTypesIOwn.delete(type);
82733
+ return pr.unregister(type);
82734
+ },
81607
82735
  create: (cfg) => pr.create(cfg),
81608
82736
  list: () => pr.list()
81609
82737
  };
81610
- this.mcp = init.mcpRegistry ?? noopMcp;
82738
+ const mcpRegistry = init.mcpRegistry;
82739
+ if (!mcpRegistry) {
82740
+ this.mcp = noopMcp;
82741
+ } else {
82742
+ const mcpServersIStarted = /* @__PURE__ */ new Set();
82743
+ const assertOwnsMcpServer = (name, op) => {
82744
+ if (isOfficial) return;
82745
+ if (mcpServersIStarted.has(name)) return;
82746
+ if (!mcpRegistry.list().some((s) => s.name === name)) return;
82747
+ throw new Error(
82748
+ `Plugin "${owner}" may not ${op} MCP server "${name}" \u2014 it was not started by this plugin.`
82749
+ );
82750
+ };
82751
+ this.mcp = {
82752
+ start: async (cfg) => {
82753
+ const name = cfg?.name;
82754
+ if (typeof name === "string" && mcpRegistry.list().some((s) => s.name === name)) {
82755
+ assertOwnsMcpServer(name, "start");
82756
+ }
82757
+ await mcpRegistry.start(cfg);
82758
+ if (typeof name === "string") mcpServersIStarted.add(name);
82759
+ },
82760
+ stop: async (name) => {
82761
+ assertOwnsMcpServer(name, "stop");
82762
+ await mcpRegistry.stop(name);
82763
+ },
82764
+ restart: async (name) => {
82765
+ assertOwnsMcpServer(name, "restart");
82766
+ await mcpRegistry.restart(name);
82767
+ },
82768
+ list: () => mcpRegistry.list()
82769
+ };
82770
+ }
81611
82771
  const scr = init.slashCommandRegistry;
81612
82772
  const official = init.official === true;
82773
+ const commandsIOwn = /* @__PURE__ */ new Set();
81613
82774
  this.slashCommands = scr ? {
81614
- register: (cmd) => scr.register(cmd, owner, { official }),
81615
- unregister: (name) => scr.unregister(name),
82775
+ register: (cmd) => {
82776
+ scr.register(cmd, owner, { official });
82777
+ for (const key of [cmd.name, ...cmd.aliases ?? []]) {
82778
+ commandsIOwn.add(key);
82779
+ commandsIOwn.add(`${owner}:${key}`);
82780
+ }
82781
+ },
82782
+ unregister: (name) => {
82783
+ if (!official && !commandsIOwn.has(name) && scr.get(name) !== void 0) {
82784
+ throw new Error(
82785
+ `Plugin "${owner}" may not unregister slash command "${name}" \u2014 it was not registered by this plugin.`
82786
+ );
82787
+ }
82788
+ for (const key of [
82789
+ name,
82790
+ `${owner}:${name}`,
82791
+ name.startsWith(`${owner}:`) ? name.slice(owner.length + 1) : name
82792
+ ]) {
82793
+ commandsIOwn.delete(key);
82794
+ }
82795
+ return scr.unregister(name);
82796
+ },
81616
82797
  get: (name) => scr.get(name),
81617
82798
  list: () => scr.list()
81618
82799
  } : noopSlashCommands;
@@ -82152,7 +83333,7 @@ async function snapshotChangedFiles(cwd) {
82152
83333
  }
82153
83334
 
82154
83335
  // src/plugins/review-claim-registry.ts
82155
- import { createHash as createHash33, randomUUID as randomUUID40 } from "node:crypto";
83336
+ import { createHash as createHash33, randomUUID as randomUUID41 } from "node:crypto";
82156
83337
  import * as fsp46 from "node:fs/promises";
82157
83338
  import { hostname as hostname5 } from "node:os";
82158
83339
  import * as path103 from "node:path";
@@ -82215,7 +83396,7 @@ async function breakStaleLock(lockPath) {
82215
83396
  return false;
82216
83397
  }
82217
83398
  async function breakLockAtomically(lockPath) {
82218
- const tombstone = `${lockPath}.stale-${randomUUID40()}.tmp`;
83399
+ const tombstone = `${lockPath}.stale-${randomUUID41()}.tmp`;
82219
83400
  try {
82220
83401
  await fsp46.rename(lockPath, tombstone);
82221
83402
  } catch {
@@ -82275,7 +83456,7 @@ async function withStoreLock(storeDir, fn, waitMs = LOCK_WAIT_MS) {
82275
83456
  }
82276
83457
  }
82277
83458
  var MAX_LEDGER_LINES = 1e4;
82278
- var HOST_SID = randomUUID40();
83459
+ var HOST_SID = randomUUID41();
82279
83460
  var claimsByEventBus = /* @__PURE__ */ new WeakMap();
82280
83461
  var startedReviews = /* @__PURE__ */ new WeakMap();
82281
83462
  var pendingStartedReviews = /* @__PURE__ */ new WeakMap();
@@ -82356,7 +83537,7 @@ async function compactLedger(storeDir, active) {
82356
83537
  );
82357
83538
  }
82358
83539
  }
82359
- const tmp = `${claimsFilePath(storeDir)}.tmp-${randomUUID40()}`;
83540
+ const tmp = `${claimsFilePath(storeDir)}.tmp-${randomUUID41()}`;
82360
83541
  await fsp46.writeFile(tmp, lines.length > 0 ? `${lines.join("\n")}
82361
83542
  ` : "", "utf8");
82362
83543
  let replaced = false;
@@ -83285,7 +84466,7 @@ init_review_finding_store();
83285
84466
 
83286
84467
  // src/plugins/review-finding-parser.ts
83287
84468
  init_review_finding_types();
83288
- import { randomUUID as randomUUID42 } from "node:crypto";
84469
+ import { randomUUID as randomUUID43 } from "node:crypto";
83289
84470
  var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
83290
84471
  var CATEGORIES = /* @__PURE__ */ new Set([
83291
84472
  "bug",
@@ -83357,7 +84538,7 @@ function parseChimeraReviewReport(reportText, context = {}) {
83357
84538
  }
83358
84539
  const structured = extractStructuredFindingsBlock(reportText);
83359
84540
  if (structured) {
83360
- const reportId2 = context.reportId ?? randomUUID42();
84541
+ const reportId2 = context.reportId ?? randomUUID43();
83361
84542
  const findings2 = structured.findings.map(
83362
84543
  (item) => buildFindingFromStructuredItem(item, { ...context, reportId: reportId2 })
83363
84544
  );
@@ -83369,7 +84550,7 @@ function parseChimeraReviewReport(reportText, context = {}) {
83369
84550
  };
83370
84551
  }
83371
84552
  const findings = [];
83372
- const reportId = context.reportId ?? randomUUID42();
84553
+ const reportId = context.reportId ?? randomUUID43();
83373
84554
  let unparseableCount = 0;
83374
84555
  let durationSeconds;
83375
84556
  let currentSeverity = null;
@@ -83470,7 +84651,7 @@ function parseFindingSegment(segment, severity, context) {
83470
84651
  const fullDesc = suggestions.length > 0 ? cleanDesc + "\n" + suggestions.map((s) => " \u2192 " + s).join("\n") : cleanDesc;
83471
84652
  const suggestedFix = suggestions.length > 0 ? suggestions.join("\n") : void 0;
83472
84653
  return {
83473
- id: randomUUID42(),
84654
+ id: randomUUID43(),
83474
84655
  fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
83475
84656
  severity,
83476
84657
  source: normalizeFindingSource(context.reviewType),
@@ -83481,7 +84662,7 @@ function parseFindingSegment(segment, severity, context) {
83481
84662
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
83482
84663
  status: "active",
83483
84664
  originReport: {
83484
- reportId: context.reportId ?? randomUUID42(),
84665
+ reportId: context.reportId ?? randomUUID43(),
83485
84666
  sessionId: context.sessionId ?? "",
83486
84667
  agentId: context.agentId ?? "",
83487
84668
  reviewerModel: context.reviewerModel ?? ""
@@ -83505,7 +84686,7 @@ function buildFindingFromStructuredItem(item, context) {
83505
84686
  const title = item.title;
83506
84687
  const description = item.description ?? title;
83507
84688
  return {
83508
- id: randomUUID42(),
84689
+ id: randomUUID43(),
83509
84690
  fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
83510
84691
  severity: item.severity,
83511
84692
  source: normalizeFindingSource(context.reviewType),
@@ -83518,7 +84699,7 @@ function buildFindingFromStructuredItem(item, context) {
83518
84699
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
83519
84700
  status: "active",
83520
84701
  originReport: {
83521
- reportId: context.reportId ?? randomUUID42(),
84702
+ reportId: context.reportId ?? randomUUID43(),
83522
84703
  sessionId: context.sessionId ?? "",
83523
84704
  agentId: context.agentId ?? "",
83524
84705
  reviewerModel: context.reviewerModel ?? ""
@@ -86217,14 +87398,21 @@ async function openInEditor(filePath, env = process.env) {
86217
87398
  context: { filePath }
86218
87399
  });
86219
87400
  }
86220
- const child = spawn12(parts[0], [...parts.slice(1), filePath], {
87401
+ const editorArgs = [...parts.slice(1), filePath];
87402
+ const child = shell ? (() => {
87403
+ const inv = buildWin32CmdShimInvocation(parts[0], editorArgs);
87404
+ return spawn12(inv.command, inv.args, {
87405
+ stdio: "ignore",
87406
+ detached: true,
87407
+ windowsVerbatimArguments: inv.windowsVerbatimArguments,
87408
+ // Suppresses the console flash the cmd.exe wrapper would otherwise
87409
+ // show before the editor appears. Repo convention — see
87410
+ // `core/tests/architecture/spawn-convention.test.ts`.
87411
+ windowsHide: true
87412
+ });
87413
+ })() : spawn12(parts[0], editorArgs, {
86221
87414
  stdio: "ignore",
86222
87415
  detached: true,
86223
- shell,
86224
- // `shell` routes through cmd.exe on win32, which flashes a console window
86225
- // before the editor appears. A GUI editor is unaffected by the flag; the
86226
- // shell wrapper is what it suppresses. Repo convention — see
86227
- // `core/tests/architecture/spawn-convention.test.ts`.
86228
87416
  windowsHide: true
86229
87417
  });
86230
87418
  child.unref();
@@ -89169,7 +90357,7 @@ var ReplayProviderRunner = class {
89169
90357
  };
89170
90358
 
89171
90359
  // src/session-catalog/store.ts
89172
- import { randomBytes as randomBytes8, randomUUID as randomUUID43 } from "node:crypto";
90360
+ import { randomBytes as randomBytes8, randomUUID as randomUUID44 } from "node:crypto";
89173
90361
  import * as fs59 from "node:fs";
89174
90362
  import * as path119 from "node:path";
89175
90363
  init_atomic_write();
@@ -89520,7 +90708,7 @@ var SessionCatalogStore = class {
89520
90708
  if (!Number.isSafeInteger(entry.pid) || entry.pid <= 0)
89521
90709
  throw new TypeError("Invalid owner pid");
89522
90710
  const now = Date.now();
89523
- const leaseId = randomUUID43();
90711
+ const leaseId = randomUUID44();
89524
90712
  const leaseSecret = randomBytes8(32).toString("hex");
89525
90713
  const expiresAt = now + boundedMs(leaseMs, SESSION_CATALOG_DEFAULT_LEASE_MS, MAX_LEASE_MS);
89526
90714
  this.db.prepare(`INSERT INTO session_leases(
@@ -89592,7 +90780,7 @@ var SessionCatalogStore = class {
89592
90780
  const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
89593
90781
  if (!catalog && !fs59.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
89594
90782
  throw new Error(`Session not found: ${targetSessionId}`);
89595
- const reservationId = randomUUID43();
90783
+ const reservationId = randomUUID44();
89596
90784
  const now = Date.now();
89597
90785
  const expiresAt = now + boundedMs(reservationMs, SESSION_CATALOG_DEFAULT_RESERVATION_MS, MAX_RESERVATION_MS);
89598
90786
  try {
@@ -89888,7 +91076,7 @@ var SessionCatalogStore = class {
89888
91076
  "SELECT 1 AS yes FROM resume_reservations WHERE target_session_id=? AND expires_at>?"
89889
91077
  ).get(sessionId, Date.now());
89890
91078
  if (reservation) throw conflict(`Session ${sessionId} is reserved for resume`);
89891
- const leaseId = randomUUID43();
91079
+ const leaseId = randomUUID44();
89892
91080
  const now = Date.now();
89893
91081
  const expiresAt = now + boundedMs(leaseMs, 6e4, MAX_MAINTENANCE_MS);
89894
91082
  try {
@@ -90846,7 +92034,7 @@ import * as fs62 from "node:fs/promises";
90846
92034
  import * as path122 from "node:path";
90847
92035
 
90848
92036
  // src/session-registry-atomic-file.ts
90849
- import { randomUUID as randomUUID44 } from "node:crypto";
92037
+ import { randomUUID as randomUUID45 } from "node:crypto";
90850
92038
  import * as fs61 from "node:fs/promises";
90851
92039
  import { hostname as hostname6 } from "node:os";
90852
92040
  import * as path121 from "node:path";
@@ -90919,7 +92107,7 @@ async function breakStaleLockVerified2(lockPath, verify) {
90919
92107
  return await breakLockAtomically2(lockPath) === true;
90920
92108
  }
90921
92109
  async function breakLockAtomically2(lockPath) {
90922
- const tombstone = `${lockPath}.stale-${randomUUID44()}.tmp`;
92110
+ const tombstone = `${lockPath}.stale-${randomUUID45()}.tmp`;
90923
92111
  try {
90924
92112
  await fs61.rename(lockPath, tombstone);
90925
92113
  } catch {
@@ -90931,7 +92119,7 @@ async function breakLockAtomically2(lockPath) {
90931
92119
  async function writeAtomicFile(filePath, registry2) {
90932
92120
  const tmp = path121.join(
90933
92121
  path121.dirname(filePath),
90934
- `.${path121.basename(filePath)}.${randomUUID44().slice(0, 8)}.tmp`
92122
+ `.${path121.basename(filePath)}.${randomUUID45().slice(0, 8)}.tmp`
90935
92123
  );
90936
92124
  let tmpPersisted = false;
90937
92125
  try {
@@ -91494,7 +92682,7 @@ var SessionRegistry = class {
91494
92682
  init_errors();
91495
92683
  init_atomic_write();
91496
92684
  init_error();
91497
- import { randomUUID as randomUUID45 } from "node:crypto";
92685
+ import { randomUUID as randomUUID46 } from "node:crypto";
91498
92686
  import * as fs63 from "node:fs/promises";
91499
92687
  var FILE_VERSION = 1;
91500
92688
  var MAX_TEXT_LENGTH = 2e3;
@@ -91592,7 +92780,7 @@ var AnnotationsStore = class {
91592
92780
  });
91593
92781
  }
91594
92782
  const annotation = {
91595
- id: randomUUID45(),
92783
+ id: randomUUID46(),
91596
92784
  sessionId: input.sessionId,
91597
92785
  atEventIndex: input.atEventIndex,
91598
92786
  authorId: input.authorId,
@@ -91945,7 +93133,7 @@ var InputHistoryStore = class {
91945
93133
 
91946
93134
  // src/storage/memory-backend.ts
91947
93135
  init_file_permissions();
91948
- import { randomUUID as randomUUID46 } from "node:crypto";
93136
+ import { randomUUID as randomUUID47 } from "node:crypto";
91949
93137
  import * as fs65 from "node:fs/promises";
91950
93138
  import * as path124 from "node:path";
91951
93139
 
@@ -92173,7 +93361,7 @@ var FileMemoryBackend = class {
92173
93361
  }
92174
93362
  async remember(scope, entry, filePath) {
92175
93363
  const file = this.resolveFile(filePath, scope);
92176
- const id = `mem_${Date.now()}_${randomUUID46().slice(0, 8)}`;
93364
+ const id = `mem_${Date.now()}_${randomUUID47().slice(0, 8)}`;
92177
93365
  const meta = formatMetadata(entry);
92178
93366
  const line = `- [${entry.ts}] ${id}${meta} ${entry.text.replace(/\n/g, " ")}
92179
93367
  `;
@@ -94187,7 +95375,7 @@ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
94187
95375
  init_file_permissions();
94188
95376
  init_atomic_write();
94189
95377
  init_error();
94190
- import { createHash as createHash39, randomUUID as randomUUID47 } from "node:crypto";
95378
+ import { createHash as createHash39, randomUUID as randomUUID48 } from "node:crypto";
94191
95379
  import * as fs70 from "node:fs/promises";
94192
95380
  var GENESIS_PREV = "0".repeat(64);
94193
95381
  var DEFAULT_FSYNC_EVERY = 100;
@@ -94232,7 +95420,7 @@ var ToolAuditLog = class {
94232
95420
  const tip = await this._resolveChainTip(input.sessionId, fp);
94233
95421
  const prevHash = tip.prevHash;
94234
95422
  const index = tip.nextIndex;
94235
- const id = randomUUID47();
95423
+ const id = randomUUID48();
94236
95424
  const ts = (/* @__PURE__ */ new Date()).toISOString();
94237
95425
  const content = {
94238
95426
  id,
@@ -96773,7 +97961,7 @@ var DEFAULT_SPEC_TEMPLATE = {
96773
97961
  // src/worktree/worktree-manager.ts
96774
97962
  init_error();
96775
97963
  import { mkdir as mkdir35, readFile as readFile73 } from "node:fs/promises";
96776
- import { join as join102, resolve as resolve56, sep as sep9 } from "node:path";
97964
+ import { join as join103, resolve as resolve56, sep as sep9 } from "node:path";
96777
97965
 
96778
97966
  // src/worktree/worktree-git.ts
96779
97967
  import { spawn as spawn13 } from "node:child_process";
@@ -97005,7 +98193,7 @@ var WorktreeManager = class {
97005
98193
  }
97006
98194
  const slug = this.makeSlug(opts.slugHint ?? ownerId);
97007
98195
  const branch = `wstack/ap/${slug}`;
97008
- const dir = join102(this.worktreesRoot(), slug);
98196
+ const dir = join103(this.worktreesRoot(), slug);
97009
98197
  const absDir = resolve56(dir);
97010
98198
  const absRoot = resolve56(this.projectRoot);
97011
98199
  if (!absDir.startsWith(absRoot + sep9)) {
@@ -97396,7 +98584,7 @@ ${merged.stderr}`);
97396
98584
  const startMarker = /^(?:<{7,}(?: |$)|\|{7,}(?: |$))/m;
97397
98585
  for (const rel of paths) {
97398
98586
  try {
97399
- const content = (await readFile73(join102(this.projectRoot, rel), "utf8")).replace(/\r/g, "");
98587
+ const content = (await readFile73(join103(this.projectRoot, rel), "utf8")).replace(/\r/g, "");
97400
98588
  const lines = content.split("\n");
97401
98589
  let seenStart = false;
97402
98590
  for (const line of lines) {
@@ -97440,7 +98628,7 @@ ${merged.stderr}`);
97440
98628
  }
97441
98629
  // ── internals ────────────────────────────────────────────────────────────
97442
98630
  worktreesRoot() {
97443
- return join102(this.projectRoot, ".wrongstack", "worktrees");
98631
+ return join103(this.projectRoot, ".wrongstack", "worktrees");
97444
98632
  }
97445
98633
  async detectBaseBranch() {
97446
98634
  const head = await this.runGit(["rev-parse", "--abbrev-ref", "HEAD"], this.projectRoot);
@@ -97858,6 +99046,7 @@ export {
97858
99046
  QUEUE_MAX_ITEMS,
97859
99047
  QUEUE_MAX_ITEM_BYTES,
97860
99048
  QueueStore,
99049
+ REASONING_EFFORT_LEVELS,
97861
99050
  REFACTOR_PLANNER_AGENT,
97862
99051
  REPORT_STORE_FILE,
97863
99052
  REVIEW_AGENTS,
@@ -98321,6 +99510,7 @@ export {
98321
99510
  isPrivateIPv6,
98322
99511
  isProjectId,
98323
99512
  isProvenDirective,
99513
+ isReasoningEffort,
98324
99514
  isRetryableKind,
98325
99515
  isSafePathSegment,
98326
99516
  isSddError,
@@ -98395,6 +99585,7 @@ export {
98395
99585
  makeMailInboxTool,
98396
99586
  makeMailSendTool,
98397
99587
  makeMailboxTool,
99588
+ makeMutationTestTool,
98398
99589
  makeQualityGateTool,
98399
99590
  makeRollUpTool,
98400
99591
  makeSpawnTool,
@@ -98609,6 +99800,8 @@ export {
98609
99800
  sanitizeModel,
98610
99801
  sanitizeNodeOptions,
98611
99802
  sanitizeRequest,
99803
+ sanitizeTerminalPreview,
99804
+ sanitizeTerminalText,
98612
99805
  sanitizeWireToolName,
98613
99806
  saveCompletedWorkCheckpoint,
98614
99807
  saveGoal,