@sema-agent/server 7.138.0 → 7.140.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 +457 -7
- package/README.md +7 -4
- package/README.zh-CN.md +6 -4
- package/USAGE.md +91 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +26 -26
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +28 -34
- package/dist/boot/execution-lane.d.ts +9 -1
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/governance-seams.d.ts +3 -2
- package/dist/boot/governance-seams.js +9 -11
- package/dist/boot/memory-boundary.d.ts +14 -51
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +15 -11
- package/dist/boot/memory-consolidation.js +25 -14
- package/dist/boot/resolve-spec.d.ts +4 -6
- package/dist/boot/resolve-spec.js +10 -13
- package/dist/boot/retention-lane.d.ts +5 -5
- package/dist/boot/retention-lane.js +0 -6
- package/dist/boot/runner-deps.d.ts +6 -4
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +2 -6
- package/dist/boot/stage-06-runners.js +2 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -6
- package/dist/boot/stage-08-reapers.d.ts +2 -6
- package/dist/boot/stage-09-leader.d.ts +2 -6
- package/dist/boot/stage-09-leader.js +1 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -6
- package/dist/boot/stage-10-http-server.js +7 -6
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +27 -40
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +37 -11
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/mcp-probe.js +1 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +15 -7
- package/dist/capability-facts.js +8 -3
- package/dist/config-catalog.js +9 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +33 -37
- package/dist/config-provider.js +51 -30
- package/dist/config-types.d.ts +21 -7
- package/dist/config.d.ts +33 -0
- package/dist/config.js +46 -16
- package/dist/delegated-session-owner.d.ts +4 -3
- package/dist/delegated-session-owner.js +1 -1
- package/dist/deployment-shape.d.ts +10 -9
- package/dist/deployment-shape.js +4 -4
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +115 -105
- package/dist/execution-lane-caps.js +76 -82
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/durable-run-leg.js +9 -5
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +63 -36
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-ctx.d.ts +14 -8
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +8 -1
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +5 -0
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +143 -22
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +4 -5
- package/dist/http/routes/tasks.js +41 -33
- package/dist/http/server.d.ts +40 -44
- package/dist/http/server.js +13 -2
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +8 -12
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +29 -5
- package/dist/memory-scope.js +16 -4
- package/dist/memory-sync-client.d.ts +70 -18
- package/dist/memory-sync-client.js +215 -117
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.js +0 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +34 -9
- package/dist/parked-decide.js +12 -19
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +13 -7
- package/dist/peer-seat.js +37 -3
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +44 -26
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -2
- package/dist/plugins/caching-session-store.js +9 -3
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
- package/dist/plugins/checkpoint-store-sql.js +21 -23
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +68 -122
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +9 -8
- package/dist/plugins/file-history-store-sql.js +9 -9
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-session-store.d.ts +149 -50
- package/dist/plugins/local-session-store.js +382 -107
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -8
- package/dist/plugins/one-time-migrations.js +408 -38
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -19
- package/dist/plugins/permission-rule-store-sql.js +29 -32
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +31 -20
- package/dist/plugins/pg-session-storage.js +120 -52
- package/dist/plugins/remote-env-adb.d.ts +18 -19
- package/dist/plugins/remote-env-adb.js +91 -66
- package/dist/plugins/remote-env-device.d.ts +23 -31
- package/dist/plugins/remote-env-device.js +19 -17
- package/dist/plugins/remote-env-e2b.d.ts +27 -21
- package/dist/plugins/remote-env-e2b.js +91 -45
- package/dist/plugins/remote-env-file-error.d.ts +3 -1
- package/dist/plugins/remote-env-file-error.js +9 -3
- package/dist/plugins/remote-env-host.d.ts +31 -34
- package/dist/plugins/remote-env-host.js +91 -72
- package/dist/plugins/remote-env-k8s.d.ts +13 -29
- package/dist/plugins/remote-env-k8s.js +63 -55
- package/dist/plugins/remote-env-local-docker.d.ts +24 -12
- package/dist/plugins/remote-env-local-docker.js +49 -61
- package/dist/plugins/remote-env-ssh.d.ts +11 -19
- package/dist/plugins/remote-env-ssh.js +74 -57
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +10 -7
- package/dist/plugins/retention-store-sql.js +13 -7
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +20 -15
- package/dist/plugins/schema-consistency.d.ts +21 -11
- package/dist/plugins/schema-consistency.js +15 -9
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-fork.d.ts +54 -0
- package/dist/plugins/session-fork.js +38 -1
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +5 -7
- package/dist/plugins/store-backend.js +23 -11
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +13 -7
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +57 -26
- package/dist/plugins/tidb-session-store.js +127 -59
- package/dist/plugins/tool-result-store-sql.d.ts +5 -4
- package/dist/plugins/tool-result-store-sql.js +21 -13
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +34 -35
- package/dist/runs.d.ts +10 -5
- package/dist/runs.js +52 -66
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +9 -5
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +20 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +22 -9
- package/dist/task-mcp.js +17 -9
- package/dist/terminal.d.ts +27 -0
- package/dist/terminal.js +6 -0
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +8 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +80 -28
- package/dist/trace/self-minted-wire.d.ts +169 -2
- package/dist/trace/self-minted-wire.js +54 -1
- package/dist/url-refusal.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/dist/web-admin-client/skills-mcp.js +3 -2
- package/dist/web-admin-client/types.d.ts +6 -0
- package/package.json +3 -3
- package/wire-changes/server@7.139.0.json +348 -0
- package/wire-changes/server@7.140.0.json +184 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
|
@@ -4,10 +4,12 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { hostSubprocessEnv } from "../server-secret-env.js";
|
|
6
6
|
import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
|
|
7
|
-
import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
|
|
7
|
+
import { appendFileStaged, rewriteAppend, buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace, writeFileStaged } from "./remote-shell.js";
|
|
8
8
|
import { signalNumber } from "./host-platform.js";
|
|
9
9
|
import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
10
|
+
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
10
11
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
12
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
11
13
|
import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
12
14
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
13
15
|
const PROVIDER = "adb";
|
|
@@ -20,14 +22,14 @@ function lazyConnectExecError(e) {
|
|
|
20
22
|
return e.code === "aborted" ? new ExecutionError("aborted", e.message, e) : new ExecutionError("shell_unavailable", `ADB lazy-connect failed: ${e.message}`, e);
|
|
21
23
|
}
|
|
22
24
|
function lazyConnectFileError(e, absPath) {
|
|
23
|
-
return
|
|
25
|
+
return fileErrorFromExec(e, absPath);
|
|
24
26
|
}
|
|
25
27
|
const isTcp = (serial) => /:\d+$/.test(serial);
|
|
26
28
|
export function isAdbTransportLost(stderr) {
|
|
27
29
|
return /device offline|device '.*' not found|no devices\/emulators found|connection reset|protocol fault|device disconnected/i.test(stderr);
|
|
28
30
|
}
|
|
29
31
|
export class RemoteAdbExecutionEnv {
|
|
30
|
-
capabilities = {
|
|
32
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "adb" } });
|
|
31
33
|
cwd;
|
|
32
34
|
connected = false;
|
|
33
35
|
destroyed = false;
|
|
@@ -36,6 +38,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
36
38
|
return this.homeSeat.home;
|
|
37
39
|
}
|
|
38
40
|
homeSeat;
|
|
41
|
+
homeRefusal;
|
|
39
42
|
cdPath = "unknown";
|
|
40
43
|
withheldEnv = [];
|
|
41
44
|
cfg;
|
|
@@ -51,7 +54,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
51
54
|
retry: { maxAttempts: config.retry?.maxAttempts ?? 3, backoffMs: config.retry?.backoffMs ?? 800 },
|
|
52
55
|
};
|
|
53
56
|
this.cwd = this.cfg.mountPath;
|
|
54
|
-
this.homeSeat = new LaneHomeSeat(
|
|
57
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
55
58
|
this.spawnFn = deps?.spawn ?? spawn;
|
|
56
59
|
this.injectedConnectOnce = deps?.connectOnce;
|
|
57
60
|
}
|
|
@@ -64,13 +67,22 @@ export class RemoteAdbExecutionEnv {
|
|
|
64
67
|
async connect(config) {
|
|
65
68
|
if (this.destroyed)
|
|
66
69
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "ADB env destroyed — create a fresh env instead of reviving") };
|
|
70
|
+
if (this.homeRefusal)
|
|
71
|
+
return { ok: false, error: this.homeRefusal };
|
|
67
72
|
const policy = {
|
|
68
73
|
retryableCodes: ["auth_transient", "connect_failed", "timeout"],
|
|
69
74
|
maxAttempts: this.cfg.retry.maxAttempts,
|
|
70
75
|
backoffMs: (failed) => this.cfg.retry.backoffMs * failed,
|
|
71
76
|
};
|
|
72
77
|
const once = this.injectedConnectOnce ?? ((s) => this.connectOnce(s));
|
|
73
|
-
|
|
78
|
+
const refused = new AbortController();
|
|
79
|
+
const halt = config?.abortSignal ? AbortSignal.any([config.abortSignal, refused.signal]) : refused.signal;
|
|
80
|
+
return withRetry(async () => {
|
|
81
|
+
const r = await once(config?.abortSignal);
|
|
82
|
+
if (!r.ok && this.homeRefusal)
|
|
83
|
+
refused.abort();
|
|
84
|
+
return r;
|
|
85
|
+
}, policy, { signal: halt });
|
|
74
86
|
}
|
|
75
87
|
async ensureConnected() {
|
|
76
88
|
if (this.destroyed)
|
|
@@ -126,7 +138,13 @@ export class RemoteAdbExecutionEnv {
|
|
|
126
138
|
const home = await this.adb(this.deviceShellArgs(LANE_HOME_PROBE_COMMAND), { signal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
127
139
|
if (!home.ok && home.error.code === "aborted")
|
|
128
140
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted while reading the device's home") };
|
|
129
|
-
this.homeSeat.settle(home.ok && home.value.exitCode === 0 ? { ok: true, stdout: home.value.stdout } : { ok: false, reason: home.ok ? `exit ${home.value.exitCode}` : home.error.message });
|
|
141
|
+
const verdict = this.homeSeat.settle(home.ok && home.value.exitCode === 0 ? { ok: true, stdout: home.value.stdout } : { ok: false, reason: home.ok ? `exit ${home.value.exitCode}` : home.error.message });
|
|
142
|
+
if (verdict !== undefined) {
|
|
143
|
+
const error = new RemoteExecutionError("connect_failed", verdict.refusal);
|
|
144
|
+
if (verdict.answered)
|
|
145
|
+
this.homeRefusal = error;
|
|
146
|
+
return { ok: false, error };
|
|
147
|
+
}
|
|
130
148
|
}
|
|
131
149
|
this.connected = true;
|
|
132
150
|
return ok(this.workspaceHandle());
|
|
@@ -341,57 +359,46 @@ export class RemoteAdbExecutionEnv {
|
|
|
341
359
|
lines = lines.slice(0, options.maxLines);
|
|
342
360
|
return ok(lines);
|
|
343
361
|
}
|
|
362
|
+
stagedLane(content) {
|
|
363
|
+
return { upload: (dest, signal) => this.push(content, dest, signal), run: (cmd, signal) => this.fsExec(cmd, signal) };
|
|
364
|
+
}
|
|
344
365
|
async writeFile(p, content, abortSignal) {
|
|
366
|
+
const gated = await this.fsGate(p);
|
|
367
|
+
if (gated)
|
|
368
|
+
return gated;
|
|
369
|
+
return writeFileStaged(this.stagedLane(content), this.resolve(p), abortSignal);
|
|
370
|
+
}
|
|
371
|
+
async appendFile(p, content, abortSignal) {
|
|
345
372
|
const gated = await this.fsGate(p);
|
|
346
373
|
if (gated)
|
|
347
374
|
return gated;
|
|
348
375
|
const abs = this.resolve(p);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return { ok: false, error: fileErrorFromExec(mk.error, dir) };
|
|
354
|
-
if (mk.value.exitCode !== 0)
|
|
355
|
-
return { ok: false, error: new FileError("unknown", mk.value.stderr.trim() || `mkdir -p failed for ${dir}`, dir) };
|
|
356
|
-
}
|
|
357
|
-
let local;
|
|
376
|
+
return appendFileStaged(this.stagedLane(content), abs, abortSignal, (ctx) => rewriteAppend((signal) => this.readBinaryFile(abs, signal), (bytes, signal) => this.push(bytes, abs, signal), content, ctx));
|
|
377
|
+
}
|
|
378
|
+
async push(content, dest, abortSignal) {
|
|
379
|
+
let localDir;
|
|
358
380
|
try {
|
|
359
|
-
|
|
381
|
+
localDir = await fs.mkdtemp(path.join(os.tmpdir(), "sema-adb-push-"));
|
|
382
|
+
const local = path.join(localDir, "data");
|
|
360
383
|
await fs.writeFile(local, typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content));
|
|
361
|
-
const r = await this.adb(["-s", this.cfg.serial, "push", local,
|
|
384
|
+
const r = await this.adb(["-s", this.cfg.serial, "push", local, dest], { signal: abortSignal, timeoutMs: this.cfg.dataTimeoutMs });
|
|
362
385
|
if (!r.ok)
|
|
363
|
-
return { ok: false, error: fileErrorFromExec(r.error,
|
|
386
|
+
return { ok: false, error: fileErrorFromExec(r.error, dest) };
|
|
364
387
|
const out = `${r.value.stdout} ${r.value.stderr}`;
|
|
365
388
|
if (/permission denied/i.test(out))
|
|
366
|
-
return { ok: false, error: new FileError("permission_denied", out.trim(),
|
|
389
|
+
return { ok: false, error: new FileError("permission_denied", out.trim(), dest) };
|
|
367
390
|
if (r.value.exitCode !== 0)
|
|
368
|
-
return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `adb push failed for ${
|
|
391
|
+
return { ok: false, error: new FileError("unknown", r.value.stderr.trim() || `adb push failed for ${dest}`, dest) };
|
|
369
392
|
return ok(undefined);
|
|
370
393
|
}
|
|
371
394
|
catch (e) {
|
|
372
|
-
return { ok: false, error: new FileError("unknown", e instanceof Error ? e.message : String(e),
|
|
395
|
+
return { ok: false, error: new FileError("unknown", e instanceof Error ? e.message : String(e), dest) };
|
|
373
396
|
}
|
|
374
397
|
finally {
|
|
375
|
-
if (
|
|
376
|
-
await fs.rm(
|
|
398
|
+
if (localDir)
|
|
399
|
+
await fs.rm(localDir, { recursive: true, force: true }).catch(() => { });
|
|
377
400
|
}
|
|
378
401
|
}
|
|
379
|
-
async appendFile(p, content, abortSignal) {
|
|
380
|
-
const abs = this.resolve(p);
|
|
381
|
-
const existing = await this.readBinaryFile(abs, abortSignal);
|
|
382
|
-
let base;
|
|
383
|
-
if (existing.ok)
|
|
384
|
-
base = existing.value;
|
|
385
|
-
else if (existing.error.code === "not_found")
|
|
386
|
-
base = new Uint8Array(0);
|
|
387
|
-
else
|
|
388
|
-
return existing;
|
|
389
|
-
const add = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
390
|
-
const merged = new Uint8Array(base.length + add.length);
|
|
391
|
-
merged.set(base, 0);
|
|
392
|
-
merged.set(add, base.length);
|
|
393
|
-
return this.writeFile(abs, merged, abortSignal);
|
|
394
|
-
}
|
|
395
402
|
posixFsInst;
|
|
396
403
|
get posixFs() {
|
|
397
404
|
return (this.posixFsInst ??= createPosixShellFs({
|
|
@@ -451,23 +458,44 @@ export class RemoteAdbExecutionEnv {
|
|
|
451
458
|
const outBufs = [];
|
|
452
459
|
const outTail = binary ? undefined : new RollingTailBuffer();
|
|
453
460
|
const errBuf = new RollingTailBuffer();
|
|
454
|
-
const
|
|
461
|
+
const recordStderr = (bytes) => {
|
|
462
|
+
if (bytes.length > 0)
|
|
463
|
+
errBuf.push(bytes);
|
|
464
|
+
};
|
|
465
|
+
const emitStderr = (bytes) => {
|
|
466
|
+
if (bytes.length > 0)
|
|
467
|
+
opts?.onStderr?.(bytes.toString());
|
|
468
|
+
};
|
|
469
|
+
const takeStderr = (bytes) => {
|
|
470
|
+
recordStderr(bytes);
|
|
471
|
+
emitStderr(bytes);
|
|
472
|
+
};
|
|
473
|
+
const heldStderr = () => opts?.stderrFilter?.endStderr();
|
|
474
|
+
const captured = () => {
|
|
475
|
+
const held = heldStderr();
|
|
476
|
+
if (held)
|
|
477
|
+
recordStderr(held);
|
|
478
|
+
return buildCapturedOutput(outTail, errBuf);
|
|
479
|
+
};
|
|
480
|
+
const finish = (outcome) => {
|
|
455
481
|
if (settled)
|
|
456
|
-
return;
|
|
482
|
+
return false;
|
|
457
483
|
settled = true;
|
|
484
|
+
const r = outcome();
|
|
458
485
|
clearTimeout(timer);
|
|
459
486
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
460
487
|
resolve(r);
|
|
488
|
+
return true;
|
|
461
489
|
};
|
|
462
490
|
const timeoutMs = opts?.timeoutMs ?? this.cfg.commandTimeoutMs;
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
killGrace();
|
|
469
|
-
finish({ ok: false, error: new ExecutionError("aborted", "adb command aborted") });
|
|
491
|
+
const cut = (c) => {
|
|
492
|
+
finish(() => {
|
|
493
|
+
killGrace();
|
|
494
|
+
return { ok: false, error: createCutError(c, captured()) };
|
|
495
|
+
});
|
|
470
496
|
};
|
|
497
|
+
const timer = setTimeout(() => cut({ code: "timeout", message: `adb ${args[0]} exceeded ${timeoutMs}ms` }), timeoutMs);
|
|
498
|
+
const onAbort = () => cut({ code: "aborted", message: "adb command aborted" });
|
|
471
499
|
if (opts?.signal) {
|
|
472
500
|
if (opts.signal.aborted)
|
|
473
501
|
return onAbort();
|
|
@@ -480,30 +508,27 @@ export class RemoteAdbExecutionEnv {
|
|
|
480
508
|
outTail.push(d);
|
|
481
509
|
opts?.onStdout?.(d.toString());
|
|
482
510
|
});
|
|
483
|
-
const takeStderr = (bytes) => {
|
|
484
|
-
if (bytes.length === 0)
|
|
485
|
-
return;
|
|
486
|
-
errBuf.push(bytes);
|
|
487
|
-
opts?.onStderr?.(bytes.toString());
|
|
488
|
-
};
|
|
489
511
|
child.stderr.on("data", (d) => takeStderr(opts?.stderrFilter ? opts.stderrFilter.filterStderr(d) : d));
|
|
490
|
-
child.on("error", (e) => finish({ ok: false, error: new ExecutionError("spawn_error", `adb spawn failed (is '${this.cfg.adbPath}' installed?): ${e.message}`, e) }));
|
|
512
|
+
child.on("error", (e) => finish(() => ({ ok: false, error: new ExecutionError("spawn_error", `adb spawn failed (is '${this.cfg.adbPath}' installed?): ${e.message}`, e) })));
|
|
491
513
|
child.on("close", (code, signal) => {
|
|
492
514
|
if (forceSettleTimer)
|
|
493
515
|
clearTimeout(forceSettleTimer);
|
|
494
|
-
if (opts?.stderrFilter)
|
|
495
|
-
takeStderr(opts.stderrFilter.endStderr());
|
|
496
|
-
const err = errBuf.result();
|
|
497
|
-
const stderr = markTruncated(err.text, err.droppedBytes);
|
|
498
516
|
const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 9) : 0);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
517
|
+
const held = heldStderr();
|
|
518
|
+
const won = finish(() => {
|
|
519
|
+
if (held)
|
|
520
|
+
recordStderr(held);
|
|
521
|
+
const err = errBuf.result();
|
|
522
|
+
const stderr = markTruncated(err.text, err.droppedBytes);
|
|
523
|
+
if (binary) {
|
|
524
|
+
const stdoutBytes = Buffer.concat(outBufs);
|
|
525
|
+
return ok({ stdout: stdoutBytes.toString("utf8"), stderr, exitCode, stdoutBytes: new Uint8Array(stdoutBytes) });
|
|
526
|
+
}
|
|
504
527
|
const out = outTail.result();
|
|
505
|
-
|
|
506
|
-
}
|
|
528
|
+
return ok({ stdout: markTruncated(out.text, out.droppedBytes), stderr, exitCode });
|
|
529
|
+
});
|
|
530
|
+
if (won && held)
|
|
531
|
+
emitStderr(held);
|
|
507
532
|
});
|
|
508
533
|
});
|
|
509
534
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ExecutionError, FileError, RemoteExecutionError, type ExecutionEnv, type ExecutionEnvFactory, type ExecStreamOptions, type FileInfo, type OutputChunk, type RemoteConnectConfig, type RemoteExecutionEnv, type Result, type SessionToken, type SnapshotId, type VmLifecycleOptions, type WorkspaceHandle, type WriteExpectation, type WriteReceipt } from "@sema-agent/core";
|
|
2
2
|
import { type DeviceOwner, type DeviceRejectCode, type DeviceRow, type DeviceStore } from "../device-store.js";
|
|
3
3
|
import type { DeviceEnrollment } from "../device-enrollment.js";
|
|
4
|
+
import { type ExecutionPlacementSeat } from "../execution-placement.js";
|
|
4
5
|
import type { DeviceWsHub } from "../device-ws-hub.js";
|
|
5
6
|
import type { DeviceInstructionKind, DeviceOutcomeErrorCode } from "../device-ws-protocol.js";
|
|
6
7
|
/** `WorkspaceHandle.provider` 与 `REMOTE_EXEC` 词表的那一个词,单点。 */
|
|
@@ -57,6 +58,8 @@ export interface DeviceEnvFactoryDeps {
|
|
|
57
58
|
homeDir?: string;
|
|
58
59
|
hub: DeviceWsHub;
|
|
59
60
|
store: DeviceStore;
|
|
61
|
+
/** 会话店的执行席(绑定的唯一载体与唯一读;装配点交会话店的 `executionPlacement`,与准入链同一只)。工厂只调它的 `read(root)`。 */
|
|
62
|
+
seat: Pick<ExecutionPlacementSeat, "read">;
|
|
60
63
|
enrollment: DeviceEnrollment;
|
|
61
64
|
/** 指令级默认超时(ms;`DEVICE_EXEC_TIMEOUT_MS`,§4.7)。core 的 per-command `timeout` 可收紧它。 */
|
|
62
65
|
execTimeoutMs?: number;
|
|
@@ -92,32 +95,8 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
|
|
|
92
95
|
/** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在设备上**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
|
|
93
96
|
* 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
|
|
94
97
|
readonly withheldEnv: readonly [];
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
* load-bearing 不变量)—— 设备工作区 = 员工机磁盘,真持久 ✅,park-only 降级腿因此合法可用。
|
|
98
|
-
* 谁要把 executor 的工作区改成临时目录,先来改这一行:那一刻这个 `false` 就变成谎话,park 赎回会
|
|
99
|
-
* 落到一个空工作区上而链路上没有任何一层会报错。
|
|
100
|
-
*/
|
|
101
|
-
/**
|
|
102
|
-
* 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
|
|
103
|
-
* 就是**命令真正跑的那块文件系统**的权威答案(设备 WS 协议的**必答** `canonicalPath` op)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
104
|
-
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
105
|
-
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
106
|
-
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
107
|
-
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
108
|
-
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
109
|
-
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
110
|
-
*/
|
|
111
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在注册设备上(设备执行器),不在本进程所在的进程表。 */
|
|
112
|
-
readonly capabilities: {
|
|
113
|
-
isolation: boolean;
|
|
114
|
-
suspendable: boolean;
|
|
115
|
-
canonicalPathAuthoritative: boolean;
|
|
116
|
-
sharesProcessTable: boolean;
|
|
117
|
-
};
|
|
118
|
-
readonly externalContentTarget = true;
|
|
119
|
-
/** [ref]:设备路径不在 worker 宿主文件系统上 —— host-path 回执消费方据此拒绝把它当宿主路径呈现。 */
|
|
120
|
-
readonly hostLocalPaths = false;
|
|
98
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
99
|
+
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
121
100
|
cwd: string;
|
|
122
101
|
/** 可选面(presence-typed,见文件头「铁律」段):构造期按设备声明装配,缺席就是缺席。 */
|
|
123
102
|
readLink?: (path: string, abortSignal?: AbortSignal) => Promise<Result<string, FileError>>;
|
|
@@ -332,11 +311,24 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
|
|
|
332
311
|
* `DEVICE_ERROR_CODES` 闭集(装配 / 数据错误取 {@link DEVICE_FACTORY_CONFIG_CODES}:placement 根为空 · 有效工作目录非绝对路径),句子里的 `[码]` 与 `.code` 同值。修前抛裸 `Error`,码只在句子里
|
|
333
312
|
* (有效工作目录那一只直到 7.135.0 仍是裸 `Error`;钉:本工厂与 {@link effectiveDeviceCwd} 的函数体里没有 `new Error(`)。
|
|
334
313
|
*
|
|
335
|
-
* 🔴
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
314
|
+
* 🔴 **工厂认设备不认住客**(读口一只 = 会话店执行席 `seat.read(root)`,与解析器 / 换绑门 / 执行放置读面 / 准入链同一只读):按 placement 根读**当前**格,
|
|
315
|
+
* 与这条腿带来的设备句柄 `honor` 比,不同即 typed 拒。判序(一次行读):
|
|
316
|
+
* root = ctx.placementRootSessionId // 空 ⇒ config.placement_root_invalid
|
|
317
|
+
* honor = ctx.resumeWorkspace?.deviceId
|
|
318
|
+
* cell = seat.read(root).cell // 行不在 / 格缺 / mirror / writer(device, ∅) ⇒ device.not_bound(唯一读法 `boundDeviceOf`)
|
|
319
|
+
* honor ≠ undefined ∧ 格上设备 ≠ honor ⇒ device.identity_mismatch(句点名两台,零连接)
|
|
320
|
+
* readDeviceForAdmission(格上设备, 格上属主)
|
|
321
|
+
* 工厂**不**读 `task_run`、不读子代分区行、不按 `ctx.taskId`、不比化身、不比属主(住客谓词只用于写与 judged 复核,不是工厂的读者)。
|
|
322
|
+
* 续跑腿(core 7.54.0 [ref] r3 / [ref],`ExecutionEnvFactoryContext.resumeWorkspace`):行记下了设备 ⇒ 本工厂**只**在那台设备上铸 —— 根此刻绑的不是它(换绑过)
|
|
323
|
+
* ⇒ `device.identity_mismatch`(换绑后赎回的同一个词,零新码);不连另一台、不改道(新设备没有那个工作区)。「绑的就是它但现在不在线」与新跑同形:
|
|
324
|
+
* 构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。行没记设备(早于该字段的行)⇒ 引擎已命名的「行未记设备 ⇒ 普通路由」臂,按根读当前格。
|
|
325
|
+
* 句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄 `resumeWorkspace`。
|
|
326
|
+
*
|
|
327
|
+
* 🔴 **过渡期(条件句)**:新派后代腿的放置句柄(引擎在 `ExecutionEnvFactoryContext` 上的放置工作区字段,`honor` 的第二来源)**只在本仓所基的引擎版本带它时**
|
|
328
|
+
* 进判序;本仓今天所基的引擎上下文没有这个字段 ⇒ `honor` 只取续跑腿的 `resumeWorkspace`,新派后代腿按根读当前格。本仓**不**注入任何替代见证、不造任何行
|
|
329
|
+
* (修在源头)。敞口(在那个字段到货之前成立):活过父 turn 的后台子代,在根换绑之后新派的腿会落到根当前设备 —— 换绑半边由换绑门的子代活性判关闭(归换绑门那一车),
|
|
330
|
+
* 删除半边另有票。字段到货后的形:`honor = ctx.resumeWorkspace?.deviceId ?? <放置句柄>.deviceId`,放置句柄的 provider 不是本车道 ⇒ 装配拒(`config.placement_root_invalid` 同级)。
|
|
331
|
+
* 本仓任何文本不得宣称工厂「一律按住客定址」。
|
|
340
332
|
*/
|
|
341
333
|
export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): ExecutionEnvFactory;
|
|
342
334
|
/**
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
2
|
import { ExecutionError, FileError, RemoteExecutionError, } from "@sema-agent/core";
|
|
3
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
3
4
|
import { DEVICE_ERROR_CODES, isDeviceAbsolutePath, } from "../device-store.js";
|
|
5
|
+
import { boundDeviceOf } from "../execution-placement.js";
|
|
6
|
+
import { createCutError, createExecFailure } from "../exec-cut.js";
|
|
4
7
|
export const DEVICE_PROVIDER = "device";
|
|
5
8
|
const ok = (value) => ({ ok: true, value });
|
|
6
9
|
const STREAM_PENDING_MAX_BYTES = 2 * 1024 * 1024;
|
|
@@ -126,9 +129,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
126
129
|
homeDir;
|
|
127
130
|
cdPath = "unknown";
|
|
128
131
|
withheldEnv = [];
|
|
129
|
-
capabilities = {
|
|
130
|
-
externalContentTarget = true;
|
|
131
|
-
hostLocalPaths = false;
|
|
132
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "device" } });
|
|
132
133
|
cwd;
|
|
133
134
|
readLink;
|
|
134
135
|
writeFileExclusive;
|
|
@@ -227,7 +228,8 @@ export class RemoteDeviceExecutionEnv {
|
|
|
227
228
|
}
|
|
228
229
|
let admitted;
|
|
229
230
|
try {
|
|
230
|
-
|
|
231
|
+
const placement = await this.cfg.enrollment.readPlacement(this.cfg.rootSessionId);
|
|
232
|
+
admitted = await this.cfg.enrollment.admitSession({ rootSessionId: this.cfg.rootSessionId, placement, requester: this.cfg.owner });
|
|
231
233
|
}
|
|
232
234
|
catch (err) {
|
|
233
235
|
return {
|
|
@@ -302,7 +304,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
302
304
|
return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
|
|
303
305
|
const failure = await this.ensureConnected();
|
|
304
306
|
if (failure)
|
|
305
|
-
return { ok: false, error:
|
|
307
|
+
return { ok: false, error: createExecFailure(failure.exec, failure.message) };
|
|
306
308
|
return undefined;
|
|
307
309
|
}
|
|
308
310
|
async fsGate(p) {
|
|
@@ -381,15 +383,14 @@ export class RemoteDeviceExecutionEnv {
|
|
|
381
383
|
case "env_terminal":
|
|
382
384
|
return { ok: false, error: new ExecutionError("shell_unavailable", ENV_TERMINAL_MESSAGE) };
|
|
383
385
|
case "rejected":
|
|
384
|
-
return { ok: false, error:
|
|
386
|
+
return { ok: false, error: createExecFailure(DEVICE_REJECT_TO_EXEC_ERROR[res.reject.code], rejectText(res.reject)) };
|
|
385
387
|
case "never_started":
|
|
386
388
|
case "settled": {
|
|
387
389
|
const o = res.outcome;
|
|
388
|
-
if (!o.ok)
|
|
389
|
-
return { ok: false, error:
|
|
390
|
-
}
|
|
390
|
+
if (!o.ok)
|
|
391
|
+
return { ok: false, error: createExecFailure(DEVICE_OUTCOME_TO_EXEC_ERROR[o.errorCode], o.message, res.kind === "settled" ? { stdout, stderr } : undefined) };
|
|
391
392
|
if (o.kind === "cancelled") {
|
|
392
|
-
return { ok: false, error:
|
|
393
|
+
return { ok: false, error: createCutError({ code: "aborted", message: `the device reported the command as ${o.state} after a cancel` }, { stdout, stderr }) };
|
|
393
394
|
}
|
|
394
395
|
if (o.kind !== "exec")
|
|
395
396
|
return { ok: false, error: new ExecutionError("unknown", `device returned a "${o.kind}" outcome for an exec — protocol violation`) };
|
|
@@ -799,17 +800,18 @@ export function deviceExecutionEnvFactory(deps) {
|
|
|
799
800
|
throw deviceFactoryRefusal(DEVICE_FACTORY_CONFIG_CODES.PLACEMENT_ROOT_INVALID, `device lane: ExecutionEnvFactoryContext.placementRootSessionId is empty [${DEVICE_FACTORY_CONFIG_CODES.PLACEMENT_ROOT_INVALID}] — ` +
|
|
800
801
|
"the device placement is keyed on the run tree's placement root; defaulting to the child sessionId would read as a NEW placement (v2 §4.3.2 / core design/380 O1①)");
|
|
801
802
|
}
|
|
802
|
-
const
|
|
803
|
+
const placement = await deps.seat.read(rootSessionId);
|
|
804
|
+
const binding = placement === undefined ? undefined : boundDeviceOf(placement.cell);
|
|
803
805
|
if (!binding) {
|
|
804
806
|
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_BOUND, `device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
|
|
805
807
|
"the submit-time admission chain must bind the root session to a device before a run reaches the factory (v2 §4.3.2)");
|
|
806
808
|
}
|
|
807
|
-
const
|
|
808
|
-
if (
|
|
809
|
-
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${
|
|
809
|
+
const honor = ctx.resumeWorkspace?.deviceId;
|
|
810
|
+
if (honor !== undefined && honor !== binding.id) {
|
|
811
|
+
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${honor}, but placement root ${rootSessionId} is now bound to ${binding.id} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
|
|
810
812
|
"a parked call does not follow a rebind (the new device has no copy of that workspace); finish or cancel the parked task, then start it again on the device now bound");
|
|
811
813
|
}
|
|
812
|
-
const view = await deps.store.readDeviceForAdmission(binding.
|
|
814
|
+
const view = await deps.store.readDeviceForAdmission(binding.id, binding.owner);
|
|
813
815
|
if (!view) {
|
|
814
816
|
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_FOUND, `device lane: the bound device is gone or not owned by the binding's principal [${DEVICE_ERROR_CODES.NOT_FOUND}]`);
|
|
815
817
|
}
|
|
@@ -821,13 +823,13 @@ export function deviceExecutionEnvFactory(deps) {
|
|
|
821
823
|
hub: deps.hub,
|
|
822
824
|
enrollment: deps.enrollment,
|
|
823
825
|
rootSessionId,
|
|
824
|
-
deviceId: binding.
|
|
826
|
+
deviceId: binding.id,
|
|
825
827
|
owner: binding.owner,
|
|
826
828
|
cwd: effectiveDeviceCwd(deps.perSessionCwd?.get(ctx.sessionId), ctx.parentCwd, view.row),
|
|
827
829
|
...(deps.homeDir === undefined ? {} : { homeDir: deps.homeDir }),
|
|
828
830
|
...(deps.perSessionShellEnv?.get(ctx.sessionId) === undefined ? {} : { shellEnv: deps.perSessionShellEnv.get(ctx.sessionId) }),
|
|
829
831
|
execTimeoutMs,
|
|
830
|
-
supports: deps.hub.declaredSupports(binding.
|
|
832
|
+
supports: deps.hub.declaredSupports(binding.id),
|
|
831
833
|
...(deps.logger === undefined ? {} : { logger: deps.logger }),
|
|
832
834
|
});
|
|
833
835
|
};
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { Sandbox, type SandboxOpts, type SandboxConnectOpts } from "e2b";
|
|
23
23
|
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 ExecutionEnvFactory, type VmLifecycleOptions, type BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll } from "@sema-agent/core";
|
|
24
|
+
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
24
25
|
/** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
|
|
25
26
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
26
27
|
/** Minimal optional observability hooks (kept structural so the adapter stays standalone-testable). */
|
|
@@ -34,22 +35,13 @@ export interface RemoteEnvObservers {
|
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
/** Construction config for {@link RemoteContainerExecutionEnv}. Secrets/resolved values are control-plane supplied. */
|
|
37
|
-
export interface RemoteContainerEnvConfig extends RemoteEnvObservers {
|
|
38
|
+
export interface RemoteContainerEnvConfig extends RemoteEnvObservers, LaneHomeConfig {
|
|
38
39
|
/** E2B API key. Resolved by the trusted control plane from env — never baked into an image or committed. */
|
|
39
40
|
apiKey: string;
|
|
40
41
|
/** E2B template id (image). Defaults to the E2B base template. */
|
|
41
42
|
template?: string;
|
|
42
43
|
/** Sandbox lifetime in ms (E2B billing window). Default {@link DEFAULT_TIMEOUT_MS}. */
|
|
43
44
|
timeoutMs?: number;
|
|
44
|
-
/**
|
|
45
|
-
* [ref]④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
|
|
46
|
-
* 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
|
|
47
|
-
* 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
|
|
48
|
-
* 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
|
|
49
|
-
* 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
|
|
50
|
-
* adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
|
|
51
|
-
*/
|
|
52
|
-
homeDir?: string;
|
|
53
45
|
/** Workspace root inside the VM; remote paths normalize against it. Default {@link DEFAULT_MOUNT_PATH}. */
|
|
54
46
|
mountPath?: string;
|
|
55
47
|
/** Outbound network for the sandbox (clay: service needs external-network capability). Default `true`. */
|
|
@@ -99,8 +91,9 @@ export interface RemoteContainerEnvDeps {
|
|
|
99
91
|
export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
|
|
100
92
|
/** Current working directory for relative paths (FileSystem contract; mutable, core may reassign). */
|
|
101
93
|
cwd: string;
|
|
102
|
-
/** core `ExecutionEnv.homeDir`([ref])
|
|
103
|
-
|
|
94
|
+
/** core `ExecutionEnv.homeDir`([ref]):沙箱里执行用户的 home —— 主张在场 ⇒ 主张(每只沙箱建起来 / 恢复时与沙箱里的 `$HOME` 对证过),
|
|
95
|
+
* 否则建沙箱那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没主张时沙箱建起来之前缺席)。 */
|
|
96
|
+
get homeDir(): string | undefined;
|
|
104
97
|
/** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 e2b 沙箱里(模板镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
105
98
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
106
99
|
readonly cdPath: "unknown";
|
|
@@ -123,6 +116,10 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
123
116
|
* and the post-await re-check in BOTH async-provision paths ({@link doConnect} and {@link doResumeVM}) against
|
|
124
117
|
* a destroy that races an in-flight provision/restore. */
|
|
125
118
|
private destroyed;
|
|
119
|
+
/** 家目录的座(主张在场 ⇒ 每只沙箱对证 · 否则建沙箱那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。 */
|
|
120
|
+
private readonly homeSeat;
|
|
121
|
+
/** 家目录:沙箱答了而与主张不一致(或答的不是绝对路径)⇒ 这只环境的拒,记一次、此后每次连接 / 恢复原样回它(同一模板同一个答案:不再建沙箱、不再问)。 */
|
|
122
|
+
private homeRefusal?;
|
|
126
123
|
/** In-flight connect/resume, memoized so concurrent lazy first-use OR resume provisions exactly one VM (no double-bill). */
|
|
127
124
|
private connecting?;
|
|
128
125
|
/** Count of exec/execStream calls in flight — `suspendVM` refuses while > 0 (council #4: never snapshot a running command). */
|
|
@@ -133,15 +130,8 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
133
130
|
* SAME env instance skips the reconnect and every later fs/exec runs against the dead VM. */
|
|
134
131
|
private paused;
|
|
135
132
|
constructor(config: RemoteContainerEnvConfig, deps?: RemoteContainerEnvDeps);
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在云沙箱里,不在本进程所在的进程表。 */
|
|
139
|
-
readonly capabilities: {
|
|
140
|
-
isolation: boolean;
|
|
141
|
-
suspendable: boolean;
|
|
142
|
-
canonicalPathAuthoritative: boolean;
|
|
143
|
-
sharesProcessTable: boolean;
|
|
144
|
-
};
|
|
133
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
134
|
+
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
145
135
|
workspaceHandle(): WorkspaceHandle;
|
|
146
136
|
/**
|
|
147
137
|
* Public host for a port inside the sandbox (E2B reverse proxy → `<port>-<id>.e2b.app`). The LSP sidecar
|
|
@@ -212,6 +202,13 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
212
202
|
ok: false;
|
|
213
203
|
error: RemoteExecutionError;
|
|
214
204
|
}>;
|
|
205
|
+
/**
|
|
206
|
+
* 家目录:座要问({@link LaneHomeSeat.asks}:主张在场 ⇒ 对证;没主张 ⇒ 量到绝对路径为止)⇒ 在这只沙箱里问一次 `$HOME`(前台 `commands.run`,与命令同一个
|
|
207
|
+
* 模板缺省用户、同一份沙箱 env),读数交座。返回这只沙箱的拒(`connect_failed`,拒句 = 同一只判官的句子),或 `undefined`(照用)。
|
|
208
|
+
* 沙箱答了的拒记在环境上(同一模板同一个答案);没答上(非零退出 / RPC 失败 / 输出里没有标记行)⇒ 这一次不开、不记,下一次重问;调用方中止 ⇒ `aborted`、不交座。
|
|
209
|
+
* 上界 = 控制面 RPC 的墙钟(`rpcTimeoutMs`:问一个环境变量,不是跑用户命令)。
|
|
210
|
+
*/
|
|
211
|
+
private homeVerdict;
|
|
215
212
|
/** BL-31 — (re)start the periodic sandbox-lifetime extension. `setTimeout(timeoutMs)` resets the auto-kill window
|
|
216
213
|
* from now; we re-arm at ~80% of the (clamped, ≤1h) window so a long-held sandbox never expires mid-run. A no-op
|
|
217
214
|
* when paused/destroyed; `unref`'d so it never keeps the process alive; cleared on destroy. */
|
|
@@ -254,7 +251,14 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
254
251
|
abortSignal?: AbortSignal;
|
|
255
252
|
}): Promise<Result<string[], FileError>>;
|
|
256
253
|
readBinaryFile(p: string, abortSignal?: AbortSignal): Promise<Result<Uint8Array, FileError>>;
|
|
254
|
+
/** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts` `writeFileStaged` / `appendFileStaged`;本腿是原生 SDK 腿,与四条
|
|
255
|
+
* shell 腿共用的是这只编排,不是 `posix-shell-fs` 基座;父目录与私有暂存目录由编排的准备那一步建):上传 = `files.write` 整份写到私有目录里的
|
|
256
|
+
* 暂存件;提交 = 一次 `exec`。两者同一身份(`files.write` 与 `commands.run` 都不带 `user`,沙箱侧取同一个缺省用户)。 */
|
|
257
|
+
private stagedLane;
|
|
258
|
+
/** 整旧或整新(暂存 → 一次 `mv -f`)。修前 `files.write` 直落目标:上传中途断 / 取消时目标剩什么取决于沙箱侧的落地实现,本适配器不依赖它。 */
|
|
257
259
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
260
|
+
/** 追加(暂存要加的字节 → 一次 `cat >>`;inode 不换、已有字节不重写)。修前 = 读旧 + 拼 + 整份 `files.write`(O(文件);中途断 ⇒
|
|
261
|
+
* 半份)。目录不收新名字 / 私有目录收不成私有时的退路 = 旧形(`rewriteAppend`:读旧 + 拼 + 整份 `files.write` 到目标;只有写那一步经 `ctx.inPlace`)。 */
|
|
258
262
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
259
263
|
fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
260
264
|
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
@@ -340,6 +344,8 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
|
|
|
340
344
|
private tempResult;
|
|
341
345
|
private toFileInfo;
|
|
342
346
|
private fileError;
|
|
347
|
+
/** A thrown provider error → this exec's error. The two cuts (aborted, the provider's own command deadline) are a command that RAN: they carry what it had
|
|
348
|
+
* written (`captured`, `createCutError`); an unclassified fault does not (its outcome is unknown, the engine reads no partial output from it). */
|
|
343
349
|
private execError;
|
|
344
350
|
private remoteError;
|
|
345
351
|
}
|