@sema-agent/server 7.148.0 → 7.150.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 (360) hide show
  1. package/MIGRATION.md +551 -9
  2. package/README.md +17 -2
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +126 -68
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +33 -33
  7. package/dist/approval-gate-currency.d.ts +1 -1
  8. package/dist/approval-leg-origin.d.ts +33 -0
  9. package/dist/approval-leg-origin.js +4 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.js +2 -2
  12. package/dist/auth-bridge.d.ts +9 -1
  13. package/dist/auth-bridge.js +4 -4
  14. package/dist/auth-keys.d.ts +10 -1
  15. package/dist/auth-keys.js +8 -0
  16. package/dist/background-session-reap.d.ts +1 -1
  17. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  18. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  19. package/dist/boot/execution-env.js +10 -11
  20. package/dist/boot/execution-lane.d.ts +6 -4
  21. package/dist/boot/execution-lane.js +3 -1
  22. package/dist/boot/org-delivery.d.ts +3 -2
  23. package/dist/boot/parked-revive-gate.d.ts +5 -5
  24. package/dist/boot/parked-revive-gate.js +2 -3
  25. package/dist/boot/resolve-spec.d.ts +4 -5
  26. package/dist/boot/resolve-spec.js +66 -28
  27. package/dist/boot/retention-lane.d.ts +1 -1
  28. package/dist/boot/runner-deps.js +3 -1
  29. package/dist/boot/session-faces.js +21 -21
  30. package/dist/boot/stage-01-config.d.ts +1 -0
  31. package/dist/boot/stage-01-config.js +4 -3
  32. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  33. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  34. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  35. package/dist/boot/stage-03-store-backend.js +4 -4
  36. package/dist/boot/stage-04-stores.d.ts +1 -0
  37. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  38. package/dist/boot/stage-06-runners.d.ts +2 -1
  39. package/dist/boot/stage-06-runners.js +1 -2
  40. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  41. package/dist/boot/stage-07-capability-layer.js +1 -1
  42. package/dist/boot/stage-08-reapers.d.ts +2 -1
  43. package/dist/boot/stage-09-leader.d.ts +2 -1
  44. package/dist/boot/stage-10-http-server.d.ts +35 -2
  45. package/dist/boot/stage-10-http-server.js +9 -4
  46. package/dist/boot/stores.d.ts +3 -0
  47. package/dist/boot/stores.js +22 -19
  48. package/dist/boot/web-admin.d.ts +1 -1
  49. package/dist/capabilities/tool-defer.d.ts +1 -1
  50. package/dist/capability-facts.d.ts +70 -60
  51. package/dist/capability-facts.js +45 -49
  52. package/dist/card-settle-stored-scope.d.ts +56 -0
  53. package/dist/card-settle-stored-scope.js +27 -0
  54. package/dist/card-void.d.ts +7 -12
  55. package/dist/card-void.js +2 -7
  56. package/dist/config-catalog.d.ts +12 -0
  57. package/dist/config-catalog.js +37 -33
  58. package/dist/config-invariants.d.ts +5 -4
  59. package/dist/config-invariants.js +4 -4
  60. package/dist/config-types.d.ts +49 -16
  61. package/dist/config.d.ts +6 -5
  62. package/dist/config.js +35 -24
  63. package/dist/context-window-notice.d.ts +31 -0
  64. package/dist/context-window-notice.js +28 -0
  65. package/dist/deployment-governance.d.ts +7 -28
  66. package/dist/deployment-governance.js +1 -14
  67. package/dist/deployment-shape.d.ts +20 -11
  68. package/dist/deployment-shape.js +1 -0
  69. package/dist/device-ws-hub.d.ts +1 -0
  70. package/dist/device-ws-hub.js +64 -28
  71. package/dist/device-ws-protocol.d.ts +20 -6
  72. package/dist/device-ws-protocol.js +9 -2
  73. package/dist/engine-identity.d.ts +7 -1
  74. package/dist/engine-identity.js +5 -0
  75. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  76. package/dist/env-name-allowlist-knobs.js +5 -0
  77. package/dist/execution-lane-caps.d.ts +20 -5
  78. package/dist/execution-lane-caps.js +18 -4
  79. package/dist/field-conflict.d.ts +2 -2
  80. package/dist/fleet/engine-signal.d.ts +14 -1
  81. package/dist/fleet/engine-signal.js +5 -1
  82. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  83. package/dist/fleet/serving-lease.d.ts +18 -3
  84. package/dist/fleet/serving-lease.js +81 -35
  85. package/dist/fleet-client.d.ts +13 -11
  86. package/dist/fleet-client.js +53 -33
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +1 -3
  89. package/dist/hitl-delivery.js +1 -1
  90. package/dist/hooks/command-spawn.d.ts +7 -2
  91. package/dist/hooks/command-spawn.js +13 -6
  92. package/dist/hooks/hook-runner.d.ts +5 -0
  93. package/dist/hooks/hook-runner.js +11 -7
  94. package/dist/http/active-run-conflict.d.ts +3 -7
  95. package/dist/http/active-run-conflict.js +2 -2
  96. package/dist/http/admission.js +5 -5
  97. package/dist/http/approval-sweeps.js +5 -3
  98. package/dist/http/caller-trust.d.ts +2 -2
  99. package/dist/http/caller-trust.js +3 -3
  100. package/dist/http/checkpoint-ctx.d.ts +18 -63
  101. package/dist/http/checkpoint-ctx.js +1 -24
  102. package/dist/http/claim-heartbeat.d.ts +6 -6
  103. package/dist/http/claim-heartbeat.js +6 -6
  104. package/dist/http/claimed-run.d.ts +16 -9
  105. package/dist/http/claimed-run.js +8 -0
  106. package/dist/http/dispatch.js +7 -5
  107. package/dist/http/durable-run-leg.d.ts +8 -1
  108. package/dist/http/durable-run-leg.js +34 -15
  109. package/dist/http/execution-context.d.ts +115 -14
  110. package/dist/http/execution-context.js +130 -40
  111. package/dist/http/owner-access.d.ts +246 -21
  112. package/dist/http/owner-access.js +186 -8
  113. package/dist/http/principal-gate.d.ts +18 -11
  114. package/dist/http/principal-gate.js +3 -1
  115. package/dist/http/readiness.d.ts +4 -2
  116. package/dist/http/resume-ctx-read.d.ts +14 -3
  117. package/dist/http/resume-ctx-read.js +24 -3
  118. package/dist/http/resume-legs.d.ts +35 -3
  119. package/dist/http/resume-legs.js +262 -106
  120. package/dist/http/route-ctx.d.ts +29 -11
  121. package/dist/http/routes/a2a-serve.js +17 -14
  122. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  123. package/dist/http/routes/approvals-assistant.js +11 -11
  124. package/dist/http/routes/capabilities.js +5 -4
  125. package/dist/http/routes/config-catalog.d.ts +1 -1
  126. package/dist/http/routes/diagnostics.d.ts +3 -1
  127. package/dist/http/routes/diagnostics.js +30 -5
  128. package/dist/http/routes/mcp-reconnect.js +2 -2
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/notify-wake.js +2 -3
  131. package/dist/http/routes/principal-inbox.d.ts +3 -1
  132. package/dist/http/routes/principal-inbox.js +3 -2
  133. package/dist/http/routes/projects.d.ts +1 -0
  134. package/dist/http/routes/projects.js +5 -3
  135. package/dist/http/routes/retention-ops.d.ts +1 -1
  136. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  137. package/dist/http/routes/run-permission-mode.js +2 -2
  138. package/dist/http/routes/runs.d.ts +1 -1
  139. package/dist/http/routes/runs.js +33 -37
  140. package/dist/http/routes/session-memory-sync.js +4 -5
  141. package/dist/http/routes/session-sync.d.ts +24 -5
  142. package/dist/http/routes/session-sync.js +45 -37
  143. package/dist/http/routes/sessions-list.d.ts +1 -1
  144. package/dist/http/routes/sessions.d.ts +1 -1
  145. package/dist/http/routes/sessions.js +18 -44
  146. package/dist/http/routes/tasks.js +136 -101
  147. package/dist/http/routes/trace-usage.js +14 -14
  148. package/dist/http/routes/workflows.d.ts +1 -0
  149. package/dist/http/routes/workflows.js +20 -38
  150. package/dist/http/server.d.ts +12 -9
  151. package/dist/http/server.js +35 -6
  152. package/dist/http/stored-body-replay.d.ts +3 -24
  153. package/dist/http/stored-body-replay.js +2 -12
  154. package/dist/http/submit-ctx.d.ts +2 -2
  155. package/dist/http/submit-ctx.js +5 -5
  156. package/dist/http/submit-replay.d.ts +15 -2
  157. package/dist/http/submit-replay.js +10 -4
  158. package/dist/http/wire-types.d.ts +6 -3
  159. package/dist/index.d.ts +3 -1
  160. package/dist/index.js +1 -0
  161. package/dist/leader/endpoint.js +9 -8
  162. package/dist/leg-facts.d.ts +13 -2
  163. package/dist/leg-facts.js +2 -0
  164. package/dist/local-store-declaration.d.ts +54 -0
  165. package/dist/local-store-declaration.js +59 -0
  166. package/dist/mcp-assertion-header.d.ts +1 -1
  167. package/dist/mcp-assertion-header.js +1 -1
  168. package/dist/mcp-authorization-face.d.ts +18 -14
  169. package/dist/mcp-authorization-face.js +3 -2
  170. package/dist/memory-project-layer.js +1 -1
  171. package/dist/memory-scope.d.ts +4 -4
  172. package/dist/memory-scope.js +4 -4
  173. package/dist/memory-store-refusal.d.ts +1 -1
  174. package/dist/memory-sync-client.js +2 -2
  175. package/dist/model-context-window.d.ts +50 -11
  176. package/dist/model-context-window.js +20 -1
  177. package/dist/model-entry-refusal.d.ts +5 -4
  178. package/dist/model-entry-refusal.js +17 -3
  179. package/dist/model-reach-scope.d.ts +2 -1
  180. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  181. package/dist/observability/fail-open.d.ts +19 -7
  182. package/dist/observability/fail-open.js +19 -7
  183. package/dist/observability/logger.d.ts +7 -0
  184. package/dist/observability/logger.js +3 -1
  185. package/dist/observability/metrics.js +1 -1
  186. package/dist/observability/run-terminal.d.ts +1 -1
  187. package/dist/observability/run-terminal.js +1 -0
  188. package/dist/observability/secret-env-scrub.d.ts +1 -1
  189. package/dist/observability/secret-env-scrub.js +1 -1
  190. package/dist/org-delivery-gate.d.ts +31 -6
  191. package/dist/org-delivery-gate.js +7 -4
  192. package/dist/owner-faces.d.ts +30 -5
  193. package/dist/owner-faces.js +2 -0
  194. package/dist/park-row-commit.d.ts +67 -0
  195. package/dist/park-row-commit.js +76 -0
  196. package/dist/parked-decide.d.ts +9 -7
  197. package/dist/parked-decide.js +25 -6
  198. package/dist/peer-directory.d.ts +1 -1
  199. package/dist/peer-seat.d.ts +1 -1
  200. package/dist/peer-seat.js +52 -59
  201. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  202. package/dist/plugins/adoption-log-sql.js +19 -19
  203. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  204. package/dist/plugins/approval-ask-store-sql.js +3 -5
  205. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  206. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  207. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  208. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  209. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  210. package/dist/plugins/approval-exemption-store.js +28 -19
  211. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  212. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  213. package/dist/plugins/exec-path-law.d.ts +34 -0
  214. package/dist/plugins/exec-path-law.js +13 -0
  215. package/dist/plugins/file-run-store.d.ts +22 -9
  216. package/dist/plugins/file-run-store.js +51 -23
  217. package/dist/plugins/local-checkpoint-store.d.ts +8 -1
  218. package/dist/plugins/local-checkpoint-store.js +5 -1
  219. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  220. package/dist/plugins/local-data-root-admission.js +100 -0
  221. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  222. package/dist/plugins/local-engine-high-water.js +27 -22
  223. package/dist/plugins/local-session-store.d.ts +134 -49
  224. package/dist/plugins/local-session-store.js +149 -108
  225. package/dist/plugins/memory-run-store.d.ts +14 -9
  226. package/dist/plugins/memory-run-store.js +48 -22
  227. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  228. package/dist/plugins/one-time-migration-marker.js +7 -1
  229. package/dist/plugins/one-time-migrations.d.ts +110 -59
  230. package/dist/plugins/one-time-migrations.js +224 -136
  231. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  232. package/dist/plugins/org-delivery-quota-store.js +29 -14
  233. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  234. package/dist/plugins/permission-rule-store-sql.js +5 -9
  235. package/dist/plugins/pg-pool.d.ts +1 -1
  236. package/dist/plugins/pg-pool.js +14 -12
  237. package/dist/plugins/posix-shell-fs.js +4 -2
  238. package/dist/plugins/remote-env-adb.js +3 -2
  239. package/dist/plugins/remote-env-device.d.ts +3 -2
  240. package/dist/plugins/remote-env-device.js +8 -6
  241. package/dist/plugins/remote-env-e2b.d.ts +4 -8
  242. package/dist/plugins/remote-env-e2b.js +21 -15
  243. package/dist/plugins/remote-env-host.d.ts +51 -10
  244. package/dist/plugins/remote-env-host.js +97 -31
  245. package/dist/plugins/remote-env-k8s.js +7 -4
  246. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  247. package/dist/plugins/remote-env-local-docker.js +11 -25
  248. package/dist/plugins/remote-env-ssh.js +4 -3
  249. package/dist/plugins/remote-shell.d.ts +3 -1
  250. package/dist/plugins/remote-shell.js +3 -3
  251. package/dist/plugins/replica-registration.d.ts +35 -0
  252. package/dist/plugins/replica-registration.js +48 -0
  253. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  254. package/dist/plugins/run-store-sql.d.ts +27 -8
  255. package/dist/plugins/run-store-sql.js +46 -18
  256. package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
  257. package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
  258. package/dist/plugins/schema-consistency.d.ts +91 -5
  259. package/dist/plugins/schema-consistency.js +189 -7
  260. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  261. package/dist/plugins/schema-drift-sentence.js +13 -5
  262. package/dist/plugins/serving-requirement.d.ts +49 -0
  263. package/dist/plugins/serving-requirement.js +53 -0
  264. package/dist/plugins/session-placement.d.ts +5 -4
  265. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  266. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  267. package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
  268. package/dist/plugins/stop-then-start-sentence.js +51 -0
  269. package/dist/plugins/store-backend.d.ts +23 -9
  270. package/dist/plugins/store-backend.js +58 -24
  271. package/dist/plugins/store-contracts.d.ts +45 -5
  272. package/dist/plugins/store-contracts.js +19 -0
  273. package/dist/plugins/tidb-pool.d.ts +11 -6
  274. package/dist/plugins/tidb-pool.js +22 -12
  275. package/dist/plugins/workspace-custody.d.ts +44 -0
  276. package/dist/plugins/workspace-custody.js +47 -0
  277. package/dist/process-instance.d.ts +3 -0
  278. package/dist/process-instance.js +7 -0
  279. package/dist/project-context.d.ts +66 -0
  280. package/dist/project-context.js +70 -0
  281. package/dist/project-identity.d.ts +8 -7
  282. package/dist/project-identity.js +40 -17
  283. package/dist/project-layer-key.d.ts +13 -2
  284. package/dist/project-layer-key.js +9 -1
  285. package/dist/question.d.ts +9 -21
  286. package/dist/question.js +28 -19
  287. package/dist/read-deny-floor-notice.d.ts +36 -0
  288. package/dist/read-deny-floor-notice.js +40 -0
  289. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  290. package/dist/request-declaration-disposition.js +39 -0
  291. package/dist/run-cancel-card.d.ts +28 -0
  292. package/dist/run-cancel-card.js +20 -0
  293. package/dist/run-cancel-context.d.ts +10 -0
  294. package/dist/run-cancel-context.js +1 -0
  295. package/dist/run-local.js +28 -13
  296. package/dist/runs.d.ts +31 -7
  297. package/dist/runs.js +34 -16
  298. package/dist/security.d.ts +3 -3
  299. package/dist/server-secret-env.d.ts +34 -20
  300. package/dist/server-secret-env.js +61 -18
  301. package/dist/session-agent.d.ts +4 -12
  302. package/dist/session-agent.js +0 -6
  303. package/dist/session-children-retire.d.ts +6 -3
  304. package/dist/session-children-retire.js +2 -5
  305. package/dist/session-incarnation.d.ts +18 -4
  306. package/dist/session-incarnation.js +4 -1
  307. package/dist/session-notify-door.d.ts +4 -4
  308. package/dist/session-notify-door.js +5 -6
  309. package/dist/session-rules-seat.d.ts +4 -3
  310. package/dist/session-rules-seat.js +3 -11
  311. package/dist/session-sync.js +3 -3
  312. package/dist/session-workspace.d.ts +3 -3
  313. package/dist/store-admission.d.ts +33 -0
  314. package/dist/store-admission.js +49 -0
  315. package/dist/store-posture.d.ts +5 -0
  316. package/dist/store-posture.js +18 -1
  317. package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
  318. package/dist/stores-sql/checkpoint-store-sql.js +109 -23
  319. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  320. package/dist/stores-sql/file-history-store-sql.js +3 -5
  321. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  322. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  323. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  324. package/dist/task-a2a.d.ts +3 -8
  325. package/dist/task-a2a.js +25 -27
  326. package/dist/task-cwd.d.ts +27 -10
  327. package/dist/task-cwd.js +3 -0
  328. package/dist/task-mcp.d.ts +3 -3
  329. package/dist/task-mcp.js +1 -1
  330. package/dist/task-settings.d.ts +13 -0
  331. package/dist/task-settings.js +14 -7
  332. package/dist/terminal.d.ts +2 -0
  333. package/dist/terminal.js +21 -6
  334. package/dist/tool-approval.d.ts +50 -41
  335. package/dist/tool-approval.js +96 -73
  336. package/dist/trace/engine-notice-wire.d.ts +38 -21
  337. package/dist/trace/engine-notice-wire.js +25 -4
  338. package/dist/trace/ledger-sink.d.ts +3 -1
  339. package/dist/trace/project.d.ts +8 -5
  340. package/dist/trace/project.js +11 -9
  341. package/dist/trace/projection-table.d.ts +33 -2
  342. package/dist/trace/redact.d.ts +12 -0
  343. package/dist/trace/redact.js +5 -1
  344. package/dist/trace/self-minted-wire.d.ts +268 -56
  345. package/dist/trace/self-minted-wire.js +110 -40
  346. package/dist/trace/tool-card.d.ts +3 -0
  347. package/dist/trace/tool-card.js +24 -0
  348. package/dist/trace/tool-output-media.d.ts +2 -1
  349. package/dist/unattended-permission-mode.d.ts +1 -1
  350. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  351. package/dist/web-admin-client/apply-effective.js +22 -9
  352. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  353. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  354. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  355. package/docs/error-text-lexicon.json +317 -0
  356. package/package.json +4 -2
  357. package/scripts/embed-minimal.mjs +183 -0
  358. package/wire-changes/server@7.149.0.json +372 -0
  359. package/wire-changes/server@7.150.0.json +335 -0
  360. package/dist/plain-string-record.js +0 -70
