@sema-agent/server 7.137.0 → 7.139.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 +600 -142
- package/README.md +7 -3
- package/README.zh-CN.md +6 -3
- package/USAGE.md +132 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +23 -16
- package/dist/approval-card.js +1 -0
- package/dist/audit.js +1 -0
- package/dist/background-agent-name.d.ts +5 -0
- package/dist/background-agent-name.js +5 -0
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/budget-tracing.d.ts +1 -12
- package/dist/boot/deployment-mods.d.ts +46 -16
- package/dist/boot/deployment-mods.js +82 -6
- 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 +16 -24
- package/dist/boot/execution-lane.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +12 -18
- package/dist/boot/governance-seams.js +10 -24
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/memory-consolidation.js +2 -2
- package/dist/boot/org-delivery.js +15 -2
- package/dist/boot/resolve-spec.d.ts +4 -1
- package/dist/boot/resolve-spec.js +29 -19
- package/dist/boot/retention-lane.d.ts +14 -8
- package/dist/boot/retention-lane.js +2 -8
- package/dist/boot/runner-deps.d.ts +10 -4
- package/dist/boot/runner-deps.js +2 -0
- 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-04-stores.d.ts +1 -12
- package/dist/boot/stage-05-execution-env.d.ts +3 -12
- package/dist/boot/stage-05-execution-env.js +8 -5
- package/dist/boot/stage-06-runners.d.ts +3 -12
- package/dist/boot/stage-06-runners.js +5 -3
- package/dist/boot/stage-07-capability-layer.d.ts +3 -12
- package/dist/boot/stage-07-capability-layer.js +2 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -12
- package/dist/boot/stage-09-leader.d.ts +3 -12
- package/dist/boot/stage-09-leader.js +2 -3
- package/dist/boot/stage-10-http-server.d.ts +3 -12
- package/dist/boot/stage-10-http-server.js +5 -3
- package/dist/boot/stores.js +4 -4
- package/dist/boot/web-admin.d.ts +5 -2
- package/dist/boot/web-admin.js +5 -5
- package/dist/brain.js +5 -5
- package/dist/budget.d.ts +17 -15
- package/dist/budget.js +2 -2
- package/dist/capabilities/mcp-probe.d.ts +36 -11
- package/dist/capabilities/mcp-probe.js +44 -5
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +21 -7
- package/dist/capability-facts.js +13 -3
- package/dist/checkpoint-identity.d.ts +13 -6
- package/dist/config-catalog.js +14 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +19 -24
- package/dist/config-provider.js +27 -12
- package/dist/config-types.d.ts +25 -12
- package/dist/config.d.ts +33 -0
- package/dist/config.js +52 -20
- package/dist/delegated-session-owner.d.ts +2 -2
- package/dist/deployment-shape.d.ts +7 -7
- 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 +19 -0
- package/dist/execution-lane-caps.js +10 -0
- 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/gate-settlement.d.ts +71 -0
- package/dist/gate-settlement.js +40 -0
- package/dist/hooks/classic-stations.d.ts +1 -0
- package/dist/hooks/classic-stations.js +1 -0
- package/dist/hooks/command-spawn.d.ts +2 -2
- package/dist/hooks/hook-runner.js +14 -14
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +10 -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/dispatch.d.ts +4 -0
- package/dist/http/dispatch.js +3 -0
- package/dist/http/durable-run-leg.js +12 -6
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/resume-legs.d.ts +5 -11
- package/dist/http/resume-legs.js +179 -104
- package/dist/http/route-ctx.d.ts +44 -59
- package/dist/http/route-ctx.js +2 -5
- package/dist/http/routes/a2a-serve.js +10 -4
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +5 -1
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/capabilities.js +12 -10
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/mcp-reconnect.d.ts +3 -2
- package/dist/http/routes/mcp-reconnect.js +7 -5
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +3 -3
- package/dist/http/routes/runs.js +11 -7
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +119 -13
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +6 -8
- package/dist/http/routes/tasks.js +800 -782
- package/dist/http/server.d.ts +61 -39
- package/dist/http/server.js +11 -0
- 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 +22 -18
- package/dist/identity-source.d.ts +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +7 -0
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +51 -40
- package/dist/mcp-assertion-header.js +10 -11
- package/dist/mcp-authorization-face.d.ts +27 -0
- package/dist/mcp-authorization-face.js +15 -0
- package/dist/memory-operator-faces.d.ts +13 -4
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-sync-client.d.ts +53 -14
- package/dist/memory-sync-client.js +187 -92
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/model-route-gap.d.ts +6 -22
- package/dist/model-route-gap.js +7 -5
- package/dist/mods-declaration.d.ts +110 -0
- package/dist/mods-declaration.js +109 -0
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/err-text.d.ts +5 -0
- package/dist/observability/err-text.js +9 -0
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/run-terminal.js +0 -1
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -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 +49 -11
- package/dist/parked-decide.js +25 -26
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +156 -0
- package/dist/peer-seat.js +273 -0
- package/dist/permission-rule-vocab.d.ts +7 -7
- package/dist/permission-rule-vocab.js +3 -3
- package/dist/plain-string-record.d.ts +38 -0
- package/dist/plain-string-record.js +38 -0
- 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 +30 -12
- package/dist/plugins/background-agent-store-sql.js +6 -3
- package/dist/plugins/caching-session-store.d.ts +11 -2
- package/dist/plugins/caching-session-store.js +15 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
- package/dist/plugins/checkpoint-store-sql.js +13 -15
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +49 -103
- 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 +7 -6
- package/dist/plugins/file-history-store-sql.js +8 -7
- 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/k8s-pod-reaper.d.ts +2 -8
- package/dist/plugins/k8s-pod-reaper.js +3 -3
- package/dist/plugins/local-checkpoint-store.d.ts +1 -11
- package/dist/plugins/local-checkpoint-store.js +0 -1
- package/dist/plugins/local-session-store.d.ts +183 -45
- package/dist/plugins/local-session-store.js +412 -105
- package/dist/plugins/mailbox-store-sql.d.ts +11 -3
- package/dist/plugins/mailbox-store-sql.js +103 -15
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
- package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -6
- package/dist/plugins/one-time-migrations.js +383 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
- package/dist/plugins/permission-rule-store-sql.js +34 -29
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +41 -22
- package/dist/plugins/pg-session-storage.js +136 -55
- package/dist/plugins/remote-env-adb.d.ts +22 -4
- package/dist/plugins/remote-env-adb.js +70 -62
- package/dist/plugins/remote-env-device.d.ts +32 -12
- package/dist/plugins/remote-env-device.js +18 -17
- package/dist/plugins/remote-env-e2b.d.ts +19 -3
- package/dist/plugins/remote-env-e2b.js +41 -42
- package/dist/plugins/remote-env-file-error.d.ts +2 -0
- package/dist/plugins/remote-env-file-error.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +23 -8
- package/dist/plugins/remote-env-host.js +66 -70
- package/dist/plugins/remote-env-k8s.d.ts +13 -16
- package/dist/plugins/remote-env-k8s.js +61 -54
- package/dist/plugins/remote-env-local-docker.d.ts +30 -5
- package/dist/plugins/remote-env-local-docker.js +46 -59
- package/dist/plugins/remote-env-ssh.d.ts +18 -4
- package/dist/plugins/remote-env-ssh.js +72 -56
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/retention-store-sql.d.ts +23 -8
- package/dist/plugins/retention-store-sql.js +13 -1
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/schema-consistency.d.ts +15 -5
- package/dist/plugins/schema-consistency.js +14 -8
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/session-fork.d.ts +63 -12
- package/dist/plugins/session-fork.js +38 -21
- package/dist/plugins/session-ownership.d.ts +7 -0
- package/dist/plugins/session-ownership.js +5 -0
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +17 -0
- package/dist/plugins/session-workspace-cell.js +38 -0
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-backend.js +26 -14
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-store.d.ts +74 -28
- package/dist/plugins/tidb-session-store.js +132 -53
- package/dist/plugins/tool-result-store-sql.d.ts +16 -8
- package/dist/plugins/tool-result-store-sql.js +26 -17
- package/dist/plugins/worktree-isolation.d.ts +3 -2
- package/dist/plugins/worktree-isolation.js +1 -7
- package/dist/project-identity.d.ts +1 -14
- package/dist/project-identity.js +0 -4
- 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/rules-consent.js +6 -4
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +36 -26
- package/dist/runs.d.ts +9 -2
- package/dist/runs.js +9 -2
- package/dist/sandbox-git-plane.d.ts +2 -2
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +15 -8
- package/dist/server-secret-env.js +4 -2
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +19 -10
- package/dist/session-incarnation.js +5 -3
- 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/session-workspace.d.ts +130 -0
- package/dist/session-workspace.js +83 -0
- package/dist/spec-fields.d.ts +1 -1
- package/dist/standing-answer-grant.d.ts +2 -13
- package/dist/standing-answer-grant.js +0 -7
- package/dist/task-a2a.d.ts +44 -14
- package/dist/task-a2a.js +46 -20
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +33 -20
- package/dist/task-mcp.js +46 -22
- package/dist/tool-approval.d.ts +38 -8
- package/dist/tool-approval.js +57 -13
- package/dist/trace/core-keyset-guard.d.ts +33 -18
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +20 -1
- package/dist/trace/exit-seat.js +3 -0
- package/dist/trace/extension-call-face.d.ts +33 -0
- package/dist/trace/extension-call-face.js +39 -0
- package/dist/trace/project.d.ts +26 -7
- package/dist/trace/project.js +30 -3
- package/dist/trace/redact.js +42 -13
- package/dist/trace/self-minted-wire.d.ts +240 -12
- package/dist/trace/self-minted-wire.js +100 -4
- package/dist/trace/wire-projection-faces.d.ts +6 -6
- package/dist/trace/wire-projection-faces.js +6 -5
- package/dist/url-refusal.d.ts +1 -1
- package/dist/user-authored-text.d.ts +2 -2
- package/dist/user-authored-text.js +16 -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/center-request.d.ts +3 -2
- package/dist/web-admin-client/center-request.js +2 -3
- package/dist/web-admin-client/skills-mcp.d.ts +8 -4
- package/dist/web-admin-client/skills-mcp.js +32 -18
- package/dist/web-admin-client/types.d.ts +11 -0
- package/package.json +3 -3
- package/wire-changes/server@7.138.0.json +446 -0
- package/wire-changes/server@7.139.0.json +348 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
- package/dist/http/mcp-remote-text.d.ts +0 -5
- package/dist/http/mcp-remote-text.js +0 -7
|
@@ -7,6 +7,7 @@ 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";
|
|
@@ -37,7 +38,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
|
|
|
37
38
|
maxScheduledHorizonSec: 0,
|
|
38
39
|
};
|
|
39
40
|
export class RemoteHostExecutionEnv {
|
|
40
|
-
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
|
|
41
|
+
capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: true, hostLocalPaths: true, externalContentTarget: false, io: true, keepsReadFloor: false };
|
|
41
42
|
get schedulerCapabilities() {
|
|
42
43
|
return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
|
|
43
44
|
}
|
|
@@ -454,11 +455,11 @@ export class RemoteHostExecutionEnv {
|
|
|
454
455
|
};
|
|
455
456
|
const timer = setTimeout(() => {
|
|
456
457
|
kill();
|
|
457
|
-
finish({ ok: false, error:
|
|
458
|
+
finish({ ok: false, error: createCutError({ code: "timeout", message: `host exec exceeded ${timeoutMs}ms` }, buildCapturedOutput(outBuf, errBuf)) });
|
|
458
459
|
}, timeoutMs);
|
|
459
460
|
const onAbort = () => {
|
|
460
461
|
kill();
|
|
461
|
-
finish({ ok: false, error:
|
|
462
|
+
finish({ ok: false, error: createCutError({ code: "aborted", message: "host exec aborted" }, buildCapturedOutput(outBuf, errBuf)) });
|
|
462
463
|
};
|
|
463
464
|
if (options?.abortSignal)
|
|
464
465
|
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -607,32 +608,36 @@ export class RemoteHostExecutionEnv {
|
|
|
607
608
|
}
|
|
608
609
|
catch { }
|
|
609
610
|
};
|
|
610
|
-
const
|
|
611
|
+
const record = (lane, inc) => {
|
|
611
612
|
lane.buf.push(inc);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
catch { }
|
|
613
|
+
};
|
|
614
|
+
const deliver = (lane, inc) => {
|
|
615
|
+
try {
|
|
616
|
+
lane.cb?.(inc.toString("utf8"));
|
|
617
617
|
}
|
|
618
|
+
catch { }
|
|
618
619
|
};
|
|
619
|
-
const
|
|
620
|
+
const feed = (lane, inc) => {
|
|
621
|
+
record(lane, inc);
|
|
622
|
+
deliver(lane, inc);
|
|
623
|
+
};
|
|
624
|
+
const pumpOnce = (take) => {
|
|
620
625
|
for (const lane of lanes) {
|
|
621
626
|
skipAhead(lane, ROTATE_BYTES);
|
|
622
627
|
const inc = readIncrement(lane.path, lane.key);
|
|
623
628
|
if (inc)
|
|
624
|
-
|
|
629
|
+
take(lane, inc);
|
|
625
630
|
reclaim(lane);
|
|
626
631
|
}
|
|
627
632
|
};
|
|
628
|
-
const drainFinal = () => {
|
|
633
|
+
const drainFinal = (take) => {
|
|
629
634
|
for (const lane of lanes) {
|
|
630
635
|
skipAhead(lane, FINAL_TAIL_BYTES);
|
|
631
636
|
for (let i = 0; i < 4; i++) {
|
|
632
637
|
const inc = readIncrement(lane.path, lane.key);
|
|
633
638
|
if (!inc)
|
|
634
639
|
break;
|
|
635
|
-
|
|
640
|
+
take(lane, inc);
|
|
636
641
|
}
|
|
637
642
|
}
|
|
638
643
|
};
|
|
@@ -661,15 +666,17 @@ export class RemoteHostExecutionEnv {
|
|
|
661
666
|
}
|
|
662
667
|
};
|
|
663
668
|
let removeDetachListener;
|
|
664
|
-
const finish = (
|
|
669
|
+
const finish = (outcome) => {
|
|
665
670
|
if (settled)
|
|
666
|
-
return;
|
|
671
|
+
return false;
|
|
667
672
|
settled = true;
|
|
673
|
+
const r = outcome();
|
|
668
674
|
clearTimeout(timer);
|
|
669
675
|
options?.abortSignal?.removeEventListener("abort", onAbort);
|
|
670
676
|
removeDetachListener?.();
|
|
671
677
|
cleanupSpool();
|
|
672
678
|
resolve(r);
|
|
679
|
+
return true;
|
|
673
680
|
};
|
|
674
681
|
const closeSpoolFds = () => {
|
|
675
682
|
for (const fd of spool.fds) {
|
|
@@ -697,66 +704,56 @@ export class RemoteHostExecutionEnv {
|
|
|
697
704
|
ticket = adoptHostForegroundChild(child);
|
|
698
705
|
closeSpoolFds();
|
|
699
706
|
child.on("error", (e) => {
|
|
700
|
-
finish({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) });
|
|
707
|
+
finish(() => ({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) }));
|
|
701
708
|
});
|
|
702
|
-
const
|
|
703
|
-
pumpOnce();
|
|
704
|
-
|
|
705
|
-
const e = errBuf.result();
|
|
706
|
-
return { stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes) };
|
|
709
|
+
const captured = () => {
|
|
710
|
+
pumpOnce(record);
|
|
711
|
+
return buildCapturedOutput(outBuf, errBuf);
|
|
707
712
|
};
|
|
708
713
|
let exited = false;
|
|
709
|
-
const
|
|
714
|
+
const cut = (c) => {
|
|
710
715
|
if (exited)
|
|
711
716
|
return;
|
|
712
|
-
|
|
713
|
-
if (child.pid != null)
|
|
714
|
-
killTreeHard(child.pid);
|
|
715
|
-
}
|
|
716
|
-
catch {
|
|
717
|
+
finish(() => {
|
|
717
718
|
try {
|
|
718
|
-
child.
|
|
719
|
+
if (child.pid != null)
|
|
720
|
+
killTreeHard(child.pid);
|
|
719
721
|
}
|
|
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");
|
|
722
|
+
catch {
|
|
723
|
+
try {
|
|
724
|
+
child.kill("SIGKILL");
|
|
725
|
+
}
|
|
726
|
+
catch { }
|
|
735
727
|
}
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
finish({ ok: false, error: new ExecutionError("aborted", "host exec aborted") });
|
|
728
|
+
return { ok: false, error: createCutError(c, captured()) };
|
|
729
|
+
});
|
|
739
730
|
};
|
|
731
|
+
const timer = setTimeout(() => cut({ code: "timeout", message: `host exec exceeded ${timeoutMs}ms` }), timeoutMs);
|
|
732
|
+
const onAbort = () => cut({ code: "aborted", message: "host exec aborted" });
|
|
740
733
|
if (options?.abortSignal?.aborted) {
|
|
741
734
|
onAbort();
|
|
742
735
|
return;
|
|
743
736
|
}
|
|
744
737
|
if (options?.abortSignal)
|
|
745
738
|
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
746
|
-
pump = setInterval(pumpOnce, PUMP_MS);
|
|
739
|
+
pump = setInterval(() => pumpOnce(feed), PUMP_MS);
|
|
747
740
|
pump.unref?.();
|
|
748
741
|
const onDetach = () => {
|
|
749
742
|
if (settled || exited)
|
|
750
743
|
return;
|
|
751
|
-
pumpOnce();
|
|
744
|
+
pumpOnce(feed);
|
|
745
|
+
if (settled)
|
|
746
|
+
return;
|
|
752
747
|
const fgDropped = spoolRemoved.out + spoolRemoved.err;
|
|
753
748
|
const shellId = this.adoptRunningExecSpool(child, spool, { out: 0, err: 0 }, fgDropped);
|
|
754
749
|
if (shellId === undefined)
|
|
755
750
|
return;
|
|
756
751
|
handedOver = true;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
752
|
+
finish(() => {
|
|
753
|
+
const o = outBuf.result();
|
|
754
|
+
const e = errBuf.result();
|
|
755
|
+
return ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode: 0, detached: { shellId } });
|
|
756
|
+
});
|
|
760
757
|
};
|
|
761
758
|
if (detachSignal) {
|
|
762
759
|
if (detachSignal.aborted)
|
|
@@ -767,13 +764,20 @@ export class RemoteHostExecutionEnv {
|
|
|
767
764
|
}
|
|
768
765
|
}
|
|
769
766
|
const settleFromTermination = (code, signal) => {
|
|
770
|
-
if (settled)
|
|
771
|
-
return;
|
|
772
|
-
drainFinal();
|
|
773
|
-
const o = outBuf.result();
|
|
774
|
-
const e = errBuf.result();
|
|
775
767
|
const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 0) : 1);
|
|
776
|
-
|
|
768
|
+
const drained = [];
|
|
769
|
+
const won = finish(() => {
|
|
770
|
+
drainFinal((lane, inc) => {
|
|
771
|
+
record(lane, inc);
|
|
772
|
+
drained.push({ lane, inc });
|
|
773
|
+
});
|
|
774
|
+
const o = outBuf.result();
|
|
775
|
+
const e = errBuf.result();
|
|
776
|
+
return ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode });
|
|
777
|
+
});
|
|
778
|
+
if (won)
|
|
779
|
+
for (const d of drained)
|
|
780
|
+
deliver(d.lane, d.inc);
|
|
777
781
|
};
|
|
778
782
|
child.on("close", (code, signal) => {
|
|
779
783
|
if (drainTimer) {
|
|
@@ -972,13 +976,13 @@ export class RemoteHostExecutionEnv {
|
|
|
972
976
|
lines = lines.slice(0, options.maxLines);
|
|
973
977
|
return ok(lines);
|
|
974
978
|
}
|
|
975
|
-
|
|
979
|
+
coreFs;
|
|
976
980
|
async openBound(p, abortSignal) {
|
|
977
981
|
const ready = await this.fsReady(p);
|
|
978
982
|
if (!ready.ok)
|
|
979
983
|
return ready;
|
|
980
|
-
this.
|
|
981
|
-
return this.
|
|
984
|
+
this.coreFs ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
|
|
985
|
+
return this.coreFs.openBound(this.resolve(p), abortSignal);
|
|
982
986
|
}
|
|
983
987
|
async readBinaryFile(p, abortSignal) {
|
|
984
988
|
const ready = await this.fsReady(p);
|
|
@@ -998,16 +1002,8 @@ export class RemoteHostExecutionEnv {
|
|
|
998
1002
|
const ready = await this.fsReady(p);
|
|
999
1003
|
if (!ready.ok)
|
|
1000
1004
|
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
|
-
}
|
|
1005
|
+
this.coreFs ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
|
|
1006
|
+
return this.coreFs.writeFile(this.resolve(p), content, abortSignal);
|
|
1011
1007
|
});
|
|
1012
1008
|
}
|
|
1013
1009
|
appendFile(p, content, abortSignal) {
|
|
@@ -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
|
|
@@ -296,15 +296,13 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
296
296
|
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
297
297
|
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
298
298
|
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
299
|
-
* 布尔字面量零影响(
|
|
299
|
+
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
300
300
|
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
301
301
|
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
302
302
|
*/
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
canonicalPathAuthoritative: true;
|
|
307
|
-
};
|
|
303
|
+
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在沙箱 pod 自己的 PID 命名空间里(不开 hostPID),不在本进程所在的进程表。 */
|
|
304
|
+
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
305
|
+
readonly capabilities: ExecutionEnvCapabilities;
|
|
308
306
|
cwd: string;
|
|
309
307
|
private podName?;
|
|
310
308
|
/** Cluster-internal pod address, captured when the pod reaches Running — the LSP `getHost` surface.
|
|
@@ -811,19 +809,18 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
811
809
|
maxLines?: number;
|
|
812
810
|
abortSignal?: AbortSignal;
|
|
813
811
|
}): Promise<Result<string[], FileError>>;
|
|
814
|
-
/** 父目录自建(与 host/local-docker/adb/ssh 同口径)。抽成共用是因为 `appendFile` 曾**跳过**它:
|
|
815
|
-
* 🔴 2026-07-25 实测的跨腿分叉 —— `appendFile` 直接调 `writeChunked`,整条路上没有 `mkdir -p`,于是
|
|
816
|
-
* 「直接 append 建一个新日志路径」在 k8s 腿上返 `not_found`,而其余 5 腿都成功(host/local-docker/adb 实测,
|
|
817
|
-
* ssh/e2b 由方法体判据)。core 的后台输出镜像正是"每拍 append 一次"的形,这条分叉会让它在 k8s 腿上从第一拍
|
|
818
|
-
* 就失败,而 `mirrorFailed` 只会表现成一句「output file is INCOMPLETE」。 */
|
|
819
|
-
private ensureParentDir;
|
|
820
812
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
813
|
+
/** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts`;父目录与私有暂存目录由编排的准备那一步建):上传 = 分块整份写进私有目录里的暂存件;
|
|
814
|
+
* 提交 = 文件面探针 exec。两者同一身份(同一只容器里的 exec)。 */
|
|
815
|
+
private stagedLane;
|
|
821
816
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
822
817
|
/**
|
|
823
818
|
* Write `content` to `abs` via argv-embedded base64, chunked at WRITE_CHUNK_BYTES (no stdin on the exec
|
|
824
819
|
* channel we use; v4 has no per-channel close). `truncateFirst` → the FIRST chunk uses `>` (overwrite),
|
|
825
820
|
* the rest `>>` (append); `false` → all chunks `>>`. Empty content still runs exactly one command so an
|
|
826
821
|
* empty file is created (write) / left intact (append). Shared by writeFile/appendFile (council DESIGN#2).
|
|
822
|
+
* Both file faces point this at a STAGING name (`writeFileStaged` / `appendFileStaged`), so a cut mid-way never reaches
|
|
823
|
+
* 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.
|
|
827
824
|
*/
|
|
828
825
|
private writeChunked;
|
|
829
826
|
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,6 +15,7 @@ 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";
|
|
20
21
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
@@ -171,7 +172,6 @@ export function errorMessageOf(e) {
|
|
|
171
172
|
return typeof m === "string" && m ? m : String(e);
|
|
172
173
|
}
|
|
173
174
|
export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
|
|
174
|
-
export const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
|
|
175
175
|
const SANDBOX_SECRET_NAME_PREFIX = "sbx-env-";
|
|
176
176
|
const SANDBOX_SECRET_LABEL = "sema.io/sandbox-secret";
|
|
177
177
|
const secretEnvKey = (name) => `env.${name}`;
|
|
@@ -235,7 +235,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
235
235
|
token: config.token,
|
|
236
236
|
caCert: config.caCert,
|
|
237
237
|
insecureTls: config.insecureTls ?? false,
|
|
238
|
-
namespace: config.namespace
|
|
238
|
+
namespace: config.namespace,
|
|
239
239
|
image: config.image,
|
|
240
240
|
runtimeClass: config.runtimeClass ?? "kata-qemu",
|
|
241
241
|
mountPath: config.mountPath ?? K8S_WORKSPACE_MOUNT_DEFAULT,
|
|
@@ -268,7 +268,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
268
268
|
this.logger = config.logger;
|
|
269
269
|
this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
|
|
270
270
|
this.cacheMisses = config.cacheMisses;
|
|
271
|
-
this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true };
|
|
271
|
+
this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
|
|
272
272
|
const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
|
|
273
273
|
this.backgroundCapabilities = {
|
|
274
274
|
supported: true,
|
|
@@ -1498,10 +1498,11 @@ export class RemoteK8sExecutionEnv {
|
|
|
1498
1498
|
let sinkClosed = false;
|
|
1499
1499
|
let removeDetachListener;
|
|
1500
1500
|
const detachTee = opts?.detach?.tee;
|
|
1501
|
-
const finish = (
|
|
1501
|
+
const finish = (outcome) => {
|
|
1502
1502
|
if (settled)
|
|
1503
|
-
return;
|
|
1503
|
+
return false;
|
|
1504
1504
|
settled = true;
|
|
1505
|
+
const r = outcome();
|
|
1505
1506
|
clearTimeout(wallTimer);
|
|
1506
1507
|
clearTimeout(livenessTimer);
|
|
1507
1508
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
@@ -1512,11 +1513,37 @@ export class RemoteK8sExecutionEnv {
|
|
|
1512
1513
|
catch {
|
|
1513
1514
|
}
|
|
1514
1515
|
resolvePromise(r);
|
|
1516
|
+
return true;
|
|
1515
1517
|
};
|
|
1516
1518
|
const bounds = commandBoundsOf({ timeoutMs: opts?.timeoutMs, readTimeoutMs: opts?.readTimeoutMs }, this.laneLiveness);
|
|
1517
1519
|
const capMs = opts?.capMs;
|
|
1518
1520
|
const wallMs = bounds.wallMs === undefined ? capMs : capMs === undefined ? bounds.wallMs : Math.min(bounds.wallMs, capMs);
|
|
1519
|
-
const
|
|
1521
|
+
const recordStderr = (bytes) => {
|
|
1522
|
+
if (bytes.length === 0)
|
|
1523
|
+
return;
|
|
1524
|
+
errBuf.push(bytes);
|
|
1525
|
+
if (detachTee)
|
|
1526
|
+
feedMemStream(detachTee.err, bytes, K8S_BG_MEM_CAP);
|
|
1527
|
+
};
|
|
1528
|
+
const emitStderr = (bytes) => {
|
|
1529
|
+
if (bytes.length > 0 && errDecoder)
|
|
1530
|
+
opts.onStderr(errDecoder.write(bytes));
|
|
1531
|
+
};
|
|
1532
|
+
const takeStderr = (bytes) => {
|
|
1533
|
+
recordStderr(bytes);
|
|
1534
|
+
emitStderr(bytes);
|
|
1535
|
+
};
|
|
1536
|
+
const heldStderr = () => opts?.stderrFilter?.endStderr();
|
|
1537
|
+
const captured = () => {
|
|
1538
|
+
const held = heldStderr();
|
|
1539
|
+
if (held)
|
|
1540
|
+
recordStderr(held);
|
|
1541
|
+
return buildCapturedOutput(outTail, errBuf);
|
|
1542
|
+
};
|
|
1543
|
+
const cut = (c) => {
|
|
1544
|
+
finish(() => ({ ok: false, error: createCutError(c, captured()) }));
|
|
1545
|
+
};
|
|
1546
|
+
const wallTimer = wallMs !== undefined ? setTimeout(() => cut({ code: "timeout", message: `command exceeded ${wallMs}ms` }), wallMs) : undefined;
|
|
1520
1547
|
let livenessTimer;
|
|
1521
1548
|
const liveness = bounds.liveness;
|
|
1522
1549
|
const armLiveness = () => {
|
|
@@ -1525,28 +1552,18 @@ export class RemoteK8sExecutionEnv {
|
|
|
1525
1552
|
clearTimeout(livenessTimer);
|
|
1526
1553
|
const idle = Date.now() - lastProgress;
|
|
1527
1554
|
if (idle >= liveness.ms) {
|
|
1528
|
-
|
|
1529
|
-
finish({ ok: false, error: new ExecutionError(cut.code, cut.message, undefined, undefined, cut.noOutputSec) });
|
|
1555
|
+
cut(buildLivenessCut(liveness));
|
|
1530
1556
|
return;
|
|
1531
1557
|
}
|
|
1532
1558
|
livenessTimer = setTimeout(armLiveness, liveness.ms - idle);
|
|
1533
1559
|
};
|
|
1534
1560
|
armLiveness();
|
|
1535
|
-
const onAbort = () =>
|
|
1561
|
+
const onAbort = () => cut({ code: "aborted", message: "command aborted" });
|
|
1536
1562
|
if (opts?.signal) {
|
|
1537
1563
|
if (opts.signal.aborted)
|
|
1538
1564
|
return onAbort();
|
|
1539
1565
|
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
1540
1566
|
}
|
|
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
1567
|
sock.on("message", (...args) => {
|
|
1551
1568
|
const data = args[0];
|
|
1552
1569
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
@@ -1588,7 +1605,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
1588
1605
|
}
|
|
1589
1606
|
return;
|
|
1590
1607
|
}
|
|
1591
|
-
finish({ ok: false, error: new ExecutionError("transport_lost", `exec socket error: ${errorMessageOf(e)}`, e instanceof Error ? e : undefined) });
|
|
1608
|
+
finish(() => ({ ok: false, error: new ExecutionError("transport_lost", `exec socket error: ${errorMessageOf(e)}`, e instanceof Error ? e : undefined) }));
|
|
1592
1609
|
});
|
|
1593
1610
|
sock.on("close", () => {
|
|
1594
1611
|
if (spliceSink) {
|
|
@@ -1601,25 +1618,30 @@ export class RemoteK8sExecutionEnv {
|
|
|
1601
1618
|
return spliceSink.closed("error" in parsed ? { failed: true } : { failed: false, exitCode: parsed.exitCode });
|
|
1602
1619
|
}
|
|
1603
1620
|
if (statusFrame === undefined) {
|
|
1604
|
-
finish({ ok: false, error: new ExecutionError("transport_lost", "exec socket closed without status — k8s transport lost mid-command") });
|
|
1621
|
+
finish(() => ({ ok: false, error: new ExecutionError("transport_lost", "exec socket closed without status — k8s transport lost mid-command") }));
|
|
1605
1622
|
return;
|
|
1606
1623
|
}
|
|
1607
1624
|
const parsed = exitCodeFromStatus(statusFrame);
|
|
1608
1625
|
if ("error" in parsed) {
|
|
1609
|
-
|
|
1626
|
+
const said = parsed.error;
|
|
1627
|
+
finish(() => ({ ok: false, error: new ExecutionError("spawn_error", said) }));
|
|
1610
1628
|
return;
|
|
1611
1629
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1630
|
+
const exitCode = parsed.exitCode;
|
|
1631
|
+
const held = heldStderr();
|
|
1632
|
+
const won = finish(() => {
|
|
1633
|
+
if (held)
|
|
1634
|
+
recordStderr(held);
|
|
1635
|
+
const err = errBuf.result();
|
|
1636
|
+
const stderr = markTruncated(err.text, err.droppedBytes);
|
|
1637
|
+
if (boundOutput) {
|
|
1638
|
+
const out = outTail.result();
|
|
1639
|
+
return ok({ stdout: markTruncated(out.text, out.droppedBytes), stderr, exitCode });
|
|
1640
|
+
}
|
|
1641
|
+
return ok({ stdout: Buffer.concat(outBufs).toString("utf8"), stderr, exitCode });
|
|
1642
|
+
});
|
|
1643
|
+
if (won && held)
|
|
1644
|
+
emitStderr(held);
|
|
1623
1645
|
});
|
|
1624
1646
|
const detach = opts?.detach;
|
|
1625
1647
|
if (detach) {
|
|
@@ -1925,30 +1947,15 @@ export class RemoteK8sExecutionEnv {
|
|
|
1925
1947
|
lines = lines.slice(0, options.maxLines);
|
|
1926
1948
|
return ok(lines);
|
|
1927
1949
|
}
|
|
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
1950
|
async writeFile(p, content, abortSignal) {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
return this.writeChunked(abs, content, true, abortSignal);
|
|
1951
|
+
return writeFileStaged(this.stagedLane(content), this.resolve(p), abortSignal);
|
|
1952
|
+
}
|
|
1953
|
+
stagedLane(content) {
|
|
1954
|
+
return { upload: (dest, signal) => this.writeChunked(dest, content, true, signal), run: (cmd, signal) => this.fsExec(cmd, signal) };
|
|
1945
1955
|
}
|
|
1946
1956
|
async appendFile(p, content, abortSignal) {
|
|
1947
1957
|
const abs = this.resolve(p);
|
|
1948
|
-
|
|
1949
|
-
if (bad)
|
|
1950
|
-
return bad;
|
|
1951
|
-
return this.writeChunked(abs, content, false, abortSignal);
|
|
1958
|
+
return appendFileStaged(this.stagedLane(content), abs, abortSignal, (ctx) => ctx.inPlace((signal) => this.writeChunked(abs, content, false, signal)));
|
|
1952
1959
|
}
|
|
1953
1960
|
async writeChunked(abs, content, truncateFirst, abortSignal) {
|
|
1954
1961
|
const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
@@ -2119,7 +2126,7 @@ export function createSandboxPodApi(config, deps) {
|
|
|
2119
2126
|
let env;
|
|
2120
2127
|
const control = () => (env ??= new RemoteK8sExecutionEnv(config, deps));
|
|
2121
2128
|
return {
|
|
2122
|
-
namespace: config.namespace
|
|
2129
|
+
namespace: config.namespace,
|
|
2123
2130
|
list: () => control().listSandboxPods(),
|
|
2124
2131
|
delete: (name) => control().deletePod(name),
|
|
2125
2132
|
};
|
|
@@ -23,7 +23,7 @@ export interface LocalDockerEnvConfig extends LaneHomeConfig {
|
|
|
23
23
|
* lane stays `local-docker` (`REMOTE_EXEC=local-docker` + `DOCKER_HOST`; the settings-schema lane table lists
|
|
24
24
|
* `DOCKER_HOST` as this lane's optional coordinate, and there is no separate `remote-docker` lane since 7.0.0).
|
|
25
25
|
* Nothing about the class is host-local: the workspace is `mkdir`'d INSIDE the container (never a bind-mount of a
|
|
26
|
-
* host path) and bytes move by `docker
|
|
26
|
+
* host path) and bytes move by `docker exec -i … cat >` / `docker exec cat`, which stream over the daemon API.
|
|
27
27
|
*/
|
|
28
28
|
dockerHost?: string;
|
|
29
29
|
/** Container memory limit (docker `--memory`, e.g. "2g"). Absent = no limit. */
|
|
@@ -62,10 +62,17 @@ export interface LocalDockerEnvDeps {
|
|
|
62
62
|
}
|
|
63
63
|
export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
|
|
64
64
|
/** DUAL-MODE-DESIGN §5: a container is a real isolation boundary; v1 has no managed memory snapshot. */
|
|
65
|
+
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在本机容器自己的 PID 命名空间里(不传 `--pid=host`),不在本进程所在的进程表。 */
|
|
66
|
+
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
65
67
|
readonly capabilities: {
|
|
66
|
-
isolation:
|
|
67
|
-
suspendable:
|
|
68
|
-
canonicalPathAuthoritative:
|
|
68
|
+
isolation: true;
|
|
69
|
+
suspendable: false;
|
|
70
|
+
canonicalPathAuthoritative: true;
|
|
71
|
+
sharesProcessTable: false;
|
|
72
|
+
hostLocalPaths: false;
|
|
73
|
+
externalContentTarget: false;
|
|
74
|
+
io: true;
|
|
75
|
+
keepsReadFloor: false;
|
|
69
76
|
};
|
|
70
77
|
/** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
|
|
71
78
|
cwd: string;
|
|
@@ -161,10 +168,27 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
161
168
|
maxLines?: number;
|
|
162
169
|
abortSignal?: AbortSignal;
|
|
163
170
|
}): Promise<Result<string[], FileError>>;
|
|
171
|
+
/** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts` `writeFileStaged` / `appendFileStaged`;父目录与私有暂存目录由编排的准备那一步建):
|
|
172
|
+
* 上传 = 容器内 `cat` 从 stdin 收整份字节落到私有目录里的暂存件({@link pipeIn});提交 = 容器内一次 shell 往返。两者同一身份 ——
|
|
173
|
+
* 同一只 argv 构造({@link execArgv}),都是镜像的用户。不用 `docker cp`:它由守护进程以 root 落盘,镜像用户 ≠ root 时暂存件属 root、
|
|
174
|
+
* 提交方拿不走也清不掉,「目录不收新名字」也探不出来(root 哪里都写得进)。 */
|
|
175
|
+
private stagedLane;
|
|
176
|
+
/** 整旧或整新(暂存 → 一次 `mv -f`)。修前 `docker cp` 直落目标(以守护进程的 root 身份):传输中途断 / 取消时目标剩什么取决于守护进程的解包实现,本适配器不依赖它。 */
|
|
164
177
|
writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
178
|
+
/** 追加(暂存要加的字节 → 一次 `cat >>`;inode 不换、已有字节不重写)。修前 = 读旧 + 拼 + 整份 cp(O(文件);中途断 ⇒ 目标没了或半份)。
|
|
179
|
+
* 目录不收新名字 / 私有目录收不成私有时的退路 = 容器内 `cat >>` 从 stdin 直接追加到目标(同一身份)。 */
|
|
165
180
|
appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
181
|
+
/**
|
|
182
|
+
* 把整份内容经 stdin 交给容器里的 `cat`,落到 `dest`(`>` 建或覆盖;`>>` 追加)。走与提交 / 文件面探针同一只往返({@link fsExec},argv 只多一个 `-i`)
|
|
183
|
+
* ⇒ 同一身份、同一工作目录、同一道守卫;二进制安全(载荷不经 shell 转义,也不在宿主落临时件)。stdin 没写完而进程退 0 ⇒ 按失败答(载荷未必整份到了)。
|
|
184
|
+
*/
|
|
185
|
+
private pipeIn;
|
|
166
186
|
private posixFsInst?;
|
|
167
187
|
private get posixFs();
|
|
188
|
+
/** 文件面的一次容器内 shell 往返(基座、暂存写的准备 / 上传 / 提交 / 清理共用)。承 fsReady 契约(其余三腿走公有 exec、守卫天然在;本腿直用私有
|
|
189
|
+
* docker,必须自带):destroyed 守卫 + 懒连接,fail closed 不抛 —— 消息保留 "destroyed" 字样(测试钉的就是它)。`input` 在场 ⇒ 同一只 argv 多一个
|
|
190
|
+
* `-i`、载荷走 stdin({@link pipeIn});身份与工作目录与不带载荷的往返逐字相同。 */
|
|
191
|
+
private fsExec;
|
|
168
192
|
fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
|
|
169
193
|
listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
170
194
|
readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
@@ -198,7 +222,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
198
222
|
/** Prepend `-H <dockerHost>` to a docker argv when configured (per-invocation, not the process env). */
|
|
199
223
|
private withHostFlag;
|
|
200
224
|
/** Build the `docker exec` argv for a command, applying cwd (`-w`) and per-command env (`-e K=V`).
|
|
201
|
-
* argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's `/bin/sh -c`.
|
|
225
|
+
* argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's `/bin/sh -c`.
|
|
226
|
+
* `stdin` ⇒ `-i` (the payload of {@link pipeIn}); nothing else differs, so a piped upload runs as the same user as every other exec. */
|
|
202
227
|
private execArgv;
|
|
203
228
|
/** Like {@link execArgv} but for an internal FileSystem helper command (uses the workspace cwd + adapter env). */
|
|
204
229
|
private execRaw;
|