@sema-agent/server 7.132.0 → 7.134.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 +353 -0
- package/README.md +8 -9
- package/README.zh-CN.md +7 -6
- package/USAGE.md +301 -64
- package/deploy/sema-up/README.md +35 -1
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
- package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +19 -2
- package/deploy/sema-up/chart/templates/server.yaml +19 -2
- package/deploy/sema-up/chart/values-full-single.yaml +5 -1
- package/deploy/sema-up/chart/values.yaml +34 -0
- package/deploy/sema-up/stack/compose.yaml +3 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
- package/dist/adoption/plan.d.ts +3 -2
- package/dist/adoption/plan.js +7 -5
- package/dist/adoption/runner.js +3 -10
- package/dist/adoption/sql.d.ts +3 -2
- package/dist/adoption/sql.js +3 -5
- package/dist/adoption/wire.d.ts +4 -1
- package/dist/adoption/wire.js +1 -0
- package/dist/approval-ask-audit-store.d.ts +32 -14
- package/dist/approval-ask-audit-store.js +272 -74
- package/dist/approval-ask-machine.d.ts +15 -0
- package/dist/approval-ask-machine.js +6 -0
- package/dist/approval-ask-retention.d.ts +136 -0
- package/dist/approval-ask-retention.js +102 -0
- package/dist/approval-card.d.ts +83 -23
- package/dist/approval-card.js +45 -15
- package/dist/approval-content-kind.d.ts +5 -2
- package/dist/approval-content-kind.js +16 -3
- package/dist/approval-deny-reasons.d.ts +1 -1
- package/dist/approval-gate-currency.d.ts +72 -0
- package/dist/approval-gate-currency.js +94 -0
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +55 -17
- package/dist/audit.d.ts +27 -10
- package/dist/audit.js +12 -16
- package/dist/bake-runner/main.js +3 -2
- package/dist/boot/coordinators.d.ts +6 -1
- package/dist/boot/coordinators.js +6 -3
- package/dist/boot/deferred-sandbox-path-env.js +1 -1
- package/dist/boot/execution-env.js +9 -7
- package/dist/boot/execution-lane.js +11 -0
- package/dist/boot/leader.d.ts +2 -0
- package/dist/boot/leader.js +4 -1
- package/dist/boot/memory-boundary.js +2 -2
- package/dist/boot/memory-consolidation.d.ts +5 -4
- package/dist/boot/memory-consolidation.js +7 -6
- package/dist/boot/org-delivery.d.ts +28 -0
- package/dist/boot/org-delivery.js +20 -0
- package/dist/boot/read-deny-anchor-audit.js +2 -2
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +21 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +60 -81
- package/dist/boot/runner-deps.d.ts +18 -5
- package/dist/boot/runner-deps.js +10 -0
- package/dist/boot/session-faces.d.ts +1 -0
- package/dist/boot/session-faces.js +4 -2
- package/dist/boot/stage-02-web-admin-pull.js +4 -1
- package/dist/boot/stage-06-runners.d.ts +6 -3
- package/dist/boot/stage-06-runners.js +20 -4
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-07-capability-layer.js +24 -3
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +5 -1
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -1
- package/dist/boot/stage-10-http-server.js +6 -6
- package/dist/boot/stores.d.ts +8 -0
- package/dist/boot/stores.js +10 -14
- package/dist/boot/web-admin.d.ts +10 -0
- package/dist/boot/web-admin.js +162 -65
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/brain.js +8 -3
- package/dist/breaker-key.d.ts +6 -0
- package/dist/breaker-key.js +29 -0
- package/dist/budget.js +5 -1
- package/dist/caller-text-echo.d.ts +11 -6
- package/dist/caller-text-echo.js +10 -12
- package/dist/canonical-json.d.ts +2 -41
- package/dist/canonical-json.js +2 -13
- package/dist/capabilities/center-plugins.d.ts +7 -0
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +21 -0
- package/dist/capabilities/hands-lane.js +10 -0
- package/dist/capabilities/mcp-probe.d.ts +36 -4
- package/dist/capabilities/mcp-probe.js +29 -6
- package/dist/capabilities/scenarios.d.ts +63 -7
- package/dist/capabilities/scenarios.js +39 -7
- package/dist/capability-facts.d.ts +36 -4
- package/dist/capability-facts.js +30 -4
- package/dist/center-config-tier.d.ts +69 -0
- package/dist/center-config-tier.js +42 -0
- package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
- package/dist/checkpoint-ctx-seal-m28.js +19 -0
- package/dist/checkpoint-identity.d.ts +16 -0
- package/dist/checkpoint-identity.js +19 -0
- package/dist/config-catalog.d.ts +17 -10
- package/dist/config-catalog.js +43 -28
- package/dist/config-d-migration.d.ts +3 -1
- package/dist/config-d-migration.js +2 -2
- package/dist/config-dir-trust.d.ts +114 -0
- package/dist/config-dir-trust.js +388 -0
- package/dist/config-invariants.d.ts +18 -5
- package/dist/config-invariants.js +35 -3
- package/dist/config-provider.d.ts +6 -1
- package/dist/config-provider.js +6 -2
- package/dist/config-types.d.ts +71 -14
- package/dist/config-types.js +8 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +121 -59
- package/dist/delegated-session-owner.d.ts +185 -0
- package/dist/delegated-session-owner.js +119 -0
- package/dist/device-protocol.d.ts +5 -3
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.js +21 -2
- package/dist/device-ws-protocol.d.ts +70 -6
- package/dist/device-ws-protocol.js +50 -23
- package/dist/elicitation.js +3 -2
- package/dist/entry-problems.d.ts +34 -0
- package/dist/entry-problems.js +14 -0
- package/dist/execution-lane-caps.d.ts +49 -10
- package/dist/execution-lane-caps.js +43 -4
- package/dist/field-conflict.d.ts +44 -0
- package/dist/field-conflict.js +21 -0
- package/dist/file-history-disclosure.d.ts +34 -5
- package/dist/file-history-disclosure.js +2 -2
- package/dist/fleet/fleet-bus.d.ts +84 -6
- package/dist/fleet/fleet-bus.js +42 -9
- package/dist/hooks/classic-stations.d.ts +10 -5
- package/dist/hooks/classic-stations.js +4 -4
- package/dist/hooks/command-spawn.d.ts +57 -0
- package/dist/hooks/command-spawn.js +139 -0
- package/dist/hooks/hook-llm.js +2 -2
- package/dist/hooks/hook-runner.d.ts +127 -25
- package/dist/hooks/hook-runner.js +668 -287
- package/dist/hooks/hook-source.d.ts +44 -0
- package/dist/hooks/hook-source.js +40 -0
- package/dist/http/active-run-conflict.d.ts +5 -13
- package/dist/http/active-run-conflict.js +17 -27
- package/dist/http/admission.js +111 -60
- package/dist/http/approval-sweeps.js +58 -19
- package/dist/http/caller-trust.d.ts +77 -0
- package/dist/http/caller-trust.js +143 -0
- package/dist/http/checkpoint-ctx.d.ts +173 -18
- package/dist/http/checkpoint-ctx.js +171 -12
- package/dist/http/claimed-run.d.ts +4 -3
- package/dist/http/device-placement.d.ts +2 -4
- package/dist/http/device-placement.js +2 -1
- package/dist/http/dispatch.js +13 -7
- package/dist/http/durable-run-leg.d.ts +4 -32
- package/dist/http/durable-run-leg.js +37 -12
- package/dist/http/idempotency.d.ts +2 -2
- package/dist/http/idempotency.js +2 -2
- package/dist/http/owner-access.d.ts +56 -0
- package/dist/http/owner-access.js +42 -0
- package/dist/http/principal-gate.d.ts +78 -27
- package/dist/http/principal-gate.js +64 -14
- package/dist/http/readiness.d.ts +11 -5
- package/dist/http/readiness.js +10 -3
- package/dist/http/refusal-body.d.ts +12 -0
- package/dist/http/refusal-body.js +11 -0
- package/dist/http/request-guards.d.ts +16 -32
- package/dist/http/request-guards.js +31 -30
- package/dist/http/resume-ctx-read.d.ts +50 -6
- package/dist/http/resume-ctx-read.js +33 -7
- package/dist/http/resume-legs.d.ts +5 -5
- package/dist/http/resume-legs.js +163 -91
- package/dist/http/retired-service-deps.d.ts +43 -0
- package/dist/http/retired-service-deps.js +18 -0
- package/dist/http/route-ctx.d.ts +44 -37
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/a2a-serve.js +3 -4
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.js +1 -1
- package/dist/http/routes/admin-memory-optout.js +5 -1
- package/dist/http/routes/adoption.js +2 -9
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +91 -56
- package/dist/http/routes/background.js +3 -2
- package/dist/http/routes/capabilities.js +20 -4
- package/dist/http/routes/config-catalog.d.ts +3 -3
- package/dist/http/routes/config-catalog.js +1 -1
- package/dist/http/routes/devices.js +3 -17
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +6 -2
- package/dist/http/routes/fleet.js +4 -4
- package/dist/http/routes/images.js +6 -7
- package/dist/http/routes/leader.js +1 -8
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +3 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/memory-origin.js +1 -1
- package/dist/http/routes/memory-policy.d.ts +1 -1
- package/dist/http/routes/memory-policy.js +3 -2
- package/dist/http/routes/notify-wake.js +8 -21
- package/dist/http/routes/observability.js +2 -2
- package/dist/http/routes/principal-inbox.d.ts +39 -0
- package/dist/http/routes/principal-inbox.js +76 -0
- package/dist/http/routes/retention-ops.js +2 -9
- package/dist/http/routes/rules.js +9 -22
- package/dist/http/routes/runs.d.ts +5 -0
- package/dist/http/routes/runs.js +63 -115
- package/dist/http/routes/session-sync.d.ts +3 -2
- package/dist/http/routes/session-sync.js +53 -48
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions-list.js +3 -4
- package/dist/http/routes/sessions.js +33 -43
- package/dist/http/routes/side-query.js +6 -4
- package/dist/http/routes/tasks.js +71 -30
- package/dist/http/routes/trace-usage.js +7 -9
- package/dist/http/routes/workflows.d.ts +351 -0
- package/dist/http/routes/workflows.js +153 -119
- package/dist/http/run-declared-families.d.ts +16 -0
- package/dist/http/run-declared-families.js +12 -0
- package/dist/http/send.d.ts +24 -3
- package/dist/http/send.js +14 -2
- package/dist/http/server.d.ts +23 -18
- package/dist/http/server.js +20 -15
- package/dist/http/stored-body-replay.js +17 -4
- package/dist/http/submit-ctx.d.ts +4 -3
- package/dist/http/submit-ctx.js +23 -6
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +5 -4
- package/dist/http/wire-types.d.ts +17 -10
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/lane-home-probe.d.ts +78 -9
- package/dist/lane-home-probe.js +78 -12
- package/dist/leader/endpoint.d.ts +4 -1
- package/dist/leader/endpoint.js +11 -10
- package/dist/leader/wire.d.ts +4 -0
- package/dist/leader/wire.js +8 -7
- package/dist/limit-refusal.d.ts +17 -5
- package/dist/limit-refusal.js +4 -1
- package/dist/main.js +1 -1
- package/dist/mcp-assertion-header.d.ts +31 -10
- package/dist/mcp-assertion-header.js +10 -3
- package/dist/memory-origin-fence.js +2 -2
- package/dist/memory-project-layer.d.ts +37 -0
- package/dist/memory-project-layer.js +46 -0
- package/dist/memory-scope.d.ts +23 -16
- package/dist/memory-scope.js +24 -8
- package/dist/memory-sync-client.d.ts +27 -13
- package/dist/memory-sync-client.js +107 -18
- package/dist/model-compat.d.ts +1 -1
- package/dist/model-entry-refusal.d.ts +9 -1
- package/dist/model-entry-refusal.js +6 -1
- package/dist/model-plane-face.d.ts +51 -0
- package/dist/model-plane-face.js +24 -0
- package/dist/model-provider.d.ts +5 -0
- package/dist/model-provider.js +4 -2
- package/dist/model-route-endpoint.d.ts +8 -11
- package/dist/model-route-endpoint.js +4 -7
- package/dist/model-route-env.d.ts +22 -0
- package/dist/model-route-env.js +2 -0
- package/dist/model-route-gap.d.ts +60 -30
- package/dist/model-route-gap.js +21 -15
- package/dist/observability/corrupt-read-seat.d.ts +7 -0
- package/dist/observability/corrupt-read-seat.js +9 -0
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +18 -4
- package/dist/observability/fail-open.d.ts +35 -9
- package/dist/observability/fail-open.js +35 -9
- package/dist/observability/metrics.js +1 -0
- package/dist/observability/prompt-manifest.d.ts +22 -25
- package/dist/observability/prompt-manifest.js +54 -50
- package/dist/observability/tool-trace.js +9 -10
- package/dist/operator-ask.d.ts +10 -0
- package/dist/operator-ask.js +3 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
- package/dist/orchestration/workflow-agent-session-index.js +9 -13
- package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
- package/dist/orchestration/workflow-completion-inbox.js +13 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
- package/dist/orchestration/workflow-notify-journal.js +27 -34
- package/dist/orchestration/workflow-park-decision.d.ts +89 -0
- package/dist/orchestration/workflow-park-decision.js +119 -0
- package/dist/org-delivery-gate.d.ts +81 -0
- package/dist/org-delivery-gate.js +59 -0
- package/dist/panel-tool-names.d.ts +23 -0
- package/dist/panel-tool-names.js +8 -0
- package/dist/parked-decide.d.ts +43 -11
- package/dist/parked-decide.js +100 -51
- package/dist/parked-revive-surface.d.ts +9 -2
- package/dist/parked-revive-surface.js +11 -8
- package/dist/permission-rule-list.d.ts +1 -1
- package/dist/permission-rule-list.js +2 -2
- package/dist/permission-rule-vocab.d.ts +1 -23
- package/dist/permission-rule-vocab.js +0 -13
- package/dist/plugins/approval-ask-store-file.d.ts +16 -1
- package/dist/plugins/approval-ask-store-file.js +21 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
- package/dist/plugins/approval-ask-store-memory.js +34 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
- package/dist/plugins/approval-ask-store-sql.js +37 -1
- package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
- package/dist/plugins/approval-payload-fingerprint.js +1 -1
- package/dist/plugins/blob-backend.d.ts +38 -25
- package/dist/plugins/blob-backend.js +29 -19
- package/dist/plugins/breaker-state-sql.d.ts +2 -2
- package/dist/plugins/breaker-state-sql.js +4 -4
- package/dist/plugins/caching-session-store.d.ts +4 -1
- package/dist/plugins/caching-session-store.js +7 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +36 -1
- package/dist/plugins/checkpoint-store-sql.js +65 -3
- package/dist/plugins/file-history-store-sql.d.ts +5 -2
- package/dist/plugins/file-history-store-sql.js +3 -1
- package/dist/plugins/file-run-store.d.ts +5 -13
- package/dist/plugins/file-run-store.js +9 -0
- package/dist/plugins/fork-routing-session-store.d.ts +9 -1
- package/dist/plugins/fork-routing-session-store.js +12 -1
- package/dist/plugins/host-platform.d.ts +2 -0
- package/dist/plugins/host-platform.js +2 -1
- package/dist/plugins/local-checkpoint-store.d.ts +15 -2
- package/dist/plugins/local-checkpoint-store.js +32 -1
- package/dist/plugins/local-session-store.d.ts +13 -10
- package/dist/plugins/local-session-store.js +23 -14
- package/dist/plugins/local-session-window.d.ts +45 -0
- package/dist/plugins/local-session-window.js +38 -0
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
- package/dist/plugins/mailbox-store-sql.d.ts +30 -48
- package/dist/plugins/mailbox-store-sql.js +77 -52
- package/dist/plugins/memory-embedder-fingerprint.js +2 -1
- package/dist/plugins/memory-run-store.d.ts +5 -13
- package/dist/plugins/memory-run-store.js +8 -0
- package/dist/plugins/one-time-migrations.d.ts +121 -5
- package/dist/plugins/one-time-migrations.js +332 -8
- package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
- package/dist/plugins/org-delivery-quota-store.js +228 -0
- package/dist/plugins/permission-rule-store-file.js +10 -6
- package/dist/plugins/permission-rule-store-sql.js +8 -8
- package/dist/plugins/pg-pool.js +39 -7
- package/dist/plugins/pg-session-storage.d.ts +18 -5
- package/dist/plugins/pg-session-storage.js +68 -23
- package/dist/plugins/remote-env-adb.d.ts +6 -12
- package/dist/plugins/remote-env-adb.js +12 -2
- package/dist/plugins/remote-env-device.d.ts +14 -7
- package/dist/plugins/remote-env-device.js +23 -15
- package/dist/plugins/remote-env-k8s.d.ts +8 -12
- package/dist/plugins/remote-env-k8s.js +23 -5
- package/dist/plugins/remote-env-local-docker.d.ts +10 -15
- package/dist/plugins/remote-env-local-docker.js +12 -9
- package/dist/plugins/remote-env-ssh.d.ts +10 -16
- package/dist/plugins/remote-env-ssh.js +17 -11
- package/dist/plugins/run-store-sql.d.ts +16 -14
- package/dist/plugins/run-store-sql.js +22 -2
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-user-file.js +2 -2
- package/dist/plugins/session-epoch-anchor.d.ts +32 -6
- package/dist/plugins/session-epoch-anchor.js +2 -3
- package/dist/plugins/session-ownership.d.ts +11 -0
- package/dist/plugins/session-ownership.js +11 -0
- package/dist/plugins/session-placement.d.ts +12 -14
- package/dist/plugins/session-placement.js +2 -15
- package/dist/plugins/session-store.d.ts +18 -8
- package/dist/plugins/session-store.js +15 -4
- package/dist/plugins/sql-driver.d.ts +74 -11
- package/dist/plugins/sql-driver.js +210 -12
- package/dist/plugins/sql-errors.d.ts +23 -0
- package/dist/plugins/sql-errors.js +21 -0
- package/dist/plugins/store-backend.d.ts +36 -1
- package/dist/plugins/store-backend.js +40 -14
- package/dist/plugins/store-contracts.d.ts +42 -0
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/tidb-pool.d.ts +6 -1
- package/dist/plugins/tidb-pool.js +44 -10
- package/dist/plugins/tidb-session-storage.d.ts +3 -2
- package/dist/plugins/tidb-session-storage.js +2 -2
- package/dist/plugins/tidb-session-store.d.ts +25 -5
- package/dist/plugins/tidb-session-store.js +67 -21
- package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
- package/dist/plugins/workflow-journal-store-sql.js +19 -6
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
- package/dist/plugins/workflow-run-store-sql.js +50 -9
- package/dist/posture-source.d.ts +18 -10
- package/dist/posture-source.js +4 -1
- package/dist/project-identity.d.ts +45 -6
- package/dist/project-identity.js +162 -8
- package/dist/project-layer-key.d.ts +28 -0
- package/dist/project-layer-key.js +29 -0
- package/dist/question.js +3 -2
- package/dist/read-deny-entry.d.ts +25 -0
- package/dist/read-deny-entry.js +12 -0
- package/dist/read-face-posture.d.ts +37 -11
- package/dist/read-face-posture.js +32 -3
- package/dist/request-key-closure.d.ts +7 -0
- package/dist/request-key-closure.js +9 -1
- package/dist/request-tool-axes.d.ts +12 -0
- package/dist/request-tool-axes.js +20 -0
- package/dist/request-value-closure.d.ts +5 -0
- package/dist/request-value-closure.js +11 -3
- package/dist/rule-entry-list.d.ts +26 -2
- package/dist/rule-entry-list.js +72 -12
- package/dist/rules-consent.js +3 -4
- package/dist/run-acceptance.d.ts +19 -3
- package/dist/run-acceptance.js +13 -1
- package/dist/run-local.js +37 -6
- package/dist/runs.d.ts +12 -10
- package/dist/runs.js +25 -6
- package/dist/runtime-caps-resolver.d.ts +16 -6
- package/dist/runtime-caps-resolver.js +18 -5
- package/dist/sandbox-pkg-source.d.ts +19 -1
- package/dist/sandbox-pkg-source.js +29 -7
- package/dist/sealed-key.d.ts +32 -0
- package/dist/sealed-key.js +55 -1
- package/dist/security.d.ts +95 -15
- package/dist/security.js +52 -9
- package/dist/session-error-wire.d.ts +31 -7
- package/dist/session-error-wire.js +9 -2
- package/dist/session-sync-kernel.d.ts +37 -10
- package/dist/session-sync-kernel.js +10 -0
- package/dist/session-titled-frame.d.ts +33 -0
- package/dist/session-titled-frame.js +4 -0
- package/dist/session-titler.d.ts +9 -1
- package/dist/session-titler.js +15 -4
- package/dist/spec-fields.js +21 -2
- package/dist/sql-engine-posture.d.ts +3 -1
- package/dist/task-a2a.js +4 -3
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +5 -5
- package/dist/task-mcp.js +27 -74
- package/dist/task-settings.d.ts +93 -88
- package/dist/task-settings.js +71 -63
- package/dist/text-bidi.d.ts +0 -4
- package/dist/text-bidi.js +1 -3
- package/dist/tighten-only-seats.d.ts +150 -0
- package/dist/tighten-only-seats.js +196 -0
- package/dist/tool-approval.d.ts +451 -136
- package/dist/tool-approval.js +733 -256
- package/dist/tool-rule-names.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +108 -25
- package/dist/trace/exit-seat.d.ts +40 -0
- package/dist/trace/exit-seat.js +130 -0
- package/dist/trace/ledger-events.d.ts +6 -3
- package/dist/trace/project.d.ts +98 -13
- package/dist/trace/project.js +57 -32
- package/dist/trace/projection-table.d.ts +100 -0
- package/dist/trace/projection-table.js +130 -0
- package/dist/trace/redact.d.ts +57 -0
- package/dist/trace/redact.js +962 -168
- package/dist/trace/self-minted-wire.d.ts +349 -1
- package/dist/trace/self-minted-wire.js +118 -1
- package/dist/trace/wire-projection-faces.d.ts +304 -2
- package/dist/trace/wire-projection-faces.js +160 -13
- package/dist/url-refusal.js +2 -0
- package/dist/web-admin-client/apply-effective.d.ts +12 -0
- package/dist/web-admin-client/apply-effective.js +56 -5
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/read-face.js +5 -9
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +6 -8
- package/dist/web-admin-client/types.d.ts +1 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
- package/package.json +8 -4
- package/scripts/drain-check-local.mjs +34 -2
- package/scripts/drain-check.mysql.sql +20 -2
- package/scripts/drain-check.pg.sql +20 -2
- package/dist/mcp-tool-faces.d.ts +0 -69
- package/dist/mcp-tool-faces.js +0 -106
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { emitLeafAdvance } from "../session-leaf-bus.js";
|
|
2
|
-
import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, } from "@sema-agent/core";
|
|
2
|
+
import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, admitSessionPlacement, } from "@sema-agent/core";
|
|
3
3
|
import { SESSION_TITLE_MAX_CHARS } from "../session-row.js";
|
|
4
4
|
import { escapeLike } from "./sql-escape.js";
|
|
5
5
|
import { decodeSessionEventPayload } from "./session-event-payload.js";
|
|
@@ -7,14 +7,17 @@ import { scanEpochAnchor } from "./session-epoch-anchor.js";
|
|
|
7
7
|
import { jsonColumnText } from "./sql-json-column.js";
|
|
8
8
|
import { pgSafeJsonStringify } from "./pg-safe-json.js";
|
|
9
9
|
import { toIso } from "./sql-row-helpers.js";
|
|
10
|
-
import {
|
|
10
|
+
import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SQL_SESSION_PLACEMENTS, } from "./session-placement.js";
|
|
11
11
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
12
|
-
import {
|
|
12
|
+
import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
|
|
13
|
+
import { createLogger } from "../observability/logger.js";
|
|
14
|
+
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
13
15
|
import { isPgUniqueViolation } from "./sql-errors.js";
|
|
14
16
|
import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
|
|
15
17
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
16
18
|
import { SESSION_META_INCARNATION_COLUMN, carriedIncarnation, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
17
19
|
import { ensureIndexes } from "./ensure-index.js";
|
|
20
|
+
import { assertFirstOwnerWritable, forkOwnerOf } from "./session-ownership.js";
|
|
18
21
|
const isDupKey = isPgUniqueViolation;
|
|
19
22
|
function baselineQuery(text, values) {
|
|
20
23
|
return { text, values, query_timeout: BASELINE_IO_DEADLINE_MS };
|
|
@@ -83,10 +86,10 @@ export class PgSessionStorage extends BaseSessionStorage {
|
|
|
83
86
|
this.floorSeq = floorSeq;
|
|
84
87
|
}
|
|
85
88
|
async getEpochAnchor() {
|
|
86
|
-
return scanEpochAnchor(this.
|
|
89
|
+
return scanEpochAnchor(this.floorSeq, async (beforeSeq) => {
|
|
87
90
|
const res = await this.pool.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 AND seq < $2 AND type IN ('prompt_epoch','compaction') ORDER BY seq DESC LIMIT 100`, [this.sessionId, beforeSeq]);
|
|
88
91
|
return res.rows;
|
|
89
|
-
});
|
|
92
|
+
}, (row) => decodeSessionEventPayload(row.payload, this.sessionId, row.seq));
|
|
90
93
|
}
|
|
91
94
|
static async wake(pool, sessionId) {
|
|
92
95
|
const metaRes = await pool.query(`SELECT created_at, leaf_id, leaf_seq, incarnation, ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = $1`, [sessionId]);
|
|
@@ -175,13 +178,15 @@ export class PgSessionStore {
|
|
|
175
178
|
retention = MANAGED_RETENTION;
|
|
176
179
|
placements = SQL_SESSION_PLACEMENTS;
|
|
177
180
|
pending = new Map();
|
|
178
|
-
|
|
181
|
+
log;
|
|
182
|
+
constructor(pool, opts = {}) {
|
|
179
183
|
this.pool = pool;
|
|
184
|
+
this.log = opts.log ?? createLogger();
|
|
180
185
|
}
|
|
181
186
|
async acquire(sessionId, opts) {
|
|
182
187
|
if (!sessionId) {
|
|
183
188
|
const id = uuidv7();
|
|
184
|
-
await this.
|
|
189
|
+
await this.createOnAcquire(id, opts?.placement);
|
|
185
190
|
return this.load(id, false, opts);
|
|
186
191
|
}
|
|
187
192
|
if (opts?.requireExisting)
|
|
@@ -189,7 +194,7 @@ export class PgSessionStore {
|
|
|
189
194
|
const inflight = this.pending.get(sessionId);
|
|
190
195
|
if (inflight) {
|
|
191
196
|
const acquired = await inflight;
|
|
192
|
-
|
|
197
|
+
admitSessionPlacement(sessionId, await this.placementOf(sessionId), opts ?? {}, placementCapabilityOf(this));
|
|
193
198
|
return acquired;
|
|
194
199
|
}
|
|
195
200
|
const p = this.load(sessionId, false, opts).finally(() => this.pending.delete(sessionId));
|
|
@@ -199,20 +204,44 @@ export class PgSessionStore {
|
|
|
199
204
|
async load(sessionId, requireExisting = false, opts) {
|
|
200
205
|
let storage = await PgSessionStorage.wake(this.pool, sessionId);
|
|
201
206
|
if (storage) {
|
|
202
|
-
|
|
207
|
+
admitSessionPlacement(sessionId, (await storage.getMetadata()).placement, opts ?? {}, placementCapabilityOf(this));
|
|
203
208
|
return { session: new StoredSession(storage), sessionId };
|
|
204
209
|
}
|
|
205
210
|
if (requireExisting)
|
|
206
211
|
throw new SessionError("not_found", `session ${sessionId} not found`);
|
|
207
|
-
await this.
|
|
212
|
+
await this.createOnAcquire(sessionId, opts?.placement);
|
|
208
213
|
storage = await PgSessionStorage.wake(this.pool, sessionId);
|
|
209
214
|
if (!storage) {
|
|
210
215
|
throw new SessionError("storage", `session ${sessionId} vanished between register and wake (a concurrent purge won the race) — retry`);
|
|
211
216
|
}
|
|
212
|
-
|
|
217
|
+
admitSessionPlacement(sessionId, (await storage.getMetadata()).placement, opts ?? {}, placementCapabilityOf(this));
|
|
213
218
|
return { session: new StoredSession(storage), sessionId };
|
|
214
219
|
}
|
|
215
|
-
async register(sessionId, owner
|
|
220
|
+
async register(sessionId, owner) {
|
|
221
|
+
if (typeof owner !== "string" && owner !== null) {
|
|
222
|
+
if ((await this.ownerOf(sessionId)) !== undefined)
|
|
223
|
+
return;
|
|
224
|
+
assertFirstOwnerWritable(sessionId, owner);
|
|
225
|
+
}
|
|
226
|
+
await this.insertMeta(sessionId, owner, undefined);
|
|
227
|
+
}
|
|
228
|
+
async createOnAcquire(sessionId, placement) {
|
|
229
|
+
if (placement === undefined)
|
|
230
|
+
return this.insertMeta(sessionId, null, undefined);
|
|
231
|
+
await createPlacedSessionRow(sessionId, placement, {
|
|
232
|
+
readFaces: (ids) => this.sourceFaces(ids),
|
|
233
|
+
insert: (owner) => this.insertMeta(sessionId, owner, placement),
|
|
234
|
+
readOwner: () => this.ownerOf(sessionId),
|
|
235
|
+
}, this.log);
|
|
236
|
+
}
|
|
237
|
+
async claimOwnerless(sessionId, incarnation, owner) {
|
|
238
|
+
return ((await this.pool.query(CLAIM_OWNERLESS_PLACED_SQL.pg, [...claimOwnerlessParams(sessionId, incarnation, owner)])).rowCount ?? 0) > 0;
|
|
239
|
+
}
|
|
240
|
+
async sourceFaces(ids) {
|
|
241
|
+
const res = await this.pool.query(`SELECT session_id, owner, incarnation FROM session_meta WHERE session_id IN (${ids.map((_, i) => `$${i + 1}`).join(", ")})`, [...ids]);
|
|
242
|
+
return new Map(res.rows.map((r) => [String(r.session_id), sessionFaceFromRow(String(r.session_id), r.owner ?? null, r.incarnation)]));
|
|
243
|
+
}
|
|
244
|
+
async insertMeta(sessionId, owner, placement) {
|
|
216
245
|
const now = new Date();
|
|
217
246
|
await this.pool.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, " +
|
|
218
247
|
"placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) " +
|
|
@@ -406,10 +435,11 @@ export class PgSessionStore {
|
|
|
406
435
|
}
|
|
407
436
|
}
|
|
408
437
|
async fork(sourceId, owner) {
|
|
409
|
-
const metaRes = await this.pool.query("SELECT leaf_id FROM session_meta WHERE session_id = $1", [sourceId]);
|
|
438
|
+
const metaRes = await this.pool.query("SELECT leaf_id, owner FROM session_meta WHERE session_id = $1", [sourceId]);
|
|
410
439
|
if (!metaRes.rows[0])
|
|
411
440
|
return null;
|
|
412
441
|
const leafId = metaRes.rows[0].leaf_id ?? null;
|
|
442
|
+
const forkOwner = forkOwnerOf(owner, metaRes.rows[0].owner ?? null) ?? null;
|
|
413
443
|
const eventRes = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("pg", "payload")}, ts FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [sourceId]);
|
|
414
444
|
const rows = eventRes.rows;
|
|
415
445
|
const newId = uuidv7();
|
|
@@ -422,7 +452,7 @@ export class PgSessionStore {
|
|
|
422
452
|
const payload = String(r.payload);
|
|
423
453
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
|
|
424
454
|
}
|
|
425
|
-
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7)", [newId,
|
|
455
|
+
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7)", [newId, forkOwner, now, now, leafId, rows.length, mintSessionIncarnation()]);
|
|
426
456
|
await conn.query("COMMIT");
|
|
427
457
|
return newId;
|
|
428
458
|
}
|
|
@@ -619,6 +649,23 @@ class PgStagingHandle {
|
|
|
619
649
|
const conn = await this.pool.connect();
|
|
620
650
|
try {
|
|
621
651
|
await conn.query("BEGIN");
|
|
652
|
+
const now = new Date();
|
|
653
|
+
const minted = mintSessionIncarnation();
|
|
654
|
+
if (opts.expectedOwner !== undefined) {
|
|
655
|
+
const cas = await conn.query("UPDATE session_meta SET leaf_seq = leaf_seq + 1 WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2", [this.realSessionId, opts.expectedOwner]);
|
|
656
|
+
if (cas.rowCount !== 1) {
|
|
657
|
+
await conn.query("ROLLBACK");
|
|
658
|
+
throw new StagedCommitOwnerChangedError(this.realSessionId);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,NULL,0,$5) ON CONFLICT (session_id) DO NOTHING", [this.realSessionId, owner, now, now, minted]);
|
|
663
|
+
const claimed = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = $1 FOR UPDATE", [this.realSessionId]);
|
|
664
|
+
if (String(claimed.rows[0]?.incarnation) !== minted) {
|
|
665
|
+
await conn.query("ROLLBACK");
|
|
666
|
+
throw new StagedCommitOwnerChangedError(this.realSessionId);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
622
669
|
const staged = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
|
|
623
670
|
const dst = await conn.query("SELECT entry_id FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
|
|
624
671
|
const dstMeta = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = $1", [this.realSessionId]);
|
|
@@ -627,15 +674,15 @@ class PgStagingHandle {
|
|
|
627
674
|
throw new SessionError("placement_refused", `session ${this.realSessionId} belongs to the ${dstMeta.rows[0].placement_kind} placement partition — a session-sync staged commit cannot overwrite a delegation transcript`);
|
|
628
675
|
}
|
|
629
676
|
const srcIds = staged.rows.map((r) => String(r.entry_id));
|
|
630
|
-
const dstIds =
|
|
677
|
+
const dstIds = opts.expectedOwner !== undefined ? dst.rows.map((r) => String(r.entry_id)) : null;
|
|
631
678
|
const rel = classifySyncRelationshipByIds(srcIds, dstIds);
|
|
632
|
-
if ((rel.relation === "fork" || rel.relation === "stale") && opts
|
|
679
|
+
if ((rel.relation === "fork" || rel.relation === "stale") && opts.resolution !== "overwrite-dst") {
|
|
633
680
|
await conn.query("ROLLBACK");
|
|
634
681
|
throw new SyncConflictError(this.realSessionId, rel);
|
|
635
682
|
}
|
|
636
|
-
if (rel.relation === "fast_forward" && opts
|
|
637
|
-
const dstFull = await conn.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [this.realSessionId]);
|
|
638
|
-
const stagedFull = await conn.query(`SELECT entry_id, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [this.stagingId]);
|
|
683
|
+
if (rel.relation === "fast_forward" && opts.resolution !== "overwrite-dst" && dstIds !== null && dstIds.length > 0) {
|
|
684
|
+
const dstFull = await conn.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE`, [this.realSessionId]);
|
|
685
|
+
const stagedFull = await conn.query(`SELECT entry_id, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq ASC FOR UPDATE`, [this.stagingId]);
|
|
639
686
|
const dstSet = new Set(dstIds);
|
|
640
687
|
const dstEntries = dstFull.rows.map((r) => decodeSessionEventPayload(r.payload, this.realSessionId, r.seq));
|
|
641
688
|
const stagedShared = stagedFull.rows.filter((r) => dstSet.has(String(r.entry_id))).map((r) => decodeSessionEventPayload(r.payload, this.stagingId, r.entry_id));
|
|
@@ -647,13 +694,11 @@ class PgStagingHandle {
|
|
|
647
694
|
}
|
|
648
695
|
await conn.query("DELETE FROM session_event WHERE session_id = $1", [this.realSessionId]);
|
|
649
696
|
await conn.query("UPDATE session_event SET session_id = $1 WHERE session_id = $2", [this.realSessionId, this.stagingId]);
|
|
650
|
-
const lastRes = await conn.query(`SELECT ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq DESC LIMIT 1`, [this.realSessionId]);
|
|
697
|
+
const lastRes = await conn.query(`SELECT ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 ORDER BY seq DESC LIMIT 1 FOR UPDATE`, [this.realSessionId]);
|
|
651
698
|
let leafId = null;
|
|
652
699
|
if (lastRes.rows[0])
|
|
653
700
|
leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRes.rows[0].payload, this.realSessionId, "last staged row"));
|
|
654
|
-
|
|
655
|
-
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES ($1,$2,$3,$4,$5,$6,$7) " +
|
|
656
|
-
"ON CONFLICT (session_id) DO UPDATE SET owner = EXCLUDED.owner, updated_at = EXCLUDED.updated_at, leaf_id = EXCLUDED.leaf_id, leaf_seq = EXCLUDED.leaf_seq", [this.realSessionId, owner, now, now, leafId, srcIds.length, mintSessionIncarnation()]);
|
|
701
|
+
await conn.query("UPDATE session_meta SET owner = $1, updated_at = $2, leaf_id = $3, leaf_seq = $4 WHERE session_id = $5", [owner, now, leafId, srcIds.length, this.realSessionId]);
|
|
657
702
|
const afterMeta = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = $1", [this.realSessionId]);
|
|
658
703
|
if (afterMeta.rows[0]?.placement_kind != null) {
|
|
659
704
|
await conn.query("ROLLBACK");
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
2
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 } from "@sema-agent/core";
|
|
3
4
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
4
|
-
export interface AdbEnvConfig {
|
|
5
|
+
export interface AdbEnvConfig extends LaneHomeConfig {
|
|
5
6
|
/** Device serial or `host:port` (TCP). `host:port` triggers `adb connect` on connect()/reconnect(). */
|
|
6
7
|
serial: string;
|
|
7
8
|
/** Path to the adb binary. Default "adb" (must be on PATH in the runner image). */
|
|
8
9
|
adbPath?: string;
|
|
9
|
-
/**
|
|
10
|
-
* S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
|
|
11
|
-
* 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
|
|
12
|
-
* 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
|
|
13
|
-
* 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
|
|
14
|
-
* 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
|
|
15
|
-
* adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
|
|
16
|
-
*/
|
|
17
|
-
homeDir?: string;
|
|
18
10
|
/** On-device workspace root; relative paths resolve against it. Default "/data/local/tmp". */
|
|
19
11
|
mountPath?: string;
|
|
20
12
|
commandTimeoutMs?: number;
|
|
@@ -86,8 +78,10 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
|
|
|
86
78
|
private destroyed;
|
|
87
79
|
/** 在飞的那一次懒连接(单飞记忆)——见 {@link ensureConnected}。settle 即清,**不缓存败诺**。 */
|
|
88
80
|
private connectPromise?;
|
|
89
|
-
/** core `ExecutionEnv.homeDir`([ref])
|
|
90
|
-
|
|
81
|
+
/** core `ExecutionEnv.homeDir`([ref]):设备 shell 里执行用户的 home —— 声明胜,否则首连时 `adb shell` 实测的 `$HOME`(Android 常为 `/` 或 `/data/local/tmp` 一类,读到什么填什么,绝对即可),见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
|
|
82
|
+
get homeDir(): string | undefined;
|
|
83
|
+
/** 家目录的座(声明胜 · 否则首连时实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道**不对证**声明:Android 没有 POSIX 登录家目录,声明是运维的断言,照单全收。 */
|
|
84
|
+
private readonly homeSeat;
|
|
91
85
|
/** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 Android 设备的 shell 里(设备自己的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
92
86
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
93
87
|
readonly cdPath: "unknown";
|
|
@@ -3,6 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { hostSubprocessEnv } from "../server-secret-env.js";
|
|
6
|
+
import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
|
|
6
7
|
import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
|
|
7
8
|
import { signalNumber } from "./host-platform.js";
|
|
8
9
|
import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
@@ -31,7 +32,10 @@ export class RemoteAdbExecutionEnv {
|
|
|
31
32
|
connected = false;
|
|
32
33
|
destroyed = false;
|
|
33
34
|
connectPromise;
|
|
34
|
-
homeDir
|
|
35
|
+
get homeDir() {
|
|
36
|
+
return this.homeSeat.home;
|
|
37
|
+
}
|
|
38
|
+
homeSeat;
|
|
35
39
|
cdPath = "unknown";
|
|
36
40
|
cfg;
|
|
37
41
|
spawnFn;
|
|
@@ -46,7 +50,7 @@ export class RemoteAdbExecutionEnv {
|
|
|
46
50
|
retry: { maxAttempts: config.retry?.maxAttempts ?? 3, backoffMs: config.retry?.backoffMs ?? 800 },
|
|
47
51
|
};
|
|
48
52
|
this.cwd = this.cfg.mountPath;
|
|
49
|
-
this.
|
|
53
|
+
this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: false, onAbsent: config.onHomeAbsent });
|
|
50
54
|
this.spawnFn = deps?.spawn ?? spawn;
|
|
51
55
|
this.injectedConnectOnce = deps?.connectOnce;
|
|
52
56
|
}
|
|
@@ -117,6 +121,12 @@ export class RemoteAdbExecutionEnv {
|
|
|
117
121
|
error: new RemoteExecutionError("unsupported", `device ${this.cfg.serial} does not advertise the 'shell_v2' feature, so this adapter cannot establish that its command round-trip carries a real exit code with stderr kept separate — the two properties every filesystem judgment on this lane depends on to tell failure from success. Refusing to connect rather than run on an unverifiable transport: attach a device that supports adb shell protocol v2.`),
|
|
118
122
|
};
|
|
119
123
|
}
|
|
124
|
+
if (this.homeSeat.asks) {
|
|
125
|
+
const home = await this.adb(this.deviceShellArgs(LANE_HOME_PROBE_COMMAND), { signal, timeoutMs: this.cfg.commandTimeoutMs });
|
|
126
|
+
if (!home.ok && home.error.code === "aborted")
|
|
127
|
+
return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted while reading the device's home") };
|
|
128
|
+
this.homeSeat.settle(home.ok && home.value.exitCode === 0 ? { ok: true, stdout: home.value.stdout } : { ok: false, reason: home.ok ? `exit ${home.value.exitCode}` : home.error.message });
|
|
129
|
+
}
|
|
120
130
|
this.connected = true;
|
|
121
131
|
return ok(this.workspaceHandle());
|
|
122
132
|
}
|
|
@@ -17,10 +17,9 @@ type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
|
17
17
|
* 🔴 三条不许折错的线:
|
|
18
18
|
* ① `outcome_unknown` **原样过境**——core 把它排除在 `RETRYABLE_REMOTE_ERROR_CODES` 之外正是为了
|
|
19
19
|
* 「可能已经执行过」这一格;折成 `timeout`/`transport_lost` 会让上层按可重试处理,重驱已提交副作用。
|
|
20
|
-
* ② `target_unavailable`(never-started 的诚实词)
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* 粒度粗)。要分得开需要 core 在 `FileErrorCode` 上补 never-started 词 —— 已作 O12 的后续请托登记。
|
|
20
|
+
* ② `target_unavailable`(never-started 的诚实词)**原样过境** —— core 7.54.0([ref] r3)给 `FileErrorCode` 补了同名词
|
|
21
|
+
* (目标在 commit 前拒、从未开始、零效果,目标回来后重试安全;写车道据它撤回首触记录、不计 edited)。修前文件面无对位词,
|
|
22
|
+
* 落 `transport_lost`(「传输中断」= 重试白名单读作「可能已执行」),file 面分不出「从未开始」与「传输中断」—— 那条 O12 后续请托随本版兑现。
|
|
24
23
|
* ③ exec 专属词(`shell_unavailable`/`spawn_error`/`callback_error`/`suspended`/`auth_failed`)出现在
|
|
25
24
|
* file 面上是**设备端协议违约**(它把一个 exec 结局贴到一次文件操作上)⇒ 落 `unknown`,不猜。
|
|
26
25
|
*/
|
|
@@ -38,9 +37,8 @@ type RejectCode = DeviceRejectCode;
|
|
|
38
37
|
/**
|
|
39
38
|
* 汇聚端的 **typed 拒**(投递门**之前**,§5.3 投递边界:设备从未可能见过这条指令)→ core 错误码。
|
|
40
39
|
*
|
|
41
|
-
* 🔴 全部落在**可安全重试**的那一侧,这是投递边界给的硬事实,不是乐观估计。exec
|
|
42
|
-
* `target_unavailable`(「目标在 commit
|
|
43
|
-
* `transport_lost`。
|
|
40
|
+
* 🔴 全部落在**可安全重试**的那一侧,这是投递边界给的硬事实,不是乐观估计。exec 面与 file 面用同一个
|
|
41
|
+
* `target_unavailable`(「目标在 commit 前拒了,指令从未启动」的诚实词;file 面自 core 7.54.0 起有它,修前落 `transport_lost`)。
|
|
44
42
|
* 例外 `identity_mismatch` / `revoked` / `not_found`:那是**永久**拒(换设备/吊销/不是你的),重试
|
|
45
43
|
* 只会同样失败 —— exec 面落 `auth_failed`(NEVER retry 的那一格),file 面无对位词落 `permission_denied`
|
|
46
44
|
* (「这条路你走不通,而且不是暂时的」是它最近的语义)。
|
|
@@ -324,6 +322,15 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
|
|
|
324
322
|
* ③ 「这个 placement 根本没绑设备」是**装配/准入错误**,不是一次运行期业务拒绝 —— 响亮抛,让 spawn
|
|
325
323
|
* 失败(core 对 `isolation:"worktree"` 的同款姿势:宁可 throw,绝不静默给一个不对的 env)。
|
|
326
324
|
* 对比:「绑了但现在不在线」是运行期事实 ⇒ 构造成功,`connect()` 回 typed `device.offline`。
|
|
325
|
+
*
|
|
326
|
+
* 🔴 **工厂的每一只拒都是 typed 拒**(core 7.54.0 [ref] r3 契约:工厂抛的 `.code` 原样成失败终局的码):经 {@link deviceFactoryRefusal} 一处铸,码取
|
|
327
|
+
* `DEVICE_ERROR_CODES` 闭集(装配错误取 `config.placement_root_invalid`),句子里的 `[码]` 与 `.code` 同值。修前抛裸 `Error`,码只在句子里。
|
|
328
|
+
*
|
|
329
|
+
* 🔴 **续跑腿的投递围栏**(core 7.54.0 [ref] r3 / #2061,`ExecutionEnvFactoryContext.resumeWorkspace`):行记下了设备(`resumeWorkspace.deviceId`)⇒
|
|
330
|
+
* 本工厂**只**在那台设备上铸 —— placement 根此刻绑的不是它(换绑过)⇒ 抛 typed `device.identity_mismatch`(换绑后赎回的同一个词,零新码),
|
|
331
|
+
* 句子点名两台设备;不连另一台、不改道(新设备没有那个工作区)。读当前绑定只为判出这一拒与取属主,铸的设备恒是行记下的那一台。
|
|
332
|
+
* 「绑的就是它但现在不在线」与新跑同形:构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。
|
|
333
|
+
* 行没记设备(非设备车道 / 早于该字段的行)⇒ 不声明要兑现的设备,按普通路由走。句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄 `resumeWorkspace`。
|
|
327
334
|
*/
|
|
328
335
|
export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): ExecutionEnvFactory;
|
|
329
336
|
/**
|
|
@@ -22,7 +22,7 @@ export const DEVICE_OUTCOME_TO_FILE_ERROR = {
|
|
|
22
22
|
shell_unavailable: "unknown",
|
|
23
23
|
spawn_error: "unknown",
|
|
24
24
|
suspended: "unknown",
|
|
25
|
-
target_unavailable: "
|
|
25
|
+
target_unavailable: "target_unavailable",
|
|
26
26
|
timeout: "timeout",
|
|
27
27
|
transport_lost: "transport_lost",
|
|
28
28
|
unknown: "unknown",
|
|
@@ -66,20 +66,20 @@ export const DEVICE_REJECT_TO_EXEC_ERROR = {
|
|
|
66
66
|
"draining": "target_unavailable",
|
|
67
67
|
};
|
|
68
68
|
export const DEVICE_REJECT_TO_FILE_ERROR = {
|
|
69
|
-
"device.not_bound": "
|
|
70
|
-
"device.offline": "
|
|
71
|
-
"device.busy": "
|
|
72
|
-
"device.attached_elsewhere": "
|
|
73
|
-
"device.claim_conflict": "
|
|
69
|
+
"device.not_bound": "target_unavailable",
|
|
70
|
+
"device.offline": "target_unavailable",
|
|
71
|
+
"device.busy": "target_unavailable",
|
|
72
|
+
"device.attached_elsewhere": "target_unavailable",
|
|
73
|
+
"device.claim_conflict": "target_unavailable",
|
|
74
74
|
"device.provider_mismatch": "permission_denied",
|
|
75
75
|
"device.identity_mismatch": "permission_denied",
|
|
76
76
|
"device.revoked": "permission_denied",
|
|
77
77
|
"device.enrollment_invalid": "permission_denied",
|
|
78
78
|
"not_found.device": "permission_denied",
|
|
79
|
-
"device.rebind_rev_conflict": "
|
|
80
|
-
"device.rebind_active_run": "
|
|
81
|
-
"limit.rate_exceeded": "
|
|
82
|
-
"draining": "
|
|
79
|
+
"device.rebind_rev_conflict": "target_unavailable",
|
|
80
|
+
"device.rebind_active_run": "target_unavailable",
|
|
81
|
+
"limit.rate_exceeded": "target_unavailable",
|
|
82
|
+
"draining": "target_unavailable",
|
|
83
83
|
};
|
|
84
84
|
const DEVICE_REJECT_TO_REMOTE_ERROR = {
|
|
85
85
|
"device.not_bound": "connect_failed",
|
|
@@ -232,7 +232,7 @@ export class RemoteDeviceExecutionEnv {
|
|
|
232
232
|
failure: {
|
|
233
233
|
remote: "connect_failed",
|
|
234
234
|
exec: "target_unavailable",
|
|
235
|
-
file: "
|
|
235
|
+
file: "target_unavailable",
|
|
236
236
|
message: `[device.admission_unavailable] the device admission read failed, so this session's device could not be confirmed (fail-closed): ${err instanceof Error ? err.message : String(err)}`,
|
|
237
237
|
},
|
|
238
238
|
};
|
|
@@ -780,25 +780,33 @@ const OUTCOME_TO_REMOTE_ERROR = {
|
|
|
780
780
|
transport_lost: "transport_lost",
|
|
781
781
|
unknown: "unknown",
|
|
782
782
|
};
|
|
783
|
+
function deviceFactoryRefusal(code, message) {
|
|
784
|
+
return Object.assign(new Error(message), { code });
|
|
785
|
+
}
|
|
783
786
|
export function deviceExecutionEnvFactory(deps) {
|
|
784
787
|
const execTimeoutMs = deps.execTimeoutMs ?? 1_800_000;
|
|
785
788
|
return async (ctx) => {
|
|
786
789
|
const rootSessionId = ctx.placementRootSessionId;
|
|
787
790
|
if (typeof rootSessionId !== "string" || rootSessionId.length === 0) {
|
|
788
|
-
throw
|
|
791
|
+
throw deviceFactoryRefusal("config.placement_root_invalid", "device lane: ExecutionEnvFactoryContext.placementRootSessionId is empty [config.placement_root_invalid] — " +
|
|
789
792
|
"the device placement is keyed on the run tree's placement root; defaulting to the child sessionId would read as a NEW placement (v2 §4.3.2 / core design/380 O1①)");
|
|
790
793
|
}
|
|
791
794
|
const binding = await deps.store.getSessionBinding(rootSessionId);
|
|
792
795
|
if (!binding) {
|
|
793
|
-
throw
|
|
796
|
+
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_BOUND, `device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
|
|
794
797
|
"the submit-time admission chain must bind the root session to a device before a run reaches the factory (v2 §4.3.2)");
|
|
795
798
|
}
|
|
799
|
+
const recordedDevice = ctx.resumeWorkspace?.deviceId;
|
|
800
|
+
if (recordedDevice !== undefined && recordedDevice !== binding.deviceId) {
|
|
801
|
+
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${recordedDevice}, but placement root ${rootSessionId} is now bound to ${binding.deviceId} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
|
|
802
|
+
"a parked call does not follow a rebind (the new device has no copy of that workspace); finish or cancel the parked task, then start it again on the device now bound");
|
|
803
|
+
}
|
|
796
804
|
const view = await deps.store.readDeviceForAdmission(binding.deviceId, binding.owner);
|
|
797
805
|
if (!view) {
|
|
798
|
-
throw
|
|
806
|
+
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_FOUND, `device lane: the bound device is gone or not owned by the binding's principal [${DEVICE_ERROR_CODES.NOT_FOUND}]`);
|
|
799
807
|
}
|
|
800
808
|
if (view.row.status === "revoked") {
|
|
801
|
-
throw
|
|
809
|
+
throw deviceFactoryRefusal(DEVICE_ERROR_CODES.REVOKED, `device lane: the bound device has been revoked [${DEVICE_ERROR_CODES.REVOKED}] — revocation is terminal; re-enrolling mints a NEW deviceId (v2 §8-R4)`);
|
|
802
810
|
}
|
|
803
811
|
assertPosixDevicePlatform(view.row.platformOs, view.row.deviceId);
|
|
804
812
|
return new RemoteDeviceExecutionEnv({
|
|
@@ -4,9 +4,10 @@ import { type CacheMissMemory, type WorkspaceCacheCapability } from "../workspac
|
|
|
4
4
|
import { type SandboxEgressGap, type SandboxIpv6 } from "../sandbox-egress-policy.js";
|
|
5
5
|
import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll } from "@sema-agent/core";
|
|
6
6
|
import type { PodSpecPatch } from "./k8s-exec-protocol.js";
|
|
7
|
+
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
7
8
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
8
9
|
export { buildBgRunnerScript, buildBgLauncherScript, buildBgPollScript, buildBgKillScript, buildBgDisposeScript, buildDetachCapableExec, buildKillAllScript, parseBgPollOutput, type ParsedBgPoll, } from "./k8s-bg-scripts.js";
|
|
9
|
-
export interface K8sEnvConfig {
|
|
10
|
+
export interface K8sEnvConfig extends LaneHomeConfig {
|
|
10
11
|
/** API server URL. Default: in-cluster (`https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT`). */
|
|
11
12
|
apiUrl?: string;
|
|
12
13
|
/** Bearer token. Default: in-cluster service-account token file. */
|
|
@@ -20,15 +21,6 @@ export interface K8sEnvConfig {
|
|
|
20
21
|
image: string;
|
|
21
22
|
/** RuntimeClass for the VM boundary. Default "kata-qemu" (set "" to run plain runc — tests/dev only). */
|
|
22
23
|
runtimeClass?: string;
|
|
23
|
-
/**
|
|
24
|
-
* S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
|
|
25
|
-
* 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
|
|
26
|
-
* 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
|
|
27
|
-
* 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
|
|
28
|
-
* 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
|
|
29
|
-
* adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
|
|
30
|
-
*/
|
|
31
|
-
homeDir?: string;
|
|
32
24
|
/** Workspace root inside the pod; created on connect. Default "/workspace". */
|
|
33
25
|
mountPath?: string;
|
|
34
26
|
/** Pod lifetime (`activeDeadlineSeconds`; env `K8S_TIMEOUT_MS`) — the whole-pod leak guard, and the ceiling background-shell
|
|
@@ -275,8 +267,10 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
275
267
|
* the space restore (`latest` names an archive that is there and did not come back — {@link restoreSnapshot}) and the pod's admission (the cluster created
|
|
276
268
|
* the pod and it ended `Failed` before it ran — {@link createPod}). Neither goes away by asking again: the same request answers the same. */
|
|
277
269
|
private connectRefusal?;
|
|
278
|
-
/** core `ExecutionEnv.homeDir`([ref])
|
|
279
|
-
|
|
270
|
+
/** core `ExecutionEnv.homeDir`([ref]):pod 里执行用户的 home —— 声明胜,否则 pod 就绪那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时 pod 起来之前缺席)。 */
|
|
271
|
+
get homeDir(): string | undefined;
|
|
272
|
+
/** 家目录的座(声明胜 · 否则 pod 就绪那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证(与 local-docker / ssh 同一只判官、同一句拒)。 */
|
|
273
|
+
private readonly homeSeat;
|
|
280
274
|
/** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
281
275
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
282
276
|
readonly cdPath: "unknown";
|
|
@@ -616,6 +610,8 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
|
|
|
616
610
|
* `latest` is not published, loud) → move `latest` → keep N → the row is deleted (always, saved or failed).
|
|
617
611
|
* A lost fence, an upload or a pointer failure ⇒ one `workspace_snapshot_failed` line + the `failed` outcome; `latest`
|
|
618
612
|
* keeps naming the previous snapshot, so the next run restores THAT — never an empty workspace dressed up as a restore.
|
|
613
|
+
* `latest` is moved only onto an object the upload wrote: a pod that is gone when the tar is due (it vanished while the fence was
|
|
614
|
+
* being claimed) uploads nothing, and is that same upload failure.
|
|
619
615
|
*/
|
|
620
616
|
private persistWorkspace;
|
|
621
617
|
private execUrl;
|
|
@@ -18,6 +18,7 @@ import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js"
|
|
|
18
18
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
20
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
21
|
+
import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
|
|
21
22
|
const PROVIDER = "k8s";
|
|
22
23
|
const K8S_BG_DIR_ROOT = "/tmp/.sema-bg";
|
|
23
24
|
const K8S_BG_MAX_CONCURRENT = numEnvOr("K8S_BG_MAX_CONCURRENT", 8, 1);
|
|
@@ -177,7 +178,10 @@ export class RemoteK8sExecutionEnv {
|
|
|
177
178
|
idleWaiters = [];
|
|
178
179
|
connectInFlight;
|
|
179
180
|
connectRefusal;
|
|
180
|
-
homeDir
|
|
181
|
+
get homeDir() {
|
|
182
|
+
return this.homeSeat.home;
|
|
183
|
+
}
|
|
184
|
+
homeSeat;
|
|
181
185
|
cdPath = "unknown";
|
|
182
186
|
cfg;
|
|
183
187
|
deps;
|
|
@@ -217,7 +221,7 @@ export class RemoteK8sExecutionEnv {
|
|
|
217
221
|
...(config.snapshotExclude && config.snapshotExclude.length > 0 ? { snapshotExclude: config.snapshotExclude } : {}),
|
|
218
222
|
};
|
|
219
223
|
this.cwd = this.cfg.mountPath;
|
|
220
|
-
this.
|
|
224
|
+
this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
|
|
221
225
|
this.deps = deps ?? {};
|
|
222
226
|
this.laneLiveness = laneLivenessOf(this.cfg.livenessMs, "K8S_LIVENESS_MS");
|
|
223
227
|
const occupant = occupantKeyOf(config.spaceOccupant);
|
|
@@ -675,13 +679,24 @@ export class RemoteK8sExecutionEnv {
|
|
|
675
679
|
}
|
|
676
680
|
this.podName = name;
|
|
677
681
|
this.podIP = podIP;
|
|
678
|
-
const
|
|
682
|
+
const askHome = this.homeSeat.asks;
|
|
683
|
+
const mk = await this.runExec(`mkdir -p ${shellQuote(this.cfg.mountPath)}${askHome ? ` && { ${LANE_HOME_PROBE_COMMAND} || true; }` : ""}`);
|
|
679
684
|
if (!mk.ok || mk.value.exitCode !== 0) {
|
|
680
685
|
await this.deletePod(name).catch(() => { });
|
|
681
686
|
this.podName = undefined;
|
|
682
687
|
this.podIP = undefined;
|
|
683
688
|
return { ok: false, error: new RemoteExecutionError("connect_failed", `workspace mkdir failed: ${mk.ok ? mk.value.stderr : mk.error.message}`) };
|
|
684
689
|
}
|
|
690
|
+
if (askHome) {
|
|
691
|
+
const verdict = this.homeSeat.settle({ ok: true, stdout: mk.value.stdout });
|
|
692
|
+
if (verdict !== undefined) {
|
|
693
|
+
await this.deletePod(name).catch(() => { });
|
|
694
|
+
this.podName = undefined;
|
|
695
|
+
this.podIP = undefined;
|
|
696
|
+
const error = new RemoteExecutionError("connect_failed", verdict.refusal);
|
|
697
|
+
return verdict.answered ? this.holdRefusal(error) : { ok: false, error };
|
|
698
|
+
}
|
|
699
|
+
}
|
|
685
700
|
if (cache?.fresh)
|
|
686
701
|
this.logger?.warn("workspace_cache_fresh", { space: incarnationLogPrefix(cache.spaceKey), claim: cache.claim.name, size: cache.size });
|
|
687
702
|
return ok(this.workspaceHandle());
|
|
@@ -927,8 +942,11 @@ export class RemoteK8sExecutionEnv {
|
|
|
927
942
|
if (target.lane === "park" && this.inFlight.size > 0)
|
|
928
943
|
return { ok: false, error: new RemoteExecutionError("command_in_flight", "cannot suspend while a command is in flight (would snapshot a torn workspace)") };
|
|
929
944
|
const snapshotId = mintSnapshotId(this.occupantIncarnation);
|
|
930
|
-
if (!this.podName)
|
|
931
|
-
return
|
|
945
|
+
if (!this.podName) {
|
|
946
|
+
return target.lane === "space"
|
|
947
|
+
? { 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") }
|
|
948
|
+
: ok(snapshotId);
|
|
949
|
+
}
|
|
932
950
|
const presigned = this.presignSnapshot({ ...target, method: "PUT", snapshotId });
|
|
933
951
|
if (!presigned.ok)
|
|
934
952
|
return presigned;
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { type LaneHomeConfig } from "../lane-home-probe.js";
|
|
2
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 } from "@sema-agent/core";
|
|
3
4
|
/** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
|
|
4
5
|
type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
|
|
5
6
|
/** Construction config for {@link RemoteLocalDockerExecutionEnv}. */
|
|
6
|
-
export interface LocalDockerEnvConfig {
|
|
7
|
+
export interface LocalDockerEnvConfig extends LaneHomeConfig {
|
|
7
8
|
/**
|
|
8
9
|
* Base container image to run the task in. **REQUIRED — no default** (domestic-images iron rule: the registry
|
|
9
10
|
* is the operator's choice, never a hardcoded `docker.io` origin). e.g. the docker.io/claybobby pool or an
|
|
10
11
|
* in-network `node:20` mirror (SWR deprecated).
|
|
11
12
|
*/
|
|
12
13
|
image: string;
|
|
13
|
-
/**
|
|
14
|
-
* S-213④ / core [ref] —— 本执行环境的 **home 目录**(`ExecutionEnv.homeDir` 座,绝对路径,本环境自己的
|
|
15
|
-
* 命名空间里)。`~/…` 形权限规则(`Edit(~/.ssh/**)`)对**跑在本 env 里的调用**就以它为基。
|
|
16
|
-
* 缺席 ⇒ 本 env 声明「我没有 home」,core 对 `~/` 规则判 `unreadable`(fail-closed 的 ask),
|
|
17
|
-
* 绝不回落引擎进程自己的 home(那在远端腿上是另一个用户的另一个目录 —— 静默守错目录正是 [ref] 的病)。
|
|
18
|
-
* 值由 `boot/execution-env.ts` 从**唯一属主** `execution-lane-caps.ts` 的 `executionLaneHomeDir` 递进来;
|
|
19
|
-
* adapter 不自算(两处各算一份 = 422 门与引擎对同一条规则给相反答案)。
|
|
20
|
-
*/
|
|
21
|
-
homeDir?: string;
|
|
22
14
|
/** Workspace root INSIDE the container; relative paths resolve against it. Default {@link DEFAULT_MOUNT_PATH}. */
|
|
23
15
|
mountPath?: string;
|
|
24
16
|
/** Path to the docker binary (or a docker-compatible CLI). Default "docker" (must be on PATH in the worker). */
|
|
@@ -77,8 +69,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
77
69
|
};
|
|
78
70
|
/** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
|
|
79
71
|
cwd: string;
|
|
80
|
-
/** core `ExecutionEnv.homeDir`([ref])
|
|
81
|
-
|
|
72
|
+
/** core `ExecutionEnv.homeDir`([ref]):本容器里执行用户的 home —— 声明胜,否则开容器那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
|
|
73
|
+
get homeDir(): string | undefined;
|
|
82
74
|
/** core 7.49.0 #1554:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在容器里(镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
|
|
83
75
|
* 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
|
|
84
76
|
readonly cdPath: "unknown";
|
|
@@ -95,6 +87,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
95
87
|
private connecting?;
|
|
96
88
|
/** 家目录:环境答了而与声明不一致 ⇒ 这只环境的拒,记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。 */
|
|
97
89
|
private homeRefusal?;
|
|
90
|
+
/** 家目录的座(声明胜 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证。 */
|
|
91
|
+
private readonly homeSeat;
|
|
98
92
|
constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
|
|
99
93
|
private resolve;
|
|
100
94
|
workspaceHandle(): WorkspaceHandle;
|
|
@@ -107,9 +101,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
|
|
|
107
101
|
}>;
|
|
108
102
|
private doConnect;
|
|
109
103
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
104
|
+
* 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 实测)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、同一份 `-e` env),读数交座。
|
|
105
|
+
* 声明在场:环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,
|
|
106
|
+
* 但不记(与任何一次起容器失败同:下一次连接重问)。没声明:座收下绝对路径;答不上 / 不是绝对路径 ⇒ 座缺席 + 一行 warn,环境照开(没声明就没有声明可拒)。
|
|
107
|
+
* 调用方中止 ⇒ `aborted`、不记、不交座。容器由调用方收。
|
|
113
108
|
*/
|
|
114
109
|
private homeVerdict;
|
|
115
110
|
suspendVM(_options?: VmLifecycleOptions): Promise<{
|