@sema-agent/core 5.39.0 → 5.40.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 +99 -11
- package/dist/core/checkpoint-store.d.ts +12 -0
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.js +9 -1
- package/dist/core/memory-engine/engine.d.ts +27 -0
- package/dist/core/memory-engine/engine.js +103 -1
- package/dist/core/memory-engine/export-bundle.d.ts +192 -0
- package/dist/core/memory-engine/export-bundle.js +306 -0
- package/dist/core/memory-engine/file-backend.d.ts +178 -1
- package/dist/core/memory-engine/file-backend.js +637 -6
- package/dist/core/memory-engine/index.d.ts +2 -1
- package/dist/core/memory-engine/index.js +1 -0
- package/dist/core/memory-engine/layout.d.ts +89 -1
- package/dist/core/memory-engine/layout.js +131 -1
- package/dist/core/memory-engine/memory-backend-contract.d.ts +1 -1
- package/dist/core/memory-engine/memory-backend-contract.js +52 -0
- package/dist/core/memory-engine/tools.js +8 -1
- package/dist/core/permission-rule-consent.js +14 -2
- package/dist/core/runner/prepare-task.js +13 -0
- package/dist/core/runner/runtask.js +5 -0
- package/dist/core/runner/synthetic-tools.js +3 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/write-protect.d.ts +0 -20
- package/dist/core/write-protect.js +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/safety.js +1 -1
- package/dist/tools/fs/search.d.ts +33 -0
- package/dist/tools/fs/search.js +72 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -1
|
@@ -344,7 +344,7 @@ export declare function withinAnyRoot(rootsCanonical: readonly string[], p: stri
|
|
|
344
344
|
* just let the model discover it by trial), but it is a statement, not a recommendation, and it
|
|
345
345
|
* comes last. Every shell call still passes the deployment's approval policy.
|
|
346
346
|
*/
|
|
347
|
-
export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
|
|
347
|
+
export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
|
|
348
348
|
/** inv 1 (read-before-edit): a file must have been read this task before it can be edited/overwritten.
|
|
349
349
|
* Message is CC 2.1.198 live-verbatim (all-tools-live-probe 2026-07-08 §2.1/§3.1/§5.1 — one message for
|
|
350
350
|
* Edit/Write/NotebookEdit: "before writing to it", not the old sema "before editing").
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -493,7 +493,7 @@ export function violationDetails(v) {
|
|
|
493
493
|
export function withinAnyRoot(rootsCanonical, p) {
|
|
494
494
|
return rootsCanonical.some((r) => within(r, p));
|
|
495
495
|
}
|
|
496
|
-
export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
|
|
496
|
+
export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
|
|
497
497
|
export function requireRead(state, key) {
|
|
498
498
|
const entry = state.get(key);
|
|
499
499
|
if (entry === undefined || entry.isPartialView) {
|
|
@@ -208,6 +208,39 @@ export interface GrepRunResult {
|
|
|
208
208
|
* withheld / no deny judge in play. */
|
|
209
209
|
withheld?: ReadDenyWithheld;
|
|
210
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* backlog #303 (rescan-hardened form) — the rg leg's deny TRIPWIRE. rg's exclusion globs are spelled
|
|
213
|
+
* from the pattern text and cannot express the win32 component-alias family (`.aws.` / `.aws ` —
|
|
214
|
+
* trailing dots/spaces the win32 namespace strips), so a directory planted under an alias spelling
|
|
215
|
+
* slips past the globs while the shared `matchPath` (two-view, read-deny.ts) correctly names it —
|
|
216
|
+
* the JS leg pruned it, the rg leg returned its contents, and the same tree answered differently
|
|
217
|
+
* depending on whether ripgrep was installed.
|
|
218
|
+
*
|
|
219
|
+
* The first fix FILTERED rg's text output line-by-line. The rescan killed that shape twice over:
|
|
220
|
+
* ripgrep prints filenames verbatim, so a path component containing a NEWLINE splits one record
|
|
221
|
+
* across physical lines whose fragments carry no `:line:` boundary — the filter kept them and the
|
|
222
|
+
* deny-listed content passed (a protection hole, not a precision residual); and pruning every line
|
|
223
|
+
* of a partial run fabricated a `No matches.` row inside the structured card. Text-splitting rg
|
|
224
|
+
* records is unsalvageable without structured output (`--json`, backlog #306), so this function no
|
|
225
|
+
* longer edits anything: it JUDGES. Any deny hit — or any line the format cannot account for —
|
|
226
|
+
* trips, and the caller abandons the rg run for the JS scanner, whose walk prunes with the
|
|
227
|
+
* authoritative judge and needs no path parsing at all. rg stays the fast path for the common case
|
|
228
|
+
* (no guarded entries in the result); the moment a guarded spelling is involved, the engine that
|
|
229
|
+
* cannot mis-parse it owns the answer.
|
|
230
|
+
*
|
|
231
|
+
* Trip conditions by mode — `files_with_matches`: every line IS a path, judged whole; `count`:
|
|
232
|
+
* `path:N` (a line without the numeric tail is a split record — ambiguous, trip); `content`:
|
|
233
|
+
* judged at EVERY `:digits:` boundary and every `-digits-` boundary (a candidate cut inside match
|
|
234
|
+
* text can over-trip — safe: the fallback re-derives the exact answer), and a non-separator line
|
|
235
|
+
* with NO boundary at all is a split record — trip. `--` separators and empty lines pass.
|
|
236
|
+
*/
|
|
237
|
+
export declare function rgOutputDenyTripwire(stdout: string, mode: "content" | "files_with_matches" | "count", judge: Pick<ReadDenyJudge, "matchPath">): {
|
|
238
|
+
trip: false;
|
|
239
|
+
} | {
|
|
240
|
+
trip: true;
|
|
241
|
+
reason: "deny-hit" | "ambiguous-record";
|
|
242
|
+
pattern?: string;
|
|
243
|
+
};
|
|
211
244
|
/** ripgrep grep: build flags from params, run, normalize to the same output as {@link jsGrep}.
|
|
212
245
|
* Result-fidelity contract: output ripgrep DID produce is never silently replaced by a fallback
|
|
213
246
|
* rescan — partial results ship with a caveat; only a zero-output failure degrades to
|
package/dist/tools/fs/search.js
CHANGED
|
@@ -1043,6 +1043,63 @@ function formatRgStdout(stdout, p, caveat = "") {
|
|
|
1043
1043
|
(off > 0 ? `\n[offset ${off}]` : "") +
|
|
1044
1044
|
caveat);
|
|
1045
1045
|
}
|
|
1046
|
+
export function rgOutputDenyTripwire(stdout, mode, judge) {
|
|
1047
|
+
if (stdout.length === 0)
|
|
1048
|
+
return { trip: false };
|
|
1049
|
+
const judgeBoundaries = (line, boundary) => {
|
|
1050
|
+
let sawBoundary = false;
|
|
1051
|
+
for (let m = boundary.exec(line); m !== null; m = boundary.exec(line)) {
|
|
1052
|
+
sawBoundary = true;
|
|
1053
|
+
const prefix = line.slice(0, m.index);
|
|
1054
|
+
if (prefix.length === 0)
|
|
1055
|
+
continue;
|
|
1056
|
+
const hit = judge.matchPath(prefix);
|
|
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" };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return { trip: false };
|
|
1090
|
+
}
|
|
1091
|
+
async function jsGrepDenyTripFallback(env, root, p, signal, trip, deny) {
|
|
1092
|
+
const denyOut = {};
|
|
1093
|
+
const text = await jsGrep(env, root, p, signal, undefined, deny, denyOut);
|
|
1094
|
+
const why = trip.reason === "deny-hit" ? "its output involved sensitive-path deny-listed entries the exclusion globs cannot express" : "its output contained a record the line format cannot account for";
|
|
1095
|
+
return {
|
|
1096
|
+
text: text.startsWith("Error (grep)")
|
|
1097
|
+
? text
|
|
1098
|
+
: `${text}\n[note: the ripgrep pass was abandoned (${why}); results are from the fallback scanner, which prunes with the authoritative deny judge]`,
|
|
1099
|
+
degraded: { fallback: "js-scan", reason: `ripgrep output tripped the deny tripwire (${trip.reason})` },
|
|
1100
|
+
...(denyOut.withheld !== undefined ? { withheld: denyOut.withheld } : {}),
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1046
1103
|
async function jsGrepFallback(env, root, p, signal, reason, deny) {
|
|
1047
1104
|
const denyOut = {};
|
|
1048
1105
|
const text = await jsGrep(env, root, p, signal, undefined, deny, denyOut);
|
|
@@ -1097,6 +1154,11 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1097
1154
|
if (err.code === "timeout") {
|
|
1098
1155
|
const partial = err.partialStdout ?? "";
|
|
1099
1156
|
if (partial.trim().length > 0) {
|
|
1157
|
+
if (deny !== undefined) {
|
|
1158
|
+
const trip = rgOutputDenyTripwire(partial, mode, deny);
|
|
1159
|
+
if (trip.trip)
|
|
1160
|
+
return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
|
|
1161
|
+
}
|
|
1100
1162
|
return {
|
|
1101
1163
|
text: `${delimitUntrusted("ripgrep partial output", formatRgStdout(partial, p))}\n…[ripgrep timed out after producing partial output — results may be incomplete]`,
|
|
1102
1164
|
degraded: { partial: true, reason: "ripgrep timed out" },
|
|
@@ -1124,6 +1186,11 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1124
1186
|
}
|
|
1125
1187
|
if (exitCode >= 2) {
|
|
1126
1188
|
if (stdout.trim().length > 0) {
|
|
1189
|
+
if (deny !== undefined) {
|
|
1190
|
+
const trip = rgOutputDenyTripwire(stdout, mode, deny);
|
|
1191
|
+
if (trip.trip)
|
|
1192
|
+
return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
|
|
1193
|
+
}
|
|
1127
1194
|
return {
|
|
1128
1195
|
text: `${delimitUntrusted("ripgrep partial output", formatRgStdout(stdout, p))}\n…[ripgrep exited with an error after producing partial output — results may be incomplete]`,
|
|
1129
1196
|
degraded: { partial: true, reason: `ripgrep exited with code ${exitCode}` },
|
|
@@ -1131,6 +1198,11 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
|
|
|
1131
1198
|
}
|
|
1132
1199
|
return jsGrepFallback(env, root, p, signal, `exited with code ${exitCode} and produced no output`, deny);
|
|
1133
1200
|
}
|
|
1201
|
+
if (deny !== undefined) {
|
|
1202
|
+
const trip = rgOutputDenyTripwire(stdout, mode, deny);
|
|
1203
|
+
if (trip.trip)
|
|
1204
|
+
return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
|
|
1205
|
+
}
|
|
1134
1206
|
const orderedStdout = mode === "files_with_matches" ? await sortRgFilesByMtime(env, root, stdout, signal) : stdout;
|
|
1135
1207
|
const d = await denyDisclosure();
|
|
1136
1208
|
return { text: formatRgStdout(orderedStdout, p) + d.note, ...(d.withheld !== undefined ? { withheld: d.withheld } : {}) };
|
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": 1605,
|
|
4
4
|
"exports": {
|
|
5
5
|
"A2ATaskState": "type",
|
|
6
6
|
"A2ATaskStateReversal": "type",
|
|
@@ -119,6 +119,9 @@
|
|
|
119
119
|
"BudgetAxis": "type",
|
|
120
120
|
"BuildAutoModePromptOptions": "interface",
|
|
121
121
|
"BuiltinWorkflowDefinition": "interface",
|
|
122
|
+
"BundleChallengeRow": "interface",
|
|
123
|
+
"BundleLineageRow": "interface",
|
|
124
|
+
"BundlePollutedSession": "interface",
|
|
122
125
|
"CC_MODEL_TIER_ALIASES": "variable",
|
|
123
126
|
"CHALLENGED_HISTORY_FILE": "variable",
|
|
124
127
|
"CHALLENGES_FILE": "variable",
|
|
@@ -467,14 +470,18 @@
|
|
|
467
470
|
"MemoryAnnouncement": "interface",
|
|
468
471
|
"MemoryBackend": "interface",
|
|
469
472
|
"MemoryBackendContractHooks": "interface",
|
|
473
|
+
"MemoryBundleImportPlan": "interface",
|
|
470
474
|
"MemoryEngine": "class",
|
|
471
475
|
"MemoryEngineOptions": "interface",
|
|
472
476
|
"MemoryEntry": "interface",
|
|
473
477
|
"MemoryEntryFrontmatter": "interface",
|
|
474
478
|
"MemoryEntryHeader": "interface",
|
|
475
479
|
"MemoryErasureAttestation": "interface",
|
|
480
|
+
"MemoryExportBundle": "interface",
|
|
481
|
+
"MemoryExportSnapshot": "interface",
|
|
476
482
|
"MemoryGateError": "class",
|
|
477
483
|
"MemoryGetDetails": "interface",
|
|
484
|
+
"MemoryImportReport": "interface",
|
|
478
485
|
"MemoryInjection": "interface",
|
|
479
486
|
"MemoryListDetails": "type",
|
|
480
487
|
"MemoryNoteHeader": "interface",
|
|
@@ -1180,6 +1187,7 @@
|
|
|
1180
1187
|
"computeCatalogDigest": "function",
|
|
1181
1188
|
"computeCostMicroUsd": "function",
|
|
1182
1189
|
"computeEntryRev": "function",
|
|
1190
|
+
"computeMemoryBundleHash": "function",
|
|
1183
1191
|
"computeShapeDigest": "function",
|
|
1184
1192
|
"confirmRuleApproval": "function",
|
|
1185
1193
|
"consolidateScope": "function",
|