@sema-agent/core 5.20.0 → 5.21.1

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 (313) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/agents/agent-definition.d.ts +7 -0
  3. package/dist/agents/agent-transcript-tool.d.ts +30 -0
  4. package/dist/agents/builtin-agents.d.ts +60 -0
  5. package/dist/agents/cascade.d.ts +107 -0
  6. package/dist/agents/cumulative-stats.d.ts +56 -0
  7. package/dist/agents/observer.d.ts +162 -0
  8. package/dist/agents/peer-admission.d.ts +95 -0
  9. package/dist/agents/repair-loop.d.ts +226 -0
  10. package/dist/agents/retain-ledger.d.ts +141 -0
  11. package/dist/agents/roster-store.d.ts +105 -0
  12. package/dist/agents/send-message-tool.d.ts +92 -0
  13. package/dist/agents/session-util.d.ts +5 -0
  14. package/dist/agents/subagent-steps.d.ts +66 -0
  15. package/dist/agents/subagent.d.ts +600 -0
  16. package/dist/agents/suspend-guard.d.ts +29 -0
  17. package/dist/agents/teacher.d.ts +75 -0
  18. package/dist/agents/team.d.ts +120 -1
  19. package/dist/agents/tool-filter.d.ts +34 -0
  20. package/dist/agents/verify.d.ts +198 -0
  21. package/dist/bench/metrics.d.ts +455 -0
  22. package/dist/brain/anthropic.d.ts +30 -0
  23. package/dist/brain/circuit-breaker.d.ts +33 -0
  24. package/dist/brain/context-overflow.d.ts +60 -3
  25. package/dist/brain/degrading.d.ts +67 -0
  26. package/dist/brain/errors.d.ts +42 -0
  27. package/dist/brain/failover.d.ts +15 -0
  28. package/dist/brain/media-degrade.d.ts +39 -0
  29. package/dist/brain/model-presets.d.ts +31 -0
  30. package/dist/brain/open-responses.d.ts +19 -0
  31. package/dist/brain/openai.d.ts +46 -0
  32. package/dist/brain/reasoning.d.ts +106 -1
  33. package/dist/brain/repetition.d.ts +83 -0
  34. package/dist/brain/request-params.d.ts +56 -0
  35. package/dist/brain/retry.d.ts +46 -0
  36. package/dist/brain/routing.d.ts +12 -0
  37. package/dist/brain/status-sink.d.ts +30 -1
  38. package/dist/brain/stream-engine.d.ts +147 -0
  39. package/dist/brain/stream-shared.d.ts +34 -0
  40. package/dist/brain/terminal-cause.d.ts +31 -0
  41. package/dist/brain/timeout.d.ts +107 -0
  42. package/dist/brain/tool-call-id.d.ts +20 -0
  43. package/dist/brain/tool-call-repair.d.ts +13 -0
  44. package/dist/config/catalog.d.ts +47 -0
  45. package/dist/config/defaults.d.ts +33 -0
  46. package/dist/core/a2a-task-state.d.ts +53 -0
  47. package/dist/core/a2a.d.ts +51 -0
  48. package/dist/core/arg-summary.d.ts +62 -0
  49. package/dist/core/ask-question.d.ts +272 -2
  50. package/dist/core/auto-compaction.d.ts +467 -0
  51. package/dist/core/auto-compaction.js +20 -6
  52. package/dist/core/auto-mode-prompt-assets.d.ts +8 -0
  53. package/dist/core/auto-mode-prompt.d.ts +27 -0
  54. package/dist/core/auto-mode.d.ts +54 -3
  55. package/dist/core/auto-promote.d.ts +100 -0
  56. package/dist/core/background-agent-store.d.ts +292 -0
  57. package/dist/core/background-shell.d.ts +110 -0
  58. package/dist/core/cache-break-detector.d.ts +34 -0
  59. package/dist/core/canonical-json.d.ts +57 -0
  60. package/dist/core/checkpoint-store.d.ts +1574 -16
  61. package/dist/core/compliance.d.ts +30 -0
  62. package/dist/core/consolidate-scope.d.ts +75 -0
  63. package/dist/core/context-edit.d.ts +99 -0
  64. package/dist/core/context-guard.d.ts +46 -0
  65. package/dist/core/exec-gate.d.ts +44 -0
  66. package/dist/core/exec-output-tail.d.ts +61 -0
  67. package/dist/core/file-snapshot-store.d.ts +104 -0
  68. package/dist/core/fs-write-gate-policy.d.ts +36 -0
  69. package/dist/core/git-worktree-env.d.ts +31 -0
  70. package/dist/core/governance-codes.d.ts +19 -0
  71. package/dist/core/hooks.d.ts +608 -1
  72. package/dist/core/human-input-projection.d.ts +37 -0
  73. package/dist/core/human-input-projection.js +13 -0
  74. package/dist/core/image-downsample.d.ts +74 -0
  75. package/dist/core/locked-config.d.ts +37 -0
  76. package/dist/core/lsp-diagnostics.d.ts +77 -0
  77. package/dist/core/lsp-protocol.d.ts +29 -0
  78. package/dist/core/lsp-session.d.ts +60 -1
  79. package/dist/core/lsp.d.ts +150 -1
  80. package/dist/core/mailbox-store.d.ts +57 -0
  81. package/dist/core/mcp.d.ts +381 -0
  82. package/dist/core/media-byte-cap.d.ts +21 -0
  83. package/dist/core/memory-admission.d.ts +71 -0
  84. package/dist/core/memory-engine/content-origin.d.ts +64 -0
  85. package/dist/core/memory-engine/data-plane.d.ts +43 -0
  86. package/dist/core/memory-engine/dual-root.d.ts +24 -0
  87. package/dist/core/memory-engine/engine.d.ts +230 -0
  88. package/dist/core/memory-engine/engine.js +103 -35
  89. package/dist/core/memory-engine/file-backend.d.ts +155 -0
  90. package/dist/core/memory-engine/frontmatter.d.ts +16 -0
  91. package/dist/core/memory-engine/layout.d.ts +217 -0
  92. package/dist/core/memory-engine/memory-backend-contract.d.ts +12 -0
  93. package/dist/core/memory-engine/migrate.d.ts +9 -0
  94. package/dist/core/memory-engine/scan.d.ts +12 -0
  95. package/dist/core/memory-engine/scope-contract.d.ts +61 -1
  96. package/dist/core/memory-engine/sync-client.d.ts +87 -0
  97. package/dist/core/memory-engine/sync.d.ts +60 -0
  98. package/dist/core/memory-engine/tools.d.ts +41 -0
  99. package/dist/core/memory-engine/types.d.ts +188 -0
  100. package/dist/core/memory-recall.d.ts +141 -0
  101. package/dist/core/memory-vector.d.ts +20 -0
  102. package/dist/core/memory.d.ts +458 -0
  103. package/dist/core/message-utils.d.ts +6 -0
  104. package/dist/core/oracle-isolation.d.ts +69 -0
  105. package/dist/core/permission-rule-consent.d.ts +138 -0
  106. package/dist/core/permission-rule-model.d.ts +122 -0
  107. package/dist/core/permission-rule-store.d.ts +119 -3
  108. package/dist/core/permission-rules.d.ts +87 -1
  109. package/dist/core/present-plan-tool.d.ts +20 -0
  110. package/dist/core/pricing.d.ts +26 -0
  111. package/dist/core/property-harness.d.ts +86 -0
  112. package/dist/core/protocol-naming.d.ts +38 -0
  113. package/dist/core/protocol-table.d.ts +61 -0
  114. package/dist/core/push-queue.d.ts +1 -0
  115. package/dist/core/remote-env.d.ts +383 -1
  116. package/dist/core/retention-policy.d.ts +33 -0
  117. package/dist/core/retention.d.ts +51 -0
  118. package/dist/core/roles.d.ts +59 -0
  119. package/dist/core/runner/active-skill-scope.d.ts +34 -0
  120. package/dist/core/runner/assemble-result.d.ts +134 -0
  121. package/dist/core/runner/compaction-call-options.d.ts +97 -1
  122. package/dist/core/runner/grounding-signal.d.ts +10 -0
  123. package/dist/core/runner/image.d.ts +17 -0
  124. package/dist/core/runner/image.js +29 -15
  125. package/dist/core/runner/memory-consolidation.d.ts +60 -0
  126. package/dist/core/runner/prepare-memory.d.ts +59 -0
  127. package/dist/core/runner/prepare-task.d.ts +1011 -2
  128. package/dist/core/runner/prepare-task.js +12 -11
  129. package/dist/core/runner/prompt-suggestions.d.ts +7 -0
  130. package/dist/core/runner/runtask.d.ts +304 -3
  131. package/dist/core/runner/session-file-state-replay.d.ts +49 -0
  132. package/dist/core/runner/session-rule-policy.d.ts +58 -0
  133. package/dist/core/runner/strict-output-schema.d.ts +18 -3
  134. package/dist/core/runner/synthetic-tools.d.ts +113 -0
  135. package/dist/core/runner/teardown-bounded.d.ts +34 -0
  136. package/dist/core/runner/tool-disclosure.d.ts +227 -0
  137. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  138. package/dist/core/runner/turn-attachments.d.ts +634 -0
  139. package/dist/core/runner/usage-accounting.d.ts +32 -0
  140. package/dist/core/runtime.d.ts +9 -0
  141. package/dist/core/safe-notify.d.ts +64 -0
  142. package/dist/core/safety-axis-vocab.d.ts +23 -0
  143. package/dist/core/safety-merge-corpus.d.ts +37 -0
  144. package/dist/core/scheduler.d.ts +121 -0
  145. package/dist/core/secret-env.d.ts +32 -0
  146. package/dist/core/select-model.d.ts +15 -0
  147. package/dist/core/sensitive-path-policy.d.ts +42 -0
  148. package/dist/core/session-policy-store.d.ts +94 -0
  149. package/dist/core/session-reconcile.d.ts +80 -0
  150. package/dist/core/session-store.d.ts +85 -0
  151. package/dist/core/session.d.ts +153 -0
  152. package/dist/core/shared-memory/contract.d.ts +22 -0
  153. package/dist/core/shared-memory/normalize.d.ts +123 -2
  154. package/dist/core/shared-memory/tools.d.ts +14 -0
  155. package/dist/core/shared-memory/types.d.ts +105 -0
  156. package/dist/core/shutdown-debug.d.ts +6 -0
  157. package/dist/core/side-query.d.ts +38 -0
  158. package/dist/core/side-query.js +6 -1
  159. package/dist/core/skill-tool-specifier.d.ts +72 -0
  160. package/dist/core/skills-directory.d.ts +100 -1
  161. package/dist/core/spec-contract.d.ts +89 -0
  162. package/dist/core/store-contracts/background-agent-store-contract.d.ts +31 -0
  163. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +26 -0
  164. package/dist/core/store-contracts/contract-harness.d.ts +20 -0
  165. package/dist/core/store-contracts/contract-kit-version.d.ts +27 -0
  166. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +8 -0
  167. package/dist/core/store-contracts/mailbox-store-contract.d.ts +42 -0
  168. package/dist/core/store-contracts/tool-result-store-contract.d.ts +13 -0
  169. package/dist/core/strategy-store.d.ts +37 -0
  170. package/dist/core/stub-env.d.ts +7 -0
  171. package/dist/core/surrogate-safe-slice.d.ts +35 -0
  172. package/dist/core/task-notification.d.ts +202 -0
  173. package/dist/core/task-outcome.d.ts +53 -0
  174. package/dist/core/task-registry-agent.d.ts +337 -1
  175. package/dist/core/task-registry-monitor.d.ts +12 -0
  176. package/dist/core/task-registry-shared.d.ts +540 -0
  177. package/dist/core/task-registry.d.ts +343 -0
  178. package/dist/core/task-registry.js +13 -2
  179. package/dist/core/task-tool-shape.d.ts +44 -0
  180. package/dist/core/tighten-task-spec.d.ts +21 -0
  181. package/dist/core/tool-detach.d.ts +21 -0
  182. package/dist/core/tool-errors.d.ts +131 -0
  183. package/dist/core/tool-errors.js +4 -0
  184. package/dist/core/tool-name-aliases.d.ts +27 -0
  185. package/dist/core/tool-policy.d.ts +555 -0
  186. package/dist/core/tool-result-budget.d.ts +32 -0
  187. package/dist/core/tool-result-store.d.ts +174 -1
  188. package/dist/core/tools.d.ts +45 -0
  189. package/dist/core/trace.d.ts +323 -0
  190. package/dist/core/types.d.ts +3859 -2
  191. package/dist/core/untrusted-egress.d.ts +8 -0
  192. package/dist/core/untrusted-text.d.ts +156 -0
  193. package/dist/core/usage-window-store.d.ts +95 -0
  194. package/dist/core/version.d.ts +1 -0
  195. package/dist/core/warm-resume.d.ts +17 -0
  196. package/dist/core/wiring-manifest.d.ts +169 -0
  197. package/dist/core/with-retry.d.ts +24 -0
  198. package/dist/core/workflow-journal-store.d.ts +160 -0
  199. package/dist/core/workflow-run-store-contract.d.ts +25 -0
  200. package/dist/core/workflow-run-store.d.ts +119 -0
  201. package/dist/engine/compaction/compaction.d.ts +256 -1
  202. package/dist/engine/compaction/utils.d.ts +94 -0
  203. package/dist/engine/execution-env/kill-tree.d.ts +29 -0
  204. package/dist/engine/execution-env/node-execution-env.d.ts +94 -0
  205. package/dist/engine/harness/agent-harness.d.ts +116 -0
  206. package/dist/engine/harness/agent-harness.js +3 -14
  207. package/dist/engine/harness/messages.d.ts +15 -0
  208. package/dist/engine/harness/types.d.ts +464 -2
  209. package/dist/engine/llm/diagnostics.d.ts +4 -0
  210. package/dist/engine/llm/event-stream.d.ts +3 -0
  211. package/dist/engine/llm/index.d.ts +7 -0
  212. package/dist/engine/llm/types.d.ts +500 -3
  213. package/dist/engine/llm/validation.d.ts +3 -0
  214. package/dist/engine/loop/agent-loop.d.ts +87 -2
  215. package/dist/engine/loop/runtime-deps.d.ts +7 -0
  216. package/dist/engine/loop/types.d.ts +424 -0
  217. package/dist/engine/lsp/frame-decoder.d.ts +13 -0
  218. package/dist/engine/lsp/node-lsp-manager.d.ts +107 -0
  219. package/dist/engine/lsp/stdio-lsp-transport.d.ts +7 -0
  220. package/dist/engine/session/import-validate.d.ts +27 -0
  221. package/dist/engine/session/log-digest.d.ts +93 -0
  222. package/dist/engine/session/memory-repo.d.ts +6 -0
  223. package/dist/engine/session/memory-storage.d.ts +2 -0
  224. package/dist/engine/session/session.d.ts +75 -0
  225. package/dist/engine/session/storage-base.d.ts +8 -0
  226. package/dist/fixtures/index.d.ts +36 -0
  227. package/dist/index.d.ts +16 -1
  228. package/dist/index.js +0 -1
  229. package/dist/internal/harness-types.d.ts +6 -0
  230. package/dist/internal/harness.d.ts +11 -0
  231. package/dist/internal/llm.d.ts +6 -0
  232. package/dist/orchestration/builtin-workflows.d.ts +53 -0
  233. package/dist/orchestration/dev-vm-script-runner.d.ts +19 -0
  234. package/dist/orchestration/goal.d.ts +57 -0
  235. package/dist/orchestration/goal.js +3 -0
  236. package/dist/orchestration/run-spec.d.ts +42 -0
  237. package/dist/orchestration/run-workflow-tool.d.ts +169 -0
  238. package/dist/orchestration/workflow-governance.d.ts +61 -0
  239. package/dist/orchestration/workflow-meta.d.ts +28 -0
  240. package/dist/orchestration/workflow-observe.d.ts +60 -0
  241. package/dist/orchestration/workflow-primitives.d.ts +23 -1
  242. package/dist/orchestration/workflow-sandbox-conformance.d.ts +38 -0
  243. package/dist/orchestration/workflow-script-runner.d.ts +88 -0
  244. package/dist/orchestration/workflow-script-store.d.ts +98 -0
  245. package/dist/orchestration/workflow-size-guideline.d.ts +67 -0
  246. package/dist/orchestration/workflow-types.d.ts +169 -2
  247. package/dist/orchestration/workflow.d.ts +358 -0
  248. package/dist/prompt-assembly/artifact-store.d.ts +33 -0
  249. package/dist/prompt-assembly/artifact.d.ts +25 -0
  250. package/dist/prompt-assembly/assemble.d.ts +20 -0
  251. package/dist/prompt-assembly/composer.d.ts +29 -0
  252. package/dist/prompt-assembly/epoch.d.ts +55 -1
  253. package/dist/prompt-assembly/event-registry.d.ts +35 -0
  254. package/dist/prompt-assembly/explain.d.ts +12 -0
  255. package/dist/prompt-assembly/tool-catalog.d.ts +38 -0
  256. package/dist/prompt-assembly/turn-snapshot.d.ts +40 -0
  257. package/dist/prompt-assembly/types.d.ts +115 -0
  258. package/dist/prompts/coordinator.d.ts +27 -0
  259. package/dist/prompts/default.d.ts +539 -0
  260. package/dist/prompts/simple-sections.d.ts +45 -0
  261. package/dist/prompts/supervisor.d.ts +66 -0
  262. package/dist/scenarios/env.d.ts +28 -0
  263. package/dist/scenarios/full-body.d.ts +50 -0
  264. package/dist/scenarios/scenario-registry.d.ts +60 -0
  265. package/dist/scenarios/teacher-quickstart.d.ts +27 -0
  266. package/dist/server/http.d.ts +17 -0
  267. package/dist/stores/cc/lockfile.d.ts +6 -0
  268. package/dist/stores/cc/mailbox-store.d.ts +8 -0
  269. package/dist/stores/cc/roster-adapter.d.ts +4 -0
  270. package/dist/stores/cc/sidecar-transcript.d.ts +66 -0
  271. package/dist/stores/file/background-agent-store.d.ts +24 -0
  272. package/dist/stores/file/checkpoint-store.d.ts +38 -0
  273. package/dist/stores/file/file-snapshot-store.d.ts +25 -0
  274. package/dist/stores/file/fs-atomic.d.ts +155 -0
  275. package/dist/stores/file/index.d.ts +89 -0
  276. package/dist/stores/file/mailbox-store.d.ts +36 -0
  277. package/dist/stores/file/memory-store.d.ts +82 -0
  278. package/dist/stores/file/permission-rule-store.d.ts +79 -0
  279. package/dist/stores/file/session-policy-store.d.ts +28 -0
  280. package/dist/stores/file/session-store.d.ts +40 -0
  281. package/dist/stores/file/shared-ledger.d.ts +83 -0
  282. package/dist/stores/file/tool-result-store.d.ts +11 -0
  283. package/dist/stores/file/usage-window-store.d.ts +18 -0
  284. package/dist/stores/file/workflow-journal-store.d.ts +100 -0
  285. package/dist/stores/file/workflow-run-store.d.ts +11 -0
  286. package/dist/tools/fs/bash-readonly-classifier.d.ts +282 -0
  287. package/dist/tools/fs/encoding.d.ts +60 -0
  288. package/dist/tools/fs/fs-bash.d.ts +133 -0
  289. package/dist/tools/fs/fs-pdf.d.ts +28 -0
  290. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  291. package/dist/tools/fs/fs-search-tools.js +0 -1
  292. package/dist/tools/fs/fs-shared.d.ts +360 -0
  293. package/dist/tools/fs/fs-write.d.ts +16 -0
  294. package/dist/tools/fs/gh-rate-limit.d.ts +22 -0
  295. package/dist/tools/fs/index.d.ts +79 -0
  296. package/dist/tools/fs/notebook.d.ts +56 -0
  297. package/dist/tools/fs/pdf.d.ts +70 -0
  298. package/dist/tools/fs/pdf.js +8 -2
  299. package/dist/tools/fs/repo-map.d.ts +10 -0
  300. package/dist/tools/fs/safety.d.ts +376 -0
  301. package/dist/tools/fs/search.d.ts +131 -0
  302. package/dist/tools/loop-tick.d.ts +24 -0
  303. package/dist/tools/monitor.d.ts +55 -0
  304. package/dist/tools/scheduler-tools.d.ts +51 -0
  305. package/dist/tools/sql-adapters.d.ts +18 -0
  306. package/dist/tools/sql.d.ts +25 -0
  307. package/dist/tools/task-list.d.ts +77 -0
  308. package/dist/tools/todo.d.ts +8 -0
  309. package/dist/tools/web.d.ts +184 -0
  310. package/dist/tools/worktree.d.ts +81 -0
  311. package/package.json +2 -2
  312. package/dist/tools/gitea-issue.d.ts +0 -13
  313. package/dist/tools/gitea-issue.js +0 -75
