@sema-agent/server 7.141.0 → 7.143.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 +657 -0
- package/USAGE.md +102 -30
- package/dist/adoption/plan.js +22 -0
- package/dist/approval-ask-audit-store.d.ts +61 -43
- package/dist/approval-ask-audit-store.js +69 -44
- package/dist/approval-ask-machine.d.ts +15 -7
- package/dist/approval-ask-machine.js +10 -4
- package/dist/approval-card.d.ts +126 -27
- package/dist/approval-card.js +103 -23
- package/dist/approval-crash-convergence.d.ts +17 -7
- package/dist/approval-crash-convergence.js +94 -22
- package/dist/approval-deny-reasons.d.ts +34 -30
- package/dist/approval-deny-reasons.js +21 -6
- package/dist/approval-policy-settlement.d.ts +1 -1
- package/dist/approval-queue-kind.d.ts +27 -0
- package/dist/approval-queue-kind.js +6 -0
- package/dist/approval-reconciler.d.ts +41 -9
- package/dist/approval-reconciler.js +45 -42
- package/dist/audit.js +4 -2
- package/dist/auth-bridge.d.ts +9 -6
- package/dist/auth-bridge.js +9 -3
- package/dist/boot/adoption.js +2 -1
- package/dist/boot/budget-tracing.d.ts +17 -1
- package/dist/boot/budget-tracing.js +10 -6
- package/dist/boot/device-lane.d.ts +1 -1
- package/dist/boot/execution-env.d.ts +24 -4
- package/dist/boot/execution-env.js +26 -23
- package/dist/boot/execution-lane.js +2 -1
- package/dist/boot/exit-boundary.js +5 -4
- package/dist/boot/governance-seams.d.ts +26 -41
- package/dist/boot/governance-seams.js +61 -28
- package/dist/boot/late-binds.d.ts +7 -4
- package/dist/boot/late-binds.js +3 -1
- package/dist/boot/leader.js +2 -4
- package/dist/boot/memory-consolidation.d.ts +0 -8
- package/dist/boot/memory-consolidation.js +5 -4
- package/dist/boot/parked-revive-gate.js +2 -1
- package/dist/boot/permission-rules-audit.d.ts +0 -28
- package/dist/boot/permission-rules-audit.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -1
- package/dist/boot/reapers.js +9 -7
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +19 -10
- package/dist/boot/retention-lane.js +5 -3
- package/dist/boot/runner-deps.d.ts +10 -7
- package/dist/boot/runner-deps.js +20 -29
- package/dist/boot/runtime-caps.js +3 -2
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.d.ts +9 -0
- package/dist/boot/shutdown.js +19 -4
- package/dist/boot/stage-02-web-admin-pull.js +3 -0
- package/dist/boot/stage-03-store-backend.d.ts +4 -1
- package/dist/boot/stage-03-store-backend.js +1 -2
- package/dist/boot/stage-04-stores.d.ts +5 -2
- package/dist/boot/stage-04-stores.js +6 -7
- package/dist/boot/stage-05-execution-env.d.ts +6 -3
- package/dist/boot/stage-05-execution-env.js +8 -6
- package/dist/boot/stage-06-runners.d.ts +6 -3
- package/dist/boot/stage-06-runners.js +3 -4
- package/dist/boot/stage-07-capability-layer.d.ts +6 -3
- package/dist/boot/stage-07-capability-layer.js +7 -2
- package/dist/boot/stage-08-reapers.d.ts +6 -3
- package/dist/boot/stage-08-reapers.js +3 -2
- package/dist/boot/stage-09-leader.d.ts +6 -3
- package/dist/boot/stage-10-http-server.d.ts +6 -3
- package/dist/boot/stage-10-http-server.js +14 -5
- package/dist/boot/stores.d.ts +5 -2
- package/dist/boot/stores.js +10 -9
- package/dist/boot/web-admin.d.ts +5 -1
- package/dist/boot/web-admin.js +48 -36
- package/dist/boot/webfetch-summarize-lane.js +2 -1
- package/dist/boot/workflow-orchestration.js +3 -2
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/builtin-tools.js +2 -4
- package/dist/capabilities/composite-members.d.ts +2 -1
- package/dist/capabilities/composite-members.js +2 -1
- package/dist/capabilities/hands-lane.d.ts +9 -39
- package/dist/capabilities/hands-lane.js +8 -13
- package/dist/capabilities/mcp-probe.d.ts +3 -2
- package/dist/capabilities/scenarios.d.ts +11 -1
- package/dist/capabilities/scenarios.js +1 -0
- package/dist/capabilities/skills.d.ts +1 -17
- package/dist/capabilities/skills.js +0 -8
- package/dist/capability-facts.d.ts +31 -7
- package/dist/capability-facts.js +27 -4
- package/dist/card-void.d.ts +6 -9
- package/dist/config-catalog.js +2 -1
- package/dist/config-dir-trust.js +58 -29
- package/dist/config-invariants.d.ts +7 -1
- package/dist/config-invariants.js +7 -0
- package/dist/config-provider.d.ts +10 -0
- package/dist/config-provider.js +15 -4
- package/dist/config-root-migration.d.ts +13 -0
- package/dist/config-root-migration.js +99 -0
- package/dist/config-types.d.ts +19 -8
- package/dist/config.d.ts +10 -3
- package/dist/config.js +18 -4
- package/dist/delegated-session-owner.d.ts +5 -3
- package/dist/delegated-session-owner.js +1 -1
- package/dist/device-ws-protocol.d.ts +1 -0
- package/dist/device-ws-protocol.js +2 -1
- package/dist/env-model-table-gate.d.ts +53 -0
- package/dist/env-model-table-gate.js +53 -0
- package/dist/execution-lane-caps.d.ts +2 -1
- package/dist/fleet/fleet-bus.d.ts +34 -55
- package/dist/fleet/fleet-bus.js +35 -100
- package/dist/fleet/fleet-reconciler.d.ts +4 -4
- package/dist/fleet/fleet-reconciler.js +1 -1
- package/dist/fleet/subagent-tail-bus.d.ts +14 -11
- package/dist/fleet/subagent-tail-bus.js +15 -28
- package/dist/hooks/hook-runner.d.ts +6 -0
- package/dist/hooks/hook-runner.js +58 -8
- package/dist/host-decision.d.ts +54 -20
- package/dist/host-decision.js +42 -14
- package/dist/http/admission.d.ts +1 -1
- package/dist/http/admission.js +7 -3
- package/dist/http/approval-sweeps.js +11 -8
- package/dist/http/caller-trust.d.ts +1 -1
- package/dist/http/dispatch.js +4 -2
- package/dist/http/durable-run-leg.js +2 -2
- package/dist/http/engine-refusal.d.ts +16 -0
- package/dist/http/engine-refusal.js +17 -0
- package/dist/http/execution-context.d.ts +2 -2
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
- package/dist/http/memory-compliance-failure-reply.js +4 -6
- package/dist/http/owner-access.d.ts +11 -0
- package/dist/http/owner-access.js +13 -0
- package/dist/http/principal-gate.d.ts +3 -1
- package/dist/http/principal-gate.js +17 -8
- package/dist/http/resume-legs.d.ts +21 -7
- package/dist/http/resume-legs.js +76 -58
- package/dist/http/resume-restatement.d.ts +39 -0
- package/dist/http/resume-restatement.js +25 -0
- package/dist/http/route-ctx.d.ts +35 -10
- package/dist/http/route-ctx.js +1 -1
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/approvals-assistant.d.ts +9 -1
- package/dist/http/routes/approvals-assistant.js +142 -31
- package/dist/http/routes/attachments.js +18 -0
- package/dist/http/routes/capabilities.js +16 -4
- package/dist/http/routes/memory-policy.d.ts +2 -2
- package/dist/http/routes/memory-policy.js +27 -7
- package/dist/http/routes/notify-wake.js +23 -25
- package/dist/http/routes/rules.js +48 -13
- package/dist/http/routes/runs.js +12 -11
- package/dist/http/routes/session-memory-sync.d.ts +91 -0
- package/dist/http/routes/session-memory-sync.js +174 -0
- package/dist/http/routes/session-sync.js +13 -20
- package/dist/http/routes/sessions-list.d.ts +2 -1
- package/dist/http/routes/sessions.d.ts +2 -2
- package/dist/http/routes/sessions.js +22 -23
- package/dist/http/routes/tasks.js +34 -54
- package/dist/http/run-read-face.d.ts +7 -0
- package/dist/http/run-read-face.js +9 -3
- package/dist/http/send.d.ts +5 -0
- package/dist/http/send.js +9 -2
- package/dist/http/server.d.ts +27 -15
- package/dist/http/server.js +4 -6
- package/dist/http/stored-body-replay.js +15 -4
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +2 -2
- package/dist/http/sync-submit-body.d.ts +21 -4
- package/dist/http/sync-submit-body.js +13 -0
- package/dist/http/wire-types.d.ts +17 -7
- package/dist/identity-source.d.ts +21 -2
- package/dist/identity-source.js +6 -0
- package/dist/leader/leader.js +5 -2
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +6 -5
- package/dist/leg-facts.d.ts +37 -0
- package/dist/leg-facts.js +15 -0
- package/dist/memory-bundle-engine.d.ts +1 -4
- package/dist/memory-bundle-engine.js +2 -2
- package/dist/memory-incident-sink.d.ts +19 -6
- package/dist/memory-incident-sink.js +8 -4
- package/dist/memory-operator-faces.d.ts +30 -66
- package/dist/memory-operator-faces.js +17 -88
- package/dist/memory-scope-stores.d.ts +38 -40
- package/dist/memory-scope-stores.js +51 -36
- package/dist/memory-scope.d.ts +0 -9
- package/dist/memory-scope.js +0 -9
- package/dist/memory-sync-client.d.ts +21 -21
- package/dist/memory-sync-client.js +62 -43
- package/dist/memory-sync-identity.d.ts +24 -0
- package/dist/memory-sync-identity.js +57 -0
- package/dist/memory-sync.d.ts +27 -5
- package/dist/memory-sync.js +29 -7
- package/dist/model-plane-face.d.ts +13 -7
- package/dist/model-plane-face.js +2 -1
- package/dist/observability/fail-open.d.ts +47 -14
- package/dist/observability/fail-open.js +37 -11
- package/dist/observability/run-terminal.d.ts +92 -34
- package/dist/observability/run-terminal.js +171 -162
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
- package/dist/orchestration/workflow-completion-inbox.js +0 -12
- package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
- package/dist/orchestration/workflow-park-decision.d.ts +8 -3
- package/dist/orchestration/workflow-park-decision.js +5 -3
- package/dist/org-delivery-gate.d.ts +11 -0
- package/dist/org-delivery-gate.js +19 -1
- package/dist/parked-decide.d.ts +13 -9
- package/dist/parked-decide.js +7 -7
- package/dist/parked-revive-surface.js +1 -1
- package/dist/peer-inbound-face.d.ts +127 -0
- package/dist/peer-inbound-face.js +158 -0
- package/dist/permission-rule-list.d.ts +13 -9
- package/dist/permission-rule-list.js +7 -3
- package/dist/plugins/approval-ask-store-file.d.ts +14 -4
- package/dist/plugins/approval-ask-store-file.js +64 -11
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +17 -14
- package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
- package/dist/plugins/approval-ask-store-sql.js +79 -30
- package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
- package/dist/plugins/checkpoint-store-sql.js +9 -7
- package/dist/plugins/file-history-store-sql.d.ts +24 -18
- package/dist/plugins/file-history-store-sql.js +24 -19
- package/dist/plugins/file-run-store.d.ts +35 -1
- package/dist/plugins/file-run-store.js +108 -9
- package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
- package/dist/plugins/imported-layer-root-retirement.js +96 -0
- package/dist/plugins/local-checkpoint-store.d.ts +1 -1
- package/dist/plugins/local-checkpoint-store.js +2 -2
- package/dist/plugins/local-session-store.d.ts +42 -9
- package/dist/plugins/local-session-store.js +83 -33
- package/dist/plugins/local-task-attachment-store.d.ts +12 -2
- package/dist/plugins/local-task-attachment-store.js +67 -43
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
- package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
- package/dist/plugins/mailbox-store-sql.d.ts +12 -8
- package/dist/plugins/mailbox-store-sql.js +76 -7
- package/dist/plugins/memory-key-guards.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +6 -1
- package/dist/plugins/memory-run-store.js +11 -1
- package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
- package/dist/plugins/memory-sync-store-pg.js +21 -0
- package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
- package/dist/plugins/memory-sync-store-tidb.js +13 -1
- package/dist/plugins/one-time-migrations.d.ts +58 -2
- package/dist/plugins/one-time-migrations.js +161 -4
- package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
- package/dist/plugins/org-delivery-quota-store.js +2 -12
- package/dist/plugins/park-commit-watch.d.ts +1 -1
- package/dist/plugins/permission-rule-store-file.d.ts +10 -1
- package/dist/plugins/permission-rule-store-file.js +15 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
- package/dist/plugins/permission-rule-store-sql.js +35 -2
- package/dist/plugins/remote-env-device.d.ts +8 -11
- package/dist/plugins/remote-env-device.js +6 -3
- package/dist/plugins/remote-env-e2b.d.ts +23 -4
- package/dist/plugins/remote-env-e2b.js +111 -28
- package/dist/plugins/remote-env-k8s.d.ts +61 -22
- package/dist/plugins/remote-env-k8s.js +133 -33
- package/dist/plugins/remote-env-local-docker.d.ts +15 -3
- package/dist/plugins/remote-env-local-docker.js +40 -12
- package/dist/plugins/retention-store-sql.d.ts +2 -2
- package/dist/plugins/run-store-sql.d.ts +9 -1
- package/dist/plugins/run-store-sql.js +9 -1
- package/dist/plugins/sandbox-read-floor.d.ts +5 -7
- package/dist/plugins/sandbox-read-floor.js +2 -7
- package/dist/plugins/sql-errors.d.ts +10 -1
- package/dist/plugins/sql-errors.js +13 -0
- package/dist/plugins/store-backend.d.ts +33 -35
- package/dist/plugins/store-backend.js +10 -1
- package/dist/plugins/store-contracts.d.ts +50 -1
- package/dist/plugins/store-contracts.js +6 -0
- package/dist/plugins/task-attachment-store.d.ts +112 -14
- package/dist/plugins/task-attachment-store.js +207 -29
- package/dist/plugins/tool-result-store-sql.d.ts +9 -8
- package/dist/plugins/tool-result-store-sql.js +23 -2
- package/dist/plugins/wiring-section-codec.d.ts +32 -0
- package/dist/plugins/wiring-section-codec.js +55 -0
- package/dist/plugins/workspace-fill.d.ts +60 -0
- package/dist/plugins/workspace-fill.js +106 -0
- package/dist/project-bundle.d.ts +38 -0
- package/dist/project-bundle.js +609 -0
- package/dist/rule-entry-list.d.ts +3 -2
- package/dist/rule-entry-list.js +5 -1
- package/dist/rules-consent.d.ts +28 -5
- package/dist/rules-consent.js +40 -28
- package/dist/run-local.js +11 -13
- package/dist/runs.d.ts +12 -8
- package/dist/runs.js +6 -8
- package/dist/runtime-governance.d.ts +1 -1
- package/dist/sandbox-egress-policy.d.ts +4 -1
- package/dist/sandbox-egress-policy.js +3 -1
- package/dist/sandbox-git-plane.d.ts +31 -12
- package/dist/sandbox-git-plane.js +24 -4
- package/dist/security.d.ts +9 -0
- package/dist/security.js +8 -0
- package/dist/server-secret-env.d.ts +4 -0
- package/dist/session-error-wire.d.ts +36 -0
- package/dist/session-error-wire.js +13 -0
- package/dist/session-incarnation.d.ts +1 -1
- package/dist/session-notify-door.d.ts +106 -0
- package/dist/session-notify-door.js +93 -0
- package/dist/session-sync.d.ts +9 -14
- package/dist/session-sync.js +5 -5
- package/dist/session-workspace.d.ts +3 -13
- package/dist/session-workspace.js +0 -9
- package/dist/standing-answer-grant.d.ts +5 -0
- package/dist/standing-answer-grant.js +10 -0
- package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
- package/dist/stores-sql/session-policy-store-sql.js +3 -13
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +21 -10
- package/dist/task-settings.js +46 -13
- package/dist/terminal.d.ts +39 -19
- package/dist/terminal.js +65 -22
- package/dist/tool-approval.d.ts +56 -37
- package/dist/tool-approval.js +78 -71
- package/dist/trace/core-keyset-guard.d.ts +134 -14
- package/dist/trace/engine-notice-wire.d.ts +9 -3
- package/dist/trace/engine-notice-wire.js +7 -3
- package/dist/trace/exit-seat.d.ts +9 -2
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/ledger-sink.d.ts +10 -6
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/model-segment.d.ts +55 -25
- package/dist/trace/model-segment.js +101 -33
- package/dist/trace/project.d.ts +25 -5
- package/dist/trace/project.js +22 -0
- package/dist/trace/redact.d.ts +61 -9
- package/dist/trace/redact.js +1490 -468
- package/dist/trace/self-minted-wire.d.ts +745 -24
- package/dist/trace/self-minted-wire.js +239 -9
- package/dist/trace/wire-projection-faces.d.ts +2 -2
- package/dist/trace/wire-projection-faces.js +2 -2
- package/dist/verifier-pause-card.d.ts +33 -5
- package/dist/verifier-pause-card.js +15 -10
- package/dist/web-admin-client/apply-effective.d.ts +12 -16
- package/dist/web-admin-client/apply-effective.js +109 -9
- package/dist/web-admin-client/effective-keys.js +2 -0
- package/dist/web-admin-client/types.d.ts +4 -0
- package/dist/workspace-space.d.ts +19 -3
- package/dist/workspace-space.js +8 -4
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
- package/package.json +3 -2
- package/wire-changes/server@7.142.0.json +639 -0
- package/wire-changes/server@7.143.0.json +1167 -0
|
@@ -8,7 +8,7 @@ import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, shellQuote,
|
|
|
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";
|
|
11
|
-
import { K8S_WORKSPACE_MOUNT_DEFAULT, SNAPSHOT_KEY_SEGMENT, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, mintSnapshotId, snapshotKeyPrefixProblem, snapshotNameOwnedBy } from "../workspace-space.js";
|
|
11
|
+
import { K8S_WORKSPACE_MOUNT_DEFAULT, SNAPSHOT_KEY_SEGMENT, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, mintSnapshotId, snapshotIdHolds, snapshotKeyPrefixProblem, snapshotNameOwnedBy } from "../workspace-space.js";
|
|
12
12
|
import { spaceCommitAlive } from "../workspace-space-commit.js";
|
|
13
13
|
import { incarnationLogPrefix, redactIncarnations } from "../session-incarnation.js";
|
|
14
14
|
import { WORKSPACE_CACHE_SPACE_LABEL, workspaceCacheClaimOf, workspaceCacheEnv } from "../workspace-cache.js";
|
|
@@ -48,6 +48,9 @@ export { applyPodSpecPatch } from "./k8s-exec-protocol.js";
|
|
|
48
48
|
import { sandboxRunEndHoldMs } from "./k8s-pod-reaper.js";
|
|
49
49
|
import { BROKER_COMMAND, BROKER_CONTAINER_NAME, BROKER_LEASE_DIR, BROKER_LEASE_KEY, BROKER_PROBE_ARG, BROKER_VOLUME_NAME } from "../broker/constants.js";
|
|
50
50
|
import { buildSandboxLeaseFile, placeholderLeaseEntries, sandboxGitRouteEnv } from "../sandbox-git-lease.js";
|
|
51
|
+
function minted(c) {
|
|
52
|
+
return c.ok ? { ...c, origin: "minted" } : c;
|
|
53
|
+
}
|
|
51
54
|
const CACHE_VOLUME = ADAPTER_VOLUME_NAMES.cache;
|
|
52
55
|
function occupantKeyOf(occupant) {
|
|
53
56
|
if (occupant === undefined)
|
|
@@ -213,6 +216,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
213
216
|
inFlight = new Set();
|
|
214
217
|
idleWaiters = [];
|
|
215
218
|
connectInFlight;
|
|
219
|
+
resumeInFlight;
|
|
216
220
|
connectRefusal;
|
|
217
221
|
get homeDir() {
|
|
218
222
|
return this.homeSeat.home;
|
|
@@ -231,6 +235,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
231
235
|
cacheUnavailableSaid = false;
|
|
232
236
|
logger;
|
|
233
237
|
onWorkspaceSnapshot;
|
|
238
|
+
workspaceFill;
|
|
234
239
|
cacheMisses;
|
|
235
240
|
constructor(config, deps) {
|
|
236
241
|
this.cfg = {
|
|
@@ -270,6 +275,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
270
275
|
this.cacheLane = cacheLaneOf(config.cache, config.spaceOccupant);
|
|
271
276
|
this.logger = config.logger;
|
|
272
277
|
this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
|
|
278
|
+
this.workspaceFill = config.workspaceFill;
|
|
273
279
|
this.cacheMisses = config.cacheMisses;
|
|
274
280
|
this.floor = this.deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor, ...(config.logger !== undefined ? { logger: config.logger } : {}) });
|
|
275
281
|
this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER, s3Snapshot: this.cfg.s3Snapshot }, sandboxReadFloor: config.sandboxReadFloor });
|
|
@@ -374,10 +380,14 @@ export class RemoteK8sExecutionEnv {
|
|
|
374
380
|
});
|
|
375
381
|
}
|
|
376
382
|
connect(config) {
|
|
377
|
-
this.connectInFlight
|
|
378
|
-
this.connectInFlight
|
|
383
|
+
if (this.connectInFlight)
|
|
384
|
+
return this.connectInFlight;
|
|
385
|
+
const walk = this.establish(config).finally(() => {
|
|
386
|
+
if (this.connectInFlight === walk)
|
|
387
|
+
this.connectInFlight = undefined;
|
|
379
388
|
});
|
|
380
|
-
|
|
389
|
+
this.connectInFlight = walk;
|
|
390
|
+
return walk;
|
|
381
391
|
}
|
|
382
392
|
async establish(config) {
|
|
383
393
|
if (this.connectRefusal)
|
|
@@ -386,10 +396,10 @@ export class RemoteK8sExecutionEnv {
|
|
|
386
396
|
const lane = this.spaceLane;
|
|
387
397
|
switch (lane.state) {
|
|
388
398
|
case "off":
|
|
389
|
-
return this.provision(signal);
|
|
399
|
+
return this.settle(minted(await this.provision(signal)));
|
|
390
400
|
case "unavailable":
|
|
391
401
|
this.sayUnavailable(lane.reason);
|
|
392
|
-
return this.provision(signal);
|
|
402
|
+
return this.settle(minted(await this.provision(signal)));
|
|
393
403
|
case "unreadable":
|
|
394
404
|
return this.provision(signal);
|
|
395
405
|
case "open": {
|
|
@@ -399,7 +409,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
399
409
|
const latest = await this.readLatest(lane.space, signal);
|
|
400
410
|
if (!latest.ok)
|
|
401
411
|
return latest;
|
|
402
|
-
const c = latest.value === undefined ? await this.provision(signal) : await this.restoreSnapshot({ lane: "space", space: lane.space }, latest.value, signal);
|
|
412
|
+
const c = await this.settle(latest.value === undefined ? minted(await this.provision(signal)) : await this.restoreSnapshot({ lane: "space", space: lane.space }, latest.value, signal));
|
|
403
413
|
if (c.ok)
|
|
404
414
|
this.armSpace();
|
|
405
415
|
return c;
|
|
@@ -410,6 +420,43 @@ export class RemoteK8sExecutionEnv {
|
|
|
410
420
|
}
|
|
411
421
|
}
|
|
412
422
|
}
|
|
423
|
+
async settle(c) {
|
|
424
|
+
if (!c.ok)
|
|
425
|
+
return c;
|
|
426
|
+
const fill = this.workspaceFill;
|
|
427
|
+
if (fill !== undefined) {
|
|
428
|
+
const filled = await fill.fill(this.fillChannel(), this.cwd, c.origin);
|
|
429
|
+
if (!filled.ok) {
|
|
430
|
+
await this.releasePod();
|
|
431
|
+
return filled.standing ? this.holdRefusal(filled.error) : { ok: false, error: filled.error };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (this.destroyed) {
|
|
435
|
+
await this.releasePod();
|
|
436
|
+
return { ok: false, error: new RemoteExecutionError("connect_failed", "env destroyed during connect") };
|
|
437
|
+
}
|
|
438
|
+
return { ok: true, value: c.value };
|
|
439
|
+
}
|
|
440
|
+
fillChannel() {
|
|
441
|
+
const exec = (command) => this.runExec(ADAPTER_START, command, { capMs: this.cfg.dataTimeoutMs });
|
|
442
|
+
return {
|
|
443
|
+
run: async (command) => {
|
|
444
|
+
const r = await exec(command);
|
|
445
|
+
return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
|
|
446
|
+
},
|
|
447
|
+
write: async (abs, bytes) => {
|
|
448
|
+
const w = await this.writeChunked(abs, bytes, true, undefined, exec);
|
|
449
|
+
return w.ok ? { ok: true } : { ok: false, error: w.error.message };
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
async releasePod() {
|
|
454
|
+
const name = this.podName;
|
|
455
|
+
this.podName = undefined;
|
|
456
|
+
this.podIP = undefined;
|
|
457
|
+
if (name)
|
|
458
|
+
await this.deletePod(name).catch(() => { });
|
|
459
|
+
}
|
|
413
460
|
armSpace() {
|
|
414
461
|
if (this.spaceLane.state === "open")
|
|
415
462
|
this.spaceLane.space.fence.arm(this.cfg.dataTimeoutMs);
|
|
@@ -462,8 +509,10 @@ export class RemoteK8sExecutionEnv {
|
|
|
462
509
|
return spaceReadFailed("occupant", ` (${code}) — refusing to start this run on a workspace that could be neither restored nor saved; retry the run once the session store answers, or set ${ways}`);
|
|
463
510
|
}
|
|
464
511
|
spaceLine(event, space, fields) {
|
|
465
|
-
|
|
466
|
-
|
|
512
|
+
this.snapshotLine(event, { space: incarnationLogPrefix(space.incarnation), ...fields });
|
|
513
|
+
}
|
|
514
|
+
snapshotLine(event, fields) {
|
|
515
|
+
this.logger?.warn(event, Object.fromEntries(Object.entries(fields).map(([k, v]) => [k, typeof v === "string" ? redactIncarnations(v) : v])));
|
|
467
516
|
}
|
|
468
517
|
provision(signal) {
|
|
469
518
|
const space = this.spaceLane;
|
|
@@ -1177,12 +1226,12 @@ export class RemoteK8sExecutionEnv {
|
|
|
1177
1226
|
async uploadSnapshot(target, boundMs = this.cfg.dataTimeoutMs) {
|
|
1178
1227
|
if (target.lane === "park" && this.inFlight.size > 0)
|
|
1179
1228
|
return { ok: false, error: new RemoteExecutionError("command_in_flight", "cannot suspend while a command is in flight (would snapshot a torn workspace)") };
|
|
1180
|
-
const snapshotId = mintSnapshotId(this.occupantIncarnation);
|
|
1181
1229
|
if (!this.podName) {
|
|
1182
1230
|
return target.lane === "space"
|
|
1183
1231
|
? { ok: false, error: new RemoteExecutionError("connect_failed", "the sandbox pod was gone when the run-end snapshot reached the upload — nothing was saved (the workspace could not be read), and `latest` keeps naming the previous snapshot") }
|
|
1184
|
-
: ok(
|
|
1232
|
+
: ok(mintSnapshotId(this.occupantIncarnation, "nothing"));
|
|
1185
1233
|
}
|
|
1234
|
+
const snapshotId = mintSnapshotId(this.occupantIncarnation, "archive");
|
|
1186
1235
|
const presigned = this.presignSnapshot({ ...target, method: "PUT", snapshotId });
|
|
1187
1236
|
if (!presigned.ok)
|
|
1188
1237
|
return presigned;
|
|
@@ -1202,16 +1251,50 @@ export class RemoteK8sExecutionEnv {
|
|
|
1202
1251
|
await this.deletePod(name).catch(() => { });
|
|
1203
1252
|
return ok(snapshotId);
|
|
1204
1253
|
}
|
|
1205
|
-
|
|
1254
|
+
resumeVM(snapshotId, options) {
|
|
1206
1255
|
if (!this.cfg.s3Snapshot)
|
|
1207
|
-
return unsupported("resumeVM");
|
|
1208
|
-
const
|
|
1256
|
+
return Promise.resolve(unsupported("resumeVM"));
|
|
1257
|
+
const same = this.resumeInFlight;
|
|
1258
|
+
if (same?.snapshotId === snapshotId)
|
|
1259
|
+
return same.walk;
|
|
1260
|
+
const prior = this.connectInFlight;
|
|
1261
|
+
const walk = this.resumeWalk(prior, snapshotId, options).finally(() => {
|
|
1262
|
+
if (this.connectInFlight === walk)
|
|
1263
|
+
this.connectInFlight = undefined;
|
|
1264
|
+
if (this.resumeInFlight?.walk === walk)
|
|
1265
|
+
this.resumeInFlight = undefined;
|
|
1266
|
+
});
|
|
1267
|
+
this.connectInFlight = walk;
|
|
1268
|
+
this.resumeInFlight = { snapshotId, walk };
|
|
1269
|
+
return walk;
|
|
1270
|
+
}
|
|
1271
|
+
async resumeWalk(prior, snapshotId, options) {
|
|
1272
|
+
if (prior)
|
|
1273
|
+
await prior;
|
|
1274
|
+
if (this.podName) {
|
|
1275
|
+
this.logger?.warn("sandbox_pod_released_before_restore", { pod: this.podName, ...(this.cfg.sessionId ? { sessionId: this.cfg.sessionId } : {}), effect: "this env held a pod minted before the park snapshot's restore — it is deleted; the restore provisions the leg's one pod" });
|
|
1276
|
+
await this.releasePod();
|
|
1277
|
+
}
|
|
1278
|
+
const r = await this.restorePointOf(snapshotId, options?.abortSignal);
|
|
1209
1279
|
if (!r.ok)
|
|
1210
1280
|
return r;
|
|
1211
1281
|
this.handleSnapshotId = snapshotId;
|
|
1212
1282
|
this.armSpace();
|
|
1213
1283
|
return ok(this.workspaceHandle());
|
|
1214
1284
|
}
|
|
1285
|
+
async restorePointOf(snapshotId, abortSignal) {
|
|
1286
|
+
const holds = snapshotIdHolds(snapshotId);
|
|
1287
|
+
switch (holds) {
|
|
1288
|
+
case "archive":
|
|
1289
|
+
return this.settle(await this.restoreSnapshot({ lane: "park" }, snapshotId, abortSignal));
|
|
1290
|
+
case "nothing":
|
|
1291
|
+
return this.establish(abortSignal ? { abortSignal } : undefined);
|
|
1292
|
+
default: {
|
|
1293
|
+
const unreachable = holds;
|
|
1294
|
+
throw new Error(`unhandled snapshot id family: ${String(unreachable)}`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1215
1298
|
async restoreSnapshot(target, snapshotId, abortSignal) {
|
|
1216
1299
|
const presigned = this.presignSnapshot({ ...target, method: "GET", snapshotId });
|
|
1217
1300
|
if (!presigned.ok)
|
|
@@ -1234,15 +1317,13 @@ export class RemoteK8sExecutionEnv {
|
|
|
1234
1317
|
? { cls: restoreClassOfExit(r.value.exitCode), detail: `workspace snapshot restore exited ${r.value.exitCode}: ${this.redactUrl(r.value.stderr, url).slice(0, 300)}` }
|
|
1235
1318
|
: undefined;
|
|
1236
1319
|
if (!failure) {
|
|
1237
|
-
if (
|
|
1238
|
-
await this.
|
|
1239
|
-
|
|
1320
|
+
if (r.ok && r.value.stdout.includes("EMPTY_SNAPSHOT")) {
|
|
1321
|
+
await this.dropDangling(target, snapshotId);
|
|
1322
|
+
return { ok: true, value: this.workspaceHandle(), origin: "minted" };
|
|
1323
|
+
}
|
|
1324
|
+
return { ok: true, value: this.workspaceHandle(), origin: "restored" };
|
|
1240
1325
|
}
|
|
1241
|
-
|
|
1242
|
-
this.podName = undefined;
|
|
1243
|
-
this.podIP = undefined;
|
|
1244
|
-
if (name)
|
|
1245
|
-
await this.deletePod(name).catch(() => { });
|
|
1326
|
+
await this.releasePod();
|
|
1246
1327
|
if (attempt < RESTORE_MAX_ATTEMPTS[target.lane] && restoreFailureIsTransient(failure.cls)) {
|
|
1247
1328
|
if (await this.restorePause(abortSignal))
|
|
1248
1329
|
continue;
|
|
@@ -1280,15 +1361,34 @@ export class RemoteK8sExecutionEnv {
|
|
|
1280
1361
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1281
1362
|
});
|
|
1282
1363
|
}
|
|
1283
|
-
async
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1364
|
+
async dropDangling(target, snapshotId) {
|
|
1365
|
+
const key = `${this.snapshotBase(target)}${snapshotId}.tar.gz`;
|
|
1366
|
+
switch (target.lane) {
|
|
1367
|
+
case "space": {
|
|
1368
|
+
const dropped = await this.writePointer(target.space, "latest", null);
|
|
1369
|
+
this.spaceLine("workspace_latest_dangling", target.space, {
|
|
1370
|
+
lane: "space",
|
|
1371
|
+
key,
|
|
1372
|
+
deleted: dropped.ok,
|
|
1373
|
+
...(dropped.ok ? {} : { error: dropped.error.message }),
|
|
1374
|
+
effect: "the space's latest snapshot is gone (removed by a bucket lifecycle rule or by hand) — this run starts from an empty /workspace; its run-end snapshot becomes the new latest",
|
|
1375
|
+
});
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
case "park":
|
|
1379
|
+
this.snapshotLine("workspace_latest_dangling", {
|
|
1380
|
+
lane: "park",
|
|
1381
|
+
key,
|
|
1382
|
+
...(this.cfg.sessionId ? { sessionId: this.cfg.sessionId } : {}),
|
|
1383
|
+
effect: "the parked run's snapshot is gone (removed by a bucket lifecycle rule or by hand) — the resumed leg starts from an empty /workspace" +
|
|
1384
|
+
(this.spaceLane.state === "open" ? "; its run-end snapshot becomes the space's new latest" : ""),
|
|
1385
|
+
});
|
|
1386
|
+
return;
|
|
1387
|
+
default: {
|
|
1388
|
+
const unreachable = target;
|
|
1389
|
+
throw new Error(`unhandled snapshot lane: ${String(unreachable.lane)}`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1292
1392
|
}
|
|
1293
1393
|
async postResumeInit() {
|
|
1294
1394
|
if (!this.cfg.s3Snapshot)
|
|
@@ -2081,12 +2181,12 @@ export class RemoteK8sExecutionEnv {
|
|
|
2081
2181
|
const abs = this.resolve(p);
|
|
2082
2182
|
return appendFileStaged(this.stagedLane(content), abs, abortSignal, (ctx) => ctx.inPlace((signal) => this.writeChunked(abs, content, false, signal)));
|
|
2083
2183
|
}
|
|
2084
|
-
async writeChunked(abs, content, truncateFirst, abortSignal) {
|
|
2184
|
+
async writeChunked(abs, content, truncateFirst, abortSignal, exec = (command) => this.execData(command, abortSignal)) {
|
|
2085
2185
|
const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
2086
2186
|
for (let off = 0, first = true; off < bytes.length || first; off += WRITE_CHUNK_BYTES, first = false) {
|
|
2087
2187
|
const b64 = bytes.subarray(off, off + WRITE_CHUNK_BYTES).toString("base64");
|
|
2088
2188
|
const redirect = first && truncateFirst ? ">" : ">>";
|
|
2089
|
-
const r = await
|
|
2189
|
+
const r = await exec(`printf '%s' ${shellQuote(b64)} | base64 -d ${redirect} ${shellQuote(abs)}`);
|
|
2090
2190
|
if (!r.ok)
|
|
2091
2191
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
2092
2192
|
if (r.value.exitCode !== 0)
|
|
@@ -3,6 +3,7 @@ import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
|
3
3
|
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 ExecutionEnvCapabilities } from "@sema-agent/core";
|
|
4
4
|
import { type SandboxReadFloorKnob } from "../execution-lane-caps.js";
|
|
5
5
|
import { type SandboxReadFloor } from "./sandbox-read-floor.js";
|
|
6
|
+
import type { WorkspaceFill, WorkspaceFillOf } from "./workspace-fill.js";
|
|
6
7
|
/** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
|
|
7
8
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
8
9
|
/** Construction config for {@link RemoteLocalDockerExecutionEnv}. */
|
|
@@ -59,6 +60,8 @@ export interface LocalDockerEnvConfig extends LaneHomeConfig {
|
|
|
59
60
|
id?: string;
|
|
60
61
|
/** `SANDBOX_READ_FLOOR`(keep | off;缺席 ⇒ keep):本容器守不守读拒表的字节地板 = 读地板模块服务本车道 ∧ 本键不是 off(`sandbox-read-floor.ts`)。 */
|
|
61
62
|
sandboxReadFloor?: SandboxReadFloorKnob;
|
|
63
|
+
/** 恢复点的填充座(`workspace-fill.ts`;工厂按 run 装配):容器新建之后、交给本环境之前,经本适配器自己的 `docker exec` 往工作区里放内容。缺席 ⇒ 不放。 */
|
|
64
|
+
workspaceFill?: WorkspaceFill;
|
|
62
65
|
}
|
|
63
66
|
/** Inject `spawn` for tests (a fake docker CLI). Defaults to node's child_process spawn. */
|
|
64
67
|
export interface LocalDockerEnvDeps {
|
|
@@ -92,12 +95,15 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
92
95
|
private destroyed;
|
|
93
96
|
/** In-flight connect, memoized so concurrent lazy first-use starts exactly one container (no double-run). */
|
|
94
97
|
private connecting?;
|
|
95
|
-
/**
|
|
96
|
-
|
|
98
|
+
/** 这只环境的拒 —— 记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。两个写者:家目录(环境答了而与声明不一致)
|
|
99
|
+
* 与恢复点的填充(换一只容器也是同一个答的拒:种子不是项目包、摘要不符、镜像没有 `tar`、镜像预置撞名 —— `FillOutcome.standing`)。 */
|
|
100
|
+
private connectRefusal?;
|
|
97
101
|
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
98
102
|
private readonly homeSeat;
|
|
99
103
|
constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
|
|
100
104
|
private resolve;
|
|
105
|
+
/** 收据句柄。还没连上(懒环境:续跑腿的引擎在恢复相**之前**先取它比车道)⇒ 未连接形 `{ sandboxId: "(unconnected)", provider, mountPath }`(与 k8s / e2b 车道同形),
|
|
106
|
+
* 不抛 —— 抛了,续跑腿在恢复相就失败。 */
|
|
101
107
|
workspaceHandle(): WorkspaceHandle;
|
|
102
108
|
connect(config?: RemoteConnectConfig): Promise<{
|
|
103
109
|
ok: true;
|
|
@@ -107,6 +113,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
107
113
|
error: RemoteExecutionError;
|
|
108
114
|
}>;
|
|
109
115
|
private doConnect;
|
|
116
|
+
/** 恢复点的填充:有座 ⇒ 经 {@link fillChannel} 放(容器恒是新起的 ⇒ `minted`;本车道不可挂起);没有 ⇒ 什么都不放。 */
|
|
117
|
+
private fillWorkspace;
|
|
118
|
+
/** 恢复点手里的通道:`docker exec` 经唯一起动接缝(起动形 `adapter`,与引擎命令同一执行用户;连接进行中,不经懒连接的文件面);写 = 容器内 `cat >` 从 stdin 收整份字节。 */
|
|
119
|
+
private fillChannel;
|
|
110
120
|
/**
|
|
111
121
|
* 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 实测)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、同一份 `-e` env),读数交座。
|
|
112
122
|
* 声明在场:环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,
|
|
@@ -258,6 +268,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
258
268
|
* owns the lifetime and calls `destroy()` (= `docker rm -f`) on task end. Folds the per-task `ctx.taskId`/
|
|
259
269
|
* `ctx.sessionId` into the container name so two concurrent tasks never share a container.
|
|
260
270
|
*/
|
|
261
|
-
export declare function localDockerExecutionEnvFactory(config: LocalDockerEnvConfig,
|
|
271
|
+
export declare function localDockerExecutionEnvFactory(config: Omit<LocalDockerEnvConfig, "workspaceFill"> & {
|
|
272
|
+
readonly workspaceFillOf?: WorkspaceFillOf;
|
|
273
|
+
}, deps?: LocalDockerEnvDeps): ExecutionEnvFactory;
|
|
262
274
|
export {};
|
|
263
275
|
//# sourceMappingURL=remote-env-local-docker.d.ts.map
|
|
@@ -37,7 +37,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
37
37
|
handle;
|
|
38
38
|
destroyed = false;
|
|
39
39
|
connecting;
|
|
40
|
-
|
|
40
|
+
connectRefusal;
|
|
41
41
|
homeSeat;
|
|
42
42
|
constructor(config, deps = {}) {
|
|
43
43
|
if (!config.image || !config.image.trim()) {
|
|
@@ -64,17 +64,15 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
64
64
|
return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
|
|
65
65
|
}
|
|
66
66
|
workspaceHandle() {
|
|
67
|
-
|
|
68
|
-
throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
|
|
69
|
-
return this.handle;
|
|
67
|
+
return this.handle ?? { sandboxId: "(unconnected)", provider: PROVIDER, mountPath: this.cfg.mountPath };
|
|
70
68
|
}
|
|
71
69
|
async connect(config) {
|
|
72
70
|
if (config?.snapshotId)
|
|
73
71
|
return unsupported("connect(snapshotId)");
|
|
74
72
|
if (this.destroyed)
|
|
75
73
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
76
|
-
if (this.
|
|
77
|
-
return { ok: false, error: this.
|
|
74
|
+
if (this.connectRefusal)
|
|
75
|
+
return { ok: false, error: this.connectRefusal };
|
|
78
76
|
if (config?.abortSignal?.aborted)
|
|
79
77
|
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
|
|
80
78
|
if (this.containerId)
|
|
@@ -139,12 +137,38 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
139
137
|
await this.forceRemove().catch(() => { });
|
|
140
138
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
|
|
141
139
|
}
|
|
140
|
+
const filled = await this.fillWorkspace();
|
|
141
|
+
if (!filled.ok || this.destroyed) {
|
|
142
|
+
await this.forceRemove().catch(() => { });
|
|
143
|
+
if (!filled.ok && filled.standing)
|
|
144
|
+
this.connectRefusal = filled.error;
|
|
145
|
+
return { ok: false, error: filled.ok ? new RemoteExecutionError("connect_failed", "execution env destroyed during connect") : filled.error };
|
|
146
|
+
}
|
|
142
147
|
this.containerId = run.value.stdout.trim() || this.containerName;
|
|
143
148
|
this.handle = { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
|
|
144
149
|
if (config?.secrets?.length) {
|
|
145
150
|
}
|
|
146
151
|
return ok(this.handle);
|
|
147
152
|
}
|
|
153
|
+
fillWorkspace() {
|
|
154
|
+
return this.cfg.workspaceFill === undefined ? Promise.resolve({ ok: true }) : this.cfg.workspaceFill.fill(this.fillChannel(), this.cfg.mountPath, "minted");
|
|
155
|
+
}
|
|
156
|
+
fillChannel() {
|
|
157
|
+
const run = async (command, input) => {
|
|
158
|
+
const argv = this.startArgv(ADAPTER_START, command, { shell: "/bin/sh", ...(input !== undefined ? { stdin: true } : {}) });
|
|
159
|
+
if (!argv.ok)
|
|
160
|
+
return { ok: false, error: argv.error.message };
|
|
161
|
+
const r = await this.docker(argv.value, { timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
|
|
162
|
+
return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
run: (command) => run(command),
|
|
166
|
+
write: async (abs, bytes) => {
|
|
167
|
+
const r = await run(`cat > ${shellQuote(abs)}`, bytes);
|
|
168
|
+
return !r.ok ? r : r.exitCode === 0 ? { ok: true } : { ok: false, error: r.stderr.trim() || `exit ${r.exitCode}` };
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
148
172
|
async homeVerdict(signal) {
|
|
149
173
|
if (!this.homeSeat.asks)
|
|
150
174
|
return undefined;
|
|
@@ -157,7 +181,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
157
181
|
return undefined;
|
|
158
182
|
const error = new RemoteExecutionError("connect_failed", verdict.refusal);
|
|
159
183
|
if (verdict.answered)
|
|
160
|
-
this.
|
|
184
|
+
this.connectRefusal = error;
|
|
161
185
|
return error;
|
|
162
186
|
}
|
|
163
187
|
async suspendVM(_options) {
|
|
@@ -172,8 +196,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
172
196
|
async reconnect(sessionToken) {
|
|
173
197
|
if (this.destroyed)
|
|
174
198
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
|
|
175
|
-
if (this.
|
|
176
|
-
return { ok: false, error: this.
|
|
199
|
+
if (this.connectRefusal)
|
|
200
|
+
return { ok: false, error: this.connectRefusal };
|
|
177
201
|
if (sessionToken && sessionToken !== this.containerName) {
|
|
178
202
|
return { ok: false, error: new RemoteExecutionError("connect_failed", `reconnect token '${String(sessionToken)}' does not match this env's container '${this.containerName}'`) };
|
|
179
203
|
}
|
|
@@ -189,8 +213,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
189
213
|
const home = this.containerId === undefined ? await this.homeVerdict() : undefined;
|
|
190
214
|
if (home !== undefined)
|
|
191
215
|
return { ok: false, error: home };
|
|
192
|
-
if (this.destroyed || this.
|
|
193
|
-
return { ok: false, error: this.
|
|
216
|
+
if (this.destroyed || this.connectRefusal)
|
|
217
|
+
return { ok: false, error: this.connectRefusal ?? new RemoteExecutionError("connect_failed", "execution env destroyed during reconnect") };
|
|
194
218
|
this.containerId = this.containerName;
|
|
195
219
|
this.handle ??= { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
|
|
196
220
|
return ok(this.handle);
|
|
@@ -682,6 +706,10 @@ function errMsg(e) {
|
|
|
682
706
|
return e instanceof Error ? e.message : String(e);
|
|
683
707
|
}
|
|
684
708
|
export function localDockerExecutionEnvFactory(config, deps = {}) {
|
|
685
|
-
|
|
709
|
+
const { workspaceFillOf, ...base } = config;
|
|
710
|
+
return (ctx) => {
|
|
711
|
+
const workspaceFill = workspaceFillOf?.(ctx);
|
|
712
|
+
return new RemoteLocalDockerExecutionEnv({ ...base, id: base.id ?? ctx.taskId ?? ctx.sessionId, ...(workspaceFill !== undefined ? { workspaceFill } : {}) }, deps);
|
|
713
|
+
};
|
|
686
714
|
}
|
|
687
715
|
//# sourceMappingURL=remote-env-local-docker.js.map
|
|
@@ -387,7 +387,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
387
387
|
* 它的域完全靠「属主 sessionId 的墓碑」推。而会话 id 是**调用方可自选**的 ⇒ 两个租户先后用过同一个 id
|
|
388
388
|
* 完全合法;两边都删过之后,同一个 row_key 上会有**两个域**的墓碑,这时任何一边的 sweep 拿自己的墓碑
|
|
389
389
|
* 去认领这些字节都只是猜。判据因此是:**存在第二个域的同名墓碑 ⇒ 这批行不可归属 ⇒ 一根不删**,
|
|
390
|
-
* 实体清理退回既有的 TTL 腿(`
|
|
390
|
+
* 实体清理退回既有的 TTL 腿(core `ToolResultStore.reap`)。少删一次是可接受的;删掉另一个租户的字节、还跳过它的
|
|
391
391
|
* legal hold 与审计域,不可接受。
|
|
392
392
|
*/
|
|
393
393
|
protected orphanToolResultCandidates(exec: {
|
|
@@ -475,7 +475,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
475
475
|
* 也只计 `skipped` 不删。会话本体的清理归 {@link SqlRetentionStore.deleteExpiredSessions}(它会连着
|
|
476
476
|
* 这行一起删),两条腿因此不重叠也不互相抢行。
|
|
477
477
|
* · **一根不碰的那一支**:`owner_session_id IS NULL` 的**无出处**行。没有出处就没有域,按任何域去删它
|
|
478
|
-
* 都是猜;实体清理归既有的 TTL 腿(`
|
|
478
|
+
* 都是猜;实体清理归既有的 TTL 腿(core `ToolResultStore.reap`,`boot/reapers.ts` 驱动)。这与 core 的
|
|
479
479
|
* 「an UNOWNED entry is never deleted」是同一条纪律的两处执行点。
|
|
480
480
|
*
|
|
481
481
|
* 事务形与另外两只方法逐字同款(hold 哨兵行锁读 → 删除+墓碑 → 审计行,同 commit 同 rollback)。
|
|
@@ -2,7 +2,7 @@ import type { TaskStatus } from "@sema-agent/core";
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool } from "pg";
|
|
4
4
|
import { type UsageRow } from "../usage-analytics.js";
|
|
5
|
-
import { type ClaimFenced, type CreateRunOutcome, type FencedRun, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
5
|
+
import { type ClaimFenced, type CreateRunOutcome, type FencedRun, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef, type SettledLegMemoryObservation } from "./store-contracts.js";
|
|
6
6
|
import { type SqlDriver, type SqlTxConn } from "./sql-driver.js";
|
|
7
7
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
8
8
|
import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
|
|
@@ -228,6 +228,14 @@ export declare class SqlRunStore {
|
|
|
228
228
|
* 两方言只差占位符(动态子句为零,不像 listRuns/listSessions 那样需要各自的构造器臂)。
|
|
229
229
|
*/
|
|
230
230
|
latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
|
|
231
|
+
/**
|
|
232
|
+
* [ref] —— 这个会话**最近一条已结腿**与它的记忆观测(规则属主 = `store-contracts.ts` 的 {@link SettledLegMemoryObservation};
|
|
233
|
+
* 文件 / 内存两店是它的孪生)。排序口径与 {@link latestRunForSession} 逐字同一句;已结词集按本文件头注的惯例写字面(两方言并排可读),
|
|
234
|
+
* 与属主谓词 `isTerminalRunStatus` 的同源由方言对 parity 门钉(`test/source-hygiene-gate.test.ts` 的语句规则 `settled-leg-observation-read`)。
|
|
235
|
+
* 同一条索引路径(`idx_task_run_session_status` 的 `(session_id, status)` 前缀),**不新加索引**。
|
|
236
|
+
* 结果列走 {@link rowToRun} 同一只解码口与同一个失败方向(读不出 ⇒ 拒,不把一条结了的腿读成「没有观测」)。
|
|
237
|
+
*/
|
|
238
|
+
latestMemoryObservationForSession(sessionId: string): Promise<SettledLegMemoryObservation | undefined>;
|
|
231
239
|
/** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
|
|
232
240
|
* (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
|
|
233
241
|
* session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
|
|
@@ -6,7 +6,7 @@ import { pgSanitizeText } from "./pg-safe-json.js";
|
|
|
6
6
|
import { toIso as iso } from "./sql-row-helpers.js";
|
|
7
7
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
8
8
|
import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonObjectColumn, readNonNullJsonValueColumn } from "./sql-json-column.js";
|
|
9
|
-
import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode } from "./store-contracts.js";
|
|
9
|
+
import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode, settledLegObservationOf } from "./store-contracts.js";
|
|
10
10
|
import { notifyRunTerminal, runTerminalLogLevel } from "../observability/run-terminal.js";
|
|
11
11
|
import { mysqlDriver, pgDriver, dialectJsonEncoder, SQL_DB_NOW_MS, sqlDbNowMs } from "./sql-driver.js";
|
|
12
12
|
import { isDupKeyError } from "./sql-errors.js";
|
|
@@ -413,6 +413,14 @@ export class SqlRunStore {
|
|
|
413
413
|
const r = rows[0];
|
|
414
414
|
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
415
415
|
}
|
|
416
|
+
async latestMemoryObservationForSession(sessionId) {
|
|
417
|
+
const { rows } = await this.db.query(this.q("SELECT task_id, " + jsonColumnText("mysql", "result") + " FROM task_run WHERE session_id = ? AND status IN ('completed','failed','blocked') ORDER BY created_at DESC, task_id DESC LIMIT 1", "SELECT task_id, " + jsonColumnText("pg", "result") + " FROM task_run WHERE session_id = $1 AND status IN ('completed','failed','blocked') ORDER BY created_at DESC, task_id DESC LIMIT 1"), [sessionId]);
|
|
418
|
+
const r = rows[0];
|
|
419
|
+
if (r === undefined)
|
|
420
|
+
return undefined;
|
|
421
|
+
const taskId = String(r.task_id);
|
|
422
|
+
return settledLegObservationOf(taskId, refusing("read a settled leg as if it had observed no memory", () => decodeResultColumn(r.result, taskId)) ?? null);
|
|
423
|
+
}
|
|
416
424
|
async listSessions(opts) {
|
|
417
425
|
if (this.db.dialect === "mysql") {
|
|
418
426
|
const params = [];
|
|
@@ -5,11 +5,9 @@ export declare const READ_FLOOR_UNAVAILABLE_REASONS: readonly ["arch", "user_not
|
|
|
5
5
|
export type ReadFloorUnavailableReason = (typeof READ_FLOOR_UNAVAILABLE_REASONS)[number];
|
|
6
6
|
/** F2:附着不过的环境上,带地板起动的拒句(纯字符串 ⇒ `build*`)。首句点名原因词;点名表的首行与旋钮。 */
|
|
7
7
|
export declare function buildReadFloorUnavailableMessage(reason: ReadFloorUnavailableReason, detail: string | undefined, floor: ReadFloor | undefined): string;
|
|
8
|
-
/** F6:守地板的环境上,不带地板的引擎起动的拒句。 */
|
|
9
|
-
export declare function buildReadFloorMissingMessage(lane: SandboxFloorLane): string;
|
|
10
8
|
/**
|
|
11
9
|
* 一次起动是什么(闭集,调用方必须表态 —— 没有缺省臂):
|
|
12
|
-
* · `engine` —— 经执行环境公共起动口要起的命令(引擎的,或替引擎干活的服务端调用方的)
|
|
10
|
+
* · `engine` —— 经执行环境公共起动口要起的命令(引擎的,或替引擎干活的服务端调用方的);它带的地板就是它的表,缺席 = 表无行。`hold` = 适配器之后还会以
|
|
13
11
|
* 同一起动号再起 `run`(k8s 后台作业的轮询 / 停 / 收尾读的是这次起动的产物,按这次起动的地板跑),登记留到 {@link SandboxReadFloor.release};
|
|
14
12
|
* · `under` —— 以一次已登记(`hold`)起动的号再起一只 `run`(上面那类轮询);
|
|
15
13
|
* · `adapter` —— 适配器自己的固定命令(文件面助手、探针、数据搬运、全杀);附着成功之后经监督者的 `plain` 起;
|
|
@@ -29,7 +27,7 @@ export type FloorStart = {
|
|
|
29
27
|
};
|
|
30
28
|
export declare const ADAPTER_START: FloorStart;
|
|
31
29
|
export declare const BOOTSTRAP_START: FloorStart;
|
|
32
|
-
/** 引擎起动:`readFloor` 原样交(`undefined` =
|
|
30
|
+
/** 引擎起动:`readFloor` 原样交(`undefined` = 这次起动的表一行都没有)。 */
|
|
33
31
|
export declare function engineStart(readFloor: ReadFloor | undefined, hold?: boolean): FloorStart;
|
|
34
32
|
/** 起动身份:`root` = 车道以 root 起(e2b `user:"root"`、local-docker `-u 0`);`exec-user` = 车道自己的执行身份(改前的形)。 */
|
|
35
33
|
export type FloorLaunchIdentity = "root" | "exec-user";
|
|
@@ -125,8 +123,8 @@ export interface RelayAnswer {
|
|
|
125
123
|
export declare const UNJUDGED_VERDICT = "read floor: no verdict for this start";
|
|
126
124
|
/** 判官:适配器进程里唯一回答「这条路径拒不拒」的东西。live object ⇒ `create*`。 */
|
|
127
125
|
export interface FloorJudge {
|
|
128
|
-
/**
|
|
129
|
-
register(floor: ReadFloor, held: boolean): string;
|
|
126
|
+
/** 登记一次引擎起动(带它的表;`undefined` = 表无行),回它的起动号(随机、不可猜)。`held` ⇒ 之后还会以这个号再起 `run`(`under`),直到 {@link release};否则这一次就是它唯一的起动。 */
|
|
127
|
+
register(floor: ReadFloor | undefined, held: boolean): string;
|
|
130
128
|
/** 以这个号再起一次 `run`(`under`)之前问:在册且适配器没说过「不再起」⇒ `true`(记下又有一只监督者将连上);否则 `false`(起动拒)。 */
|
|
131
129
|
launch(startId: string): boolean;
|
|
132
130
|
/** 适配器不会再以这个号起 `run` 了。登记要两件都到才撤(协议 §5.5):这一句 ∧ 此后的 `gone` —— `gone` 已在 ⇒ 当即撤;否则等它(还连着的监督者照旧有判官答)。 */
|
|
@@ -202,7 +200,7 @@ export interface SandboxReadFloor {
|
|
|
202
200
|
readonly state: SandboxReadFloorState;
|
|
203
201
|
/** 这只模块钉住的监督者二进制(守地板 ⇒ 在;裸模块 ⇒ 缺席)。k8s 的 pod 座({@link buildFloorPodSeat})只读它 —— 放进 pod 的与自检核的是同一份钉。 */
|
|
204
202
|
readonly binaries: FloorBinaries | undefined;
|
|
205
|
-
/** 唯一接缝:一次起动 → 答案,或 `spawn_error`(F2 /
|
|
203
|
+
/** 唯一接缝:一次起动 → 答案,或 `spawn_error`(F2 / 时机不对的引导起动 / 已撤的号)。 */
|
|
206
204
|
wrap(start: FloorStart): Result<FloorLaunch, ExecutionError>;
|
|
207
205
|
/** 适配器不会再以这个号起 `run`(`hold` 起动的收尾)。登记在其后的 `gone` 到时撤(还连着的监督者照旧有判官答)。 */
|
|
208
206
|
release(startId: string | undefined): void;
|
|
@@ -35,11 +35,6 @@ export function buildReadFloorUnavailableMessage(reason, detail, floor) {
|
|
|
35
35
|
`This command carries a read-deny table${first !== undefined ? ` (first row "${first}")` : ""} this sandbox cannot keep, so it is refused rather than run with fewer rows. ` +
|
|
36
36
|
`To run this lane without the byte floor, set ${SANDBOX_READ_FLOOR_ENV}=off (the lane then declares keepsReadFloor=false: each run with read-deny rows is announced, or refused when READ_DENY_FLOOR_REQUIRED=true).`);
|
|
37
37
|
}
|
|
38
|
-
export function buildReadFloorMissingMessage(lane) {
|
|
39
|
-
return (`read floor: this ${lane} sandbox keeps the read-deny byte floor, and this command start carries no floor — it is refused rather than run a process the floor does not judge ` +
|
|
40
|
-
`(every start on a lane that keeps the floor must carry the run's read-deny table). ` +
|
|
41
|
-
`To run this lane without the byte floor, set ${SANDBOX_READ_FLOOR_ENV}=off.`);
|
|
42
|
-
}
|
|
43
38
|
export const ADAPTER_START = Object.freeze({ kind: "adapter" });
|
|
44
39
|
export const BOOTSTRAP_START = Object.freeze({ kind: "bootstrap" });
|
|
45
40
|
export function engineStart(readFloor, hold = false) {
|
|
@@ -121,6 +116,8 @@ export function parseRelayFrame(line) {
|
|
|
121
116
|
}
|
|
122
117
|
export const UNJUDGED_VERDICT = "read floor: no verdict for this start";
|
|
123
118
|
function judgeOne(floor, path) {
|
|
119
|
+
if (floor === undefined)
|
|
120
|
+
return null;
|
|
124
121
|
let v;
|
|
125
122
|
try {
|
|
126
123
|
v = floor.denies(path);
|
|
@@ -433,8 +430,6 @@ export function createKeptSandboxReadFloor(opts) {
|
|
|
433
430
|
}
|
|
434
431
|
case "engine": {
|
|
435
432
|
const floor = start.readFloor;
|
|
436
|
-
if (floor === undefined)
|
|
437
|
-
return spawnError(buildReadFloorMissingMessage(lane));
|
|
438
433
|
if (phase.kind === "unavailable")
|
|
439
434
|
return spawnError(buildReadFloorUnavailableMessage(phase.reason, phase.detail, floor));
|
|
440
435
|
if (phase.kind !== "ready" || bin === undefined)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SQL 驱动错误的**方言判别**——「这是不是唯一键冲突」的单一属主([ref] P1-①,病族
|
|
3
3
|
* single-semantic-multi-site-drift)。`sql-escape.ts` / `sql-row-helpers.ts` / `store-contracts.ts`
|
|
4
|
-
* 的同级叶子:零 store 逻辑、零驱动依赖(不 import mysql2/pg)
|
|
4
|
+
* 的同级叶子:零 store 逻辑、零驱动依赖(不 import mysql2/pg),纯谓词 —— 外加死锁判据唯一的消费形 {@link retryOnDeadlock}
|
|
5
|
+
* (「撞死锁重来几次」与「什么算死锁」住在一处:上界只有一个数,不在各店各写一个)。
|
|
5
6
|
*
|
|
6
7
|
* ## 为什么必须只有一份
|
|
7
8
|
* 收编前这条谓词在 src/ 手铸 ≥7 处,而且**识别集互相漂移**:一半站点只认 `code === "ER_DUP_ENTRY"`,
|
|
@@ -96,6 +97,14 @@ export declare const PG_DEADLOCK_DETECTED_SQLSTATE = "40P01";
|
|
|
96
97
|
* **MySQL 协议腿**,两方言都认只是为了让店不必自己判引擎。
|
|
97
98
|
*/
|
|
98
99
|
export declare function isDeadlockError(dialect: SqlDialect, err: unknown): boolean;
|
|
100
|
+
/** 引擎仲裁之后整段重来的**唯一**上界(全仓每一条「撞死锁就重来」的腿共用这一个数)。8 = 锁形并发套的并发度:缺行首写的 N 路竞争每轮至多多走一轮([ref] 读数 1 成 7 死锁)。 */
|
|
101
|
+
export declare const DEADLOCK_RETRY_LIMIT = 8;
|
|
102
|
+
/**
|
|
103
|
+
* 「引擎判了死锁 ⇒ 整段重来」的**唯一**写法:有界({@link DEADLOCK_RETRY_LIMIT}),判据 {@link isDeadlockError};用尽 / 其它错误原样抛。
|
|
104
|
+
* `attempt` 必须整段重做语义等价(读-判-写全在里面的一只事务,或一条幂等语句)。`alsoArbitrated`:店自己认的另一种仲裁产物(例:缺行首写撞主键 ——
|
|
105
|
+
* 并发的另一只首写赢了,重读即走更新腿),与死锁同族、同一个上界。锁等待超时不在任何一族里(延迟信号,见 {@link isDeadlockError} 的注):它照抛。
|
|
106
|
+
*/
|
|
107
|
+
export declare function retryOnDeadlock<T>(dialect: SqlDialect, attempt: () => Promise<T>, alsoArbitrated?: (err: unknown) => boolean): Promise<T>;
|
|
99
108
|
/** TiDB `ErrEntryTooLarge` (one row's key + value over `txn-entry-size-limit`, 6 MiB by default) — TiDB's own
|
|
100
109
|
* errno, which mysql2 carries without a symbolic name. */
|
|
101
110
|
export declare const TIDB_ER_ENTRY_TOO_LARGE_ERRNO = 8025;
|