@sema-agent/core 7.17.7 → 7.19.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.
Files changed (201) hide show
  1. package/CHANGELOG.md +233 -13
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +5 -0
  5. package/dist/agents/teacher.js +1 -0
  6. package/dist/agents/verify.js +3 -0
  7. package/dist/brain/anthropic.js +6 -3
  8. package/dist/brain/open-responses.js +10 -4
  9. package/dist/brain/openai.js +5 -2
  10. package/dist/brain/reasoning.d.ts +13 -13
  11. package/dist/brain/reasoning.js +2 -2
  12. package/dist/brain/route-adjudicator.d.ts +18 -0
  13. package/dist/brain/route-adjudicator.js +16 -0
  14. package/dist/brain/stream-engine.js +2 -1
  15. package/dist/brain/stream-shared.d.ts +3 -0
  16. package/dist/brain/stream-shared.js +1 -0
  17. package/dist/core/agent-definition.d.ts +10 -0
  18. package/dist/core/artifact-host.d.ts +41 -4
  19. package/dist/core/artifact-host.js +18 -2
  20. package/dist/core/ask-origin.d.ts +16 -0
  21. package/dist/core/ask-origin.js +8 -0
  22. package/dist/core/auto-mode-arming.d.ts +6 -0
  23. package/dist/core/auto-mode-arming.js +5 -5
  24. package/dist/core/auto-mode-defaults.d.ts +43 -15
  25. package/dist/core/auto-mode-defaults.js +4 -1
  26. package/dist/core/auto-mode.d.ts +184 -59
  27. package/dist/core/auto-mode.js +188 -13
  28. package/dist/core/checkpoint-store.d.ts +23 -2
  29. package/dist/core/checkpoint-store.js +2 -2
  30. package/dist/core/engine-notice.d.ts +77 -1
  31. package/dist/core/engine-notice.js +7 -0
  32. package/dist/core/gate-fold.js +5 -1
  33. package/dist/core/gate-lanes.js +9 -8
  34. package/dist/core/gate-outcome.d.ts +42 -2
  35. package/dist/core/gate-outcome.js +23 -0
  36. package/dist/core/governance-codes.d.ts +1 -1
  37. package/dist/core/governance-codes.js +14 -0
  38. package/dist/core/hook-seats.d.ts +66 -0
  39. package/dist/core/hook-seats.js +37 -0
  40. package/dist/core/hooks.d.ts +1 -1
  41. package/dist/core/read-root-candidate.d.ts +12 -0
  42. package/dist/core/read-root-candidate.js +24 -0
  43. package/dist/core/roles.d.ts +9 -1
  44. package/dist/core/roles.js +4 -0
  45. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  46. package/dist/core/runner/assemble-result.d.ts +2 -1
  47. package/dist/core/runner/assemble-result.js +1 -1
  48. package/dist/core/runner/classifier-ladder.d.ts +227 -0
  49. package/dist/core/runner/classifier-ladder.js +214 -0
  50. package/dist/core/runner/contracts.d.ts +71 -13
  51. package/dist/core/runner/decide-continuation.d.ts +1 -3
  52. package/dist/core/runner/decide-continuation.js +1 -7
  53. package/dist/core/runner/deferral-census.d.ts +52 -0
  54. package/dist/core/runner/deferral-census.js +44 -0
  55. package/dist/core/runner/denial-limit-arms.js +2 -2
  56. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  57. package/dist/core/runner/derived-route-fallback.js +8 -3
  58. package/dist/core/runner/gate-exit.d.ts +12 -0
  59. package/dist/core/runner/gate-exit.js +6 -3
  60. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  61. package/dist/core/runner/prepare-artifact.js +5 -4
  62. package/dist/core/runner/prepare-ask-lane.js +1 -0
  63. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  64. package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
  65. package/dist/core/runner/prepare-config-doors.d.ts +7 -1
  66. package/dist/core/runner/prepare-config-doors.js +13 -13
  67. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  68. package/dist/core/runner/prepare-defer-classify.js +8 -3
  69. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  70. package/dist/core/runner/prepare-gate-stations.js +1 -1
  71. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  72. package/dist/core/runner/prepare-hands-readface.js +60 -24
  73. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  74. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  75. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  76. package/dist/core/runner/prepare-lsp.js +1 -1
  77. package/dist/core/runner/prepare-policy-chain.js +2 -0
  78. package/dist/core/runner/prepare-project-context.d.ts +5 -1
  79. package/dist/core/runner/prepare-project-context.js +7 -4
  80. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  81. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  82. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  83. package/dist/core/runner/prepare-run-refs.js +3 -2
  84. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  85. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  86. package/dist/core/runner/prepare-task.js +19 -14
  87. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  88. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  89. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  90. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  91. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  92. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  93. package/dist/core/runner/resume-apply.js +3 -3
  94. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  95. package/dist/core/runner/run-harness-handlers.js +4 -1
  96. package/dist/core/runner/run-identity-wiring.js +6 -2
  97. package/dist/core/runner/run-leg.d.ts +2 -0
  98. package/dist/core/runner/run-leg.js +45 -45
  99. package/dist/core/runner/run-notification-lane.js +55 -27
  100. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  101. package/dist/core/runner/run-terminal-adoption.js +16 -13
  102. package/dist/core/runner/run-turn-boundary.js +15 -5
  103. package/dist/core/runner/runtask.d.ts +6 -5
  104. package/dist/core/runner/runtask.js +26 -14
  105. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  106. package/dist/core/runner/skill-base-dir.js +42 -0
  107. package/dist/core/runner/synthetic-tools.d.ts +83 -48
  108. package/dist/core/runner/synthetic-tools.js +69 -32
  109. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  110. package/dist/core/runner/tool-defer-gate.js +15 -5
  111. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  112. package/dist/core/runner/tool-disclosure.js +2 -0
  113. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  114. package/dist/core/runner/tool-progress-lane.js +61 -0
  115. package/dist/core/runner/turn-attachments.d.ts +43 -12
  116. package/dist/core/runner/turn-attachments.js +58 -14
  117. package/dist/core/runner-deps.d.ts +48 -7
  118. package/dist/core/sensitive-path-policy.d.ts +58 -17
  119. package/dist/core/sensitive-path-policy.js +33 -5
  120. package/dist/core/skill-listing-budget.d.ts +112 -0
  121. package/dist/core/skill-listing-budget.js +58 -0
  122. package/dist/core/swappable-deps.d.ts +9 -7
  123. package/dist/core/swappable-deps.js +7 -3
  124. package/dist/core/task-event.d.ts +96 -29
  125. package/dist/core/task-notification.d.ts +142 -16
  126. package/dist/core/task-notification.js +106 -0
  127. package/dist/core/task-result.d.ts +9 -1
  128. package/dist/core/task-spec.d.ts +46 -3
  129. package/dist/core/tighten-task-spec.js +7 -5
  130. package/dist/core/tool-model-gate.d.ts +1 -1
  131. package/dist/core/tool-policy.d.ts +19 -4
  132. package/dist/core/tool-policy.js +6 -7
  133. package/dist/core/tool-search-setting.d.ts +78 -0
  134. package/dist/core/tool-search-setting.js +90 -0
  135. package/dist/core/tool-spec.d.ts +45 -4
  136. package/dist/core/types.d.ts +2 -2
  137. package/dist/core/types.js +1 -1
  138. package/dist/core/wiring-manifest.d.ts +46 -0
  139. package/dist/core/wiring-manifest.js +9 -1
  140. package/dist/core/write-protect.d.ts +8 -3
  141. package/dist/core/write-protect.js +8 -5
  142. package/dist/engine/execution-env/node-execution-env.js +10 -4
  143. package/dist/engine/harness/agent-harness.d.ts +42 -0
  144. package/dist/engine/harness/agent-harness.js +61 -29
  145. package/dist/engine/loop/agent-loop.d.ts +68 -3
  146. package/dist/engine/loop/agent-loop.js +109 -16
  147. package/dist/index.d.ts +13 -8
  148. package/dist/index.js +11 -7
  149. package/dist/internal/harness.d.ts +1 -1
  150. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  151. package/dist/orchestration/run-workflow-tool.js +6 -1
  152. package/dist/orchestration/workflow-governance.d.ts +7 -1
  153. package/dist/orchestration/workflow-governance.js +9 -5
  154. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  155. package/dist/orchestration/workflow-primitives.js +6 -1
  156. package/dist/orchestration/workflow-types.d.ts +48 -13
  157. package/dist/orchestration/workflow-types.js +1 -1
  158. package/dist/orchestration/workflow.js +45 -17
  159. package/dist/prompt-assembly/epoch.js +2 -0
  160. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  161. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  162. package/dist/prompt-assembly/section-usage.js +10 -0
  163. package/dist/prompt-assembly/types.d.ts +8 -6
  164. package/dist/prompts/default.d.ts +13 -0
  165. package/dist/prompts/default.js +16 -8
  166. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  167. package/dist/tools/artifact/artifact-text.js +6 -3
  168. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  169. package/dist/tools/artifact/artifact-tool.js +203 -70
  170. package/dist/tools/artifact/conformance.d.ts +56 -0
  171. package/dist/tools/artifact/conformance.js +349 -0
  172. package/dist/tools/artifact/local-stub.js +1 -1
  173. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  174. package/dist/tools/artifact/reject-breaker.js +42 -0
  175. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  176. package/dist/tools/fs/bash-lexer.js +563 -0
  177. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  178. package/dist/tools/fs/bash-program-position.js +125 -0
  179. package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
  180. package/dist/tools/fs/bash-readonly-classifier.js +180 -558
  181. package/dist/tools/fs/containment-face.d.ts +89 -0
  182. package/dist/tools/fs/containment-face.js +81 -0
  183. package/dist/tools/fs/fs-bash.d.ts +46 -1
  184. package/dist/tools/fs/fs-bash.js +165 -25
  185. package/dist/tools/fs/fs-read.d.ts +1 -1
  186. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  187. package/dist/tools/fs/fs-write.d.ts +4 -3
  188. package/dist/tools/fs/fs-write.js +9 -9
  189. package/dist/tools/fs/index.d.ts +24 -5
  190. package/dist/tools/fs/index.js +18 -6
  191. package/dist/tools/fs/read-deny.d.ts +4 -3
  192. package/dist/tools/fs/repo-map.d.ts +2 -2
  193. package/dist/tools/fs/safety.d.ts +18 -3
  194. package/dist/tools/fs/safety.js +11 -4
  195. package/package.json +1 -1
  196. package/test/export-surface.snapshot.json +99 -13
  197. package/wire-changes/core@7.17.7.json +49 -0
  198. package/wire-changes/core@7.18.0.json +1273 -0
  199. package/wire-changes/core@7.19.0.json +457 -0
  200. package/dist/tools/fs/read-face.d.ts +0 -68
  201. 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