@@ -1,5 +1,6 @@
1
- import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type ExecResult, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions, type SecretEnvFinding } from "@sema-agent/core";
1
+ import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type ExecResult, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type ExecutionEnvFactoryContext, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions, type SecretEnvFinding } from "@sema-agent/core";
2
2
  import { type LaneHomeConfig } from "../lane-home-probe.js";
3
+ import { type CustodyMember } from "./workspace-custody.js";
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
  /** SIGTERM → grace → SIGKILL (no host isolation). 🔴 宽限的**唯一属主**:本 driver 的 `kill` 梯子用它排升级定时器;会话级收敛
@@ -10,7 +11,7 @@ export declare const HOST_BG_KILL_GRACE_MS: number;
10
11
  export interface HostEnvConfig extends LaneHomeConfig {
11
12
  /**
12
13
  * Base directory under which this adapter creates its per-task working directory. Default {@link os.tmpdir}.
13
- * The actual workspace is `<workspaceBase>/sema-host-<id>` and is `rm -rf`'d on {@link destroy}.
14
+ * The actual workspace is `<workspaceBase>/sema-host-<id>-<rand>`; its last holder `rm -rf`s it on {@link destroy} (see {@link custody}).
14
15
  */
15
16
  workspaceBase?: string;
16
17
  /**
@@ -44,14 +45,21 @@ export interface HostEnvConfig extends LaneHomeConfig {
44
45
  * user's OWN directory (e.g. the cwd `run-local` was invoked in), not an adapter-created ephemeral sandbox,
45
46
  * so the agent's file edits PERSIST and are user-visible (like a local coding agent working in your repo).
46
47
  * Takes precedence over `workspaceBase`/`id` (those are ignored). Default unset = ephemeral random + cleanup.
48
+ * 唯一的例外由托管表说:它落在一只本服务铸的临时根里({@link custody} 的 `sharedRoot`)⇒ 本环境共持那只根,不是调用方自有目录。
47
49
  */
