@sema-agent/core 7.52.0 → 7.54.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 (357) hide show
  1. package/CHANGELOG.md +470 -0
  2. package/LICENSE +3 -1
  3. package/README.md +3 -1
  4. package/dist/agents/cross-session-envelope.d.ts +8 -0
  5. package/dist/agents/cross-session-envelope.js +6 -0
  6. package/dist/agents/cross-session-judge.d.ts +21 -1
  7. package/dist/agents/cross-session-judge.js +8 -0
  8. package/dist/agents/cross-session-ref.d.ts +5 -0
  9. package/dist/agents/cross-session-ref.js +1 -0
  10. package/dist/agents/org-delivery-gate.d.ts +122 -0
  11. package/dist/agents/org-delivery-gate.js +69 -0
  12. package/dist/agents/peer-held-queue.d.ts +9 -4
  13. package/dist/agents/peer-held-queue.js +6 -5
  14. package/dist/agents/peer-session-drain.d.ts +35 -6
  15. package/dist/agents/peer-session-drain.js +271 -182
  16. package/dist/agents/read-inbox-tool.d.ts +7 -0
  17. package/dist/agents/read-inbox-tool.js +5 -1
  18. package/dist/agents/send-message-tool.js +3 -4
  19. package/dist/agents/subagent.d.ts +6 -5
  20. package/dist/agents/subagent.js +117 -83
  21. package/dist/agents/tool-filter.d.ts +25 -4
  22. package/dist/agents/tool-filter.js +13 -3
  23. package/dist/brain/anthropic.d.ts +6 -1
  24. package/dist/brain/anthropic.js +4 -4
  25. package/dist/brain/model-presets.js +2 -1
  26. package/dist/brain/route-adjudicator.d.ts +6 -0
  27. package/dist/brain/routing.d.ts +24 -1
  28. package/dist/brain/routing.js +54 -19
  29. package/dist/brain/timeout.js +5 -4
  30. package/dist/core/a2a.d.ts +5 -2
  31. package/dist/core/a2a.js +33 -14
  32. package/dist/core/allow-entry.d.ts +3 -1
  33. package/dist/core/allow-entry.js +3 -2
  34. package/dist/core/approval-object.d.ts +87 -0
  35. package/dist/core/approval-object.js +85 -0
  36. package/dist/core/arg-summary.d.ts +3 -2
  37. package/dist/core/arg-summary.js +17 -11
  38. package/dist/core/ask-origin.d.ts +43 -27
  39. package/dist/core/ask-origin.js +20 -11
  40. package/dist/core/ask-question.d.ts +0 -1
  41. package/dist/core/ask-question.js +4 -67
  42. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  43. package/dist/core/auto-compaction.d.ts +106 -50
  44. package/dist/core/auto-compaction.js +69 -53
  45. package/dist/core/auto-mode-prompt.d.ts +4 -2
  46. package/dist/core/auto-mode-prompt.js +4 -3
  47. package/dist/core/canonical-json.d.ts +69 -0
  48. package/dist/core/canonical-json.js +130 -8
  49. package/dist/core/checkpoint-requirements.d.ts +13 -7
  50. package/dist/core/checkpoint-requirements.js +4 -1
  51. package/dist/core/checkpoint-store.d.ts +60 -36
  52. package/dist/core/checkpoint-store.js +7 -6
  53. package/dist/core/child-clamp-seats.d.ts +45 -6
  54. package/dist/core/child-clamp-seats.js +56 -4
  55. package/dist/core/compliance.js +3 -1
  56. package/dist/core/consolidate-scope.d.ts +1 -1
  57. package/dist/core/cross-boundary-taint.d.ts +213 -0
  58. package/dist/core/cross-boundary-taint.js +196 -0
  59. package/dist/core/decision-receipt.d.ts +44 -1
  60. package/dist/core/decision-receipt.js +16 -0
  61. package/dist/core/effective-path-target.d.ts +28 -6
  62. package/dist/core/effective-path-target.js +28 -2
  63. package/dist/core/engine-notice.d.ts +47 -4
  64. package/dist/core/engine-notice.js +17 -1
  65. package/dist/core/engine-storage-backend.d.ts +73 -0
  66. package/dist/core/engine-storage-backend.js +1 -0
  67. package/dist/core/file-history-store.d.ts +6 -2
  68. package/dist/core/file-history-store.js +1 -0
  69. package/dist/core/gate-fold.js +13 -4
  70. package/dist/core/gate-lanes.js +41 -34
  71. package/dist/core/gate-outcome.d.ts +18 -9
  72. package/dist/core/governance-codes.d.ts +9 -2
  73. package/dist/core/governance-codes.js +9 -0
  74. package/dist/core/hands-band-screen.d.ts +7 -0
  75. package/dist/core/hands-band-screen.js +32 -0
  76. package/dist/core/hooks.d.ts +21 -19
  77. package/dist/core/hooks.js +6 -5
  78. package/dist/core/locked-config.d.ts +110 -31
  79. package/dist/core/locked-config.js +155 -42
  80. package/dist/core/lsp.d.ts +1 -1
  81. package/dist/core/mcp-probe.js +1 -1
  82. package/dist/core/mcp-server-spec.d.ts +152 -17
  83. package/dist/core/mcp-server-spec.js +223 -40
  84. package/dist/core/mcp.d.ts +21 -14
  85. package/dist/core/mcp.js +59 -54
  86. package/dist/core/memory-engine/engine.d.ts +14 -19
  87. package/dist/core/memory-engine/engine.js +9 -6
  88. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  89. package/dist/core/memory-engine/export-bundle.js +1 -1
  90. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  91. package/dist/core/memory-engine/file-backend.js +1 -12
  92. package/dist/core/memory.d.ts +3 -3
  93. package/dist/core/mode-policy.d.ts +16 -26
  94. package/dist/core/mode-policy.js +5 -18
  95. package/dist/core/mods/kernel/dispatch.js +13 -15
  96. package/dist/core/mods/kernel/events.d.ts +9 -6
  97. package/dist/core/mods/kernel/events.js +2 -2
  98. package/dist/core/mods/kernel/receive.d.ts +41 -11
  99. package/dist/core/mods/kernel/receive.js +124 -54
  100. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  101. package/dist/core/mods/kernel/site-rules.js +232 -40
  102. package/dist/core/mods/stations.d.ts +0 -6
  103. package/dist/core/mods/stations.js +0 -7
  104. package/dist/core/mounted-mcp.d.ts +57 -0
  105. package/dist/core/mounted-mcp.js +71 -2
  106. package/dist/core/own-property-receipt.d.ts +13 -0
  107. package/dist/core/own-property-receipt.js +3 -1
  108. package/dist/core/parent-spec-seat.d.ts +12 -15
  109. package/dist/core/parent-spec-seat.js +14 -6
  110. package/dist/core/pause-registry.d.ts +35 -1
  111. package/dist/core/pause-registry.js +10 -0
  112. package/dist/core/permission-rule-consent.d.ts +18 -5
  113. package/dist/core/permission-rule-consent.js +13 -8
  114. package/dist/core/permission-rule-model.d.ts +87 -15
  115. package/dist/core/permission-rule-model.js +87 -8
  116. package/dist/core/permission-rule-org.d.ts +200 -33
  117. package/dist/core/permission-rule-org.js +217 -91
  118. package/dist/core/permission-rule-session.js +2 -2
  119. package/dist/core/permission-rule-store.d.ts +8 -0
  120. package/dist/core/permission-rule-store.js +1 -0
  121. package/dist/core/permission-rule-sync.js +3 -2
  122. package/dist/core/permission-rules.d.ts +7 -1
  123. package/dist/core/permission-rules.js +37 -8
  124. package/dist/core/read-only-shell.d.ts +1 -1
  125. package/dist/core/read-only-shell.js +30 -9
  126. package/dist/core/remote-env.d.ts +41 -1
  127. package/dist/core/remote-env.js +18 -0
  128. package/dist/core/resume-startup-cause.d.ts +18 -0
  129. package/dist/core/resume-startup-cause.js +12 -0
  130. package/dist/core/retention.js +7 -2
  131. package/dist/core/retired-keys.d.ts +8 -2
  132. package/dist/core/retired-keys.js +12 -1
  133. package/dist/core/roles.js +60 -10
  134. package/dist/core/rule-lane-reads.js +1 -1
  135. package/dist/core/runner/approval-feedback.d.ts +46 -0
  136. package/dist/core/runner/approval-feedback.js +41 -0
  137. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  138. package/dist/core/runner/compaction-call-options.js +2 -2
  139. package/dist/core/runner/contracts.d.ts +34 -41
  140. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  141. package/dist/core/runner/cross-boundary-lane.js +190 -0
  142. package/dist/core/runner/decide-continuation.d.ts +4 -4
  143. package/dist/core/runner/decide-continuation.js +3 -1
  144. package/dist/core/runner/gate-exit.d.ts +32 -12
  145. package/dist/core/runner/gate-exit.js +77 -34
  146. package/dist/core/runner/inherited-ask-grants.js +2 -2
  147. package/dist/core/runner/mcp-redial.d.ts +52 -10
  148. package/dist/core/runner/mcp-redial.js +38 -9
  149. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  150. package/dist/core/runner/permission-rule-lanes.js +22 -10
  151. package/dist/core/runner/person-path-rows.d.ts +5 -3
  152. package/dist/core/runner/person-path-rows.js +2 -2
  153. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
  154. package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
  155. package/dist/core/runner/prepare-ask-lane.js +3 -3
  156. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  157. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  158. package/dist/core/runner/prepare-config-doors.d.ts +58 -15
  159. package/dist/core/runner/prepare-config-doors.js +167 -146
  160. package/dist/core/runner/prepare-context-lane.js +6 -3
  161. package/dist/core/runner/prepare-defer-classify.js +4 -7
  162. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  163. package/dist/core/runner/prepare-delegation-surface.js +8 -7
  164. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  165. package/dist/core/runner/prepare-execution-env.js +2 -1
  166. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  167. package/dist/core/runner/prepare-gate-stations.js +4 -3
  168. package/dist/core/runner/prepare-hands-readface.d.ts +3 -3
  169. package/dist/core/runner/prepare-hands-readface.js +3 -2
  170. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  171. package/dist/core/runner/prepare-inherited-gate.js +3 -2
  172. package/dist/core/runner/prepare-listings.d.ts +1 -1
  173. package/dist/core/runner/prepare-listings.js +4 -2
  174. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  175. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  176. package/dist/core/runner/prepare-memory.d.ts +3 -2
  177. package/dist/core/runner/prepare-memory.js +6 -2
  178. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  179. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  180. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  181. package/dist/core/runner/prepare-park-ask.js +9 -3
  182. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  183. package/dist/core/runner/prepare-policy-chain.js +14 -19
  184. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  185. package/dist/core/runner/prepare-project-context.js +6 -4
  186. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  187. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  188. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  189. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  190. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  191. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  192. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  193. package/dist/core/runner/prepare-question-face.js +4 -3
  194. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  195. package/dist/core/runner/prepare-safety-scan.js +34 -27
  196. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  197. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  198. package/dist/core/runner/prepare-task.js +22 -19
  199. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  200. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  201. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -5
  202. package/dist/core/runner/prepare-wiring-manifest.js +9 -6
  203. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  204. package/dist/core/runner/resume-admission.d.ts +4 -1
  205. package/dist/core/runner/resume-admission.js +16 -14
  206. package/dist/core/runner/resume-apply.js +4 -4
  207. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  208. package/dist/core/runner/resume-internals-and-config.js +3 -8
  209. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  210. package/dist/core/runner/resume-policy-outcome.js +18 -15
  211. package/dist/core/runner/resume-preflight.js +2 -2
  212. package/dist/core/runner/resume-review-outcome.js +3 -3
  213. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  214. package/dist/core/runner/retired-runner-deps.js +1 -4
  215. package/dist/core/runner/run-leg.js +97 -43
  216. package/dist/core/runner/run-recovery-lanes.js +5 -3
  217. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  218. package/dist/core/runner/run-terminal-adoption.js +21 -9
  219. package/dist/core/runner/run-turn-boundary.js +9 -3
  220. package/dist/core/runner/runtask.js +1 -1
  221. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  222. package/dist/core/runner/session-file-state-replay.js +3 -2
  223. package/dist/core/runner/skills-seat.d.ts +15 -0
  224. package/dist/core/runner/skills-seat.js +57 -0
  225. package/dist/core/runner/stream-steer-verb.js +2 -2
  226. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  227. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  228. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  229. package/dist/core/runner/synthetic-tools.js +2 -12
  230. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  231. package/dist/core/runner/tool-face-overlay.js +24 -15
  232. package/dist/core/runner-deps.d.ts +37 -22
  233. package/dist/core/session-policy-store.d.ts +19 -0
  234. package/dist/core/session-policy-store.js +22 -2
  235. package/dist/core/session-store.d.ts +35 -16
  236. package/dist/core/session-store.js +20 -12
  237. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  238. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  239. package/dist/core/shell-wrapper-table.d.ts +5 -3
  240. package/dist/core/shell-wrapper-table.js +1 -1
  241. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  242. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  243. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  244. package/dist/core/store-contracts/session-store-contract.js +639 -0
  245. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  246. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  247. package/dist/core/task-event.d.ts +44 -6
  248. package/dist/core/task-notification.js +2 -2
  249. package/dist/core/task-registry-agent.js +1 -0
  250. package/dist/core/task-registry-shared.d.ts +5 -0
  251. package/dist/core/task-registry.d.ts +7 -0
  252. package/dist/core/task-registry.js +17 -0
  253. package/dist/core/task-result.d.ts +1 -1
  254. package/dist/core/task-spec.d.ts +7 -7
  255. package/dist/core/tighten-task-spec.d.ts +6 -4
  256. package/dist/core/tighten-task-spec.js +2 -2
  257. package/dist/core/tool-catalog-entries.d.ts +1 -1
  258. package/dist/core/tool-catalog-entries.js +52 -52
  259. package/dist/core/tool-face-reader.d.ts +49 -0
  260. package/dist/core/tool-face-reader.js +94 -0
  261. package/dist/core/tool-face.d.ts +17 -8
  262. package/dist/core/tool-face.js +1 -0
  263. package/dist/core/tool-policy.d.ts +67 -23
  264. package/dist/core/tool-policy.js +72 -60
  265. package/dist/core/tool-roster.d.ts +46 -28
  266. package/dist/core/tool-roster.js +91 -14
  267. package/dist/core/tool-spec-members.js +2 -2
  268. package/dist/core/tool-spec.d.ts +20 -4
  269. package/dist/core/tools.js +2 -0
  270. package/dist/core/trace.d.ts +24 -2
  271. package/dist/core/types.d.ts +3 -3
  272. package/dist/core/types.js +1 -1
  273. package/dist/core/untrusted-text.d.ts +23 -0
  274. package/dist/core/untrusted-text.js +10 -10
  275. package/dist/core/user-attachment.d.ts +56 -2
  276. package/dist/core/user-attachment.js +139 -56
  277. package/dist/core/wiring-manifest.d.ts +4 -2
  278. package/dist/engine/compaction/compaction.d.ts +2 -2
  279. package/dist/engine/compaction/utils.d.ts +2 -2
  280. package/dist/engine/harness/agent-harness.js +2 -1
  281. package/dist/engine/harness/types.d.ts +10 -4
  282. package/dist/engine/llm/types.d.ts +7 -2
  283. package/dist/engine/llm/types.js +2 -1
  284. package/dist/engine/loop/agent-loop.d.ts +12 -1
  285. package/dist/engine/loop/agent-loop.js +17 -5
  286. package/dist/engine/loop/types.d.ts +7 -1
  287. package/dist/engine/session/repo-utils.d.ts +5 -0
  288. package/dist/engine/session/repo-utils.js +9 -0
  289. package/dist/engine/session/storage-base.js +2 -2
  290. package/dist/index.d.ts +37 -17
  291. package/dist/index.js +31 -14
  292. package/dist/internal/harness.d.ts +1 -0
  293. package/dist/internal/harness.js +1 -0
  294. package/dist/internal/llm.d.ts +1 -1
  295. package/dist/internal/llm.js +1 -1
  296. package/dist/orchestration/goal.d.ts +24 -1
  297. package/dist/orchestration/goal.js +3 -2
  298. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  299. package/dist/orchestration/run-workflow-tool.js +1 -0
  300. package/dist/orchestration/workflow-governance.d.ts +1 -1
  301. package/dist/orchestration/workflow.d.ts +3 -0
  302. package/dist/orchestration/workflow.js +2 -1
  303. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  304. package/dist/prompt-assembly/artifact-store.js +38 -12
  305. package/dist/prompt-assembly/artifact.d.ts +12 -11
  306. package/dist/prompt-assembly/artifact.js +52 -21
  307. package/dist/prompt-assembly/assemble.d.ts +2 -0
  308. package/dist/prompt-assembly/assemble.js +2 -1
  309. package/dist/prompt-assembly/catalog/index.js +2 -1
  310. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  311. package/dist/prompt-assembly/catalog/memory.js +6 -0
  312. package/dist/prompt-assembly/catalog/schema.d.ts +19 -2
  313. package/dist/prompt-assembly/catalog/schema.js +3 -0
  314. package/dist/prompt-assembly/composer.d.ts +2 -0
  315. package/dist/prompt-assembly/composer.js +1 -1
  316. package/dist/prompt-assembly/overlay.d.ts +21 -11
  317. package/dist/prompt-assembly/overlay.js +57 -19
  318. package/dist/prompt-assembly/select.d.ts +21 -6
  319. package/dist/prompt-assembly/select.js +41 -4
  320. package/dist/prompts/default.d.ts +1 -12
  321. package/dist/prompts/default.js +0 -3
  322. package/dist/shell/lex.d.ts +87 -8
  323. package/dist/shell/lex.js +283 -49
  324. package/dist/stores/file/index.d.ts +8 -6
  325. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  326. package/dist/stores/file/permission-rule-store.js +2 -1
  327. package/dist/tools/artifact/artifact-tool.js +14 -3
  328. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  329. package/dist/tools/fs/bash-program-position.js +3 -10
  330. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  331. package/dist/tools/fs/fs-bash.d.ts +3 -3
  332. package/dist/tools/fs/fs-bash.js +72 -101
  333. package/dist/tools/fs/fs-read.d.ts +12 -0
  334. package/dist/tools/fs/fs-read.js +10 -9
  335. package/dist/tools/fs/fs-shared.d.ts +27 -31
  336. package/dist/tools/fs/fs-shared.js +12 -15
  337. package/dist/tools/fs/fs-write.js +1 -1
  338. package/dist/tools/fs/index.js +2 -2
  339. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  340. package/dist/tools/fs/read-deny-home.js +5 -4
  341. package/dist/tools/fs/read-deny.d.ts +16 -3
  342. package/dist/tools/fs/read-deny.js +12 -9
  343. package/dist/tools/fs/safety.d.ts +7 -6
  344. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  345. package/dist/tools/fs/shell-read-bindings.js +15 -16
  346. package/dist/tools/fs/shell-read-device.js +1 -1
  347. package/dist/tools/fs/shell-read-plan.js +36 -47
  348. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  349. package/dist/tools/fs/shell-read-word.js +2 -20
  350. package/dist/tools/fs/shell-reentry-text.js +1 -1
  351. package/dist/tools/web.d.ts +2 -2
  352. package/package.json +4 -1
  353. package/test/export-surface.snapshot.json +5651 -5457
  354. package/wire-changes/core@7.53.0.json +1968 -0
  355. package/wire-changes/core@7.54.0.json +2870 -0
  356. package/dist/core/shell-scan.d.ts +0 -60
  357. package/dist/core/shell-scan.js +0 -183
