@sema-agent/core 7.54.1 → 7.55.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 (399) hide show
  1. package/CHANGELOG.md +437 -0
  2. package/dist/agents/agent-transcript-tool.js +2 -1
  3. package/dist/agents/builtin-agents.d.ts +12 -14
  4. package/dist/agents/cc-peer-directory.d.ts +36 -0
  5. package/dist/agents/cc-peer-directory.js +138 -0
  6. package/dist/agents/cc-peer-wire.d.ts +105 -0
  7. package/dist/agents/cc-peer-wire.js +98 -0
  8. package/dist/agents/cc-uds-peer-channel.d.ts +40 -0
  9. package/dist/agents/cc-uds-peer-channel.js +457 -0
  10. package/dist/agents/cross-session-envelope.d.ts +4 -1
  11. package/dist/agents/cross-session-envelope.js +8 -1
  12. package/dist/agents/cross-session-judge.d.ts +2 -2
  13. package/dist/agents/list-agents-tool.js +2 -1
  14. package/dist/agents/observer.d.ts +4 -5
  15. package/dist/agents/observer.js +2 -1
  16. package/dist/agents/peer-admission.d.ts +6 -0
  17. package/dist/agents/peer-admission.js +4 -1
  18. package/dist/agents/peer-directory.d.ts +43 -9
  19. package/dist/agents/peer-directory.js +66 -23
  20. package/dist/agents/peer-notices.d.ts +1 -1
  21. package/dist/agents/peer-session-drain.d.ts +3 -1
  22. package/dist/agents/peer-session-drain.js +1 -0
  23. package/dist/agents/read-inbox-tool.js +2 -1
  24. package/dist/agents/retain-ledger.d.ts +1 -1
  25. package/dist/agents/roster-store.d.ts +1 -1
  26. package/dist/agents/send-message-tool.d.ts +10 -0
  27. package/dist/agents/send-message-tool.js +34 -5
  28. package/dist/agents/subagent.d.ts +34 -79
  29. package/dist/agents/subagent.js +45 -46
  30. package/dist/agents/tool-filter.d.ts +29 -46
  31. package/dist/agents/tool-filter.js +8 -31
  32. package/dist/agents/verify.d.ts +3 -3
  33. package/dist/brain/anthropic.js +14 -9
  34. package/dist/brain/errors.d.ts +15 -0
  35. package/dist/brain/errors.js +10 -0
  36. package/dist/brain/model-presets.d.ts +4 -4
  37. package/dist/brain/open-responses.js +15 -10
  38. package/dist/brain/openai.js +17 -12
  39. package/dist/brain/provider-dialect.d.ts +39 -3
  40. package/dist/brain/provider-dialect.js +28 -0
  41. package/dist/brain/reasoning.d.ts +1 -1
  42. package/dist/brain/request-params.d.ts +55 -5
  43. package/dist/brain/request-params.js +35 -3
  44. package/dist/brain/retry.d.ts +1 -1
  45. package/dist/brain/route-adjudicator.d.ts +15 -0
  46. package/dist/brain/route-adjudicator.js +15 -0
  47. package/dist/brain/stream-engine.js +6 -5
  48. package/dist/brain/stream-shared.d.ts +1 -1
  49. package/dist/core/advisor-verdict.d.ts +120 -0
  50. package/dist/core/advisor-verdict.js +92 -0
  51. package/dist/core/agent-definition.d.ts +7 -8
  52. package/dist/core/allow-entry.d.ts +49 -23
  53. package/dist/core/allow-entry.js +57 -32
  54. package/dist/core/allow-rule-shadow.d.ts +7 -0
  55. package/dist/core/approval-object.d.ts +2 -1
  56. package/dist/core/approval-object.js +6 -6
  57. package/dist/core/ask-origin.d.ts +63 -11
  58. package/dist/core/ask-origin.js +27 -5
  59. package/dist/core/ask-question.js +10 -4
  60. package/dist/core/auto-compaction.d.ts +11 -8
  61. package/dist/core/auto-compaction.js +4 -2
  62. package/dist/core/auto-mode-arming.d.ts +2 -2
  63. package/dist/core/auto-mode-defaults.d.ts +8 -8
  64. package/dist/core/auto-mode-prompt-assets.d.ts +3 -3
  65. package/dist/core/auto-mode-prompt.d.ts +1 -1
  66. package/dist/core/auto-mode.d.ts +17 -9
  67. package/dist/core/auto-mode.js +9 -6
  68. package/dist/core/background-agent-store.d.ts +3 -3
  69. package/dist/core/canonical-json.d.ts +1 -1
  70. package/dist/core/canonical-json.js +2 -2
  71. package/dist/core/checkpoint-requirements.d.ts +6 -3
  72. package/dist/core/checkpoint-requirements.js +4 -14
  73. package/dist/core/checkpoint-store.d.ts +80 -51
  74. package/dist/core/checkpoint-store.js +56 -74
  75. package/dist/core/child-clamp-seats.d.ts +6 -0
  76. package/dist/core/child-clamp-seats.js +3 -16
  77. package/dist/core/compliance.d.ts +2 -2
  78. package/dist/core/context-edit.d.ts +6 -7
  79. package/dist/core/cross-boundary-taint.d.ts +9 -3
  80. package/dist/core/cross-boundary-taint.js +5 -3
  81. package/dist/core/decision-receipt.js +2 -2
  82. package/dist/core/effective-path-target.d.ts +1 -1
  83. package/dist/core/engine-notice.d.ts +19 -5
  84. package/dist/core/exec-output-tail.d.ts +1 -1
  85. package/dist/core/file-history-retention.d.ts +1 -1
  86. package/dist/core/file-history-store.d.ts +59 -9
  87. package/dist/core/file-history-store.js +35 -17
  88. package/dist/core/gate-fold.js +3 -2
  89. package/dist/core/gate-lanes.js +12 -8
  90. package/dist/core/gate-outcome.d.ts +17 -4
  91. package/dist/core/gate-outcome.js +30 -0
  92. package/dist/core/governance-codes.d.ts +13 -3
  93. package/dist/core/governance-codes.js +10 -0
  94. package/dist/core/hands-band.d.ts +1 -1
  95. package/dist/core/hooks.d.ts +4 -4
  96. package/dist/core/host-target.d.ts +49 -0
  97. package/dist/core/host-target.js +59 -0
  98. package/dist/core/image-downsample.d.ts +1 -1
  99. package/dist/core/lsp-diagnostics.d.ts +4 -4
  100. package/dist/core/lsp.d.ts +3 -3
  101. package/dist/core/mailbox-store.d.ts +4 -0
  102. package/dist/core/mcp-connection-pool.d.ts +18 -2
  103. package/dist/core/mcp-connection-pool.js +53 -6
  104. package/dist/core/mcp-failure.d.ts +23 -4
  105. package/dist/core/mcp-failure.js +26 -11
  106. package/dist/core/mcp-listing-drift.d.ts +10 -0
  107. package/dist/core/mcp-listing-drift.js +6 -0
  108. package/dist/core/mcp-server-link.d.ts +173 -0
  109. package/dist/core/mcp-server-link.js +127 -0
  110. package/dist/core/mcp-server-spec.d.ts +4 -2
  111. package/dist/core/mcp-session-end.d.ts +26 -0
  112. package/dist/core/mcp-session-end.js +29 -0
  113. package/dist/core/mcp-startup-request.d.ts +17 -0
  114. package/dist/core/mcp-startup-request.js +17 -0
  115. package/dist/core/mcp.d.ts +13 -13
  116. package/dist/core/mcp.js +187 -162
  117. package/dist/core/media-byte-cap.d.ts +1 -1
  118. package/dist/core/memory-engine/engine.d.ts +9 -9
  119. package/dist/core/memory-engine/provenance-wording.d.ts +1 -1
  120. package/dist/core/memory-engine/tools.js +4 -1
  121. package/dist/core/mode-policy.d.ts +1 -1
  122. package/dist/core/namespaced-rule-name.d.ts +33 -7
  123. package/dist/core/namespaced-rule-name.js +39 -25
  124. package/dist/core/output-style.d.ts +1 -1
  125. package/dist/core/param-rule-reach.d.ts +15 -0
  126. package/dist/core/param-rule-reach.js +30 -0
  127. package/dist/core/parent-spec-seat.d.ts +8 -1
  128. package/dist/core/parent-spec-seat.js +4 -0
  129. package/dist/core/pause-registry.d.ts +16 -5
  130. package/dist/core/pause-registry.js +6 -2
  131. package/dist/core/peer-channel.d.ts +70 -0
  132. package/dist/core/peer-channel.js +1 -0
  133. package/dist/core/permission-rule-consent.d.ts +11 -5
  134. package/dist/core/permission-rule-consent.js +37 -30
  135. package/dist/core/permission-rule-model.d.ts +233 -64
  136. package/dist/core/permission-rule-model.js +384 -85
  137. package/dist/core/permission-rule-provider.d.ts +2 -1
  138. package/dist/core/permission-rule-store.d.ts +20 -25
  139. package/dist/core/permission-rule-store.js +1 -4
  140. package/dist/core/permission-rule-sync.d.ts +11 -2
  141. package/dist/core/permission-rule-sync.js +8 -1
  142. package/dist/core/permission-rules.d.ts +49 -11
  143. package/dist/core/permission-rules.js +123 -89
  144. package/dist/core/persisted-rule-arms.d.ts +4 -2
  145. package/dist/core/present-plan-tool.js +3 -1
  146. package/dist/core/protocol-naming.d.ts +2 -2
  147. package/dist/core/read-only-shell-table.d.ts +1 -1
  148. package/dist/core/read-only-shell.d.ts +1 -1
  149. package/dist/core/roles.d.ts +6 -2
  150. package/dist/core/roles.js +45 -44
  151. package/dist/core/rule-offer-read.d.ts +53 -0
  152. package/dist/core/rule-offer-read.js +146 -0
  153. package/dist/core/rule-owner.d.ts +46 -0
  154. package/dist/core/rule-owner.js +28 -0
  155. package/dist/core/runner/advisor-instructions.d.ts +70 -0
  156. package/dist/core/runner/advisor-instructions.js +61 -0
  157. package/dist/core/runner/advisor-tool.d.ts +33 -0
  158. package/dist/core/runner/advisor-tool.js +112 -0
  159. package/dist/core/runner/announce-once-ledger.d.ts +4 -26
  160. package/dist/core/runner/announce-once-ledger.js +14 -46
  161. package/dist/core/runner/approval-feedback.js +6 -3
  162. package/dist/core/runner/assemble-result.d.ts +1 -1
  163. package/dist/core/runner/caller-roster.d.ts +7 -0
  164. package/dist/core/runner/caller-roster.js +11 -0
  165. package/dist/core/runner/classifier-ladder.d.ts +27 -5
  166. package/dist/core/runner/classifier-ladder.js +12 -11
  167. package/dist/core/runner/compaction-restate.d.ts +10 -6
  168. package/dist/core/runner/compaction-restate.js +9 -3
  169. package/dist/core/runner/contracts.d.ts +13 -9
  170. package/dist/core/runner/conversation-segment.d.ts +28 -0
  171. package/dist/core/runner/conversation-segment.js +42 -0
  172. package/dist/core/runner/cross-boundary-lane.d.ts +6 -5
  173. package/dist/core/runner/cross-boundary-lane.js +26 -15
  174. package/dist/core/runner/decide-continuation.d.ts +7 -4
  175. package/dist/core/runner/decide-continuation.js +2 -2
  176. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  177. package/dist/core/runner/derived-route-fallback.d.ts +24 -24
  178. package/dist/core/runner/derived-route-fallback.js +9 -7
  179. package/dist/core/runner/gate-exit.d.ts +6 -0
  180. package/dist/core/runner/gate-exit.js +7 -2
  181. package/dist/core/runner/initial-run-state.js +1 -1
  182. package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
  183. package/dist/core/runner/permission-rule-lanes.js +14 -7
  184. package/dist/core/runner/person-path-rows.js +1 -1
  185. package/dist/core/runner/prepare-advisor.d.ts +63 -0
  186. package/dist/core/runner/prepare-advisor.js +77 -0
  187. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  188. package/dist/core/runner/prepare-config-doors.d.ts +6 -4
  189. package/dist/core/runner/prepare-config-doors.js +18 -12
  190. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  191. package/dist/core/runner/prepare-delegation-surface.js +1 -1
  192. package/dist/core/runner/prepare-file-history.js +39 -39
  193. package/dist/core/runner/prepare-gate-stations.d.ts +1 -4
  194. package/dist/core/runner/prepare-gate-stations.js +2 -7
  195. package/dist/core/runner/prepare-inherited-gate.d.ts +5 -4
  196. package/dist/core/runner/prepare-inherited-gate.js +3 -3
  197. package/dist/core/runner/prepare-park-ask.js +7 -7
  198. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  199. package/dist/core/runner/prepare-policy-chain.js +4 -10
  200. package/dist/core/runner/prepare-project-context.d.ts +5 -2
  201. package/dist/core/runner/prepare-project-context.js +6 -3
  202. package/dist/core/runner/prepare-prompt-inputs.js +1 -1
  203. package/dist/core/runner/prepare-protocol-tools.d.ts +2 -2
  204. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  205. package/dist/core/runner/prepare-safety-scan.d.ts +9 -12
  206. package/dist/core/runner/prepare-safety-scan.js +8 -6
  207. package/dist/core/runner/prepare-task.js +15 -18
  208. package/dist/core/runner/prepare-tool-disclosure-mount.js +6 -4
  209. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -3
  210. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  211. package/dist/core/runner/resume-policy-outcome.js +1 -1
  212. package/dist/core/runner/retired-runner-deps.js +1 -1
  213. package/dist/core/runner/run-attachment-seats.js +0 -1
  214. package/dist/core/runner/run-compaction-machinery.d.ts +1 -1
  215. package/dist/core/runner/run-compaction-machinery.js +2 -12
  216. package/dist/core/runner/run-git-lane.d.ts +1 -0
  217. package/dist/core/runner/run-harness-handlers.js +2 -3
  218. package/dist/core/runner/run-leg.js +13 -0
  219. package/dist/core/runner/run-settle-and-teardown.js +1 -1
  220. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  221. package/dist/core/runner/run-turn-boundary.d.ts +1 -0
  222. package/dist/core/runner/run-turn-boundary.js +14 -12
  223. package/dist/core/runner/session-rule-policy.d.ts +22 -21
  224. package/dist/core/runner/session-rule-policy.js +13 -28
  225. package/dist/core/runner/side-call-account.d.ts +32 -8
  226. package/dist/core/runner/side-call-account.js +23 -1
  227. package/dist/core/runner/strict-output-schema.d.ts +1 -1
  228. package/dist/core/runner/sub-agent-deny-lists.js +4 -3
  229. package/dist/core/runner/synthetic-tools.d.ts +10 -13
  230. package/dist/core/runner/synthetic-tools.js +4 -2
  231. package/dist/core/runner/tool-disclosure.js +2 -1
  232. package/dist/core/runner/tool-face-overlay.d.ts +2 -1
  233. package/dist/core/runner/tool-face-overlay.js +17 -1
  234. package/dist/core/runner/tool-progress-lane.d.ts +2 -2
  235. package/dist/core/runner/tool-turn-repair-seat.d.ts +3 -2
  236. package/dist/core/runner/turn-attachments.d.ts +60 -67
  237. package/dist/core/runner-deps.d.ts +37 -37
  238. package/dist/core/runtime-caps.d.ts +4 -4
  239. package/dist/core/safety-merge-corpus.js +17 -1
  240. package/dist/core/same-plain-value.d.ts +40 -0
  241. package/dist/core/same-plain-value.js +49 -0
  242. package/dist/core/scheduler.d.ts +1 -1
  243. package/dist/core/sensitive-path-policy.d.ts +1 -1
  244. package/dist/core/sensitive-path-policy.js +1 -1
  245. package/dist/core/session-policy-store.d.ts +77 -22
  246. package/dist/core/session-policy-store.js +56 -14
  247. package/dist/core/session-reconcile.d.ts +2 -2
  248. package/dist/core/session-store.d.ts +80 -30
  249. package/dist/core/session-store.js +118 -76
  250. package/dist/core/session.d.ts +24 -46
  251. package/dist/core/shared-memory/tools.js +3 -1
  252. package/dist/core/shared-memory/types.d.ts +1 -1
  253. package/dist/core/side-query.d.ts +10 -0
  254. package/dist/core/side-query.js +5 -0
  255. package/dist/core/store-contracts/checkpoint-store-contract.js +29 -2
  256. package/dist/core/store-contracts/file-history-store-contract.js +43 -3
  257. package/dist/core/store-contracts/session-policy-store-contract.d.ts +7 -0
  258. package/dist/core/store-contracts/session-policy-store-contract.js +130 -0
  259. package/dist/core/store-contracts/session-repo-contract.js +125 -0
  260. package/dist/core/store-contracts/session-store-contract.d.ts +4 -7
  261. package/dist/core/store-contracts/session-store-contract.js +215 -41
  262. package/dist/core/store-contracts/workflow-journal-store-contract.js +26 -1
  263. package/dist/core/surrogate-safe-slice.d.ts +3 -3
  264. package/dist/core/task-event.d.ts +10 -4
  265. package/dist/core/task-notification.d.ts +3 -3
  266. package/dist/core/task-registry-agent.d.ts +4 -4
  267. package/dist/core/task-registry-shared.d.ts +4 -4
  268. package/dist/core/task-registry-shared.js +1 -0
  269. package/dist/core/task-registry.d.ts +4 -4
  270. package/dist/core/task-registry.js +3 -1
  271. package/dist/core/task-result.d.ts +3 -3
  272. package/dist/core/task-spec.d.ts +52 -52
  273. package/dist/core/task-stream.d.ts +6 -6
  274. package/dist/core/task-tool-shape.d.ts +4 -4
  275. package/dist/core/tighten-task-spec.d.ts +1 -1
  276. package/dist/core/tool-catalog-entries.d.ts +19 -2
  277. package/dist/core/tool-catalog-entries.js +57 -42
  278. package/dist/core/tool-conformance.js +1 -0
  279. package/dist/core/tool-errors.d.ts +2 -2
  280. package/dist/core/tool-face-reader.js +5 -3
  281. package/dist/core/tool-face.d.ts +21 -2
  282. package/dist/core/tool-face.js +1 -1
  283. package/dist/core/tool-model-gate.d.ts +3 -3
  284. package/dist/core/tool-name-aliases.d.ts +29 -8
  285. package/dist/core/tool-name-aliases.js +27 -3
  286. package/dist/core/tool-name-readings.d.ts +5 -8
  287. package/dist/core/tool-name-readings.js +1 -2
  288. package/dist/core/tool-policy.d.ts +24 -5
  289. package/dist/core/tool-policy.js +34 -10
  290. package/dist/core/tool-registry.d.ts +14 -7
  291. package/dist/core/tool-registry.js +17 -7
  292. package/dist/core/tool-result-store.js +2 -1
  293. package/dist/core/tool-roster.d.ts +47 -18
  294. package/dist/core/tool-roster.js +53 -32
  295. package/dist/core/tool-spec-members.js +1 -1
  296. package/dist/core/tool-spec.d.ts +35 -21
  297. package/dist/core/tools.js +3 -2
  298. package/dist/core/trace.d.ts +4 -0
  299. package/dist/core/untrusted-egress.d.ts +1 -1
  300. package/dist/core/untrusted-text.d.ts +1 -1
  301. package/dist/core/warm-resume.d.ts +1 -1
  302. package/dist/core/wiring-manifest.d.ts +49 -0
  303. package/dist/core/wiring-manifest.js +13 -5
  304. package/dist/core/workflow-journal-store.d.ts +62 -0
  305. package/dist/core/workflow-journal-store.js +77 -1
  306. package/dist/core/workflow-run-store.d.ts +6 -0
  307. package/dist/core/workflow-run-store.js +2 -0
  308. package/dist/engine/compaction/compaction.d.ts +9 -9
  309. package/dist/engine/execution-env/node-execution-env.d.ts +19 -5
  310. package/dist/engine/execution-env/node-execution-env.js +43 -22
  311. package/dist/engine/harness/agent-harness.d.ts +14 -8
  312. package/dist/engine/harness/agent-harness.js +33 -26
  313. package/dist/engine/harness/types.d.ts +29 -7
  314. package/dist/engine/harness/types.js +12 -1
  315. package/dist/engine/llm/types.d.ts +9 -9
  316. package/dist/engine/llm/validation.d.ts +4 -1
  317. package/dist/engine/llm/validation.js +13 -1
  318. package/dist/engine/loop/agent-loop.d.ts +1 -1
  319. package/dist/engine/loop/agent-loop.js +10 -8
  320. package/dist/engine/loop/types.d.ts +15 -13
  321. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  322. package/dist/engine/session/memory-repo.d.ts +10 -11
  323. package/dist/engine/session/memory-repo.js +13 -8
  324. package/dist/engine/session/parent-chain-guard.d.ts +3 -2
  325. package/dist/engine/session/parent-chain-guard.js +1 -1
  326. package/dist/engine/session/repo-utils.d.ts +31 -3
  327. package/dist/engine/session/repo-utils.js +42 -5
  328. package/dist/index.d.ts +27 -15
  329. package/dist/index.js +21 -11
  330. package/dist/internal/harness-types.d.ts +1 -1
  331. package/dist/internal/harness.d.ts +3 -3
  332. package/dist/internal/harness.js +2 -2
  333. package/dist/internal/llm.d.ts +1 -1
  334. package/dist/internal/llm.js +1 -1
  335. package/dist/orchestration/goal.js +2 -2
  336. package/dist/orchestration/run-workflow-tool.d.ts +2 -2
  337. package/dist/orchestration/run-workflow-tool.js +5 -3
  338. package/dist/orchestration/workflow-size-guideline.d.ts +4 -4
  339. package/dist/orchestration/workflow-types.d.ts +18 -0
  340. package/dist/orchestration/workflow-types.js +6 -0
  341. package/dist/orchestration/workflow.d.ts +2 -13
  342. package/dist/orchestration/workflow.js +21 -31
  343. package/dist/prompt-assembly/event-registry.js +1 -0
  344. package/dist/prompt-assembly/tool-catalog.d.ts +3 -0
  345. package/dist/prompt-assembly/tool-catalog.js +3 -0
  346. package/dist/prompts/default.d.ts +14 -14
  347. package/dist/scenarios/full-body.d.ts +4 -4
  348. package/dist/stores/file/adoption/adopt.d.ts +4 -3
  349. package/dist/stores/file/adoption/adopt.js +24 -31
  350. package/dist/stores/file/adoption/marker.d.ts +6 -13
  351. package/dist/stores/file/adoption/marker.js +6 -15
  352. package/dist/stores/file/checkpoint-store.d.ts +3 -0
  353. package/dist/stores/file/checkpoint-store.js +9 -3
  354. package/dist/stores/file/file-history-store.d.ts +20 -19
  355. package/dist/stores/file/file-history-store.js +152 -135
  356. package/dist/stores/file/fs-atomic.d.ts +15 -0
  357. package/dist/stores/file/fs-atomic.js +89 -14
  358. package/dist/stores/file/index.d.ts +1 -1
  359. package/dist/stores/file/mailbox-store.d.ts +3 -0
  360. package/dist/stores/file/mailbox-store.js +3 -1
  361. package/dist/stores/file/permission-rule-adopt.d.ts +1 -1
  362. package/dist/stores/file/permission-rule-adopt.js +15 -16
  363. package/dist/stores/file/permission-rule-store.d.ts +18 -2
  364. package/dist/stores/file/permission-rule-store.js +21 -14
  365. package/dist/stores/file/session-policy-store.d.ts +3 -3
  366. package/dist/stores/file/session-store.d.ts +26 -11
  367. package/dist/stores/file/session-store.js +77 -40
  368. package/dist/stores/file/workflow-journal-store.d.ts +10 -4
  369. package/dist/stores/file/workflow-journal-store.js +28 -13
  370. package/dist/tools/artifact/artifact-text.d.ts +1 -1
  371. package/dist/tools/artifact/artifact-tool.js +2 -1
  372. package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -3
  373. package/dist/tools/fs/fs-bash.d.ts +2 -2
  374. package/dist/tools/fs/fs-bash.js +3 -1
  375. package/dist/tools/fs/fs-shared.d.ts +8 -8
  376. package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
  377. package/dist/tools/fs/notebook.d.ts +2 -2
  378. package/dist/tools/fs/read-deny.d.ts +29 -7
  379. package/dist/tools/fs/read-deny.js +63 -22
  380. package/dist/tools/fs/removal-targets.d.ts +1 -1
  381. package/dist/tools/fs/safety.d.ts +9 -9
  382. package/dist/tools/fs/search.d.ts +6 -6
  383. package/dist/tools/fs/shell-read-check.js +6 -1
  384. package/dist/tools/fs/shell-read-judge.d.ts +4 -0
  385. package/dist/tools/fs/shell-read-judge.js +5 -2
  386. package/dist/tools/loop-tick.d.ts +2 -2
  387. package/dist/tools/scheduler-tools.js +5 -1
  388. package/dist/tools/task-list.d.ts +7 -7
  389. package/dist/tools/task-list.js +5 -1
  390. package/dist/tools/todo.js +2 -1
  391. package/dist/tools/web.d.ts +1 -1
  392. package/dist/tools/web.js +40 -37
  393. package/dist/tools/worktree.d.ts +3 -3
  394. package/dist/tools/worktree.js +3 -1
  395. package/package.json +1 -1
  396. package/test/export-surface.snapshot.json +5756 -5638
  397. package/wire-changes/core@7.55.0.json +3318 -0
  398. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +0 -39
  399. package/dist/core/store-contracts/session-store-incarnation-contract.js +0 -95