48
50
  workspaceDir?: string;
51
+ /**
52
+ * 工厂的工作区托管表交给它铸的每一只环境的那一面({@link hostExecutionEnvFactory} 装配;嵌入方直接构造的环境不带 ⇒ 不在任何表里,没有别的持有者)。
53
+ */
54
+ custody?: HostWorkspaceCustodySeat;
49
55
  /** R7 self-wake: the injected `SchedulerCapability` backend (the TOC self-wake scheduler write side).
50
56
  * When set, `hasScheduler(env)` is true and core mounts CronCreate/CronDelete/CronList(旧名 CronCancel;Sleep 已 [ref] 撤除;挂载在 prepare-task 非 createHandsToolkit——L8 订正); when absent the env
51
57
  * reports `supported:false` → INERT (no scheduler tools). Injected at boot only on the single-user host lane. */
52
58
  scheduler?: SchedulerCapability;
53
- /** [ref] background shell: `false` → INERT (multi-tenant host lane gate). Default (undefined) = on for the
54
- * single-user TOC host lane. `HOST_BG_ENABLED=false` is an additional global kill switch(🪦 旧名
59
+ /** [ref] background shell: the assembly hands the deployment-shape row's answer here
60
+ * (`DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds(config)`); `false` → INERT (multi-tenant host lane gate).
61
+ * Default (undefined) = the SAME row's answer for an undeclared principal (the single-user TOC host lane), never a literal.
62
+ * `HOST_BG_ENABLED=false` is an additional global kill switch(🪦 旧名
55
63
  * `HOST_BG_DISABLED` 自 server 3.0.0 起是 fail-loud 墓碑:设了即拒启并指路新名,不再被读取)。 */