@@ -1,5 +1,5 @@
1
1
  import { posix } from "node:path";
2
- import { readShellCommand } from "./shell-lexer.js";
2
+ import { lexedWordOf, readShellCommand } from "./shell-run-candidates.js";
3
3
  import { descriptorsOfPlan, newLineDescriptors, standardDeviceUnrebound } from "../tools/fs/shell-read-device.js";
4
4
  import { planShellReads } from "../tools/fs/shell-read-plan.js";
5
5
  import { writesOnlyStandardDevices } from "../tools/fs/shell-read-write-targets.js";
@@ -33,8 +33,7 @@ const TABLE_ROWS = Object.entries(READ_ONLY_COMMAND_TABLE)
33
33
  .map(([key, row]) => ({ words: key.split(" "), row }))
34
34
  .sort((a, b) => b.words.length - a.words.length);
35
35
  const READING_REDIRECTS = new Set(["<", "<<", "<<-", "<&", "<<<"]);
36
- const LITERAL_ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)\+?=(?:"[^"$`\\]*"|'[^']*'|[A-Za-z0-9_./:+-]*)$/;
37
- const ASSIGNMENT_NAME = /^([A-Za-z_][A-Za-z0-9_]*)(?:\[[^\]]*\])?\+?=/;
36
+ const BARE_VALUE_CHARACTERS = /^[A-Za-z0-9_./:+-]*$/;
38
37
  const FLAG_WORD = /^-[a-zA-Z0-9_-]/;
39
38
  const NUMBER_LITERAL = /^[-+]?(0[xX][0-9a-fA-F]+|[0-9]+#[0-9a-zA-Z]+|[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)$/;
40
39
  const PRINTF_LENGTH = "[lLhqjzZt]*";
@@ -90,20 +89,42 @@ export function readOnlyLineObjection(command, cwd) {
90
89
  return lineStructureObjection(shape) ?? (changesDirectoryBeforeGit(shape, cwd) ? CD_GIT_REASON : undefined);
91
90
  }
92
91
  function expansionKind(w) {
92
+ const lexed = lexedWordOf(w);
93
+ if (lexed !== undefined && (lexed.unresolved !== undefined || lexed.quotedSpans.some((s) => s.by === "$'")))
94
+ return "variable";
93
95
  if (w.expands === false)
94
96
  return "none";
95
- if (/[$`]|[<>]\(/.test(w.raw))
97
+ if (lexed === undefined)
98
+ return "variable";
99
+ const computed = (e) => VALUE_EXPANSIONS.has(e.kind) || e.inner?.some(computed) === true;
100
+ if (lexed.expansions.some((p) => p.expansion !== undefined && computed(p.expansion)))
96
101
  return "variable";
