@sema-agent/core 7.33.8 → 7.34.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 (197) hide show
  1. package/CHANGELOG.md +339 -0
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/open-responses.js +1 -0
  11. package/dist/brain/openai.js +1 -0
  12. package/dist/brain/stream-engine.d.ts +3 -0
  13. package/dist/brain/stream-engine.js +2 -1
  14. package/dist/core/a2a.d.ts +3 -1
  15. package/dist/core/a2a.js +1 -1
  16. package/dist/core/ask-origin.d.ts +351 -154
  17. package/dist/core/ask-origin.js +223 -45
  18. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  19. package/dist/core/ask-unresolvable-notice.js +4 -3
  20. package/dist/core/auto-mode-arming.d.ts +17 -4
  21. package/dist/core/auto-mode-arming.js +31 -6
  22. package/dist/core/auto-mode-defaults.d.ts +16 -7
  23. package/dist/core/auto-mode-defaults.js +2 -1
  24. package/dist/core/auto-mode-directive.d.ts +18 -30
  25. package/dist/core/auto-mode-directive.js +9 -18
  26. package/dist/core/auto-mode-prompt.d.ts +62 -1
  27. package/dist/core/auto-mode-prompt.js +42 -4
  28. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  29. package/dist/core/auto-mode-rebuild.js +1 -0
  30. package/dist/core/auto-mode.d.ts +32 -3
  31. package/dist/core/auto-mode.js +80 -41
  32. package/dist/core/checkpoint-store.d.ts +50 -35
  33. package/dist/core/checkpoint-store.js +58 -3
  34. package/dist/core/child-clamp-seats.js +1 -0
  35. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  36. package/dist/core/classifier-unavailable-arm.js +17 -0
  37. package/dist/core/effective-path-target.d.ts +12 -0
  38. package/dist/core/effective-path-target.js +18 -7
  39. package/dist/core/engine-notice.d.ts +53 -2
  40. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  41. package/dist/core/fs-write-gate-policy.js +4 -1
  42. package/dist/core/gate-fold.js +17 -1
  43. package/dist/core/gate-lanes.js +111 -46
  44. package/dist/core/gate-outcome.d.ts +40 -7
  45. package/dist/core/gate-outcome.js +26 -4
  46. package/dist/core/governance-codes.d.ts +1 -1
  47. package/dist/core/governance-codes.js +12 -0
  48. package/dist/core/hooks.d.ts +50 -29
  49. package/dist/core/hooks.js +1 -3
  50. package/dist/core/mcp.js +1 -1
  51. package/dist/core/mode-policy.d.ts +182 -0
  52. package/dist/core/mode-policy.js +179 -0
  53. package/dist/core/oracle-isolation.d.ts +3 -3
  54. package/dist/core/permission-rule-model.d.ts +16 -3
  55. package/dist/core/permission-rule-model.js +36 -3
  56. package/dist/core/permission-rule-provider.d.ts +22 -11
  57. package/dist/core/permission-rule-provider.js +17 -7
  58. package/dist/core/permission-rules.d.ts +23 -1
  59. package/dist/core/permission-rules.js +35 -8
  60. package/dist/core/persisted-rule-arms.d.ts +10 -9
  61. package/dist/core/persisted-rule-arms.js +9 -6
  62. package/dist/core/present-plan-tool.d.ts +1 -1
  63. package/dist/core/present-plan-tool.js +3 -3
  64. package/dist/core/read-only-shell.d.ts +19 -19
  65. package/dist/core/read-only-shell.js +38 -12
  66. package/dist/core/retired-keys.d.ts +9 -3
  67. package/dist/core/retired-keys.js +5 -1
  68. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  69. package/dist/core/runner/classifier-ladder.js +29 -0
  70. package/dist/core/runner/contracts.d.ts +37 -35
  71. package/dist/core/runner/denial-limit-arms.d.ts +30 -7
  72. package/dist/core/runner/denial-limit-arms.js +15 -9
  73. package/dist/core/runner/env-capability-census.js +1 -1
  74. package/dist/core/runner/gate-exit.d.ts +30 -4
  75. package/dist/core/runner/gate-exit.js +28 -7
  76. package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
  77. package/dist/core/runner/permission-rule-lanes.js +44 -40
  78. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  79. package/dist/core/runner/persisted-path-rows.js +89 -0
  80. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  81. package/dist/core/runner/prepare-artifact.js +9 -3
  82. package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
  83. package/dist/core/runner/prepare-ask-lane.js +1 -1
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  85. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  86. package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
  87. package/dist/core/runner/prepare-gate-stations.js +32 -19
  88. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  89. package/dist/core/runner/prepare-hands-readface.js +64 -53
  90. package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
  91. package/dist/core/runner/prepare-inherited-gate.js +37 -23
  92. package/dist/core/runner/prepare-park-ask.d.ts +2 -2
  93. package/dist/core/runner/prepare-park-ask.js +2 -1
  94. package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
  95. package/dist/core/runner/prepare-policy-chain.js +39 -12
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  97. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  98. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  99. package/dist/core/runner/prepare-run-refs.js +9 -4
  100. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  101. package/dist/core/runner/prepare-safety-scan.js +19 -3
  102. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  103. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  104. package/dist/core/runner/prepare-task.js +51 -22
  105. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  106. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  107. package/dist/core/runner/read-root-grant.d.ts +9 -0
  108. package/dist/core/runner/read-root-grant.js +12 -0
  109. package/dist/core/runner/retired-runner-deps.js +1 -0
  110. package/dist/core/runner/run-identity-wiring.js +1 -1
  111. package/dist/core/runner/run-leg.js +24 -15
  112. package/dist/core/runner/run-notification-lane.js +1 -0
  113. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  114. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  115. package/dist/core/runner/run-turn-boundary.js +1 -1
  116. package/dist/core/runner/runtask.js +3 -0
  117. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  118. package/dist/core/runner/stream-mode-verb.js +19 -0
  119. package/dist/core/runner/turn-attachments.d.ts +3 -3
  120. package/dist/core/runner/turn-attachments.js +1 -1
  121. package/dist/core/runner/write-protection-station.d.ts +7 -1
  122. package/dist/core/runner/write-protection-station.js +6 -3
  123. package/dist/core/runner-deps.d.ts +31 -34
  124. package/dist/core/runtime-caps.d.ts +12 -3
  125. package/dist/core/safety-axis-vocab.d.ts +2 -2
  126. package/dist/core/safety-axis-vocab.js +1 -1
  127. package/dist/core/sensitive-path-policy.d.ts +4 -2
  128. package/dist/core/sensitive-path-policy.js +39 -30
  129. package/dist/core/task-event.d.ts +1 -1
  130. package/dist/core/task-result.d.ts +1 -1
  131. package/dist/core/task-spec.d.ts +41 -89
  132. package/dist/core/task-stream.d.ts +16 -0
  133. package/dist/core/terminal-cause.js +1 -0
  134. package/dist/core/tighten-task-spec.d.ts +2 -2
  135. package/dist/core/tighten-task-spec.js +12 -14
  136. package/dist/core/tool-catalog-entries.js +4 -3
  137. package/dist/core/tool-policy.d.ts +54 -25
  138. package/dist/core/tool-policy.js +43 -2
  139. package/dist/core/tool-registry.d.ts +2 -0
  140. package/dist/core/tool-registry.js +3 -0
  141. package/dist/core/tool-spec.d.ts +8 -9
  142. package/dist/core/trace.d.ts +7 -6
  143. package/dist/core/types.d.ts +2 -1
  144. package/dist/core/wiring-manifest.d.ts +31 -5
  145. package/dist/core/wiring-manifest.js +28 -1
  146. package/dist/core/write-protect.d.ts +11 -1
  147. package/dist/core/write-protect.js +26 -15
  148. package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
  149. package/dist/engine/execution-env/node-execution-env.js +11 -1
  150. package/dist/engine/harness/types.d.ts +6 -7
  151. package/dist/engine/llm/types.d.ts +2 -4
  152. package/dist/engine/loop/types.d.ts +7 -0
  153. package/dist/index.d.ts +15 -5
  154. package/dist/index.js +12 -3
  155. package/dist/orchestration/workflow-governance.js +1 -1
  156. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  157. package/dist/orchestration/workflow-size-guideline.js +2 -2
  158. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  159. package/dist/tools/artifact/artifact-tool.js +16 -1
  160. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  161. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  162. package/dist/tools/fs/containment-face.d.ts +9 -3
  163. package/dist/tools/fs/containment-face.js +2 -0
  164. package/dist/tools/fs/file-read-check.d.ts +49 -0
  165. package/dist/tools/fs/file-read-check.js +71 -0
  166. package/dist/tools/fs/fs-bash.d.ts +3 -3
  167. package/dist/tools/fs/fs-read.d.ts +1 -0
  168. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  169. package/dist/tools/fs/fs-search-tools.js +4 -4
  170. package/dist/tools/fs/fs-shared.js +20 -6
  171. package/dist/tools/fs/fs-write.d.ts +4 -3
  172. package/dist/tools/fs/fs-write.js +9 -9
  173. package/dist/tools/fs/index.d.ts +39 -3
  174. package/dist/tools/fs/index.js +38 -15
  175. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  176. package/dist/tools/fs/read-deny-home.js +225 -0
  177. package/dist/tools/fs/read-deny.d.ts +176 -34
  178. package/dist/tools/fs/read-deny.js +268 -61
  179. package/dist/tools/fs/safety.d.ts +6 -1
  180. package/dist/tools/fs/safety.js +18 -5
  181. package/dist/tools/fs/search.d.ts +3 -0
  182. package/dist/tools/fs/search.js +47 -9
  183. package/dist/tools/fs/shell-read-check.d.ts +42 -11
  184. package/dist/tools/fs/shell-read-check.js +188 -13
  185. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  186. package/dist/tools/fs/shell-read-establish.js +134 -21
  187. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  188. package/dist/tools/fs/shell-read-identity.js +21 -0
  189. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  190. package/dist/tools/fs/shell-read-judge.js +43 -18
  191. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  192. package/dist/tools/fs/shell-read-plan.js +26 -4
  193. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  194. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  195. package/package.json +1 -1
  196. package/test/export-surface.snapshot.json +103 -11
  197. package/wire-changes/core@7.34.0.json +2621 -0
