@sema-agent/server 1.322.0 → 2.0.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 (539) hide show
  1. package/dist/approval-hmac.d.ts +40 -0
  2. package/dist/approval-hmac.js +67 -0
  3. package/dist/approval.d.ts +31 -0
  4. package/dist/approval.js +72 -4
  5. package/dist/audit.d.ts +26 -0
  6. package/dist/audit.js +40 -1
  7. package/dist/auth-bridge.d.ts +10 -0
  8. package/dist/auth-bridge.js +47 -4
  9. package/dist/auth-keys.d.ts +19 -0
  10. package/dist/auth-keys.js +13 -0
  11. package/dist/bake-runner/main.d.ts +5 -0
  12. package/dist/bake-runner/main.js +43 -4
  13. package/dist/bake-runner/protocol.d.ts +57 -0
  14. package/dist/bake-runner/protocol.js +58 -3
  15. package/dist/bake-runner/runner.d.ts +54 -0
  16. package/dist/bake-runner/runner.js +87 -5
  17. package/dist/bench/l8/artifact.d.ts +63 -0
  18. package/dist/bench/l8/artifact.js +61 -2
  19. package/dist/bench/l8/escape.d.ts +36 -0
  20. package/dist/bench/l8/escape.js +25 -0
  21. package/dist/bench/l8/index.d.ts +14 -0
  22. package/dist/bench/l8/index.js +14 -0
  23. package/dist/bench/l8/probes.d.ts +74 -0
  24. package/dist/bench/l8/probes.js +60 -0
  25. package/dist/bench/l8/run-probes.d.ts +43 -0
  26. package/dist/bench/l8/run-probes.js +45 -2
  27. package/dist/bench/s1/arms.d.ts +126 -0
  28. package/dist/bench/s1/arms.js +188 -13
  29. package/dist/bench/s1/live-deps.d.ts +66 -0
  30. package/dist/bench/s1/live-deps.js +249 -11
  31. package/dist/bench/s1/oracle.d.ts +49 -0
  32. package/dist/bench/s1/oracle.js +36 -1
  33. package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
  34. package/dist/bench/s1/repair-oracle-adapter.js +30 -0
  35. package/dist/bench/s1/reviewer.d.ts +38 -0
  36. package/dist/bench/s1/reviewer.js +34 -0
  37. package/dist/bench/s1/row.d.ts +77 -0
  38. package/dist/bench/s1/row.js +62 -0
  39. package/dist/bench/s1/run-firm.d.ts +47 -0
  40. package/dist/bench/s1/run-firm.js +70 -4
  41. package/dist/bench/s1/runner-ctx.d.ts +71 -0
  42. package/dist/bench/s1/runner-ctx.js +36 -0
  43. package/dist/bench/s1/tasks.d.ts +108 -0
  44. package/dist/bench/s1/tasks.js +115 -0
  45. package/dist/boot-reclaim.d.ts +19 -0
  46. package/dist/boot-reclaim.js +20 -0
  47. package/dist/brain.d.ts +25 -0
  48. package/dist/brain.js +61 -1
  49. package/dist/budget.d.ts +66 -0
  50. package/dist/budget.js +128 -4
  51. package/dist/capabilities/builtin-tools.d.ts +3 -0
  52. package/dist/capabilities/builtin-tools.js +3 -0
  53. package/dist/capabilities/center-plugins.d.ts +17 -1
  54. package/dist/capabilities/center-plugins.js +45 -4
  55. package/dist/capabilities/center-prompts.d.ts +46 -0
  56. package/dist/capabilities/center-prompts.js +57 -3
  57. package/dist/capabilities/code-review-council.d.ts +28 -0
  58. package/dist/capabilities/code-review-council.js +40 -7
  59. package/dist/capabilities/collab-wire.d.ts +2 -0
  60. package/dist/capabilities/collab-workflows.d.ts +7 -0
  61. package/dist/capabilities/collab-workflows.js +44 -1
  62. package/dist/capabilities/oa-tools.d.ts +6 -0
  63. package/dist/capabilities/oa-tools.js +10 -0
  64. package/dist/capabilities/prompt.d.ts +14 -0
  65. package/dist/capabilities/prompt.js +14 -0
  66. package/dist/capabilities/prompts/code-review.d.ts +14 -0
  67. package/dist/capabilities/prompts/code-review.js +14 -0
  68. package/dist/capabilities/prompts/identity.d.ts +6 -0
  69. package/dist/capabilities/prompts/identity.js +6 -0
  70. package/dist/capabilities/prompts/team.d.ts +4 -0
  71. package/dist/capabilities/prompts/team.js +4 -0
  72. package/dist/capabilities/repo-tools.d.ts +11 -0
  73. package/dist/capabilities/repo-tools.js +8 -0
  74. package/dist/capabilities/sandbox-file-send.d.ts +95 -0
  75. package/dist/capabilities/sandbox-file-send.js +91 -3
  76. package/dist/capabilities/scenarios.d.ts +130 -0
  77. package/dist/capabilities/scenarios.js +180 -7
  78. package/dist/capabilities/select-environment-tool.d.ts +7 -0
  79. package/dist/capabilities/select-environment-tool.js +30 -0
  80. package/dist/capabilities/send-user-file-tool.d.ts +33 -0
  81. package/dist/capabilities/send-user-file-tool.js +65 -1
  82. package/dist/capabilities/skills.d.ts +29 -0
  83. package/dist/capabilities/skills.js +13 -2
  84. package/dist/capabilities/team.d.ts +22 -0
  85. package/dist/capabilities/team.js +22 -2
  86. package/dist/capabilities/tool-defer.d.ts +26 -0
  87. package/dist/capabilities/tool-defer.js +3 -0
  88. package/dist/config-center/apply-effective.d.ts +65 -0
  89. package/dist/config-center/apply-effective.js +204 -12
  90. package/dist/config-center/http-client.d.ts +34 -0
  91. package/dist/config-center/http-client.js +68 -0
  92. package/dist/config-center/restart-signal.d.ts +22 -0
  93. package/dist/config-center/restart-signal.js +39 -0
  94. package/dist/config-center/skills-mcp.d.ts +20 -1
  95. package/dist/config-center/skills-mcp.js +42 -7
  96. package/dist/config-center/types.d.ts +92 -1
  97. package/dist/config-lkg.d.ts +12 -0
  98. package/dist/config-lkg.js +41 -0
  99. package/dist/config-provider.d.ts +78 -0
  100. package/dist/config-provider.js +165 -2
  101. package/dist/config-types.d.ts +546 -0
  102. package/dist/config.d.ts +64 -0
  103. package/dist/config.js +455 -60
  104. package/dist/degenerate-instrument.d.ts +38 -0
  105. package/dist/degenerate-instrument.js +55 -3
  106. package/dist/elicitation.d.ts +39 -0
  107. package/dist/elicitation.js +87 -3
  108. package/dist/env-facts.d.ts +61 -0
  109. package/dist/env-facts.js +73 -7
  110. package/dist/fleet/fleet-bus.d.ts +185 -0
  111. package/dist/fleet/fleet-bus.js +333 -27
  112. package/dist/fleet/subagent-tail-bus.d.ts +11 -0
  113. package/dist/fleet/subagent-tail-bus.js +33 -2
  114. package/dist/fleet-client.d.ts +38 -0
  115. package/dist/fleet-client.js +44 -9
  116. package/dist/fleet-lease.d.ts +46 -0
  117. package/dist/fleet-lease.js +73 -5
  118. package/dist/hooks/branch-transcript.d.ts +8 -0
  119. package/dist/hooks/branch-transcript.js +33 -0
  120. package/dist/hooks/cc-stop-prompt.d.ts +42 -0
  121. package/dist/hooks/cc-stop-prompt.js +43 -1
  122. package/dist/hooks/hook-llm.d.ts +15 -0
  123. package/dist/hooks/hook-llm.js +47 -0
  124. package/dist/hooks/hook-runner.d.ts +89 -0
  125. package/dist/hooks/hook-runner.js +327 -18
  126. package/dist/http/idempotency.d.ts +31 -0
  127. package/dist/http/idempotency.js +34 -0
  128. package/dist/http/principal-gate.d.ts +17 -0
  129. package/dist/http/principal-gate.js +35 -3
  130. package/dist/http/route-ctx.d.ts +36 -0
  131. package/dist/http/routes/approvals-assistant.d.ts +25 -0
  132. package/dist/http/routes/approvals-assistant.js +253 -23
  133. package/dist/http/routes/attachments.js +22 -4
  134. package/dist/http/routes/capabilities.d.ts +7 -0
  135. package/dist/http/routes/capabilities.js +202 -1
  136. package/dist/http/routes/fleet.d.ts +6 -0
  137. package/dist/http/routes/fleet.js +94 -5
  138. package/dist/http/routes/images.d.ts +17 -0
  139. package/dist/http/routes/images.js +222 -14
  140. package/dist/http/routes/leader.d.ts +7 -0
  141. package/dist/http/routes/leader.js +10 -0
  142. package/dist/http/routes/memory-policy.d.ts +6 -0
  143. package/dist/http/routes/memory-policy.js +38 -6
  144. package/dist/http/routes/notify-wake.d.ts +10 -0
  145. package/dist/http/routes/notify-wake.js +19 -3
  146. package/dist/http/routes/observability.d.ts +6 -0
  147. package/dist/http/routes/observability.js +19 -2
  148. package/dist/http/routes/runs.d.ts +15 -0
  149. package/dist/http/routes/runs.js +464 -41
  150. package/dist/http/routes/session-sync.d.ts +15 -0
  151. package/dist/http/routes/session-sync.js +229 -19
  152. package/dist/http/routes/sessions-list.d.ts +6 -0
  153. package/dist/http/routes/sessions-list.js +35 -4
  154. package/dist/http/routes/sessions.d.ts +14 -0
  155. package/dist/http/routes/sessions.js +176 -31
  156. package/dist/http/routes/side-query.d.ts +8 -0
  157. package/dist/http/routes/side-query.js +26 -1
  158. package/dist/http/routes/tasks.d.ts +18 -0
  159. package/dist/http/routes/tasks.js +501 -38
  160. package/dist/http/routes/trace-usage.d.ts +10 -0
  161. package/dist/http/routes/trace-usage.js +66 -14
  162. package/dist/http/routes/workflows.d.ts +12 -0
  163. package/dist/http/routes/workflows.js +180 -19
  164. package/dist/http/run-meta.d.ts +11 -0
  165. package/dist/http/run-meta.js +6 -0
  166. package/dist/http/send.d.ts +19 -0
  167. package/dist/http/send.js +11 -0
  168. package/dist/http/server.d.ts +255 -0
  169. package/dist/http/server.js +921 -75
  170. package/dist/http/sse-log.d.ts +15 -0
  171. package/dist/http/sse-log.js +16 -0
  172. package/dist/http/tar.d.ts +9 -0
  173. package/dist/http/tar.js +21 -5
  174. package/dist/http/wire-gate.d.ts +9 -0
  175. package/dist/http/wire-gate.js +9 -0
  176. package/dist/http/wire-types.d.ts +187 -0
  177. package/dist/http/workspace-content.d.ts +8 -0
  178. package/dist/http/workspace-content.js +10 -0
  179. package/dist/images/bake-validate.d.ts +63 -0
  180. package/dist/images/bake-validate.js +71 -1
  181. package/dist/images/manifest.d.ts +19 -0
  182. package/dist/images/manifest.js +6 -1
  183. package/dist/index.d.ts +1 -5
  184. package/dist/index.js +10 -5
  185. package/dist/key-resolver.d.ts +23 -0
  186. package/dist/key-resolver.js +27 -2
  187. package/dist/leader/diffout.d.ts +23 -0
  188. package/dist/leader/diffout.js +8 -1
  189. package/dist/leader/diffup.d.ts +32 -0
  190. package/dist/leader/diffup.js +43 -0
  191. package/dist/leader/endpoint.d.ts +20 -0
  192. package/dist/leader/endpoint.js +25 -2
  193. package/dist/leader/fanout.d.ts +60 -0
  194. package/dist/leader/fanout.js +29 -3
  195. package/dist/leader/grader-env-factory.d.ts +74 -0
  196. package/dist/leader/grader-env-factory.js +62 -3
  197. package/dist/leader/leader.d.ts +97 -0
  198. package/dist/leader/leader.js +151 -4
  199. package/dist/leader/merge.d.ts +94 -0
  200. package/dist/leader/merge.js +112 -9
  201. package/dist/leader/planner.d.ts +52 -0
  202. package/dist/leader/planner.js +47 -3
  203. package/dist/leader/repair-oracle.d.ts +61 -0
  204. package/dist/leader/repair-oracle.js +59 -1
  205. package/dist/leader/repair-wire.d.ts +78 -0
  206. package/dist/leader/repair-wire.js +79 -2
  207. package/dist/leader/wire.d.ts +79 -0
  208. package/dist/leader/wire.js +255 -8
  209. package/dist/lsp/e2b-bridge.d.ts +16 -0
  210. package/dist/lsp/e2b-bridge.js +32 -4
  211. package/dist/lsp/e2b-manager.d.ts +10 -0
  212. package/dist/lsp/e2b-manager.js +18 -2
  213. package/dist/lsp/lsp-frames.d.ts +12 -0
  214. package/dist/lsp/lsp-frames.js +12 -0
  215. package/dist/lsp/manager.d.ts +12 -0
  216. package/dist/lsp/manager.js +35 -2
  217. package/dist/lsp/types.d.ts +10 -0
  218. package/dist/lsp/ws-transport.d.ts +12 -0
  219. package/dist/lsp/ws-transport.js +53 -5
  220. package/dist/lsp-evict.d.ts +14 -0
  221. package/dist/lsp-evict.js +15 -1
  222. package/dist/main.js +2132 -96
  223. package/dist/memory-export.d.ts +1 -0
  224. package/dist/memory-export.js +4 -0
  225. package/dist/memory-scope.d.ts +49 -0
  226. package/dist/memory-scope.js +87 -4
  227. package/dist/memory-sync-client.d.ts +24 -0
  228. package/dist/memory-sync-client.js +41 -2
  229. package/dist/memory-sync.d.ts +23 -0
  230. package/dist/memory-sync.js +70 -1
  231. package/dist/model-select.d.ts +39 -0
  232. package/dist/model-select.js +42 -1
  233. package/dist/observability/cost-quota.d.ts +20 -0
  234. package/dist/observability/cost-quota.js +4 -0
  235. package/dist/observability/cost-taxonomy.d.ts +45 -0
  236. package/dist/observability/cost-taxonomy.js +34 -0
  237. package/dist/observability/logger.d.ts +1 -0
  238. package/dist/observability/logger.js +6 -0
  239. package/dist/observability/metrics.d.ts +13 -0
  240. package/dist/observability/metrics.js +63 -0
  241. package/dist/observability/otel-exporter.d.ts +9 -0
  242. package/dist/observability/otel-exporter.js +11 -3
  243. package/dist/observability/principal-context.d.ts +9 -0
  244. package/dist/observability/principal-context.js +9 -0
  245. package/dist/observability/prompt-manifest.d.ts +37 -0
  246. package/dist/observability/prompt-manifest.js +35 -1
  247. package/dist/observability/rate-limit.d.ts +10 -0
  248. package/dist/observability/rate-limit.js +1 -0
  249. package/dist/observability/tool-trace.d.ts +24 -0
  250. package/dist/observability/tool-trace.js +41 -1
  251. package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
  252. package/dist/orchestration/hardened-vm-runner.js +119 -4
  253. package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
  254. package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
  255. package/dist/orchestration/hardened-vm-worker.js +27 -0
  256. package/dist/orchestration/subagent-steer.d.ts +44 -0
  257. package/dist/orchestration/subagent-steer.js +45 -1
  258. package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
  259. package/dist/orchestration/workflow-agent-steer.js +80 -1
  260. package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
  261. package/dist/orchestration/workflow-completion-inbox.js +283 -32
  262. package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
  263. package/dist/orchestration/workflow-notify-journal.js +187 -13
  264. package/dist/parked-decide.d.ts +61 -0
  265. package/dist/parked-decide.js +58 -1
  266. package/dist/per-task-image.d.ts +54 -0
  267. package/dist/per-task-image.js +41 -0
  268. package/dist/plan-cache-probe.d.ts +35 -0
  269. package/dist/plan-cache-probe.js +18 -3
  270. package/dist/plugins/approval-exemption-store.d.ts +7 -0
  271. package/dist/plugins/approval-exemption-store.js +28 -2
  272. package/dist/plugins/approval-store-sql.d.ts +64 -0
  273. package/dist/plugins/approval-store-sql.js +32 -0
  274. package/dist/plugins/background-agent-store-sql.d.ts +55 -0
  275. package/dist/plugins/background-agent-store-sql.js +91 -4
  276. package/dist/plugins/background-shell-support.d.ts +98 -0
  277. package/dist/plugins/background-shell-support.js +117 -13
  278. package/dist/plugins/blob-backend.d.ts +104 -0
  279. package/dist/plugins/blob-backend.js +163 -6
  280. package/dist/plugins/breaker-state-sql.d.ts +65 -1
  281. package/dist/plugins/breaker-state-sql.js +46 -8
  282. package/dist/plugins/caching-session-store.d.ts +42 -0
  283. package/dist/plugins/caching-session-store.js +63 -3
  284. package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
  285. package/dist/plugins/checkpoint-store-sql.js +239 -11
  286. package/dist/plugins/file-outcome-sink.d.ts +4 -0
  287. package/dist/plugins/file-outcome-sink.js +9 -0
  288. package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
  289. package/dist/plugins/file-resume-anchor-store.js +43 -4
  290. package/dist/plugins/file-run-store.d.ts +90 -0
  291. package/dist/plugins/file-run-store.js +145 -13
  292. package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
  293. package/dist/plugins/file-snapshot-store-sql.js +178 -10
  294. package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
  295. package/dist/plugins/file-workflow-journal-store.js +10 -0
  296. package/dist/plugins/fork-routing-session-store.d.ts +47 -0
  297. package/dist/plugins/fork-routing-session-store.js +51 -3
  298. package/dist/plugins/host-platform.d.ts +69 -0
  299. package/dist/plugins/host-platform.js +81 -2
  300. package/dist/plugins/image-bake-store-sql.d.ts +136 -1
  301. package/dist/plugins/image-bake-store-sql.js +241 -7
  302. package/dist/plugins/image-index-sql.d.ts +46 -0
  303. package/dist/plugins/image-index-sql.js +113 -4
  304. package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
  305. package/dist/plugins/k8s-bg-scripts.js +88 -4
  306. package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
  307. package/dist/plugins/k8s-exec-protocol.js +41 -0
  308. package/dist/plugins/local-checkpoint-store.d.ts +40 -0
  309. package/dist/plugins/local-checkpoint-store.js +86 -7
  310. package/dist/plugins/local-session-store.d.ts +157 -0
  311. package/dist/plugins/local-session-store.js +289 -34
  312. package/dist/plugins/local-task-attachment-store.js +14 -2
  313. package/dist/plugins/mailbox-store-sql.d.ts +38 -0
  314. package/dist/plugins/mailbox-store-sql.js +39 -6
  315. package/dist/plugins/memory-engine-pg.d.ts +30 -0
  316. package/dist/plugins/memory-engine-pg.js +122 -4
  317. package/dist/plugins/memory-engine-tidb.d.ts +26 -0
  318. package/dist/plugins/memory-engine-tidb.js +115 -5
  319. package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
  320. package/dist/plugins/memory-engine-vector-util.js +5 -0
  321. package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
  322. package/dist/plugins/memory-resume-anchor-store.js +17 -0
  323. package/dist/plugins/memory-run-store.d.ts +63 -0
  324. package/dist/plugins/memory-run-store.js +73 -8
  325. package/dist/plugins/memory-session-policy-store.d.ts +17 -0
  326. package/dist/plugins/memory-session-policy-store.js +17 -0
  327. package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
  328. package/dist/plugins/memory-sync-store-pg.js +45 -2
  329. package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
  330. package/dist/plugins/memory-sync-store-tidb.js +31 -1
  331. package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
  332. package/dist/plugins/outcome-ledger-sql.js +97 -3
  333. package/dist/plugins/pg-approval-store.d.ts +7 -0
  334. package/dist/plugins/pg-approval-store.js +7 -0
  335. package/dist/plugins/pg-breaker-state.d.ts +6 -0
  336. package/dist/plugins/pg-breaker-state.js +6 -0
  337. package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
  338. package/dist/plugins/pg-checkpoint-store.js +8 -0
  339. package/dist/plugins/pg-cost-quota.d.ts +17 -0
  340. package/dist/plugins/pg-cost-quota.js +6 -0
  341. package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
  342. package/dist/plugins/pg-file-snapshot-store.js +6 -0
  343. package/dist/plugins/pg-image-bake.d.ts +9 -0
  344. package/dist/plugins/pg-image-bake.js +9 -0
  345. package/dist/plugins/pg-image-index.d.ts +9 -0
  346. package/dist/plugins/pg-image-index.js +9 -0
  347. package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
  348. package/dist/plugins/pg-outcome-ledger.js +9 -0
  349. package/dist/plugins/pg-pool.d.ts +50 -0
  350. package/dist/plugins/pg-pool.js +78 -0
  351. package/dist/plugins/pg-query.d.ts +11 -0
  352. package/dist/plugins/pg-rate-limiter.d.ts +23 -0
  353. package/dist/plugins/pg-rate-limiter.js +12 -2
  354. package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
  355. package/dist/plugins/pg-resume-anchor-store.js +5 -0
  356. package/dist/plugins/pg-run-store.d.ts +7 -0
  357. package/dist/plugins/pg-run-store.js +7 -0
  358. package/dist/plugins/pg-safe-json.d.ts +13 -0
  359. package/dist/plugins/pg-safe-json.js +40 -4
  360. package/dist/plugins/pg-session-policy-store.d.ts +5 -0
  361. package/dist/plugins/pg-session-policy-store.js +5 -0
  362. package/dist/plugins/pg-session-storage.d.ts +72 -0
  363. package/dist/plugins/pg-session-storage.js +152 -20
  364. package/dist/plugins/pg-session-store.d.ts +10 -0
  365. package/dist/plugins/pg-session-store.js +10 -0
  366. package/dist/plugins/pg-tool-result-store.d.ts +7 -0
  367. package/dist/plugins/pg-tool-result-store.js +7 -0
  368. package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
  369. package/dist/plugins/pg-workflow-journal-store.js +7 -0
  370. package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
  371. package/dist/plugins/pg-workflow-run-store.js +7 -0
  372. package/dist/plugins/posix-shell-fs.d.ts +3 -0
  373. package/dist/plugins/posix-shell-fs.js +31 -1
  374. package/dist/plugins/remote-env-adb.d.ts +25 -0
  375. package/dist/plugins/remote-env-adb.js +95 -12
  376. package/dist/plugins/remote-env-e2b.d.ts +146 -0
  377. package/dist/plugins/remote-env-e2b.js +371 -40
  378. package/dist/plugins/remote-env-file-error.d.ts +34 -0
  379. package/dist/plugins/remote-env-file-error.js +37 -0
  380. package/dist/plugins/remote-env-host.d.ts +151 -0
  381. package/dist/plugins/remote-env-host.js +468 -70
  382. package/dist/plugins/remote-env-k8s.d.ts +135 -0
  383. package/dist/plugins/remote-env-k8s.js +318 -32
  384. package/dist/plugins/remote-env-local-docker.d.ts +65 -0
  385. package/dist/plugins/remote-env-local-docker.js +149 -24
  386. package/dist/plugins/remote-env-ssh.d.ts +35 -0
  387. package/dist/plugins/remote-env-ssh.js +117 -20
  388. package/dist/plugins/remote-scratchpad.d.ts +38 -0
  389. package/dist/plugins/remote-scratchpad.js +32 -2
  390. package/dist/plugins/remote-shell.d.ts +22 -0
  391. package/dist/plugins/remote-shell.js +27 -1
  392. package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
  393. package/dist/plugins/resume-anchor-store-sql.js +11 -0
  394. package/dist/plugins/roster-store-sql.d.ts +27 -0
  395. package/dist/plugins/roster-store-sql.js +54 -0
  396. package/dist/plugins/run-store-sql.d.ts +194 -0
  397. package/dist/plugins/run-store-sql.js +210 -9
  398. package/dist/plugins/s3-presign.d.ts +20 -0
  399. package/dist/plugins/s3-presign.js +49 -2
  400. package/dist/plugins/scheduler-support.d.ts +12 -0
  401. package/dist/plugins/scheduler-support.js +75 -3
  402. package/dist/plugins/send-file-ledger.d.ts +15 -0
  403. package/dist/plugins/send-file-ledger.js +53 -4
  404. package/dist/plugins/send-user-file.d.ts +77 -0
  405. package/dist/plugins/send-user-file.js +92 -4
  406. package/dist/plugins/session-policy-store-sql.d.ts +15 -0
  407. package/dist/plugins/session-policy-store-sql.js +75 -3
  408. package/dist/plugins/session-store.d.ts +45 -0
  409. package/dist/plugins/session-store.js +51 -0
  410. package/dist/plugins/sql-driver.d.ts +77 -0
  411. package/dist/plugins/sql-driver.js +4 -0
  412. package/dist/plugins/sql-escape.d.ts +8 -0
  413. package/dist/plugins/sql-escape.js +8 -0
  414. package/dist/plugins/sql-row-helpers.d.ts +25 -0
  415. package/dist/plugins/sql-row-helpers.js +25 -0
  416. package/dist/plugins/store-backend.d.ts +103 -0
  417. package/dist/plugins/store-backend.js +120 -27
  418. package/dist/plugins/store-contracts.d.ts +62 -0
  419. package/dist/plugins/store-contracts.js +3 -0
  420. package/dist/plugins/task-attachment-store.d.ts +60 -0
  421. package/dist/plugins/task-attachment-store.js +49 -3
  422. package/dist/plugins/task-list-store-sql.d.ts +25 -0
  423. package/dist/plugins/task-list-store-sql.js +21 -0
  424. package/dist/plugins/tidb-approval-store.d.ts +6 -0
  425. package/dist/plugins/tidb-approval-store.js +6 -0
  426. package/dist/plugins/tidb-breaker-state.d.ts +5 -0
  427. package/dist/plugins/tidb-breaker-state.js +5 -0
  428. package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
  429. package/dist/plugins/tidb-checkpoint-store.js +7 -0
  430. package/dist/plugins/tidb-cost-quota.d.ts +6 -0
  431. package/dist/plugins/tidb-cost-quota.js +1 -0
  432. package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
  433. package/dist/plugins/tidb-file-snapshot-store.js +6 -0
  434. package/dist/plugins/tidb-image-bake.d.ts +8 -0
  435. package/dist/plugins/tidb-image-bake.js +8 -0
  436. package/dist/plugins/tidb-image-index.d.ts +8 -0
  437. package/dist/plugins/tidb-image-index.js +8 -0
  438. package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
  439. package/dist/plugins/tidb-outcome-ledger.js +10 -0
  440. package/dist/plugins/tidb-pool.d.ts +56 -0
  441. package/dist/plugins/tidb-pool.js +181 -1
  442. package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
  443. package/dist/plugins/tidb-rate-limiter.js +5 -2
  444. package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
  445. package/dist/plugins/tidb-resume-anchor-store.js +5 -0
  446. package/dist/plugins/tidb-run-store.d.ts +7 -0
  447. package/dist/plugins/tidb-run-store.js +7 -0
  448. package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
  449. package/dist/plugins/tidb-session-policy-store.js +5 -0
  450. package/dist/plugins/tidb-session-storage.d.ts +43 -0
  451. package/dist/plugins/tidb-session-storage.js +62 -3
  452. package/dist/plugins/tidb-session-store.d.ts +142 -0
  453. package/dist/plugins/tidb-session-store.js +264 -16
  454. package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
  455. package/dist/plugins/tidb-tool-result-store.js +7 -0
  456. package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
  457. package/dist/plugins/tidb-workflow-journal-store.js +7 -0
  458. package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
  459. package/dist/plugins/tidb-workflow-run-store.js +8 -0
  460. package/dist/plugins/tool-result-store-sql.d.ts +58 -0
  461. package/dist/plugins/tool-result-store-sql.js +38 -2
  462. package/dist/plugins/web-search.d.ts +35 -0
  463. package/dist/plugins/web-search.js +45 -9
  464. package/dist/plugins/workflow-journal-limits.d.ts +10 -0
  465. package/dist/plugins/workflow-journal-limits.js +10 -0
  466. package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
  467. package/dist/plugins/workflow-journal-store-sql.js +59 -3
  468. package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
  469. package/dist/plugins/workflow-run-store-sql.js +84 -6
  470. package/dist/plugins/worktree-isolation.d.ts +45 -0
  471. package/dist/plugins/worktree-isolation.js +105 -6
  472. package/dist/plugins/write-behind-counter.d.ts +56 -1
  473. package/dist/plugins/write-behind-counter.js +53 -14
  474. package/dist/principal-jwt.d.ts +24 -0
  475. package/dist/principal-jwt.js +60 -5
  476. package/dist/project-memory.d.ts +22 -0
  477. package/dist/project-memory.js +144 -15
  478. package/dist/prompts-domain-validate.d.ts +28 -0
  479. package/dist/prompts-domain-validate.js +42 -1
  480. package/dist/question.d.ts +33 -0
  481. package/dist/question.js +69 -2
  482. package/dist/resource-suspend.d.ts +21 -0
  483. package/dist/resource-suspend.js +16 -0
  484. package/dist/router/route-orchestration.d.ts +78 -0
  485. package/dist/router/route-orchestration.js +77 -0
  486. package/dist/run-local.d.ts +32 -0
  487. package/dist/run-local.js +172 -8
  488. package/dist/runs.d.ts +189 -4
  489. package/dist/runs.js +465 -24
  490. package/dist/runtime-caps-resolver.d.ts +106 -0
  491. package/dist/runtime-caps-resolver.js +89 -4
  492. package/dist/runtime-governance.d.ts +84 -0
  493. package/dist/runtime-governance.js +127 -1
  494. package/dist/sandbox-pkg-source.d.ts +23 -0
  495. package/dist/sandbox-pkg-source.js +37 -0
  496. package/dist/sealed-key.d.ts +54 -0
  497. package/dist/sealed-key.js +68 -3
  498. package/dist/security.d.ts +108 -0
  499. package/dist/security.js +145 -6
  500. package/dist/sema-registry.d.ts +21 -0
  501. package/dist/sema-registry.js +21 -0
  502. package/dist/session-leaf-bus.d.ts +31 -0
  503. package/dist/session-leaf-bus.js +32 -0
  504. package/dist/session-sync-content.d.ts +46 -0
  505. package/dist/session-sync-content.js +47 -1
  506. package/dist/session-sync-kernel.d.ts +89 -0
  507. package/dist/session-sync-kernel.js +48 -3
  508. package/dist/session-sync.d.ts +125 -0
  509. package/dist/session-sync.js +181 -8
  510. package/dist/session-titler.d.ts +26 -0
  511. package/dist/session-titler.js +41 -7
  512. package/dist/session-watch.d.ts +53 -1
  513. package/dist/session-watch.js +73 -9
  514. package/dist/sighup-idle.d.ts +30 -0
  515. package/dist/sighup-idle.js +12 -1
  516. package/dist/spec-fields.d.ts +91 -3
  517. package/dist/spec-fields.js +116 -4
  518. package/dist/task-cwd.d.ts +50 -0
  519. package/dist/task-cwd.js +68 -1
  520. package/dist/task-mcp.d.ts +47 -0
  521. package/dist/task-mcp.js +29 -0
  522. package/dist/task-settings.d.ts +175 -0
  523. package/dist/task-settings.js +226 -11
  524. package/dist/task-workflow.d.ts +67 -0
  525. package/dist/task-workflow.js +75 -8
  526. package/dist/tool-approval.d.ts +91 -0
  527. package/dist/tool-approval.js +215 -0
  528. package/dist/trace/artifacts.d.ts +9 -0
  529. package/dist/trace/artifacts.js +30 -5
  530. package/dist/trace/core-keyset-guard.d.ts +18 -0
  531. package/dist/trace/ledger-sink.d.ts +43 -0
  532. package/dist/trace/ledger-sink.js +64 -5
  533. package/dist/trace/project.d.ts +133 -0
  534. package/dist/trace/project.js +189 -2
  535. package/dist/trace/redact.d.ts +19 -0
  536. package/dist/trace/redact.js +66 -9
  537. package/dist/usage-analytics.d.ts +26 -0
  538. package/dist/usage-analytics.js +26 -3
  539. package/package.json +1 -1
