@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
|
@@ -7,10 +7,13 @@ import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdi
|
|
|
7
7
|
import { StringDecoder } from "node:string_decoder";
|
|
8
8
|
import { armPipeDestroyGrace, numEnvOr } from "./remote-shell.js";
|
|
9
9
|
import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
10
|
+
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
10
11
|
import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
|
|
11
12
|
import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
|
|
12
13
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
13
14
|
import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
|
|
15
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
16
|
+
import { LaneHomeSeat } from "../lane-home-probe.js";
|
|
14
17
|
import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
|
|
15
18
|
import { nameTooLongCode } from "./remote-env-file-error.js";
|
|
16
19
|
import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
|
|
@@ -37,7 +40,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
|
|
|
37
40
|
maxScheduledHorizonSec: 0,
|
|
38
41
|
};
|
|
39
42
|
export class RemoteHostExecutionEnv {
|
|
40
|
-
capabilities = {
|
|
43
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "host" } });
|
|
41
44
|
get schedulerCapabilities() {
|
|
42
45
|
return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
|
|
43
46
|
}
|
|
@@ -57,7 +60,11 @@ export class RemoteHostExecutionEnv {
|
|
|
57
60
|
: Promise.resolve({ ok: false, error: new SchedulerError("unsupported", "scheduler not enabled on this worker") });
|
|
58
61
|
}
|
|
59
62
|
cwd;
|
|
60
|
-
homeDir
|
|
63
|
+
get homeDir() {
|
|
64
|
+
return this.homeSeat.home;
|
|
65
|
+
}
|
|
66
|
+
homeSeat;
|
|
67
|
+
homeRefusal;
|
|
61
68
|
get cdPath() {
|
|
62
69
|
const value = this.childEnv(undefined, undefined).CDPATH;
|
|
63
70
|
return value === undefined || value === "" ? [] : value.split(":");
|
|
@@ -139,8 +146,9 @@ export class RemoteHostExecutionEnv {
|
|
|
139
146
|
if (Array.isArray(config.inheritEnv))
|
|
140
147
|
this.cfg.inheritEnv = [...config.inheritEnv];
|
|
141
148
|
this.cwd = this.workspaceDir;
|
|
142
|
-
this.homeDir = config.homeDir;
|
|
143
149
|
this.handle = { sandboxId: this.workspaceDir, provider: PROVIDER, mountPath: this.workspaceDir };
|
|
150
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config, IS_WIN32 ? "win32" : "posix");
|
|
151
|
+
this.homeRefusal = this.homeSeat.settle(this.childHome())?.refusal;
|
|
144
152
|
}
|
|
145
153
|
workspaceHandle() {
|
|
146
154
|
return this.handle;
|
|
@@ -150,6 +158,8 @@ export class RemoteHostExecutionEnv {
|
|
|
150
158
|
return unsupported("connect(snapshotId)");
|
|
151
159
|
if (this.destroyed)
|
|
152
160
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
161
|
+
if (this.homeRefusal !== undefined)
|
|
162
|
+
return { ok: false, error: new RemoteExecutionError("connect_failed", this.homeRefusal) };
|
|
153
163
|
if (config?.abortSignal?.aborted)
|
|
154
164
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
|
|
155
165
|
try {
|
|
@@ -454,11 +464,11 @@ export class RemoteHostExecutionEnv {
|
|
|
454
464
|
};
|
|
455
465
|
const timer = setTimeout(() => {
|
|
456
466
|
kill();
|
|
457
|
-
finish({ ok: false, error:
|
|
467
|
+
finish({ ok: false, error: createCutError({ code: "timeout", message: `host exec exceeded ${timeoutMs}ms` }, buildCapturedOutput(outBuf, errBuf)) });
|
|
458
468
|
}, timeoutMs);
|
|
459
469
|
const onAbort = () => {
|
|
460
470
|
kill();
|
|
461
|
-
finish({ ok: false, error:
|
|
471
|
+
finish({ ok: false, error: createCutError({ code: "aborted", message: "host exec aborted" }, buildCapturedOutput(outBuf, errBuf)) });
|
|
462
472
|
};
|
|
463
473
|
if (options?.abortSignal)
|
|
464
474
|
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -607,32 +617,36 @@ export class RemoteHostExecutionEnv {
|
|
|
607
617
|
}
|
|
608
618
|
catch { }
|
|
609
619
|
};
|
|
610
|
-
const
|
|
620
|
+
const record = (lane, inc) => {
|
|
611
621
|
lane.buf.push(inc);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
catch { }
|
|
622
|
+
};
|
|
623
|
+
const deliver = (lane, inc) => {
|
|
624
|
+
try {
|
|
625
|
+
lane.cb?.(inc.toString("utf8"));
|
|
617
626
|
}
|
|
627
|
+
catch { }
|
|
618
628
|
};
|
|
619
|
-
const
|
|
629
|
+
const feed = (lane, inc) => {
|
|
630
|
+
record(lane, inc);
|
|
631
|
+
deliver(lane, inc);
|
|
632
|
+
};
|
|
633
|
+
const pumpOnce = (take) => {
|
|
620
634
|
for (const lane of lanes) {
|
|
621
635
|
skipAhead(lane, ROTATE_BYTES);
|
|
622
636
|
const inc = readIncrement(lane.path, lane.key);
|
|
623
637
|
if (inc)
|
|
624
|
-
|
|
638
|
+
take(lane, inc);
|
|
625
639
|
reclaim(lane);
|
|
626
640
|
}
|
|
627
641
|
};
|
|
628
|
-
const drainFinal = () => {
|
|
642
|
+
const drainFinal = (take) => {
|
|
629
643
|
for (const lane of lanes) {
|
|
630
644
|
skipAhead(lane, FINAL_TAIL_BYTES);
|
|
631
645
|
for (let i = 0; i < 4; i++) {
|
|
632
646
|
const inc = readIncrement(lane.path, lane.key);
|
|
633
647
|
if (!inc)
|
|
634
648
|
break;
|
|
635
|
-
|
|
649
|
+
take(lane, inc);
|
|
636
650
|
}
|
|
637
651
|
}
|
|
638
652
|
};
|
|
@@ -661,15 +675,17 @@ export class RemoteHostExecutionEnv {
|
|
|
661
675
|
}
|
|
662
676
|
};
|
|
663
677
|
let removeDetachListener;
|
|
664
|
-
const finish = (
|
|
678
|
+
const finish = (outcome) => {
|
|
665
679
|
if (settled)
|
|
666
|
-
return;
|
|
680
|
+
return false;
|
|
667
681
|
settled = true;
|
|
682
|
+
const r = outcome();
|
|
668
683
|
clearTimeout(timer);
|
|
669
684
|
options?.abortSignal?.removeEventListener("abort", onAbort);
|
|
670
685
|
removeDetachListener?.();
|
|
671
686
|
cleanupSpool();
|
|
672
687
|
resolve(r);
|
|
688
|
+
return true;
|
|
673
689
|
};
|
|
674
690
|
const closeSpoolFds = () => {
|
|
675
691
|
for (const fd of spool.fds) {
|
|
@@ -697,66 +713,56 @@ export class RemoteHostExecutionEnv {
|
|
|
697
713
|
ticket = adoptHostForegroundChild(child);
|
|
698
714
|
closeSpoolFds();
|
|
699
715
|
child.on("error", (e) => {
|
|
700
|
-
finish({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) });
|
|
716
|
+
finish(() => ({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) }));
|
|
701
717
|
});
|
|
702
|
-
const
|
|
703
|
-
pumpOnce();
|
|
704
|
-
|
|
705
|
-
const e = errBuf.result();
|
|
706
|
-
return { stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes) };
|
|
718
|
+
const captured = () => {
|
|
719
|
+
pumpOnce(record);
|
|
720
|
+
return buildCapturedOutput(outBuf, errBuf);
|
|
707
721
|
};
|
|
708
722
|
let exited = false;
|
|
709
|
-
const
|
|
723
|
+
const cut = (c) => {
|
|
710
724
|
if (exited)
|
|
711
725
|
return;
|
|
712
|
-
|
|
713
|
-
if (child.pid != null)
|
|
714
|
-
killTreeHard(child.pid);
|
|
715
|
-
}
|
|
716
|
-
catch {
|
|
726
|
+
finish(() => {
|
|
717
727
|
try {
|
|
718
|
-
child.
|
|
728
|
+
if (child.pid != null)
|
|
729
|
+
killTreeHard(child.pid);
|
|
719
730
|
}
|
|
720
|
-
catch {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const onAbort = () => {
|
|
726
|
-
if (exited)
|
|
727
|
-
return;
|
|
728
|
-
try {
|
|
729
|
-
if (child.pid != null)
|
|
730
|
-
killTreeHard(child.pid);
|
|
731
|
-
}
|
|
732
|
-
catch {
|
|
733
|
-
try {
|
|
734
|
-
child.kill("SIGKILL");
|
|
731
|
+
catch {
|
|
732
|
+
try {
|
|
733
|
+
child.kill("SIGKILL");
|
|
734
|
+
}
|
|
735
|
+
catch { }
|
|
735
736
|
}
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
finish({ ok: false, error: new ExecutionError("aborted", "host exec aborted") });
|
|
737
|
+
return { ok: false, error: createCutError(c, captured()) };
|
|
738
|
+
});
|
|
739
739
|
};
|
|
740
|
+
const timer = setTimeout(() => cut({ code: "timeout", message: `host exec exceeded ${timeoutMs}ms` }), timeoutMs);
|
|
741
|
+
const onAbort = () => cut({ code: "aborted", message: "host exec aborted" });
|
|
740
742
|
if (options?.abortSignal?.aborted) {
|
|
741
743
|
onAbort();
|
|
742
744
|
return;
|
|
743
745
|
}
|
|
744
746
|
if (options?.abortSignal)
|
|
745
747
|
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
746
|
-
pump = setInterval(pumpOnce, PUMP_MS);
|
|
748
|
+
pump = setInterval(() => pumpOnce(feed), PUMP_MS);
|
|
747
749
|
pump.unref?.();
|
|
748
750
|
const onDetach = () => {
|
|
749
751
|
if (settled || exited)
|
|
750
752
|
return;
|
|
751
|
-
pumpOnce();
|
|
753
|
+
pumpOnce(feed);
|
|
754
|
+
if (settled)
|
|
755
|
+
return;
|
|
752
756
|
const fgDropped = spoolRemoved.out + spoolRemoved.err;
|
|
753
757
|
const shellId = this.adoptRunningExecSpool(child, spool, { out: 0, err: 0 }, fgDropped);
|
|
754
758
|
if (shellId === undefined)
|
|
755
759
|
return;
|
|
756
760
|
handedOver = true;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
761
|
+
finish(() => {
|
|
762
|
+
const o = outBuf.result();
|
|
763
|
+
const e = errBuf.result();
|
|
764
|
+
return ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode: 0, detached: { shellId } });
|
|
765
|
+
});
|
|
760
766
|
};
|
|
761
767
|
if (detachSignal) {
|
|
762
768
|
if (detachSignal.aborted)
|
|
@@ -767,13 +773,20 @@ export class RemoteHostExecutionEnv {
|
|
|
767
773
|
}
|
|
768
774
|
}
|
|
769
775
|
const settleFromTermination = (code, signal) => {
|
|
770
|
-
if (settled)
|
|
771
|
-
return;
|
|
772
|
-
drainFinal();
|
|
773
|
-
const o = outBuf.result();
|
|
774
|
-
const e = errBuf.result();
|
|
775
776
|
const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 0) : 1);
|
|
776
|
-
|
|
777
|
+
const drained = [];
|
|
778
|
+
const won = finish(() => {
|
|
779
|
+
drainFinal((lane, inc) => {
|
|
780
|
+
record(lane, inc);
|
|
781
|
+
drained.push({ lane, inc });
|
|
782
|
+
});
|
|
783
|
+
const o = outBuf.result();
|
|
784
|
+
const e = errBuf.result();
|
|
785
|
+
return ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode });
|
|
786
|
+
});
|
|
787
|
+
if (won)
|
|
788
|
+
for (const d of drained)
|
|
789
|
+
deliver(d.lane, d.inc);
|
|
777
790
|
};
|
|
778
791
|
child.on("close", (code, signal) => {
|
|
779
792
|
if (drainTimer) {
|
|
@@ -972,13 +985,13 @@ export class RemoteHostExecutionEnv {
|
|
|
972
985
|
lines = lines.slice(0, options.maxLines);
|
|
973
986
|
return ok(lines);
|
|
974
987
|
}
|
|
975
|
-
|
|
988
|
+
coreFs;
|
|
976
989
|
async openBound(p, abortSignal) {
|
|
977
990
|
const ready = await this.fsReady(p);
|
|
978
991
|
if (!ready.ok)
|
|
979
992
|
return ready;
|
|
980
|
-
this.
|
|
981
|
-
return this.
|
|
993
|
+
this.coreFs ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
|
|
994
|
+
return this.coreFs.openBound(this.resolve(p), abortSignal);
|
|
982
995
|
}
|
|
983
996
|
async readBinaryFile(p, abortSignal) {
|
|
984
997
|
const ready = await this.fsReady(p);
|
|
@@ -998,16 +1011,8 @@ export class RemoteHostExecutionEnv {
|
|
|
998
1011
|
const ready = await this.fsReady(p);
|
|
999
1012
|
if (!ready.ok)
|
|
1000
1013
|
return ready;
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
1004
|
-
const data = typeof content === "string" ? content : Buffer.from(content);
|
|
1005
|
-
await fs.writeFile(abs, data, { signal: abortSignal });
|
|
1006
|
-
return ok(undefined);
|
|
1007
|
-
}
|
|
1008
|
-
catch (e) {
|
|
1009
|
-
return { ok: false, error: this.fileError(e, abs, abortSignal) };
|
|
1010
|
-
}
|
|
1014
|
+
this.coreFs ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
|
|
1015
|
+
return this.coreFs.writeFile(this.resolve(p), content, abortSignal);
|
|
1011
1016
|
});
|
|
1012
1017
|
}
|
|
1013
1018
|
appendFile(p, content, abortSignal) {
|
|
@@ -1257,6 +1262,8 @@ export class RemoteHostExecutionEnv {
|
|
|
1257
1262
|
async shellReady() {
|
|
1258
1263
|
if (this.destroyed)
|
|
1259
1264
|
return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
|
|
1265
|
+
if (this.homeRefusal !== undefined)
|
|
1266
|
+
return { ok: false, error: new ExecutionError("shell_unavailable", this.homeRefusal) };
|
|
1260
1267
|
try {
|
|
1261
1268
|
try {
|
|
1262
1269
|
await resolveHostShell();
|
|
@@ -1288,6 +1295,8 @@ export class RemoteHostExecutionEnv {
|
|
|
1288
1295
|
return (this.spoolRootDir !== undefined && inside(this.spoolRootDir)) || (this.persistent && inside(this.workspaceDir));
|
|
1289
1296
|
}
|
|
1290
1297
|
async fsReady(p, opts) {
|
|
1298
|
+
if (this.homeRefusal !== undefined)
|
|
1299
|
+
return { ok: false, error: new FileError("target_unavailable", this.homeRefusal, this.resolve(p)) };
|
|
1291
1300
|
if (this.destroyed) {
|
|
1292
1301
|
const lingering = opts?.lingeringBgWrite === true && this._bgManager?.hasShells() === true && this.isLingeringMirrorTarget(p);
|
|
1293
1302
|
if (!lingering)
|
|
@@ -1317,6 +1326,16 @@ export class RemoteHostExecutionEnv {
|
|
|
1317
1326
|
pinHostShell(base, spawnShell);
|
|
1318
1327
|
return collapseWin32EnvKeys(base);
|
|
1319
1328
|
}
|
|
1329
|
+
childHome() {
|
|
1330
|
+
try {
|
|
1331
|
+
const env = this.childEnv(undefined, undefined);
|
|
1332
|
+
const home = Object.entries(env).find(([k]) => (IS_WIN32 ? k.toUpperCase() : k) === "HOME")?.[1];
|
|
1333
|
+
return { ok: true, value: home ?? os.userInfo().homedir };
|
|
1334
|
+
}
|
|
1335
|
+
catch (e) {
|
|
1336
|
+
return { ok: false, reason: errMsg(e) };
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1320
1339
|
fileError(e, p, abortSignal) {
|
|
1321
1340
|
if (e instanceof FileError)
|
|
1322
1341
|
return e;
|
|
@@ -2,7 +2,7 @@ import { type SpaceReclaimer, type WorkspaceSpacePolicy, type CacheReclaimReason
|
|
|
2
2
|
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
|
-
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 } from "@sema-agent/core";
|
|
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
6
|
import type { PodSpecPatch } from "./k8s-exec-protocol.js";
|
|
7
7
|
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
8
8
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
@@ -16,7 +16,9 @@ export interface K8sEnvConfig extends LaneHomeConfig {
|
|
|
16
16
|
caCert?: string;
|
|
17
17
|
/** Skip TLS verification (lab only — never production). */
|
|
18
18
|
insecureTls?: boolean;
|
|
19
|
-
namespace
|
|
19
|
+
/** The namespace the sandbox pods live in (env `K8S_NAMESPACE`). REQUIRED, no default: the deployment declares it, and every reader
|
|
20
|
+
* (the adapter's API paths, the reaper's pod face, the boot's egress reading) reads this one field. */
|
|
21
|
+
namespace: string;
|
|
20
22
|
/** Sandbox pod image (a plain docker image present in the cluster). REQUIRED. */
|
|
21
23
|
image: string;
|
|
22
24
|
/** RuntimeClass for the VM boundary. Default "kata-qemu" (set "" to run plain runc — tests/dev only). */
|
|
@@ -258,8 +260,6 @@ export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
|
|
|
258
260
|
* String() is "[object ErrorEvent]" — that masked the real TLS failure for a whole drill. */
|
|
259
261
|
export declare function errorMessageOf(e: unknown): string;
|
|
260
262
|
export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
|
|
261
|
-
/** The sandbox namespace when `K8S_NAMESPACE` is unset (the one spelling: the adapter and the boot's egress line read it). */
|
|
262
|
-
export declare const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
|
|
263
263
|
/**
|
|
264
264
|
* The boot reading of the sandbox Secret seat, a closed set: `granted` — every verb allowed · `denied` — the API answered "not allowed" for these
|
|
265
265
|
* verbs (a definite answer; it wins over a verb that could not be asked) · `unverified` — some verb could not be asked (the request failed, the
|
|
@@ -290,23 +290,8 @@ export type SandboxLeaseWrite = {
|
|
|
290
290
|
readonly message: string;
|
|
291
291
|
};
|
|
292
292
|
export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
|
|
293
|
-
/**
|
|
294
|
-
|
|
295
|
-
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,经 pod exec 解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
296
|
-
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
297
|
-
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
298
|
-
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
299
|
-
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
300
|
-
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
301
|
-
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
302
|
-
*/
|
|
303
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在沙箱 pod 自己的 PID 命名空间里(不开 hostPID),不在本进程所在的进程表。 */
|
|
304
|
-
readonly capabilities: {
|
|
305
|
-
isolation: boolean;
|
|
306
|
-
suspendable: boolean;
|
|
307
|
-
canonicalPathAuthoritative: true;
|
|
308
|
-
sharesProcessTable: false;
|
|
309
|
-
};
|
|
293
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。快照店是它的输入(构造期铸,见下)。 */
|
|
294
|
+
readonly capabilities: ExecutionEnvCapabilities;
|
|
310
295
|
cwd: string;
|
|
311
296
|
private podName?;
|
|
312
297
|
/** Cluster-internal pod address, captured when the pod reaches Running — the LSP `getHost` surface.
|
|
@@ -331,9 +316,9 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
331
316
|
* 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
|
|
332
317
|
* the pod and it ended `Failed` before it ran — {@link createPod}). Neither goes away by asking again: the same request answers the same. */
|
|
333
318
|
private connectRefusal?;
|
|
334
|
-
/** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home ——
|
|
319
|
+
/** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则 pod 就绪那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时 pod 起来之前缺席)。 */
|
|
335
320
|
get homeDir(): string | undefined;
|
|
336
|
-
/** 家目录的座(
|
|
321
|
+
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则 pod 就绪那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
337
322
|
private readonly homeSeat;
|
|
338
323
|
/** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
339
324
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
@@ -813,19 +798,18 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
813
798
|
maxLines?: number;
|
|
814
799
|
abortSignal?: AbortSignal;
|
|
815
800
|
}): Promise<Result<string[], FileError>>;
|
|
816
|
-
/** 父目录自建(与 host/local-docker/adb/ssh 同口径)。抽成共用是因为 `appendFile` 曾**跳过**它:
|
|
817
|
-
* 🔴 2026-07-25 实测的跨腿分叉 —— `appendFile` 直接调 `writeChunked`,整条路上没有 `mkdir -p`,于是
|
|
818
|
-
* 「直接 append 建一个新日志路径」在 k8s 腿上返 `not_found`,而其余 5 腿都成功(host/local-docker/adb 实测,
|
|
819
|
-
* ssh/e2b 由方法体判据)。core 的后台输出镜像正是"每拍 append 一次"的形,这条分叉会让它在 k8s 腿上从第一拍
|
|
820
|
-
* 就失败,而 `mirrorFailed` 只会表现成一句「output file is INCOMPLETE」。 */
|
|
821
|
-
private ensureParentDir;
|
|
822
801
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
802
|
+
/** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts`;父目录与私有暂存目录由编排的准备那一步建):上传 = 分块整份写进私有目录里的暂存件;
|
|
803
|
+
* 提交 = 文件面探针 exec。两者同一身份(同一只容器里的 exec)。 */
|
|
804
|
+
private stagedLane;
|
|
823
805
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
824
806
|
/**
|
|
825
807
|
* Write `content` to `abs` via argv-embedded base64, chunked at WRITE_CHUNK_BYTES (no stdin on the exec
|
|
826
808
|
* channel we use; v4 has no per-channel close). `truncateFirst` → the FIRST chunk uses `>` (overwrite),
|
|
827
809
|
* the rest `>>` (append); `false` → all chunks `>>`. Empty content still runs exactly one command so an
|
|
828
810
|
* empty file is created (write) / left intact (append). Shared by writeFile/appendFile (council DESIGN#2).
|
|
811
|
+
* Both file faces point this at a STAGING name (`writeFileStaged` / `appendFileStaged`), so a cut mid-way never reaches
|
|
812
|
+
* the target; the direct uses are the fallbacks of both faces (writeFile passes truncateFirst, appendFile does not) when the target's directory refuses a new name or the private staging directory could not be made private.
|
|
829
813
|
*/
|
|
830
814
|
private writeChunked;
|
|
831
815
|
private posixFsInst?;
|
|
@@ -4,7 +4,7 @@ import https from "node:https";
|
|
|
4
4
|
import http from "node:http";
|
|
5
5
|
import { StringDecoder } from "node:string_decoder";
|
|
6
6
|
import WebSocket from "ws";
|
|
7
|
-
import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, kindFromMode, numEnvOr } from "./remote-shell.js";
|
|
7
|
+
import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, shellQuote, kindFromMode, numEnvOr, writeFileStaged } from "./remote-shell.js";
|
|
8
8
|
import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
|
|
9
9
|
import { WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
|
|
10
10
|
import { SNAPSHOT_BUCKET_MISSING, isNoSuchBucketAnswer } from "./s3-answer.js";
|
|
@@ -15,8 +15,10 @@ import { WORKSPACE_CACHE_SPACE_LABEL, workspaceCacheClaimOf, workspaceCacheEnv }
|
|
|
15
15
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
16
16
|
import { PodDeleteRefused, reportPodDeleteFailed } from "./k8s-pod-delete-failure.js";
|
|
17
17
|
import { buildLivenessCut, commandBoundsOf, laneLivenessOf, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
|
|
18
|
+
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
18
19
|
import { SANDBOX_IPV6_DEFAULT, sandboxEgressGaps, sandboxPodSysctls } from "../sandbox-egress-policy.js";
|
|
19
20
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
21
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
20
22
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
21
23
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
22
24
|
import { randomUUID } from "node:crypto";
|
|
@@ -171,7 +173,6 @@ export function errorMessageOf(e) {
|
|
|
171
173
|
return typeof m === "string" && m ? m : String(e);
|
|
172
174
|
}
|
|
173
175
|
export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
|
|
174
|
-
export const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
|
|
175
176
|
const SANDBOX_SECRET_NAME_PREFIX = "sbx-env-";
|
|
176
177
|
const SANDBOX_SECRET_LABEL = "sema.io/sandbox-secret";
|
|
177
178
|
const secretEnvKey = (name) => `env.${name}`;
|
|
@@ -235,7 +236,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
235
236
|
token: config.token,
|
|
236
237
|
caCert: config.caCert,
|
|
237
238
|
insecureTls: config.insecureTls ?? false,
|
|
238
|
-
namespace: config.namespace
|
|
239
|
+
namespace: config.namespace,
|
|
239
240
|
image: config.image,
|
|
240
241
|
runtimeClass: config.runtimeClass ?? "kata-qemu",
|
|
241
242
|
mountPath: config.mountPath ?? K8S_WORKSPACE_MOUNT_DEFAULT,
|
|
@@ -258,7 +259,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
258
259
|
...(config.snapshotExclude && config.snapshotExclude.length > 0 ? { snapshotExclude: config.snapshotExclude } : {}),
|
|
259
260
|
};
|
|
260
261
|
this.cwd = this.cfg.mountPath;
|
|
261
|
-
this.homeSeat = new LaneHomeSeat(
|
|
262
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
262
263
|
this.deps = deps ?? {};
|
|
263
264
|
this.laneLiveness = laneLivenessOf(this.cfg.livenessMs, "K8S_LIVENESS_MS");
|
|
264
265
|
const occupant = occupantKeyOf(config.spaceOccupant);
|
|
@@ -268,7 +269,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
268
269
|
this.logger = config.logger;
|
|
269
270
|
this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
|
|
270
271
|
this.cacheMisses = config.cacheMisses;
|
|
271
|
-
this.capabilities = {
|
|
272
|
+
this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER, s3Snapshot: this.cfg.s3Snapshot } });
|
|
272
273
|
const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
|
|
273
274
|
this.backgroundCapabilities = {
|
|
274
275
|
supported: true,
|
|
@@ -1498,10 +1499,11 @@ export class RemoteK8sExecutionEnv {
|
|
|
1498
1499
|
let sinkClosed = false;
|
|
1499
1500
|
let removeDetachListener;
|
|
1500
1501
|
const detachTee = opts?.detach?.tee;
|
|
1501
|
-
const finish = (
|
|
1502
|
+
const finish = (outcome) => {
|
|
1502
1503
|
if (settled)
|
|
1503
|
-
return;
|
|
1504
|
+
return false;
|
|
1504
1505
|
settled = true;
|
|
1506
|
+
const r = outcome();
|
|
1505
1507
|
clearTimeout(wallTimer);
|
|
1506
1508
|
clearTimeout(livenessTimer);
|
|
1507
1509
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
@@ -1512,11 +1514,37 @@ export class RemoteK8sExecutionEnv {
|
|
|
1512
1514
|
catch {
|
|
1513
1515
|
}
|
|
1514
1516
|
resolvePromise(r);
|
|
1517
|
+
return true;
|
|
1515
1518
|
};
|
|
1516
1519
|
const bounds = commandBoundsOf({ timeoutMs: opts?.timeoutMs, readTimeoutMs: opts?.readTimeoutMs }, this.laneLiveness);
|
|
1517
1520
|
const capMs = opts?.capMs;
|
|
1518
1521
|
const wallMs = bounds.wallMs === undefined ? capMs : capMs === undefined ? bounds.wallMs : Math.min(bounds.wallMs, capMs);
|
|
1519
|
-
const
|
|
1522
|
+
const recordStderr = (bytes) => {
|
|
1523
|
+
if (bytes.length === 0)
|
|
1524
|
+
return;
|
|
1525
|
+
errBuf.push(bytes);
|
|
1526
|
+
if (detachTee)
|
|
1527
|
+
feedMemStream(detachTee.err, bytes, K8S_BG_MEM_CAP);
|
|
1528
|
+
};
|
|
1529
|
+
const emitStderr = (bytes) => {
|
|
1530
|
+
if (bytes.length > 0 && errDecoder)
|
|
1531
|
+
opts.onStderr(errDecoder.write(bytes));
|
|
1532
|
+
};
|
|
1533
|
+
const takeStderr = (bytes) => {
|
|
1534
|
+
recordStderr(bytes);
|
|
1535
|
+
emitStderr(bytes);
|
|
1536
|
+
};
|
|
1537
|
+
const heldStderr = () => opts?.stderrFilter?.endStderr();
|
|
1538
|
+
const captured = () => {
|
|
1539
|
+
const held = heldStderr();
|
|
1540
|
+
if (held)
|
|
1541
|
+
recordStderr(held);
|
|
1542
|
+
return buildCapturedOutput(outTail, errBuf);
|
|
1543
|
+
};
|
|
1544
|
+
const cut = (c) => {
|
|
1545
|
+
finish(() => ({ ok: false, error: createCutError(c, captured()) }));
|
|
1546
|
+
};
|
|
1547
|
+
const wallTimer = wallMs !== undefined ? setTimeout(() => cut({ code: "timeout", message: `command exceeded ${wallMs}ms` }), wallMs) : undefined;
|
|
1520
1548
|
let livenessTimer;
|
|
1521
1549
|
const liveness = bounds.liveness;
|
|
1522
1550
|
const armLiveness = () => {
|
|
@@ -1525,28 +1553,18 @@ export class RemoteK8sExecutionEnv {
|
|
|
1525
1553
|
clearTimeout(livenessTimer);
|
|
1526
1554
|
const idle = Date.now() - lastProgress;
|
|
1527
1555
|
if (idle >= liveness.ms) {
|
|
1528
|
-
|
|
1529
|
-
finish({ ok: false, error: new ExecutionError(cut.code, cut.message, undefined, undefined, cut.noOutputSec) });
|
|
1556
|
+
cut(buildLivenessCut(liveness));
|
|
1530
1557
|
return;
|
|
1531
1558
|
}
|
|
1532
1559
|
livenessTimer = setTimeout(armLiveness, liveness.ms - idle);
|
|
1533
1560
|
};
|
|
1534
1561
|
armLiveness();
|
|
1535
|
-
const onAbort = () =>
|
|
1562
|
+
const onAbort = () => cut({ code: "aborted", message: "command aborted" });
|
|
1536
1563
|
if (opts?.signal) {
|
|
1537
1564
|
if (opts.signal.aborted)
|
|
1538
1565
|
return onAbort();
|
|
1539
1566
|
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
1540
1567
|
}
|
|
1541
|
-
const takeStderr = (bytes) => {
|
|
1542
|
-
if (bytes.length === 0)
|
|
1543
|
-
return;
|
|
1544
|
-
errBuf.push(bytes);
|
|
1545
|
-
if (detachTee)
|
|
1546
|
-
feedMemStream(detachTee.err, bytes, K8S_BG_MEM_CAP);
|
|
1547
|
-
if (errDecoder)
|
|
1548
|
-
opts.onStderr(errDecoder.write(bytes));
|
|
1549
|
-
};
|
|
1550
1568
|
sock.on("message", (...args) => {
|
|
1551
1569
|
const data = args[0];
|
|
1552
1570
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
@@ -1588,7 +1606,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
1588
1606
|
}
|
|
1589
1607
|
return;
|
|
1590
1608
|
}
|
|
1591
|
-
finish({ ok: false, error: new ExecutionError("transport_lost", `exec socket error: ${errorMessageOf(e)}`, e instanceof Error ? e : undefined) });
|
|
1609
|
+
finish(() => ({ ok: false, error: new ExecutionError("transport_lost", `exec socket error: ${errorMessageOf(e)}`, e instanceof Error ? e : undefined) }));
|
|
1592
1610
|
});
|
|
1593
1611
|
sock.on("close", () => {
|
|
1594
1612
|
if (spliceSink) {
|
|
@@ -1601,25 +1619,30 @@ export class RemoteK8sExecutionEnv {
|
|
|
1601
1619
|
return spliceSink.closed("error" in parsed ? { failed: true } : { failed: false, exitCode: parsed.exitCode });
|
|
1602
1620
|
}
|
|
1603
1621
|
if (statusFrame === undefined) {
|
|
1604
|
-
finish({ ok: false, error: new ExecutionError("transport_lost", "exec socket closed without status — k8s transport lost mid-command") });
|
|
1622
|
+
finish(() => ({ ok: false, error: new ExecutionError("transport_lost", "exec socket closed without status — k8s transport lost mid-command") }));
|
|
1605
1623
|
return;
|
|
1606
1624
|
}
|
|
1607
1625
|
const parsed = exitCodeFromStatus(statusFrame);
|
|
1608
1626
|
if ("error" in parsed) {
|
|
1609
|
-
|
|
1627
|
+
const said = parsed.error;
|
|
1628
|
+
finish(() => ({ ok: false, error: new ExecutionError("spawn_error", said) }));
|
|
1610
1629
|
return;
|
|
1611
1630
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1631
|
+
const exitCode = parsed.exitCode;
|
|
1632
|
+
const held = heldStderr();
|
|
1633
|
+
const won = finish(() => {
|
|
1634
|
+
if (held)
|
|
1635
|
+
recordStderr(held);
|
|
1636
|
+
const err = errBuf.result();
|
|
1637
|
+
const stderr = markTruncated(err.text, err.droppedBytes);
|
|
1638
|
+
if (boundOutput) {
|
|
1639
|
+
const out = outTail.result();
|
|
1640
|
+
return ok({ stdout: markTruncated(out.text, out.droppedBytes), stderr, exitCode });
|
|
1641
|
+
}
|
|
1642
|
+
return ok({ stdout: Buffer.concat(outBufs).toString("utf8"), stderr, exitCode });
|
|
1643
|
+
});
|
|
1644
|
+
if (won && held)
|
|
1645
|
+
emitStderr(held);
|
|
1623
1646
|
});
|
|
1624
1647
|
const detach = opts?.detach;
|
|
1625
1648
|
if (detach) {
|
|
@@ -1925,30 +1948,15 @@ export class RemoteK8sExecutionEnv {
|
|
|
1925
1948
|
lines = lines.slice(0, options.maxLines);
|
|
1926
1949
|
return ok(lines);
|
|
1927
1950
|
}
|
|
1928
|
-
async ensureParentDir(abs, abortSignal) {
|
|
1929
|
-
const dir = path.posix.dirname(abs);
|
|
1930
|
-
if (!dir || dir === "." || dir === "/")
|
|
1931
|
-
return undefined;
|
|
1932
|
-
const mk = await this.fsExec(`mkdir -p ${shellQuote(dir)}`, abortSignal);
|
|
1933
|
-
if (!mk.ok)
|
|
1934
|
-
return { ok: false, error: fileErrorFromExec(mk.error, dir) };
|
|
1935
|
-
if (mk.value.exitCode !== 0)
|
|
1936
|
-
return { ok: false, error: classifyFsStderr(mk.value.stderr, dir) };
|
|
1937
|
-
return undefined;
|
|
1938
|
-
}
|
|
1939
1951
|
async writeFile(p, content, abortSignal) {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
return this.writeChunked(abs, content, true, abortSignal);
|
|
1952
|
+
return writeFileStaged(this.stagedLane(content), this.resolve(p), abortSignal);
|
|
1953
|
+
}
|
|
1954
|
+
stagedLane(content) {
|
|
1955
|
+
return { upload: (dest, signal) => this.writeChunked(dest, content, true, signal), run: (cmd, signal) => this.fsExec(cmd, signal) };
|
|
1945
1956
|
}
|
|
1946
1957
|
async appendFile(p, content, abortSignal) {
|
|
1947
1958
|
const abs = this.resolve(p);
|
|
1948
|
-
|
|
1949
|
-
if (bad)
|
|
1950
|
-
return bad;
|
|
1951
|
-
return this.writeChunked(abs, content, false, abortSignal);
|
|
1959
|
+
return appendFileStaged(this.stagedLane(content), abs, abortSignal, (ctx) => ctx.inPlace((signal) => this.writeChunked(abs, content, false, signal)));
|
|
1952
1960
|
}
|
|
1953
1961
|
async writeChunked(abs, content, truncateFirst, abortSignal) {
|
|
1954
1962
|
const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
@@ -2119,7 +2127,7 @@ export function createSandboxPodApi(config, deps) {
|
|
|
2119
2127
|
let env;
|
|
2120
2128
|
const control = () => (env ??= new RemoteK8sExecutionEnv(config, deps));
|
|
2121
2129
|
return {
|
|
2122
|
-
namespace: config.namespace
|
|
2130
|
+
namespace: config.namespace,
|
|
2123
2131
|
list: () => control().listSandboxPods(),
|
|
2124
2132
|
delete: (name) => control().deletePod(name),
|
|
2125
2133
|
};
|