@sema-agent/server 7.139.0 → 7.141.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 +471 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +94 -26
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +37 -22
- package/dist/approval-card.js +6 -3
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.js +18 -12
- package/dist/boot/execution-lane.d.ts +8 -0
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/memory-boundary.d.ts +16 -53
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +16 -12
- package/dist/boot/memory-consolidation.js +24 -13
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/resolve-spec.d.ts +3 -5
- package/dist/boot/resolve-spec.js +9 -8
- package/dist/boot/runner-deps.d.ts +3 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.js +5 -0
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +3 -1
- package/dist/boot/stage-06-runners.d.ts +2 -8
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -7
- package/dist/boot/stage-07-capability-layer.js +8 -10
- package/dist/boot/stage-08-reapers.d.ts +2 -7
- package/dist/boot/stage-09-leader.d.ts +2 -7
- package/dist/boot/stage-10-http-server.d.ts +2 -7
- package/dist/boot/stage-10-http-server.js +6 -4
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +37 -44
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +36 -9
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +9 -0
- package/dist/capability-facts.js +9 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +12 -8
- package/dist/config-provider.d.ts +18 -17
- package/dist/config-provider.js +28 -36
- package/dist/config-types.d.ts +36 -9
- package/dist/config.js +56 -46
- package/dist/delegated-session-owner.d.ts +23 -20
- package/dist/delegated-session-owner.js +2 -2
- package/dist/deployment-shape.d.ts +9 -8
- package/dist/deployment-shape.js +4 -4
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +145 -107
- package/dist/execution-lane-caps.js +144 -83
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +4 -0
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +49 -35
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +5 -4
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +4 -1
- package/dist/http/routes/memory-compliance.js +9 -2
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +7 -2
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions.js +33 -15
- package/dist/http/routes/tasks.js +67 -68
- package/dist/http/run-read-face.js +3 -3
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +2 -2
- package/dist/http/server.d.ts +14 -27
- package/dist/http/server.js +2 -2
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +33 -9
- package/dist/memory-scope.js +17 -5
- package/dist/memory-sync-client.d.ts +34 -9
- package/dist/memory-sync-client.js +108 -28
- package/dist/observability/fail-open.d.ts +30 -2
- package/dist/observability/fail-open.js +30 -2
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +6 -2
- package/dist/observability/run-terminal.js +25 -26
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-sql.js +14 -14
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +90 -19
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +23 -20
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +19 -9
- package/dist/plugins/execution-placement-sql.js +20 -9
- package/dist/plugins/file-history-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.js +4 -4
- package/dist/plugins/file-run-store.d.ts +2 -2
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +227 -35
- package/dist/plugins/local-session-store.js +518 -37
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +2 -2
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +52 -11
- package/dist/plugins/one-time-migrations.js +101 -39
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -16
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-adb.d.ts +7 -24
- package/dist/plugins/remote-env-adb.js +22 -5
- package/dist/plugins/remote-env-device.d.ts +2 -28
- package/dist/plugins/remote-env-device.js +2 -1
- package/dist/plugins/remote-env-e2b.d.ts +40 -27
- package/dist/plugins/remote-env-e2b.js +180 -20
- package/dist/plugins/remote-env-file-error.d.ts +1 -1
- package/dist/plugins/remote-env-file-error.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +19 -35
- package/dist/plugins/remote-env-host.js +26 -3
- package/dist/plugins/remote-env-k8s.d.ts +22 -14
- package/dist/plugins/remote-env-k8s.js +150 -26
- package/dist/plugins/remote-env-local-docker.d.ts +27 -24
- package/dist/plugins/remote-env-local-docker.js +91 -24
- package/dist/plugins/remote-env-ssh.d.ts +4 -24
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +6 -6
- package/dist/plugins/run-store-sql.d.ts +3 -3
- package/dist/plugins/run-store-sql.js +19 -18
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/schema-consistency.d.ts +6 -6
- package/dist/plugins/schema-consistency.js +1 -1
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +35 -6
- package/dist/plugins/store-backend.js +27 -10
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.js +7 -6
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +16 -15
- package/dist/plugins/tool-result-store-sql.d.ts +2 -2
- package/dist/plugins/tool-result-store-sql.js +3 -3
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/run-local.js +14 -19
- package/dist/runs.d.ts +47 -16
- package/dist/runs.js +112 -113
- package/dist/security.d.ts +2 -2
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +22 -4
- package/dist/session-error-wire.js +16 -0
- package/dist/session-incarnation.d.ts +10 -4
- package/dist/session-incarnation.js +1 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-mcp.d.ts +21 -8
- package/dist/task-mcp.js +17 -9
- package/dist/task-settings.d.ts +52 -25
- package/dist/task-settings.js +91 -28
- package/dist/terminal.d.ts +103 -19
- package/dist/terminal.js +72 -20
- package/dist/trace/core-keyset-guard.d.ts +79 -2
- package/dist/trace/exit-seat.d.ts +7 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +395 -110
- package/dist/trace/self-minted-wire.d.ts +226 -2
- package/dist/trace/self-minted-wire.js +79 -0
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.140.0.json +184 -0
- package/wire-changes/server@7.141.0.json +367 -0
|
@@ -8,6 +8,8 @@ import { signalNumber } from "./host-platform.js";
|
|
|
8
8
|
import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
9
9
|
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
10
10
|
import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
11
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
12
|
+
import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, dockerIdentityFlags, engineStart, floorArgv, } from "./sandbox-read-floor.js";
|
|
11
13
|
import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
|
|
12
14
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
13
15
|
const PROVIDER = "local-docker";
|
|
@@ -20,7 +22,8 @@ const unsupported = (op) => ({
|
|
|
20
22
|
error: new RemoteExecutionError("unsupported", `${op} is not supported on a local-docker target (v1: a container is an isolation boundary but not a managed memory snapshot; capabilities.suspendable=false)`),
|
|
21
23
|
});
|
|
22
24
|
export class RemoteLocalDockerExecutionEnv {
|
|
23
|
-
capabilities
|
|
25
|
+
capabilities;
|
|
26
|
+
floor;
|
|
24
27
|
cwd;
|
|
25
28
|
get homeDir() {
|
|
26
29
|
return this.homeSeat.home;
|
|
@@ -49,8 +52,10 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
49
52
|
controlTimeoutMs: config.controlTimeoutMs ?? DEFAULT_CONTROL_TIMEOUT_MS,
|
|
50
53
|
};
|
|
51
54
|
this.cwd = this.cfg.mountPath;
|
|
52
|
-
this.homeSeat = new LaneHomeSeat(
|
|
55
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
53
56
|
this.spawnFn = deps.spawn ?? spawn;
|
|
57
|
+
this.floor = deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor });
|
|
58
|
+
this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER }, sandboxReadFloor: config.sandboxReadFloor });
|
|
54
59
|
const id = config.id ?? randomBytes(6).toString("hex");
|
|
55
60
|
const suffix = randomBytes(4).toString("hex");
|
|
56
61
|
this.containerName = `sema-docker-${sanitizeId(id)}-${suffix}`;
|
|
@@ -129,6 +134,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
129
134
|
await this.forceRemove().catch(() => { });
|
|
130
135
|
return { ok: false, error: home };
|
|
131
136
|
}
|
|
137
|
+
await this.floor.attach(this.floorTransport(), { origin: "created", workspaceRoot: this.cfg.mountPath });
|
|
132
138
|
if (this.destroyed) {
|
|
133
139
|
await this.forceRemove().catch(() => { });
|
|
134
140
|
return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
|
|
@@ -142,7 +148,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
142
148
|
async homeVerdict(signal) {
|
|
143
149
|
if (!this.homeSeat.asks)
|
|
144
150
|
return undefined;
|
|
145
|
-
const
|
|
151
|
+
const argv = this.startArgv(ADAPTER_START, LANE_HOME_PROBE_COMMAND);
|
|
152
|
+
const probe = argv.ok ? await this.docker(argv.value, { signal, timeoutMs: this.cfg.controlTimeoutMs }) : argv;
|
|
146
153
|
if (!probe.ok && probe.error.code === "aborted")
|
|
147
154
|
return new RemoteExecutionError("aborted", "connect aborted while reading the environment's home");
|
|
148
155
|
const verdict = this.homeSeat.settle(probe.ok && probe.value.exitCode === 0 ? { ok: true, stdout: probe.value.stdout } : { ok: false, reason: probe.ok ? `exit ${probe.value.exitCode}` : probe.error.message });
|
|
@@ -198,6 +205,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
198
205
|
this.connecting = undefined;
|
|
199
206
|
this.containerId = undefined;
|
|
200
207
|
await inflight?.catch(() => { });
|
|
208
|
+
this.floor.dispose();
|
|
201
209
|
await this.forceRemove().catch(() => { });
|
|
202
210
|
}
|
|
203
211
|
async cleanup() {
|
|
@@ -212,8 +220,10 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
212
220
|
const ready = await this.ensureConnectedExec();
|
|
213
221
|
if (!ready.ok)
|
|
214
222
|
return ready;
|
|
215
|
-
const execArgs = this.
|
|
216
|
-
|
|
223
|
+
const execArgs = this.startArgv(engineStart(options?.readFloor), command, { cwd: options?.cwd, env: options?.env });
|
|
224
|
+
if (!execArgs.ok)
|
|
225
|
+
return execArgs;
|
|
226
|
+
const r = await this.docker(execArgs.value, {
|
|
217
227
|
signal: options?.abortSignal,
|
|
218
228
|
timeoutMs: options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs,
|
|
219
229
|
onStdout: options?.onStdout,
|
|
@@ -227,8 +237,10 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
227
237
|
const ready = await this.ensureConnectedExec();
|
|
228
238
|
if (!ready.ok)
|
|
229
239
|
throw new RemoteExecutionError("connect_failed", ready.error.message, ready.error);
|
|
230
|
-
const execArgs = this.
|
|
231
|
-
|
|
240
|
+
const execArgs = this.startArgv(engineStart(undefined), command, { cwd: options?.cwd, env: options?.env });
|
|
241
|
+
if (!execArgs.ok)
|
|
242
|
+
throw new RemoteExecutionError("unsupported", execArgs.error.message, execArgs.error);
|
|
243
|
+
const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(execArgs.value), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
|
|
232
244
|
const queue = [];
|
|
233
245
|
let finished = false;
|
|
234
246
|
let failure;
|
|
@@ -360,7 +372,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
360
372
|
if (!ready.ok)
|
|
361
373
|
return ready;
|
|
362
374
|
const abs = this.resolve(p);
|
|
363
|
-
const
|
|
375
|
+
const argv = this.startArgv(ADAPTER_START, `cat ${shellQuote(abs)}`);
|
|
376
|
+
const r = argv.ok ? await this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, binary: true }) : argv;
|
|
364
377
|
if (!r.ok)
|
|
365
378
|
return { ok: false, error: fileErrorFromExec(r.error, abs) };
|
|
366
379
|
if (r.value.exitCode !== 0)
|
|
@@ -420,9 +433,12 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
420
433
|
if (!this.containerId) {
|
|
421
434
|
const c = await this.connect();
|
|
422
435
|
if (!c.ok)
|
|
423
|
-
return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}
|
|
436
|
+
return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`, c.error) };
|
|
424
437
|
}
|
|
425
|
-
|
|
438
|
+
const argv = this.startArgv(ADAPTER_START, cmd, { stdin: input !== undefined });
|
|
439
|
+
if (!argv.ok)
|
|
440
|
+
return argv;
|
|
441
|
+
return this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
|
|
426
442
|
}
|
|
427
443
|
async fileInfo(p, abortSignal) {
|
|
428
444
|
return this.posixFs.statFileInfo(p, abortSignal);
|
|
@@ -460,18 +476,68 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
460
476
|
withHostFlag(args) {
|
|
461
477
|
return this.cfg.dockerHost ? ["-H", this.cfg.dockerHost, ...args] : args;
|
|
462
478
|
}
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
479
|
+
startArgv(start, command, o = {}) {
|
|
480
|
+
const launch = this.floor.wrap(start);
|
|
481
|
+
if (!launch.ok)
|
|
482
|
+
return launch;
|
|
483
|
+
const args = o.stdin ? ["exec", "-i"] : ["exec"];
|
|
484
|
+
args.push(...dockerIdentityFlags(launch.value));
|
|
485
|
+
if (o.containerDefaults !== true) {
|
|
486
|
+
args.push("-w", o.cwd != null ? this.resolve(o.cwd) : this.cwd);
|
|
487
|
+
const mergedEnv = { ...(this.cfg.env ?? {}), ...(o.env ?? {}) };
|
|
488
|
+
for (const [k, v] of Object.entries(mergedEnv))
|
|
489
|
+
args.push("-e", `${k}=${v}`);
|
|
490
|
+
}
|
|
491
|
+
args.push(this.containerName, ...floorArgv(launch.value, [o.shell ?? this.containerShell, "-c", command]));
|
|
492
|
+
return ok(args);
|
|
493
|
+
}
|
|
494
|
+
floorTransport() {
|
|
495
|
+
const control = { timeoutMs: this.cfg.controlTimeoutMs };
|
|
496
|
+
return {
|
|
497
|
+
bootstrap: async (command) => {
|
|
498
|
+
const argv = this.startArgv(BOOTSTRAP_START, command, { shell: "/bin/sh" });
|
|
499
|
+
if (!argv.ok)
|
|
500
|
+
return { ok: false, error: argv.error.message };
|
|
501
|
+
const r = await this.docker(argv.value, control);
|
|
502
|
+
return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
|
|
503
|
+
},
|
|
504
|
+
place: async (dest, bytes) => {
|
|
505
|
+
const dir = path.posix.dirname(dest);
|
|
506
|
+
const tmp = `${dest}.${randomBytes(6).toString("hex")}`;
|
|
507
|
+
const cmd = `umask 022 && mkdir -p ${shellQuote(dir)} && cat > ${shellQuote(tmp)} && chmod 0755 ${shellQuote(tmp)} && mv -f ${shellQuote(tmp)} ${shellQuote(dest)}`;
|
|
508
|
+
const argv = this.startArgv(BOOTSTRAP_START, cmd, { shell: "/bin/sh", stdin: true });
|
|
509
|
+
if (!argv.ok)
|
|
510
|
+
return { ok: false, error: argv.error.message };
|
|
511
|
+
const r = await this.docker(argv.value, { ...control, input: bytes });
|
|
512
|
+
if (!r.ok)
|
|
513
|
+
return { ok: false, error: r.error.message };
|
|
514
|
+
return r.value.exitCode === 0 ? { ok: true } : { ok: false, error: r.value.stderr.trim() || `exit ${r.value.exitCode}` };
|
|
515
|
+
},
|
|
516
|
+
openRelay: (identity, command, events) => {
|
|
517
|
+
const args = ["exec", "-i", ...dockerIdentityFlags({ mode: "control", identity }), this.containerName, "/bin/sh", "-c", command];
|
|
518
|
+
const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(args), { stdio: ["pipe", "pipe", "pipe"], env: this.cliEnv() });
|
|
519
|
+
let closed = false;
|
|
520
|
+
const close = (why) => {
|
|
521
|
+
if (closed)
|
|
522
|
+
return;
|
|
523
|
+
closed = true;
|
|
524
|
+
events.closed(why);
|
|
525
|
+
};
|
|
526
|
+
child.stdout?.setEncoding("utf8");
|
|
527
|
+
child.stdout?.on("data", (d) => events.data(d));
|
|
528
|
+
child.stderr?.on("data", () => { });
|
|
529
|
+
child.stdin?.on("error", () => { });
|
|
530
|
+
child.on("error", (e) => close(`docker exec failed: ${e.message}`));
|
|
531
|
+
child.on("close", (code, signal) => close(`relay exited (${code ?? signal ?? "unknown"})`));
|
|
532
|
+
return {
|
|
533
|
+
send: (line) => void child.stdin?.write(line),
|
|
534
|
+
close: () => {
|
|
535
|
+
child.stdin?.end();
|
|
536
|
+
child.kill("SIGKILL");
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
},
|
|
540
|
+
};
|
|
475
541
|
}
|
|
476
542
|
docker(args, opts) {
|
|
477
543
|
return new Promise((resolve) => {
|
|
@@ -570,7 +636,8 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
570
636
|
}
|
|
571
637
|
async detectContainerShell() {
|
|
572
638
|
try {
|
|
573
|
-
const
|
|
639
|
+
const argv = this.startArgv(ADAPTER_START, "command -v bash", { shell: "/bin/sh", containerDefaults: true });
|
|
640
|
+
const r = argv.ok ? await this.docker(argv.value, { timeoutMs: this.cfg.controlTimeoutMs }) : argv;
|
|
574
641
|
if (r.ok && r.value.exitCode === 0 && r.value.stdout.trim().length > 0)
|
|
575
642
|
this.containerShell = "bash";
|
|
576
643
|
}
|
|
@@ -597,7 +664,7 @@ export class RemoteLocalDockerExecutionEnv {
|
|
|
597
664
|
return ok(undefined);
|
|
598
665
|
const c = await this.connect();
|
|
599
666
|
if (!c.ok)
|
|
600
|
-
return { ok: false, error:
|
|
667
|
+
return { ok: false, error: fileErrorFromExec(c.error, this.resolve(p)) };
|
|
601
668
|
return ok(undefined);
|
|
602
669
|
}
|
|
603
670
|
dockerToRemoteError(e, fallback) {
|
|
@@ -53,28 +53,8 @@ export interface SshEnvDeps {
|
|
|
53
53
|
connectOnce?: (signal?: AbortSignal) => Promise<ConnectResult>;
|
|
54
54
|
}
|
|
55
55
|
export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
* 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,在真机上解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
|
|
59
|
-
* (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
|
|
60
|
-
* 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
|
|
61
|
-
* 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
|
|
62
|
-
* 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
|
|
63
|
-
* core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
|
|
64
|
-
* 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
|
|
65
|
-
*/
|
|
66
|
-
/** `sharesProcessTable`(core 7.57.0,必填):命令跑在 SSH 目标机上;目标恰是本机(或本进程所在容器的宿主)时命令与本进程同表,这一形今天没有配置面可声明 `true`。 */
|
|
67
|
-
/** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
|
|
68
|
-
readonly capabilities: {
|
|
69
|
-
isolation: false;
|
|
70
|
-
suspendable: false;
|
|
71
|
-
canonicalPathAuthoritative: true;
|
|
72
|
-
sharesProcessTable: false;
|
|
73
|
-
hostLocalPaths: false;
|
|
74
|
-
externalContentTarget: false;
|
|
75
|
-
io: true;
|
|
76
|
-
keepsReadFloor: false;
|
|
77
|
-
};
|
|
56
|
+
/** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
|
|
57
|
+
readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
|
|
78
58
|
/** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at mountPath. */
|
|
79
59
|
cwd: string;
|
|
80
60
|
private conn?;
|
|
@@ -91,9 +71,9 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
|
|
|
91
71
|
* =重连风暴);②共享尝试**不携带任何 caller 信号**(首创者的 pre-abort 不得毒化 joiner;单 caller
|
|
92
72
|
* 取消只退它自己的等待,底层握手余命 ≤ readyTimeout 有界,彻底取消归 destroy 代际闸)。 */
|
|
93
73
|
private inFlight?;
|
|
94
|
-
/** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home ——
|
|
74
|
+
/** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
|
|
95
75
|
get homeDir(): string | undefined;
|
|
96
|
-
/** 家目录的座(
|
|
76
|
+
/** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则连上之后实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
|
|
97
77
|
private readonly homeSeat;
|
|
98
78
|
/** 家目录声明 × 实测的判定,按连接记(同一条连接只问一次;重连换了连接再问 —— 登录用户不变时答案相同,代价一次往返;没声明且已量到绝对路径之后不再问)。 */
|
|
99
79
|
private homeChecked?;
|
|
@@ -7,6 +7,7 @@ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
|
|
|
7
7
|
import { buildCapturedOutput, createCutError } from "../exec-cut.js";
|
|
8
8
|
import { createPosixShellFs } from "./posix-shell-fs.js";
|
|
9
9
|
import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
|
|
10
|
+
import { capabilitiesFor } from "../execution-lane-caps.js";
|
|
10
11
|
import { fileErrorFromExec } from "./remote-env-file-error.js";
|
|
11
12
|
import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
|
|
12
13
|
const PROVIDER = "ssh";
|
|
@@ -19,7 +20,7 @@ function exitCodeForSignalKill(signal) {
|
|
|
19
20
|
return 128 + (signalNumber(`SIG${signal}`) ?? 9);
|
|
20
21
|
}
|
|
21
22
|
export class RemoteSshExecutionEnv {
|
|
22
|
-
capabilities = {
|
|
23
|
+
capabilities = capabilitiesFor({ remoteExec: { provider: "ssh" } });
|
|
23
24
|
cwd;
|
|
24
25
|
conn;
|
|
25
26
|
sftpPromise;
|
|
@@ -52,7 +53,7 @@ export class RemoteSshExecutionEnv {
|
|
|
52
53
|
hostKey: config.hostKey,
|
|
53
54
|
};
|
|
54
55
|
this.cwd = this.cfg.mountPath;
|
|
55
|
-
this.homeSeat = new LaneHomeSeat(
|
|
56
|
+
this.homeSeat = new LaneHomeSeat(PROVIDER, config);
|
|
56
57
|
}
|
|
57
58
|
resolve(p) {
|
|
58
59
|
return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
|
|
@@ -579,7 +580,7 @@ export class RemoteSshExecutionEnv {
|
|
|
579
580
|
async sftp(abortSignal) {
|
|
580
581
|
const ready = await this.ensureConnected(abortSignal);
|
|
581
582
|
if (!ready.ok)
|
|
582
|
-
return Promise.reject(ready.error.code === "aborted" ? fileErrorFromExec(ready.error) : new
|
|
583
|
+
return Promise.reject(ready.error.code === "aborted" ? fileErrorFromExec(ready.error) : new FileError("target_unavailable", ready.error.message, undefined, ready.error));
|
|
583
584
|
if (!this.conn)
|
|
584
585
|
return Promise.reject(new Error("SSH not connected"));
|
|
585
586
|
if (!this.sftpPromise) {
|
|
@@ -4,8 +4,8 @@ export class SqlResumeAnchorStore {
|
|
|
4
4
|
constructor(db) {
|
|
5
5
|
this.db = db;
|
|
6
6
|
}
|
|
7
|
-
q(
|
|
8
|
-
return this.db.dialect === "
|
|
7
|
+
q(mysql, pg) {
|
|
8
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
9
9
|
}
|
|
10
10
|
async put(sessionId, eventId, entryId, owner) {
|
|
11
11
|
await this.db.query(this.q("INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES (?,?,?,?,?) " +
|
|
@@ -118,7 +118,7 @@ export declare class SqlRetentionLaneStore implements RetentionLaneStore {
|
|
|
118
118
|
protected readonly db: SqlDriver;
|
|
119
119
|
constructor(db: SqlDriver);
|
|
120
120
|
/** 方言取文(两条语句都写在调用点;A12 判据)。 */
|
|
121
|
-
protected q(
|
|
121
|
+
protected q(mysql: string, pg: string): string;
|
|
122
122
|
/**
|
|
123
123
|
* 抢/续租一次(单行 CAS)。抢到 ⇒ `{held:true, fencingToken}`;别人正持着 ⇒ `{held:false}`,调用方
|
|
124
124
|
* **本 tick 零写静默跳过**。
|
|
@@ -7,8 +7,8 @@ export class SqlRetentionLaneStore {
|
|
|
7
7
|
constructor(db) {
|
|
8
8
|
this.db = db;
|
|
9
9
|
}
|
|
10
|
-
q(
|
|
11
|
-
return this.db.dialect === "
|
|
10
|
+
q(mysql, pg) {
|
|
11
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
12
12
|
}
|
|
13
13
|
async acquire(holder, ttlMs, nowMs) {
|
|
14
14
|
const conn = await this.db.connect();
|
|
@@ -183,7 +183,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
183
183
|
readonly retention: RetentionDeclaration;
|
|
184
184
|
constructor(db: SqlDriver, opts: SqlRetentionStoreOptions);
|
|
185
185
|
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE (A12 判据)。 */
|
|
186
|
-
protected q(
|
|
186
|
+
protected q(mysql: string, pg: string): string;
|
|
187
187
|
/**
|
|
188
188
|
* 域谓词。**四种写法逐条写出来**(方言 × 有主/无主):
|
|
189
189
|
* · 有主:`col = ?` / `col = $n`;
|
|
@@ -155,8 +155,8 @@ export class SqlRetentionStore {
|
|
|
155
155
|
this.db = db;
|
|
156
156
|
this.opts = opts;
|
|
157
157
|
}
|
|
158
|
-
q(
|
|
159
|
-
return this.db.dialect === "
|
|
158
|
+
q(mysql, pg) {
|
|
159
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
160
160
|
}
|
|
161
161
|
domainWhere(col, domain, pgIndex) {
|
|
162
162
|
return domain === ""
|
|
@@ -164,7 +164,7 @@ export class SqlRetentionStore {
|
|
|
164
164
|
: { sql: this.q(`${col} = ?`, `${col} = $${pgIndex}`), params: [domain] };
|
|
165
165
|
}
|
|
166
166
|
idSet(col, ids, pgIndex) {
|
|
167
|
-
return this.db.dialect === "
|
|
167
|
+
return this.db.dialect === "mysql"
|
|
168
168
|
? { sql: `${col} IN (${ids.map(() => "?").join(",")})`, params: [...ids] }
|
|
169
169
|
: { sql: `${col} = ANY($${pgIndex}::text[])`, params: [ids] };
|
|
170
170
|
}
|
|
@@ -210,7 +210,7 @@ export class SqlRetentionStore {
|
|
|
210
210
|
if (keys.length === 0)
|
|
211
211
|
return 0;
|
|
212
212
|
const now = Date.now();
|
|
213
|
-
if (this.db.dialect === "
|
|
213
|
+
if (this.db.dialect === "mysql") {
|
|
214
214
|
const params = [];
|
|
215
215
|
for (const k of keys)
|
|
216
216
|
params.push(domain, kind, k, now);
|
|
@@ -267,7 +267,7 @@ export class SqlRetentionStore {
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
horizonArms(pgFirst) {
|
|
270
|
-
return this.db.dialect === "
|
|
270
|
+
return this.db.dialect === "mysql"
|
|
271
271
|
? `((c.deadline IS NOT NULL AND c.deadline <= ?) OR (c.deadline IS NULL AND c.created_at_ms <= ?))`
|
|
272
272
|
: `((c.deadline IS NOT NULL AND c.deadline <= $${pgFirst}) OR (c.deadline IS NULL AND c.created_at_ms <= $${pgFirst + 1}))`;
|
|
273
273
|
}
|
|
@@ -287,7 +287,7 @@ export class SqlRetentionStore {
|
|
|
287
287
|
return Number(rows[0]?.n ?? 0);
|
|
288
288
|
}
|
|
289
289
|
liveReferenceExists() {
|
|
290
|
-
const workflow = this.db.dialect === "
|
|
290
|
+
const workflow = this.db.dialect === "mysql"
|
|
291
291
|
? `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND JSON_UNQUOTE(JSON_EXTRACT(wr.run, '$.originatingSessionId')) = sm.session_id)`
|
|
292
292
|
: `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND wr.run::json->>'originatingSessionId' = sm.session_id)`;
|
|
293
293
|
return (`(sm.placement_kind IS NOT NULL` +
|
|
@@ -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 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 } 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";
|
|
@@ -171,7 +171,7 @@ export declare class SqlRunStore {
|
|
|
171
171
|
/**
|
|
172
172
|
* 认领当写栅栏用(进程内两孪生的契约 = `store-contracts.ts` 的 `InProcessClaimFence`;本孪生是它的事务形):一个事务 ——
|
|
173
173
|
* ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
|
|
174
|
-
* ③ 刷新这条 run 的活性戳;④ `write`
|
|
174
|
+
* ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑(另收锁住那一行的 {@link FencedRun});提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
|
|
175
175
|
* 🔴 **取锁序 = task_run → task_active**,与 `setTerminal` / `reapStale` / `reapSuspended` 同序(它们先改 run 行、再删认领行):两行用两条语句按序锁,
|
|
176
176
|
* 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun` 先锁根会话行再插认领行(本序从不碰 `session_meta`)、
|
|
177
177
|
* `deleteBySession` 先锁根会话行、见认领行在就中止,都不与本序成环。锁住之后:收割的改行 / 终局与收割的删认领 / 别人的建行都排在本事务之后,收割器提交后重判 `updated_at` 看到的是刚刷的。
|
|
@@ -180,7 +180,7 @@ export declare class SqlRunStore {
|
|
|
180
180
|
* 🔴 **`write` 不许写 `session_meta`**:本事务持着 `task_active`,而 `createRun` 与改根行的写者都先锁根会话行再碰 `task_*`(全局锁序见 {@link createRun})——
|
|
181
181
|
* 回调去锁根行就是反序,与等着本会话认领的 `createRun` 各攥一行成环。门:`test/submission-root-contract.test.ts` 扫每个调用点的回调体。
|
|
182
182
|
*/
|
|
183
|
-
whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn) => Promise<T>): Promise<ClaimFenced<T>>;
|
|
183
|
+
whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn, run: FencedRun) => Promise<T>): Promise<ClaimFenced<T>>;
|
|
184
184
|
/** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
|
|
185
185
|
getActiveTaskId(sessionId: string): Promise<string | undefined>;
|
|
186
186
|
/** Shared row→record mapper — byte-identical between dialects (both drivers hand back the same column
|
|
@@ -47,8 +47,8 @@ export class SqlRunStore {
|
|
|
47
47
|
constructor(db) {
|
|
48
48
|
this.db = db;
|
|
49
49
|
}
|
|
50
|
-
q(
|
|
51
|
-
return this.db.dialect === "
|
|
50
|
+
q(mysql, pg) {
|
|
51
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
52
52
|
}
|
|
53
53
|
isDupKey(err) {
|
|
54
54
|
return isDupKeyError(this.db.dialect, err);
|
|
@@ -103,7 +103,7 @@ export class SqlRunStore {
|
|
|
103
103
|
cells.identitySource,
|
|
104
104
|
cells.identityScope,
|
|
105
105
|
cells.identityPrincipalInc,
|
|
106
|
-
this.db.dialect === "
|
|
106
|
+
this.db.dialect === "mysql"
|
|
107
107
|
? meta.objectivePreview ?? null
|
|
108
108
|
: meta.objectivePreview == null
|
|
109
109
|
? null
|
|
@@ -181,8 +181,8 @@ export class SqlRunStore {
|
|
|
181
181
|
const w = await conn.query(this.q("UPDATE task_run SET status = ?, result = ?, error = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','suspended','needs_review')", "UPDATE task_run SET status = $1, result = $2::jsonb, error = $3, error_code = $4, updated_at = $5 WHERE task_id = $6 AND status IN ('running','suspended','needs_review')"), [
|
|
182
182
|
status,
|
|
183
183
|
result == null ? null : this.json(result),
|
|
184
|
-
this.db.dialect === "
|
|
185
|
-
this.db.dialect === "
|
|
184
|
+
this.db.dialect === "mysql" ? error : error == null ? null : pgSanitizeText(error),
|
|
185
|
+
this.db.dialect === "mysql" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
|
|
186
186
|
new Date(),
|
|
187
187
|
taskId,
|
|
188
188
|
]);
|
|
@@ -253,7 +253,7 @@ export class SqlRunStore {
|
|
|
253
253
|
}
|
|
254
254
|
async claimSpaceCommitIn(conn, spaceKey, taskId, state, claim) {
|
|
255
255
|
if (!claim.requireOwn) {
|
|
256
|
-
await conn.query(this.q(`INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES (?,?,?,${SQL_DB_NOW_MS.
|
|
256
|
+
await conn.query(this.q(`INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES (?,?,?,${SQL_DB_NOW_MS.mysql} + ?) ON DUPLICATE KEY UPDATE space_key = space_key`, `INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES ($1,$2,$3,${SQL_DB_NOW_MS.pg} + $4::bigint) ON CONFLICT (space_key) DO UPDATE SET space_key = EXCLUDED.space_key`), [spaceKey, taskId, state, claim.boundMs]);
|
|
257
257
|
}
|
|
258
258
|
const row = await this.readSpaceCommitIn(conn, spaceKey, true);
|
|
259
259
|
switch (decideSpaceCommitClaim(row, taskId, claim)) {
|
|
@@ -262,7 +262,7 @@ export class SqlRunStore {
|
|
|
262
262
|
case "insert":
|
|
263
263
|
throw new Error("workspace_space_commit: a space's row vanished under this transaction's own lock");
|
|
264
264
|
case "update": {
|
|
265
|
-
const w = await conn.query(this.q(`UPDATE workspace_space_commit SET task_id = ?, state = ?, expires_at_ms = ${SQL_DB_NOW_MS.
|
|
265
|
+
const w = await conn.query(this.q(`UPDATE workspace_space_commit SET task_id = ?, state = ?, expires_at_ms = ${SQL_DB_NOW_MS.mysql} + ? WHERE space_key = ? AND task_id = ?`, `UPDATE workspace_space_commit SET task_id = $1, state = $2, expires_at_ms = ${SQL_DB_NOW_MS.pg} + $3::bigint WHERE space_key = $4 AND task_id = $5`), [taskId, state, claim.boundMs, spaceKey, row.taskId]);
|
|
266
266
|
return w.affected > 0;
|
|
267
267
|
}
|
|
268
268
|
}
|
|
@@ -282,15 +282,16 @@ export class SqlRunStore {
|
|
|
282
282
|
async whileHoldingClaim(sessionId, taskId, write) {
|
|
283
283
|
let fenced = { held: false };
|
|
284
284
|
await this.tx(async (conn) => {
|
|
285
|
-
const run = await conn.query(this.q("SELECT task_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
|
|
286
|
-
|
|
285
|
+
const run = await conn.query(this.q("SELECT task_id, instance_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id, instance_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
|
|
286
|
+
const row = run.rows[0];
|
|
287
|
+
if (row === undefined)
|
|
287
288
|
return;
|
|
288
289
|
const claim = await conn.query(this.q("SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE", "SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE"), [sessionId]);
|
|
289
290
|
const holder = claim.rows[0];
|
|
290
291
|
if (holder === undefined || String(holder.task_id) !== taskId)
|
|
291
292
|
return;
|
|
292
293
|
await conn.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ?", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2"), [new Date(), taskId]);
|
|
293
|
-
fenced = { held: true, value: await write(conn) };
|
|
294
|
+
fenced = { held: true, value: await write(conn, { instanceId: row.instance_id ?? null }) };
|
|
294
295
|
});
|
|
295
296
|
return fenced;
|
|
296
297
|
}
|
|
@@ -318,20 +319,20 @@ export class SqlRunStore {
|
|
|
318
319
|
};
|
|
319
320
|
}
|
|
320
321
|
async getRun(taskId) {
|
|
321
|
-
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("
|
|
322
|
+
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
|
|
322
323
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
323
324
|
}
|
|
324
325
|
async observeSubmittedRow(conn, taskId, idemKey) {
|
|
325
326
|
if (idemKey !== null) {
|
|
326
|
-
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("
|
|
327
|
+
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
|
|
327
328
|
if (rows[0])
|
|
328
329
|
return this.rowToRun(rows[0]);
|
|
329
330
|
}
|
|
330
|
-
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("
|
|
331
|
+
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
|
|
331
332
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
332
333
|
}
|
|
333
334
|
async getRunByIdem(idemKey) {
|
|
334
|
-
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("
|
|
335
|
+
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
|
|
335
336
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
336
337
|
}
|
|
337
338
|
async releaseIdemKey(taskId) {
|
|
@@ -346,7 +347,7 @@ export class SqlRunStore {
|
|
|
346
347
|
async listRuns(opts) {
|
|
347
348
|
if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
|
|
348
349
|
return [];
|
|
349
|
-
if (this.db.dialect === "
|
|
350
|
+
if (this.db.dialect === "mysql") {
|
|
350
351
|
const where = [];
|
|
351
352
|
const params = [];
|
|
352
353
|
if (opts.status) {
|
|
@@ -372,7 +373,7 @@ export class SqlRunStore {
|
|
|
372
373
|
where.push("(created_at < ? OR (created_at = ? AND task_id < ?))");
|
|
373
374
|
params.push(new Date(opts.cursor.createdAt), new Date(opts.cursor.createdAt), opts.cursor.taskId);
|
|
374
375
|
}
|
|
375
|
-
const sql = `SELECT ${runRowCols("
|
|
376
|
+
const sql = `SELECT ${runRowCols("mysql")} FROM task_run` +
|
|
376
377
|
(where.length ? " WHERE " + where.join(" AND ") : "") +
|
|
377
378
|
" ORDER BY created_at DESC, task_id DESC LIMIT ?";
|
|
378
379
|
params.push(opts.limit);
|
|
@@ -413,7 +414,7 @@ export class SqlRunStore {
|
|
|
413
414
|
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
414
415
|
}
|
|
415
416
|
async listSessions(opts) {
|
|
416
|
-
if (this.db.dialect === "
|
|
417
|
+
if (this.db.dialect === "mysql") {
|
|
417
418
|
const params = [];
|
|
418
419
|
let innerWhere = "";
|
|
419
420
|
if (opts.owner !== undefined) {
|
|
@@ -533,7 +534,7 @@ export class SqlRunStore {
|
|
|
533
534
|
return m == null ? 0 : Number(m);
|
|
534
535
|
}
|
|
535
536
|
async getEvents(taskId, afterSeq) {
|
|
536
|
-
const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("
|
|
537
|
+
const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("mysql", "data")}, ts FROM task_event WHERE task_id = ? AND seq > ? ORDER BY seq ASC`, `SELECT seq, type, ${jsonColumnText("pg", "data")}, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC`), [taskId, afterSeq]);
|
|
537
538
|
return rows.map((r) => ({
|
|
538
539
|
seq: Number(r.seq),
|
|
539
540
|
type: String(r.type),
|