@@ -1,39 +1,84 @@
1
+ /** The staging-id format: a real session id with a `#stg-${token}` suffix. The `#` is illegal in a uuidv7, so a
2
+ * staging id can never collide with a real session id (the orphan invariant) and is recognizable for the reaper. */
1
3
  export function stagingIdFor(realSessionId, token) {
2
4
  return `${realSessionId}#stg-${token}`;
3
5
  }
6
+ /** The substring marker every staging id carries (the reaper's `LIKE '%#stg-%'` selector). */
4
7
  export const STAGING_ID_MARKER = "#stg-";
8
+ /** Recover the real session id from a staging id (everything before the `#stg-` marker), or null if not a staging id. */
5
9
  export function realIdOfStaging(stagingId) {
6
10
  const i = stagingId.indexOf(STAGING_ID_MARKER);
7
11
  return i < 0 ? null : stagingId.slice(0, i);
8
12
  }
13
+ /**
14
+ * §7 — classify how importing `srcEntries` into a destination holding `dstEntries` would relate the two logs. PURE
15
+ * (no I/O); the §7 fork detector. The comparison is over the ENTRY-ID SETS, not leaf_id:
16
+ *
17
+ * Entry ids are copied VERBATIM across backends (fork/import re-seq only `seq`; id/parentId/payload are byte-identical
18
+ * — see tidb-session-store fork/importEntries), so a set comparison of ids is the SOUND divergence test §7 demands. It
19
+ * fixes v1's leaf_id-only check, which was unsound BOTH ways:
20
+ * - FALSE NEGATIVE (the dangerous one): a cross-end rewind moves `LeafEntry.targetId` back onto a shared OLD entry id,
21
+ * so two genuinely-forked logs report the SAME leaf_id → v1 would silently overwrite = data loss (exactly the 2c
22
+ * use case). Set comparison sees each side's exclusive ids and reports `fork`.
23
+ * - FALSE POSITIVE: a clean fast-forward ends on a NEW leaf id ≠ the dst's leaf → v1 flags a (non-)conflict. Set
24
+ * comparison sees dst ⊊ src and reports `fast_forward` (safe).
25
+ *
26
+ * `commonAncestor` for a fork = the entry id present in BOTH logs that appears LAST in `srcEntries` oldest-first order
27
+ * (the deepest shared appended entry = the divergence point), or `null` if the logs share no entry at all.
28
+ */
9
29
  export function classifySyncRelationship(srcEntries, dstEntries) {
30
+ // Map entries → their ids (the only thing the comparison reads) and delegate to the id-set classifier — ONE source
31
+ // of the §7 relation logic. (P1d's HTTP plan route, where one peer is REMOTE and sends only an entry-id LIST, calls
32
+ // classifySyncRelationshipByIds DIRECTLY; this entries-typed entry point is the same logic with a nicer call shape.)
10
33
  return classifySyncRelationshipByIds(srcEntries.map((e) => e.id), dstEntries === null ? null : dstEntries.map((e) => e.id));
11
34
  }
