@sema-agent/core 7.33.8 → 7.35.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 (228) hide show
  1. package/CHANGELOG.md +462 -3
  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/errors.d.ts +23 -1
  11. package/dist/brain/errors.js +19 -2
  12. package/dist/brain/open-responses.js +1 -0
  13. package/dist/brain/openai.js +1 -0
  14. package/dist/brain/status-sink.d.ts +7 -1
  15. package/dist/brain/stream-engine.d.ts +3 -0
  16. package/dist/brain/stream-engine.js +2 -1
  17. package/dist/brain/stream-shared.d.ts +3 -1
  18. package/dist/brain/stream-shared.js +16 -4
  19. package/dist/core/a2a.d.ts +3 -1
  20. package/dist/core/a2a.js +1 -1
  21. package/dist/core/ask-origin.d.ts +379 -154
  22. package/dist/core/ask-origin.js +224 -45
  23. package/dist/core/ask-question.js +2 -1
  24. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  25. package/dist/core/ask-unresolvable-notice.js +4 -3
  26. package/dist/core/auto-mode-arming.d.ts +17 -4
  27. package/dist/core/auto-mode-arming.js +31 -6
  28. package/dist/core/auto-mode-defaults.d.ts +16 -7
  29. package/dist/core/auto-mode-defaults.js +2 -1
  30. package/dist/core/auto-mode-directive.d.ts +18 -30
  31. package/dist/core/auto-mode-directive.js +9 -18
  32. package/dist/core/auto-mode-prompt.d.ts +62 -1
  33. package/dist/core/auto-mode-prompt.js +42 -4
  34. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  35. package/dist/core/auto-mode-rebuild.js +1 -0
  36. package/dist/core/auto-mode.d.ts +32 -3
  37. package/dist/core/auto-mode.js +80 -41
  38. package/dist/core/checkpoint-store.d.ts +54 -35
  39. package/dist/core/checkpoint-store.js +58 -3
  40. package/dist/core/child-clamp-seats.js +1 -0
  41. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  42. package/dist/core/classifier-unavailable-arm.js +17 -0
  43. package/dist/core/effective-path-target.d.ts +12 -0
  44. package/dist/core/effective-path-target.js +18 -7
  45. package/dist/core/engine-notice.d.ts +53 -2
  46. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  47. package/dist/core/fs-write-gate-policy.js +4 -1
  48. package/dist/core/gate-fold.js +19 -3
  49. package/dist/core/gate-lanes.js +116 -50
  50. package/dist/core/gate-outcome.d.ts +40 -7
  51. package/dist/core/gate-outcome.js +26 -4
  52. package/dist/core/governance-codes.d.ts +18 -2
  53. package/dist/core/governance-codes.js +23 -1
  54. package/dist/core/hooks.d.ts +53 -29
  55. package/dist/core/hooks.js +1 -3
  56. package/dist/core/mcp.js +1 -1
  57. package/dist/core/message-utils.d.ts +2 -1
  58. package/dist/core/message-utils.js +2 -1
  59. package/dist/core/mode-policy.d.ts +185 -0
  60. package/dist/core/mode-policy.js +184 -0
  61. package/dist/core/oracle-isolation.d.ts +3 -3
  62. package/dist/core/permission-rule-model.d.ts +20 -3
  63. package/dist/core/permission-rule-model.js +40 -3
  64. package/dist/core/permission-rule-provider.d.ts +22 -11
  65. package/dist/core/permission-rule-provider.js +17 -7
  66. package/dist/core/permission-rules.d.ts +23 -1
  67. package/dist/core/permission-rules.js +56 -9
  68. package/dist/core/persisted-rule-arms.d.ts +14 -10
  69. package/dist/core/persisted-rule-arms.js +13 -6
  70. package/dist/core/present-plan-tool.d.ts +1 -1
  71. package/dist/core/present-plan-tool.js +3 -3
  72. package/dist/core/read-only-shell.d.ts +19 -19
  73. package/dist/core/read-only-shell.js +38 -12
  74. package/dist/core/retired-keys.d.ts +9 -3
  75. package/dist/core/retired-keys.js +5 -1
  76. package/dist/core/runner/active-skill-scope.js +5 -4
  77. package/dist/core/runner/assemble-result.js +2 -2
  78. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  79. package/dist/core/runner/classifier-ladder.js +29 -0
  80. package/dist/core/runner/contracts.d.ts +62 -38
  81. package/dist/core/runner/denial-limit-arms.d.ts +35 -7
  82. package/dist/core/runner/denial-limit-arms.js +15 -9
  83. package/dist/core/runner/env-capability-census.js +1 -1
  84. package/dist/core/runner/file-history-request.d.ts +26 -0
  85. package/dist/core/runner/file-history-request.js +90 -0
  86. package/dist/core/runner/gate-exit.d.ts +30 -4
  87. package/dist/core/runner/gate-exit.js +28 -7
  88. package/dist/core/runner/permission-rule-lanes.d.ts +11 -6
  89. package/dist/core/runner/permission-rule-lanes.js +55 -48
  90. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  91. package/dist/core/runner/persisted-path-rows.js +89 -0
  92. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  93. package/dist/core/runner/prepare-artifact.js +9 -3
  94. package/dist/core/runner/prepare-ask-lane.d.ts +6 -4
  95. package/dist/core/runner/prepare-ask-lane.js +3 -3
  96. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  97. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  98. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  99. package/dist/core/runner/prepare-file-history.js +4 -90
  100. package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
  101. package/dist/core/runner/prepare-gate-stations.js +46 -29
  102. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  103. package/dist/core/runner/prepare-hands-readface.js +64 -53
  104. package/dist/core/runner/prepare-inherited-gate.d.ts +30 -23
  105. package/dist/core/runner/prepare-inherited-gate.js +45 -25
  106. package/dist/core/runner/prepare-park-ask.d.ts +6 -4
  107. package/dist/core/runner/prepare-park-ask.js +7 -5
  108. package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
  109. package/dist/core/runner/prepare-policy-chain.js +203 -180
  110. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  111. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  112. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  113. package/dist/core/runner/prepare-run-refs.js +9 -4
  114. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  115. package/dist/core/runner/prepare-safety-scan.js +19 -3
  116. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  117. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  118. package/dist/core/runner/prepare-task.js +56 -24
  119. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  120. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  121. package/dist/core/runner/read-root-grant.d.ts +9 -0
  122. package/dist/core/runner/read-root-grant.js +12 -0
  123. package/dist/core/runner/retired-runner-deps.js +1 -0
  124. package/dist/core/runner/run-brain-sinks.js +3 -1
  125. package/dist/core/runner/run-identity-wiring.js +1 -1
  126. package/dist/core/runner/run-leg.js +29 -18
  127. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  128. package/dist/core/runner/run-notification-lane.js +3 -1
  129. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  130. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  131. package/dist/core/runner/run-turn-boundary.js +1 -1
  132. package/dist/core/runner/runtask.d.ts +1 -0
  133. package/dist/core/runner/runtask.js +8 -11
  134. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  135. package/dist/core/runner/session-mode-seats.js +73 -0
  136. package/dist/core/runner/session-rule-policy.js +3 -2
  137. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  138. package/dist/core/runner/stream-mode-verb.js +19 -0
  139. package/dist/core/runner/turn-attachments.d.ts +3 -3
  140. package/dist/core/runner/turn-attachments.js +1 -1
  141. package/dist/core/runner/write-protection-station.d.ts +7 -1
  142. package/dist/core/runner/write-protection-station.js +6 -3
  143. package/dist/core/runner-deps.d.ts +31 -34
  144. package/dist/core/runtime-caps.d.ts +12 -3
  145. package/dist/core/safety-axis-vocab.d.ts +2 -2
  146. package/dist/core/safety-axis-vocab.js +1 -1
  147. package/dist/core/secret-env.d.ts +30 -58
  148. package/dist/core/secret-env.js +149 -586
  149. package/dist/core/sensitive-path-policy.d.ts +4 -2
  150. package/dist/core/sensitive-path-policy.js +41 -31
  151. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  152. package/dist/core/task-event.d.ts +1 -1
  153. package/dist/core/task-result.d.ts +1 -1
  154. package/dist/core/task-spec.d.ts +41 -89
  155. package/dist/core/task-stream.d.ts +16 -0
  156. package/dist/core/terminal-cause.js +1 -0
  157. package/dist/core/tighten-task-spec.d.ts +2 -2
  158. package/dist/core/tighten-task-spec.js +12 -14
  159. package/dist/core/tool-catalog-entries.js +4 -3
  160. package/dist/core/tool-policy.d.ts +84 -30
  161. package/dist/core/tool-policy.js +76 -16
  162. package/dist/core/tool-registry.d.ts +38 -0
  163. package/dist/core/tool-registry.js +25 -1
  164. package/dist/core/tool-roster.d.ts +17 -2
  165. package/dist/core/tool-roster.js +11 -1
  166. package/dist/core/tool-spec.d.ts +8 -9
  167. package/dist/core/trace.d.ts +18 -6
  168. package/dist/core/types.d.ts +2 -1
  169. package/dist/core/wiring-manifest.d.ts +31 -5
  170. package/dist/core/wiring-manifest.js +28 -1
  171. package/dist/core/write-protect.d.ts +11 -1
  172. package/dist/core/write-protect.js +26 -15
  173. package/dist/engine/execution-env/node-execution-env.d.ts +6 -1
  174. package/dist/engine/execution-env/node-execution-env.js +16 -3
  175. package/dist/engine/harness/agent-harness.js +2 -1
  176. package/dist/engine/harness/types.d.ts +6 -7
  177. package/dist/engine/llm/types.d.ts +2 -4
  178. package/dist/engine/loop/types.d.ts +7 -0
  179. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  180. package/dist/index.d.ts +18 -8
  181. package/dist/index.js +15 -6
  182. package/dist/orchestration/workflow-governance.js +1 -1
  183. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  184. package/dist/orchestration/workflow-size-guideline.js +2 -2
  185. package/dist/stores/file/index.d.ts +5 -3
  186. package/dist/stores/file/session-store.d.ts +8 -2
  187. package/dist/stores/file/session-store.js +5 -5
  188. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  189. package/dist/tools/artifact/artifact-tool.js +16 -1
  190. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  191. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  192. package/dist/tools/fs/containment-face.d.ts +9 -3
  193. package/dist/tools/fs/containment-face.js +2 -0
  194. package/dist/tools/fs/file-read-check.d.ts +50 -0
  195. package/dist/tools/fs/file-read-check.js +79 -0
  196. package/dist/tools/fs/fs-bash.d.ts +3 -3
  197. package/dist/tools/fs/fs-read.d.ts +1 -0
  198. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  199. package/dist/tools/fs/fs-search-tools.js +4 -4
  200. package/dist/tools/fs/fs-shared.js +20 -6
  201. package/dist/tools/fs/fs-write.d.ts +4 -3
  202. package/dist/tools/fs/fs-write.js +9 -9
  203. package/dist/tools/fs/index.d.ts +39 -3
  204. package/dist/tools/fs/index.js +38 -15
  205. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  206. package/dist/tools/fs/read-deny-home.js +225 -0
  207. package/dist/tools/fs/read-deny.d.ts +176 -34
  208. package/dist/tools/fs/read-deny.js +268 -61
  209. package/dist/tools/fs/safety.d.ts +6 -1
  210. package/dist/tools/fs/safety.js +18 -5
  211. package/dist/tools/fs/search.d.ts +3 -0
  212. package/dist/tools/fs/search.js +47 -9
  213. package/dist/tools/fs/shell-read-check.d.ts +52 -13
  214. package/dist/tools/fs/shell-read-check.js +199 -20
  215. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  216. package/dist/tools/fs/shell-read-establish.js +134 -21
  217. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  218. package/dist/tools/fs/shell-read-identity.js +21 -0
  219. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  220. package/dist/tools/fs/shell-read-judge.js +43 -18
  221. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  222. package/dist/tools/fs/shell-read-plan.js +26 -4
  223. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  224. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  225. package/package.json +1 -1
  226. package/test/export-surface.snapshot.json +117 -11
  227. package/wire-changes/core@7.34.0.json +2621 -0
  228. package/wire-changes/core@7.35.0.json +584 -0
