@sema-agent/core 7.48.0 → 7.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (421) hide show
  1. package/CHANGELOG.md +457 -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 +38 -31
  51. package/dist/core/arg-summary.d.ts +21 -28
  52. package/dist/core/arg-summary.js +235 -88
  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 +94 -65
  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 +43 -11
  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 +21 -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.d.ts +4 -0
  101. package/dist/core/mcp.js +70 -43
  102. package/dist/core/memory-engine/content-origin.js +2 -14
  103. package/dist/core/memory-engine/engine.d.ts +32 -22
  104. package/dist/core/memory-engine/engine.js +103 -55
  105. package/dist/core/memory-engine/file-backend.d.ts +13 -30
  106. package/dist/core/memory-engine/file-backend.js +37 -252
  107. package/dist/core/memory-engine/index.d.ts +1 -1
  108. package/dist/core/memory-engine/index.js +1 -1
  109. package/dist/core/memory-engine/layout.d.ts +139 -9
  110. package/dist/core/memory-engine/layout.js +350 -7
  111. package/dist/core/memory-engine/scope-contract.d.ts +10 -0
  112. package/dist/core/memory-engine/scope-contract.js +8 -0
  113. package/dist/core/memory-engine/session-incarnation.d.ts +14 -4
  114. package/dist/core/memory-engine/session-incarnation.js +5 -2
  115. package/dist/core/memory-engine/types.d.ts +10 -1
  116. package/dist/core/memory.js +5 -17
  117. package/dist/core/model-seat.d.ts +4 -4
  118. package/dist/core/model-seat.js +1 -1
  119. package/dist/core/mounted-mcp.d.ts +57 -0
  120. package/dist/core/mounted-mcp.js +19 -0
  121. package/dist/core/namespaced-rule-name.d.ts +29 -0
  122. package/dist/core/namespaced-rule-name.js +52 -0
  123. package/dist/core/park-selfcheck.d.ts +1 -1
  124. package/dist/core/park-selfcheck.js +16 -6
  125. package/dist/core/path-guard-rows.d.ts +99 -0
  126. package/dist/core/path-guard-rows.js +45 -0
  127. package/dist/core/path-identity.d.ts +22 -16
  128. package/dist/core/path-identity.js +10 -10
  129. package/dist/core/path-rule-binding.d.ts +2 -1
  130. package/dist/core/path-rule-binding.js +3 -1
  131. package/dist/core/pause-registry.d.ts +16 -2
  132. package/dist/core/pause-registry.js +6 -0
  133. package/dist/core/permission-rule-consent.js +0 -7
  134. package/dist/core/permission-rule-model.d.ts +68 -24
  135. package/dist/core/permission-rule-model.js +105 -24
  136. package/dist/core/permission-rule-store.js +2 -2
  137. package/dist/core/permission-rules.d.ts +3 -30
  138. package/dist/core/permission-rules.js +4 -54
  139. package/dist/core/read-root-candidate.d.ts +1 -3
  140. package/dist/core/read-root-candidate.js +4 -15
  141. package/dist/core/real-approval.d.ts +4 -3
  142. package/dist/core/retired-keys.d.ts +13 -0
  143. package/dist/core/retired-keys.js +14 -0
  144. package/dist/core/roles.d.ts +2 -3
  145. package/dist/core/roles.js +15 -6
  146. package/dist/core/runner/assemble-result.d.ts +0 -7
  147. package/dist/core/runner/assemble-result.js +19 -31
  148. package/dist/core/runner/caller-roster.d.ts +78 -0
  149. package/dist/core/runner/caller-roster.js +219 -0
  150. package/dist/core/runner/checkpoint-scope.d.ts +16 -16
  151. package/dist/core/runner/checkpoint-scope.js +2 -2
  152. package/dist/core/runner/child-tree.js +2 -2
  153. package/dist/core/runner/contracts.d.ts +68 -35
  154. package/dist/core/runner/decide-continuation.d.ts +51 -3
  155. package/dist/core/runner/decide-continuation.js +76 -21
  156. package/dist/core/runner/denial-limit-arms.d.ts +17 -5
  157. package/dist/core/runner/denial-limit-arms.js +11 -9
  158. package/dist/core/runner/extra-body-dropped-seat.d.ts +8 -0
  159. package/dist/core/runner/extra-body-dropped-seat.js +22 -0
  160. package/dist/core/runner/finish-reason-seat.d.ts +9 -0
  161. package/dist/core/runner/finish-reason-seat.js +40 -0
  162. package/dist/core/runner/gate-exit.d.ts +44 -32
  163. package/dist/core/runner/gate-exit.js +112 -25
  164. package/dist/core/runner/inherited-ask-grants.js +6 -4
  165. package/dist/core/runner/mcp-redial.d.ts +3 -3
  166. package/dist/core/runner/mcp-redial.js +3 -4
  167. package/dist/core/runner/permission-rule-lanes.d.ts +1 -2
  168. package/dist/core/runner/permission-rule-lanes.js +5 -4
  169. package/dist/core/runner/prepare-artifact.d.ts +4 -0
  170. package/dist/core/runner/prepare-artifact.js +2 -1
  171. package/dist/core/runner/prepare-ask-lane.d.ts +2 -0
  172. package/dist/core/runner/prepare-ask-lane.js +12 -6
  173. package/dist/core/runner/prepare-boundary-parks.d.ts +5 -8
  174. package/dist/core/runner/prepare-boundary-parks.js +4 -7
  175. package/dist/core/runner/prepare-caps-and-workflow.d.ts +11 -15
  176. package/dist/core/runner/prepare-caps-and-workflow.js +19 -27
  177. package/dist/core/runner/prepare-config-doors.d.ts +9 -9
  178. package/dist/core/runner/prepare-config-doors.js +10 -5
  179. package/dist/core/runner/prepare-defer-classify.d.ts +2 -2
  180. package/dist/core/runner/prepare-defer-classify.js +5 -3
  181. package/dist/core/runner/prepare-delegation-surface.d.ts +1 -1
  182. package/dist/core/runner/prepare-delegation-surface.js +3 -3
  183. package/dist/core/runner/prepare-gate-stations.d.ts +4 -4
  184. package/dist/core/runner/prepare-gate-stations.js +9 -7
  185. package/dist/core/runner/prepare-hands-readface.d.ts +7 -7
  186. package/dist/core/runner/prepare-hands-readface.js +7 -7
  187. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -6
  188. package/dist/core/runner/prepare-inherited-gate.js +14 -15
  189. package/dist/core/runner/prepare-listings.d.ts +7 -24
  190. package/dist/core/runner/prepare-listings.js +4 -5
  191. package/dist/core/runner/prepare-memory-engine-session.d.ts +2 -0
  192. package/dist/core/runner/prepare-memory-engine-session.js +4 -3
  193. package/dist/core/runner/prepare-memory.d.ts +6 -0
  194. package/dist/core/runner/prepare-memory.js +13 -13
  195. package/dist/core/runner/prepare-offload-wrappers.d.ts +5 -0
  196. package/dist/core/runner/prepare-offload-wrappers.js +3 -3
  197. package/dist/core/runner/prepare-park-ask.d.ts +7 -8
  198. package/dist/core/runner/prepare-park-ask.js +17 -10
  199. package/dist/core/runner/prepare-policy-chain.js +24 -44
  200. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  201. package/dist/core/runner/prepare-project-context.js +3 -4
  202. package/dist/core/runner/prepare-protocol-tools.d.ts +9 -3
  203. package/dist/core/runner/prepare-protocol-tools.js +3 -4
  204. package/dist/core/runner/prepare-run-refs.d.ts +3 -0
  205. package/dist/core/runner/prepare-run-refs.js +2 -1
  206. package/dist/core/runner/prepare-safety-scan.d.ts +4 -8
  207. package/dist/core/runner/prepare-safety-scan.js +1 -2
  208. package/dist/core/runner/prepare-suspend-saga.d.ts +7 -1
  209. package/dist/core/runner/prepare-suspend-saga.js +28 -12
  210. package/dist/core/runner/prepare-task.d.ts +1 -1
  211. package/dist/core/runner/prepare-task.js +67 -56
  212. package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -10
  213. package/dist/core/runner/prepare-wiring-manifest.js +5 -8
  214. package/dist/core/runner/resume-apply.js +7 -14
  215. package/dist/core/runner/resume-checkpoint-screen.js +11 -4
  216. package/dist/core/runner/resume-internals-and-config.js +2 -2
  217. package/dist/core/runner/resume-policy-outcome.js +5 -32
  218. package/dist/core/runner/resume-review-outcome.js +4 -1
  219. package/dist/core/runner/retired-runner-deps.d.ts +6 -0
  220. package/dist/core/runner/retired-runner-deps.js +10 -1
  221. package/dist/core/runner/run-brain-sinks.d.ts +4 -0
  222. package/dist/core/runner/run-brain-sinks.js +5 -2
  223. package/dist/core/runner/run-harness-handlers.js +29 -36
  224. package/dist/core/runner/run-leg.d.ts +0 -2
  225. package/dist/core/runner/run-leg.js +32 -33
  226. package/dist/core/runner/run-terminal-adoption.js +4 -3
  227. package/dist/core/runner/runtask.js +8 -8
  228. package/dist/core/runner/session-rule-policy.js +5 -5
  229. package/dist/core/runner/stream-settle-backstop.js +7 -4
  230. package/dist/core/runner/sub-agent-deny-lists.d.ts +51 -12
  231. package/dist/core/runner/sub-agent-deny-lists.js +111 -80
  232. package/dist/core/runner/terminal-account.d.ts +48 -0
  233. package/dist/core/runner/terminal-account.js +186 -0
  234. package/dist/core/runner/tool-end-body.d.ts +54 -17
  235. package/dist/core/runner/tool-end-body.js +27 -2
  236. package/dist/core/runner/tool-face-overlay.d.ts +3 -1
  237. package/dist/core/runner/tool-face-overlay.js +6 -4
  238. package/dist/core/runner/tool-occurrence.d.ts +106 -0
  239. package/dist/core/runner/tool-occurrence.js +123 -0
  240. package/dist/core/runner/tool-progress-lane.d.ts +16 -8
  241. package/dist/core/runner/tool-progress-lane.js +25 -34
  242. package/dist/core/runner/tool-turn-repair-seat.d.ts +28 -4
  243. package/dist/core/runner/tool-turn-repair-seat.js +36 -13
  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/secret-env.d.ts +16 -5
  249. package/dist/core/secret-env.js +23 -63
  250. package/dist/core/sensitive-path-policy.d.ts +19 -18
  251. package/dist/core/sensitive-path-policy.js +73 -49
  252. package/dist/core/session-reconcile.d.ts +3 -0
  253. package/dist/core/session-reconcile.js +5 -4
  254. package/dist/core/session-store.d.ts +5 -0
  255. package/dist/core/session-store.js +12 -0
  256. package/dist/core/session.d.ts +13 -0
  257. package/dist/core/shared-memory/normalize.d.ts +17 -4
  258. package/dist/core/shared-memory/normalize.js +7 -7
  259. package/dist/core/shell-lexer.js +5 -4
  260. package/dist/core/shell-wrapper-projection.js +1 -1
  261. package/dist/core/skill-grant.js +1 -1
  262. package/dist/core/store-contracts/checkpoint-store-contract.js +13 -0
  263. package/dist/core/store-contracts/mailbox-store-contract.d.ts +7 -14
  264. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +27 -0
  265. package/dist/core/store-contracts/session-store-incarnation-contract.js +92 -0
  266. package/dist/core/stub-env.d.ts +2 -0
  267. package/dist/core/stub-env.js +1 -0
  268. package/dist/core/synthetic-read-verdict.js +2 -2
  269. package/dist/core/task-event.d.ts +59 -5
  270. package/dist/core/task-result.d.ts +1 -1
  271. package/dist/core/task-spec.d.ts +42 -9
  272. package/dist/core/terminal-cause.d.ts +94 -11
  273. package/dist/core/tighten-task-spec.d.ts +5 -4
  274. package/dist/core/tighten-task-spec.js +20 -25
  275. package/dist/core/tool-catalog-entries.d.ts +5 -7
  276. package/dist/core/tool-catalog-entries.js +14 -15
  277. package/dist/core/tool-face.d.ts +47 -0
  278. package/dist/core/tool-face.js +5 -0
  279. package/dist/core/tool-model-gate.d.ts +5 -5
  280. package/dist/core/tool-name-aliases.d.ts +26 -2
  281. package/dist/core/tool-name-aliases.js +13 -0
  282. package/dist/core/tool-name-list.d.ts +30 -0
  283. package/dist/core/tool-name-list.js +55 -0
  284. package/dist/core/tool-name-readings.d.ts +1 -9
  285. package/dist/core/tool-name-readings.js +1 -3
  286. package/dist/core/tool-policy.d.ts +195 -85
  287. package/dist/core/tool-policy.js +165 -380
  288. package/dist/core/tool-registry.d.ts +44 -1
  289. package/dist/core/tool-registry.js +26 -0
  290. package/dist/core/tool-roster.d.ts +19 -1
  291. package/dist/core/tool-roster.js +21 -2
  292. package/dist/core/tool-spec-members.d.ts +45 -0
  293. package/dist/core/tool-spec-members.js +169 -0
  294. package/dist/core/tool-spec.d.ts +46 -8
  295. package/dist/core/tools.d.ts +6 -2
  296. package/dist/core/tools.js +8 -4
  297. package/dist/core/untrusted-egress.js +29 -26
  298. package/dist/core/untrusted-text.d.ts +13 -10
  299. package/dist/core/untrusted-text.js +6 -44
  300. package/dist/core/workflow-journal-store.d.ts +4 -1
  301. package/dist/core/write-protect.d.ts +65 -29
  302. package/dist/core/write-protect.js +235 -177
  303. package/dist/engine/compaction/compaction.d.ts +2 -2
  304. package/dist/engine/execution-env/node-execution-env.d.ts +5 -23
  305. package/dist/engine/execution-env/node-execution-env.js +16 -38
  306. package/dist/engine/execution-env/symlink-chain.d.ts +23 -0
  307. package/dist/engine/execution-env/symlink-chain.js +32 -0
  308. package/dist/engine/harness/types.d.ts +7 -0
  309. package/dist/engine/llm/tool-turns.d.ts +3 -0
  310. package/dist/engine/llm/tool-turns.js +1 -0
  311. package/dist/engine/llm/types.d.ts +59 -23
  312. package/dist/engine/loop/agent-loop.js +16 -2
  313. package/dist/engine/session/memory-repo.js +11 -3
  314. package/dist/engine/session/repo-utils.d.ts +8 -0
  315. package/dist/engine/session/repo-utils.js +13 -0
  316. package/dist/index.d.ts +20 -23
  317. package/dist/index.js +17 -20
  318. package/dist/internal/harness.d.ts +1 -1
  319. package/dist/internal/harness.js +1 -1
  320. package/dist/internal/llm.d.ts +2 -2
  321. package/dist/internal/llm.js +1 -1
  322. package/dist/orchestration/governance-baseline-validity.js +1 -1
  323. package/dist/orchestration/run-spec.js +3 -3
  324. package/dist/orchestration/run-workflow-tool.d.ts +16 -35
  325. package/dist/orchestration/run-workflow-tool.js +38 -78
  326. package/dist/orchestration/workflow-governance.d.ts +21 -25
  327. package/dist/orchestration/workflow-governance.js +38 -78
  328. package/dist/orchestration/workflow-primitives.d.ts +7 -22
  329. package/dist/orchestration/workflow-primitives.js +5 -12
  330. package/dist/orchestration/workflow-types.d.ts +1 -1
  331. package/dist/orchestration/workflow.d.ts +61 -11
  332. package/dist/orchestration/workflow.js +189 -117
  333. package/dist/scenarios/full-body.d.ts +1 -1
  334. package/dist/shell/lex.d.ts +0 -21
  335. package/dist/shell/lex.js +250 -68
  336. package/dist/shell/shell-words.d.ts +35 -0
  337. package/dist/shell/shell-words.js +65 -0
  338. package/dist/stores/file/checkpoint-store.d.ts +5 -5
  339. package/dist/stores/file/checkpoint-store.js +5 -2
  340. package/dist/stores/file/index.d.ts +0 -8
  341. package/dist/stores/file/index.js +0 -12
  342. package/dist/stores/file/session-store.d.ts +3 -0
  343. package/dist/stores/file/session-store.js +44 -19
  344. package/dist/tools/artifact/artifact-tool.js +2 -0
  345. package/dist/tools/fs/bash-program-position.d.ts +12 -4
  346. package/dist/tools/fs/bash-program-position.js +32 -9
  347. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  348. package/dist/tools/fs/bash-readonly-classifier.js +8 -7
  349. package/dist/tools/fs/containment-face.d.ts +4 -0
  350. package/dist/tools/fs/containment-face.js +3 -0
  351. package/dist/tools/fs/fs-shared.d.ts +10 -0
  352. package/dist/tools/fs/fs-shared.js +18 -0
  353. package/dist/tools/fs/fs-write.js +4 -2
  354. package/dist/tools/fs/index.d.ts +1 -1
  355. package/dist/tools/fs/program-read-model.d.ts +46 -20
  356. package/dist/tools/fs/program-read-model.js +50 -20
  357. package/dist/tools/fs/program-read-model.rows.d.ts +0 -14
  358. package/dist/tools/fs/program-read-model.rows.js +16 -15
  359. package/dist/tools/fs/read-deny-home.d.ts +33 -47
  360. package/dist/tools/fs/read-deny-home.js +35 -49
  361. package/dist/tools/fs/read-deny.d.ts +23 -22
  362. package/dist/tools/fs/read-deny.js +114 -89
  363. package/dist/tools/fs/removal-targets.d.ts +56 -23
  364. package/dist/tools/fs/removal-targets.js +65 -87
  365. package/dist/tools/fs/safety.d.ts +29 -22
  366. package/dist/tools/fs/safety.js +17 -11
  367. package/dist/tools/fs/search.js +10 -8
  368. package/dist/tools/fs/shell-read-argv.js +1 -1
  369. package/dist/tools/fs/shell-read-bindings.d.ts +169 -37
  370. package/dist/tools/fs/shell-read-bindings.js +324 -86
  371. package/dist/tools/fs/shell-read-builtins.d.ts +35 -0
  372. package/dist/tools/fs/shell-read-builtins.js +43 -0
  373. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  374. package/dist/tools/fs/shell-read-check.js +67 -43
  375. package/dist/tools/fs/shell-read-establish.js +6 -6
  376. package/dist/tools/fs/shell-read-functions.d.ts +22 -0
  377. package/dist/tools/fs/shell-read-functions.js +29 -0
  378. package/dist/tools/fs/shell-read-judge.d.ts +51 -18
  379. package/dist/tools/fs/shell-read-judge.js +40 -4
  380. package/dist/tools/fs/shell-read-plan.d.ts +12 -6
  381. package/dist/tools/fs/shell-read-plan.js +118 -113
  382. package/dist/tools/fs/shell-read-start.d.ts +29 -6
  383. package/dist/tools/fs/shell-read-start.js +16 -9
  384. package/dist/tools/fs/shell-read-word.js +4 -0
  385. package/dist/tools/fs/shell-read-workdir.d.ts +156 -0
  386. package/dist/tools/fs/shell-read-workdir.js +281 -0
  387. package/dist/tools/fs/shell-read-write-targets.d.ts +5 -2
  388. package/dist/tools/fs/shell-read-write-targets.js +14 -5
  389. package/dist/tools/fs/shell-reentry-text.d.ts +4 -6
  390. package/dist/tools/fs/shell-reentry-text.js +3 -11
  391. package/dist/tools/task-list.d.ts +1 -2
  392. package/dist/tools/web.js +1 -1
  393. package/package.json +4 -1
  394. package/test/export-surface.snapshot.json +107 -143
  395. package/wire-changes/core@7.49.0.json +4765 -0
  396. package/dist/agents/teacher.d.ts +0 -159
  397. package/dist/agents/teacher.js +0 -577
  398. package/dist/agents/team.d.ts +0 -226
  399. package/dist/agents/team.js +0 -257
  400. package/dist/core/strategy-store.d.ts +0 -238
  401. package/dist/core/strategy-store.js +0 -253
  402. package/dist/orchestration/builtin-workflows.d.ts +0 -133
  403. package/dist/orchestration/builtin-workflows.js +0 -159
  404. package/dist/scenarios/env.d.ts +0 -40
  405. package/dist/scenarios/env.js +0 -39
  406. package/dist/scenarios/scenario-registry.d.ts +0 -110
  407. package/dist/scenarios/scenario-registry.js +0 -140
  408. package/dist/scenarios/teacher-quickstart.d.ts +0 -40
  409. package/dist/scenarios/teacher-quickstart.js +0 -12
  410. package/dist/stores/cc/lockfile.d.ts +0 -10
  411. package/dist/stores/cc/lockfile.js +0 -98
  412. package/dist/stores/cc/mailbox-store.d.ts +0 -20
  413. package/dist/stores/cc/mailbox-store.js +0 -255
  414. package/dist/stores/cc/roster-adapter.d.ts +0 -12
  415. package/dist/stores/cc/roster-adapter.js +0 -117
  416. package/dist/stores/cc/sidecar-transcript.d.ts +0 -97
  417. package/dist/stores/cc/sidecar-transcript.js +0 -264
  418. package/dist/stores/cc/task-list-store.d.ts +0 -2
  419. package/dist/stores/cc/task-list-store.js +0 -125
  420. package/dist/stores/file/strategy-store.d.ts +0 -97
  421. package/dist/stores/file/strategy-store.js +0 -341