@@ -16,3 +16,10 @@ export interface AllowRuleShadow {
16
16
  readonly entries: readonly string[];
17
17
  readonly omitted?: number;
18
18
  }
19
+ /** The allow-layer member that CLEARED a call's ask (`tool_end.gate.disposition.clearedBy`): the shadow record's shape — the member
20
+ * and the entries it stood on, sanitized and capped by the same mint — plus, for a LIST member (`request`, `deployment`),
21
+ * `positions`: the index of every element of the list the run received that is one of those entries (a string listed twice has
22
+ * both indices), so a host reads which line of its own declaration spoke even where the shown text is capped. */
23
+ export interface AllowRuleClearance extends AllowRuleShadow {
24
+ readonly positions?: readonly number[];
25
+ }
@@ -1,7 +1,7 @@
1
1
  import type { PendingAction } from "./checkpoint-store.js";
2
2
  import type { CallTargetReading } from "./effective-path-target.js";
3
3
  import { type SkillGrantSource } from "./skill-grant.js";
4
- import type { ToolCallPathTarget, ToolFamily } from "./tool-face.js";
4
+ import type { ToolCallPathTarget, ToolFamily, ToolHostTarget } from "./tool-face.js";
5
5
  import { type ToolRosterEntry } from "./tool-roster.js";