@@ -1,45 +1,221 @@
1
1
  import type { DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
2
2
  import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../internal/harness-types.js";
3
3
  import type { PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
4
+ /**
5
+ * In-process hook seam (design/37) — a small, provider-agnostic interception layer modeled on CC's
6
+ * hooks but reduced to three process-internal callbacks (no shell/HTTP executors, no settings files):
7
+ *
8
+ * - {@link Hooks.preToolUse} runs before a tool executes. It may **rewrite** the args (an `allow` with
9
+ * `updatedInput`), **restrict** the call (`deny`/`ask`), and/or inject `additionalContext`. It is a
10
+ * pre-filter only: a hook's `allow` does NOT bypass the tool-policy gate — the policy still runs on
11
+ * the (possibly rewritten) args and has the final say (load-bearing invariant; see {@link runToolGate}).
12
+ * - {@link Hooks.postToolUse} runs after a tool executes. It may replace the result content
13
+ * (`updatedOutput`) and/or append `additionalContext`.
14
+ * - {@link Hooks.userPromptSubmit} runs before the objective becomes a user message. It may `block`
15
+ * submission (the task fails with a model-readable reason) or inject `additionalContext` ahead of it.
16
+ *
17
+ * All feedback the model should see (a block reason, injected context) is wrapped as a
18
+ * `<system-reminder>` via {@link formatHookFeedback} so the model can read and self-correct.
19
+ */
4
20
  export interface Hooks {
5
21
  preToolUse?(toolName: string, input: unknown, ctx: HookToolContext): PreToolUseResult | undefined | Promise<PreToolUseResult | undefined>;
22
+ /**
23
+ * Deployment SELF-DECLARATION: this deployment's {@link preToolUse} is a pure OBSERVER — a tracer, an
24
+ * audit sink, a metrics counter — that never judges a call. Declaring it changes exactly one thing:
25
+ * the face stops being folded into the DELEGATION chain (`InheritedGate.parentConstraints`), so a
26
+ * delegated child inherits no screening entry for it.
27
+ *
28
+ * Why the declaration exists. The delegation fold (issue #33) keys on PRESENCE — "a preToolUse is
29
+ * wired" — because a callback cannot be inspected for whether it will ever return a verdict. That
30
+ * reading is right for a screening face and wrong for an observer, and the difference is not free:
31
+ * every inherited entry is one more OPAQUE constraint on the child, recorded on a durable park as
32
+ * `parentConstraintCount`. A deployment that flips on a default-OFF diagnostic tracer therefore also
33
+ * changes the shape every parked child's resume has to re-supply. The declaration is how a deployment
34
+ * says which of the two it wired, on the same footing as every other thing the library trusts a
35
+ * deployment to state about its own wiring.
36
+ *
37
+ * What does NOT change: the callback still runs in THIS task's own tool gate, on every call, with the
38
+ * same context and the same crash posture (a throw is still the fail-closed deny). Observation is
39
+ * untouched; only the inherited constraint goes away.
40
+ *
41
+ * The face also receives a DETACHED copy of the arguments rather than the live object, because the
42
+ * gate reads a rewrite off reference identity and an in-place edit would otherwise be a second channel
43
+ * that outlives the dropped return value. That detachment is BEST-EFFORT, in the same sense (and by the
44
+ * same helper) as every other observe-only payload here: a graph `structuredClone` cannot take — a
45
+ * function, a live handle — falls back to a shallow copy whose nested objects still alias, and a
46
+ * `SharedArrayBuffer` shares its memory even when the clone succeeds. Phase-1 arguments are the model's
47
+ * JSON on the ordinary path, which clones whole; the residual is reachable only through values a
48
+ * DEPLOYMENT put there (its own rewrite, or driving `runToolGate` directly), and the face and the
49
+ * deployment are one trust principal — this seam helps a deployment keep its own declaration, it is not
50
+ * a boundary against an adversary.
51
+ *
52
+ * If a declared-observational face RETURNS SOMETHING ANYWAY, the return is REFUSED, not obeyed: the
53
+ * call proceeds as if the hook had no opinion (`undefined`), and the deployment's `onError` face is
54
+ * told (`phase:"hook"`, classification `observational-hook-verdict-ignored`). That covers the whole
55
+ * result object — an `additionalContext` injection is a run-affecting capability too, so a face that
56
+ * declared it only watches does not get to write into the model's transcript either. The refusal is
57
+ * loud on purpose: a declaration that could be quietly contradicted would be a way to keep a
58
+ * screening face's verdicts while shedding the delegation constraint they belong to.
59
+ */
6
60
  preToolUseObservational?: true;
7
61
  postToolUse?(toolName: string, input: unknown, output: HookToolOutput, ctx: HookToolContext): PostToolUseResult | undefined | Promise<PostToolUseResult | undefined>;
8
62
  userPromptSubmit?(prompt: string): UserPromptSubmitResult | undefined | Promise<UserPromptSubmitResult | undefined>;
63
+ /**
64
+ * roadmap #5 (CC 198 Stop hook, :473831-): runs when the agent WOULD OTHERWISE END its run (no
65
+ * more tool calls, steering and follow-up queues dry). Return `{ block: reason }` to PUSH BACK —
66
+ * the reason is injected as a model-readable message and the run continues another turn (external
67
+ * adjudication: "the tests still fail", "the deliverable is missing X"). Return `undefined` to let
68
+ * the run end. Guardrails (CC-exact): consecutive blocks are capped (default 8 — the loop then
69
+ * overrides and ends with a warning); `ctx.stopHookActive` is true when the run is ALREADY
70
+ * continuing due to a previous block — a hook that ignores it and blocks unconditionally loops
71
+ * until the cap. `maxTurns` still outranks everything.
72
+ */
9
73
  stop?(ctx: StopHookContext): StopHookResult | undefined | Promise<StopHookResult | undefined>;
74
+ /**
75
+ * design/134 (CC PostToolUseFailure parity, SDK 0.3.202): runs after a tool EXECUTION fails —
76
+ * mutually exclusive with {@link postToolUse} (success → postToolUse; failure → this). Fires ONLY
77
+ * for genuine execution failures: a gate/policy block, a plan-mode deny, a pre-execution abort or a
78
+ * durable suspend triggers NEITHER callback (CC scopes those to the PermissionDenied event domain,
79
+ * not implemented here). Capability is additionalContext-only (CC-exact): the text is appended to
80
+ * the error tool result as a `<system-reminder>`; the failure itself cannot be rewritten.
81
+ */
10
82
  postToolUseFailure?(toolName: string, input: unknown, failure: HookToolFailure, ctx: HookToolContext): PostToolUseFailureResult | undefined | Promise<PostToolUseFailureResult | undefined>;
83
+ /**
84
+ * design/134 (CC PostToolBatch parity, SDK 0.3.202): fires ONCE after every tool call in a batch
85
+ * (one assistant turn's calls, design/120 stream-inline included) has resolved, before the next
86
+ * model request. Per-tool postToolUse/postToolUseFailure fire first; the batch callback fires after,
87
+ * at the turn boundary. A zero-tool turn does not fire; an aborted/steered-away boundary does not
88
+ * fire (same gate family as design/133 attachments). additionalContext is injected once for the
89
+ * whole batch as a boundary `<system-reminder>` (sanitized, shares the attachment byte cap).
90
+ *
91
+ * RB-467 (test-AI black-box repro, board 2305: ONE injection ate a whole subsequent batch): a batch
92
+ * landing on a boundary where the engine itself injected (final-verification nudge / finalize
93
+ * write-out) is now DELIVERED with `meta.injectedThisTurn` set, instead of being suppressed — for a
94
+ * deployment observer, any suppressed boundary is lost audit data; attribution belongs to the
95
+ * observer. On such a boundary the hook's `additionalContext` is IGNORED (the boundary already
96
+ * carries an engine injection; stacking observer text there is the double-inject this gate
97
+ * originally guarded against — that half of the old behavior stands).
98
+ */
11
99
  postToolBatch?(batch: PostToolBatchCall[], meta?: {
12
100
  injectedThisTurn: "final_verification" | "finalize";
13
101
  }): PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>;
102
+ /**
103
+ * design/134 (CC PreCompact parity): runs before each compaction, AFTER the trigger gate and a
104
+ * valid cut point are confirmed (so every preCompact corresponds to a compaction that would
105
+ * actually happen) and before the summary LLM call. `trigger`: "auto" (threshold) | "manual"
106
+ * (/compact) | "forced" (promptTooLong recovery and trim-pressure — compaction is not optional
107
+ * there). Return `{ block }` to skip this compaction (honored on auto/manual ONLY; ignored with a
108
+ * trace on forced — blocking a compaction the provider already demanded would kill the run).
109
+ * `additionalInstructions` is merged (appended) into the summarization instructions (CC
110
+ * mergeHookInstructions shape). A THROWING preCompact is swallowed on every path (traced, treated
111
+ * as no-block): an observer bug must never feed the compaction breaker or kill PTL recovery.
112
+ */
14
113
  preCompact?(ctx: PreCompactContext): PreCompactResult | undefined | Promise<PreCompactResult | undefined>;
114
+ /**
115
+ * design/134 (CC PostCompact parity): observe-only, after the compaction landed (summary appended
116
+ * to the session). No return capability (CC-exact); a throw is swallowed and traced.
117
+ */
15
118
  postCompact?(ctx: PostCompactContext): void | Promise<void>;
119
+ /**
120
+ * StopFailure (the last engine-owned event of the registry-core 0.1.51 hooks contract; CC semantics:
121
+ * "the turn ended because of an API error"). Fires ONCE, after the result is assembled, when the task
122
+ * terminates `failed` BECAUSE the model/API layer errored (a brain error terminal —
123
+ * auth / network / rate_limit / server_error / …). It does NOT fire for engine-owned failure classes:
124
+ * budget, timeout/abort, max-turns, degenerate/walltime cutoffs (deliberate engine cuts), storage
125
+ * conflict, blocked, or a durable suspend — those are not API errors. Observe-only (CC-exact: the
126
+ * contract defines no StopFailure output capability); a throwing callback is swallowed + traced
127
+ * (design/134 discipline — an observer bug must never rewrite a terminal state).
128
+ */
16
129
  stopFailure?(ctx: StopFailureContext): void | Promise<void>;
130
+ /**
131
+ * design/134 R5 (CC PermissionDenied parity, SDK 0.3.202 — service [418]③): runs when the tool gate's
132
+ * adjudicate chain DENY-SHORT-CIRCUITS a tool call — a ToolPolicy deny, a gate tighten (egress /
133
+ * irreversibility / coarse shellGate) whose `ask` resolved to deny (headless auto-deny included), or the
134
+ * plan-mode write-deny. Closes the observation blind spot where a blocked call fires NEITHER postToolUse
135
+ * NOR postToolUseFailure (R3 scoping). Does NOT fire for:
136
+ * - a **durable suspend** (the ask path — CC surfaces that via a `can_use_tool` control_request);
137
+ * - a **PreToolUse hook deny** (CC-exact: "PreToolUse hook denies bypass canUseTool and are not
138
+ * covered here").
139
+ * DOES fire for a **PreToolUse hook CRASH** (RB-442, `source:"hook"`): a throwing hook is intercepted by
140
+ * the ENGINE, which then makes its OWN fail-closed deny decision. The R5 exclusion above covers the
141
+ * hook's own DENY DECISION — not an engine decision that merely happened near a hook.
142
+ * Observe-only v1 (CC-exact payload `{toolName, input, toolCallId, reason}` + our `source` enum standing
143
+ * in for CC `decision_reason_type`): no `retry` capability (recorded — retry semantics vs our adjudicate
144
+ * chain need their own design), no additionalContext. A throwing callback is swallowed + traced via
145
+ * `onError(phase:"hook")` — an observer bug must never alter the deny outcome (design/134 discipline).
146
+ */
17
147
  permissionDenied?(payload: PermissionDeniedPayload): void | Promise<void>;
18
148
  }
149
+ /**
150
+ * Where a {@link Hooks.permissionDenied} deny came from — our gate-source enum standing in for CC's
151
+ * `decision_reason_type` ('classifier'|'asyncAgent'|'mode'|'rule'), named after OUR adjudicate-chain
152
+ * sources (design/134 R5):
153
+ * - `"policy"` — the ToolPolicy denied directly, or a policy-raised `ask` resolved to deny
154
+ * (headless auto-deny / approver said no / approval aborted).
155
+ * - `"hook"` — a PreToolUse hook `ask` (folded allow→ask) resolved to deny, OR the engine's fail-closed
156
+ * interception of a hook that THREW (RB-442). (A hook's own `deny` short-circuit is EXCLUDED from the
157
+ * event, CC-exact — a decision the hook made; the crash interception is a decision the ENGINE made.)
158
+ * - `"safety"` — the gate's deterministic egress/irreversibility tighten (design/70 / design/77 §4)
159
+ * raised the `ask` that resolved to deny, from an explicit per-tool mark.
160
+ * - `"shellGate"` — same tighten-deny, but the tool's irreversibility tier was installed by the COARSE
161
+ * `TaskSpec.shellGate` doctrine (design/80 D-E), not an explicit per-tool mark.
162
+ * - `"planMode"` — the design/108 plan-mode write-deny short-circuit (a read-only fidelity gate).
163
+ */
19
164
  export type PermissionDeniedSource = "policy" | "hook" | "safety" | "shellGate" | "planMode" | "classifier";
165
+ /** The payload a {@link Hooks.permissionDenied} callback observes (CC-exact fields + `source`). */
20
166
  export interface PermissionDeniedPayload {
21
167
  toolName: string;
168
+ /** The FINAL (post-hook-rewrite / post-policy-rewrite) args the chain adjudicated — what would have
169
+ * executed; not necessarily the model's original args. */
22
170
  input: unknown;
23
171
  toolCallId: string;
172
+ /** Human/model-readable deny reason (CC `reason`) — the RAW decision text, NOT `<system-reminder>`-wrapped. */
24
173
  reason: string;
174
+ /** Which gate source produced the deny (our `decision_reason_type` analog). */
25
175
  source: PermissionDeniedSource;
26
176
  }
177
+ /**
178
+ * 1.256 复审 MED-1 — observe-only payload isolation for {@link Hooks.permissionDenied}: clone the tool
179
+ * args before they ride the observer payload, so a hook mutating `payload.input` can never pollute the
180
+ * LIVE args object (later events / audit records share it). Same posture as the postToolUseFailure
181
+ * details clone (prepare-task): `structuredClone` first; a non-structured-cloneable graph
182
+ * (functions/handles) falls back to a SHALLOW plain object/array copy (top-level mutation isolated);
183
+ * a non-object primitive passes through as-is (immutable anyway).
184
+ */
27
185
  export declare function cloneObserverInput(input: unknown): unknown;
186
+ /** Context for {@link Hooks.stopFailure} — aligned with the TaskResult error face (observe-only). */
28
187
  export interface StopFailureContext {
188
+ /** Human-readable error message (the assembled `errorMessage`; any `[code]` prefix already stripped). */
29
189
  error: string;
190
+ /** Machine-readable kind — the assembled `errorCode` lifted from the brain's `[code]` prefix
191
+ * (auth / network / rate_limit / server_error / …); absent when the provider stamped no code. */
30
192
  errorKind?: string;
193
+ /** Turns completed when the failure ended the run. */
31
194
  turns: number;
32
195
  }
196
+ /** The failed tool execution a PostToolUseFailure callback inspects (design/134). */
33
197
  export interface HookToolFailure {
198
+ /** Model-facing error text of the failed call (the error tool result's text content, joined). */
34
199
  error: string;
200
+ /** Wide any-abort semantics (⊇ CC `is_interrupt`, which is user-interrupt specifically): true when
201
+ * the task abort signal fired — timeout, budget, cancel or suspend — not only a human interrupt. */
35
202
  isInterrupt: boolean;
203
+ /** Tool execution time in ms when the engine has it (CC 0.3.202 `duration_ms` parity); else absent. */
36
204
  durationMs?: number;
205
+ /** Element-level clone of the error result's content blocks (mutating them cannot rewrite the
206
+ * transcript — the failure is observe-only). */
37
207
  content: Array<TextContent | ImageContent | DocumentContent>;
208
+ /** BEST-EFFORT deep clone (structuredClone) of the tool's raw details. When the details graph is
209
+ * not structured-cloneable (functions/handles), this is the LIVE reference — treat it as
210
+ * READ-ONLY: a mutation would corrupt the result the loop is about to commit. */
38
211
  details: unknown;
39
212
  }
213
+ /** A PostToolUseFailure result: append model-readable context to the error result (CC-exact, no rewrite). */
40
214
  export interface PostToolUseFailureResult {
41
215
  additionalContext?: string;
42
216
  }
217
+ /** One resolved call in a postToolBatch payload (CC PostToolBatchToolCall shape, thin projection —
218
+ * `response` is a bounded text digest of the result, never the full details object). */
43
219
  export interface PostToolBatchCall {
44
220
  toolName: string;
45
221
  input: unknown;
@@ -47,88 +223,307 @@ export interface PostToolBatchCall {
47
223
  response?: string;
48
224
  isError: boolean;
49
225
  }
226
+ /** A PostToolBatch result: inject context once for the whole batch at the turn boundary. */
50
227
  export interface PostToolBatchResult {
51
228
  additionalContext?: string;
52
229
  }
230
+ /** Context for {@link Hooks.preCompact} (design/134). */
53
231
  export interface PreCompactContext {
232
+ /** "auto" = threshold-triggered; "manual" = /compact; "forced" = promptTooLong recovery or
233
+ * trim-pressure propagation (block is ignored on forced — the compaction is not optional). */
54
234
  trigger: "auto" | "manual" | "forced";
235
+ /** The summarization instructions in effect (spec/deployment-level), when set. */
55
236
  customInstructions?: string;
56
237
  }
238
+ /** A PreCompact result: skip this compaction (auto/manual only) and/or extend the summary instructions. */
57
239
  export interface PreCompactResult {
240
+ /** When set (and trigger is not "forced"), this compaction is skipped; the reason is traced. */
58
241
  block?: string;
242
+ /** Appended to the summarization instructions for this compaction (never replaces them).
243
+ * LLM-summary path only: a compaction served from a `summaryProvider` reuse (Seam C) never runs
244
+ * the summarization call, so these instructions are not consumed there (recorded design gap —
245
+ * extending the provider contract is out of scope for design/134). */
59
246
  additionalInstructions?: string;
60
247
  }
248
+ /** Context for {@link Hooks.postCompact} (design/134, observe-only). */
61
249
  export interface PostCompactContext {
62
250
  trigger: "auto" | "manual" | "forced";
251
+ /** The conversation summary the compaction produced (CC `compact_summary` parity). */
63
252
  summary: string;
64
253
  tokensBefore?: number;
65
254
  tokensAfter?: number;
66
255
  }
256
+ /** Context for the {@link Hooks.stop} hook (CC `stop_hook_active` parity). */
67
257
  export interface StopHookContext {
258
+ /** True when this run is already continuing because a previous stop() blocked — check it and
259
+ * return success (undefined) once your condition can't be improved, or you will loop to the cap. */
68
260
  stopHookActive: boolean;
261
+ /** Consecutive blocks so far in this run (resets when a stop() call lets the run proceed). */
69
262
  consecutiveBlocks: number;
263
+ /**
264
+ * RB-177 ([1730], measured downstream) — a view of the conversation this hook is being asked to judge.
265
+ *
266
+ * Until now a Stop hook received two counters and nothing else. That is enough for a hook that asks
267
+ * "have I blocked too often?", and structurally insufficient for the entire class of hooks that ask
268
+ * anything ABOUT THE RUN — which is what the prompt-shaped hooks a settings file can declare are for.
269
+ * Measured end to end downstream: the carrier was constructed, invoked, and returned without error, and
270
+ * the model answered "I don't have direct access to your file system…" — because nobody had given the
271
+ * evaluator the session. Every layer reported success and the feature could not work. A hook cannot
272
+ * judge a run it cannot see, so the gap belonged here rather than in any of the layers that were
273
+ * dutifully passing an empty view along.
274
+ *
275
+ * LAZY on purpose: a hook that only reads the counters pays nothing, and a hook that needs the
276
+ * conversation gets it in full rather than through a truncation policy the engine would have to invent
277
+ * on its behalf. Callers decide what to send to a model and how much — that decision has a budget and a
278
+ * trust tier attached, and both belong to the caller.
279
+ *
280
+ * Absent when the runner cannot supply one (a stop gate wired without a session).
281
+ */
70
282
  getBranch?: () => Promise<readonly SessionTreeEntry[]>;
71
283
  }
284
+ /** A Stop hook result: block the run from ending, with a model-readable reason. */
72
285
  export interface StopHookResult {
286
+ /** When set, the run does NOT end: this reason is injected (as a `<system-reminder>`) and the
287
+ * model gets another turn to address it. */
73
288
  block?: string;
289
+ /**
290
+ * CC 2.1.201 parity (CC :472050-472077): extra model-readable context injected at the stop point —
291
+ * DECOUPLED from `block`. When present it is injected (as a `<system-reminder>`, neutral framing:
292
+ * "Stop hook additional context: …") whether or not the hook blocked; `additionalContext` WITHOUT
293
+ * `block` still continues the run one more turn, but does NOT count toward the consecutive-block
294
+ * cap (the cap is driven by `block` alone).
295
+ */
74
296
  additionalContext?: string;
75
297
  }
298
+ /**
299
+ * A READ-ONLY path-resolution capability face over the task's {@link ExecutionEnv}, handed to tool hooks
300
+ * on {@link HookToolContext.env} (the hook-env capability seam).
301
+ *
302
+ * **Why it exists.** A hook that judges a path — "is this write inside the workspace", "does this
303
+ * innocent-looking name resolve onto a guarded file" — must resolve it in the filesystem the HANDS
304
+ * actually write to. For a sandboxed/remote deployment that filesystem is NOT the host process's `fs`:
305
+ * the env is minted by `RunnerDeps.executionEnvFactory` DURING prepare, i.e. after the spec (and any
306
+ * policy/hook the caller built with it) already exists, so a hook closed over a spec-time env resolves
307
+ * against the wrong disk and answers `exists`/symlink questions about a different machine. Reading the
308
+ * env off the CALL CONTEXT removes the ordering problem structurally: the context is built after the env
309
+ * is, and a resumed/revived run's rebuilt env is picked up with no re-instantiation protocol.
310
+ *
311
+ * **Capabilities, not the object.** Hooks are an extension boundary the deployment fills with code whose
312
+ * job is to DECIDE; the whole `ExecutionEnv` is a face that can ACT (`exec`, `writeFile`, `remove`,
313
+ * `cleanup`). So this is four INDEPENDENTLY BOUND read primitives and nothing else: no `env`
314
+ * back-reference, no prototype of the env object, and the face itself is frozen (a hook cannot re-point a
315
+ * member for a later hook in the same run). Widening it later is additive and cheap; taking a wide object
316
+ * back is not.
317
+ *
318
+ * The line this draws, stated so nobody reads more into it: it separates what a hook is HANDED from what
319
+ * the environment can do. It is NOT an isolation boundary against code that can already reach the env's own
320
+ * class — anything that can redefine `SomeExecutionEnv.prototype.exec` intercepts every call the ENGINE
321
+ * makes into that env, receiver included, and needs nothing from this face to do it. Deployments that must
322
+ * withstand that run their hooks somewhere else, not behind a narrower argument.
323
+ *
324
+ * **Names are the env's own** ({@link ExecutionEnv} / `FileSystem`), deliberately: a hook author reads one
325
+ * contract, not a translation of it. The seam sketch's `canonicalize`/`root` are `canonicalPath`/`cwd`
326
+ * here — `canonicalPath` IS the realpath-semantics primitive, and `ExecutionEnv` declares no "root" at all
327
+ * (the engine's task root is a RUNNER notion — `deps.rootPath`/worktree/`requestedCwd` — not something the
328
+ * environment reports about itself), so the honest anchor is the env's own working directory. `cwd` is a
329
+ * FUNCTION, not the snapshot string, so a hook reading it late sees where the env is NOW. Read {@link
330
+ * HookEnvCapabilities.cwd} before using it as a base for RELATIVE paths — the task's tracked cwd is a
331
+ * different value, and this face carries the env's, on purpose.
332
+ *
333
+ * **Every member is optional, and absence is the signal.** A member is present iff the underlying env
334
+ * exposes that primitive (`readLink` is optional in `FileSystem`; a transport env may implement less than
335
+ * it declares). A missing member is never a stub that throws or an emulation that guesses — the hook is
336
+ * told, in the only way that cannot be misread, that this environment cannot answer, and takes its own
337
+ * conservative branch. The face carries a NULL PROTOTYPE so that reading is a question about this object
338
+ * alone (see {@link createHookEnvCapabilities}). Errors that the env CAN report stay in the `Result`
339
+ * (these methods never throw, inheriting the `FileSystem` contract verbatim).
340
+ */
76
341
  export interface HookEnvCapabilities {
342
+ /** {@link ExecutionEnv.canonicalPath} — the canonical path of an EXISTING path, symlinks resolved
343
+ * (realpath semantics), in the env's own namespace. */
77
344
  canonicalPath?(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
345
+ /** {@link ExecutionEnv.exists} — `false` for a missing path; other failures (permission, transport)
346
+ * come back as a `FileError`, which is NOT an answer of "absent". */
78
347
  exists?(path: string, abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
348
+ /** {@link ExecutionEnv.readLink} — a symlink's raw target, ONE hop, not followed. Absent whenever the
349
+ * env omits the primitive: a DANGLING symlink's real target is then unknowable to the hook. */
79
350
  readLink?(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
351
+ /**
352
+ * {@link ExecutionEnv.cwd} — the ENVIRONMENT's own working directory, read live: the base the env's own
353
+ * primitives resolve a relative path against, and the only anchor it reports about itself.
354
+ *
355
+ * NOT the task's tracked cwd, and the difference is load-bearing for anyone judging a RELATIVE path.
356
+ * The engine keeps a separate per-task cwd — it starts at the resolved task root (which is
357
+ * `deps.rootPath` when that is set, not necessarily the env's cwd) and moves with the shell's `cd` and
358
+ * with worktree entry — and the TOOLS' relative paths resolve against THAT, while the environment is
359
+ * never told it moved. So a hook that must decide a relative target the way the tool will interpret it
360
+ * cannot take the base from here: judge absolute targets, or have the deployment hand the tracked cwd
361
+ * to the hook through its own channel. What this member is good for is the env-side anchor — which
362
+ * filesystem/mount the answers below are about.
363
+ */
80
364
  cwd?(): string;
81
365
  }
366
+ /**
367
+ * Build the {@link HookEnvCapabilities} face for `env`. One wrapper per primitive (never a bound method of
368
+ * the env, never the env itself), presence probed at build time so the face's shape reports what this env
369
+ * can actually do, and frozen so it cannot be re-pointed by whoever receives it.
370
+ *
371
+ * Two details the "absent means cannot" contract rests on, both cheap and both load-bearing:
372
+ * · **null prototype, both ways.** Absence must be a fact about THIS object — on the way OUT (a face
373
+ * behind `Object.prototype` would answer `ctx.env.readLink` from there, turning "this env cannot read
374
+ * links" into "here is some function"; freezing the face does not freeze what it inherits) and on the
375
+ * way IN ({@link findEnvSlot} stops before `Object.prototype`, so an ambient property of that name is
376
+ * never adopted as a capability and never gets called with the env as its receiver).
377
+ * · **capture, do not re-read.** Each primitive is read ONCE, checked, and that captured callable is what
378
+ * the wrapper invokes — through module-load-captured intrinsics, never a build-time or call-time global
379
+ * lookup. Re-reading the property, or the machinery that does the reading, would let something other
380
+ * than what was validated end up running, or the receiver end up somewhere it was never handed.
381
+ * {@link createCwdReader} carries the one live read under the same rules.
382
+ */
82
383
  export declare function createHookEnvCapabilities(env: ExecutionEnv): HookEnvCapabilities;
384
+ /** Identifying context passed to tool hooks. */
83
385
  export interface HookToolContext {
84
386
  toolCallId: string;
85
387
  toolName: string;
388
+ /**
389
+ * The read-only path-resolution face over the env the hands run against (see
390
+ * {@link HookEnvCapabilities} for the why, and for why it is capabilities rather than the env object).
391
+ *
392
+ * ABSENT when the deployment wired no execution environment at all (no `executionEnv` /
393
+ * `executionEnvFactory`: the run has no hands, and the engine's internal no-I/O placeholder is not an
394
+ * environment to report). Present ⇒ inspect the individual members: each one is itself optional.
395
+ */
86
396
  env?: HookEnvCapabilities;
87
397
  }
398
+ /** The executed tool result a PostToolUse hook inspects. */
88
399
  export interface HookToolOutput {
89
400
  content: Array<TextContent | ImageContent | DocumentContent>;
90
401
  details: unknown;
91
402
  isError: boolean;
92
403
  }
404
+ /**
405
+ * A PreToolUse hook result: a {@link PermissionResult} (so a hook can `deny`/`ask`, or `allow` with an
406
+ * `updatedInput` rewrite) plus optional `additionalContext` injected into the eventual tool result.
407
+ */
93
408
  export type PreToolUseResult = PermissionResult & {
94
409
  additionalContext?: string;
95
410
  };
411
+ /** A PostToolUse hook result: replace the tool output and/or append context (both optional). */
96
412
  export interface PostToolUseResult {
413
+ /** If provided, replaces the tool result content array in full. */
97
414
  updatedOutput?: Array<TextContent | ImageContent | DocumentContent>;
415
+ /** Appended to the result as a `<system-reminder>` the model can read. */
98
416
  additionalContext?: string;
99
417
  }
418
+ /** A UserPromptSubmit hook result: block the submission, or inject context ahead of the prompt. */
100
419
  export interface UserPromptSubmitResult {
420
+ /** Block submission entirely; the task fails with this model-readable reason. */
101
421
  block?: string;
422
+ /** Injected ahead of the user's prompt (wrapped as a `<system-reminder>`). */
102
423
  additionalContext?: string;
103
424
  }
425
+ /** Wrap model-facing hook/gate feedback in a `<system-reminder>` so it reads as guidance, not data.
426
+ * NOTE (council design/74 #6): this does NOT escape a literal `</system-reminder>` in `text` — callers MUST
427
+ * pass trusted, first-party strings (every current caller does: fixed gate/limit messages). If a future
428
+ * caller needs to relay UNTRUSTED content (tool output, user data), it must sanitize the close tag first
429
+ * (or use the `delimitUntrusted` fence), or a crafted payload could break out of the reminder framing. */
104
430
  export declare function formatHookFeedback(text: string): string;
431
+ /** The outcome of the two-phase tool gate, mapped onto the harness `tool_call` hook return shape. */
105
432
  export interface ToolGateResult {
433
+ /** Block execution (the loop emits an error tool result with `reason`). */
106
434
  block?: boolean;
435
+ /** Model-readable block reason (already `<system-reminder>`-wrapped). */
107
436
  reason?: string;
437
+ /**
438
+ * What ended the approval this call was waiting on, when this gate pass settled one (the ask
439
+ * resolution's own {@link import("./tool-policy.js").ApprovalSettledBy}, carried out verbatim — this
440
+ * layer classifies nothing). Present on BOTH terminal exits, the block and the allow: an approved
441
+ * call is a settled approval too, and a caller that saw the source only on refusals would read its
442
+ * absence on an execution as "nobody approved this". Absent on everything that settled no approval —
443
+ * a straight policy deny, a hook deny, a posture verdict where nobody was asked, every ordinary
444
+ * allow. The caller records it on its own per-call sideband so it reaches that call's `tool_end`
445
+ * frame: the machine-readable half of the disclosure `reason` makes in words, on a channel no tool
446
+ * and no post-tool hook can write.
447
+ */
108
448
  settledBy?: import("./tool-policy.js").ApprovalSettledBy;
449
+ /** Rewritten args to execute with (re-validated by the loop); omitted when nothing rewrote. */
109
450
  updatedInput?: unknown;
451
+ /**
452
+ * design/45: the gate routed a policy `ask` to a **durable suspension** (no synchronous approver).
453
+ * `suspendAsk` already persisted the checkpoint and aborted the run; the caller records this so the
454
+ * task assembles as `status:"suspended"` with the token. Suspend lives ONLY here in the gate's `ask`
455
+ * branch — it never becomes a `PermissionResult` action (policy stays pure allow/ask/deny, §15.2).
456
+ */
110
457
  suspend?: {
111
458
  token: import("./checkpoint-store.js").CheckpointToken;
112
459
  gate: import("./checkpoint-store.js").CheckpointGate;
113
460
  };
461
+ /** PreToolUse `additionalContext` to append to this call's eventual tool result (correlated by id). */
114
462
  preToolContext: string[];
115
463
  }
464
+ /**
465
+ * The gate's content-ask routing outcome — deliberately a channel of its OWN, not a
466
+ * field on the permission verdict. A permission verdict is folded, re-adjudicated and re-presented by
467
+ * layers that know nothing about answers; an answer riding one of those would be dropped by the fold or
468
+ * carried into a second adjudication. Three arms, one per thing that can happen:
469
+ * · `answered` — a person answered. The gate lets the call execute and the answer is delivered to the
470
+ * tool through the run's own one-shot binding (the gate never carries the answer itself).
471
+ * · `unavailable` — nobody was reachable and NOTHING was shown (the {@link
472
+ * import("./ask-question.js").QuestionUnavailable} contract). Only this arm may be re-routed — the
473
+ * gate parks the question for a later human, or answers the honest "it was NOT shown" refusal.
474
+ * · `delivery_failure` — delivery was attempted and failed. NEVER re-routed: the question may already
475
+ * have been rendered to a person, so parking it would put one question in front of two deciders. It
476
+ * is reported to the model through the tool's own coded-failure / degrade exits.
477
+ */
116
478
  export type ContentAskOutcome = {
117
479
  kind: "answered";
480
+ /** The EXACT batch that was put in front of the person, retained by the resolver as an isolated
481
+ * snapshot. The gate executes this and nothing else, so "what was shown" and "what runs" are the
482
+ * same object graph by construction rather than by two parties agreeing not to mutate one. */
118
483
  presentedInput: unknown;
119
484
  } | {
120
485
  kind: "unavailable";
486
+ /** The retained snapshot, when the resolver got far enough to make one (i.e. it actually waited
487
+ * on a callback). The park persists THIS, not the caller's live object: the wait is a window in
488
+ * which whoever owns the rewrite can change it, and a checkpoint holding bytes the gate never
489
+ * adjudicated is one a later human would answer as a different question. Absent when no wait
490
+ * happened, in which case there is no window and the live object is still the validated one. */
121
491
  presentedInput?: unknown;
492
+ /** True iff the park closure took its SYNC-FIRST DECLINE for this call — it has not yet made a
493
+ * real suspend attempt, so re-entering it now IS that attempt rather than a second one. False
494
+ * when the park already had its attempt (no live face to prefer, or a durable mandate): the gate
495
+ * must not re-enter, because a suspend and a checkpoint write are not idempotent, and a backend
496
+ * that committed before surfacing a transport error would be left an orphan row under a second
497
+ * token. Exactly one real attempt per call, on every path. */
122
498
  parkDeclined: boolean;
123
499
  } | {
124
500
  kind: "delivery_failure";
125
501
  code: string;
502
+ /** The retained snapshot, when the resolver got far enough to make one. Executed for the same
503
+ * reason the answered arm's is: the bound failure names the batch it belongs to, and executing a
504
+ * different one would drop it and re-ask the person. Absent when the refusal happened before a
505
+ * snapshot could be taken. */
126
506
  presentedInput?: unknown;
127
507
  };
508
+ /**
509
+ * A durable park that was ATTEMPTED for this call and could not be minted, carrying the reason in the
510
+ * words of whatever refused it. Distinct from the `undefined` return, which means "no park attempt
511
+ * happened" (no facility armed, or the closure took its sync-first decline) — that case hides nothing,
512
+ * while this one does: the gate falls back to the synchronous approver chain either way (the
513
+ * compensation contract — an uncommitted checkpoint is a safe fallback — is unchanged), but on a
514
+ * headless deployment that fallback is the auto-deny whose text is "no approver is wired". True as far
515
+ * as it goes, and the whole reason the call did not park lives somewhere else entirely (the host's
516
+ * `onError` face). So the reason travels with the fall-through and the deny quotes it.
517
+ */
128
518
  export interface ParkAttemptFailed {
519
+ /** Why the park could not be minted — a sentence, already worded for a reader. */
129
520
  parkFailed: string;
130
521
  }
522
+ /** Inputs to the two-phase tool gate. `adjudicate`/`resolveAsk` are pre-bound to the task abort signal. */
131
523
  export interface ToolGateInput {
524
+ /** RB-473 batch 2b — bounded disclosure for a HOST callback that throws inside this gate call's
525
+ * isolation scope (onHookError itself, the deny observer, the durable-park observer). The runner
526
+ * wires this to an `observer.notify_failed` trace event; absent = counted only (unchanged). */
132
527
  onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
133
528
  event: {
134
529
  toolCallId: string;
@@ -136,29 +531,181 @@ export interface ToolGateInput {
136
531
  input: Record<string, unknown>;
137
532
  };
138
533
  preToolUse?: Hooks["preToolUse"];
534
+ /** The read-only env capability face put on every {@link HookToolContext} this gate call
535
+ * builds ({@link HookEnvCapabilities}). Built ONCE per task by the runner (after the env is minted) and
536
+ * passed through unchanged; omitted when the deployment wired no execution environment. */
139
537
  hookEnv?: HookEnvCapabilities;
538
+ /** The combined tool-policy check (abort-bound), or undefined when no policy is wired (→ allow). */
140
539
  adjudicate?: (req: ToolCallRequest) => Promise<PermissionResult>;
540
+ /** Resolve an `ask` to allow/deny via `onAsk` (abort-bound). Required iff a decision can be `ask`.
541
+ * G1 three-value ([871]①): the result may carry `approverUnavailable` — the live approver affirmed no
542
+ * operator is reachable for THIS ask; the gate then re-routes onto the durable park leg (below),
543
+ * keeping the carried fail-closed deny when no park is armed. */
141
544
  resolveAsk: (decision: PermissionResult, req: ToolCallRequest) => Promise<ResolvedAsk>;
142
- suspendAsk?: (req: ToolCallRequest, postHookArgs: unknown, safety?: import("./checkpoint-store.js").SafetyAxis, liveFaceUnavailable?: boolean) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
545
+ /**
546
+ * design/45: route a policy `ask` to a **durable suspension** instead of the synchronous `resolveAsk`
547
+ * (onAsk) path. Called in the `ask` branch with the FINAL post-hook args; if it returns a suspend
548
+ * directive it has already persisted the checkpoint + aborted the run, and the gate short-circuits to
549
+ * `{ suspend }`. Returns `undefined` to fall through to `resolveAsk` (non-durable / declined). A
550
+ * {@link ParkAttemptFailed} ALSO falls through to `resolveAsk` — same fallback, same compensation —
551
+ * but carries why the mint failed so a resulting deny can name it instead of leaving the model and
552
+ * the operator with the fallback's own reason as if it were the cause.
553
+ * Omitted entirely → no durable mode (the 1.63 synchronous path, unchanged).
554
+ *
555
+ * The armed closure itself may also DECLINE (return `undefined` up front) to prefer the live
556
+ * synchronous approver: the Runner's closure does so for a plain AND for a SAFETY ask whenever the
557
+ * resolved seat is a FUNCTION-valued `onAsk` — even under `durableApproval`, which is the FALLBACK for
558
+ * when no live approver is present, not an "always park" mandate — an interactive per-edit ask must
559
+ * resolve in-stream, not cost a park/rebuild round-trip. The gate stays a pure fall-through consumer of
560
+ * that choice. What KEEPS the durable leg is no function seat (absent, or a blanket `"allow"`/`"deny"`
561
+ * string mode — a policy setting is not a live approver), a control-plane `forceDurableGate`, a live
562
+ * face that already answered `"unavailable"` for this ask, or an inherited-unavailable marked call.
563
+ * `safety` no longer participates in the park/sync decision at all — it only selects the gate KIND
564
+ * (`irreversible_ask`) once a park is minted.
565
+ */
566
+ suspendAsk?: (req: ToolCallRequest, postHookArgs: unknown,
567
+ /** design/80 D-2: present (non-undefined) ONLY when this ask is a SAFETY tighten (the egress/
568
+ * irreversibility gate tightened a surviving `allow` to `ask`, `decisionReason === "safety"`). Drives the
569
+ * mint-site gate-kind choice — a safety tighten mints `irreversible_ask` (non-budgetable) EVEN WHEN
570
+ * `durableApproval` is wired. The object records which axis(es) tightened (persisted as the gate's
571
+ * `safetyAxis`). Absent for a plain policy/hook `ask` (→ the normal `human`/`irreversible_ask` choice). */
572
+ safety?: import("./checkpoint-store.js").SafetyAxis,
573
+ /** The three-valued live-face verdict, widened by design/174 to cover BOTH live faces: true ⇒ the live face this
574
+ * ask would have been routed to already answered `"unavailable"` for it — the permission approver for
575
+ * a permission ask, the question face for a content ask. Either way there is no live leg left to
576
+ * prefer, so the closure must NOT take its sync-first decline and should park durably. All other
577
+ * decline/pre-commit-failure paths keep their existing `undefined` fallbacks (the gate then keeps the
578
+ * fail-closed deny / typed refusal the caller carries). */
579
+ liveFaceUnavailable?: boolean) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
580
+ /**
581
+ * design/174 — route a policy `ask` on the reserved question tool to this run's CONTENT-ask channel
582
+ * before it can become a park or a refusal. Called in the `ask` branch with the FINAL post-hook,
583
+ * post-policy request (the same args that will execute), and it owns everything about delivery:
584
+ * validating the batch before anything is put in front of a person, calling the live face, and binding
585
+ * the answer to this call id for the tool to pick up. It returns only WHICH of the three outcomes
586
+ * happened — never the answer, which travels the run's own binding.
587
+ *
588
+ * CONTRACT: it must not throw (a delivery failure is a returned value, so the gate can tell "failed"
589
+ * from "nobody home" — the two have opposite re-routing rules). Passed UNCONDITIONALLY by the runner,
590
+ * including on runs with no live face: it then answers `unavailable` without contacting anything, which
591
+ * is exactly the pre-174 park/refusal behavior. Absent entirely ⇒ the pre-174 path verbatim.
592
+ */
143
593
  resolveContentAsk?: (req: ToolCallRequest) => Promise<ContentAskOutcome>;
594
+ /**
595
+ * design/70: the called tool is egress-marked (`ToolSpec.egress` — an external write: open a PR,
596
+ * push, send). The gate tightens a surviving `allow` to `ask` so an egress tool is NEVER
597
+ * auto-allowed — execution always passes an explicit ask-resolution (onAsk / durable suspend);
598
+ * headless resolves to deny. Filled by the caller from the tool's spec; policies stay egress-blind.
599
+ */
144
600
  egress?: boolean;
601
+ /**
602
+ * design/77 §4 (Gate 4): the called tool's irreversibility tier (`ToolSpec.irreversibility`), filled by
603
+ * the caller from the tool's spec. After the egress tighten, the gate runs a SECOND deterministic tighten:
604
+ * `"always"` tightens a surviving `allow` to `ask`; `"maybe"` calls {@link reversibilityProbe} and tightens
605
+ * UNLESS the probe reports reversible (fail-closed on timeout/throw); `"never"`/undefined → untouched.
606
+ * Policies stay irreversibility-blind; the gate is the single chokepoint.
607
+ */
145
608
  irreversibility?: "never" | "maybe" | "always";
609
+ /**
610
+ * design/77 §4: the `"maybe"`-tier probe from the tool's spec (read at prepare-time, captured here — not a
611
+ * tool argument). Called ONLY when `irreversibility === "maybe"` && the surviving decision is `allow`,
612
+ * time-bounded by {@link approvalTimeoutMs}. Fail-closed: anything other than `{ reversible: true }`
613
+ * (including a timeout or a throw) tightens to `ask`. A probe is never trusted to AUTO-ALLOW past the gate.
614
+ */
146
615
  reversibilityProbe?: (args: unknown) => {
147
616
  reversible: boolean;
148
617
  } | Promise<{
149
618
  reversible: boolean;
150
619
  }>;
620
+ /** design/77 §4: deadline (ms) for {@link reversibilityProbe}; on timeout the gate fails closed to `ask`. */
151
621
  approvalTimeoutMs?: number;
622
+ /** design/77 §4: the task abort signal — bounds {@link reversibilityProbe} by the task's real deadline
623
+ * (timeout/cancel) in addition to {@link approvalTimeoutMs}; an abort while probing fails closed to `ask`. */
152
624
  abortSignal?: AbortSignal;
625
+ /**
626
+ * design/134 R5: observer for the gate's DENY short-circuit ({@link Hooks.permissionDenied}), pre-wrapped
627
+ * by the caller (swallow + onError) so it never throws. Fired ONCE, at the single deny exit of the
628
+ * adjudicate chain — a policy deny or a resolved-ask deny. NOT fired for a PreToolUse hook deny
629
+ * (CC-exact exclusion) or a durable suspend (the ask path). runToolGate still try/catches defensively:
630
+ * an observer must never alter the deny outcome.
631
+ *
632
+ * RB-442: ALSO fired for the engine's fail-closed interception of a THROWING PreToolUse hook, with
633
+ * `source:"hook"` — the CC-exact exclusion is the hook's own deny DECISION, not an engine decision.
634
+ */
153
635
  permissionDenied?: (payload: PermissionDeniedPayload) => void | Promise<void>;
636
+ /**
637
+ * RB-442: trace sink for a PreToolUse hook that THREW — the deployment-facing half of the crash
638
+ * (`onError(phase:"hook")`, wired by the caller). The model-facing half is the fail-closed block reason;
639
+ * this one carries the exception object itself to whoever runs the deployment, because a crashing hook is
640
+ * a bug someone has to fix and the model-facing summary is bounded/sanitized. Never affects the outcome
641
+ * (a throwing sink is swallowed).
642
+ */
154
643
  onHookError?: (err: unknown) => void;
644
+ /**
645
+ * design/134 R5: true when this tool's `irreversibility` tier was installed by the COARSE
646
+ * `TaskSpec.shellGate` doctrine (design/80 D-E `shellGatedBash`/`shellGatedMonitor`), not an explicit
647
+ * per-tool mark — attributes a tighten-deny to `source:"shellGate"` instead of `"safety"`.
648
+ */
155
649
  shellGated?: boolean;
650
+ /**
651
+ * design/143 批2 ([672]-A, CC 2.1.207 auto mode): when present, a surviving `ask` is routed to the
652
+ * small-model policy CLASSIFIER before any human/durable resolution:
653
+ * - verdict `allow` → the ask resolves to allow (`decisionReason:"classifier"`) — no suspend, no onAsk;
654
+ * - verdict `block` → deny (`decisionReason:"classifier"`, `source:"classifier"`; CC `automode-blocked`);
655
+ * - `unavailable`/`parse_error` → FAIL-CLOSED: the ask continues down the ORIGINAL chain
656
+ * (durable suspend / onAsk / headless auto-deny) exactly as if auto mode were absent
657
+ * (CC `automode-unavailable`/`automode-parsing-error` — "NOT a policy decision").
658
+ * SCOPE (declared semantic): this covers EVERY surviving ask, INCLUDING the deterministic egress/
659
+ * irreversibility safety tightens — auto mode is the deployment's explicit choice to let the
660
+ * classifier be the "explicit ask-resolution" for this session (its rule set covers exactly those
661
+ * action classes: outbound writes, irreversible operations, and sensitive-value handling). A
662
+ * deployment that wants human-only
663
+ * resolution for safety asks simply does not arm auto mode. The classifier can never AUTO-ALLOW by
664
+ * failing — only an affirmative `<block>no</block>` allows.
665
+ */
156
666
  autoMode?: {
157
667
  decider: import("./auto-mode.js").AutoModeDecider;
158
668
  };
669
+ /**
670
+ * design/153 §2/§7.4 (件4 复审, MED): true means this call's ask is MARKED — an inherited ancestor
671
+ * constraint already determined "no synchronous layer may resolve this ask" (the ancestor's frozen
672
+ * approver was unreachable, OR an ancestor durable mandate was floated back because THIS task's own
673
+ * park facility can honor it). The marker's whole contract is that the ask reaches EITHER the durable
674
+ * park leg OR the fail-closed `resolveAskBound` intercept, with no synchronous decision-maker in
675
+ * between — the auto-mode classifier is exactly such a decision-maker (`decide()` can `allow`, executing
676
+ * the call) and sat strictly BEFORE both marker consumers, so a marked call under an armed classifier
677
+ * bypassed the marker's contract entirely. When true, the classifier step is skipped (falls through to
678
+ * `suspendAsk`/`resolveAsk` untouched) so a marked ask can only ever be resolved by an operator (durable
679
+ * park) or fail closed — never by the classifier.
680
+ */
159
681
  isMarkedUnresolvable?: (toolCallId: string) => boolean;
682
+ /**
683
+ * design/179 — the persisted allow-rule lane. Present only when a deployment wired a rule store; absent
684
+ * it, this whole step does not exist and the gate's decision path is byte-identical to what it was.
685
+ *
686
+ * A persisted rule is the standing form of ONE recorded human approval, so it resolves the asks a real
687
+ * person saying "allow" would have resolved — and nothing else. It sits post-fold, at the entry of the
688
+ * `ask` branch, BEFORE the classifier and AFTER every tighten: earlier and a tighten would simply put
689
+ * the ask back; later and the classifier would already have spoken.
690
+ *
691
+ * Its consumption predicate is its own, not the classifier's blanket flip:
692
+ * · `requiresRealApproval` asks are never consumed — the two integrity gates are the floor, and the
693
+ * same flag is what a v2 organization-level ask will carry;
694
+ * · a PreToolUse hook's ask is never consumed — that is the deployment's own adjudication, and a rule
695
+ * minted for a command does not overrule it;
696
+ * · the reserved question tool and a call MARKED unresolvable are excluded for the same reason the
697
+ * classifier excludes them: both contracts require that no synchronous decision-maker stands between
698
+ * the ask and the park / content route, and this lane is one.
699
+ * What it DOES consume is the egress and irreversibility/shellGate tightens and unmarked bare asks —
700
+ * deliberately, because a deployment forcing shell classification is exactly where the feature is for.
701
+ * Consuming such an ask also skips the park it would have minted; that IS what a standing approval
702
+ * means, and the first bullet is what keeps an integrity ask out of that set.
703
+ */
160
704
  persistedRules?: {
705
+ /** The canonical text of the rule that admits this call, or `undefined`. Must not throw: a store that
706
+ * cannot be read answers "no rule" and discloses on its own channel (fail-closed = more asks). */
161
707
  admits: (req: ToolCallRequest) => Promise<string | undefined>;
708
+ /** Observation sink for an ask a rule resolved — the allow-side disclosure. Never affects the outcome. */
162
709
  onResolved?: (info: {
163
710
  toolName: string;
164
711
  toolCallId: string;
@@ -166,5 +713,65 @@ export interface ToolGateInput {
166
713
  }) => void;
167
714
  };
168
715
  }
716
+ /**
717
+ * Fold a {@link Hooks.preToolUse} callback into a {@link ToolPolicy} layer, so a screening face a task
718
+ * installed for ITSELF can also travel down a delegation chain as one more inherited constraint.
719
+ *
720
+ * Why a policy and not a second hook channel: a delegated child already folds each ancestor's caller
721
+ * policy (`InheritedGate.parentConstraints`), and that fold is where `deny > ask > allow`, the rewrite
722
+ * cascade, the ancestor-frozen approver and the durable mandate all already live. Expressing the
723
+ * screening face in the same shape means the child gains no new decision channel — the existing fold
724
+ * consumes it — and the tighten-only property is structural rather than restated.
725
+ *
726
+ * The mapping mirrors the gate's own phase 1 (see {@link runToolGate}) decision for decision:
727
+ * - no opinion (`undefined`) → `allow`, contributing nothing;
728
+ * - `deny` → `deny` in the fold's own words (the fold short-circuits, and the gate's deny exit supplies
729
+ * the same `formatHookFeedback` envelope phase 1 would have);
730
+ * - `ask` → `ask`, stamped `decisionReason:"hook"` so every consumer that distinguishes a screening ask
731
+ * from a policy ask reads the same fact one level down — notably the persisted-rule lane, which must
732
+ * not let a standing allow-rule resolve a screening ask;
733
+ * - `allow`/`ask` with `updatedInput` → the same rewrite, which the fold cascades to later layers and
734
+ * re-checks against the narrowing layers;
735
+ * - a THROW → the fail-closed deny with the RB-442 attribution text (naming the hook and the
736
+ * non-execution), because a screening face that cannot answer must not pass the call.
737
+ *
738
+ * **The callback inherits the fold's idempotency requirement** ({@link combinePolicies}): a layer's
739
+ * `check` may run more than once for a single tool call — the fold scans past an `ask` looking for a
740
+ * later `deny`, a rewrite emitted by a LATER layer re-runs the prefix that never saw the final args, and
741
+ * an approved operator edit is re-adjudicated. A callback whose consultation has a side effect (an audit
742
+ * row, a counter) will therefore see that call more than once on those paths. Its own gate's phase 1 has
743
+ * always had the second of these (an approved edit is re-screened there too).
744
+ *
745
+ * Two properties are deliberately NOT carried, and neither is reconstructible in this shape:
746
+ * - `additionalContext` — a policy verdict has no channel for model-facing text. Context a screening
747
+ * face injects is a property of the run whose gate ran it; a descendant's transcript does not receive
748
+ * it. (The decision half — the part that restricts — is what travels.)
749
+ * - the phase-1 observer exclusion — a deny raised here is a fold deny, so the descendant's
750
+ * `permissionDenied` observer sees it with `source:"policy"`. That exclusion is about a hook deciding
751
+ * in ITS OWN gate; here the descendant's policy fold is what refused the call.
752
+ *
753
+ * `env` is the capability face of the environment the callback was installed against — captured at the
754
+ * install site, not rebuilt in the descendant, for the same reason an inherited policy closure keeps its
755
+ * own captured environment (the descendant may run against a different root). `onCrash` is the install
756
+ * site's deployment error lane: the gate's phase 1 routes a crashing callback to `onError(phase:"hook")`
757
+ * so a broken screening face is distinguishable from a deliberate refusal, and the folded form has to
758
+ * reach the same lane from inside a descendant's fold.
759
+ */
169
760
  export declare function createPreToolUseConstraintPolicy(preToolUse: NonNullable<Hooks["preToolUse"]>, env?: HookEnvCapabilities, onCrash?: (err: unknown) => void): ToolPolicy;
761
+ /**
762
+ * The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
763
+ * structural ("a hook's `allow` cannot bypass the policy's `deny`/`ask`"):
764
+ *
765
+ * 1. **collect** — run the PreToolUse hook, threading any `updatedInput` rewrite into `currentInput`
766
+ * and collecting `additionalContext`. A hook `deny` short-circuits to a block immediately; a hook
767
+ * `ask` is remembered (it does not short-circuit — a later policy `deny` outranks it).
768
+ * 2. **adjudicate** — run the tool policy on the FINAL `currentInput` (never the model's stale args),
769
+ * fold it with any remembered hook-ask via `deny > ask > allow`, and resolve a surviving `ask`
770
+ * through `onAsk`. The policy ALWAYS runs regardless of the hook's verdict. A policy `allow` may
771
+ * itself carry an `updatedInput` rewrite (redact/clamp), applied last over any hook rewrite.
772
+ *
773
+ * Because both phases run in this one linear function, the ordering — and the "policy is final"
774
+ * invariant — is enforced by the call stack, not by registration convention. Returns the gate result
775
+ * for the harness `tool_call` hook plus the PreToolUse context to attach to the tool result.
776
+ */
170
777
  export declare function runToolGate(input: ToolGateInput): Promise<ToolGateResult>;