@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,11 +1,12 @@
1
1
  import { type AgentMessage, type CompactionSettings, type Session, type ThinkingLevel } from "../internal/harness.js";
2
- import type { Message, Model, Tool } from "../internal/llm.js";
2
+ import { type Message, type Model, type Tool } from "../internal/llm.js";
3
3
  import type { ContextWindowOverlay } from "./context-settings.js";
4
4
  import type { PostCompactContext, PreCompactContext, PreCompactResult } from "./hooks.js";
5
5
  import type { TraceEvent } from "./trace.js";
6
6
  import type { Brain } from "./types.js";
7
7
  import type { TaskEvent, TaskEventIdentity } from "./task-event.js";
8
8
  import type { CompactionEffect } from "./frame-settlement.js";
9
+ import type { MintedAttachmentFrame } from "./user-attachment.js";
9
10
  /**
10
11
  * Default summarization focus, applied when a task does not provide `compaction.instructions`.
11
12
  * Addresses the common fidelity failure where summaries drop specific facts.
@@ -101,6 +102,32 @@ export interface CompactionForkContext {
101
102
  * eligibility gate refuses a snapshot from a different model (see the engine-side twin's doc). */
102
103
  modelId?: string;
103
104
  }
105
+ /**
106
+ * What a compaction pass reports at the moment it commits ({@link MaybeCompactOptions.onStarted}) — and the
107
+ * whole content of its `compaction_started` wire frame. `pass` is minted HERE, by the one function every lane
108
+ * runs, so no lane can number its own passes; the frame that ends the pass carries the same value.
109
+ */
110
+ export interface CompactionPassStart {
111
+ readonly pass: string;
112
+ /** The trigger this pass runs under — the value it hands the `preCompact` hook. */
113
+ readonly trigger: "auto" | "manual" | "forced";
114
+ /** Engine clock, epoch ms, at the commit. */
115
+ readonly startedAt: number;
116
+ }
117
+ /** What a landed compaction asks its restore for ({@link MaybeCompactOptions.workingFileAttachments}). */
118
+ export interface CompactionRestoreAsk {
119
+ /** The files to re-read, most recent first — already without the ones the context the summary leaves still shows. */
120
+ readonly candidates: readonly string[];
121
+ readonly maxFiles: number;
122
+ /** What one file may deliver, in its own text (before the Read's line numbers — the unit `maxCharsPerFile` counts),
123
+ * and all of them together, in the file contents the frames send (line numbers included — the unit of the
124
+ * window's share); an image costs the engine estimate's fixed weight × `charsPerToken` in both. */
125
+ readonly perFileChars: number;
126
+ readonly totalChars: number;
127
+ readonly charsPerToken: number;
128
+ /** The reads the context the summary leaves still shows (canonical keys) — the restore dedups against them. */
129
+ readonly preserveKeys: readonly string[];
130
+ }
104
131
  export interface MaybeCompactOptions {
105
132
  /** Disclosure channel for CONTAINED host-callback failures in this scope (the RB-463
106
133
  * isolation primitive's bounded first-failure per site). Absent ⇒ failures are counted only. */
@@ -267,31 +294,26 @@ export interface MaybeCompactOptions {
267
294
  */
268
295
  forceUnderThreshold?: boolean;
269
296
  /**
270
- * Working-file attachments (LONGRUN-2, CC-compact parity review 2026-06-12): after a successful
271
- * summary, re-read up to `maxFiles` of the files the summarized region MODIFIED (from
272
- * CompactionDetails.modifiedFiles) and append their current on-disk contents to the summary text.
273
- * Why: LONGRUN-2 measured the model spending ≈2.3 extra read calls per compaction re-fetching
274
- * files it had just lost — each one a full request round-trip at post-compaction context size.
275
- * CC ships the same idea ("attachments", ≤5 files × ~5k tokens). Bounded: per-file char cap plus
276
- * a total cap that self-scales to ~15% of the model window, so small windows never drown in
277
- * attachments; the §25.2 freedTokens measurement naturally accounts for the added mass (the
278
- * anti-thrash floor stays truthful). Reader failures skip the file (best-effort, never fails the
279
- * compaction). Absent (default) = current behavior, paths-only.
280
- * Pick order (design/71 P2-① resolved the LONGRUN-2b limitation): `maxFiles` picks the most
281
- * RECENTLY touched files first (`CompactionDetails.modifiedFilesByRecency`, recency threaded
282
- * through the engine fileOps accumulator); alphabetical `modifiedFiles` remains the fallback for
283
- * details persisted before the field existed.
297
+ * The post-compaction restore: after a summary lands, re-read up to `maxFiles`
298
+ * of the files the conversation had read (or, without read tracking, modified) that the context the summary leaves
299
+ * no longer shows, and append each as an attachment frame — its own engine-note message after the compaction entry
300
+ * (summary, kept tail, restored files: the reference's post-compact order, cli296.js:249517 `hookResults`). Why: without it the model spends ≈2.3 extra read round-trips per
301
+ * compaction re-fetching what it just lost (LONGRUN-2). Bounded: a per-file cap in the file's own characters and a
302
+ * total, in the file contents the frames send (line numbers included), that self-scales to ~15% of the window; a
303
+ * file over either is a frame that names it and says it is too large, never part of its contents. The post measurement counts the frames, so the anti-thrash floor stays truthful. Absent ⇒ no restore.
304
+ * Pick order: most recently READ first (`recentlyReadFiles`), else most recently modified
305
+ * (`CompactionDetails.modifiedFilesByRecency`), else the alphabetical `modifiedFiles` of older details.
284
306
  */
285
307
  workingFileAttachments?: {
286
- /** Read a task workspace file; null/throw = skip it. Wired by the Runner from the task's ExecutionEnv. */
287
- readFile: (path: string) => Promise<string | null | {
288
- withheld: {
289
- pattern: string;
290
- };
291
- }>;
292
- /** Max modified files to attach. Default 3. */
308
+ /** Re-read the candidates and mint their frames — the engine's one read of a file for the model, deduplicated against
309
+ * the context the summary leaves (`preserveKeys` = the reads that context still shows). Side-effect free: each frame
310
+ * carries the commit its delivery owes, run here once the frame is in the transcript. Wired by the Runner. */
311
+ restore: (ask: CompactionRestoreAsk) => Promise<ReadonlyArray<MintedAttachmentFrame>>;
312
+ /** A minted restore frame the transcript would not take (not delivered, nothing committed) — the operator's line. */
313
+ undelivered?: (label: string, reason: string) => void;
314
+ /** Max files to restore. Default 3. */
293
315
  maxFiles?: number;
294
- /** Per-file char cap (≈4 chars/token). Default 16_000 (~4k tokens). */
316
+ /** Per-file cap in the file's own characters (≈4 chars/token). Default 16_000 (~4k tokens). */
295
317
  maxCharsPerFile?: number;
296
318
  /**
297
319
  * CC-parity selection set: the files the task has READ, most recent
@@ -313,9 +335,9 @@ export interface MaybeCompactOptions {
313
335
  instructionSourcePaths?: ReadonlyArray<string>;
314
336
  /**
315
337
  * `recentlyReadFiles` yields CANONICAL keys (resolveKey
316
- * output — the same coordinate `ReadFileState` is keyed in), but `instructionSourcePaths` and the
317
- * kept-tail scan inside `maybeCompact` (which reads raw tool-call arguments via
318
- * `extractFileOpsFromMessage`) do NOT share that coordinate — a model-typed relative path and its
338
+ * output — the same coordinate `ReadFileState` is keyed in, and the one an attachment frame's recorded
339
+ * read carries), but `instructionSourcePaths` and the kept tail's Read tool calls (raw model-typed
340
+ * arguments) do NOT share that coordinate — a model-typed relative path and its
319
341
  * canonical form are different strings, so a plain `Set.has()` between them silently never matches
320
342
  * even for the identical file. Optional callback (present exactly when there's a containment root
321
343
  * to resolve against — i.e. hands enabled) that canonicalizes a raw path the SAME way the hands
@@ -340,29 +362,18 @@ export interface MaybeCompactOptions {
340
362
  isDedupStubResult?: (resultText: string) => boolean;
341
363
  };
342
364
  /**
343
- * Fires ONCE per LANDED compaction (after `appendCompaction` committed the new baseline), with the
344
- * files that were attached WHOLE (untruncated) into the summary. The Runner wires this to the read-state
345
- * invalidation (`applyContextShrinkToReadFileState`): the summary just replaced the history the read-dedup
346
- * stubs point at, so stale entries must drop and the re-attached content re-registers. Kept OUTSIDE
347
- * `workingFileAttachments` deliberately — the invalidation is owed even when attachments are disabled
348
- * (an empty array then). Best-effort: a throwing callback must not fail the landed compaction.
365
+ * Fires ONCE per LANDED compaction (after `appendCompaction` committed the new baseline and the restore frames were
366
+ * appended), immediately followed by the delivered restore frames' commits — the read state's clear and the restore's
367
+ * entries land together, after the baseline; a compaction that does not land touches neither. The Runner wires this to
368
+ * the read-state invalidation (`applyContextShrinkToReadFileState`): the summary just replaced the history the
369
+ * read-dedup stubs point at. Kept OUTSIDE `workingFileAttachments` deliberately — the invalidation is owed even when
370
+ * the restore is off. Best-effort: a throwing callback must not fail the landed compaction.
349
371
  *
350
- * `preserveReadState`:
351
- * canonical-coordinate paths (same shape as `normalizePath`'s output) whose read-state entry must
352
- * survive the compaction's clear even though they are NOT in `attachedComplete` — every file
353
- * `maybeCompact` deliberately skipped re-attaching, for EITHER exclusion reason (kept-tail-visible,
354
- * or matches a declared instruction source). Without this the read-state clear (see
355
- * `applyContextShrinkToReadFileState`) would wipe them anyway, defeating both the read-before-edit gate
356
- * and the next read's dedup for exactly the files this optimization was supposed to leave untouched.
357
- * (The instruction-source half of this was missing in an earlier revision: `instructionSources` and
358
- * `seededFiles` are independent `ProjectMemoryLoad` fields — a path can be declared in the former
359
- * without ever being seeded via the latter, so an instruction-excluded file the model explicitly Read
360
- * has a live, non-seeded entry that needs the SAME preservation as a kept-tail one.)
372
+ * `preserveReadState`: canonical-coordinate paths whose entry survives the clear — every file the context the summary
373
+ * leaves still shows (a kept-tail read or attachment frame) or that a declared instruction source re-seeds; the restore
374
+ * deduplicates against the same set.
361
375
  */
362
- onApplied?: (attachedComplete: ReadonlyArray<{
363
- path: string;
364
- content: string;
365
- }>, preserveReadState?: ReadonlyArray<string>) => void;
376
+ onApplied?: (preserveReadState: ReadonlyArray<string>) => void;
366
377
  /**
367
378
  * #1036 — THE COMMIT-BEAT EFFECT SINK: what this compaction did to the transcript, reported
368
379
  * once, right after the `appendCompaction` that landed it. `summarizedEntryIds` = every entry id
@@ -432,7 +443,7 @@ export interface MaybeCompactOptions {
432
443
  * Inputs:
433
444
  * - `messagesToSummarize`: the message range that would be summarized this boundary.
434
445
  * - `previousSummary`: the prior compaction's summary (present on the UPDATE/iterative-merge path).
435
- * - `modifiedFiles`: files the summarized range modified, newest-first (same source the working-file
446
+ * - `modifiedFiles`: files the summarized range modified, newest-first (same source the restore's
436
447
  * attachments use), so a provider can decide whether the reused summary is still representative.
437
448
  */
438
449
  summaryProvider?: (input: {
@@ -482,6 +493,17 @@ export interface MaybeCompactOptions {
482
493
  * not optional there, so a preCompact `block` is IGNORED on "forced").
483
494
  */
484
495
  trigger?: "auto" | "manual" | "forced";
496
+ /**
497
+ * The pass has COMMITTED: the trigger gate passed, a valid cut point exists and no `preCompact` handler
498
+ * blocked it, and nothing has yet consulted the summary provider or called the model. Fired exactly once per
499
+ * pass — a retry inside the summary call is the same pass — and never for a pass that ends before this point
500
+ * (under threshold, held under the floor, disabled, nothing to compact, blocked, or cut by its own signal while
501
+ * the `preCompact` hook ran). A pass that fired it ends
502
+ * in `compacted: true` or a throw, so the caller that put a frame on the wire here owes the matching end.
503
+ * Observe-only: a throw — or the rejection of an `async` callback, which this seat does not await — is
504
+ * contained and never reaches the pass.
505
+ */
506
+ onStarted?: (start: CompactionPassStart) => void;
485
507
  /**
486
508
  * design/134 (CC PreCompact parity) — fired AFTER the trigger gate and a CONFIRMED valid cut point
487
509
  * (so every invocation corresponds to a compaction that would actually happen), BEFORE the
@@ -553,6 +575,10 @@ export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
553
575
  * nothing to do looked identical to one that never wanted to run; the caller's outcome event
554
576
  * (`compaction_outcome{outcome:"noop"}`) reads this flag. */
555
577
  noop?: true;
578
+ /** The id of the pass this result concludes — the value {@link MaybeCompactOptions.onStarted} was handed.
579
+ * Set exactly when `compacted` is true: every other return of this function ends before the pass commits,
580
+ * and a pass that commits and does not land THROWS (the caller learned the id from the start callback). */
581
+ pass?: string;
556
582
  tokensBefore?: number;
557
583
  /** The §25.2 STRUCTURAL pre/post delta in ONE coordinate — the only subtractable quantity here.
558
584
  * Absent ⇒ the post measurement failed and no delta was taken. */
@@ -594,7 +620,7 @@ export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
594
620
  * consult → clamp dry run + 门A/门B gating (everything before the summary call). `summaryMs` = the
595
621
  * summary LLM leg (`compact()`: serialization → clamp → completion, incl. any PTL retries) — ABSENT
596
622
  * on a provider-reuse pass (no LLM call happened; its provider consult time sits in `prepareMs`).
597
- * `persistMs` = working-file attachments → session rewrite (`appendCompaction`) → post measurement →
623
+ * `persistMs` = restore reads → session rewrite (`appendCompaction`) → restore frames → post measurement →
598
624
  * observers. The phases tile the pass exactly: prepareMs + (summaryMs ?? 0) + persistMs ===
599
625
  * durationMs. `ptlRetries` = reactive prompt-too-long retries burned inside this pass's summary
600
626
  * call(s) (both split-turn legs accumulate into the one counter; 0 on provider reuse). Set only when
@@ -669,6 +695,8 @@ export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
669
695
  /** What a landed compaction hands the wire frame. Structural, so both the full `maybeCompact` result
670
696
  * and the settle lane's narrower `EndOfTaskCompaction` satisfy it without either importing the other. */
671
697
  export interface CompactedFrameSource {
698
+ /** The pass this landing concludes (see {@link MaybeCompactOptions.onStarted}). */
699
+ pass?: string;
672
700
  tokensBefore?: number;
673
701
  freedTokens?: number;
674
702
  postTriggerTokens?: number;
@@ -703,6 +731,34 @@ export interface CompactedFrameSource {
703
731
  export declare function mintCompactedEvent(comp: CompactedFrameSource, trigger: "auto" | "manual" | "forced", identity: TaskEventIdentity): Extract<TaskEvent, {
704
732
  type: "compacted";
705
733
  }>;
734
+ /**
735
+ * THE mint of the `compaction_started` wire frame — the opening half of the pair whose closing half is the
736
+ * `compacted` frame above (or a `compaction_outcome` that carries the same `pass`). One function, three
737
+ * lanes, as the landing's: the frame is the start report itself plus the identity envelope, so a lane that
738
+ * wants to announce a pass can only announce the one `maybeCompact` committed.
739
+ */
740
+ export declare function mintCompactionStartedEvent(start: CompactionPassStart, identity: TaskEventIdentity): Extract<TaskEvent, {
741
+ type: "compaction_started";
742
+ }>;
743
+ /**
744
+ * One compaction pass's account on the wire: the `onStarted` seat to hand `maybeCompact`, and the id the frame
745
+ * that ENDS the pass must carry. A lane makes one per pass, hands `onStarted` to the pass, and spreads
746
+ * {@link CompactionPassReport.end} into every `compaction_outcome` the pass can end with after it committed — a
747
+ * pass that threw before it committed leaves it empty, which is exactly "no start was announced, so no `pass`
748
+ * names it". The landing needs no spread: `maybeCompact` returns the id and `mintCompactedEvent` reads it.
749
+ */
750
+ export interface CompactionPassReport {
751
+ readonly onStarted: (start: CompactionPassStart) => void;
752
+ /** The announced pass's id; `undefined` until a start went on the wire. */
753
+ readonly pass: string | undefined;
754
+ /** `{ pass }` once a start was announced, `{}` before — for the frame that ends the pass. */
755
+ readonly end: {
756
+ pass?: string;
757
+ };
758
+ }
759
+ export declare function compactionPassReport(sink: {
760
+ push(event: TaskEvent): void;
761
+ }, ident: () => TaskEventIdentity): CompactionPassReport;
706
762
  /**
707
763
  * How many cross-session message frames the summarized prefix held: the engine-note user messages
708
764
  * whose text is the drain's `<cross-session-message …>` envelope, before the kept tail begins. The
@@ -737,7 +793,7 @@ export declare function countSummarizedCrossSessionMessages(branch: ReadonlyArra
737
793
  * 5. (COMPACTION-LIVE-AUDIT D-1, evaluated BEFORE rule 3) a TRIM-FORCED landing with
738
794
  * `freedTokens ≤ 0` → negative-yield: BACKOFF ON. The trim layer deceives the usage-anchored
739
795
  * estimate down into the sawtooth trough (live: est 18807 on a ~55k session), the forced pass
740
- * finds little compressible history, and summary + working-file attachments make the transcript
796
+ * finds little compressible history, and summary + restore frames make the transcript
741
797
  * LARGER (live: 18807→44609, 12679→35214). `freedTokens` is the structural pre/post delta
742
798
  * (clamped ≥0, so ≤0 means "freed nothing") — orthogonal to rule 2/3's threshold test, which
743
799
  * misses this shape because the bloated post (44609) can still sit UNDER the threshold (44800),
@@ -1,11 +1,13 @@
1
1
  import { CROSS_SESSION_MESSAGE_TAG } from "../agents/cross-session-envelope.js";
2
- import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatFileOperations, formatPersistedOutputRefs, prepareCompaction, shouldCompact, } from "../internal/harness.js";
2
+ import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatFileOperations, formatPersistedOutputRefs, prepareCompaction, shouldCompact, uuidv7, } from "../internal/harness.js";
3
3
  import { fileArgPath } from "./tool-registry.js";
4
+ import { userAttachmentOf } from "../internal/llm.js";
4
5
  import { contextEditFrontier, resolveTriggerWindow } from "./context-edit.js";
5
6
  import { selectCompactionEpoch } from "../prompt-assembly/epoch.js";
6
7
  import { brainToRuntime } from "./runtime.js";
7
8
  import { defuseFenceMarkers, sanitizeUntrustedText } from "./untrusted-text.js";
8
- import { createSafeNotifier } from "./safe-notify.js";
9
+ import { createSafeNotifier, observeThenableRejection } from "./safe-notify.js";
10
+ import { thrownText } from "./thrown-value.js";
9
11
  export const DEFAULT_COMPACTION_INSTRUCTIONS = "Preserve verbatim every code, identifier, name, number, file path, error message, credential " +
10
12
  "reference, and explicit user instruction or decision mentioned anywhere in the conversation. " +
11
13
  "When in doubt, keep the specific detail rather than generalizing it.";
@@ -18,10 +20,6 @@ export function sanitizeCompactionInstructions(text) {
18
20
  ? cps.slice(0, COMPACTION_INSTRUCTIONS_MAX_CHARS).join("")
19
21
  : defused;
20
22
  }
21
- const WORKING_FILE_BREAKOUT_TAGS = ["working-file", "summary"];
22
- function escapeWorkingFilePathAttr(path) {
23
- return path.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/[\r\n]+/g, " ");
24
- }
25
23
  const COMPACTION_MANUAL_CANCEL_FLAG = "semaCompactionManualCancel";
26
24
  export function isCompactionManualCancel(err) {
27
25
  return (typeof err === "object" && err !== null && err[COMPACTION_MANUAL_CANCEL_FLAG] === true);
@@ -152,6 +150,12 @@ export async function maybeCompact(opts) {
152
150
  ? `${specInstructions}\n\n${hookSanitized}`
153
151
  : hookSanitized
154
152
  : specInstructions;
153
+ const signalAtCommit = opts.signal;
154
+ if (signalAtCommit?.aborted === true) {
155
+ throw Object.assign(new CompactionError("aborted", "compaction cancelled before the pass committed — nothing was summarized or persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
156
+ }
157
+ const pass = uuidv7();
158
+ notifier.notify(() => observeThenableRejection(opts.onStarted?.({ pass, trigger, startedAt: Date.now() }), notifier, "compaction.onStarted"), "compaction.onStarted");
155
159
  const maxReuse = opts.maxConsecutiveProviderReuse ?? 3;
156
160
  const forceRealSummary = maxReuse > 0 && (opts.consecutiveProviderReuse ?? 0) >= maxReuse;
157
161
  let providerSummary;
@@ -330,10 +334,15 @@ export async function maybeCompact(opts) {
330
334
  details = res.value.details;
331
335
  }
332
336
  const persistStartAt = summaryEndAt ?? Date.now();
333
- let summaryWithAttachments = summary;
334
- const attachedFiles = [];
335
- const attachedComplete = [];
337
+ let restoreFrames = [];
336
338
  const att = opts.workingFileAttachments;
339
+ const undelivered = (label, reason) => {
340
+ try {
341
+ att?.undelivered?.(label, reason);
342
+ }
343
+ catch {
344
+ }
345
+ };
337
346
  const recentlyRead = att?.recentlyReadFiles?.() ?? [];
338
347
  const rawCandidateFiles = recentlyRead.length > 0 ? recentlyRead : (details?.modifiedFilesByRecency ?? details?.modifiedFiles);
339
348
  const maxFilesForSelection = Math.max(1, att?.maxFiles ?? 3);
@@ -346,6 +355,9 @@ export async function maybeCompact(opts) {
346
355
  const dedupStub = att?.isDedupStubResult;
347
356
  for (let i = firstKeptIdx; i < branch.length; i++) {
348
357
  const entry = branch[i];
358
+ const frame = entry?.type === "message" ? userAttachmentOf(entry.message) : undefined;
359
+ if (frame?.observation !== undefined && (frame.outcome === "attached" || frame.outcome === "partial"))
360
+ keptTailReadPathsRaw.add(frame.observation.key);
349
361
  if (entry.type !== "message" || entry.message.role !== "assistant")
350
362
  continue;
351
363
  const msg = entry.message;
@@ -426,44 +438,13 @@ export async function maybeCompact(opts) {
426
438
  const maxFiles = maxFilesForSelection;
427
439
  const perFileCap = Math.max(200, att.maxCharsPerFile ?? 16_000);
428
440
  const windowShareChars = windowKnown ? Math.floor(window * 0.15) * cpt : Number.POSITIVE_INFINITY;
429
- let remaining = Math.min(maxFiles * perFileCap, windowShareChars);
430
- const blocks = [];
431
- for (const path of candidateFiles.slice(0, maxFiles)) {
432
- if (remaining <= 0)
433
- break;
434
- let content = null;
435
- let withheldPattern;
436
- try {
437
- const r = await att.readFile(path);
438
- if (typeof r === "string")
439
- content = r;
440
- else if (r !== null && typeof r === "object")
441
- withheldPattern = r.withheld.pattern;
442
- }
443
- catch {
444
- content = null;
445
- }
446
- if (withheldPattern !== undefined) {
447
- blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}" withheld="content withheld: matches the sensitive-path read deny list (pattern ${escapeWorkingFilePathAttr(withheldPattern)})"/>`);
448
- continue;
449
- }
450
- if (content === null)
451
- continue;
452
- const cap = Math.min(perFileCap, remaining);
453
- const wasClipped = content.length > cap;
454
- const clipped = wasClipped
455
- ? `${content.slice(0, cap)}\n[... ${content.length - cap} more characters truncated — read the file for the rest]`
456
- : content;
457
- remaining -= clipped.length;
458
- blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}">\n${sanitizeUntrustedText(clipped, WORKING_FILE_BREAKOUT_TAGS)}\n</working-file>`);
459
- attachedFiles.push({ path, chars: Math.min(content.length, cap), truncated: wasClipped });
460
- if (!wasClipped)
461
- attachedComplete.push({ path, content });
462
- }
463
- if (blocks.length > 0) {
464
- summaryWithAttachments +=
465
- "\n\nCurrent on-disk contents of files touched in the summarized work (re-read at compaction time — trust these over any excerpts in the summary above):\n" +
466
- blocks.join("\n");
441
+ try {
442
+ restoreFrames = await att.restore({ candidates: candidateFiles, maxFiles, perFileChars: perFileCap, totalChars: Math.min(maxFiles * perFileCap, windowShareChars), charsPerToken: cpt, preserveKeys: excludedReadStatePreserveKeys });
443
+ }
444
+ catch (err) {
445
+ restoreFrames = [];
446
+ for (const path of candidateFiles.slice(0, maxFiles))
447
+ undelivered(path, `the restore failed: ${thrownText(err)}`);
467
448
  }
468
449
  }
469
450
  let preChars = 0;
@@ -478,7 +459,7 @@ export async function maybeCompact(opts) {
478
459
  if (opts.signal?.aborted === true) {
479
460
  throw Object.assign(new Error("compaction cancelled before commit — the abort signal fired during the summary call; nothing was persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
480
461
  }
481
- await opts.session.appendCompaction(summaryWithAttachments, firstKeptEntryId, summaryTokensBefore, {
462
+ await opts.session.appendCompaction(summary, firstKeptEntryId, summaryTokensBefore, {
482
463
  ...(details ?? {}),
483
464
  ...(restatedEpoch !== undefined ? { promptEpoch: restatedEpoch } : {}),
484
465
  ...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
@@ -496,6 +477,26 @@ export async function maybeCompact(opts) {
496
477
  catch {
497
478
  }
498
479
  }
480
+ const delivered = [];
481
+ for (const f of restoreFrames) {
482
+ try {
483
+ await opts.session.appendMessage({ role: "user", content: [...f.seed.content], timestamp: Date.now(), provenance: "engine-note", attachment: f.seed.attachment });
484
+ delivered.push(f);
485
+ }
486
+ catch (err) {
487
+ for (const g of restoreFrames.slice(delivered.length))
488
+ undelivered(g.seed.attachment.label, `the transcript did not take the restore frame: ${thrownText(err)}`);
489
+ break;
490
+ }
491
+ }
492
+ try {
493
+ opts.onApplied?.(excludedReadStatePreserveKeys);
494
+ for (const f of delivered)
495
+ f.commit?.();
496
+ }
497
+ catch {
498
+ }
499
+ const attachedFiles = delivered.flatMap(({ seed: { attachment: a } }) => ((a.outcome === "attached" || a.outcome === "partial") && a.dataBlocks.end > a.dataBlocks.start ? [{ path: a.label, chars: a.chars, truncated: a.truncated }] : []));
499
500
  if (opts.gitRestate !== undefined) {
500
501
  try {
501
502
  await opts.gitRestate.land();
@@ -510,11 +511,6 @@ export async function maybeCompact(opts) {
510
511
  catch {
511
512
  }
512
513
  }
513
- try {
514
- opts.onApplied?.(attachedComplete, excludedReadStatePreserveKeys);
515
- }
516
- catch {
517
- }
518
514
  if (ca !== undefined && restatedEpoch !== undefined) {
519
515
  try {
520
516
  ca.apply(restatedEpoch.identity);
@@ -555,6 +551,7 @@ export async function maybeCompact(opts) {
555
551
  return {
556
552
  contextUsage,
557
553
  compacted: true,
554
+ pass,
558
555
  tokensBefore: summaryTokensBefore,
559
556
  freedTokens,
560
557
  triggerTokens: tokens,
@@ -585,6 +582,7 @@ export function mintCompactedEvent(comp, trigger, identity) {
585
582
  return {
586
583
  type: "compacted",
587
584
  trigger,
585
+ ...(comp.pass !== undefined ? { pass: comp.pass } : {}),
588
586
  tokensBefore: comp.tokensBefore ?? 0,
589
587
  ...(comp.freedTokens !== undefined ? { freedTokens: comp.freedTokens } : {}),
590
588
  ...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
@@ -601,6 +599,24 @@ export function mintCompactedEvent(comp, trigger, identity) {
601
599
  ...identity,
602
600
  };
603
601
  }
602
+ export function mintCompactionStartedEvent(start, identity) {
603
+ return { type: "compaction_started", trigger: start.trigger, pass: start.pass, startedAt: start.startedAt, ...identity };
604
+ }
605
+ export function compactionPassReport(sink, ident) {
606
+ let pass;
607
+ return {
608
+ onStarted: (start) => {
609
+ sink.push(mintCompactionStartedEvent(start, ident()));
610
+ pass = start.pass;
611
+ },
612
+ get pass() {
613
+ return pass;
614
+ },
615
+ get end() {
616
+ return pass !== undefined ? { pass } : {};
617
+ },
618
+ };
619
+ }
604
620
  export function countSummarizedCrossSessionMessages(branch, firstKeptEntryId) {
605
621
  const firstKeptIdx = branch.findIndex((e) => e.id === firstKeptEntryId);
606
622
  const summarized = firstKeptIdx >= 0 ? branch.slice(0, firstKeptIdx) : branch;
@@ -47,14 +47,16 @@ export interface DerivedDenyRule {
47
47
  * The effective READ-deny table as the classifier's deny lines — one line per row, the row's reach in the
48
48
  * engine's own `Read(...)` path-rule grammar.
49
49
  *
50
- * A read-deny row is a run of path SEGMENTS matched anywhere in a path — the entry itself and everything under
50
+ * An UNANCHORED read-deny row is a run of path SEGMENTS matched anywhere in a path — the entry itself and everything under
51
51
  * it — and its `*` never crosses a `/` (read-deny.ts: `.ssh` refuses `~/.ssh/id_rsa` and `/srv/x/.ssh/config`
52
52
  * alike; a `**` segment matches exactly one segment). The rule grammar reaches the path a pattern names and
53
53
  * everything under it, so the line is `//**` + `/` + the row with every run of stars written `*` (one segment,
54
54
  * as in the row): `.ssh` ⇒ `Read(//**\/.ssh)`, `**\/secrets/**` ⇒ `Read(//**\/*\/secrets/*)`, `*` (every
55
55
  * path) ⇒ `Read(//**\/*)`. A row that names no segment at all (`""`, `/`) is not stated. A HOME-ANCHORED row
56
56
  * (`~/<run>`, read-deny.ts `READ_DENY_HOME_WORD`) is the rule grammar's home word with the same reach:
57
- * `~/.sema/settings.json` ⇒ `Read(~/.sema/settings.json)`.
57
+ * `~/.sema/settings.json` ⇒ `Read(~/.sema/settings.json)`; a ROOTED row (`/etc/shadow`, anchored at the filesystem
58
+ * root) is the rule grammar's absolute path: `Read(//etc/shadow)`. Which of the three a row is, is the one judge's
59
+ * answer (`readDenyRowAnchor`), asked here as everywhere a row is read.
58
60
  *
59
61
  * The text of a row can come from a task (a spawned task's author included) and it is spliced into a trusted
60
62
  * prompt, so a row is stated as a rule and nothing else: every character the grammar or the prompt must not
@@ -1,7 +1,7 @@
1
1
  import { AUTO_MODE_DEFAULT_WINDOW_MAX_CHARS, AUTO_MODE_DEFAULT_WINDOW_MAX_ENTRIES, AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
2
2
  import { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./auto-mode-prompt-assets.js";
3
3
  import { cutEnginePrefix, cutEngineSegments, flattenableUserText } from "./untrusted-text.js";
4
- import { readDenyPatternRun } from "../tools/fs/read-deny.js";
4
+ import { readDenyPatternRun, readDenyRowAnchor } from "../tools/fs/read-deny.js";
5
5
  import { renderContentBlock, renderUserAttachmentView, userAttachmentOf } from "../internal/llm.js";
6
6
  export { AUTO_MODE_DEFAULTS_SENTINEL } from "./auto-mode-defaults.js";
7
7
  function mergeRuleSection(user, defaultsInner) {
@@ -36,12 +36,13 @@ export function readDenyClassifierRules(entries, rules = []) {
36
36
  out.push(line);
37
37
  };
38
38
  for (const { pattern, caseSensitive } of entries) {
39
- const { homeAnchored, run } = readDenyPatternRun(pattern);
39
+ const { run } = readDenyPatternRun(pattern);
40
40
  const segments = run.split("/").filter((seg) => seg.length > 0);
41
41
  if (segments.length === 0)
42
42
  continue;
43
43
  const stated = statedPattern(segments.join("/"));
44
- push({ rule: homeAnchored ? `Read(~/${stated})` : `Read(//**/${stated})`, ...(caseSensitive !== true && /[A-Za-z]/.test(stated) ? { anyCase: true } : {}) });
44
+ const anchor = readDenyRowAnchor(pattern);
45
+ push({ rule: anchor === "home" ? `Read(~/${stated})` : anchor === "root" ? `Read(//${stated})` : `Read(//**/${stated})`, ...(caseSensitive !== true && /[A-Za-z]/.test(stated) ? { anyCase: true } : {}) });
45
46
  }
46
47
  for (const { tool, pattern } of rules) {
47
48
  if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(tool) || pattern.length === 0)
@@ -1,3 +1,4 @@
1
+ import { type ReceiptFault } from "./mods/kernel/receive.js";
1
2
  /**
2
3
  * design/80 D-1 §2 (slice 1a.2) — the engine-only **vetted canonical serializer** used to mint a stable,
3
4
  * opaque {@link boundInputHashOf | boundInputHash} over a pending tool call's arguments.
@@ -60,3 +61,71 @@ export declare const UNCANONICALIZABLE = "<uncanonicalizable>";
60
61
  * The hex digest is fixed-length and opaque — exactly what the operator sees / the D-G signer signs.
61
62
  */
62
63
  export declare function boundInputHashOf(value: unknown): string;
64
+ /**
65
+ * The {@link boundInputHashOf} digest of `value` when an approval can be bound to it ({@link bindableValueOf}), else `undefined` —
66
+ * the digest a license is compared by, one only those bytes have. Over the bindable domain the canonical form is exact (no
67
+ * sentinel token stands for part of it), so the digest is the bytes; outside it there is none to give.
68
+ */
69
+ export declare function exactBoundInputHashOf(value: unknown): string | undefined;
70
+ /**
71
+ * Why a value is outside the bindable domain: its top is not the payload's shape, a refusal of the receipt it is read by, or one of
72
+ * JSON's own five — a number that is not finite, a hole in a list, a named member on a list, an `undefined` list element, one
73
+ * object met twice.
74
+ */
75
+ export type BindingFault = ReceiptFault | {
76
+ readonly kind: "not-container" | "not-finite" | "hole" | "list-member" | "undefined-element" | "shared";
77
+ readonly path: readonly string[];
78
+ };
79
+ /**
80
+ * THE judge of the BINDABLE domain — the values an approval can be bound to, and so the only values with an exact digest: plain
81
+ * JSON data (records, dense lists, strings, finite numbers, booleans, `null`; a record member that is `undefined` is kept as it
82
+ * is, as absent from JSON) under a top of the payload's own shape (`top`: a call's arguments are a RECORD — the default, and the
83
+ * only shape an exact digest is taken of; a question's answers are a LIST), a tree (no object met twice), at most 256 levels of
84
+ * containers. Read by the engine's one receipt of a caller's value in {@link BINDING_RECEIPT} (nothing of the caller's runs: a
85
+ * getter, a hidden or symbol-keyed member, a Proxy, a function, a symbol, a bigint, a Map / Set / Date / binary or any other kind
86
+ * of object, a cycle, nesting past the levels — each refused), then JSON's own five on the receipt's frozen copy. Ok ⇒ that copy.
87
+ */
88
+ export declare function bindableValueOf(value: unknown, top?: BindingTop): {
89
+ readonly ok: true;
90
+ readonly value: unknown;
91
+ } | {
92
+ readonly ok: false;
93
+ readonly fault: BindingFault;
94
+ };
95
+ /** The top a bound payload has: a call's arguments are a record, a question's answers a list. */
96
+ export type BindingTop = "record" | "list";
97
+ /** THE capture of a caller-supplied payload the engine persists and later compares (a durable decide's edit — a record —, a
98
+ * question's answers — a list): the judge's receipt ({@link bindableValueOf}) kept in the form a durable store's JSON round trip
99
+ * hands back ({@link jsonFormOf}). */
100
+ export declare function capturedJsonOf(value: unknown, top?: BindingTop): {
101
+ readonly ok: true;
102
+ readonly value: unknown;
103
+ } | {
104
+ readonly ok: false;
105
+ readonly fault: BindingFault;
106
+ };
107
+ /** One clause naming a binding fault, its path rooted at `args` (`value` is what the judge was given: for a value that is not
108
+ * data, its `typeof` is read at the fault's place). The text holds the value's own member names: contain it to show it. */
109
+ export declare function describeBindingFault(value: unknown, fault: BindingFault): string;
110
+ /**
111
+ * Recursion-depth ceiling. Makes {@link encode} TOTAL against the two ways a naive recursive walk throws:
112
+ * a CIRCULAR reference (infinite recursion → `Maximum call stack size`) and pathological DEEP nesting (a
113
+ * RangeError before the JS stack limit). At/below this depth a deterministic sentinel is emitted instead of
114
+ * recursing — so {@link boundInputHashOf} can never throw. This is load-bearing (MAJOR-4): digests are read after a remote
115
+ * VM is paused (the park's target mint compares one), where a throw would skip the unpause compensation and leak the VM.
116
+ * 256 is far below the engine's stack limit yet far deeper than any real tool-arg payload. It is also the bindable domain's
117
+ * bound ({@link BINDING_RECEIPT}), so no value with an exact digest meets the sentinel.
118
+ */
119
+ export declare const MAX_DEPTH = 256;
120
+ /** Canonical JSON (recursively sorted keys, no whitespace) — the erasure protocol's ONE comparison
121
+ * and hash basis: `selectHash` complains bind to it, and evidence rows are written/compared in this
122
+ * form so a re-serialized import (key order changed by a middle layer) still compares equal. The memory
123
+ * export bundle's section hashes are sha256 over it, and a backend that hashes or compares the same
124
+ * records calls this function — the bytes are the contract, so there is no second copy to keep in step.
125
+ *
126
+ * Not {@link canonicalize}: that one emits a type-tagged, length-prefixed form that is not JSON and is
127
+ * never compared across runtimes; this one emits JSON text for JSON-shaped input (an `undefined` member
128
+ * omitted, an `undefined` / function / symbol array element as `null`, a scalar through `JSON.stringify`,
129
+ * an object read by its own enumerable keys with no `toJSON` call). The two answer different questions
130
+ * and their outputs are not interchangeable. */
131
+ export declare function canonicalJsonStringify(value: unknown): string;