35
+ /**
36
+ * §7 — the SAME relation classifier as {@link classifySyncRelationship}, over entry-id LISTS (oldest-first) instead of
37
+ * full entries. This is the canonical implementation the entries-typed wrapper delegates to. It exists in its own right
38
+ * for the P1d HTTP `/sync/plan` route: the two peers are different PROCESSES (the local shell + the cloud), so the
39
+ * planning peer holds only the OTHER end's entry-id list (not its `SessionTreeEntry[]`) — the relation is decidable from
40
+ * the ids alone (ids are copied VERBATIM across backends, so a set comparison of ids is the sound divergence test §7
41
+ * demands; see the wrapper's doc-comment for why leaf_id was unsound). `srcIds`/`dstIds` are OLDEST-FIRST so the
42
+ * fork `commonAncestor` (the deepest shared id) and the ordered exclusive/new lists are derived correctly.
43
+ */
12
44
  export function classifySyncRelationshipByIds(srcIds, dstIds) {
45
+ // dst absent OR empty ⇒ nothing at the destination to relate to / lose → an unconditional fresh import.
13
46
  if (dstIds === null || dstIds.length === 0)
14
47
  return { relation: "fresh" };
15
48
  const srcSet = new Set(srcIds);
16
49
  const dstSet = new Set(dstIds);
50
+ // identical: same cardinality AND every src id is in dst (⟹ the sets are equal) → already present & equal (no-op).
17
51
  if (srcSet.size === dstSet.size && [...srcSet].every((id) => dstSet.has(id)))
18
52
  return { relation: "identical" };
19
- const allDstInSrc = [...dstSet].every((id) => srcSet.has(id));
20
- const allSrcInDst = [...srcSet].every((id) => dstSet.has(id));
53
+ const allDstInSrc = [...dstSet].every((id) => srcSet.has(id)); // dst ⊆ src
54
+ const allSrcInDst = [...srcSet].every((id) => dstSet.has(id)); // src ⊆ dst
55
+ // fast_forward: dst ⊊ src (src strictly ahead — a clean append) → safe to apply. newEntryIds = the src ids the dst
56
+ // lacks, in SRC oldest-first order (the appended tail), so the caller can surface "what would be added".
21
57
  if (allDstInSrc && !allSrcInDst) {
22
58
  const newEntryIds = srcIds.filter((id) => !dstSet.has(id));
23
59
  return { relation: "fast_forward", newEntryIds };
24
60
  }
61
+ // stale: src ⊊ dst (src strictly behind) → applying would DROP the dst entries src never saw = data loss. dstAheadBy
62
+ // = the dst ids src lacks (dst order). Refused as a conflict unless the caller explicitly resolves overwrite-dst.
25
63
  if (allSrcInDst && !allDstInSrc) {
26
64
  const dstAheadBy = dstIds.filter((id) => !srcSet.has(id));
27
65
  return { relation: "stale", dstAheadBy };
28
66
  }
67
+ // fork: neither set contains the other ⇒ each side has ≥1 exclusive entry (true divergence). The common ancestor is
68
+ // the deepest shared entry = the LAST id in src oldest-first order that the dst also holds (null if no shared id).
29
69
  const srcExclusive = srcIds.filter((id) => !dstSet.has(id));
30
70
  const dstExclusive = dstIds.filter((id) => !srcSet.has(id));
31
71
  let commonAncestor = null;
32
72
  for (const id of srcIds)
33
73
  if (dstSet.has(id))
34
- commonAncestor = id;
74
+ commonAncestor = id; // last shared id in src oldest-first order
35
75
  return { relation: "fork", commonAncestor, srcExclusive, dstExclusive };
36
76
  }