@@ -11,6 +11,7 @@ export * from "./fs-search-tools.js";
11
11
  export * from "./bash-readonly-classifier.js";
12
12
  export * from "./fs-bash.js";
13
13
  export * from "./read-deny.js";
14
+ export { readDenyHomesOf } from "./read-deny-home.js";
14
15
  import type { ShellReadAdmission } from "./shell-read-check.js";
15
16
  export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
16
17
  import { type ReadDenyEntry } from "./read-deny.js";
@@ -55,6 +56,10 @@ export interface HandsToolkitOptions {
55
56
  * consumer mounting the read-only band outside the Runner must run `runToolGate` with a station of its own
56
57
  * (`createShellReadStation`). */
57
58
  shellReadAdmission?: ShellReadAdmission;
59
+ /** The gate's file read station's ADMISSION (`FileReadStation.admitted`, file-read-check.ts), handed to Read / Grep /
60
+ * Glob: an out-of-root target the gate put to a question and then let through is read once, on the canonical key the
61
+ * station judged. ABSENT ⇒ no station (a library-direct toolkit): the tools refuse every out-of-root target. */
62
+ readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean;
58
63
  /** Commit-message attribution trailer for the `bash` git protocol. Default NONE — attribution is a
59
64
  * deployment identity asset, so the branded deployment sets it; pass `"Name <email>"` to advertise
60
65
  * one, `false` (≡ unset) for no trailer section. */
@@ -167,11 +172,10 @@ export interface HandsToolkitOptions {
167
172
  * time. The write faces are untouched (their guard is createSensitivePathPolicy). */
168
173
  readDenyPatterns?: readonly ReadDenyEntry[];
169
174
  /** The deployment's built-in-table TIER selection ({@link import("./read-deny.js").ReadDenyBuiltinConfig}
170
- * `tiers` key) — the ON/OFF switch for the built-in table, OFF unless this seat says otherwise:
175
+ * `tiers` key) — the switch for the built-in table:
171
176
  * EXACTLY the listed tiers of {@link import("./read-deny.js").READ_FACE_BUILTIN_DENY_TABLE}
172
177
  * are active (`[]` = none — explicit and legal); absent = the default selection
173
- * ({@link import("./read-deny.js").READ_DENY_DEFAULT_TIERS}), which is EMPTY — an unconfigured
174
- * mount activates no built-in row.
178
+ * ({@link import("./read-deny.js").READ_DENY_DEFAULT_TIERS}: `credentials` and the home-anchored `agent-config`).
175
179
  * Composes with {@link readDenyBuiltinExclude} (tier selection first, then row removal) and never
176
180
  * touches the additions seats. Unknown tier names throw at wiring time. Deployment seat ONLY —
177
181
  * deliberately not a TaskSpec key, so no task can widen below its deployment. */
@@ -181,6 +185,22 @@ export interface HandsToolkitOptions {
181
185
  * of an inactive tier is a satisfied intent (no-op); a name matching NO table row throws at wiring
182
186
  * time (never silently ignored). Deployment seat ONLY, same clamp argument as the tiers key. */
183
187
  readDenyBuiltinExclude?: readonly string[];
188
+ /** The spellings of the execution environment's home a home-anchored deny row (`~/…`) is placed under
189
+ * ({@link import("./read-deny-home.js").readDenyHomesOf}: the declared home and its canonical spelling). Absolute path
190
+ * strings only — any other shape throws at wiring time, naming the seat. The Runner passes the spellings its own judge
191
+ * was compiled with. */
192
+ readDenyHomes?: readonly string[];
193
+ /** Where each home-anchored row's literal head leads beyond the home's spellings
194
+ * ({@link import("./read-deny-home.js").readDenyHomeLinksOf}, keyed by the row's pattern) — the Runner passes the
195
+ * locations its own judge was compiled with, so the band and the gate judge one placement.
196
+ * Unless a mount carries both, the band resolves the placement itself the way the Runner does (the homes it carries
197
+ * are used as given; {@link import("./read-deny-home.js").placeReadDenyAtHome}), and its read tools (Read / Grep /
198
+ * Glob / RepoMap) wait for that placement before they judge anything. A placement that fails leaves the home-anchored
199
+ * rows held anywhere. */
200
+ readDenyHomeLinks?: ReadonlyMap<string, readonly string[]>;
201
+ /** The permission-rule rows of the deny table ({@link import("./read-deny.js").PathRuleDenyRow}, `ToolPolicy.pathDenyRules`):
202
+ * the read-face rows join the structured read faces' judge beside the segment rows. The Runner passes the leg's own. */
203
+ pathDenyRules?: readonly import("./read-deny.js").PathRuleDenyRow[];
184
204
  /** The READ-face containment state for the structured read faces
185
205
  * ({@link import("./containment-face.js").ContainmentFace}; resolved through the SAME
186
206
  * {@link import("./containment-face.js").resolveContainmentFace} order prepare-task uses). Absent ⇒ the
@@ -202,6 +222,10 @@ export interface HandsToolkitOptions {
202
222
  * wins the same way the read seat's does (that mount carries no write faces at all), announced as
203
223
  * `config.write_face_deployment_clamped`. Never affects the read faces. */
204
224
  writeFace?: ContainmentFace;
225
+ /** A runner's LIVE write face — its resolution, which reads the permission mode's face on every call. Present ⇒
226
+ * the write tools read it per call and {@link writeFace} is not consulted (the runner resolved and screened the
227
+ * seats itself); absent ⇒ the toolkit's own resolution of {@link writeFace}, fixed at creation. */
228
+ writeFaceLive?: () => ContainmentFace;
205
229
  /** The session's reminder provenance mark: the band's engine-minted
206
230
  * `<system-reminder>` open tags (the Read cyber/dedup/offset/empty reminders and the gh
207
231
  * rate-limit hint) carry it as `mark="…"`, matching the system-prompt declaration the Runner
@@ -224,6 +248,18 @@ export interface HandsToolkitOptions {
224
248
  * predicate {@link createHandsToolkit} branches on and prepare-task plans by (the memory phase must know, before the
225
249
  * band exists, whether the instruction-taught write channel will be there). `readOnly` is the verifier boundary. */
226
250
  export declare function handsWriteHalfMounts(opts: Pick<HandsToolkitOptions, "readOnly">): boolean;
251
+ /** The engine's background-output read exemption over one toolkit's seats: the exact registered output file of a
252
+ * background command, visible to the caller's own owner / scope / session, on THIS execution env — the file the
253
+ * launch receipt tells the model to Read. Shared by the Read tool's fence and the file read station, so the station
254
+ * puts no question where the fence would admit. */
255
+ export declare function backgroundOutputReadExemption(registry: TaskRegistry | undefined, seat: {
256
+ readonly taskOwner?: string;
257
+ readonly taskScope?: string;
258
+ readonly sessionId?: string;
259
+ }, env: ExecutionEnv): ((canonicalKey: string, c?: {
260
+ taskId?: string;
261
+ principal?: string;
262
+ }) => boolean) | undefined;
227
263
  /**
228
264
  * Build the per-task hand tool band over an injected env + fresh per-task read state.
229
265
  * `rootCanonical` is the already-canonicalized containment root (prepare-task resolves it once). Normal
@@ -11,9 +11,12 @@ export * from "./fs-search-tools.js";
11
11
  export * from "./bash-readonly-classifier.js";
12
12
  export * from "./fs-bash.js";
13
13
  export * from "./read-deny.js";
14
+ export { readDenyHomesOf } from "./read-deny-home.js";
14
15
  import { BASH_READONLY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
15
16
  export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
16
- import { compileReadDeny } from "./read-deny.js";
17
+ import { checkedReadDenyHomes, compileReadDeny, compileWriteDeny, readDenyBuiltinConfigOf, readDenyPatternRun } from "./read-deny.js";
18
+ import { withGateTimeout } from "../../core/gate-bounded-await.js";
19
+ import { placeReadDenyAtHome, readDenyPlacedLater } from "./read-deny-home.js";
17
20
  import { deploymentFaceClampNotice, resolveContainmentFace } from "./containment-face.js";
18
21
  export * from "./containment-face.js";
19
22
  import {} from "./fs-shared.js";
@@ -24,22 +27,41 @@ import { createBashTool, createBashReadonlyTool, createEnvTaskOutputTool, create
24
27
  export function handsWriteHalfMounts(opts) {
25
28
  return opts.readOnly !== true;
26
29
  }
30
+ export function backgroundOutputReadExemption(registry, seat, env) {
31
+ if (registry === undefined)
32
+ return undefined;
33
+ return (canonicalKey, c) => registry.isBackgroundOutputFile(canonicalKey, { owner: c?.taskId ?? seat.taskOwner, scope: c?.principal ?? seat.taskScope, ...(seat.sessionId !== undefined ? { sessionId: seat.sessionId } : {}) }, env);
34
+ }
27
35
  export function createHandsToolkit(env, readFileState, rootCanonical, opts = {}) {
28
36
  const { includeShell = false, readOnly = false, bashReadonlyAllow, commitCoAuthor = false, mountBackgroundTaskTools = true, additionalRoots, additionalReadRoots, } = opts;
29
37
  const readFaceRoots = additionalReadRoots === undefined ? additionalRoots : () => (additionalReadRoots.length === 0 ? additionalRoots : [...(additionalRoots ?? []), ...additionalReadRoots]);
30
38
  const cwdRef = opts.cwdRef ?? { current: rootCanonical };
31
- const bgReadRegistry = opts.taskRegistry;
32
- const bgOutputReadExemption = bgReadRegistry === undefined
39
+ const bgOutput = backgroundOutputReadExemption(opts.taskRegistry, opts, env);
40
+ const readAdmission = opts.readAdmission;
41
+ const bgOutputReadExemption = bgOutput === undefined && readAdmission === undefined
33
42
  ? undefined
34
- : (canonicalKey, c) => bgReadRegistry.isBackgroundOutputFile(canonicalKey, {
35
- owner: c.taskId ?? opts.taskOwner,
36
- scope: c.principal ?? opts.taskScope,
37
- ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
38
- }, env);
39
- const readDeny = compileReadDeny(opts.readDenyPatterns ?? [], "HandsToolkitOptions.readDenyPatterns", {
40
- ...(opts.readDenyBuiltinTiers !== undefined ? { tiers: opts.readDenyBuiltinTiers } : {}),
41
- ...(opts.readDenyBuiltinExclude !== undefined ? { exclude: opts.readDenyBuiltinExclude } : {}),
42
- });
43
+ : (canonicalKey, c) => readAdmission?.(c.toolCallId, canonicalKey) === true || (bgOutput?.(canonicalKey, c) ?? false);
44
+ const readDenyAdditions = (opts.readDenyPatterns ?? []).map((e) => (typeof e === "object" && e !== null ? { ...e } : e));
45
+ const readDenyRules = opts.pathDenyRules === undefined ? undefined : [...opts.pathDenyRules];
46
+ const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: opts.readDenyBuiltinTiers, exclude: opts.readDenyBuiltinExclude });
47
+ const readDenyHomes = opts.readDenyHomes === undefined ? undefined : [...checkedReadDenyHomes(opts.readDenyHomes)];
48
+ const compileAt = (homes, linked) => compileReadDeny(readDenyAdditions, "HandsToolkitOptions.readDenyPatterns", readDenyBuiltinCfg, readDenyRules, homes, linked);
49
+ const unplaced = compileAt([]);
50
+ const carried = readDenyHomes !== undefined && opts.readDenyHomeLinks !== undefined;
51
+ const placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored);
52
+ const late = carried || placesNothing ? undefined : readDenyPlacedLater(unplaced, () => placeReadDenyAtHome(env, compileAt, readDenyHomes));
53
+ const readDeny = late?.matcher ?? (carried ? compileAt(readDenyHomes, opts.readDenyHomeLinks) : unplaced);
54
+ const afterPlacement = (tool) => late === undefined
55
+ ? tool
56
+ : {
57
+ ...tool,
58
+ execute: async (...args) => {
59
+ await withGateTimeout(late.ready(), undefined, args[2], "readDenyPlacement").catch(() => undefined);
60
+ return await tool.execute(...args);
61
+ },
62
+ };
63
+ const writeDenyCompiled = compileWriteDeny(readDenyRules);
64
+ const writeDeny = writeDenyCompiled.ruleRows.length > 0 ? writeDenyCompiled : undefined;
43
65
  const readFace = resolveContainmentFace({
44
66
  kind: "read",
45
67
  depsFace: opts.readFace,
@@ -56,13 +78,14 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
56
78
  fullShellReachable: includeShell && !readOnly,
57
79
  onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("write")),
58
80
  });
81
+ const writeFaceOf = opts.writeFaceLive ?? (() => writeFace);
59
82
  const tools = [
60
- createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts),
83
+ afterPlacement(createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts)),
61
84
  ];
62
85
  if (handsWriteHalfMounts({ readOnly })) {
63
- tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFace), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFace), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFace));
86
+ tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny));
64
87
  }
65
- tools.push(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace));
88
+ tools.push(afterPlacement(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace)));
66
89
  if (includeShell) {
67
90
  tools.push(readOnly
68
91
  ? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
@@ -0,0 +1,93 @@
1
+ import type { ExecutionEnv } from "../../internal/harness-types.js";
2
+ import { type PathFamily } from "./safety.js";
3
+ import { type ReadDenyMatcher } from "./read-deny.js";
4
+ /** The environment's DECLARED home as an anchor list: `[homeDir]` when it declares an absolute one, else `[]` (no
5
+ * home — a home-anchored row then holds its run anywhere, see `compileReadDeny`). */
6
+ export declare function declaredReadDenyHomes(env: Pick<ExecutionEnv, "homeDir">): string[];
7
+ /** The declared home and, where the environment's canonical spelling of it differs (a home reached through a
8
+ * symlink), that spelling too: the faces judge canonical keys beside the spelled views, so a row placed only at
9
+ * the spelled home would never meet a canonical key. A home the environment cannot resolve keeps its spelling. */
10
+ export declare function readDenyHomesOf(env: Pick<ExecutionEnv, "homeDir" | "canonicalPath">, signal?: AbortSignal): Promise<string[]>;
11
+ /**
12
+ * Where the home-anchored rows of a table LEAD beyond the home's own spellings, keyed by the row's pattern — the
13
+ * `linked` argument of `compileReadDeny`. For each row, its LITERAL HEAD (`readDenyRunHead`: the run's segments before
14
+ * the first one carrying `*`) is spelled under each home and resolved the way the read faces mint their keys
15
+ * (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined); a location that differs from every
16
+ * home's own spelling of the head is where one of the head's segments is a link, and the row is placed there too.
17
+ * · a head that resolves to its own spelling under a home adds nothing (the row is already placed there);
18
+ * · a head that cannot be resolved (an unanswered or refused probe, a probe that throws) adds nothing, and the answer
19
+ * is not WHOLE (`whole: false`): the row stays placed at the home's spellings until the mount places again;
20
+ * · a row whose head is empty (`~/.sema.*`) has no location to resolve and adds nothing;
21
+ * · the heads are resolved together, so one placement waits at most one probe bound;
22
+ * · read per placement — a link created afterwards is followed from the next placement on.
23
+ */
24
+ export declare function readDenyHomeLinksOf(env: ExecutionEnv, entries: readonly {
25
+ readonly pattern: string;
26
+ readonly caseSensitive?: boolean;
27
+ }[], homes: readonly string[], signal?: AbortSignal): Promise<{
28
+ readonly linked: Map<string, string[]>;
29
+ readonly whole: boolean;
30
+ }>;
31
+ /** Where an absolute location LEADS in one environment — resolved the way the read and write faces mint their keys
32
+ * (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined). `undefined` when the environment
33
+ * does not answer (a refused or failed probe, one that throws). The one resolution a deny row's placement reads: a
34
+ * home-anchored row's head ({@link readDenyHomeLinksOf}) and a rule row's head ({@link whereItsAnsweredPrefixLeads}). */
35
+ export declare function whereItLeads(env: ExecutionEnv, at: string, family?: PathFamily, signal?: AbortSignal): Promise<string | undefined>;
36
+ /** {@link whereItLeads} with a floor: where the environment does not answer for `at` (a probe that fails), the longest
37
+ * prefix it does answer for is resolved and the rest rejoined — a rule row's head below a base that resolves is never
38
+ * placed narrower than that base's own canonical spelling. The whole walk waits at most one bounded-await deadline.
39
+ * `undefined` when no prefix answers in time. */
40
+ export declare function whereItsAnsweredPrefixLeads(env: ExecutionEnv, at: string, family?: PathFamily, signal?: AbortSignal): Promise<string | undefined>;
41
+ /** One mount's read-deny judge with its home-anchored rows placed: at the home's spellings (`homes` when the mount
42
+ * carries them, else {@link readDenyHomesOf}, under the tree's bounded-await deadline — an environment that cannot
43
+ * answer rejects the placement instead of holding it open) and where their heads lead ({@link readDenyHomeLinksOf}).
44
+ * `whole` says whether the home and every head answered. A home the environment did not answer for places nothing:
45
+ * the rows are held anywhere (`compile([])`, the rule for a row that cannot be placed) rather than at a spelling that
46
+ * may miss the home's canonical one. `compile` is the mount's own compile over the two; the rows' patterns are read
47
+ * off its compile at the homes alone. */
48
+ export declare function placeReadDenyAtHome(env: ExecutionEnv, compile: (homes: readonly string[], linked?: ReadonlyMap<string, readonly string[]>) => ReadDenyMatcher, homes?: readonly string[], signal?: AbortSignal): Promise<{
49
+ readonly matcher: ReadDenyMatcher;
50
+ readonly homes: readonly string[];
51
+ readonly linked: ReadonlyMap<string, readonly string[]>;
52
+ readonly whole: boolean;
53
+ }>;
54
+ /**
55
+ * A judge for a mount that is built before its placement settles (the hands band is built synchronously): the table as
56
+ * `unplaced` compiles it — home-anchored rows held anywhere, the rule for a row that cannot be placed — until a
57
+ * placement settles, then the placed table. `place` starts at once; `ready()` settles when the latest placement has
58
+ * (either way) and, while no placement has come back WHOLE (a failed one, or one with a head that did not answer),
59
+ * places again first — an unanswered lookup is never kept as the answer. A failed placement leaves the table it
60
+ * replaced (the rows held anywhere, or an earlier placement), never unplaced-and-narrow. The mount holds its read faces
61
+ * until `ready()`.
62
+ */
63
+ export declare function readDenyPlacedLater(unplaced: ReadDenyMatcher, place: () => Promise<{
64
+ readonly matcher: ReadDenyMatcher;
65
+ readonly whole: boolean;
66
+ }>): {
67
+ readonly matcher: ReadDenyMatcher;
68
+ readonly ready: () => Promise<void>;
69
+ };
70
+ /** The environment surface a home view reads. */
71
+ export type HomeViewEnv = Pick<ExecutionEnv, "homeDir" | "canonicalPath" | "fileIdentity">;
72
+ /**
73
+ * A path's HOME VIEW — the one reader every face that judges a deny row by location uses (the structured read and
74
+ * write tools' target judgment, the shell station's establishment, the search walker, the ripgrep leg's output check
75
+ * and its scope choice). The view of a path is the path itself when it IS a directory on the home's chain (the
76
+ * canonical home and each of its ancestors, matched by {@link ExecutionEnv.fileIdentity}) — that directory's
77
+ * canonical spelling — and otherwise its parent's view with its own name rejoined. `of` answers the view only where
78
+ * it differs from the path as spelled; `undefined` otherwise, and wherever it cannot be read:
79
+ * · the environment declares no home, no `fileIdentity`, or its home does not resolve ⇒ no view (the spelling form);
80
+ * a home, or a level of its chain, that could not be read is asked again by the next judgment — an unanswered
81
+ * lookup is never kept as the answer;
82
+ * · a level that does not exist (a write's missing tail) has no identity of its own and takes its parent's view;
83
+ * · any other failure at a level stops the view there: the path is judged as spelled, never wider.
84
+ * `leaf: "file"` skips the path's own identity (a file is never a directory on the chain).
85
+ */
86
+ export interface HomeViews {
87
+ of(path: string, opts?: {
88
+ readonly leaf?: "file";
89
+ }): Promise<string | undefined>;
90
+ }
91
+ /** The home views of one judgment (a tool call, a command's establishment, one search): a view per directory is read
92
+ * once and shared by every path below it. */
93
+ export declare function homeViewsOf(env: HomeViewEnv, signal?: AbortSignal): HomeViews;
@@ -0,0 +1,225 @@
1
+ import { canonicalizeTarget, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
2
+ import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "../../core/gate-bounded-await.js";
3
+ import { readDenyPatternRun, readDenyRunHead } from "./read-deny.js";
4
+ export function declaredReadDenyHomes(env) {
5
+ const home = env.homeDir;
6
+ return home !== undefined && isAbsolutePathForm(home) ? [home] : [];
7
+ }
8
+ export async function readDenyHomesOf(env, signal) {
9
+ return (await readDenyHomesAnswered(env, signal)).homes;
10
+ }
11
+ async function readDenyHomesAnswered(env, signal) {
12
+ const home = declaredReadDenyHomes(env)[0];
13
+ if (home === undefined)
14
+ return { homes: [], whole: true };
15
+ const canonical = await env.canonicalPath(home, signal);
16
+ if (canonical.ok)
17
+ return { homes: canonical.value !== home ? [home, canonical.value] : [home], whole: true };
18
+ return { homes: [home], whole: canonical.error.code === "not_found" || canonical.error.code === "not_directory" };
19
+ }
20
+ export async function readDenyHomeLinksOf(env, entries, homes, signal) {
21
+ const rows = [];
22
+ const familyOf = new Map();
23
+ for (const { pattern, caseSensitive } of entries) {
24
+ const { homeAnchored, run } = readDenyPatternRun(pattern);
25
+ if (!homeAnchored)
26
+ continue;
27
+ const head = run.split("/").filter(Boolean).slice(0, readDenyRunHead(run)).join("/");
28
+ if (head.length === 0)
29
+ continue;
30
+ const spelled = homes.map((home) => {
31
+ const family = pathFamilyOf({ root: home });
32
+ const at = normalizeAbsPathLexically(joinForFamily(family, home, head));
33
+ familyOf.set(at, family);
34
+ return at;
35
+ });
36
+ rows.push({ pattern, caseSensitive: caseSensitive === true, spelled });
37
+ }
38
+ const resolved = new Map(await Promise.all([...familyOf].map(async ([at, family]) => [at, await whereItLeads(env, at, family, signal)])));
39
+ const out = new Map();
40
+ const whole = [...resolved.values()].every((location) => location !== undefined);
41
+ for (const { pattern, caseSensitive, spelled } of rows) {
42
+ const fold = (p) => (caseSensitive ? p : p.replace(/[A-Z]/g, (c) => c.toLowerCase()));
43
+ const own = new Set(spelled.map(fold));
44
+ const locations = [];
45
+ for (const at of spelled) {
46
+ const location = resolved.get(at);
47
+ if (location !== undefined && !own.has(fold(location)) && !locations.includes(location))
48
+ locations.push(location);
49
+ }
50
+ if (locations.length > 0)
51
+ out.set(pattern, locations);
52
+ }
53
+ return { linked: out, whole };
54
+ }
55
+ export async function whereItLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
56
+ try {
57
+ const key = await canonicalizeTarget(env, at, signal, family);
58
+ return key.ok ? normalizeAbsPathLexically(key.key) : undefined;
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ export async function whereItsAnsweredPrefixLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
65
+ const walk = async () => {
66
+ const rest = [];
67
+ for (let here = normalizeAbsPathLexically(at);;) {
68
+ const led = await whereItLeads(env, here, family, signal);
69
+ if (led !== undefined)
70
+ return rest.reduce((p, name) => joinForFamily(family, p, name), led);
71
+ const parent = normalizeAbsPathLexically(`${here}/..`);
72
+ if (parent === here)
73
+ return undefined;
74
+ rest.unshift(here.slice(parent.length).replace(/^[\\/]+/, ""));
75
+ here = parent;
76
+ }
77
+ };
78
+ return await withGateTimeout(walk(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "ruleRowHead").catch(() => undefined);
79
+ }
80
+ export async function placeReadDenyAtHome(env, compile, homes, signal) {
81
+ let at = homes;
82
+ if (at === undefined) {
83
+ const answer = await withGateTimeout(readDenyHomesAnswered(env, signal), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "readDenyHomes");
84
+ if (!answer.whole)
85
+ return { matcher: compile([]), homes: [], linked: new Map(), whole: false };
86
+ at = answer.homes;
87
+ }
88
+ const atHomes = compile(at);
89
+ const { linked, whole } = await readDenyHomeLinksOf(env, atHomes.entries, at, signal);
90
+ return { matcher: linked.size === 0 ? atHomes : compile(at, linked), homes: at, linked, whole };
91
+ }
92
+ export function readDenyPlacedLater(unplaced, place) {
93
+ let current = unplaced;
94
+ let whole = false;
95
+ let pending;
96
+ const again = () => (pending ??= place()
97
+ .then((placed) => {
98
+ current = placed.matcher;
99
+ whole = placed.whole;
100
+ }, () => undefined)
101
+ .finally(() => {
102
+ pending = undefined;
103
+ }));
104
+ void again();
105
+ const ready = async () => {
106
+ if (pending !== undefined)
107
+ await pending;
108
+ if (!whole)
109
+ await again();
110
+ };
111
+ const matcher = {
112
+ get entries() {
113
+ return current.entries;
114
+ },
115
+ get ruleRows() {
116
+ return current.ruleRows;
117
+ },
118
+ matchPath: (path, opts) => current.matchPath(path, opts),
119
+ matchTarget: (canonicalKey, lexicalView, opts) => current.matchTarget(canonicalKey, lexicalView, opts),
120
+ get rgExclusionGlobs() {
121
+ return current.rgExclusionGlobs;
122
+ },
123
+ get rgProbeGlobs() {
124
+ return current.rgProbeGlobs;
125
+ },
126
+ unexcludableUnder: (scope) => current.unexcludableUnder?.(scope) ?? false,
127
+ rowAtOrBelow: (path) => current.rowAtOrBelow?.(path) ?? null,
128
+ };
129
+ return { matcher, ready };
130
+ }
131
+ const HOME_CHAINS = new WeakMap();
132
+ function homeChainOf(env) {
133
+ const identity = env.fileIdentity;
134
+ const home = declaredReadDenyHomes(env)[0];
135
+ if (typeof identity !== "function" || home === undefined)
136
+ return undefined;
137
+ let perEnv = HOME_CHAINS.get(env);
138
+ if (perEnv === undefined)
139
+ HOME_CHAINS.set(env, (perEnv = new Map()));
140
+ const known = perEnv;
141
+ let chain = known.get(home);
142
+ if (chain === undefined) {
143
+ const reading = withGateTimeout((async () => {
144
+ const canonical = await env.canonicalPath(home);
145
+ if (!canonical.ok)
146
+ return { index: undefined, whole: false };
147
+ const out = new Map();
148
+ let whole = true;
149
+ for (let dir = normalizeAbsPathLexically(canonical.value);;) {
150
+ const id = await identityOf(env, identity, dir);
151
+ if (id?.ok === true) {
152
+ if (!out.has(id.value))
153
+ out.set(id.value, dir);
154
+ }
155
+ else
156
+ whole = false;
157
+ const parent = normalizeAbsPathLexically(`${dir}/..`);
158
+ if (parent === dir)
159
+ break;
160
+ dir = parent;
161
+ }
162
+ return { index: out.size > 0 ? out : undefined, whole };
163
+ })(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, undefined, "homeChain").catch(() => ({ index: undefined, whole: false }));
164
+ chain = reading;
165
+ known.set(home, reading);
166
+ const drop = () => {
167
+ if (known.get(home) === reading)
168
+ known.delete(home);
169
+ };
170
+ void reading.then((c) => (c.whole ? undefined : drop()));
171
+ }
172
+ return chain.then((c) => c.index);
173
+ }
174
+ async function identityOf(env, identity, path, signal) {
175
+ try {
176
+ return await identity.call(env, path, signal);
177
+ }
178
+ catch {
179
+ return undefined;
180
+ }
181
+ }
182
+ export function homeViewsOf(env, signal) {
183
+ const chain = homeChainOf(env);
184
+ const identity = env.fileIdentity;
185
+ const views = new Map();
186
+ const viewOf = (path, leafIsFile) => {
187
+ const key = `${leafIsFile ? "f" : "d"}:${path}`;
188
+ let v = views.get(key);
189
+ if (v === undefined) {
190
+ v = (async () => {
191
+ const index = await chain;
192
+ if (index === undefined || typeof identity !== "function")
193
+ return undefined;
194
+ if (!leafIsFile) {
195
+ const id = await identityOf(env, identity, path, signal);
196
+ if (id === undefined)
197
+ return undefined;
198
+ if (id.ok) {
199
+ const at = index.get(id.value);
200
+ if (at !== undefined)
201
+ return at;
202
+ }
203
+ else if (id.error.code !== "not_found" && id.error.code !== "not_directory")
204
+ return undefined;
205
+ }
206
+ const parent = normalizeAbsPathLexically(`${path}/..`);
207
+ if (parent === path)
208
+ return undefined;
209
+ const up = await viewOf(parent, false);
210
+ return up === undefined ? undefined : joinForFamily(pathFamilyOf({ root: path }), up, path.slice(parent.length).replace(/^[\\/]+/, ""));
211
+ })();
212
+ views.set(key, v);
213
+ }
214
+ return v;
215
+ };
216
+ return {
217
+ async of(path, opts) {
218
+ if (chain === undefined || !isAbsolutePathForm(path))
219
+ return undefined;
220
+ const spelled = normalizeAbsPathLexically(path);
221
+ const view = await viewOf(spelled, opts?.leaf === "file");
222
+ return view === undefined || view === spelled ? undefined : view;
223
+ },
224
+ };
225
+ }