@sema-agent/server 7.139.0 → 7.141.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 (328) hide show
  1. package/MIGRATION.md +471 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +94 -26
  5. package/dist/adoption/runner.js +1 -1
  6. package/dist/adoption/sql.js +1 -1
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +37 -22
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval.d.ts +15 -0
  12. package/dist/approval.js +10 -0
  13. package/dist/audit.d.ts +4 -2
  14. package/dist/audit.js +2 -2
  15. package/dist/bench/s1/live-deps.js +3 -2
  16. package/dist/bench/s1/row.d.ts +5 -2
  17. package/dist/bench/s1/row.js +2 -22
  18. package/dist/boot/adoption.js +1 -1
  19. package/dist/boot/device-lane.d.ts +11 -0
  20. package/dist/boot/device-lane.js +16 -0
  21. package/dist/boot/engine-lease.d.ts +24 -3
  22. package/dist/boot/engine-lease.js +9 -2
  23. package/dist/boot/execution-env.js +18 -12
  24. package/dist/boot/execution-lane.d.ts +8 -0
  25. package/dist/boot/execution-lane.js +6 -1
  26. package/dist/boot/memory-boundary.d.ts +16 -53
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +16 -12
  29. package/dist/boot/memory-consolidation.js +24 -13
  30. package/dist/boot/reapers.d.ts +2 -2
  31. package/dist/boot/resolve-spec.d.ts +3 -5
  32. package/dist/boot/resolve-spec.js +9 -8
  33. package/dist/boot/runner-deps.d.ts +3 -3
  34. package/dist/boot/runner-deps.js +1 -0
  35. package/dist/boot/session-faces.d.ts +1 -1
  36. package/dist/boot/session-faces.js +4 -3
  37. package/dist/boot/shutdown.js +5 -0
  38. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  39. package/dist/boot/stage-03-store-backend.js +2 -3
  40. package/dist/boot/stage-04-stores.d.ts +2 -6
  41. package/dist/boot/stage-04-stores.js +5 -5
  42. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  43. package/dist/boot/stage-05-execution-env.js +3 -1
  44. package/dist/boot/stage-06-runners.d.ts +2 -8
  45. package/dist/boot/stage-06-runners.js +3 -3
  46. package/dist/boot/stage-07-capability-layer.d.ts +2 -7
  47. package/dist/boot/stage-07-capability-layer.js +8 -10
  48. package/dist/boot/stage-08-reapers.d.ts +2 -7
  49. package/dist/boot/stage-09-leader.d.ts +2 -7
  50. package/dist/boot/stage-10-http-server.d.ts +2 -7
  51. package/dist/boot/stage-10-http-server.js +6 -4
  52. package/dist/boot/stores.d.ts +3 -6
  53. package/dist/boot/stores.js +37 -44
  54. package/dist/boot/task-list-lane.d.ts +1 -1
  55. package/dist/boot/task-list-lane.js +1 -1
  56. package/dist/boot/web-admin.js +36 -9
  57. package/dist/capabilities/execution-lane.d.ts +2 -2
  58. package/dist/capabilities/scenarios.d.ts +1 -1
  59. package/dist/capability-facts.d.ts +9 -0
  60. package/dist/capability-facts.js +9 -0
  61. package/dist/card-void.d.ts +38 -0
  62. package/dist/card-void.js +23 -0
  63. package/dist/config-catalog.js +12 -8
  64. package/dist/config-provider.d.ts +18 -17
  65. package/dist/config-provider.js +28 -36
  66. package/dist/config-types.d.ts +36 -9
  67. package/dist/config.js +56 -46
  68. package/dist/delegated-session-owner.d.ts +23 -20
  69. package/dist/delegated-session-owner.js +2 -2
  70. package/dist/deployment-shape.d.ts +9 -8
  71. package/dist/deployment-shape.js +4 -4
  72. package/dist/device-store.d.ts +7 -0
  73. package/dist/device-store.js +3 -0
  74. package/dist/execution-config-root.d.ts +22 -0
  75. package/dist/execution-config-root.js +35 -0
  76. package/dist/execution-lane-caps.d.ts +145 -107
  77. package/dist/execution-lane-caps.js +144 -83
  78. package/dist/execution-placement.d.ts +3 -0
  79. package/dist/execution-placement.js +7 -0
  80. package/dist/fleet/drain-entry.d.ts +3 -0
  81. package/dist/hosted-posture.d.ts +8 -0
  82. package/dist/hosted-posture.js +4 -0
  83. package/dist/http/admission.js +3 -3
  84. package/dist/http/caller-trust.d.ts +3 -2
  85. package/dist/http/caller-trust.js +2 -1
  86. package/dist/http/dispatch.js +4 -0
  87. package/dist/http/live-frame.d.ts +24 -0
  88. package/dist/http/live-frame.js +16 -0
  89. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  90. package/dist/http/memory-compliance-failure-reply.js +5 -0
  91. package/dist/http/memory-layer-reply.d.ts +30 -0
  92. package/dist/http/memory-layer-reply.js +24 -0
  93. package/dist/http/resume-legs.js +49 -35
  94. package/dist/http/retired-service-deps.d.ts +1 -1
  95. package/dist/http/retired-service-deps.js +1 -1
  96. package/dist/http/route-table.js +2 -0
  97. package/dist/http/routes/adoption.js +1 -1
  98. package/dist/http/routes/approvals-assistant.js +1 -0
  99. package/dist/http/routes/capabilities.js +5 -4
  100. package/dist/http/routes/diagnostics.d.ts +0 -19
  101. package/dist/http/routes/diagnostics.js +16 -2
  102. package/dist/http/routes/memory-bundle.d.ts +2 -2
  103. package/dist/http/routes/memory-bundle.js +4 -1
  104. package/dist/http/routes/memory-compliance.js +9 -2
  105. package/dist/http/routes/memory-consolidation.js +12 -1
  106. package/dist/http/routes/memory-origin.js +14 -2
  107. package/dist/http/routes/memory-policy.js +7 -2
  108. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  109. package/dist/http/routes/run-permission-mode.js +170 -0
  110. package/dist/http/routes/session-sync.d.ts +3 -6
  111. package/dist/http/routes/session-sync.js +7 -2
  112. package/dist/http/routes/sessions.js +33 -15
  113. package/dist/http/routes/tasks.js +67 -68
  114. package/dist/http/run-read-face.js +3 -3
  115. package/dist/http/send.d.ts +1 -1
  116. package/dist/http/send.js +2 -2
  117. package/dist/http/server.d.ts +14 -27
  118. package/dist/http/server.js +2 -2
  119. package/dist/http/sse-log.d.ts +3 -8
  120. package/dist/http/stored-body-replay.d.ts +3 -3
  121. package/dist/http/stored-body-replay.js +3 -1
  122. package/dist/identity-source.d.ts +1 -1
  123. package/dist/identity-source.js +1 -1
  124. package/dist/instruction-source-notice.d.ts +7 -0
  125. package/dist/instruction-source-notice.js +6 -6
  126. package/dist/lane-home-probe.d.ts +68 -42
  127. package/dist/lane-home-probe.js +53 -30
  128. package/dist/leader/wire.js +5 -4
  129. package/dist/mcp-assertion-header.d.ts +4 -0
  130. package/dist/mcp-assertion-header.js +5 -0
  131. package/dist/memory-bundle-engine.d.ts +16 -9
  132. package/dist/memory-bundle-engine.js +29 -6
  133. package/dist/memory-operator-faces.d.ts +85 -30
  134. package/dist/memory-operator-faces.js +136 -30
  135. package/dist/memory-posture.d.ts +1 -1
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +33 -9
  141. package/dist/memory-scope.js +17 -5
  142. package/dist/memory-sync-client.d.ts +34 -9
  143. package/dist/memory-sync-client.js +108 -28
  144. package/dist/observability/fail-open.d.ts +30 -2
  145. package/dist/observability/fail-open.js +30 -2
  146. package/dist/observability/metrics.js +0 -1
  147. package/dist/observability/run-terminal.d.ts +6 -2
  148. package/dist/observability/run-terminal.js +25 -26
  149. package/dist/observability/secret-env-scrub.d.ts +20 -1
  150. package/dist/observability/secret-env-scrub.js +14 -0
  151. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  152. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  153. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  154. package/dist/orchestration/workflow-notify-journal.js +2 -1
  155. package/dist/permission-mode-admission.d.ts +68 -0
  156. package/dist/permission-mode-admission.js +23 -0
  157. package/dist/permission-rule-list.d.ts +55 -6
  158. package/dist/permission-rule-list.js +52 -5
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-sql.js +14 -14
  161. package/dist/plugins/approval-nonce-store.js +4 -4
  162. package/dist/plugins/blob-backend.d.ts +4 -4
  163. package/dist/plugins/blob-backend.js +7 -7
  164. package/dist/plugins/breaker-state-sql.js +2 -2
  165. package/dist/plugins/caching-session-store.d.ts +6 -0
  166. package/dist/plugins/caching-session-store.js +3 -0
  167. package/dist/plugins/checkpoint-index-columns.js +2 -2
  168. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  169. package/dist/plugins/checkpoint-store-sql.js +90 -19
  170. package/dist/plugins/device-store-sql.d.ts +5 -2
  171. package/dist/plugins/device-store-sql.js +23 -20
  172. package/dist/plugins/ensure-index.d.ts +1 -2
  173. package/dist/plugins/execution-placement-sql.d.ts +19 -9
  174. package/dist/plugins/execution-placement-sql.js +20 -9
  175. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  176. package/dist/plugins/file-history-store-sql.js +4 -4
  177. package/dist/plugins/file-run-store.d.ts +2 -2
  178. package/dist/plugins/file-run-store.js +1 -1
  179. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  180. package/dist/plugins/fork-routing-session-store.js +2 -61
  181. package/dist/plugins/image-bake-store-sql.js +9 -9
  182. package/dist/plugins/image-index-sql.js +11 -11
  183. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  184. package/dist/plugins/k8s-exec-protocol.js +6 -1
  185. package/dist/plugins/leader-run-store-sql.js +4 -4
  186. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  187. package/dist/plugins/local-checkpoint-store.js +22 -0
  188. package/dist/plugins/local-session-store.d.ts +227 -35
  189. package/dist/plugins/local-session-store.js +518 -37
  190. package/dist/plugins/local-session-window.d.ts +17 -3
  191. package/dist/plugins/local-session-window.js +7 -13
  192. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  193. package/dist/plugins/mailbox-store-sql.js +4 -4
  194. package/dist/plugins/memory-engine-tidb.js +5 -5
  195. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  196. package/dist/plugins/memory-run-store.d.ts +2 -2
  197. package/dist/plugins/memory-run-store.js +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +52 -11
  200. package/dist/plugins/one-time-migrations.js +101 -39
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.js +2 -2
  203. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  204. package/dist/plugins/permission-rule-store-sql.js +16 -16
  205. package/dist/plugins/pg-pool.js +3 -2
  206. package/dist/plugins/pg-session-storage.d.ts +2 -0
  207. package/dist/plugins/pg-session-storage.js +3 -2
  208. package/dist/plugins/remote-env-adb.d.ts +7 -24
  209. package/dist/plugins/remote-env-adb.js +22 -5
  210. package/dist/plugins/remote-env-device.d.ts +2 -28
  211. package/dist/plugins/remote-env-device.js +2 -1
  212. package/dist/plugins/remote-env-e2b.d.ts +40 -27
  213. package/dist/plugins/remote-env-e2b.js +180 -20
  214. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  215. package/dist/plugins/remote-env-file-error.js +8 -2
  216. package/dist/plugins/remote-env-host.d.ts +19 -35
  217. package/dist/plugins/remote-env-host.js +26 -3
  218. package/dist/plugins/remote-env-k8s.d.ts +22 -14
  219. package/dist/plugins/remote-env-k8s.js +150 -26
  220. package/dist/plugins/remote-env-local-docker.d.ts +27 -24
  221. package/dist/plugins/remote-env-local-docker.js +91 -24
  222. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  223. package/dist/plugins/remote-env-ssh.js +4 -3
  224. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  225. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  226. package/dist/plugins/retention-lane-store-sql.js +2 -2
  227. package/dist/plugins/retention-store-sql.d.ts +1 -1
  228. package/dist/plugins/retention-store-sql.js +6 -6
  229. package/dist/plugins/run-store-sql.d.ts +3 -3
  230. package/dist/plugins/run-store-sql.js +19 -18
  231. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  232. package/dist/plugins/sandbox-read-floor.js +608 -0
  233. package/dist/plugins/schema-consistency.d.ts +6 -6
  234. package/dist/plugins/schema-consistency.js +1 -1
  235. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  236. package/dist/plugins/schema-ensure-legs.js +2 -2
  237. package/dist/plugins/schema-live-columns.d.ts +1 -1
  238. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  239. package/dist/plugins/session-ownership.d.ts +2 -1
  240. package/dist/plugins/session-placement.d.ts +17 -4
  241. package/dist/plugins/session-placement.js +2 -1
  242. package/dist/plugins/session-rules-column.d.ts +1 -1
  243. package/dist/plugins/session-rules-column.js +1 -1
  244. package/dist/plugins/session-store.d.ts +18 -20
  245. package/dist/plugins/session-store.js +4 -12
  246. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  247. package/dist/plugins/session-workspace-cell.js +1 -1
  248. package/dist/plugins/shared-memory-store-sql.js +2 -2
  249. package/dist/plugins/sql-driver.d.ts +10 -5
  250. package/dist/plugins/sql-driver.js +6 -6
  251. package/dist/plugins/sql-errors.js +1 -1
  252. package/dist/plugins/store-backend.d.ts +35 -6
  253. package/dist/plugins/store-backend.js +27 -10
  254. package/dist/plugins/store-contracts.d.ts +9 -2
  255. package/dist/plugins/task-list-store-sql.js +11 -11
  256. package/dist/plugins/tidb-pool.js +7 -6
  257. package/dist/plugins/tidb-session-storage.js +4 -4
  258. package/dist/plugins/tidb-session-store.d.ts +4 -1
  259. package/dist/plugins/tidb-session-store.js +16 -15
  260. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  261. package/dist/plugins/tool-result-store-sql.js +3 -3
  262. package/dist/plugins/user-input-ledger-sql.js +2 -2
  263. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  264. package/dist/plugins/workflow-run-store-sql.js +19 -19
  265. package/dist/plugins/write-behind-counter.d.ts +2 -3
  266. package/dist/plugins/write-behind-counter.js +5 -5
  267. package/dist/resource-suspend.d.ts +14 -12
  268. package/dist/resource-suspend.js +1 -1
  269. package/dist/resource-window.d.ts +4 -3
  270. package/dist/run-cancel-context.d.ts +34 -0
  271. package/dist/run-cancel-context.js +7 -0
  272. package/dist/run-local.js +14 -19
  273. package/dist/runs.d.ts +47 -16
  274. package/dist/runs.js +112 -113
  275. package/dist/security.d.ts +2 -2
  276. package/dist/server-secret-env.d.ts +2 -1
  277. package/dist/server-secret-env.js +2 -4
  278. package/dist/session-error-wire.d.ts +22 -4
  279. package/dist/session-error-wire.js +16 -0
  280. package/dist/session-incarnation.d.ts +10 -4
  281. package/dist/session-incarnation.js +1 -1
  282. package/dist/session-policy-wire.d.ts +1 -1
  283. package/dist/session-sync-kernel.d.ts +42 -1
  284. package/dist/session-sync-kernel.js +17 -0
  285. package/dist/session-sync.js +16 -4
  286. package/dist/sql-engine-coordinates.d.ts +49 -0
  287. package/dist/sql-engine-coordinates.js +27 -0
  288. package/dist/sql-engine-posture.d.ts +1 -1
  289. package/dist/sql-engine-posture.js +1 -1
  290. package/dist/standing-answer-grant.d.ts +31 -2
  291. package/dist/standing-answer-grant.js +14 -2
  292. package/dist/store-posture.d.ts +1 -1
  293. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  294. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  295. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  296. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  297. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  298. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  299. package/dist/task-a2a.d.ts +1 -1
  300. package/dist/task-a2a.js +5 -6
  301. package/dist/task-mcp.d.ts +21 -8
  302. package/dist/task-mcp.js +17 -9
  303. package/dist/task-settings.d.ts +52 -25
  304. package/dist/task-settings.js +91 -28
  305. package/dist/terminal.d.ts +103 -19
  306. package/dist/terminal.js +72 -20
  307. package/dist/trace/core-keyset-guard.d.ts +79 -2
  308. package/dist/trace/exit-seat.d.ts +7 -0
  309. package/dist/trace/exit-seat.js +1 -0
  310. package/dist/trace/model-segment.d.ts +2 -2
  311. package/dist/trace/redact.d.ts +3 -1
  312. package/dist/trace/redact.js +395 -110
  313. package/dist/trace/self-minted-wire.d.ts +226 -2
  314. package/dist/trace/self-minted-wire.js +79 -0
  315. package/dist/verifier-pause-card.d.ts +8 -0
  316. package/dist/verifier-pause-card.js +19 -0
  317. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  318. package/dist/web-admin-client/apply-effective.js +2 -2
  319. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  320. package/dist/web-admin-client/candidate-gate.js +1 -1
  321. package/dist/web-admin-client/http-client.d.ts +5 -4
  322. package/dist/web-admin-client/http-client.js +2 -2
  323. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  324. package/dist/web-admin-client/read-warnings.js +5 -9
  325. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  326. package/package.json +2 -2
  327. package/wire-changes/server@7.140.0.json +184 -0
  328. package/wire-changes/server@7.141.0.json +367 -0