@@ -4,8 +4,8 @@ import { type CwdRef } from "./fs-shared.js";
4
4
  import type { ReversibilityVerdict } from "../../core/types.js";
5
5
  import type { ShellReadAdmission } from "./shell-read-check.js";
6
6
  /**
7
- * The classify doctrine's SHAPE classifier for the `bash` tool, exposed as a `ToolSpec.reversibilityProbe`
8
- * (the "maybe" tier's probe under `shellGate:"classify"`). A command is reversible ONLY if every connected
7
+ * The full shell face's SHAPE classifier for the `bash` tool, exposed as a `ToolSpec.reversibilityProbe`
8
+ * (the "maybe" tier's probe, installed on every full shell). A command is reversible ONLY if every connected
9
9
  * segment is a bare command whose program is on the reversible allowlist (default
10
10
  * {@link bashClassifyDefaultAllow} — curated to commands with NO write/mutation mode under ANY args) and whose
11
11
  * SHAPE the compound classifier accepts (no redirects, substitutions, subshells, escapes or backgrounding; no
@@ -17,7 +17,7 @@ import type { ShellReadAdmission } from "./shell-read-check.js";
17
17
  *
18
18
  * WHERE a command reads is not this probe's question. The read boundary — deny rows, containment, symlinks,
19
19
  * patterns, traversals, the working directory a `cd` moves — is the gate's shell READ STATION
20
- * (`shell-read-check.ts`), judged on every shell call under every doctrine, ahead of this probe; a call the
20
+ * (`shell-read-check.ts`), judged on every shell call under every mode, ahead of this probe; a call the
21
21
  * station refused never reaches it, and a call the station asks about is asked about whatever this probe
22
22
  * says. `boundary` supplies only the environment's declared home, so the `$HOME`/`~` spellings fold here the
23
23
  * way the station folds them; nothing else of it is read. A caller-supplied `allow` is honored verbatim, so a
@@ -6,4 +6,5 @@ import { type ReminderDisclosureCounts } from "../../core/reminder-disclosure.js
6
6
  export declare function createReadFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: RootsSeat, imageDownsampler?: ReadImageDownsamplerOption, pdfCapabilities?: PdfModelCapabilities, bgOutputReadExemption?: (canonicalKey: string, ctx: {
7
7
  taskId?: string;
8
8
  principal?: string;
9
+ toolCallId?: string;
9
10
  }) => boolean, readCyberReminder?: boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./containment-face.js").ContainmentFace, reminderMark?: string, reminderDisclosureCounts?: ReminderDisclosureCounts): AgentTool;
@@ -12,5 +12,5 @@ import type { CwdRef } from "./fs-shared.js";
12
12
  * honesty boundary.
13
13
  */
