@sema-agent/server 7.150.0 → 7.152.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 (514) hide show
  1. package/MIGRATION.md +451 -0
  2. package/README.md +15 -3
  3. package/README.zh-CN.md +10 -2
  4. package/USAGE.md +144 -87
  5. package/deploy/sema-up/README.md +11 -4
  6. package/deploy/sema-up/chart/Chart.yaml +1 -1
  7. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  9. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  10. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  11. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  12. package/deploy/sema-up/chart/values.yaml +8 -1
  13. package/deploy/sema-up/cluster-up.sh +7 -6
  14. package/deploy/sema-up/kube-up.sh +6 -6
  15. package/deploy/sema-up/lib/flagshape.sh +36 -8
  16. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  17. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  18. package/deploy/sema-up/machines.example.yaml +3 -1
  19. package/deploy/sema-up/sema-up.sh +22 -16
  20. package/dist/adoption/import.js +3 -3
  21. package/dist/approval-ask-audit-store.d.ts +1 -1
  22. package/dist/approval-ask-machine.d.ts +4 -10
  23. package/dist/approval-ask-retention.d.ts +1 -1
  24. package/dist/approval-card.d.ts +12 -14
  25. package/dist/approval-gate-currency.d.ts +9 -9
  26. package/dist/approval-gate-currency.js +3 -4
  27. package/dist/approval-hmac.d.ts +17 -1
  28. package/dist/approval-hmac.js +7 -1
  29. package/dist/approval-queue-kind.d.ts +1 -1
  30. package/dist/approval.d.ts +2 -2
  31. package/dist/approval.js +2 -2
  32. package/dist/auth-bridge.d.ts +20 -0
  33. package/dist/auth-bridge.js +9 -0
  34. package/dist/background-session-reap.d.ts +1 -1
  35. package/dist/bake-runner/main.d.ts +4 -2
  36. package/dist/bind-host.d.ts +3 -6
  37. package/dist/bind-host.js +4 -7
  38. package/dist/boot/budget-tracing.js +1 -1
  39. package/dist/boot/coordinators.d.ts +1 -1
  40. package/dist/boot/coordinators.js +9 -10
  41. package/dist/boot/device-lane.js +2 -0
  42. package/dist/boot/engine-lease.d.ts +5 -0
  43. package/dist/boot/engine-lease.js +1 -0
  44. package/dist/boot/execution-env.d.ts +9 -14
  45. package/dist/boot/execution-env.js +85 -22
  46. package/dist/boot/execution-lane.js +32 -5
  47. package/dist/boot/late-binds.d.ts +8 -4
  48. package/dist/boot/late-binds.js +7 -1
  49. package/dist/boot/leader.d.ts +31 -30
  50. package/dist/boot/leader.js +38 -28
  51. package/dist/boot/leg-path-adjudication.d.ts +40 -0
  52. package/dist/boot/leg-path-adjudication.js +18 -0
  53. package/dist/boot/memory-consolidation.d.ts +3 -2
  54. package/dist/boot/memory-consolidation.js +6 -6
  55. package/dist/boot/parked-revive-gate.js +3 -15
  56. package/dist/boot/reapers.d.ts +6 -1
  57. package/dist/boot/reapers.js +13 -2
  58. package/dist/boot/resolve-spec.d.ts +3 -3
  59. package/dist/boot/resolve-spec.js +43 -48
  60. package/dist/boot/runner-deps.d.ts +46 -35
  61. package/dist/boot/runner-deps.js +94 -91
  62. package/dist/boot/session-age-out.d.ts +36 -0
  63. package/dist/boot/session-age-out.js +101 -0
  64. package/dist/boot/session-faces.d.ts +5 -14
  65. package/dist/boot/session-faces.js +9 -5
  66. package/dist/boot/shutdown.d.ts +6 -0
  67. package/dist/boot/shutdown.js +10 -4
  68. package/dist/boot/stage-01-config.js +2 -0
  69. package/dist/boot/stage-02-web-admin-pull.js +4 -5
  70. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  71. package/dist/boot/stage-05-execution-env.js +3 -2
  72. package/dist/boot/stage-06-runners.d.ts +9 -4
  73. package/dist/boot/stage-06-runners.js +24 -9
  74. package/dist/boot/stage-07-capability-layer.d.ts +8 -4
  75. package/dist/boot/stage-07-capability-layer.js +3 -2
  76. package/dist/boot/stage-08-reapers.d.ts +8 -4
  77. package/dist/boot/stage-08-reapers.js +4 -2
  78. package/dist/boot/stage-09-leader.d.ts +12 -5
  79. package/dist/boot/stage-09-leader.js +14 -5
  80. package/dist/boot/stage-10-http-server.d.ts +19 -5
  81. package/dist/boot/stage-10-http-server.js +14 -21
  82. package/dist/boot/stores.js +7 -4
  83. package/dist/boot/web-admin.js +5 -8
  84. package/dist/boot/workflow-orchestration.d.ts +7 -2
  85. package/dist/boot/workflow-orchestration.js +24 -7
  86. package/dist/bounded-rotation.d.ts +29 -0
  87. package/dist/bounded-rotation.js +24 -0
  88. package/dist/capabilities/approvals-push.d.ts +3 -3
  89. package/dist/capabilities/scenarios.d.ts +28 -13
  90. package/dist/capabilities/scenarios.js +2 -2
  91. package/dist/capability-facts.d.ts +43 -32
  92. package/dist/capability-facts.js +45 -51
  93. package/dist/card-rule-offers.d.ts +32 -0
  94. package/dist/card-rule-offers.js +130 -0
  95. package/dist/center-credential.d.ts +25 -6
  96. package/dist/center-credential.js +25 -5
  97. package/dist/config-catalog.d.ts +26 -7
  98. package/dist/config-catalog.js +26 -22
  99. package/dist/config-dir-trust.js +4 -2
  100. package/dist/config-invariants.d.ts +20 -8
  101. package/dist/config-invariants.js +55 -12
  102. package/dist/config-lkg.d.ts +5 -19
  103. package/dist/config-lkg.js +8 -62
  104. package/dist/config-provider.d.ts +1 -1
  105. package/dist/config-root-migration.d.ts +2 -2
  106. package/dist/config-types.d.ts +38 -42
  107. package/dist/config.d.ts +58 -7
  108. package/dist/config.js +90 -40
  109. package/dist/control-socket.d.ts +51 -0
  110. package/dist/control-socket.js +115 -0
  111. package/dist/deployment-governance.d.ts +2 -1
  112. package/dist/deployment-shape.d.ts +9 -1
  113. package/dist/deployment-shape.js +2 -1
  114. package/dist/device-protocol.d.ts +5 -4
  115. package/dist/device-protocol.js +1 -1
  116. package/dist/device-ws-hub.d.ts +7 -0
  117. package/dist/device-ws-hub.js +10 -3
  118. package/dist/device-ws-protocol.d.ts +63 -10
  119. package/dist/device-ws-protocol.js +56 -25
  120. package/dist/elicitation.js +2 -2
  121. package/dist/engine-identity.d.ts +19 -7
  122. package/dist/engine-identity.js +10 -2
  123. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  124. package/dist/entry-problems.d.ts +40 -3
  125. package/dist/entry-problems.js +3 -1
  126. package/dist/execution-env-refusal.d.ts +29 -0
  127. package/dist/execution-env-refusal.js +8 -0
  128. package/dist/execution-lane-caps.d.ts +3 -1
  129. package/dist/field-conflict.d.ts +1 -1
  130. package/dist/file-history-capture.d.ts +11 -4
  131. package/dist/file-history-capture.js +9 -6
  132. package/dist/file-history-disclosure.d.ts +7 -5
  133. package/dist/file-history-disclosure.js +7 -5
  134. package/dist/fleet/drain-entry.d.ts +4 -0
  135. package/dist/fleet/engine-signal.d.ts +12 -29
  136. package/dist/fleet/engine-signal.js +1 -7
  137. package/dist/fleet-client.js +2 -5
  138. package/dist/gate-settlement.d.ts +1 -8
  139. package/dist/gate-settlement.js +0 -4
  140. package/dist/history-anchor.js +1 -1
  141. package/dist/hitl-delivery.d.ts +2 -3
  142. package/dist/hitl-delivery.js +0 -2
  143. package/dist/hooks/hook-llm.js +4 -2
  144. package/dist/hooks/hook-runner.d.ts +3 -2
  145. package/dist/hooks/hook-runner.js +18 -3
  146. package/dist/host-decision.d.ts +11 -11
  147. package/dist/host-lsp-manager.d.ts +2 -2
  148. package/dist/http/active-run-conflict.d.ts +35 -2
  149. package/dist/http/active-run-conflict.js +48 -19
  150. package/dist/http/admission.js +17 -2
  151. package/dist/http/approval-sweeps.d.ts +1 -1
  152. package/dist/http/approval-sweeps.js +10 -7
  153. package/dist/http/checkpoint-ctx.d.ts +4 -0
  154. package/dist/http/checkpoint-ctx.js +2 -0
  155. package/dist/http/dispatch.d.ts +8 -3
  156. package/dist/http/dispatch.js +59 -32
  157. package/dist/http/durable-run-leg.js +16 -17
  158. package/dist/http/health-reader.d.ts +16 -9
  159. package/dist/http/health-reader.js +2 -1
  160. package/dist/http/health-self-report.d.ts +9 -1
  161. package/dist/http/health-self-report.js +3 -1
  162. package/dist/http/owner-access.d.ts +33 -14
  163. package/dist/http/owner-access.js +40 -10
  164. package/dist/http/principal-gate.d.ts +49 -21
  165. package/dist/http/principal-gate.js +61 -11
  166. package/dist/http/request-channel.d.ts +23 -0
  167. package/dist/http/request-channel.js +9 -0
  168. package/dist/http/request-guards.js +17 -2
  169. package/dist/http/resume-ctx-read.js +42 -3
  170. package/dist/http/resume-legs.d.ts +4 -3
  171. package/dist/http/resume-legs.js +367 -117
  172. package/dist/http/route-ctx.d.ts +91 -15
  173. package/dist/http/route-ctx.js +17 -0
  174. package/dist/http/route-table.d.ts +5 -1
  175. package/dist/http/route-table.js +4 -0
  176. package/dist/http/routes/admin-config-refresh.js +3 -3
  177. package/dist/http/routes/admin-drain.d.ts +3 -0
  178. package/dist/http/routes/admin-drain.js +7 -7
  179. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  180. package/dist/http/routes/admin-memory-optout.js +3 -3
  181. package/dist/http/routes/adoption.js +3 -5
  182. package/dist/http/routes/agents-roster.js +2 -3
  183. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  184. package/dist/http/routes/approvals-assistant.js +99 -51
  185. package/dist/http/routes/background.d.ts +3 -3
  186. package/dist/http/routes/capabilities.d.ts +4 -0
  187. package/dist/http/routes/capabilities.js +38 -10
  188. package/dist/http/routes/config-catalog.js +2 -2
  189. package/dist/http/routes/devices.js +1 -1
  190. package/dist/http/routes/diagnostics.d.ts +3 -3
  191. package/dist/http/routes/diagnostics.js +19 -7
  192. package/dist/http/routes/fleet.js +10 -8
  193. package/dist/http/routes/images.js +10 -10
  194. package/dist/http/routes/memory-bundle.js +3 -3
  195. package/dist/http/routes/memory-compliance.js +3 -3
  196. package/dist/http/routes/memory-consolidation.js +2 -2
  197. package/dist/http/routes/memory-origin.js +3 -3
  198. package/dist/http/routes/memory-policy.js +2 -2
  199. package/dist/http/routes/observability.js +3 -3
  200. package/dist/http/routes/projects.d.ts +3 -2
  201. package/dist/http/routes/projects.js +4 -5
  202. package/dist/http/routes/retention-ops.js +3 -3
  203. package/dist/http/routes/rules.js +5 -5
  204. package/dist/http/routes/run-permission-mode.d.ts +2 -1
  205. package/dist/http/routes/run-permission-mode.js +2 -2
  206. package/dist/http/routes/runs.d.ts +4 -9
  207. package/dist/http/routes/runs.js +171 -65
  208. package/dist/http/routes/session-sync.d.ts +22 -9
  209. package/dist/http/routes/session-sync.js +204 -107
  210. package/dist/http/routes/sessions-list.js +13 -7
  211. package/dist/http/routes/sessions.js +30 -15
  212. package/dist/http/routes/tasks.js +46 -41
  213. package/dist/http/routes/trace-usage.js +2 -2
  214. package/dist/http/routes/workflows.js +1 -1
  215. package/dist/http/server.d.ts +30 -30
  216. package/dist/http/server.js +16 -4
  217. package/dist/http/submit-ctx.d.ts +1 -1
  218. package/dist/http/submit-ctx.js +7 -4
  219. package/dist/http/wire-types.d.ts +14 -4
  220. package/dist/human-facing-args.d.ts +33 -0
  221. package/dist/human-facing-args.js +33 -0
  222. package/dist/identity-source.d.ts +20 -3
  223. package/dist/identity-source.js +14 -0
  224. package/dist/index.d.ts +5 -1
  225. package/dist/k8s-api-credentials.d.ts +29 -0
  226. package/dist/k8s-api-credentials.js +25 -0
  227. package/dist/lane-home-probe.d.ts +3 -7
  228. package/dist/lane-home-probe.js +2 -2
  229. package/dist/leader/diffup.d.ts +19 -0
  230. package/dist/leader/diffup.js +7 -0
  231. package/dist/leader/leader.d.ts +116 -11
  232. package/dist/leader/leader.js +69 -5
  233. package/dist/leader/repair-wire.js +2 -2
  234. package/dist/leader/run-entry.d.ts +2 -1
  235. package/dist/leader/run-entry.js +1 -1
  236. package/dist/leader/wire.d.ts +42 -132
  237. package/dist/leader/wire.js +81 -67
  238. package/dist/leg-facts.d.ts +13 -5
  239. package/dist/leg-facts.js +2 -1
  240. package/dist/memory-scope.d.ts +23 -11
  241. package/dist/memory-scope.js +18 -6
  242. package/dist/memory-sync-client.d.ts +30 -2
  243. package/dist/memory-sync-client.js +16 -9
  244. package/dist/model-entry-headers.d.ts +9 -10
  245. package/dist/model-entry-headers.js +0 -4
  246. package/dist/model-entry-refusal.d.ts +7 -4
  247. package/dist/model-entry-refusal.js +11 -5
  248. package/dist/model-plane-face.d.ts +1 -1
  249. package/dist/model-plane-face.js +3 -1
  250. package/dist/model-route-endpoint.d.ts +13 -7
  251. package/dist/model-route-endpoint.js +14 -11
  252. package/dist/model-select.d.ts +22 -0
  253. package/dist/model-select.js +9 -0
  254. package/dist/model-unreachable-notice.d.ts +11 -0
  255. package/dist/model-unreachable-notice.js +4 -4
  256. package/dist/object-store-buckets.d.ts +640 -6
  257. package/dist/object-store-buckets.js +166 -0
  258. package/dist/observability/err-text.d.ts +7 -0
  259. package/dist/observability/err-text.js +5 -0
  260. package/dist/observability/fail-open.d.ts +25 -17
  261. package/dist/observability/fail-open.js +25 -17
  262. package/dist/observability/logger.js +3 -1
  263. package/dist/observability/metrics.js +1 -0
  264. package/dist/operator-roster.d.ts +68 -0
  265. package/dist/operator-roster.js +32 -0
  266. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  267. package/dist/orchestration/workflow-notify-journal.js +12 -1
  268. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  269. package/dist/orchestration/workflow-park-decision.js +49 -10
  270. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  271. package/dist/orchestration/workflow-park-holder.js +98 -0
  272. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  273. package/dist/orchestration/workflow-record-fence.js +42 -0
  274. package/dist/outbound-dispatcher.d.ts +71 -8
  275. package/dist/outbound-dispatcher.js +48 -7
  276. package/dist/owner-faces.d.ts +2 -2
  277. package/dist/parked-decide.d.ts +106 -55
  278. package/dist/parked-decide.js +153 -117
  279. package/dist/peer-directory.d.ts +2 -2
  280. package/dist/peer-inbound-face.d.ts +2 -6
  281. package/dist/peer-inbound-face.js +0 -3
  282. package/dist/permission-mode-admission.d.ts +74 -5
  283. package/dist/permission-mode-admission.js +56 -3
  284. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  285. package/dist/plugins/adoption-log-sql.js +7 -1
  286. package/dist/plugins/approval-ask-store-file.d.ts +14 -2
  287. package/dist/plugins/approval-ask-store-file.js +11 -0
  288. package/dist/plugins/approval-ask-store-memory.d.ts +10 -1
  289. package/dist/plugins/approval-ask-store-memory.js +35 -10
  290. package/dist/plugins/approval-ask-store-sql.d.ts +69 -4
  291. package/dist/plugins/approval-ask-store-sql.js +45 -5
  292. package/dist/plugins/approval-exemption-store.js +3 -0
  293. package/dist/plugins/approval-nonce-store.js +2 -0
  294. package/dist/plugins/blob-backend.d.ts +83 -7
  295. package/dist/plugins/blob-backend.js +57 -6
  296. package/dist/plugins/breaker-state-sql.js +2 -0
  297. package/dist/plugins/caching-session-store.d.ts +9 -15
  298. package/dist/plugins/caching-session-store.js +6 -0
  299. package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
  300. package/dist/plugins/checkpoint-index-columns.js +3 -6
  301. package/dist/plugins/checkpoint-store-face.d.ts +119 -61
  302. package/dist/plugins/checkpoint-store-face.js +134 -55
  303. package/dist/plugins/device-store-sql.js +6 -0
  304. package/dist/plugins/error-origin.d.ts +18 -0
  305. package/dist/plugins/error-origin.js +10 -0
  306. package/dist/plugins/execution-placement-sql.js +6 -6
  307. package/dist/plugins/image-bake-store-sql.js +6 -0
  308. package/dist/plugins/image-index-sql.js +4 -0
  309. package/dist/plugins/landed-attendants.d.ts +212 -0
  310. package/dist/plugins/landed-attendants.js +161 -0
  311. package/dist/plugins/leader-run-store-sql.js +4 -0
  312. package/dist/plugins/local-checkpoint-store.d.ts +13 -3
  313. package/dist/plugins/local-checkpoint-store.js +38 -49
  314. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  315. package/dist/plugins/local-session-live-reference.js +39 -0
  316. package/dist/plugins/local-session-store.d.ts +71 -33
  317. package/dist/plugins/local-session-store.js +129 -27
  318. package/dist/plugins/memory-engine-pg.js +4 -0
  319. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  320. package/dist/plugins/memory-engine-tidb.js +4 -0
  321. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  322. package/dist/plugins/memory-sync-store-pg.js +5 -0
  323. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  324. package/dist/plugins/one-time-migrations.d.ts +104 -80
  325. package/dist/plugins/one-time-migrations.js +226 -132
  326. package/dist/plugins/org-delivery-quota-store.js +4 -1
  327. package/dist/plugins/outcome-ledger-sql.js +2 -0
  328. package/dist/plugins/permission-rule-store-sql.js +11 -4
  329. package/dist/plugins/pg-cost-quota.js +4 -0
  330. package/dist/plugins/pg-pool.js +35 -10
  331. package/dist/plugins/pg-rate-limiter.js +2 -0
  332. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  333. package/dist/plugins/posix-shell-fs.js +4 -1
  334. package/dist/plugins/remote-env-adb.d.ts +19 -15
  335. package/dist/plugins/remote-env-adb.js +41 -15
  336. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  337. package/dist/plugins/remote-env-connect-seat.js +72 -0
  338. package/dist/plugins/remote-env-device.d.ts +22 -5
  339. package/dist/plugins/remote-env-device.js +106 -8
  340. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  341. package/dist/plugins/remote-env-e2b.js +42 -32
  342. package/dist/plugins/remote-env-file-error.js +6 -2
  343. package/dist/plugins/remote-env-host.d.ts +46 -10
  344. package/dist/plugins/remote-env-host.js +124 -52
  345. package/dist/plugins/remote-env-k8s.d.ts +109 -41
  346. package/dist/plugins/remote-env-k8s.js +160 -54
  347. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  348. package/dist/plugins/remote-env-local-docker.js +34 -41
  349. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  350. package/dist/plugins/remote-env-ssh.js +60 -59
  351. package/dist/plugins/remote-shell.d.ts +98 -1
  352. package/dist/plugins/remote-shell.js +218 -0
  353. package/dist/plugins/replica-registration.d.ts +1 -1
  354. package/dist/plugins/replica-registration.js +1 -1
  355. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  356. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  357. package/dist/plugins/retention-lane-store-sql.js +4 -0
  358. package/dist/plugins/retention-sql.d.ts +20 -2
  359. package/dist/plugins/retention-sql.js +11 -0
  360. package/dist/plugins/run-store-sql.d.ts +21 -2
  361. package/dist/plugins/run-store-sql.js +31 -7
  362. package/dist/plugins/s3-presign.d.ts +23 -0
  363. package/dist/plugins/s3-presign.js +37 -3
  364. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  365. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  366. package/dist/plugins/scheduler-support.d.ts +2 -2
  367. package/dist/plugins/scheduler-support.js +0 -3
  368. package/dist/plugins/schema-consistency.d.ts +4 -2
  369. package/dist/plugins/schema-consistency.js +19 -2
  370. package/dist/plugins/schema-indexes.js +2 -0
  371. package/dist/plugins/send-file-ledger.d.ts +2 -2
  372. package/dist/plugins/send-file-ledger.js +5 -2
  373. package/dist/plugins/send-user-file.d.ts +3 -4
  374. package/dist/plugins/send-user-file.js +56 -52
  375. package/dist/plugins/serving-requirement.d.ts +26 -7
  376. package/dist/plugins/serving-requirement.js +11 -4
  377. package/dist/plugins/session-birth.d.ts +6 -2
  378. package/dist/plugins/session-blob-claim-token.d.ts +15 -0
  379. package/dist/plugins/session-blob-claim-token.js +20 -0
  380. package/dist/plugins/session-blob-namespace.d.ts +46 -0
  381. package/dist/plugins/session-blob-namespace.js +116 -0
  382. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  383. package/dist/plugins/session-ext-table.d.ts +14 -1
  384. package/dist/plugins/session-ext-table.js +5 -1
  385. package/dist/plugins/session-row-sql.d.ts +49 -2
  386. package/dist/plugins/session-row-sql.js +56 -13
  387. package/dist/plugins/session-store-sql.d.ts +34 -18
  388. package/dist/plugins/session-store-sql.js +98 -17
  389. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  390. package/dist/plugins/shared-memory-store-sql.js +6 -2
  391. package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
  392. package/dist/plugins/snapshot-bucket-presence.js +8 -2
  393. package/dist/plugins/sql-driver.d.ts +2 -1
  394. package/dist/plugins/sql-driver.js +18 -3
  395. package/dist/plugins/sql-errors.d.ts +11 -6
  396. package/dist/plugins/sql-errors.js +5 -6
  397. package/dist/plugins/sql-json-column.d.ts +1 -1
  398. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  399. package/dist/plugins/stop-then-start-sentence.js +14 -10
  400. package/dist/plugins/stop-then-start.d.ts +109 -0
  401. package/dist/plugins/stop-then-start.js +104 -0
  402. package/dist/plugins/store-backend.d.ts +84 -15
  403. package/dist/plugins/store-backend.js +236 -72
  404. package/dist/plugins/store-boundary.d.ts +57 -0
  405. package/dist/plugins/store-boundary.js +139 -0
  406. package/dist/plugins/store-contracts.d.ts +17 -0
  407. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  408. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  409. package/dist/plugins/store-fault.d.ts +26 -7
  410. package/dist/plugins/store-fault.js +18 -2
  411. package/dist/plugins/task-attachment-store.d.ts +2 -1
  412. package/dist/plugins/task-attachment-store.js +14 -1
  413. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  414. package/dist/plugins/task-list-store-sql.js +10 -8
  415. package/dist/plugins/tidb-cost-quota.js +4 -0
  416. package/dist/plugins/tidb-pool.js +21 -25
  417. package/dist/plugins/tidb-rate-limiter.js +4 -0
  418. package/dist/plugins/user-input-ledger-sql.js +4 -0
  419. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  420. package/dist/plugins/workflow-run-store-sql.js +64 -4
  421. package/dist/plugins/workspace-custody.d.ts +4 -3
  422. package/dist/plugins/workspace-fill.d.ts +4 -7
  423. package/dist/plugins/workspace-fill.js +2 -1
  424. package/dist/plugins/write-behind-counter.js +5 -0
  425. package/dist/project-context.d.ts +0 -2
  426. package/dist/project-context.js +4 -8
  427. package/dist/project-registry.d.ts +46 -0
  428. package/dist/project-registry.js +44 -0
  429. package/dist/question.d.ts +2 -1
  430. package/dist/question.js +5 -13
  431. package/dist/request-form.d.ts +6 -1
  432. package/dist/request-form.js +4 -1
  433. package/dist/request-key-closure.js +1 -1
  434. package/dist/request-value-closure.d.ts +4 -0
  435. package/dist/request-value-closure.js +10 -0
  436. package/dist/rule-issue-reading.d.ts +1 -1
  437. package/dist/rules-consent.d.ts +1 -1
  438. package/dist/run-acceptance.d.ts +3 -2
  439. package/dist/run-acceptance.js +4 -6
  440. package/dist/run-local.js +9 -9
  441. package/dist/runs.d.ts +54 -16
  442. package/dist/runs.js +67 -13
  443. package/dist/runtime-governance.d.ts +5 -4
  444. package/dist/security.d.ts +30 -32
  445. package/dist/security.js +41 -7
  446. package/dist/sema-config-dir.d.ts +2 -1
  447. package/dist/server-secret-env.d.ts +12 -10
  448. package/dist/server-secret-env.js +24 -15
  449. package/dist/session-bindings.d.ts +82 -0
  450. package/dist/session-bindings.js +18 -0
  451. package/dist/session-children-retire.d.ts +2 -2
  452. package/dist/session-children-retire.js +2 -6
  453. package/dist/session-deletion.d.ts +16 -1
  454. package/dist/session-error-wire.d.ts +3 -3
  455. package/dist/session-error-wire.js +3 -3
  456. package/dist/session-incarnation.d.ts +10 -0
  457. package/dist/session-incarnation.js +10 -1
  458. package/dist/session-kind.d.ts +57 -0
  459. package/dist/session-kind.js +22 -0
  460. package/dist/session-row.d.ts +21 -1
  461. package/dist/session-row.js +7 -1
  462. package/dist/session-sync-kernel.d.ts +24 -14
  463. package/dist/session-sync-kernel.js +3 -6
  464. package/dist/session-sync.d.ts +25 -103
  465. package/dist/session-sync.js +14 -76
  466. package/dist/session-titler.d.ts +10 -3
  467. package/dist/session-titler.js +19 -6
  468. package/dist/ssh-host-key.d.ts +5 -0
  469. package/dist/ssh-host-key.js +3 -0
  470. package/dist/stores-sql/blob-index-sql.d.ts +30 -9
  471. package/dist/stores-sql/blob-index-sql.js +40 -21
  472. package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
  473. package/dist/stores-sql/checkpoint-store-sql.js +70 -146
  474. package/dist/stores-sql/file-history-store-sql.d.ts +113 -15
  475. package/dist/stores-sql/file-history-store-sql.js +205 -95
  476. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  477. package/dist/stores-sql/pg-session-storage.js +37 -13
  478. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  479. package/dist/stores-sql/retention-store-sql.js +5 -19
  480. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  481. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  482. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  483. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  484. package/dist/stores-sql/tidb-session-store.js +37 -13
  485. package/dist/submission-exit.d.ts +3 -2
  486. package/dist/submission-exit.js +9 -1
  487. package/dist/task-cwd.d.ts +2 -2
  488. package/dist/task-settings.d.ts +7 -5
  489. package/dist/task-settings.js +3 -3
  490. package/dist/terminal.d.ts +33 -2
  491. package/dist/terminal.js +139 -9
  492. package/dist/tool-approval.d.ts +465 -507
  493. package/dist/tool-approval.js +606 -1222
  494. package/dist/trace/core-keyset-guard.d.ts +1 -1
  495. package/dist/trace/ledger-events.d.ts +3 -3
  496. package/dist/trace/project.d.ts +2 -3
  497. package/dist/trace/redact.js +3 -2
  498. package/dist/trace/self-minted-wire.d.ts +571 -178
  499. package/dist/trace/self-minted-wire.js +189 -56
  500. package/dist/web-admin-client/apply-effective.js +2 -2
  501. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  502. package/dist/web-admin-client/read-warnings.js +1 -1
  503. package/dist/web-admin-client/types.d.ts +1 -1
  504. package/dist/workspace-space.d.ts +10 -4
  505. package/dist/workspace-space.js +4 -1
  506. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +79 -28
  507. package/package.json +7 -3
  508. package/scripts/embed-minimal.mjs +3 -3
  509. package/wire-changes/server@7.151.0.json +762 -0
  510. package/wire-changes/server@7.152.0.json +900 -0
  511. package/dist/config-d-migration.d.ts +0 -37
  512. package/dist/config-d-migration.js +0 -60
  513. package/dist/http/session-inherit-env.d.ts +0 -36
  514. package/dist/http/session-inherit-env.js +0 -6