97
102
  if (w.expands === "many")
98
103
  return "glob";
99
104
  return "none";
100
105
  }
106
+ const VALUE_EXPANSIONS = new Set(["parameter", "command", "process", "arithmetic"]);
101
107
  function leadingAssignmentCount(argv) {
102
108
  let k = 0;
103
- while (k < argv.length && ASSIGNMENT_NAME.test(argv[k].raw))
109
+ while (k < argv.length && lexedWordOf(argv[k])?.assignment?.bare === true)
104
110
  k++;
105
111
  return k;
106
112
  }
113
+ function literalAssignmentValue(a) {
114
+ if (a.subscript !== undefined || a.form !== "scalar" || a.value.unresolved !== undefined || a.value.expansions.length > 0)
115
+ return false;
116
+ const [only, ...rest] = a.value.pieces;
117
+ if (only === undefined)
118
+ return true;
119
+ if (rest.length > 0 || only.literal === undefined)
120
+ return false;
121
+ if (only.quotedBy === undefined)
122
+ return BARE_VALUE_CHARACTERS.test(only.literal);
123
+ const oneRegion = a.value.raw.length === only.literal.length + 2;
124
+ if (only.quotedBy === "'")
125
+ return oneRegion;
126
+ return only.quotedBy === '"' && oneRegion && !/[$`\\]/.test(only.literal);
127
+ }
107
128
  function peelBuiltinPrefix(words) {
108
129
  let t = words;
109
130
  for (;;) {
@@ -142,11 +163,11 @@ function segmentVerdict(seg, line, bindings) {
142
163
  }
143
164
  const k = leadingAssignmentCount(seg.argv);
144
165
  for (const w of seg.argv.slice(0, k)) {
145
- const m = LITERAL_ASSIGNMENT.exec(w.raw);
146
- if (m === null)
166
+ const a = lexedWordOf(w).assignment;
167
+ if (!literalAssignmentValue(a))
147
168
  return NOT(`the assignment \`${w.raw}\` is not a literal value`);
148
- if (!ENV_NAMES.has(m[1]))
149
- return NOT(`the environment variable \`${m[1]}\` can alter what a program does`);
169
+ if (!ENV_NAMES.has(a.name))
170
+ return NOT(`the environment variable \`${a.name}\` can alter what a program does`);
150
171
  }