14
14
  export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
15
- export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: RootsSeat, readDeny?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef): AgentTool;
16
- export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: RootsSeat, readDeny?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef): AgentTool;
15
+ export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: RootsSeat, readDeny?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef, readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean): AgentTool;
16
+ export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: RootsSeat, readDeny?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef, readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean): AgentTool;
@@ -74,7 +74,7 @@ export function grepDetailFields(text, mode, offset, structuredRows) {
74
74
  }
75
75
  return detailFields;
76
76
  }
77
- export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, readFace, cwdRef) {
77
+ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, readFace, cwdRef, readAdmission) {
78
78
  const declaredTarget = toolFace("grep").pathTarget;
79
79
  return defineTool({
80
80
  name: "Grep",
@@ -143,7 +143,7 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
143
143
  const scopeSpelling = declaredTarget === undefined ? undefined : pathTargetValue(a, declaredTarget);
144
144
  let scoped = scopeSpelling;
145
145
  if (scopeSpelling !== undefined) {
146
- const r = await resolveKey(env, rootCanonical, scopeSpelling, ctx.signal, baseCwd, additionalRoots, undefined, readDeny, readFace);
146
+ const r = await resolveKey(env, rootCanonical, scopeSpelling, ctx.signal, baseCwd, additionalRoots, readAdmission && ((key) => readAdmission(ctx.toolCallId, key)), readDeny, readFace);
147
147
  if (!r.ok)
148
148
  return errorResult(violationText("Grep", r.violation), violationDetails(r.violation));
149
149
  scoped = r.key;
@@ -177,7 +177,7 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
177
177
  },
178
178
  });
179
179
  }
