@sema-agent/core 7.17.7 → 7.18.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 +140 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/auto-mode.d.ts +141 -45
- package/dist/core/auto-mode.js +109 -3
- package/dist/core/checkpoint-store.d.ts +16 -2
- package/dist/core/engine-notice.d.ts +47 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +17 -2
- package/dist/core/gate-outcome.js +16 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +8 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +89 -0
- package/dist/core/runner/classifier-ladder.js +65 -0
- package/dist/core/runner/contracts.d.ts +69 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
- package/dist/core/runner/prepare-config-doors.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.js +5 -3
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.js +4 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +18 -13
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +35 -41
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +3 -1
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +16 -19
- package/dist/core/runner/synthetic-tools.js +18 -10
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +20 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +8 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +10 -5
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
- package/dist/tools/fs/bash-readonly-classifier.js +121 -552
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +158 -22
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +3 -3
- package/dist/tools/fs/safety.js +4 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -9
- package/wire-changes/core@7.17.7.json +49 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** A containment-face state. `"roots"` = the containment judgment runs. `"open"` = the containment
|
|
2
|
+
* step is skipped (canonicalization, the deny/sensitive sets, the UNC out-of-set refusal and the
|
|
3
|
+
* type gates all still run). One type for both seats: the two faces differ in WHICH band consults
|
|
4
|
+
* them, never in what the word means. */
|
|
5
|
+
export type ContainmentFace = "open" | "roots";
|
|
6
|
+
/** Which band a face governs. Selects the seat spellings and the error codes below — never the
|
|
7
|
+
* resolution ORDER, which is one body shared by both. */
|
|
8
|
+
export type ContainmentFaceKind = "read" | "write";
|
|
9
|
+
/** Which layer a seat belongs to. The SPELLING is derived from (kind, layer), never re-typed at a
|
|
10
|
+
* call site: three doors screen the same two seats, and the value gate's message is the only place
|
|
11
|
+
* a deployment reads the seat's name back. */
|
|
12
|
+
export type ContainmentFaceLayer = "spec" | "deps";
|
|
13
|
+
/** The seat spelling for a (kind, layer) pair — single-sourced from {@link FACE_DIALECT}. */
|
|
14
|
+
export declare function containmentFaceSeatName(kind: ContainmentFaceKind, layer: ContainmentFaceLayer): string;
|
|
15
|
+
/** Loud value gate: a containment-face seat carries exactly "open" | "roots" — anything else (case
|
|
16
|
+
* variants, truthy garbage) refuses at wiring time, never folds to a default. Exported
|
|
17
|
+
* module-internally (not on the package surface) so prepare's unconditional config-guard leg can
|
|
18
|
+
* screen the seats on runs that never mount hands. The resolver itself also runs on every hands-less
|
|
19
|
+
* leg; this screen still fires first, so a garbage value refuses before any leg-shape branching. */
|
|
20
|
+
export declare function assertContainmentFaceValue(v: unknown, kind: ContainmentFaceKind, layer: ContainmentFaceLayer): ContainmentFace | undefined;
|
|
21
|
+
/** Inputs to {@link resolveContainmentFace} — all structural/declaration facts, never permission modes
|
|
22
|
+
* (a deliberate axis separation: this shape carries facts, not verdicts). */
|
|
23
|
+
export interface ContainmentFaceInputs {
|
|
24
|
+
/** Which face is being resolved. Selects the seat spellings and the mint codes; the ORDER below is
|
|
25
|
+
* one body, identical for both. */
|
|
26
|
+
kind: ContainmentFaceKind;
|
|
27
|
+
/** TaskSpec.readFace / TaskSpec.writeFace (raw — validated here). The TASK layer: may only tighten
|
|
28
|
+
* under governance. */
|
|
29
|
+
specFace?: unknown;
|
|
30
|
+
/** RunnerDeps / HandsToolkitOptions seat of the same face (raw) — the DEPLOYMENT's own declaration. */
|
|
31
|
+
depsFace?: unknown;
|
|
32
|
+
/** The verifier read-only mount (`handsReadOnly` / toolkit `readOnly`): for the read face its
|
|
33
|
+
* containment is a LOAD-BEARING wall (bash_readonly is genuinely confined by it); for the write
|
|
34
|
+
* face it is the absence of the band entirely. Neither is openable. */
|
|
35
|
+
readOnlyMount: boolean;
|
|
36
|
+
/** Org governance declared (`RunnerDeps.permissionRuleStore.partitions.org === true`): default roots; the task layer
|
|
37
|
+
* may not open (tighten-only under governance; the deps layer, being the deployment's own
|
|
38
|
+
* declaration, still may). */
|
|
39
|
+
orgGoverned: boolean;
|
|
40
|
+
/** Is the FULL shell (`core.bash@1`) structurally reachable on this mount? Computed BEFORE
|
|
41
|
+
* tool-band assembly from spec-time facts (env kind / handsReadOnly / Bash exclusion — every
|
|
42
|
+
* exclusion source is spec-time-frozen, so this is always computable). When false, the roots fence
|
|
43
|
+
* is the ONLY boundary this face has, so no IMPLICIT default may open it; an EXPLICIT "open" on
|
|
44
|
+
* either seat still wins (row 4 of the order is a default, not a clamp). */
|
|
45
|
+
fullShellReachable: boolean;
|
|
46
|
+
/** Observation hook for the ONE silent branch of the order: row 1's deployment-seat clamp
|
|
47
|
+
* (readOnly mount + deps "open" + NO task seat). Fired exactly when the deps declaration was the
|
|
48
|
+
* deciding seat and the mount overrode it — not when a task-level "roots" pinned the same value
|
|
49
|
+
* anyway (the mount changed nothing there, and announcing it would claim a false cause), and
|
|
50
|
+
* never on the throw rows (those are already loud). The resolver stays the single owner of the
|
|
51
|
+
* clamp predicate; stations own delivery/dedup ({@link deploymentFaceClampNotice}). */
|
|
52
|
+
onDeploymentClamp?: () => void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The ONE resolution order, first hit wins — ONE body for BOTH faces. prepare-task AND
|
|
56
|
+
* createHandsToolkit both call it, once per face, so a library-direct mount gets identical validation
|
|
57
|
+
* and identical defaults on each seat.
|
|
58
|
+
*
|
|
59
|
+
* 1. read-only (verifier) mount → ROOTS; an explicit "open" on the TASK seat is a genuine
|
|
60
|
+
* per-call CONTRADICTION and refuses loudly (never silently pick a side). An "open" on the
|
|
61
|
+
* DEPLOYMENT seat alone is a deployment-wide default, not a per-task assertion — it silently
|
|
62
|
+
* CLAMPS to roots (stricter-wins), the same seat distinction row 2 draws for org governance.
|
|
63
|
+
* 2. org-governed: the task seat's "open" refuses (task layer only tightens); deps "open" wins;
|
|
64
|
+
* otherwise ROOTS.
|
|
65
|
+
* 3. explicit seat: spec ?? deps.
|
|
66
|
+
* 4. bash-less mount: ROOTS (the fence is the only boundary there — "the fence guards nothing bash
|
|
67
|
+
* reaches anyway" does not hold, so no implicit default may open it).
|
|
68
|
+
* 5. default: ROOTS — the engine never opens implicitly.
|
|
69
|
+
*
|
|
70
|
+
* @contract fs.containment_face.seat_independence — the two containment seats never read each other:
|
|
71
|
+
* each band passes its OWN resolved value into the shared containment step, a seat nobody declared
|
|
72
|
+
* resolves to "roots" on its own, and no gate other than that step is touched by either word. A
|
|
73
|
+
* deployment that wants both bands open declares both.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveContainmentFace(i: ContainmentFaceInputs): ContainmentFace;
|
|
76
|
+
/**
|
|
77
|
+
* The ONE text of a face's deployment-clamp announcement (`config.<face>_face_deployment_clamped`).
|
|
78
|
+
* Both stations deliver THIS object through their own `deliverEngineNotice` call (prepare-task with
|
|
79
|
+
* the `RunnerDeps.onNotice` seat, once per notice sink — the sink, not the deps object, because the
|
|
80
|
+
* Runner rebuilds its deps; `createHandsToolkit` with its band-local `HandsToolkitOptions.onNotice`
|
|
81
|
+
* seat — toolkit creation is itself the one-per-mount boot moment, and an absent seat prints the
|
|
82
|
+
* same line via `console.warn`). Built here, import-free, so the message can never fork between
|
|
83
|
+
* the stations OR between the two faces.
|
|
84
|
+
*/
|
|
85
|
+
export declare function deploymentFaceClampNotice(kind: ContainmentFaceKind): {
|
|
86
|
+
code: string;
|
|
87
|
+
message: string;
|
|
88
|
+
detail: Record<string, unknown>;
|
|
89
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describeRejectedValue } from "../../core/tool-search-setting.js";
|
|
2
|
+
const FACE_DIALECT = {
|
|
3
|
+
read: {
|
|
4
|
+
key: "readFace",
|
|
5
|
+
specSeat: "TaskSpec.readFace",
|
|
6
|
+
depsSeat: "readFace (deployment seat)",
|
|
7
|
+
depsOwnKey: "RunnerDeps.readFace",
|
|
8
|
+
noun: "open read face",
|
|
9
|
+
invalidCode: "config.read_face_invalid",
|
|
10
|
+
readOnlyConflictCode: "config.read_face_readonly_conflict",
|
|
11
|
+
orgEscalationCode: "config.read_face_org_task_escalation",
|
|
12
|
+
readOnlyWhy: `that mount's containment is load-bearing (its read-only shell face is confined by it) and is never openable.`,
|
|
13
|
+
clampWhy: `the mount's containment is load-bearing and never openable`,
|
|
14
|
+
},
|
|
15
|
+
write: {
|
|
16
|
+
key: "writeFace",
|
|
17
|
+
specSeat: "TaskSpec.writeFace",
|
|
18
|
+
depsSeat: "writeFace (deployment seat)",
|
|
19
|
+
depsOwnKey: "RunnerDeps.writeFace",
|
|
20
|
+
noun: "open write face",
|
|
21
|
+
invalidCode: "config.write_face_invalid",
|
|
22
|
+
readOnlyConflictCode: "config.write_face_readonly_conflict",
|
|
23
|
+
orgEscalationCode: "config.write_face_org_task_escalation",
|
|
24
|
+
readOnlyWhy: `that mount carries no write faces at all (the hand band's write half does not mount on it), so an open write face declares a posture for tools this call does not have.`,
|
|
25
|
+
clampWhy: `that mount carries no write faces for it to widen`,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export function containmentFaceSeatName(kind, layer) {
|
|
29
|
+
const d = FACE_DIALECT[kind];
|
|
30
|
+
return layer === "spec" ? d.specSeat : d.depsSeat;
|
|
31
|
+
}
|
|
32
|
+
export function assertContainmentFaceValue(v, kind, layer) {
|
|
33
|
+
if (v === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (v === "open" || v === "roots")
|
|
36
|
+
return v;
|
|
37
|
+
const d = FACE_DIALECT[kind];
|
|
38
|
+
const e = new Error(`${containmentFaceSeatName(kind, layer)}: invalid ${d.key} value ${describeRejectedValue(v)} — expected "open" or "roots" (bad values refuse loudly; nothing falls back to a default).`);
|
|
39
|
+
e.code = d.invalidCode;
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
export function resolveContainmentFace(i) {
|
|
43
|
+
const d = FACE_DIALECT[i.kind];
|
|
44
|
+
const spec = assertContainmentFaceValue(i.specFace, i.kind, "spec");
|
|
45
|
+
const deps = assertContainmentFaceValue(i.depsFace, i.kind, "deps");
|
|
46
|
+
if (i.readOnlyMount) {
|
|
47
|
+
if (spec === "open") {
|
|
48
|
+
const e = new Error(`${d.key}: "open" contradicts the read-only (verifier) mount — ${d.readOnlyWhy} Drop the ${d.key} declaration or the readOnly mount.`);
|
|
49
|
+
e.code = d.readOnlyConflictCode;
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
if (deps === "open" && spec === undefined)
|
|
53
|
+
i.onDeploymentClamp?.();
|
|
54
|
+
return "roots";
|
|
55
|
+
}
|
|
56
|
+
if (i.orgGoverned) {
|
|
57
|
+
if (spec === "open") {
|
|
58
|
+
const e = new Error(`${d.specSeat}: "open" is refused under organization governance — the task layer may only tighten (set ${d.depsOwnKey}: "open" if the DEPLOYMENT declares the ${d.noun}).`);
|
|
59
|
+
e.code = d.orgEscalationCode;
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
if (spec === "roots")
|
|
63
|
+
return "roots";
|
|
64
|
+
return deps ?? "roots";
|
|
65
|
+
}
|
|
66
|
+
if (spec !== undefined)
|
|
67
|
+
return spec;
|
|
68
|
+
if (deps !== undefined)
|
|
69
|
+
return deps;
|
|
70
|
+
if (!i.fullShellReachable)
|
|
71
|
+
return "roots";
|
|
72
|
+
return "roots";
|
|
73
|
+
}
|
|
74
|
+
export function deploymentFaceClampNotice(kind) {
|
|
75
|
+
const d = FACE_DIALECT[kind];
|
|
76
|
+
const message = `${d.depsSeat}: the deployment-wide "open" declaration is not in force on this read-only (verifier) mount — clamped to "roots" (stricter-wins: ${d.clampWhy}). Other mounts still honor "open".`;
|
|
77
|
+
const detail = { seat: d.depsSeat, declared: "open", inForce: "roots", cause: "read_only_mount" };
|
|
78
|
+
return kind === "read"
|
|
79
|
+
? { code: "config.read_face_deployment_clamped", message, detail }
|
|
80
|
+
: { code: "config.write_face_deployment_clamped", message, detail };
|
|
81
|
+
}
|
|
@@ -115,7 +115,10 @@ export declare function bashReversibilityProbe(allow?: readonly string[], bounda
|
|
|
115
115
|
* on the filesystem, and a target that leaves the roots only after a symlink is followed is REFUSED —
|
|
116
116
|
* refused rather than asked, because by then the approval step is behind the call (see `createBashTool`'s
|
|
117
117
|
* `readBoundary` option). The leg runs where an environment can resolve a link locally; a remote adapter
|
|
118
|
-
* keeps the textual answer alone.
|
|
118
|
+
* keeps the textual answer alone. A program FILE named in the slot of a verb this floor has no row for
|
|
119
|
+
* (`awk -f prog.awk`, `jq -f filter.jq`, `python3 script.py`) is judged as the in-root operand it is, and the
|
|
120
|
+
* program inside it is text nobody here parsed — vouched as any in-root operand is, which is the floor's
|
|
121
|
+
* stated reach, not a hole in the sandbox.
|
|
119
122
|
*/
|
|
120
123
|
export declare function bashReadBoundaryProbe(boundary: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => ReversibilityVerdict;
|
|
121
124
|
/**
|
|
@@ -131,6 +134,16 @@ export { FULL_SHELL_CONTRACT_ID } from "../../core/tool-catalog-entries.js";
|
|
|
131
134
|
* (CC `cLp`). Exported for its unit tests. */
|
|
132
135
|
export declare function bashExitOneInterpretation(command: string): string | undefined;
|
|
133
136
|
export declare function canAutoBackground(command: string, depth?: number): boolean;
|
|
137
|
+
/** One observation of a running command, handed to {@link runShell}'s caller once per
|
|
138
|
+
* {@link TOOL_PROGRESS_BOUND}.intervalMs. NO clock: how long the call has been running is the RUN's fact,
|
|
139
|
+
* not this leg's — the two producers of a progress frame do not start their clocks at the same instant, so
|
|
140
|
+
* the lane that mints the frame owns the origin. The output trio rides or stays away TOGETHER, and only
|
|
141
|
+
* when the environment has actually streamed something — see the frame's own absence law. */
|
|
142
|
+
export interface RunningShellTick {
|
|
143
|
+
output?: string;
|
|
144
|
+
totalLines?: number;
|
|
145
|
+
totalBytes?: number;
|
|
146
|
+
}
|
|
134
147
|
/**
|
|
135
148
|
* `bash` (effect:write) — a full shell. ⚠️ It runs with `rootCanonical` as the initial cwd but is NOT
|
|
136
149
|
* sandboxed: a command can `cd` out, read/write/delete any path the process can reach, and use the
|
|
@@ -169,8 +182,35 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
|
|
|
169
182
|
* `canAutoBackground`-eligible command it never expected to survive its timeout started surviving
|
|
170
183
|
* it). A deployment that wants the CC-parity behavior sets this explicitly; sema's own default
|
|
171
184
|
* Runner wiring currently does NOT set it, so this feature ships INERT until a caller opts in.
|
|
185
|
+
*
|
|
186
|
+
* IT ARMS TWO TRIGGERS, not one — the name is older than the second. Both are the engine deciding
|
|
187
|
+
* to adopt a running command instead of ending it, both take the same adoption chain, and both are
|
|
188
|
+
* gated on the same eligibility read of this command's text (`canAutoBackground`): (1) the command's
|
|
189
|
+
* OWN timeout fires, and (2) a person's input is waiting for the turn boundary while this command
|
|
190
|
+
* still runs (see {@link queuedHumanInput}). A deployment that opted in for one has opted in for
|
|
191
|
+
* both; there is deliberately no second knob, because "may this command be moved to the background
|
|
192
|
+
* by the engine" is one question and answering it twice is how the two triggers would drift apart.
|
|
172
193
|
*/
|
|
173
194
|
autoBackgroundOnTimeout?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* The run's tool-progress sink: one call per liveness tick of a running foreground command (about
|
|
197
|
+
* once a second), minted into a `tool_progress` frame by whoever wired this. Runner-wired; a
|
|
198
|
+
* standalone toolkit build leaves it absent, and the execute leg then opens no streaming callbacks
|
|
199
|
+
* at all — its `env.exec` options are byte-identical to what they were before this seat existed.
|
|
200
|
+
*/
|
|
201
|
+
onToolProgress?: (body: import("../../core/runner/contracts.js").ToolProgressBody) => void;
|
|
202
|
+
/**
|
|
203
|
+
* Is a PERSON's input waiting for this run's next turn boundary? Read on each liveness tick of a
|
|
204
|
+
* running foreground command: `true` on an {@link autoBackgroundOnTimeout}-armed command moves that
|
|
205
|
+
* command into the background (through the SAME detach verb ctrl+b fires) so the message is
|
|
206
|
+
* delivered this turn instead of after the command. CC 2.1.259 `backgroundedToDeliverMessage`.
|
|
207
|
+
*
|
|
208
|
+
* The predicate is the HARNESS's own accepted-but-unserved account and deliberately counts only what
|
|
209
|
+
* a PERSON sent: an engine-authored reminder is not somebody waiting, and stepping a build aside for
|
|
210
|
+
* one would fire on nearly every long command. Absent ⇒ nothing is ever auto-backgrounded for a
|
|
211
|
+
* message (the pre-seat behavior), which is also what a standalone toolkit build gets.
|
|
212
|
+
*/
|
|
213
|
+
queuedHumanInput?: () => boolean;
|
|
174
214
|
/**
|
|
175
215
|
* Mirrors {@link import("../../core/types.js").TaskSpec.oneShot}: this run has no later
|
|
176
216
|
* turn for an async background notification to land in (a headless `-p` process exits once the
|
|
@@ -253,6 +293,11 @@ export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical:
|
|
|
253
293
|
* canonical). A read this deployment already sanctions for Read/Grep/Glob is equally sanctioned
|
|
254
294
|
* here — the two faces must not disagree about which directories exist for this session. */
|
|
255
295
|
additionalRoots?: readonly string[];
|
|
296
|
+
/** The run's tool-progress sink — the SAME seat the full shell leg takes, for the same reason its
|
|
297
|
+
* two neighbours above are shared: both foreground legs run through `runShell`, so a long command
|
|
298
|
+
* must breathe on both or a person watching one of them is told nothing. This face has no detach
|
|
299
|
+
* chain, so it ticks and never auto-backgrounds. Absent ⇒ no streaming callbacks, byte-unchanged. */
|
|
300
|
+
onToolProgress?: (body: import("../../core/runner/contracts.js").ToolProgressBody) => void;
|
|
256
301
|
}): AgentTool;
|
|
257
302
|
/**
|
|
258
303
|
* `TaskOutput` — read a background shell's NEW output
|
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
3
4
|
import { defaultTaskRegistry } from "../../core/task-registry.js";
|
|
4
5
|
import { accountDroppedBytes, spoolDropNote } from "../../core/task-registry-shared.js";
|
|
6
|
+
import { RollingTailBuffer } from "../../core/exec-output-tail.js";
|
|
5
7
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, ENV_DIRECT_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "../../core/task-tool-shape.js";
|
|
6
8
|
import { hasBackgroundShell } from "../../core/background-shell.js";
|
|
7
9
|
import { delimitUntrusted } from "../../core/untrusted-text.js";
|
|
@@ -349,27 +351,142 @@ function msTimeoutToRequestedSec(timeoutMs) {
|
|
|
349
351
|
return undefined;
|
|
350
352
|
return Math.max(1, Math.round(timeoutMs / 1000));
|
|
351
353
|
}
|
|
352
|
-
|
|
354
|
+
const TOOL_PROGRESS_BOUND = Object.freeze({ intervalMs: 1_000, tailLines: 20, tailMaxBytes: 16 * 1024 });
|
|
355
|
+
function lastLines(text, n) {
|
|
356
|
+
const body = text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
357
|
+
const lines = body.split("\n");
|
|
358
|
+
return lines.length <= n ? body : lines.slice(-n).join("\n");
|
|
359
|
+
}
|
|
360
|
+
const EPILOGUE_HOLD_MAX_CHARS = 4096;
|
|
361
|
+
function createEpilogueFilter(marker) {
|
|
362
|
+
let held = "";
|
|
363
|
+
let inside = false;
|
|
364
|
+
return (chunk) => {
|
|
365
|
+
let buf = held + chunk;
|
|
366
|
+
held = "";
|
|
367
|
+
let out = "";
|
|
368
|
+
for (;;) {
|
|
369
|
+
if (inside) {
|
|
370
|
+
const close = buf.indexOf(marker);
|
|
371
|
+
if (close === -1)
|
|
372
|
+
break;
|
|
373
|
+
buf = buf.slice(close + marker.length);
|
|
374
|
+
inside = false;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const open = buf.indexOf(marker);
|
|
378
|
+
if (open === -1)
|
|
379
|
+
break;
|
|
380
|
+
out += buf.slice(0, open);
|
|
381
|
+
buf = buf.slice(open + marker.length);
|
|
382
|
+
inside = true;
|
|
383
|
+
}
|
|
384
|
+
if (inside) {
|
|
385
|
+
if (buf.length > EPILOGUE_HOLD_MAX_CHARS) {
|
|
386
|
+
inside = false;
|
|
387
|
+
return out + marker + buf;
|
|
388
|
+
}
|
|
389
|
+
held = buf;
|
|
390
|
+
return out;
|
|
391
|
+
}
|
|
392
|
+
for (let n = Math.min(marker.length - 1, buf.length); n > 0; n--) {
|
|
393
|
+
if (buf.endsWith(marker.slice(0, n))) {
|
|
394
|
+
held = buf.slice(buf.length - n);
|
|
395
|
+
return out + buf.slice(0, buf.length - n);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return out + buf;
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function readCwdEpilogue(stderr, marker) {
|
|
402
|
+
let out = "";
|
|
403
|
+
let rest = stderr;
|
|
404
|
+
let cwd;
|
|
405
|
+
for (;;) {
|
|
406
|
+
const open = rest.indexOf(marker);
|
|
407
|
+
if (open === -1)
|
|
408
|
+
break;
|
|
409
|
+
const close = rest.indexOf(marker, open + marker.length);
|
|
410
|
+
if (close === -1)
|
|
411
|
+
break;
|
|
412
|
+
out += rest.slice(0, open);
|
|
413
|
+
cwd = rest.slice(open + marker.length, close);
|
|
414
|
+
rest = rest.slice(close + marker.length);
|
|
415
|
+
}
|
|
416
|
+
return { stderr: out + rest, cwd };
|
|
417
|
+
}
|
|
418
|
+
function describeSafely(sink, body) {
|
|
419
|
+
if (sink === undefined)
|
|
420
|
+
return;
|
|
421
|
+
try {
|
|
422
|
+
const returned = sink(body);
|
|
423
|
+
if (typeof returned?.then === "function")
|
|
424
|
+
void returned.then(undefined, () => { });
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function startLivenessTicks(onTick, filterStderr) {
|
|
430
|
+
const tail = new RollingTailBuffer(TOOL_PROGRESS_BOUND.tailMaxBytes);
|
|
431
|
+
let totalBytes = 0;
|
|
432
|
+
let totalLines = 0;
|
|
433
|
+
let cached;
|
|
434
|
+
const feed = (chunk) => {
|
|
435
|
+
if (chunk === "")
|
|
436
|
+
return;
|
|
437
|
+
const bytes = Buffer.from(chunk, "utf8");
|
|
438
|
+
totalBytes += bytes.length;
|
|
439
|
+
totalLines += chunk.split("\n").length - 1;
|
|
440
|
+
tail.push(bytes);
|
|
441
|
+
};
|
|
442
|
+
const describeTo = (t) => describeSafely(onTick, t);
|
|
443
|
+
const tick = () => {
|
|
444
|
+
if (totalBytes === 0) {
|
|
445
|
+
describeTo({});
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (cached?.atBytes !== totalBytes)
|
|
449
|
+
cached = { atBytes: totalBytes, text: lastLines(tail.result().text, TOOL_PROGRESS_BOUND.tailLines) };
|
|
450
|
+
describeTo({ output: cached.text, totalLines, totalBytes });
|
|
451
|
+
};
|
|
452
|
+
const timer = setInterval(tick, TOOL_PROGRESS_BOUND.intervalMs);
|
|
453
|
+
timer.unref?.();
|
|
454
|
+
return {
|
|
455
|
+
onStdout: feed,
|
|
456
|
+
onStderr: filterStderr === undefined ? feed : (chunk) => feed(filterStderr(chunk)),
|
|
457
|
+
stop: () => clearInterval(timer),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, cwdRef, detach, readOnly, containmentRoots, onTick) {
|
|
353
461
|
const requestedSec = Math.max(1, Math.floor(timeoutSec ?? caps.defaultSec));
|
|
354
462
|
let timeout = Math.min(caps.maxSec, requestedSec);
|
|
355
463
|
const cappedByMaxTimeout = requestedSec > caps.maxSec;
|
|
356
464
|
const effectiveCwd = cwdRef ? cwdRef.current : cwd;
|
|
465
|
+
const cwdMarker = cwdRef ? `${CWD_SENTINEL}${randomBytes(8).toString("hex")}` : CWD_SENTINEL;
|
|
357
466
|
const toRun = cwdRef
|
|
358
|
-
? `${command}\n__cc_rc
|
|
467
|
+
? `${command}\n__cc_rc=$?\n{ set +vx; } 2>/dev/null\nprintf '%s%s%s' '${cwdMarker}' "$(pwd)" '${cwdMarker}' 1>&2\nexit $__cc_rc`
|
|
359
468
|
: command;
|
|
360
469
|
const execEnv = cwdRef ? { env: { PWD: effectiveCwd } } : undefined;
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
470
|
+
const live = onTick === undefined ? undefined : startLivenessTicks(onTick, cwdRef ? createEpilogueFilter(cwdMarker) : undefined);
|
|
471
|
+
let res;
|
|
472
|
+
try {
|
|
473
|
+
res = await env.exec(toRun, {
|
|
474
|
+
cwd: effectiveCwd,
|
|
475
|
+
timeout,
|
|
476
|
+
abortSignal: signal,
|
|
477
|
+
...execEnv,
|
|
478
|
+
...(live !== undefined ? { onStdout: live.onStdout, onStderr: live.onStderr } : {}),
|
|
479
|
+
...(detach
|
|
480
|
+
? {
|
|
481
|
+
detachSignal: detach.signal,
|
|
482
|
+
autoBackgroundOnTimeout: detach.autoBackgroundEligible === true,
|
|
483
|
+
}
|
|
484
|
+
: {}),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
live?.stop();
|
|
489
|
+
}
|
|
373
490
|
if (!res.ok) {
|
|
374
491
|
if (res.error.code === "spawn_error" || res.error.code === "shell_unavailable") {
|
|
375
492
|
const diagnosis = await diagnoseDeadCwd(env, toolName, effectiveCwd, cwd, cwdRef);
|
|
@@ -461,10 +578,10 @@ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, c
|
|
|
461
578
|
let { stderr } = res.value;
|
|
462
579
|
const { stdout, exitCode } = res.value;
|
|
463
580
|
if (cwdRef) {
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const captured =
|
|
581
|
+
const epilogue = readCwdEpilogue(stderr, cwdMarker);
|
|
582
|
+
stderr = epilogue.stderr;
|
|
583
|
+
if (epilogue.cwd !== undefined) {
|
|
584
|
+
const captured = epilogue.cwd.trim();
|
|
468
585
|
const windowsWorld = /^[A-Za-z]:[\\/]/.test(cwdRef.current);
|
|
469
586
|
const msys = /^\/([A-Za-z])(\/.*)?$/.exec(captured);
|
|
470
587
|
if (/^[A-Za-z]:[\\/]/.test(captured))
|
|
@@ -756,6 +873,22 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
756
873
|
}
|
|
757
874
|
const detachHub = taskOpts.detachHub;
|
|
758
875
|
const detachRegistry = taskOpts.taskRegistry;
|
|
876
|
+
const autoBackgroundArmed = taskOpts.autoBackgroundOnTimeout === true && canAutoBackground(command);
|
|
877
|
+
let backgroundedForQueuedMessage = false;
|
|
878
|
+
const wantsTicks = taskOpts.onToolProgress !== undefined || (autoBackgroundArmed && detachHub !== undefined && taskOpts.queuedHumanInput !== undefined);
|
|
879
|
+
const onTick = !wantsTicks
|
|
880
|
+
? undefined
|
|
881
|
+
: (t) => {
|
|
882
|
+
describeSafely(taskOpts.onToolProgress, { toolCallId: ctx.toolCallId, toolName: "Bash", ...t });
|
|
883
|
+
if (!autoBackgroundArmed || backgroundedForQueuedMessage || detachHub === undefined)
|
|
884
|
+
return;
|
|
885
|
+
if (ctx.signal?.aborted === true)
|
|
886
|
+
return;
|
|
887
|
+
if (taskOpts.queuedHumanInput?.() !== true)
|
|
888
|
+
return;
|
|
889
|
+
backgroundedForQueuedMessage = true;
|
|
890
|
+
detachHub.request(ctx.toolCallId);
|
|
891
|
+
};
|
|
759
892
|
const detachEnv = hasBackgroundShell(env) ? env : undefined;
|
|
760
893
|
const detachChain = detachHub !== undefined && detachRegistry !== undefined && detachEnv !== undefined && detachEnv.backgroundCapabilities.supportsDetach === true
|
|
761
894
|
? {
|
|
@@ -804,7 +937,9 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
804
937
|
return {
|
|
805
938
|
content: (cause === "timeout"
|
|
806
939
|
? `Command exceeded its timeout but is still running — moved to background instead of being killed (task_id=${taskId}). `
|
|
807
|
-
:
|
|
940
|
+
: backgroundedForQueuedMessage
|
|
941
|
+
? `Command is still running and was moved to the background so a queued message could be delivered without waiting for it (task_id=${taskId}). It was NOT stopped and nothing about it failed. `
|
|
942
|
+
: `Command moved to background; task_id=${taskId}. `) +
|
|
808
943
|
(outputFile !== undefined
|
|
809
944
|
? `Output file: ${outputFile} (full output is appended there — Read it any time). `
|
|
810
945
|
: `Use TaskOutput("${taskId}") to read its output. `) +
|
|
@@ -818,7 +953,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
818
953
|
adoptBudgetNote +
|
|
819
954
|
backgroundLifetimeNote(adoptCaps, bgSessionScoped, bgEnvIsolatedOwned) +
|
|
820
955
|
(tail ? `\n--- output so far (tail) ---\n${tail}` : ""),
|
|
821
|
-
details: { type: "bash", detached: true, task_id: taskId, ...(description !== undefined ? { description } : {}), ...(outputFile !== undefined ? { output_file: outputFile } : {}), ...(cause === "timeout" ? { autoBackgrounded: true } : {}) },
|
|
956
|
+
details: { type: "bash", detached: true, task_id: taskId, ...(description !== undefined ? { description } : {}), ...(outputFile !== undefined ? { output_file: outputFile } : {}), ...(cause === "timeout" || backgroundedForQueuedMessage ? { autoBackgrounded: true } : {}) },
|
|
822
957
|
};
|
|
823
958
|
},
|
|
824
959
|
autoBackgroundEligible: taskOpts.autoBackgroundOnTimeout === true && canAutoBackground(command),
|
|
@@ -826,7 +961,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
826
961
|
: undefined;
|
|
827
962
|
let res;
|
|
828
963
|
try {
|
|
829
|
-
res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, cwdRef, detachChain, false, [rootCanonical, ...(taskOpts.additionalRoots ?? [])]);
|
|
964
|
+
res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, cwdRef, detachChain, false, [rootCanonical, ...(taskOpts.additionalRoots ?? [])], onTick);
|
|
830
965
|
}
|
|
831
966
|
finally {
|
|
832
967
|
taskOpts.detachHub?.gc(ctx.toolCallId);
|
|
@@ -1210,7 +1345,8 @@ export function createBashReadonlyTool(env, rootCanonical, allow, opts) {
|
|
|
1210
1345
|
const escape = await canonicalEscapeRefusal(canonicalBoundary, { literal: [...(boundary.checkedPaths ?? []), ...(boundary.checkedSpellings ?? [])], patterns: [...(boundary.undecidedPaths ?? []), ...(boundary.undecidedSpellings ?? [])] }, readRoots, READONLY_FACE_ESCAPE_PROSE, ctx.signal);
|
|
1211
1346
|
if (escape !== undefined)
|
|
1212
1347
|
return escape;
|
|
1213
|
-
|
|
1348
|
+
const readOnlyTick = opts?.onToolProgress === undefined ? undefined : (t) => describeSafely(opts.onToolProgress, { toolCallId: ctx.toolCallId, toolName: "Bash", ...t });
|
|
1349
|
+
return await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, undefined, undefined, true, undefined, readOnlyTick);
|
|
1214
1350
|
},
|
|
1215
1351
|
});
|
|
1216
1352
|
}
|
|
@@ -6,4 +6,4 @@ import { type ReminderDisclosureCounts } from "../../core/reminder-disclosure.js
|
|
|
6
6
|
export declare function createReadFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], imageDownsampler?: ReadImageDownsamplerOption, pdfCapabilities?: PdfModelCapabilities, bgOutputReadExemption?: (canonicalKey: string, ctx: {
|
|
7
7
|
taskId?: string;
|
|
8
8
|
principal?: string;
|
|
9
|
-
}) => boolean, readCyberReminder?: boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./
|
|
9
|
+
}) => boolean, readCyberReminder?: boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./containment-face.js").ContainmentFace, reminderMark?: string, reminderDisclosureCounts?: ReminderDisclosureCounts): AgentTool;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import type { ReadDenyMatcher } from "./read-deny.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ContainmentFace } from "./containment-face.js";
|
|
4
4
|
import type { CwdRef } from "./fs-shared.js";
|
|
5
5
|
/**
|
|
6
6
|
* The Grep card's details assembly, PURE over the engine text ({mode, offset} from the request) —
|
|
@@ -11,5 +11,5 @@ import type { CwdRef } from "./fs-shared.js";
|
|
|
11
11
|
* honesty boundary.
|
|
12
12
|
*/
|
|
13
13
|
export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
|
|
14
|
-
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?:
|
|
15
|
-
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?:
|
|
14
|
+
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef): AgentTool;
|
|
15
|
+
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef): AgentTool;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import type { BeforeWriteHook, FileEditedHook, TrackFileEditHook } from "../../core/types.js";
|
|
3
3
|
import { type ReadFileState } from "./safety.js";
|
|
4
|
+
import type { ContainmentFace } from "./containment-face.js";
|
|
4
5
|
import { type CwdRef } from "./fs-shared.js";
|
|
5
6
|
/**
|
|
6
7
|
* The `beforeWrite` CONTENT hook, mounted here rather than in ToolPolicy because only this band sees
|
|
@@ -12,8 +13,8 @@ import { type CwdRef } from "./fs-shared.js";
|
|
|
12
13
|
* Absent hook ⇒ byte-identical behavior.
|
|
13
14
|
*/
|
|
14
15
|
export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook } from "../../core/types.js";
|
|
15
|
-
export declare function createEditFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook): AgentTool;
|
|
16
|
-
export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook): AgentTool;
|
|
16
|
+
export declare function createEditFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook, writeFace?: ContainmentFace): AgentTool;
|
|
17
|
+
export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook, writeFace?: ContainmentFace): AgentTool;
|
|
17
18
|
/**
|
|
18
19
|
* design v1.163 — NotebookEdit: replace/insert/delete a single cell in a .ipynb. CC-parity tool over the SAME hand-band
|
|
19
20
|
* safety skeleton as Edit/Write (resolveKey containment → requireRead read-before-edit → checkStale content-hash
|
|
@@ -21,4 +22,4 @@ export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileSt
|
|
|
21
22
|
* .ipynb as the cell projection but records read state (hash/totalLines) in the RAW notebook-text
|
|
22
23
|
* coordinate — that raw-coordinate read record is what this tool's freshness check depends on.
|
|
23
24
|
*/
|
|
24
|
-
export declare function createNotebookEditTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook): AgentTool;
|
|
25
|
+
export declare function createNotebookEditTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook, writeFace?: ContainmentFace): AgentTool;
|
|
@@ -86,7 +86,7 @@ async function gateToolWrite(hook, tool, path, key, content) {
|
|
|
86
86
|
return `Error (${tool}): write rejected by the write gate (${res.code}): ${clipHookText(res.reason)}`;
|
|
87
87
|
return undefined;
|
|
88
88
|
}
|
|
89
|
-
export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
|
|
89
|
+
export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
|
|
90
90
|
return defineTool({
|
|
91
91
|
name: "Edit",
|
|
92
92
|
...toolFace("edit"),
|
|
@@ -111,7 +111,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
111
111
|
const path = fileArgPath(args);
|
|
112
112
|
if (path === undefined)
|
|
113
113
|
return { ok: true };
|
|
114
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
|
|
114
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
|
|
115
115
|
if (!r.ok)
|
|
116
116
|
return { ok: true };
|
|
117
117
|
if (a.old_string === a.new_string)
|
|
@@ -149,7 +149,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
149
149
|
const path = fileArgPath(args);
|
|
150
150
|
if (path === undefined)
|
|
151
151
|
return errorResult(`Error (Edit): file_path is required.`);
|
|
152
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
|
|
152
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
|
|
153
153
|
if (!r.ok)
|
|
154
154
|
return errorResult(violationText("Edit", r.violation), violationDetails(r.violation));
|
|
155
155
|
if (!batch && a.old_string === a.new_string) {
|
|
@@ -324,7 +324,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
324
324
|
},
|
|
325
325
|
});
|
|
326
326
|
}
|
|
327
|
-
export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
|
|
327
|
+
export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
|
|
328
328
|
return defineTool({
|
|
329
329
|
name: "Write",
|
|
330
330
|
...toolFace("write"),
|
|
@@ -343,7 +343,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
343
343
|
return { ok: true };
|
|
344
344
|
if (ipynbRedirect("Write", path))
|
|
345
345
|
return { ok: true };
|
|
346
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
|
|
346
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
|
|
347
347
|
if (!r.ok)
|
|
348
348
|
return { ok: true };
|
|
349
349
|
const exists = await env.exists(r.key, ctx.signal);
|
|
@@ -362,7 +362,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
362
362
|
const ipynb = ipynbRedirect("Write", path);
|
|
363
363
|
if (ipynb)
|
|
364
364
|
return errorResult(ipynb);
|
|
365
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots);
|
|
365
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
|
|
366
366
|
if (!r.ok)
|
|
367
367
|
return errorResult(violationText("Write", r.violation), violationDetails(r.violation));
|
|
368
368
|
const exists = await env.exists(r.key, ctx.signal);
|
|
@@ -423,7 +423,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
423
423
|
const notebookNotIpynbMessage = (notebookPath) => `Error (NotebookEdit): "${notebookPath}" is not a .ipynb file; use Edit for other file types.`;
|
|
424
424
|
const NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE = `Error (NotebookEdit): Cell type is required when using edit_mode=insert.`;
|
|
425
425
|
const NOTEBOOK_CELL_ID_REQUIRED_MESSAGE = `Error (NotebookEdit): cell_id is required for replace/delete.`;
|
|
426
|
-
export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
|
|
426
|
+
export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
|
|
427
427
|
return defineTool({
|
|
428
428
|
name: "NotebookEdit",
|
|
429
429
|
...toolFace("notebook-edit"),
|
|
@@ -457,7 +457,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
|
|
|
457
457
|
return { ok: false, code: "invalid", message: NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE };
|
|
458
458
|
if (mode !== "insert" && !a.cell_id)
|
|
459
459
|
return { ok: false, code: "invalid", message: NOTEBOOK_CELL_ID_REQUIRED_MESSAGE };
|
|
460
|
-
const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots);
|
|
460
|
+
const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
|
|
461
461
|
if (!r.ok)
|
|
462
462
|
return { ok: true };
|
|
463
463
|
const notRead = requireRead(state, r.key);
|
|
@@ -477,7 +477,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
|
|
|
477
477
|
return errorResult(NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE);
|
|
478
478
|
if (mode !== "insert" && !cell_id)
|
|
479
479
|
return errorResult(NOTEBOOK_CELL_ID_REQUIRED_MESSAGE);
|
|
480
|
-
const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots);
|
|
480
|
+
const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
|
|
481
481
|
if (!r.ok)
|
|
482
482
|
return errorResult(violationText("NotebookEdit", r.violation), violationDetails(r.violation));
|
|
483
483
|
const notRead = requireRead(state, r.key);
|