@sema-agent/server 7.132.0 → 7.134.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 (473) hide show
  1. package/MIGRATION.md +353 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +301 -64
  5. package/deploy/sema-up/README.md +35 -1
  6. package/deploy/sema-up/chart/Chart.yaml +1 -1
  7. package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
  9. package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
  10. package/deploy/sema-up/chart/templates/server-v4pro.yaml +19 -2
  11. package/deploy/sema-up/chart/templates/server.yaml +19 -2
  12. package/deploy/sema-up/chart/values-full-single.yaml +5 -1
  13. package/deploy/sema-up/chart/values.yaml +34 -0
  14. package/deploy/sema-up/stack/compose.yaml +3 -0
  15. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  16. package/dist/adoption/plan.d.ts +3 -2
  17. package/dist/adoption/plan.js +7 -5
  18. package/dist/adoption/runner.js +3 -10
  19. package/dist/adoption/sql.d.ts +3 -2
  20. package/dist/adoption/sql.js +3 -5
  21. package/dist/adoption/wire.d.ts +4 -1
  22. package/dist/adoption/wire.js +1 -0
  23. package/dist/approval-ask-audit-store.d.ts +32 -14
  24. package/dist/approval-ask-audit-store.js +272 -74
  25. package/dist/approval-ask-machine.d.ts +15 -0
  26. package/dist/approval-ask-machine.js +6 -0
  27. package/dist/approval-ask-retention.d.ts +136 -0
  28. package/dist/approval-ask-retention.js +102 -0
  29. package/dist/approval-card.d.ts +83 -23
  30. package/dist/approval-card.js +45 -15
  31. package/dist/approval-content-kind.d.ts +5 -2
  32. package/dist/approval-content-kind.js +16 -3
  33. package/dist/approval-deny-reasons.d.ts +1 -1
  34. package/dist/approval-gate-currency.d.ts +72 -0
  35. package/dist/approval-gate-currency.js +94 -0
  36. package/dist/approval.d.ts +15 -0
  37. package/dist/approval.js +55 -17
  38. package/dist/audit.d.ts +27 -10
  39. package/dist/audit.js +12 -16
  40. package/dist/bake-runner/main.js +3 -2
  41. package/dist/boot/coordinators.d.ts +6 -1
  42. package/dist/boot/coordinators.js +6 -3
  43. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  44. package/dist/boot/execution-env.js +9 -7
  45. package/dist/boot/execution-lane.js +11 -0
  46. package/dist/boot/leader.d.ts +2 -0
  47. package/dist/boot/leader.js +4 -1
  48. package/dist/boot/memory-boundary.js +2 -2
  49. package/dist/boot/memory-consolidation.d.ts +5 -4
  50. package/dist/boot/memory-consolidation.js +7 -6
  51. package/dist/boot/org-delivery.d.ts +28 -0
  52. package/dist/boot/org-delivery.js +20 -0
  53. package/dist/boot/read-deny-anchor-audit.js +2 -2
  54. package/dist/boot/reapers.d.ts +3 -0
  55. package/dist/boot/reapers.js +21 -1
  56. package/dist/boot/resolve-spec.d.ts +3 -0
  57. package/dist/boot/resolve-spec.js +60 -81
  58. package/dist/boot/runner-deps.d.ts +18 -5
  59. package/dist/boot/runner-deps.js +10 -0
  60. package/dist/boot/session-faces.d.ts +1 -0
  61. package/dist/boot/session-faces.js +4 -2
  62. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  63. package/dist/boot/stage-06-runners.d.ts +6 -3
  64. package/dist/boot/stage-06-runners.js +20 -4
  65. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  66. package/dist/boot/stage-07-capability-layer.js +24 -3
  67. package/dist/boot/stage-08-reapers.d.ts +4 -1
  68. package/dist/boot/stage-08-reapers.js +2 -2
  69. package/dist/boot/stage-09-leader.d.ts +5 -1
  70. package/dist/boot/stage-09-leader.js +2 -2
  71. package/dist/boot/stage-10-http-server.d.ts +5 -1
  72. package/dist/boot/stage-10-http-server.js +6 -6
  73. package/dist/boot/stores.d.ts +8 -0
  74. package/dist/boot/stores.js +10 -14
  75. package/dist/boot/web-admin.d.ts +10 -0
  76. package/dist/boot/web-admin.js +162 -65
  77. package/dist/boot/workflow-orchestration.d.ts +2 -0
  78. package/dist/boot/workflow-orchestration.js +2 -1
  79. package/dist/brain.js +8 -3
  80. package/dist/breaker-key.d.ts +6 -0
  81. package/dist/breaker-key.js +29 -0
  82. package/dist/budget.js +5 -1
  83. package/dist/caller-text-echo.d.ts +11 -6
  84. package/dist/caller-text-echo.js +10 -12
  85. package/dist/canonical-json.d.ts +2 -41
  86. package/dist/canonical-json.js +2 -13
  87. package/dist/capabilities/center-plugins.d.ts +7 -0
  88. package/dist/capabilities/center-plugins.js +1 -1
  89. package/dist/capabilities/composite-members.d.ts +1 -1
  90. package/dist/capabilities/composite-members.js +1 -1
  91. package/dist/capabilities/execution-lane.js +2 -2
  92. package/dist/capabilities/hands-lane.d.ts +21 -0
  93. package/dist/capabilities/hands-lane.js +10 -0
  94. package/dist/capabilities/mcp-probe.d.ts +36 -4
  95. package/dist/capabilities/mcp-probe.js +29 -6
  96. package/dist/capabilities/scenarios.d.ts +63 -7
  97. package/dist/capabilities/scenarios.js +39 -7
  98. package/dist/capability-facts.d.ts +36 -4
  99. package/dist/capability-facts.js +30 -4
  100. package/dist/center-config-tier.d.ts +69 -0
  101. package/dist/center-config-tier.js +42 -0
  102. package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
  103. package/dist/checkpoint-ctx-seal-m28.js +19 -0
  104. package/dist/checkpoint-identity.d.ts +16 -0
  105. package/dist/checkpoint-identity.js +19 -0
  106. package/dist/config-catalog.d.ts +17 -10
  107. package/dist/config-catalog.js +43 -28
  108. package/dist/config-d-migration.d.ts +3 -1
  109. package/dist/config-d-migration.js +2 -2
  110. package/dist/config-dir-trust.d.ts +114 -0
  111. package/dist/config-dir-trust.js +388 -0
  112. package/dist/config-invariants.d.ts +18 -5
  113. package/dist/config-invariants.js +35 -3
  114. package/dist/config-provider.d.ts +6 -1
  115. package/dist/config-provider.js +6 -2
  116. package/dist/config-types.d.ts +71 -14
  117. package/dist/config-types.js +8 -0
  118. package/dist/config.d.ts +13 -0
  119. package/dist/config.js +121 -59
  120. package/dist/delegated-session-owner.d.ts +185 -0
  121. package/dist/delegated-session-owner.js +119 -0
  122. package/dist/device-protocol.d.ts +5 -3
  123. package/dist/device-protocol.js +1 -1
  124. package/dist/device-ws-hub.js +21 -2
  125. package/dist/device-ws-protocol.d.ts +70 -6
  126. package/dist/device-ws-protocol.js +50 -23
  127. package/dist/elicitation.js +3 -2
  128. package/dist/entry-problems.d.ts +34 -0
  129. package/dist/entry-problems.js +14 -0
  130. package/dist/execution-lane-caps.d.ts +49 -10
  131. package/dist/execution-lane-caps.js +43 -4
  132. package/dist/field-conflict.d.ts +44 -0
  133. package/dist/field-conflict.js +21 -0
  134. package/dist/file-history-disclosure.d.ts +34 -5
  135. package/dist/file-history-disclosure.js +2 -2
  136. package/dist/fleet/fleet-bus.d.ts +84 -6
  137. package/dist/fleet/fleet-bus.js +42 -9
  138. package/dist/hooks/classic-stations.d.ts +10 -5
  139. package/dist/hooks/classic-stations.js +4 -4
  140. package/dist/hooks/command-spawn.d.ts +57 -0
  141. package/dist/hooks/command-spawn.js +139 -0
  142. package/dist/hooks/hook-llm.js +2 -2
  143. package/dist/hooks/hook-runner.d.ts +127 -25
  144. package/dist/hooks/hook-runner.js +668 -287
  145. package/dist/hooks/hook-source.d.ts +44 -0
  146. package/dist/hooks/hook-source.js +40 -0
  147. package/dist/http/active-run-conflict.d.ts +5 -13
  148. package/dist/http/active-run-conflict.js +17 -27
  149. package/dist/http/admission.js +111 -60
  150. package/dist/http/approval-sweeps.js +58 -19
  151. package/dist/http/caller-trust.d.ts +77 -0
  152. package/dist/http/caller-trust.js +143 -0
  153. package/dist/http/checkpoint-ctx.d.ts +173 -18
  154. package/dist/http/checkpoint-ctx.js +171 -12
  155. package/dist/http/claimed-run.d.ts +4 -3
  156. package/dist/http/device-placement.d.ts +2 -4
  157. package/dist/http/device-placement.js +2 -1
  158. package/dist/http/dispatch.js +13 -7
  159. package/dist/http/durable-run-leg.d.ts +4 -32
  160. package/dist/http/durable-run-leg.js +37 -12
  161. package/dist/http/idempotency.d.ts +2 -2
  162. package/dist/http/idempotency.js +2 -2
  163. package/dist/http/owner-access.d.ts +56 -0
  164. package/dist/http/owner-access.js +42 -0
  165. package/dist/http/principal-gate.d.ts +78 -27
  166. package/dist/http/principal-gate.js +64 -14
  167. package/dist/http/readiness.d.ts +11 -5
  168. package/dist/http/readiness.js +10 -3
  169. package/dist/http/refusal-body.d.ts +12 -0
  170. package/dist/http/refusal-body.js +11 -0
  171. package/dist/http/request-guards.d.ts +16 -32
  172. package/dist/http/request-guards.js +31 -30
  173. package/dist/http/resume-ctx-read.d.ts +50 -6
  174. package/dist/http/resume-ctx-read.js +33 -7
  175. package/dist/http/resume-legs.d.ts +5 -5
  176. package/dist/http/resume-legs.js +163 -91
  177. package/dist/http/retired-service-deps.d.ts +43 -0
  178. package/dist/http/retired-service-deps.js +18 -0
  179. package/dist/http/route-ctx.d.ts +44 -37
  180. package/dist/http/route-ctx.js +4 -0
  181. package/dist/http/route-table.js +2 -0
  182. package/dist/http/routes/a2a-serve.js +3 -4
  183. package/dist/http/routes/admin-config-refresh.js +1 -1
  184. package/dist/http/routes/admin-drain.js +1 -1
  185. package/dist/http/routes/admin-memory-optout.js +5 -1
  186. package/dist/http/routes/adoption.js +2 -9
  187. package/dist/http/routes/agents-roster.js +2 -3
  188. package/dist/http/routes/approvals-assistant.js +91 -56
  189. package/dist/http/routes/background.js +3 -2
  190. package/dist/http/routes/capabilities.js +20 -4
  191. package/dist/http/routes/config-catalog.d.ts +3 -3
  192. package/dist/http/routes/config-catalog.js +1 -1
  193. package/dist/http/routes/devices.js +3 -17
  194. package/dist/http/routes/diagnostics.d.ts +3 -3
  195. package/dist/http/routes/diagnostics.js +6 -2
  196. package/dist/http/routes/fleet.js +4 -4
  197. package/dist/http/routes/images.js +6 -7
  198. package/dist/http/routes/leader.js +1 -8
  199. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  200. package/dist/http/routes/mcp-reconnect.js +3 -2
  201. package/dist/http/routes/memory-bundle.js +1 -1
  202. package/dist/http/routes/memory-compliance.js +1 -1
  203. package/dist/http/routes/memory-consolidation.js +1 -1
  204. package/dist/http/routes/memory-origin.d.ts +1 -1
  205. package/dist/http/routes/memory-origin.js +1 -1
  206. package/dist/http/routes/memory-policy.d.ts +1 -1
  207. package/dist/http/routes/memory-policy.js +3 -2
  208. package/dist/http/routes/notify-wake.js +8 -21
  209. package/dist/http/routes/observability.js +2 -2
  210. package/dist/http/routes/principal-inbox.d.ts +39 -0
  211. package/dist/http/routes/principal-inbox.js +76 -0
  212. package/dist/http/routes/retention-ops.js +2 -9
  213. package/dist/http/routes/rules.js +9 -22
  214. package/dist/http/routes/runs.d.ts +5 -0
  215. package/dist/http/routes/runs.js +63 -115
  216. package/dist/http/routes/session-sync.d.ts +3 -2
  217. package/dist/http/routes/session-sync.js +53 -48
  218. package/dist/http/routes/sessions-list.d.ts +1 -1
  219. package/dist/http/routes/sessions-list.js +3 -4
  220. package/dist/http/routes/sessions.js +33 -43
  221. package/dist/http/routes/side-query.js +6 -4
  222. package/dist/http/routes/tasks.js +71 -30
  223. package/dist/http/routes/trace-usage.js +7 -9
  224. package/dist/http/routes/workflows.d.ts +351 -0
  225. package/dist/http/routes/workflows.js +153 -119
  226. package/dist/http/run-declared-families.d.ts +16 -0
  227. package/dist/http/run-declared-families.js +12 -0
  228. package/dist/http/send.d.ts +24 -3
  229. package/dist/http/send.js +14 -2
  230. package/dist/http/server.d.ts +23 -18
  231. package/dist/http/server.js +20 -15
  232. package/dist/http/stored-body-replay.js +17 -4
  233. package/dist/http/submit-ctx.d.ts +4 -3
  234. package/dist/http/submit-ctx.js +23 -6
  235. package/dist/http/submit-replay.d.ts +4 -3
  236. package/dist/http/submit-replay.js +5 -4
  237. package/dist/http/wire-types.d.ts +17 -10
  238. package/dist/index.d.ts +3 -1
  239. package/dist/index.js +1 -1
  240. package/dist/lane-home-probe.d.ts +78 -9
  241. package/dist/lane-home-probe.js +78 -12
  242. package/dist/leader/endpoint.d.ts +4 -1
  243. package/dist/leader/endpoint.js +11 -10
  244. package/dist/leader/wire.d.ts +4 -0
  245. package/dist/leader/wire.js +8 -7
  246. package/dist/limit-refusal.d.ts +17 -5
  247. package/dist/limit-refusal.js +4 -1
  248. package/dist/main.js +1 -1
  249. package/dist/mcp-assertion-header.d.ts +31 -10
  250. package/dist/mcp-assertion-header.js +10 -3
  251. package/dist/memory-origin-fence.js +2 -2
  252. package/dist/memory-project-layer.d.ts +37 -0
  253. package/dist/memory-project-layer.js +46 -0
  254. package/dist/memory-scope.d.ts +23 -16
  255. package/dist/memory-scope.js +24 -8
  256. package/dist/memory-sync-client.d.ts +27 -13
  257. package/dist/memory-sync-client.js +107 -18
  258. package/dist/model-compat.d.ts +1 -1
  259. package/dist/model-entry-refusal.d.ts +9 -1
  260. package/dist/model-entry-refusal.js +6 -1
  261. package/dist/model-plane-face.d.ts +51 -0
  262. package/dist/model-plane-face.js +24 -0
  263. package/dist/model-provider.d.ts +5 -0
  264. package/dist/model-provider.js +4 -2
  265. package/dist/model-route-endpoint.d.ts +8 -11
  266. package/dist/model-route-endpoint.js +4 -7
  267. package/dist/model-route-env.d.ts +22 -0
  268. package/dist/model-route-env.js +2 -0
  269. package/dist/model-route-gap.d.ts +60 -30
  270. package/dist/model-route-gap.js +21 -15
  271. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  272. package/dist/observability/corrupt-read-seat.js +9 -0
  273. package/dist/observability/err-text.d.ts +6 -0
  274. package/dist/observability/err-text.js +18 -4
  275. package/dist/observability/fail-open.d.ts +35 -9
  276. package/dist/observability/fail-open.js +35 -9
  277. package/dist/observability/metrics.js +1 -0
  278. package/dist/observability/prompt-manifest.d.ts +22 -25
  279. package/dist/observability/prompt-manifest.js +54 -50
  280. package/dist/observability/tool-trace.js +9 -10
  281. package/dist/operator-ask.d.ts +10 -0
  282. package/dist/operator-ask.js +3 -0
  283. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  284. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  285. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  286. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  287. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  288. package/dist/orchestration/workflow-notify-journal.js +27 -34
  289. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  290. package/dist/orchestration/workflow-park-decision.js +119 -0
  291. package/dist/org-delivery-gate.d.ts +81 -0
  292. package/dist/org-delivery-gate.js +59 -0
  293. package/dist/panel-tool-names.d.ts +23 -0
  294. package/dist/panel-tool-names.js +8 -0
  295. package/dist/parked-decide.d.ts +43 -11
  296. package/dist/parked-decide.js +100 -51
  297. package/dist/parked-revive-surface.d.ts +9 -2
  298. package/dist/parked-revive-surface.js +11 -8
  299. package/dist/permission-rule-list.d.ts +1 -1
  300. package/dist/permission-rule-list.js +2 -2
  301. package/dist/permission-rule-vocab.d.ts +1 -23
  302. package/dist/permission-rule-vocab.js +0 -13
  303. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  304. package/dist/plugins/approval-ask-store-file.js +21 -0
  305. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  306. package/dist/plugins/approval-ask-store-memory.js +34 -2
  307. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  308. package/dist/plugins/approval-ask-store-sql.js +37 -1
  309. package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
  310. package/dist/plugins/approval-payload-fingerprint.js +1 -1
  311. package/dist/plugins/blob-backend.d.ts +38 -25
  312. package/dist/plugins/blob-backend.js +29 -19
  313. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  314. package/dist/plugins/breaker-state-sql.js +4 -4
  315. package/dist/plugins/caching-session-store.d.ts +4 -1
  316. package/dist/plugins/caching-session-store.js +7 -3
  317. package/dist/plugins/checkpoint-store-sql.d.ts +36 -1
  318. package/dist/plugins/checkpoint-store-sql.js +65 -3
  319. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  320. package/dist/plugins/file-history-store-sql.js +3 -1
  321. package/dist/plugins/file-run-store.d.ts +5 -13
  322. package/dist/plugins/file-run-store.js +9 -0
  323. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  324. package/dist/plugins/fork-routing-session-store.js +12 -1
  325. package/dist/plugins/host-platform.d.ts +2 -0
  326. package/dist/plugins/host-platform.js +2 -1
  327. package/dist/plugins/local-checkpoint-store.d.ts +15 -2
  328. package/dist/plugins/local-checkpoint-store.js +32 -1
  329. package/dist/plugins/local-session-store.d.ts +13 -10
  330. package/dist/plugins/local-session-store.js +23 -14
  331. package/dist/plugins/local-session-window.d.ts +45 -0
  332. package/dist/plugins/local-session-window.js +38 -0
  333. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  334. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  335. package/dist/plugins/mailbox-store-sql.js +77 -52
  336. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  337. package/dist/plugins/memory-run-store.d.ts +5 -13
  338. package/dist/plugins/memory-run-store.js +8 -0
  339. package/dist/plugins/one-time-migrations.d.ts +121 -5
  340. package/dist/plugins/one-time-migrations.js +332 -8
  341. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  342. package/dist/plugins/org-delivery-quota-store.js +228 -0
  343. package/dist/plugins/permission-rule-store-file.js +10 -6
  344. package/dist/plugins/permission-rule-store-sql.js +8 -8
  345. package/dist/plugins/pg-pool.js +39 -7
  346. package/dist/plugins/pg-session-storage.d.ts +18 -5
  347. package/dist/plugins/pg-session-storage.js +68 -23
  348. package/dist/plugins/remote-env-adb.d.ts +6 -12
  349. package/dist/plugins/remote-env-adb.js +12 -2
  350. package/dist/plugins/remote-env-device.d.ts +14 -7
  351. package/dist/plugins/remote-env-device.js +23 -15
  352. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  353. package/dist/plugins/remote-env-k8s.js +23 -5
  354. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  355. package/dist/plugins/remote-env-local-docker.js +12 -9
  356. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  357. package/dist/plugins/remote-env-ssh.js +17 -11
  358. package/dist/plugins/run-store-sql.d.ts +16 -14
  359. package/dist/plugins/run-store-sql.js +22 -2
  360. package/dist/plugins/schema-indexes.js +2 -0
  361. package/dist/plugins/send-user-file.js +2 -2
  362. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  363. package/dist/plugins/session-epoch-anchor.js +2 -3
  364. package/dist/plugins/session-ownership.d.ts +11 -0
  365. package/dist/plugins/session-ownership.js +11 -0
  366. package/dist/plugins/session-placement.d.ts +12 -14
  367. package/dist/plugins/session-placement.js +2 -15
  368. package/dist/plugins/session-store.d.ts +18 -8
  369. package/dist/plugins/session-store.js +15 -4
  370. package/dist/plugins/sql-driver.d.ts +74 -11
  371. package/dist/plugins/sql-driver.js +210 -12
  372. package/dist/plugins/sql-errors.d.ts +23 -0
  373. package/dist/plugins/sql-errors.js +21 -0
  374. package/dist/plugins/store-backend.d.ts +36 -1
  375. package/dist/plugins/store-backend.js +40 -14
  376. package/dist/plugins/store-contracts.d.ts +42 -0
  377. package/dist/plugins/store-contracts.js +1 -0
  378. package/dist/plugins/tidb-pool.d.ts +6 -1
  379. package/dist/plugins/tidb-pool.js +44 -10
  380. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  381. package/dist/plugins/tidb-session-storage.js +2 -2
  382. package/dist/plugins/tidb-session-store.d.ts +25 -5
  383. package/dist/plugins/tidb-session-store.js +67 -21
  384. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  385. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  386. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  387. package/dist/plugins/workflow-run-store-sql.js +50 -9
  388. package/dist/posture-source.d.ts +18 -10
  389. package/dist/posture-source.js +4 -1
  390. package/dist/project-identity.d.ts +45 -6
  391. package/dist/project-identity.js +162 -8
  392. package/dist/project-layer-key.d.ts +28 -0
  393. package/dist/project-layer-key.js +29 -0
  394. package/dist/question.js +3 -2
  395. package/dist/read-deny-entry.d.ts +25 -0
  396. package/dist/read-deny-entry.js +12 -0
  397. package/dist/read-face-posture.d.ts +37 -11
  398. package/dist/read-face-posture.js +32 -3
  399. package/dist/request-key-closure.d.ts +7 -0
  400. package/dist/request-key-closure.js +9 -1
  401. package/dist/request-tool-axes.d.ts +12 -0
  402. package/dist/request-tool-axes.js +20 -0
  403. package/dist/request-value-closure.d.ts +5 -0
  404. package/dist/request-value-closure.js +11 -3
  405. package/dist/rule-entry-list.d.ts +26 -2
  406. package/dist/rule-entry-list.js +72 -12
  407. package/dist/rules-consent.js +3 -4
  408. package/dist/run-acceptance.d.ts +19 -3
  409. package/dist/run-acceptance.js +13 -1
  410. package/dist/run-local.js +37 -6
  411. package/dist/runs.d.ts +12 -10
  412. package/dist/runs.js +25 -6
  413. package/dist/runtime-caps-resolver.d.ts +16 -6
  414. package/dist/runtime-caps-resolver.js +18 -5
  415. package/dist/sandbox-pkg-source.d.ts +19 -1
  416. package/dist/sandbox-pkg-source.js +29 -7
  417. package/dist/sealed-key.d.ts +32 -0
  418. package/dist/sealed-key.js +55 -1
  419. package/dist/security.d.ts +95 -15
  420. package/dist/security.js +52 -9
  421. package/dist/session-error-wire.d.ts +31 -7
  422. package/dist/session-error-wire.js +9 -2
  423. package/dist/session-sync-kernel.d.ts +37 -10
  424. package/dist/session-sync-kernel.js +10 -0
  425. package/dist/session-titled-frame.d.ts +33 -0
  426. package/dist/session-titled-frame.js +4 -0
  427. package/dist/session-titler.d.ts +9 -1
  428. package/dist/session-titler.js +15 -4
  429. package/dist/spec-fields.js +21 -2
  430. package/dist/sql-engine-posture.d.ts +3 -1
  431. package/dist/task-a2a.js +4 -3
  432. package/dist/task-cwd.d.ts +1 -1
  433. package/dist/task-mcp.d.ts +5 -5
  434. package/dist/task-mcp.js +27 -74
  435. package/dist/task-settings.d.ts +93 -88
  436. package/dist/task-settings.js +71 -63
  437. package/dist/text-bidi.d.ts +0 -4
  438. package/dist/text-bidi.js +1 -3
  439. package/dist/tighten-only-seats.d.ts +150 -0
  440. package/dist/tighten-only-seats.js +196 -0
  441. package/dist/tool-approval.d.ts +451 -136
  442. package/dist/tool-approval.js +733 -256
  443. package/dist/tool-rule-names.d.ts +6 -0
  444. package/dist/trace/core-keyset-guard.d.ts +108 -25
  445. package/dist/trace/exit-seat.d.ts +40 -0
  446. package/dist/trace/exit-seat.js +130 -0
  447. package/dist/trace/ledger-events.d.ts +6 -3
  448. package/dist/trace/project.d.ts +98 -13
  449. package/dist/trace/project.js +57 -32
  450. package/dist/trace/projection-table.d.ts +100 -0
  451. package/dist/trace/projection-table.js +130 -0
  452. package/dist/trace/redact.d.ts +57 -0
  453. package/dist/trace/redact.js +962 -168
  454. package/dist/trace/self-minted-wire.d.ts +349 -1
  455. package/dist/trace/self-minted-wire.js +118 -1
  456. package/dist/trace/wire-projection-faces.d.ts +304 -2
  457. package/dist/trace/wire-projection-faces.js +160 -13
  458. package/dist/url-refusal.js +2 -0
  459. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  460. package/dist/web-admin-client/apply-effective.js +56 -5
  461. package/dist/web-admin-client/facade.d.ts +1 -1
  462. package/dist/web-admin-client/facade.js +1 -1
  463. package/dist/web-admin-client/read-face.js +5 -9
  464. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  465. package/dist/web-admin-client/skills-mcp.js +6 -8
  466. package/dist/web-admin-client/types.d.ts +1 -1
  467. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  468. package/package.json +8 -4
  469. package/scripts/drain-check-local.mjs +34 -2
  470. package/scripts/drain-check.mysql.sql +20 -2
  471. package/scripts/drain-check.pg.sql +20 -2
  472. package/dist/mcp-tool-faces.d.ts +0 -69
  473. package/dist/mcp-tool-faces.js +0 -106