@@ -5,17 +5,15 @@ import type { ToolCallRequest } from "../../core/tool-policy.js";
5
5
  * recursive option (removing an empty home or the task root is still removing it). */
6
6
  export declare const SHELL_REMOVAL_COMMANDS: readonly ["rm", "rmdir"];
7
7
  export type ShellRemovalCommand = (typeof SHELL_REMOVAL_COMMANDS)[number];
8
- /** What makes a removal target dangerous, in the order a card names it. `runtime_script`: a shell's script text is built
9
- * only when the command runs, and what it may run includes a removal nobody can read before it does. */
10
- export type DangerousRemovalKind = "filesystem_root" | "home" | "workspace" | "top_level" | "runtime_script";
8
+ /** What makes a removal target dangerous, in the order a card names it. */
9
+ export type DangerousRemovalKind = "filesystem_root" | "home" | "workspace" | "top_level";
11
10
  /** The protected locations a removal is judged against. `homes`: the environment's declared home and every other spelling
12
11
  * of it (its canonical one); `workspace`: the task root, the working directory, the additional working directories. */
13
12
  export interface RemovalSeat {
14
13
  readonly homes: readonly string[];
15
14
  readonly workspace: readonly string[];
16
15
  }
17
- /** One dangerous removal a command makes: the program (the removal program, or the shell a runtime-built script is handed
18
- * to), the operand as written, the target as established (the script word as written, for `runtime_script`). */
16
+ /** One dangerous removal a command makes: the removal program, the operand as written, the target as established. */
19
17
  export interface DangerousRemoval {
20
18
  readonly kind: DangerousRemovalKind;
21
19
  readonly program: string;
@@ -30,33 +28,64 @@ export interface DangerousRemoval {
30
28
  * directory (one segment below the root). `undefined` for a relative path or any other target.
31
29
  */
32
30
  export declare function dangerousRemovalOf(path: string, seat: RemovalSeat): DangerousRemovalKind | undefined;
33
- /** What one removal finding asks: a protected location (`dangerous_delete`), or a target the command builds from a value
34
- * of its own that the guard cannot read (`unverifiable_delete`). */
31
+ /** What one removal finding asks: a protected location (`dangerous_delete`; `untrackedFrom` — the segment the working
32
+ * directory stops being tracked at, where the target is relative and was judged where the text stood then); a recursive
33
+ * forced removal whose target the
34
+ * guard cannot verify (`unverifiable_delete`) — an operand the shell rewrites from a value the plan does not settle
35
+ * (`rewritten`), the operands of a removal its argv stream fills in (`stream`), or an operand relative to the directory a
36
+ * launcher option moved the removal into (`relocated`); or a shell handed text the engine cannot read
37
+ * (`unverifiable_shell_text`) — a text a program produces (`produced`: what a pipe or a `<(…)` carries, an argv a stream
38
+ * fills in, a substitution), or a text nobody read (`unread`: one the lexer could not read at all, {@link unreadPlan}, or a
39
+ * segment whose program the walk could not name, {@link programUnread}). */
35
40
  export type RemovalFinding = (DangerousRemoval & {
36
41
  readonly guard: "dangerous_delete";
42
+ readonly untrackedFrom?: string;
37
43
  }) | {
38
44
  readonly guard: "unverifiable_delete";
39
45
  readonly program: string;
40
46
  readonly operand: string;
41
- readonly name: string;
47
+ readonly source: "rewritten" | "stream" | "relocated";
48
+ } | {
49
+ readonly guard: "unverifiable_shell_text";
50
+ readonly program: string;
51
+ readonly text: string;
52
+ readonly source: "produced" | "unread";
42
53
  };
54
+ /**
55
+ * The names a removal target may be built on and still be verified: a root the SHELL provides, never one a command's
56
+ * output fed — `rm -rf "$TMPDIR"/<literal path below it>` is not asked about. `TMPDIR` is the reference's carve-out (its
57
+ * rule exempts "a well-known-safe temp pattern"); `HOME` is sema's (a registered divergence, looser than the reference,
58
+ * whose rule names `$HOME` as the blast radius: it is a root the shell provides too). A declared home is read by the
59
+ * value table itself — `$HOME` is then its value, and the target is judged where it lands; `PWD` is no root of this kind:
60
+ * its value is where the working-directory thread says the command stands.
61
+ */
62
+ export declare const KNOWN_SAFE_ROOTS: readonly string[];
43
63
  /**
44
64
  * The removal findings of an established plan — ONE walk, nested plans included (a `bash -c` / `sh -c` text, a substitution
45
65
  * body), two words. For every `rm` / `rmdir` segment, each of its OPERANDS (every write target that is not one of its
46
66
  * option words: a word some implementation's reading of the program's row writes, {@link writeReadings}) as the plan
47
67
  * established it on the WRITE face (the entry the program unlinks, and the deny-floor shadow of a home word the plan did
48
- * not place) is judged by {@link dangerousRemovalOf}; an operand the plan could not place that expands a name the command
49
- * itself binds is judged in every spelling those bindings give it ({@link boundSpellingsOf}), and one with a binding this
50
- * reading cannot read is an unverifiable target (a recursive forced removal, {@link recursiveForce}). A shell handed a
51
- * script built only when the command runs is a dangerous removal of its own ({@link runtimeScriptOf}). The read-face copy
52
- * of an operand is not judged: it follows a link the removal does not follow.
68
+ * not place) is judged by {@link dangerousRemovalOf}. In a recursive forced removal ({@link recursiveForce}), an operand
69
+ * the shell rewrites from a value the plan's table does not settle — a name with no value here or one set in a form the
70
+ * table cannot read, a substitution, a parameter operator, a positional parameter — is an unverifiable target, save a
71
+ * literal path below a known-safe root the shell still provides there ({@link belowKnownSafeRoot}); so are the operands
72
+ * of a removal its argv stream fills in (`… | xargs rm -rf`), and an operand the plan cannot place — in a removal a
73
+ * launcher moved into another base, or in text such a removal hands a shell (`env -C <dir> rm -rf build`). A relative
74
+ * operand past a directory move the working-directory thread cannot follow is judged where the text stood when it lost
75
+ * track (the reference resolves it against the original working directory: KL-1403-b), and a card it earns says so. A `read` fills its name from the input: a value
76
+ * the table does not settle (stricter than the reference, whose rule counts `read VAR` as an in-window assignment). A
77
+ * name the table settles is its value, so `D=./build; rm -rf "$D/../build"` is judged
78
+ * where it lands, as `rm -rf ./build` is. A shell handed a text the engine cannot read — one a program produces
79
+ * ({@link runtimeScriptOf}), or one nobody read ({@link unreadPlan}, {@link programUnread}) — is an unreadable text of its
80
+ * own. The read-face copy of an operand is not judged: it follows a link the removal does not follow.
53
81
  */
54
- export declare function removalFindingsOf(established: EstablishedPlan, seat: RemovalSeat): RemovalFinding[];
55
- /** Does any segment of this plan (nested plans included) run a removal program, or hand a shell a script built when it
56
- * runs? The guard establishes nothing for a command that does neither. */
82
+ export declare function removalFindingsOf(established: EstablishedPlan, seat: RemovalSeat, safeRoots?: readonly string[], enclosingRelocated?: boolean): RemovalFinding[];
83
+ /** Does this plan (nested plans included) run a removal program, hand a shell a script built when it runs, or hold text
84
+ * nobody could read ({@link unreadPlan}, {@link programUnread})? The guard establishes nothing for a command that does none. */
57
85
  export declare function planRunsRemoval(plan: EstablishedPlan["plan"]): boolean;
58
86
  /** What the removal guard asks for one command: the guard word and its sentence's account — the first protected location
59
- * (the program, the location, the target), else the first bound value the guard cannot read; `undefined` when neither. */
87
+ * (the program, the location, the target), else the first target the guard cannot verify, else the first shell text the
88
+ * engine cannot read; `undefined` when none. */
60
89
  export interface RemovalVerdict {
61
90
  readonly guard: RemovalFinding["guard"];
62
91
  readonly text: string;
@@ -72,12 +101,16 @@ export interface RemovalGuardSeat {
72
101
  readonly additionalRoots?: () => readonly string[];
73
102
  }
74
103
  /**
75
- * The dangerous-removal READING of one command — undefined when no segment of it (nested shell text included) runs a
76
- * removal program, so a command that removes nothing costs the gate no suspension. Otherwise `judge` establishes the plan
77
- * the read station plans (the same planner, the same write-target reading) lexically against the request's `cwd` and the
78
- * seat's home, and — where an environment is seated — through its resolver too, and returns what either establishment
79
- * finds ({@link removalFindingsOf}: the protected-location word first, then the unverifiable one).
104
+ * The removal guard's READING of one command — undefined when no segment of it (nested shell text included) runs a
105
+ * removal program or hands a shell a script built when it runs, and every text in it was read, so a command that removes
106
+ * nothing costs the gate no suspension (a text the lexer could not read is not one: bash may run the lines before its
107
+ * syntax error, and the guard reads no prefix of it). Otherwise `judge` establishes the plan the read
108
+ * station plans (the same planner, the same value table, the same write-target reading) lexically against the request's
109
+ * `cwd` and the seat's home, and — where an environment is seated — through its resolver too, and returns what either
110
+ * establishment finds ({@link removalFindingsOf}: the protected-location word first, then the unverifiable target, then the
111
+ * unreadable text).
112
+ * `safeRoots`: the known-safe roots ({@link KNOWN_SAFE_ROOTS}, with a deployment's own added by the policy).
80
113
  */
81
- export declare function removalReadingOf(command: string, req: Pick<ToolCallRequest, "cwd" | "root">, seat: RemovalGuardSeat | undefined): {
114
+ export declare function removalReadingOf(command: string, req: Pick<ToolCallRequest, "cwd" | "root">, seat: RemovalGuardSeat | undefined, safeRoots?: readonly string[]): {
82
115
  judge(signal?: AbortSignal): Promise<RemovalVerdict | undefined>;
83
116
  } | undefined;
@@ -1,9 +1,9 @@
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";
4
+ import { isAbsoluteForFamily, isWinFormPath, normalizeAbsPathLexically } from "./safety.js";
5
5
  import { launchedCommands, launchedReadings, reentryText, scriptFileAt, spellLaunched } from "./shell-reentry-text.js";
6
- import { isShellSyntaxWord, LOOP_LIST_HEADS, readProgramPosition } from "./bash-program-position.js";
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";
9
9
  import { implementationsOf, programRow, runsShellText, selectedOptions, writeReadings } from "./program-read-model.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 (isShellSyntaxWord(head, LOOP_LIST_HEADS) && 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;
@@ -187,38 +149,54 @@ function runtimeScriptOf(segment) {
187
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)) }] : [];
188
150
  const launched = launchedCommands(segment.argv, pos).flatMap((c) => {
189
151
  const shell = launchedReadings(segment.argv, c).shell;
190
- 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 }) }];
191
153
  });
