@sema-agent/server 7.140.0 → 7.142.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 (296) hide show
  1. package/MIGRATION.md +505 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +142 -25
  5. package/dist/approval-ask-audit-store.d.ts +54 -38
  6. package/dist/approval-ask-audit-store.js +58 -39
  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 +39 -24
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval-crash-convergence.d.ts +17 -7
  12. package/dist/approval-crash-convergence.js +94 -22
  13. package/dist/approval-deny-reasons.d.ts +34 -30
  14. package/dist/approval-deny-reasons.js +21 -6
  15. package/dist/approval-policy-settlement.d.ts +1 -1
  16. package/dist/approval-reconciler.d.ts +33 -1
  17. package/dist/approval-reconciler.js +43 -40
  18. package/dist/approval.d.ts +15 -0
  19. package/dist/approval.js +10 -0
  20. package/dist/audit.d.ts +4 -2
  21. package/dist/audit.js +2 -2
  22. package/dist/auth-bridge.d.ts +9 -6
  23. package/dist/auth-bridge.js +9 -3
  24. package/dist/boot/adoption.js +2 -1
  25. package/dist/boot/budget-tracing.d.ts +16 -0
  26. package/dist/boot/budget-tracing.js +10 -6
  27. package/dist/boot/device-lane.d.ts +11 -0
  28. package/dist/boot/device-lane.js +16 -0
  29. package/dist/boot/engine-lease.d.ts +24 -3
  30. package/dist/boot/engine-lease.js +9 -2
  31. package/dist/boot/execution-env.d.ts +24 -4
  32. package/dist/boot/execution-env.js +34 -27
  33. package/dist/boot/execution-lane.js +2 -1
  34. package/dist/boot/exit-boundary.js +5 -4
  35. package/dist/boot/memory-boundary.d.ts +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +1 -1
  37. package/dist/boot/memory-consolidation.js +3 -2
  38. package/dist/boot/parked-revive-gate.js +2 -1
  39. package/dist/boot/permission-rules-audit.d.ts +0 -28
  40. package/dist/boot/permission-rules-audit.js +2 -1
  41. package/dist/boot/read-deny-anchor-audit.js +2 -1
  42. package/dist/boot/reapers.d.ts +2 -2
  43. package/dist/boot/reapers.js +4 -3
  44. package/dist/boot/resolve-spec.js +18 -6
  45. package/dist/boot/retention-lane.js +5 -3
  46. package/dist/boot/runner-deps.js +16 -13
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.d.ts +1 -1
  49. package/dist/boot/session-faces.js +8 -6
  50. package/dist/boot/shutdown.js +8 -2
  51. package/dist/boot/stage-04-stores.js +3 -2
  52. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  53. package/dist/boot/stage-05-execution-env.js +11 -7
  54. package/dist/boot/stage-06-runners.d.ts +1 -3
  55. package/dist/boot/stage-06-runners.js +3 -2
  56. package/dist/boot/stage-07-capability-layer.d.ts +1 -2
  57. package/dist/boot/stage-07-capability-layer.js +10 -11
  58. package/dist/boot/stage-08-reapers.d.ts +1 -2
  59. package/dist/boot/stage-08-reapers.js +3 -2
  60. package/dist/boot/stage-09-leader.d.ts +1 -2
  61. package/dist/boot/stage-10-http-server.d.ts +1 -2
  62. package/dist/boot/stage-10-http-server.js +6 -4
  63. package/dist/boot/stores.js +13 -6
  64. package/dist/boot/web-admin.js +18 -17
  65. package/dist/boot/webfetch-summarize-lane.js +2 -1
  66. package/dist/boot/workflow-orchestration.js +3 -2
  67. package/dist/capabilities/builtin-tools.d.ts +2 -1
  68. package/dist/capabilities/builtin-tools.js +2 -4
  69. package/dist/capabilities/composite-members.d.ts +2 -1
  70. package/dist/capabilities/composite-members.js +2 -1
  71. package/dist/capabilities/scenarios.d.ts +1 -1
  72. package/dist/capability-facts.d.ts +33 -0
  73. package/dist/capability-facts.js +32 -0
  74. package/dist/card-void.d.ts +38 -0
  75. package/dist/card-void.js +23 -0
  76. package/dist/config-catalog.js +12 -8
  77. package/dist/config-provider.d.ts +13 -13
  78. package/dist/config-provider.js +19 -33
  79. package/dist/config-types.d.ts +39 -10
  80. package/dist/config.d.ts +6 -0
  81. package/dist/config.js +72 -49
  82. package/dist/delegated-session-owner.d.ts +21 -19
  83. package/dist/delegated-session-owner.js +1 -1
  84. package/dist/device-store.d.ts +7 -0
  85. package/dist/device-store.js +3 -0
  86. package/dist/execution-config-root.d.ts +22 -0
  87. package/dist/execution-config-root.js +35 -0
  88. package/dist/execution-lane-caps.d.ts +50 -2
  89. package/dist/execution-lane-caps.js +90 -13
  90. package/dist/execution-placement.d.ts +3 -0
  91. package/dist/execution-placement.js +7 -0
  92. package/dist/fleet/drain-entry.d.ts +3 -0
  93. package/dist/fleet/fleet-bus.d.ts +6 -3
  94. package/dist/hooks/hook-runner.d.ts +6 -0
  95. package/dist/hooks/hook-runner.js +30 -1
  96. package/dist/hosted-posture.d.ts +8 -0
  97. package/dist/hosted-posture.js +4 -0
  98. package/dist/http/admission.js +3 -3
  99. package/dist/http/caller-trust.d.ts +3 -2
  100. package/dist/http/caller-trust.js +2 -1
  101. package/dist/http/dispatch.js +8 -2
  102. package/dist/http/execution-context.d.ts +2 -2
  103. package/dist/http/idempotency.d.ts +1 -1
  104. package/dist/http/live-frame.d.ts +24 -0
  105. package/dist/http/live-frame.js +16 -0
  106. package/dist/http/principal-gate.d.ts +3 -1
  107. package/dist/http/principal-gate.js +17 -8
  108. package/dist/http/resume-legs.js +42 -29
  109. package/dist/http/route-table.js +4 -0
  110. package/dist/http/routes/approvals-assistant.js +1 -0
  111. package/dist/http/routes/attachments.js +18 -0
  112. package/dist/http/routes/capabilities.js +10 -6
  113. package/dist/http/routes/diagnostics.d.ts +0 -19
  114. package/dist/http/routes/diagnostics.js +16 -2
  115. package/dist/http/routes/memory-bundle.d.ts +2 -2
  116. package/dist/http/routes/memory-bundle.js +1 -1
  117. package/dist/http/routes/memory-compliance.js +1 -1
  118. package/dist/http/routes/memory-policy.js +27 -7
  119. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  120. package/dist/http/routes/run-permission-mode.js +170 -0
  121. package/dist/http/routes/runs.js +3 -3
  122. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  123. package/dist/http/routes/session-memory-sync.js +157 -0
  124. package/dist/http/routes/session-sync.d.ts +3 -6
  125. package/dist/http/routes/session-sync.js +7 -2
  126. package/dist/http/routes/sessions-list.d.ts +2 -1
  127. package/dist/http/routes/sessions.js +5 -4
  128. package/dist/http/routes/tasks.js +70 -71
  129. package/dist/http/run-read-face.d.ts +7 -0
  130. package/dist/http/run-read-face.js +10 -4
  131. package/dist/http/send.d.ts +6 -1
  132. package/dist/http/send.js +10 -3
  133. package/dist/http/server.d.ts +20 -7
  134. package/dist/http/server.js +2 -4
  135. package/dist/http/sse-log.d.ts +3 -8
  136. package/dist/http/stored-body-replay.d.ts +3 -3
  137. package/dist/http/stored-body-replay.js +3 -1
  138. package/dist/http/submit-replay.d.ts +4 -3
  139. package/dist/http/submit-replay.js +2 -2
  140. package/dist/http/sync-submit-body.d.ts +21 -4
  141. package/dist/http/sync-submit-body.js +13 -0
  142. package/dist/identity-source.d.ts +21 -2
  143. package/dist/identity-source.js +6 -0
  144. package/dist/instruction-source-notice.d.ts +7 -0
  145. package/dist/instruction-source-notice.js +6 -6
  146. package/dist/mcp-assertion-header.d.ts +4 -0
  147. package/dist/mcp-assertion-header.js +5 -0
  148. package/dist/memory-incident-sink.d.ts +19 -6
  149. package/dist/memory-incident-sink.js +8 -4
  150. package/dist/memory-posture.d.ts +1 -1
  151. package/dist/memory-scope.d.ts +4 -4
  152. package/dist/memory-scope.js +1 -1
  153. package/dist/memory-sync-client.d.ts +35 -23
  154. package/dist/memory-sync-client.js +122 -52
  155. package/dist/memory-sync-identity.d.ts +24 -0
  156. package/dist/memory-sync-identity.js +57 -0
  157. package/dist/memory-sync.d.ts +7 -1
  158. package/dist/memory-sync.js +6 -3
  159. package/dist/observability/fail-open.d.ts +57 -12
  160. package/dist/observability/fail-open.js +50 -9
  161. package/dist/observability/metrics.js +0 -1
  162. package/dist/observability/run-terminal.d.ts +93 -31
  163. package/dist/observability/run-terminal.js +175 -171
  164. package/dist/observability/secret-env-scrub.d.ts +20 -1
  165. package/dist/observability/secret-env-scrub.js +14 -0
  166. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  167. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  168. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  169. package/dist/orchestration/workflow-notify-journal.js +2 -1
  170. package/dist/permission-mode-admission.d.ts +68 -0
  171. package/dist/permission-mode-admission.js +23 -0
  172. package/dist/permission-rule-list.d.ts +55 -6
  173. package/dist/permission-rule-list.js +52 -5
  174. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  175. package/dist/plugins/approval-ask-store-file.js +48 -7
  176. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  177. package/dist/plugins/approval-ask-store-memory.js +9 -8
  178. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  179. package/dist/plugins/approval-ask-store-sql.js +49 -19
  180. package/dist/plugins/caching-session-store.d.ts +6 -0
  181. package/dist/plugins/caching-session-store.js +3 -0
  182. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  183. package/dist/plugins/checkpoint-store-sql.js +74 -3
  184. package/dist/plugins/device-store-sql.d.ts +5 -2
  185. package/dist/plugins/device-store-sql.js +4 -1
  186. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  187. package/dist/plugins/execution-placement-sql.js +12 -1
  188. package/dist/plugins/file-run-store.d.ts +7 -2
  189. package/dist/plugins/file-run-store.js +12 -2
  190. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  191. package/dist/plugins/fork-routing-session-store.js +2 -61
  192. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  193. package/dist/plugins/k8s-exec-protocol.js +6 -1
  194. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  195. package/dist/plugins/local-checkpoint-store.js +22 -0
  196. package/dist/plugins/local-session-store.d.ts +260 -39
  197. package/dist/plugins/local-session-store.js +582 -57
  198. package/dist/plugins/local-session-window.d.ts +17 -3
  199. package/dist/plugins/local-session-window.js +7 -13
  200. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  201. package/dist/plugins/local-task-attachment-store.js +67 -43
  202. package/dist/plugins/memory-key-guards.d.ts +3 -3
  203. package/dist/plugins/memory-run-store.d.ts +7 -2
  204. package/dist/plugins/memory-run-store.js +12 -2
  205. package/dist/plugins/one-time-migrations.d.ts +68 -2
  206. package/dist/plugins/one-time-migrations.js +120 -4
  207. package/dist/plugins/pg-pool.js +3 -2
  208. package/dist/plugins/pg-session-storage.d.ts +2 -0
  209. package/dist/plugins/pg-session-storage.js +3 -2
  210. package/dist/plugins/remote-env-e2b.d.ts +47 -7
  211. package/dist/plugins/remote-env-e2b.js +242 -46
  212. package/dist/plugins/remote-env-k8s.d.ts +51 -6
  213. package/dist/plugins/remote-env-k8s.js +234 -44
  214. package/dist/plugins/remote-env-local-docker.d.ts +40 -14
  215. package/dist/plugins/remote-env-local-docker.js +127 -33
  216. package/dist/plugins/run-store-sql.d.ts +11 -3
  217. package/dist/plugins/run-store-sql.js +13 -4
  218. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  219. package/dist/plugins/sandbox-read-floor.js +608 -0
  220. package/dist/plugins/session-ownership.d.ts +2 -1
  221. package/dist/plugins/session-placement.d.ts +17 -4
  222. package/dist/plugins/session-placement.js +2 -1
  223. package/dist/plugins/session-store.d.ts +18 -20
  224. package/dist/plugins/session-store.js +4 -12
  225. package/dist/plugins/store-backend.d.ts +41 -7
  226. package/dist/plugins/store-backend.js +22 -4
  227. package/dist/plugins/store-contracts.d.ts +35 -3
  228. package/dist/plugins/store-contracts.js +3 -0
  229. package/dist/plugins/task-attachment-store.d.ts +112 -14
  230. package/dist/plugins/task-attachment-store.js +221 -28
  231. package/dist/plugins/tidb-pool.js +2 -1
  232. package/dist/plugins/tidb-session-store.d.ts +4 -1
  233. package/dist/plugins/tidb-session-store.js +3 -2
  234. package/dist/plugins/workspace-fill.d.ts +60 -0
  235. package/dist/plugins/workspace-fill.js +106 -0
  236. package/dist/project-bundle.d.ts +38 -0
  237. package/dist/project-bundle.js +609 -0
  238. package/dist/run-cancel-context.d.ts +34 -0
  239. package/dist/run-cancel-context.js +7 -0
  240. package/dist/run-local.js +2 -8
  241. package/dist/runs.d.ts +39 -12
  242. package/dist/runs.js +69 -55
  243. package/dist/sandbox-egress-policy.d.ts +4 -1
  244. package/dist/sandbox-egress-policy.js +3 -1
  245. package/dist/sandbox-git-plane.d.ts +31 -12
  246. package/dist/sandbox-git-plane.js +24 -4
  247. package/dist/security.d.ts +11 -2
  248. package/dist/security.js +8 -0
  249. package/dist/server-secret-env.d.ts +2 -1
  250. package/dist/server-secret-env.js +2 -4
  251. package/dist/session-error-wire.d.ts +46 -4
  252. package/dist/session-error-wire.js +23 -0
  253. package/dist/session-incarnation.d.ts +9 -3
  254. package/dist/session-sync-kernel.d.ts +42 -1
  255. package/dist/session-sync-kernel.js +17 -0
  256. package/dist/session-sync.js +16 -4
  257. package/dist/sql-engine-coordinates.d.ts +49 -0
  258. package/dist/sql-engine-coordinates.js +27 -0
  259. package/dist/standing-answer-grant.d.ts +31 -2
  260. package/dist/standing-answer-grant.js +14 -2
  261. package/dist/store-posture.d.ts +1 -1
  262. package/dist/task-a2a.d.ts +1 -1
  263. package/dist/task-mcp.d.ts +1 -1
  264. package/dist/task-settings.d.ts +62 -27
  265. package/dist/task-settings.js +105 -29
  266. package/dist/terminal.d.ts +97 -20
  267. package/dist/terminal.js +107 -18
  268. package/dist/tool-approval.d.ts +4 -3
  269. package/dist/tool-approval.js +12 -14
  270. package/dist/trace/core-keyset-guard.d.ts +196 -2
  271. package/dist/trace/engine-notice-wire.d.ts +8 -2
  272. package/dist/trace/engine-notice-wire.js +4 -2
  273. package/dist/trace/exit-seat.d.ts +9 -1
  274. package/dist/trace/exit-seat.js +1 -0
  275. package/dist/trace/ledger-sink.d.ts +8 -4
  276. package/dist/trace/ledger-sink.js +2 -2
  277. package/dist/trace/model-segment.d.ts +2 -2
  278. package/dist/trace/project.d.ts +18 -1
  279. package/dist/trace/project.js +16 -0
  280. package/dist/trace/redact.js +323 -90
  281. package/dist/trace/self-minted-wire.d.ts +419 -4
  282. package/dist/trace/self-minted-wire.js +145 -0
  283. package/dist/trace/wire-projection-faces.d.ts +1 -1
  284. package/dist/trace/wire-projection-faces.js +1 -1
  285. package/dist/verifier-pause-card.d.ts +8 -0
  286. package/dist/verifier-pause-card.js +19 -0
  287. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  288. package/dist/web-admin-client/apply-effective.js +22 -2
  289. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  290. package/dist/web-admin-client/effective-keys.js +2 -0
  291. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  292. package/dist/web-admin-client/types.d.ts +4 -0
  293. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  294. package/package.json +2 -2
  295. package/wire-changes/server@7.141.0.json +367 -0
  296. package/wire-changes/server@7.142.0.json +639 -0