151
172
  const run = seg.argv.slice(k);
152
173
  if (run.length === 0)
@@ -66,6 +66,19 @@ export interface WorkspaceHandle {
66
66
  */
67
67
  deviceId?: string;
68
68
  }
69
+ /** The one list of what a {@link WorkspaceHandle} is: every member the interface declares. */
70
+ export declare const WORKSPACE_HANDLE_MEMBERS: ReadonlyArray<keyof WorkspaceHandle>;
71
+ /**
72
+ * A frozen copy of `handle` holding exactly the declared members it carries as strings: shallow-frozen, which is deep here
73
+ * because every member is a string (a decoration or a function member of the source never reaches the copy), and no
74
+ * structured-clone step to fail on an exotic value. Each member is read the way the engine's own handle comparison reads it.
75
+ *
76
+ * ONE copy per live handle: a second call for the same object answers the copy the first call made (the members are read
77
+ * once), so every place the engine hands a checkpointed handle to the outside — the resume preflight's facts, the env
78
+ * factory's {@link ExecutionEnvFactoryContext.resumeWorkspace}, the restore's {@link VmLifecycleOptions.priorHandle} —
79
+ * delivers the same object, not three renderings of one fact.
80
+ */
81
+ export declare function frozenHandleCopy(handle: WorkspaceHandle): Readonly<WorkspaceHandle>;
69
82
  /**
70
83
  * A reference to a secret injected at {@link RemoteExecutionEnv.connect}/{@link RemoteExecutionEnv.postResumeInit}
71
84
  * time (design/48 §5 gap 7) — never baked into the image. The control plane resolves {@link ref} to a value out of
@@ -114,8 +127,12 @@ export interface VmLifecycleOptions {
114
127
  * handle (never echo `priorHandle.mountPath` unhonored) — the engine compares the two and surfaces a
115
128
  * divergence observation. Absent on non-resume ops and from pre-1.360 engines (adapters treat absence
116
129
  * as the legacy current-config behavior).
130
+ *
131
+ * A frozen copy holding exactly the declared members ({@link WORKSPACE_HANDLE_MEMBERS}) the checkpoint row carries
132
+ * as strings — the SAME object the env factory's {@link ExecutionEnvFactoryContext.resumeWorkspace} was, for the
133
+ * same row — not the stored row's own object: writing to it throws, and a decoration on the stored handle is absent.
117
134
  */
118
- priorHandle?: WorkspaceHandle;
135
+ priorHandle?: Readonly<WorkspaceHandle>;
119
136
  }
120
137
  /**
121
138
  * One chunk streamed from {@link RemoteExecutionEnv.execStream} (design/48 §5 gap 3, council BUG#3). A run is a
@@ -494,6 +511,29 @@ export interface ExecutionEnvFactoryContext {
494
511
  * already shares the parent env (and its cwd).
495
512
  */
496
513
  parentCwd?: string;
