@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
@@ -1,17 +1,21 @@
1
1
  import { homedir } from "node:os";
2
2
  import { shellWordsOf } from "../shell/shell-words.js";
3
+ import { lexShell } from "../shell/lex.js";
4
+ import { readProgramPosition } from "../tools/fs/bash-program-position.js";
3
5
  import { parsePermissionRule } from "./permission-rule-syntax.js";
4
6
  import { compileReadDeny } from "../tools/fs/read-deny.js";
5
7
  import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
6
8
  import { INLINE_TRUNCATION_MARK, inlineUntrusted } from "./untrusted-text.js";
7
- import { isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
9
+ import { catalogRuleFaceOf, hostTargetOf, isShellFaceToolName, pathTargetOf, protectivePathTargetOf } from "./tool-registry.js";
10
+ import { HOST_UNREADABLE_SENTENCE, hostOfSpelling, isAddressHost } from "./host-target.js";
8
11
  import { heldValues } from "./tool-name-readings.js";
9
12
  import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
10
13
  import { bindPathRules, judgeRuleRows, rowsWhereTheyLead } from "./path-rule-binding.js";
11
14
  import { readShellCommand } from "./shell-run-candidates.js";
12
15
  import { lineDescriptorsOfText, readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
13
16
  import { ARTIFACT_ACTIONS } from "./artifact-host.js";
14
- import { ARTIFACT_TOOL_NAME } from "./tool-catalog-entries.js";
17
+ import { ARTIFACT_TOOL_NAME, TOOL_CATALOG_ENTRIES } from "./tool-catalog-entries.js";
18
+ import { paramRuleReaches } from "./param-rule-reach.js";
15
19
  export const RULE_BEHAVIORS = Object.freeze(["deny", "ask", "allow"]);
16
20
  const RULE_BEHAVIOR_SET = new Set(RULE_BEHAVIORS);
17
21
  export function isRuleBehavior(v) {
@@ -43,64 +47,121 @@ export function callPathFaceOf(call) {
43
47
  export function faceRowsSpeakFor(face) {
44
48
  return face !== undefined && Object.values(PATH_RULE_FACES).some((columns) => columns.deny?.includes(face) === true || columns.ask?.includes(face) === true);
45
49
  }
50
+ function catalogVocabularyOf(tool) {
51
+ const params = catalogRuleFaceOf(tool)?.params;
52
+ if (params === undefined)
53
+ throw new Error(`permission rules: the catalog declares no rule vocabulary for ${JSON.stringify(tool)}`);
54
+ return params;
55
+ }
56
+ const DELEGATION_RULE_TOOL = (() => {
57
+ const row = TOOL_CATALOG_ENTRIES.find((e) => e.id === "agent");
58
+ if (row === undefined)
59
+ throw new Error("permission rules: the catalog carries no delegation tool row (`agent`)");
60
+ return row.name;
61
+ })();
46
62
  export const NAME_RULE_FACES = Object.freeze({
47
- Skill: Object.freeze({ param: "skill", params: Object.freeze(["skill", "args"]) }),
63
+ Skill: Object.freeze({ param: "skill", params: Object.freeze(catalogVocabularyOf("Skill")) }),
48
64
  [ARTIFACT_TOOL_NAME]: Object.freeze({ param: "action", params: Object.freeze(["action"]), words: ARTIFACT_ACTIONS }),
65
+ [DELEGATION_RULE_TOOL]: Object.freeze({ param: "subagent_type", params: Object.freeze(["subagent_type"]), values: "mounted" }),
49
66
  });
67
+ function nameRuleFaceOf(tool) {
68
+ return Object.hasOwn(NAME_RULE_FACES, tool) ? NAME_RULE_FACES[tool] : undefined;
69
+ }
50
70
  function keyedValueParamOf(tool) {
51
- const face = Object.hasOwn(NAME_RULE_FACES, tool) ? NAME_RULE_FACES[tool] : undefined;
71
+ const face = nameRuleFaceOf(tool);
52
72
  return face?.words !== undefined ? face.param : undefined;
53
73
  }
54
- export function keyedValueAbsentReason(tool) {
55
- const key = keyedValueParamOf(tool);
56
- return key === undefined ? undefined : `the ${tool} call names no ${key} as the tool reads it, so the ${key} it runs is not one its arguments show — a rule by ${key} cannot be read against it`;
74
+ export function normalizeSubagentType(value) {
75
+ return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
76
+ }
77
+ export function canonicalNameValue(value, values) {
78
+ if (values.includes(value))
79
+ return { kind: "entry", entry: value };
80
+ const key = normalizeSubagentType(value);
81
+ const hits = key === "" ? [] : [...new Set(values.filter((v) => normalizeSubagentType(v) === key))];
82
+ if (hits.length === 1)
83
+ return { kind: "entry", entry: hits[0] };
84
+ return hits.length > 1 ? { kind: "ambiguous", entries: hits } : { kind: "unknown" };
85
+ }
86
+ export function nameValueAbsentReason(tool) {
87
+ const face = nameRuleFaceOf(tool);
88
+ if (face === undefined || (face.words === undefined && face.values === undefined))
89
+ return undefined;
90
+ const key = face.param;
91
+ return `the ${tool} call names no ${key} as the tool reads it, so the ${key} it runs is not one its arguments show — a rule by ${key} cannot be read against it`;
57
92
  }
58
93
  export function nameGrammarReads(tool, content) {
59
94
  if (ruleToolGrammarOf(tool) !== "name")
60
95
  return false;
61
- const key = keyedValueParamOf(tool);
62
- if (key === undefined)
63
- return true;
96
+ const face = nameRuleFaceOf(tool);
97
+ const key = contentKeyOf(content);
98
+ if (face.words !== undefined)
99
+ return key === face.param;
100
+ return key === undefined || face.params.includes(key) || !(catalogRuleFaceOf(tool)?.params ?? []).includes(key);
101
+ }
102
+ export function mountedValueArmOf(text, tool, behavior) {
103
+ if (behavior === "allow" || nameRuleFaceOf(tool)?.values !== "mounted")
104
+ return undefined;
105
+ const parsed = parseRuleText(text, behavior);
106
+ return "rule" in parsed && parsed.rule.match === "name" ? parsed.rule : undefined;
107
+ }
108
+ export function mountedOnlyArgumentOf(tool, content, mountedParams) {
109
+ if (nameRuleFaceOf(tool)?.values !== "mounted" || mountedParams === undefined)
110
+ return undefined;
111
+ const key = contentKeyOf(content);
112
+ return key !== undefined && mountedParams.includes(key) && !(catalogRuleFaceOf(tool)?.params ?? []).includes(key) ? key : undefined;
113
+ }
114
+ export function mountedArgumentArmOf(rule) {
115
+ const face = nameRuleFaceOf(rule.tool);
116
+ if (rule.behavior === "allow" || rule.match !== "name" || face?.values !== "mounted")
117
+ return undefined;
118
+ const key = contentKeyOf(rule.command);
119
+ if (key === undefined || face.params.includes(key) || !(catalogRuleFaceOf(rule.tool)?.params ?? []).includes(key))
120
+ return undefined;
121
+ const pattern = rule.command.slice(rule.command.indexOf(":") + 1).trim();
122
+ return pattern === "" ? undefined : { param: key, pattern };
123
+ }
124
+ function contentKeyOf(content) {
64
125
  const colon = content.indexOf(":");
65
- return colon > 0 && content.slice(0, colon).trim() === key;
126
+ return colon > 0 ? content.slice(0, colon).trim() : undefined;
66
127
  }
67
- export const DOMAIN_RULE_FACES = Object.freeze({
68
- WebFetch: Object.freeze({ param: "url" }),
69
- });
128
+ export const DOMAIN_RULE_MARKER = "domain:";
70
129
  export function ruleToolGrammarOf(toolName) {
71
130
  if (toolName === COMMAND_RULE_TOOL)
72
131
  return "command";
73
132
  if (Object.hasOwn(NAME_RULE_FACES, toolName))
74
133
  return "name";
75
- if (Object.hasOwn(DOMAIN_RULE_FACES, toolName))
134
+ if (hostTargetOf(toolName) !== undefined)
76
135
  return "domain";
77
136
  return pathTargetOf(toolName) !== undefined ? "path" : undefined;
78
137
  }
79
- export function domainRuleSubjectOf(toolName, args) {
80
- const face = Object.hasOwn(DOMAIN_RULE_FACES, toolName) ? DOMAIN_RULE_FACES[toolName] : undefined;
81
- if (face === undefined || typeof args !== "object" || args === null || Array.isArray(args) || !Object.hasOwn(args, face.param))
82
- return undefined;
83
- const value = args[face.param];
84
- if (typeof value !== "string")
85
- return undefined;
86
- let url;
87
- try {
88
- url = new URL(value);
89
- }
90
- catch {
138
+ export function shellRuleParamOf(tool, content) {
139
+ const colon = content.indexOf(":");
140
+ if (colon <= 0)
91
141
  return undefined;
142
+ const param = content.slice(0, colon).trim();
143
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(param) && (catalogRuleFaceOf(tool)?.params ?? []).includes(param) ? param : undefined;
144
+ }
145
+ export function ruleLaneOf(tool, content) {
146
+ if (isShellFaceToolName(tool))
147
+ return shellRuleParamOf(tool, content) !== undefined && !(tool === COMMAND_RULE_TOOL && content.endsWith(":*")) ? "param" : "command";
148
+ switch (ruleToolGrammarOf(tool)) {
149
+ case "name":
150
+ return nameGrammarReads(tool, content) ? "name" : "param";
151
+ case "path":
152
+ return content.indexOf(":") <= 0 ? "path" : "param";
153
+ case "domain":
154
+ return content.startsWith(DOMAIN_RULE_MARKER) ? "domain" : "param";
155
+ default:
156
+ return "param";
92
157
  }
93
- if ((url.protocol !== "http:" && url.protocol !== "https:") || url.username !== "" || url.password !== "")
94
- return undefined;
95
- const host = url.hostname.replace(/\.+$/, "");
96
- return host === "" ? undefined : host;
97
158
  }
98
159
  export function nameRuleSubjectOf(toolName, args) {
99
160
  const face = Object.hasOwn(NAME_RULE_FACES, toolName) ? NAME_RULE_FACES[toolName] : undefined;
100
161
  if (face === undefined || typeof args !== "object" || args === null || Array.isArray(args) || !Object.hasOwn(args, face.param))
101
162
  return undefined;
102
163
  const value = args[face.param];
103
- return typeof value === "string" ? value : undefined;
164
+ return typeof value === "string" && value !== "" ? value : undefined;
104
165
  }
105
166
  export function laneToolNamesOf(req) {
106
167
  const words = heldValues("policy.persistedGrammar", { get: ruleWordOf }, req);
@@ -111,9 +172,9 @@ function ruleWordOf(name) {
111
172
  return COMMAND_RULE_TOOL;
112
173
  return ruleToolGrammarOf(name) !== undefined ? name : undefined;
113
174
  }
114
- export const PERSISTED_RULE_MATCHES = Object.freeze(["exact", "prefix", "wildcard", "subpath", "path", "name", "name_prefix", "host", "subdomain"]);
175
+ export const PERSISTED_RULE_MATCHES = Object.freeze(["exact", "prefix", "wildcard", "pattern", "subpath", "path", "name", "name_prefix", "host", "subdomain"]);
115
176
  function isCommandMatch(match) {
116
- return match === "exact" || match === "prefix" || match === "wildcard";
177
+ return match === "exact" || match === "prefix" || match === "wildcard" || match === "pattern";
117
178
  }
118
179
  export const RULE_ADD_ORIGINS = Object.freeze(["user", "imported-cc", "starter"]);
119
180
  export const MAX_RULE_TEXT_CHARS = 512;
@@ -282,10 +343,32 @@ const CONTROL_CHARS_GLOBAL_RE = new RegExp(CONTROL_CHARS_RE.source, "gu");
282
343
  const RULE_SPELLING_REJECT_BYTES = "<>";
283
344
  const RULE_SPELLING_REJECT_RE = new RegExp(`[${RULE_SPELLING_REJECT_BYTES}]`);
284
345
  function valueRuleFormsSentence() {
285
- return Object.entries(NAME_RULE_FACES)
286
- .map(([tool, face]) => (face.words !== undefined ? `${tool}(${face.param}:<${face.param}>) rules` : `${tool}(<${face.param}>) rules`))
346
+ return Object.keys(NAME_RULE_FACES)
347
+ .map((tool) => `${nameRuleFormOf(tool)} rules`)
287
348
  .join(", ");
288
349
  }
350
+ function nameRuleFormOf(tool) {
351
+ return formatRuleText(`<${nameRuleFaceOf(tool).param}>`, "name", tool);
352
+ }
353
+ export function ruleShapeOf(call, behavior) {
354
+ if (isShellFaceToolName(call.toolName))
355
+ return formatRuleText("<command>", "exact", COMMAND_RULE_TOOL);
356
+ switch (ruleToolGrammarOf(call.toolName)) {
357
+ case "name":
358
+ return nameRuleFormOf(call.toolName);
359
+ case "domain":
360
+ return formatRuleText("<host>", "host", call.toolName);
361
+ case "path": {
362
+ if (behavior !== "allow")
363
+ return formatRuleText("<path>", "path", call.toolName);
364
+ const face = callPathFaceOf(call) ?? (pathTargetOf(call.toolName)?.access === "read" ? "read" : "write");
365
+ const word = Object.keys(PATH_RULE_FACES).find((tool) => PATH_RULE_FACES[tool].allow?.includes(face) === true) ?? call.toolName;
366
+ return formatRuleText("<path>", "path", word);
367
+ }
368
+ default:
369
+ return undefined;
370
+ }
371
+ }
289
372
  const DISCLOSED_RULE_TEXT_MAX_CHARS = 120;
290
373
  export function escapeForDisclosure(value) {
291
374
  let text;
@@ -319,6 +402,10 @@ export function renderUntrustedCommandText(text, maxLen = DISCLOSED_RULE_TEXT_MA
319
402
  export function hasUnrenderableCharacters(text) {
320
403
  return CONTROL_CHARS_RE.test(text);
321
404
  }
405
+ export function ruleContentOf(parsed) {
406
+ const content = parsed.ruleContent;
407
+ return content === undefined || (parsed.toolName === COMMAND_RULE_TOOL && foldSpacing(content) === "*") ? undefined : content;
408
+ }
322
409
  export function parseRuleText(text, behavior) {
323
410
  if (!isRuleBehavior(behavior)) {
324
411
  return reject("invalid.grammar", `rule "${text}" was parsed under behavior ${escapeForDisclosure(behavior)}, which is not one of ${RULE_BEHAVIORS.join("/")}`);
@@ -331,7 +418,8 @@ export function parseRuleText(text, behavior) {
331
418
  return reject("invalid.control_chars", "rule text contains control characters (C0/C1, tab excepted), which cannot be part of a readable command");
332
419
  }
333
420
  const parsed = parsePermissionRule(text);
334
- if (parsed.ruleContent === undefined) {
421
+ const ruleContent = ruleContentOf(parsed);
422
+ if (ruleContent === undefined) {
335
423
  return reject("invalid.grammar", `"${text}" is not a Tool(content) rule — a bare tool name claims the whole tool and is not a command rule`);
336
424
  }
337
425
  if (parsed.toolName !== COMMAND_RULE_TOOL) {
@@ -339,16 +427,16 @@ export function parseRuleText(text, behavior) {
339
427
  if (grammar === undefined) {
340
428
  return reject("unsupported.tool", `only Bash command rules, rules on path-targeting tools (Read, Edit, Write, …), ${valueRuleFormsSentence()} and WebFetch domain rules have a rule form in this version (got "${parsed.toolName}")`);
341
429
  }
342
- if (!tighten && RULE_SPELLING_REJECT_RE.test(parsed.ruleContent)) {
430
+ if (!tighten && RULE_SPELLING_REJECT_RE.test(ruleContent)) {
343
431
  return reject("invalid.grammar", `"${text}" carries a shell redirection byte (${[...RULE_SPELLING_REJECT_BYTES].map((b) => `\`${b}\``).join(" or ")}) — no path, name or host allow spells one, and the grant would not read as what it looks like`);
344
432
  }
345
433
  if (grammar === "name")
346
- return parseNameRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
434
+ return parseNameRuleContent(text, parsed.toolName, ruleContent, behavior);
347
435
  if (grammar === "domain")
348
- return parseDomainRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
349
- return parsePathRuleContent(text, parsed.toolName, parsed.ruleContent, behavior);
436
+ return parseDomainRuleContent(text, parsed.toolName, ruleContent, behavior);
437
+ return parsePathRuleContent(text, parsed.toolName, ruleContent, behavior);
350
438
  }
351
- const content = parsed.ruleContent;
439
+ const content = foldSpacing(ruleContent) ?? ruleContent;
352
440
  const prefixBody = /^(.+):\*$/.exec(content)?.[1];
353
441
  let match;
354
442
  let body;
@@ -366,14 +454,18 @@ export function parseRuleText(text, behavior) {
366
454
  body = wildcardBody;
367
455
  }
368
456
  else if (hasUnescapedStar(content)) {
369
- return reject("unsupported.wildcard", `a \`*\` before the end of the pattern matches anything at that position, which has no rule form in this version — only the trailing \`<command> *\` form does ("${text}")`);
457
+ match = "pattern";
458
+ body = content;
370
459
  }
371
460
  else {
372
461
  match = "exact";
373
462
  body = content;
374
463
  }
375
464
  }
376
- const shape = ruleLaneShapeOf(body, MATCH_READING);
465
+ if (match === "pattern" && !tighten && hasUnescapedStar(body.trim().split(/[ \t]+/)[0] ?? "")) {
466
+ return reject("unsupported.wildcard", `a \`*\` in the first word of "${text}" leaves open which program runs — an allow names the program it grants (a deny or an ask may leave it open)`);
467
+ }
468
+ const shape = ruleLaneShapeOf(match === "pattern" ? globFloorText(body) : body, MATCH_READING);
377
469
  if ("reject" in shape) {
378
470
  if (body.trim() === "" || shape.reject === "empty command")
379
471
  return reject("invalid.empty_command", `rule "${text}" names no command`);
@@ -381,14 +473,16 @@ export function parseRuleText(text, behavior) {
381
473
  return reject("invalid.unbalanced_quotes", `rule "${text}" has unbalanced quoting, so the command it names is not determinable`);
382
474
  return reject("invalid.not_simple_command", `rule "${text}" is not a command this lane can name (${shape.reject})`);
383
475
  }
384
- if (match === "wildcard" && shape.segments.length > 1) {
385
- return reject("unsupported.wildcard", `a \`<chain> *\` pattern names more than one command, and the trailing-star form has no chain reading — the compound-prefix form spells one with the \`:*\` marker ("${text}")`);
476
+ if ((match === "wildcard" || match === "pattern") && shape.segments.length > 1) {
477
+ return reject("unsupported.wildcard", match === "pattern"
478
+ ? `a \`*\` pattern names one command, and "${text}" names more than one — a compound is covered segment by segment, and the compound-prefix form spells a chain with the \`:*\` marker`
479
+ : `a \`<chain> *\` pattern names more than one command, and the trailing-star form has no chain reading — the compound-prefix form spells one with the \`:*\` marker ("${text}")`);
386
480
  }
387
481
  const head = commandBasename(shape.names[shape.names.length - 1] ?? "");
388
- if ((match === "prefix" || match === "wildcard") && BARE_INTERPRETER_NAMES.has(head) && !tighten) {
482
+ if (match !== "exact" && BARE_INTERPRETER_NAMES.has(head) && !tighten) {
389
483
  return reject("invalid.bare_interpreter_prefix", shape.segments.length > 1
390
484
  ? `prefix rule "${text}" leaves its final segment open-ended under the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`
391
- : `prefix rule "${text}" is headed by the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`);
485
+ : `${match === "pattern" ? "pattern" : "prefix"} rule "${text}" is headed by the interpreter "${head}" — such a rule authorizes running arbitrary programs, which one approval click cannot be read as having granted (an exact rule naming the whole command line is accepted)`);
392
486
  }
393
487
  const command = foldSpacing(body);
394
488
  if (command === undefined) {
@@ -411,6 +505,8 @@ function parseNameRuleContent(text, tool, content, behavior, verifyCanonical = t
411
505
  const face = NAME_RULE_FACES[tool];
412
506
  if (face?.words !== undefined)
413
507
  return parseKeyedValueRuleContent(text, tool, content, behavior, { param: face.param, words: face.words });
508
+ if (face?.values === "mounted")
509
+ return parseMountedValueRuleContent(text, tool, content, behavior, face);
414
510
  const bare = content.startsWith("/") ? content.slice(1) : content;
415
511
  const colon = bare.indexOf(":");
416
512
  if (face !== undefined && colon > 0 && face.params.includes(bare.slice(0, colon).trim())) {
@@ -457,6 +553,28 @@ function parseKeyedValueRuleContent(text, tool, content, behavior, face) {
457
553
  }
458
554
  return { rule: { behavior, rule: formatRuleText(word, "name", tool), tool, match: "name", command: word } };
459
555
  }
556
+ function parseMountedValueRuleContent(text, tool, content, behavior, face) {
557
+ const form = nameRuleFormOf(tool);
558
+ const key = contentKeyOf(content);
559
+ if (key !== undefined && face.params.includes(key)) {
560
+ return reject("invalid.grammar", `"${text}" is the parameter spelling, which a ${tool} rule does not have — a ${tool} rule names the ${face.param} itself: ${form}`);
561
+ }
562
+ if (behavior === "allow" && key !== undefined && (catalogRuleFaceOf(tool)?.params ?? []).includes(key)) {
563
+ return reject("invalid.grammar", `"${text}" is a rule on the ${key} argument, which an allow entry has no form for — an allow reads one way, so it does not grant the ${face.param} "${content}" either; a ${tool} allow names the ${face.param}: ${form}`);
564
+ }
565
+ if (content === "" || content.trim() !== content) {
566
+ return reject("invalid.grammar", `the ${face.param} in "${text}" is empty or carries surrounding whitespace — a ${tool} rule names the ${face.param} exactly as it is spelled, so the spelling must be the one meant`);
567
+ }
568
+ if (content.includes("*")) {
569
+ return reject("unsupported.wildcard", `a \`*\` in the ${face.param} of "${text}" has no rule form — a ${tool} rule names one ${face.param} (${form}); the whole tool is the bare ${tool} entry`);
570
+ }
571
+ const canonical = formatRuleText(content, "name", tool);
572
+ const back = parsePermissionRule(canonical);
573
+ if (back.toolName !== tool || back.ruleContent !== content) {
574
+ return reject("invalid.grammar", `the ${face.param} in "${text}" has no spelling the rule syntax reads back (a backslash or an unbalanced parenthesis) — no ${tool} rule can name it`);
575
+ }
576
+ return { rule: { behavior, rule: canonical, tool, match: "name", command: content } };
577
+ }
460
578
  export function nameRuleAdmits(rule, name) {
461
579
  if (rule.match === "name")
462
580
  return name === rule.command;
@@ -464,14 +582,31 @@ export function nameRuleAdmits(rule, name) {
464
582
  return name.startsWith(rule.command);
465
583
  return false;
466
584
  }
585
+ export function nameRuleReach(rule, name, values) {
586
+ const face = nameRuleFaceOf(rule.tool);
587
+ if (face?.values !== "mounted")
588
+ return nameRuleAdmits(rule, name) ? "reached" : "not_reached";
589
+ if (rule.match !== "name")
590
+ return "not_reached";
591
+ if (values === undefined)
592
+ return "unread";
593
+ const operand = canonicalNameValue(rule.command, values);
594
+ const called = canonicalNameValue(name, values);
595
+ if (operand.kind === "ambiguous")
596
+ return rule.behavior !== "allow" && called.kind === "entry" && operand.entries.includes(called.entry) ? "reached" : "not_reached";
597
+ if (operand.kind === "entry" && called.kind === "entry")
598
+ return operand.entry === called.entry ? "reached" : "not_reached";
599
+ return name === rule.command ? "reached" : "not_reached";
600
+ }
601
+ export function nameValuesUnreadReason(tool) {
602
+ const face = nameRuleFaceOf(tool);
603
+ return `the ${tool} tool this call reaches declares no ${face?.param ?? "value"} values it offers, so which ${face?.param ?? "value"} the call runs cannot be read against a ${tool} rule`;
604
+ }
467
605
  function parseDomainRuleContent(text, tool, content, behavior) {
468
- if (behavior !== "allow") {
469
- return reject("unsupported.tool", `a ${tool} rule by host has an allow form only in this version ("${text}") — a ${behavior} by host is not read; write the ${behavior} on the whole ${tool} tool`);
470
- }
471
- if (!content.startsWith("domain:")) {
606
+ if (!content.startsWith(DOMAIN_RULE_MARKER)) {
472
607
  return reject("invalid.grammar", `a ${tool} rule names a host: ${tool}(domain:<host>) for one, ${tool}(domain:*.<domain>) for every host below a domain ("${text}")`);
473
608
  }
474
- const spelled = content.slice("domain:".length);
609
+ const spelled = content.slice(DOMAIN_RULE_MARKER.length);
475
610
  if (spelled === "*")
476
611
  return reject("unsupported.wildcard", `"${text}" names every host — the whole tool is the bare ${tool} entry`);
477
612
  const below = spelled.startsWith("*.");
@@ -488,24 +623,6 @@ function parseDomainRuleContent(text, tool, content, behavior) {
488
623
  const match = below ? "subdomain" : "host";
489
624
  return { rule: { behavior, rule: formatRuleText(host, match, tool), tool, match, command: host } };
490
625
  }
491
- function hostOfSpelling(spelled) {
492
- if (spelled === "" || /[\s/\\@?#%]/.test(spelled) || (spelled.includes(":") && !/^\[[^\]]+\]$/.test(spelled)))
493
- return undefined;
494
- let url;
495
- try {
496
- url = new URL(`https://${spelled}/`);
497
- }
498
- catch {
499
- return undefined;
500
- }
501
- if (url.port !== "" || url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "")
502
- return undefined;
503
- const host = url.hostname.replace(/\.+$/, "");
504
- return host === "" ? undefined : host;
505
- }
506
- function isAddressHost(host) {
507
- return host.startsWith("[") || /^\d+\.\d+\.\d+\.\d+$/.test(host);
508
- }
509
626
  export function domainRuleAdmits(rule, host) {
510
627
  if (rule.match === "host")
511
628
  return host === rule.command;
@@ -712,6 +829,10 @@ const NOT_REACHED = { reach: "not_reached" };
712
829
  export function programRunReachOf(rule, command) {
713
830
  if (!isCommandMatch(rule.match))
714
831
  return NOT_REACHED;
832
+ if (rule.match === "pattern") {
833
+ const pattern = commandGlobOf(rule.command, "words");
834
+ return pattern === undefined ? NOT_REACHED : anySegmentReach(readCommandMemo(command), (argv) => globArgvReachOf(pattern, argv));
835
+ }
715
836
  const body = readRuleBody(rule.command);
716
837
  if (body === undefined)
717
838
  return NOT_REACHED;
@@ -727,7 +848,8 @@ export function programRunReachOf(rule, command) {
727
848
  return hidden === undefined ? NOT_REACHED : { reach: "unreadable", reason: hidden.peelUnreadable };
728
849
  let outcome = REACHED;
729
850
  for (let i = 0; i < body.segments.length; i++) {
730
- const r = segmentReachOf(body.segments[i].argv, shape.segments[i], exact || i < body.segments.length - 1);
851
+ const bodyArgv = body.segments[i].argv;
852
+ const r = segmentReachOf(shape.segments[i], (argv) => argvReachOf(bodyArgv, argv, exact || i < body.segments.length - 1));
731
853
  if (r.reach === "not_reached")
732
854
  return hidden === undefined ? NOT_REACHED : { reach: "unreadable", reason: hidden.peelUnreadable };
733
855
  if (r.reach === "unreadable")
@@ -736,9 +858,12 @@ export function programRunReachOf(rule, command) {
736
858
  return outcome;
737
859
  }
738
860
  const bodyArgv = body.segments[0].argv;
861
+ return anySegmentReach(shape, (argv) => argvReachOf(bodyArgv, argv, exact));
862
+ }
863
+ function anySegmentReach(shape, reachOf) {
739
864
  let unreadable;
740
865
  for (const segment of shape.segments) {
741
- const r = segmentReachOf(bodyArgv, segment, exact);
866
+ const r = segmentReachOf(segment, reachOf);
742
867
  if (r.reach === "reached")
743
868
  return REACHED;
744
869
  if (r.reach === "unreadable")
@@ -749,12 +874,12 @@ export function programRunReachOf(rule, command) {
749
874
  export function ruleReachesProgramRun(rule, command) {
750
875
  return programRunReachOf(rule, command).reach === "reached";
751
876
  }
752
- function segmentReachOf(bodyArgv, segment, exact) {
877
+ function segmentReachOf(segment, reachOf) {
753
878
  if (segment.unreadable !== undefined)
754
879
  return { reach: "unreadable", reason: segment.unreadable };
755
880
  let unreadable;
756
881
  for (const argv of [segment.argv, ...segment.peeled]) {
757
- const r = argvReachOf(bodyArgv, argv, exact);
882
+ const r = reachOf(argv);
758
883
  if (r.reach === "reached")
759
884
  return REACHED;
760
885
  if (r.reach === "unreadable")
@@ -783,6 +908,148 @@ function argvReachOf(bodyArgv, argv, exact) {
783
908
  const loose = argv.slice(bodyArgv.length).find((w) => w.expands === "many");
784
909
  return loose === undefined ? NOT_REACHED : { reach: "unreadable", reason: `an unquoted expansion (${loose.raw}) may expand to nothing` };
785
910
  }
911
+ const GLOB_STAR = -1;
912
+ const GLOB_HOLE = -2;
913
+ const GLOB_GROUP = -3;
914
+ function globFloorText(body) {
915
+ let out = "";
916
+ for (let i = 0; i < body.length; i++) {
917
+ if (body[i] === "\\" && body[i + 1] === "*") {
918
+ out += "*";
919
+ i++;
920
+ }
921
+ else
922
+ out += body[i] === "*" ? "x" : body[i];
923
+ }
924
+ return out;
925
+ }
926
+ function commandGlobOf(body, reading) {
927
+ const shape = ruleLaneShapeOf(globFloorText(body), MATCH_READING);
928
+ return "reject" in shape || shape.segments.length !== 1 ? undefined : compileGlob(reading === "words" ? unquotedPattern(body) : body);
929
+ }
930
+ function unquotedPattern(body) {
931
+ let out = "";
932
+ let quote;
933
+ for (const ch of body) {
934
+ if (quote === undefined ? ch === "'" || ch === '"' : ch === quote)
935
+ quote = quote === undefined ? ch : undefined;
936
+ else
937
+ out += ch;
938
+ }
939
+ return out;
940
+ }
941
+ function compileGlob(pattern) {
942
+ const nodes = [];
943
+ const groupEnds = new Map();
944
+ for (let i = 0; i < pattern.length; i++) {
945
+ const ch = pattern[i];
946
+ if (ch === "\\" && pattern[i + 1] === "*") {
947
+ nodes.push(0x2a);
948
+ i++;
949
+ continue;
950
+ }
951
+ if (ch === "/" && pattern.startsWith("**/", i + 1)) {
952
+ let j = i + 1;
953
+ while (pattern.startsWith("**/", j))
954
+ j += 3;
955
+ nodes.push(0x2f, GLOB_GROUP, GLOB_STAR, 0x2f);
956
+ groupEnds.set(nodes.length - 3, nodes.length);
957
+ i = j - 1;
958
+ continue;
959
+ }
960
+ nodes.push(ch === "*" ? GLOB_STAR : ch.charCodeAt(0));
961
+ }
962
+ return { nodes, groupEnds };
963
+ }
964
+ function globMatches(glob, subject, holes) {
965
+ const { nodes, groupEnds } = glob;
966
+ const m = nodes.length;
967
+ let live = new Uint8Array(m + 1);
968
+ let next = new Uint8Array(m + 1);
969
+ const close = (set) => {
970
+ for (let p = 0; p < m; p++) {
971
+ if (set[p] !== 1)
972
+ continue;
973
+ if (nodes[p] === GLOB_STAR || nodes[p] === GLOB_GROUP)
974
+ set[p + 1] = 1;
975
+ if (nodes[p] === GLOB_GROUP)
976
+ set[groupEnds.get(p)] = 1;
977
+ }
978
+ };
979
+ live[0] = 1;
980
+ close(live);
981
+ for (const unit of subject) {
982
+ next.fill(0);
983
+ if (unit === GLOB_HOLE && holes === "any")
984
+ next.fill(1, live.indexOf(1));
985
+ else {
986
+ for (let p = 0; p < m; p++) {
987
+ if (live[p] !== 1)
988
+ continue;
989
+ if (nodes[p] === GLOB_STAR)
990
+ next[p] = 1;
991
+ else if (nodes[p] === unit)
992
+ next[p + 1] = 1;
993
+ }
994
+ close(next);
995
+ }
996
+ if (next.indexOf(1) < 0)
997
+ return false;
998
+ [live, next] = [next, live];
999
+ }
1000
+ return live[m] === 1;
1001
+ }
1002
+ function unitsOf(text) {
1003
+ const out = [];
1004
+ for (let i = 0; i < text.length; i++)
1005
+ out.push(text.charCodeAt(i));
1006
+ return out;
1007
+ }
1008
+ function globAdmitsSpelling(body, folded) {
1009
+ const glob = commandGlobOf(body, "spelling");
1010
+ return glob !== undefined && globMatches(glob, unitsOf(folded), "inside_star");
1011
+ }
1012
+ function globArgvReachOf(glob, argv) {
1013
+ if (argv.length === 0)
1014
+ return NOT_REACHED;
1015
+ const subject = [];
1016
+ let hole;
1017
+ let anchored = false;
1018
+ argv.forEach((w, k) => {
1019
+ if (w.expands !== "many") {
1020
+ if (anchored)
1021
+ subject.push(0x20);
1022
+ anchored = true;
1023
+ }
1024
+ if (w.expands === false)
1025
+ subject.push(...unitsOf(w.text));
1026
+ else {
1027
+ subject.push(GLOB_HOLE);
1028
+ hole ??= { at: k, raw: w.raw };
1029
+ }
1030
+ });
1031
+ if (globMatches(glob, subject, "inside_star"))
1032
+ return REACHED;
1033
+ if (hole === undefined || !globMatches(glob, subject, "any"))
1034
+ return NOT_REACHED;
1035
+ return { reach: "unreadable", reason: hole.at === 0 ? "the program name is an expansion" : `an expansion (${hole.raw}) stands where the rule's pattern reads` };
1036
+ }
1037
+ export function commandPatternMayRun(body, programs) {
1038
+ const lexed = lexShell(body);
1039
+ const words = "parseFailure" in lexed ? undefined : lexed.segments[0]?.tokens.filter((t) => t.kind === "word");
1040
+ if (words === undefined || words.length === 0)
1041
+ return true;
1042
+ const position = readProgramPosition(words);
1043
+ const program = position.program;
1044
+ if (program === undefined || position.refusal !== undefined || hasUnescapedStar(words[program.index]?.raw ?? "*"))
1045
+ return true;
1046
+ const rest = words.slice(program.index + 1).map((w) => ` ${w.raw}`).join("");
1047
+ const glob = compileGlob(unquotedPattern(`${program.basename}${rest}`.toLowerCase()));
1048
+ return programs.some((p) => {
1049
+ const head = unitsOf(p.toLowerCase());
1050
+ return globMatches(glob, head, "inside_star") || globMatches(glob, [...head, 0x20, GLOB_HOLE], "any");
1051
+ });
1052
+ }
786
1053
  function readRuleBody(body) {
787
1054
  const shape = readShellCommand(body);
788
1055
  if (shape.grouped || shape.segments.length === 0)
@@ -806,6 +1073,8 @@ function admitsUnder(rule, command, reading) {
806
1073
  return false;
807
1074
  if (rule.match === "exact")
808
1075
  return folded === rule.command;
1076
+ if (rule.match === "pattern")
1077
+ return shape.segments.length === 1 && globAdmitsSpelling(rule.command, folded);
809
1078
  const bodyShape = ruleLaneShapeOf(rule.command, reading);
810
1079
  if ("reject" in bodyShape || foldSpacing(rule.command) === undefined)
811
1080
  return false;
@@ -815,9 +1084,9 @@ function admitsUnder(rule, command, reading) {
815
1084
  }
816
1085
  export const EDITED_RULE_BREADTH_WARNING_CODES = Object.freeze(["broad_prefix", "compound_prefix"]);
817
1086
  export function ruleBreadthWarningsOf(rule) {
818
- if (rule.match !== "prefix" && rule.match !== "wildcard")
1087
+ if (rule.match !== "prefix" && rule.match !== "wildcard" && rule.match !== "pattern")
819
1088
  return [];
820
- const bodyShape = ruleLaneShapeOf(rule.command, MATCH_READING);
1089
+ const bodyShape = ruleLaneShapeOf(rule.match === "pattern" ? globFloorText(rule.command) : rule.command, MATCH_READING);
821
1090
  if ("reject" in bodyShape)
822
1091
  return [];
823
1092
  if (bodyShape.segments.length > 1) {
@@ -1102,8 +1371,10 @@ export function adjudicatePersistedPathRules(rules, call, views, anchorsOf, lead
1102
1371
  return undefined;
1103
1372
  }
1104
1373
  export function adjudicatePersistedNameRules(rules, call) {
1105
- if (call.name === undefined) {
1106
- if (keyedValueAbsentReason(call.tool) === undefined)
1374
+ const name = nameRuleSubjectOf(call.tool, call.request.args);
1375
+ const values = call.request.face?.nameValues;
1376
+ if (name === undefined) {
1377
+ if (nameValueAbsentReason(call.tool) === undefined)
1107
1378
  return undefined;
1108
1379
  for (const behavior of ["deny", "ask"]) {
1109
1380
  const row = rules.find((rule) => rule.match === "name" && eligiblePersisted(rule, call, behavior));
@@ -1114,21 +1385,48 @@ export function adjudicatePersistedNameRules(rules, call) {
1114
1385
  }
1115
1386
  for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
1116
1387
  for (const rule of rules) {
1117
- if (eligiblePersisted(rule, call, behavior) && nameRuleAdmits(rule, call.name))
1388
+ if (!eligiblePersisted(rule, call, behavior))
1389
+ continue;
1390
+ const arm = mountedArgumentArmOf(rule);
1391
+ if (arm !== undefined && paramRuleReaches(call.request, arm.param, arm.pattern))
1392
+ return { behavior, rules: [rule] };
1393
+ const reach = nameRuleReach(rule, name, values);
1394
+ if (reach === "reached")
1118
1395
  return { behavior, rules: [rule] };
1396
+ if (reach === "unread" && behavior !== "allow")
1397
+ return behavior === "deny" ? { behavior, rules: [rule] } : { behavior, rules: [rule], unreadable: nameValuesUnreadReason(call.tool) };
1119
1398
  }
1120
1399
  }
1121
1400
  return undefined;
1122
1401
  }
1123
1402
  export function adjudicatePersistedDomainRules(rules, call) {
1124
- if (call.host === undefined)
1125
- return undefined;
1126
- for (const rule of rules) {
1127
- if (eligiblePersisted(rule, call, "allow") && domainRuleAdmits(rule, call.host))
1128
- return { behavior: "allow", rules: [rule] };
1403
+ for (const behavior of RULE_BEHAVIORS_BY_PRECEDENCE) {
1404
+ const reading = behavior === "allow" ? call.hosts.grant : call.hosts.tighten;
1405
+ if (reading === undefined)
1406
+ continue;
1407
+ const speaks = (rule) => (rule.match === "host" || rule.match === "subdomain") && eligiblePersisted(rule, call, behavior);
1408
+ if ("unreadable" in reading) {
1409
+ if (behavior === "allow")
1410
+ continue;
1411
+ const row = rules.find(speaks);
1412
+ if (row === undefined)
1413
+ continue;
1414
+ return behavior === "deny" ? { behavior: "deny", rules: [row] } : { behavior: "ask", rules: [row], unreadable: `the host the call reaches cannot be read (${HOST_UNREADABLE_SENTENCE[reading.unreadable]})` };
1415
+ }
1416
+ const row = rules.find((rule) => speaks(rule) && domainRuleAdmits(rule, reading.host));
1417
+ if (row !== undefined)
1418
+ return { behavior, rules: [row] };
1129
1419
  }
1130
1420
  return undefined;
1131
1421
  }
1422
+ export function suggestRuleForHost(tool, host) {
1423
+ if (ruleToolGrammarOf(tool) !== "domain")
1424
+ return undefined;
1425
+ const parsed = parseRuleText(formatRuleText(host, "host", tool), "allow");
1426
+ if (!("rule" in parsed) || parsed.rule.match !== "host" || parsed.rule.command !== host)
1427
+ return undefined;
1428
+ return { kind: "single", rule: parsed.rule.rule, match: "host", command: host };
1429
+ }
1132
1430
  export function suggestRuleForName(tool, name) {
1133
1431
  if (ruleToolGrammarOf(tool) !== "name")
1134
1432
  return undefined;
@@ -1163,6 +1461,7 @@ export function segmentCoverageOf(command, rules, call) {
1163
1461
  });
1164
1462
  }
1165
1463
  export const RULE_OFFER_BATCH_MEMBER_KINDS = Object.freeze(["command", "directoryRead"]);
1464
+ export const RULE_OFFER_KINDS = Object.freeze(["single", "batch"]);
1166
1465
  export const UNCOVERED_SEGMENT_REASONS = Object.freeze(["redirection", "no_rule_form", "cap_overflow"]);
1167
1466
  export const UNCOVERED_SEGMENT_REASON_BASELINE = {
1168
1467
  redirection: "This part carries a redirection, so it is approved per use — no rule can cover this spelling. If you trust its redirection-free form, a rule can be written for that form (the card's edit row, or settings).",