@@ -0,0 +1,22 @@
1
+ export const SESSION_KINDS = ["sidechain"];
2
+ export function isSessionKind(v) {
3
+ return typeof v === "string" && SESSION_KINDS.includes(v);
4
+ }
5
+ export function sessionKindListed(rowKind, view) {
6
+ return rowKind === view;
7
+ }
8
+ export function sessionKindFaceOf(store) {
9
+ const s = store;
10
+ return typeof s?.sessionKindOf === "function" ? { sessionKindOf: s.sessionKindOf.bind(s) } : undefined;
11
+ }
12
+ export function sessionKindFromCell(sessionId, cell) {
13
+ if (cell === null || cell === undefined)
14
+ return undefined;
15
+ if (isSessionKind(cell))
16
+ return cell;
17
+ throw new Error(`session ${sessionId} carries a session kind this build does not know (${JSON.stringify(String(cell).slice(0, 32))}) — refusing to read it as a top-level session; a newer build wrote it, or the row was edited by hand`);
18
+ }
19
+ export function idleSessionAfter(row, cursor) {
20
+ return cursor === undefined || row.lastActivityAt > cursor.lastActivityAt || (row.lastActivityAt === cursor.lastActivityAt && row.sessionId > cursor.sessionId);
21
+ }
22
+ //# sourceMappingURL=session-kind.js.map
@@ -1,4 +1,5 @@
1
1
  import { type SessionLogMark, type SessionRowExpectation } from "@sema-agent/core";
