@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.
- package/MIGRATION.md +505 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +142 -25
- package/dist/approval-ask-audit-store.d.ts +54 -38
- package/dist/approval-ask-audit-store.js +58 -39
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +39 -24
- package/dist/approval-card.js +6 -3
- package/dist/approval-crash-convergence.d.ts +17 -7
- package/dist/approval-crash-convergence.js +94 -22
- package/dist/approval-deny-reasons.d.ts +34 -30
- package/dist/approval-deny-reasons.js +21 -6
- package/dist/approval-policy-settlement.d.ts +1 -1
- package/dist/approval-reconciler.d.ts +33 -1
- package/dist/approval-reconciler.js +43 -40
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/auth-bridge.d.ts +9 -6
- package/dist/auth-bridge.js +9 -3
- package/dist/boot/adoption.js +2 -1
- package/dist/boot/budget-tracing.d.ts +16 -0
- package/dist/boot/budget-tracing.js +10 -6
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.d.ts +24 -4
- package/dist/boot/execution-env.js +34 -27
- package/dist/boot/execution-lane.js +2 -1
- package/dist/boot/exit-boundary.js +5 -4
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/memory-consolidation.js +3 -2
- package/dist/boot/parked-revive-gate.js +2 -1
- package/dist/boot/permission-rules-audit.d.ts +0 -28
- package/dist/boot/permission-rules-audit.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -1
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/reapers.js +4 -3
- package/dist/boot/resolve-spec.js +18 -6
- package/dist/boot/retention-lane.js +5 -3
- package/dist/boot/runner-deps.js +16 -13
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +8 -6
- package/dist/boot/shutdown.js +8 -2
- package/dist/boot/stage-04-stores.js +3 -2
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.js +11 -7
- package/dist/boot/stage-06-runners.d.ts +1 -3
- package/dist/boot/stage-06-runners.js +3 -2
- package/dist/boot/stage-07-capability-layer.d.ts +1 -2
- package/dist/boot/stage-07-capability-layer.js +10 -11
- package/dist/boot/stage-08-reapers.d.ts +1 -2
- package/dist/boot/stage-08-reapers.js +3 -2
- package/dist/boot/stage-09-leader.d.ts +1 -2
- package/dist/boot/stage-10-http-server.d.ts +1 -2
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.js +13 -6
- package/dist/boot/web-admin.js +18 -17
- package/dist/boot/webfetch-summarize-lane.js +2 -1
- package/dist/boot/workflow-orchestration.js +3 -2
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/builtin-tools.js +2 -4
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +33 -0
- package/dist/capability-facts.js +32 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +12 -8
- package/dist/config-provider.d.ts +13 -13
- package/dist/config-provider.js +19 -33
- package/dist/config-types.d.ts +39 -10
- package/dist/config.d.ts +6 -0
- package/dist/config.js +72 -49
- package/dist/delegated-session-owner.d.ts +21 -19
- package/dist/delegated-session-owner.js +1 -1
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +50 -2
- package/dist/execution-lane-caps.js +90 -13
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/fleet/fleet-bus.d.ts +6 -3
- package/dist/hooks/hook-runner.d.ts +6 -0
- package/dist/hooks/hook-runner.js +30 -1
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +8 -2
- package/dist/http/execution-context.d.ts +2 -2
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/principal-gate.d.ts +3 -1
- package/dist/http/principal-gate.js +17 -8
- package/dist/http/resume-legs.js +42 -29
- package/dist/http/route-table.js +4 -0
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/attachments.js +18 -0
- package/dist/http/routes/capabilities.js +10 -6
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-policy.js +27 -7
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/runs.js +3 -3
- package/dist/http/routes/session-memory-sync.d.ts +63 -0
- package/dist/http/routes/session-memory-sync.js +157 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions-list.d.ts +2 -1
- package/dist/http/routes/sessions.js +5 -4
- package/dist/http/routes/tasks.js +70 -71
- package/dist/http/run-read-face.d.ts +7 -0
- package/dist/http/run-read-face.js +10 -4
- package/dist/http/send.d.ts +6 -1
- package/dist/http/send.js +10 -3
- package/dist/http/server.d.ts +20 -7
- package/dist/http/server.js +2 -4
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +2 -2
- package/dist/http/sync-submit-body.d.ts +21 -4
- package/dist/http/sync-submit-body.js +13 -0
- package/dist/identity-source.d.ts +21 -2
- package/dist/identity-source.js +6 -0
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-incident-sink.d.ts +19 -6
- package/dist/memory-incident-sink.js +8 -4
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-scope.d.ts +4 -4
- package/dist/memory-scope.js +1 -1
- package/dist/memory-sync-client.d.ts +35 -23
- package/dist/memory-sync-client.js +122 -52
- package/dist/memory-sync-identity.d.ts +24 -0
- package/dist/memory-sync-identity.js +57 -0
- package/dist/memory-sync.d.ts +7 -1
- package/dist/memory-sync.js +6 -3
- package/dist/observability/fail-open.d.ts +57 -12
- package/dist/observability/fail-open.js +50 -9
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +93 -31
- package/dist/observability/run-terminal.js +175 -171
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/approval-ask-store-file.d.ts +14 -4
- package/dist/plugins/approval-ask-store-file.js +48 -7
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +9 -8
- package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
- package/dist/plugins/approval-ask-store-sql.js +49 -19
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +74 -3
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +4 -1
- package/dist/plugins/execution-placement-sql.d.ts +11 -1
- package/dist/plugins/execution-placement-sql.js +12 -1
- package/dist/plugins/file-run-store.d.ts +7 -2
- package/dist/plugins/file-run-store.js +12 -2
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +260 -39
- package/dist/plugins/local-session-store.js +582 -57
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/local-task-attachment-store.d.ts +12 -2
- package/dist/plugins/local-task-attachment-store.js +67 -43
- package/dist/plugins/memory-key-guards.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +7 -2
- package/dist/plugins/memory-run-store.js +12 -2
- package/dist/plugins/one-time-migrations.d.ts +68 -2
- package/dist/plugins/one-time-migrations.js +120 -4
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-e2b.d.ts +47 -7
- package/dist/plugins/remote-env-e2b.js +242 -46
- package/dist/plugins/remote-env-k8s.d.ts +51 -6
- package/dist/plugins/remote-env-k8s.js +234 -44
- package/dist/plugins/remote-env-local-docker.d.ts +40 -14
- package/dist/plugins/remote-env-local-docker.js +127 -33
- package/dist/plugins/run-store-sql.d.ts +11 -3
- package/dist/plugins/run-store-sql.js +13 -4
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/store-backend.d.ts +41 -7
- package/dist/plugins/store-backend.js +22 -4
- package/dist/plugins/store-contracts.d.ts +35 -3
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +112 -14
- package/dist/plugins/task-attachment-store.js +221 -28
- package/dist/plugins/tidb-pool.js +2 -1
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +3 -2
- package/dist/plugins/workspace-fill.d.ts +60 -0
- package/dist/plugins/workspace-fill.js +106 -0
- package/dist/project-bundle.d.ts +38 -0
- package/dist/project-bundle.js +609 -0
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +2 -8
- package/dist/runs.d.ts +39 -12
- package/dist/runs.js +69 -55
- package/dist/sandbox-egress-policy.d.ts +4 -1
- package/dist/sandbox-egress-policy.js +3 -1
- package/dist/sandbox-git-plane.d.ts +31 -12
- package/dist/sandbox-git-plane.js +24 -4
- package/dist/security.d.ts +11 -2
- package/dist/security.js +8 -0
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +46 -4
- package/dist/session-error-wire.js +23 -0
- package/dist/session-incarnation.d.ts +9 -3
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +62 -27
- package/dist/task-settings.js +105 -29
- package/dist/terminal.d.ts +97 -20
- package/dist/terminal.js +107 -18
- package/dist/tool-approval.d.ts +4 -3
- package/dist/tool-approval.js +12 -14
- package/dist/trace/core-keyset-guard.d.ts +196 -2
- package/dist/trace/engine-notice-wire.d.ts +8 -2
- package/dist/trace/engine-notice-wire.js +4 -2
- package/dist/trace/exit-seat.d.ts +9 -1
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +8 -4
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -0
- package/dist/trace/redact.js +323 -90
- package/dist/trace/self-minted-wire.d.ts +419 -4
- package/dist/trace/self-minted-wire.js +145 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +22 -2
- package/dist/web-admin-client/candidate-gate.d.ts +1 -1
- package/dist/web-admin-client/effective-keys.js +2 -0
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/dist/web-admin-client/types.d.ts +4 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.141.0.json +367 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
79
|
-
return { ok: false, error: this.
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
259
|
+
resumeVM(snapshotId, options) {
|
|
162
260
|
if (this.destroyed)
|
|
163
|
-
return { ok: false, error: new RemoteExecutionError("connect_failed", "env already destroyed") };
|
|
164
|
-
if (this.
|
|
165
|
-
return { ok: false, error: this.
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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.
|
|
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 =
|
|
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 =
|
|
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", () =>
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
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
|
|
312
|
-
*
|
|
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 (
|
|
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
|
|
637
|
-
*
|
|
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
|