@sema-agent/core 7.17.4 → 7.17.5
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 +29 -0
- package/dist/core/runner/prepare-hands-readface.js +10 -8
- package/dist/core/tool-catalog-entries.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +18 -5
- package/dist/tools/fs/bash-readonly-classifier.js +180 -61
- package/dist/tools/fs/fs-bash.d.ts +28 -0
- package/dist/tools/fs/fs-bash.js +52 -10
- package/dist/tools/fs/index.d.ts +13 -0
- package/dist/tools/fs/index.js +1 -0
- package/package.json +3 -2
- package/wire-changes/core@7.10.0.json +739 -0
- package/wire-changes/core@7.11.0.json +685 -0
- package/wire-changes/core@7.11.1.json +112 -0
- package/wire-changes/core@7.11.2.json +73 -0
- package/wire-changes/core@7.12.0.json +356 -0
- package/wire-changes/core@7.13.0.json +199 -0
- package/wire-changes/core@7.14.0.json +578 -0
- package/wire-changes/core@7.15.0.json +174 -0
- package/wire-changes/core@7.16.0.json +132 -0
- package/wire-changes/core@7.17.0.json +1621 -0
- package/wire-changes/core@7.17.1.json +139 -0
- package/wire-changes/core@7.17.2.json +65 -0
- package/wire-changes/core@7.17.3.json +44 -0
- package/wire-changes/core@7.17.4.json +52 -0
- package/wire-changes/core@7.17.5.json +67 -0
- package/wire-changes/core@7.5.0.json +1063 -0
- package/wire-changes/core@7.5.1.json +252 -0
- package/wire-changes/core@7.5.2.json +418 -0
- package/wire-changes/core@7.6.0.json +1243 -0
- package/wire-changes/core@7.6.1.json +1130 -0
- package/wire-changes/core@7.7.0.json +509 -0
- package/wire-changes/core@7.8.0.json +1293 -0
- package/wire-changes/core@7.9.0.json +691 -0
- package/wire-changes/core@7.9.1.json +518 -0
- package/wire-changes/core@7.9.2.json +46 -0
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -615,6 +615,25 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
615
615
|
const bgRetained = hasBackgroundShell(env) && env.backgroundCapabilities.retainBackgroundProcesses === true;
|
|
616
616
|
const bgSessionScoped = taskOpts.sessionId !== undefined && taskOpts.taskRegistry !== undefined;
|
|
617
617
|
const bgEnvIsolatedOwned = hasDestroy(env) && isIsolated(env);
|
|
618
|
+
const recheckReaders = new Set(BASH_CLASSIFY_DEFAULT_ALLOW);
|
|
619
|
+
let canonicalForRoots;
|
|
620
|
+
const canonicalBoundaryFor = (roots) => {
|
|
621
|
+
const key = roots.join("\u0000");
|
|
622
|
+
if (canonicalForRoots?.key !== key)
|
|
623
|
+
canonicalForRoots = { key, boundary: createCanonicalReadBoundary(env, roots) };
|
|
624
|
+
return canonicalForRoots.boundary;
|
|
625
|
+
};
|
|
626
|
+
const canonicalEscape = async (command, signal) => {
|
|
627
|
+
const resolvedBoundary = typeof taskOpts.readBoundary === "function" ? taskOpts.readBoundary() : taskOpts.readBoundary;
|
|
628
|
+
if (resolvedBoundary === undefined || resolvedBoundary.face === "open")
|
|
629
|
+
return undefined;
|
|
630
|
+
const faces = [
|
|
631
|
+
classifyCompoundReadonlyDetailed(command, recheckReaders, resolvedBoundary),
|
|
632
|
+
classifyBoundedReadonlyPollLoopDetailed(command, recheckReaders, resolvedBoundary),
|
|
633
|
+
];
|
|
634
|
+
const literal = [...new Set(faces.flatMap((v) => v.checkedPaths ?? []))];
|
|
635
|
+
return await canonicalEscapeRefusal(canonicalBoundaryFor(resolvedBoundary.roots), { literal, patterns: [] }, resolvedBoundary.roots, FULL_SHELL_FACE_ESCAPE_PROSE, signal);
|
|
636
|
+
};
|
|
618
637
|
return defineTool({
|
|
619
638
|
name: "Bash",
|
|
620
639
|
...toolFace("bash"),
|
|
@@ -650,6 +669,9 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
650
669
|
const timeoutRefusal = bashTimeoutArgRefusal(timeout, timeoutCaps);
|
|
651
670
|
if (timeoutRefusal !== undefined)
|
|
652
671
|
return errorResult(timeoutRefusal, { type: "bash_invalid_timeout", code: "bash_invalid_timeout" });
|
|
672
|
+
const escape = await canonicalEscape(command, ctx.signal);
|
|
673
|
+
if (escape !== undefined)
|
|
674
|
+
return escape;
|
|
653
675
|
if (run_in_background) {
|
|
654
676
|
if (!hasBackgroundShell(env)) {
|
|
655
677
|
return errorResult("Error (Bash): this environment does not support background processes (run_in_background).");
|
|
@@ -659,6 +681,8 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
659
681
|
if (diagnosis !== undefined)
|
|
660
682
|
throw new Error(diagnosis);
|
|
661
683
|
}
|
|
684
|
+
if (ctx.signal?.aborted === true)
|
|
685
|
+
return errorResult("Error (Bash): the call was cancelled before the background command started.");
|
|
662
686
|
const requestedTimeoutSec = timeout !== undefined ? Math.max(1, Math.round(timeout / 1000)) : undefined;
|
|
663
687
|
const r = await env.spawnBackground(command, {
|
|
664
688
|
cwd: cwdRef.current,
|
|
@@ -923,6 +947,8 @@ function createCanonicalReadBoundary(env, readRoots) {
|
|
|
923
947
|
while (parts.length > 0) {
|
|
924
948
|
const canon = await env.canonicalPath(parts.join("/") || "/", signal);
|
|
925
949
|
if (canon.ok) {
|
|
950
|
+
if (tail.length === 0)
|
|
951
|
+
return canon.value;
|
|
926
952
|
return [canon.value.replace(/[\\/]+$/, ""), ...[...tail].reverse()].join("/");
|
|
927
953
|
}
|
|
928
954
|
tail.push(parts.pop());
|
|
@@ -1045,6 +1071,29 @@ function createCanonicalReadBoundary(env, readRoots) {
|
|
|
1045
1071
|
},
|
|
1046
1072
|
};
|
|
1047
1073
|
}
|
|
1074
|
+
async function canonicalEscapeRefusal(canonical, candidates, readRoots, face, signal) {
|
|
1075
|
+
const resolved = await canonical.escapesAfterResolution(candidates, signal);
|
|
1076
|
+
if (resolved.unverifiable !== undefined) {
|
|
1077
|
+
return errorResult(`Error (Bash): ${resolved.unverifiable}, so this command cannot be confirmed to read only inside the allowed directories for this session: ${readRoots.join(", ")}. ` +
|
|
1078
|
+
face.note, { type: "readonly_out_of_root", code: "readonly_out_of_root", paths: [] });
|
|
1079
|
+
}
|
|
1080
|
+
if (resolved.escaping.length > 0) {
|
|
1081
|
+
const quoted = resolved.escaping.map((p) => `"${p}"`).join(", ");
|
|
1082
|
+
return errorResult(`Error (Bash): ${face.subject} resolves through a symlink to ${quoted}, outside the allowed directories for this session: ${readRoots.join(", ")}${face.verdictPhrase}. ` +
|
|
1083
|
+
face.note, { type: "readonly_out_of_root", code: "readonly_out_of_root", paths: resolved.escaping });
|
|
1084
|
+
}
|
|
1085
|
+
return undefined;
|
|
1086
|
+
}
|
|
1087
|
+
const READONLY_FACE_ESCAPE_PROSE = {
|
|
1088
|
+
subject: "a path this command reads",
|
|
1089
|
+
verdictPhrase: ` ${NOT_AUTO_ALLOWED}`,
|
|
1090
|
+
note: BASH_READONLY_CONFINEMENT_NOTE,
|
|
1091
|
+
};
|
|
1092
|
+
const FULL_SHELL_FACE_ESCAPE_PROSE = {
|
|
1093
|
+
subject: "a path this command names",
|
|
1094
|
+
verdictPhrase: "",
|
|
1095
|
+
note: "The read boundary judged this command's written spelling before the call was approved, and execution is past the approval step — a target reached only through symlink resolution is refused here rather than escalated.",
|
|
1096
|
+
};
|
|
1048
1097
|
export function createBashReadonlyTool(env, rootCanonical, allow, opts) {
|
|
1049
1098
|
const timeoutCaps = resolveBashTimeoutCaps(opts);
|
|
1050
1099
|
const timeoutCapsSecView = bashTimeoutCapsSec(timeoutCaps);
|
|
@@ -1092,16 +1141,9 @@ export function createBashReadonlyTool(env, rootCanonical, allow, opts) {
|
|
|
1092
1141
|
return errorResult(`Error (Bash): ${boundary.reason}. ${BASH_READONLY_CONFINEMENT_NOTE}`, { type: "readonly_out_of_root", code: "readonly_out_of_root", paths: boundary.outOfRootPaths ?? [] });
|
|
1093
1142
|
}
|
|
1094
1143
|
}
|
|
1095
|
-
const
|
|
1096
|
-
if (
|
|
1097
|
-
return
|
|
1098
|
-
BASH_READONLY_CONFINEMENT_NOTE, { type: "readonly_out_of_root", code: "readonly_out_of_root", paths: [] });
|
|
1099
|
-
}
|
|
1100
|
-
if (resolved.escaping.length > 0) {
|
|
1101
|
-
const quoted = resolved.escaping.map((p) => `"${p}"`).join(", ");
|
|
1102
|
-
return errorResult(`Error (Bash): a path this command reads resolves through a symlink to ${quoted}, outside the allowed directories for this session: ${readRoots.join(", ")} ${NOT_AUTO_ALLOWED}. ` +
|
|
1103
|
-
BASH_READONLY_CONFINEMENT_NOTE, { type: "readonly_out_of_root", code: "readonly_out_of_root", paths: resolved.escaping });
|
|
1104
|
-
}
|
|
1144
|
+
const escape = await canonicalEscapeRefusal(canonicalBoundary, { literal: boundary.checkedPaths ?? [], patterns: boundary.undecidedPaths ?? [] }, readRoots, READONLY_FACE_ESCAPE_PROSE, ctx.signal);
|
|
1145
|
+
if (escape !== undefined)
|
|
1146
|
+
return escape;
|
|
1105
1147
|
return await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, undefined, undefined, true);
|
|
1106
1148
|
},
|
|
1107
1149
|
});
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./fs-search-tools.js";
|
|
|
11
11
|
export * from "./bash-readonly-classifier.js";
|
|
12
12
|
export * from "./fs-bash.js";
|
|
13
13
|
export * from "./read-deny.js";
|
|
14
|
+
import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
|
|
14
15
|
export { BASH_CLASSIFY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
|
|
15
16
|
import { type ReadDenyEntry } from "./read-deny.js";
|
|
16
17
|
export * from "./read-face.js";
|
|
@@ -40,6 +41,18 @@ export interface HandsToolkitOptions {
|
|
|
40
41
|
readOnly?: boolean;
|
|
41
42
|
/** Override the `bash_readonly` command allowlist (default {@link BASH_READONLY_DEFAULT_ALLOW}). */
|
|
42
43
|
bashReadonlyAllow?: readonly string[];
|
|
44
|
+
/** The deployment's shell READ BOUNDARY, for the FULL `bash` face's execute-time canonical recheck
|
|
45
|
+
* (see `createBashTool`'s `readBoundary`). The caller passes the SAME seat it gives the gate's shell
|
|
46
|
+
* probes, so the boundary that judges a command's written spelling before approval and the leg that
|
|
47
|
+
* re-judges its symlink-resolved targets before execution are one declaration. Ignored in `readOnly`
|
|
48
|
+
* mode — `bash_readonly` builds its own from the roots it is confined to.
|
|
49
|
+
*
|
|
50
|
+
* ABSENT ⇒ the leg does not run and costs no I/O: with no boundary declared there is no "outside" for
|
|
51
|
+
* a read to be outside of, which is the same reading the probe seats take of an absent boundary. It
|
|
52
|
+
* deliberately does NOT fall back to `additionalRoots`/`rootCanonical`: those are the WRITE side's
|
|
53
|
+
* fence, they exclude `additionalReadRoots`, and standing a second root table up here would refuse
|
|
54
|
+
* reads this deployment sanctions while claiming to enforce the boundary it was never given. */
|
|
55
|
+
shellReadBoundary?: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined);
|
|
43
56
|
/** Commit-message attribution trailer for the `bash` git protocol. Default NONE — attribution is a
|
|
44
57
|
* deployment identity asset, so the branded deployment sets it; pass `"Name <email>"` to advertise
|
|
45
58
|
* one, `false` (≡ unset) for no trailer section. */
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -74,6 +74,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
74
74
|
...(opts.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: opts.autoBackgroundOnTimeout } : {}),
|
|
75
75
|
...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
|
|
76
76
|
...(additionalRoots !== undefined ? { additionalRoots } : {}),
|
|
77
|
+
...(opts.shellReadBoundary !== undefined ? { readBoundary: opts.shellReadBoundary } : {}),
|
|
77
78
|
...(opts.bashDefaultTimeoutMs !== undefined ? { bashDefaultTimeoutMs: opts.bashDefaultTimeoutMs } : {}),
|
|
78
79
|
...(opts.bashMaxTimeoutMs !== undefined ? { bashMaxTimeoutMs: opts.bashMaxTimeoutMs } : {}),
|
|
79
80
|
...(opts.onNotice !== undefined ? { onNotice: opts.onNotice } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "7.17.
|
|
3
|
+
"version": "7.17.5",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"README.md",
|
|
41
41
|
"NOTICE.md",
|
|
42
42
|
"CHANGELOG.md",
|
|
43
|
-
"LICENSE"
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"wire-changes/"
|
|
44
45
|
],
|
|
45
46
|
"engines": {
|
|
46
47
|
"node": ">=20.3.0"
|