@sema-agent/server 7.138.0 → 7.140.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +457 -7
- package/README.md +7 -4
- package/README.zh-CN.md +6 -4
- package/USAGE.md +91 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +26 -26
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +28 -34
- package/dist/boot/execution-lane.d.ts +9 -1
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/governance-seams.d.ts +3 -2
- package/dist/boot/governance-seams.js +9 -11
- package/dist/boot/memory-boundary.d.ts +14 -51
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +15 -11
- package/dist/boot/memory-consolidation.js +25 -14
- package/dist/boot/resolve-spec.d.ts +4 -6
- package/dist/boot/resolve-spec.js +10 -13
- package/dist/boot/retention-lane.d.ts +5 -5
- package/dist/boot/retention-lane.js +0 -6
- package/dist/boot/runner-deps.d.ts +6 -4
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +2 -6
- package/dist/boot/stage-06-runners.js +2 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -6
- package/dist/boot/stage-08-reapers.d.ts +2 -6
- package/dist/boot/stage-09-leader.d.ts +2 -6
- package/dist/boot/stage-09-leader.js +1 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -6
- package/dist/boot/stage-10-http-server.js +7 -6
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +27 -40
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +37 -11
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/mcp-probe.js +1 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +15 -7
- package/dist/capability-facts.js +8 -3
- package/dist/config-catalog.js +9 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +33 -37
- package/dist/config-provider.js +51 -30
- package/dist/config-types.d.ts +21 -7
- package/dist/config.d.ts +33 -0
- package/dist/config.js +46 -16
- package/dist/delegated-session-owner.d.ts +4 -3
- package/dist/delegated-session-owner.js +1 -1
- package/dist/deployment-shape.d.ts +10 -9
- package/dist/deployment-shape.js +4 -4
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +115 -105
- package/dist/execution-lane-caps.js +76 -82
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/durable-run-leg.js +9 -5
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +63 -36
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-ctx.d.ts +14 -8
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +8 -1
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +5 -0
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +143 -22
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +4 -5
- package/dist/http/routes/tasks.js +41 -33
- package/dist/http/server.d.ts +40 -44
- package/dist/http/server.js +13 -2
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +8 -12
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +29 -5
- package/dist/memory-scope.js +16 -4
- package/dist/memory-sync-client.d.ts +70 -18
- package/dist/memory-sync-client.js +215 -117
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.js +0 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +34 -9
- package/dist/parked-decide.js +12 -19
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +13 -7
- package/dist/peer-seat.js +37 -3
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +44 -26
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -2
- package/dist/plugins/caching-session-store.js +9 -3
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
- package/dist/plugins/checkpoint-store-sql.js +21 -23
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +68 -122
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +9 -8
- package/dist/plugins/file-history-store-sql.js +9 -9
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-session-store.d.ts +149 -50
- package/dist/plugins/local-session-store.js +382 -107
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -8
- package/dist/plugins/one-time-migrations.js +408 -38
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -19
- package/dist/plugins/permission-rule-store-sql.js +29 -32
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +31 -20
- package/dist/plugins/pg-session-storage.js +120 -52
- package/dist/plugins/remote-env-adb.d.ts +18 -19
- package/dist/plugins/remote-env-adb.js +91 -66
- package/dist/plugins/remote-env-device.d.ts +23 -31
- package/dist/plugins/remote-env-device.js +19 -17
- package/dist/plugins/remote-env-e2b.d.ts +27 -21
- package/dist/plugins/remote-env-e2b.js +91 -45
- package/dist/plugins/remote-env-file-error.d.ts +3 -1
- package/dist/plugins/remote-env-file-error.js +9 -3
- package/dist/plugins/remote-env-host.d.ts +31 -34
- package/dist/plugins/remote-env-host.js +91 -72
- package/dist/plugins/remote-env-k8s.d.ts +13 -29
- package/dist/plugins/remote-env-k8s.js +63 -55
- package/dist/plugins/remote-env-local-docker.d.ts +24 -12
- package/dist/plugins/remote-env-local-docker.js +49 -61
- package/dist/plugins/remote-env-ssh.d.ts +11 -19
- package/dist/plugins/remote-env-ssh.js +74 -57
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +10 -7
- package/dist/plugins/retention-store-sql.js +13 -7
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +20 -15
- package/dist/plugins/schema-consistency.d.ts +21 -11
- package/dist/plugins/schema-consistency.js +15 -9
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-fork.d.ts +54 -0
- package/dist/plugins/session-fork.js +38 -1
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +5 -7
- package/dist/plugins/store-backend.js +23 -11
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +13 -7
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +57 -26
- package/dist/plugins/tidb-session-store.js +127 -59
- package/dist/plugins/tool-result-store-sql.d.ts +5 -4
- package/dist/plugins/tool-result-store-sql.js +21 -13
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +34 -35
- package/dist/runs.d.ts +10 -5
- package/dist/runs.js +52 -66
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +9 -5
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +20 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +22 -9
- package/dist/task-mcp.js +17 -9
- package/dist/terminal.d.ts +27 -0
- package/dist/terminal.js +6 -0
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +8 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +80 -28
- package/dist/trace/self-minted-wire.d.ts +169 -2
- package/dist/trace/self-minted-wire.js +54 -1
- package/dist/url-refusal.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/dist/web-admin-client/skills-mcp.js +3 -2
- package/dist/web-admin-client/types.d.ts +6 -0
- package/package.json +3 -3
- package/wire-changes/server@7.139.0.json +348 -0
- package/wire-changes/server@7.140.0.json +184 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared shell/path helpers for the
|
|
3
|
-
* security-sensitive quoting can't drift between adapters (council design/#4, #5).
|
|
2
|
+
* Shared shell/path helpers for the remote `RemoteExecutionEnv` adapters (ssh / adb / k8s / local-docker, and e2b where it
|
|
3
|
+
* shells out). Kept in ONE place so the security-sensitive quoting can't drift between adapters (council design/#4, #5).
|
|
4
4
|
*/
|
|
5
|
-
import { ExecutionError,
|
|
5
|
+
import { ExecutionError, FileError, type FileInfo, type RemoteExecutionError, type Result, type SymlinkChain } from "@sema-agent/core";
|
|
6
6
|
import type { ChildProcess } from "node:child_process";
|
|
7
7
|
/**
|
|
8
8
|
* D5 pipe-destroy grace (parity with core's vendored `EXEC_FORCE_SETTLE_GRACE_MS`, harness/env/nodejs.js):
|
|
@@ -204,4 +204,101 @@ export type ShellChainRun = (cmd: string) => Promise<Result<{
|
|
|
204
204
|
* 三条远端腿的文件面探针不带 cwd(车PL 验修 ③)⇒ 这条命令不会因为某个工作目录不可用而不跑。
|
|
205
205
|
*/
|
|
206
206
|
export declare function runSymlinkChain(absPath: string, run: ShellChainRun): Promise<Result<SymlinkChain, FileError>>;
|
|
207
|
+
/**
|
|
208
|
+
* 脚本删一只私有目录没删掉时写到 **stdout** 的那一条记录的头:`<头> <目录路径>\0`(NUL 收尾 —— 路径里可以有换行)。
|
|
209
|
+
* 残留记录走 stdout、错误走 stderr:两条通道不混,分类器读到的 stderr 里没有路径文本。记录不改脚本的退出码:写照它自己的结局答,残留另行留痕。
|
|
210
|
+
*/
|
|
211
|
+
export declare const STAGE_DIR_LEFT_MARK = "sema: staging directory left behind:";
|
|
212
|
+
/** 一次远端写的暂存位置(纯数据):`dir` = 目标目录下的私有目录,`file` = 其中那只暂存文件。 */
|
|
213
|
+
export interface StagingPaths {
|
|
214
|
+
readonly dir: string;
|
|
215
|
+
readonly file: string;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 远端写的暂存位置(纯数据 ⇒ `build*`):私有目录在**目标同目录**(替换的提交是同一文件系统内的 rename),名 = `.<目标名>.stage-<12 位十六进制>`;
|
|
219
|
+
* 目标名已贴着 NAME_MAX 时逐码点截短到放得下 —— 一个目录装不下的暂存名会把一次就地形本来接得住的写拒掉。这只名字铸一次、用到底:
|
|
220
|
+
* 提交脚本跟一跳链接到别的目录里重新暂存时,用的仍是这只名字(已在上限之内,放到哪个目录都装得下)。`nonce` 只给测试钉序用。
|
|
221
|
+
*/
|
|
222
|
+
export declare function buildStagingPaths(abs: string, nonce?: string): StagingPaths;
|
|
223
|
+
/**
|
|
224
|
+
* 准备脚本(纯数据 ⇒ `build*`):建目标的父目录(`mkdir -p`,失败 ⇒ 退出 1),再建私有目录({@link privateMkdir};失败 ⇒ 退出 {@link STAGE_DIR_REFUSED_EXIT}),
|
|
225
|
+
* 再收紧({@link hardenPrivate};不成 ⇒ 放弃这只目录({@link abandonPrivate})、写 {@link STAGE_DIR_UNSEALED_MESSAGE}、退出 {@link STAGE_DIR_UNSEALED_EXIT} —— 编排退到直接写目标),
|
|
226
|
+
* 再核里面没有 `data`({@link emptyPrivate};不成 ⇒ 放弃这只目录、写明原因、退出 1 —— 响亮答错,不退;原因句不带路径:stderr 只给分类器读)。
|
|
227
|
+
* 非零退出之后目标机上没有这次写的私有目录(删不掉的那一只由 {@link STAGE_DIR_LEFT_MARK} 记录点名)。**退出 0 ⇔ 目录是读回核实过的私有的、且是空的。**
|
|
228
|
+
*/
|
|
229
|
+
export declare function buildStagingPrepareCommand(stage: StagingPaths): string;
|
|
230
|
+
/**
|
|
231
|
+
* 整文件替换的提交脚本(POSIX sh,纯数据 ⇒ `build*`)。一次往返,**整条命令只有一个退出状态**(`{ …; }`,理由同
|
|
232
|
+
* {@link buildSymlinkChainCommand} 头注)。`d` = 私有目录,`s` = 其中的暂存件,`t` = 目标。
|
|
233
|
+
*
|
|
234
|
+
* 1. 叶是符号链接 ⇒ 读**一跳**(`readlink`,不带 `-f`;相对目标锚在链接自己的目录,不折 `..`,与链视图同一词法;
|
|
235
|
+
* 链接文本的结尾换行原样保留 —— `-n` 不加换行,`$(…)` 会吞掉结尾换行,所以输出后垫一个 `.` 再剥掉;`-n` 不被认 ⇒ 读不出一跳 ⇒ 第 3 步经链接就地写):那一跳是常规文件或还不存在
|
|
236
|
+
* ⇒ 提交落到那一跳上(链接保持是链接);否则(链 / 非常规)⇒ 走第 3 步就地写。
|
|
237
|
+
* 2. 目标是常规文件或不存在 ⇒ 私有目录不在目标的目录里(跟了一跳)⇒ 先在目标目录里用**同一只私有目录名**、同样生来 0700、同样收紧过地**重新暂存**一份
|
|
238
|
+
* (目标机本地拷贝,不过网络;拷成了就改用新的那只、删原私有目录;拷不成就删掉新建的那只、收紧不成就放弃它,用原暂存件 ——
|
|
239
|
+
* 删不掉的都写一条 {@link STAGE_DIR_LEFT_MARK} 记录),
|
|
240
|
+
* 让 rename 不跨文件系统(与 core 把暂存放在最终目标旁边同形);
|
|
241
|
+
* 已有文件的权限位带到暂存件上(`stat -c %a`,BSD 退到 `stat -f %Lp`;带不过去不拦)—— **在 `mv` 之前**,换上去的那一刻权限位已是目标的;
|
|
242
|
+
* `mv -f` 换上去,成功 ⇒ 删私有目录(删不掉 ⇒ 一条 {@link STAGE_DIR_LEFT_MARK} 记录,仍 `exit 0`:目标已是新件)。
|
|
243
|
+
* 3. 其余(链接链 / FIFO / 设备 / 目录 / `mv` 被拒,如单文件绑定挂载的 EBUSY)⇒ 已落定的暂存件就地拷进目标(与 core 的回落同形:
|
|
244
|
+
* 非原子,但不再经过网络传输),见 {@link takeAndWrite}:先把暂存件**打开**(读不了 ⇒ 先补上属主读位,仍读不了 ⇒ 非零退出、目标未碰),
|
|
245
|
+
* 再**删掉暂存名**(删不掉 ⇒ 非零退出、目标未碰,残留目录由脚本点名),然后才从已打开的那份拷进目标,最后关 fd、删私有目录(删不掉只记录)。于是
|
|
246
|
+
* 「目标被动过」⇒「暂存名已不在」,残留至多是一只空目录(或文件系统为打开着的文件留的隐藏项)。退出码 = 拷入那一步的退出码
|
|
247
|
+
* (目录 ⇒ `Is a directory` 经 {@link classifyFsStderr} 归 `is_directory`)。
|
|
248
|
+
*/
|
|
249
|
+
export declare function buildStagedCommitCommand(stage: StagingPaths, target: string): string;
|
|
250
|
+
/**
|
|
251
|
+
* 追加的提交脚本(纯数据 ⇒ `build*`):取走暂存件并写目标({@link takeAndWrite},次序同 {@link buildStagedCommitCommand} 第 3 步:先开、再删暂存名、后写、再关 fd 删私有目录),
|
|
252
|
+
* 一次 `cat >>` 加到目标尾(跟随链接、不换 inode、不碰已有字节)。暂存名删不掉 ⇒ 目标一个字节没加。
|
|
253
|
+
*/
|
|
254
|
+
export declare function buildStagedAppendCommand(stage: StagingPaths, target: string): string;
|
|
255
|
+
/** 清掉一次写的私有目录({@link removalOf}:只删 `data` 与这只目录;不在 ⇒ 什么都不做;退出 0 ⇔ 不在了)。纯数据 ⇒ `build*`。 */
|
|
256
|
+
export declare function buildStagingDiscardCommand(stage: StagingPaths): string;
|
|
257
|
+
/**
|
|
258
|
+
* 一次暂存写交给 {@link writeFileStaged} / {@link appendFileStaged} 的两只面 —— 各腿的传输留在腿上,规则在这里。
|
|
259
|
+
* 🔴 **`upload` 与 `run` 必须是同一个身份、在同一台机器上**:私有目录由 `run` 建(0700,属写入方),`upload` 要进得去;提交由 `run` 做,
|
|
260
|
+
* 要拿得走、清得掉 `upload` 落下的东西。一条腿的传输若以别的身份落盘(例如容器守护进程以 root 拷入、而命令以镜像用户跑),那条腿必须改走
|
|
261
|
+
* 与 `run` 同一身份的传输,不能让编排迁就它。
|
|
262
|
+
*/
|
|
263
|
+
export interface StagedWriteLane {
|
|
264
|
+
/** 把**整份**字节放到 `dest`(新建;`dest` 的目录已在且是写入方的私有目录)—— 腿自己的传输(sftp / adb push / 容器内 `cat` 收 stdin / 分块 exec / provider 文件 API)。 */
|
|
265
|
+
upload(dest: string, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
266
|
+
/** 目标机上的一次 shell 往返(腿的文件面探针 exec:只用绝对路径,不进工作目录)。`abortSignal` 只有准备那一步带(提交一旦发出不再听取消)。 */
|
|
267
|
+
run(cmd: string, abortSignal?: AbortSignal): ReturnType<ShellChainRun>;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 退路拿到的两样(编排为每一次退路造一只,见 {@link createDirectContext}):`signal` = 这次写的取消信号;`inPlace(step)` = 把**写目标的那一步**包起来
|
|
271
|
+
* (连同那一步自己的通道准备:ssh 在步内拿 sftp 通道)。取消的读法只看那一步有没有被包住:包住的那一步**答了** `aborted` ⇒ 改写成 `unknown`(可能已有字节到了目标,
|
|
272
|
+
* 保守;通道准备里被取消也算在内);那一步开始之前取消已发生 ⇒ `aborted`、那一步不跑;那一步不收取消、照样写完(ssh 的 sftp)⇒ 结果照实(成功);
|
|
273
|
+
* 没包的步骤(读旧)答的 `aborted` 原样透出(目标未碰)。编排不猜退路走到了哪儿。
|
|
274
|
+
*/
|
|
275
|
+
export interface StagedDirectContext {
|
|
276
|
+
readonly signal: AbortSignal | undefined;
|
|
277
|
+
inPlace<T>(step: (abortSignal?: AbortSignal) => Promise<Result<T, FileError>>): Promise<Result<T, FileError>>;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 不暂存时的退路 —— 目录不收新名字(建私有目录被 `permission_denied` 拒)或私有目录收不成私有:腿直接对目标做的那一步(core 同一条回落:文件本身可能可写)。
|
|
281
|
+
* 两只文件面、五条腿**必传**:写面 = 腿的上传直落目标;追加面 = 能追加写的腿(ssh / local-docker / k8s)直接追加到目标,只能把整份文件落到一个路径上的腿
|
|
282
|
+
* (e2b 的 `files.write`、adb 的 push)读旧 + 拼 + 整份写目标(adb 的这一形就是 7.138.x 的追加)。字节流向目标的那一步必须经 `ctx.inPlace`(读旧不包)。
|
|
283
|
+
*/
|
|
284
|
+
export type StagedWriteDirect = (ctx: StagedDirectContext) => Promise<Result<void, FileError>>;
|
|
285
|
+
/**
|
|
286
|
+
* 只能把整份文件落到一个路径上的腿(e2b 的 `files.write`、adb 的 push)在退路里的追加 —— 两腿共用这一只,各自只给读与写:
|
|
287
|
+
* 读旧(`not_found` ⇒ 按空;别的读错原样透出、不写)+ 按字节拼(二进制安全)+ 整份写目标。读旧**不**经 `ctx.inPlace`(取消落在这里 ⇒ `aborted`、目标未碰),
|
|
288
|
+
* 整份写经 `ctx.inPlace`(见 {@link StagedDirectContext})。adb 的这一形就是 7.138.x 的追加。
|
|
289
|
+
*/
|
|
290
|
+
export declare function rewriteAppend(read: (abortSignal?: AbortSignal) => Promise<Result<Uint8Array, FileError>>, write: (bytes: Uint8Array, abortSignal?: AbortSignal) => Promise<Result<void, FileError>>, add: string | Uint8Array, ctx: StagedDirectContext): Promise<Result<void, FileError>>;
|
|
291
|
+
/**
|
|
292
|
+
* 远端整文件替换(ssh / adb / local-docker / k8s / e2b 五条腿调同一只;父目录由准备那一步建)。契约:成功 ⇒ 目标整份是新内容;传输失败 ⇒ 目标整份是旧内容;
|
|
293
|
+
* `aborted` ⇒ 目标未碰。私有目录清不掉 ⇒ 错误消息点名残留的目录。目录不收新名字、或私有目录收不成私有 ⇒ 不暂存,腿的上传直接落目标一次(旧形,core 同一条回落;非原子)。
|
|
294
|
+
* 提交往返本身断了(传输层错误)⇒ 答 `unknown`,消息写明提交跑没跑不可知。
|
|
295
|
+
*/
|
|
296
|
+
export declare function writeFileStaged(lane: StagedWriteLane, abs: string, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
297
|
+
/**
|
|
298
|
+
* 远端追加(五条腿调同一只;`lane.upload` 上传的是**要加的那几个字节**)。暂存那条路的契约:成功 ⇒ 目标 = 旧内容 + 新字节;传输失败 / `aborted` ⇒
|
|
299
|
+
* 目标未碰;已有字节从不被重写、inode 不换。`direct` = 目录不收新名字 / 私有目录收不成私有时腿直接写目标的那一步(必传,见 {@link StagedWriteDirect}),
|
|
300
|
+
* 不在上面的契约里:非原子 —— 中途断 ⇒ 目标可能只加上了一部分(整份写目标的 e2b / adb:已有字节被重写,目标可能没了或只剩半份);取消落在**写目标那一步开始之前**
|
|
301
|
+
* (读旧)⇒ `aborted`、目标未碰;写目标那一步因取消中断 ⇒ `unknown`、目标可能只写了一半,那一步自己的通道准备算在步内(保守,见头注与 {@link StagedDirectContext})。
|
|
302
|
+
*/
|
|
303
|
+
export declare function appendFileStaged(lane: StagedWriteLane, abs: string, abortSignal: AbortSignal | undefined, direct: StagedWriteDirect): Promise<Result<void, FileError>>;
|
|
207
304
|
//# sourceMappingURL=remote-shell.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ExecutionError, SYMLINK_CHAIN_MAX_HOPS } from "@sema-agent/core";
|
|
2
|
-
import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
1
|
+
import { ExecutionError, FileError, SYMLINK_CHAIN_MAX_HOPS } from "@sema-agent/core";
|
|
2
|
+
import { NAME_MAX_BYTES, classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
|
|
3
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
3
4
|
import { randomBytes } from "node:crypto";
|
|
4
5
|
export const EXEC_FORCE_SETTLE_GRACE_MS = 3000;
|
|
5
6
|
export function armPipeDestroyGrace(child) {
|
|
@@ -153,4 +154,167 @@ export async function runSymlinkChain(absPath, run) {
|
|
|
153
154
|
return { ok: false, error: classifyFsStderr(r.value.stderr, absPath, "unknown") };
|
|
154
155
|
return { ok: true, value: { hops: symlinkChainHopsFromStdout(r.value.stdout, absPath) } };
|
|
155
156
|
}
|
|
157
|
+
const STAGED_FILE = "data";
|
|
158
|
+
const STAGE_DIR_REFUSED_EXIT = 3;
|
|
159
|
+
const STAGE_DIR_UNSEALED_EXIT = 4;
|
|
160
|
+
const STAGE_DIR_UNSEALED_MESSAGE = "sema: the private staging directory could not be made private (chmod 700 failed or did not take effect, or the mode or owner read back differently — the filesystem ignores permission bits or the parent directory carries a default ACL); nothing was staged";
|
|
161
|
+
export const STAGE_DIR_LEFT_MARK = "sema: staging directory left behind:";
|
|
162
|
+
export function buildStagingPaths(abs, nonce = randomBytes(6).toString("hex")) {
|
|
163
|
+
const cut = abs.lastIndexOf("/");
|
|
164
|
+
const parent = abs.slice(0, cut + 1);
|
|
165
|
+
const suffix = `.stage-${nonce}`;
|
|
166
|
+
const name = Array.from(abs.slice(cut + 1));
|
|
167
|
+
while (name.length > 0 && Buffer.byteLength(`.${name.join("")}${suffix}`, "utf8") > NAME_MAX_BYTES)
|
|
168
|
+
name.pop();
|
|
169
|
+
const dir = `${parent}.${name.join("")}${suffix}`;
|
|
170
|
+
return { dir, file: `${dir}/${STAGED_FILE}` };
|
|
171
|
+
}
|
|
172
|
+
function privateMkdir(dir) {
|
|
173
|
+
return `(umask 077 && mkdir -- ${dir})`;
|
|
174
|
+
}
|
|
175
|
+
function hardenPrivate(dir) {
|
|
176
|
+
const statOf = (gnu, bsd) => `$(stat -c ${gnu} -- ${dir} 2>/dev/null || stat -f ${bsd} -- ${dir} 2>/dev/null)`;
|
|
177
|
+
return `( chmod -- 700 ${dir} 2>/dev/null && m=${statOf("%a", "%Lp")} && u=${statOf("%u", "%u")} && case "$m" in 700|[1-7]700) [ "$u" = "$(id -u)" ];; *) false;; esac )`;
|
|
178
|
+
}
|
|
179
|
+
function emptyPrivate(dir) {
|
|
180
|
+
return `[ ! -e ${dir}/${STAGED_FILE} ] && [ ! -L ${dir}/${STAGED_FILE} ]`;
|
|
181
|
+
}
|
|
182
|
+
function sealPrivate(dir) {
|
|
183
|
+
return `${hardenPrivate(dir)} && ${emptyPrivate(dir)}`;
|
|
184
|
+
}
|
|
185
|
+
function removalOf(dir) {
|
|
186
|
+
return `{ [ ! -L ${dir} ] && rm -f -- ${dir}/${STAGED_FILE} && { rmdir -- ${dir} 2>/dev/null || [ ! -e ${dir} ]; }; }`;
|
|
187
|
+
}
|
|
188
|
+
function leftMark(dir) {
|
|
189
|
+
return `printf '%s %s\\0' ${shellQuote(STAGE_DIR_LEFT_MARK)} ${dir}`;
|
|
190
|
+
}
|
|
191
|
+
function removePrivate(dir) {
|
|
192
|
+
return `{ ${removalOf(dir)} || ${leftMark(dir)}; }`;
|
|
193
|
+
}
|
|
194
|
+
function abandonPrivate(dir) {
|
|
195
|
+
return `{ rmdir -- ${dir} 2>/dev/null || ${leftMark(dir)}; }`;
|
|
196
|
+
}
|
|
197
|
+
export function buildStagingPrepareCommand(stage) {
|
|
198
|
+
const parent = stage.dir.slice(0, stage.dir.lastIndexOf("/")) || "/";
|
|
199
|
+
const dir = shellQuote(stage.dir);
|
|
200
|
+
return (`{ mkdir -p -- ${shellQuote(parent)} || exit 1; ${privateMkdir(dir)} || exit ${STAGE_DIR_REFUSED_EXIT}; ` +
|
|
201
|
+
`${hardenPrivate(dir)} || { ${abandonPrivate(dir)}; printf '%s\\n' ${shellQuote(STAGE_DIR_UNSEALED_MESSAGE)} >&2; exit ${STAGE_DIR_UNSEALED_EXIT}; }; ` +
|
|
202
|
+
`${emptyPrivate(dir)} || { ${abandonPrivate(dir)}; printf '%s\\n' ${shellQuote("sema: the private staging directory gained an entry before it was made private; not used")} >&2; exit 1; }; }`);
|
|
203
|
+
}
|
|
204
|
+
function takeAndWrite(redirect) {
|
|
205
|
+
return (`[ -r "$s" ] || chmod -- u+r "$s" 2>/dev/null; exec 3< "$s" || exit 1; { [ ! -L "$d" ] && rm -f -- "$s"; } || { ${leftMark(`"$d"`)}; exit 1; }; ` +
|
|
206
|
+
`cat <&3 ${redirect} "$t"; w=$?; exec 3<&-; ${removePrivate(`"$d"`)}; exit $w; `);
|
|
207
|
+
}
|
|
208
|
+
export function buildStagedCommitCommand(stage, target) {
|
|
209
|
+
return (`{ d=${shellQuote(stage.dir)}; s="$d/${STAGED_FILE}"; t=${shellQuote(target)}; ` +
|
|
210
|
+
`if [ -L "$t" ]; then h=$(readlink -n -- "$t" && printf .) || h=; h=\${h%.}; ` +
|
|
211
|
+
`case "$h" in "") ;; /*) ;; *) l="\${t%/*}"; case "$l" in "") h="/$h" ;; */) h="$l$h" ;; *) h="$l/$h" ;; esac ;; esac; ` +
|
|
212
|
+
`if [ -n "$h" ] && [ ! -L "$h" ] && { [ ! -e "$h" ] || [ -f "$h" ]; }; then t="$h"; fi; fi; ` +
|
|
213
|
+
`if [ ! -L "$t" ] && { [ ! -e "$t" ] || [ -f "$t" ]; }; then ` +
|
|
214
|
+
`if [ "\${t%/*}" != "\${d%/*}" ]; then e="\${t%/*}/\${d##*/}"; ` +
|
|
215
|
+
`if { ${privateMkdir(`"$e"`)}; } 2>/dev/null; then ` +
|
|
216
|
+
`if { ${sealPrivate(`"$e"`)}; } 2>/dev/null; then ` +
|
|
217
|
+
`if cat -- "$s" > "$e/${STAGED_FILE}" 2>/dev/null; then o="$d"; d="$e"; s="$e/${STAGED_FILE}"; ${removePrivate(`"$o"`)}; else ${removePrivate(`"$e"`)}; fi; ` +
|
|
218
|
+
`else ${abandonPrivate(`"$e"`)}; fi; fi; fi; ` +
|
|
219
|
+
`if [ -f "$t" ]; then m=$(stat -c %a -- "$t" 2>/dev/null || stat -f %Lp -- "$t" 2>/dev/null) && chmod "$m" "$s" 2>/dev/null; fi; ` +
|
|
220
|
+
`if mv -f -- "$s" "$t" 2>/dev/null; then ${removePrivate(`"$d"`)}; exit 0; fi; fi; ` +
|
|
221
|
+
`${takeAndWrite(">")}}`);
|
|
222
|
+
}
|
|
223
|
+
export function buildStagedAppendCommand(stage, target) {
|
|
224
|
+
return `{ d=${shellQuote(stage.dir)}; s="$d/${STAGED_FILE}"; t=${shellQuote(target)}; ${takeAndWrite(">>")}}`;
|
|
225
|
+
}
|
|
226
|
+
export function buildStagingDiscardCommand(stage) {
|
|
227
|
+
return removalOf(shellQuote(stage.dir));
|
|
228
|
+
}
|
|
229
|
+
async function discardStaged(lane, stage) {
|
|
230
|
+
const r = await lane.run(buildStagingDiscardCommand(stage));
|
|
231
|
+
if (!r.ok)
|
|
232
|
+
return r.error.message;
|
|
233
|
+
return r.value.exitCode === 0 ? undefined : r.value.stderr.trim() || "it holds something this write did not put there, or it was replaced by a link";
|
|
234
|
+
}
|
|
235
|
+
function withResidue(e, stage, why) {
|
|
236
|
+
return why === undefined ? e : new FileError(e.code, `${e.message} (the staging directory ${stage.dir} could not be removed: ${why})`, e.path, e);
|
|
237
|
+
}
|
|
238
|
+
async function landStaged(lane, abs, abortSignal, commitOf, direct) {
|
|
239
|
+
if (abortSignal?.aborted)
|
|
240
|
+
return { ok: false, error: new FileError("aborted", "aborted before the write started; the target was not touched", abs) };
|
|
241
|
+
const stage = buildStagingPaths(abs);
|
|
242
|
+
const prep = await lane.run(buildStagingPrepareCommand(stage), abortSignal);
|
|
243
|
+
if (!prep.ok) {
|
|
244
|
+
const left = await discardStaged(lane, stage);
|
|
245
|
+
return { ok: false, error: withResidue(fileErrorFromExec(prep.error, abs), stage, left) };
|
|
246
|
+
}
|
|
247
|
+
if (prep.value.exitCode !== 0) {
|
|
248
|
+
noteLeftDirs(prep.value.stdout);
|
|
249
|
+
if (abortSignal?.aborted)
|
|
250
|
+
return { ok: false, error: new FileError("aborted", "aborted before the write started; the target was not touched", abs) };
|
|
251
|
+
const e = classifyFsStderr(prep.value.stderr, abs, "unknown");
|
|
252
|
+
if (prep.value.exitCode === STAGE_DIR_REFUSED_EXIT && e.code === "permission_denied")
|
|
253
|
+
return direct(createDirectContext(abs, abortSignal, "the directory does not accept new names"));
|
|
254
|
+
if (prep.value.exitCode === STAGE_DIR_UNSEALED_EXIT) {
|
|
255
|
+
recordFailOpen("server.remote-write.stage-seal-fallback", `${abs}: ${prep.value.stderr.trim()}`);
|
|
256
|
+
return direct(createDirectContext(abs, abortSignal, "the private staging directory could not be made private"));
|
|
257
|
+
}
|
|
258
|
+
return { ok: false, error: e };
|
|
259
|
+
}
|
|
260
|
+
if (abortSignal?.aborted) {
|
|
261
|
+
const left = await discardStaged(lane, stage);
|
|
262
|
+
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the write started; the target was not touched", abs), stage, left) };
|
|
263
|
+
}
|
|
264
|
+
const up = await lane.upload(stage.file, abortSignal);
|
|
265
|
+
if (!up.ok) {
|
|
266
|
+
const left = await discardStaged(lane, stage);
|
|
267
|
+
return { ok: false, error: withResidue(up.error, stage, left) };
|
|
268
|
+
}
|
|
269
|
+
if (abortSignal?.aborted) {
|
|
270
|
+
const left = await discardStaged(lane, stage);
|
|
271
|
+
return { ok: false, error: withResidue(new FileError("aborted", "aborted before the commit; the target was not touched", abs), stage, left) };
|
|
272
|
+
}
|
|
273
|
+
const c = await lane.run(commitOf(stage));
|
|
274
|
+
if (!c.ok) {
|
|
275
|
+
const left = await discardStaged(lane, stage);
|
|
276
|
+
const e = fileErrorFromExec(c.error, abs);
|
|
277
|
+
return { ok: false, error: withResidue(new FileError("unknown", `${e.message} (the commit's round trip failed, so whether it ran is unknown — re-read the target before retrying)`, abs, e), stage, left) };
|
|
278
|
+
}
|
|
279
|
+
noteLeftDirs(c.value.stdout);
|
|
280
|
+
if (c.value.exitCode !== 0) {
|
|
281
|
+
const left = await discardStaged(lane, stage);
|
|
282
|
+
return { ok: false, error: withResidue(classifyFsStderr(c.value.stderr, abs, "unknown"), stage, left) };
|
|
283
|
+
}
|
|
284
|
+
return { ok: true, value: undefined };
|
|
285
|
+
}
|
|
286
|
+
function noteLeftDirs(stdout) {
|
|
287
|
+
const head = `${STAGE_DIR_LEFT_MARK} `;
|
|
288
|
+
for (const rec of stdout.split("\0")) {
|
|
289
|
+
const at = rec.indexOf(head);
|
|
290
|
+
if (at >= 0)
|
|
291
|
+
recordFailOpen("server.remote-write.staging-dir-left", rec.slice(at + head.length));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function createDirectContext(abs, abortSignal, why) {
|
|
295
|
+
return {
|
|
296
|
+
signal: abortSignal,
|
|
297
|
+
inPlace: async (step) => {
|
|
298
|
+
if (abortSignal?.aborted)
|
|
299
|
+
return { ok: false, error: new FileError("aborted", `aborted before the in-place write started (${why}); the target was not touched`, abs) };
|
|
300
|
+
const r = await step(abortSignal);
|
|
301
|
+
if (r.ok || r.error.code !== "aborted")
|
|
302
|
+
return r;
|
|
303
|
+
return { ok: false, error: new FileError("unknown", `cancelled during an in-place write (${why}, so the bytes went straight to the target); the target may be partially written — re-read it before retrying`, abs, r.error) };
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
export async function rewriteAppend(read, write, add, ctx) {
|
|
308
|
+
const existing = await read(ctx.signal);
|
|
309
|
+
if (!existing.ok && existing.error.code !== "not_found")
|
|
310
|
+
return existing;
|
|
311
|
+
const merged = Buffer.concat([existing.ok ? existing.value : new Uint8Array(0), typeof add === "string" ? Buffer.from(add, "utf8") : add]);
|
|
312
|
+
return ctx.inPlace((signal) => write(merged, signal));
|
|
313
|
+
}
|
|
314
|
+
export function writeFileStaged(lane, abs, abortSignal) {
|
|
315
|
+
return landStaged(lane, abs, abortSignal, (stage) => buildStagedCommitCommand(stage, abs), (ctx) => ctx.inPlace((signal) => lane.upload(abs, signal)));
|
|
316
|
+
}
|
|
317
|
+
export function appendFileStaged(lane, abs, abortSignal, direct) {
|
|
318
|
+
return landStaged(lane, abs, abortSignal, (stage) => buildStagedAppendCommand(stage, abs), direct);
|
|
319
|
+
}
|
|
156
320
|
//# sourceMappingURL=remote-shell.js.map
|
|
@@ -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`;
|
|
@@ -404,6 +404,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
404
404
|
* ① `retention_hold` 哨兵行的**锁读** —— `held=1` ⇒ 零删、全 skip 返回;锁持有到 COMMIT;
|
|
405
405
|
* ② **一条**加锁读同时完成「候选 + 活引用重查」(谓词下推 + `FOR UPDATE`,见
|
|
406
406
|
* {@link SqlRetentionStore.sessionCandidates});
|
|
407
|
+
* ②′ 读写序律的写者半:锁住候选行之后,逐只用**本事务的连接、新语句**锁定读一次认领(`lockRootAndReadClaim`);有认领的树整棵跳过;
|
|
407
408
|
* ③ 按 E21 顺序逐表删 → 墓碑 → 审计行(同事务)→ COMMIT。
|
|
408
409
|
*
|
|
409
410
|
* 🔴 活引用重查为什么是**候选查询本身**而不是第二条查询(codex 交叉复审 R1 的两条 high 合并采纳):
|
|
@@ -411,12 +412,14 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
411
412
|
* · `FOR UPDATE` 让这些会话行在本事务期间不可被 `touch()` 拉回视界、也不可被 session-sync 改写属主 ——
|
|
412
413
|
* 旧形(不加锁 + 尾部无条件删 `session_event`)可以在属主被并发改写后,把**别的租户**的历史删掉;
|
|
413
414
|
* · 旧形还在删完 `task_active` **之后**才去复核它,那道复核因此恒真(自己刚把证据删了)—— 一段恒真的
|
|
414
|
-
* "防护"
|
|
415
|
-
*
|
|
416
|
-
* `session_meta`)
|
|
417
|
-
*
|
|
418
|
-
* (
|
|
419
|
-
*
|
|
415
|
+
* "防护"比没有更坏。删除之前的判据是候选查询 + ②′ 那一读,删除后不再假装复核。
|
|
416
|
+
* 与认领的互斥(共同锁序,读写序律两半):
|
|
417
|
+
* · 认领半:`createRun` 在认领事务里**先锁根会话行**(`session_meta … FOR UPDATE`)再插 `task_active`(`run-store-sql.ts` 的 `createRun` 顶注)
|
|
418
|
+
* ⇒ 候选查询锁住会话行之后才来的认领等在那一行上,本事务提交后它锁定读到零行 ⇒ `sessionAbsent`,不建行;
|
|
419
|
+
* · 写者半(②′):候选查询开始时**已经持着**会话行锁、尚未提交的认领 —— 候选查询等它提交后才拿到行锁,而那条 `NOT EXISTS task_active`
|
|
420
|
+
* 读的是语句开始时的快照(PG 读已提交下非目标表按语句快照读),看不见刚提交的认领;②′ 是行锁之后的**新语句**锁定读(PG 新快照、TiDB 当前读),
|
|
421
|
+
* 认领已提交就读得到 ⇒ 这棵树跳过。被锁住的根行上不会再有新认领(认领先锁根行),所以 ②′ 之后到提交之间没有窗。
|
|
422
|
+
* `skipped` 仍由 {@link SqlRetentionStore.countProtectedSessions} 独立测得(非锁定读:PG 新语句看得见这一只,TiDB 读事务快照可能少计它一只 —— 回执的计数口径,不是删除判据)。
|
|
420
423
|
*
|
|
421
424
|
* 活引用的四条腿(与设计稿 §2 的清单对表,并如实标注一条**结构上不存在**的边):
|
|
422
425
|
* · `task_active` —— 活跑 claim(在线协调器的第一道门,同一张表同一条判据);
|
|
@@ -3,6 +3,7 @@ import { TASK_LIST_ITEM_TABLE, TASK_LIST_META_TABLE, taskListKeyFor } from "./ta
|
|
|
3
3
|
import { USER_INPUT_ACCEPTANCE_TABLE } from "./user-input-ledger-sql.js";
|
|
4
4
|
import { ensureIndexes } from "./ensure-index.js";
|
|
5
5
|
import { incarnationFromCell } from "../session-incarnation.js";
|
|
6
|
+
import { lockRootAndReadClaim, rowsOnSqlExec } from "./execution-placement-sql.js";
|
|
6
7
|
export const MANAGED_RETENTION = "managed";
|
|
7
8
|
export const UNMANAGED_RETENTION = "none";
|
|
8
9
|
export function retentionSweepRuns(sweep) {
|
|
@@ -154,8 +155,8 @@ export class SqlRetentionStore {
|
|
|
154
155
|
this.db = db;
|
|
155
156
|
this.opts = opts;
|
|
156
157
|
}
|
|
157
|
-
q(
|
|
158
|
-
return this.db.dialect === "
|
|
158
|
+
q(mysql, pg) {
|
|
159
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
159
160
|
}
|
|
160
161
|
domainWhere(col, domain, pgIndex) {
|
|
161
162
|
return domain === ""
|
|
@@ -163,7 +164,7 @@ export class SqlRetentionStore {
|
|
|
163
164
|
: { sql: this.q(`${col} = ?`, `${col} = $${pgIndex}`), params: [domain] };
|
|
164
165
|
}
|
|
165
166
|
idSet(col, ids, pgIndex) {
|
|
166
|
-
return this.db.dialect === "
|
|
167
|
+
return this.db.dialect === "mysql"
|
|
167
168
|
? { sql: `${col} IN (${ids.map(() => "?").join(",")})`, params: [...ids] }
|
|
168
169
|
: { sql: `${col} = ANY($${pgIndex}::text[])`, params: [ids] };
|
|
169
170
|
}
|
|
@@ -209,7 +210,7 @@ export class SqlRetentionStore {
|
|
|
209
210
|
if (keys.length === 0)
|
|
210
211
|
return 0;
|
|
211
212
|
const now = Date.now();
|
|
212
|
-
if (this.db.dialect === "
|
|
213
|
+
if (this.db.dialect === "mysql") {
|
|
213
214
|
const params = [];
|
|
214
215
|
for (const k of keys)
|
|
215
216
|
params.push(domain, kind, k, now);
|
|
@@ -266,7 +267,7 @@ export class SqlRetentionStore {
|
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
horizonArms(pgFirst) {
|
|
269
|
-
return this.db.dialect === "
|
|
270
|
+
return this.db.dialect === "mysql"
|
|
270
271
|
? `((c.deadline IS NOT NULL AND c.deadline <= ?) OR (c.deadline IS NULL AND c.created_at_ms <= ?))`
|
|
271
272
|
: `((c.deadline IS NOT NULL AND c.deadline <= $${pgFirst}) OR (c.deadline IS NULL AND c.created_at_ms <= $${pgFirst + 1}))`;
|
|
272
273
|
}
|
|
@@ -286,7 +287,7 @@ export class SqlRetentionStore {
|
|
|
286
287
|
return Number(rows[0]?.n ?? 0);
|
|
287
288
|
}
|
|
288
289
|
liveReferenceExists() {
|
|
289
|
-
const workflow = this.db.dialect === "
|
|
290
|
+
const workflow = this.db.dialect === "mysql"
|
|
290
291
|
? `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND JSON_UNQUOTE(JSON_EXTRACT(wr.run, '$.originatingSessionId')) = sm.session_id)`
|
|
291
292
|
: `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND wr.run::json->>'originatingSessionId' = sm.session_id)`;
|
|
292
293
|
return (`(sm.placement_kind IS NOT NULL` +
|
|
@@ -336,7 +337,12 @@ export class SqlRetentionStore {
|
|
|
336
337
|
skippedOnAbort = (await this.sessionCandidates(conn, input)).length + (await this.countProtectedSessions(conn, input));
|
|
337
338
|
throw new RetentionAborted("hold_in_force");
|
|
338
339
|
}
|
|
339
|
-
const
|
|
340
|
+
const candidates = await this.sessionCandidates(conn, input, true);
|
|
341
|
+
const rowsOn = rowsOnSqlExec(conn);
|
|
342
|
+
const doomed = [];
|
|
343
|
+
for (const c of candidates)
|
|
344
|
+
if ((await lockRootAndReadClaim(rowsOn, this.db.dialect, c.id)).activeTaskId === null)
|
|
345
|
+
doomed.push(c);
|
|
340
346
|
const skipped = await this.countProtectedSessions(conn, input);
|
|
341
347
|
skippedOnAbort = doomed.length + skipped;
|
|
342
348
|
let deleted = 0;
|
|
@@ -42,6 +42,14 @@ export declare class SqlRunStore {
|
|
|
42
42
|
* `task_id` ⇒ `{ ok:false, existing }` (no new row; the submission surface renders the receipt); otherwise the
|
|
43
43
|
* session claim was the collision ⇒ `{ ok:false, activeTaskId }`. A dup on the ROW insert that names no
|
|
44
44
|
* observable row is not attributable to either key ⇒ rethrown (loud), never reported as a busy session.
|
|
45
|
+
*
|
|
46
|
+
* 🔴 **根会话行锁(读写序律)**:认领之前,同一事务先锁根会话行(`session_meta … FOR UPDATE`,两方言同一句)。改根行住客 / 放置的写者
|
|
47
|
+
* (删除 / 换装 / 留存 …)也在自己的事务里先锁这一行 ⇒ 认领与它们在根行上按同一锁序串行:写者持锁期间认领等锁,写者提交后认领读到的是新行。
|
|
48
|
+
* **锁定读零行**(根行在受理之后被删)⇒ 不插认领、不建行,答 `{ ok:false, sessionAbsent:true }` —— 否则认领会挂在一只没有会话行的根上,
|
|
49
|
+
* 随后同 id 登记的新住客就带着别人的认领出生。全局锁序:根 `session_meta` → `task_active` / `task_run`(删除 / 留存同序;认领持有者的
|
|
50
|
+
* `whileHoldingClaim` 只取 `task_run` → `task_active`,从不碰 `session_meta` —— 它的回调**不许**写 `session_meta`:回调持 `task_active`
|
|
51
|
+
* 等根行、认领持根行等 `task_active`,即成环;初始放置写走自己的事务、按全局锁序取锁)。fresh 根行由 authorizer 在受理时登记(唯一铸点即登记点),
|
|
52
|
+
* 续跑与 wake 复用 checkpoint 的会话 —— 所以零行只在「受理之后被删」这一种竞争里出现。
|
|
45
53
|
*/
|
|
46
54
|
createRun(taskId: string, sessionId: string, owner: string | null, instanceId: string, meta?: NewRunMeta): Promise<CreateRunOutcome>;
|
|
47
55
|
/** Request cancel of a RUNNING run (durable, cross-replica). Sets the flag the owning instance's heartbeat
|
|
@@ -165,10 +173,12 @@ export declare class SqlRunStore {
|
|
|
165
173
|
* ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
|
|
166
174
|
* ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑;提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
|
|
167
175
|
* 🔴 **取锁序 = task_run → task_active**,与 `setTerminal` / `reapStale` / `reapSuspended` 同序(它们先改 run 行、再删认领行):两行用两条语句按序锁,
|
|
168
|
-
* 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun`
|
|
169
|
-
*
|
|
176
|
+
* 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun` 先锁根会话行再插认领行(本序从不碰 `session_meta`)、
|
|
177
|
+
* `deleteBySession` 先锁根会话行、见认领行在就中止,都不与本序成环。锁住之后:收割的改行 / 终局与收割的删认领 / 别人的建行都排在本事务之后,收割器提交后重判 `updated_at` 看到的是刚刷的。
|
|
170
178
|
* 锁读没命中时 InnoDB 可能取一把间隙锁;本事务此后不往这两张表插任何行(见 space-commit 那条锁形注)。
|
|
171
179
|
* 担保止于提交:判出「持有」的是这只事务;提交确认在路上迟到、这一跑随后失去认领,不在本栅栏的射程里(调用方的已知边界)。
|
|
180
|
+
* 🔴 **`write` 不许写 `session_meta`**:本事务持着 `task_active`,而 `createRun` 与改根行的写者都先锁根会话行再碰 `task_*`(全局锁序见 {@link createRun})——
|
|
181
|
+
* 回调去锁根行就是反序,与等着本会话认领的 `createRun` 各攥一行成环。门:`test/submission-root-contract.test.ts` 扫每个调用点的回调体。
|
|
172
182
|
*/
|
|
173
183
|
whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn) => Promise<T>): Promise<ClaimFenced<T>>;
|
|
174
184
|
/** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
|
|
@@ -187,7 +197,7 @@ export declare class SqlRunStore {
|
|
|
187
197
|
* `uq_task_run_idem` makes it at most one row. */
|
|
188
198
|
getRunByIdem(idemKey: string): Promise<RunRecord | undefined>;
|
|
189
199
|
/** Undo THIS submission's binding (`idem_key` + `idem_body_fingerprint` → NULL) — called only where the submit surface refuses
|
|
190
|
-
* the submission before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal,
|
|
200
|
+
* the submission before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, an execution-placement
|
|
191
201
|
* reject): the error answer invites a retry, and a retry with the same key must run, not replay the refusal. The row stays (it
|
|
192
202
|
* is the refusal's record); only the binding is freed. */
|
|
193
203
|
releaseIdemKey(taskId: string): Promise<void>;
|
|
@@ -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);
|
|
@@ -81,6 +81,11 @@ export class SqlRunStore {
|
|
|
81
81
|
let stage = "claim";
|
|
82
82
|
try {
|
|
83
83
|
await conn.begin();
|
|
84
|
+
const { rows: root } = await conn.query(this.q("SELECT session_id FROM session_meta WHERE session_id = ? FOR UPDATE", "SELECT session_id FROM session_meta WHERE session_id = $1 FOR UPDATE"), [sessionId]);
|
|
85
|
+
if (root.length === 0) {
|
|
86
|
+
await conn.rollback();
|
|
87
|
+
return { ok: false, sessionAbsent: true };
|
|
88
|
+
}
|
|
84
89
|
const now = new Date();
|
|
85
90
|
try {
|
|
86
91
|
await conn.query(this.q("INSERT INTO task_active (session_id, task_id, since) VALUES (?,?,?)", "INSERT INTO task_active (session_id, task_id, since) VALUES ($1,$2,$3)"), [sessionId, taskId, now]);
|
|
@@ -98,7 +103,7 @@ export class SqlRunStore {
|
|
|
98
103
|
cells.identitySource,
|
|
99
104
|
cells.identityScope,
|
|
100
105
|
cells.identityPrincipalInc,
|
|
101
|
-
this.db.dialect === "
|
|
106
|
+
this.db.dialect === "mysql"
|
|
102
107
|
? meta.objectivePreview ?? null
|
|
103
108
|
: meta.objectivePreview == null
|
|
104
109
|
? null
|
|
@@ -176,8 +181,8 @@ export class SqlRunStore {
|
|
|
176
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')"), [
|
|
177
182
|
status,
|
|
178
183
|
result == null ? null : this.json(result),
|
|
179
|
-
this.db.dialect === "
|
|
180
|
-
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),
|
|
181
186
|
new Date(),
|
|
182
187
|
taskId,
|
|
183
188
|
]);
|
|
@@ -248,7 +253,7 @@ export class SqlRunStore {
|
|
|
248
253
|
}
|
|
249
254
|
async claimSpaceCommitIn(conn, spaceKey, taskId, state, claim) {
|
|
250
255
|
if (!claim.requireOwn) {
|
|
251
|
-
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]);
|
|
252
257
|
}
|
|
253
258
|
const row = await this.readSpaceCommitIn(conn, spaceKey, true);
|
|
254
259
|
switch (decideSpaceCommitClaim(row, taskId, claim)) {
|
|
@@ -257,7 +262,7 @@ export class SqlRunStore {
|
|
|
257
262
|
case "insert":
|
|
258
263
|
throw new Error("workspace_space_commit: a space's row vanished under this transaction's own lock");
|
|
259
264
|
case "update": {
|
|
260
|
-
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]);
|
|
261
266
|
return w.affected > 0;
|
|
262
267
|
}
|
|
263
268
|
}
|
|
@@ -313,20 +318,20 @@ export class SqlRunStore {
|
|
|
313
318
|
};
|
|
314
319
|
}
|
|
315
320
|
async getRun(taskId) {
|
|
316
|
-
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("
|
|
321
|
+
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]);
|
|
317
322
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
318
323
|
}
|
|
319
324
|
async observeSubmittedRow(conn, taskId, idemKey) {
|
|
320
325
|
if (idemKey !== null) {
|
|
321
|
-
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("
|
|
326
|
+
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]);
|
|
322
327
|
if (rows[0])
|
|
323
328
|
return this.rowToRun(rows[0]);
|
|
324
329
|
}
|
|
325
|
-
const { rows } = await conn.query(this.q(`SELECT ${runRowCols("
|
|
330
|
+
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]);
|
|
326
331
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
327
332
|
}
|
|
328
333
|
async getRunByIdem(idemKey) {
|
|
329
|
-
const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("
|
|
334
|
+
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]);
|
|
330
335
|
return rows[0] ? this.rowToRun(rows[0]) : undefined;
|
|
331
336
|
}
|
|
332
337
|
async releaseIdemKey(taskId) {
|
|
@@ -341,7 +346,7 @@ export class SqlRunStore {
|
|
|
341
346
|
async listRuns(opts) {
|
|
342
347
|
if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
|
|
343
348
|
return [];
|
|
344
|
-
if (this.db.dialect === "
|
|
349
|
+
if (this.db.dialect === "mysql") {
|
|
345
350
|
const where = [];
|
|
346
351
|
const params = [];
|
|
347
352
|
if (opts.status) {
|
|
@@ -367,7 +372,7 @@ export class SqlRunStore {
|
|
|
367
372
|
where.push("(created_at < ? OR (created_at = ? AND task_id < ?))");
|
|
368
373
|
params.push(new Date(opts.cursor.createdAt), new Date(opts.cursor.createdAt), opts.cursor.taskId);
|
|
369
374
|
}
|
|
370
|
-
const sql = `SELECT ${runRowCols("
|
|
375
|
+
const sql = `SELECT ${runRowCols("mysql")} FROM task_run` +
|
|
371
376
|
(where.length ? " WHERE " + where.join(" AND ") : "") +
|
|
372
377
|
" ORDER BY created_at DESC, task_id DESC LIMIT ?";
|
|
373
378
|
params.push(opts.limit);
|
|
@@ -408,7 +413,7 @@ export class SqlRunStore {
|
|
|
408
413
|
return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
|
|
409
414
|
}
|
|
410
415
|
async listSessions(opts) {
|
|
411
|
-
if (this.db.dialect === "
|
|
416
|
+
if (this.db.dialect === "mysql") {
|
|
412
417
|
const params = [];
|
|
413
418
|
let innerWhere = "";
|
|
414
419
|
if (opts.owner !== undefined) {
|
|
@@ -528,7 +533,7 @@ export class SqlRunStore {
|
|
|
528
533
|
return m == null ? 0 : Number(m);
|
|
529
534
|
}
|
|
530
535
|
async getEvents(taskId, afterSeq) {
|
|
531
|
-
const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("
|
|
536
|
+
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]);
|
|
532
537
|
return rows.map((r) => ({
|
|
533
538
|
seq: Number(r.seq),
|
|
534
539
|
type: String(r.type),
|