@@ -1,5 +1,5 @@
1
1
  import { emitLeafAdvance } from "../session-leaf-bus.js";
2
- import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, } from "@sema-agent/core";
2
+ import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, admitSessionPlacement, } from "@sema-agent/core";
3
3
  import { SESSION_TITLE_MAX_CHARS } from "../session-row.js";
4
4
  import { escapeLike } from "./sql-escape.js";
5
5
  import { decodeSessionEventPayload } from "./session-event-payload.js";
@@ -7,14 +7,17 @@ import { scanEpochAnchor } from "./session-epoch-anchor.js";
7
7
  import { jsonColumnText } from "./sql-json-column.js";
8
8
  import { pgSafeJsonStringify } from "./pg-safe-json.js";
9
9
  import { toIso } from "./sql-row-helpers.js";
10
- import { assertPlacementAdmission, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SQL_SESSION_PLACEMENTS, } from "./session-placement.js";
10
+ import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SQL_SESSION_PLACEMENTS, } from "./session-placement.js";
11
11
  import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
12
- import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
12
+ import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
13
+ import { createLogger } from "../observability/logger.js";
14
+ import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
13
15
  import { isPgUniqueViolation } from "./sql-errors.js";
14
16
  import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
15
17
  import { MANAGED_RETENTION } from "./retention-store-sql.js";