@@ -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";
@@ -48,13 +50,17 @@ export function bashReversibilityProbe(allow, boundary) {
48
50
  if (loop.reason === undefined)
49
51
  return { reversible: true };
50
52
  if (boundaryDeclared(loop) || unjudgedRead(command, allowSet, resolved, loop))
51
- return { reversible: false, mandated: true };
52
- return { reversible: false, ...boundaryGate(detailed) };
53
+ return { reversible: false, mandated: true, ...readRootCandidateOf(loop) };
54
+ return { reversible: false, ...boundaryGate(detailed), ...readRootCandidateOf(detailed) };
53
55
  };
54
56
  }
55
57
  function boundaryUnread(verdict) {
56
58
  return boundaryUndecided(verdict) || boundaryUnjudged(verdict);
57
59
  }
60
+ function readRootCandidateOf(verdict) {
61
+ const dir = verdict.outOfRootGrantCandidate;
62
+ return dir !== undefined ? { readRootCandidate: { dir, clearsThisAsk: true } } : {};
63
+ }
58
64
  function boundaryUndecided(verdict) {
59
65
  return verdict.undecidedPaths !== undefined && verdict.undecidedPaths.length > 0;
60
66
  }
@@ -132,7 +138,7 @@ export function bashReadBoundaryProbe(boundary) {
132
138
  const loopVerdict = seatVerdict(loop);
133
139
  const mandated = [compoundVerdict, loopVerdict].find((verdict) => verdict?.mandated === true);
134
140
  if (mandated !== undefined)
135
- return mandated;
141
+ return { ...mandated, ...readRootCandidateOf(detailed), ...readRootCandidateOf(loop) };
136
142
  if (readBoundaryMandate(command, allowSet, resolved, detailed).mandated === true)
137
143
  return { reversible: false, mandated: true };
138
144
  return compoundVerdict ?? loopVerdict ?? { reversible: true };
@@ -349,27 +355,142 @@ function msTimeoutToRequestedSec(timeoutMs) {
349
355
  return undefined;
350
356
  return Math.max(1, Math.round(timeoutMs / 1000));
351
357
  }
352
- async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, cwdRef, detach, readOnly, containmentRoots) {
358
+ const TOOL_PROGRESS_BOUND = Object.freeze({ intervalMs: 1_000, tailLines: 20, tailMaxBytes: 16 * 1024 });
359
+ function lastLines(text, n) {
360
+ const body = text.endsWith("\n") ? text.slice(0, -1) : text;
361
+ const lines = body.split("\n");
362
+ return lines.length <= n ? body : lines.slice(-n).join("\n");
363
+ }
364
+ const EPILOGUE_HOLD_MAX_CHARS = 4096;
365
+ function createEpilogueFilter(marker) {
366
+ let held = "";
367
+ let inside = false;
368
+ return (chunk) => {
369
+ let buf = held + chunk;
370
+ held = "";
371
+ let out = "";
372
+ for (;;) {
373
+ if (inside) {
374
+ const close = buf.indexOf(marker);
375
+ if (close === -1)
376
+ break;
377
+ buf = buf.slice(close + marker.length);
378
+ inside = false;
379
+ continue;
380
+ }
381
+ const open = buf.indexOf(marker);
382
+ if (open === -1)
383
+ break;
384
+ out += buf.slice(0, open);
385
+ buf = buf.slice(open + marker.length);
386
+ inside = true;
387
+ }
388
+ if (inside) {
389
+ if (buf.length > EPILOGUE_HOLD_MAX_CHARS) {
390
+ inside = false;
391
+ return out + marker + buf;
392
+ }
393
+ held = buf;
394
+ return out;
395
+ }
396
+ for (let n = Math.min(marker.length - 1, buf.length); n > 0; n--) {
397
+ if (buf.endsWith(marker.slice(0, n))) {
398
+ held = buf.slice(buf.length - n);
399
+ return out + buf.slice(0, buf.length - n);
400
+ }
401
+ }
402
+ return out + buf;
403
+ };
404
+ }
405
+ function readCwdEpilogue(stderr, marker) {
406
+ let out = "";
407
+ let rest = stderr;
408
+ let cwd;
409
+ for (;;) {
410
+ const open = rest.indexOf(marker);
411
+ if (open === -1)
412
+ break;
413
+ const close = rest.indexOf(marker, open + marker.length);
414
+ if (close === -1)
415
+ break;
416
+ out += rest.slice(0, open);
417
+ cwd = rest.slice(open + marker.length, close);
418
+ rest = rest.slice(close + marker.length);
419
+ }
420
+ return { stderr: out + rest, cwd };
421
+ }
422
+ function describeSafely(sink, body) {
423
+ if (sink === undefined)
424
+ return;
425
+ try {
426
+ const returned = sink(body);
427
+ if (typeof returned?.then === "function")
428
+ void returned.then(undefined, () => { });
429
+ }
430
+ catch {
431
+ }
432
+ }
433
+ function startLivenessTicks(onTick, filterStderr) {
434
+ const tail = new RollingTailBuffer(TOOL_PROGRESS_BOUND.tailMaxBytes);
435
+ let totalBytes = 0;
436
+ let totalLines = 0;
437
+ let cached;
438
+ const feed = (chunk) => {
439
+ if (chunk === "")
440
+ return;
441
+ const bytes = Buffer.from(chunk, "utf8");
442
+ totalBytes += bytes.length;
443
+ totalLines += chunk.split("\n").length - 1;
444
+ tail.push(bytes);
445
+ };
446
+ const describeTo = (t) => describeSafely(onTick, t);
447
+ const tick = () => {
448
+ if (totalBytes === 0) {
449
+ describeTo({});
450
+ return;
451
+ }
452
+ if (cached?.atBytes !== totalBytes)
453
+ cached = { atBytes: totalBytes, text: lastLines(tail.result().text, TOOL_PROGRESS_BOUND.tailLines) };
454
+ describeTo({ output: cached.text, totalLines, totalBytes });
455
+ };
456
+ const timer = setInterval(tick, TOOL_PROGRESS_BOUND.intervalMs);
457
+ timer.unref?.();
458
+ return {
459
+ onStdout: feed,
460
+ onStderr: filterStderr === undefined ? feed : (chunk) => feed(filterStderr(chunk)),
461
+ stop: () => clearInterval(timer),
462
+ };
463
+ }
464
+ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, cwdRef, detach, readOnly, containmentRoots, onTick) {
353
465
  const requestedSec = Math.max(1, Math.floor(timeoutSec ?? caps.defaultSec));
354
466
  let timeout = Math.min(caps.maxSec, requestedSec);
355
467
  const cappedByMaxTimeout = requestedSec > caps.maxSec;
356
468
  const effectiveCwd = cwdRef ? cwdRef.current : cwd;
469
+ const cwdMarker = cwdRef ? `${CWD_SENTINEL}${randomBytes(8).toString("hex")}` : CWD_SENTINEL;
357
470
  const toRun = cwdRef
358
- ? `${command}\n__cc_rc=$?; printf '%s%s%s' '${CWD_SENTINEL}' "$(pwd)" '${CWD_SENTINEL}' 1>&2; exit $__cc_rc`
471
+ ? `${command}\n__cc_rc=$?\n{ set +vx; } 2>/dev/null\nprintf '%s%s%s' '${cwdMarker}' "$(pwd)" '${cwdMarker}' 1>&2\nexit $__cc_rc`
359
472
  : command;
360
473
  const execEnv = cwdRef ? { env: { PWD: effectiveCwd } } : undefined;
361
- const res = await env.exec(toRun, {
362
- cwd: effectiveCwd,
363
- timeout,
364
- abortSignal: signal,
365
- ...execEnv,
366
- ...(detach
367
- ? {
368
- detachSignal: detach.signal,
369
- autoBackgroundOnTimeout: detach.autoBackgroundEligible === true,
370
- }
371
- : {}),
372
- });
474
+ const live = onTick === undefined ? undefined : startLivenessTicks(onTick, cwdRef ? createEpilogueFilter(cwdMarker) : undefined);
475
+ let res;
476
+ try {
477
+ res = await env.exec(toRun, {
478
+ cwd: effectiveCwd,
479
+ timeout,
480
+ abortSignal: signal,
481
+ ...execEnv,
482
+ ...(live !== undefined ? { onStdout: live.onStdout, onStderr: live.onStderr } : {}),
483
+ ...(detach
484
+ ? {
485
+ detachSignal: detach.signal,
486
+ autoBackgroundOnTimeout: detach.autoBackgroundEligible === true,
487
+ }
488
+ : {}),
489
+ });
490
+ }
491
+ finally {
492
+ live?.stop();
493
+ }
373
494
  if (!res.ok) {
374
495
  if (res.error.code === "spawn_error" || res.error.code === "shell_unavailable") {
375
496
  const diagnosis = await diagnoseDeadCwd(env, toolName, effectiveCwd, cwd, cwdRef);
@@ -461,10 +582,10 @@ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, c
461
582
  let { stderr } = res.value;
462
583
  const { stdout, exitCode } = res.value;
463
584
  if (cwdRef) {
464
- const m = stderr.match(new RegExp(`${CWD_SENTINEL}([\\s\\S]*?)${CWD_SENTINEL}`));
465
- if (m) {
466
- stderr = stderr.replace(m[0], "");
467
- const captured = m[1].trim();
585
+ const epilogue = readCwdEpilogue(stderr, cwdMarker);
586
+ stderr = epilogue.stderr;
587
+ if (epilogue.cwd !== undefined) {
588
+ const captured = epilogue.cwd.trim();
468
589
  const windowsWorld = /^[A-Za-z]:[\\/]/.test(cwdRef.current);
469
590
  const msys = /^\/([A-Za-z])(\/.*)?$/.exec(captured);
470
591
  if (/^[A-Za-z]:[\\/]/.test(captured))
@@ -756,6 +877,22 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
756
877
  }
757
878
  const detachHub = taskOpts.detachHub;
758
879
  const detachRegistry = taskOpts.taskRegistry;
880
+ const autoBackgroundArmed = taskOpts.autoBackgroundOnTimeout === true && canAutoBackground(command);
881
+ let backgroundedForQueuedMessage = false;
882
+ const wantsTicks = taskOpts.onToolProgress !== undefined || (autoBackgroundArmed && detachHub !== undefined && taskOpts.queuedHumanInput !== undefined);
883
+ const onTick = !wantsTicks
884
+ ? undefined
885
+ : (t) => {
886
+ describeSafely(taskOpts.onToolProgress, { toolCallId: ctx.toolCallId, toolName: "Bash", ...t });
887
+ if (!autoBackgroundArmed || backgroundedForQueuedMessage || detachHub === undefined)
888
+ return;
889
+ if (ctx.signal?.aborted === true)
890
+ return;
891
+ if (taskOpts.queuedHumanInput?.() !== true)
892
+ return;
893
+ backgroundedForQueuedMessage = true;
894
+ detachHub.request(ctx.toolCallId);
895
+ };
759
896
  const detachEnv = hasBackgroundShell(env) ? env : undefined;
760
897
  const detachChain = detachHub !== undefined && detachRegistry !== undefined && detachEnv !== undefined && detachEnv.backgroundCapabilities.supportsDetach === true
761
898
  ? {
@@ -804,7 +941,9 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
804
941
  return {
805
942
  content: (cause === "timeout"
806
943
  ? `Command exceeded its timeout but is still running — moved to background instead of being killed (task_id=${taskId}). `
807
- : `Command moved to background; task_id=${taskId}. `) +
944
+ : backgroundedForQueuedMessage
945
+ ? `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. `
946
+ : `Command moved to background; task_id=${taskId}. `) +
808
947
  (outputFile !== undefined
809
948
  ? `Output file: ${outputFile} (full output is appended there — Read it any time). `
810
949
  : `Use TaskOutput("${taskId}") to read its output. `) +
@@ -818,7 +957,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
818
957
  adoptBudgetNote +
819
958
  backgroundLifetimeNote(adoptCaps, bgSessionScoped, bgEnvIsolatedOwned) +
820
959
  (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 } : {}) },
960
+ details: { type: "bash", detached: true, task_id: taskId, ...(description !== undefined ? { description } : {}), ...(outputFile !== undefined ? { output_file: outputFile } : {}), ...(cause === "timeout" || backgroundedForQueuedMessage ? { autoBackgrounded: true } : {}) },
822
961
  };
823
962
  },
824
963
  autoBackgroundEligible: taskOpts.autoBackgroundOnTimeout === true && canAutoBackground(command),
@@ -826,7 +965,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
826
965
  : undefined;
827
966
  let res;
828
967
  try {
829
- res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, cwdRef, detachChain, false, [rootCanonical, ...(taskOpts.additionalRoots ?? [])]);
968
+ res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, cwdRef, detachChain, false, [rootCanonical, ...(taskOpts.additionalRoots ?? [])], onTick);
830
969
  }