77
+ /**
78
+ * §7 — a typed conflict the route maps to HTTP 409: an import that WOULD lose destination history (a `fork` or a
79
+ * `stale` source) and the caller did NOT pass an explicit `{ resolution: "overwrite-dst" }`. Carries the classified
80
+ * relation so the route/shell can surface the exclusive entry sets to the user for a keep-local / keep-cloud decision.
81
+ */
37
82
  export class SyncConflictError extends Error {
38
83
  sessionId;
39
84
  relation;
@@ -1,47 +1,164 @@
1
+ /**
2
+ * 2c session-sync — P1b ORCHESTRATION: export a session's WHOLE portable state from one StoreBackend and import it
3
+ * verbatim into another (the seamless local↔cloud "save state" move; docs/DESIGN-session-sync.md §5–§10). All the
4
+ * per-store PRIMITIVES already exist (P1a entries + P1b snapshots/policy/anchor); this module is the COORDINATOR that
5
+ * (a) reads them into a {@link SessionBundle} and (b) replays them across backends in the §8 fail-closed atomic order.
6
+ *
7
+ * This is a SERVICE function — the caller (the future HTTP route, P1d) owns the `sessionOwnerScope` gate (§9: export
8
+ * 404s a non-owner, import re-stamps owner to the authenticated principal). exportSession/importSession themselves do
9
+ * NOT authenticate; they assume the caller proved ownership.
10
+ *
11
+ * Scope (P1b): a one-shot whole-session move with FAIL-CLOSED validation (§10) + cross-store atomic ordering (§8) +
12
+ * owner re-stamp (§9). NOT in scope here: sync-watermark / fork detection (§7) and HTTP/streaming blob negotiation
13
+ * (§5) — those are P1c/P1d. Blobs are NOT held in memory (§5 OOM guard): the bundle carries only the manifest
14
+ * (relPath→blobHash); bytes flow through a `getBlob(hash)` getter the importer pulls content-addressed (deduped).
15
+ */
1
16
  import type { StoreBackend } from "./plugins/store-backend.js";
2
17
  import { type SyncRelation } from "./session-sync-kernel.js";
3
18
  import type { SessionTreeEntry, SessionRulesRecord } from "@sema-agent/core";
19
+ /** A blob getter: content-addressed bytes for a sha256 hash, or `undefined` if the source can't supply them.
20
+ * Returned by {@link exportSession} (closes over the source store), consumed by {@link importSession}. */
4
21
  export type BlobGetter = (hash: string) => Promise<Uint8Array | undefined>;
5
22
  export { stagingIdFor, STAGING_ID_MARKER, realIdOfStaging, classifySyncRelationship, classifySyncRelationshipByIds, SyncConflictError } from "./session-sync-kernel.js";
6
23
  export type { StagingHandle, ImportStagingStore, SyncRelation } from "./session-sync-kernel.js";
24
+ /**
25
+ * The portable state of ONE session, ready to replay into another backend. Blobs are deliberately NOT inlined
26
+ * (§5: a turn's working-tree blobs can be tens of MiB → OOM/413 if buffered); each snapshot carries only its
27
+ * `manifest` (relPath → blobHash) and the bytes are streamed through the paired {@link BlobGetter}.
28
+ */
7
29
  export interface SessionBundle {
8
30
  sessionId: string;
31
+ /** The FULL durable conversation log (compaction floor bypassed), verbatim ids/parents/payload — `exportEntries`. */
9
32
  entries: SessionTreeEntry[];
33
+ /** One per E19 file snapshot (keyed by `SessionTreeEntry.id` = `key`); manifest = [relPath, blobHash] tuples. */
10
34
  snapshots: Array<{
11
35
  key: string;
12
36
  manifest: Array<[string, string]>;
13
37
  }>;
38
+ /** Every (principal, rules) record for the session, across ALL principals (E6 `listBySession`). */
14
39
  policy: SessionRulesRecord[];
40
+ /** Every E18 resume-at anchor (eventId→entryId + its source owner; owner is RE-KEYED on import, §9). */
15
41
  anchors: Array<{
16
42
  eventId: string;
17
43
  entryId: string;
18
44
  owner: string | null;
19
45
  }>;
20
46
  }
47
+ /**
48
+ * P1d-α (PULL streaming, §1.5/§5/§15) — a {@link SessionBundle} with the ENTRIES LIFTED OFF THE WIRE: the HTTP PULL
49
+ * `GET /sync/manifest` returns THIS (small, bounded), and the entries flow separately through the NDJSON
50
+ * `GET /sync/entries` stream (bounded memory regardless of session size — the §1.5 ruling: entries are small text
51
+ * rows, so streaming is about ROW COUNT, not blob size). It carries the entries' IDS (`entryIds`, oldest-first — the
52
+ * local peer feeds these to {@link classifySyncRelationshipByIds} to decide fast-forward / fork BEFORE pulling the
53
+ * stream) + the count + the current leaf, and the snapshots/policy/anchors EXACTLY as {@link SessionBundle} does.
54
+ */
21
55
  export interface SessionManifest {
22
56
  sessionId: string;
57
+ /** The full durable log's entry IDS, oldest-first (verbatim, cross-backend stable) — NOT the entry payloads. */
23
58
  entryIds: string[];
59
+ /** `entryIds.length` — the number of entries the paired `/sync/entries` stream will yield (the trailer's count). */
24
60
  entryCount: number;
61
+ /** The session's current leaf `SessionTreeEntry.id` (session_meta.leaf_id), or null if it has none. */
25
62
  leafId: string | null;
63
+ /** One per E19 file snapshot (keyed by entry id); manifest = [relPath, blobHash] tuples. EXACTLY as the bundle. */
26
64
  snapshots: Array<{
27
65
  key: string;
28
66
  manifest: Array<[string, string]>;
29
67
  }>;
68
+ /** Every (principal, rules) record across ALL principals (E6 `listBySession`). EXACTLY as the bundle. */
30
69
  policy: SessionRulesRecord[];
70
+ /** Every E18 resume-at anchor (eventId→entryId + source owner; RE-KEYED on import, §9). EXACTLY as the bundle. */
31
71
  anchors: Array<{
32
72
  eventId: string;
33
73
  entryId: string;
34
74
  owner: string | null;
35
75
  }>;
36
76
  }
77
+ /**
78
+ * EXPORT a session's whole portable state from `srcBackend` into a {@link SessionBundle} + a {@link BlobGetter}.
79
+ *
80
+ * - entries: `exportEntries(sessionId)` — `null` ⇒ the session does not exist ⇒ this returns `null` (no partial bundle).
81
+ * - snapshots: `listKeys(sessionId)` → for each key `exportManifest(scope,key)` → `{key, [...manifest]}`. A key whose
82
+ * manifest reads back `null` (a concurrent reap between listKeys and exportManifest) is dropped, not exported empty.
83
+ * - policy: `listBySession(sessionId)` (empty `[]` if the store lacks the optional seam — honest degrade).
84
+ * - anchors: `listBySession(sessionId)` (empty `[]` if the store has no resumeAnchor seam).
85
+ * - getBlob: bound to the source `fileSnapshot().getBlob` so the importer can pull content-addressed bytes lazily.
86
+ *
87
+ * The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
88
+ * `null` ONLY for "session does not exist", never as an authz signal.
89
+ *
90
+ * NB (P1d-α): the HTTP PULL no longer uses this — it uses {@link exportSessionManifest} (entries lifted off the wire)
91
+ * + the keyset-paged `exportEntriesStream` NDJSON route so memory is bounded by ROW COUNT, not the whole-log array.
92
+ * exportSession (entries-inline) STAYS for the local same-process import path, P1d-β (PUSH), and the tests.
93
+ */
37
94
  export declare function exportSession(sessionId: string, srcBackend: StoreBackend): Promise<{
38
95
  bundle: SessionBundle;
39
96
  getBlob: BlobGetter;
40
97
  } | null>;
98
+ /**
99
+ * P1d-α (PULL streaming, §15) — EXPORT a session's portable state as a {@link SessionManifest} (entries LIFTED OFF
100
+ * the wire) + a {@link BlobGetter}. Like {@link exportSession} but it does NOT load entry payloads: it reads the
101
+ * IDS-only projection via `listEntryIds` (`null` ⇒ no such session ⇒ this returns `null`), counts them, reads the
102
+ * current `leafId`, and builds snapshots/policy/anchors EXACTLY as exportSession does. The paired NDJSON
103
+ * `GET /sync/entries` route streams the entry payloads separately (keyset-paged → bounded memory), so a 1M-entry
104
+ * session's manifest stays small (ids + counts + the snapshot/policy/anchor metadata).
105
+ *
106
+ * The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
107
+ * `null` ONLY for "session does not exist". (Named exportSessionManifest, NOT exportManifest, to avoid the collision
108
+ * with `FileSnapshotStore.exportManifest`.)
109
+ */
41
110
  export declare function exportSessionManifest(sessionId: string, srcBackend: StoreBackend): Promise<{
42
111
  manifest: SessionManifest;
43
112
  getBlob: BlobGetter;
44
113
  } | null>;
114
+ /**
115
+ * IMPORT a {@link SessionBundle} (+ its {@link BlobGetter}) into `dstBackend` under `bundle.sessionId`, re-stamping
116
+ * ownership to `importingPrincipal`. Returns the snapshot tally (imported vs honestly-skipped on a destination whose
117
+ * FileSnapshotStore lacks the importManifest seam — pre-1.141.0; core's InMemory/File NOW carry it, so a local dst
118
+ * imports snapshots for real).
119
+ *
120
+ * §10 FAIL-CLOSED VALIDATION runs FIRST — BEFORE any write:
121
+ * 1. `validateEntriesForImport(bundle.entries)` (the single core invariant gate: unique ids / parent-before-child /
122
+ * one root / leaf-resolvable). Throws on violation; NOTHING is written.
123
+ * 2. Every blobHash referenced by every snapshot manifest MUST be supplied by `getBlob` — a missing blob would
124
+ * restore a PARTIAL tree (files half-written, conversation whole → breaks the entry↔file lockstep). A lightweight
125
+ * PRESENCE pre-check (fetch each distinct hash once, no hashing) fails FAST here; the AUTHORITATIVE content-address
126
+ * integrity check (`sha256(bytes)===hash`) is done ONCE inside `importManifest` (core 1.141.0), so we don't double-
127
+ * hash. (Runs only when the dst supports snapshot import; otherwise there is nothing to half-write.)
128
+ *
129
+ * §7 CLASSIFICATION GATE — BEFORE any write, the destination's CURRENT entry log is read and compared to the bundle
130
+ * via {@link classifySyncRelationship} (a sound entry-id SET comparison, not leaf_id). A `fork` (true divergence) or a
131
+ * `stale` source (the dst is strictly ahead) WOULD lose destination history → it throws {@link SyncConflictError}
132
+ * (route → 409) UNLESS the caller passes `{ resolution: "overwrite-dst" }`. An `identical` relation SKIPS the entries
133
+ * write entirely (the log is already present & equal; the idempotent snapshot/policy/anchor replay below still runs to
134
+ * heal anything missing). `fresh` / `fast_forward` (and an overwrite-resolved fork/stale) write the entries via the
135
+ * IDEMPOTENT {@link OwnerAwareSessionStore.replaceEntries} (purge-then-import) — NOT `importEntries`, whose plain
136
+ * INSERT crashes on a duplicate PK when the session already exists at the destination.
137
+ *
138
+ * §8 CROSS-STORE ATOMIC ORDER + IDEMPOTENCY — session_meta (written LAST inside `replaceEntries`) is the sole
139
+ * commit point, so a crash mid-import leaves orphan blobs/manifests/anchors with NO session_meta = invisible to
140
+ * wake (never a half-session, only collectable orphans). Order:
141
+ * ① snapshots (manifests + content-addressed blobs) — content-addressed + create-once = idempotent retry-to-completion.
142
+ * `importManifest` returns a {@link FileSnapshotResult} (NEVER throws — core 1.141.0); a `{ok:false}` (a
143
+ * rejecting/missing/hash-mismatched blob the §10 pre-check didn't catch — e.g. a blob corrupted in transit) is
144
+ * re-raised as a fail-closed THROW so the import aborts BEFORE the entries commit (no entry↔file split).
145
+ * ② entries (`replaceEntries` runs the core gate AGAIN and writes session_meta LAST = the commit point; idempotent
146
+ * over an existing session — skipped entirely when the relation is `identical`)
147
+ * ③ policy (replay each record via putRules; `opts.operatorOk` defaults FALSE → the E6 tighten-only gate APPLIES, so
148
+ * a replay that would LOOSEN an operator-tightened dst policy throws `SessionPolicyError("loosen_forbidden")` —
149
+ * `operator:true` is set ONLY when the authenticated importer is itself a verified operator)
150
+ * ④ anchors (owner RE-KEYED to `importingPrincipal`, §9)
151
+ * Steps ③/④ are post-commit; re-running them is idempotent — ③ SKIPS a put whose content already equals the dst row
152
+ * (a "no-op rev bump" is NOT idempotent: rev is the operator's optimistic-lock token — the A6 fix below), ④ is a
153
+ * same-value upsert — so a crash between ②–④ is recovered by a retry that converges to the same bytes.
154
+ *
155
+ * @param opts.resolution — `"overwrite-dst"` consents to a `fork`/`stale` import that overwrites destination history
156
+ * (the user's keep-source decision); omitted ⇒ such an import is refused with {@link SyncConflictError}.
157
+ * @param opts.operatorOk — when `true`, the policy replay (step ③) is an OPERATOR write (may loosen the dst rules); set
158
+ * ONLY when the AUTHENTICATED importer is a verified operator. DEFAULT false → the E6 tighten-only gate applies, so a
159
+ * non-operator import is TIGHTEN-ONLY (design §4): a replay that would loosen an operator-tightened dst session policy
160
+ * throws `SessionPolicyError("loosen_forbidden")`, which propagates (NOT swallowed) so the route can refuse the loosen.
161
+ */
45
162
  export declare function importSession(bundle: SessionBundle, getBlob: BlobGetter, dstBackend: StoreBackend, importingPrincipal: string | null, opts?: {
46
163
  resolution?: "overwrite-dst";
47
164
  operatorOk?: boolean;
@@ -50,5 +167,13 @@ export declare function importSession(bundle: SessionBundle, getBlob: BlobGetter
50
167
  snapshotsSkipped: number;
51
168
  relation: SyncRelation["relation"];
52
169
  }>;
170
+ /**
171
+ * §7 DRY-RUN — classify what importing `sessionId` from `srcBackend` into `dstBackend` WOULD do, WITHOUT writing
172
+ * anything. The route (P1d) calls this to surface a conflict (fork/stale) to the user BEFORE committing an
173
+ * {@link importSession} (so the shell can present a keep-local / keep-cloud / fork-new choice up front).
174
+ *
175
+ * Returns `null` when the SOURCE session does not exist (no bundle to plan); otherwise the {@link SyncRelation}
176
+ * between the source's full durable log and the destination's current log (a `null` dst log ⇒ `fresh`). No writes.
177
+ */
53
178
  export declare function planSync(sessionId: string, srcBackend: StoreBackend, dstBackend: StoreBackend): Promise<SyncRelation | null>;
54
179
  //# sourceMappingURL=session-sync.d.ts.map
@@ -1,19 +1,50 @@
1
1
  import { identicalIdsAlsoIdenticalContent } from "./session-sync-content.js";
2
2
  import { classifySyncRelationship, SyncConflictError } from "./session-sync-kernel.js";
3
3
  import { validateEntriesForImport, normalizeRules, stripRev } from "@sema-agent/core";
4
+ // ── PURE KERNEL moved to ./session-sync-kernel.ts (design/158 A2) ─────────────────────────────────────────────
5
+ // The §7 relation classifier (classifySyncRelationship[ByIds]), the staging-id scheme (stagingIdFor /
6
+ // STAGING_ID_MARKER / realIdOfStaging), the StagingHandle / ImportStagingStore contracts and SyncConflictError
7
+ // lived here (lines ~26–187) but depend on NOTHING in this repo — while THIS module value-imports StoreBackend and
8
+ // type-imports security's OwnerAwareSessionStore. The three DB session adapters (local/pg/tidb) and security.ts
9
+ // need only those pure symbols, and security.ts importing them from HERE formed the repo's only true type cycle
10
+ // (security ↔ session-sync; lens2 §B/§E). They now live in the leaf module session-sync-kernel.ts (same shape as
11
+ // session-sync-content.ts) and are RE-EXPORTED verbatim below, so existing importers of this module keep working.
4
12
  export { stagingIdFor, STAGING_ID_MARKER, realIdOfStaging, classifySyncRelationship, classifySyncRelationshipByIds, SyncConflictError } from "./session-sync-kernel.js";
13
+ /** Narrow `backend.session()` to the optional ownership/export seam (the concrete TiDB/PG/Local stores carry it;
14
+ * the bare in-memory dev store doesn't → the methods are optional, exactly like the fork route narrows). */
5
15
  function ownerAware(backend) {
6
16
  return backend.session();
7
17
  }
18
+ /**
19
+ * EXPORT a session's whole portable state from `srcBackend` into a {@link SessionBundle} + a {@link BlobGetter}.
20
+ *
21
+ * - entries: `exportEntries(sessionId)` — `null` ⇒ the session does not exist ⇒ this returns `null` (no partial bundle).
22
+ * - snapshots: `listKeys(sessionId)` → for each key `exportManifest(scope,key)` → `{key, [...manifest]}`. A key whose
23
+ * manifest reads back `null` (a concurrent reap between listKeys and exportManifest) is dropped, not exported empty.
24
+ * - policy: `listBySession(sessionId)` (empty `[]` if the store lacks the optional seam — honest degrade).
25
+ * - anchors: `listBySession(sessionId)` (empty `[]` if the store has no resumeAnchor seam).
26
+ * - getBlob: bound to the source `fileSnapshot().getBlob` so the importer can pull content-addressed bytes lazily.
27
+ *
28
+ * The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
29
+ * `null` ONLY for "session does not exist", never as an authz signal.
30
+ *
31
+ * NB (P1d-α): the HTTP PULL no longer uses this — it uses {@link exportSessionManifest} (entries lifted off the wire)
32
+ * + the keyset-paged `exportEntriesStream` NDJSON route so memory is bounded by ROW COUNT, not the whole-log array.
33
+ * exportSession (entries-inline) STAYS for the local same-process import path, P1d-β (PUSH), and the tests.
34
+ */
8
35
  export async function exportSession(sessionId, srcBackend) {
9
36
  const session = ownerAware(srcBackend);
37
+ // entries FIRST — null ⇒ no such session ⇒ no bundle at all (the route maps this to 404).
10
38
  const exportEntries = session.exportEntries?.bind(session);
11
39
  if (!exportEntries) {
40
+ // A backend whose session store lacks the export seam can't be a sync SOURCE (older/in-memory dev store). Treat as
41
+ // "nothing to export" rather than crash — the route already gates on the durable backend being present.
12
42
  return null;
13
43
  }
14
44
  const entries = await exportEntries(sessionId);
15
45
  if (entries === null)
16
- return null;
46
+ return null; // session does not exist
47
+ // snapshots — listKeys → per-key manifest. exportManifest may be absent (no snapshot export seam) → no snapshots.
17
48
  const fs = srcBackend.fileSnapshot();
18
49
  const snapshots = [];
19
50
  if (typeof fs.listKeys === "function" && typeof fs.exportManifest === "function") {
@@ -21,32 +52,53 @@ export async function exportSession(sessionId, srcBackend) {
21
52
  for (const key of keys) {
22
53
  const manifest = await fs.exportManifest(sessionId, key);
23
54
  if (manifest === null)
24
- continue;
55
+ continue; // raced reap between listKeys and exportManifest — skip (never an empty snapshot)
25
56
  snapshots.push({ key, manifest: [...manifest] });
26
57
  }
27
58
  }
59
+ // policy — every (principal, rules) record across ALL principals (E6 listBySession; optional → []).
28
60
  const policyStore = srcBackend.sessionPolicy();
29
61
  const policy = typeof policyStore.listBySession === "function"
30
62
  ? await policyStore.listBySession(sessionId)
31
63
  : [];
64
+ // anchors — every E18 resume anchor (eventId→entryId + source owner). resumeAnchor is REQUIRED on all backends.
32
65
  const anchorStore = srcBackend.resumeAnchor();
33
66
  const anchors = typeof anchorStore.listBySession === "function"
34
67
  ? await anchorStore.listBySession(sessionId)
35
68
  : [];
69
+ // getBlob — bound to the SOURCE snapshot store so the importer pulls bytes content-addressed (deduped), lazily.
70
+ // A source without getBlob (no blobs to move) supplies a getter that always returns undefined → snapshots with a
71
+ // missing blob fail the §10 validation below before any write (never a partial restore).
36
72
  const getBlobFn = fs.getBlob?.bind(fs);
37
73
  const getBlob = getBlobFn ? (hash) => getBlobFn(hash) : async () => undefined;
38
74
  return { bundle: { sessionId, entries, snapshots, policy, anchors }, getBlob };
39
75
  }
76
+ /**
77
+ * P1d-α (PULL streaming, §15) — EXPORT a session's portable state as a {@link SessionManifest} (entries LIFTED OFF
78
+ * the wire) + a {@link BlobGetter}. Like {@link exportSession} but it does NOT load entry payloads: it reads the
79
+ * IDS-only projection via `listEntryIds` (`null` ⇒ no such session ⇒ this returns `null`), counts them, reads the
80
+ * current `leafId`, and builds snapshots/policy/anchors EXACTLY as exportSession does. The paired NDJSON
81
+ * `GET /sync/entries` route streams the entry payloads separately (keyset-paged → bounded memory), so a 1M-entry
82
+ * session's manifest stays small (ids + counts + the snapshot/policy/anchor metadata).
83
+ *
84
+ * The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
85
+ * `null` ONLY for "session does not exist". (Named exportSessionManifest, NOT exportManifest, to avoid the collision
86
+ * with `FileSnapshotStore.exportManifest`.)
87
+ */
40
88
  export async function exportSessionManifest(sessionId, srcBackend) {
41
89
  const session = ownerAware(srcBackend);
90
+ // entry IDS FIRST — null ⇒ no such session ⇒ no manifest (the route maps this to 404). A backend whose session store
91
+ // lacks the seam can't be a sync source (older/in-memory dev store) → treat as nothing to export.
42
92
  const listEntryIds = session.listEntryIds?.bind(session);
43
93
  if (!listEntryIds)
44
94
  return null;
45
95
  const entryIds = await listEntryIds(sessionId);
46
96
  if (entryIds === null)
47
- return null;
97
+ return null; // session does not exist
98
+ // leafId — the session's current leaf (cache-bypassing single read). Absent seam / no leaf ⇒ null.
48
99
  const getLeafId = session.getLeafId?.bind(session);
49
100
  const leafId = getLeafId ? await getLeafId(sessionId) : null;
101
+ // snapshots / policy / anchors — IDENTICAL to exportSession (no entry payloads loaded anywhere here).
50
102
  const fs = srcBackend.fileSnapshot();
51
103
  const snapshots = [];
52
104
  if (typeof fs.listKeys === "function" && typeof fs.exportManifest === "function") {
@@ -54,7 +106,7 @@ export async function exportSessionManifest(sessionId, srcBackend) {
54
106
  for (const key of keys) {
55
107
  const manifest = await fs.exportManifest(sessionId, key);
56
108
  if (manifest === null)
57
- continue;
109
+ continue; // raced reap between listKeys and exportManifest — skip (never an empty snapshot)
58
110
  snapshots.push({ key, manifest: [...manifest] });
59
111
  }
60
112
  }
@@ -66,6 +118,7 @@ export async function exportSessionManifest(sessionId, srcBackend) {
66
118
  const anchors = typeof anchorStore.listBySession === "function"
67
119
  ? await anchorStore.listBySession(sessionId)
68
120
  : [];
121
+ // getBlob — bound to the SOURCE snapshot store (content-addressed, lazy), exactly like exportSession.
69
122
  const getBlobFn = fs.getBlob?.bind(fs);
70
123
  const getBlob = getBlobFn ? (hash) => getBlobFn(hash) : async () => undefined;
71
124
  return {
@@ -73,16 +126,78 @@ export async function exportSessionManifest(sessionId, srcBackend) {
73
126
  getBlob,
74
127
  };
75
128
  }
129
+ /**
130
+ * IMPORT a {@link SessionBundle} (+ its {@link BlobGetter}) into `dstBackend` under `bundle.sessionId`, re-stamping
131
+ * ownership to `importingPrincipal`. Returns the snapshot tally (imported vs honestly-skipped on a destination whose
132
+ * FileSnapshotStore lacks the importManifest seam — pre-1.141.0; core's InMemory/File NOW carry it, so a local dst
133
+ * imports snapshots for real).
134
+ *
135
+ * §10 FAIL-CLOSED VALIDATION runs FIRST — BEFORE any write:
136
+ * 1. `validateEntriesForImport(bundle.entries)` (the single core invariant gate: unique ids / parent-before-child /
137
+ * one root / leaf-resolvable). Throws on violation; NOTHING is written.
138
+ * 2. Every blobHash referenced by every snapshot manifest MUST be supplied by `getBlob` — a missing blob would
139
+ * restore a PARTIAL tree (files half-written, conversation whole → breaks the entry↔file lockstep). A lightweight
140
+ * PRESENCE pre-check (fetch each distinct hash once, no hashing) fails FAST here; the AUTHORITATIVE content-address
141
+ * integrity check (`sha256(bytes)===hash`) is done ONCE inside `importManifest` (core 1.141.0), so we don't double-
142
+ * hash. (Runs only when the dst supports snapshot import; otherwise there is nothing to half-write.)
143
+ *
144
+ * §7 CLASSIFICATION GATE — BEFORE any write, the destination's CURRENT entry log is read and compared to the bundle
145
+ * via {@link classifySyncRelationship} (a sound entry-id SET comparison, not leaf_id). A `fork` (true divergence) or a
146
+ * `stale` source (the dst is strictly ahead) WOULD lose destination history → it throws {@link SyncConflictError}
147
+ * (route → 409) UNLESS the caller passes `{ resolution: "overwrite-dst" }`. An `identical` relation SKIPS the entries
148
+ * write entirely (the log is already present & equal; the idempotent snapshot/policy/anchor replay below still runs to
149
+ * heal anything missing). `fresh` / `fast_forward` (and an overwrite-resolved fork/stale) write the entries via the
150
+ * IDEMPOTENT {@link OwnerAwareSessionStore.replaceEntries} (purge-then-import) — NOT `importEntries`, whose plain
151
+ * INSERT crashes on a duplicate PK when the session already exists at the destination.
152
+ *
153
+ * §8 CROSS-STORE ATOMIC ORDER + IDEMPOTENCY — session_meta (written LAST inside `replaceEntries`) is the sole
154
+ * commit point, so a crash mid-import leaves orphan blobs/manifests/anchors with NO session_meta = invisible to
155
+ * wake (never a half-session, only collectable orphans). Order:
156
+ * ① snapshots (manifests + content-addressed blobs) — content-addressed + create-once = idempotent retry-to-completion.
157
+ * `importManifest` returns a {@link FileSnapshotResult} (NEVER throws — core 1.141.0); a `{ok:false}` (a
158
+ * rejecting/missing/hash-mismatched blob the §10 pre-check didn't catch — e.g. a blob corrupted in transit) is
159
+ * re-raised as a fail-closed THROW so the import aborts BEFORE the entries commit (no entry↔file split).
160
+ * ② entries (`replaceEntries` runs the core gate AGAIN and writes session_meta LAST = the commit point; idempotent
161
+ * over an existing session — skipped entirely when the relation is `identical`)
162
+ * ③ policy (replay each record via putRules; `opts.operatorOk` defaults FALSE → the E6 tighten-only gate APPLIES, so
163
+ * a replay that would LOOSEN an operator-tightened dst policy throws `SessionPolicyError("loosen_forbidden")` —
164
+ * `operator:true` is set ONLY when the authenticated importer is itself a verified operator)
165
+ * ④ anchors (owner RE-KEYED to `importingPrincipal`, §9)
166
+ * Steps ③/④ are post-commit; re-running them is idempotent — ③ SKIPS a put whose content already equals the dst row
167
+ * (a "no-op rev bump" is NOT idempotent: rev is the operator's optimistic-lock token — the A6 fix below), ④ is a
168
+ * same-value upsert — so a crash between ②–④ is recovered by a retry that converges to the same bytes.
169
+ *
170
+ * @param opts.resolution — `"overwrite-dst"` consents to a `fork`/`stale` import that overwrites destination history
171
+ * (the user's keep-source decision); omitted ⇒ such an import is refused with {@link SyncConflictError}.
172
+ * @param opts.operatorOk — when `true`, the policy replay (step ③) is an OPERATOR write (may loosen the dst rules); set
173
+ * ONLY when the AUTHENTICATED importer is a verified operator. DEFAULT false → the E6 tighten-only gate applies, so a
174
+ * non-operator import is TIGHTEN-ONLY (design §4): a replay that would loosen an operator-tightened dst session policy
175
+ * throws `SessionPolicyError("loosen_forbidden")`, which propagates (NOT swallowed) so the route can refuse the loosen.
176
+ */
76
177
  export async function importSession(bundle, getBlob, dstBackend, importingPrincipal, opts) {
77
178
  const sessionId = bundle.sessionId;
78
179
  const fs = dstBackend.fileSnapshot();
79
180
  const canImportSnapshots = typeof fs.importManifest === "function";
181
+ // ── §7 classification gate, BEFORE any write ───────────────────────────────────────────────────────────────
182
+ // Read the destination's CURRENT entry log and classify the relationship (sound entry-id set comparison). A fork or
183
+ // a stale source would lose dst history → refuse with a typed 409 unless the caller explicitly resolved overwrite-dst.
80
184
  const dstEntries = (await ownerAware(dstBackend).exportEntries?.(sessionId)) ?? null;
81
185
  const rel = classifySyncRelationship(bundle.entries, dstEntries);
82
186
  if ((rel.relation === "fork" || rel.relation === "stale") && opts?.resolution !== "overwrite-dst") {
83
- throw new SyncConflictError(sessionId, rel);
187
+ throw new SyncConflictError(sessionId, rel); // 409 — surface the exclusive sets so the user picks keep-local/cloud
84
188
  }
189
+ // ── §10 fail-closed validation, BEFORE any write ────────────────────────────────────────────────────────────
190
+ // (1) entries invariants (the core gate). validateEntriesForImport throws on any violation; we discard its result
191
+ // here (importEntries re-runs it as the authoritative write-time gate) — this is the early fail-closed check so
192
+ // we never start writing snapshots for a session whose entries the gate will reject.
85
193
  validateEntriesForImport(bundle.entries);
194
+ // (2) every referenced blob must be fetchable — ONLY when the destination can actually import snapshots (otherwise
195
+ // there is nothing to half-write, so a missing blob is moot; the snapshot is honestly skipped below). Fetch each
196
+ // DISTINCT hash once (content-addressed) so a large manifest doesn't refetch. This is a lightweight PRESENCE
197
+ // pre-check (fail-FAST before we write any entries) — the AUTHORITATIVE sha256 content-address integrity check is
198
+ // done ONCE inside importManifest (core 1.141.0); we deliberately don't re-hash here to avoid double-hashing
199
+ // a possibly-large blob set. The importManifest {ok:false} below is the backstop for anything the presence check
200
+ // can't see (a blob present-but-corrupted-in-transit, a getBlob that rejects only on a later call).
86
201
  if (canImportSnapshots) {
87
202
  const distinct = new Set();
88
203
  for (const snap of bundle.snapshots)
@@ -95,6 +210,19 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
95
210
  }
96
211
  }
97
212
  }
213
+ // ── ⓪ overwrite-dst subsystem WIPE (fork/stale with explicit consent ONLY) ─────────────────────────────────
214
+ // "overwrite-dst" means the user chose KEEP-SOURCE — the result must be the SOURCE's state, not "the source's
215
+ // conversation + the destination's leftover policy/anchors/snapshots" (a MIXED state is worse than lost history:
216
+ // dangling anchors resolve onto purged entries, an abandoned branch's policy rows keep gating, and a shared-key
217
+ // snapshot keeps the dst's bytes because importManifest is create-once). So before replaying ①/③/④ we wipe the
218
+ // dst's per-session subsystem state. Crash-safety: the user consented to discarding the dst's state, and every
219
+ // wipe+replay leg is idempotent → a crash mid-wipe is recovered by the same retry-to-completion as §8's ②–④.
220
+ // · snapshots: prefer the purge seam `deleteBySession` (SQL twins: manifests drop now, blobs go to the async
221
+ // orphan GC); fall back to the core-REQUIRED `reap(scope, [])` (in-memory/file stores GC unreferenced blobs
222
+ // inline — content-addressed, so blobs still referenced by other scopes survive).
223
+ // · anchors / policy: `deleteBySession` where the store carries it — since core 1.423 that is EVERY in-tree
224
+ // store (the seam is optional on core's SessionPolicyStore interface and implemented by its File/InMemory
225
+ // stores too — [1796]§三 → [1801]; the feature-detect stays for third-party stores that predate the seam).
98
226
  const overwriting = (rel.relation === "fork" || rel.relation === "stale") && opts?.resolution === "overwrite-dst";
99
227
  if (overwriting) {
100
228
  const fsWipe = fs;
@@ -109,6 +237,14 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
109
237
  if (typeof policyWipe.deleteBySession === "function")
110
238
  await policyWipe.deleteBySession(sessionId);
111
239
  }
240
+ // ── §8 atomic order ─────────────────────────────────────────────────────────────────────────────────────────
241
+ // ① snapshots — content-addressed blobs + manifest, BEFORE session_meta. Feature-detect importManifest: ALL backends
242
+ // now carry it (durable TiDB/PG + core's File/InMemory since 1.141.0) → a local dst imports snapshots for
243
+ // real. The only remaining honest-degrade is a backend whose FileSnapshotStore predates the seam (none in-tree).
244
+ // importManifest NEVER throws — it returns a FileSnapshotResult; a {ok:false} (rejecting/missing/hash-mismatched
245
+ // blob — e.g. corrupted in transit) is re-raised here as a fail-closed THROW so we abort BEFORE the entries commit
246
+ // (no entry↔file split). The whole import is the caller's transaction: a throw here means no session_meta is ever
247
+ // written (the commit point is inside importEntries, step ②), so the dst stays clean (only collectable orphans).
112
248
  let snapshotsImported = 0;
113
249
  let snapshotsSkipped = 0;
114
250
  if (canImportSnapshots) {
@@ -122,8 +258,16 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
122
258
  }
123
259
  }
124
260
  else {
125
- snapshotsSkipped = bundle.snapshots.length;
261
+ snapshotsSkipped = bundle.snapshots.length; // pre-1.141.0 dst — files pend the import seam; honest degrade (NOT a silent drop)
126
262
  }
263
+ // ② entries — core gate (again, authoritative) + session_meta LAST = the commit point. Owner = importingPrincipal
264
+ // (NEVER the bundle's, §9). Written via the IDEMPOTENT replaceEntries (purge-then-import) — NOT importEntries,
265
+ // whose plain INSERT crashes on a duplicate PK when the session already exists (fast-forward / overwrite-resolved
266
+ // / a §8 retry). When the relation is `identical` the log is already present & equal → SKIP the entries write
267
+ // entirely (the snapshot/policy/anchor replay below is idempotent and still runs to heal anything missing). A
268
+ // destination without the replace seam can't accept a session at all → throw.
269
+ // 🔴 同上(`session-sync-content.ts` 顶注):`identical` 只说明 id 集合相等,不说明内容相同。这条路上
270
+ // `bundle.entries` 与目的端日志都在手上 ⇒ 直接比内容摘要;不符就照常改写(不抛错、不 409)。
127
271
  const dstEntriesForContent = await ownerAware(dstBackend).exportEntries?.(sessionId).catch(() => null) ?? null;
128
272
  const contentEqual = rel.relation === "identical" && identicalIdsAlsoIdenticalContent(bundle.entries, dstEntriesForContent);
129
273
  if (rel.relation !== "identical" || !contentEqual) {
@@ -134,24 +278,53 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
134
278
  }
135
279
  await replaceEntries(sessionId, importingPrincipal, bundle.entries);
136
280
  }
281
+ // ③ policy — replay each (principal, rules) record VERBATIM. The E6 tighten-only gate is the security boundary here:
282
+ // we do NOT pass operator:true unconditionally (a non-operator pushing a client-controlled bundle could otherwise
283
+ // LOOSEN an operator-tightened dst session policy — the loosen the E6 route refuses). `opts.operatorOk` defaults
284
+ // FALSE → the tighten gate applies, so a TIGHTEN-ONLY import succeeds (design §4 "merge tighten-only") and a
285
+ // loosening replay throws SessionPolicyError("loosen_forbidden") — which we do NOT swallow; it propagates to the
286
+ // route so the malicious loosen is refused (entries may already have committed: the conversation syncs, only the
287
+ // loosen is refused = the correct fail-closed outcome). operator:true ONLY when the AUTHENTICATED importer is a
288
+ // verified operator. principal is carried VERBATIM (NOT re-keyed to importingPrincipal): the policy `principal` is
289
+ // the per-TASK rule-owner key (`spec.principal`), not the session owner, so re-keying would collapse distinct
290
+ // per-principal rows and could clobber. The session-wide row (principal=undefined) stays session-wide and still
291
+ // applies to the new owner.
292
+ // IDEMPOTENT REPLAY (the A6 fix): a put whose content (rev stripped, normalized) already equals the dst row is
293
+ // SKIPPED — putRules bumps `rev` on every write, and `rev` is the optimistic-lock token operators hold for
294
+ // read-modify-write. Without the skip, every §8 crash-retry (and every harmless duplicate sync) advances every
295
+ // row's rev while changing nothing ⇒ the operator's next putRules({expectedRev}) throws `conflict` on rules whose
296
+ // content never moved. With it, retry-after-crash converges to the same BYTES as a single clean run, which is
297
+ // what "steps ③/④ are idempotent" (§8 above) actually has to mean. normalizeRules gives both sides the same
298
+ // canonical field order/shape, so JSON.stringify is a sound equality here (array ORDER is significant on purpose:
299
+ // a reordered list is a different stored byte-state and must be written).
137
300
  const policyStore = dstBackend.sessionPolicy();
138
301
  const canonRules = (r) => JSON.stringify(normalizeRules(stripRev(r)));
139
302
  for (const rec of bundle.policy) {
140
303
  const cur = await policyStore.getRules(sessionId, rec.principal);
141
304
  if (cur !== null && canonRules(cur) === canonRules(rec.rules))
142
- continue;
305
+ continue; // content already identical — don't touch rev
143
306
  await policyStore.putRules(sessionId, rec.principal, rec.rules, { operator: opts?.operatorOk === true });
144
307
  }
308
+ // ④ anchors — owner RE-KEYED to importingPrincipal (§9): the E18 owner-guard resolves under the NEW owner so
309
+ // rewind-to-message keeps working post-move (the source owner, e.g. local null, would never resolve in the cloud).
145
310
  const anchorStore = dstBackend.resumeAnchor();
146
311
  for (const a of bundle.anchors) {
147
312
  await anchorStore.put(sessionId, a.eventId, a.entryId, importingPrincipal);
148
313
  }
149
314
  return { snapshotsImported, snapshotsSkipped, relation: rel.relation };
150
315
  }
316
+ /**
317
+ * §7 DRY-RUN — classify what importing `sessionId` from `srcBackend` into `dstBackend` WOULD do, WITHOUT writing
318
+ * anything. The route (P1d) calls this to surface a conflict (fork/stale) to the user BEFORE committing an
319
+ * {@link importSession} (so the shell can present a keep-local / keep-cloud / fork-new choice up front).
320
+ *
321
+ * Returns `null` when the SOURCE session does not exist (no bundle to plan); otherwise the {@link SyncRelation}
322
+ * between the source's full durable log and the destination's current log (a `null` dst log ⇒ `fresh`). No writes.
323
+ */
151
324
  export async function planSync(sessionId, srcBackend, dstBackend) {
152
325
  const srcEntries = (await ownerAware(srcBackend).exportEntries?.(sessionId)) ?? null;
153
326
  if (srcEntries === null)
154
- return null;
327
+ return null; // no such source session — nothing to plan (the route maps this to 404)
155
328
  const dstEntries = (await ownerAware(dstBackend).exportEntries?.(sessionId)) ?? null;
156
329
  return classifySyncRelationship(srcEntries, dstEntries);
157
330
  }
@@ -1,7 +1,31 @@
1
+ /**
2
+ * Auto-generated session titles.
3
+ *
4
+ * SHAPE: fire-and-forget at FIRST task submit for a session (the objective is known then — no need to wait
5
+ * for the run to finish, and the web Recents gets the title sooner). Write-once (`setTitleIfNull`, store-side
6
+ * `WHERE title IS NULL`) — the USER-facing rename lives BFF-side as a label overlay (user label wins
7
+ * over engine title, two layers never fight). Generation = one cheap-model one-liner via the deployment's
8
+ * hookLlm face (summarize role → MODEL_CHEAP_ID when configured, else the main model; bounded + timed out).
9
+ *
10
+ * FAILURE POSTURE: everything is best-effort — a generation/store failure logs and leaves title null (the
11
+ * list falls back to objectivePreview). An in-process pending set dedupes concurrent submits on the same
12
+ * session; the store's IS NULL guard closes the cross-replica race (double generation costs one extra cheap
13
+ * call at worst, and exactly one title lands).
14
+ */
1
15
  export interface SessionTitlerDeps {
16
+ /** Secret scrub applied to the RAW llm text before sanitize/persist (workflow audit 2026-07-13: the
17
+ * objective may carry a pasted secret and the model can echo it into the title — titles are persisted,
18
+ * listed over HTTP, and logged, all outside the trace redact pipeline; wire src/trace/redact.ts here). */
2
19
  redact?: (text: string) => string;
20
+ /** Write-once store leg (SQL twins / local sidecar). Resolves false when a title already existed. */
3
21
  setTitleIfNull(sessionId: string, title: string): Promise<boolean>;
22
+ /** Cheap pre-LLM probe: "titled" = skip (a restart/evicted-seen/another replica
23
+ * already paid — don't re-spend an LLM call); "untitled" = generate; "none" = the session_meta row hasn't
24
+ * been REGISTERED yet (createRun fires before the Runner's session acquire) — retry after a grace window,
25
+ * and if still absent, release the seen slot so the NEXT submit retries (never a permanently-untitled
26
+ * session via a swallowed early UPDATE-0). Local lane has no row-existence dimension → never "none". */
4
27
  probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
28
+ /** The deployment's cheap one-shot LLM face (main.ts hookLlm — summarize role default). */
5
29
  llm(opts: {
6
30
  prompt: string;
7
31
  timeoutMs: number;
@@ -22,9 +46,11 @@ export interface SessionTitlerDeps {
22
46
  };
23
47
  }
24
48
  export interface SessionTitler {
49
+ /** Fire-and-forget: maybe generate + persist a title for this session. Never throws, never blocks the caller. */
25
50
  maybeTitle(sessionId: string, objective: string): void;
26
51
  }
27
52
  export declare const TITLE_MAX_CHARS = 80;
53
+ /** Single line, control chars stripped, hard cap; surrounding quotes (a common LLM tic) trimmed. */
28
54
  export declare function sanitizeTitle(raw: string): string;
29
55
  export declare function buildTitlePrompt(objective: string): string;
30
56
  export declare function createSessionTitler(deps: SessionTitlerDeps): SessionTitler;