16
18
  import { SESSION_META_INCARNATION_COLUMN, carriedIncarnation, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
17
19
  import { ensureIndexes } from "./ensure-index.js";
20
+ import { assertFirstOwnerWritable, forkOwnerOf } from "./session-ownership.js";
18
21
  const isDupKey = isPgUniqueViolation;
19
22
  function baselineQuery(text, values) {
20
23
  return { text, values, query_timeout: BASELINE_IO_DEADLINE_MS };
@@ -83,10 +86,10 @@ export class PgSessionStorage extends BaseSessionStorage {
83
86
  this.floorSeq = floorSeq;
84
87
  }
85
88
  async getEpochAnchor() {
86
- return scanEpochAnchor(this.sessionId, this.floorSeq, async (beforeSeq) => {
89
+ return scanEpochAnchor(this.floorSeq, async (beforeSeq) => {
87
90
  const res = await this.pool.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 AND seq < $2 AND type IN ('prompt_epoch','compaction') ORDER BY seq DESC LIMIT 100`, [this.sessionId, beforeSeq]);
88
91
  return res.rows;
89
- });
92
+ }, (row) => decodeSessionEventPayload(row.payload, this.sessionId, row.seq));
90
93
  }
91
94
  static async wake(pool, sessionId) {
92
95
  const metaRes = await pool.query(`SELECT created_at, leaf_id, leaf_seq, incarnation, ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = $1`, [sessionId]);
@@ -175,13 +178,15 @@ export class PgSessionStore {
175
178
  retention = MANAGED_RETENTION;
176
179
  placements = SQL_SESSION_PLACEMENTS;
177
180
  pending = new Map();
178
- constructor(pool) {
181
+ log;
182
+ constructor(pool, opts = {}) {
179
183
  this.pool = pool;
184
+ this.log = opts.log ?? createLogger();
180
185
  }
181
186
  async acquire(sessionId, opts) {
182
187
  if (!sessionId) {
183
188
  const id = uuidv7();
184
- await this.register(id, null, opts?.placement);
189
+ await this.createOnAcquire(id, opts?.placement);
185
190
  return this.load(id, false, opts);
186
191
  }
187
192
  if (opts?.requireExisting)
@@ -189,7 +194,7 @@ export class PgSessionStore {
189
194
  const inflight = this.pending.get(sessionId);
190
195
  if (inflight) {
191
196
  const acquired = await inflight;
192
- assertPlacementAdmission(sessionId, await this.placementOf(sessionId), opts);
197
+ admitSessionPlacement(sessionId, await this.placementOf(sessionId), opts ?? {}, placementCapabilityOf(this));
193
198
  return acquired;
194
199
  }
195
200
  const p = this.load(sessionId, false, opts).finally(() => this.pending.delete(sessionId));
@@ -199,20 +204,44 @@ export class PgSessionStore {
199
204
  async load(sessionId, requireExisting = false, opts) {
200
205
  let storage = await PgSessionStorage.wake(this.pool, sessionId);
201
206
  if (storage) {
202
- assertPlacementAdmission(sessionId, (await storage.getMetadata()).placement, opts);
207
+ admitSessionPlacement(sessionId, (await storage.getMetadata()).placement, opts ?? {}, placementCapabilityOf(this));
203
208
  return { session: new StoredSession(storage), sessionId };
204
209
  }
205
210
  if (requireExisting)
206
211
  throw new SessionError("not_found", `session ${sessionId} not found`);
207
- await this.register(sessionId, null, opts?.placement);
212
+ await this.createOnAcquire(sessionId, opts?.placement);
208
213
  storage = await PgSessionStorage.wake(this.pool, sessionId);
209
214
  if (!storage) {
210
215
  throw new SessionError("storage", `session ${sessionId} vanished between register and wake (a concurrent purge won the race) — retry`);
211
216
  }
212
- assertPlacementAdmission(sessionId, (await storage.getMetadata()).placement, opts);
217
+ admitSessionPlacement(sessionId, (await storage.getMetadata()).placement, opts ?? {}, placementCapabilityOf(this));
213
218
  return { session: new StoredSession(storage), sessionId };
214
219
  }
215
- async register(sessionId, owner, placement) {
220
+ async register(sessionId, owner) {
221
+ if (typeof owner !== "string" && owner !== null) {
222
+ if ((await this.ownerOf(sessionId)) !== undefined)
223
+ return;
224
+ assertFirstOwnerWritable(sessionId, owner);
225
+ }
226
+ await this.insertMeta(sessionId, owner, undefined);
227
+ }
228
+ async createOnAcquire(sessionId, placement) {
229
+ if (placement === undefined)
230
+ return this.insertMeta(sessionId, null, undefined);
231
+ await createPlacedSessionRow(sessionId, placement, {
232
+ readFaces: (ids) => this.sourceFaces(ids),
233
+ insert: (owner) => this.insertMeta(sessionId, owner, placement),
234
+ readOwner: () => this.ownerOf(sessionId),
235
+ }, this.log);
236
+ }
237
+ async claimOwnerless(sessionId, incarnation, owner) {
238
+ return ((await this.pool.query(CLAIM_OWNERLESS_PLACED_SQL.pg, [...claimOwnerlessParams(sessionId, incarnation, owner)])).rowCount ?? 0) > 0;
239
+ }
240
+ async sourceFaces(ids) {
241
+ const res = await this.pool.query(`SELECT session_id, owner, incarnation FROM session_meta WHERE session_id IN (${ids.map((_, i) => `$${i + 1}`).join(", ")})`, [...ids]);
242
+ return new Map(res.rows.map((r) => [String(r.session_id), sessionFaceFromRow(String(r.session_id), r.owner ?? null, r.incarnation)]));
243
+ }
244
+ async insertMeta(sessionId, owner, placement) {
216
245
  const now = new Date();
217
246
  await this.pool.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, " +
218
247
  "placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) " +
@@ -406,10 +435,11 @@ export class PgSessionStore {
406
435
  }
407
436
  }
408
437
  async fork(sourceId, owner) {
409
- const metaRes = await this.pool.query("SELECT leaf_id FROM session_meta WHERE session_id = $1", [sourceId]);
438
+ const metaRes = await this.pool.query("SELECT leaf_id, owner FROM session_meta WHERE session_id = $1", [sourceId]);
410
439
  if (!metaRes.rows[0])
411
440
  return null;
412
441
  const leafId = metaRes.rows[0].leaf_id ?? null;
442
+ const forkOwner = forkOwnerOf(owner, metaRes.rows[0].owner ?? null) ?? null;
413
443
  const eventRes = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("pg", "payload")}, ts FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [sourceId]);
414
444
  const rows = eventRes.rows;
415
445
  const newId = uuidv7();
@@ -422,7 +452,7 @@ export class PgSessionStore {
422
452
  const payload = String(r.payload);
423
453
  await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
424
454
  }
425
- await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7)", [newId, owner, now, now, leafId, rows.length, mintSessionIncarnation()]);
455
+ await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7)", [newId, forkOwner, now, now, leafId, rows.length, mintSessionIncarnation()]);
426
456
  await conn.query("COMMIT");
427
457
  return newId;
428
458
  }
@@ -619,6 +649,23 @@ class PgStagingHandle {
619
649
  const conn = await this.pool.connect();
620
650
  try {
621
651
  await conn.query("BEGIN");
652
+ const now = new Date();
653
+ const minted = mintSessionIncarnation();
654
+ if (opts.expectedOwner !== undefined) {
655
+ const cas = await conn.query("UPDATE session_meta SET leaf_seq = leaf_seq + 1 WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2", [this.realSessionId, opts.expectedOwner]);
656
+ if (cas.rowCount !== 1) {
657
+ await conn.query("ROLLBACK");
658
+ throw new StagedCommitOwnerChangedError(this.realSessionId);
659
+ }
660
+ }
661
+ else {
662
+ await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,NULL,0,$5) ON CONFLICT (session_id) DO NOTHING", [this.realSessionId, owner, now, now, minted]);
663
+ const claimed = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = $1 FOR UPDATE", [this.realSessionId]);
664
+ if (String(claimed.rows[0]?.incarnation) !== minted) {
665
+ await conn.query("ROLLBACK");
666
+ throw new StagedCommitOwnerChangedError(this.realSessionId);
667
+ }
668
+ }
622
669
  const staged = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
623
670
  const dst = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
624
671
  const dstMeta = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1", [this.realSessionId]);
@@ -627,15 +674,15 @@ class PgStagingHandle {
627
674
  throw new SessionError("placement_refused", `session ${this.realSessionId} belongs to the ${dstMeta.rows[0].placement_kind} placement partition — a session-sync staged commit cannot overwrite a delegation transcript`);
628
675
  }
629
676
  const srcIds = staged.rows.map((r) => String(r.entry_id));
630
- const dstIds = dstMeta.rows.length > 0 ? dst.rows.map((r) => String(r.entry_id)) : null;
677
+ const dstIds = opts.expectedOwner !== undefined ? dst.rows.map((r) => String(r.entry_id)) : null;
631
678
  const rel = classifySyncRelationshipByIds(srcIds, dstIds);
632
- if ((rel.relation === "fork" || rel.relation === "stale") && opts?.resolution !== "overwrite-dst") {
679
+ if ((rel.relation === "fork" || rel.relation === "stale") && opts.resolution !== "overwrite-dst") {
633
680
  await conn.query("ROLLBACK");
634
681
  throw new SyncConflictError(this.realSessionId, rel);
635
682
  }
636
- if (rel.relation === "fast_forward" && opts?.resolution !== "overwrite-dst" && dstIds !== null && dstIds.length > 0) {
637
- const dstFull = await conn.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [this.realSessionId]);
638
- const stagedFull = await conn.query(`SELECT entry_id, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [this.stagingId]);
683
+ if (rel.relation === "fast_forward" && opts.resolution !== "overwrite-dst" && dstIds !== null && dstIds.length > 0) {
684
+ const dstFull = await conn.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE`, [this.realSessionId]);
685
+ const stagedFull = await conn.query(`SELECT entry_id, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE`, [this.stagingId]);
639
686
  const dstSet = new Set(dstIds);
640
687
  const dstEntries = dstFull.rows.map((r) => decodeSessionEventPayload(r.payload, this.realSessionId, r.seq));
641
688
  const stagedShared = stagedFull.rows.filter((r) => dstSet.has(String(r.entry_id))).map((r) => decodeSessionEventPayload(r.payload, this.stagingId, r.entry_id));
@@ -647,13 +694,11 @@ class PgStagingHandle {
647
694
  }
648
695
  await conn.query("DELETE FROM session_event WHERE session_id = $1", [this.realSessionId]);
649
696
  await conn.query("UPDATE session_event SET session_id = $1 WHERE session_id = $2", [this.realSessionId, this.stagingId]);
650
- const lastRes = await conn.query(`SELECT ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq DESC LIMIT 1`, [this.realSessionId]);
697
+ const lastRes = await conn.query(`SELECT ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq DESC LIMIT 1 FOR UPDATE`, [this.realSessionId]);
651
698
  let leafId = null;
652
699
  if (lastRes.rows[0])
653
700
  leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRes.rows[0].payload, this.realSessionId, "last staged row"));
654
- const now = new Date();
655
- await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7) " +
656
- "ON CONFLICT (session_id) DO UPDATE SET owner = EXCLUDED.owner, updated_at = EXCLUDED.updated_at, leaf_id = EXCLUDED.leaf_id, leaf_seq = EXCLUDED.leaf_seq", [this.realSessionId, owner, now, now, leafId, srcIds.length, mintSessionIncarnation()]);
701
+ await conn.query("UPDATE session_meta SET owner = $1, updated_at = $2, leaf_id = $3, leaf_seq = $4 WHERE session_id = $5", [owner, now, leafId, srcIds.length, this.realSessionId]);
657
702
  const afterMeta = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = $1", [this.realSessionId]);
658
703
  if (afterMeta.rows[0]?.placement_kind != null) {
659
704
  await conn.query("ROLLBACK");
@@ -1,20 +1,12 @@
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
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
4
- export interface AdbEnvConfig {
5
+ export interface AdbEnvConfig extends LaneHomeConfig {
5
6
  /** Device serial or `host:port` (TCP). `host:port` triggers `adb connect` on connect()/reconnect(). */
6
7
  serial: string;
7
8
  /** Path to the adb binary. Default "adb" (must be on PATH in the runner image). */
8
9
  adbPath?: string;
9
- /**
10
- * S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
11
- * 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
12
- * 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
13
- * 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
14
- * 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
15
- * adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
16
- */
17
- homeDir?: string;
18
10
  /** On-device workspace root; relative paths resolve against it. Default "/data/local/tmp". */
19
11
  mountPath?: string;
20
12
  commandTimeoutMs?: number;
@@ -86,8 +78,10 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
86
78
  private destroyed;
87
79
  /** 在飞的那一次懒连接(单飞记忆)——见 {@link ensureConnected}。settle 即清,**不缓存败诺**。 */
88
80
  private connectPromise?;
89
- /** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
90
- readonly homeDir?: string | undefined;
81
+ /** core `ExecutionEnv.homeDir`([ref]):设备 shell 里执行用户的 home —— 声明胜,否则首连时 `adb shell` 实测的 `$HOME`(Android 常为 `/` 或 `/data/local/tmp` 一类,读到什么填什么,绝对即可),见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
82
+ get homeDir(): string | undefined;
83
+ /** 家目录的座(声明胜 · 否则首连时实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道**不对证**声明:Android 没有 POSIX 登录家目录,声明是运维的断言,照单全收。 */
84
+ private readonly homeSeat;
91
85
  /** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 Android 设备的 shell 里(设备自己的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
92
86
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
93
87
  readonly cdPath: "unknown";
@@ -3,6 +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, LaneHomeSeat } from "../lane-home-probe.js";
6
7
  import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
7
8
  import { signalNumber } from "./host-platform.js";
8
9
  import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
@@ -31,7 +32,10 @@ export class RemoteAdbExecutionEnv {
31
32
  connected = false;
32
33
  destroyed = false;
33
34
  connectPromise;
34
- homeDir;
35
+ get homeDir() {
36
+ return this.homeSeat.home;
37
+ }
38
+ homeSeat;
35
39
  cdPath = "unknown";
36
40
  cfg;
37
41
  spawnFn;
@@ -46,7 +50,7 @@ export class RemoteAdbExecutionEnv {
46
50
  retry: { maxAttempts: config.retry?.maxAttempts ?? 3, backoffMs: config.retry?.backoffMs ?? 800 },
47
51
  };
48
52
  this.cwd = this.cfg.mountPath;
49
- this.homeDir = config.homeDir;
53
+ this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: false, onAbsent: config.onHomeAbsent });
50
54
  this.spawnFn = deps?.spawn ?? spawn;
51
55
  this.injectedConnectOnce = deps?.connectOnce;
52
56
  }
@@ -117,6 +121,12 @@ export class RemoteAdbExecutionEnv {
117
121
  error: new RemoteExecutionError("unsupported", `device ${this.cfg.serial} does not advertise the 'shell_v2' feature, so this adapter cannot establish that its command round-trip carries a real exit code with stderr kept separate — the two properties every filesystem judgment on this lane depends on to tell failure from success. Refusing to connect rather than run on an unverifiable transport: attach a device that supports adb shell protocol v2.`),
118
122
  };
119
123
  }
124
+ if (this.homeSeat.asks) {
125
+ const home = await this.adb(this.deviceShellArgs(LANE_HOME_PROBE_COMMAND), { signal, timeoutMs: this.cfg.commandTimeoutMs });
126
+ if (!home.ok && home.error.code === "aborted")
127
+ return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted while reading the device's home") };
128
+ this.homeSeat.settle(home.ok && home.value.exitCode === 0 ? { ok: true, stdout: home.value.stdout } : { ok: false, reason: home.ok ? `exit ${home.value.exitCode}` : home.error.message });
129
+ }
120
130
  this.connected = true;
121
131
  return ok(this.workspaceHandle());
122
132
  }
@@ -17,10 +17,9 @@ type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
17
17
  * 🔴 三条不许折错的线:
18
18
  * ① `outcome_unknown` **原样过境**——core 把它排除在 `RETRYABLE_REMOTE_ERROR_CODES` 之外正是为了
19
19
  * 「可能已经执行过」这一格;折成 `timeout`/`transport_lost` 会让上层按可重试处理,重驱已提交副作用。
20
- * ② `target_unavailable`(never-started 的诚实词)在 `FileErrorCode` 上**没有对位词** —— 落
21
- * `transport_lost`:它的契约是「连接故事已知、重连后可重试」,而 never-started 的重试确实是安全的。
22
- * 诚实残余:file 面因此**分不出**「从未开始」与「传输中断」,两者都落在同一个可重试桶里(方向对、
23
- * 粒度粗)。要分得开需要 core 在 `FileErrorCode` 上补 never-started 词 —— 已作 O12 的后续请托登记。
20
+ * ② `target_unavailable`(never-started 的诚实词)**原样过境** —— core 7.54.0([ref] r3)给 `FileErrorCode` 补了同名词
21
+ * (目标在 commit 前拒、从未开始、零效果,目标回来后重试安全;写车道据它撤回首触记录、不计 edited)。修前文件面无对位词,
22
+ * 落 `transport_lost`(「传输中断」= 重试白名单读作「可能已执行」),file 面分不出「从未开始」与「传输中断」—— 那条 O12 后续请托随本版兑现。
24
23
  * ③ exec 专属词(`shell_unavailable`/`spawn_error`/`callback_error`/`suspended`/`auth_failed`)出现在
25
24
  * file 面上是**设备端协议违约**(它把一个 exec 结局贴到一次文件操作上)⇒ 落 `unknown`,不猜。
26
25
  */
@@ -38,9 +37,8 @@ type RejectCode = DeviceRejectCode;
38
37
  /**
39
38
  * 汇聚端的 **typed 拒**(投递门**之前**,§5.3 投递边界:设备从未可能见过这条指令)→ core 错误码。
40
39
  *
41
- * 🔴 全部落在**可安全重试**的那一侧,这是投递边界给的硬事实,不是乐观估计。exec 面用 O12 的
42
- * `target_unavailable`(「目标在 commit 前拒了,命令从未启动」的诚实词);file 面无对位词,同上落
43
- * `transport_lost`。
40
+ * 🔴 全部落在**可安全重试**的那一侧,这是投递边界给的硬事实,不是乐观估计。exec 面与 file 面用同一个
41
+ * `target_unavailable`(「目标在 commit 前拒了,指令从未启动」的诚实词;file 面自 core 7.54.0 起有它,修前落 `transport_lost`)。
44
42
  * 例外 `identity_mismatch` / `revoked` / `not_found`:那是**永久**拒(换设备/吊销/不是你的),重试
45
43
  * 只会同样失败 —— exec 面落 `auth_failed`(NEVER retry 的那一格),file 面无对位词落 `permission_denied`
46
44
  * (「这条路你走不通,而且不是暂时的」是它最近的语义)。
@@ -324,6 +322,15 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
324
322
  * ③ 「这个 placement 根本没绑设备」是**装配/准入错误**,不是一次运行期业务拒绝 —— 响亮抛,让 spawn
325
323
  * 失败(core 对 `isolation:"worktree"` 的同款姿势:宁可 throw,绝不静默给一个不对的 env)。
326
324
  * 对比:「绑了但现在不在线」是运行期事实 ⇒ 构造成功,`connect()` 回 typed `device.offline`。
325
+ *
326
+ * 🔴 **工厂的每一只拒都是 typed 拒**(core 7.54.0 [ref] r3 契约:工厂抛的 `.code` 原样成失败终局的码):经 {@link deviceFactoryRefusal} 一处铸,码取
327
+ * `DEVICE_ERROR_CODES` 闭集(装配错误取 `config.placement_root_invalid`),句子里的 `[码]` 与 `.code` 同值。修前抛裸 `Error`,码只在句子里。
328
+ *
329
+ * 🔴 **续跑腿的投递围栏**(core 7.54.0 [ref] r3 / #2061,`ExecutionEnvFactoryContext.resumeWorkspace`):行记下了设备(`resumeWorkspace.deviceId`)⇒
330
+ * 本工厂**只**在那台设备上铸 —— placement 根此刻绑的不是它(换绑过)⇒ 抛 typed `device.identity_mismatch`(换绑后赎回的同一个词,零新码),
331
+ * 句子点名两台设备;不连另一台、不改道(新设备没有那个工作区)。读当前绑定只为判出这一拒与取属主,铸的设备恒是行记下的那一台。
332
+ * 「绑的就是它但现在不在线」与新跑同形:构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。
333
+ * 行没记设备(非设备车道 / 早于该字段的行)⇒ 不声明要兑现的设备,按普通路由走。句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄 `resumeWorkspace`。
327
334
  */
328
335
  export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): ExecutionEnvFactory;
329
336
  /**
@@ -22,7 +22,7 @@ export const DEVICE_OUTCOME_TO_FILE_ERROR = {
22
22
  shell_unavailable: "unknown",
23
23
  spawn_error: "unknown",
24
24
  suspended: "unknown",
25
- target_unavailable: "transport_lost",
25
+ target_unavailable: "target_unavailable",
26
26
  timeout: "timeout",
27
27
  transport_lost: "transport_lost",
28
28
  unknown: "unknown",
@@ -66,20 +66,20 @@ export const DEVICE_REJECT_TO_EXEC_ERROR = {
66
66
  "draining": "target_unavailable",
67
67
  };
68
68
  export const DEVICE_REJECT_TO_FILE_ERROR = {
69
- "device.not_bound": "transport_lost",
70
- "device.offline": "transport_lost",
71
- "device.busy": "transport_lost",
72
- "device.attached_elsewhere": "transport_lost",
73
- "device.claim_conflict": "transport_lost",
69
+ "device.not_bound": "target_unavailable",
70
+ "device.offline": "target_unavailable",
71
+ "device.busy": "target_unavailable",
72
+ "device.attached_elsewhere": "target_unavailable",
73
+ "device.claim_conflict": "target_unavailable",
74
74
  "device.provider_mismatch": "permission_denied",
75
75
  "device.identity_mismatch": "permission_denied",
76
76
  "device.revoked": "permission_denied",
77
77
  "device.enrollment_invalid": "permission_denied",
78
78
  "not_found.device": "permission_denied",
79
- "device.rebind_rev_conflict": "transport_lost",
80
- "device.rebind_active_run": "transport_lost",
81
- "limit.rate_exceeded": "transport_lost",
82
- "draining": "transport_lost",
79
+ "device.rebind_rev_conflict": "target_unavailable",
80
+ "device.rebind_active_run": "target_unavailable",
81
+ "limit.rate_exceeded": "target_unavailable",
82
+ "draining": "target_unavailable",
83
83
  };
84
84
  const DEVICE_REJECT_TO_REMOTE_ERROR = {
85
85
  "device.not_bound": "connect_failed",
@@ -232,7 +232,7 @@ export class RemoteDeviceExecutionEnv {
232
232
  failure: {
233
233
  remote: "connect_failed",
234
234
  exec: "target_unavailable",
235
- file: "transport_lost",
235
+ file: "target_unavailable",
236
236
  message: `[device.admission_unavailable] the device admission read failed, so this session's device could not be confirmed (fail-closed): ${err instanceof Error ? err.message : String(err)}`,
237
237
  },
238
238
  };
@@ -780,25 +780,33 @@ const OUTCOME_TO_REMOTE_ERROR = {
780
780
  transport_lost: "transport_lost",
781
781
  unknown: "unknown",
782
782
  };
783
+ function deviceFactoryRefusal(code, message) {
784
+ return Object.assign(new Error(message), { code });
785
+ }
783
786
  export function deviceExecutionEnvFactory(deps) {
784
787
  const execTimeoutMs = deps.execTimeoutMs ?? 1_800_000;
785
788
  return async (ctx) => {
786
789
  const rootSessionId = ctx.placementRootSessionId;
787
790
  if (typeof rootSessionId !== "string" || rootSessionId.length === 0) {
788
- throw new Error("device lane: ExecutionEnvFactoryContext.placementRootSessionId is empty [config.placement_root_invalid] — " +
791
+ throw deviceFactoryRefusal("config.placement_root_invalid", "device lane: ExecutionEnvFactoryContext.placementRootSessionId is empty [config.placement_root_invalid] — " +
789
792
  "the device placement is keyed on the run tree's placement root; defaulting to the child sessionId would read as a NEW placement (v2 §4.3.2 / core design/380 O1①)");
790
793
  }
791
794
  const binding = await deps.store.getSessionBinding(rootSessionId);
792
795
  if (!binding) {
793
- throw new Error(`device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
796
+ throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_BOUND, `device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
794
797
  "the submit-time admission chain must bind the root session to a device before a run reaches the factory (v2 §4.3.2)");
795
798
  }
799
+ const recordedDevice = ctx.resumeWorkspace?.deviceId;
800
+ if (recordedDevice !== undefined && recordedDevice !== binding.deviceId) {
801
+ throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${recordedDevice}, but placement root ${rootSessionId} is now bound to ${binding.deviceId} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
802
+ "a parked call does not follow a rebind (the new device has no copy of that workspace); finish or cancel the parked task, then start it again on the device now bound");
803
+ }
796
804
  const view = await deps.store.readDeviceForAdmission(binding.deviceId, binding.owner);
797
805
  if (!view) {
798
- throw new Error(`device lane: the bound device is gone or not owned by the binding's principal [${DEVICE_ERROR_CODES.NOT_FOUND}]`);
806
+ throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_FOUND, `device lane: the bound device is gone or not owned by the binding's principal [${DEVICE_ERROR_CODES.NOT_FOUND}]`);
799
807
  }
800
808
  if (view.row.status === "revoked") {
801
- throw new Error(`device lane: the bound device has been revoked [${DEVICE_ERROR_CODES.REVOKED}] — revocation is terminal; re-enrolling mints a NEW deviceId (v2 §8-R4)`);
809
+ throw deviceFactoryRefusal(DEVICE_ERROR_CODES.REVOKED, `device lane: the bound device has been revoked [${DEVICE_ERROR_CODES.REVOKED}] — revocation is terminal; re-enrolling mints a NEW deviceId (v2 §8-R4)`);
802
810
  }
803
811
  assertPosixDevicePlatform(view.row.platformOs, view.row.deviceId);
804
812
  return new RemoteDeviceExecutionEnv({
@@ -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<{