831
970
  finally {
832
971
  taskOpts.detachHub?.gc(ctx.toolCallId);
@@ -1210,7 +1349,8 @@ export function createBashReadonlyTool(env, rootCanonical, allow, opts) {
1210
1349
  const escape = await canonicalEscapeRefusal(canonicalBoundary, { literal: [...(boundary.checkedPaths ?? []), ...(boundary.checkedSpellings ?? [])], patterns: [...(boundary.undecidedPaths ?? []), ...(boundary.undecidedSpellings ?? [])] }, readRoots, READONLY_FACE_ESCAPE_PROSE, ctx.signal);
1211
1350
  if (escape !== undefined)
1212
1351
  return escape;
1213
- return await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, undefined, undefined, true);
1352
+ const readOnlyTick = opts?.onToolProgress === undefined ? undefined : (t) => describeSafely(opts.onToolProgress, { toolCallId: ctx.toolCallId, toolName: "Bash", ...t });
1353
+ return await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, undefined, undefined, true, undefined, readOnlyTick);
1214
1354
  },
1215
1355
  });
1216
1356
  }
@@ -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("./read-face.js").ReadFace, reminderMark?: string, reminderDisclosureCounts?: ReminderDisclosureCounts): AgentTool;
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 { ReadFace } from "./read-face.js";
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?: ReadFace, cwdRef?: CwdRef): AgentTool;
15
- export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace, cwdRef?: CwdRef): AgentTool;
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;