192
154
  const texts = [...named, ...launched].flatMap(({ shell, words }) => [reentryText(shell, words), ...(segment.stdin ?? []).map((s) => reentryText(shell, words, s.literal ?? `${MARK}${s.from}`))]);
193
155
  const text = texts.find((t) => t?.includes(MARK) === true);
194
156
  return text === undefined ? undefined : text.split(MARK).join("");
195
157
  }
158
+ const unreadPlan = (plan) => plan.unreadable !== undefined && plan.segments.length === 0;
159
+ const programUnread = (s) => s.declines.includes("unreadable_program_position");
196
160
  export function planRunsRemoval(plan) {
197
- 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));
198
162
  }
199
163
  const DANGEROUS_REMOVAL_SENTENCE = {
200
164
  filesystem_root: (p, t) => `\`${p}\` would remove the filesystem root (${t})`,
201
165
  home: (p, t) => `\`${p}\` would remove a home directory (${t})`,
202
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})`,
203
167
  top_level: (p, t) => `\`${p}\` would remove a top-level system directory (${t})`,
204
- 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`,
205
177
  };
206
178
  function verdictOf(findings) {
207
- for (const f of findings)
208
- if (f.guard === "dangerous_delete")
209
- 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
+ }
210
185
  for (const f of findings)
211
186
  if (f.guard === "unverifiable_delete")
212
- 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)) };
213
191
  return undefined;
214
192
  }
215
193
  const REMOVAL_RESOLVE_TIMEOUT_MS = 10_000;
216
- export function removalReadingOf(command, req, seat) {
194
+ export function removalReadingOf(command, req, seat, safeRoots = KNOWN_SAFE_ROOTS) {
217
195
  const homeDir = seat?.env?.homeDir;
218
196
  const cwd = req.cwd ?? req.root ?? "";
219
197
  const roots = req.root !== undefined ? [req.root] : cwd !== "" ? [cwd] : [];
220
198
  const boundary = { roots, ...(cwd !== "" ? { cwd } : {}), ...(homeDir !== undefined ? { homeDir } : {}) };
221
- 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 } : {}) });
222
200
  if (!planRunsRemoval(plan))
223
201
  return undefined;
224
202
  return {
@@ -229,11 +207,11 @@ export function removalReadingOf(command, req, seat) {
229
207
  homes: [...(homeDir !== undefined ? [homeDir] : []), ...(resolved !== undefined && resolved.ok ? [resolved.value] : [])],
230
208
  workspace: [...(req.root !== undefined ? [req.root] : []), ...(req.cwd !== undefined ? [req.cwd] : []), ...(seat?.additionalRoots?.() ?? [])],
231
209
  };
232
- const lexical = removalFindingsOf(establishShellReadsLexically(plan, boundary), removalSeat);
210
+ const lexical = removalFindingsOf(establishShellReadsLexically(plan, boundary), removalSeat, safeRoots);
233
211
  if (lexical.some((f) => f.guard === "dangerous_delete") || env === undefined || roots.length === 0)
234
212
  return verdictOf(lexical);
235
213
  const established = await resolvedOrUndefined(createShellReadEstablisher(env, roots, { rootsCanonical: true }).establish(plan, boundary, signal), signal);
236
- return verdictOf(established === undefined ? lexical : [...removalFindingsOf(established, removalSeat), ...lexical]);
214
+ return verdictOf(established === undefined ? lexical : [...removalFindingsOf(established, removalSeat, safeRoots), ...lexical]);
237
215
  },
238
216
  };
239
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 };