@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.
- package/dist/coordination/director/director-toolset.d.ts +2 -2
- package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
- package/dist/coordination/director-tools.d.ts +2 -0
- package/dist/coordination/director.d.ts +16 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/fleet.d.ts +12 -0
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +990 -61
- package/dist/coordination/mail-tools.d.ts +10 -6
- package/dist/coordination/mailbox-codecs.d.ts +31 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
- package/dist/coordination/multi-agent-timeout.d.ts +11 -1
- package/dist/coordination/mutation-engine.d.ts +76 -0
- package/dist/coordination/subagent-budget.d.ts +54 -0
- package/dist/coordination/subagent-finish.d.ts +78 -0
- package/dist/core/index.js +58 -13
- package/dist/defaults/index.js +1132 -106
- package/dist/execution/index.js +260 -20
- package/dist/hq/index.js +45 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1456 -263
- package/dist/infrastructure/index.js +22 -3
- package/dist/kernel/events/agent-events.d.ts +31 -2
- package/dist/models/index.js +11 -1
- package/dist/observability/index.js +1 -1
- package/dist/plugin/index.js +113 -12
- package/dist/prompts/index.js +360 -3
- package/dist/security/auto-approve-policy.d.ts +2 -2
- package/dist/security/index.js +177 -50
- package/dist/security/permission-helpers.d.ts +11 -0
- package/dist/security/permission-policy.d.ts +10 -1
- package/dist/security/yolo-risk.d.ts +17 -0
- package/dist/session-catalog/index.js +32 -2
- package/dist/session-catalog/project-server.js +32 -2
- package/dist/skills/index.js +39 -6
- package/dist/storage/index.js +44 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +14 -0
- package/dist/types/multi-agent.d.ts +15 -0
- package/dist/types/provider.d.ts +29 -1
- package/dist/types/tool.d.ts +15 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +54 -4
- package/dist/utils/terminal-sanitize.d.ts +41 -0
- package/dist/utils/tool-subject.d.ts +1 -1
- package/instructions/agents/chaos-monkey.md +61 -0
- 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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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 = /(?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 = /[&|<>"
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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}.${
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
{
|
|
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 ||
|
|
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)
|
|
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:
|
|
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
|
|
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,
|
|
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
|
|
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 ??
|
|
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
|
|
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-${
|
|
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
|
|
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:
|
|
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}-${
|
|
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}-${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
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:
|
|
44693
|
-
body,
|
|
44694
|
-
priority:
|
|
44695
|
-
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
48754
|
-
const sub = { id:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
51546
|
-
token:
|
|
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
|
|
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 ??
|
|
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}:${
|
|
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
|
|
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
|
|
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.
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
56048
|
+
const logicalRequestId = randomUUID35();
|
|
55039
56049
|
const promptManifest = createChroniclePromptManifest(request);
|
|
55040
56050
|
let attempt = 0;
|
|
55041
56051
|
for (; ; ) {
|
|
55042
|
-
const attemptId =
|
|
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 === "
|
|
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
|
|
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 =
|
|
60734
|
+
if (av !== null) a = clamp2(av, 0, 1);
|
|
59728
60735
|
}
|
|
59729
|
-
return [
|
|
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
|
|
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
|
|
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-${
|
|
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-${
|
|
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 =
|
|
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
|
|
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
|
|
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}-${
|
|
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
|
|
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-${
|
|
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-${
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
68563
|
+
const { readFileSync: readFileSync26 } = await import("node:fs");
|
|
67543
68564
|
server = createServer(
|
|
67544
|
-
{ cert:
|
|
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(
|
|
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 === ".\\*")
|
|
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(
|
|
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(
|
|
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 =
|
|
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]
|
|
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
|
-
|
|
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
|
|
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()}_${
|
|
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()}_${
|
|
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
|
|
73724
|
-
|
|
73725
|
-
|
|
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
|
|
73730
|
-
|
|
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(
|
|
79330
|
+
status: deriveSessionStatus(effectiveAgents),
|
|
78220
79331
|
startedAt,
|
|
78221
79332
|
lastActivityAt,
|
|
78222
|
-
agentCount:
|
|
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) =>
|
|
81606
|
-
|
|
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
|
-
|
|
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) =>
|
|
81615
|
-
|
|
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
|
|
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-${
|
|
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 =
|
|
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-${
|
|
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
|
|
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 ??
|
|
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 ??
|
|
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:
|
|
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 ??
|
|
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:
|
|
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 ??
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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-${
|
|
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)}.${
|
|
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
|
|
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:
|
|
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
|
|
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()}_${
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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,
|