2
+ import type { SessionFace } from "./session-incarnation.js";
2
3
  /** `title` 列的宽度(字符)= 用户标题的上界。两方言 DDL 内插它。 */
3
4
  export declare const SESSION_TITLE_MAX_CHARS = 120;
4
5
  /** 会话行的可写位(体闭集;两键至少一键)。会话店唯一行格写口 `writeSessionRowCells` 的格参。`title: null` = 清回自动标题(列写 NULL)。 */
@@ -81,6 +82,9 @@ export interface LockedSessionRow {
81
82
  readonly incarnation: string | undefined;
82
83
  /** 委派分区行(SQL `placement_kind` 非空;local 没有)—— 不在列表面上,不写。 */
83
84
  readonly placed: boolean;
85
+ /** 这只会话在删(会话三态 活 → 在删 → 没了,`src/session-deletion.ts`):SQL = 锁读的旁表在删令牌非空;local 的锁内读法({@link LockedSessionRow} 的 local 半)
86
+ * 只交活格,在删格在那里已读作「没有行」⇒ 恒 `false`。判官把它读作行不在(三态律:删除门之外的一切写者把在删读作行不在)。 */
87
+ readonly deleting: boolean;
84
88
  /** 执行格是 `mirror`(写者在别处,本处只读)。 */
85
89
  readonly mirror: boolean;
86
90
  readonly cells: SessionRowCells;
@@ -89,7 +93,7 @@ export interface LockedSessionRow {
89
93
  export type SessionRowVerdict = "write" | "changed" | "mirror" | "absent";
90
94
  /**
91
95
  * 写原语的**唯一判官**(头注「写原语」):三店在各自的锁下读出那一行、交这一只,判序因此逐字同形 ——
92
- * ① **行期望**:core 的 `sessionRowMatches(how.row, 锁住的行, logNow)`;不中、没有行(`undefined`)、委派分区行 ⇒ `absent`;
96
+ * ① **行期望**:core 的 `sessionRowMatches(how.row, 锁住的行, logNow)`;不中、没有行(`undefined`)、在删行、委派分区行 ⇒ `absent`(在删 = 行不在,三态律);
93
97
  * ② **写者身份**:`client` ∧ 执行格为 `mirror` ⇒ `mirror`(镜像在本处只读;`replay` 是镜像的名写者,照写);
94
98
  * ③ **标签**:`client` 带 `rowTag` ∧ 锁住的行算出的标签({@link sessionRowTagOf})≠ 它 ⇒ `changed`(行上没有化身 ⇒ 算不出标签 ⇒ 任何标签都不中);
95
99
  * ④ `write`。
@@ -97,6 +101,22 @@ export type SessionRowVerdict = "write" | "changed" | "mirror" | "absent";
97
101
  * `logNow` 只在行期望带 `log` 时被调(调用方在同一把锁下先读好那只 mark)。
98
102
  */
99
103
  export declare function judgeSessionRowWrite(sessionId: string, how: SessionRowWrite, locked: LockedSessionRow | undefined, logNow: () => SessionLogMark): SessionRowVerdict;
104
+ /**
105
+ * **落定段**的结局(SQL `withLandedRow` · local `LocalSessionStore.withLanded` 两只实现同一形):`landed: true` = 段首锁住的那一行仍是落定的那一位住客
106
+ * ({@link judgeLandedRow} 答 `write`),段体在同一事务 / 同一把会话写锁里跑完、交回它的值;`landed: false` = 那一位已不在(行不在 / 在删 / 换了住客 / 属主换代 /
107
+ * 委派分区行),段体一个字节都没跑。会话的附随物(文件历史图 · 会话规则行 · 回退锚 · 自动标题)只经这一段写 —— 律在 `plugins/landed-attendants.ts` 头注。
108
+ */
109
+ export type LandedOutcome<T> = {
110
+ readonly landed: true;
111
+ readonly value: T;
112
+ } | {
113
+ readonly landed: false;
114
+ };
115
+ /**
116
+ * 落定段的判:锁住的那一行是不是 `landed` 那一位住客 —— **就是**行格写原语的判官 {@link judgeSessionRowWrite} 以 `replay` 写者问(行期望 = `{ row: landed }`;
117
+ * 行在 ∧ 非在删 ∧ 非委派分区 ∧ 属主与化身整对同,core `sessionRowMatches`)。一只判官,没有第二份比较。
118
+ */
119
+ export declare function judgeLandedRow(sessionId: string, landed: SessionFace, locked: LockedSessionRow | undefined): boolean;
100
120
  /** 应答行:提交 / 回滚之后另读的那一行 + 读到它时行上的住客(属主 + 化身那一格)。 */
101
121
  export interface SessionRowAnswer<Row> {
102
122
  readonly row: Row;
@@ -13,7 +13,7 @@ export function sessionRowTagKey(row) {
13
13
  return row.incarnation === undefined ? {} : { rowTag: sessionRowTagOf({ sessionId: row.sessionId, owner: row.owner, incarnation: row.incarnation, cells: row.cells }) };
14
14
  }
15
15
  export function judgeSessionRowWrite(sessionId, how, locked, logNow) {
16
- if (locked === undefined || !sessionRowMatches(how.row, { owner: locked.owner, incarnation: locked.incarnation }, logNow) || locked.placed)
16
+ if (locked === undefined || locked.deleting || !sessionRowMatches(how.row, { owner: locked.owner, incarnation: locked.incarnation }, logNow) || locked.placed)
17
17
  return "absent";
18
18
  switch (how.writer) {
19
19
  case "replay":
@@ -32,6 +32,12 @@ export function judgeSessionRowWrite(sessionId, how, locked, logNow) {
32
32
  }
33
33
  }
34
34
  }
35
+ export function judgeLandedRow(sessionId, landed, locked) {
36
+ return judgeSessionRowWrite(sessionId, { writer: "replay", row: { row: landed } }, locked, landedCarriesNoLogMark) === "write";
37
+ }
38
+ function landedCarriesNoLogMark() {
39
+ throw new Error("judgeLandedRow: a landed-section expectation carries no log mark, yet the judge asked for one");
40
+ }
35
41
  export function settleSessionRowWrite(how, verdict, answer) {
36
42
  const judged = how.row.row;
37
43
  if (judged === null || answer === undefined)
@@ -28,11 +28,11 @@ export interface StagingHandle {
28
28
  /** Append a bounded batch of entries to the staging id, re-seq'd dense from a running counter held in the handle.
29
29
  * Idempotent (ON DUPLICATE KEY / ON CONFLICT DO NOTHING) — a re-streamed batch is a no-op. One txn per batch. */
30
30
  appendBatch(entries: SessionTreeEntry[]): Promise<void>;
31
- /** ATOMICALLY swap the staged rows into the real session id (ONE txn): owner CAS ({@link StagedCommitOptions.expectedOwner},
31
+ /** ATOMICALLY swap the staged rows into the real session id (ONE txn): occupant CAS ({@link StagedCommitOptions.expectedOccupant},
32
32
  * before anything about the row is classified or reported, so a refused commit reveals nothing about a row it did not judge — no
33
33
  * placement, no relation, no entry ids) → in-txn re-classify
34
34
  * (TOCTOU close) → purge dst → row-key rename staging→real → session_meta settled; COMMIT is the commit point. `owner` is re-stamped (§9).
35
- * Throws {@link StagedCommitOwnerChangedError} when the owner cell is not the one the import's gate judged, and
35
+ * Throws {@link StagedCommitOccupantChangedError} when the row is not the occupant the import's gate judged, and
36
36
  * {@link SyncConflictError} on an unconsented fork/stale flip — both after rolling back. Returns the committed relation **and the settled session
37
37
  * face** — the occupant (owner + incarnation) the commit left in the real session id, read INSIDE the same txn (the local
38
38
  * store: the same hold of the session's write lock), never re-read afterwards. The post-commit replays conditioned on the
@@ -47,29 +47,39 @@ export interface StagingHandle {
47
47
  abort(): Promise<void>;
48
48
  }
49
49
  /**
50
- * What a staged commit is conditioned on ([ref] — one gate predicate, two phases). `expectedOwner` = the owner cell the import's gate
51
- * judged when it opened the staging, in the head-read reading (`headOf(id)?.owner`): `undefined` = no such session (a fresh push), `null` = ownerless, a string =
52
- * that owner. The route judges `mayTouch` on that cell at Phase A and again on the session's current cell right before Phase B commits; the
53
- * commit itself lands only on that same cell — each store compares it inside the commit txn (local: inside the session's write lock) and, when
54
- * the cell differs, rolls back and throws {@link StagedCommitOwnerChangedError} without writing a byte. The compare is on the owner cell, not
55
- * on the incarnation: the gate's predicate reads only the owner (plus placement, which the commit gate refuses on its own), so a same-owner
56
- * re-create is judged the same and is not a reason to refuse.
50
+ * What a staged commit is conditioned on ([ref] — one gate predicate, two phases). `expectedOccupant` = the occupant the import's gate judged when it
51
+ * opened the staging — the live row's owner **and** incarnation, one head read (`undefined` = no live session: a fresh push, which claims the id).
52
+ * The route judges `mayTouch` at Phase A and again on the session's current row right before Phase B commits; the commit itself lands only on that
53
+ * same occupant — each store compares owner ∧ incarnation inside the commit txn (SQL `owner <=> ? AND incarnation = ?`; local: the same judge as every
54
+ * occupant check, `sameOccupant`, inside the session's write lock) and, when it differs, rolls back and throws {@link StagedCommitOccupantChangedError}
55
+ * without writing a byte. So a session deleted and re-created while the entries streamed — by anyone, the same owner included — is not the session
56
+ * phase one classified, and the commit does not land on it (the push restarts from phase one).
57
57
  */
58
+ /**
59
+ * 拉取流的选项:`afterSeq` 续传游标(后端原生 seq)· `batchSize` 页长 · `recheck` = 调用方的住客复判 —— **每页读后、交出那一页的任何一条之前**调一次
60
+ * (local 孪生整读之后、交出第一条之前调一次);它抛 ⇒ 流在那里结束(调用方读作截断)。必填:流不会在没有复判的情况下交出条目。
61
+ */
62
+ export interface EntriesStreamOptions {
63
+ readonly afterSeq?: number;
64
+ readonly batchSize?: number;
65
+ readonly recheck: () => Promise<void>;
66
+ }
58
67
  export interface StagedCommitOptions {
59
- readonly expectedOwner: string | null | undefined;
68
+ readonly expectedOccupant: {
69
+ readonly owner: string | null;
70
+ readonly incarnation: string;
71
+ } | undefined;
60
72
  readonly resolution?: "overwrite-dst";
61
73
  }
62
74
  /**
63
- * The staged commit's owner CAS did not hold: the session's owner cell at commit time is not the one the import's gate judged (another replica
75
+ * The staged commit's occupant CAS did not hold: the session row at commit time is not the occupant the import's gate judged (another replica
64
76
  * deleted / re-created / claimed / created the session while the import streamed). Code = core's closed-set `conflict` (wire: the import face's
65
77
  * row in `session-error-wire.ts`, 409); thrown after the commit txn rolled back — the destination is untouched. The three stores throw this one
66
78
  * class (one sentence, minted here).
67
79
  */
68
- export declare class StagedCommitOwnerChangedError extends SessionError {
80
+ export declare class StagedCommitOccupantChangedError extends SessionError {
69
81
  constructor(sessionId: string);
70
82
  }
71
- /** The owner-cell comparison of {@link StagedCommitOptions.expectedOwner} against a store's own owner read (`headOf(id)?.owner`; `undefined` = no row). */
72
- export declare function ownerCellIs(expectedOwner: string | null | undefined, actual: string | null | undefined): boolean;
73
83
  /** A session store that can stage a streamed import (P1d-β). The 3 backends implement it; the in-memory dev store
74
84
  * does not → the method is optional (the route 501s when absent, parity with the other §0.5 optional seams). */
75
85
  export type ImportStagingStore = {
@@ -1,14 +1,11 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { SessionError, StreamingImportValidator } from "@sema-agent/core";
3
- export class StagedCommitOwnerChangedError extends SessionError {
3
+ export class StagedCommitOccupantChangedError extends SessionError {
4
4
  constructor(sessionId) {
5
- super("conflict", `session ${sessionId}: owner changed while the import streamed — the staged commit was rolled back`);
6
- this.name = "StagedCommitOwnerChangedError";
5
+ super("conflict", `session ${sessionId}: the session changed hands while the import streamed (deleted, re-created or re-owned) — the staged commit was rolled back`);
6
+ this.name = "StagedCommitOccupantChangedError";
7
7
  }
8
8
  }
9
- export function ownerCellIs(expectedOwner, actual) {
10
- return expectedOwner === undefined ? actual === undefined : actual !== undefined && (actual ?? null) === expectedOwner;
11
- }
12
9
  export function stagingIdFor(realSessionId, token) {
13
10
  return `${realSessionId}#stg-${token}`;
14
11
  }
@@ -36,6 +36,8 @@
36
36
  * Neither gap is fixed by this car (mechanical removal, no behavior changes) — they are reported here + in the
37
37
  * removal's final report for separate triage (port the fix to the live route, or explicitly accept the regression).
38
38
  *
39
+ * ✅ **STATUS UPDATE(会话附随物绑住客)**:附随物的写半(擦除 / 文件历史重放 / 规则重放与等内容判据 / 锚)整体搬到落定席位 `plugins/landed-attendants.ts`
40
+ * —— 每一笔写都在锁住会话根行、判过住客的落定段里;本模块只剩读半(manifest 导出、文件历史导出、blob 取值口)与干跑。
39
41
  * ✅ **STATUS UPDATE (2026-08-06 同日,[ref] 红先修绿)**:上面两条「NO live-route equivalent」已作废——
40
42
  * 两个保护都已移植到活路由(本文件尾部 [ref] 段:`sameRulesContent`/`replayPolicyRecords`/
41
43
  * `overwriteWipeIncapable`/`wipeSessionAttendants`;路由接线=src/http/routes/session-sync.ts 的两条重放腿
@@ -45,8 +47,8 @@
45
47
  */
46
48
  import type { StoreBackend } from "./plugins/store-backend.js";
47
49
  import { type SyncRelation } from "./session-sync-kernel.js";
48
- import { type SessionTreeEntry, type SessionRulesRecord, type FileHistoryError, type FileHistoryExport, type FileHistoryResult, type SessionPermissionRules, type StoredSessionRules, type SessionPolicyStore } from "@sema-agent/core";
49
- import type { ServiceBlobExchange, ServiceFileHistoryStore, ServiceSessionPolicyStore, ResumeAnchorStore } from "./plugins/store-backend.js";
50
+ import type { SessionTreeEntry, SessionRulesRecord, FileHistoryError, FileHistoryExport } from "@sema-agent/core";
51
+ import type { ServiceBlobExchange, ServiceFileHistoryStore, SqlBlobPutResult, SyncLanding } from "./plugins/store-backend.js";
50
52
  /**
51
53
  * core 7.55.0 [ref] T2([ref]):一只会话的文件历史**读不全**(撕裂 / 读不出的边界、读不出的跟踪集 —— 店的 `exportHistory` 答 `{ ok: false }`,`adopt_failed`
52
54
  * 带店判的 `permanence`)。它**不是**「没有历史」:读成 `null` 的同步会把这只会话的全部 rewind 历史丢在路上、还答成功。会话同步的两只拉取口
@@ -64,68 +66,38 @@ export declare class FileHistoryUnreadableError extends Error {
64
66
  */
65
67
  export declare function exportedFileHistory(store: Pick<ServiceFileHistoryStore, "exportHistory">, sessionId: string): Promise<FileHistoryExport | null>;
66
68
  /** A blob getter: content-addressed bytes for a sha256 hash, or `undefined` if the source can't supply them.
67
- * Returned by {@link exportSessionManifest} (closes over the source store); consumed wherever a caller pulls
68
- * content-addressed history bytes lazily (the live PUSH route's `importHistory` replay). */
69
+ * The live PUSH route's `importHistory` replay reads one — always the getter of a {@link VisibleBlobs} answer (never an unscoped read of the store). */
69
70
  export type BlobGetter = (hash: string) => Promise<Uint8Array | undefined>;
70
71
  /**
71
72
  * 「这只文件历史店能不能交换 blob」的**唯一**读法:店的 `blobExchange` 组,经 core 的判官 `fileHistoryBlobExchangeOf` 读 —— 没有组 ⇒ `undefined`
72
73
  * (core 的 local 文件店);整组 ⇒ 三只成员(判官已绑好);缺一员 / 组不是对象 / 店还带退役的标记词 ⇒ 判官拒(`config.invalid_argument`,响亮,不是「没有」)。
73
- * 能力事实 `sessionSyncFileHistory`(capability-facts)与取值口 {@link blobGetterOf} 都读它 —— 事实说有,取值口就通到同一组的 `get`;事实说没有,取值口恒空。
74
+ * 会话同步的 blob 面 {@link landingBlobFacesOf} 读它取字节;交换组的 `put` / `has` 路由不握有 —— 读它们的是 core 契约套件与店自己的按落点来历面(经它写字节、问在场)。
74
75
  * 返回型收窄到本仓的 {@link ServiceBlobExchange}:判官交回的是店自己那一组的成员(只绑定、不包装),店型声明的组就是那一型。
75
76
  */
76
77
  export declare function blobExchangeOf(store: ServiceFileHistoryStore): ServiceBlobExchange | undefined;
77
- /** 一只文件历史店自己的 blob 取值口({@link BlobGetter}):从 {@link blobExchangeOf} 派生 —— 有交换组 ⇒ 组的 `get`;没有(core 的 local 文件店)⇒ 恒答 `undefined`(「这里取不到」)。
78
- * 拉取面(manifest 的 `getBlob`)、identical 自愈臂、提交臂三处读同一只,不各写一份「无组则 undefined」。 */
79
- export declare function blobGetterOf(store: ServiceFileHistoryStore): BlobGetter;
80
- /** The history-import capability face of a `FileHistoryStore` (`importHistory` is OPTIONAL on the core
81
- * contract — a store without it cannot be a 2c history sink). */
82
- export interface FileHistoryImportFace {
83
- importHistory(scope: string, data: FileHistoryExport, srcGetBlob: BlobGetter): Promise<FileHistoryResult>;
78
+ /**
79
+ * 一次可见性读的答案:这个落点此刻看得见的 hash 集,和**只答这几枚**的取值口(集外的一律 `undefined` —— 重放观察到它,答与预检同一个 `missing_blob`)。
80
+ * 预检的答案就是重放取值口的许可集:第一阶段读一次、冻结进暂存随第二阶段走 —— 覆盖写的擦除排在重放之前,擦掉旧图不改许可。
81
+ */
82
+ export interface VisibleBlobs {
83
+ readonly hashes: ReadonlySet<string>;
84
+ readonly get: BlobGetter;
84
85
  }
85
- /** Single-point capability probe ([ref] R7): does `fs` carry the optional `importHistory` seam?
86
- * Returns the bound method (never a naked function reference — `this` inside must resolve to `fs`) or
87
- * `undefined` when absent. Callers gate on the RETURN VALUE, not a separate `typeof` check, so there is
88
- * exactly one place that knows the shape of the probe. */
89
- export declare function fileHistoryImportFace(fs: unknown): FileHistoryImportFace | undefined;
86
+ /** 什么都看不见的答案:信封不引用 blob,或这台部署没有 blob 面(下面 {@link landingBlobFacesOf} 答 `undefined`)。 */
87
+ export declare const NO_VISIBLE_BLOBS: VisibleBlobs;
90
88
  /**
91
- * {@link replayFileHistory} 的结果 = 店的 {@link FileHistoryResult};失败臂可再带 `missingBlob` —— 这一次尝试里**重放自己交给店的 getter 亲口答过 `undefined`** 的那枚 hash
92
- * (「这枚 blob 不在」是 getter 观察到的事实,不是从店码里猜的:同一个 `adopt_failed` 也可能是取值抛了 / 发布步撞上库的故障,那两形 `missingBlob` 缺席)。
93
- * 下发口({@link sendFileHistoryReplayFailure} 在路由里)据它铸 wire `missing_blob`,否则原样送店的码。
89
+ * 会话同步路由读写 blob 的**唯一**面(按落点):上传 = 店的按落点来历面 `putAt`(核址 → 字节 → 授予);可见 = 店的 `visibleAt`,取字节经交换组的 `get`、只放行可见集里的那几枚。
90
+ * 预检、identical 自愈臂与提交臂的重放取值、`GET …/sync/history/blobs/:hash` 三口读的是同一只 `visible`。
94
91
  */
95
- export type FileHistoryReplayResult = {
96
- readonly ok: true;
97
- } | {
98
- readonly ok: false;
99
- readonly error: FileHistoryError;
100
- readonly missingBlob?: string;
101
- };
102
- export type FileHistoryReplayFailure = Extract<FileHistoryReplayResult, {
103
- ok: false;
104
- }>;
92
+ export interface LandingBlobFaces {
93
+ put(landing: SyncLanding, hash: string, bytes: Uint8Array): Promise<SqlBlobPutResult>;
94
+ visible(landing: SyncLanding, hashes: readonly string[]): Promise<VisibleBlobs>;
95
+ }
105
96
  /**
106
- * The 2c PUSH replay of a session's file-history graph — keep-SOURCE convergence over core's
107
- * non-destructive-target law. `importHistory` refuses a target that already holds a DIFFERENT graph
108
- * (typed `conflict`; the identical envelope re-publishes idempotently). On that arm, when the store has
109
- * the purge seam, the sync's documented semantic (the destination converges to the source's state, same
110
- * posture as the policy/anchor replays) is applied: wipe the scope, then import once more. Every bundled
111
- * store carries the verb (the SQL twins, and core's local File store from core 7.64.0), so the three backends
112
- * read one rule; a store WITHOUT `deleteBySession` (an embedder's) surfaces the typed conflict unchanged —
113
- * an honest capability limit, never a silent half-state. The wipe throws on a store fault (and core's stores
114
- * refuse it while they run a write of the scope): the throw propagates, nothing is re-imported.
115
- *
116
- * Honest residual (recorded, not hidden): if the RE-import after the wipe fails (a blob corrupted in
117
- * transit past the Phase-A presence check), the destination's old graph is already gone and the target
118
- * is left EMPTY — worse than the moment before this call, but strictly self-healing: the client re-PUTs
119
- * the blob and re-POSTs, and the retry lands on an empty target (importHistory's first-import arm).
120
- * The alternative (keeping the old graph) would wedge every retry on the same typed conflict forever.
121
- *
122
- * 缺 blob 的事实只在这里读一次:每次尝试给店的 getter 都包一层观察,店取到 `undefined` 就记下那枚 hash(按次计,替换臂的第二次尝试从空白起),
123
- * 失败时随结果交出({@link FileHistoryReplayResult.missingBlob})。预检({@link sessionSyncFileHistory} 事实 + 交换组的 `has`)管「这台部署根本收不到 / 索引里没有」,
124
- * 这里管「预检过了而这一枚取不到」(对象存储里已丢 / 校验和不符按缺席读 / 预检与重放之间被孤儿回收清走)—— 同一只不变量的两端,wire 上同一个词。
97
+ * 「这只文件历史店有没有会话同步的 blob 面」的**唯一**读法(能力事实 `sessionSyncFileHistory` 读它):交换组({@link blobExchangeOf})∧ 按落点来历面(`blobLanding`)两样都在 ⇒ 面;
98
+ * 缺一样 ⇒ `undefined`(fail-closed:给不出按落点可见性的店没有 blob 两口 —— 只有交换组而无来历面,就只能按全局内容地址表答「在不在」,那正是这只面要关掉的预言机)。
125
99
  */
126
- export declare function replayFileHistory(store: ServiceFileHistoryStore, scope: string, envelope: FileHistoryExport, srcGetBlob: BlobGetter, opts: {
127
- allowReplace: boolean;
128
- }): Promise<FileHistoryReplayResult>;
100
+ export declare function landingBlobFacesOf(store: ServiceFileHistoryStore): LandingBlobFaces | undefined;
129
101
  /**
130
102
  * The portable state of ONE session, ready to replay into another backend. Blobs are deliberately NOT inlined
131
103
  * (§5: a turn's working-tree blobs can be tens of MiB → OOM/413 if buffered); each snapshot carries only its
@@ -196,10 +168,7 @@ export interface SessionManifest {
196
168
  export declare function exportSessionManifest(sessionId: string, srcBackend: StoreBackend,
197
169
  /** [ref]:文件历史读**装配结果**(捕获脸),不直取 `srcBackend.fileHistory()` —— 「这台部署有没有文件历史店」
198
170
  * 只在 `assembleFileHistory` 判一次。同一个后端铸的同一只店,四店同源的不变式不变。 */
199
- historyStore: ServiceFileHistoryStore): Promise<{
200
- manifest: SessionManifest;
201
- getBlob: BlobGetter;
202
- } | null>;
171
+ historyStore: ServiceFileHistoryStore): Promise<SessionManifest | null>;
203
172
  /**
204
173
  * §7 DRY-RUN — classify what importing `sessionId` from `srcBackend` into `dstBackend` WOULD do, WITHOUT writing
205
174
  * anything (the shell can present a keep-local / keep-cloud / fork-new choice up front).
@@ -208,51 +177,4 @@ historyStore: ServiceFileHistoryStore): Promise<{
208
177
  * between the source's full durable log and the destination's current log (a `null` dst log ⇒ `fresh`). No writes.
209
178
  */
210
179
  export declare function planSync(sessionId: string, srcBackend: StoreBackend, dstBackend: StoreBackend): Promise<SyncRelation | null>;
211
- /** [ref] ①(死码 A6 修的移植):同内容判据 —— 比较「归一化、去 rev」后的字节形。putRules 每次
212
- * 无条件写都 rev+1,而 rev 是 operator 乐观锁的 CAS 令牌(`PutRulesOptions.expectedRev`),
213
- * 幂等重试/无害重复同步不该把在途 CAS 打失败。归一化用 core 的 `normalizeRules`(store 落盘
214
- * 前走同一函数 ⇒ 两侧同坐标);它略过店的戳 `rev` / `gen`(`listBySession` 的 rules 含 rev,
215
- * 它不是内容;core 7.51.0 [ref] 起 `normalizeRules` 自己略过,修前的 `stripRev` 包裹随之退役)。
216
- * 两侧过的是同一只形读法(core 7.51.0 `readSessionRulesShape`):`incoming` 由写口读者(引擎的写口判官)交出,`stored` 由店的读回交出 ——
217
- * 店读回读不出形的行答 `corrupt`、根本到不了这里,所以 `normalizeRules` 对两侧都不抛(修前本仓的读回比引擎宽,另有一只判官先挡一次)。
218
- * 🔴 字节同 ≠ 意思同(core 7.51.2 行自带文法戳;codex r1 [high] 验真):`normalizeRules` 把店的戳 `rev` / `gen` / `grammar` 都略过,而同一串桶字节在两种文法里是两句话 ——
219
- * 目的行无戳(文法 0:`toolAllow` 里的 `Bash(rm:*)` 什么都不放行)、导入记录带 `grammar: 1`(放行 rm),只比字节会判「同内容」、零次 putRules、同步答 200 而目的行仍是旧意思。
220
- * 所以字节同之外还问引擎的收紧比对 `loosenReasons` **双向**(两侧各在自己的文法里读:`stored` 带 `rev`,`incoming` 带 `grammar`):两个方向都「没放宽」才是同一句话;
221
- * 意思不同就交给 `putRules` 的判官(非 operator ⇒ `loosen_forbidden`;operator ⇒ 改写成当前文法、盖戳)。本仓仍零处读 / 比戳值;意思由引擎说。
222
- * 🔴 core 7.55.0 [ref]:行还有一格 **operator 部分**(`StoredSessionRules.operatorRules`:非 operator 的写只能收回非 operator 写加上去的东西;缺席 = 整行归 operator)。
223
- * 桶同、意思同,这一次写仍可能挪动它 —— operator 重放把整行变成 operator 的(引擎写口判官的答:operator 写的值整只归 operator)。只比桶会判「同内容」⇒ 零次 putRules ⇒
224
- * 属主盖过戳的目的行保持「属主收得回」,源端 operator 断言过的条目在目的端被属主一条非 operator 的写收回(安全轴的静默放宽)。所以第三问:这一次写之后的部分
225
- * = 引擎写口判官对(目的行, 记录, 这次重放的态度)的答,与目的行现有的部分按同一读法(缺席 = 整行)比;不同就交给 `putRules`。判官拒 ⇒ 原样上抛(`putRules` 会拒同一句)。
226
- * `operator` 必填:同一份记录在 operator 与非 operator 的重放下是两种写(漏传是编译错,不是一条静默的读法)。 */
227
- export declare function sameRulesContent(stored: StoredSessionRules | null, incoming: SessionPermissionRules, operator: boolean): boolean;
228
- /** [ref] ① 的重放腿共享实现:PUSH 路由的两条 policy 重放腿(Phase A identical 短路支 / Phase B
229
- * commit 后支)都从这里走 —— 等内容跳过(rev 零漂移),真变更照常落地(E6 tighten-only 门在
230
- * putRules 内部,此处不重述)。 */
231
- export declare function replayPolicyRecords(store: Pick<SessionPolicyStore, "getRules" | "putRules">, sessionId: string, records: readonly SessionRulesRecord[], operatorOk: boolean): Promise<void>;
232
- /** [ref] ② 能力预检(fail-loud 方向,[ref] 判据:保护型能力缺席=fail-closed):consented
233
- * overwrite-dst 需要 policy 店的 `deleteBySession` purge seam 才能不留混合态;缺席=true,路由在
234
- * commit **之前**用它干净中止(此时零写入落地),wire 拒因字面量归路由所有(api-error-text-freeze
235
- * 锚字面站点)。anchor 店的 deleteBySession 是类型必填、history 店有 core 必填的 `reap` 兜底(+
236
- * {@link replayFileHistory} 的 conflict 收敛臂),都不构成缺席面;唯一可缺的就是 policy 这条
237
- * (core 接口上是可选方法)。 */
238
- export declare function overwriteWipeIncapable(policyStore: ServiceSessionPolicyStore | undefined): boolean;
239
- /** [ref] ②(死码 ⓪ 擦除步的移植):consented overwrite-dst = keep-source,结果必须是**源端那
240
- * 一份**,不得混成「源端对话 + 目的端遗留 policy/anchor/历史」(混合态比丢历史更危险:悬空
241
- * anchor 指向已 purge 的 entry、被放弃分支的 policy 行继续 gating、目的端旧历史图挡住源端图的
242
- * importHistory 空目标法则)。调用时点=commit(原子 swap)成功**之后**、附随物重放**之前**:
243
- * 死码在任何写入前擦(其 entries 写本身非原子);活路由有真 commit point,擦在 commit 前会在
244
- * 「commit 失败」时留下反向混合态(目的端对话完好、附随物已毁)——方向更坏。
245
- * 诚实残余:进程在 commit 与本函数之间崩溃时,重试落 Phase A identical 支(无 wipe)⇒ 遗留行
246
- * 可存活;该窗口窄于修前世界(修前=遗留行**恒**存活),与 §8「历史重放失败 ⇒ 422 重试自愈」
247
- * 同款接受,记录于此不装没有。
248
- * 各店姿势:file history=deleteBySession(整 scope 清空;blob 走异步孤儿 GC)缺席则 core 必填
249
- * `reap(scope, [])`(丢全部 boundary;v1 基线按 core 契约留存 —— 随后 {@link replayFileHistory}
250
- * 的 conflict 臂在有 purge 缝的店上收敛,无缝的店如实 422);anchor=直调(类型必填);
251
- * policy=deleteBySession(路由已用 {@link overwriteWipeIncapable} 预检过,此处缺席=类型外的
252
- * JS 层残缺实现,直调抛 TypeError 即 fail-loud)。 */
253
- export declare function wipeSessionAttendants(stores: {
254
- fileHistoryStore?: ServiceFileHistoryStore | undefined;
255
- sessionPolicyStore?: ServiceSessionPolicyStore | undefined;
256
- resumeAnchorStore?: Pick<ResumeAnchorStore, "deleteBySession"> | undefined;
257
- }, sessionId: string): Promise<void>;
258
180
  //# sourceMappingURL=session-sync.d.ts.map
@@ -1,8 +1,7 @@
1
1
  import { anchorEntryDeclared, classifySyncRelationship } from "./session-sync-kernel.js";
2
2
  import { recordFailOpen } from "./observability/fail-open.js";
3
- import { loosenReasons, normalizeRules } from "@sema-agent/core";
4
3
  import { sessionFactsReadOf } from "./http/owner-access.js";
5
- import { fileHistoryBlobExchangeOf, judgeSessionRulesWrite } from "@sema-agent/core";
4
+ import { fileHistoryBlobExchangeOf } from "@sema-agent/core";
6
5
  export class FileHistoryUnreadableError extends Error {
7
6
  sessionId;
8
7
  failure;
@@ -25,38 +24,21 @@ export async function exportedFileHistory(store, sessionId) {
25
24
  export function blobExchangeOf(store) {
26
25
  return fileHistoryBlobExchangeOf(store);
27
26
  }
28
- export function blobGetterOf(store) {
27
+ export const NO_VISIBLE_BLOBS = { hashes: new Set(), get: async () => undefined };
28
+ export function landingBlobFacesOf(store) {
29
29
  const exchange = blobExchangeOf(store);
30
- return exchange ? exchange.get : async () => undefined;
31
- }
32
- export function fileHistoryImportFace(fs) {
33
- const candidate = fs?.importHistory;
34
- if (typeof candidate !== "function")
30
+ const provenance = store.blobLanding;
31
+ if (exchange === undefined || provenance === undefined)
35
32
  return undefined;
36
- return { importHistory: candidate.bind(fs) };
37
- }
38
- export async function replayFileHistory(store, scope, envelope, srcGetBlob, opts) {
39
- const face = fileHistoryImportFace(store);
40
- if (face === undefined) {
41
- return { ok: false, error: { code: "unsupported_format", message: "this deployment's file-history store has no importHistory seam" } };
42
- }
43
- const attempt = async () => {
44
- let missing;
45
- const observed = async (hash) => {
46
- const bytes = await srcGetBlob(hash);
47
- if (bytes === undefined && missing === undefined)
48
- missing = hash;
49
- return bytes;
50
- };
51
- const r = await face.importHistory(scope, envelope, observed);
52
- return r.ok || missing === undefined ? r : { ok: false, error: r.error, missingBlob: missing };
33
+ return {
34
+ put: (landing, hash, bytes) => provenance.putAt(landing, hash, bytes),
35
+ visible: async (landing, hashes) => {
36
+ if (hashes.length === 0)
37
+ return NO_VISIBLE_BLOBS;
38
+ const seen = await provenance.visibleAt(landing, hashes);
39
+ return { hashes: seen, get: async (hash) => (seen.has(hash) ? exchange.get(hash) : undefined) };
40
+ },
53
41
  };
54
- let r = await attempt();
55
- if (!r.ok && r.error.code === "conflict" && opts.allowReplace && typeof store.deleteBySession === "function") {
56
- await store.deleteBySession(scope);
57
- r = await attempt();
58
- }
59
- return r;
60
42
  }
61
43
  function ownerAware(backend) {
62
44
  return backend.session();
@@ -87,7 +69,6 @@ export async function exportSessionManifest(sessionId, srcBackend, historyStore)
87
69
  if (summaryOf === undefined)
88
70
  throw new Error("exportSessionManifest: the session store has no row read face (sessionSummaryOf) — the sessionSync fact should have refused this deployment");
89
71
  const row = await summaryOf(sessionId);
90
- const getBlob = blobGetterOf(fs);
91
72
  const declared = new Set(entryIds);
92
73
  const soundAnchors = anchors.filter((a) => {
93
74
  if (anchorEntryDeclared(declared, a.entryId))
@@ -95,10 +76,7 @@ export async function exportSessionManifest(sessionId, srcBackend, historyStore)
95
76
  recordFailOpen("server.session-sync.dangling-anchor-export", `sessionId=${brief(sessionId)} eventId=${brief(a.eventId)} entryId=${brief(a.entryId)}`);
96
77
  return false;
97
78
  });
98
- return {
99
- manifest: { sessionId, entryIds, entryCount: entryIds.length, leafId, fileHistory, policy, anchors: soundAnchors, ...(row !== undefined ? { title: row.title, pinned: row.pinned } : {}) },
100
- getBlob,
101
- };
79
+ return { sessionId, entryIds, entryCount: entryIds.length, leafId, fileHistory, policy, anchors: soundAnchors, ...(row !== undefined ? { title: row.title, pinned: row.pinned } : {}) };
102
80
  }
103
81
  export async function planSync(sessionId, srcBackend, dstBackend) {
104
82
  const srcEntries = (await ownerAware(srcBackend).exportEntries?.(sessionId)) ?? null;
@@ -107,44 +85,4 @@ export async function planSync(sessionId, srcBackend, dstBackend) {
107
85
  const dstEntries = (await ownerAware(dstBackend).exportEntries?.(sessionId)) ?? null;
108
86
  return classifySyncRelationship(srcEntries, dstEntries);
109
87
  }
110
- export function sameRulesContent(stored, incoming, operator) {
111
- if (stored === null)
112
- return false;
113
- const norm = (r) => JSON.stringify(normalizeRules(r));
114
- if (norm(stored) !== norm(incoming))
115
- return false;
116
- if (loosenReasons(stored, incoming).length !== 0 || loosenReasons(incoming, stored).length !== 0)
117
- return false;
118
- return operatorPartUnmoved(stored, incoming, operator);
119
- }
120
- function operatorPartUnmoved(stored, incoming, operator) {
121
- const next = judgeSessionRulesWrite(stored, incoming, { operator });
122
- const partOf = (row) => JSON.stringify(normalizeRules(row.operatorRules ?? row));
123
- return partOf(next) === partOf(stored);
124
- }
125
- export async function replayPolicyRecords(store, sessionId, records, operatorOk) {
126
- for (const rec of records) {
127
- const cur = await store.getRules(sessionId, rec.principal);
128
- if (sameRulesContent(cur, rec.rules, operatorOk))
129
- continue;
130
- await store.putRules(sessionId, rec.principal, rec.rules, { operator: operatorOk });
131
- }
132
- }
133
- export function overwriteWipeIncapable(policyStore) {
134
- return policyStore !== undefined && typeof policyStore.deleteBySession !== "function";
135
- }
136
- export async function wipeSessionAttendants(stores, sessionId) {
137
- const fs = stores.fileHistoryStore;
138
- if (fs !== undefined) {
139
- if (typeof fs.deleteBySession === "function")
140
- await fs.deleteBySession(sessionId);
141
- else
142
- await fs.reap(sessionId, []);
143
- }
144
- if (stores.resumeAnchorStore !== undefined)
145
- await stores.resumeAnchorStore.deleteBySession(sessionId);
146
- const policy = stores.sessionPolicyStore;
147
- if (policy !== undefined)
148
- await policy.deleteBySession(sessionId);
149
- }
150
88
  //# sourceMappingURL=session-sync.js.map
@@ -28,9 +28,15 @@
28
28
  */
29
29
  import type { HookLlmCall } from "./hooks/hook-runner.js";
30
30
  import { type SessionTitledFrame } from "./session-titled-frame.js";
31
+ import type { SessionFace } from "./session-incarnation.js";
31
32
  export interface SessionTitlerDeps {
32
- /** Write-once store leg (SQL twins / local sidecar). Resolves false when a title already existed. */
33
- setTitleIfNull(sessionId: string, title: string): Promise<boolean>;
33
+ /** Write-once store leg (SQL twins / local sidecar). Resolves false when a title already existed — or when the session is no longer `landed`
34
+ * (the occupant read at acceptance: the store judges it under the session's row lock with the row-write judge, so a title generated for one
35
+ * occupant never lands on a session deleted and re-created under the same id meanwhile, nor on a freed id). */
36
+ setTitleIfNull(sessionId: string, title: string, landed: SessionFace): Promise<boolean>;
37
+ /** The session's occupant now (the owner accessor's `sessionOccupantReadOf`) — read once, at acceptance, while the accepting leg waits on it
38
+ * (so the run is live and the occupant is that run's); `undefined` ⇒ no row to title. */
39
+ occupantOf(sessionId: string): Promise<SessionFace | undefined>;
34
40
  /** Cheap pre-LLM probe: "titled" = skip (a restart/evicted-seen/another replica
35
41
  * already paid — don't re-spend an LLM call); "untitled" = generate; "none" = the session_meta row hasn't
36
42
  * been REGISTERED (or was deleted meanwhile) — retry after a grace window,
@@ -54,7 +60,8 @@ export interface SessionTitler {
54
60
  /** `model`([ref]②)= 本 turn 真实驱动的模型——带上则标题 hook 同链路,不再裸落 env 缺省。 */
55
61
  /** `onTitled` = 这一次调用**自己**把标题写成之后(`setTitleIfNull` 落行)交出的活通知(`session_titled` 帧,契约 §4a-ttl):只在落行的那一支调用恰一次 ——
56
62
  * 已有标题 / 竞态输了 / 模型失败 / 空标题 / 这个会话已有一次在飞的调用,都不调。回调同步执行、**不许抛**;抛了也不影响已落行的标题(见下 `frame_failed`)。 */
57
- maybeTitle(sessionId: string, objective: string, model?: string, onTitled?: (frame: SessionTitledFrame) => void): void;
63
+ /** Resolves once the occupant to title is pinned (bounded; the accepting leg awaits it), the model call and the write then run in the background. */
64
+ maybeTitle(sessionId: string, objective: string, model?: string, onTitled?: (frame: SessionTitledFrame) => void): Promise<void>;
58
65
  }
59
66
  /** 标题的显示预算(码元,含截断记号)—— 界的属主是出口座的 `session-title` 行,这里读它;落在 `title` 列宽 `SESSION_TITLE_MAX_CHARS`(session-row.ts)之内。 */
60
67
  export declare const TITLE_MAX_CHARS: number;
@@ -2,6 +2,7 @@ import { recordFailOpen } from "./observability/fail-open.js";
2
2
  import { seatMaxChars, seatText } from "./trace/exit-seat.js";
3
3
  import { userAuthoredText } from "./user-authored-text.js";
4
4
  import { buildSessionTitledFrame } from "./session-titled-frame.js";
5
+ import { withinIoDeadline } from "./instruction-source-notice.js";
5
6
  export const TITLE_MAX_CHARS = seatMaxChars("session-title");
6
7
  const OBJECTIVE_SNIPPET_CHARS = 600;
7
8
  const LLM_TIMEOUT_MS = 15_000;
@@ -52,7 +53,7 @@ export function createSessionTitler(deps) {
52
53
  .then((state) => ({ state, unreadable: false }))
53
54
  .catch(() => ({ state: continueWord, unreadable: true }));
54
55
  return {
55
- maybeTitle(sessionId, objective, model, onTitled) {
56
+ async maybeTitle(sessionId, objective, model, onTitled) {
56
57
  if (!sessionId || !userAuthoredText(objective))
57
58
  return;
58
59
  if (seen.has(sessionId))
@@ -63,6 +64,19 @@ export function createSessionTitler(deps) {
63
64
  seen.delete(oldest);
64
65
  }
65
66
  seen.add(sessionId);
67
+ let landed;
68
+ try {
69
+ landed = await withinIoDeadline(() => deps.occupantOf(sessionId));
70
+ }
71
+ catch {
72
+ landed = undefined;
73
+ }
74
+ if (landed === undefined) {
75
+ seen.delete(sessionId);
76
+ deps.metrics?.inc("session_title_total", { outcome: "row_not_yet" });
77
+ return;
78
+ }
79
+ const occupant = landed;
66
80
  void (async () => {
67
81
  let state = (await probeOrContinue(sessionId, "untitled")).state;
68
82
  if (state === "none") {
@@ -90,12 +104,11 @@ export function createSessionTitler(deps) {
90
104
  deps.metrics?.inc("session_title_total", { outcome: "empty" });
91
105
  return;
92
106
  }
93
- const landed = await deps.setTitleIfNull(sessionId, title);
94
- if (!landed) {
107
+ const wrote = await deps.setTitleIfNull(sessionId, title, occupant);
108
+ if (!wrote) {
95
109
  const after = await probeOrContinue(sessionId, "none");
96
- if (after.state !== "untitled")
97
- seen.delete(sessionId);
98
- deps.metrics?.inc("session_title_total", { outcome: after.unreadable ? "probe_failed" : after.state === "none" ? "row_not_yet" : "already_titled" });
110
+ seen.delete(sessionId);
111
+ deps.metrics?.inc("session_title_total", { outcome: after.unreadable ? "probe_failed" : after.state === "none" ? "row_not_yet" : after.state === "untitled" ? "occupant_changed" : "already_titled" });
99
112
  return;
100
113
  }
101
114
  seen.delete(sessionId);
@@ -36,12 +36,17 @@ export interface SshHostKeyPolicy {
36
36
  knownHostsPath?: string;
37
37
  }
38
38
  /** {@link createSshHostKeyVerifier} 的判词。`verified:false` = 两旋钮都缺席那一格(照连,但响亮)。 */
39
+ /**
40
+ * 判官的一次判。拒的那一臂带 `standing`:对面出示的密钥被钉判了不过(指纹不符 / 条目换了钥 / `@revoked` / 没有可用条目)⇒ `true` —— 同一配置同一主机每次同答;
41
+ * 钉的文件此刻读不出 ⇒ `false`(这一次判不了,文件可能正被换写,下一次连接重判)。
42
+ */
39
43
  export type SshHostKeyVerdict = {
40
44
  ok: true;
41
45
  verified: boolean;
42
46
  } | {
43
47
  ok: false;
44
48
  reason: string;
49
+ standing: boolean;
45
50
  };
46
51
  /** SHA256 base64 指纹的**归一**:`SHA256:` 前缀可选、`=` 填充可选;形不合 ⇒ **抛**(调用方在 boot 期拒启)。 */
47
52
  export declare function normalizeSshFingerprint(raw: string): string;
@@ -82,6 +82,7 @@ export function createSshHostKeyVerifier(opts) {
82
82
  ok: false,
83
83
  reason: `SSH host key rejected for ${host}:${port} — fingerprint mismatch. presented=${presented} expected=SHA256:${fingerprint} (SSH_HOST_FINGERPRINT). ` +
84
84
  keyscanHint(host, port),
85
+ standing: true,
85
86
  };
86
87
  }
87
88
  if (knownHostsPath !== undefined) {
@@ -95,6 +96,7 @@ export function createSshHostKeyVerifier(opts) {
95
96
  reason: `SSH host key rejected for ${host}:${port} — SSH_KNOWN_HOSTS (${knownHostsPath}) could not be read at connect time: ` +
96
97
  `${e instanceof Error ? e.message : String(e)}. presented=${presented}. ` +
97
98
  keyscanHint(host, port),
99
+ standing: false,
98
100
  };
99
101
  }
100
102
  const verdict = matchKnownHosts(text, host, port, key.toString("base64"));
@@ -108,6 +110,7 @@ export function createSshHostKeyVerifier(opts) {
108
110
  ok: false,
109
111
  reason: `SSH host key rejected for ${host}:${port} — ${why}. presented=${presented} source=SSH_KNOWN_HOSTS:${knownHostsPath}. ` +
110
112
  keyscanHint(host, port),
113
+ standing: true,
111
114
  };
112
115
  }
113
116
  }