6
6
  type ParkedApproval = Extract<PendingAction, {
7
7
  kind: "tool_approval";
@@ -24,6 +24,7 @@ export interface ApprovalTool {
24
24
  readonly implementationRevision?: string;
25
25
  readonly family?: ToolFamily;
26
26
  readonly pathTarget?: ToolCallPathTarget;
27
+ readonly hostTarget?: ToolHostTarget;
27
28
  }
28
29
  export interface ApprovalObject {
29
30
  /** Absent on a row minted before the identity members (all four absent): such a row binds the tool's name alone. */
@@ -5,10 +5,10 @@ import { toolIdentityOf } from "./tool-roster.js";
5
5
  import { inlineUntrusted } from "./untrusted-text.js";
6
6
  export const APPROVAL_TARGET_ROLES = Object.freeze(["protective", "declared"]);
7
7
  export function approvalObjectOf(row) {
8
- const { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, targets, boundGrantDigest } = row;
8
+ const { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, hostTarget, targets, boundGrantDigest } = row;
9
9
  const carried = [capabilityId, contractId, shapeDigest, implementationRevision].some((m) => m !== undefined);
10
10
  return {
11
- ...(carried ? { tool: { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget } } : {}),
11
+ ...(carried ? { tool: { capabilityId, contractId, shapeDigest, implementationRevision, family, pathTarget, hostTarget } } : {}),
12
12
  ...(targets !== undefined ? { targets } : {}),
13
13
  ...(boundGrantDigest !== undefined ? { grants: boundGrantDigest } : {}),
14
14
  };
@@ -30,9 +30,9 @@ export const APPROVAL_STATION_MEMBERS = Object.freeze({
30
30
  const IDENTITY_MEMBERS = Object.freeze(["capabilityId", "contractId", "shapeDigest", "implementationRevision"]);
31
31
  const shown = (s) => `"${inlineUntrusted(s, 240)}"`;
32
32
  const vetoed = (name, why) => `The approved tool call "${name}" was not executed: ${why}, so the approval does not cover it (resume.target_moved). This approval is spent — the call has to be re-issued: its new card shows what it would act on now.`;
33
- function faceKey(family, pt) {
33
+ function faceKey(family, pt, ht) {
34
34
  const slot = pt === undefined ? null : { param: pt.param, access: pt.access, aliases: [...(pt.aliases ?? [])], base: pt.base ?? "cwd", absent: pt.absent ?? "none", patternParam: pt.patternParam ?? null, skillScopeEligible: pt.skillScopeEligible === true };
35
- return canonicalize({ family: family ?? null, pathTarget: slot });
35
+ return canonicalize({ family: family ?? null, pathTarget: slot, hostTarget: ht === undefined ? null : { param: ht.param } });
36
36
  }
37
37
  function judgeTool(row, live) {
38
38
  const entry = live.tool;
@@ -47,9 +47,9 @@ function judgeTool(row, live) {
47
47
  const named = differ.map((k) => (bound[k] === undefined ? `${k} (missing on the row)` : k)).join(", ");
48
48
  return { member: "tool", disposition: "reopen", code: "resume.tool_contract_mismatch", message: `the approved tool "${row.toolName}" on this leg is not the tool the approval was minted against (${named} differ) — refusing to execute a replacement wearing the name; the checkpoint is reopened for a retry with the original tool present` };
49
49
  }
50
- if (faceKey(bound.family, bound.pathTarget) === faceKey(entry.family, entry.pathTarget))
50
+ if (faceKey(bound.family, bound.pathTarget, bound.hostTarget) === faceKey(entry.family, entry.pathTarget, entry.hostTarget))
51
51
  return undefined;
52
- return { member: "tool", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, "the tool's face (its display family or the path target it declares) changed after the approval") };
52
+ return { member: "tool", disposition: "veto", code: "resume.target_moved", message: vetoed(row.toolName, "the tool's face (its display family, or the path target or URL slot it declares) changed after the approval") };
53
53
  }
54
54
  function judgeTargets(row, live) {
55
55
  const bound = row.targets;
@@ -251,6 +251,18 @@ export declare function askMandateMember(ask: RuleClearanceFacts | undefined): {
251
251
  mandate?: PersistedRuleMandate;
252
252
  authoredBy?: "deployment";
253
253
  };
254
+ /**
255
+ * A MANDATE merged onto an ask another layer raised — the one writer of both merge arms (the read station's, the reversibility
256
+ * probe's; `gate-fold.ts`). The mandate is the fact that a standing allow rule may not clear the ask, and its account is the only
257
+ * sentence that can say why (the allow layer's disclosure words a `probe_mandate` with it, on the card and in the parked row): the
258
+ * two travel together. The FIRST layer to mandate an ask brings its account, or none: a `probeReason` the ask arrived with can only
259
+ * be its own author's prose, and is replaced by the account — or removed where the mandating layer gave none, so it is never read as
260
+ * the reason the mandate stands; an ask already mandated keeps the first layer's. The ask's action, message, reason and every other
261
+ * disclosure are untouched.
262
+ */
263
+ export declare function withProbeMandate<D extends Extract<PermissionResult, {
264
+ action: "ask";
265
+ }>>(decision: D, account: string | undefined): D;
254
266
  /** The clearance facts of an ask the engine did not construct (a policy's verdict), each member read ONCE into
255
267
  * an engine-owned value — for a station that keeps the answer past its own side effects, where the verdict's
256
268
  * accessors must not run again. The members a verdict may carry that narrow what may clear it. */
@@ -300,8 +312,10 @@ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<As
300
312
  *
301
313
  * - `operator_always` — the operator's `always` floor: per-call confirmation for shell commands.
302
314
  * - `tool_marks` — the tool's own egress / irreversibility / interaction declaration.
303
- * - `probe_mandate` — this call's reversibility probe declared the demotion STRUCTURAL.
304
- * - `probe_unanswered` — the probe was consulted and did not answer: the boundary is UNKNOWN.
315
+ * - `probe_mandate` — this call's reversibility probe declared the demotion STRUCTURAL. For the engine's own shell station it
316
+ * is a FINDING about this call — a path outside the allowed directories (read or written) or a command whose reads it could
317
+ * not establish — and the finding's account (the ask's `probeReason`) is the word's whole disclosure ({@link FINDING_ACCOUNT}).
318
+ * - `probe_unanswered` — the probe was consulted and did not answer: whether the call is reversible is UNKNOWN.
305
319
  * - `write_protection` — the table covers this write's target. A write onto load-executed configuration
306
320
  * re-authorizes the agent, so the question is a property of the TARGET, not of anyone's hesitation.
307
321
  * - `target_unresolved` — the reading of a written target could not say where it leads on disk:
@@ -314,11 +328,20 @@ export declare const PERSISTED_RULE_MANDATES: readonly ["operator_always", "tool
314
328
  export type PersistedRuleMandate = (typeof PERSISTED_RULE_MANDATES)[number];
315
329
  /** Membership test for {@link PersistedRuleMandate} — the screen a row reader applies to a stored word. */
316
330
  export declare function isPersistedRuleMandate(v: unknown): v is PersistedRuleMandate;
331
+ /** The row of a word whose reason is a FINDING about this very call (what the read station found — a path outside the allowed
332
+ * directories, read or written, or a command whose reads it could not establish): no sentence of the table can say which, so
333
+ * the reason is the finding's own account — the sentence the station minted for the card, which the ask carries as
334
+ * `probeReason` ({@link mandateDisclosureOf} reads it there). A row, not an absence: the word is disclosed, by the ask. */
335
+ export declare const FINDING_ACCOUNT: Readonly<{
336
+ readonly reasonIs: "the account of the finding, carried on the ask as probeReason";
337
+ }>;
317
338
  /** WHY a matching persisted allow rule did not clear the ask, in the sentence a PERSON reads — one row per
318
339
  * word (the ladder this replaces ended in a catch-all arm). Display prose; nothing reads it back to decide. */
319
- declare const MANDATE_DISCLOSURE: {
320
- readonly probe_mandate: "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule";
321
- readonly probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule";
340
+ export declare const MANDATE_DISCLOSURE: {
341
+ readonly probe_mandate: Readonly<{
342
+ readonly reasonIs: "the account of the finding, carried on the ask as probeReason";
343
+ }>;
344
+ readonly probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call is reversible is unknown — it is cleared by confirming this call, never by a standing allow rule";
322
345
  readonly operator_always: "this deployment mandates per-call confirmation for shell commands (shellGate: always)";
323
346
  readonly tool_marks: "this tool carries egress/irreversibility/interaction marks (a mandated confirmation a rule cannot clear)";
324
347
  readonly write_protection: "this write's target is on the deployment's write-protection table (the agent's own configuration is guarded one look per write) — it is cleared by confirming this call, never by a standing allow rule";
@@ -327,9 +350,18 @@ declare const MANDATE_DISCLOSURE: {
327
350
  };
328
351
  /** The fence over the disclosure table: `never` while every word has a row. */
329
352
  export type DisclosureTableCoversEveryMandate = AssertAllKeysHandled<Exclude<PersistedRuleMandate, keyof typeof MANDATE_DISCLOSURE>>;
330
- /** The disclosed reason for one word. `undefined` — no mandate — is the lane's OTHER refusal: a standing
331
- * "ask me each time" rule matched, which outranks the remembered yes on its own. */
332
- export declare function mandateDisclosureOf(mandate: PersistedRuleMandate | undefined): string;
353
+ /** The sentence for the lane's OTHER refusal — no mandate: a standing "ask me each time" rule matched, which outranks the
354
+ * remembered yes on its own. */
355
+ export declare const ASK_RULE_DISCLOSURE = "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)";
356
+ /**
357
+ * The disclosed reason for one word. `undefined` — no mandate — is the lane's OTHER refusal ({@link ASK_RULE_DISCLOSURE}).
358
+ * A word whose row is {@link FINDING_ACCOUNT} is disclosed by the ask's own account of its finding (`account`, the
359
+ * `probeReason` the ask carries) and by nothing else: with no account there is no sentence (`undefined`) — a word whose reason is
360
+ * a finding is never worded from a general statement, which would be true of one finding and false of the next. Every
361
+ * mandate the engine's own read station raises carries its account; an ask that arrives without one (a deployment probe that
362
+ * supplied no `reason`, a station the host mounted) is a mandate stated without a reason, and the card does not invent one.
363
+ */
364
+ export declare function mandateDisclosureOf(mandate: PersistedRuleMandate | undefined, account?: string): string | undefined;
333
365
  /**
334
366
  * The mandate provenance of one call, judged from the SAME mark inputs the gate is driven with —
335
367
  * the WORD a mandated ask discloses for its marks (the class derivation reads the same marks as classes,
@@ -809,9 +841,25 @@ export declare function classifyAsk(d: Extract<PermissionResult, {
809
841
  action: "ask";
810
842
  }>, facts: AskClassFacts): AskClassification;
811
843
  /** The refusal the model reads when an ask reaches a run with no approver: the reference implementation's sentence
812
- * (2.1.280) — the call did NOT run because this session has no approval surface — with the run's own fact beside it
844
+ * the call did NOT run because this session has no approval surface — with the run's own fact beside it
813
845
  * and the ask's prompt as what was requested. The gate appends the caller-side remedy. */
814
846
  export declare function headlessDenialMessage(toolName: string, askMessage: string): string;
847
+ /**
848
+ * The refusal the model reads when the `dontAsk` permission mode refuses an ask (`deniedBy: "mode"`): the reference implementation's
849
+ * head (whose permission, which mode — with the ask's class beside it) and its guidance ({@link REFUSED_CAPABILITY_GUIDANCE}), then
850
+ * the way out, the ONE remedy function's `dontAsk` arm ({@link headlessApprovalRemedy}) — the same sentence a headless refusal's
851
+ * remedy is written by, so a mode refusal and a headless one say the way out in one voice. `ruleShape` is the shape of the allow
852
+ * rule that would speak for the call (`ruleShapeOf` under `allow`, the rule model's reading of the call's grammar). The sentence
853
+ * names where allow rules live in a person's words (a settings file, the request's or the deployment's allow list), never a seat
854
+ * of the library: a host reads the same answer off the denied disposition — `deniedBy: "mode"`, `mode`, and the folded
855
+ * `classes`, whose row ({@link foldArbitrationRow}) says `ruleMayClear` — and offers its own seat.
856
+ */
857
+ export declare function dontAskRefusalMessage(input: {
858
+ readonly toolName: string;
859
+ readonly classification: AskClassification;
860
+ readonly coveredWrite: boolean;
861
+ readonly ruleShape: string;
862
+ }): string;
815
863
  /** The state of the approver seat a remedy is written for: `absent` — no approver is wired for the leg; `unavailable` — one is
816
864
  * wired and answered that nobody is reachable for this ask; `declared_none` — the run declared that nobody on it can be
817
865
  * asked (`approvalReach: "none"`), whatever is wired. Absent in the input reads as `absent` (the posture the function was
@@ -832,6 +880,10 @@ export type HeadlessApproverSeat = "absent" | "unavailable" | "declared_none";
832
880
  * wire one; one wired that answered `unavailable` for this ask ⇒ make a person reachable through it (a wired approver is
833
881
  * never told to be wired); the run declared nobody can be asked ⇒ run it where a person can be, or park it. The refusal's tool text and the `delegation.ask_unresolvable` notice both carry this sentence —
834
882
  * they call this function, neither spells it.
883
+ * Under `dontAsk` (the mode answers no ask: {@link dontAskRefusalMessage} carries this arm) the one question is whether an allow
884
+ * rule may clear the ask — the folded row's `ruleMayClear`, the allow layer's own reading: yes ⇒ add an allow rule covering the
885
+ * call, in the tool's rule shape (`ruleShape`, the caller's reading of the call's grammar — never a rule minted for the call), or
886
+ * run under a mode with an approver; no ⇒ only a person can answer it.
835
887
  */
836
888
  export declare function headlessApprovalRemedy(input: {
837
889
  readonly mode: PermissionMode | undefined;
@@ -841,8 +893,9 @@ export declare function headlessApprovalRemedy(input: {
841
893
  readonly program: "cp" | "mv";
842
894
  };
843
895
  readonly approverSeat?: HeadlessApproverSeat;
896
+ readonly ruleShape?: string;
844
897
  }): string;
845
- /** The reference implementation's sentence (2.1.280) for a `cp` / `mv` the mode will not answer because it carries option
898
+ /** The reference implementation's sentence for a `cp` / `mv` the mode will not answer because it carries option
846
899
  * words — the approval card's message under `acceptEdits` and the head of the headless remedy. */
847
900
  export declare function fileCommandOptionsSentence(program: "cp" | "mv"): string;
848
901
  /** What the mode arbitration decided for one surviving ask. `ask` hands it on unchanged (park / approver); `accepted` is
@@ -890,4 +943,3 @@ export declare function arbitrateByMode(mode: PermissionMode, c: AskClassificati
890
943
  }, call?: {
891
944
  readonly fileCommand?: boolean;
892
945
  }): Arbitration;
893
- export {};
@@ -57,6 +57,12 @@ export function askMandateMember(ask) {
57
57
  const authored = ask?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {};
58
58
  return mandated ? { mandated: true, ...(mandate !== undefined ? { mandate } : {}), ...authored } : authored;
59
59
  }
60
+ export function withProbeMandate(decision, account) {
61
+ if (decision.probeMandated === true)
62
+ return decision;
63
+ const { probeReason: _arrivedWith, ...rest } = decision;
64
+ return { ...rest, probeMandated: true, ...(account !== undefined ? { probeReason: account } : {}) };
65
+ }
60
66
  export function ownedClearanceFacts(d) {
61
67
  const { mandated, requiresRealApproval, decisionReason, matchedAskRule, probeMandated, crossBoundaryMandated, authoredBy, foldedAskClasses } = d;
62
68
  return {
@@ -112,17 +118,22 @@ const PERSISTED_RULE_MANDATE_SET = new Set(PERSISTED_RULE_MANDATES);
112
118
  export function isPersistedRuleMandate(v) {
113
119
  return PERSISTED_RULE_MANDATE_SET.has(v);
114
120
  }
115
- const MANDATE_DISCLOSURE = {
116
- probe_mandate: "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule",
117
- probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule",
121
+ export const FINDING_ACCOUNT = Object.freeze({ reasonIs: "the account of the finding, carried on the ask as probeReason" });
122
+ export const MANDATE_DISCLOSURE = {
123
+ probe_mandate: FINDING_ACCOUNT,
124
+ probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call is reversible is unknown — it is cleared by confirming this call, never by a standing allow rule",
118
125
  operator_always: "this deployment mandates per-call confirmation for shell commands (shellGate: always)",
119
126
  tool_marks: "this tool carries egress/irreversibility/interaction marks (a mandated confirmation a rule cannot clear)",
120
127
  write_protection: "this write's target is on the deployment's write-protection table (the agent's own configuration is guarded one look per write) — it is cleared by confirming this call, never by a standing allow rule",
121
128
  target_unresolved: "where this write leads on disk could not be determined (a link on the way could not be examined, or the environment could not resolve the target), so whether it lands on a guarded path is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can",
122
129
  cross_boundary_taint: "this run executes on a device outside the deployment's trust boundary and the deployment is multi-tenant, so an org-side write — or, after an off-target read, a call bound for the device — is confirmed by the deployment's approver for this call: no standing allow rule and no automatic lane answers for it, and an allow the configuration gives (a blanket seat or a policy's answer) is refused",
123
130
  };
124
- export function mandateDisclosureOf(mandate) {
125
- return mandate === undefined ? "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)" : MANDATE_DISCLOSURE[mandate];
131
+ export const ASK_RULE_DISCLOSURE = "an explicit ask rule matched this call (a person's or the deployment's ask-me-each-time outranks a standing allow rule)";
132
+ export function mandateDisclosureOf(mandate, account) {
133
+ if (mandate === undefined)
134
+ return ASK_RULE_DISCLOSURE;
135
+ const row = MANDATE_DISCLOSURE[mandate];
136
+ return typeof row === "string" ? row : account;
126
137
  }
127
138
  export function persistedRuleMandateOf(marks) {
128
139
  if (marks.targetUnresolved === true)
@@ -366,7 +377,18 @@ export function classifyAsk(d, facts) {
366
377
  export function headlessDenialMessage(toolName, askMessage) {
367
378
  return `Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session (nobody can be asked to approve "${toolName}" in this run — headless auto-deny). The action was NOT performed. Do not claim it succeeded, and do not retry it in this session — report the limitation to the user, or suggest an alternative. What was requested: ${askMessage}`;
368
379
  }
380
+ const REFUSED_CAPABILITY_GUIDANCE = "IMPORTANT: you may try to accomplish this with other tools that would naturally be used for it (for example `head` instead of `cat`), but do not work around this denial in malicious ways — for example, do not use your ability to run tests to run non-test actions; only work around it in reasonable ways that keep to the intent behind it. If you believe this capability is essential to complete the user's request, STOP and explain to the user what you were trying to do and why you need this permission, and let the user decide how to proceed.";
381
+ export function dontAskRefusalMessage(input) {
382
+ const remedy = headlessApprovalRemedy({ mode: "dontAsk", classification: input.classification, coveredWrite: input.coveredWrite, ruleShape: input.ruleShape });
383
+ return `Permission to use ${input.toolName} has been denied because this run is in the dontAsk permission mode (this call needed approval: ${input.classification.askClass}). ${REFUSED_CAPABILITY_GUIDANCE} To let a call like this run: ${remedy}.`;
384
+ }
369
385
  export function headlessApprovalRemedy(input) {
386
+ if (input.mode === "dontAsk") {
387
+ const c = input.classification;
388
+ return c?.row.ruleMayClear === true
389
+ ? `an allow rule covering this call answers it under dontAsk — add one${input.ruleShape !== undefined ? ` in this tool's rule shape (\`${input.ruleShape}\`)` : ""} to the allow rules (a settings file's \`permissions.allow\`, or the request's or the deployment's allow list), or run under a mode with an approver`
390
+ : `no allow rule answers this ask${c !== undefined ? ` (${c.askClass})` : ""} — only a person can: run under a mode with an approver, or change the call`;
391
+ }
370
392
  const approver = input.approverSeat === "unavailable"
371
393
  ? "make a person reachable through the approver that is wired (it answered `unavailable` for this ask)"
372
394
  : input.approverSeat === "declared_none"
@@ -3,7 +3,7 @@ import { uuidv7 } from "../engine/session/uuid.js";
3
3
  import { boundInputHashOf, capturedJsonOf } from "./canonical-json.js";
4
4
  import { defineTool, errorResult } from "./tools.js";
5
5
  import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
6
- import { toolFace } from "./tool-catalog-entries.js";
6
+ import { mountRuleFace, toolFace } from "./tool-catalog-entries.js";
7
7
  import { thrownText } from "./thrown-value.js";
8
8
  import { heldBy } from "./tool-name-readings.js";
9
9
  export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
@@ -112,6 +112,7 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
112
112
  return defineTool({
113
113
  name: ASK_USER_QUESTION_TOOL_NAME,
114
114
  ...toolFace("ask-user-question"),
115
+ ruleFace: mountRuleFace("ask-user-question"),
115
116
  description: "Ask the user a structured multiple-choice question when you are genuinely stuck or a decision is " +
116
117
  "the user's to make (which approach, which of these). Provide 1-4 questions, each with 2-4 options " +
117
118
  "(label + description). Use sparingly — not for anything you can determine yourself from the code or " +
@@ -256,6 +257,11 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
256
257
  const untrustedParts = [];
257
258
  const answeredHeaders = new Set();
258
259
  const inlineSafe = (s) => inlineUntrusted(String(s), 300);
260
+ const echoLabel = (q, label, safe) => {
261
+ const place = q.options.findIndex((o) => o.label === label);
262
+ return place < 0 ? safe(label) : `[${place + 1}] ${safe(label)}`;
263
+ };
264
+ const asIs = (s) => s;
259
265
  const capNote = (s) => (s.length > 4000 ? s.slice(0, 4000) + "…" : s);
260
266
  let answers;
261
267
  try {
@@ -290,15 +296,15 @@ export function createAskUserQuestionTool(onQuestion, source, opts) {
290
296
  chosen = chosen.slice(0, 1);
291
297
  }
292
298
  if (answeredHeaders.has(q.header)) {
293
- untrustedParts.push(`[${inlineSafe(q.header)}] duplicate answer (already answered): ${[...chosen, ...offList].join(" ")}`);
299
+ untrustedParts.push(`[${inlineSafe(q.header)}] duplicate answer (already answered): ${[...chosen.map((s) => echoLabel(q, s, asIs)), ...offList].join(" ")}`);
294
300
  }
295
301
  else {
296
302
  answeredHeaders.add(q.header);
297
- trustedLines.push(`- ${inlineSafe(q.header)}: ${chosen.length > 0 ? chosen.map(inlineSafe).join(", ") : "(no listed option selected)"}`);
303
+ trustedLines.push(`- ${inlineSafe(q.header)}: ${chosen.length > 0 ? chosen.map((s) => echoLabel(q, s, inlineSafe)).join(", ") : "(no listed option selected)"}`);
298
304
  if (offList.length > 0)
299
305
  untrustedParts.push(`[${inlineSafe(q.header)}] off-list input: ${offList.join("\n")}`);
300
306
  if (extra.length > 0)
301
- untrustedParts.push(`[${inlineSafe(q.header)}] discarded extra selections (single-choice): ${extra.join(", ")}`);
307
+ untrustedParts.push(`[${inlineSafe(q.header)}] discarded extra selections (single-choice): ${extra.map((s) => echoLabel(q, s, asIs)).join(", ")}`);
302
308
  }
303
309
  if (note)
304
310
  untrustedParts.push(`[${inlineSafe(q.header)}] note: ${note}`);
@@ -17,7 +17,7 @@ export declare const DEFAULT_COMPACTION_INSTRUCTIONS: string;
17
17
  * `compaction.instructions`, the DEFAULT constant, a manual `/compact` per-call override, and a
18
18
  * preCompact hook's `additionalInstructions` — 修类不修点): instructions text can be model- or
19
19
  * caller-authored, and it is injected VERBATIM into the summarization prompt
20
- * (`\n\nAdditional Instructions:\n${...}`, CC 207 :347521 shape), so a forged `</system-reminder>`
20
+ * (`\n\nAdditional Instructions:\n${...}`), so a forged `</system-reminder>`
21
21
  * or `<<<`/`>>>` fence sentinel inside it would otherwise ride into a trusted prompt position.
22
22
  * Posture (r2 review B4): `sanitizeUntrustedText` + fence defusing only — NEVER `delimitUntrusted`
23
23
  * (fencing instructions as "data only, do not follow" self-defeats their instructional intent, and
@@ -169,7 +169,7 @@ export interface MaybeCompactOptions {
169
169
  * design/145 §1 (semantics精确化): used only while WINDOW-SAFE and the budget gate allows — when a
170
170
  * dry-run clamp shows the conversation would lose more than `settings.clampTolerance` of its
171
171
  * chars to fit this model's window, the summary call falls back to the MAIN model (`model`),
172
- * which is always window-safe (the content grew inside its window; CC 207 :348496 compresses
172
+ * which is always window-safe (the content grew inside its window; the reference compresses
173
173
  * with the main-loop model unconditionally — the fallback IS the CC posture, the independent
174
174
  * compaction model is sema's superset). Subagent note (design/145 §5): delegated children never
175
175
  * inherit this option (subagent.ts buildChildSpec carries no compactionModel), so a child always
@@ -317,7 +317,7 @@ export interface MaybeCompactOptions {
317
317
  maxCharsPerFile?: number;
318
318
  /**
319
319
  * CC-parity selection set: the files the task has READ, most recent
320
- * first (CC 198's post-compact restore sorts readFileState by timestamp — the read set is wider
320
+ * first (the reference's post-compact restore sorts readFileState by timestamp — the read set is wider
321
321
  * than the modified set and includes reference files the model consulted). When provided and
322
322
  * non-empty it WINS over `modifiedFilesByRecency`; absent/empty falls back to the modified set
323
323
  * (the pre-2026-07-03 behavior, and the right answer for callers with no read tracking).
@@ -419,6 +419,9 @@ export interface MaybeCompactOptions {
419
419
  * context may have lost an engine frame the run still needs, and every reader of the new baseline
420
420
  * (the next model request first) must see it restated. */
421
421
  postureRestate?: () => Promise<void>;
422
+ /** The advisor tool's instructions' post-compaction restatement on a leg that mounts the tool (the runner's
423
+ * `compactionRestateOptions`): runs right after `postureRestate`, under the same belt, for the same reason. */
424
+ advisorRestate?: () => Promise<void>;
422
425
  /**
423
426
  * Fixed per-request prompt overhead (system prompt + tool schemas, in tokens), applied ONLY in the
424
427
  * anchor-less regime (design/64 §26.7, search [91] fourth candidate): when no assistant in the
@@ -458,7 +461,7 @@ export interface MaybeCompactOptions {
458
461
  * (`reused`) and feeds it back via this option; core treats `summaryProvider` as if absent for that one
459
462
  * boundary when `consecutiveProviderReuse >= maxConsecutiveProviderReuse`. Default `3`, a
460
463
  * SEMA choice — the earlier "(CC parity)" label was unsupported and is withdrawn (anchoring
461
- * re-check 2026-08-23: CC 2.1.223 has no external summary provider and no reuse concept at all;
464
+ * re-check 2026-08-23: the reference has no external summary provider and no reuse concept at all;
462
465
  * its three 3s are the PTL retry cap `b$d`, the consecutive-compaction-failure breaker `C$d` and
463
466
  * the rapid-refill trip `Q3u`, none of them this). 3 is picked for the same reason those are: a
464
467
  * small bound on how long a degraded path may keep answering before the real one is forced.
@@ -801,8 +804,8 @@ export declare function countSummarizedCrossSessionMessages(branch: ReadonlyArra
801
804
  * SEMA-ONLY DEFENSE, not a CC port — but the ORIGINAL justification for that label was wrong
802
805
  * on both of its premises, and the corrected pair still supports it. (a) "CC's full compact
803
806
  * keeps tail 0 and monotonically shrinks" describes the `GNo` branch, which a LOCAL threshold
804
- * compaction never reaches (CC 2.1.223 `y9s` :432704 routes local passes to the reactive
805
- * pipeline, which preserves the last group verbatim — `YMo` :399971, `s = 1` :399982); CC's
807
+ * compaction never reaches (the reference routes local passes to the reactive
808
+ * pipeline, which preserves the last group verbatim); the reference's
806
809
  * default posture therefore has a keep-tail, exactly as ours does. (b) "CC has no
807
810
  * request-layer trim" holds only for the MAIN conversation request: CC does own a
808
811
  * keep-the-newest-groups-that-fit primitive (`UPb` :645223), whose only caller is the
@@ -819,7 +822,7 @@ export declare function nextTrimForceBackoff(prev: boolean, comp: {
819
822
  freedTokens?: number;
820
823
  naturalTrigger?: boolean;
821
824
  }, trimForced?: boolean): boolean;
822
- /** design/119 S3 (CC 198 rapid_refill_breaker): mutable per-task refill tracking state. */
825
+ /** design/119 S3 (rapid_refill_breaker): mutable per-task refill tracking state. */
823
826
  export interface RapidRefillState {
824
827
  lastCompactionTurn: number;
825
828
  count: number;
@@ -827,7 +830,7 @@ export interface RapidRefillState {
827
830
  /** Fresh tracking state (no compaction seen yet). */
828
831
  export declare function createRapidRefillState(): RapidRefillState;
829
832
  /**
830
- * design/119 S3 (CC 2.1.198 rapid_refill_breaker, :229717/:476338): record that a compaction just
833
+ * design/119 S3 (rapid_refill_breaker): record that a compaction just
831
834
  * happened at `turn` and report whether the thrash breaker should TRIP. A compaction landing within
832
835
  * `minGapTurns` of the previous one is a "rapid refill"; `tripAt` consecutive rapid refills = the
833
836
  * context is dominated by incompressible content and further compaction only burns summary calls.
@@ -504,9 +504,11 @@ export async function maybeCompact(opts) {
504
504
  catch {
505
505
  }
506
506
  }
507
- if (opts.postureRestate !== undefined) {
507
+ for (const restate of [opts.postureRestate, opts.advisorRestate]) {
508
+ if (restate === undefined)
509
+ continue;
508
510
  try {
509
- await opts.postureRestate();
511
+ await restate();
510
512
  }
511
513
  catch {
512
514
  }
@@ -18,7 +18,7 @@ export declare const AUTO_MODE_ARMING_RECIPE_VERSION = 2;
18
18
  export interface AutoModeArmingRecipe {
19
19
  /** {@link AUTO_MODE_ARMING_RECIPE_VERSION}. An unknown version is refused, never partially read. */
20
20
  v: number;
21
- /** `buildAutoModePrompt` rule overrides for the four paired sections (omitted = CC defaults verbatim). */
21
+ /** `buildAutoModePrompt` rule overrides for the four paired sections (omitted = the defaults). */
22
22
  rules?: AutoModeRules;
23
23
  /** Settings-plane deny rules rendered into `<settings_deny_rules>`. */
24
24
  settingsDenyRules?: readonly string[];
@@ -37,7 +37,7 @@ export interface AutoModeArmingRecipe {
37
37
  /** Classify round-trip cap, ms. */
38
38
  timeoutMs?: number;
39
39
  /**
40
- * The classifier DENIAL-LIMIT bounds (`RunnerDeps.autoMode.denialLimit`, CC 2.1.250 `FO`/`AKe`):
40
+ * The classifier DENIAL-LIMIT bounds (`RunnerDeps.autoMode.denialLimit`):
41
41
  * how many blocks the ancestor's classifier was allowed before a person had to look, and the window the
42
42
  * fallback ask ran under. A KNOB triple, not prompt body — every member is orderable, so the fold takes
43
43
  * the strictest of the two sides rather than refusing on a difference.
@@ -3,14 +3,14 @@
3
3
  * `RunnerDeps.autoMode.timeoutMs` is omitted. Sized for a self-hosted gateway, not a cloud API: a
4
4
  * thinking-by-default gateway answers a classification in 5–165 s (Qwen 3.8 p50 11–24 s, kimi and GLM p50
5
5
  * ~10 s), and queueing plus prefill of 2–5 min is a normal wait on such a gateway (`docs/TIMEOUTS.md`). 180 s
6
- * clears the slowest classification observed with margin; CC 2.1.259's per-attempt base is 60 s (`j7e`,
6
+ * clears the slowest classification observed with margin; the reference's per-attempt base is 60 s (
7
7
  * stretched to 120 s with prompt size). Worst-case walk = this × rungs (2) = 360 s. Where the leg has an
8
8
  * approval seat a rung that runs out of time costs a question to a person, not a refusal (see the gate's
9
9
  * classifier station).
10
10
  */
11
11
  export declare const AUTO_MODE_DEFAULT_TIMEOUT_MS = 180000;
12
12
  /**
13
- * The classifier call's transient retry budget — the reference implementation's short ladder (2.1.280 `J9e = 4`): a
13
+ * The classifier call's transient retry budget — the reference implementation's short ladder: a
14
14
  * provider error (an HTTP 5xx, a dropped connection) is retried this many times inside the rung's budget, then the rung
15
15
  * reports `unavailable` / `error`. Where the leg has an approval seat that question goes to a person, so a long ladder
16
16
  * would only make the person wait out the rung's budget and hear "timed out" for a failure that was not one.
@@ -52,8 +52,8 @@ export declare const AUTO_MODE_CLASSIFIER_MAX_TOKENS = 256;
52
52
  * which on a default-on gateway is a think). The seat pays for the least the wire can grade plus the verdict,
53
53
  * never for an open-ended budget.
54
54
  *
55
- * 2048 is anchored twice: CC 2.1.259 `MU` adds exactly this many tokens to the verdict cap for a model in
56
- * its `rejects_disabled_thinking` family (2.1.250 `E4`/`BHe`); and it is 2 × `MIN_THINKING_TOKENS` (1024), so
55
+ * 2048 is anchored twice: the reference adds exactly this many tokens to the verdict cap for a model in
56
+ * its `rejects_disabled_thinking` family; and it is 2 × `MIN_THINKING_TOKENS` (1024), so
57
57
  * on the anthropic BUDGET path a HARD cap of 256 + 2048 clears the cap-wins skip (`budgetCapSkipsThinking`)
58
58
  * and hosts a legal `low` budget (`1024 ≤ budget_tokens < max_tokens`) without the brain having to raise a
59
59
  * cap it refuses to raise. Live 2026-09-15: deepseek's default think fits it ~90–100% of the time; the tail
@@ -62,8 +62,8 @@ export declare const AUTO_MODE_CLASSIFIER_MAX_TOKENS = 256;
62
62
  */
63
63
  export declare const AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS = 2048;
64
64
  /**
65
- * The RE-ASK seat's allowance — the room a rung gets ONCE after its first attempt produced no verdict: CC
66
- * 2.1.259's stage-2 classifier cap (`max_tokens: 8192 + bn`). Live 2026-09-15 the thinking tail that overruns
65
+ * The RE-ASK seat's allowance — the room a rung gets ONCE after its first attempt produced no verdict: the reference's
66
+ * stage-2 classifier cap (`max_tokens: 8192 + bn`). Live 2026-09-15 the thinking tail that overruns
67
67
  * 2048 (deepseek 1–2 of 20, some past 4096; Qwen up to 6k) sits under it. One re-ask, never a loop.
68
68
  */
69
69
  export declare const AUTO_MODE_CLASSIFIER_THINK_BUDGET_TOKENS = 8192;
@@ -78,14 +78,14 @@ export declare const AUTO_MODE_CLASSIFIER_CONTRACT_BROKEN_AFTER = 2;
78
78
  * recipe canonicalizer needs its VALUE and must not load the assembly face (and its SHA-locked assets)
79
79
  * to get it; `auto-mode-prompt.js` re-exports it, so the public name and its module face are unchanged. */
80
80
  export declare const AUTO_MODE_DEFAULTS_SENTINEL = "$defaults";
81
- /** CC 2.1.250 `FO = {maxConsecutive: 3, maxTotal: 20}` — the classifier DENIAL LIMITS applied when
81
+ /** The classifier DENIAL LIMITS applied when
82
82
  * `RunnerDeps.autoMode.denialLimit` omits a bound. Count-then-judge: the block that reaches a bound is
83
83
  * itself the one that falls back to a person (the 3rd consecutive block asks; the 20th total asks). */
84
84
  export declare const AUTO_MODE_DENIAL_LIMIT_DEFAULTS: Readonly<{
85
85
  maxConsecutive: number;
86
86
  maxTotal: number;
87
87
  }>;
88
- /** CC 2.1.250 `AKe = 120000` — the fallback ask's auto-deny window (ms) when `autoDenyAfterMs` is omitted.
88
+ /** The fallback ask's auto-deny window (ms) when `autoDenyAfterMs` is omitted.
89
89
  * `0` disarms the window (the ask waits on the approver alone — the upstream original "fall back to
90
90
  * prompting" shape). */
91
91
  export declare const AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS = 120000;
@@ -1,10 +1,10 @@
1
- /** The auto-mode classifier SYSTEM prompt shell (CC 2.1.207 `auto_mode_system_prompt`, with consent-bar
2
- * principle 7 at its CC 2.1.291 text — see the header note).
1
+ /** The auto-mode classifier SYSTEM prompt shell (`auto_mode_system_prompt`, with consent-bar
2
+ * principle 7 at its later text — see the header note).
3
3
  * Contains two assembly placeholders consumed by `buildAutoModePrompt`: `<permissions_template>`
4
4
  * (replaced by the permissions document) and `<cross_session_messages_rule>` (replaced with "" —
5
5
  * the 206/207 default). */
6
6
  export declare const AUTO_MODE_BASE_PROMPT: string;
7
- /** The external permissions document (CC 2.1.207 `permissions_external`). Four PAIRED placeholder
7
+ /** The external permissions document (`permissions_external`). Four PAIRED placeholder
8
8
  * tags wrap the DEFAULT rule sets (environment / hard deny / soft deny / allow) and one UNPAIRED tag
9
9
  * (`<settings_deny_rules>`) marks the settings-plane deny injection point — all consumed by
10
10
  * `buildAutoModePrompt` with the CC-exact merge semantics (user rules REPLACE the defaults; the
@@ -7,7 +7,7 @@ import type { Message } from "../internal/llm.js";
7
7
  export { AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
8
8
  /** Deployment-authored rule overrides for the four paired sections of the permissions document.
9
9
  * Each entry is ONE rule (rendered as a `- ` bullet); omit a field (or pass an empty array) to keep
10
- * the CC default rules for that section verbatim. */
10
+ * the default rules for that section. */
11
11
  export interface AutoModeRules {
12
12
  allow?: readonly string[];
13
13
  softDeny?: readonly string[];
@@ -16,7 +16,7 @@ export declare function isAutoModeUnavailableCause(v: unknown): v is AutoModeUna
16
16
  * HOW a reply failed to carry a verdict — the closed set behind `parse_error.form`. `missing` = the reader found
17
17
  * NO answer (empty text, prose, an answer only inside a thinking block); `conflict` = it found TWO distinct
18
18
  * `<block>` answers (raw or thinking-stripped). The split decides who gets a SECOND ATTEMPT at the same
19
- * question, on both seats that offer one: a missing answer is asked for again (CC 2.1.259 `Vrr` re-asks an
19
+ * question, on both seats that offer one: a missing answer is asked for again (the reference re-asks an
20
20
  * unparseable reply) and handed on to the next candidate ({@link rungDidNotRule}); a conflict gets neither —
21
21
  * it carries a `yes`, and any second attempt that answered `no` would erase it. One rule, two seats: whatever
22
22
  * a re-ask may not overwrite, a later rung may not overwrite either. Not on any wire face; the trace frame and
@@ -157,7 +157,7 @@ export declare function autoModeRoundNotAsked(reason: unknown): Error & {
157
157
  */
158
158
  export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCause | undefined;
159
159
  /**
160
- * The model-facing deny text for an `unavailable` verdict — CC 2.1.250 `x1t`, with the parenthetical `EIt`
160
+ * The model-facing deny text for an `unavailable` verdict — with the parenthetical
161
161
  * spells for the causes this engine distinguishes (a wall-clock / connection timeout reads " (timed out)";
162
162
  * an unknown kind reads nothing). The subject is the classifier itself rather than a model id: without a
163
163
  * candidate ladder the decider cannot vouch for a name. One writer for every deny site. ROOT CAUSE, when
@@ -173,8 +173,8 @@ export declare function classifierUnavailableDenyMessage(toolName: string, cause
173
173
  */
174
174
  export declare function classifierNoRulingAskMessage(askText: string, cause: AutoModeFallbackCause): string;
175
175
  /**
176
- * The model-facing deny text for a `parse_error` verdict — CC 2.1.259 `i7t` stem ("Auto mode could not evaluate
177
- * this action and is blocking it for safety"; 2.1.250 `Ure`): the classifier answered, but not with a verdict,
176
+ * The model-facing deny text for a `parse_error` verdict — stem ("Auto mode could not evaluate
177
+ * this action and is blocking it for safety"): the classifier answered, but not with a verdict,
178
178
  * and auto mode blocks what it could not evaluate. With a candidate ladder wired the tail NAMES the model whose
179
179
  * reply carried no verdict (`attribution.modelUsed`) — a declared departure from upstream, whose card names no
180
180
  * model: the person reading this card is the one who can change the catalog, and on a single-model deployment
@@ -183,8 +183,8 @@ export declare function classifierNoRulingAskMessage(askText: string, cause: Aut
183
183
  */
184
184
  export declare function classifierParseFailureDenyMessage(attribution?: AutoModeClassifierRound): string;
185
185
  /**
186
- * Parse the classifier's output contract — the CC 2.1.259 verdict reader (`MTt`, its thinking strip `QTt`, its
187
- * reason reader `TTt`; unchanged from 2.1.250 `wCe`/`xCe`/`yCe`), plus ONE arm of sema's own:
186
+ * Parse the classifier's output contract — the verdict reader (with its thinking strip and its
187
+ * reason reader), plus ONE arm of sema's own:
188
188
  * blocked: `<block>yes</block><category>Rule Name</category><reason>[Rule Name] …</reason>`
189
189
  * allowed: `<block>no</block>`
190
190
  *
@@ -212,7 +212,7 @@ export declare function classifierParseFailureDenyMessage(attribution?: AutoMode
212
212
  * line from the tool name in that case. Downgrading a stated block to `parse_error` would lose the
213
213
  * classifier's explicit verdict on the exact calls it flagged.
214
214
  *
215
- * `parse_error` is fail-closed: the gate handles it as a BLOCK (CC 2.1.259's own outcome for a reply with no
215
+ * `parse_error` is fail-closed: the gate handles it as a BLOCK (the reference's own outcome for a reply with no
216
216
  * verdict) that the denial limit counts like any other — never a silent allow, and never a question handed
217
217
  * to a person on the classifier's behalf.
218
218
  */
@@ -340,8 +340,15 @@ export interface DenialLimitCounts {
340
340
  /** Total blocks this run INCLUDING the one that tripped the bound (read before the total-bound reset). */
341
341
  readonly total: number;
342
342
  /** Which bound tripped. */
343
- readonly limit: "consecutive" | "total";
343
+ readonly limit: DenialLimitBound;
344
344
  }
345
+ /** The two denial bounds, spelled once: {@link DenialLimitBound} is derived from this tuple, and every reader of a
346
+ * stored or foreign fallback member judges its `limit` word through {@link isDenialLimitBound}. */
347
+ export declare const DENIAL_LIMIT_BOUNDS: readonly ["consecutive", "total"];
348
+ /** Which denial bound tripped: `consecutive` blocks in a row, or the run's `total`. */
349
+ export type DenialLimitBound = (typeof DENIAL_LIMIT_BOUNDS)[number];
350
+ /** Whether a value is one of {@link DENIAL_LIMIT_BOUNDS} — the screen a reader applies to a stored `limit` word. */
351
+ export declare function isDenialLimitBound(v: unknown): v is DenialLimitBound;
345
352
  /**
346
353
  * The DECISION-layer member of a denial-limit fallback ask (`PermissionResult` ask arm): the counts
347
354
  * that tripped the bound, and NOTHING about a window. A window is a fact of the ask's ROUTE — a live
@@ -386,7 +393,8 @@ export type DenialLimitFallbackFace = UnarmedDenialLimitFallback | DenialLimitFa
386
393
  * finite non-negative window), nothing else carried. A deployment-written row (a checkpoint store is a
387
394
  * host's) can hold any shape; the faces that echo it (the inbox summary) read through here, so a bad
388
395
  * shape reads as ABSENT rather than reaching a consumer as a malformed card. Not a parser of intent —
389
- * `"3"` is not 3.
396
+ * `"3"` is not 3. The value is a plain record (no member comes from a prototype it was given); each member is read once,
397
+ * in declaration order, and the answer is the values judged.
390
398
  */
391
399
  export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback | undefined;
392
400
  /** The window-less route's member, spelled in ONE place: every station that hands a fallback somewhere
@@ -1,6 +1,7 @@
1
1
  import { AUTO_MODE_DEFAULT_TIMEOUT_MS, AUTO_MODE_DENIAL_AUTO_DENY_DEFAULT_MS, AUTO_MODE_DENIAL_LIMIT_DEFAULTS } from "./auto-mode-defaults.js";
2
2
  import { thrownIs } from "./thrown-value.js";
3
3
  import { describeRejectedValue } from "./tool-search-setting.js";
4
+ import { isPlainRecord } from "./mods/kernel/receive.js";
4
5
  export const AUTO_MODE_UNAVAILABLE_CAUSES = Object.freeze(["error", "timeout"]);
5
6
  const AUTO_MODE_UNAVAILABLE_CAUSE_SET = new Set(AUTO_MODE_UNAVAILABLE_CAUSES);
6
7
  export function isAutoModeUnavailableCause(v) {
@@ -312,16 +313,18 @@ class AutoModeTimeout extends Error {
312
313
  }
313
314
  }
314
315
  const isAutoModeTimeout = (e) => thrownIs(e, AutoModeTimeout);
316
+ export const DENIAL_LIMIT_BOUNDS = Object.freeze(["consecutive", "total"]);
317
+ export function isDenialLimitBound(v) {
318
+ return DENIAL_LIMIT_BOUNDS.some((bound) => v === bound);
319
+ }
315
320
  export function readDenialLimitFallback(v) {
316
- if (typeof v !== "object" || v === null)
321
+ if (!isPlainRecord(v))
317
322
  return undefined;
318
- const o = v;
319
323
  const count = (x) => typeof x === "number" && Number.isFinite(x) && x >= 0;
320
- if (!count(o["consecutive"]) || !count(o["total"]) || !count(o["autoDenyAfterMs"]))
321
- return undefined;
322
- if (o["limit"] !== "consecutive" && o["limit"] !== "total")
324
+ const [consecutive, total, limit, autoDenyAfterMs] = [v["consecutive"], v["total"], v["limit"], v["autoDenyAfterMs"]];
325
+ if (!count(consecutive) || !count(total) || !count(autoDenyAfterMs) || !isDenialLimitBound(limit))
323
326
  return undefined;
324
- return { consecutive: o["consecutive"], total: o["total"], limit: o["limit"], autoDenyAfterMs: o["autoDenyAfterMs"] };
327
+ return { consecutive, total, limit, autoDenyAfterMs };
325
328
  }
326
329
  export function unarmedWindow(fallback) {
327
330
  return { ...countsOf(fallback), autoDenyAfterMs: 0 };