514
+ /**
515
+ * design/380 O1 delivery fence — the workspace a DURABLE RESUME leg is rebuilding: a frozen copy of the
516
+ * {@link WorkspaceHandle} the checkpoint row recorded at suspend, holding exactly the members a handle declares
517
+ * ({@link WORKSPACE_HANDLE_MEMBERS}) that the row carries as strings. Present ONLY on that leg — the key is
518
+ * absent (not an `undefined` value) on every fresh run, and on a resume whose row recorded no workspace
519
+ * handle. Mutating the copy reaches nothing the engine reads.
520
+ *
521
+ * A factory bound to a target (the device lane) **MUST** mint the env on the device
522
+ * `resumeWorkspace.deviceId` names and, when it cannot (offline, busy, the placement moved to another
523
+ * device), **THROW** a typed refusal. It **MUST NOT** connect some other device and leave the engine to
524
+ * notice, and it **MUST NOT** look the current binding up afresh from {@link placementRootSessionId} for
525
+ * this leg. A row recorded without `deviceId` (a non-device lane, or a row older than the field) states no
526
+ * device to honor; the factory then follows its ordinary routing.
527
+ *
528
+ * The minted env's `workspaceHandle()` **MUST** report the device its connection actually reached; it is
529
+ * never copied from `resumeWorkspace`. The engine's comparison of that report against the row (`provider`
530
+ * equal; `deviceId` equal whenever the row carries one) stays as the receipt check and TRUSTS the report: a
531
+ * factory that connected elsewhere and reports so is refused (`resume.env_failed`), only after the wrong
532
+ * connection was made, but one that echoes the fence while connecting elsewhere defeats its own protection
533
+ * and passes. The fence moves no refusal earlier than it was: it runs after the claim, so what a deployment
534
+ * can still catch BEFORE the claim is `RunnerDeps.resumePreflight` alone.
535
+ */
536
+ resumeWorkspace?: Readonly<WorkspaceHandle>;
497
537
  }
498
538
  /**
499
539
  * A **trusted control-plane** factory that mints a per-task {@link ExecutionEnv} (design/48 §5 answers core
@@ -1,3 +1,21 @@
1
+ const WORKSPACE_HANDLE_MEMBER_TABLE = { sandboxId: 1, provider: 1, mountPath: 1, snapshotId: 1, sessionToken: 1, restoreMode: 1, deviceId: 1 };
2
+ export const WORKSPACE_HANDLE_MEMBERS = Object.keys(WORKSPACE_HANDLE_MEMBER_TABLE);
3
+ const FROZEN_COPY_OF = new WeakMap();
4
+ export function frozenHandleCopy(handle) {
5
+ const known = FROZEN_COPY_OF.get(handle);
6
+ if (known !== undefined)
7
+ return known;
8
+ const copy = {};
9
+ for (const member of WORKSPACE_HANDLE_MEMBERS) {
10
+ const value = handle[member];
11
+ if (typeof value === "string")
12
+ copy[member] = value;
13
+ }
14
+ const frozen = Object.freeze(copy);
15
+ if (typeof handle === "object" && handle !== null)
16
+ FROZEN_COPY_OF.set(handle, frozen);
17
+ return frozen;
18
+ }
1
19
  export const RETRYABLE_REMOTE_ERROR_CODES = [
2
20
  "auth_transient",
3
21
  "connect_failed",
@@ -23,6 +23,7 @@
23
23
  * unsafe payload, an input the constraints refuse): correct the decision and resubmit.
24
24
  */
25
25
  import type { AssertAllKeysHandled } from "./ask-origin.js";
26
+ import type { TerminalFailureDetail } from "./terminal-cause.js";
26
27
  /**
27
28
  * design/80 D-1: why a consumed checkpoint was {@link import("./checkpoint-store.js").CheckpointStore.reopen | reopened}
28
29
  * (resolved → pending). The reason drives re-resume validation (§3 invariant #1): an `env_failed` reopen is a SYSTEM
@@ -51,6 +52,23 @@ export type ResumeStartupCause = (typeof RESUME_STARTUP_CAUSES)[number];
51
52
  /** The cause a resume failure's code names; `undefined` when the code is absent or the table does not
52
53
  * name it. Every `config.*` code is a configuration door's refusal. */
53
54
  export declare function resumeStartupCauseOf(code: string | undefined): ResumeStartupCause | undefined;
