@wrongstack/core 0.309.0 → 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.d.ts +7 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/index.js +331 -59
- package/dist/coordination/mutation-engine.d.ts +5 -3
- package/dist/core/index.js +39 -9
- package/dist/defaults/index.js +479 -101
- package/dist/execution/index.js +27 -9
- package/dist/hq/index.js +45 -5
- package/dist/index.js +640 -131
- package/dist/infrastructure/index.js +22 -3
- 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/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 +5 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -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(" ");
|
|
@@ -27976,6 +28043,14 @@ var PATTERNS = [
|
|
|
27976
28043
|
anchor: "sk-ant-"
|
|
27977
28044
|
},
|
|
27978
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
|
+
},
|
|
27979
28054
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g, anchor: "ghp_" },
|
|
27980
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_" },
|
|
27981
28056
|
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g, anchor: "AKIA" },
|
|
@@ -28066,8 +28141,8 @@ var PATTERNS = [
|
|
|
28066
28141
|
// replacement so the separator between adjacent secrets is preserved
|
|
28067
28142
|
// rather than collapsed. Capture groups are therefore: 1=leading
|
|
28068
28143
|
// delimiter, 2=key name, 3=value.
|
|
28069
|
-
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
28070
|
-
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"]
|
|
28071
28146
|
},
|
|
28072
28147
|
{
|
|
28073
28148
|
type: "json_credential_key",
|
|
@@ -28184,6 +28259,27 @@ var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key
|
|
|
28184
28259
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
28185
28260
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
28186
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
|
+
}
|
|
28187
28283
|
var PATTERN_ANCHORS = [
|
|
28188
28284
|
...new Set(
|
|
28189
28285
|
PATTERNS.flatMap(
|
|
@@ -28220,6 +28316,7 @@ var DefaultSecretScrubber = class {
|
|
|
28220
28316
|
}
|
|
28221
28317
|
}
|
|
28222
28318
|
end = safe === -1 ? end : safe + 1;
|
|
28319
|
+
end = extendChunkBoundaryPastPem(text2, i, end);
|
|
28223
28320
|
}
|
|
28224
28321
|
out.push(this.scrubOne(text2.slice(i, end)));
|
|
28225
28322
|
i = end;
|
|
@@ -32326,24 +32423,29 @@ var TOKEN_PATTERNS = [
|
|
|
32326
32423
|
kind: "return-null",
|
|
32327
32424
|
// `return <expr>;` where expr is not already null/undefined/void.
|
|
32328
32425
|
regex: /(?<indent>\breturn\b)(?<expr>\s+[^;{}\n]+?)\s*;/g,
|
|
32329
|
-
replace: () => "return null;"
|
|
32426
|
+
replace: () => "return null;",
|
|
32427
|
+
endpointsInCode: true
|
|
32330
32428
|
}
|
|
32331
32429
|
];
|
|
32332
32430
|
function planMutations(file, source, opts = {}) {
|
|
32333
32431
|
const maxPerFile = opts.maxPerFile ?? 25;
|
|
32334
32432
|
const out = [];
|
|
32335
32433
|
const lines = source.split("\n");
|
|
32434
|
+
const masks = computeLineMasks(source);
|
|
32336
32435
|
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
32337
32436
|
const line = lines[lineIdx];
|
|
32338
32437
|
const t2 = line.trim();
|
|
32339
|
-
if (t2.startsWith("//")
|
|
32438
|
+
if (t2.startsWith("//")) continue;
|
|
32439
|
+
const codeRanges = masks[lineIdx];
|
|
32440
|
+
const inCode = (start) => codeRanges.some(([s, e]) => start >= s && start < e);
|
|
32340
32441
|
for (const pattern of TOKEN_PATTERNS) {
|
|
32341
32442
|
pattern.regex.lastIndex = 0;
|
|
32342
32443
|
let m;
|
|
32343
32444
|
while ((m = pattern.regex.exec(line)) !== null) {
|
|
32344
32445
|
const token = m.groups?.["op"] ?? m[0];
|
|
32345
32446
|
const tokenStart = m.index + m[0].indexOf(token);
|
|
32346
|
-
if (
|
|
32447
|
+
if (!inCode(tokenStart)) continue;
|
|
32448
|
+
if (pattern.endpointsInCode && !inCode(tokenStart + token.length - 1)) continue;
|
|
32347
32449
|
const original = line.slice(tokenStart, tokenStart + token.length);
|
|
32348
32450
|
const replacement = pattern.replace(token);
|
|
32349
32451
|
if (replacement === original) continue;
|
|
@@ -32362,19 +32464,179 @@ function planMutations(file, source, opts = {}) {
|
|
|
32362
32464
|
}
|
|
32363
32465
|
return out.slice(0, maxPerFile);
|
|
32364
32466
|
}
|
|
32365
|
-
function
|
|
32366
|
-
|
|
32367
|
-
|
|
32368
|
-
|
|
32369
|
-
|
|
32370
|
-
|
|
32371
|
-
|
|
32372
|
-
|
|
32373
|
-
|
|
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++;
|
|
32374
32637
|
}
|
|
32375
|
-
|
|
32376
|
-
|
|
32377
|
-
return /['"]/.test(window);
|
|
32638
|
+
while (i < line.length && /[a-z]/.test(line[i])) i++;
|
|
32639
|
+
return i;
|
|
32378
32640
|
}
|
|
32379
32641
|
function parseMutationReport(text2) {
|
|
32380
32642
|
const candidates = [];
|
|
@@ -32425,7 +32687,7 @@ function normalizeMutantEntry(value) {
|
|
|
32425
32687
|
const rec = value;
|
|
32426
32688
|
const id = typeof rec["id"] === "string" ? rec["id"] : void 0;
|
|
32427
32689
|
const status = rec["status"];
|
|
32428
|
-
if (!id || status !== "killed" && status !== "survived" && status !== "skipped") {
|
|
32690
|
+
if (!id || status !== "killed" && status !== "survived" && status !== "skipped" && status !== "killed-by-hang") {
|
|
32429
32691
|
return void 0;
|
|
32430
32692
|
}
|
|
32431
32693
|
return {
|
|
@@ -32481,7 +32743,7 @@ function makeMutationTestTool(director, roster, opts = {}) {
|
|
|
32481
32743
|
},
|
|
32482
32744
|
chaosWorktree: {
|
|
32483
32745
|
anyOf: [{ type: "boolean" }, { type: "string", enum: ["auto", "required", "off"] }],
|
|
32484
|
-
description: "Worktree override for the chaos agent.
|
|
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."
|
|
32485
32747
|
},
|
|
32486
32748
|
timeoutMs: { type: "number", minimum: 1, description: "Per-task timeout for chaos/strengthen/rerun tasks." },
|
|
32487
32749
|
reportOnly: {
|
|
@@ -32503,8 +32765,16 @@ function makeMutationTestTool(director, roster, opts = {}) {
|
|
|
32503
32765
|
error: "No mutable sites found in the given targets (after comment/string filtering)."
|
|
32504
32766
|
};
|
|
32505
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
|
+
}
|
|
32506
32776
|
const chaosSubagentId = await director.spawn(
|
|
32507
|
-
makeChaosConfig(
|
|
32777
|
+
makeChaosConfig(chaosBase, i.chaosWorktree ?? chaosBase.worktree ?? "off")
|
|
32508
32778
|
);
|
|
32509
32779
|
const chaosTaskId = await director.assign({
|
|
32510
32780
|
id: randomUUID14(),
|
|
@@ -32522,6 +32792,7 @@ function makeMutationTestTool(director, roster, opts = {}) {
|
|
|
32522
32792
|
);
|
|
32523
32793
|
const attempts = [];
|
|
32524
32794
|
let current = survivors;
|
|
32795
|
+
let rerunUnknowns = [];
|
|
32525
32796
|
while (current.length > 0 && attempts.length < maxAttempts && i.repairSubagentId) {
|
|
32526
32797
|
const attemptNo = attempts.length + 1;
|
|
32527
32798
|
const strengthenTaskId = await director.assign({
|
|
@@ -32543,7 +32814,7 @@ function makeMutationTestTool(director, roster, opts = {}) {
|
|
|
32543
32814
|
}
|
|
32544
32815
|
const survivorPlan = plan.filter((p) => current.some((s) => s.id === p.id));
|
|
32545
32816
|
const rerunSubagentId = await director.spawn(
|
|
32546
|
-
makeChaosConfig(
|
|
32817
|
+
makeChaosConfig(chaosBase, i.chaosWorktree ?? chaosBase.worktree ?? "off")
|
|
32547
32818
|
);
|
|
32548
32819
|
const rerunTaskId = await director.assign({
|
|
32549
32820
|
id: randomUUID14(),
|
|
@@ -32553,29 +32824,36 @@ function makeMutationTestTool(director, roster, opts = {}) {
|
|
|
32553
32824
|
});
|
|
32554
32825
|
const [rerunResult] = await director.awaitTasks([rerunTaskId]);
|
|
32555
32826
|
const passN = collectOutcomes(rerunResult, survivorPlan);
|
|
32556
|
-
const stillSurviving = passN.filter((m) => m.status
|
|
32827
|
+
const stillSurviving = passN.filter((m) => !isKill(m.status));
|
|
32828
|
+
rerunUnknowns = passN.filter((m) => m.status === "skipped");
|
|
32557
32829
|
attempts.push({
|
|
32558
32830
|
attempt: attemptNo,
|
|
32559
32831
|
survivorsBefore: current,
|
|
32560
32832
|
strengthenResult: { taskId: strengthenResult.taskId, status: strengthenResult.status },
|
|
32561
32833
|
rerunResult: { taskId: rerunTaskId, status: rerunResult?.status ?? "unknown" },
|
|
32562
32834
|
survivorsAfter: stillSurviving,
|
|
32563
|
-
suspectedEquivalent: stillSurviving.filter((m) => current.some((c) => c.id === m.id)).map((m) => m.id)
|
|
32835
|
+
suspectedEquivalent: stillSurviving.filter((m) => m.status === "survived" && current.some((c) => c.id === m.id)).map((m) => m.id)
|
|
32564
32836
|
});
|
|
32565
|
-
current = stillSurviving
|
|
32566
|
-
if (passN.every((m) => m.status === "skipped")) break;
|
|
32837
|
+
current = stillSurviving;
|
|
32567
32838
|
}
|
|
32568
|
-
const finalSurvivors = current;
|
|
32839
|
+
const finalSurvivors = current.filter((m) => m.status === "survived");
|
|
32569
32840
|
const verifiedCount = pass1.filter((m) => m.status !== "skipped").length;
|
|
32570
32841
|
const skippedCount = pass1.filter((m) => m.status === "skipped").length;
|
|
32571
|
-
const
|
|
32572
|
-
const
|
|
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";
|
|
32573
32845
|
return {
|
|
32574
32846
|
verdict,
|
|
32575
32847
|
passed: verdict === "pass",
|
|
32576
32848
|
mutationScore: Number.parseFloat(score.toFixed(3)),
|
|
32577
32849
|
planned: plan.length,
|
|
32578
|
-
killed: pass1.filter((m) => m.status
|
|
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,
|
|
32579
32857
|
survived: pass1.filter((m) => m.status === "survived").length,
|
|
32580
32858
|
skipped: pass1.filter((m) => m.status === "skipped").length,
|
|
32581
32859
|
finalSurvivors: finalSurvivors.map((m) => ({ id: m.id, file: m.file, kind: m.kind })),
|
|
@@ -32583,7 +32861,11 @@ function makeMutationTestTool(director, roster, opts = {}) {
|
|
|
32583
32861
|
strengthenAttempts: attempts.length,
|
|
32584
32862
|
attempts,
|
|
32585
32863
|
chaosTaskId,
|
|
32586
|
-
|
|
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"
|
|
32587
32869
|
};
|
|
32588
32870
|
}
|
|
32589
32871
|
};
|
|
@@ -32599,7 +32881,7 @@ function normalizeMutationTestInput(input) {
|
|
|
32599
32881
|
maxPerFile: typeof raw["maxPerFile"] === "number" ? raw["maxPerFile"] : void 0,
|
|
32600
32882
|
maxStrengthenAttempts: typeof raw["maxStrengthenAttempts"] === "number" ? raw["maxStrengthenAttempts"] : void 0,
|
|
32601
32883
|
repairSubagentId: typeof raw["repairSubagentId"] === "string" && raw["repairSubagentId"].trim() ? raw["repairSubagentId"].trim() : void 0,
|
|
32602
|
-
chaosWorktree: raw["chaosWorktree"]
|
|
32884
|
+
chaosWorktree: normalizeWorktreeOverride(raw["chaosWorktree"]),
|
|
32603
32885
|
timeoutMs: typeof raw["timeoutMs"] === "number" ? raw["timeoutMs"] : void 0,
|
|
32604
32886
|
reportOnly: raw["reportOnly"] === true
|
|
32605
32887
|
};
|
|
@@ -32621,8 +32903,7 @@ function buildPlan(i, projectRoot) {
|
|
|
32621
32903
|
}
|
|
32622
32904
|
return plan;
|
|
32623
32905
|
}
|
|
32624
|
-
function makeChaosConfig(
|
|
32625
|
-
const base = roster?.[CHAOS_ROLE] ?? getAgentDefinition(CHAOS_ROLE)?.config ?? { name: "Chaos Monkey", role: CHAOS_ROLE };
|
|
32906
|
+
function makeChaosConfig(base, worktree) {
|
|
32626
32907
|
return { ...instantiateRosterConfig(CHAOS_ROLE, base), worktree };
|
|
32627
32908
|
}
|
|
32628
32909
|
function buildChaosTask(plan, i, pass, priorSurvivors) {
|
|
@@ -32638,7 +32919,7 @@ ${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join(
|
|
|
32638
32919
|
"For each mutant, in order:",
|
|
32639
32920
|
"1. Apply ONLY that mutation at its exact (file, line, column).",
|
|
32640
32921
|
`2. Run the test command: ${i.testCommand}${i.cwd ? ` (cwd: ${i.cwd})` : ""}`,
|
|
32641
|
-
"3. Record killed (tests failed \u2014 quote first failing assertion)
|
|
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).",
|
|
32642
32923
|
"4. Restore the file byte-for-byte before the next mutant.",
|
|
32643
32924
|
"",
|
|
32644
32925
|
"Mutants:",
|
|
@@ -32650,23 +32931,49 @@ ${priorSurvivors.map((s) => `- ${s.id} (${s.kind} @ ${s.file}:${s.line})`).join(
|
|
|
32650
32931
|
].join("\n");
|
|
32651
32932
|
}
|
|
32652
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}` : ""}`;
|
|
32653
32937
|
return [
|
|
32654
|
-
`Strengthen the tests so
|
|
32655
|
-
"",
|
|
32656
|
-
"Each survivor below was a deliberate sabotage of production code that the current suite did NOT catch:",
|
|
32657
|
-
...survivors.map((s) => `- ${s.id} | ${s.file}:${s.line} | ${s.kind}${s.evidence ? ` | ${s.evidence}` : ""}`),
|
|
32938
|
+
`Strengthen the tests so the mutants below die (attempt ${attempt}).`,
|
|
32658
32939
|
"",
|
|
32659
|
-
|
|
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}`,
|
|
32660
32951
|
"",
|
|
32661
|
-
"For each survivor add or tighten exactly one assertion that pins the sabotaged boundary/behavior. Do not change production code. Do not weaken other tests. Run the suite green on clean code before finishing."
|
|
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."
|
|
32662
32953
|
].join("\n");
|
|
32663
32954
|
}
|
|
32664
32955
|
function collectOutcomes(result, plan) {
|
|
32665
32956
|
const fromText = parseTextOutcomes(result);
|
|
32666
32957
|
if (fromText.length > 0) {
|
|
32667
|
-
const
|
|
32668
|
-
const matched =
|
|
32669
|
-
|
|
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
|
+
}
|
|
32670
32977
|
}
|
|
32671
32978
|
return plan.map((p) => ({
|
|
32672
32979
|
id: p.id,
|
|
@@ -32677,6 +32984,9 @@ function collectOutcomes(result, plan) {
|
|
|
32677
32984
|
evidence: result ? `chaos task ended ${result.status}` : "chaos task produced no result"
|
|
32678
32985
|
}));
|
|
32679
32986
|
}
|
|
32987
|
+
function isKill(status) {
|
|
32988
|
+
return status === "killed" || status === "killed-by-hang";
|
|
32989
|
+
}
|
|
32680
32990
|
function parseTextOutcomes(result) {
|
|
32681
32991
|
const text2 = typeof result?.result === "string" ? result.result : void 0;
|
|
32682
32992
|
if (!text2) return [];
|
|
@@ -39402,11 +39712,13 @@ var CHAOS_MONKEY_AGENT = {
|
|
|
39402
39712
|
tools: [...TOOLS.build],
|
|
39403
39713
|
skillNames: ["testing", "typescript-strict"],
|
|
39404
39714
|
spawnBudgetExempt: true,
|
|
39405
|
-
//
|
|
39406
|
-
//
|
|
39407
|
-
//
|
|
39408
|
-
//
|
|
39409
|
-
|
|
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",
|
|
39410
39722
|
// Report travels via submit_result + final text, not the leader's stream.
|
|
39411
39723
|
textStream: "silent",
|
|
39412
39724
|
toolStream: "silent"
|
|
@@ -40665,6 +40977,7 @@ function worktreeOwnerLabel(task, config) {
|
|
|
40665
40977
|
}
|
|
40666
40978
|
|
|
40667
40979
|
// src/coordination/director.ts
|
|
40980
|
+
var BUSY_REARM_FLOOR_MS = 1e3;
|
|
40668
40981
|
var Director = class _Director {
|
|
40669
40982
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars — just a cast helper */
|
|
40670
40983
|
static _asManifestEntry(v) {
|
|
@@ -40730,6 +41043,13 @@ var Director = class _Director {
|
|
|
40730
41043
|
subagentIdleTimeoutMs;
|
|
40731
41044
|
retireSubagentOnTaskComplete;
|
|
40732
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();
|
|
40733
41053
|
sharedScratchpadPath;
|
|
40734
41054
|
maxSpawns;
|
|
40735
41055
|
maxSpawnDepth;
|
|
@@ -40886,7 +41206,13 @@ var Director = class _Director {
|
|
|
40886
41206
|
handleTaskCompleted(payload) {
|
|
40887
41207
|
const r = payload.result;
|
|
40888
41208
|
const settled = this.tasks.settle(r);
|
|
40889
|
-
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
|
+
}
|
|
40890
41216
|
const title = this.tasks.descriptionFor(r.taskId, payload.task.description ?? r.taskId);
|
|
40891
41217
|
if (!settled.consumedInBand && this.taskResultNotifier) {
|
|
40892
41218
|
const resultText = typeof r.result === "string" ? r.result : r.result !== void 0 ? safeStringify(r.result) : void 0;
|
|
@@ -40951,7 +41277,7 @@ var Director = class _Director {
|
|
|
40951
41277
|
}
|
|
40952
41278
|
this.armSubagentIdleRetirement(
|
|
40953
41279
|
r.subagentId,
|
|
40954
|
-
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleTimeoutMs
|
|
41280
|
+
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleDelayMs.get(r.subagentId) ?? this.subagentIdleTimeoutMs
|
|
40955
41281
|
);
|
|
40956
41282
|
}
|
|
40957
41283
|
extensionsFor(subagentId) {
|
|
@@ -41037,6 +41363,7 @@ var Director = class _Director {
|
|
|
41037
41363
|
this.resolveSpawnModel(config);
|
|
41038
41364
|
const subagentId = await spawn6(this, config, priceLookup);
|
|
41039
41365
|
const perSubagentIdleMs = typeof config.idleTimeoutMs === "number" && Number.isFinite(config.idleTimeoutMs) && config.idleTimeoutMs >= 0 ? config.idleTimeoutMs : this.subagentIdleTimeoutMs;
|
|
41366
|
+
this.subagentIdleDelayMs.set(subagentId, perSubagentIdleMs);
|
|
41040
41367
|
this.armSubagentIdleRetirement(subagentId, perSubagentIdleMs);
|
|
41041
41368
|
return subagentId;
|
|
41042
41369
|
}
|
|
@@ -41090,6 +41417,7 @@ var Director = class _Director {
|
|
|
41090
41417
|
this.budgetPolicy.dispose();
|
|
41091
41418
|
for (const timer of this.subagentIdleTimers.values()) clearTimeout(timer);
|
|
41092
41419
|
this.subagentIdleTimers.clear();
|
|
41420
|
+
this.subagentIdleDelayMs.clear();
|
|
41093
41421
|
await this.coordinator.stopAll();
|
|
41094
41422
|
this.tasks.resolveWaitersOnShutdown();
|
|
41095
41423
|
for (const b of this.subagentBridges.values()) {
|
|
@@ -41148,6 +41476,7 @@ var Director = class _Director {
|
|
|
41148
41476
|
}
|
|
41149
41477
|
async remove(subagentId) {
|
|
41150
41478
|
this.clearSubagentIdleRetirement(subagentId);
|
|
41479
|
+
this.subagentIdleDelayMs.delete(subagentId);
|
|
41151
41480
|
void this.appendSessionEvent({
|
|
41152
41481
|
type: "agent_stopped",
|
|
41153
41482
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -41200,9 +41529,13 @@ var Director = class _Director {
|
|
|
41200
41529
|
const timer = setTimeout(() => {
|
|
41201
41530
|
this.subagentIdleTimers.delete(subagentId);
|
|
41202
41531
|
const entry = this.coordinator.getStatus().subagents.find((a) => a.id === subagentId);
|
|
41203
|
-
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
|
+
}
|
|
41204
41537
|
if (this.coordinator.listPendingTasks().some((task) => task.subagentId === subagentId)) {
|
|
41205
|
-
this.armSubagentIdleRetirement(subagentId,
|
|
41538
|
+
this.armSubagentIdleRetirement(subagentId, Math.max(delayMs, BUSY_REARM_FLOOR_MS));
|
|
41206
41539
|
return;
|
|
41207
41540
|
}
|
|
41208
41541
|
void this.remove(subagentId).catch(
|
|
@@ -44858,16 +45191,14 @@ var ExploreCompanion = class {
|
|
|
44858
45191
|
this.running = true;
|
|
44859
45192
|
this.unsubscribers.push(
|
|
44860
45193
|
this.opts.events.on("tool.executed", (e) => {
|
|
44861
|
-
|
|
44862
|
-
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
45194
|
+
if (e.sessionId !== this.resolveLeaderSessionId()) return;
|
|
44863
45195
|
this.trackToolExecuted(e);
|
|
44864
45196
|
})
|
|
44865
45197
|
);
|
|
44866
45198
|
if (this.cfg.signals.todoInProgress && this.resolveLeaderAgentId()) {
|
|
44867
45199
|
this.unsubscribers.push(
|
|
44868
45200
|
this.opts.events.on("session.agents_updated", (e) => {
|
|
44869
|
-
|
|
44870
|
-
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
45201
|
+
if (e.sessionId !== this.resolveLeaderSessionId()) return;
|
|
44871
45202
|
this.trackAgentTodos(e.agents);
|
|
44872
45203
|
})
|
|
44873
45204
|
);
|
|
@@ -44875,8 +45206,7 @@ var ExploreCompanion = class {
|
|
|
44875
45206
|
if (this.cfg.signals.errorSymbol) {
|
|
44876
45207
|
this.unsubscribers.push(
|
|
44877
45208
|
this.opts.events.on("error", (e) => {
|
|
44878
|
-
|
|
44879
|
-
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
45209
|
+
if (e.sessionId !== this.resolveLeaderSessionId()) return;
|
|
44880
45210
|
this.trackError(e.err);
|
|
44881
45211
|
})
|
|
44882
45212
|
);
|
|
@@ -44989,9 +45319,18 @@ var ExploreCompanion = class {
|
|
|
44989
45319
|
limit: 20
|
|
44990
45320
|
});
|
|
44991
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
|
+
);
|
|
44992
45329
|
for (const msg of messages) {
|
|
44993
45330
|
if (msg.type !== "ask" && msg.type !== "assign") continue;
|
|
44994
|
-
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;
|
|
44995
45334
|
if (!fromLeader) continue;
|
|
44996
45335
|
this.engage({
|
|
44997
45336
|
id: randomUUID24(),
|
|
@@ -54945,6 +55284,7 @@ function requestLimitExtension(opts) {
|
|
|
54945
55284
|
// src/prompts/prompt-journal.ts
|
|
54946
55285
|
import * as fs31 from "node:fs/promises";
|
|
54947
55286
|
import * as path77 from "node:path";
|
|
55287
|
+
var defaultScrubber2 = new DefaultSecretScrubber();
|
|
54948
55288
|
var PROMPT_JOURNAL_RAW_MARKER = "promptJournal.raw";
|
|
54949
55289
|
async function ensureGitignore(projectRoot) {
|
|
54950
55290
|
const gitignorePath = path77.join(projectRoot, ".gitignore");
|
|
@@ -54973,7 +55313,9 @@ async function recordPromptJournalEntry(opts) {
|
|
|
54973
55313
|
const monthStr = dateStr.slice(0, 7);
|
|
54974
55314
|
const sessionId = opts.sessionId && opts.sessionId.trim() ? opts.sessionId.trim() : "general";
|
|
54975
55315
|
const id = `pmt_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
54976
|
-
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;
|
|
54977
55319
|
const lines = content.split("\n");
|
|
54978
55320
|
const characterCount = content.length;
|
|
54979
55321
|
const lineCount2 = lines.length;
|
|
@@ -54986,7 +55328,7 @@ async function recordPromptJournalEntry(opts) {
|
|
|
54986
55328
|
role: opts.role ?? (opts.category === "system_prompt" ? "system" : "user"),
|
|
54987
55329
|
category: opts.category,
|
|
54988
55330
|
content,
|
|
54989
|
-
rawContent
|
|
55331
|
+
rawContent,
|
|
54990
55332
|
metadata: {
|
|
54991
55333
|
model: opts.model,
|
|
54992
55334
|
provider: opts.provider,
|
|
@@ -54997,7 +55339,7 @@ async function recordPromptJournalEntry(opts) {
|
|
|
54997
55339
|
activeTools: opts.activeTools,
|
|
54998
55340
|
contextFiles: opts.contextFiles,
|
|
54999
55341
|
durationMs: opts.durationMs,
|
|
55000
|
-
decisionReason
|
|
55342
|
+
decisionReason,
|
|
55001
55343
|
tags: opts.tags
|
|
55002
55344
|
}
|
|
55003
55345
|
};
|
|
@@ -56560,10 +56902,7 @@ function createAgentToolHandler(a) {
|
|
|
56560
56902
|
} catch {
|
|
56561
56903
|
}
|
|
56562
56904
|
}
|
|
56563
|
-
if (decision === "
|
|
56564
|
-
const p = a.permission;
|
|
56565
|
-
p.allowOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
|
|
56566
|
-
} else if (decision === "no") {
|
|
56905
|
+
if (decision === "no") {
|
|
56567
56906
|
const p = a.permission;
|
|
56568
56907
|
p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
|
|
56569
56908
|
}
|
|
@@ -64327,6 +64666,10 @@ var DefaultSkillLoader = class {
|
|
|
64327
64666
|
);
|
|
64328
64667
|
for (const e of entries) {
|
|
64329
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
|
+
}
|
|
64330
64673
|
const skillFile = path80.join(dir, e.name, "SKILL.md");
|
|
64331
64674
|
let raw;
|
|
64332
64675
|
try {
|
|
@@ -65731,7 +66074,7 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
65731
66074
|
// redaction function (false positive = cosmetic noise; false negative = leak).
|
|
65732
66075
|
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
65733
66076
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
65734
|
-
/(?: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,
|
|
65735
66078
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
65736
66079
|
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
65737
66080
|
// every such flag in the command line is redacted, not just the first.
|
|
@@ -66106,7 +66449,7 @@ var ToolExecutor = class _ToolExecutor {
|
|
|
66106
66449
|
return { result, tool, durationMs: Date.now() - start };
|
|
66107
66450
|
}
|
|
66108
66451
|
if (effectivePermission === "confirm") {
|
|
66109
|
-
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;
|
|
66110
66453
|
if (this.opts.confirmAwaiter) {
|
|
66111
66454
|
const awaiter = this.opts.confirmAwaiter;
|
|
66112
66455
|
const choice = await new Promise(
|
|
@@ -68643,7 +68986,9 @@ function hasRecursiveForceDelete(command, projectRoot) {
|
|
|
68643
68986
|
if (token === "rd" || token === "rmdir") {
|
|
68644
68987
|
const args = commandSegment(tokens, i + 1).map((arg) => arg.toLowerCase());
|
|
68645
68988
|
if (args.includes("/s")) {
|
|
68646
|
-
const targets = args.filter(
|
|
68989
|
+
const targets = args.filter(
|
|
68990
|
+
(arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
|
|
68991
|
+
);
|
|
68647
68992
|
if (targets.length === 0) return true;
|
|
68648
68993
|
if (targets.some(isCatastrophicDeleteTarget)) return true;
|
|
68649
68994
|
if (targets.some((target) => !pathLooksInsideProject(target, projectRoot))) return true;
|
|
@@ -68712,7 +69057,8 @@ function hasFindExec(command) {
|
|
|
68712
69057
|
function isCatastrophicDeleteTarget(rawTarget) {
|
|
68713
69058
|
const t2 = rawTarget.replace(/^['"]|['"]$/g, "").trim();
|
|
68714
69059
|
if (!t2) return false;
|
|
68715
|
-
if (t2 === "*" || t2 === "." || t2 === "./" || t2 === ".\\" || t2 === "./*" || t2 === ".\\*")
|
|
69060
|
+
if (t2 === "*" || t2 === "." || t2 === "./" || t2 === ".\\" || t2 === "./*" || t2 === ".\\*")
|
|
69061
|
+
return true;
|
|
68716
69062
|
const s = t2.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
|
|
68717
69063
|
if (s === "") return true;
|
|
68718
69064
|
if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
|
|
@@ -68752,12 +69098,16 @@ function hasCatastrophicDelete(command) {
|
|
|
68752
69098
|
const args = tokens.slice(i + 1);
|
|
68753
69099
|
const recursive = args.some((arg) => arg.toLowerCase() === "/s");
|
|
68754
69100
|
if (!recursive) continue;
|
|
68755
|
-
const targets = args.filter(
|
|
69101
|
+
const targets = args.filter(
|
|
69102
|
+
(arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
|
|
69103
|
+
);
|
|
68756
69104
|
if (targets.some(isCatastrophicDeleteTarget)) return true;
|
|
68757
69105
|
}
|
|
68758
69106
|
if (token === "del" || token === "erase") {
|
|
68759
69107
|
const args = tokens.slice(i + 1);
|
|
68760
|
-
const targets = args.filter(
|
|
69108
|
+
const targets = args.filter(
|
|
69109
|
+
(arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg)
|
|
69110
|
+
);
|
|
68761
69111
|
if (targets.some(isCatastrophicDeleteTarget)) return true;
|
|
68762
69112
|
}
|
|
68763
69113
|
}
|
|
@@ -68818,6 +69168,47 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
|
|
|
68818
69168
|
if (HIGH_IMPACT_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
|
|
68819
69169
|
return false;
|
|
68820
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
|
+
}
|
|
68821
69212
|
|
|
68822
69213
|
// src/security/permission-helpers.ts
|
|
68823
69214
|
function matchesTrust(patterns, subject2) {
|
|
@@ -68834,7 +69225,7 @@ function hasShellSubject(tool) {
|
|
|
68834
69225
|
]);
|
|
68835
69226
|
}
|
|
68836
69227
|
function alwaysAllowUnavailableReason(tool, input) {
|
|
68837
|
-
const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
69228
|
+
const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
|
|
68838
69229
|
if (subject2 !== void 0) return void 0;
|
|
68839
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.`;
|
|
68840
69231
|
}
|
|
@@ -68894,8 +69285,18 @@ var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trus
|
|
|
68894
69285
|
function unescapeGlobSubject(value) {
|
|
68895
69286
|
return value.replace(/\\([*?[\]])/g, "$1");
|
|
68896
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
|
+
}
|
|
68897
69296
|
function normalizeForCompare(value) {
|
|
68898
|
-
const forward =
|
|
69297
|
+
const forward = stripAdsSuffix(
|
|
69298
|
+
unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "")
|
|
69299
|
+
);
|
|
68899
69300
|
return process.platform === "win32" ? forward.toLowerCase() : forward;
|
|
68900
69301
|
}
|
|
68901
69302
|
function realpathOfNearestExisting(p) {
|
|
@@ -68932,7 +69333,7 @@ function isProtectedAgentStatePath(absPath) {
|
|
|
68932
69333
|
return AGENT_STATE_SENSITIVE_BASENAMES.test(path90.basename(normalizeForCompare(absPath)));
|
|
68933
69334
|
}
|
|
68934
69335
|
function pathLooksSensitive(rawPath) {
|
|
68935
|
-
const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
|
|
69336
|
+
const normalized = stripAdsSuffix(stripShellQuotes(rawPath).replace(/\\/g, "/"));
|
|
68936
69337
|
if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
|
|
68937
69338
|
return isProtectedAgentStatePath(normalized);
|
|
68938
69339
|
}
|
|
@@ -68956,10 +69357,24 @@ function shellCommandReadsSensitivePath(command) {
|
|
|
68956
69357
|
}
|
|
68957
69358
|
return false;
|
|
68958
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
|
+
}
|
|
68959
69374
|
|
|
68960
69375
|
// src/security/permission-explain.ts
|
|
68961
69376
|
function explainPermissionTrace(state, tool, input, ctx) {
|
|
68962
|
-
const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
69377
|
+
const subject2 = subjectForToolInput(tool.name, input, tool.subjectKey, tool.subjectFields);
|
|
68963
69378
|
const steps = [];
|
|
68964
69379
|
let winnerIndex = -1;
|
|
68965
69380
|
const add = (rule, matched, decision, source, detail) => {
|
|
@@ -69180,13 +69595,7 @@ function explainPermissionTrace(state, tool, input, ctx) {
|
|
|
69180
69595
|
}
|
|
69181
69596
|
};
|
|
69182
69597
|
}
|
|
69183
|
-
add(
|
|
69184
|
-
"yolo",
|
|
69185
|
-
true,
|
|
69186
|
-
"auto",
|
|
69187
|
-
"yolo",
|
|
69188
|
-
"YOLO mode is active \u2014 auto-approving every non-denied call"
|
|
69189
|
-
);
|
|
69598
|
+
add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
|
|
69190
69599
|
winnerIndex = steps.length - 1;
|
|
69191
69600
|
return {
|
|
69192
69601
|
toolName: tool.name,
|
|
@@ -69458,7 +69867,14 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
69458
69867
|
static isMcpTool(name) {
|
|
69459
69868
|
return name.startsWith("mcp__");
|
|
69460
69869
|
}
|
|
69461
|
-
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
|
+
}
|
|
69462
69878
|
const caps = tool.capabilities ?? [];
|
|
69463
69879
|
const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
|
|
69464
69880
|
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
@@ -69485,8 +69901,8 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
69485
69901
|
}
|
|
69486
69902
|
allowOnce() {
|
|
69487
69903
|
}
|
|
69488
|
-
async explain(tool) {
|
|
69489
|
-
const decision = await this.evaluate(tool);
|
|
69904
|
+
async explain(tool, input) {
|
|
69905
|
+
const decision = await this.evaluate(tool, input);
|
|
69490
69906
|
return {
|
|
69491
69907
|
toolName: tool.name,
|
|
69492
69908
|
subject: null,
|
|
@@ -69536,6 +69952,17 @@ function fsWriteTargetPaths(input) {
|
|
|
69536
69952
|
}
|
|
69537
69953
|
return out;
|
|
69538
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
|
+
}
|
|
69539
69966
|
var DefaultPermissionPolicy = class {
|
|
69540
69967
|
policy = {};
|
|
69541
69968
|
loaded = false;
|
|
@@ -69574,6 +70001,7 @@ var DefaultPermissionPolicy = class {
|
|
|
69574
70001
|
yoloBlockedAsDestructive(tool, input, ctx) {
|
|
69575
70002
|
if (!this.yolo || this.yoloDestructive) return false;
|
|
69576
70003
|
if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
|
|
70004
|
+
if (attachesWellKnownCredential(input)) return true;
|
|
69577
70005
|
const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
|
|
69578
70006
|
if (!isShellSurface) return false;
|
|
69579
70007
|
const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
|
|
@@ -69667,8 +70095,8 @@ var DefaultPermissionPolicy = class {
|
|
|
69667
70095
|
};
|
|
69668
70096
|
}
|
|
69669
70097
|
const namespaceEntry = this.findNamespaceEntry(tool.name);
|
|
69670
|
-
const entry = this.policy[tool.name]
|
|
69671
|
-
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);
|
|
69672
70100
|
const cacheKey = `${tool.name}::${subject2 ?? tool.name}`;
|
|
69673
70101
|
const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
|
|
69674
70102
|
if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
|
|
@@ -69685,15 +70113,6 @@ var DefaultPermissionPolicy = class {
|
|
|
69685
70113
|
this._evalCache.set(evalKey, decision);
|
|
69686
70114
|
return decision;
|
|
69687
70115
|
}
|
|
69688
|
-
if (this.sessionAllowed.has(cacheKey)) {
|
|
69689
|
-
this.sessionAllowed.delete(cacheKey);
|
|
69690
|
-
const decision = {
|
|
69691
|
-
permission: "auto",
|
|
69692
|
-
source: "trust",
|
|
69693
|
-
reason: "session one-shot allow (user pressed yes)"
|
|
69694
|
-
};
|
|
69695
|
-
return decision;
|
|
69696
|
-
}
|
|
69697
70116
|
if (entry?.deny && subject2 && matchesTrust(entry.deny, subject2)) {
|
|
69698
70117
|
this._logDeny(tool.name, subject2, "matched deny pattern");
|
|
69699
70118
|
const decision = {
|
|
@@ -69704,6 +70123,15 @@ var DefaultPermissionPolicy = class {
|
|
|
69704
70123
|
this._evalCache.set(evalKey, decision);
|
|
69705
70124
|
return decision;
|
|
69706
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
|
+
}
|
|
69707
70135
|
if (tool.permission === "deny") {
|
|
69708
70136
|
this._logDeny(tool.name, subject2, "tool default deny");
|
|
69709
70137
|
const decision = {
|
|
@@ -69714,6 +70142,7 @@ var DefaultPermissionPolicy = class {
|
|
|
69714
70142
|
this._evalCache.set(evalKey, decision);
|
|
69715
70143
|
return decision;
|
|
69716
70144
|
}
|
|
70145
|
+
const denyUnevaluated = Boolean(entry?.deny?.length) && subject2 === void 0;
|
|
69717
70146
|
const allowMatches = hasShellSubject(tool) ? matchesCommandTrust : matchesTrust;
|
|
69718
70147
|
if (entry?.allow && subject2 && allowMatches(entry.allow, subject2)) {
|
|
69719
70148
|
const decision = {
|
|
@@ -69724,7 +70153,7 @@ var DefaultPermissionPolicy = class {
|
|
|
69724
70153
|
this._evalCache.set(evalKey, decision);
|
|
69725
70154
|
return decision;
|
|
69726
70155
|
}
|
|
69727
|
-
if (entry?.auto) {
|
|
70156
|
+
if (entry?.auto && !denyUnevaluated) {
|
|
69728
70157
|
const decision = { permission: "auto", source: "trust" };
|
|
69729
70158
|
this._evalCache.set(evalKey, decision);
|
|
69730
70159
|
return decision;
|
|
@@ -69824,19 +70253,10 @@ var DefaultPermissionPolicy = class {
|
|
|
69824
70253
|
}
|
|
69825
70254
|
return { permission: "confirm", source: "default" };
|
|
69826
70255
|
}
|
|
70256
|
+
// Delegates to the shared helper so the subagent policy applies the exact
|
|
70257
|
+
// same rule — see `isSensitiveReadCall` in ./permission-helpers.ts.
|
|
69827
70258
|
isSensitiveReadCall(tool, input) {
|
|
69828
|
-
|
|
69829
|
-
if (isReadTool && inputPathLooksSensitive(input)) return true;
|
|
69830
|
-
const hasShellCap = hasCapability(tool, [
|
|
69831
|
-
ToolCapabilities.SHELL_ARBITRARY,
|
|
69832
|
-
ToolCapabilities.SHELL_RESTRICTED,
|
|
69833
|
-
ToolCapabilities.SHELL_EXEC
|
|
69834
|
-
]);
|
|
69835
|
-
if (!hasShellCap && tool.name !== "bash" && tool.name !== "shell" && tool.name !== "exec") {
|
|
69836
|
-
return false;
|
|
69837
|
-
}
|
|
69838
|
-
const command = shellCommandLineFromInput(input);
|
|
69839
|
-
return command ? shellCommandReadsSensitivePath(command) : false;
|
|
70259
|
+
return isSensitiveReadCall(tool, input);
|
|
69840
70260
|
}
|
|
69841
70261
|
async trust(rule) {
|
|
69842
70262
|
if (!this.loaded) await this.reload();
|
|
@@ -78870,6 +79290,15 @@ function deriveSessionStatus(agents) {
|
|
|
78870
79290
|
(a) => a.status === "running" || a.status === "streaming" || a.status === "waiting_user"
|
|
78871
79291
|
) ? "active" : "idle";
|
|
78872
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
|
+
}
|
|
78873
79302
|
function startSessionTelemetryBridge(opts) {
|
|
78874
79303
|
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
78875
79304
|
const publisher = opts.publisher;
|
|
@@ -78890,6 +79319,7 @@ function startSessionTelemetryBridge(opts) {
|
|
|
78890
79319
|
let lastPublishedAtMs = Date.now();
|
|
78891
79320
|
let disposed = false;
|
|
78892
79321
|
function buildSnapshot() {
|
|
79322
|
+
const effectiveAgents = downgradeStaleAgentStatuses(agents, Date.parse(now()));
|
|
78893
79323
|
return {
|
|
78894
79324
|
sessionId: opts.sessionId,
|
|
78895
79325
|
clientKind: identity2.kind,
|
|
@@ -78897,11 +79327,11 @@ function startSessionTelemetryBridge(opts) {
|
|
|
78897
79327
|
projectId: project2.projectId,
|
|
78898
79328
|
projectName: opts.projectName ?? project2.projectName,
|
|
78899
79329
|
projectRoot: opts.projectRoot,
|
|
78900
|
-
status: deriveSessionStatus(
|
|
79330
|
+
status: deriveSessionStatus(effectiveAgents),
|
|
78901
79331
|
startedAt,
|
|
78902
79332
|
lastActivityAt,
|
|
78903
|
-
agentCount:
|
|
78904
|
-
agents,
|
|
79333
|
+
agentCount: effectiveAgents.length,
|
|
79334
|
+
agents: effectiveAgents,
|
|
78905
79335
|
...identity2.hostname !== void 0 ? { hostname: identity2.hostname } : {},
|
|
78906
79336
|
...identity2.pid !== void 0 ? { pid: identity2.pid } : {},
|
|
78907
79337
|
...opts.gitBranch !== void 0 ? { gitBranch: opts.gitBranch } : {}
|
|
@@ -82282,18 +82712,88 @@ var DefaultPluginAPI = class {
|
|
|
82282
82712
|
list: () => tr.list()
|
|
82283
82713
|
};
|
|
82284
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
|
+
};
|
|
82285
82724
|
this.providers = {
|
|
82286
|
-
register: (f) =>
|
|
82287
|
-
|
|
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
|
+
},
|
|
82288
82735
|
create: (cfg) => pr.create(cfg),
|
|
82289
82736
|
list: () => pr.list()
|
|
82290
82737
|
};
|
|
82291
|
-
|
|
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
|
+
}
|
|
82292
82771
|
const scr = init.slashCommandRegistry;
|
|
82293
82772
|
const official = init.official === true;
|
|
82773
|
+
const commandsIOwn = /* @__PURE__ */ new Set();
|
|
82294
82774
|
this.slashCommands = scr ? {
|
|
82295
|
-
register: (cmd) =>
|
|
82296
|
-
|
|
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
|
+
},
|
|
82297
82797
|
get: (name) => scr.get(name),
|
|
82298
82798
|
list: () => scr.list()
|
|
82299
82799
|
} : noopSlashCommands;
|
|
@@ -86898,14 +87398,21 @@ async function openInEditor(filePath, env = process.env) {
|
|
|
86898
87398
|
context: { filePath }
|
|
86899
87399
|
});
|
|
86900
87400
|
}
|
|
86901
|
-
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, {
|
|
86902
87414
|
stdio: "ignore",
|
|
86903
87415
|
detached: true,
|
|
86904
|
-
shell,
|
|
86905
|
-
// `shell` routes through cmd.exe on win32, which flashes a console window
|
|
86906
|
-
// before the editor appears. A GUI editor is unaffected by the flag; the
|
|
86907
|
-
// shell wrapper is what it suppresses. Repo convention — see
|
|
86908
|
-
// `core/tests/architecture/spawn-convention.test.ts`.
|
|
86909
87416
|
windowsHide: true
|
|
86910
87417
|
});
|
|
86911
87418
|
child.unref();
|
|
@@ -99293,6 +99800,8 @@ export {
|
|
|
99293
99800
|
sanitizeModel,
|
|
99294
99801
|
sanitizeNodeOptions,
|
|
99295
99802
|
sanitizeRequest,
|
|
99803
|
+
sanitizeTerminalPreview,
|
|
99804
|
+
sanitizeTerminalText,
|
|
99296
99805
|
sanitizeWireToolName,
|
|
99297
99806
|
saveCompletedWorkCheckpoint,
|
|
99298
99807
|
saveGoal,
|