@@ -1,6 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { Sandbox, CommandExitError, FileType } from "e2b";
3
3
  import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
4
+ import { capabilitiesFor } from "../execution-lane-caps.js";
5
+ import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, e2bFloorCommand, engineStart, } from "./sandbox-read-floor.js";
6
+ import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
4
7
  import { fileErrorFromExec } from "./remote-env-file-error.js";
5
8
  import { appendFileStaged, rewriteAppend, numEnvOr, runSymlinkChain, shellQuote, writeFileStaged } from "./remote-shell.js";
6
9
  import { buildLivenessCut, commandBoundsOf, firstBoundOf, laneLivenessOf, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
@@ -19,7 +22,9 @@ const E2B_BG_PROVIDER_BACKSTOP_PAD_SEC = 30;
19
22
  const ok = (value) => ({ ok: true, value });
20
23
  export class RemoteContainerExecutionEnv {
21
24
  cwd;
22
- homeDir;
25
+ get homeDir() {
26
+ return this.homeSeat.home;
27
+ }
23
28
  cdPath = "unknown";
24
29
  withheldEnv = [];
25
30
  cfg;
@@ -29,6 +34,8 @@ export class RemoteContainerExecutionEnv {
29
34
  keepAlive;
30
35
  handle;
31
36
  destroyed = false;
37
+ homeSeat;
38
+ homeRefusal;
32
39
  connecting;
33
40
  activeCommands = 0;
34
41
  paused = false;
@@ -44,10 +51,92 @@ export class RemoteContainerExecutionEnv {
44
51
  };
45
52
  this.laneLiveness = laneLivenessOf(this.cfg.livenessMs, "E2B_LIVENESS_MS");
46
53
  this.cwd = this.cfg.mountPath;
47
- this.homeDir = config.homeDir;
54
+ this.homeSeat = new LaneHomeSeat(PROVIDER, config);
48
55
  this.sandboxApi = deps.sandboxApi ?? Sandbox;
56
+ this.floor = deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor, ...(config.logger !== undefined ? { logger: config.logger } : {}) });
57
+ this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER }, sandboxReadFloor: config.sandboxReadFloor });
58
+ }
59
+ capabilities;
60
+ floor;
61
+ startIn(start, command, envs) {
62
+ const launch = this.floor.wrap(start);
63
+ return launch.ok ? ok(e2bFloorCommand(launch.value, command, envs)) : launch;
64
+ }
65
+ runIn(sbx, s, opts = {}) {
66
+ const { background, ...rest } = opts;
67
+ const o = { ...rest, ...(s.user !== undefined ? { user: s.user } : {}), ...(s.envs !== undefined ? { envs: s.envs } : {}) };
68
+ return background ? sbx.commands.run(s.cmd, { ...o, background: true }) : sbx.commands.run(s.cmd, o);
69
+ }
70
+ floorTransport(sbx) {
71
+ const bootstrap = async (command) => {
72
+ const s = this.startIn(BOOTSTRAP_START, command, undefined);
73
+ if (!s.ok)
74
+ return { ok: false, error: s.error.message };
75
+ try {
76
+ const r = await this.rpc("floor.bootstrap", (signal) => this.runIn(sbx, s.value, { signal, timeoutMs: this.cfg.rpcTimeoutMs }));
77
+ return { ok: true, exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
78
+ }
79
+ catch (e) {
80
+ if (e instanceof CommandExitError)
81
+ return { ok: true, exitCode: e.exitCode, stdout: e.stdout, stderr: e.stderr };
82
+ return { ok: false, error: errMsg(e) };
83
+ }
84
+ };
85
+ return {
86
+ bootstrap,
87
+ place: async (dest, bytes) => {
88
+ const dir = path.posix.dirname(dest);
89
+ const tmp = `${dest}.${Math.random().toString(16).slice(2, 10)}`;
90
+ const mk = await bootstrap(`umask 022 && mkdir -p ${shellQuote(dir)}`);
91
+ if (!mk.ok || mk.exitCode !== 0)
92
+ return { ok: false, error: mk.ok ? mk.stderr.trim() || `exit ${mk.exitCode}` : mk.error };
93
+ try {
94
+ const ab = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
95
+ await this.rpc("files.write", (signal) => sbx.files.write(tmp, ab, { user: "root", signal }), undefined, this.cfg.dataTimeoutMs);
96
+ }
97
+ catch (e) {
98
+ return { ok: false, error: errMsg(e) };
99
+ }
100
+ const mv = await bootstrap(`chmod 0755 ${shellQuote(tmp)} && mv -f ${shellQuote(tmp)} ${shellQuote(dest)}`);
101
+ return mv.ok && mv.exitCode === 0 ? { ok: true } : { ok: false, error: mv.ok ? mv.stderr.trim() || `exit ${mv.exitCode}` : mv.error };
102
+ },
103
+ openRelay: (identity, command, events) => {
104
+ let handle;
105
+ let closed = false;
106
+ const pending = [];
107
+ const user = (() => {
108
+ switch (identity) {
109
+ case "root":
110
+ return { user: "root" };
111
+ case "exec-user":
112
+ return {};
113
+ }
114
+ })();
115
+ sbx.commands
116
+ .run(command, { background: true, stdin: true, timeoutMs: 0, ...user, onStdout: (d) => events.data(d) })
117
+ .then((h) => {
118
+ handle = h;
119
+ if (closed)
120
+ return void h.kill().catch(() => { });
121
+ for (const line of pending.splice(0))
122
+ void h.sendStdin(line).catch(() => { });
123
+ h.wait().then(() => events.closed("relay exited"), (e) => events.closed(`relay exited: ${errMsg(e)}`));
124
+ }, (e) => events.closed(`relay start failed: ${errMsg(e)}`));
125
+ return {
126
+ send: (line) => {
127
+ if (handle !== undefined)
128
+ void handle.sendStdin(line).catch(() => { });
129
+ else
130
+ pending.push(line);
131
+ },
132
+ close: () => {
133
+ closed = true;
134
+ void handle?.kill().catch(() => { });
135
+ },
136
+ };
137
+ },
138
+ };
49
139
  }
50
- capabilities = { isolation: true, suspendable: true, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
51
140
  workspaceHandle() {
52
141
  if (!this.handle) {
53
142
  throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
@@ -60,8 +149,12 @@ export class RemoteContainerExecutionEnv {
60
149
  }
61
150
  async startBackground(command) {
62
151
  const conn = await this.ensureConnected();
63
- if (conn.ok)
64
- await conn.value.commands.run(command, { background: true });
152
+ if (!conn.ok)
153
+ return;
154
+ const s = this.startIn(engineStart(undefined), command, undefined);
155
+ if (!s.ok)
156
+ return void this.cfg.logger?.warn?.("remote_env_background_refused", { error: s.error.message });
157
+ await this.runIn(conn.value, s.value, { background: true });
65
158
  }
66
159
  async connect(config) {
67
160
  if (config?.snapshotId) {
@@ -69,6 +162,8 @@ export class RemoteContainerExecutionEnv {
69
162
  }
70
163
  if (this.destroyed)
71
164
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
165
+ if (this.homeRefusal)
166
+ return { ok: false, error: this.homeRefusal };
72
167
  if (this.paused)
73
168
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env is suspended (paused) — resumeVM(snapshotId) before use") };
74
169
  if (this.sbx)
@@ -95,6 +190,17 @@ export class RemoteContainerExecutionEnv {
95
190
  await sbx.kill().catch(() => { });
96
191
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
97
192
  }
193
+ const home = await this.homeVerdict(sbx, config?.abortSignal);
194
+ if (home !== undefined || this.destroyed) {
195
+ await sbx.kill().catch(() => { });
196
+ this.cfg.metrics?.inc("remote_env_connect_total", { provider: PROVIDER, result: "error" });
197
+ return { ok: false, error: home ?? new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
198
+ }
199
+ await this.floor.attach(this.floorTransport(sbx), { origin: "created", workspaceRoot: this.cfg.mountPath });
200
+ if (this.destroyed) {
201
+ await sbx.kill().catch(() => { });
202
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
203
+ }
98
204
  this.sbx = sbx;
99
205
  this.paused = false;
100
206
  this.startKeepAlive();
@@ -147,6 +253,8 @@ export class RemoteContainerExecutionEnv {
147
253
  async resumeVM(snapshotId, options) {
148
254
  if (this.destroyed)
149
255
  return { ok: false, error: new RemoteExecutionError("connect_failed", "env already destroyed") };
256
+ if (this.homeRefusal)
257
+ return { ok: false, error: this.homeRefusal };
150
258
  if (this.sbx && !this.paused && this.sbx.sandboxId === snapshotId)
151
259
  return ok(this.workspaceHandle());
152
260
  if (this.connecting)
@@ -164,11 +272,26 @@ export class RemoteContainerExecutionEnv {
164
272
  await sbx.kill().catch(() => { });
165
273
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during resume") };
166
274
  }
275
+ const restoredRoot = options?.priorHandle?.mountPath ?? this.cfg.mountPath;
276
+ await this.floor.attach(this.floorTransport(sbx), { origin: "resumed", workspaceRoot: restoredRoot });
277
+ if (this.destroyed) {
278
+ await sbx.kill().catch(() => { });
279
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during resume") };
280
+ }
281
+ const home = await this.homeVerdict(sbx, options?.abortSignal);
282
+ if (this.destroyed) {
283
+ await sbx.kill().catch(() => { });
284
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during resume") };
285
+ }
286
+ if (home !== undefined) {
287
+ await this.rpc("sandbox.pause", (signal) => sbx.pause({ signal }), undefined, this.cfg.dataTimeoutMs).catch((e) => this.cfg.logger?.warn?.("remote_env_home_refused_repause_failed", { sandboxId: sbx.sandboxId, error: errMsg(e) }));
288
+ this.cfg.metrics?.inc("remote_env_resume_total", { provider: PROVIDER, result: "error" });
289
+ return { ok: false, error: home };
290
+ }
167
291
  this.sbx = sbx;
168
292
  this.paused = false;
169
293
  this.startKeepAlive();
170
294
  this.cfg.logger?.info?.("remote_env_resume", { requested: snapshotId, connected: sbx.sandboxId });
171
- const restoredRoot = options?.priorHandle?.mountPath ?? this.cfg.mountPath;
172
295
  this.cwd = restoredRoot;
173
296
  this.handle = { sandboxId: sbx.sandboxId, provider: PROVIDER, mountPath: restoredRoot, snapshotId, sessionToken: sbx.sandboxId };
174
297
  this.cfg.metrics?.inc("remote_env_resume_total", { provider: PROVIDER, result: "ok" });
@@ -196,6 +319,30 @@ export class RemoteContainerExecutionEnv {
196
319
  return { ok: false, error: this.remoteError(e, "post_resume_failed") };
197
320
  }
198
321
  }
322
+ async homeVerdict(sbx, signal) {
323
+ if (!this.homeSeat.asks)
324
+ return undefined;
325
+ let probe;
326
+ try {
327
+ const start = this.startIn(ADAPTER_START, LANE_HOME_PROBE_COMMAND, undefined);
328
+ if (!start.ok)
329
+ throw start.error;
330
+ const r = await this.rpc("sandbox.home", (s) => this.runIn(sbx, start.value, { signal: s }), signal);
331
+ probe = r.exitCode === 0 ? { ok: true, stdout: r.stdout } : { ok: false, reason: `exit ${r.exitCode}` };
332
+ }
333
+ catch (e) {
334
+ probe = { ok: false, reason: e instanceof CommandExitError ? `exit ${e.exitCode}` : errMsg(e) };
335
+ }
336
+ if (signal?.aborted)
337
+ return new RemoteExecutionError("aborted", "connect aborted while reading the sandbox's home");
338
+ const verdict = this.homeSeat.settle(probe);
339
+ if (verdict === undefined)
340
+ return undefined;
341
+ const error = new RemoteExecutionError("connect_failed", verdict.refusal);
342
+ if (verdict.answered)
343
+ this.homeRefusal = error;
344
+ return error;
345
+ }
199
346
  startKeepAlive() {
200
347
  this.stopKeepAlive();
201
348
  const everyMs = Math.max(30_000, Math.floor(this.cfg.timeoutMs * 0.8));
@@ -218,6 +365,7 @@ export class RemoteContainerExecutionEnv {
218
365
  this.destroyed = true;
219
366
  this.connecting = undefined;
220
367
  this.stopKeepAlive();
368
+ this.floor.dispose();
221
369
  await this.disposeBackgroundShells();
222
370
  const sbx = this.sbx;
223
371
  this.sbx = undefined;
@@ -238,12 +386,21 @@ export class RemoteContainerExecutionEnv {
238
386
  await this.destroy();
239
387
  }
240
388
  async exec(command, options) {
389
+ return this.execAs(engineStart(options?.readFloor), command, options);
390
+ }
391
+ execInternal(command, options) {
392
+ return this.execAs(ADAPTER_START, command, options);
393
+ }
394
+ async execAs(start, command, options) {
241
395
  if (options?.abortSignal?.aborted)
242
396
  return { ok: false, error: new ExecutionError("aborted", "exec aborted before start") };
243
397
  const conn = await this.ensureConnected();
244
398
  if (!conn.ok)
245
399
  return { ok: false, error: new ExecutionError("shell_unavailable", `remote connect failed: ${conn.error.message}`, conn.error) };
246
400
  const sbx = conn.value;
401
+ const launch = this.startIn(start, command, options?.env);
402
+ if (!launch.ok)
403
+ return launch;
247
404
  this.activeCommands++;
248
405
  const detachSignal = options?.detachSignal;
249
406
  const tee = detachSignal
@@ -268,10 +425,9 @@ export class RemoteContainerExecutionEnv {
268
425
  let starting;
269
426
  let startCut = false;
270
427
  try {
271
- handle = await this.rpc("commands.run", () => (starting = sbx.commands.run(command, {
428
+ handle = await this.rpc("commands.run", () => (starting = this.runIn(sbx, launch.value, {
272
429
  background: true,
273
430
  cwd: this.resolve(options?.cwd ?? this.cwd),
274
- ...(options?.env ? { envs: options.env } : {}),
275
431
  timeoutMs: tee
276
432
  ? Math.max(this.commandTimeoutMs(options?.timeout), (E2B_BG_MAX_TIMEOUT_SEC + E2B_BG_PROVIDER_BACKSTOP_PAD_SEC) * 1000)
277
433
  : this.commandTimeoutMs(options?.timeout),
@@ -395,6 +551,9 @@ export class RemoteContainerExecutionEnv {
395
551
  if (!conn.ok)
396
552
  throw conn.error;
397
553
  const sbx = conn.value;
554
+ const launch = this.startIn(engineStart(undefined), command, options?.env);
555
+ if (!launch.ok)
556
+ throw new RemoteExecutionError("unsupported", launch.error.message, launch.error);
398
557
  const queue = [];
399
558
  let finished = false;
400
559
  let failure;
@@ -421,10 +580,9 @@ export class RemoteContainerExecutionEnv {
421
580
  let handle;
422
581
  let starting;
423
582
  try {
424
- handle = await this.rpc("commands.run", () => (starting = sbx.commands.run(command, {
583
+ handle = await this.rpc("commands.run", () => (starting = this.runIn(sbx, launch.value, {
425
584
  background: true,
426
585
  cwd: this.resolve(options?.cwd ?? this.cwd),
427
- ...(options?.env ? { envs: options.env } : {}),
428
586
  timeoutMs: this.commandTimeoutMs(options?.timeout),
429
587
  onStdout: (d) => push("stdout", d),
430
588
  onStderr: (d) => push("stderr", d),
@@ -552,12 +710,14 @@ export class RemoteContainerExecutionEnv {
552
710
  if (!conn.ok)
553
711
  throw new Error(`remote connect failed: ${conn.error.message}`);
554
712
  const sbx = conn.value;
713
+ const launch = self.startIn(engineStart(options?.readFloor), command, options?.env);
714
+ if (!launch.ok)
715
+ throw launch.error;
555
716
  const stdout = { tail: new RollingTailBuffer(), total: 0, cursor: 0 };
556
717
  const stderr = { tail: new RollingTailBuffer(), total: 0, cursor: 0 };
557
- const handle = await self.rpc("commands.run.bg", () => sbx.commands.run(command, {
718
+ const handle = await self.rpc("commands.run.bg", () => self.runIn(sbx, launch.value, {
558
719
  background: true,
559
720
  cwd: self.resolve(options?.cwd ?? self.cwd),
560
- ...(options?.env ? { envs: options.env } : {}),
561
721
  timeoutMs: (ctx.bgTimeoutSec + E2B_BG_PROVIDER_BACKSTOP_PAD_SEC) * 1000,
562
722
  onStdout: (d) => {
563
723
  const b = Buffer.from(d, "utf8");
@@ -675,7 +835,7 @@ export class RemoteContainerExecutionEnv {
675
835
  return { ok: false, error: this.fileError(e, dest, signal) };
676
836
  }
677
837
  },
678
- run: (cmd, signal) => this.exec(cmd, { cwd: "/", ...(signal !== undefined ? { abortSignal: signal } : {}) }),
838
+ run: (cmd, signal) => this.execInternal(cmd, { cwd: "/", ...(signal !== undefined ? { abortSignal: signal } : {}) }),
679
839
  };
680
840
  }
681
841
  async writeFile(p, content, abortSignal) {
@@ -717,7 +877,7 @@ export class RemoteContainerExecutionEnv {
717
877
  }
718
878
  async readLink(p, abortSignal) {
719
879
  const abs = this.resolve(p);
720
- const r = await this.exec(`readlink -- ${shellQuote(abs)}`, { abortSignal });
880
+ const r = await this.execInternal(`readlink -- ${shellQuote(abs)}`, { abortSignal });
721
881
  if (!r.ok)
722
882
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
723
883
  if (r.value.exitCode !== 0)
@@ -725,11 +885,11 @@ export class RemoteContainerExecutionEnv {
725
885
  return ok(r.value.stdout.trim());
726
886
  }
727
887
  async canonicalChain(p, abortSignal) {
728
- return runSymlinkChain(this.resolve(p), (cmd) => this.exec(cmd, { abortSignal }));
888
+ return runSymlinkChain(this.resolve(p), (cmd) => this.execInternal(cmd, { abortSignal }));
729
889
  }
730
890
  async canonicalPath(p, abortSignal) {
731
891
  const abs = this.resolve(p);
732
- const r = await this.exec(`realpath -- ${shellQuote(abs)}`, { abortSignal });
892
+ const r = await this.execInternal(`realpath -- ${shellQuote(abs)}`, { abortSignal });
733
893
  if (!r.ok)
734
894
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
735
895
  if (r.value.exitCode !== 0) {
@@ -776,7 +936,7 @@ export class RemoteContainerExecutionEnv {
776
936
  const abs = this.resolve(p);
777
937
  if (options?.recursive) {
778
938
  const flags = options.force ? "-rf" : "-r";
779
- const r = await this.exec(`rm ${flags} -- ${shellQuote(abs)}`, { abortSignal: options.abortSignal });
939
+ const r = await this.execInternal(`rm ${flags} -- ${shellQuote(abs)}`, { abortSignal: options.abortSignal });
780
940
  if (!r.ok)
781
941
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
782
942
  if (r.value.exitCode !== 0)
@@ -795,14 +955,14 @@ export class RemoteContainerExecutionEnv {
795
955
  }
796
956
  }
797
957
  async createTempDir(prefix = "tmp-", abortSignal) {
798
- const r = await this.exec(`mktemp -d -t ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal });
958
+ const r = await this.execInternal(`mktemp -d -t ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal });
799
959
  return this.tempResult(r, "mktemp -d");
800
960
  }
801
961
  async createTempFile(options) {
802
962
  const prefix = options?.prefix ?? "";
803
963
  const suffix = options?.suffix ?? "";
804
964
  const suffixArg = suffix ? `--suffix=${shellQuote(suffix)} -t` : "-t";
805
- const r = await this.exec(`mktemp ${suffixArg} ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal: options?.abortSignal });
965
+ const r = await this.execInternal(`mktemp ${suffixArg} ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal: options?.abortSignal });
806
966
  return this.tempResult(r, "mktemp");
807
967
  }
808
968
  resolve(p) {
@@ -905,7 +1065,7 @@ export class RemoteContainerExecutionEnv {
905
1065
  async fsReady(p) {
906
1066
  const conn = await this.ensureConnected();
907
1067
  if (!conn.ok)
908
- return { ok: false, error: new FileError("unknown", `remote connect failed: ${conn.error.message}`, this.resolve(p), conn.error) };
1068
+ return { ok: false, error: fileErrorFromExec(conn.error, this.resolve(p)) };
909
1069
  return ok(conn.value);
910
1070
  }
911
1071
  tempResult(r, op) {
@@ -15,7 +15,7 @@
15
15
  * 那条教训:宣布"类修完成"而三个实现只落了一个)。收成一处之后,判据只有一条 ——
16
16
  * **文件面的 exec 包装必须走这里**,而这条能被机器检查(见 `test/env-adapter-file-error-parity.test.ts`)。
17
17
  */
18
- import { FileError, type ExecutionError, type RemoteExecutionError } from "@sema-agent/core";
18
+ import { FileError, RemoteExecutionError, type ExecutionError } from "@sema-agent/core";
19
19
  /** exec 层的错误码 → 文件层的码。只映射**有对应语义**的那些,其余落 `unknown`(诚实降级)。 */
20
20
  export declare function fileErrorFromExec(e: ExecutionError | RemoteExecutionError, path?: string): FileError;
21
21
  /** POSIX 的一个名字段上限(NAME_MAX,字节;Linux / macOS / 常见文件系统同值)。也是远端写的私有暂存目录名的上限(`remote-shell.ts` `buildStagingPaths`)。 */
@@ -1,6 +1,12 @@
1
- import { FileError } from "@sema-agent/core";
1
+ import { FileError, RemoteExecutionError } from "@sema-agent/core";
2
+ function neverOpened(e) {
3
+ if (e.code === "connect_failed")
4
+ return true;
5
+ const cause = e.cause;
6
+ return cause instanceof RemoteExecutionError && cause.code === "connect_failed";
7
+ }
2
8
  export function fileErrorFromExec(e, path) {
3
- const code = e.code === "aborted" ? "aborted" : "unknown";
9
+ const code = e.code === "aborted" ? "aborted" : neverOpened(e) ? "target_unavailable" : "unknown";
4
10
  return new FileError(code, e.message, path, e);
5
11
  }
6
12
  export const NAME_MAX_BYTES = 255;
@@ -1,4 +1,5 @@
1
1
  import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, 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";
2
+ import { type LaneHomeConfig } from "../lane-home-probe.js";
2
3
  /** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
3
4
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
4
5
  /** SIGTERM → grace → SIGKILL (no host isolation). 🔴 宽限的**唯一属主**:本 driver 的 `kill` 梯子用它排升级定时器;会话级收敛
@@ -6,7 +7,7 @@ type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
6
7
  * 这把梯子的用户面宽限取消),不另抄数。启动期读一次;`0` 被 `numEnvOr` 读作缺席(回落 1000),要几乎不等写 `1`。 */
7
8
  export declare const HOST_BG_KILL_GRACE_MS: number;
8
9
  /** Construction config for {@link RemoteHostExecutionEnv}. */
9
- export interface HostEnvConfig {
10
+ export interface HostEnvConfig extends LaneHomeConfig {
10
11
  /**
11
12
  * Base directory under which this adapter creates its per-task working directory. Default {@link os.tmpdir}.
12
13
  * The actual workspace is `<workspaceBase>/sema-host-<id>` and is `rm -rf`'d on {@link destroy}.
@@ -24,15 +25,6 @@ export interface HostEnvConfig {
24
25
  * own `process.env` is the default base (a local coding agent inherits the user's environment).
25
26
  */
26
27
  env?: Record<string, string>;
27
- /**
28
- * [ref]④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
29
- * 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
30
- * 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
31
- * 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
32
- * 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
33
- * adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
34
- */
35
- homeDir?: string;
36
28
  /** Per-command wall-clock bound (ms) when the caller gives none. Default {@link DEFAULT_COMMAND_TIMEOUT_MS}. */
37
29
  commandTimeoutMs?: number;
38
30
  /** Shell-child env policy — core's `InheritEnvPolicy`, read by core's `inheritedHostEnv` (the three words are judged
@@ -64,29 +56,8 @@ export interface HostEnvConfig {
64
56
  backgroundShell?: boolean;
65
57
  }
66
58
  export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability {
67
- /** [ref] §9 / DUAL-MODE-DESIGN §5: a `host` env is the operator's real machine — no isolation, no snapshot. */
68
- /**
69
- * 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
70
- * 就是**命令真正跑的那块文件系统**的权威答案(worker 本机的 `fs.realpath`)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
71
- * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
72
- * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
73
- * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
74
- * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
75
- * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
76
- * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
77
- */
78
- /** `sharesProcessTable`(core 7.57.0,必填):命令是本进程 spawn 的子进程(同一张进程表),引擎的模式杀守卫对它照 NodeExecutionEnv 一样问 `pgrep`。 */
79
- /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
80
- readonly capabilities: {
81
- isolation: false;
82
- suspendable: false;
83
- canonicalPathAuthoritative: true;
84
- sharesProcessTable: true;
85
- hostLocalPaths: true;
86
- externalContentTarget: false;
87
- io: true;
88
- keepsReadFloor: false;
89
- };
59
+ /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
60
+ readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
90
61
  /** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
91
62
  * presence (always, since we implement the interface) AND `schedulerCapabilities.supported===true` — which is only
92
63
  * true when a backend was injected, so a host env without one stays INERT (the four scheduler tools don't mount). */
@@ -98,8 +69,13 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
98
69
  list(ctx: SchedulerContext): Promise<Result<ScheduledTaskSummary[], SchedulerError>>;
99
70
  /** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at the workspace root. */
100
71
  cwd: string;
101
- /** core `ExecutionEnv.homeDir`([ref]):本沙箱/目标机上执行用户的 home。见配置同名字段。 */
102
- readonly homeDir?: string | undefined;
72
+ /** core `ExecutionEnv.homeDir`([ref]):本机执行用户的 home —— 主张在场 ⇒ 主张(构造时与子进程拿到的 `HOME` 对证过),否则构造时读到的那个 `HOME`,见 {@link LaneHomeSeat}。 */
73
+ get homeDir(): string | undefined;
74
+ /** 家目录的座(主张在场 ⇒ 构造时对证 · 否则构造时实测)。读数 = {@link childHome}:本类自己组合给子进程的那份 env,不 spawn。 */
75
+ private readonly homeSeat;
76
+ /** 家目录:主张与子进程拿到的 `HOME` 对不上 ⇒ 这只环境的拒句;`shellReady` / `fsReady` / `connect` 三道门原样回它,一条命令、一次文件动作都不做。
77
+ * 读数是本类自己组合的 env(纯函数,同一份输入同一个答案)⇒ 构造时判一次、记一辈子。 */
78
+ private readonly homeRefusal;
103
79
  /**
104
80
  * core 7.49.0 [ref]:shell 子进程**真拿到的** `CDPATH` 条目(`[]` = 没有)—— 删除守卫按它解裸 `cd <dir>`。本车道的命令就是本机子进程,
105
81
  * env 是本类自己组合的(继承姿态 + `cfg.env`),所以说得出:读同一只组合({@link childEnv} 的**读面**,不披露、不钉 shell),切法与 core
@@ -454,6 +430,14 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
454
430
  * 引擎对点到自有凭据名的白名单在这里**抛**(spawn 时的拒;构造器已先问过同一只判)—— 子进程不起,拒句带名。
455
431
  */
456
432
  private childEnv;
433
+ /**
434
+ * 家目录的读数:本类组合给子进程的那份 env 里的 `HOME`(组合层,不 spawn、不记剥除 —— 与 {@link cdPath} 同一只读面)。env 里没有 `HOME` ⇒ 读
435
+ * 账户的家目录(`os.userInfo().homedir`):那正是子进程的 shell 对缺席 `HOME` 的 `~` 展开(POSIX 取账户 passwd 行;win32 是账户的配置目录)。
436
+ * 不读 `os.homedir()`:它先读**服务进程自己的** `HOME` —— 子进程没继承到的那个值(对抗轮 r1 [high] 实测:服务 `HOME` 指别处 + `inheritEnv: []` 时两者分叉)。
437
+ * 账户查不到(无 passwd 行)⇒ 读不出(有主张 ⇒ 不开;没主张 ⇒ 座缺席 + warn)。
438
+ * 残余(引擎 `NodeExecutionEnv.homeDir` 注同一句):POSIX 上 `HOME` 缺席时 shell 里的 `$HOME` 字面量展开为空,与 `~` 不是一个值 —— 本读数按 `~` 记。
439
+ */
440
+ private childHome;
457
441
  private fileError;
458
442
  }
459
443
  /**
@@ -12,6 +12,8 @@ import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
12
12
  import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
13
13
  import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
14
14
  import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
15
+ import { capabilitiesFor } from "../execution-lane-caps.js";
16
+ import { LaneHomeSeat } from "../lane-home-probe.js";
15
17
  import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
16
18
  import { nameTooLongCode } from "./remote-env-file-error.js";
17
19
  import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
@@ -38,7 +40,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
38
40
  maxScheduledHorizonSec: 0,
39
41
  };
40
42
  export class RemoteHostExecutionEnv {
41
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: true, hostLocalPaths: true, externalContentTarget: false, io: true, keepsReadFloor: false };
43
+ capabilities = capabilitiesFor({ remoteExec: { provider: "host" } });
42
44
  get schedulerCapabilities() {
43
45
  return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
44
46
  }
@@ -58,7 +60,11 @@ export class RemoteHostExecutionEnv {
58
60
  : Promise.resolve({ ok: false, error: new SchedulerError("unsupported", "scheduler not enabled on this worker") });
59
61
  }
60
62
  cwd;
61
- homeDir;
63
+ get homeDir() {
64
+ return this.homeSeat.home;
65
+ }
66
+ homeSeat;
67
+ homeRefusal;
62
68
  get cdPath() {
63
69
  const value = this.childEnv(undefined, undefined).CDPATH;
64
70
  return value === undefined || value === "" ? [] : value.split(":");
@@ -140,8 +146,9 @@ export class RemoteHostExecutionEnv {
140
146
  if (Array.isArray(config.inheritEnv))
141
147
  this.cfg.inheritEnv = [...config.inheritEnv];
142
148
  this.cwd = this.workspaceDir;
143
- this.homeDir = config.homeDir;
144
149
  this.handle = { sandboxId: this.workspaceDir, provider: PROVIDER, mountPath: this.workspaceDir };
150
+ this.homeSeat = new LaneHomeSeat(PROVIDER, config, IS_WIN32 ? "win32" : "posix");
151
+ this.homeRefusal = this.homeSeat.settle(this.childHome())?.refusal;
145
152
  }
146
153
  workspaceHandle() {
147
154
  return this.handle;
@@ -151,6 +158,8 @@ export class RemoteHostExecutionEnv {
151
158
  return unsupported("connect(snapshotId)");
152
159
  if (this.destroyed)
153
160
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
161
+ if (this.homeRefusal !== undefined)
162
+ return { ok: false, error: new RemoteExecutionError("connect_failed", this.homeRefusal) };
154
163
  if (config?.abortSignal?.aborted)
155
164
  return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
156
165
  try {
@@ -1253,6 +1262,8 @@ export class RemoteHostExecutionEnv {
1253
1262
  async shellReady() {
1254
1263
  if (this.destroyed)
1255
1264
  return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
1265
+ if (this.homeRefusal !== undefined)
1266
+ return { ok: false, error: new ExecutionError("shell_unavailable", this.homeRefusal) };
1256
1267
  try {
1257
1268
  try {
1258
1269
  await resolveHostShell();
@@ -1284,6 +1295,8 @@ export class RemoteHostExecutionEnv {
1284
1295
  return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.persistent && inside(this.workspaceDir));
1285
1296
  }
1286
1297
  async fsReady(p, opts) {
1298
+ if (this.homeRefusal !== undefined)
1299
+ return { ok: false, error: new FileError("target_unavailable", this.homeRefusal, this.resolve(p)) };
1287
1300
  if (this.destroyed) {
1288
1301
  const lingering = opts?.lingeringBgWrite === true && this._bgManager?.hasShells() === true && this.isLingeringMirrorTarget(p);
1289
1302
  if (!lingering)
@@ -1313,6 +1326,16 @@ export class RemoteHostExecutionEnv {
1313
1326
  pinHostShell(base, spawnShell);
1314
1327
  return collapseWin32EnvKeys(base);
1315
1328
  }
1329
+ childHome() {
1330
+ try {
1331
+ const env = this.childEnv(undefined, undefined);
1332
+ const home = Object.entries(env).find(([k]) => (IS_WIN32 ? k.toUpperCase() : k) === "HOME")?.[1];
1333
+ return { ok: true, value: home ?? os.userInfo().homedir };
1334
+ }
1335
+ catch (e) {
1336
+ return { ok: false, reason: errMsg(e) };
1337
+ }
1338
+ }
1316
1339
  fileError(e, p, abortSignal) {
1317
1340
  if (e instanceof FileError)
1318
1341
  return e;