@@ -2,6 +2,7 @@ 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
4
  import { capabilitiesFor } from "../execution-lane-caps.js";
5
+ import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, e2bFloorCommand, engineStart, } from "./sandbox-read-floor.js";
5
6
  import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
6
7
  import { fileErrorFromExec } from "./remote-env-file-error.js";
7
8
  import { appendFileStaged, rewriteAppend, numEnvOr, runSymlinkChain, shellQuote, writeFileStaged } from "./remote-shell.js";
@@ -34,7 +35,7 @@ export class RemoteContainerExecutionEnv {
34
35
  handle;
35
36
  destroyed = false;
36
37
  homeSeat;
37
- homeRefusal;
38
+ connectRefusal;
38
39
  connecting;
39
40
  activeCommands = 0;
40
41
  paused = false;
@@ -52,13 +53,92 @@ export class RemoteContainerExecutionEnv {
52
53
  this.cwd = this.cfg.mountPath;
53
54
  this.homeSeat = new LaneHomeSeat(PROVIDER, config);
54
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
+ };
55
139
  }
56
- capabilities = capabilitiesFor({ remoteExec: { provider: "e2b" } });
57
140
  workspaceHandle() {
58
- if (!this.handle) {
59
- throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
60
- }
61
- return this.handle;
141
+ return this.handle ?? { sandboxId: "(unconnected)", provider: PROVIDER, mountPath: this.cwd };
62
142
  }