55
+ /**
56
+ * The members of a resume refusal's `detail` the parked-resume receipt carries beside its `cause` — the three a
57
+ * {@link import("./checkpoint-store.js").CheckpointError} states where its `code` alone does not say which input or which arm refused:
58
+ * `code` (on `checkpoint.config_refused`: prepare's own code, the word a fresh run of the same bad config reports), `field` (which
59
+ * input carries the refusal) and `reason` (which arm fired). Their words are those families' own; the receipt adds no word of its own.
60
+ */
61
+ export declare const RESUME_REFUSAL_MEMBERS: readonly ["code", "field", "reason"];
62
+ /** {@link RESUME_REFUSAL_MEMBERS} as the receipt carries them: each a string, each present only when the failure stated it. */
63
+ export type ResumeRefusalDetail = {
64
+ readonly [K in (typeof RESUME_REFUSAL_MEMBERS)[number]]?: string;
65
+ };
66
+ /**
67
+ * The refusal members a failed resume stated, read from the failure's coded account ({@link TerminalFailureDetail}, `kind: "coded"`:
68
+ * the `detail` its throw site stated) — never from its sentence. `undefined` when the failure carries no coded account or none of
69
+ * the three members is a string in it.
70
+ */
71
+ export declare function resumeRefusalDetailOf(account: TerminalFailureDetail | undefined): ResumeRefusalDetail | undefined;
54
72
  declare const REOPEN_REASON_OF_CAUSE: {
55
73
  readonly environment: "env_failed";
56
74
  readonly session_gone: "env_failed";
@@ -21,6 +21,18 @@ export function resumeStartupCauseOf(code) {
21
21
  return undefined;
22
22
  return CAUSE_OF_CODE.get(code) ?? (code.startsWith("config.") ? "configuration" : undefined);
23
23
  }
24
+ export const RESUME_REFUSAL_MEMBERS = Object.freeze(["code", "field", "reason"]);
25
+ export function resumeRefusalDetailOf(account) {
26
+ if (account === undefined || account.kind !== "coded")
27
+ return undefined;
28
+ const out = {};
29
+ for (const member of RESUME_REFUSAL_MEMBERS) {
30
+ const value = Object.hasOwn(account.detail, member) ? account.detail[member] : undefined;
31
+ if (typeof value === "string")
32
+ out[member] = value;
33
+ }
34
+ return Object.keys(out).length > 0 ? out : undefined;
35
+ }
24
36
  const REOPEN_REASON_OF_CAUSE = {
25
37
  environment: "env_failed",
26
38
  session_gone: "env_failed",
@@ -1,3 +1,4 @@
1
+ import { describeRejectedValue } from "./tool-search-setting.js";
1
2
  export const RETENTION_CASCADE_DOMAINS = Object.freeze(["checkpoints", "session-trees", "orphan-tool-results"]);
2
3
  export const RETENTION_CASCADE_EXEMPT = Object.freeze(["session-pollution", "session-capture-optout", "lineage.json"]);
3
4
  function codedError(code, message) {
@@ -7,8 +8,12 @@ function codedError(code, message) {
7
8
  }
8
9
  export function assertRetentionCapability(input) {
9
10
  const { policy, locked, stores } = input;
11
+ const declared = policy;
12
+ if (declared !== undefined && (typeof declared !== "object" || declared === null || Array.isArray(declared))) {
13
+ throw codedError("config.retention_policy", `retentionPolicy must be an object carrying maxAgeDays (got ${declared === null ? "null" : Array.isArray(declared) ? "an array" : typeof declared}).`);
14
+ }
10
15
  if (policy !== undefined && (!Number.isFinite(policy.maxAgeDays) || policy.maxAgeDays < 0)) {
11
- throw codedError("config.retention_policy", `retentionPolicy.maxAgeDays must be a finite non-negative number of days (got ${String(policy.maxAgeDays)}).`);
16
+ throw codedError("config.retention_policy", `retentionPolicy.maxAgeDays must be a finite non-negative number of days (got ${describeRejectedValue(policy.maxAgeDays)}).`);
12
17
  }
13
18
  const declaredRetention = (name, store) => {
14
19
  const r = store.retention;
@@ -19,7 +24,7 @@ export function assertRetentionCapability(input) {
19
24
  throw codedError("config.retention_capability", `store "${name}" declares an unparseable retention value ${JSON.stringify(r)} — expected "managed" or "none".`);
20
25
  };
21
26
  const declarations = stores
22
- .filter((s) => s.store !== undefined)
27
+ .filter((s) => s.store !== undefined && s.store !== null)
23
28
  .map(({ name, store }) => ({ name, retention: declaredRetention(name, store) }));
24
29
  if (!locked || policy === undefined)
25
30
  return;
@@ -46,7 +46,13 @@ export declare const RETIRED_SHELL_READ_BOUNDARY_KEYS: Readonly<Record<string, s
46
46
  * decision that carries one is denied in the engine's own words, never read without it: the type retires a member only where
47
47
  * a decision is a literal in an annotated position, and a `check` whose return type is inferred still compiles with it. */
48
48
  export declare const RETIRED_DECISION_MEMBERS: Readonly<Record<string, string>>;
49
- /** The `ToolPolicy` door ({@link RETIRED_TOOL_POLICY_KEYS}); `undefined` is no policy. */
50
- export declare function assertNoRetiredToolPolicyKeys(policy: object | undefined): void;
49
+ /**
50
+ * The `ToolPolicy` door — every policy the engine installs passes it (a task's or deployment's seat, a combined member, an inherited
51
+ * constraint): `undefined` (or `null`, the seat's other absence spelling) is no policy; a value that is not an object whose `check`
52
+ * is a function is no policy either, and refuses by name (`config.invalid_argument`, `detail.field: "ToolPolicy"`) — a policy the
53
+ * engine cannot call is never installed as if it were absent, which would run the leg with nothing the deployment meant to apply;
54
+ * a retired member refuses by name ({@link RETIRED_TOOL_POLICY_KEYS}).
55
+ */
56
+ export declare function assertNoRetiredToolPolicyKeys(policy: unknown): void;
51
57
  /** The `ShellReadBoundary` door ({@link RETIRED_SHELL_READ_BOUNDARY_KEYS}). */
52
58
  export declare function assertNoRetiredShellReadBoundaryKeys(boundary: object): void;
@@ -45,8 +45,19 @@ export const RETIRED_DECISION_MEMBERS = Object.freeze({
45
45
  guard: 'write `guards: ["<word>"]` instead (the same five words, as a list: one ask may stand on several integrity guards)',
46
46
  });
47
47
  export function assertNoRetiredToolPolicyKeys(policy) {
48
- if (policy !== undefined)
48
+ if (policy === undefined || policy === null)
49
+ return;
50
+ const isRecord = typeof policy === "object" && !Array.isArray(policy);
51
+ if (isRecord)
49
52
  assertNoRetiredKeys(policy, RETIRED_TOOL_POLICY_KEYS, "ToolPolicy");
53
+ const check = isRecord ? policy.check : undefined;
54
+ if (typeof check !== "function") {
55
+ const got = typeof policy !== "object" ? typeof policy : Array.isArray(policy) ? "an array" : `an object whose check is ${check === null ? "null" : typeof check}`;
56
+ const e = new Error(`ToolPolicy must be an object whose \`check\` is a function (got ${got}) — a policy the engine cannot call is refused rather than run as no policy; omit the seat for none.`);
57
+ e.code = "config.invalid_argument";
58
+ e.detail = { field: "ToolPolicy" };
59
+ throw e;
60
+ }
50
61
  }
51
62
  export function assertNoRetiredShellReadBoundaryKeys(boundary) {
52
63
  assertNoRetiredKeys(boundary, RETIRED_SHELL_READ_BOUNDARY_KEYS, "ShellReadBoundary");
@@ -1,11 +1,29 @@
1
1
  import { selectModel } from "./select-model.js";
2
2
  import { MODEL_ROLES } from "./types.js";
3
3
  export { MODEL_ROLES } from "./types.js";
4
+ function catalogModel(ref, models) {
5
+ const m = models != null && (typeof models === "object" || typeof models === "function") && Object.hasOwn(models, ref) ? models[ref] : undefined;
6
+ return m || undefined;
7
+ }
8
+ function modelBindingRefusal(seat, reason, message) {
9
+ const e = new Error(message);
10
+ e.code = "config.unknown_role";
11
+ e.detail = { field: seat, reason };
12
+ return e;
13
+ }
14
+ function bindingShown(value) {
15
+ if (typeof value === "string")
16
+ return JSON.stringify(value.length > 80 ? `${value.slice(0, 80)}…` : value);
17
+ return value === null ? "null" : Array.isArray(value) ? "a list" : `a ${typeof value}`;
18
+ }
19
+ function unknownModelRefRefusal(ref, seat) {
20
+ return modelBindingRefusal(seat, "unknown_model_ref", `Unknown model ref ${bindingShown(ref)} in ${seat}. Provide it in RunnerDeps.models or pass a Model object.`);
21
+ }
4
22
  export function resolveModel(ref, models) {
5
23
  if (typeof ref !== "string") {
6
24
  return ref;
7
25
  }
8
- const m = models !== undefined && Object.hasOwn(models, ref) ? models[ref] : undefined;
26
+ const m = catalogModel(ref, models);
9
27
  if (!m) {
10
28
  throw new Error(`Unknown model ref "${ref}". Provide it in RunnerDeps.models or pass a Model object.`);
11
29
  }
@@ -33,7 +51,19 @@ export function expandTiers(models, tiers) {
33
51
  out[TIER_ALIAS_VIA] = aliasVia;
34
52
  const bound = (name) => {
35
53
  const b = Object.hasOwn(tiers, name) ? tiers[name] : undefined;
36
- return b === undefined ? undefined : resolveModel(b, models);
54
+ if (b === undefined)
55
+ return undefined;
56
+ const seat = `tiers.${name}`;
57
+ if (typeof b === "string") {
58
+ const held = catalogModel(b, models);
59
+ if (held === undefined)
60
+ throw unknownModelRefRefusal(b, seat);
61
+ return held;
62
+ }
63
+ if (typeof b !== "object" || b === null || Array.isArray(b)) {
64
+ throw modelBindingRefusal(seat, "not_a_model_ref", `${seat} is ${bindingShown(b)}, not a model reference — a tier binds a catalog model name or a Model object.`);
65
+ }
66
+ return b;
37
67
  };
38
68
  const ownKey = (k) => models !== undefined && Object.hasOwn(models, k) && Boolean(models[k]);
39
69
  const landing = (t, self) => {
@@ -131,13 +161,16 @@ const FALLBACK = {
131
161
  classifier: ["summarize", "default"],
132
162
  consolidate: ["summarize"],
133
163
  };
134
- function specParts(s) {
164
+ function specParts(s, seat = "a role seat") {
135
165
  if (s == null) {
136
166
  return {};
137
167
  }
138
168
  if (typeof s === "string") {
139
169
  return { model: s };
140
170
  }
171
+ if (typeof s !== "object" && typeof s !== "function") {
172
+ throw modelBindingRefusal(seat, "not_a_model_ref", `${seat} is ${bindingShown(s)}, not a model reference — a role binds a catalog model name, a Model object or a { model, select, thinking, systemPrompt } record.`);
173
+ }
141
174
  if ("id" in s && "api" in s) {
142
175
  return { model: s };
143
176
  }
@@ -191,12 +224,17 @@ function mergeRoles(base, over) {
191
224
  }
192
225
  return out;
193
226
  }
194
- function partsModel(parts, models) {
227
+ function partsModel(parts, models, seat = "a role seat") {
195
228
  if (parts.model) {
196
229
  return resolveModel(parts.model, models);
197
230
  }
198
231
  if (parts.select) {
199
- return selectModel(models ?? {}, parts.select);
232
+ const needs = parts.select.needs;
233
+ if (needs != null && !Array.isArray(needs)) {
234
+ throw modelBindingRefusal(`${seat}.select`, "not_a_model_ref", `${seat}.select.needs is ${bindingShown(needs)}, not a list of capabilities (vision / reasoning).`);
235
+ }
236
+ const held = models != null && typeof models === "object" ? Object.values(models).filter(Boolean) : [];
237
+ return selectModel(held, parts.select);
200
238
  }
201
239
  return undefined;
202
240
  }
@@ -204,7 +242,7 @@ export function resolveTaskModel(spec, deps) {
204
242
  const start = spec.modelRole ?? "default";
205
243
  assertKnownRole(start, "modelRole");
206
244
  if (spec.model) {
207
- const startParts = specParts(mergeRoles(deps.roles, spec.roles)[start]);
245
+ const startParts = specParts(mergeRoles(deps.roles, spec.roles)[start], `roles.${start}`);
208
246
  return {
209
247
  model: resolveModel(spec.model, deps.models),
210
248
  modelRef: spec.model,
@@ -213,10 +251,10 @@ export function resolveTaskModel(spec, deps) {
213
251
  };
214
252
  }
215
253
  const merged = mergeRoles(deps.roles, spec.roles);
216
- const startSystemPrompt = specParts(merged[start]).systemPrompt;
254
+ const startSystemPrompt = specParts(merged[start], `roles.${start}`).systemPrompt;
217
255
  for (const role of [start, ...FALLBACK[start]]) {
218
- const parts = specParts(merged[role]);
219
- const fromParts = partsModel(parts, deps.models);
256
+ const parts = specParts(merged[role], `roles.${role}`);
257
+ const fromParts = partsModel(parts, deps.models, `roles.${role}`);
220
258
  const model = fromParts ?? roleTierModel(role, deps.models);
221
259
  if (model) {
222
260
  const modelRef = fromParts !== undefined ? (parts.model ?? model) : (ROLE_TIER_DEFAULTS[role] ?? model);
@@ -227,5 +265,17 @@ export function resolveTaskModel(spec, deps) {
227
265
  }
228
266
  export function roleModelIfSet(role, spec, deps) {
229
267
  const merged = mergeRoles(deps.roles, spec.roles);
230
- return partsModel(specParts(merged[role]), deps.models) ?? roleTierModel(role, deps.models);
268
+ const seat = `roles.${role}`;
269
+ const parts = specParts(merged[role], seat);
270
+ const named = parts.model;
271
+ if (named) {
272
+ if (typeof named === "string") {
273
+ if (catalogModel(named, deps.models) === undefined)
274
+ throw unknownModelRefRefusal(named, seat);
275
+ }
276
+ else if (typeof named !== "object" || Array.isArray(named)) {
277
+ throw modelBindingRefusal(seat, "not_a_model_ref", `${seat}.model is ${bindingShown(named)}, not a model reference — a role binds a catalog model name or a Model object.`);
278
+ }
279
+ }
280
+ return partsModel(parts, deps.models, seat) ?? roleTierModel(role, deps.models);
231
281
  }
@@ -65,7 +65,7 @@ export function normalizeOrgGateVerdict(answer, unreadable) {
65
65
  if ((behavior !== "deny" && behavior !== "ask") || typeof rule !== "string")
66
66
  return clean({ status: "unavailable", disclosures: [unreadable] });
67
67
  const reason = ownDataValue(verdict, "unreadable");
68
- const unreadableCell = behavior === "ask" && typeof reason === "string" ? { unreadable: reason } : {};
68
+ const unreadableCell = typeof reason === "string" ? { unreadable: reason } : {};
69
69
  return clean({ status: "available", verdict: clean({ behavior: behavior, rule, ...unreadableCell }), ...revisionCell });
70
70
  }
71
71
  function copiedDenial(raw, ruleTexts) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * What the approving person wrote on the card ({@link ResumeOutcome} `policy_ask` `allow` `feedback`) — the one place that
3
+ * says what counts as written, which decide may carry it, and how it joins the resume continuation.
4
+ *
5
+ * ONE READING of "did the person write anything" ({@link writtenFeedback}): the text with the whitespace the card's input does
6
+ * not keep removed, or nothing when that leaves nothing. The pre-CAS checks and the continuation both ask it, so they cannot
7
+ * disagree about a blank value. (A deny's `reason` keeps its own rule, `decidersNote`: only `""` is blank there.)
8
+ *
9
+ * The words ride the resume continuation user message — the first message after the batch's tool results, once the batch's
10
+ * unanswered siblings have settled — as a user-lane range of their own: outside every engine segment, so a derived view reads
11
+ * them as user speech; verbatim, no fence, no wrapper sentence, no length bound. They are owed with the rest of the
12
+ * continuation: when the batch parks again before the prompt lands they ride the new row as its last parked steer.
13
+ */
14
+ import { type PendingSteerEntry, type ResumeOutcome } from "../checkpoint-store.js";
15
+ type PolicyAsk = Extract<ResumeOutcome, {
16
+ gate: "policy_ask";
17
+ }>;
18
+ /** The words as the model is to read them, or `undefined` when nothing was written (absent, empty, whitespace-only). */
19
+ export declare function writtenFeedback(feedback: string | undefined): string | undefined;
20
+ /**
21
+ * Pre-CAS: a WRITTEN feedback is the allow arm's, a person's, and free of the `<system-reminder>` tag (opening or closing — the one
22
+ * judge `sanitizeUntrustedText`, the same rule a deny's `reason` is held to, applied here by the same call). Each is refused
23
+ * (`checkpoint.invalid_outcome`, the row stays pending for a well-formed decide) rather than dropped or delivered as something it
24
+ * is not: a deny's words are `reason`, and a machine's text must never reach the model as the user's. The twin is the captured
25
+ * plain data (its text already held to the string domain by the capture), never the caller's object.
26
+ */
27
+ export declare function refuseUnfitFeedback(twin: PolicyAsk): void;
28
+ /** The words this resume is to deliver: a person's `allow` carrying a written feedback (the pre-CAS checks have run), else `undefined`. */
29
+ export declare function approvalWordsOf(outcome: ResumeOutcome): string | undefined;
30
+ /**
31
+ * The continuation with the words as its last range. The separator glue before them is engine bytes (its own segment), so the
32
+ * stored message's user-lane range is exactly the words. Appends to `engineSegments` in place; the caller owns both.
33
+ */
34
+ export declare function withApprovalWords(continuation: string, engineSegments: Array<{
35
+ start: number;
36
+ end: number;
37
+ }>, words: string): string;
38
+ /**
39
+ * The words as the continuation's owed content: the last parked steer, an untrusted one (the carrier that exists for an owed instruction).
40
+ * The frame is remembered by identity ({@link isApprovalWordsFrame}) — never by a member, which the steer store would refuse as an unknown
41
+ * key — so the owed account can count it as what it is (`detail.kind: "approval_feedback"`) and tell a recoverable retry from a loss.
42
+ */
43
+ export declare function owedApprovalWords(words: string | undefined, inputId: string): Array<Omit<PendingSteerEntry, "seq">>;
44
+ /** Whether `frame` is one {@link owedApprovalWords} minted. */
45
+ export declare function isApprovalWordsFrame(frame: object): boolean;
46
+ export {};
@@ -0,0 +1,41 @@
1
+ import { CheckpointError } from "../checkpoint-store.js";
2
+ import { sanitizeUntrustedText, sentinelTagRefusal } from "../untrusted-text.js";
3
+ export function writtenFeedback(feedback) {
4
+ const trimmed = feedback?.trim();
5
+ return trimmed === undefined || trimmed === "" ? undefined : trimmed;
6
+ }
7
+ export function refuseUnfitFeedback(twin) {
8
+ const words = writtenFeedback(twin.feedback);
9
+ if (words === undefined)
10
+ return;
11
+ if (twin.decision !== "allow") {
12
+ throw new CheckpointError("checkpoint.invalid_outcome", "resume carried `feedback` on a `deny` — feedback is the approving person's words on an `allow` (a denial's words are `reason`); refusing rather than dropping it silently, the checkpoint stays pending");
13
+ }
14
+ const byPerson = twin.hostDecision.decidedBy === "person";
15
+ if (!byPerson) {
16
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carried \`feedback\` on a decide made by "${twin.hostDecision.decidedBy}" — feedback is delivered to the model as the user's own words, which only a person's decide can be; refusing rather than attributing a machine's text to the user, the checkpoint stays pending`);
17
+ }
18
+ if (sanitizeUntrustedText(words) !== words) {
19
+ throw new CheckpointError("checkpoint.invalid_outcome", sentinelTagRefusal("resume allow feedback"));
20
+ }
21
+ }
22
+ export function approvalWordsOf(outcome) {
23
+ return outcome.gate === "policy_ask" && outcome.decision === "allow" ? writtenFeedback(outcome.feedback) : undefined;
24
+ }
25
+ export function withApprovalWords(continuation, engineSegments, words) {
26
+ const glueStart = continuation.length;
27
+ const withGlue = `${continuation}\n\n`;
28
+ engineSegments.push({ start: glueStart, end: withGlue.length });
29
+ return withGlue + words;
30
+ }
31
+ const APPROVAL_WORDS_FRAMES = new WeakSet();
32
+ export function owedApprovalWords(words, inputId) {
33
+ if (words === undefined)
34
+ return [];
35
+ const frame = { text: words, trusted: false, inputId };
36
+ APPROVAL_WORDS_FRAMES.add(frame);
37
+ return [frame];
38
+ }
39
+ export function isApprovalWordsFrame(frame) {
40
+ return APPROVAL_WORDS_FRAMES.has(frame);
41
+ }
@@ -23,13 +23,13 @@ import { type CompactionSettings } from "../../internal/harness.js";
23
23
  * because reading it at the moment of the pass — not at prepare — is the whole of what it states.
24
24
  */
25
25
  /**
26
- * Opt-in working-file attachments (LONGRUN-2 / CC 198 post-compact restore parity).
26
+ * The post-compaction restore (LONGRUN-2; the reference's post-compact file restore, cli296.js:249523 `attachments`).
27
27
  *
28
28
  * CC parity: post-compact file re-read is DEFAULT ON (CC hard-codes it;
29
29
  * LONGRUN-2 measured ≈2.3 extra read round-trips per compaction without it). `false` opts out; an
30
30
  * object customizes caps. Selection set = most recently READ (CC readFileState semantics) with a
31
- * modified-files fallback inside maybeCompact. Returns `undefined` when attachments are off or the
32
- * task has no hands (no `readTaskFile` ⇒ nothing can be re-read).
31
+ * modified-files fallback inside maybeCompact. Returns `undefined` when the restore is off or the
32
+ * task has no hands (no `compactionRestore` ⇒ nothing can be re-read).
33
33
  */
34
34
  export declare function buildWorkingFileAttachments(spec: TaskSpec, prepared: Prepared): MaybeCompactOptions["workingFileAttachments"];
35
35
  /**
@@ -3,10 +3,10 @@ import { buildToolResultRef, OFFLOAD_TOOL_NAME, PERSISTED_OUTPUT_PREFIX, toolRes
3
3
  import { compactionEnabled, contextOverlayFor } from "../context-settings.js";
4
4
  import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
5
5
  export function buildWorkingFileAttachments(spec, prepared) {
6
- if (spec.compaction?.attachWorkingFiles === false || !prepared.readTaskFile)
6
+ if (spec.compaction?.attachWorkingFiles === false || !prepared.compactionRestore)
7
7
  return undefined;
8
8
  return {
9
- readFile: prepared.readTaskFile,
9
+ ...prepared.compactionRestore,
10
10
  ...(prepared.recentlyReadFiles ? { recentlyReadFiles: prepared.recentlyReadFiles } : {}),
11
11
  ...(prepared.normalizeAttachmentPath ? { normalizePath: prepared.normalizeAttachmentPath } : {}),
12
12
  ...(prepared.isDedupStubResult ? { isDedupStubResult: prepared.isDedupStubResult } : {}),