@sema-agent/server 7.132.0 → 7.133.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 (402) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +174 -55
  5. package/deploy/sema-up/README.md +19 -1
  6. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
  7. package/deploy/sema-up/chart/templates/server.yaml +5 -2
  8. package/deploy/sema-up/stack/compose.yaml +3 -0
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  10. package/dist/adoption/plan.d.ts +3 -2
  11. package/dist/adoption/plan.js +7 -5
  12. package/dist/adoption/runner.js +3 -10
  13. package/dist/adoption/sql.d.ts +3 -2
  14. package/dist/adoption/sql.js +3 -5
  15. package/dist/adoption/wire.d.ts +4 -1
  16. package/dist/adoption/wire.js +1 -0
  17. package/dist/approval-ask-audit-store.d.ts +31 -13
  18. package/dist/approval-ask-audit-store.js +272 -74
  19. package/dist/approval-ask-machine.d.ts +15 -0
  20. package/dist/approval-ask-machine.js +6 -0
  21. package/dist/approval-ask-retention.d.ts +136 -0
  22. package/dist/approval-ask-retention.js +102 -0
  23. package/dist/approval-card.d.ts +79 -19
  24. package/dist/approval-card.js +44 -13
  25. package/dist/approval-deny-reasons.d.ts +1 -1
  26. package/dist/approval.d.ts +9 -0
  27. package/dist/approval.js +24 -3
  28. package/dist/audit.d.ts +27 -10
  29. package/dist/audit.js +12 -16
  30. package/dist/bake-runner/main.js +3 -2
  31. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  32. package/dist/boot/execution-env.js +9 -7
  33. package/dist/boot/execution-lane.js +11 -0
  34. package/dist/boot/leader.js +2 -0
  35. package/dist/boot/memory-boundary.js +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +5 -4
  37. package/dist/boot/memory-consolidation.js +7 -6
  38. package/dist/boot/org-delivery.d.ts +28 -0
  39. package/dist/boot/org-delivery.js +20 -0
  40. package/dist/boot/reapers.d.ts +3 -0
  41. package/dist/boot/reapers.js +21 -1
  42. package/dist/boot/resolve-spec.d.ts +3 -0
  43. package/dist/boot/resolve-spec.js +60 -81
  44. package/dist/boot/runner-deps.js +1 -0
  45. package/dist/boot/session-faces.d.ts +1 -0
  46. package/dist/boot/session-faces.js +4 -2
  47. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  48. package/dist/boot/stage-06-runners.d.ts +2 -0
  49. package/dist/boot/stage-06-runners.js +6 -3
  50. package/dist/boot/stage-07-capability-layer.d.ts +2 -0
  51. package/dist/boot/stage-07-capability-layer.js +24 -3
  52. package/dist/boot/stage-08-reapers.d.ts +2 -0
  53. package/dist/boot/stage-08-reapers.js +2 -2
  54. package/dist/boot/stage-09-leader.d.ts +3 -0
  55. package/dist/boot/stage-10-http-server.d.ts +3 -0
  56. package/dist/boot/stage-10-http-server.js +5 -6
  57. package/dist/boot/stores.js +2 -2
  58. package/dist/boot/web-admin.d.ts +4 -0
  59. package/dist/boot/web-admin.js +151 -63
  60. package/dist/boot/workflow-orchestration.d.ts +2 -0
  61. package/dist/boot/workflow-orchestration.js +2 -1
  62. package/dist/brain.js +8 -3
  63. package/dist/breaker-key.d.ts +6 -0
  64. package/dist/breaker-key.js +29 -0
  65. package/dist/caller-text-echo.d.ts +9 -6
  66. package/dist/caller-text-echo.js +8 -11
  67. package/dist/capabilities/center-plugins.d.ts +7 -0
  68. package/dist/capabilities/center-plugins.js +1 -1
  69. package/dist/capabilities/composite-members.d.ts +1 -1
  70. package/dist/capabilities/composite-members.js +1 -1
  71. package/dist/capabilities/execution-lane.js +2 -2
  72. package/dist/capabilities/hands-lane.d.ts +21 -0
  73. package/dist/capabilities/hands-lane.js +10 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -4
  75. package/dist/capabilities/mcp-probe.js +27 -4
  76. package/dist/capabilities/scenarios.d.ts +63 -7
  77. package/dist/capabilities/scenarios.js +39 -7
  78. package/dist/capability-facts.d.ts +34 -4
  79. package/dist/capability-facts.js +28 -2
  80. package/dist/center-config-tier.d.ts +69 -0
  81. package/dist/center-config-tier.js +42 -0
  82. package/dist/checkpoint-identity.d.ts +16 -0
  83. package/dist/checkpoint-identity.js +19 -0
  84. package/dist/config-catalog.d.ts +17 -10
  85. package/dist/config-catalog.js +43 -28
  86. package/dist/config-d-migration.d.ts +3 -1
  87. package/dist/config-d-migration.js +2 -2
  88. package/dist/config-dir-trust.d.ts +114 -0
  89. package/dist/config-dir-trust.js +388 -0
  90. package/dist/config-invariants.d.ts +18 -5
  91. package/dist/config-invariants.js +35 -3
  92. package/dist/config-provider.d.ts +6 -1
  93. package/dist/config-provider.js +6 -2
  94. package/dist/config-types.d.ts +71 -14
  95. package/dist/config-types.js +8 -0
  96. package/dist/config.d.ts +13 -0
  97. package/dist/config.js +121 -59
  98. package/dist/delegated-session-owner.d.ts +185 -0
  99. package/dist/delegated-session-owner.js +119 -0
  100. package/dist/device-protocol.d.ts +5 -3
  101. package/dist/device-protocol.js +1 -1
  102. package/dist/device-ws-hub.js +21 -2
  103. package/dist/device-ws-protocol.d.ts +70 -6
  104. package/dist/device-ws-protocol.js +50 -23
  105. package/dist/elicitation.js +3 -2
  106. package/dist/entry-problems.d.ts +34 -0
  107. package/dist/entry-problems.js +14 -0
  108. package/dist/execution-lane-caps.d.ts +49 -10
  109. package/dist/execution-lane-caps.js +43 -4
  110. package/dist/field-conflict.d.ts +43 -0
  111. package/dist/field-conflict.js +20 -0
  112. package/dist/fleet/fleet-bus.d.ts +80 -6
  113. package/dist/fleet/fleet-bus.js +41 -9
  114. package/dist/hooks/classic-stations.d.ts +10 -5
  115. package/dist/hooks/classic-stations.js +4 -4
  116. package/dist/hooks/command-spawn.d.ts +57 -0
  117. package/dist/hooks/command-spawn.js +139 -0
  118. package/dist/hooks/hook-llm.js +2 -2
  119. package/dist/hooks/hook-runner.d.ts +127 -25
  120. package/dist/hooks/hook-runner.js +668 -287
  121. package/dist/hooks/hook-source.d.ts +44 -0
  122. package/dist/hooks/hook-source.js +40 -0
  123. package/dist/http/active-run-conflict.d.ts +5 -13
  124. package/dist/http/active-run-conflict.js +17 -27
  125. package/dist/http/admission.js +103 -60
  126. package/dist/http/approval-sweeps.js +55 -18
  127. package/dist/http/caller-trust.d.ts +77 -0
  128. package/dist/http/caller-trust.js +143 -0
  129. package/dist/http/checkpoint-ctx.d.ts +16 -16
  130. package/dist/http/checkpoint-ctx.js +13 -10
  131. package/dist/http/claimed-run.d.ts +4 -3
  132. package/dist/http/device-placement.d.ts +2 -4
  133. package/dist/http/device-placement.js +2 -1
  134. package/dist/http/dispatch.js +10 -4
  135. package/dist/http/durable-run-leg.d.ts +4 -32
  136. package/dist/http/durable-run-leg.js +34 -10
  137. package/dist/http/owner-access.d.ts +56 -0
  138. package/dist/http/owner-access.js +42 -0
  139. package/dist/http/principal-gate.d.ts +48 -23
  140. package/dist/http/principal-gate.js +39 -12
  141. package/dist/http/readiness.d.ts +11 -5
  142. package/dist/http/readiness.js +10 -3
  143. package/dist/http/request-guards.d.ts +16 -23
  144. package/dist/http/request-guards.js +30 -15
  145. package/dist/http/resume-ctx-read.d.ts +2 -5
  146. package/dist/http/resume-ctx-read.js +7 -4
  147. package/dist/http/resume-legs.d.ts +3 -4
  148. package/dist/http/resume-legs.js +126 -69
  149. package/dist/http/retired-service-deps.d.ts +43 -0
  150. package/dist/http/retired-service-deps.js +18 -0
  151. package/dist/http/route-ctx.d.ts +44 -28
  152. package/dist/http/route-ctx.js +4 -0
  153. package/dist/http/route-table.js +2 -0
  154. package/dist/http/routes/a2a-serve.js +3 -4
  155. package/dist/http/routes/admin-config-refresh.js +1 -1
  156. package/dist/http/routes/admin-drain.js +1 -1
  157. package/dist/http/routes/admin-memory-optout.js +5 -1
  158. package/dist/http/routes/adoption.js +2 -9
  159. package/dist/http/routes/approvals-assistant.js +49 -48
  160. package/dist/http/routes/background.js +3 -2
  161. package/dist/http/routes/capabilities.js +20 -4
  162. package/dist/http/routes/config-catalog.d.ts +3 -3
  163. package/dist/http/routes/config-catalog.js +1 -1
  164. package/dist/http/routes/devices.js +3 -17
  165. package/dist/http/routes/diagnostics.js +5 -1
  166. package/dist/http/routes/images.js +6 -7
  167. package/dist/http/routes/leader.js +1 -8
  168. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  169. package/dist/http/routes/mcp-reconnect.js +3 -2
  170. package/dist/http/routes/memory-bundle.js +1 -1
  171. package/dist/http/routes/memory-compliance.js +1 -1
  172. package/dist/http/routes/memory-consolidation.js +1 -1
  173. package/dist/http/routes/memory-origin.js +1 -1
  174. package/dist/http/routes/memory-policy.d.ts +1 -1
  175. package/dist/http/routes/memory-policy.js +3 -2
  176. package/dist/http/routes/notify-wake.js +8 -21
  177. package/dist/http/routes/observability.js +2 -2
  178. package/dist/http/routes/principal-inbox.d.ts +39 -0
  179. package/dist/http/routes/principal-inbox.js +76 -0
  180. package/dist/http/routes/retention-ops.js +2 -9
  181. package/dist/http/routes/rules.js +5 -18
  182. package/dist/http/routes/runs.d.ts +5 -0
  183. package/dist/http/routes/runs.js +33 -106
  184. package/dist/http/routes/session-sync.js +15 -28
  185. package/dist/http/routes/sessions.js +30 -40
  186. package/dist/http/routes/side-query.js +6 -4
  187. package/dist/http/routes/tasks.js +50 -14
  188. package/dist/http/routes/workflows.d.ts +351 -0
  189. package/dist/http/routes/workflows.js +153 -119
  190. package/dist/http/send.d.ts +24 -3
  191. package/dist/http/send.js +14 -2
  192. package/dist/http/server.d.ts +17 -18
  193. package/dist/http/server.js +15 -13
  194. package/dist/http/stored-body-replay.js +17 -4
  195. package/dist/http/submit-ctx.js +4 -3
  196. package/dist/http/submit-replay.d.ts +4 -3
  197. package/dist/http/submit-replay.js +5 -4
  198. package/dist/http/wire-types.d.ts +10 -7
  199. package/dist/index.d.ts +1 -1
  200. package/dist/index.js +0 -1
  201. package/dist/lane-home-probe.d.ts +78 -9
  202. package/dist/lane-home-probe.js +78 -12
  203. package/dist/leader/endpoint.d.ts +4 -1
  204. package/dist/leader/endpoint.js +11 -10
  205. package/dist/limit-refusal.d.ts +17 -5
  206. package/dist/limit-refusal.js +4 -1
  207. package/dist/mcp-assertion-header.d.ts +31 -10
  208. package/dist/mcp-assertion-header.js +10 -3
  209. package/dist/mcp-tool-faces.js +1 -3
  210. package/dist/memory-project-layer.d.ts +37 -0
  211. package/dist/memory-project-layer.js +46 -0
  212. package/dist/memory-scope.d.ts +23 -16
  213. package/dist/memory-scope.js +24 -8
  214. package/dist/memory-sync-client.d.ts +27 -13
  215. package/dist/memory-sync-client.js +107 -18
  216. package/dist/model-entry-refusal.d.ts +9 -1
  217. package/dist/model-entry-refusal.js +6 -1
  218. package/dist/model-plane-face.d.ts +51 -0
  219. package/dist/model-plane-face.js +24 -0
  220. package/dist/model-provider.d.ts +5 -0
  221. package/dist/model-provider.js +4 -2
  222. package/dist/model-route-endpoint.d.ts +8 -11
  223. package/dist/model-route-endpoint.js +4 -7
  224. package/dist/model-route-env.d.ts +22 -0
  225. package/dist/model-route-env.js +2 -0
  226. package/dist/model-route-gap.d.ts +60 -30
  227. package/dist/model-route-gap.js +21 -15
  228. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  229. package/dist/observability/corrupt-read-seat.js +9 -0
  230. package/dist/observability/err-text.d.ts +6 -0
  231. package/dist/observability/err-text.js +18 -4
  232. package/dist/observability/fail-open.d.ts +35 -9
  233. package/dist/observability/fail-open.js +35 -9
  234. package/dist/observability/metrics.js +1 -0
  235. package/dist/observability/prompt-manifest.d.ts +3 -17
  236. package/dist/observability/prompt-manifest.js +45 -42
  237. package/dist/observability/tool-trace.js +9 -9
  238. package/dist/operator-ask.d.ts +10 -0
  239. package/dist/operator-ask.js +3 -0
  240. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  241. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  242. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  243. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  244. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  245. package/dist/orchestration/workflow-notify-journal.js +27 -34
  246. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  247. package/dist/orchestration/workflow-park-decision.js +118 -0
  248. package/dist/org-delivery-gate.d.ts +81 -0
  249. package/dist/org-delivery-gate.js +59 -0
  250. package/dist/panel-tool-names.d.ts +23 -0
  251. package/dist/panel-tool-names.js +8 -0
  252. package/dist/parked-decide.d.ts +39 -6
  253. package/dist/parked-decide.js +98 -51
  254. package/dist/parked-revive-surface.d.ts +9 -2
  255. package/dist/parked-revive-surface.js +11 -8
  256. package/dist/permission-rule-list.d.ts +1 -1
  257. package/dist/permission-rule-list.js +2 -2
  258. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  259. package/dist/plugins/approval-ask-store-file.js +21 -0
  260. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  261. package/dist/plugins/approval-ask-store-memory.js +34 -2
  262. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  263. package/dist/plugins/approval-ask-store-sql.js +37 -1
  264. package/dist/plugins/blob-backend.d.ts +38 -25
  265. package/dist/plugins/blob-backend.js +29 -19
  266. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  267. package/dist/plugins/breaker-state-sql.js +4 -4
  268. package/dist/plugins/caching-session-store.d.ts +3 -0
  269. package/dist/plugins/caching-session-store.js +3 -0
  270. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  271. package/dist/plugins/checkpoint-store-sql.js +20 -2
  272. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  273. package/dist/plugins/file-history-store-sql.js +3 -1
  274. package/dist/plugins/file-run-store.d.ts +5 -13
  275. package/dist/plugins/file-run-store.js +9 -0
  276. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  277. package/dist/plugins/fork-routing-session-store.js +12 -1
  278. package/dist/plugins/host-platform.d.ts +2 -0
  279. package/dist/plugins/host-platform.js +2 -1
  280. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  281. package/dist/plugins/local-checkpoint-store.js +6 -1
  282. package/dist/plugins/local-session-store.d.ts +1 -1
  283. package/dist/plugins/local-session-window.d.ts +39 -0
  284. package/dist/plugins/local-session-window.js +54 -0
  285. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  286. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  287. package/dist/plugins/mailbox-store-sql.js +77 -52
  288. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  289. package/dist/plugins/memory-run-store.d.ts +5 -13
  290. package/dist/plugins/memory-run-store.js +8 -0
  291. package/dist/plugins/one-time-migrations.d.ts +110 -5
  292. package/dist/plugins/one-time-migrations.js +320 -8
  293. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  294. package/dist/plugins/org-delivery-quota-store.js +228 -0
  295. package/dist/plugins/pg-pool.js +39 -7
  296. package/dist/plugins/pg-session-storage.d.ts +17 -4
  297. package/dist/plugins/pg-session-storage.js +29 -6
  298. package/dist/plugins/remote-env-adb.d.ts +6 -12
  299. package/dist/plugins/remote-env-adb.js +12 -2
  300. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  301. package/dist/plugins/remote-env-k8s.js +23 -5
  302. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  303. package/dist/plugins/remote-env-local-docker.js +12 -9
  304. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  305. package/dist/plugins/remote-env-ssh.js +17 -11
  306. package/dist/plugins/run-store-sql.d.ts +16 -14
  307. package/dist/plugins/run-store-sql.js +22 -2
  308. package/dist/plugins/schema-indexes.js +2 -0
  309. package/dist/plugins/send-user-file.js +2 -2
  310. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  311. package/dist/plugins/session-epoch-anchor.js +2 -3
  312. package/dist/plugins/session-store.d.ts +18 -8
  313. package/dist/plugins/session-store.js +15 -4
  314. package/dist/plugins/sql-driver.d.ts +74 -11
  315. package/dist/plugins/sql-driver.js +210 -12
  316. package/dist/plugins/sql-errors.d.ts +23 -0
  317. package/dist/plugins/sql-errors.js +21 -0
  318. package/dist/plugins/store-backend.d.ts +36 -1
  319. package/dist/plugins/store-backend.js +40 -14
  320. package/dist/plugins/store-contracts.d.ts +42 -0
  321. package/dist/plugins/store-contracts.js +1 -0
  322. package/dist/plugins/tidb-pool.d.ts +6 -1
  323. package/dist/plugins/tidb-pool.js +44 -10
  324. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  325. package/dist/plugins/tidb-session-storage.js +2 -2
  326. package/dist/plugins/tidb-session-store.d.ts +24 -4
  327. package/dist/plugins/tidb-session-store.js +28 -4
  328. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  329. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  330. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  331. package/dist/plugins/workflow-run-store-sql.js +50 -9
  332. package/dist/posture-source.d.ts +18 -10
  333. package/dist/posture-source.js +4 -1
  334. package/dist/project-identity.d.ts +45 -6
  335. package/dist/project-identity.js +162 -8
  336. package/dist/project-layer-key.d.ts +28 -0
  337. package/dist/project-layer-key.js +29 -0
  338. package/dist/question.js +3 -2
  339. package/dist/read-deny-entry.d.ts +25 -0
  340. package/dist/read-deny-entry.js +12 -0
  341. package/dist/read-face-posture.d.ts +37 -11
  342. package/dist/read-face-posture.js +32 -3
  343. package/dist/request-key-closure.d.ts +7 -0
  344. package/dist/request-key-closure.js +9 -1
  345. package/dist/request-value-closure.d.ts +5 -0
  346. package/dist/request-value-closure.js +11 -3
  347. package/dist/rule-entry-list.d.ts +26 -2
  348. package/dist/rule-entry-list.js +72 -12
  349. package/dist/rules-consent.js +3 -4
  350. package/dist/run-acceptance.d.ts +19 -3
  351. package/dist/run-acceptance.js +13 -1
  352. package/dist/run-local.js +36 -6
  353. package/dist/runs.d.ts +10 -9
  354. package/dist/runs.js +21 -3
  355. package/dist/runtime-caps-resolver.d.ts +16 -6
  356. package/dist/runtime-caps-resolver.js +18 -5
  357. package/dist/sandbox-pkg-source.d.ts +19 -1
  358. package/dist/sandbox-pkg-source.js +29 -7
  359. package/dist/security.d.ts +87 -9
  360. package/dist/security.js +52 -9
  361. package/dist/session-error-wire.d.ts +19 -2
  362. package/dist/session-error-wire.js +5 -1
  363. package/dist/session-titled-frame.d.ts +33 -0
  364. package/dist/session-titled-frame.js +4 -0
  365. package/dist/session-titler.d.ts +9 -1
  366. package/dist/session-titler.js +15 -4
  367. package/dist/spec-fields.js +21 -2
  368. package/dist/sql-engine-posture.d.ts +3 -1
  369. package/dist/task-cwd.d.ts +1 -1
  370. package/dist/task-mcp.d.ts +3 -3
  371. package/dist/task-mcp.js +7 -66
  372. package/dist/task-settings.d.ts +89 -88
  373. package/dist/task-settings.js +71 -63
  374. package/dist/text-bidi.d.ts +0 -4
  375. package/dist/text-bidi.js +1 -3
  376. package/dist/tighten-only-seats.d.ts +150 -0
  377. package/dist/tighten-only-seats.js +196 -0
  378. package/dist/tool-approval.d.ts +303 -87
  379. package/dist/tool-approval.js +623 -239
  380. package/dist/trace/core-keyset-guard.d.ts +104 -22
  381. package/dist/trace/exit-seat.d.ts +28 -0
  382. package/dist/trace/exit-seat.js +76 -0
  383. package/dist/trace/ledger-events.d.ts +3 -2
  384. package/dist/trace/project.d.ts +80 -12
  385. package/dist/trace/project.js +41 -28
  386. package/dist/trace/projection-table.d.ts +100 -0
  387. package/dist/trace/projection-table.js +130 -0
  388. package/dist/trace/redact.d.ts +54 -0
  389. package/dist/trace/redact.js +778 -152
  390. package/dist/trace/self-minted-wire.d.ts +345 -1
  391. package/dist/trace/self-minted-wire.js +117 -1
  392. package/dist/trace/wire-projection-faces.d.ts +298 -2
  393. package/dist/trace/wire-projection-faces.js +154 -13
  394. package/dist/url-refusal.js +2 -0
  395. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  396. package/dist/web-admin-client/apply-effective.js +56 -5
  397. package/dist/web-admin-client/facade.d.ts +1 -1
  398. package/dist/web-admin-client/facade.js +1 -1
  399. package/dist/web-admin-client/read-face.js +5 -9
  400. package/dist/web-admin-client/skills-mcp.js +4 -5
  401. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  402. package/package.json +7 -3
