@sema-agent/core 5.40.0 → 5.42.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 +101 -3
- package/dist/core/auto-mode-prompt.js +1 -1
- package/dist/core/checkpoint-store.d.ts +15 -0
- package/dist/core/checkpoint-store.js +5 -1
- package/dist/core/memory-engine/file-backend.js +13 -2
- package/dist/core/memory-engine/layout.d.ts +15 -0
- package/dist/core/memory-engine/layout.js +17 -8
- package/dist/core/park-selfcheck.js +3 -1
- package/dist/core/runner/prepare-config-doors.js +20 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +192 -0
- package/dist/core/runner/prepare-hands-readface.js +283 -0
- package/dist/core/runner/prepare-task.d.ts +1 -5
- package/dist/core/runner/prepare-task.js +16 -229
- package/dist/core/runner/runtask.js +6 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.js +20 -0
- package/dist/engine/harness/agent-harness.d.ts +17 -0
- package/dist/engine/harness/agent-harness.js +19 -1
- package/dist/engine/harness/types.d.ts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +8 -0
- package/dist/tools/fs/fs-search-tools.js +67 -58
- package/dist/tools/fs/search.d.ts +31 -9
- package/dist/tools/fs/search.js +110 -73
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +2 -1
package/dist/tools/fs/search.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { delimitUntrusted } from "../../core/untrusted-text.js";
|
|
2
|
+
import { MAX_EXEC_OUTPUT_BYTES } from "../../core/exec-output-tail.js";
|
|
2
3
|
import { hasBinaryExtension, isAbsolutePathForm } from "./safety.js";
|
|
3
4
|
const WALK_MAX_FILES = 5000;
|
|
4
5
|
const WALK_MAX_DEPTH = 32;
|
|
5
6
|
const GREP_DEFAULT_CAP = 250;
|
|
6
7
|
const FILE_MAX_BYTES = 5 * 1024 * 1024;
|
|
8
|
+
export const JS_GREP_OUTPUT_MAX_BYTES = MAX_EXEC_OUTPUT_BYTES;
|
|
7
9
|
export const DEFAULT_IGNORE_DIRS = new Set([
|
|
8
10
|
"node_modules", ".git", ".hg", ".svn", ".bzr", ".jj", ".sl", "build", "dist", "out", "target",
|
|
9
11
|
".dart_tool", ".pub-cache", ".next", ".nuxt", ".gradle", ".idea", ".vscode", "Pods", ".venv", "venv",
|
|
@@ -840,6 +842,27 @@ export async function jsGrep(env, root, p, signal, guards, deny, denyOut) {
|
|
|
840
842
|
const off = Math.min(100_000, Math.max(0, Math.floor(p.offset ?? 0)));
|
|
841
843
|
const collectCap = cap + off;
|
|
842
844
|
const out = [];
|
|
845
|
+
const outputMaxBytes = guards?.outputMaxBytes ?? JS_GREP_OUTPUT_MAX_BYTES;
|
|
846
|
+
let outBytes = 0;
|
|
847
|
+
let outputTruncated = false;
|
|
848
|
+
let rowsBeforeWindow = 0;
|
|
849
|
+
const pushRow = (row) => {
|
|
850
|
+
if (outputTruncated)
|
|
851
|
+
return;
|
|
852
|
+
if (rowsBeforeWindow < off) {
|
|
853
|
+
rowsBeforeWindow++;
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (out.length >= cap)
|
|
857
|
+
return;
|
|
858
|
+
const size = Buffer.byteLength(row, "utf8") + 1;
|
|
859
|
+
if (outBytes + size > outputMaxBytes) {
|
|
860
|
+
outputTruncated = true;
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
outBytes += size;
|
|
864
|
+
out.push(row);
|
|
865
|
+
};
|
|
843
866
|
const fileMatches = [];
|
|
844
867
|
const counts = [];
|
|
845
868
|
let totalContent = 0;
|
|
@@ -888,14 +911,12 @@ export async function jsGrep(env, root, p, signal, guards, deny, denyOut) {
|
|
|
888
911
|
if (p.only_matching && text !== undefined) {
|
|
889
912
|
const parts = text.split("\n");
|
|
890
913
|
for (let k = 0; k < parts.length; k++) {
|
|
891
|
-
|
|
892
|
-
out.push(`${relOut(f)}:${s + k}:${clipLine(parts[k])}`);
|
|
914
|
+
pushRow(`${relOut(f)}:${s + k}:${clipLine(parts[k])}`);
|
|
893
915
|
}
|
|
894
916
|
continue;
|
|
895
917
|
}
|
|
896
918
|
for (let j = Math.max(0, s - 1 - ctxB); j <= Math.min(lines.length - 1, eL - 1 + ctxA); j++) {
|
|
897
|
-
|
|
898
|
-
out.push(`${relOut(f)}:${j + 1}:${clipLine(lines[j])}`);
|
|
919
|
+
pushRow(`${relOut(f)}:${j + 1}:${clipLine(lines[j])}`);
|
|
899
920
|
}
|
|
900
921
|
}
|
|
901
922
|
}
|
|
@@ -924,18 +945,16 @@ export async function jsGrep(env, root, p, signal, guards, deny, denyOut) {
|
|
|
924
945
|
if (mode === "content") {
|
|
925
946
|
if (p.only_matching) {
|
|
926
947
|
for (const part of matchedParts(lines[i], p)) {
|
|
927
|
-
|
|
928
|
-
out.push(`${relOut(f)}:${i + 1}:${clipLine(part)}`);
|
|
948
|
+
pushRow(`${relOut(f)}:${i + 1}:${clipLine(part)}`);
|
|
929
949
|
}
|
|
930
950
|
}
|
|
931
951
|
else if (ctx > 0) {
|
|
932
952
|
for (let j = Math.max(0, i - ctxB); j <= Math.min(lines.length - 1, i + ctxA); j++) {
|
|
933
|
-
|
|
934
|
-
out.push(`${relOut(f)}:${j + 1}:${clipLine(lines[j])}`);
|
|
953
|
+
pushRow(`${relOut(f)}:${j + 1}:${clipLine(lines[j])}`);
|
|
935
954
|
}
|
|
936
955
|
}
|
|
937
|
-
else
|
|
938
|
-
|
|
956
|
+
else {
|
|
957
|
+
pushRow(`${relOut(f)}:${i + 1}:${clipLine(lines[i])}`);
|
|
939
958
|
}
|
|
940
959
|
}
|
|
941
960
|
}
|
|
@@ -990,15 +1009,16 @@ export async function jsGrep(env, root, p, signal, guards, deny, denyOut) {
|
|
|
990
1009
|
? NO_MATCHES + offNote + typeNote + caveat
|
|
991
1010
|
: body.join("\n") + (totalFiles > off + cap ? `\n…[capped at ${cap} of ${totalFiles}]` : "") + offNote + typeNote + caveat;
|
|
992
1011
|
}
|
|
993
|
-
const body =
|
|
1012
|
+
const body = out;
|
|
1013
|
+
const byteNote = outputTruncated ? `\n…[output truncated at ${outputMaxBytes} bytes — narrow the pattern or set a smaller head_limit]` : "";
|
|
994
1014
|
if (body.length === 0)
|
|
995
|
-
return NO_MATCHES + offNote + typeNote + caveat;
|
|
996
|
-
if (out.length <
|
|
997
|
-
return body.join("\n") + offNote + typeNote + caveat;
|
|
1015
|
+
return NO_MATCHES + byteNote + offNote + typeNote + caveat;
|
|
1016
|
+
if (out.length < cap)
|
|
1017
|
+
return body.join("\n") + byteNote + offNote + typeNote + caveat;
|
|
998
1018
|
const marker = ctx > 0 || p.multiline || p.only_matching
|
|
999
1019
|
? `\n…[capped at ${cap}; ${totalContent}+ match(es) found, more output omitted]`
|
|
1000
1020
|
: `\n…[capped at ${cap} of ${totalContent}]`;
|
|
1001
|
-
return body.join("\n") + marker + offNote + typeNote + caveat;
|
|
1021
|
+
return body.join("\n") + marker + byteNote + offNote + typeNote + caveat;
|
|
1002
1022
|
}
|
|
1003
1023
|
const rgCache = new WeakMap();
|
|
1004
1024
|
export function detectRipgrep(env) {
|
|
@@ -1012,79 +1032,89 @@ export function detectRipgrep(env) {
|
|
|
1012
1032
|
}
|
|
1013
1033
|
return cached;
|
|
1014
1034
|
}
|
|
1015
|
-
async function sortRgFilesByMtime(env, root,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
return stdout;
|
|
1035
|
+
async function sortRgFilesByMtime(env, root, records, signal) {
|
|
1036
|
+
if (records.length === 0)
|
|
1037
|
+
return [...records];
|
|
1019
1038
|
const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
|
|
1020
1039
|
const toAbs = (p) => {
|
|
1021
1040
|
const stripped = p.startsWith("./") ? p.slice(2) : p;
|
|
1022
1041
|
return isAbsolutePathForm(stripped) ? stripped : `${rootPrefix}${stripped}`;
|
|
1023
1042
|
};
|
|
1043
|
+
const paths = records.map((r) => r.path ?? r.text);
|
|
1024
1044
|
const infos = await Promise.all(paths.map((p) => env.fileInfo(toAbs(p), signal)));
|
|
1025
|
-
const withMtime =
|
|
1026
|
-
const
|
|
1027
|
-
return { p, m:
|
|
1045
|
+
const withMtime = records.map((r, i) => {
|
|
1046
|
+
const info = infos[i];
|
|
1047
|
+
return { r, p: paths[i], m: info.ok && typeof info.value.mtimeMs === "number" ? info.value.mtimeMs : undefined };
|
|
1028
1048
|
});
|
|
1029
1049
|
const sorted = withMtime.every((x) => x.m !== undefined)
|
|
1030
1050
|
? [...withMtime].sort((a, b) => b.m - a.m || (a.p < b.p ? -1 : a.p > b.p ? 1 : 0))
|
|
1031
1051
|
: [...withMtime].sort((a, b) => (a.p < b.p ? -1 : a.p > b.p ? 1 : 0));
|
|
1032
|
-
return sorted.map((x) => x.
|
|
1052
|
+
return sorted.map((x) => x.r);
|
|
1053
|
+
}
|
|
1054
|
+
function rgRecordSeparator(mode, rest) {
|
|
1055
|
+
if (mode === "count")
|
|
1056
|
+
return /^\d+$/.test(rest) ? ":" : null;
|
|
1057
|
+
const m = /^\d+([:\-])/.exec(rest);
|
|
1058
|
+
return m === null ? null : m[1];
|
|
1059
|
+
}
|
|
1060
|
+
function parseRgRecords(stdout, mode, dropIncompleteTail = false) {
|
|
1061
|
+
if (stdout.length === 0)
|
|
1062
|
+
return [];
|
|
1063
|
+
if (mode === "files_with_matches") {
|
|
1064
|
+
if (!stdout.includes("\0")) {
|
|
1065
|
+
if (dropIncompleteTail)
|
|
1066
|
+
return [];
|
|
1067
|
+
return stdout.split("\n").filter((l) => l.length > 0).map((l) => ({ path: l, text: l }));
|
|
1068
|
+
}
|
|
1069
|
+
const parts = stdout.split("\0");
|
|
1070
|
+
const tail = parts.pop() ?? "";
|
|
1071
|
+
const records = parts.filter((p) => p.length > 0).map((p) => ({ path: p, text: p }));
|
|
1072
|
+
if (tail.length > 0 && !dropIncompleteTail)
|
|
1073
|
+
records.push({ text: tail });
|
|
1074
|
+
return records;
|
|
1075
|
+
}
|
|
1076
|
+
const lines = stdout.split("\n");
|
|
1077
|
+
if (!stdout.endsWith("\n") && dropIncompleteTail)
|
|
1078
|
+
lines.pop();
|
|
1079
|
+
const records = [];
|
|
1080
|
+
for (const line of lines) {
|
|
1081
|
+
if (line.length === 0)
|
|
1082
|
+
continue;
|
|
1083
|
+
const nul = line.indexOf("\0");
|
|
1084
|
+
const rest = nul < 0 ? "" : line.slice(nul + 1);
|
|
1085
|
+
const sep = nul < 0 ? null : rgRecordSeparator(mode, rest);
|
|
1086
|
+
if (nul < 0 || sep === null) {
|
|
1087
|
+
records.push({ text: line });
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
const path = line.slice(0, nul);
|
|
1091
|
+
records.push({ path, text: `${path}${sep}${rest}` });
|
|
1092
|
+
}
|
|
1093
|
+
return records;
|
|
1033
1094
|
}
|
|
1034
|
-
function
|
|
1095
|
+
function formatRgRecords(records, p, caveat = "") {
|
|
1035
1096
|
const cap = p.head_limit === 0 ? Infinity : Math.max(1, Math.floor(p.head_limit ?? GREP_DEFAULT_CAP));
|
|
1036
1097
|
const off = Math.max(0, Math.floor(p.offset ?? 0));
|
|
1037
|
-
const
|
|
1038
|
-
const capped = lines.slice(off, off + cap);
|
|
1098
|
+
const capped = records.slice(off, off + cap);
|
|
1039
1099
|
if (capped.length === 0)
|
|
1040
1100
|
return NO_MATCHES + (off > 0 ? `\n[offset ${off}]` : "") + caveat;
|
|
1041
|
-
return (capped.join("\n") +
|
|
1042
|
-
(
|
|
1101
|
+
return (capped.map((r) => r.text).join("\n") +
|
|
1102
|
+
(records.length > off + cap ? `\n…[capped at ${cap} of ${records.length}]` : "") +
|
|
1043
1103
|
(off > 0 ? `\n[offset ${off}]` : "") +
|
|
1044
1104
|
caveat);
|
|
1045
1105
|
}
|
|
1046
|
-
export function rgOutputDenyTripwire(stdout, mode, judge) {
|
|
1106
|
+
export function rgOutputDenyTripwire(stdout, mode, judge, opts = {}) {
|
|
1047
1107
|
if (stdout.length === 0)
|
|
1048
1108
|
return { trip: false };
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
sawBoundary = true;
|
|
1053
|
-
const prefix = line.slice(0, m.index);
|
|
1054
|
-
if (prefix.length === 0)
|
|
1109
|
+
for (const record of parseRgRecords(stdout, mode, opts.dropIncompleteTail === true)) {
|
|
1110
|
+
if (record.path === undefined) {
|
|
1111
|
+
if (record.text === "--")
|
|
1055
1112
|
continue;
|
|
1056
|
-
|
|
1057
|
-
if (hit !== null)
|
|
1058
|
-
return hit.pattern;
|
|
1059
|
-
}
|
|
1060
|
-
return sawBoundary ? null : "";
|
|
1061
|
-
};
|
|
1062
|
-
for (const line of stdout.split("\n")) {
|
|
1063
|
-
if (line.length === 0 || line === "--")
|
|
1064
|
-
continue;
|
|
1065
|
-
if (mode === "files_with_matches") {
|
|
1066
|
-
const h = judge.matchPath(line);
|
|
1067
|
-
if (h !== null)
|
|
1068
|
-
return { trip: true, reason: "deny-hit", pattern: h.pattern };
|
|
1069
|
-
}
|
|
1070
|
-
else if (mode === "count") {
|
|
1071
|
-
const m = /^(.*):\d+$/.exec(line);
|
|
1072
|
-
if (m === null)
|
|
1073
|
-
return { trip: true, reason: "ambiguous-record" };
|
|
1074
|
-
const h = judge.matchPath(m[1]);
|
|
1075
|
-
if (h !== null)
|
|
1076
|
-
return { trip: true, reason: "deny-hit", pattern: h.pattern };
|
|
1077
|
-
}
|
|
1078
|
-
else {
|
|
1079
|
-
const colon = judgeBoundaries(line, /:\d+:/g);
|
|
1080
|
-
if (colon !== null && colon !== "")
|
|
1081
|
-
return { trip: true, reason: "deny-hit", pattern: colon };
|
|
1082
|
-
const dash = judgeBoundaries(line, /-\d+-/g);
|
|
1083
|
-
if (dash !== null && dash !== "")
|
|
1084
|
-
return { trip: true, reason: "deny-hit", pattern: dash };
|
|
1085
|
-
if (colon === "" && dash === "")
|
|
1086
|
-
return { trip: true, reason: "ambiguous-record" };
|
|
1113
|
+
return { trip: true, reason: "ambiguous-record" };
|
|
1087
1114
|
}
|
|
1115
|
+
const hit = judge.matchPath(record.path);
|
|
1116
|
+
if (hit !== null)
|
|
1117
|
+
return { trip: true, reason: "deny-hit", pattern: hit.pattern };
|
|
1088
1118
|
}
|
|
1089
1119
|
return { trip: false };
|
|
1090
1120
|
}
|
|
@@ -1113,7 +1143,7 @@ async function jsGrepFallback(env, root, p, signal, reason, deny) {
|
|
|
1113
1143
|
}
|
|
1114
1144
|
export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
1115
1145
|
const mode = p.output_mode ?? "files_with_matches";
|
|
1116
|
-
const flags = ["--no-messages", "--no-require-git", "--hidden"];
|
|
1146
|
+
const flags = ["--null", "--no-messages", "--no-require-git", "--hidden"];
|
|
1117
1147
|
for (const d of VCS_DIRS)
|
|
1118
1148
|
flags.push("--glob", `!${d}`);
|
|
1119
1149
|
flags.push("--max-columns", "500", "--max-columns-preview");
|
|
@@ -1155,12 +1185,15 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1155
1185
|
const partial = err.partialStdout ?? "";
|
|
1156
1186
|
if (partial.trim().length > 0) {
|
|
1157
1187
|
if (deny !== undefined) {
|
|
1158
|
-
const trip = rgOutputDenyTripwire(partial, mode, deny);
|
|
1188
|
+
const trip = rgOutputDenyTripwire(partial, mode, deny, { dropIncompleteTail: true });
|
|
1159
1189
|
if (trip.trip)
|
|
1160
1190
|
return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
|
|
1161
1191
|
}
|
|
1192
|
+
const records = parseRgRecords(partial, mode, true);
|
|
1193
|
+
if (records.length === 0)
|
|
1194
|
+
return jsGrepFallback(env, root, p, signal, "timed out before completing a result", deny);
|
|
1162
1195
|
return {
|
|
1163
|
-
text: `${delimitUntrusted("ripgrep partial output",
|
|
1196
|
+
text: `${delimitUntrusted("ripgrep partial output", formatRgRecords(records, p))}\n…[ripgrep timed out after producing partial output — results may be incomplete]`,
|
|
1164
1197
|
degraded: { partial: true, reason: "ripgrep timed out" },
|
|
1165
1198
|
};
|
|
1166
1199
|
}
|
|
@@ -1187,12 +1220,15 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1187
1220
|
if (exitCode >= 2) {
|
|
1188
1221
|
if (stdout.trim().length > 0) {
|
|
1189
1222
|
if (deny !== undefined) {
|
|
1190
|
-
const trip = rgOutputDenyTripwire(stdout, mode, deny);
|
|
1223
|
+
const trip = rgOutputDenyTripwire(stdout, mode, deny, { dropIncompleteTail: true });
|
|
1191
1224
|
if (trip.trip)
|
|
1192
1225
|
return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
|
|
1193
1226
|
}
|
|
1227
|
+
const records = parseRgRecords(stdout, mode, true);
|
|
1228
|
+
if (records.length === 0)
|
|
1229
|
+
return jsGrepFallback(env, root, p, signal, `exited with code ${exitCode} and produced no complete result`, deny);
|
|
1194
1230
|
return {
|
|
1195
|
-
text: `${delimitUntrusted("ripgrep partial output",
|
|
1231
|
+
text: `${delimitUntrusted("ripgrep partial output", formatRgRecords(records, p))}\n…[ripgrep exited with an error after producing partial output — results may be incomplete]`,
|
|
1196
1232
|
degraded: { partial: true, reason: `ripgrep exited with code ${exitCode}` },
|
|
1197
1233
|
};
|
|
1198
1234
|
}
|
|
@@ -1203,9 +1239,10 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1203
1239
|
if (trip.trip)
|
|
1204
1240
|
return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
|
|
1205
1241
|
}
|
|
1206
|
-
const
|
|
1242
|
+
const parsed = parseRgRecords(stdout, mode);
|
|
1243
|
+
const ordered = mode === "files_with_matches" ? await sortRgFilesByMtime(env, root, parsed, signal) : parsed;
|
|
1207
1244
|
const d = await denyDisclosure();
|
|
1208
|
-
return { text:
|
|
1245
|
+
return { text: formatRgRecords(ordered, p) + d.note, ...(d.withheld !== undefined ? { withheld: d.withheld } : {}) };
|
|
1209
1246
|
}
|
|
1210
1247
|
async function rgDenyExistenceProbe(env, root, deny, target, signal) {
|
|
1211
1248
|
const probeFlags = ["--files", "--hidden", "--no-require-git", "--no-messages"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
|
|
3
|
-
"count":
|
|
3
|
+
"count": 1606,
|
|
4
4
|
"exports": {
|
|
5
5
|
"A2ATaskState": "type",
|
|
6
6
|
"A2ATaskStateReversal": "type",
|
|
@@ -1390,6 +1390,7 @@
|
|
|
1390
1390
|
"mergeRecallHits": "function",
|
|
1391
1391
|
"mergeWorkflowArgs": "function",
|
|
1392
1392
|
"migrateScope": "function",
|
|
1393
|
+
"mintCheckpointId": "function",
|
|
1393
1394
|
"mintCheckpointToken": "function",
|
|
1394
1395
|
"mintRuleTicket": "function",
|
|
1395
1396
|
"missingRestoreSurface": "function",
|