180
- export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, readFace, cwdRef) {
180
+ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, readFace, cwdRef, readAdmission) {
181
181
  const declaredTarget = toolFace("glob").pathTarget;
182
182
  return defineTool({
183
183
  name: "Glob",
@@ -217,7 +217,7 @@ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, re
217
217
  const baseCwd = pathTargetBaseOf(declaredTarget, { root: rootCanonical, cwd: cwdRef?.current });
218
218
  let scoped = path;
219
219
  if (path !== undefined) {
220
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, baseCwd, additionalRoots, undefined, readDeny, readFace);
220
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, baseCwd, additionalRoots, readAdmission && ((key) => readAdmission(ctx.toolCallId, key)), readDeny, readFace);
221
221
  if (!r.ok)
222
222
  return errorResult(violationText("Glob", r.violation), violationDetails(r.violation));
223
223
  scoped = r.key;
@@ -4,6 +4,7 @@ import { normalizeAbsPathLexically, sha256, similarNameSuggestion, OVERSIZE_READ
4
4
  import { decodeTextBytes, normalizeFileText } from "./encoding.js";
5
5
  import { shellQuote } from "./search.js";
6
6
  import { isNotebookPath } from "./notebook.js";
7
+ import { homeViewsOf } from "./read-deny-home.js";
7
8
  import { sharpImageDownsampler } from "../../core/mcp.js";
8
9
  import { deliverEngineNotice } from "../../core/types.js";
9
10
  import { resolveEnvBudget } from "../../core/env-budget.js";
@@ -344,24 +345,37 @@ export function applyContextShrinkToReadFileState(state, shrink) {
344
345
  export async function enoentMessage(env, key, cwd, signal, readDeny) {
345
346
  let msg = `File does not exist. Note: your current working directory is ${cwd}.`;
346
347
  const sep = Math.max(key.lastIndexOf("/"), key.lastIndexOf("\\"));
348
+ const views = readDeny !== undefined ? homeViewsOf(env, signal) : undefined;
349
+ const denied = async (path, leaf) => {
350
+ if (readDeny === undefined || views === undefined)
351
+ return false;
352
+ if (readDeny.matchPath(path) != null)
353
+ return true;
354
+ const view = await views.of(path, leaf !== undefined ? { leaf } : undefined);
355
+ return view !== undefined && readDeny.matchPath(view) != null;
356
+ };
347
357
  if (sep > 0) {
348
358
  const parent = key.slice(0, sep);
349
359
  const name = key.slice(sep + 1);
350
- if (readDeny?.matchPath(parent) != null)
360
+ if (await denied(parent))
351
361
  return msg;
352
362
  const cwdSepped = cwd.replace(/[\\/]+$/, "");
353
363
  const cwdCandidate = `${cwdSepped}${cwd.includes("\\") ? "\\" : "/"}${name}`;
354
- if (cwdCandidate !== key && readDeny?.matchPath(cwdCandidate) == null) {
364
+ if (cwdCandidate !== key && !(await denied(cwdCandidate))) {
355
365
  const hit = await env.exists(cwdCandidate, signal).catch(() => undefined);
356
366
  if (hit?.ok && hit.value)
357
367
  return `${msg} Did you mean ${cwdCandidate}?`;
358
368
  }
359
369
  const listing = await env.listDir(parent, signal).catch(() => undefined);
360
370
  if (listing?.ok) {
361
- const names = listing.value
362
- .filter((e) => e.kind !== "directory")
363
- .map((e) => e.path.slice(Math.max(e.path.lastIndexOf("/"), e.path.lastIndexOf("\\")) + 1))
364
- .filter((n2) => readDeny?.matchPath(`${parent}/${n2}`) == null);
371
+ const names = [];
372
+ for (const e of listing.value) {
373
+ if (e.kind === "directory")
374
+ continue;
375
+ const n2 = e.path.slice(Math.max(e.path.lastIndexOf("/"), e.path.lastIndexOf("\\")) + 1);
376
+ if (!(await denied(`${parent}/${n2}`, "file")))
377
+ names.push(n2);
378
+ }
365
379
  const suggestion = similarNameSuggestion(names, name);
366
380
  if (suggestion)
367
381
  msg += ` Did you mean ${suggestion}?`;
@@ -2,6 +2,7 @@ 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
4
  import type { ContainmentFace } from "./containment-face.js";
5
+ import type { WriteDenyMatcher } from "./read-deny.js";
5
6
  import { type CwdRef } from "./fs-shared.js";
6
7
  /**
7
8
  * The `beforeWrite` CONTENT hook, mounted here rather than in ToolPolicy because only this band sees
@@ -13,8 +14,8 @@ import { type CwdRef } from "./fs-shared.js";
13
14
  * Absent hook ⇒ byte-identical behavior.
14
15
  */
15
16
  export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook } from "../../core/types.js";
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
+ export declare function createEditFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook, writeFace?: () => ContainmentFace, writeDeny?: WriteDenyMatcher): AgentTool;
18
+ export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook, writeFace?: () => ContainmentFace, writeDeny?: WriteDenyMatcher): AgentTool;
18
19
  /**
19
20
  * design v1.163 — NotebookEdit: replace/insert/delete a single cell in a .ipynb. CC-parity tool over the SAME hand-band
20
21
  * safety skeleton as Edit/Write (resolveKey containment → requireRead read-before-edit → checkStale content-hash
@@ -22,4 +23,4 @@ export declare function createWriteFileTool(env: ExecutionEnv, state: ReadFileSt
22
23
  * .ipynb as the cell projection but records read state (hash/totalLines) in the RAW notebook-text
23
24
  * coordinate — that raw-coordinate read record is what this tool's freshness check depends on.
24
25
  */
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;
26
+ export declare function createNotebookEditTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: readonly string[], beforeWrite?: BeforeWriteHook, trackEdit?: TrackFileEditHook, onEdited?: FileEditedHook, writeFace?: () => ContainmentFace, writeDeny?: WriteDenyMatcher): AgentTool;
@@ -87,7 +87,7 @@ async function gateToolWrite(hook, tool, path, key, content) {
87
87
  return `Error (${tool}): write rejected by the write gate (${res.code}): ${clipHookText(res.reason)}`;
88
88
  return undefined;
89
89
  }
90
- export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
90
+ export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace, writeDeny) {
91
91
  return defineTool({
92
92
  name: "Edit",
93
93
  ...toolFace("edit"),
@@ -112,7 +112,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
112
112
  const path = fileArgPath(args);
113
113
  if (path === undefined)
114
114
  return { ok: true };
115
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
115
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
116
116
  if (!r.ok)
117
117
  return { ok: true };
118
118
  if (a.old_string === a.new_string)
@@ -150,7 +150,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
150
150
  const path = fileArgPath(args);
151
151
  if (path === undefined)
152
152
  return errorResult(`Error (Edit): file_path is required.`);
153
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
153
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
154
154
  if (!r.ok)
155
155
  return errorResult(violationText("Edit", r.violation), violationDetails(r.violation));
156
156
  if (!batch && a.old_string === a.new_string) {
@@ -325,7 +325,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
325
325
  },
326
326
  });
327
327
  }
328
- export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
328
+ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace, writeDeny) {
329
329
  return defineTool({
330
330
  name: "Write",
331
331
  ...toolFace("write"),
@@ -344,7 +344,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
344
344
  return { ok: true };
345
345
  if (ipynbRedirect("Write", path))
346
346
  return { ok: true };
347
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
347
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
348
348
  if (!r.ok)
349
349
  return { ok: true };
350
350
  const exists = await env.exists(r.key, ctx.signal);
@@ -363,7 +363,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
363
363
  const ipynb = ipynbRedirect("Write", path);
364
364
  if (ipynb)
365
365
  return errorResult(ipynb);
366
- const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
366
+ const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
367
367
  if (!r.ok)
368
368
  return errorResult(violationText("Write", r.violation), violationDetails(r.violation));
369
369
  const exists = await env.exists(r.key, ctx.signal);
@@ -424,7 +424,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
424
424
  const notebookNotIpynbMessage = (notebookPath) => `Error (NotebookEdit): "${notebookPath}" is not a .ipynb file; use Edit for other file types.`;
425
425
  const NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE = `Error (NotebookEdit): Cell type is required when using edit_mode=insert.`;
426
426
  const NOTEBOOK_CELL_ID_REQUIRED_MESSAGE = `Error (NotebookEdit): cell_id is required for replace/delete.`;
427
- export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace) {
427
+ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited, writeFace, writeDeny) {
428
428
  return defineTool({
429
429
  name: "NotebookEdit",
430
430
  ...toolFace("notebook-edit"),
@@ -458,7 +458,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
458
458
  return { ok: false, code: "invalid", message: NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE };
459
459
  if (mode !== "insert" && !a.cell_id)
460
460
  return { ok: false, code: "invalid", message: NOTEBOOK_CELL_ID_REQUIRED_MESSAGE };
461
- const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
461
+ const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
462
462
  if (!r.ok)
463
463
  return { ok: true };
464
464
  const notRead = requireRead(state, r.key);
@@ -478,7 +478,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
478
478
  return errorResult(NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE);
479
479
  if (mode !== "insert" && !cell_id)
480
480
  return errorResult(NOTEBOOK_CELL_ID_REQUIRED_MESSAGE);
481
- const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, undefined, writeFace);
481
+ const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
482
482
  if (!r.ok)
483
483
  return errorResult(violationText("NotebookEdit", r.violation), violationDetails(r.violation));
484
484
  const notRead = requireRead(state, r.key);
@@ -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;