@@ -4,9 +4,10 @@ import { type CacheMissMemory, type WorkspaceCacheCapability } from "../workspac
4
4
  import { type SandboxEgressGap, type SandboxIpv6 } from "../sandbox-egress-policy.js";
5
5
  import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll } from "@sema-agent/core";
6
6
  import type { PodSpecPatch } from "./k8s-exec-protocol.js";
7
+ import { type LaneHomeConfig } from "../lane-home-probe.js";
7
8
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
8
9
  export { buildBgRunnerScript, buildBgLauncherScript, buildBgPollScript, buildBgKillScript, buildBgDisposeScript, buildDetachCapableExec, buildKillAllScript, parseBgPollOutput, type ParsedBgPoll, } from "./k8s-bg-scripts.js";
9
- export interface K8sEnvConfig {
10
+ export interface K8sEnvConfig extends LaneHomeConfig {
10
11
  /** API server URL. Default: in-cluster (`https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT`). */
11
12
  apiUrl?: string;
12
13
  /** Bearer token. Default: in-cluster service-account token file. */
@@ -20,15 +21,6 @@ export interface K8sEnvConfig {
20
21
  image: string;
21
22
  /** RuntimeClass for the VM boundary. Default "kata-qemu" (set "" to run plain runc — tests/dev only). */
22
23
  runtimeClass?: string;
23
- /**
24
- * S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
25
- * 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
26
- * 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
27
- * 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
28
- * 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
29
- * adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
30
- */
31
- homeDir?: string;
32
24
  /** Workspace root inside the pod; created on connect. Default "/workspace". */
33
25
  mountPath?: string;
34
26
  /** Pod lifetime (`activeDeadlineSeconds`; env `K8S_TIMEOUT_MS`) — the whole-pod leak guard, and the ceiling background-shell
@@ -275,8 +267,10 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
275
267
  * the space restore (`latest` names an archive that is there and did not come back — {@link restoreSnapshot}) and the pod's admission (the cluster created
276
268
  * the pod and it ended `Failed` before it ran — {@link createPod}). Neither goes away by asking again: the same request answers the same. */
277
269
  private connectRefusal?;
278
- /** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
279
- readonly homeDir?: string | undefined;
270
+ /** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home —— 声明胜,否则 pod 就绪那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时 pod 起来之前缺席)。 */
271
+ get homeDir(): string | undefined;
272
+ /** 家目录的座(声明胜 · 否则 pod 就绪那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证(与 local-docker / ssh 同一只判官、同一句拒)。 */
273
+ private readonly homeSeat;
280
274
  /** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
281
275
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
282
276
  readonly cdPath: "unknown";
@@ -616,6 +610,8 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
616
610
  * `latest` is not published, loud) → move `latest` → keep N → the row is deleted (always, saved or failed).
617
611
  * A lost fence, an upload or a pointer failure ⇒ one `workspace_snapshot_failed` line + the `failed` outcome; `latest`
618
612
  * keeps naming the previous snapshot, so the next run restores THAT — never an empty workspace dressed up as a restore.
613
+ * `latest` is moved only onto an object the upload wrote: a pod that is gone when the tar is due (it vanished while the fence was
614
+ * being claimed) uploads nothing, and is that same upload failure.
619
615
  */
620
616
  private persistWorkspace;
621
617
  private execUrl;
@@ -18,6 +18,7 @@ import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js"
18
18
  import { createPosixShellFs } from "./posix-shell-fs.js";
19
19
  import { randomUUID } from "node:crypto";
20
20
  import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
21
+ import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
21
22
  const PROVIDER = "k8s";
22
23
  const K8S_BG_DIR_ROOT = "/tmp/.sema-bg";
23
24
  const K8S_BG_MAX_CONCURRENT = numEnvOr("K8S_BG_MAX_CONCURRENT", 8, 1);
@@ -177,7 +178,10 @@ export class RemoteK8sExecutionEnv {
177
178
  idleWaiters = [];
178
179
  connectInFlight;
179
180
  connectRefusal;
180
- homeDir;
181
+ get homeDir() {
182
+ return this.homeSeat.home;
183
+ }
184
+ homeSeat;
181
185
  cdPath = "unknown";
182
186
  cfg;
183
187
  deps;
@@ -217,7 +221,7 @@ export class RemoteK8sExecutionEnv {
217
221
  ...(config.snapshotExclude && config.snapshotExclude.length > 0 ? { snapshotExclude: config.snapshotExclude } : {}),
218
222
  };
219
223
  this.cwd = this.cfg.mountPath;
220
- this.homeDir = config.homeDir;
224
+ this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
221
225
  this.deps = deps ?? {};
222
226
  this.laneLiveness = laneLivenessOf(this.cfg.livenessMs, "K8S_LIVENESS_MS");
223
227
  const occupant = occupantKeyOf(config.spaceOccupant);
@@ -675,13 +679,24 @@ export class RemoteK8sExecutionEnv {
675
679
  }
676
680
  this.podName = name;
677
681
  this.podIP = podIP;
678
- const mk = await this.runExec(`mkdir -p ${shellQuote(this.cfg.mountPath)}`);
682
+ const askHome = this.homeSeat.asks;
683
+ const mk = await this.runExec(`mkdir -p ${shellQuote(this.cfg.mountPath)}${askHome ? ` && { ${LANE_HOME_PROBE_COMMAND} || true; }` : ""}`);
679
684
  if (!mk.ok || mk.value.exitCode !== 0) {
680
685
  await this.deletePod(name).catch(() => { });
681
686
  this.podName = undefined;
682
687
  this.podIP = undefined;
683
688
  return { ok: false, error: new RemoteExecutionError("connect_failed", `workspace mkdir failed: ${mk.ok ? mk.value.stderr : mk.error.message}`) };
684
689
  }
690
+ if (askHome) {
691
+ const verdict = this.homeSeat.settle({ ok: true, stdout: mk.value.stdout });
692
+ if (verdict !== undefined) {
693
+ await this.deletePod(name).catch(() => { });
694
+ this.podName = undefined;
695
+ this.podIP = undefined;
696
+ const error = new RemoteExecutionError("connect_failed", verdict.refusal);
697
+ return verdict.answered ? this.holdRefusal(error) : { ok: false, error };
698
+ }
699
+ }
685
700
  if (cache?.fresh)
686
701
  this.logger?.warn("workspace_cache_fresh", { space: incarnationLogPrefix(cache.spaceKey), claim: cache.claim.name, size: cache.size });
687
702
  return ok(this.workspaceHandle());
@@ -927,8 +942,11 @@ export class RemoteK8sExecutionEnv {
927
942
  if (target.lane === "park" && this.inFlight.size > 0)
928
943
  return { ok: false, error: new RemoteExecutionError("command_in_flight", "cannot suspend while a command is in flight (would snapshot a torn workspace)") };
929
944
  const snapshotId = mintSnapshotId(this.occupantIncarnation);
930
- if (!this.podName)
931
- return ok(snapshotId);
945
+ if (!this.podName) {
946
+ return target.lane === "space"
947
+ ? { ok: false, error: new RemoteExecutionError("connect_failed", "the sandbox pod was gone when the run-end snapshot reached the upload — nothing was saved (the workspace could not be read), and `latest` keeps naming the previous snapshot") }
948
+ : ok(snapshotId);
949
+ }
932
950
  const presigned = this.presignSnapshot({ ...target, method: "PUT", snapshotId });
933
951
  if (!presigned.ok)
934
952
  return presigned;
@@ -1,24 +1,16 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { type LaneHomeConfig } from "../lane-home-probe.js";
2
3
  import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory } from "@sema-agent/core";
3
4
  /** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
4
5
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
5
6
  /** Construction config for {@link RemoteLocalDockerExecutionEnv}. */
6
- export interface LocalDockerEnvConfig {
7
+ export interface LocalDockerEnvConfig extends LaneHomeConfig {
7
8
  /**
8
9
  * Base container image to run the task in. **REQUIRED — no default** (domestic-images iron rule: the registry
9
10
  * is the operator's choice, never a hardcoded `docker.io` origin). e.g. the docker.io/claybobby pool or an
10
11
  * in-network `node:20` mirror (SWR deprecated).
11
12
  */
12
13
  image: string;
13
- /**
14
- * S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
15
- * 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
16
- * 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
17
- * 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
18
- * 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
19
- * adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
20
- */
21
- homeDir?: string;
22
14
  /** Workspace root INSIDE the container; relative paths resolve against it. Default {@link DEFAULT_MOUNT_PATH}. */
23
15
  mountPath?: string;
24
16
  /** Path to the docker binary (or a docker-compatible CLI). Default "docker" (must be on PATH in the worker). */
@@ -77,8 +69,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
77
69
  };
78
70
  /** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
79
71
  cwd: string;
80
- /** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
81
- readonly homeDir?: string | undefined;
72
+ /** core `ExecutionEnv.homeDir`([ref]):本容器里执行用户的 home —— 声明胜,否则开容器那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
73
+ get homeDir(): string | undefined;
82
74
  /** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在容器里(镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
83
75
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
84
76
  readonly cdPath: "unknown";
@@ -95,6 +87,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
95
87
  private connecting?;
96
88
  /** 家目录:环境答了而与声明不一致 ⇒ 这只环境的拒,记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。 */
97
89
  private homeRefusal?;
90
+ /** 家目录的座(声明胜 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证。 */
91
+ private readonly homeSeat;
98
92
  constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
99
93
  private resolve;
100
94
  workspaceHandle(): WorkspaceHandle;
@@ -107,9 +101,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
107
101
  }>;
108
102
  private doConnect;
109
103
  /**
110
- * 家目录:声明在场(`homeDir`,只来自 `REMOTE_EXEC_HOME_DIR` —— 本车道没有自己的缺省)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、
111
- * 同一份 `-e` env),交唯一判官 `judgeLaneHome`。环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);
112
- * 没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,但不记(与任何一次起容器失败同:下一次连接重问);调用方中止 ⇒ `aborted`、不记。没声明 ⇒ 不问(`undefined`)。容器由调用方收。
104
+ * 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 实测)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、同一份 `-e` env),读数交座。
105
+ * 声明在场:环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,
106
+ * 但不记(与任何一次起容器失败同:下一次连接重问)。没声明:座收下绝对路径;答不上 / 不是绝对路径 ⇒ 座缺席 + 一行 warn,环境照开(没声明就没有声明可拒)。
107
+ * 调用方中止 ⇒ `aborted`、不记、不交座。容器由调用方收。
113
108
  */
114
109
  private homeVerdict;
115
110
  suspendVM(_options?: VmLifecycleOptions): Promise<{
@@ -3,7 +3,7 @@ import os from "node:os";
3
3
  import fs from "node:fs/promises";
4
4
  import { spawn } from "node:child_process";
5
5
  import { hostSubprocessEnv } from "../server-secret-env.js";
6
- import { LANE_HOME_PROBE_COMMAND, judgeLaneHome } from "../lane-home-probe.js";
6
+ import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
7
7
  import { randomBytes } from "node:crypto";
8
8
  import { shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
9
9
  import { signalNumber } from "./host-platform.js";
@@ -23,7 +23,9 @@ const unsupported = (op) => ({
23
23
  export class RemoteLocalDockerExecutionEnv {
24
24
  capabilities = { isolation: true, suspendable: false, canonicalPathAuthoritative: true };
25
25
  cwd;
26
- homeDir;
26
+ get homeDir() {
27
+ return this.homeSeat.home;
28
+ }
27
29
  cdPath = "unknown";
28
30
  cfg;
29
31
  spawnFn;
@@ -33,6 +35,7 @@ export class RemoteLocalDockerExecutionEnv {
33
35
  destroyed = false;
34
36
  connecting;
35
37
  homeRefusal;
38
+ homeSeat;
36
39
  constructor(config, deps = {}) {
37
40
  if (!config.image || !config.image.trim()) {
38
41
  config = { ...config, image: config.image ?? "" };
@@ -46,7 +49,7 @@ export class RemoteLocalDockerExecutionEnv {
46
49
  controlTimeoutMs: config.controlTimeoutMs ?? DEFAULT_CONTROL_TIMEOUT_MS,
47
50
  };
48
51
  this.cwd = this.cfg.mountPath;
49
- this.homeDir = config.homeDir;
52
+ this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
50
53
  this.spawnFn = deps.spawn ?? spawn;
51
54
  const id = config.id ?? randomBytes(6).toString("hex");
52
55
  const suffix = randomBytes(4).toString("hex");
@@ -117,7 +120,7 @@ export class RemoteLocalDockerExecutionEnv {
117
120
  await this.forceRemove().catch(() => { });
118
121
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
119
122
  }
120
- if (this.homeDir !== undefined && !(await this.untilOrAborted(this.probeContainerShell(), config?.abortSignal))) {
123
+ if (this.homeSeat.asks && !(await this.untilOrAborted(this.probeContainerShell(), config?.abortSignal))) {
121
124
  await this.forceRemove().catch(() => { });
122
125
  return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted while detecting the container's shell") };
123
126
  }
@@ -137,14 +140,14 @@ export class RemoteLocalDockerExecutionEnv {
137
140
  return ok(this.handle);
138
141
  }
139
142
  async homeVerdict(signal) {
140
- if (this.homeDir === undefined)
143
+ if (!this.homeSeat.asks)
141
144
  return undefined;
142
145
  const probe = await this.docker(this.execRaw(LANE_HOME_PROBE_COMMAND), { signal, timeoutMs: this.cfg.controlTimeoutMs });
143
- const verdict = judgeLaneHome(PROVIDER, this.homeDir, probe.ok && probe.value.exitCode === 0 ? { ok: true, stdout: probe.value.stdout } : { ok: false, reason: probe.ok ? `exit ${probe.value.exitCode}` : probe.error.message });
144
- if (verdict === undefined)
145
- return undefined;
146
146
  if (!probe.ok && probe.error.code === "aborted")
147
147
  return new RemoteExecutionError("aborted", "connect aborted while reading the environment's home");
148
+ const verdict = this.homeSeat.settle(probe.ok && probe.value.exitCode === 0 ? { ok: true, stdout: probe.value.stdout } : { ok: false, reason: probe.ok ? `exit ${probe.value.exitCode}` : probe.error.message });
149
+ if (verdict === undefined)
150
+ return undefined;
148
151
  const error = new RemoteExecutionError("connect_failed", verdict.refusal);
149
152
  if (verdict.answered)
150
153
  this.homeRefusal = error;
@@ -174,7 +177,7 @@ export class RemoteLocalDockerExecutionEnv {
174
177
  }
175
178
  const insp = await this.docker(["inspect", "-f", "{{.State.Running}}", this.containerName], { timeoutMs: this.cfg.controlTimeoutMs });
176
179
  if (insp.ok && insp.value.exitCode === 0 && insp.value.stdout.trim() === "true") {
177
- if (this.containerId === undefined && this.homeDir !== undefined)
180
+ if (this.containerId === undefined && this.homeSeat.asks)
178
181
  await this.probeContainerShell();
179
182
  const home = this.containerId === undefined ? await this.homeVerdict() : undefined;
180
183
  if (home !== undefined)
@@ -1,22 +1,14 @@
1
1
  import { type SshHostKeyPolicy } from "../ssh-host-key.js";
2
2
  import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory } from "@sema-agent/core";
3
+ import { type LaneHomeConfig } from "../lane-home-probe.js";
3
4
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
4
- export interface SshEnvConfig {
5
+ export interface SshEnvConfig extends LaneHomeConfig {
5
6
  host: string;
6
7
  port?: number;
7
8
  username: string;
8
9
  /** PEM private key — from the adapter-held secret env (NEVER the workspace). [ref] §5 #2. */
9
10
  privateKey: string;
10
11
  passphrase?: string;
11
- /**
12
- * S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
13
- * 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
14
- * 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
15
- * 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
16
- * 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
17
- * adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
18
- */
19
- homeDir?: string;
20
12
  /** Workspace root; relative paths resolve against it. Default "." (the login dir). */
21
13
  mountPath?: string;
22
14
  /** Connect handshake bound (ms). */
@@ -92,9 +84,11 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
92
84
  * =重连风暴);②共享尝试**不携带任何 caller 信号**(首创者的 pre-abort 不得毒化 joiner;单 caller
93
85
  * 取消只退它自己的等待,底层握手余命 ≤ readyTimeout 有界,彻底取消归 destroy 代际闸)。 */
94
86
  private inFlight?;
95
- /** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
96
- readonly homeDir?: string | undefined;
97
- /** 家目录声明 × 实测的判定,按连接记(同一条连接只问一次;重连换了连接再问 —— 登录用户不变时答案相同,代价一次往返)。 */
87
+ /** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home —— 声明胜,否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
88
+ get homeDir(): string | undefined;
89
+ /** 家目录的座(声明胜 · 否则连上之后实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证。 */
90
+ private readonly homeSeat;
91
+ /** 家目录声明 × 实测的判定,按连接记(同一条连接只问一次;重连换了连接再问 —— 登录用户不变时答案相同,代价一次往返;没声明且已量到绝对路径之后不再问)。 */
98
92
  private homeChecked?;
99
93
  /** 目标机答了而与声明不一致 ⇒ 这只环境的拒,记一次、此后每个面原样回它(同一目标机同一登录用户同一答案:不再问、不再发命令)。 */
100
94
  private homeRefusal?;
@@ -126,9 +120,9 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
126
120
  /** 家目录判定的 per-caller 可中止等待(共享的判定承诺不动,只退本 caller)。 */
127
121
  private raceVerdictAbort;
128
122
  /**
129
- * 家目录:声明在场(`homeDir`,只来自 `REMOTE_EXEC_HOME_DIR` —— 本车道没有自己的缺省)⇒ 在这条连接上问一次 `$HOME`(登录用户的 shell 答的,
130
- * 与命令同一个用户、同一份登录 env),交唯一判官 `judgeLaneHome`。答了而不一致 / 不是绝对路径 ⇒ 拒记在环境上(每个面原样回它),命令一条都不发;
131
- * 没答上(通道开不出 / 非零退出 / 超时)⇒ 这一次同样不开、不记,下一次调用重问。
123
+ * 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 量到绝对路径为止)⇒ 在这条连接上问一次 `$HOME`(登录用户的 shell 答的,与命令同一个用户、同一份登录 env),读数交座。
124
+ * 声明在场:答了而不一致 / 不是绝对路径 ⇒ 拒记在环境上(每个面原样回它),命令一条都不发;没答上(通道开不出 / 非零退出 / 超时)⇒ 这一次同样不开、不记,下一次调用重问。
125
+ * 没声明:座收下绝对路径;答不上 / 不是绝对路径 ⇒ 座缺席 + 一行 warn,命令照跑(没声明就没有声明可拒)—— 判定对这条连接记下,换了连接再问。
132
126
  * 问的是裸命令(不经工作目录守卫:工作目录进不去不影响家目录的答案)。上界 = 握手的 `readyTimeoutMs`(问一个环境变量,不是跑用户命令)。
133
127
  */
134
128
  private homeVerdict;
@@ -7,7 +7,7 @@ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
7
7
  import { createPosixShellFs } from "./posix-shell-fs.js";
8
8
  import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
9
9
  import { fileErrorFromExec } from "./remote-env-file-error.js";
10
- import { LANE_HOME_PROBE_COMMAND, judgeLaneHome } from "../lane-home-probe.js";
10
+ import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
11
11
  const PROVIDER = "ssh";
12
12
  const ok = (value) => ({ ok: true, value });
13
13
  const unsupported = (op) => ({
@@ -25,7 +25,10 @@ export class RemoteSshExecutionEnv {
25
25
  destroyed = false;
26
26
  connEpoch = 0;
27
27
  inFlight;
28
- homeDir;
28
+ get homeDir() {
29
+ return this.homeSeat.home;
30
+ }
31
+ homeSeat;
29
32
  homeChecked;
30
33
  homeRefusal;
31
34
  cdPath = "unknown";
@@ -47,7 +50,7 @@ export class RemoteSshExecutionEnv {
47
50
  hostKey: config.hostKey,
48
51
  };
49
52
  this.cwd = this.cfg.mountPath;
50
- this.homeDir = config.homeDir;
53
+ this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
51
54
  }
52
55
  resolve(p) {
53
56
  return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
@@ -110,13 +113,13 @@ export class RemoteSshExecutionEnv {
110
113
  if (this.homeRefusal)
111
114
  return { ok: false, error: this.homeRefusal };
112
115
  const transport = await this.ensureTransport(signal);
113
- if (!transport.ok || this.homeDir === undefined)
116
+ if (!transport.ok || !this.homeSeat.asks)
114
117
  return transport;
115
118
  const conn = this.conn;
116
119
  if (!conn)
117
120
  return { ok: false, error: new ExecutionError("shell_unavailable", "SSH connection dropped right after connect") };
118
121
  if (this.homeChecked?.conn !== conn) {
119
- const checked = { conn, verdict: this.homeVerdict(conn, this.homeDir) };
122
+ const checked = { conn, verdict: this.homeVerdict(conn) };
120
123
  this.homeChecked = checked;
121
124
  void checked.verdict.then((r) => {
122
125
  if (!r.ok && this.homeRefusal === undefined && this.homeChecked === checked)
@@ -148,9 +151,10 @@ export class RemoteSshExecutionEnv {
148
151
  signal.removeEventListener("abort", onAbort);
149
152
  return raced;
150
153
  }
151
- homeVerdict(conn, declared) {
154
+ homeVerdict(conn) {
152
155
  return new Promise((resolve) => {
153
- let out = "";
156
+ const chunks = [];
157
+ let outBytes = 0;
154
158
  let settled = false;
155
159
  let channel;
156
160
  let channelClosed = false;
@@ -173,7 +177,7 @@ export class RemoteSshExecutionEnv {
173
177
  const closeTrouble = closeChannel(channel);
174
178
  if (closeTrouble !== undefined && !probe.ok)
175
179
  probe = { ok: false, reason: `${probe.reason}; ${closeTrouble}` };
176
- const verdict = judgeLaneHome(PROVIDER, declared, probe);
180
+ const verdict = this.homeSeat.settle(probe);
177
181
  if (verdict === undefined)
178
182
  return resolve(ok(undefined));
179
183
  const error = new ExecutionError("shell_unavailable", verdict.refusal);
@@ -191,12 +195,14 @@ export class RemoteSshExecutionEnv {
191
195
  channel = stream;
192
196
  stream
193
197
  .on("data", (d) => {
194
- if (out.length < 4096)
195
- out += d.toString("utf8");
198
+ if (outBytes < 4096) {
199
+ chunks.push(d);
200
+ outBytes += d.length;
201
+ }
196
202
  })
197
203
  .on("close", (code) => {
198
204
  channelClosed = true;
199
- settle(code === 0 ? { ok: true, stdout: out } : { ok: false, reason: code == null ? "the channel closed without an exit status" : `exit ${code}` });
205
+ settle(code === 0 ? { ok: true, stdout: Buffer.concat(chunks).toString("utf8") } : { ok: false, reason: code == null ? "the channel closed without an exit status" : `exit ${code}` });
200
206
  });
201
207
  stream.stderr.on("data", () => { });
202
208
  });
@@ -2,13 +2,14 @@ import type { TaskStatus } from "@sema-agent/core";
2
2
  import type { Pool as MySqlPool } from "mysql2/promise";
3
3
  import type { Pool as PgPool } from "pg";
4
4
  import { type UsageRow } from "../usage-analytics.js";
5
- import { type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
6
- import { type SqlDriver } from "./sql-driver.js";
5
+ import { type ClaimFenced, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
6
+ import { type SqlDriver, type SqlTxConn } from "./sql-driver.js";
7
7
  import type { LedgerEventType } from "../trace/ledger-events.js";
8
8
  import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
9
9
  export type { RunRecord, SessionSummary, RunEvent, LatestRunRef } from "./store-contracts.js";
10
10
  export declare class SqlRunStore {
11
- protected readonly db: SqlDriver;
11
+ readonly db: SqlDriver;
12
+ /** `db` 公开只读:会话认领住在这只池背后的库里,写在认领栅栏里的店(workflow 子代决定行)读写都走它 —— 那只店的池只有这一处来源。 */
12
13
  constructor(db: SqlDriver);
13
14
  /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
14
15
  private q;
@@ -159,6 +160,17 @@ export declare class SqlRunStore {
159
160
  * (一个输掉的认领不许覆盖赢家的持有记录——同一条条件 UPDATE 天然保证)。
160
161
  */
161
162
  markResuming(taskId: string, instanceId: string): Promise<boolean>;
163
+ /**
164
+ * 认领当写栅栏用(进程内两孪生的契约 = `store-contracts.ts` 的 `InProcessClaimFence`;本孪生是它的事务形):一个事务 ——
165
+ * ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
166
+ * ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑;提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
167
+ * 🔴 **取锁序 = task_run → task_active**,与 `setTerminal` / `reapStale` / `reapSuspended` 同序(它们先改 run 行、再删认领行):两行用两条语句按序锁,
168
+ * 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun` 只插认领行、`deleteBySession` 见认领行在就中止,
169
+ * 都不与本序成环。锁住之后:收割的改行 / 终局与收割的删认领 / 别人的建行都排在本事务之后,收割器提交后重判 `updated_at` 看到的是刚刷的。
170
+ * 锁读没命中时 InnoDB 可能取一把间隙锁;本事务此后不往这两张表插任何行(见 space-commit 那条锁形注)。
171
+ * 担保止于提交:判出「持有」的是这只事务;提交确认在路上迟到、这一跑随后失去认领,不在本栅栏的射程里(调用方的已知边界)。
172
+ */
173
+ whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn) => Promise<T>): Promise<ClaimFenced<T>>;
162
174
  /** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
163
175
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
164
176
  /** Shared row→record mapper — byte-identical between dialects (both drivers hand back the same column
@@ -194,17 +206,7 @@ export declare class SqlRunStore {
194
206
  * built with plain string pushes in call order. PG's `$n` needs a NUMBERED placeholder per param, so that arm
195
207
  * keeps its own positional-counter closure (`p()`) — collapsing the two into one shared counter would hide
196
208
  * the very placeholder-numbering divergence this file exists to keep visible. */
197
- listRuns(opts: {
198
- status?: string;
199
- jobId?: string;
200
- source?: string;
201
- owner?: string;
202
- cursor?: {
203
- createdAt: string;
204
- taskId: string;
205
- };
206
- limit: number;
207
- }): Promise<RunRecord[]>;
209
+ listRuns(opts: RunListQuery): Promise<RunRecord[]>;
208
210
  /**
209
211
  * S-297 —— 这个会话**最近一条腿**的窄指针(`/mcp` 面板的 `lastLegMcp` 与任何「上一条 run 是谁」的读面共用)。
210
212
  *
@@ -269,6 +269,21 @@ export class SqlRunStore {
269
269
  const { affected } = await this.db.query(this.q("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, result = NULL, instance_id = ?, updated_at = ? WHERE task_id = ? AND status IN ('suspended','needs_review')", "UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, result = NULL, instance_id = $1, updated_at = $2 WHERE task_id = $3 AND status IN ('suspended','needs_review')"), [instanceId, new Date(), taskId]);
270
270
  return affected === 1;
271
271
  }
272
+ async whileHoldingClaim(sessionId, taskId, write) {
273
+ let fenced = { held: false };
274
+ await this.tx(async (conn) => {
275
+ const run = await conn.query(this.q("SELECT task_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
276
+ if (run.rows.length === 0)
277
+ return;
278
+ const claim = await conn.query(this.q("SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE", "SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE"), [sessionId]);
279
+ const holder = claim.rows[0];
280
+ if (holder === undefined || String(holder.task_id) !== taskId)
281
+ return;
282
+ await conn.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ?", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2"), [new Date(), taskId]);
283
+ fenced = { held: true, value: await write(conn) };
284
+ });
285
+ return fenced;
286
+ }
272
287
  async getActiveTaskId(sessionId) {
273
288
  const { rows } = await this.db.query(this.q("SELECT task_id FROM task_active WHERE session_id = ?", "SELECT task_id FROM task_active WHERE session_id = $1"), [sessionId]);
274
289
  return rows[0] ? String(rows[0].task_id) : undefined;
@@ -335,7 +350,10 @@ export class SqlRunStore {
335
350
  where.push("source = ?");
336
351
  params.push(opts.source);
337
352
  }
338
- if (opts.owner !== undefined) {
353
+ if (opts.owner === null) {
354
+ where.push("owner IS NULL");
355
+ }
356
+ else if (opts.owner !== undefined) {
339
357
  where.push("owner = ?");
340
358
  params.push(opts.owner);
341
359
  }
@@ -362,7 +380,9 @@ export class SqlRunStore {
362
380
  where.push(`job_id = ${p(opts.jobId)}`);
363
381
  if (opts.source !== undefined)
364
382
  where.push(`source = ${p(opts.source)}`);
365
- if (opts.owner !== undefined)
383
+ if (opts.owner === null)
384
+ where.push("owner IS NULL");
385
+ else if (opts.owner !== undefined)
366
386
  where.push(`owner = ${p(opts.owner)}`);
367
387
  if (opts.cursor) {
368
388
  const a = p(new Date(opts.cursor.createdAt));
@@ -21,6 +21,8 @@ export const CENTRAL_SCHEMA_INDEXES = [
21
21
  { table: "workflow_run", name: "idx_wfrun_scope_created", columns: ["scope", "created_at_ms"] },
22
22
  { table: "workflow_run", name: "idx_wfrun_scope_status", columns: ["scope", "status"] },
23
23
  { table: "workflow_agent_session", name: "idx_wfagentsess_run", columns: ["run_id"] },
24
+ { table: "workflow_park_decision", name: "idx_wfparkdec_host", columns: ["host_session_id"] },
25
+ { table: "workflow_park_decision", name: "idx_wfparkdec_run", columns: ["run_id"] },
24
26
  { table: "workflow_completion_inbox", name: "idx_wfinbox_session_seq", columns: ["session_id", "seq"] },
25
27
  { table: "workflow_inbox_fence", name: "idx_wffence_until", columns: ["until_ms"] },
26
28
  { table: "workflow_notify_journal", name: "idx_wfnotify_acked", columns: ["acked"] },
@@ -1,6 +1,6 @@
1
1
  import { createHmac } from "node:crypto";
2
2
  import { uuidv7 } from "@sema-agent/core";
3
- import { CONTROL_AND_BIDI_STRIP_RE } from "../text-bidi.js";
3
+ import { stripControlAndBidi } from "@sema-agent/settings-schema";
4
4
  import { presignS3CopyUrl, presignS3Url } from "./s3-presign.js";
5
5
  import { OBJECT_STORE_IO_TIMEOUT_MS } from "./blob-backend.js";
6
6
  export const PRESIGN_MAX_TTL_SEC = 604_800;
@@ -11,7 +11,7 @@ export function scopeSegment(secretKey, scope) {
11
11
  }
12
12
  export function sanitizeSendFileName(name) {
13
13
  const base = (name ?? "").split(/[/\\]/).pop() ?? "";
14
- let clean = base.replace(CONTROL_AND_BIDI_STRIP_RE, "").replace(/^\.+/, "").trim();
14
+ let clean = stripControlAndBidi(base).replace(/^\.+/, "").trim();
15
15
  if (clean.length > 128) {
16
16
  const dot = clean.lastIndexOf(".");
17
17
  const ext = dot > 0 && clean.length - dot <= 16 ? clean.slice(dot) : "";
@@ -1,13 +1,39 @@
1
- /** One row of the scan: its `seq` and the `payload` column projected as text (`jsonColumnText`). */
1
+ /**
2
+ * The prompt-epoch anchor below a bounded window's floor — `SessionStorage.getEpochAnchor()` for every session storage that
3
+ * loads a window instead of the whole tree: the two SQL session backends (`tidb-session-storage.ts` / `pg-session-storage.ts`,
4
+ * the run's wake and the session view alike) and the local leg's session view (`local-session-window.ts`). One reading, three
5
+ * dialects: each storage hands in only its own page and reads its own rows.
6
+ *
7
+ * ## The rule: the store carries the record, the engine reads it
8
+ * A bounded wake loads `[floor .. leaf]`; when the loaded branch holds no epoch carrier (a rewind cut inside the kept
9
+ * tail, or compactions written before the engine restated the epoch), the engine's `Session.getPromptEpoch()` asks the
10
+ * storage for the nearest carrier BELOW the floor and reads it itself. The storage returns that record **as stored**:
11
+ * · a `prompt_epoch` entry ⇒ the whole entry (the engine's branch walk reads the whole entry too);
12
+ * · a compaction whose `details.promptEpoch` is present ⇒ that restatement;
13
+ * · a compaction without a restatement carries nothing ⇒ the scan pages past it, to exhaustion (a session that lived
14
+ * across the release that introduced epochs can hold any number of them between the floor and its pin).
15
+ * The storage never judges the record's shape. The engine answers a current-shape record as a pin and a retired-shape
16
+ * record by name (`legacy`, with whether it pinned a center artifact) — a storage that normalized the record first would
17
+ * turn a session pinned under a retired shape into one with no pin, and the engine would then skip the center source's
18
+ * handling of that pin. A record that is neither is the engine's to read as well (it reads as no record).
19
+ *
20
+ * `floorSeq <= 0` (a full load: the walk already reaches the root) or no carrier below the floor ⇒ `undefined`
21
+ * (a session that predates epochs).
22
+ *
23
+ * What "below the floor, newest first" walks is the storage's: the SQL twins page their `session_event` rows by `seq` (their
24
+ * window is cut by `seq` too); the local window pages the branch path it was cut from.
25
+ */
26
+ import type { SessionTreeEntry } from "@sema-agent/core";
27
+ /** One row of a scan: its position (`seq`, the keyset cursor). What else the row holds is the storage's — it reads the entry
28
+ * out of the row itself ({@link scanEpochAnchor}'s `entryOf`), so a row is read only when the scan reaches it. */
2
29
  export interface EpochAnchorRow {
3
30
  readonly seq: string | number;
4
- readonly payload: unknown;
5
31
  }
6
32
  /**
7
- * Page the rows `seq < beforeSeq` whose type is `prompt_epoch` or `compaction`, newest first (the backend's own SQL;
8
- * an empty page = no older row).
33
+ * Page the rows `seq < beforeSeq` that may carry an epoch record, newest first (an empty page = no older row). A page may hold
34
+ * other rows too — they carry nothing and the scan passes them.
9
35
  */
10
- export type EpochAnchorPage = (beforeSeq: number) => Promise<readonly EpochAnchorRow[]>;
36
+ export type EpochAnchorPage<R extends EpochAnchorRow> = (beforeSeq: number) => Promise<readonly R[]>;
11
37
  /** The nearest epoch carrier below `floorSeq`, as stored; see the file header. */
12
- export declare function scanEpochAnchor(sessionId: string, floorSeq: number, page: EpochAnchorPage): Promise<unknown>;
38
+ export declare function scanEpochAnchor<R extends EpochAnchorRow>(floorSeq: number, page: EpochAnchorPage<R>, entryOf: (row: R) => SessionTreeEntry): Promise<unknown>;
13
39
  //# sourceMappingURL=session-epoch-anchor.d.ts.map
@@ -1,5 +1,4 @@
1
- import { decodeSessionEventPayload } from "./session-event-payload.js";
2
- export async function scanEpochAnchor(sessionId, floorSeq, page) {
1
+ export async function scanEpochAnchor(floorSeq, page, entryOf) {
3
2
  if (floorSeq <= 0)
4
3
  return undefined;
5
4
  let cursor = floorSeq;
@@ -8,7 +7,7 @@ export async function scanEpochAnchor(sessionId, floorSeq, page) {
8
7
  if (rows.length === 0)
9
8
  return undefined;
10
9
  for (const row of rows) {
11
- const entry = decodeSessionEventPayload(row.payload, sessionId, row.seq);
10
+ const entry = entryOf(row);
12
11
  if (entry.type === "prompt_epoch")
13
12
  return entry;
14
13
  if (entry.type === "compaction") {