@sema-agent/core 7.47.0 → 7.49.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 (431) hide show
  1. package/CHANGELOG.md +472 -0
  2. package/README.md +2 -2
  3. package/dist/agents/cascade.d.ts +2 -5
  4. package/dist/agents/child-failure.d.ts +20 -0
  5. package/dist/agents/child-failure.js +28 -0
  6. package/dist/agents/cross-session-ref.d.ts +11 -3
  7. package/dist/agents/cross-session-ref.js +6 -0
  8. package/dist/agents/cumulative-stats.d.ts +12 -15
  9. package/dist/agents/cumulative-stats.js +1 -1
  10. package/dist/agents/extra-tool-names.js +1 -1
  11. package/dist/agents/inherited-tool-pool.d.ts +17 -13
  12. package/dist/agents/inherited-tool-pool.js +4 -3
  13. package/dist/agents/peer-session-drain.d.ts +3 -2
  14. package/dist/agents/peer-session-drain.js +5 -4
  15. package/dist/agents/send-message-tool.js +2 -9
  16. package/dist/agents/subagent.d.ts +10 -29
  17. package/dist/agents/subagent.js +65 -53
  18. package/dist/agents/suspend-guard.d.ts +1 -1
  19. package/dist/agents/tool-filter.d.ts +76 -65
  20. package/dist/agents/tool-filter.js +122 -92
  21. package/dist/agents/verify.d.ts +4 -7
  22. package/dist/agents/verify.js +6 -7
  23. package/dist/bench/metrics.d.ts +1 -2
  24. package/dist/brain/anthropic.js +133 -79
  25. package/dist/brain/errors.d.ts +53 -6
  26. package/dist/brain/errors.js +22 -2
  27. package/dist/brain/input-too-long.d.ts +40 -19
  28. package/dist/brain/input-too-long.js +3 -16
  29. package/dist/brain/open-responses.js +19 -10
  30. package/dist/brain/openai.js +123 -32
  31. package/dist/brain/provider-dialect.d.ts +214 -0
  32. package/dist/brain/provider-dialect.js +186 -0
  33. package/dist/brain/request-params.d.ts +27 -8
  34. package/dist/brain/request-params.js +7 -12
  35. package/dist/brain/request-route.d.ts +84 -0
  36. package/dist/brain/request-route.js +41 -0
  37. package/dist/brain/retry.d.ts +8 -0
  38. package/dist/brain/retry.js +10 -4
  39. package/dist/brain/route-adjudicator.d.ts +7 -0
  40. package/dist/brain/route-adjudicator.js +5 -1
  41. package/dist/brain/status-sink.d.ts +20 -0
  42. package/dist/brain/status-sink.js +11 -0
  43. package/dist/brain/stream-engine.d.ts +52 -1
  44. package/dist/brain/stream-engine.js +81 -24
  45. package/dist/brain/stream-shared.d.ts +57 -11
  46. package/dist/brain/stream-shared.js +28 -2
  47. package/dist/core/a2a.d.ts +1 -0
  48. package/dist/core/agent-config-dir.js +3 -2
  49. package/dist/core/allow-entry.d.ts +21 -12
  50. package/dist/core/allow-entry.js +39 -32
  51. package/dist/core/arg-summary.d.ts +17 -24
  52. package/dist/core/arg-summary.js +169 -68
  53. package/dist/core/ask-origin.d.ts +42 -24
  54. package/dist/core/ask-origin.js +18 -13
  55. package/dist/core/auto-mode-prompt-assets.d.ts +2 -2
  56. package/dist/core/auto-mode-prompt-assets.js +1 -1
  57. package/dist/core/auto-mode-prompt.d.ts +11 -1
  58. package/dist/core/auto-mode-prompt.js +13 -10
  59. package/dist/core/auto-mode-rebuild.d.ts +7 -0
  60. package/dist/core/auto-mode-rebuild.js +4 -1
  61. package/dist/core/canonical-json.d.ts +3 -0
  62. package/dist/core/canonical-json.js +2 -1
  63. package/dist/core/checkpoint-requirements.d.ts +892 -0
  64. package/dist/core/checkpoint-requirements.js +333 -0
  65. package/dist/core/checkpoint-store.d.ts +95 -66
  66. package/dist/core/checkpoint-store.js +14 -26
  67. package/dist/core/child-clamp-seats.d.ts +228 -69
  68. package/dist/core/child-clamp-seats.js +211 -17
  69. package/dist/core/classifier-unavailable-arm.d.ts +5 -1
  70. package/dist/core/classifier-unavailable-arm.js +3 -3
  71. package/dist/core/compliance.d.ts +3 -0
  72. package/dist/core/compliance.js +9 -4
  73. package/dist/core/credential-vocabulary.d.ts +140 -0
  74. package/dist/core/credential-vocabulary.js +131 -0
  75. package/dist/core/decision-receipt.js +1 -1
  76. package/dist/core/effective-path-target.d.ts +73 -16
  77. package/dist/core/effective-path-target.js +63 -33
  78. package/dist/core/engine-notice.d.ts +32 -7
  79. package/dist/core/file-history-store.js +2 -2
  80. package/dist/core/fs-write-gate-policy.d.ts +4 -0
  81. package/dist/core/fs-write-gate-policy.js +22 -5
  82. package/dist/core/gate-bounded-await.d.ts +28 -13
  83. package/dist/core/gate-bounded-await.js +41 -4
  84. package/dist/core/gate-fold.d.ts +1 -1
  85. package/dist/core/gate-fold.js +24 -25
  86. package/dist/core/gate-lanes.js +16 -15
  87. package/dist/core/gate-outcome.d.ts +20 -5
  88. package/dist/core/gate-outcome.js +5 -0
  89. package/dist/core/governance-codes.d.ts +11 -4
  90. package/dist/core/governance-codes.js +14 -3
  91. package/dist/core/hooks.d.ts +4 -0
  92. package/dist/core/locked-config.d.ts +8 -0
  93. package/dist/core/locked-config.js +14 -2
  94. package/dist/core/lsp.js +3 -5
  95. package/dist/core/mailbox-store.d.ts +3 -4
  96. package/dist/core/mcp-connection-pool.d.ts +75 -0
  97. package/dist/core/mcp-connection-pool.js +128 -0
  98. package/dist/core/mcp-failure.d.ts +2 -1
  99. package/dist/core/mcp-failure.js +13 -2
  100. package/dist/core/mcp-server-spec.d.ts +1 -1
  101. package/dist/core/mcp.d.ts +4 -0
  102. package/dist/core/mcp.js +70 -43
  103. package/dist/core/memory-engine/content-origin.js +2 -14
  104. package/dist/core/memory-engine/engine.d.ts +32 -22
  105. package/dist/core/memory-engine/engine.js +103 -55
  106. package/dist/core/memory-engine/file-backend.d.ts +13 -30
  107. package/dist/core/memory-engine/file-backend.js +37 -252
  108. package/dist/core/memory-engine/index.d.ts +1 -1
  109. package/dist/core/memory-engine/index.js +1 -1
  110. package/dist/core/memory-engine/layout.d.ts +139 -9
  111. package/dist/core/memory-engine/layout.js +350 -7
  112. package/dist/core/memory-engine/scope-contract.d.ts +10 -0
  113. package/dist/core/memory-engine/scope-contract.js +8 -0
  114. package/dist/core/memory-engine/session-incarnation.d.ts +14 -4
  115. package/dist/core/memory-engine/session-incarnation.js +5 -2
  116. package/dist/core/memory-engine/types.d.ts +10 -1
  117. package/dist/core/memory.js +5 -17
  118. package/dist/core/model-seat.d.ts +4 -4
  119. package/dist/core/model-seat.js +1 -1
  120. package/dist/core/mounted-mcp.d.ts +57 -0
  121. package/dist/core/mounted-mcp.js +19 -0
  122. package/dist/core/namespaced-rule-name.d.ts +29 -0
  123. package/dist/core/namespaced-rule-name.js +52 -0
  124. package/dist/core/park-selfcheck.d.ts +1 -1
  125. package/dist/core/park-selfcheck.js +16 -6
  126. package/dist/core/path-guard-rows.d.ts +99 -0
  127. package/dist/core/path-guard-rows.js +45 -0
  128. package/dist/core/path-identity.d.ts +22 -16
  129. package/dist/core/path-identity.js +10 -10
  130. package/dist/core/path-rule-binding.d.ts +2 -1
  131. package/dist/core/path-rule-binding.js +3 -1
  132. package/dist/core/pause-registry.d.ts +16 -2
  133. package/dist/core/pause-registry.js +6 -0
  134. package/dist/core/permission-rule-consent.js +0 -7
  135. package/dist/core/permission-rule-model.d.ts +68 -24
  136. package/dist/core/permission-rule-model.js +105 -24
  137. package/dist/core/permission-rule-store.js +2 -2
  138. package/dist/core/permission-rules.d.ts +3 -30
  139. package/dist/core/permission-rules.js +4 -54
  140. package/dist/core/read-root-candidate.d.ts +1 -3
  141. package/dist/core/read-root-candidate.js +4 -15
  142. package/dist/core/real-approval.d.ts +4 -3
  143. package/dist/core/retired-keys.d.ts +13 -0
  144. package/dist/core/retired-keys.js +14 -0
  145. package/dist/core/roles.d.ts +2 -3
  146. package/dist/core/roles.js +15 -6
  147. package/dist/core/runner/assemble-result.d.ts +0 -7
  148. package/dist/core/runner/assemble-result.js +19 -31
  149. package/dist/core/runner/caller-roster.d.ts +78 -0
  150. package/dist/core/runner/caller-roster.js +219 -0
  151. package/dist/core/runner/checkpoint-scope.d.ts +16 -16
  152. package/dist/core/runner/checkpoint-scope.js +2 -2
  153. package/dist/core/runner/child-tree.js +2 -2
  154. package/dist/core/runner/contracts.d.ts +68 -35
  155. package/dist/core/runner/decide-continuation.d.ts +51 -3
  156. package/dist/core/runner/decide-continuation.js +76 -21
  157. package/dist/core/runner/denial-limit-arms.d.ts +17 -5
  158. package/dist/core/runner/denial-limit-arms.js +11 -9
  159. package/dist/core/runner/extra-body-dropped-seat.d.ts +8 -0
  160. package/dist/core/runner/extra-body-dropped-seat.js +22 -0
  161. package/dist/core/runner/finish-reason-seat.d.ts +9 -0
  162. package/dist/core/runner/finish-reason-seat.js +40 -0
  163. package/dist/core/runner/gate-exit.d.ts +44 -32
  164. package/dist/core/runner/gate-exit.js +112 -25
  165. package/dist/core/runner/inherited-ask-grants.js +6 -4
  166. package/dist/core/runner/mcp-redial.d.ts +3 -3
  167. package/dist/core/runner/mcp-redial.js +3 -4
  168. package/dist/core/runner/park-commit.d.ts +1 -1
  169. package/dist/core/runner/permission-rule-lanes.d.ts +1 -2
  170. package/dist/core/runner/permission-rule-lanes.js +5 -4
  171. package/dist/core/runner/prepare-artifact.d.ts +4 -0
  172. package/dist/core/runner/prepare-artifact.js +2 -1
  173. package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
  174. package/dist/core/runner/prepare-ask-lane.js +12 -6
  175. package/dist/core/runner/prepare-boundary-parks.d.ts +5 -8
  176. package/dist/core/runner/prepare-boundary-parks.js +4 -7
  177. package/dist/core/runner/prepare-caps-and-workflow.d.ts +11 -15
  178. package/dist/core/runner/prepare-caps-and-workflow.js +19 -27
  179. package/dist/core/runner/prepare-config-doors.d.ts +9 -9
  180. package/dist/core/runner/prepare-config-doors.js +10 -5
  181. package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
  182. package/dist/core/runner/prepare-defer-classify.js +5 -3
  183. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
  184. package/dist/core/runner/prepare-delegation-surface.js +3 -3
  185. package/dist/core/runner/prepare-gate-stations.d.ts +4 -4
  186. package/dist/core/runner/prepare-gate-stations.js +9 -7
  187. package/dist/core/runner/prepare-hands-readface.d.ts +7 -7
  188. package/dist/core/runner/prepare-hands-readface.js +7 -7
  189. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -6
  190. package/dist/core/runner/prepare-inherited-gate.js +14 -15
  191. package/dist/core/runner/prepare-listings.d.ts +7 -24
  192. package/dist/core/runner/prepare-listings.js +4 -5
  193. package/dist/core/runner/prepare-memory-engine-session.d.ts +2 -0
  194. package/dist/core/runner/prepare-memory-engine-session.js +4 -3
  195. package/dist/core/runner/prepare-memory.d.ts +6 -0
  196. package/dist/core/runner/prepare-memory.js +13 -13
  197. package/dist/core/runner/prepare-offload-wrappers.d.ts +5 -0
  198. package/dist/core/runner/prepare-offload-wrappers.js +3 -3
  199. package/dist/core/runner/prepare-park-ask.d.ts +7 -8
  200. package/dist/core/runner/prepare-park-ask.js +17 -10
  201. package/dist/core/runner/prepare-policy-chain.js +24 -44
  202. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  203. package/dist/core/runner/prepare-project-context.js +3 -4
  204. package/dist/core/runner/prepare-protocol-tools.d.ts +9 -3
  205. package/dist/core/runner/prepare-protocol-tools.js +3 -4
  206. package/dist/core/runner/prepare-run-refs.d.ts +3 -0
  207. package/dist/core/runner/prepare-run-refs.js +2 -1
  208. package/dist/core/runner/prepare-safety-scan.d.ts +4 -8
  209. package/dist/core/runner/prepare-safety-scan.js +1 -2
  210. package/dist/core/runner/prepare-suspend-saga.d.ts +7 -1
  211. package/dist/core/runner/prepare-suspend-saga.js +28 -12
  212. package/dist/core/runner/prepare-task.d.ts +1 -1
  213. package/dist/core/runner/prepare-task.js +67 -56
  214. package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -10
  215. package/dist/core/runner/prepare-wiring-manifest.js +5 -8
  216. package/dist/core/runner/resume-apply.js +7 -14
  217. package/dist/core/runner/resume-checkpoint-screen.js +11 -4
  218. package/dist/core/runner/resume-internals-and-config.js +2 -2
  219. package/dist/core/runner/resume-policy-outcome.js +5 -32
  220. package/dist/core/runner/resume-review-outcome.js +4 -1
  221. package/dist/core/runner/retired-runner-deps.d.ts +6 -0
  222. package/dist/core/runner/retired-runner-deps.js +10 -1
  223. package/dist/core/runner/run-brain-sinks.d.ts +4 -0
  224. package/dist/core/runner/run-brain-sinks.js +5 -2
  225. package/dist/core/runner/run-harness-handlers.js +27 -34
  226. package/dist/core/runner/run-leg.d.ts +0 -2
  227. package/dist/core/runner/run-leg.js +32 -33
  228. package/dist/core/runner/run-terminal-adoption.js +4 -3
  229. package/dist/core/runner/runtask.js +8 -8
  230. package/dist/core/runner/session-rule-policy.js +5 -5
  231. package/dist/core/runner/stream-settle-backstop.js +7 -4
  232. package/dist/core/runner/sub-agent-deny-lists.d.ts +51 -12
  233. package/dist/core/runner/sub-agent-deny-lists.js +111 -80
  234. package/dist/core/runner/terminal-account.d.ts +48 -0
  235. package/dist/core/runner/terminal-account.js +186 -0
  236. package/dist/core/runner/tool-end-body.d.ts +54 -17
  237. package/dist/core/runner/tool-end-body.js +27 -2
  238. package/dist/core/runner/tool-face-overlay.d.ts +3 -1
  239. package/dist/core/runner/tool-face-overlay.js +6 -4
  240. package/dist/core/runner/tool-occurrence.d.ts +106 -0
  241. package/dist/core/runner/tool-occurrence.js +123 -0
  242. package/dist/core/runner/tool-progress-lane.d.ts +16 -8
  243. package/dist/core/runner/tool-progress-lane.js +25 -34
  244. package/dist/core/runner/turn-attachments.d.ts +1 -1
  245. package/dist/core/runner/write-protection-station.d.ts +21 -9
  246. package/dist/core/runner/write-protection-station.js +20 -17
  247. package/dist/core/runner-deps.d.ts +12 -22
  248. package/dist/core/safety-axis-vocab.d.ts +1 -1
  249. package/dist/core/safety-merge-corpus.d.ts +1 -1
  250. package/dist/core/secret-env.d.ts +16 -5
  251. package/dist/core/secret-env.js +23 -63
  252. package/dist/core/sensitive-path-policy.d.ts +19 -18
  253. package/dist/core/sensitive-path-policy.js +73 -49
  254. package/dist/core/session-policy-store.d.ts +6 -3
  255. package/dist/core/session-policy-store.js +28 -10
  256. package/dist/core/session-reconcile.d.ts +3 -0
  257. package/dist/core/session-reconcile.js +5 -4
  258. package/dist/core/session-store.d.ts +5 -0
  259. package/dist/core/session-store.js +12 -0
  260. package/dist/core/session.d.ts +13 -0
  261. package/dist/core/shared-memory/normalize.d.ts +17 -4
  262. package/dist/core/shared-memory/normalize.js +7 -7
  263. package/dist/core/shell-lexer.js +5 -4
  264. package/dist/core/shell-wrapper-table.d.ts +2 -2
  265. package/dist/core/shell-wrapper-table.js +1 -1
  266. package/dist/core/skill-grant.js +1 -1
  267. package/dist/core/store-contracts/checkpoint-store-contract.js +13 -0
  268. package/dist/core/store-contracts/mailbox-store-contract.d.ts +7 -14
  269. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +27 -0
  270. package/dist/core/store-contracts/session-store-incarnation-contract.js +92 -0
  271. package/dist/core/stub-env.d.ts +2 -0
  272. package/dist/core/stub-env.js +1 -0
  273. package/dist/core/synthetic-read-verdict.js +2 -2
  274. package/dist/core/task-event.d.ts +59 -5
  275. package/dist/core/task-result.d.ts +1 -1
  276. package/dist/core/task-spec.d.ts +42 -9
  277. package/dist/core/terminal-cause.d.ts +94 -11
  278. package/dist/core/tighten-task-spec.d.ts +5 -4
  279. package/dist/core/tighten-task-spec.js +20 -25
  280. package/dist/core/tool-catalog-entries.d.ts +5 -7
  281. package/dist/core/tool-catalog-entries.js +14 -15
  282. package/dist/core/tool-face.d.ts +47 -0
  283. package/dist/core/tool-face.js +5 -0
  284. package/dist/core/tool-model-gate.d.ts +5 -5
  285. package/dist/core/tool-name-aliases.d.ts +35 -10
  286. package/dist/core/tool-name-aliases.js +15 -0
  287. package/dist/core/tool-name-list.d.ts +30 -0
  288. package/dist/core/tool-name-list.js +55 -0
  289. package/dist/core/tool-name-readings.d.ts +1 -9
  290. package/dist/core/tool-name-readings.js +1 -3
  291. package/dist/core/tool-policy.d.ts +195 -85
  292. package/dist/core/tool-policy.js +165 -381
  293. package/dist/core/tool-registry.d.ts +44 -1
  294. package/dist/core/tool-registry.js +26 -0
  295. package/dist/core/tool-roster.d.ts +19 -1
  296. package/dist/core/tool-roster.js +21 -2
  297. package/dist/core/tool-spec-members.d.ts +45 -0
  298. package/dist/core/tool-spec-members.js +169 -0
  299. package/dist/core/tool-spec.d.ts +46 -8
  300. package/dist/core/tools.d.ts +6 -2
  301. package/dist/core/tools.js +8 -4
  302. package/dist/core/untrusted-egress.js +29 -26
  303. package/dist/core/untrusted-text.d.ts +13 -10
  304. package/dist/core/untrusted-text.js +6 -44
  305. package/dist/core/workflow-journal-store.d.ts +4 -1
  306. package/dist/core/write-protect.d.ts +65 -29
  307. package/dist/core/write-protect.js +235 -177
  308. package/dist/engine/compaction/compaction.d.ts +2 -2
  309. package/dist/engine/execution-env/node-execution-env.d.ts +5 -23
  310. package/dist/engine/execution-env/node-execution-env.js +16 -38
  311. package/dist/engine/execution-env/symlink-chain.d.ts +23 -0
  312. package/dist/engine/execution-env/symlink-chain.js +32 -0
  313. package/dist/engine/harness/types.d.ts +7 -0
  314. package/dist/engine/llm/tool-turns.d.ts +3 -0
  315. package/dist/engine/llm/tool-turns.js +1 -0
  316. package/dist/engine/llm/types.d.ts +59 -23
  317. package/dist/engine/loop/agent-loop.js +16 -2
  318. package/dist/engine/session/memory-repo.js +11 -3
  319. package/dist/engine/session/repo-utils.d.ts +8 -0
  320. package/dist/engine/session/repo-utils.js +13 -0
  321. package/dist/index.d.ts +20 -23
  322. package/dist/index.js +17 -20
  323. package/dist/internal/harness.d.ts +1 -1
  324. package/dist/internal/harness.js +1 -1
  325. package/dist/internal/llm.d.ts +2 -2
  326. package/dist/internal/llm.js +1 -1
  327. package/dist/orchestration/governance-baseline-validity.js +1 -1
  328. package/dist/orchestration/run-spec.js +3 -3
  329. package/dist/orchestration/run-workflow-tool.d.ts +16 -35
  330. package/dist/orchestration/run-workflow-tool.js +38 -78
  331. package/dist/orchestration/workflow-governance.d.ts +21 -25
  332. package/dist/orchestration/workflow-governance.js +38 -78
  333. package/dist/orchestration/workflow-primitives.d.ts +7 -22
  334. package/dist/orchestration/workflow-primitives.js +5 -12
  335. package/dist/orchestration/workflow-types.d.ts +1 -1
  336. package/dist/orchestration/workflow.d.ts +61 -11
  337. package/dist/orchestration/workflow.js +191 -119
  338. package/dist/prompt-assembly/types.d.ts +1 -1
  339. package/dist/scenarios/full-body.d.ts +1 -1
  340. package/dist/shell/lex.d.ts +0 -21
  341. package/dist/shell/lex.js +278 -70
  342. package/dist/shell/shell-words.d.ts +35 -0
  343. package/dist/shell/shell-words.js +65 -0
  344. package/dist/stores/file/checkpoint-store.d.ts +5 -5
  345. package/dist/stores/file/checkpoint-store.js +5 -2
  346. package/dist/stores/file/index.d.ts +0 -8
  347. package/dist/stores/file/index.js +0 -12
  348. package/dist/stores/file/session-store.d.ts +3 -0
  349. package/dist/stores/file/session-store.js +44 -19
  350. package/dist/tools/artifact/artifact-tool.js +2 -0
  351. package/dist/tools/fs/bash-program-position.d.ts +17 -3
  352. package/dist/tools/fs/bash-program-position.js +25 -10
  353. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  354. package/dist/tools/fs/bash-readonly-classifier.js +8 -7
  355. package/dist/tools/fs/containment-face.d.ts +4 -0
  356. package/dist/tools/fs/containment-face.js +3 -0
  357. package/dist/tools/fs/fs-shared.d.ts +10 -0
  358. package/dist/tools/fs/fs-shared.js +18 -0
  359. package/dist/tools/fs/fs-write.js +4 -2
  360. package/dist/tools/fs/index.d.ts +3 -2
  361. package/dist/tools/fs/index.js +2 -2
  362. package/dist/tools/fs/program-read-model.d.ts +99 -23
  363. package/dist/tools/fs/program-read-model.js +129 -55
  364. package/dist/tools/fs/program-read-model.rows.d.ts +0 -14
  365. package/dist/tools/fs/program-read-model.rows.js +18 -10
  366. package/dist/tools/fs/read-deny-home.d.ts +35 -30
  367. package/dist/tools/fs/read-deny-home.js +45 -38
  368. package/dist/tools/fs/read-deny.d.ts +35 -27
  369. package/dist/tools/fs/read-deny.js +126 -92
  370. package/dist/tools/fs/removal-targets.d.ts +56 -23
  371. package/dist/tools/fs/removal-targets.js +74 -87
  372. package/dist/tools/fs/safety.d.ts +29 -22
  373. package/dist/tools/fs/safety.js +17 -11
  374. package/dist/tools/fs/search.js +10 -8
  375. package/dist/tools/fs/shell-read-argv.d.ts +4 -7
  376. package/dist/tools/fs/shell-read-argv.js +6 -8
  377. package/dist/tools/fs/shell-read-bindings.d.ts +203 -46
  378. package/dist/tools/fs/shell-read-bindings.js +488 -144
  379. package/dist/tools/fs/shell-read-builtins.d.ts +35 -0
  380. package/dist/tools/fs/shell-read-builtins.js +43 -0
  381. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  382. package/dist/tools/fs/shell-read-check.js +51 -42
  383. package/dist/tools/fs/shell-read-establish.js +9 -10
  384. package/dist/tools/fs/shell-read-functions.d.ts +22 -0
  385. package/dist/tools/fs/shell-read-functions.js +29 -0
  386. package/dist/tools/fs/shell-read-judge.d.ts +48 -18
  387. package/dist/tools/fs/shell-read-judge.js +33 -4
  388. package/dist/tools/fs/shell-read-plan.d.ts +13 -20
  389. package/dist/tools/fs/shell-read-plan.js +200 -194
  390. package/dist/tools/fs/shell-read-start.d.ts +80 -0
  391. package/dist/tools/fs/shell-read-start.js +99 -0
  392. package/dist/tools/fs/shell-read-word.d.ts +35 -6
  393. package/dist/tools/fs/shell-read-word.js +89 -9
  394. package/dist/tools/fs/shell-read-workdir.d.ts +156 -0
  395. package/dist/tools/fs/shell-read-workdir.js +281 -0
  396. package/dist/tools/fs/shell-read-write-targets.d.ts +5 -2
  397. package/dist/tools/fs/shell-read-write-targets.js +14 -5
  398. package/dist/tools/fs/shell-reentry-text.d.ts +26 -26
  399. package/dist/tools/fs/shell-reentry-text.js +31 -34
  400. package/dist/tools/task-list.d.ts +1 -2
  401. package/dist/tools/web.js +1 -1
  402. package/package.json +4 -1
  403. package/test/export-surface.snapshot.json +107 -143
  404. package/wire-changes/core@7.48.0.json +252 -0
  405. package/wire-changes/core@7.49.0.json +4765 -0
  406. package/dist/agents/teacher.d.ts +0 -159
  407. package/dist/agents/teacher.js +0 -577
  408. package/dist/agents/team.d.ts +0 -226
  409. package/dist/agents/team.js +0 -257
  410. package/dist/core/strategy-store.d.ts +0 -238
  411. package/dist/core/strategy-store.js +0 -253
  412. package/dist/orchestration/builtin-workflows.d.ts +0 -133
  413. package/dist/orchestration/builtin-workflows.js +0 -159
  414. package/dist/scenarios/env.d.ts +0 -40
  415. package/dist/scenarios/env.js +0 -39
  416. package/dist/scenarios/scenario-registry.d.ts +0 -110
  417. package/dist/scenarios/scenario-registry.js +0 -140
  418. package/dist/scenarios/teacher-quickstart.d.ts +0 -40
  419. package/dist/scenarios/teacher-quickstart.js +0 -12
  420. package/dist/stores/cc/lockfile.d.ts +0 -10
  421. package/dist/stores/cc/lockfile.js +0 -98
  422. package/dist/stores/cc/mailbox-store.d.ts +0 -20
  423. package/dist/stores/cc/mailbox-store.js +0 -255
  424. package/dist/stores/cc/roster-adapter.d.ts +0 -12
  425. package/dist/stores/cc/roster-adapter.js +0 -117
  426. package/dist/stores/cc/sidecar-transcript.d.ts +0 -97
  427. package/dist/stores/cc/sidecar-transcript.js +0 -264
  428. package/dist/stores/cc/task-list-store.d.ts +0 -2
  429. package/dist/stores/cc/task-list-store.js +0 -125
  430. package/dist/stores/file/strategy-store.d.ts +0 -97
  431. package/dist/stores/file/strategy-store.js +0 -341
