@sema-agent/core 5.29.0 → 5.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +141 -0
- package/dist/agents/send-message-tool.js +2 -0
- package/dist/agents/subagent.d.ts +2 -0
- package/dist/agents/subagent.js +6 -0
- package/dist/agents/teacher.js +2 -0
- package/dist/agents/verify.js +2 -0
- package/dist/core/auto-compaction.d.ts +5 -1
- package/dist/core/auto-compaction.js +10 -1
- package/dist/core/checkpoint-store.d.ts +51 -5
- package/dist/core/checkpoint-store.js +2 -1
- package/dist/core/hooks.d.ts +12 -1
- package/dist/core/hooks.js +8 -2
- package/dist/core/permission-rules.js +2 -2
- package/dist/core/runner/prepare-task.d.ts +21 -5
- package/dist/core/runner/prepare-task.js +115 -26
- package/dist/core/runner/runtask.js +29 -3
- package/dist/core/runner/session-rule-policy.d.ts +3 -2
- package/dist/core/runner/tool-output-projection.js +1 -1
- package/dist/core/sensitive-path-policy.js +5 -16
- package/dist/core/store-contracts/tool-result-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/tool-result-store-contract.js +26 -1
- package/dist/core/tighten-task-spec.js +18 -0
- package/dist/core/tool-policy.d.ts +20 -1
- package/dist/core/tool-policy.js +31 -4
- package/dist/core/tool-result-store.d.ts +6 -4
- package/dist/core/tool-result-store.js +3 -1
- package/dist/core/types.d.ts +63 -1
- package/dist/engine/harness/types.d.ts +10 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/orchestration/run-workflow-tool.d.ts +26 -0
- package/dist/orchestration/run-workflow-tool.js +7 -4
- package/dist/orchestration/workflow-governance.d.ts +53 -3
- package/dist/orchestration/workflow-governance.js +162 -25
- package/dist/orchestration/workflow-primitives.d.ts +15 -1
- package/dist/orchestration/workflow-primitives.js +13 -2
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/types.d.ts +4 -0
- package/dist/prompts/default.d.ts +14 -9
- package/dist/prompts/default.js +13 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +53 -4
- package/dist/tools/fs/bash-readonly-classifier.js +148 -16
- package/dist/tools/fs/fs-bash.d.ts +7 -0
- package/dist/tools/fs/fs-bash.js +8 -3
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-pdf.js +2 -2
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-read.js +11 -7
- package/dist/tools/fs/fs-search-tools.d.ts +4 -2
- package/dist/tools/fs/fs-search-tools.js +15 -8
- package/dist/tools/fs/fs-shared.d.ts +5 -1
- package/dist/tools/fs/fs-shared.js +8 -3
- package/dist/tools/fs/index.d.ts +18 -0
- package/dist/tools/fs/index.js +13 -2
- package/dist/tools/fs/read-deny.d.ts +110 -0
- package/dist/tools/fs/read-deny.js +159 -0
- package/dist/tools/fs/read-face.d.ts +49 -0
- package/dist/tools/fs/read-face.js +38 -0
- package/dist/tools/fs/repo-map.d.ts +3 -1
- package/dist/tools/fs/repo-map.js +11 -5
- package/dist/tools/fs/safety.d.ts +34 -11
- package/dist/tools/fs/safety.js +108 -8
- package/dist/tools/fs/search.d.ts +54 -5
- package/dist/tools/fs/search.js +107 -23
- package/package.json +1 -1
package/dist/tools/fs/search.js
CHANGED
|
@@ -218,7 +218,7 @@ export async function buildIgnore(env, root, signal) {
|
|
|
218
218
|
export function walkIsPartial(w) {
|
|
219
219
|
return w.incomplete || w.skippedUnreadable > 0 || w.aborted;
|
|
220
220
|
}
|
|
221
|
-
export async function walk(env, root, start, ignore, signal) {
|
|
221
|
+
export async function walk(env, root, start, ignore, signal, deny) {
|
|
222
222
|
const out = [];
|
|
223
223
|
const nameOnly = [];
|
|
224
224
|
const mtimes = new Map();
|
|
@@ -226,6 +226,17 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
226
226
|
let skippedLarge = 0;
|
|
227
227
|
let skippedUnreadable = 0;
|
|
228
228
|
let aborted = false;
|
|
229
|
+
let denyPruned = 0;
|
|
230
|
+
const denyPatterns = [];
|
|
231
|
+
const denyHit = (path) => {
|
|
232
|
+
const hit = deny?.matchPath(path);
|
|
233
|
+
if (!hit)
|
|
234
|
+
return false;
|
|
235
|
+
denyPruned++;
|
|
236
|
+
if (!denyPatterns.includes(hit.pattern))
|
|
237
|
+
denyPatterns.push(hit.pattern);
|
|
238
|
+
return true;
|
|
239
|
+
};
|
|
229
240
|
const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
|
|
230
241
|
const rel = (abs) => (abs.startsWith(rootPrefix) ? abs.slice(rootPrefix.length) : abs);
|
|
231
242
|
const stack = [{ dir: start, depth: 0 }];
|
|
@@ -255,6 +266,8 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
255
266
|
if (info.kind === "directory") {
|
|
256
267
|
if (ignore(rel(info.path), true) || visited.has(info.path))
|
|
257
268
|
continue;
|
|
269
|
+
if (denyHit(info.path))
|
|
270
|
+
continue;
|
|
258
271
|
if (depth < WALK_MAX_DEPTH)
|
|
259
272
|
stack.push({ dir: info.path, depth: depth + 1 });
|
|
260
273
|
else
|
|
@@ -263,6 +276,8 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
263
276
|
else if (info.kind === "file") {
|
|
264
277
|
if (ignore(rel(info.path), false))
|
|
265
278
|
continue;
|
|
279
|
+
if (denyHit(info.path))
|
|
280
|
+
continue;
|
|
266
281
|
if (typeof info.mtimeMs === "number")
|
|
267
282
|
mtimes.set(info.path, info.mtimeMs);
|
|
268
283
|
if (hasBinaryExtension(info.path)) {
|
|
@@ -290,7 +305,12 @@ export async function walk(env, root, start, ignore, signal) {
|
|
|
290
305
|
}
|
|
291
306
|
if (stack.length > 0 && out.length >= WALK_MAX_FILES)
|
|
292
307
|
incomplete = true;
|
|
293
|
-
return { files: out, nameOnly, incomplete, skippedLarge, skippedUnreadable, aborted, mtimes };
|
|
308
|
+
return { files: out, nameOnly, incomplete, skippedLarge, skippedUnreadable, aborted, mtimes, denyPruned, denyPatterns };
|
|
309
|
+
}
|
|
310
|
+
export function denyWithheldNote(w) {
|
|
311
|
+
if (w.denyPruned <= 0)
|
|
312
|
+
return "";
|
|
313
|
+
return `\n[note: ${w.denyPruned} entr${w.denyPruned === 1 ? "y" : "ies"} matching the sensitive-path read deny list ${w.denyPruned === 1 ? "was" : "were"} withheld from this traversal (pruned entries' descendants not counted; patterns: ${w.denyPatterns.join(", ")})]`;
|
|
294
314
|
}
|
|
295
315
|
function walkCaveat(w, forContentSearch) {
|
|
296
316
|
const parts = [];
|
|
@@ -737,7 +757,7 @@ function multilineSpans(content, p, captureText = false, deadline = Infinity) {
|
|
|
737
757
|
}
|
|
738
758
|
return spans;
|
|
739
759
|
}
|
|
740
|
-
export async function jsGrep(env, root, p, signal, guards) {
|
|
760
|
+
export async function jsGrep(env, root, p, signal, guards, deny, denyOut) {
|
|
741
761
|
const analysis = analyzeRedos(p.pattern, p.ignore_case === true);
|
|
742
762
|
if (analysis.risk)
|
|
743
763
|
return redosRejection(analysis.risk);
|
|
@@ -773,16 +793,22 @@ export async function jsGrep(env, root, p, signal, guards) {
|
|
|
773
793
|
skippedUnreadable: 0,
|
|
774
794
|
aborted: false,
|
|
775
795
|
mtimes: typeof info.value.mtimeMs === "number" ? new Map([[start, info.value.mtimeMs]]) : new Map(),
|
|
796
|
+
denyPruned: 0,
|
|
797
|
+
denyPatterns: [],
|
|
776
798
|
};
|
|
777
799
|
}
|
|
778
800
|
else {
|
|
779
|
-
walked = await walk(env, root, start, ignore, signal);
|
|
801
|
+
walked = await walk(env, root, start, ignore, signal, deny);
|
|
780
802
|
}
|
|
781
803
|
}
|
|
782
804
|
else {
|
|
783
|
-
walked = await walk(env, root, start, ignore, signal);
|
|
805
|
+
walked = await walk(env, root, start, ignore, signal, deny);
|
|
806
|
+
}
|
|
807
|
+
if (denyOut !== undefined && walked.denyPruned > 0) {
|
|
808
|
+
denyOut.withheld = { kind: "pruned_count", count: walked.denyPruned, patterns: walked.denyPatterns };
|
|
784
809
|
}
|
|
785
|
-
let
|
|
810
|
+
let honestyCaveat = walkCaveat(walked, true);
|
|
811
|
+
const denyNote = denyWithheldNote(walked);
|
|
786
812
|
let files = walked.files;
|
|
787
813
|
files.sort();
|
|
788
814
|
const cap = p.head_limit === 0 ? Infinity : Math.max(1, Math.floor(p.head_limit ?? GREP_DEFAULT_CAP));
|
|
@@ -937,11 +963,12 @@ export async function jsGrep(env, root, p, signal, guards) {
|
|
|
937
963
|
if (multilineSkippedFiles > 0)
|
|
938
964
|
extra.push(`${multilineSkippedFiles} file(s) skipped by multiline matching (content over ${longLineLimit} chars; the pattern's worst-case matching cost grows superlinearly with input length on the fallback engine)`);
|
|
939
965
|
if (extra.length > 0) {
|
|
940
|
-
|
|
941
|
-
?
|
|
966
|
+
honestyCaveat = honestyCaveat.length > 0
|
|
967
|
+
? honestyCaveat.replace(/\]$/, `; ${extra.join("; ")}]`)
|
|
942
968
|
: `\n…[results may be incomplete: ${extra.join("; ")}]`;
|
|
943
969
|
}
|
|
944
970
|
}
|
|
971
|
+
const caveat = honestyCaveat + denyNote;
|
|
945
972
|
const paged = (arr) => (off > 0 ? arr.slice(off, off + cap) : arr);
|
|
946
973
|
const offNote = off > 0 ? `\n[offset ${off}]` : "";
|
|
947
974
|
if (mode === "files_with_matches") {
|
|
@@ -1016,16 +1043,18 @@ function formatRgStdout(stdout, p, caveat = "") {
|
|
|
1016
1043
|
(off > 0 ? `\n[offset ${off}]` : "") +
|
|
1017
1044
|
caveat);
|
|
1018
1045
|
}
|
|
1019
|
-
async function jsGrepFallback(env, root, p, signal, reason) {
|
|
1020
|
-
const
|
|
1046
|
+
async function jsGrepFallback(env, root, p, signal, reason, deny) {
|
|
1047
|
+
const denyOut = {};
|
|
1048
|
+
const text = await jsGrep(env, root, p, signal, undefined, deny, denyOut);
|
|
1021
1049
|
return {
|
|
1022
1050
|
text: text.startsWith("Error (grep)")
|
|
1023
1051
|
? text
|
|
1024
1052
|
: `${text}\n[note: ripgrep failed (${reason}); results are from a fallback scanner]`,
|
|
1025
1053
|
degraded: { fallback: "js-scan", reason: `ripgrep failed (${reason})` },
|
|
1054
|
+
...(denyOut.withheld !== undefined ? { withheld: denyOut.withheld } : {}),
|
|
1026
1055
|
};
|
|
1027
1056
|
}
|
|
1028
|
-
export async function rgGrepDetailed(env, root, p, signal) {
|
|
1057
|
+
export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
1029
1058
|
const mode = p.output_mode ?? "files_with_matches";
|
|
1030
1059
|
const flags = ["--no-messages", "--no-require-git", "--hidden"];
|
|
1031
1060
|
for (const d of VCS_DIRS)
|
|
@@ -1055,6 +1084,9 @@ export async function rgGrepDetailed(env, root, p, signal) {
|
|
|
1055
1084
|
flags.push("--glob", shellQuote(g));
|
|
1056
1085
|
if (p.type)
|
|
1057
1086
|
flags.push("--type", shellQuote(p.type));
|
|
1087
|
+
if (deny !== undefined)
|
|
1088
|
+
for (const g of deny.rgExclusionGlobs)
|
|
1089
|
+
flags.push(g.flag, shellQuote(g.glob));
|
|
1058
1090
|
const target = p.path ? shellQuote(p.path) : ".";
|
|
1059
1091
|
const cmd = `rg ${flags.join(" ")} -e ${shellQuote(p.pattern)} -- ${target}`;
|
|
1060
1092
|
const res = await env.exec(cmd, { cwd: root, timeout: 60, abortSignal: signal });
|
|
@@ -1070,13 +1102,26 @@ export async function rgGrepDetailed(env, root, p, signal) {
|
|
|
1070
1102
|
degraded: { partial: true, reason: "ripgrep timed out" },
|
|
1071
1103
|
};
|
|
1072
1104
|
}
|
|
1073
|
-
return jsGrepFallback(env, root, p, signal, "timed out with no output");
|
|
1105
|
+
return jsGrepFallback(env, root, p, signal, "timed out with no output", deny);
|
|
1074
1106
|
}
|
|
1075
|
-
return jsGrepFallback(env, root, p, signal, `could not run: ${err.code}
|
|
1107
|
+
return jsGrepFallback(env, root, p, signal, `could not run: ${err.code}`, deny);
|
|
1076
1108
|
}
|
|
1077
1109
|
const { exitCode, stdout } = res.value;
|
|
1078
|
-
|
|
1079
|
-
|
|
1110
|
+
const denyDisclosure = async () => {
|
|
1111
|
+
if (deny === undefined || deny.rgExclusionGlobs.length === 0)
|
|
1112
|
+
return { note: "" };
|
|
1113
|
+
const withheld = await rgDenyExistenceProbe(env, root, deny, target, signal);
|
|
1114
|
+
if (withheld === undefined)
|
|
1115
|
+
return { note: "" };
|
|
1116
|
+
const note = withheld.kind === "existence"
|
|
1117
|
+
? "\n[note: entries matching the sensitive-path read deny list exist under this scope and were excluded (readDenyPatterns)]"
|
|
1118
|
+
: "\n[note: entries matching the sensitive-path read deny list may have been excluded (deny-existence probe failed)]";
|
|
1119
|
+
return { note, withheld };
|
|
1120
|
+
};
|
|
1121
|
+
if (exitCode === 1) {
|
|
1122
|
+
const d = await denyDisclosure();
|
|
1123
|
+
return { text: NO_MATCHES + d.note, ...(d.withheld !== undefined ? { withheld: d.withheld } : {}) };
|
|
1124
|
+
}
|
|
1080
1125
|
if (exitCode >= 2) {
|
|
1081
1126
|
if (stdout.trim().length > 0) {
|
|
1082
1127
|
return {
|
|
@@ -1084,15 +1129,45 @@ export async function rgGrepDetailed(env, root, p, signal) {
|
|
|
1084
1129
|
degraded: { partial: true, reason: `ripgrep exited with code ${exitCode}` },
|
|
1085
1130
|
};
|
|
1086
1131
|
}
|
|
1087
|
-
return jsGrepFallback(env, root, p, signal, `exited with code ${exitCode} and produced no output
|
|
1132
|
+
return jsGrepFallback(env, root, p, signal, `exited with code ${exitCode} and produced no output`, deny);
|
|
1088
1133
|
}
|
|
1089
1134
|
const orderedStdout = mode === "files_with_matches" ? await sortRgFilesByMtime(env, root, stdout, signal) : stdout;
|
|
1090
|
-
|
|
1135
|
+
const d = await denyDisclosure();
|
|
1136
|
+
return { text: formatRgStdout(orderedStdout, p) + d.note, ...(d.withheld !== undefined ? { withheld: d.withheld } : {}) };
|
|
1091
1137
|
}
|
|
1092
|
-
|
|
1138
|
+
async function rgDenyExistenceProbe(env, root, deny, target, signal) {
|
|
1139
|
+
const probeFlags = ["--files", "--hidden", "--no-require-git", "--no-messages"];
|
|
1140
|
+
for (const d of VCS_DIRS)
|
|
1141
|
+
probeFlags.push("--glob", `!${d}`);
|
|
1142
|
+
for (const g of deny.rgProbeGlobs)
|
|
1143
|
+
probeFlags.push(g.flag, shellQuote(g.glob));
|
|
1144
|
+
try {
|
|
1145
|
+
const res = await env.exec(`( rg ${probeFlags.join(" ")} -- ${target}; echo "__RG_PROBE_EXIT_$?" 1>&2 ) | head -n 1`, { cwd: root, timeout: 10, abortSignal: signal });
|
|
1146
|
+
if (!res.ok)
|
|
1147
|
+
return { kind: "probe_failed" };
|
|
1148
|
+
const first = res.value.stdout.split("\n").find((l) => l.trim().length > 0);
|
|
1149
|
+
if (first !== undefined) {
|
|
1150
|
+
const hit = deny.matchPath(first.trim());
|
|
1151
|
+
return { kind: "existence", ...(hit !== null ? { patterns: [hit.pattern] } : {}) };
|
|
1152
|
+
}
|
|
1153
|
+
const marker = /__RG_PROBE_EXIT_(\d+)/.exec(res.value.stderr ?? "");
|
|
1154
|
+
const rgExit = marker === null ? undefined : Number(marker[1]);
|
|
1155
|
+
if (rgExit === 1)
|
|
1156
|
+
return undefined;
|
|
1157
|
+
if (rgExit === 0)
|
|
1158
|
+
return undefined;
|
|
1159
|
+
return { kind: "probe_failed" };
|
|
1160
|
+
}
|
|
1161
|
+
catch {
|
|
1162
|
+
return { kind: "probe_failed" };
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
export async function runGrepDetailed(env, root, p, signal, deny) {
|
|
1093
1166
|
if (await detectRipgrep(env))
|
|
1094
|
-
return rgGrepDetailed(env, root, p, signal);
|
|
1095
|
-
|
|
1167
|
+
return rgGrepDetailed(env, root, p, signal, deny);
|
|
1168
|
+
const denyOut = {};
|
|
1169
|
+
const text = await jsGrep(env, root, p, signal, undefined, deny, denyOut);
|
|
1170
|
+
return { text, ...(denyOut.withheld !== undefined ? { withheld: denyOut.withheld } : {}) };
|
|
1096
1171
|
}
|
|
1097
1172
|
export async function runGrep(env, root, p, signal) {
|
|
1098
1173
|
return (await runGrepDetailed(env, root, p, signal)).text;
|
|
@@ -1180,8 +1255,8 @@ export async function runGlobDetailed(env, root, pattern, opts = {}, signal) {
|
|
|
1180
1255
|
}
|
|
1181
1256
|
return ig;
|
|
1182
1257
|
};
|
|
1183
|
-
const walked = await walk(env, root, start, ignore, signal);
|
|
1184
|
-
const caveat = walkCaveat(walked, false);
|
|
1258
|
+
const walked = await walk(env, root, start, ignore, signal, opts.deny);
|
|
1259
|
+
const caveat = walkCaveat(walked, false) + denyWithheldNote(walked);
|
|
1185
1260
|
const files = [...walked.files, ...walked.nameOnly];
|
|
1186
1261
|
const rel = (abs) => (abs.startsWith(rootPrefix) ? abs.slice(rootPrefix.length) : abs);
|
|
1187
1262
|
const cap = Math.max(1, Math.floor(opts.max ?? 500));
|
|
@@ -1212,5 +1287,14 @@ export async function runGlobDetailed(env, root, pattern, opts = {}, signal) {
|
|
|
1212
1287
|
? `\n[note: the file-walk budget (${WALK_MAX_FILES} files / ${WALK_MAX_DEPTH} directory levels) ran out before the whole tree was scanned — this empty result is NOT proof the file is absent; scope the search with \`path\`, or use a root-anchored pattern (e.g. "src/**/*.ts") so the walk is pruned toward it, then retry]`
|
|
1213
1288
|
: "";
|
|
1214
1289
|
const text = matched.length === 0 ? "No files matched." + ignoreNote + ignoredFileNote + budgetNote + caveat : matched.join("\n") + capNote + caveat;
|
|
1215
|
-
return {
|
|
1290
|
+
return {
|
|
1291
|
+
text,
|
|
1292
|
+
filenames: matched,
|
|
1293
|
+
numFiles: matched.length,
|
|
1294
|
+
truncated,
|
|
1295
|
+
durationMs: Date.now() - t0,
|
|
1296
|
+
totalMatches,
|
|
1297
|
+
countIsComplete,
|
|
1298
|
+
...(walked.denyPruned > 0 ? { withheld: { kind: "pruned_count", count: walked.denyPruned, patterns: walked.denyPatterns } } : {}),
|
|
1299
|
+
};
|
|
1216
1300
|
}
|