63
143
  async getHost(port) {
64
144
  const conn = await this.ensureConnected();
@@ -66,8 +146,12 @@ export class RemoteContainerExecutionEnv {
66
146
  }
67
147
  async startBackground(command) {
68
148
  const conn = await this.ensureConnected();
69
- if (conn.ok)
70
- await conn.value.commands.run(command, { background: true });
149
+ if (!conn.ok)
150
+ return;
151
+ const s = this.startIn(engineStart(undefined), command, undefined);
152
+ if (!s.ok)
153
+ return void this.cfg.logger?.warn?.("remote_env_background_refused", { error: s.error.message });
154
+ await this.runIn(conn.value, s.value, { background: true });
71
155
  }
72
156
  async connect(config) {
73
157
  if (config?.snapshotId) {
@@ -75,17 +159,18 @@ export class RemoteContainerExecutionEnv {
75
159
  }
76
160
  if (this.destroyed)
77
161
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
78
- if (this.homeRefusal)
79
- return { ok: false, error: this.homeRefusal };
162
+ if (this.connectRefusal)
163
+ return { ok: false, error: this.connectRefusal };
80
164
  if (this.paused)
81
165
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env is suspended (paused) — resumeVM(snapshotId) before use") };
82
166
  if (this.sbx)
83
167
  return ok(this.workspaceHandle());
84
168
  if (this.connecting)
85
- return this.connecting;
86
- this.connecting = this.doConnect(config);
87
- const r = await this.connecting;
88
- if (!r.ok)
169
+ return this.connecting.promise;
170
+ const mine = { target: undefined, promise: this.doConnect(config) };
171
+ this.connecting = mine;
172
+ const r = await mine.promise;
173
+ if (!r.ok && this.connecting === mine)
89
174
  this.connecting = undefined;
90
175
  return r;
91
176
  }
@@ -109,6 +194,19 @@ export class RemoteContainerExecutionEnv {
109
194
  this.cfg.metrics?.inc("remote_env_connect_total", { provider: PROVIDER, result: "error" });
110
195
  return { ok: false, error: home ?? new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
111
196
  }
197
+ await this.floor.attach(this.floorTransport(sbx), { origin: "created", workspaceRoot: this.cfg.mountPath });
198
+ if (this.destroyed) {
199
+ await sbx.kill().catch(() => { });
200
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
201
+ }
202
+ const filled = await this.fillAt(sbx, this.cfg.mountPath, "minted");
203
+ if (!filled.ok || this.destroyed) {
204
+ await sbx.kill().catch(() => { });
205
+ this.cfg.metrics?.inc("remote_env_connect_total", { provider: PROVIDER, result: "error" });
206
+ if (!filled.ok && filled.standing)
207
+ this.connectRefusal = filled.error;
208
+ return { ok: false, error: filled.ok ? new RemoteExecutionError("connect_failed", "execution env destroyed during connect") : filled.error };
209
+ }
112
210
  this.sbx = sbx;
113
211
  this.paused = false;
114
212
  this.startKeepAlive();
@@ -158,20 +256,50 @@ export class RemoteContainerExecutionEnv {
158
256
  return { ok: false, error: this.remoteError(e, options?.abortSignal?.aborted ? "aborted" : "unknown") };
159
257
  }
160
258
  }
161
- async resumeVM(snapshotId, options) {
259
+ resumeVM(snapshotId, options) {
162
260
  if (this.destroyed)
163
- return { ok: false, error: new RemoteExecutionError("connect_failed", "env already destroyed") };
164
- if (this.homeRefusal)
165
- return { ok: false, error: this.homeRefusal };
261
+ return Promise.resolve({ ok: false, error: new RemoteExecutionError("connect_failed", "env already destroyed") });
262
+ if (this.connectRefusal)
263
+ return Promise.resolve({ ok: false, error: this.connectRefusal });
166
264
  if (this.sbx && !this.paused && this.sbx.sandboxId === snapshotId)
167
- return ok(this.workspaceHandle());
168
- if (this.connecting)
169
- return this.connecting;
170
- this.connecting = this.doResumeVM(snapshotId, options);
171
- const r = await this.connecting;
172
- if (!r.ok)
173
- this.connecting = undefined;
174
- return r;
265
+ return Promise.resolve(ok(this.workspaceHandle()));
266
+ const inFlight = this.connecting;
267
+ if (inFlight?.target === snapshotId)
268
+ return inFlight.promise;
269
+ const mine = { target: snapshotId, promise: this.resumeWalk(inFlight, snapshotId, options) };
270
+ this.connecting = mine;
271
+ const forget = () => {
272
+ if (this.connecting === mine)
273
+ this.connecting = undefined;
274
+ };
275
+ mine.promise.then((r) => {
276
+ if (!r.ok)
277
+ forget();
278
+ }, forget);
279
+ return mine.promise;
280
+ }
281
+ async resumeWalk(inFlight, snapshotId, options) {
282
+ if (inFlight !== undefined) {
283
+ await inFlight.promise;
284
+ if (this.destroyed)
285
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "env already destroyed") };
286
+ if (this.sbx && !this.paused && this.sbx.sandboxId === snapshotId)
287
+ return ok(this.workspaceHandle());
288
+ }
289
+ if (this.sbx && !this.paused)
290
+ await this.releaseMinted(snapshotId);
291
+ return this.doResumeVM(snapshotId, options);
292
+ }
293
+ async releaseMinted(resumed) {
294
+ const sbx = this.sbx;
295
+ if (sbx === undefined)
296
+ return;
297
+ this.sbx = undefined;
298
+ this.handle = undefined;
299
+ this.stopKeepAlive();
300
+ await this.disposeBackgroundShells();
301
+ await this.rpc("sandbox.kill", () => sbx.kill()).catch((e) => this.cfg.logger?.warn("remote_env_minted_release_failed", { sandboxId: sbx.sandboxId, error: errMsg(e) }));
302
+ this.cfg.logger?.info?.("remote_env_minted_released", { sandboxId: sbx.sandboxId, resumed });
175
303
  }
176
304
  async doResumeVM(snapshotId, options) {
177
305
  try {
@@ -180,6 +308,12 @@ export class RemoteContainerExecutionEnv {
180
308
  await sbx.kill().catch(() => { });
181
309
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during resume") };
182
310
  }
311
+ const restoredRoot = options?.priorHandle?.mountPath ?? this.cfg.mountPath;
312
+ await this.floor.attach(this.floorTransport(sbx), { origin: "resumed", workspaceRoot: restoredRoot });
313
+ if (this.destroyed) {
314
+ await sbx.kill().catch(() => { });
315
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during resume") };
316
+ }
183
317
  const home = await this.homeVerdict(sbx, options?.abortSignal);
184
318
  if (this.destroyed) {
185
319
  await sbx.kill().catch(() => { });
@@ -190,11 +324,22 @@ export class RemoteContainerExecutionEnv {
190
324
  this.cfg.metrics?.inc("remote_env_resume_total", { provider: PROVIDER, result: "error" });
191
325
  return { ok: false, error: home };
192
326
  }
327
+ const filled = await this.fillAt(sbx, restoredRoot, "restored");
328
+ if (this.destroyed) {
329
+ await sbx.kill().catch(() => { });
330
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during resume") };
331
+ }
332
+ if (!filled.ok) {
333
+ await this.rpc("sandbox.pause", (signal) => sbx.pause({ signal }), undefined, this.cfg.dataTimeoutMs).catch((e) => this.cfg.logger?.warn?.("remote_env_fill_refused_repause_failed", { sandboxId: sbx.sandboxId, error: errMsg(e) }));
334
+ this.cfg.metrics?.inc("remote_env_resume_total", { provider: PROVIDER, result: "error" });
335
+ if (filled.standing)
336
+ this.connectRefusal = filled.error;
337
+ return { ok: false, error: filled.error };
338
+ }
193
339
  this.sbx = sbx;
194
340
  this.paused = false;
195
341
  this.startKeepAlive();
196
342
  this.cfg.logger?.info?.("remote_env_resume", { requested: snapshotId, connected: sbx.sandboxId });
197
- const restoredRoot = options?.priorHandle?.mountPath ?? this.cfg.mountPath;
198
343
  this.cwd = restoredRoot;
199
344
  this.handle = { sandboxId: sbx.sandboxId, provider: PROVIDER, mountPath: restoredRoot, snapshotId, sessionToken: sbx.sandboxId };
200
345
  this.cfg.metrics?.inc("remote_env_resume_total", { provider: PROVIDER, result: "ok" });
@@ -227,7 +372,10 @@ export class RemoteContainerExecutionEnv {
227
372
  return undefined;
228
373
  let probe;
229
374
  try {
230
- const r = await this.rpc("sandbox.home", (s) => sbx.commands.run(LANE_HOME_PROBE_COMMAND, { signal: s }), signal);
375
+ const start = this.startIn(ADAPTER_START, LANE_HOME_PROBE_COMMAND, undefined);
376
+ if (!start.ok)
377
+ throw start.error;
378
+ const r = await this.rpc("sandbox.home", (s) => this.runIn(sbx, start.value, { signal: s }), signal);
231
379
  probe = r.exitCode === 0 ? { ok: true, stdout: r.stdout } : { ok: false, reason: `exit ${r.exitCode}` };
232
380
  }
233
381
  catch (e) {
@@ -240,9 +388,39 @@ export class RemoteContainerExecutionEnv {
240
388
  return undefined;
241
389
  const error = new RemoteExecutionError("connect_failed", verdict.refusal);
242
390
  if (verdict.answered)
243
- this.homeRefusal = error;
391
+ this.connectRefusal = error;
244
392
  return error;
245
393
  }
394
+ fillAt(sbx, root, origin) {
395
+ return this.cfg.workspaceFill === undefined ? Promise.resolve({ ok: true }) : this.cfg.workspaceFill.fill(this.fillChannel(sbx), root, origin);
396
+ }
397
+ fillChannel(sbx) {
398
+ return {
399
+ run: async (command) => {
400
+ const s = this.startIn(ADAPTER_START, command, undefined);
401
+ if (!s.ok)
402
+ return { ok: false, error: s.error.message };
403
+ try {
404
+ const r = await this.rpc("workspace.fill", (signal) => this.runIn(sbx, s.value, { signal, timeoutMs: this.cfg.dataTimeoutMs }), undefined, this.cfg.dataTimeoutMs);
405
+ return { ok: true, exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };
406
+ }
407
+ catch (e) {
408
+ if (e instanceof CommandExitError)
409
+ return { ok: true, exitCode: e.exitCode, stdout: e.stdout, stderr: e.stderr };
410
+ return { ok: false, error: errMsg(e) };
411
+ }
412
+ },
413
+ write: async (abs, bytes) => {
414
+ try {
415
+ await this.rpc("files.write", (signal) => sbx.files.write(abs, toArrayBuffer(bytes), { signal }), undefined, this.cfg.dataTimeoutMs);
416
+ return { ok: true };
417
+ }
418
+ catch (e) {
419
+ return { ok: false, error: errMsg(e) };
420
+ }
421
+ },
422
+ };
423
+ }
246
424
  startKeepAlive() {
247
425
  this.stopKeepAlive();
248
426
  const everyMs = Math.max(30_000, Math.floor(this.cfg.timeoutMs * 0.8));
@@ -265,6 +443,7 @@ export class RemoteContainerExecutionEnv {
265
443
  this.destroyed = true;
266
444
  this.connecting = undefined;
267
445
  this.stopKeepAlive();
446
+ this.floor.dispose();
268
447
  await this.disposeBackgroundShells();
269
448
  const sbx = this.sbx;
270
449
  this.sbx = undefined;
@@ -285,12 +464,21 @@ export class RemoteContainerExecutionEnv {
285
464
  await this.destroy();
286
465
  }
287
466
  async exec(command, options) {
467
+ return this.execAs(engineStart(options?.readFloor), command, options);
468
+ }
469
+ execInternal(command, options) {
470
+ return this.execAs(ADAPTER_START, command, options);
471
+ }
472
+ async execAs(start, command, options) {
288
473
  if (options?.abortSignal?.aborted)
289
474
  return { ok: false, error: new ExecutionError("aborted", "exec aborted before start") };
290
475
  const conn = await this.ensureConnected();
291
476
  if (!conn.ok)
292
477
  return { ok: false, error: new ExecutionError("shell_unavailable", `remote connect failed: ${conn.error.message}`, conn.error) };
293
478
  const sbx = conn.value;
479
+ const launch = this.startIn(start, command, options?.env);
480
+ if (!launch.ok)
481
+ return launch;
294
482
  this.activeCommands++;
295
483
  const detachSignal = options?.detachSignal;
296
484
  const tee = detachSignal
@@ -315,10 +503,9 @@ export class RemoteContainerExecutionEnv {
315
503
  let starting;
316
504
  let startCut = false;
317
505
  try {
318
- handle = await this.rpc("commands.run", () => (starting = sbx.commands.run(command, {
506
+ handle = await this.rpc("commands.run", () => (starting = this.runIn(sbx, launch.value, {
319
507
  background: true,
320
508
  cwd: this.resolve(options?.cwd ?? this.cwd),
321
- ...(options?.env ? { envs: options.env } : {}),
322
509
  timeoutMs: tee
323
510
  ? Math.max(this.commandTimeoutMs(options?.timeout), (E2B_BG_MAX_TIMEOUT_SEC + E2B_BG_PROVIDER_BACKSTOP_PAD_SEC) * 1000)
324
511
  : this.commandTimeoutMs(options?.timeout),
@@ -442,6 +629,9 @@ export class RemoteContainerExecutionEnv {
442
629
  if (!conn.ok)
443
630
  throw conn.error;
444
631
  const sbx = conn.value;
632
+ const launch = this.startIn(engineStart(undefined), command, options?.env);
633
+ if (!launch.ok)
634
+ throw new RemoteExecutionError("unsupported", launch.error.message, launch.error);
445
635
  const queue = [];
446
636
  let finished = false;
447
637
  let failure;
@@ -468,10 +658,9 @@ export class RemoteContainerExecutionEnv {
468
658
  let handle;
469
659
  let starting;
470
660
  try {
471
- handle = await this.rpc("commands.run", () => (starting = sbx.commands.run(command, {
661
+ handle = await this.rpc("commands.run", () => (starting = this.runIn(sbx, launch.value, {
472
662
  background: true,
473
663
  cwd: this.resolve(options?.cwd ?? this.cwd),
474
- ...(options?.env ? { envs: options.env } : {}),
475
664
  timeoutMs: this.commandTimeoutMs(options?.timeout),
476
665
  onStdout: (d) => push("stdout", d),
477
666
  onStderr: (d) => push("stderr", d),
@@ -599,12 +788,14 @@ export class RemoteContainerExecutionEnv {
599
788
  if (!conn.ok)
600
789
  throw new Error(`remote connect failed: ${conn.error.message}`);
601
790
  const sbx = conn.value;
791
+ const launch = self.startIn(engineStart(options?.readFloor), command, options?.env);
792
+ if (!launch.ok)
793
+ throw launch.error;
602
794
  const stdout = { tail: new RollingTailBuffer(), total: 0, cursor: 0 };
603
795
  const stderr = { tail: new RollingTailBuffer(), total: 0, cursor: 0 };
604
- const handle = await self.rpc("commands.run.bg", () => sbx.commands.run(command, {
796
+ const handle = await self.rpc("commands.run.bg", () => self.runIn(sbx, launch.value, {
605
797
  background: true,
606
798
  cwd: self.resolve(options?.cwd ?? self.cwd),
607
- ...(options?.env ? { envs: options.env } : {}),
608
799
  timeoutMs: (ctx.bgTimeoutSec + E2B_BG_PROVIDER_BACKSTOP_PAD_SEC) * 1000,
609
800
  onStdout: (d) => {
610
801
  const b = Buffer.from(d, "utf8");
@@ -722,7 +913,7 @@ export class RemoteContainerExecutionEnv {
722
913
  return { ok: false, error: this.fileError(e, dest, signal) };
723
914
  }
724
915
  },
725
- run: (cmd, signal) => this.exec(cmd, { cwd: "/", ...(signal !== undefined ? { abortSignal: signal } : {}) }),
916
+ run: (cmd, signal) => this.execInternal(cmd, { cwd: "/", ...(signal !== undefined ? { abortSignal: signal } : {}) }),
726
917
  };
727
918
  }
728
919
  async writeFile(p, content, abortSignal) {
@@ -764,7 +955,7 @@ export class RemoteContainerExecutionEnv {
764
955
  }
765
956
  async readLink(p, abortSignal) {
766
957
  const abs = this.resolve(p);
767
- const r = await this.exec(`readlink -- ${shellQuote(abs)}`, { abortSignal });
958
+ const r = await this.execInternal(`readlink -- ${shellQuote(abs)}`, { abortSignal });
768
959
  if (!r.ok)
769
960
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
770
961
  if (r.value.exitCode !== 0)
@@ -772,11 +963,11 @@ export class RemoteContainerExecutionEnv {
772
963
  return ok(r.value.stdout.trim());
773
964
  }
774
965
  async canonicalChain(p, abortSignal) {
775
- return runSymlinkChain(this.resolve(p), (cmd) => this.exec(cmd, { abortSignal }));
966
+ return runSymlinkChain(this.resolve(p), (cmd) => this.execInternal(cmd, { abortSignal }));
776
967
  }
777
968
  async canonicalPath(p, abortSignal) {
778
969
  const abs = this.resolve(p);
779
- const r = await this.exec(`realpath -- ${shellQuote(abs)}`, { abortSignal });
970
+ const r = await this.execInternal(`realpath -- ${shellQuote(abs)}`, { abortSignal });
780
971
  if (!r.ok)
781
972
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
782
973
  if (r.value.exitCode !== 0) {
@@ -823,7 +1014,7 @@ export class RemoteContainerExecutionEnv {
823
1014
  const abs = this.resolve(p);
824
1015
  if (options?.recursive) {
825
1016
  const flags = options.force ? "-rf" : "-r";
826
- const r = await this.exec(`rm ${flags} -- ${shellQuote(abs)}`, { abortSignal: options.abortSignal });
1017
+ const r = await this.execInternal(`rm ${flags} -- ${shellQuote(abs)}`, { abortSignal: options.abortSignal });
827
1018
  if (!r.ok)
828
1019
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
829
1020
  if (r.value.exitCode !== 0)
@@ -842,14 +1033,14 @@ export class RemoteContainerExecutionEnv {
842
1033
  }
843
1034
  }
844
1035
  async createTempDir(prefix = "tmp-", abortSignal) {
845
- const r = await this.exec(`mktemp -d -t ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal });
1036
+ const r = await this.execInternal(`mktemp -d -t ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal });
846
1037
  return this.tempResult(r, "mktemp -d");
847
1038
  }
848
1039
  async createTempFile(options) {
849
1040
  const prefix = options?.prefix ?? "";
850
1041
  const suffix = options?.suffix ?? "";
851
1042
  const suffixArg = suffix ? `--suffix=${shellQuote(suffix)} -t` : "-t";
852
- const r = await this.exec(`mktemp ${suffixArg} ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal: options?.abortSignal });
1043
+ const r = await this.execInternal(`mktemp ${suffixArg} ${shellQuote(`${prefix}XXXXXX`)}`, { abortSignal: options?.abortSignal });
853
1044
  return this.tempResult(r, "mktemp");
854
1045
  }
855
1046
  resolve(p) {
@@ -1017,10 +1208,15 @@ export class RemoteContainerExecutionEnv {
1017
1208
  }
1018
1209
  }
1019
1210
  export function e2bExecutionEnvFactory(config, deps) {
1020
- return (ctx) => new RemoteContainerExecutionEnv({
1021
- ...config,
1022
- metadata: { ...config.metadata, semaManaged: "1", sessionId: ctx.sessionId, ...(ctx.taskId ? { taskId: ctx.taskId } : {}) },
1023
- }, deps);
1211
+ const { workspaceFillOf, ...base } = config;
1212
+ return (ctx) => {
1213
+ const workspaceFill = workspaceFillOf?.(ctx);
1214
+ return new RemoteContainerExecutionEnv({
1215
+ ...base,
1216
+ metadata: { ...base.metadata, semaManaged: "1", sessionId: ctx.sessionId, ...(ctx.taskId ? { taskId: ctx.taskId } : {}) },
1217
+ ...(workspaceFill !== undefined ? { workspaceFill } : {}),
1218
+ }, deps);
1219
+ };
1024
1220
  }
1025
1221
  function errMsg(e) {
1026
1222
  return e instanceof Error ? e.message : String(e);
@@ -3,6 +3,8 @@ import { type SpaceCommitFence } from "../workspace-space-commit.js";
3
3
  import { type CacheMissMemory, type WorkspaceCacheCapability } from "../workspace-cache.js";
4
4
  import { type SandboxEgressGap, type SandboxIpv6 } from "../sandbox-egress-policy.js";
5
5
  import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll, type ExecutionEnvCapabilities } from "@sema-agent/core";
6
+ import { type SandboxReadFloorKnob } from "../execution-lane-caps.js";
7
+ import { type SandboxReadFloor } from "./sandbox-read-floor.js";
6
8
  import type { PodSpecPatch } from "./k8s-exec-protocol.js";
7
9
  import { type LaneHomeConfig } from "../lane-home-probe.js";
8
10
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
@@ -121,6 +123,9 @@ export interface K8sEnvConfig extends LaneHomeConfig {
121
123
  /** Run-end outcome seat — called once per `destroy()` that attempted a space snapshot (the factory's caller binds it
122
124
  * to the run, so the run's terminal frame can carry `workspaceSnapshot: "failed"`). */
123
125
  onWorkspaceSnapshot?: (outcome: "saved" | "failed") => void;
126
+ /** 恢复点的填充座(`workspace-fill.ts`;工厂按 run 装配):pod 的工作区铸出 / 恢复之后、`connect` / `resumeVM` 交还之前,经 {@link RemoteK8sExecutionEnv} 自己的
127
+ * exec 通道(不经公开面 —— 连接进行中公开面会等同一只连接记忆)往工作区里放内容。缺席 ⇒ 不放。 */
128
+ workspaceFill?: WorkspaceFill;
124
129
  /**
125
130
  * 🔴 ADDITIVE pod-spec patch (a SERVICE-side adapter seam, NOT a core seam: the K8s pod shape is
126
131
  * provider-specific, so it stays in the adapter per [ref]). A deployment supplies extra volumes / volumeMounts
@@ -162,11 +167,14 @@ export interface K8sEnvConfig extends LaneHomeConfig {
162
167
  readonly image: string;
163
168
  readonly origins: readonly string[];
164
169
  };
170
+ /** `SANDBOX_READ_FLOOR`(keep | off;缺席 ⇒ keep):本 pod 守不守读拒表的字节地板 = 读地板模块服务本车道 ∧ 本键不是 off(`sandbox-read-floor.ts`)。 */
171
+ sandboxReadFloor?: SandboxReadFloorKnob;
165
172
  }
166
173
  import { type SandboxSecretSeatKind, type SandboxSecretVerb } from "./k8s-exec-protocol.js";
167
174
  export { type PodSpecPatch, applyPodSpecPatch } from "./k8s-exec-protocol.js";
168
175
  import { type SandboxPodApi, type SandboxPodListing } from "./k8s-pod-reaper.js";
169
176
  import { type SandboxLeaseEntryInput } from "../sandbox-git-lease.js";
177
+ import type { WorkspaceFill } from "./workspace-fill.js";
170
178
  type ConnectResult = {
171
179
  ok: true;
172
180
  value: WorkspaceHandle;
@@ -179,6 +187,8 @@ export interface ExecSocket {
179
187
  on(event: "open" | "message" | "close" | "error", listener: (...args: unknown[]) => void): void;
180
188
  close(): void;
181
189
  terminate?(): void;
190
+ /** 写一帧(读地板中继的 stdin:首字节 = 通道 0)。只读的 exec 不用;没有它的套接字起不了中继(附着按 `relay_silent` 拒)。 */
191
+ send?(data: Buffer): void;
182
192
  }
183
193
  /**
184
194
  * Test seam ([ref] §9.3 fault-injection): inject the REST `request` and/or the exec-socket factory, so pod
@@ -199,6 +209,8 @@ export interface K8sEnvDeps {
199
209
  connectOnce?: (signal?: AbortSignal) => Promise<ConnectResult>;
200
210
  /** Worker-side object-store requests (the space pointer, listing, deletes). Default: global `fetch`. */
201
211
  fetch?: typeof fetch;
212
+ /** 这只环境手里的读地板模块(缺席 ⇒ `createSandboxReadFloor` 按旋钮选;钉注入守地板的模块)。能力块的 `keepsReadFloor` 读它的状态。 */
213
+ readFloor?: SandboxReadFloor;
202
214
  }
203
215
  /**
204
216
  * One run's space occupant — the space key of both lanes keyed by it (the space lane, the cache volume) — a closed set of three
@@ -290,8 +302,10 @@ export type SandboxLeaseWrite = {
290
302
  readonly message: string;
291
303
  };
292
304
  export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
293
- /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。快照店是它的输入(构造期铸,见下)。 */
305
+ /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。快照店与读地板旋钮是它的输入(构造期铸,见下);{@link floor} 由同一只判据、同一个旋钮值选出。 */
294
306
  readonly capabilities: ExecutionEnvCapabilities;
307
+ /** 读拒表字节地板模块:pod 里每一次 exec 起动都先经它的 `wrap`({@link runExec},唯一接缝)。 */
308
+ private readonly floor;
295
309
  cwd: string;
296
310
  private podName?;
297
311
  /** Cluster-internal pod address, captured when the pod reaches Running — the LSP `getHost` surface.
@@ -308,9 +322,12 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
308
322
  private readonly inFlight;
309
323
  /** Resolvers of {@link whenIdle}: woken when the last command in flight ends. */
310
324
  private idleWaiters;
311
- /** This env's one connect in flight (see {@link connect}): the explicit connect, the lazy connect and `reconnect` that arrive while it runs
312
- * share it — one pod creation, one restore (the E2B race lesson). Cleared the moment it settles, whatever the outcome. */
325
+ /** This env's one lifecycle walk in flight — a connect ({@link connect}) or a resume ({@link resumeVM}): the explicit connect, the lazy connect of
326
+ * the first operation and `reconnect` that arrive while it runs share its answer — one pod creation, one restore, one fill (the E2B race lesson).
327
+ * Cleared the moment it settles, whatever the outcome (by the walk that set it, never by an earlier one). */
313
328
  private connectInFlight?;
329
+ /** The resume among them, with the snapshot it restores: a second `resumeVM` of the same snapshot shares it (one restore, one pod). */
330
+ private resumeInFlight?;
314
331
  /** The env's refusal — a fact of this env until `destroy`, written once ({@link holdRefusal}) and answered as it stands to every later connect (and to
315
332
  * every later pod creation: {@link provision}): the same refusal, zero pods, zero store requests, no second line. Two causes write it:
316
333
  * the space restore (`latest` names an archive that is there and did not come back — {@link restoreSnapshot}) and the pod's admission (the cluster created
@@ -345,6 +362,7 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
345
362
  private cacheUnavailableSaid;
346
363
  private readonly logger?;
347
364
  private readonly onWorkspaceSnapshot?;
365
+ private readonly workspaceFill?;
348
366
  private readonly cacheMisses?;
349
367
  constructor(config: K8sEnvConfig, deps?: K8sEnvDeps);
350
368
  /** The lease seat of the pod this env holds now — `undefined` without a broker seat or without a pod (the server's lease writes follow it: a resumed env holds a new pod and a new seat). */
@@ -381,6 +399,19 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
381
399
  connect(config?: RemoteConnectConfig): Promise<ConnectResult>;
382
400
  /** The one walk of a connect ({@link connect} runs it once at a time). */
383
401
  private establish;
402
+ /**
403
+ * The restore point's fill (`workspace-fill.ts`): a pod whose workspace this env just minted or restored is handed over only once the fill seat placed
404
+ * its content — through {@link fillChannel} (this adapter's own exec, never the public face: inside a connect the public face waits on that very connect).
405
+ * A fill that does not complete releases the pod (no tool and no run-end snapshot may see a half-filled workspace); a refusal that would answer the same
406
+ * again (`standing`) is held for the env ({@link holdRefusal}). No seat ⇒ the pod as it came. `origin` tells the seat whether the workspace was minted
407
+ * (a new pod with nothing restored: the space off / unavailable / without `latest`, or `latest` dangling) or restored (the space's `latest`, or a park snapshot whose archive came back; a park snapshot with no archive — a plan-only park — is minted).
408
+ */
409
+ private settle;
410
+ /** The restore point's channel: a fixed command through the one start seam (start form `adapter`, the engine's commands' identity) and a write in
411
+ * {@link writeChunked}'s form — both straight on {@link runExec} with the data bound (never {@link ensureConnected}). */
412
+ private fillChannel;
413
+ /** Release the pod this env holds (its workspace is not handed over): the pod deleted, its seats cleared. No pod ⇒ nothing. */
414
+ private releasePod;
384
415
  /** This env holds a workspace its `destroy()` will commit ⇒ arm its seat on the space's commit fence (from here the
385
416
  * run's terminal write fences the space; `workspace-space-commit.ts`). Each write of this run's row sets its deadline to
386
417
  * now + the upload's own wall-clock bound (`dataTimeoutMs`). No-op unless the space lane is open. */
@@ -622,7 +653,9 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
622
653
  /**
623
654
  * Resume a workspace snapshot onto a FRESH pod (resume = executionEnvFactory rebuild + resumeVM):
624
655
  * provision a pod, then download + untar the snapshot into the workspace ({@link restoreSnapshot}, lane `park`). A
625
- * missing object (empty/plan-only snapshot) resumes as a clean workspace.
656
+ * missing object (a plan-only park: no pod was ever minted, so nothing was uploaded) resumes as a clean workspace whose origin is `minted` —
657
+ * no restorable tree — so the restore point seeds it; the resume walks the park lane and never reads the space's `latest` (which a fresh connect with the space on
658
+ * would restore); a snapshot that came back is `restored` and never seeded.
626
659
  *
627
660
  * [ref]①(priorHandle 根保真)——k8s 走**诚实臂**而非绑定臂:pod 的 workspace volume 挂在**当前**
628
661
  * 配置根(cfg.mountPath),往 priorHandle.mountPath 绑=往容器 rootfs untar 大工作区,ephemeral-storage
@@ -631,10 +664,13 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
631
664
  * 变更窗的分歧由 core 侧 fail-closed reopen 兜底,审批不烧错根。
632
665
  */
633
666
  resumeVM(snapshotId: SnapshotId, options?: VmLifecycleOptions): Promise<ConnectResult>;
667
+ /** The one walk of a resume ({@link resumeVM} runs it under the connect gate). */
668
+ private resumeWalk;
634
669
  /**
635
670
  * The ONE tar download (both lanes): provision a FRESH pod, download + untar the lane's archive into the workspace.
636
- * A missing archive (404) starts from the clean workspace on both lanes — what differs is what it means:
637
- * - park: a plan-only suspend has no object — nothing to say;
671
+ * A missing archive (404) starts from the clean workspace on both lanes, and on both it is the same fact — no restorable tree ⇒ origin
672
+ * `minted` (the restore point seeds it, exactly as on a fresh connect). What differs is only what it means:
673
+ * - park: a plan-only suspend has no object (the park came before anything touched the hand) — nothing to say;
638
674
  * - space: `latest` dangles (its pointer is only written after a successful upload, so the archive was removed later —
639
675
  * by the bucket's lifecycle rule or a hand): the data is gone by an operator rule ⇒ loud and continuable —
640
676
  * {@link dropDanglingLatest} (one `workspace_latest_dangling` line, the pointer deleted); the run's own run-end
@@ -731,6 +767,15 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
731
767
  * being claimed) uploads nothing, and is that same upload failure.
732
768
  */
733
769
  private persistWorkspace;
770
+ /**
771
+ * 守地板的 pod 的二进制座:本包带的每个架构一只 ConfigMap(名 = 架构 + 摘要前 16 位,不可变;201 = 新建,409 = 同名同内容已在)。建不成
772
+ * ⇒ 告诉地板模块(附着按 `binary_digest` 拒,pod 照起、不带这只卷);不守地板 ⇒ `undefined`(pod 与改前逐字同)。部署前提:命名空间里 configmaps 的 create。
773
+ */
774
+ private ensureFloorSeat;
775
+ /** 地板模块的三件原语。引导命令经 {@link runExec}(起动形 `bootstrap`:只在附着中合法);放置由 pod 的只读卷完成(自检 `--digest` 证字节);
776
+ * 中继 = 一只开着 stdin 的长连 exec(与命令同一身份 —— k8s 的 exec 没有换身份的口),stdout 帧是中继的问,stdin 帧(首字节 0)是判官的答。 */
777
+ private floorTransport;
778
+ /** exec 的 URL:`argv` 原样作 `command` 参数(唯一来源 = {@link runExec} 里模块答案的编码,或地板模块自己的中继);`stdin` ⇒ 开 stdin 通道。 */
734
779
  private execUrl;
735
780
  /**
736
781
  * Run a command over the exec WebSocket. Frames: byte0=channel (1 stdout / 2 stderr / 3 status JSON with the