@@ -1,8 +1,8 @@
1
1
  import { planShellReads } from "./shell-read-plan.js";
2
2
  import { literalTextOf } from "../../shell/lex.js";
3
3
  import { createShellReadEstablisher, establishShellReadsLexically } from "./shell-read-establish.js";
4
- import { isAbsolutePathForm, isWinFormPath, normalizeAbsPathLexically } from "./safety.js";
5
- import { launchedCommands, launchedReadings, reentryText, spellLaunched } from "./shell-reentry-text.js";
4
+ import { isAbsoluteForFamily, isWinFormPath, normalizeAbsPathLexically } from "./safety.js";
5
+ import { launchedCommands, launchedReadings, reentryText, scriptFileAt, spellLaunched } from "./shell-reentry-text.js";
6
6
  import { readProgramPosition } from "./bash-program-position.js";
7
7
  import { bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
8
8
  import { programArgs } from "./shell-read-write-targets.js";
@@ -25,16 +25,16 @@ function driveOf(path, key) {
25
25
  return isWinFormPath(path) && key.length >= 2 && key[1] === ":" ? key.slice(0, 2) : "";
26
26
  }
27
27
  export function dangerousRemovalOf(path, seat) {
28
- if (!isAbsolutePathForm(path))
28
+ if (!isAbsoluteForFamily("own", path))
29
29
  return undefined;
30
30
  const k = removalKey(path);
31
31
  const rest = k.slice(driveOf(path, k).length);
32
32
  if (rest === "/" || /^\/\/[^/]+\/[^/]+$/.test(k))
33
33
  return "filesystem_root";
34
- if (seat.homes.some((h) => isAbsolutePathForm(h) && removalKey(h) === k))
34
+ if (seat.homes.some((h) => isAbsoluteForFamily("own", h) && removalKey(h) === k))
35
35
  return "home";
36
36
  const prefix = k.endsWith("/") ? k : `${k}/`;
37
- if (seat.workspace.some((w) => isAbsolutePathForm(w) && (removalKey(w) === k || removalKey(w).startsWith(prefix))))
37
+ if (seat.workspace.some((w) => isAbsoluteForFamily("own", w) && (removalKey(w) === k || removalKey(w).startsWith(prefix))))
38
38
  return "workspace";
39
39
  if (/^\/[^/]+$/.test(rest))
40
40
  return "top_level";
@@ -56,53 +56,17 @@ function removalKindOf(read, seat) {
56
56
  }
57
57
  return read.established === "unestablishable" && tildeNamesAHome(read.candidate.spelled) ? "home" : undefined;
58
58
  }
59
- const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
60
- const NAME_BINDERS = new Set(["unset", "local", "declare", "typeset", "readonly", "export", "read"]);
61
- function bindingsOf(plan) {
62
- const out = new Map();
63
- const bind = (name, value) => {
64
- out.set(name, [...(out.get(name) ?? []), value]);
65
- };
66
- for (const segment of plan.segments) {
67
- for (const w of segment.argv)
68
- if (w.assignment !== undefined)
69
- bind(w.assignment.name, w.assignment.settlesWholeName ? literalTextOf(w.assignment.value.pieces) : undefined);
70
- const [head, name] = segment.argv;
71
- if ((head?.folded === "for" || head?.folded === "select") && name !== undefined && NAME.test(name.folded)) {
72
- const at = segment.argv.findIndex((w, i) => i > 1 && w.folded === "in");
73
- const words = at < 0 ? [undefined] : segment.argv.slice(at + 1).map((w) => literalTextOf(w.pieces));
74
- for (const v of words.length > 0 ? words : [undefined])
75
- bind(name.folded, v);
76
- }
77
- if (head !== undefined && NAME_BINDERS.has(head.folded))
78
- for (const w of segment.argv.slice(1))
79
- if (w.assignment === undefined && NAME.test(w.folded))
80
- bind(w.folded, undefined);
81
- }
82
- return out;
83
- }
84
- const BOUND_SPELLINGS_MAX = 64;
85
- function boundSpellingsOf(word, bindings) {
86
- const named = word.pieces.filter((q) => q.literal === undefined);
87
- if (named.length === 0 || !named.some((q) => q.expansion?.kind === "parameter" && q.expansion.name !== undefined && bindings.has(q.expansion.name)))
88
- return undefined;
89
- let spellings = [""];
90
- for (const q of word.pieces) {
91
- if (q.literal !== undefined) {
92
- spellings = spellings.map((sp) => sp + q.literal);
93
- continue;
94
- }
95
- const name = q.expansion?.kind === "parameter" && (q.expansion.body === undefined || q.expansion.body === q.expansion.name) ? q.expansion.name : undefined;
96
- const values = name !== undefined ? bindings.get(name) : undefined;
97
- if (name === undefined || values === undefined)
98
- return { unreadable: q.expansion?.name ?? word.raw };
99
- if (values.some((v) => v === undefined || (q.quotedBy === undefined && /\s/.test(v))))
100
- return { unreadable: name };
101
- spellings = spellings.flatMap((sp) => values.map((v) => sp + v));
102
- if (spellings.length > BOUND_SPELLINGS_MAX)
103
- return { unreadable: name };
104
- }
105
- return { spellings };
59
+ export const KNOWN_SAFE_ROOTS = Object.freeze(["TMPDIR", "HOME"]);
60
+ function belowKnownSafeRoot(word, safe, segment) {
61
+ const [head, ...rest] = word.pieces;
62
+ const e = head?.expansion;
63
+ const name = e?.kind === "parameter" && (e.body === undefined || e.body === e.name) ? e.name : undefined;
64
+ if (name === undefined || !safe.has(name) || segment.rebound === "all" || segment.rebound.has(name))
65
+ return false;
66
+ if (rest.some((p) => p.literal === undefined))
67
+ return false;
68
+ const below = rest.map((p) => p.literal).join("");
69
+ return /^\/[^*?[\]]+$/.test(below) && !below.split("/").includes("..");
106
70
  }
107
71
  function recursiveForce(row, args) {
108
72
  return implementationsOf(row).some((view) => {
@@ -110,9 +74,11 @@ function recursiveForce(row, args) {
110
74
  return selected.some((o) => o.selects === "traversal") && selected.some((o) => o.mode === "forced");
111
75
  });
112
76
  }
113
- export function removalFindingsOf(established, seat) {
77
+ export function removalFindingsOf(established, seat, safeRoots = KNOWN_SAFE_ROOTS, enclosingRelocated = false) {
78
+ if (unreadPlan(established.plan))
79
+ return [{ guard: "unverifiable_shell_text", program: "bash", text: established.plan.unreadable, source: "unread" }];
114
80
  const out = [];
115
- const bindings = bindingsOf(established.plan);
81
+ const safe = new Set(safeRoots);
116
82
  for (const segment of established.plan.segments) {
117
83
  const program = segment.verb?.basename;
118
84
  const row = program !== undefined && REMOVAL_COMMAND_SET.has(program) ? programRow(program) : undefined;
@@ -121,45 +87,41 @@ export function removalFindingsOf(established, seat) {
121
87
  const spelled = args.map((w) => w.spelled);
122
88
  const removed = new Set(writeReadings(row, spelled).flatMap((r) => r.written.map((w) => w.at)));
123
89
  const options = new Set(args.filter((_, at) => !removed.has(at)).map((w) => w.raw));
90
+ const forced = recursiveForce(row, spelled);
91
+ if (forced && segment.reentry === "argv")
92
+ out.push({ guard: "unverifiable_delete", program, operand: segment.text.trim(), source: "stream" });
93
+ const relocated = enclosingRelocated || readProgramPosition(segment.argv).launcherMovesBase;
124
94
  for (const read of established.reads.get(segment) ?? []) {
125
95
  if (read.candidate.face !== "write" || options.has(read.candidate.raw))
126
96
  continue;
127
97
  const kind = removalKindOf(read, seat);
128
98
  if (kind !== undefined) {
129
- out.push({ guard: "dangerous_delete", kind, program, operand: read.candidate.raw, target: read.target ?? read.candidate.floorSpelled ?? read.candidate.spelled });
99
+ const untracked = read.candidate.bases === "unmodelled" ? established.plan.segments.find((s) => s.bases === "unmodelled")?.text.trim() : undefined;
100
+ out.push({ guard: "dangerous_delete", kind, program, operand: read.candidate.raw, target: read.target ?? read.candidate.floorSpelled ?? read.candidate.spelled, ...(untracked !== undefined ? { untrackedFrom: untracked } : {}) });
130
101
  continue;
131
102
  }
132
- if (read.established !== "unestablishable")
103
+ if (!forced || read.established !== "unestablishable" || (read.candidate.expands !== "unresolvable" && !relocated))
133
104
  continue;
134
105
  const word = segment.argv.find((w) => w.raw === read.candidate.raw);
135
- const bound = word === undefined ? undefined : boundSpellingsOf(word, bindings);
136
- if (bound === undefined)
106
+ if (word !== undefined && belowKnownSafeRoot(word, safe, segment))
137
107
  continue;
138
- if ("unreadable" in bound) {
139
- if (recursiveForce(row, spelled))
140
- out.push({ guard: "unverifiable_delete", program, operand: read.candidate.raw, name: bound.unreadable });
141
- continue;
142
- }
143
- const bases = segment.bases === "unmodelled" ? (read.candidate.floorBases ?? []) : segment.bases;
144
- for (const spelled of bound.spellings) {
145
- const views = isAbsolutePathForm(spelled) ? [spelled] : bases.map((base) => `${base}/${spelled}`);
146
- for (const view of views) {
147
- const hit = dangerousRemovalOf(view, seat);
148
- if (hit === undefined)
149
- continue;
150
- out.push({ guard: "dangerous_delete", kind: hit, program, operand: read.candidate.raw, target: normalizeAbsPathLexically(view) });
151
- break;
152
- }
153
- }
108
+ out.push({ guard: "unverifiable_delete", program, operand: read.candidate.raw, source: read.candidate.expands === "unresolvable" ? "rewritten" : "relocated" });
154
109
  }
155
110
  }
156
111
  const script = runtimeScriptOf(segment);
157
112
  if (script !== undefined && program !== undefined)
158
- out.push({ guard: "dangerous_delete", kind: "runtime_script", program, operand: script, target: script });
113
+ out.push({ guard: "unverifiable_shell_text", program, text: script, source: "produced" });
114
+ if (programUnread(segment))
115
+ out.push({ guard: "unverifiable_shell_text", program: segment.launcherChain[0] ?? segment.text.trim(), text: segment.text.trim(), source: "unread" });
116
+ const handedRelocated = enclosingRelocated || readProgramPosition(segment.argv).launcherMovesBase;
159
117
  for (const nested of segment.nested) {
118
+ if (unreadPlan(nested)) {
119
+ out.push({ guard: "unverifiable_shell_text", program: program ?? segment.text.trim(), text: segment.text.trim(), source: "unread" });
120
+ continue;
121
+ }
160
122
  const sub = established.nested.get(nested);
161
123
  if (sub !== undefined)
162
- out.push(...removalFindingsOf(sub, seat));
124
+ out.push(...removalFindingsOf(sub, seat, safeRoots, handedRelocated));
163
125
  }
164
126
  }
165
127
  return out;
@@ -175,41 +137,66 @@ function runtimeScriptOf(segment) {
175
137
  return literal !== undefined ? literal.slice(from) : `${MARK}${w.raw.slice(from)}`;
176
138
  };
177
139
  const pos = readProgramPosition(segment.argv);
140
+ if (pos.program !== undefined && pos.refusal === undefined) {
141
+ const argv = segment.argv.slice(pos.program.index + 1);
142
+ const file = scriptFileAt(program.basename, argv.map((w) => w.folded));
143
+ const source = file === undefined ? undefined : argv[file];
144
+ if (source !== undefined && source.pieces.length === 1 && source.pieces[0].expansion?.kind === "process")
145
+ return source.raw;
146
+ if (pos.argvFromStdin)
147
+ return segment.text.trim();
148
+ }
178
149
  const named = pos.program !== undefined && pos.refusal === undefined ? [{ shell: program.basename, words: segment.argv.slice(pos.program.index + 1).map((_, k) => spell(pos.program.index + 1 + k)) }] : [];
179
150
  const launched = launchedCommands(segment.argv, pos).flatMap((c) => {
180
151
  const shell = launchedReadings(segment.argv, c).shell;
181
- return shell === undefined ? [] : spellLaunched(segment.argv, c.args, { word: spell }).map((words) => ({ shell, words }));
152
+ return shell === undefined ? [] : [{ shell, words: spellLaunched(segment.argv, c.args, { word: spell }) }];
182
153
  });
183
154
  const texts = [...named, ...launched].flatMap(({ shell, words }) => [reentryText(shell, words), ...(segment.stdin ?? []).map((s) => reentryText(shell, words, s.literal ?? `${MARK}${s.from}`))]);
184
155
  const text = texts.find((t) => t?.includes(MARK) === true);
185
156
  return text === undefined ? undefined : text.split(MARK).join("");
186
157
  }
158
+ const unreadPlan = (plan) => plan.unreadable !== undefined && plan.segments.length === 0;
159
+ const programUnread = (s) => s.declines.includes("unreadable_program_position");
187
160
  export function planRunsRemoval(plan) {
188
- return plan.segments.some((s) => (s.verb !== undefined && REMOVAL_COMMAND_SET.has(s.verb.basename)) || runtimeScriptOf(s) !== undefined || s.nested.some(planRunsRemoval));
161
+ return unreadPlan(plan) || plan.segments.some((s) => (s.verb !== undefined && REMOVAL_COMMAND_SET.has(s.verb.basename)) || runtimeScriptOf(s) !== undefined || programUnread(s) || s.nested.some(planRunsRemoval));
189
162
  }
190
163
  const DANGEROUS_REMOVAL_SENTENCE = {
191
164
  filesystem_root: (p, t) => `\`${p}\` would remove the filesystem root (${t})`,
192
165
  home: (p, t) => `\`${p}\` would remove a home directory (${t})`,
193
166
  workspace: (p, t) => `\`${p}\` would remove a workspace directory — the task root, the working directory or an additional working directory — or one of its ancestors (${t})`,
194
167
  top_level: (p, t) => `\`${p}\` would remove a top-level system directory (${t})`,
195
- runtime_script: (p, t) => `\`${p}\` runs a script built only when the command runs (${t}), and it may remove the filesystem root, a home directory or the task's directories before anyone can read it`,
168
+ };
169
+ const UNVERIFIABLE_REMOVAL_SENTENCE = {
170
+ rewritten: (p, o) => `\`${p}\` recursive+force target ${o} is rewritten by the shell from a value this command does not settle (a name with no value here or one it sets in a form the guard cannot read, a substitution, a parameter operator) — the deleted path cannot be verified from this command`,
171
+ stream: (p, o) => `\`${p}\` recursive+force takes its targets from the stream it is fed (${o}) — the deleted paths cannot be verified from this command`,
172
+ relocated: (p, o) => `\`${p}\` recursive+force target ${o} is relative to a directory a launcher option moved the program into, which this command does not settle — the deleted path cannot be verified from this command`,
173
+ };
174
+ const UNREADABLE_TEXT_SENTENCE = {
175
+ produced: (p, t) => `\`${p}\` runs shell text that is built only when the command runs (${t}) — the text this shell will run cannot be read by the engine, so whether it removes anything is unknown`,
176
+ unread: (p, t) => `\`${p}\` runs shell text this guard cannot read (${t}) — the text this shell will run cannot be read by the engine, so whether it removes anything is unknown`,
196
177
  };
197
178
  function verdictOf(findings) {
198
- for (const f of findings)
199
- if (f.guard === "dangerous_delete")
200
- return { guard: f.guard, text: DANGEROUS_REMOVAL_SENTENCE[f.kind](f.program, inlineUntrusted(f.target, 200)) };
179
+ for (const f of findings) {
180
+ if (f.guard !== "dangerous_delete")
181
+ continue;
182
+ const untracked = f.untrackedFrom === undefined ? "" : ` — the working directory is not tracked from \`${inlineUntrusted(f.untrackedFrom, 120)}\` on (a directory change before it cannot be followed), so this target was judged where the command stood when tracking stopped`;
183
+ return { guard: f.guard, text: `${DANGEROUS_REMOVAL_SENTENCE[f.kind](f.program, inlineUntrusted(f.target, 200))}${untracked}` };
184
+ }
201
185
  for (const f of findings)
202
186
  if (f.guard === "unverifiable_delete")
203
- return { guard: f.guard, text: `\`${f.program}\` recursive+force target ${inlineUntrusted(f.operand, 200)} takes ${f.name}'s value, which this command sets in a form the guard cannot read (an expansion, an unset or bare declaration, a \`read\`, a loop over unread words) — the deleted path cannot be verified from this command` };
187
+ return { guard: f.guard, text: UNVERIFIABLE_REMOVAL_SENTENCE[f.source](f.program, inlineUntrusted(f.operand, 200)) };
188
+ for (const f of findings)
189
+ if (f.guard === "unverifiable_shell_text")
190
+ return { guard: f.guard, text: UNREADABLE_TEXT_SENTENCE[f.source](f.program, inlineUntrusted(f.text, 200)) };
204
191
  return undefined;
205
192
  }
206
193
  const REMOVAL_RESOLVE_TIMEOUT_MS = 10_000;
207
- export function removalReadingOf(command, req, seat) {
194
+ export function removalReadingOf(command, req, seat, safeRoots = KNOWN_SAFE_ROOTS) {
208
195
  const homeDir = seat?.env?.homeDir;
209
196
  const cwd = req.cwd ?? req.root ?? "";
210
197
  const roots = req.root !== undefined ? [req.root] : cwd !== "" ? [cwd] : [];
211
198
  const boundary = { roots, ...(cwd !== "" ? { cwd } : {}), ...(homeDir !== undefined ? { homeDir } : {}) };
212
- const plan = planShellReads(command, { readers: new Set(bashClassifyDefaultAllow()), cwd, roots, ...(homeDir !== undefined ? { homeDir } : {}) });
199
+ const plan = planShellReads(command, { readers: new Set(bashClassifyDefaultAllow()), cwd, roots, cdPath: seat?.env?.cdPath ?? "unknown", ...(homeDir !== undefined ? { homeDir } : {}) });
213
200
  if (!planRunsRemoval(plan))
214
201
  return undefined;
215
202
  return {
@@ -220,11 +207,11 @@ export function removalReadingOf(command, req, seat) {
220
207
  homes: [...(homeDir !== undefined ? [homeDir] : []), ...(resolved !== undefined && resolved.ok ? [resolved.value] : [])],
221
208
  workspace: [...(req.root !== undefined ? [req.root] : []), ...(req.cwd !== undefined ? [req.cwd] : []), ...(seat?.additionalRoots?.() ?? [])],
222
209
  };
223
- const lexical = removalFindingsOf(establishShellReadsLexically(plan, boundary), removalSeat);
210
+ const lexical = removalFindingsOf(establishShellReadsLexically(plan, boundary), removalSeat, safeRoots);
224
211
  if (lexical.some((f) => f.guard === "dangerous_delete") || env === undefined || roots.length === 0)
225
212
  return verdictOf(lexical);
226
213
  const established = await resolvedOrUndefined(createShellReadEstablisher(env, roots, { rootsCanonical: true }).establish(plan, boundary, signal), signal);
227
- return verdictOf(established === undefined ? lexical : [...removalFindingsOf(established, removalSeat), ...lexical]);
214
+ return verdictOf(established === undefined ? lexical : [...removalFindingsOf(established, removalSeat, safeRoots), ...lexical]);
228
215
  },
229
216
  };
230
217
  }
@@ -10,7 +10,8 @@ import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-regis
10
10
  * hole) or fail-closed (deny a legitimate write). Single source.
11
11
  *
12
12
  * The `path` arm is deliberately BROADER than what Read/Edit/Write accept: their schema requires
13
- * `file_path` and rejects the retired `path` alias outright (fs-shared.ts FILE_PATH_PARAMS), but this reader is
13
+ * `file_path` (fs-shared.ts FILE_PATH_PARAMS; Write alone repairs the `path` spelling onto it BEFORE validation,
14
+ * `coerceWriteArguments`, so a gate never sees the alias there), but this reader is
14
15
  * shared with gates that judge OTHER tool vocabularies — Glob's real `path` parameter, deployment-authored tools —
15
16
  * and a gate must never see LESS than the tool it is confining might act on. So the asymmetry is the design: the
16
17
  * schema decides what a caller may send, this decides what gets judged, and this side over-reads on purpose.
@@ -114,21 +115,12 @@ export interface FsViolation {
114
115
  message: string;
115
116
  }
116
117
  /**
117
- * Absolute in EITHER family: POSIX `/…` or drive-letter `C:\…`/`C:/…`.
118
- *
119
- * This is the ONE definition of "already absolute, therefore do not prepend a base". The failure mode
120
- * of a private re-typing is not the duplication but the drift it invites: a family added at one
121
- * coordinate leaves every other copy judging the same string differently, and this predicate gates a
122
- * security fence (frozen-path deny, readonly-boundary containment) as well as URI key minting.
123
- * Callers that need a family the predicate does not cover must widen it HERE, not locally.
124
- *
125
- * The UNC arm (`\\host\share\…`) is the shape that drift produced: held privately in one consumer, it
126
- * made that consumer resolve a UNC path as absolute while a sibling prefixed it with the task root —
127
- * two URI keys for one file. Folding it in here settles the disagreement in the direction the rest of
128
- * this file takes: a literal `\\host\share\…` is win-form (see {@link isWinFormPath}) and `within()`
129
- * lands it outside every POSIX/drive root, i.e. a location the working directory does not contain.
118
+ * Is a spelling relative in EVERY family — neither `/`- nor `\`-rooted and carrying no drive letter (not even the
119
+ * drive-relative `C:x`)? The tamper check of a recorded relative key reads this (a record that is absolute, rooted or
120
+ * drive-bearing in ANY family is refused there): the opposite stance from {@link isAbsoluteForFamily}, which answers
121
+ * one family's question, so it is a separate, named predicate rather than a mode of that one.
130
122
  */
131
- export declare function isAbsolutePathForm(p: string): boolean;
123
+ export declare function relativeInEveryFamily(p: string): boolean;
132
124
  /** Does this absolute spelling name the opening process's STANDARD INPUT (`/dev/stdin`, `/dev/fd/0`, `/proc/<pid>/fd/0`),
133
125
  * folded lexically first (`/dev/./stdin`, `/dev//fd/0`)? */
134
126
  export declare function namesStandardInput(path: string): boolean;
@@ -306,13 +298,19 @@ export declare function pathFamilyOf(bases: {
306
298
  * reads it. The ONE UNC recognizer behind {@link isAbsoluteForFamily}, {@link pathIdentityOf} and
307
299
  * {@link joinForFamily}. */
308
300
  export declare function isUncSpelling(p: string): boolean;
309
- /**
310
- * FULLY QUALIFIED under the family. win: drive-rooted (`C:\`/`C:/`) or UNC — a `/x` / `\x` is rooted-not-
311
- * qualified and joins the base's drive or share root ({@link joinForFamily}), a `C:rel` is drive-relative.
312
- * posix: `/`-rooted. `undefined` (a treeless call): only the anchor-free `/`-rooted form, the one spelling
313
- * that names a file without a tree.
314
- */
315
- export declare function isAbsoluteForFamily(family: PathFamily | undefined, p: string): boolean;
301
+ /** Where a reading takes its family from: a tree's ({@link PathFamily}), or `"own"` — an address with no tree to place
302
+ * it reads its OWN spelling as the family source (the {@link pathFamilyOf} of that spelling), so a treeless `C:\x` and a
303
+ * treeless `/x` are each read in their own family rather than one of them being declared relative. */
304
+ export type PathFamilySource = PathFamily | "own";
305
+ /** The family a {@link PathFamilySource} names for one spelling. */
306
+ export declare function familyFor(source: PathFamilySource, p: string): PathFamily;
307
+ /**
308
+ * FULLY QUALIFIED under the family — the ONE absoluteness predicate. win: drive-rooted (`C:\`/`C:/`) or UNC (a host AND a
309
+ * share: a bare `\\x` names no share and is no address) — a `/x` / `\x` is rooted-not-qualified and joins the base's drive
310
+ * or share root ({@link joinForFamily}), a `C:rel` is drive-relative. posix: `/`-rooted. `"own"`: the spelling's own
311
+ * family ({@link familyFor}) — the reading of an address no tree places.
312
+ */
313
+ export declare function isAbsoluteForFamily(family: PathFamilySource, p: string): boolean;
316
314
  /** The refusal words of {@link win32NamespaceScreen} — a closed set (docs/CLOSED-SETS.md). */
317
315
  export declare const WIN32_NAMESPACE_REFUSALS: readonly ["device_namespace", "unmappable_namespace", "unc_on_posix_tree", "drive_relative"];
318
316
  export type Win32NamespaceRefusal = (typeof WIN32_NAMESPACE_REFUSALS)[number];
@@ -436,6 +434,14 @@ export declare function foldTrailingSeparators(spelled: string, family: PathFami
436
434
  * share root, anything else under the base with the base's own separator. posix = `/`-join.
437
435
  */
438
436
  export declare function joinForFamily(family: PathFamily, base: string, rel: string): string;
437
+ /**
438
+ * THE anchor step of every path reading: a spelling fully qualified under the family stands as spelled; any other is joined
439
+ * to `base` ({@link joinForFamily}, unfolded). One definition, so "already absolute, therefore do not prepend a base" is
440
+ * decided in one place for every reader — the path prelude (`readPath`, effective-path-target.ts), the canonicalizer, a
441
+ * link's target against its parent, a search tool's root-relative output. Callers that need the namespace screen or an
442
+ * identity read the prelude; this is only its anchor.
443
+ */
444
+ export declare function anchorSpelling(family: PathFamily, base: string, spelled: string): string;
439
445
  /** The family-aware LEXICAL view of a possibly-relative spelling against a resolution base (no I/O,
440
446
  * `.`/`..` folded via {@link normalizeAbsPathLexically}): the SECOND deny-judgment view. It asks
441
447
  * "does the NAME land on a guarded path", complementing the canonical
@@ -456,6 +462,7 @@ export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, pat
456
462
  readonly homeViews?: HomeViewSource;
457
463
  matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
458
464
  readonly aliasResolved?: boolean;
465
+ readonly hops?: readonly string[];
459
466
  }): {
460
467
  pattern: string;
461
468
  matchedView?: string;
@@ -1,9 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readDenySentence } from "./read-deny.js";
3
3
  import { homeViewsOf } from "./read-deny-home.js";
4
+ import { skipsRootsContainment } from "./containment-face.js";
4
5
  import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
5
6
  import { pathTargetValue, protectivePathTargetOf } from "../../core/tool-registry.js";
6
- import { readPathViews } from "../../core/effective-path-target.js";
7
+ import { readPath } from "../../core/effective-path-target.js";
7
8
  import { withGateTimeout, DEFAULT_GATE_AWAIT_TIMEOUT_MS } from "../../core/gate-bounded-await.js";
8
9
  import { thrownText } from "../../core/thrown-value.js";
9
10
  export function fileArgPath(args) {
@@ -25,8 +26,8 @@ export function sha256(content) {
25
26
  return createHash("sha256").update(content, "utf8").digest("hex");
26
27
  }
27
28
  const SEP = "/";
28
- export function isAbsolutePathForm(p) {
29
- return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\");
29
+ export function relativeInEveryFamily(p) {
30
+ return !(p.startsWith("/") || p.startsWith("\\") || /^[A-Za-z]:/.test(p));
30
31
  }
31
32
  function nativeSepOf(p) {
32
33
  return isWinFormPath(p) && p.includes("\\") ? "\\" : "/";
@@ -314,8 +315,11 @@ export function pathFamilyOf(bases) {
314
315
  export function isUncSpelling(p) {
315
316
  return /^[\\/]{2}[^\\/]+[\\/]+[^\\/]/.test(p);
316
317
  }
318
+ export function familyFor(source, p) {
319
+ return source === "own" ? (isWinFormPath(p) ? "win" : "posix") : source;
320
+ }
317
321
  export function isAbsoluteForFamily(family, p) {
318
- return family === "win" ? /^[A-Za-z]:[\\/]/.test(p) || isUncSpelling(p) : p.startsWith("/");
322
+ return familyFor(family, p) === "win" ? /^[A-Za-z]:[\\/]/.test(p) || isUncSpelling(p) : p.startsWith("/");
319
323
  }
320
324
  export const WIN32_NAMESPACE_REFUSALS = Object.freeze(["device_namespace", "unmappable_namespace", "unc_on_posix_tree", "drive_relative"]);
321
325
  export const WIN32_NAMESPACE_REFUSAL_TEXT = {
@@ -445,9 +449,11 @@ export function joinForFamily(family, base, rel) {
445
449
  }
446
450
  return `${base.replace(/[\\/]+$/, "")}${base.includes("\\") ? "\\" : "/"}${rel}`;
447
451
  }
452
+ export function anchorSpelling(family, base, spelled) {
453
+ return isAbsoluteForFamily(family, spelled) ? spelled : joinForFamily(family, base, spelled);
454
+ }
448
455
  export function lexicalViewOf(spelled, base) {
449
- const family = pathFamilyOf({ root: base });
450
- return normalizeAbsPathLexically(isAbsoluteForFamily(family, spelled) ? spelled : joinForFamily(family, base, spelled));
456
+ return normalizeAbsPathLexically(anchorSpelling(pathFamilyOf({ root: base }), base, spelled));
451
457
  }
452
458
  function within(rootCanonical, p) {
453
459
  if (!rootCanonical)
@@ -462,7 +468,7 @@ function within(rootCanonical, p) {
462
468
  return pN.identity.startsWith(rootN.identity.endsWith(SEP) ? rootN.identity : rootN.identity + SEP);
463
469
  }
464
470
  export async function resolveKey(env, rootCanonical, path, signal, baseCwd, additionalRoots, exactFileReadExemption, readDeny, containmentFace) {
465
- const views = await readPathViews(env, path, baseCwd ?? rootCanonical, { root: rootCanonical, ...(baseCwd !== undefined ? { cwd: baseCwd } : {}) }, signal, { hops: readDeny !== undefined });
471
+ const views = await readPath(env, path, baseCwd ?? rootCanonical, { root: rootCanonical, ...(baseCwd !== undefined ? { cwd: baseCwd } : {}) }, { intent: "resolve", views: readDeny !== undefined ? "chain" : "canonical" }, signal);
466
472
  const canon = views.canonical ?? (await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootCanonical }), baseCwd));
467
473
  if (!canon.ok)
468
474
  return { ok: false, violation: { code: "invalid", message: canon.message } };
@@ -473,7 +479,7 @@ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, addi
473
479
  }
474
480
  if (readDeny !== undefined) {
475
481
  const judged = { aliasResolved: canon.aliasResolved === true, hops: views.hops };
476
- const lexical = views.lexical?.kind === "path" ? spellingOfIdentity(views.lexical.identity, pathFamilyOf({ root: rootCanonical })) : undefined;
482
+ const lexical = views.lexical?.kind === "path" ? spellingOfIdentity(views.lexical.identity, views.lexical.family) : undefined;
477
483
  let hit = readDeny.matchTarget(key, lexical, judged);
478
484
  if (hit === null) {
479
485
  const homeView = await homeViewsOf(env, readDeny, signal).of(key);
@@ -493,7 +499,7 @@ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, addi
493
499
  };
494
500
  }
495
501
  }
496
- if (containmentFace === "open") {
502
+ if (skipsRootsContainment(containmentFace)) {
497
503
  if (isUncPath(key) && isWinFormPath(key) && !within(rootCanonical, key) && !additionalRootsCanonical?.some((r) => within(r, key))) {
498
504
  return {
499
505
  ok: false,
@@ -528,7 +534,7 @@ export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
528
534
  }
529
535
  const unprefixed = nativeUncSpellingOf(family, screened.spelled);
530
536
  const spelled = family === "posix" && unprefixed.startsWith("//") ? unprefixed.replace(/^\/+/, "/") : unprefixed;
531
- const target = baseCwd && !isAbsoluteForFamily(family, spelled) ? joinForFamily(family, baseCwd, spelled) : spelled;
537
+ const target = baseCwd ? anchorSpelling(family, baseCwd, spelled) : spelled;
532
538
  let thrownByProbe;
533
539
  let seenThrow = false;
534
540
  const probe = probeRealTarget(env, path, target, family, signal).catch((e) => {
@@ -593,7 +599,7 @@ async function probeRealTarget(env, path, target, family, signal) {
593
599
  };
594
600
  }
595
601
  const hopSpelling = family === "win" ? screenedTarget.spelled : link.value;
596
- const linkTarget = isAbsoluteForFamily(family, hopSpelling) ? hopSpelling : joinForFamily(family, parentDir(abs), hopSpelling);
602
+ const linkTarget = anchorSpelling(family, parentDir(abs), hopSpelling);
597
603
  const reabs = await env.absolutePath(linkTarget, signal);
598
604
  if (!reabs.ok) {
599
605
  return { ok: false, message: `cannot resolve symlink target "${linkTarget}" for "${path}": ${reabs.error.message}`, unresolvedSymlink: true };
@@ -1,6 +1,6 @@
1
1
  import { delimitUntrusted } from "../../core/untrusted-text.js";
2
2
  import { MAX_EXEC_OUTPUT_BYTES } from "../../core/exec-output-tail.js";
3
- import { hasBinaryExtension, isAbsoluteForFamily, isWinFormPath, joinForFamily, pathFamilyOf } from "./safety.js";
3
+ import { anchorSpelling, hasBinaryExtension, isWinFormPath, pathFamilyOf } from "./safety.js";
4
4
  import { homeViewsOf } from "./read-deny-home.js";
5
5
  import { createEntryDenyJudge } from "./dir-entries.js";
6
6
  import { thrownText } from "../../core/thrown-value.js";
@@ -781,7 +781,7 @@ export async function jsGrepDetailed(env, root, p, signal, guards, deny, denyOut
781
781
  const ignore = await buildIgnore(env, root, signal);
782
782
  const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
783
783
  const family = pathFamilyOf({ root });
784
- const start = p.path ? (isAbsoluteForFamily(family, p.path) ? p.path : joinForFamily(family, root, p.path)) : root;
784
+ const start = p.path ? anchorSpelling(family, root, p.path) : root;
785
785
  let walked;
786
786
  if (p.path) {
787
787
  const info = await env.fileInfo(start, signal);
@@ -1062,7 +1062,7 @@ async function sortRgFilesByMtime(env, root, records, signal) {
1062
1062
  const family = pathFamilyOf({ root });
1063
1063
  const toAbs = (p) => {
1064
1064
  const stripped = p.startsWith("./") ? p.slice(2) : p;
1065
- return isAbsoluteForFamily(family, stripped) ? stripped : joinForFamily(family, root, stripped);
1065
+ return anchorSpelling(family, root, stripped);
1066
1066
  };
1067
1067
  const paths = records.map((r) => r.path ?? r.text);
1068
1068
  const infos = await Promise.all(paths.map((p) => env.fileInfo(toAbs(p), signal)));
@@ -1148,8 +1148,10 @@ export function rgOutputDenyTripwire(stdout, mode, judge, opts = {}) {
1148
1148
  return { trip: false };
1149
1149
  }
1150
1150
  function rgRecordAbsolute(base) {
1151
- const family = base !== undefined ? pathFamilyOf({ root: base }) : undefined;
1152
- return (p) => (base === undefined || family === undefined || isAbsoluteForFamily(family, p) ? p : joinForFamily(family, base, p.replace(/^\.[\\/]/, "")));
1151
+ if (base === undefined)
1152
+ return (p) => p;
1153
+ const family = pathFamilyOf({ root: base });
1154
+ return (p) => anchorSpelling(family, base, p.replace(/^\.[\\/]/, ""));
1153
1155
  }
1154
1156
  async function rgOutputDenyTripwireAt(env, stdout, mode, deny, opts, signal) {
1155
1157
  const views = homeViewsOf(env, deny, signal);
@@ -1258,7 +1260,7 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
1258
1260
  if (deny === undefined || deny.rgExclusionGlobs.length === 0)
1259
1261
  return { note: "" };
1260
1262
  if (p.path) {
1261
- const startPath = isAbsoluteForFamily(pathFamilyOf({ root }), p.path) ? p.path : joinForFamily(pathFamilyOf({ root }), root, p.path);
1263
+ const startPath = anchorSpelling(pathFamilyOf({ root }), root, p.path);
1262
1264
  const info = await env.fileInfo(startPath, signal);
1263
1265
  if (info.ok && info.value.kind === "file")
1264
1266
  return { note: "" };
@@ -1333,7 +1335,7 @@ async function rgDenyExistenceProbe(env, root, deny, target, signal) {
1333
1335
  }
1334
1336
  }
1335
1337
  export async function runGrepDetailed(env, root, p, signal, deny) {
1336
- const scope = p.path ? (isAbsoluteForFamily(pathFamilyOf({ root }), p.path) ? p.path : joinForFamily(pathFamilyOf({ root }), root, p.path)) : root;
1338
+ const scope = p.path ? anchorSpelling(pathFamilyOf({ root }), root, p.path) : root;
1337
1339
  const unexcludableUnder = async () => {
1338
1340
  if (deny?.unexcludableUnder === undefined)
1339
1341
  return false;
@@ -1359,7 +1361,7 @@ export async function runGlobDetailed(env, root, pattern, opts = {}, signal) {
1359
1361
  const baseIgnore = await buildIgnore(env, root, signal);
1360
1362
  const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
1361
1363
  const family = pathFamilyOf({ root });
1362
- const start = opts.path ? (isAbsoluteForFamily(family, opts.path) ? opts.path : joinForFamily(family, root, opts.path)) : root;
1364
+ const start = opts.path ? anchorSpelling(family, root, opts.path) : root;
1363
1365
  if (opts.path !== undefined) {
1364
1366
  const probe = await env.fileInfo(start, signal);
1365
1367
  if (!probe.ok && probe.error.code === "not_found") {
@@ -10,11 +10,9 @@ import { type Read } from "./shell-read-word.js";
10
10
  import { type BindingTable, type FloorValue } from "./shell-read-bindings.js";
11
11
  import { subcommandOf, type ArgRole, type ProgramRow } from "./program-read-model.js";
12
12
  import type { ReadCandidate, ReadCandidateProvenance } from "./shell-read-plan.js";
13
- /** A candidate before the threading fills its bases. `alternatives`: the other spellings its word may expand to (a name
14
- * bound to several values), each with the pattern its names are matched by — one candidate each once the segment's reads
15
- * are read. */
13
+ /** A candidate before the threading fills its bases. `floors`: the spellings its word's names left for the deny judge
14
+ * alone (a word whose names are unknown here). */
16
15
  export type Bare = Omit<ReadCandidate, "bases"> & {
17
- readonly alternatives?: readonly Pick<ReadCandidate, "spelled" | "pattern">[];
18
16
  readonly floors?: readonly FloorValue[];
19
17
  };
20
18
  /** What reading a segment's words found beside its candidates. */
@@ -36,9 +34,8 @@ export declare function pathSubcommandOf(row: ProgramRow | undefined, args: read
36
34
  /** The sentence of inline program text the station does not read. */
37
35
  export declare const programTextSentence: (language: string) => string;
38
36
  /** One candidate without its bases (filled by the threading pass): the word's spelling — or `spelled`, a caller's
39
- * reading of PART of it (an option payload, a `key=value` value) — with the word's own expansion facts. A word whose
40
- * names may hold several values offers each as its whole spelling; a PART of such a word is not followed through them
41
- * (it is unresolvable). */
37
+ * reading of PART of it (an option payload, a `key=value` value) — with the word's own expansion facts. The word is read
38
+ * in one reading of the segment's names (`segmentReadingsOf`): its names hold one value each, or are unknown. */
42
39
  export declare function bare(r: Read, provenance: ReadCandidateProvenance, spelled?: string): Bare;
43
40
  /** The reads one value of a declared role hands over: a candidate per file it names, or — a value the program expands
44
41
  * past the listed bound — the word itself, unresolvable. A mount source is a read the daemon makes of the whole tree
@@ -1,5 +1,5 @@
1
1
  import { literalTildeSpelling } from "../../shell/lex.js";
2
- import { expandsOf, patternOf, readOfText, rewritten, spellingOf } from "./shell-read-word.js";
2
+ import { expandsOf, partPattern, readOfText, rewritten } from "./shell-read-word.js";
3
3
  import { arithmeticSubstitutions } from "./shell-read-bindings.js";
4
4
  import { argvProgramOf, attachedOptionPayloads, isPathShapedToken } from "./bash-readonly-classifier.js";
5
5
  import { assignmentArgumentsAreNotPaths, evaluatesOperands, messageWords, moduleTailOf, objectPathsOf, opensNoOperand, programRow, programTextMayOpen, programTextOpensOf, readArgs, selectsContentsTraversal, slotValueCandidates, subcommandOf, valuesAreLiteral, walksCwdWithoutOperand, wordsReadBySomeImplementations, } from "./program-read-model.js";
@@ -10,16 +10,13 @@ export function pathSubcommandOf(row, args) {
10
10
  export const programTextSentence = (language) => `runs inline ${language} program text, which may open files this station does not read`;
11
11
  export function bare(r, provenance, spelled = r.folded) {
12
12
  const whole = spelled === r.folded;
13
- const alternatives = r.values !== undefined && r.values.length > 1 ? r.values.slice(1).map((v) => { const pattern = patternOf(v); return { spelled: spellingOf(v), ...(pattern !== undefined ? { pattern } : {}) }; }) : undefined;
14
- if (!whole && alternatives !== undefined)
15
- return { spelled, raw: r.raw, provenance, expands: "unresolvable" };
13
+ const pattern = whole ? r.pattern : partPattern(r, spelled);
16
14
  return {
17
15
  spelled,
18
16
  raw: r.raw,
19
17
  provenance,
20
18
  expands: r.expands,
21
- ...(whole && r.pattern !== undefined ? { pattern: r.pattern } : {}),
22
- ...(whole && alternatives !== undefined ? { alternatives } : {}),
19
+ ...(pattern !== undefined ? { pattern } : {}),
23
20
  ...(whole && r.homeFloor !== undefined ? { floorSpelled: r.homeFloor } : {}),
24
21
  ...(whole && r.floorValues !== undefined ? { floors: r.floorValues } : {}),
25
22
  };
@@ -64,8 +61,9 @@ export function pushAssignment(reads, w, provenance) {
64
61
  if (!settled && unsettled !== undefined)
65
62
  reads.push(unsettled);
66
63
  const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
64
+ const pattern = named === undefined ? undefined : partPattern({ folded: w.folded, ...(w.judged !== undefined ? { pattern: w.judged } : {}) }, named);
67
65
  if (named !== undefined && !substitutes)
68
- reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
66
+ reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w), ...(pattern !== undefined ? { pattern } : {}) });
69
67
  if (a.value.folded.includes(":"))
70
68
  for (const m of a.value.folded.split(":"))
71
69
  if (m !== "") {
@@ -94,7 +92,7 @@ export function unlistedWords(reads, toks, from, launcherIndices, provenance, ct
94
92
  const noPathOperands = slotVerb !== undefined && opensNoOperand(slotVerb);
95
93
  const expanding = new Set();
96
94
  for (let j = from; j < toks.length; j++) {
97
- if (launcherIndices.has(j))
95
+ if (launcherIndices.has(j) || message.has(j))
98
96
  continue;
99
97
  if (noPathOperands && j > argvStart && !evaluatesOperands(slotVerb))
100
98
  break;