56
64
  backgroundShell?: boolean;
57
65
  /**
@@ -61,7 +69,14 @@ export interface HostEnvConfig extends LaneHomeConfig {
61
69
  */
62
70
  gitBase?: true;
63
71
  }
64
- export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability {
72
+ /** 工作区托管表交给它铸的环境的那一面(`workspace-custody.ts`;host 车道的读法)。 */
73
+ export interface HostWorkspaceCustodySeat {
74
+ /** `workspaceDir` 落在这只本服务铸的临时根里(委派子代按父的工作目录落根)⇒ 本环境与它的铸者共持这只根:建成后登记、收场时放手、最后放手的持有者删它。 */
75
+ readonly sharedRoot?: string;
76
+ /** 表里除 `self` 之外还有没有成员持有 `root` —— 收场放手时问;有 ⇒ 删交给最后放手的那一只。 */
77
+ heldElsewhere(self: RemoteHostExecutionEnv, root: string): boolean;
78
+ }
79
+ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability, CustodyMember<never> {
65
80
  /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
66
81
  readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
67
82
  /** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
@@ -97,7 +112,7 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
97
112
  /**
98
113
  * core 7.56.0 [ref]:shell 子进程继承的环境里**收不到**的条目(名 + 规则,从不带值)—— 与 {@link cdPath} 同一只组合的读面
99
114
  * (`server-secret-env.ts` 的 `withheldFromHostSubprocess`:继承组合扣下的发现,减去 `cfg.env` 这条显式授予重供的键)。逐命令的 `options.env`
100
- * 是引擎自己交的显式授予,它知道(core KL-1963-b 同一残余)。只读:不披露、不计数、不碰 shell。
115
+ * 是引擎自己交的显式授予,它知道(core [ref] 同一残余)。只读:不披露、不计数、不碰 shell。
101
116
  */
102
117
  get withheldEnv(): readonly SecretEnvFinding[];
103
118
  private readonly workspaceDir;
@@ -106,6 +121,9 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
106
121
  private prepared;
107
122
  /** Terminal once {@link destroy} ran — guards fs/exec from running against a removed workspace. */
108
123
  private destroyed;
124
+ /** {@link destroy} 的那一次收场(第二次调用等同一只);托管表读它({@link releasing}):收场在飞时同一身份不铸新的。 */
125
+ private teardown?;
126
+ private teardownSettled;
109
127
  /** [ref] 五轮复审:fg/bg spool 根=tmpdir 私有目录(core bgSpoolDir 同形)。workspace 是命令可写区,
110
128
  * `git clean -fdx` 类清理会 unlink 活跃 spool(子进程经继承 fd 对 unlinked inode 继续写,而父进程
111
129
  * 按路径 statSync/read 静默断=输出丢失+轮转帽失效);顺带不再弄脏 user-owned workspace(TOC/CC
@@ -172,12 +190,22 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
172
190
  */
173
191
  private drainInflightFs;
174
192
  private ensureSpoolRoot;
175
- /** True when `workspaceDir` was given (user-owned persistent dir) → destroy() must NOT delete it. */
176
- private readonly persistent;
193
+ /** 本环境对它工作区的租期({@link HostWorkspaceTenure}):借用的永不删;持有的收场放手、最后放手的持有者删。唯一写处 = 构造与 {@link removeOwnedTrees}。 */
194
+ private tenure;
177
195
  /** Memoized lazy `mkdir -p` so concurrent first-use prepares the workspace exactly once. */
178
196
  private preparing?;
179
197
  private readonly handle;
180
198
  constructor(config?: HostEnvConfig);
199
+ /** 收场的进度(托管表读):活着 ⇒ 缺席;{@link destroy} 开始 ⇒ 那一次收场。 */
200
+ get releasing(): Promise<void> | undefined;
201
+ /** 收场已落定。 */
202
+ get released(): boolean;
203
+ /** 本车道收场不欠同一身份下一只环境任何账(删不掉的根留在退出兜底簿上,不交接)。 */
204
+ readonly leftover: undefined;
205
+ /** 收场落定且已放手 ⇒ 出托管表(超时臂里放手挂在在飞写之后:落定了还持有 ⇒ 仍是持有者)。 */
206
+ get settled(): boolean;
207
+ /** 本环境此刻持有的、本服务铸的临时根(托管表的持有读法读它);借用 / 已放手 ⇒ 缺席。 */
208
+ get heldWorkspace(): string | undefined;
181
209
  workspaceHandle(): WorkspaceHandle;
182
210
  connect(config?: RemoteConnectConfig): Promise<{
183
211
  ok: true;
@@ -223,6 +251,7 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
223
251
  * core 留活过的驻留 watcher 又杀了一遍(run 尾 dispose→destroy 顺序,destroy 是第二刀)。隔离 env(容器/VM)
224
252
  * 整体拆机自然带走进程,与本契约不冲突(k8s/e2b 各自 destroy 不走这条路径)。 */
225
253
  destroy(): Promise<void>;
254
+ private tearDown;
226
255
  /** `destroy()` 真正动手删的那一段(本 env **自有**的两棵树:spool 根 + ephemeral workspace)。
227
256
  * 抽成一只是因为它有两个调用时机 —— 在飞写按时清空时由 `destroy()` 直接 await,超时时挂在那批写后面异步跑
228
257
  * (见 {@link drainInflightFs} 头注③)。永不抛(teardown 契约)。 */
@@ -373,7 +402,7 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
373
402
  * 查不到根因;更糟的是每一个任务都复发一次同一条。⇒ 前判把它换成一句能直接照做的拒绝:哪个目录、
374
403
  * 哪个用户、改哪个旋钮。
375
404
  *
376
- * 只对 **ephemeral** 形做(persistent 形是用户自己的目录,`mkdir -p` 一个已存在的只读 checkout 本来
405
+ * 只对**自铸**的那一只做(没给 `workspaceDir`;给了的是用户自己的目录或父那只已判过的根,`mkdir -p` 一个已存在的只读 checkout 本来
377
406
  * 就该成功 —— 在那儿加可写判据会把"只读仓库上跑只读任务"这个正当用法拒掉)。
378
407
  * root 与非 POSIX 平台跳过:root 的可写判据恒真(root-squash 的网络盘上反而会假拒),
379
408
  * 而 `process.getuid` 不存在时这条 POSIX 权限判据无从谈起。
@@ -453,6 +482,18 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
453
482
  * Mirrors `e2bExecutionEnvFactory`: returns the env **unconnected** (lazy) and folds the per-task
454
483
  * `ctx.sessionId` into the workspace dir id so two concurrent tasks never share a working directory.
455
484
  */
456
- export declare function hostExecutionEnvFactory(config?: HostEnvConfig): ExecutionEnvFactory;
485
+ /** 一条腿的环境配置里随腿变的那几格({@link hostExecutionEnvFactory} 第二参:工厂上下文 → 值)。 */
486
+ export interface HostLegConfig extends Pick<HostEnvConfig, "workspaceDir" | "env" | "inheritEnv"> {
487
+ /** 铸成那一拍对这只环境做的一次装饰(交回已有环境时不再做)。 */
488
+ readonly minted?: (env: RemoteHostExecutionEnv) => void;
489
+ }
490
+ /**
491
+ * host 车道的工厂。**一只身份一只工作区**:经工作区托管表(`workspace-custody.ts`,与 local-docker 车道同一只)铸环境 ——
492
+ * 同一身份(`ctx.taskId ?? ctx.sessionId`)还有一只没收场的环境 ⇒ 交回它(停驻腿的环境 = 续跑腿的环境:引擎 park-only 契约要的「重连到同一工作区」);
493
+ * 正在收场 ⇒ 等它落定再铸。铸的时候问同一张表「腿的目录落在哪只本服务铸的临时根里」:落在里面(委派子代按父的工作目录落根)⇒ 与铸者共持那只根,
494
+ * 父收场时子代还持有它就不删,最后放手的持有者删。`legOf` 先于交回 / 铸判(它可以拒)。
495
+ * 起服铸**一只**工厂(每次调用另铸一只工厂 = 没有表)。
496
+ */
497
+ export declare function hostExecutionEnvFactory(config?: HostEnvConfig, legOf?: (ctx: ExecutionEnvFactoryContext) => HostLegConfig): ExecutionEnvFactory;
457
498
  export { localDockerExecutionEnvFactory, type LocalDockerEnvConfig, type LocalDockerEnvDeps, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
458
499
  //# sourceMappingURL=remote-env-host.d.ts.map
@@ -2,6 +2,7 @@ import { writeLogText } from "../observability/logger.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
5
6
  import fs from "node:fs/promises";
6
7
  import { randomBytes } from "node:crypto";
7
8
  import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdirSync, rmdirSync, mkdtempSync, existsSync, realpathSync, accessSync, constants as FS_CONST } from "node:fs";
@@ -12,12 +13,14 @@ import { buildCapturedOutput, createCutError } from "../exec-cut.js";
12
13
  import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
13
14
  import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
14
15
  import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
15
- import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
16
+ import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, physicalPathOf as kernelRealPathOf, } from "@sema-agent/core";
16
17
  import { capabilitiesFor } from "../execution-lane-caps.js";
18
+ import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
17
19
  import { LaneHomeSeat, laneHomeRefusal } from "../lane-home-probe.js";
18
20
  import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
19
21
  import { nameTooLongCode } from "./remote-env-file-error.js";
20
22
  import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
23
+ import { createWorkspaceCustody } from "./workspace-custody.js";
21
24
  import { OWNER_ONLY_FILE_MODE } from "./owner-only-file-mode.js";
22
25
  const PROVIDER = "host";
23
26
  const DEFAULT_COMMAND_TIMEOUT_MS = 30 * 60_000;
@@ -34,6 +37,8 @@ const unsupported = (op) => ({
34
37
  ok: false,
35
38
  error: new RemoteExecutionError("unsupported", `${op} is not supported on a host target (the operator's own machine — no container boundary, not snapshotable; capabilities.suspendable=false)`),
36
39
  });
40
+ const BORROWED = Object.freeze({ kind: "borrowed" });
41
+ const RELEASED = Object.freeze({ kind: "released" });
37
42
  const UNSUPPORTED_SCHEDULER_CAPS = {
38
43
  supported: false,
39
44
  maxScheduledPerScope: 0,
@@ -80,6 +85,8 @@ export class RemoteHostExecutionEnv {
80
85
  cfg;
81
86
  prepared = false;
82
87
  destroyed = false;
88
+ teardown;
89
+ teardownSettled = false;
83
90
  spoolRootDir;
84
91
  liveFgSpools = new Set();
85
92
  inflightFs = new Set();
@@ -122,20 +129,21 @@ export class RemoteHostExecutionEnv {
122
129
  this.spoolRootDir = mkdtempSync(path.join(os.tmpdir(), "sema-host-spool-"));
123
130
  return this.spoolRootDir;
124
131
  }
125
- persistent;
132
+ tenure;
126
133
  preparing;
127
134
  handle;
128
135
  constructor(config = {}) {
129
136
  if (config.workspaceDir) {
130
137
  this.workspaceDir = path.resolve(config.workspaceDir);
131
- this.persistent = true;
138
+ const shared = config.custody?.sharedRoot;
139
+ this.tenure = shared !== undefined ? { kind: "held", root: path.resolve(shared) } : BORROWED;
132
140
  }
133
141
  else {
134
142
  const base = config.workspaceBase ?? os.tmpdir();
135
143
  const id = config.id ?? randomBytes(6).toString("hex");
136
144
  const suffix = randomBytes(4).toString("hex");
137
145
  this.workspaceDir = path.resolve(base, `sema-host-${sanitizeId(id)}-${suffix}`);
138
- this.persistent = false;
146
+ this.tenure = { kind: "held", root: this.workspaceDir };
139
147
  }
140
148
  const inheritEnvProblem = inheritEnvPolicyProblem(config.inheritEnv);
141
149
  if (inheritEnvProblem !== undefined)
@@ -156,6 +164,19 @@ export class RemoteHostExecutionEnv {
156
164
  if (homeVerdict !== undefined)
157
165
  throw laneHomeRefusal(homeVerdict);
158
166
  }
167
+ get releasing() {
168
+ return this.teardown;
169
+ }
170
+ get released() {
171
+ return this.teardownSettled;
172
+ }
173
+ leftover = undefined;
174
+ get settled() {
175
+ return this.teardownSettled && this.tenure.kind !== "held";
176
+ }
177
+ get heldWorkspace() {
178
+ return this.tenure.kind === "held" ? this.tenure.root : undefined;
179
+ }
159
180
  workspaceHandle() {
160
181
  return this.handle;
161
182
  }
@@ -190,9 +211,13 @@ export class RemoteHostExecutionEnv {
190
211
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
191
212
  return ok(this.handle);
192
213
  }
193
- async destroy() {
194
- if (this.destroyed)
195
- return;
214
+ destroy() {
215
+ this.teardown ??= this.tearDown().finally(() => {
216
+ this.teardownSettled = true;
217
+ });
218
+ return this.teardown;
219
+ }
220
+ async tearDown() {
196
221
  this.destroyed = true;
197
222
  const p = this.preparing;
198
223
  this.preparing = undefined;
@@ -215,11 +240,15 @@ export class RemoteHostExecutionEnv {
215
240
  catch { }
216
241
  this.spoolRootDir = undefined;
217
242
  }
218
- if (this.persistent)
243
+ const tenure = this.tenure;
244
+ if (tenure.kind !== "held")
245
+ return;
246
+ this.tenure = RELEASED;
247
+ if (this.cfg.custody?.heldElsewhere(this, tenure.root) === true)
219
248
  return;
220
249
  try {
221
- await fs.rm(this.workspaceDir, { recursive: true, force: true });
222
- untrackEphemeralHostWorkspace(this.workspaceDir);
250
+ await fs.rm(tenure.root, { recursive: true, force: true });
251
+ untrackEphemeralHostWorkspace(tenure.root);
223
252
  }
224
253
  catch {
225
254
  }
@@ -228,12 +257,13 @@ export class RemoteHostExecutionEnv {
228
257
  return this.destroy();
229
258
  }
230
259
  get backgroundCapabilities() {
260
+ const backgroundShellOn = (this.cfg.backgroundShell ?? DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds({ remoteExec: { provider: PROVIDER } })) && hostBackgroundShellEnabled();
231
261
  return {
232
- supported: (this.cfg.backgroundShell ?? true) && hostBackgroundShellEnabled(),
262
+ supported: backgroundShellOn,
233
263
  maxConcurrent: HOST_BG_MAX_CONCURRENT,
234
264
  defaultBgTimeoutSec: HOST_BG_DEFAULT_TIMEOUT_SEC,
235
265
  maxBgTimeoutSec: HOST_BG_MAX_TIMEOUT_SEC,
236
- supportsDetach: (this.cfg.backgroundShell ?? true) && hostBackgroundShellEnabled(),
266
+ supportsDetach: backgroundShellOn,
237
267
  };
238
268
  }
239
269
  _bgManager;
@@ -975,7 +1005,7 @@ export class RemoteHostExecutionEnv {
975
1005
  return ok(this.resolve(p));
976
1006
  }
977
1007
  async joinPath(parts) {
978
- return ok(path.join(...parts));
1008
+ return ok(joinedPath(path, parts));
979
1009
  }
980
1010
  async readTextFile(p, abortSignal) {
981
1011
  const ready = await this.fsReady(p);
@@ -1077,7 +1107,7 @@ export class RemoteHostExecutionEnv {
1077
1107
  const entries = await fs.readdir(abs, { withFileTypes: true });
1078
1108
  const out = [];
1079
1109
  for (const e of entries) {
1080
- const child = path.join(abs, e.name);
1110
+ const child = anchorUnder(path, abs, e.name);
1081
1111
  let size = 0;
1082
1112
  let mtimeMs = 0;
1083
1113
  try {
@@ -1218,7 +1248,7 @@ export class RemoteHostExecutionEnv {
1218
1248
  });
1219
1249
  }
1220
1250
  resolve(p) {
1221
- return path.isAbsolute(p) ? path.normalize(p) : path.normalize(path.resolve(this.cwd, p));
1251
+ return anchoredPath(path, this.cwd, p);
1222
1252
  }
1223
1253
  assertWorkspaceParentWritable() {
1224
1254
  const uid = process.getuid?.();
@@ -1251,7 +1281,7 @@ export class RemoteHostExecutionEnv {
1251
1281
  ensurePrepared() {
1252
1282
  if (this.prepared)
1253
1283
  return Promise.resolve();
1254
- if (!this.persistent) {
1284
+ if (this.cfg.workspaceDir === undefined) {
1255
1285
  try {
1256
1286
  this.assertWorkspaceParentWritable();
1257
1287
  }
@@ -1264,8 +1294,8 @@ export class RemoteHostExecutionEnv {
1264
1294
  .mkdir(this.workspaceDir, { recursive: true })
1265
1295
  .then(() => {
1266
1296
  this.prepared = true;
1267
- if (!this.persistent)
1268
- trackEphemeralHostWorkspace(this.workspaceDir);
1297
+ if (this.tenure.kind === "held")
1298
+ trackEphemeralHostWorkspace(this.tenure.root);
1269
1299
  })
1270
1300
  .catch((e) => {
1271
1301
  this.preparing = undefined;
@@ -1295,17 +1325,25 @@ export class RemoteHostExecutionEnv {
1295
1325
  const abs = this.resolve(p);
1296
1326
  if (!existsSync(abs))
1297
1327
  return false;
1298
- const real = (x) => { try {
1299
- return realpathSync(x);
1300
- }
1301
- catch {
1302
- return path.resolve(x);
1303
- } };
1328
+ const real = (x) => {
1329
+ try {
1330
+ return kernelRealPathOf(x);
1331
+ }
1332
+ catch {
1333
+ return undefined;
1334
+ }
1335
+ };
1336
+ const target = real(abs);
1337
+ if (target === undefined)
1338
+ return false;
1304
1339
  const inside = (root) => {
1305
- const rel = path.relative(real(root), real(abs));
1340
+ const r = real(root);
1341
+ if (r === undefined)
1342
+ return false;
1343
+ const rel = path.relative(r, target);
1306
1344
  return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
1307
1345
  };
1308
- return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.persistent && inside(this.workspaceDir));
1346
+ return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.tenure.kind === "borrowed" && inside(this.workspaceDir));
1309
1347
  }
1310
1348
  async fsReady(p, opts) {
1311
1349
  if (this.destroyed) {
@@ -1389,11 +1427,39 @@ function asError(e) {
1389
1427
  function signalNumber(signal) {
1390
1428
  return os.constants.signals[signal];
1391
1429
  }
1392
- export function hostExecutionEnvFactory(config = {}) {
1393
- return (ctx) => new RemoteHostExecutionEnv({
1394
- ...config,
1395
- id: config.id ?? ctx.taskId ?? ctx.sessionId,
1396
- });
1430
+ export function hostExecutionEnvFactory(config = {}, legOf = () => ({})) {
1431
+ const custody = createWorkspaceCustody();
1432
+ const heldElsewhere = (self, root) => custody.holder((m) => m !== self && m.heldWorkspace === root) !== undefined;
1433
+ return (ctx) => {
1434
+ const { minted, ...leg } = legOf(ctx);
1435
+ const identity = ctx.taskId ?? ctx.sessionId;
1436
+ return custody.envOf(identity, () => {
1437
+ const dir = leg.workspaceDir ?? config.workspaceDir;
1438
+ const sharedRoot = dir === undefined ? undefined : custody.holder((m) => m.heldWorkspace !== undefined && dirWithin(dir, m.heldWorkspace))?.heldWorkspace;
1439
+ const env = new RemoteHostExecutionEnv({ ...config, ...leg, id: config.id ?? identity, custody: { heldElsewhere, ...(sharedRoot !== undefined ? { sharedRoot } : {}) } });
1440
+ minted?.(env);
1441
+ return env;
1442
+ });
1443
+ };
1444
+ }
1445
+ function dirWithin(dir, root) {
1446
+ const inside = (d, r) => {
1447
+ const rel = path.relative(r, d);
1448
+ return rel === "" || (rel.split(path.sep)[0] !== ".." && !path.isAbsolute(rel));
1449
+ };
1450
+ return inside(path.resolve(dir), path.resolve(root)) || inside(physicalPathOf(dir), physicalPathOf(root));
1451
+ }
1452
+ function physicalPathOf(p) {
1453
+ let head = path.resolve(p);
1454
+ const tail = [];
1455
+ while (!existsSync(head)) {
1456
+ const up = path.dirname(head);
1457
+ if (up === head)
1458
+ return path.resolve(p);
1459
+ tail.unshift(path.basename(head));
1460
+ head = up;
1461
+ }
1462
+ return path.join(realpathSync(head), ...tail);
1397
1463
  }
1398
1464
  export { localDockerExecutionEnvFactory, RemoteLocalDockerExecutionEnv } from "./remote-env-local-docker.js";
1399
1465
  //# sourceMappingURL=remote-env-host.js.map
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { anchoredPath, joinedPath } from "./exec-path-law.js";
2
3
  import { readFileSync } from "node:fs";
3
4
  import https from "node:https";
4
5
  import http from "node:http";
@@ -19,6 +20,7 @@ import { buildCapturedOutput, createCutError } from "../exec-cut.js";
19
20
  import { SANDBOX_IPV6_DEFAULT, sandboxEgressGaps, sandboxPodSysctls } from "../sandbox-egress-policy.js";
20
21
  import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
21
22
  import { capabilitiesFor } from "../execution-lane-caps.js";
23
+ import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
22
24
  import { ADAPTER_START, BOOTSTRAP_START, buildFloorPodSeat, createSandboxReadFloor, FLOOR_K8S_CONFIGMAP_KEY, engineStart, floorArgv, } from "./sandbox-read-floor.js";
23
25
  import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
24
26
  import { createPosixShellFs } from "./posix-shell-fs.js";
@@ -283,12 +285,13 @@ export class RemoteK8sExecutionEnv {
283
285
  this.floor = this.deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor, ...(config.logger !== undefined ? { logger: config.logger } : {}) });
284
286
  this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER, s3Snapshot: this.cfg.s3Snapshot }, sandboxReadFloor: config.sandboxReadFloor });
285
287
  const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
288
+ const backgroundShellOn = DEPLOYMENT_SHAPE_PREDICATES.backgroundShell.holds({ remoteExec: { provider: PROVIDER } });
286
289
  this.backgroundCapabilities = {
287
- supported: true,
290
+ supported: backgroundShellOn,
288
291
  maxConcurrent: K8S_BG_MAX_CONCURRENT,
289
292
  defaultBgTimeoutSec: Math.min(K8S_BG_DEFAULT_TIMEOUT_SEC, bgMax),
290
293
  maxBgTimeoutSec: bgMax,
291
- supportsDetach: true,
294
+ supportsDetach: backgroundShellOn,
292
295
  };
293
296
  }
294
297
  get sandboxLeaseRef() {
@@ -298,7 +301,7 @@ export class RemoteK8sExecutionEnv {
298
301
  return this.cfg.timeoutMs;
299
302
  }
300
303
  resolve(p) {
301
- return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
304
+ return anchoredPath(path.posix, this.cwd, p);
302
305
  }
303
306
  workspaceHandle() {
304
307
  return { sandboxId: this.podName ?? "(unconnected)", provider: PROVIDER, mountPath: this.cwd, ...(this.handleSnapshotId ? { snapshotId: this.handleSnapshotId } : {}) };
@@ -2194,7 +2197,7 @@ export class RemoteK8sExecutionEnv {
2194
2197
  return ok(this.resolve(p));
2195
2198
  }
2196
2199
  async joinPath(parts) {
2197
- return ok(path.posix.join(...parts));
2200
+ return ok(joinedPath(path.posix, parts));
2198
2201
  }
2199
2202
  async readBinaryFile(p, abortSignal) {
2200
2203
  const abs = this.resolve(p);
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { type LaneHomeConfig } from "../lane-home-probe.js";
3
3
  import { type LaneLogger } from "./remote-shell.js";
4
+ import { type CustodyMember } from "./workspace-custody.js";
4
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 ExecutionEnvCapabilities } from "@sema-agent/core";
5
6
  import { type SandboxReadFloorKnob } from "../execution-lane-caps.js";
6
7
  import { type SandboxReadFloor } from "./sandbox-read-floor.js";
@@ -105,7 +106,7 @@ export interface LocalDockerEnvDeps {
105
106
  /** 这只环境手里的读地板模块(缺席 ⇒ `createSandboxReadFloor` 按旋钮选;钉注入守地板的模块)。能力块的 `keepsReadFloor` 读它的状态。 */
106
107
  readFloor?: SandboxReadFloor;
107
108
  }
108
- export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
109
+ export declare class RemoteLocalDockerExecutionEnv implements CustodyMember<LocalDockerLeftover>, RemoteExecutionEnv {
109
110
  /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。读地板旋钮是它的输入,{@link floor} 由同一只判据、同一个旋钮值选出。 */
110
111
  readonly capabilities: ExecutionEnvCapabilities;
111
112
  /** 读拒表字节地板模块:容器里每一次 `docker exec` 起动都先经它的 `wrap`({@link startArgv},唯一接缝)。 */
@@ -155,8 +156,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
155
156
  get leftover(): LocalDockerLeftover | undefined;
156
157
  /** 收场的进度:活着 ⇒ 缺席;{@link destroy} 开始 ⇒ 那一次收场(容器删除证实或放弃之后落定)。工厂读它:活着就交回、收场中就等它落定。 */
157
158
  get releasing(): Promise<void> | undefined;
158
- /** 收场已落定(工厂从同身份表里清掉它)。 */
159
+ /** 收场已落定。 */
159
160
  get released(): boolean;
161
+ /** 收场落定、不欠删除 ⇒ 出托管表(欠着的由同一身份的下一只拿走)。容器名由身份派生,一只身份同一时刻只有一只没收场的环境 ⇒ 它收场即删,不存在第二个持有者。 */
162
+ get settled(): boolean;
160
163
  private resolve;
161
164
  /** 收据句柄。还没连上(懒环境:续跑腿的引擎在恢复相**之前**先取它比车道)⇒ 未连接形 `{ sandboxId: "(unconnected)", provider, mountPath }`(与 k8s / e2b 车道同形),
162
165
  * 不抛 —— 抛了,续跑腿在恢复相就失败。 */
@@ -393,7 +396,7 @@ export declare function localDockerIdentityOfName(workerLabel: string, name: str
393
396
  * owns the lifetime and calls `destroy()` (= `docker rm -f`) on task end.
394
397
  *
395
398
  * 身份 = `ctx.taskId ?? ctx.sessionId`(与引擎交给工具的宿主任务号同一读法:顶层 run = run 号,委派子代 = 它自己的会话号 —— 子代从不带 `taskId`)。
396
- * 一只身份一只工作区:本工厂对同一身份交回**那一只**还没销毁的环境(停驻腿的环境引擎还拿着;续跑腿拿到的就是它 —— 引擎 park-only 契约要的「重连到同一工作区」,
399
+ * 一只身份一只工作区(工作区托管表 `workspace-custody.ts`,与 host 车道同一只):本工厂对同一身份交回**那一只**还没销毁的环境(停驻腿的环境引擎还拿着;续跑腿拿到的就是它 —— 引擎 park-only 契约要的「重连到同一工作区」,
397
400
  * 在同一进程里就是同一只环境,读地板的中继也只有一只);上一只正在收场 ⇒ 等它的删除落定再铸;没有 ⇒ 铸一只(它连接时在跑的同名容器就附着 —— 上一只进程起的)。
398
401
  * 收场落定的从表里清掉。
399
402
  * 无主容器:停驻中取消 / 停驻过期(本服务在店层结清,不经引擎的仲裁器,停驻腿的环境没有销毁者)与重起之后才结清的停驻,容器一直在跑 ——
@@ -1,9 +1,11 @@
1
1
  import path from "node:path";
2
+ import { anchoredPath, joinedPath } from "./exec-path-law.js";
2
3
  import { spawn } from "node:child_process";
3
4
  import { hostSubprocessEnv } from "../server-secret-env.js";
4
5
  import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
5
6
  import { createHash, randomBytes } from "node:crypto";
6
7
  import { shellQuote, armPipeDestroyGrace, appendFileStaged, readyUpload, writeFileStaged } from "./remote-shell.js";
8
+ import { createWorkspaceCustody } from "./workspace-custody.js";
7
9
  import { signalNumber } from "./host-platform.js";
8
10
  import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
9
11
  import { buildCapturedOutput, createCutError } from "../exec-cut.js";
@@ -84,8 +86,11 @@ export class RemoteLocalDockerExecutionEnv {
84
86
  get released() {
85
87
  return this.teardownSettled;
86
88
  }
89
+ get settled() {
90
+ return this.teardownSettled && this.leftover === undefined;
91
+ }
87
92
  resolve(p) {
88
- return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
93
+ return anchoredPath(path.posix, this.cwd, p);
89
94
  }
90
95
  workspaceHandle() {
91
96
  return this.handle ?? { sandboxId: "(unconnected)", provider: PROVIDER, mountPath: this.cfg.mountPath };
@@ -614,7 +619,7 @@ export class RemoteLocalDockerExecutionEnv {
614
619
  return ok(this.resolve(p));
615
620
  }
616
621
  async joinPath(parts) {
617
- return ok(path.posix.join(...parts));
622
+ return ok(joinedPath(path.posix, parts));
618
623
  }
619
624
  async readBinaryFile(p, abortSignal) {
620
625
  const ready = await this.fsReady(p, abortSignal);
@@ -974,30 +979,11 @@ function errMsg(e) {
974
979
  }
975
980
  export function localDockerExecutionEnvFactory(config, deps = {}) {
976
981
  const { workspaceFillOf, ...base } = config;
977
- const live = new Map();
978
- const envOf = async (identity, ctx) => {
979
- sweep();
980
- let leftover;
981
- for (let held = live.get(identity); held !== undefined; held = live.get(identity)) {
982
- const releasing = held.releasing;
983
- if (releasing === undefined)
984
- return held;
985
- await releasing;
986
- if (live.get(identity) === held) {
987
- leftover = held.leftover;
988
- live.delete(identity);
989
- }
990
- }
982
+ const custody = createWorkspaceCustody();
983
+ const envOf = (identity, ctx) => custody.envOf(identity, (leftover) => {
991
984
  const workspaceFill = ctx !== undefined ? workspaceFillOf?.(ctx) : undefined;
992
- const env = new RemoteLocalDockerExecutionEnv({ ...base, id: identity, ...(workspaceFill !== undefined ? { workspaceFill } : {}), ...(leftover !== undefined ? { leftover } : {}) }, deps);
993
- live.set(identity, env);
994
- return env;
995
- };
996
- const sweep = () => {
997
- for (const [identity, env] of live)
998
- if (env.released && env.leftover === undefined)
999
- live.delete(identity);
1000
- };
985
+ return new RemoteLocalDockerExecutionEnv({ ...base, id: identity, ...(workspaceFill !== undefined ? { workspaceFill } : {}), ...(leftover !== undefined ? { leftover } : {}) }, deps);
986
+ });
1001
987
  let lister;
1002
988
  const reapPort = {
1003
989
  label: base.workerLabel,
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
2
3
  import { Client } from "ssh2";
3
4
  import { createSshHostKeyVerifier } from "../ssh-host-key.js";
4
5
  import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, kindFromMode, writeFileStaged } from "./remote-shell.js";
@@ -57,7 +58,7 @@ export class RemoteSshExecutionEnv {
57
58
  this.homeSeat = new LaneHomeSeat(PROVIDER, config);
58
59
  }
59
60
  resolve(p) {
60
- return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
61
+ return anchoredPath(path.posix, this.cwd, p);
61
62
  }
62
63
  workspaceHandle() {
63
64
  return { sandboxId: `${this.cfg.host}:${this.cfg.port}`, provider: PROVIDER, mountPath: this.cwd };
@@ -595,7 +596,7 @@ export class RemoteSshExecutionEnv {
595
596
  return ok(this.resolve(p));
596
597
  }
597
598
  async joinPath(parts) {
598
- return ok(path.posix.join(...parts));
599
+ return ok(joinedPath(path.posix, parts));
599
600
  }
600
601
  async readTextFile(p, abortSignal) {
601
602
  const r = await this.readBinaryFile(p, abortSignal);
@@ -674,7 +675,7 @@ export class RemoteSshExecutionEnv {
674
675
  const entries = await new Promise((resolve, reject) => sftp.readdir(abs, (err, list) => (err ? reject(err) : resolve(list))));
675
676
  return ok(entries.map((e) => {
676
677
  const a = e.attrs;
677
- return { name: e.filename, path: path.posix.join(abs, e.filename), kind: kindFromMode(a.mode), size: a.size ?? 0, mtimeMs: (a.mtime ?? 0) * 1000 };
678
+ return { name: e.filename, path: anchorUnder(path.posix, abs, e.filename), kind: kindFromMode(a.mode), size: a.size ?? 0, mtimeMs: (a.mtime ?? 0) * 1000 };
678
679
  }));
679
680
  }
680
681
  catch (e) {
@@ -60,7 +60,9 @@ export type CwdGuardFace = "settled" | "raw";
60
60
  * 🔴 [ref](7.103.1)—— 三条远端 shell 腿(ssh / k8s / adb)交给远端 `sh -c` 的那一串**唯一**的组装:进工作目录 →(settled 面)打 started 行 →
61
61
  * 导出任务 env → 跑命令。各腿只决定 `dir`(自己的缺省 cwd 规则 + 路径绝对化),串的形状只在这里。
62
62
  *
63
- * 形:`cd '<dir>' || { echo '<marker>: <dir> (cd failed; command not run)' >&2; exit 125; }; echo '<started>' >&2; export K='v'; … <command>`
63
+ * 形:`cd -P '<dir>' || { echo '<marker>: <dir> (cd failed; command not run)' >&2; exit 125; }; echo '<started>' >&2; export K='v'; … <command>`
64
+ * · `-P`(物理):工作目录按内核解 —— 执行缝路径律下 `<dir>` 是未折的拼法(`<base>/bridge/../next`),文件面由内核解 `..`;缺省的逻辑 `cd` 会先词法折成 `<base>/next`,
65
+ * 命令与文件动作就各在一只目录。`-P` 是 POSIX `cd` 的选项(dash / bash / busybox ash / mksh 都认)。
64
66
  * (raw 面没有 `echo '<started>' >&2;` 那一段)
65
67
  * · cd 失败(目录不存在 / 无权限 / 不是目录)⇒ 命令**不跑**,整条退 {@link CWD_UNAVAILABLE_EXIT};stderr 先是 shell 自己的 cd 报错,再是守卫行。
66
68
  * 修前形 `cd '<dir>' && export K='v'; <command>`:`&&` 只绑到第一只 `export`,`;` 之后的命令照样在**登录目录**里跑,还少了那只 env ——
@@ -30,7 +30,7 @@ export function buildCwdEnvCommand(command, dir, env, face) {
30
30
  const nonce = randomBytes(8).toString("hex");
31
31
  cwdGuard = { dir, marker: `${CWD_UNAVAILABLE_LINE} [${nonce}]`, started: startedLineFor(face, nonce) };
32
32
  const line = `${cwdGuard.marker}: ${dir} (cd failed; command not run)`;
33
- parts.push(`cd ${shellQuote(dir)} || { echo ${shellQuote(line)} >&2; exit ${CWD_UNAVAILABLE_EXIT}; };`);
33
+ parts.push(`cd -P ${shellQuote(dir)} || { echo ${shellQuote(line)} >&2; exit ${CWD_UNAVAILABLE_EXIT}; };`);
34
34
  if (cwdGuard.started !== undefined)
35
35
  parts.push(`echo ${shellQuote(cwdGuard.started)} >&2;`);
36
36
  }
@@ -184,11 +184,11 @@ function hardenPrivate(dir) {
184
184
  return `( chmod -- 700 ${dir} 2>/dev/null && ${readModeOwner(dir)} && case "$1" in drw[xs]--[-S]--[-T]*) [ "$3" = "$(id -u)" ];; *) false;; esac && ${aclCleared(dir)} )`;
185
185
  }
186
186
  function aclCleared(dir) {
187
- const marker = `ls_out=$(cd ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
187
+ const marker = `ls_out=$(cd -P ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
188
188
  `case "$mode_word" in d?????????*+*) echo +;; d?????????*) ;; *) false;; esac`;
189
189
  return (`plat=$(uname -s 2>/dev/null) && case "$plat" in ` +
190
190
  `Linux) acl_read() { :; }; acl_strip() { false; };; ` +
191
- `Darwin) acl_read() { ls_out=$(cd ${dir} && ls -led . 2>&1) && case "$ls_out" in d?????????*) ;; *) false;; esac && printf '%s\\n' "$ls_out" | sed 1d; }; acl_strip() { chmod -N ${dir}; };; ` +
191
+ `Darwin) acl_read() { ls_out=$(cd -P ${dir} && ls -led . 2>&1) && case "$ls_out" in d?????????*) ;; *) false;; esac && printf '%s\\n' "$ls_out" | sed 1d; }; acl_strip() { chmod -N ${dir}; };; ` +
192
192
  `FreeBSD) acl_read() { ${marker}; }; acl_strip() { setfacl -b ${dir}; };; ` +
193
193
  `SunOS) acl_read() { ${marker}; }; acl_strip() { chmod A- ${dir}; };; ` +
194
194
  `*) false;; esac && acl=$(acl_read 2>/dev/null) && { [ -z "$acl" ] || { acl_strip 2>/dev/null && acl=$(acl_read 2>/dev/null) && [ -z "$acl" ]; }; }`);