@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -1,65 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable **leader-run** registry — SINGLE-FILE DUAL-DIALECT (design/158 A12 定型半场;先例 =
|
|
3
|
-
* `image-bake-store-sql.ts`)。#193 车4 件1(台账 P1-9;设计稿 `docs/DESIGN-193-CAR4-leader-failclosed.md`)。
|
|
4
|
-
*
|
|
5
|
-
* ── 为什么这张表必须存在 ────────────────────────────────────────────────────────────────────────────
|
|
6
|
-
* 一次 leader run = 规划成 N 个子任务 → 起 N 个 worker 沙箱 → 真模型编码 → 合并 → **真 git push**,
|
|
7
|
-
* 最长 24h。它此前的登记表是 `leader/endpoint.ts` 里的一只进程内 `Map`,于是三条缺口同源:
|
|
8
|
-
* ① 跨副本 GET 404(提交打到 A、轮询打到 B),违反仓级前提「durable state lives in an external SQL store」;
|
|
9
|
-
* ② POST 零幂等 —— 每次都铸新 uuidv7,一次网络重试 = 第二次真跑 + 可能第二次 push;
|
|
10
|
-
* ③ `fanout.ts` 的 `claim` 面全链路无人传,而它的头注称并发去重「已解决」。
|
|
11
|
-
* 一张带 `idem_key UNIQUE` 的表一次解掉三条:①靠行本身,②靠 UNIQUE 的「原子赢或观察」,
|
|
12
|
-
* ③ 的真属主就是 ② —— 提交幂等挡在**准入**那一刻,比在 worker 层再补一道 CAS 早得多也准得多
|
|
13
|
-
* (worker 的 sessionId 是每次现铸的 `leader-<workerId>-<Date.now()>`,拿它做 CAS 键**永远不会**冲突,
|
|
14
|
-
* 那道 claim 由构造即无效;详见同批对 `fanout.ts` 头注的更正)。
|
|
15
|
-
*
|
|
16
|
-
* ── 这是一张 DEDICATED 表,不是 `task_run` 的复用 ────────────────────────────────────────────────────
|
|
17
|
-
* 与 bake 店同一条理由:`task_run` 与 `task_active`/run reaper/`GET /v1/runs` 列表 FK 式耦合,把 leader run
|
|
18
|
-
* (无 session、无 spend 账、无 checkpoint)混进去 = 让非任务行漏进 reapStale/listRuns 的类别错误。
|
|
19
|
-
*
|
|
20
|
-
* ── 两条保留/清算裁定(分开的两件事,别混)────────────────────────────────────────────────────────
|
|
21
|
-
* **① 行的保留:不建 TTL 腿。** 进程内 Map 时代那条 1h 的 `RUN_RETENTION_MS` 驱逐,目的是**给进程内存
|
|
22
|
-
* 封顶**,不是保留策略。落库后这个理由消失:一行 leader run 对应一次「N 个沙箱 + 数分钟到 24h + 一次真
|
|
23
|
-
* push」的动作,它就是这次动作的审计记录;行的产生速率与它的价值都不支持自动清账。要清账时按
|
|
24
|
-
* workflow_run/roster 的先例加一个 `*_RETENTION_MS` 旋钮 + reaper 腿,那是独立一件。
|
|
25
|
-
*
|
|
26
|
-
* **② 陈旧 `running` 的清算:建了**({@link SqlLeaderRunStore.reapStaleRunning},旋钮 `LEADER_RUN_STALE_MS`,
|
|
27
|
-
* 腿在 `boot/reapers.ts`)。这条与 ① 无关 —— 它不是为了控表大小,而是因为准入与驱动是两步、驱动腿只活在
|
|
28
|
-
* 受理它的那个进程里:副本一死,行就永远停在 `running`,跨副本读面永远显示「在跑」而没有任何东西在跑。
|
|
29
|
-
* 那是**静默**,不是保留问题。(本裁定是 codex 对抗复审 R1-high② 逼出来的:初版把两件事合成一件、一起
|
|
30
|
-
* 判了「不建」,而第二件的论据完全不同。)
|
|
31
|
-
*
|
|
32
|
-
* ── 方言差异台账(显式,永不藏进抽象;A12 判据)────────────────────────────────────────────────────
|
|
33
|
-
* - `?` 占位符 vs `$n` - `CAST(? AS JSON)` vs `$n::jsonb`
|
|
34
|
-
* - 表级 `COLLATE utf8mb4_bin` vs 逐列 `COLLATE "C"`(PG 无表级形;`isolation-key-collation` 门执法)
|
|
35
|
-
* - dup-key 判别走 `sql-errors.ts` 单一属主(`isDupKeyError`),本店只有一根 UNIQUE ⇒ 无需归属判别
|
|
36
|
-
* - schema 属主:MySQL DDL 由本文件导出、展开进 `tidb-pool.ts` 的 SCHEMA_STATEMENTS(跟着中央
|
|
37
|
-
* named-lock 的那条 conn 建);PG DDL 由本文件的 `ensurePgLeaderRunSchema` 导出、由 `pg-pool.ts`
|
|
38
|
-
* 的中央 `ensurePgSchema` 组合(与 approval-ask 同姿势)。
|
|
39
|
-
*/
|
|
40
1
|
import { uuidv7 } from "@sema-agent/core";
|
|
41
2
|
import { createHash } from "node:crypto";
|
|
42
3
|
import { isDupKeyError } from "./sql-errors.js";
|
|
43
4
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
44
5
|
import { parseJsonOr } from "./sql-row-helpers.js";
|
|
45
|
-
/** 表名(#192 单数纪律;`schema-naming-invariants` 的闭集词表执法)。 */
|
|
46
6
|
export const LEADER_RUN_TABLE = "leader_run";
|
|
47
7
|
const LEADER_RUN_STATUSES = new Set(["running", "completed", "failed", "needs_human"]);
|
|
48
|
-
/** `objective_preview` 的字符上限,与 DDL 的 VARCHAR(160) 同源(`task_run.objective_preview` 先例同宽)。 */
|
|
49
8
|
export const LEADER_OBJECTIVE_PREVIEW_CHARS = 160;
|
|
50
|
-
/**
|
|
51
|
-
* 幂等键的**落库形** = `sha256(scope \x1f raw)` 的 hex(64 字符,定宽)。
|
|
52
|
-
*
|
|
53
|
-
* 为什么 hash 而不是像 bake 店那样存 scope 过的明文:明文形的列宽(VARCHAR(255))与「principal(≤190)
|
|
54
|
-
* + 调用方自选的 raw 键(≤255)」的上界对不上 —— 溢出时 PG 报 `value too long`、非严格 MySQL **静默截断**,
|
|
55
|
-
* 而截断一个去重键的后果是**两个不同的请求折叠成同一次 leader run**(= 一次该跑的 run 被静默吞掉)。
|
|
56
|
-
* 定宽 hash 让这条路径按构造不存在;顺带,库里也不再留调用方的原始键字面量。
|
|
57
|
-
* (碰撞面:sha256,不在工程风险量级。)
|
|
58
|
-
*/
|
|
59
9
|
export function leaderIdemKey(raw, owner) {
|
|
60
10
|
return createHash("sha256").update(`${owner ?? ""}\x1f${raw}`).digest("hex");
|
|
61
11
|
}
|
|
62
|
-
/** MySQL 协议方言的建表语句(真源;由 `tidb-pool.ts` 展开进中央 SCHEMA_STATEMENTS)。 */
|
|
63
12
|
export const TIDB_LEADER_RUN_STATEMENTS = [
|
|
64
13
|
`CREATE TABLE IF NOT EXISTS ${LEADER_RUN_TABLE} (
|
|
65
14
|
leader_run_id VARCHAR(64) NOT NULL,
|
|
@@ -83,7 +32,6 @@ export const TIDB_LEADER_RUN_STATEMENTS = [
|
|
|
83
32
|
KEY idx_leader_run_started (started_at_ms)
|
|
84
33
|
) COLLATE utf8mb4_bin`,
|
|
85
34
|
];
|
|
86
|
-
/** PG 方言的建表语句(MySQL 孪生的逐条翻译:JSON→JSONB,逐列 `COLLATE "C"`,内联 KEY→独立 CREATE INDEX)。 */
|
|
87
35
|
export const PG_LEADER_RUN_SCHEMA = [
|
|
88
36
|
`CREATE TABLE IF NOT EXISTS ${LEADER_RUN_TABLE} (
|
|
89
37
|
leader_run_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -101,16 +49,13 @@ export const PG_LEADER_RUN_SCHEMA = [
|
|
|
101
49
|
`CREATE INDEX IF NOT EXISTS idx_leader_run_owner ON ${LEADER_RUN_TABLE} (owner)`,
|
|
102
50
|
`CREATE INDEX IF NOT EXISTS idx_leader_run_started ON ${LEADER_RUN_TABLE} (started_at_ms)`,
|
|
103
51
|
];
|
|
104
|
-
/** 幂等的 PG schema 应用(中央 `ensurePgSchema` 组合它;集成套件也直接调)。 */
|
|
105
52
|
export async function ensurePgLeaderRunSchema(q) {
|
|
106
53
|
for (const stmt of PG_LEADER_RUN_SCHEMA)
|
|
107
54
|
await q(stmt);
|
|
108
55
|
}
|
|
109
|
-
/** 清算腿写进 `error` 列的 WHY(消费方读到的就是这句;不是一个空的 failed)。 */
|
|
110
56
|
export const STALE_SWEEP_REASON = "leader run went stale: no replica settled it within LEADER_RUN_STALE_MS (the replica driving it most likely died). " +
|
|
111
57
|
"The run was NOT taken over — re-submit with a NEW Idempotency-Key to run it again.";
|
|
112
58
|
const SELECT_COLS = "leader_run_id, owner, objective_preview, status, result, error, started_at_ms, finished_at_ms";
|
|
113
|
-
/** 未知 status 字面量 = 库里躺着一条本进程不认识的行 ⇒ **响亮拒**,不静默折成 failed(闭集词表纪律 #157)。 */
|
|
114
59
|
function statusOf(raw, id) {
|
|
115
60
|
const s = String(raw);
|
|
116
61
|
if (!LEADER_RUN_STATUSES.has(s)) {
|
|
@@ -134,24 +79,14 @@ function mapRow(r) {
|
|
|
134
79
|
...(finished === undefined ? {} : { finishedAtMs: finished }),
|
|
135
80
|
};
|
|
136
81
|
}
|
|
137
|
-
/** 双方言 leader-run 店。方言差异台账见文件头注。 */
|
|
138
82
|
export class SqlLeaderRunStore {
|
|
139
83
|
db;
|
|
140
84
|
constructor(db) {
|
|
141
85
|
this.db = db;
|
|
142
86
|
}
|
|
143
|
-
/** 挑本方言的 SQL 文本。两条语句**都写在调用点**(A12 判据:读者一眼看到两份)。 */
|
|
144
87
|
q(tidb, pg) {
|
|
145
88
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
146
89
|
}
|
|
147
|
-
/**
|
|
148
|
-
* 准入一次 leader run(状态 `running`)。提交幂等照 `createBake`/`createRun` 的**原子赢或观察**:
|
|
149
|
-
* `idem_key` 是 UNIQUE,重投的那一方 INSERT 撞 dup-key ⇒ 回读**既有行**返回 `{created:false}`,
|
|
150
|
-
* 调用方据此**不再**起第二条后台腿。`idemKey=null` 恒插入(没要求去重)。
|
|
151
|
-
*
|
|
152
|
-
* 🔴 为什么不是「先 SELECT 再 INSERT」:两个副本的 check-then-act 会双双看到「没有」然后双双插入 ——
|
|
153
|
-
* 而这条路径的第二次插入意味着**第二次真 git push**。UNIQUE 是唯一能在两个进程之间成立的判据。
|
|
154
|
-
*/
|
|
155
90
|
async createRun(input) {
|
|
156
91
|
const id = uuidv7();
|
|
157
92
|
const now = Date.now();
|
|
@@ -163,8 +98,6 @@ export class SqlLeaderRunStore {
|
|
|
163
98
|
catch (err) {
|
|
164
99
|
if (isDupKeyError(this.db.dialect, err) && input.idemKey) {
|
|
165
100
|
const existing = await this.getByIdem(input.idemKey);
|
|
166
|
-
// 回读为空 = 赢家的行在这两步之间消失了(本店无删除口 ⇒ 只可能是外部 DELETE)。原样上抛而不是
|
|
167
|
-
// 悄悄再插一次:后者会在「运维正在手工清行」的窗口里放出第二次真 push。
|
|
168
101
|
if (existing)
|
|
169
102
|
return { created: false, run: existing };
|
|
170
103
|
}
|
|
@@ -175,69 +108,35 @@ export class SqlLeaderRunStore {
|
|
|
175
108
|
throw new Error(`createRun: leader_run ${id} vanished right after insert`);
|
|
176
109
|
return { created: true, run };
|
|
177
110
|
}
|
|
178
|
-
/** 按 id 读一行(跨副本可见性的全部依据)。不存在 ⇒ undefined。 */
|
|
179
111
|
async getRun(id) {
|
|
180
112
|
const res = await this.db.query(this.q(`SELECT ${SELECT_COLS} FROM ${LEADER_RUN_TABLE} WHERE leader_run_id = ?`, `SELECT ${SELECT_COLS} FROM ${LEADER_RUN_TABLE} WHERE leader_run_id = $1`), [id]);
|
|
181
113
|
const row = res.rows[0];
|
|
182
114
|
return row ? mapRow(row) : undefined;
|
|
183
115
|
}
|
|
184
|
-
/** 按幂等键读回既有行(dup-key 观察臂;也给集成套件直查)。 */
|
|
185
116
|
async getByIdem(idemKey) {
|
|
186
117
|
const res = await this.db.query(this.q(`SELECT ${SELECT_COLS} FROM ${LEADER_RUN_TABLE} WHERE idem_key = ?`, `SELECT ${SELECT_COLS} FROM ${LEADER_RUN_TABLE} WHERE idem_key = $1`), [idemKey]);
|
|
187
118
|
const row = res.rows[0];
|
|
188
119
|
return row ? mapRow(row) : undefined;
|
|
189
120
|
}
|
|
190
|
-
/**
|
|
191
|
-
* 陈旧 `running` 清算(codex 对抗复审 R1-high②,验真后修)。
|
|
192
|
-
*
|
|
193
|
-
* 病:准入(INSERT)与驱动(后台腿)是**两步**,而驱动腿只活在受理它的那个进程里。副本在这两步之间
|
|
194
|
-
* 挂掉、或在一条 24h 的 run 中途挂掉,行就永远停在 `running`:跨副本读面永远显示「在跑」,而没有任何
|
|
195
|
-
* 东西在跑;更坏的是那把幂等键被永久绑在这条无人驱动的行上,重投拿回的还是它。
|
|
196
|
-
*
|
|
197
|
-
* 处置 = **有界地说实话**,不是自动接管:把超期的 `running` 迁成 `failed` 并写清 WHY。
|
|
198
|
-
* 🔴 刻意**不做**跨副本接管(claim/lease/queued 状态机):接管一条产物是「真 git push」的 24h run
|
|
199
|
-
* 是一个独立的设计决定(要持久化完整可恢复请求、要 fencing、要想清楚两个副本同时以为自己是属主时
|
|
200
|
-
* 谁去 push),不是一次清算腿能顺手带过的。本腿把「永远 running」这个**静默**面消灭掉,把接管留给
|
|
201
|
-
* 一件具名的后续设计;当前语义写在这里,不藏着。
|
|
202
|
-
*
|
|
203
|
-
* 幂等键的后果同批写明:键**永久绑定**它当初准入的那条 run(与 `/v1/runs` 的 taskId 幂等同语义)。
|
|
204
|
-
* 一条被清算成 `failed` 的 run,用**同一个键**重投拿回的仍是那条 failed —— 调用方要重跑就换一个新键。
|
|
205
|
-
*
|
|
206
|
-
* @param staleMs 超过这个时长仍是 `running` 就算陈旧(部署旋钮 `LEADER_RUN_STALE_MS`)。
|
|
207
|
-
* @param now 注入时钟(测试要确定性)。
|
|
208
|
-
* @returns 本次迁走的行数。
|
|
209
|
-
*/
|
|
210
121
|
async reapStaleRunning(staleMs, now) {
|
|
211
122
|
const cutoff = now - staleMs;
|
|
212
123
|
const res = await this.db.query(this.q(`UPDATE ${LEADER_RUN_TABLE} SET status = 'failed', error = ?, finished_at_ms = ? WHERE status = 'running' AND started_at_ms < ?`, `UPDATE ${LEADER_RUN_TABLE} SET status = 'failed', error = $1, finished_at_ms = $2 WHERE status = 'running' AND started_at_ms < $3`), [STALE_SWEEP_REASON, now, cutoff]);
|
|
213
124
|
return res.affected;
|
|
214
125
|
}
|
|
215
|
-
/**
|
|
216
|
-
* TEST-ONLY:把一行的 `started_at_ms` 推到过去,好让 {@link reapStaleRunning} 的判据在一次测试里成立。
|
|
217
|
-
* 生产零调用(`started_at_ms` 只在准入时写一次)——具名 `ForTest` 是为了让这件事在 grep 里一眼可见,
|
|
218
|
-
* 而不是长成一个看起来像正经写口的方法(本仓 `setStatus` 那条欠账的教训)。
|
|
219
|
-
*/
|
|
220
126
|
async backdateStartedAtForTest(id, startedAtMs) {
|
|
221
127
|
await this.db.query(this.q(`UPDATE ${LEADER_RUN_TABLE} SET started_at_ms = ? WHERE leader_run_id = ?`, `UPDATE ${LEADER_RUN_TABLE} SET started_at_ms = $1 WHERE leader_run_id = $2`), [startedAtMs, id]);
|
|
222
128
|
}
|
|
223
|
-
/**
|
|
224
|
-
* 落终局。**只从 `running` 迁**(`WHERE status = 'running'`):终局是一次性的,一条已经结算过的 run
|
|
225
|
-
* 不该被后来的写者改写。回执 = 是否真的迁了,调用方据此决定要不要出声(静默丢一次终局写 = 用户永远
|
|
226
|
-
* 看到 `running`)。
|
|
227
|
-
*/
|
|
228
129
|
async finishRun(id, patch) {
|
|
229
130
|
const json = dialectJsonEncoder(this.db.dialect);
|
|
230
131
|
const res = await this.db.query(this.q(`UPDATE ${LEADER_RUN_TABLE} SET status = ?, result = CAST(? AS JSON), error = ?, finished_at_ms = ? WHERE leader_run_id = ? AND status = 'running'`, `UPDATE ${LEADER_RUN_TABLE} SET status = $1, result = $2::jsonb, error = $3, finished_at_ms = $4 WHERE leader_run_id = $5 AND status = 'running'`), [patch.status, patch.result === undefined ? null : json(patch.result), patch.error ?? null, Date.now(), id]);
|
|
231
132
|
return res.affected > 0;
|
|
232
133
|
}
|
|
233
134
|
}
|
|
234
|
-
/** MySQL-协议孪生(TiDB / MySQL / MariaDB 同一只 mysql2 池)。 */
|
|
235
135
|
export class TiDBLeaderRunStore extends SqlLeaderRunStore {
|
|
236
136
|
constructor(pool) {
|
|
237
137
|
super(mysqlDriver(pool));
|
|
238
138
|
}
|
|
239
139
|
}
|
|
240
|
-
/** PostgreSQL 孪生。 */
|
|
241
140
|
export class PgLeaderRunStore extends SqlLeaderRunStore {
|
|
242
141
|
constructor(pool) {
|
|
243
142
|
super(pgDriver(pool));
|
|
@@ -1,70 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LOCAL (file-backed) checkpoint store — EnterPlanMode / durable HITL for the TOC local lane.
|
|
3
|
-
*
|
|
4
|
-
* Shape: core's `FileCheckpointStore` (crash-safe ledger+snapshot, per-token CAS — the ENGINE contract) is the
|
|
5
|
-
* inner authority; this wrapper adds the SERVICE half the SQL twins carry (operator queue / session lookups /
|
|
6
|
-
* resume ctx / reapers) WITHOUT re-implementing any core abstraction ([[core-service-boundary]]: engine contract
|
|
7
|
-
* stays core's; wire/ops glue is ours).
|
|
8
|
-
*
|
|
9
|
-
* Enumeration seam: core's File store exposes only `listByScope(scope)` (pending-only summaries) + `get(token)`.
|
|
10
|
-
* Cross-scope queries (fleet listPending / reapExpired / session lookups) therefore ride a service-side SCOPES
|
|
11
|
-
* REGISTRY: `put` records each scope once into `checkpoint-scopes.json` (registered BEFORE the inner put so a
|
|
12
|
-
* crash can't leave an unenumerable pending row; a scope whose rows are gone is harmless — its listByScope is
|
|
13
|
-
* simply empty). Projection fields come from `get(token)` (the full Checkpoint), so listPending serves the same
|
|
14
|
-
* non-secret card as the SQL twins (shared `boundedToolInput` bound, same severity-DESC sort).
|
|
15
|
-
*
|
|
16
|
-
* Honest degrades (documented, single-user local lane):
|
|
17
|
-
* - `deleteBySession` EXPIRES the session's pending gates + unlinks its ctx file — the ledger/snapshot keeps
|
|
18
|
-
* terminal history like the local tool-result/transcript stores do (no physical row delete primitive in
|
|
19
|
-
* core's File store; the E21 functional contract — "a deleted session has no resumable future" — holds).
|
|
20
|
-
* - `taskId` on the pending card comes from an injected `getActiveTaskId` (the local FileRunStore) — `null`
|
|
21
|
-
* when not wired, which every consumer already tolerates.
|
|
22
|
-
*/
|
|
23
1
|
import { createHash } from "node:crypto";
|
|
24
2
|
import { existsSync, readFileSync, readdirSync, renameSync, unlinkSync, mkdirSync } from "node:fs";
|
|
25
3
|
import { join } from "node:path";
|
|
26
|
-
import { isApprovalGateKind } from "../tool-approval.js";
|
|
4
|
+
import { isApprovalGateKind } from "../tool-approval.js";
|
|
27
5
|
import { FileCheckpointStore, atomicWriteFile, sanitizePathComponent, } from "@sema-agent/core";
|
|
28
6
|
import { boundedRuleSuggestions, boundedToolInput, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
|
|
29
7
|
import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
30
|
-
/** design/80 D-D read-time twin of the TiDB put-time `terminal_at_ms` column (same formula — anti-drift). */
|
|
31
8
|
function terminalAtOf(cp) {
|
|
32
9
|
return Math.max(cp.createdAt + TERMINAL_BACKSTOP_MS, (cp.deadline ?? 0) + TERMINAL_GRACE_MS);
|
|
33
10
|
}
|
|
34
|
-
/** Collision-proof ctx filename: readable prefix + content hash of the FULL sessionId (sanitize can collide). */
|
|
35
11
|
function ctxFileOf(sessionId) {
|
|
36
12
|
return `${sanitizePathComponent(sessionId).slice(0, 48)}-${createHash("sha256").update(sessionId).digest("hex").slice(0, 12)}.json`;
|
|
37
13
|
}
|
|
38
14
|
export class LocalCheckpointStore {
|
|
39
|
-
/** #270 车1:托管留存声明 —— **诚实的 none**(与 local session 店同一条判据:本店的行没有按期删除的腿;
|
|
40
|
-
* 留存三方法只实现在 SQL 聚合店上)。locked policy + 本店 ⇒ core 的门拒启,那是契约的正确行为。 */
|
|
41
15
|
retention = UNMANAGED_RETENTION;
|
|
42
|
-
/**
|
|
43
|
-
* `CheckpointStore.durability` 声明(#167 欠账,#168 件5)——**如实按内核判**:本类的内核是 core 的
|
|
44
|
-
* `FileCheckpointStore`(crash-safe ledger + snapshot,自身声明 `"durable"`),本地 scope 注册表也落
|
|
45
|
-
* 在同一个盘上目录里 ⇒ 进程重启后 park 全部还在、还赎得回,`"durable"` 是诚实读数而不是抬举。
|
|
46
|
-
*
|
|
47
|
-
* 它与「session store 的 local 形声明 durable 就拒启」不矛盾:那条判据管的是**声明 vs 介质**,而这里
|
|
48
|
-
* 的介质就是盘。真正的 process-local 形(`InMemoryCheckpointStore`)由 core 自己声明 `"process-local"`。
|
|
49
|
-
*/
|
|
50
16
|
durability = "durable";
|
|
51
|
-
/**
|
|
52
|
-
* `CheckpointStore.fidelity` 声明(core 5.17.0 [3052] 提货批 #172)——**如实按内核判**,与 durability
|
|
53
|
-
* 同一条判据:本类的读写内核是 core 的 `FileCheckpointStore`,它自己声明 `"json"`(盘上是 JSON 行),
|
|
54
|
-
* 包装层只加 scope/session 索引与 ctx 附属文件,不改这条 round-trip 的宽度 ⇒ 抄内核的读数是诚实的。
|
|
55
|
-
*
|
|
56
|
-
* ⚠️ 包装类**不继承**被包装者的声明(本类是委派不是子类),所以这一格必须自己写。它与内核那一格
|
|
57
|
-
* 是否仍然一致由测试盯着(`wiring-governance-operator.test.ts` 的 #172 组直接读内核那一格对表)——
|
|
58
|
-
* 内核哪天改宽度,包装层的声明会被那条钉当场揪出来,而不是靠人记得同步。
|
|
59
|
-
*/
|
|
60
17
|
fidelity = "json";
|
|
61
|
-
/**
|
|
62
|
-
* `CheckpointStore.redecision` 声明(core 5.22.0 F-012 L2/L3;[3342] clay 裁放行)——**如实按内核判**,
|
|
63
|
-
* 与 durability/fidelity 同一条判据:内核 core `FileCheckpointStore` **自己**声明 `{ reopen: true }`,
|
|
64
|
-
* 本类的 {@link LocalCheckpointStore.reopen} 纯委派给它 ⇒ 抄内核的读数是诚实的。包装类**不继承**被
|
|
65
|
-
* 包装者的声明(委派不是子类),所以这一格必须自己写。`validatingLease` 不声明(内核也没声明)。
|
|
66
|
-
* 翻向钉:`wiring-governance-operator.test.ts`(修前恰红,与本声明同 commit)。
|
|
67
|
-
*/
|
|
68
18
|
redecision = { reopen: true };
|
|
69
19
|
inner;
|
|
70
20
|
scopesPath;
|
|
@@ -72,14 +22,6 @@ export class LocalCheckpointStore {
|
|
|
72
22
|
ctxDir;
|
|
73
23
|
tmpDir;
|
|
74
24
|
scopes = new Set();
|
|
75
|
-
/** [868] sessionId → every token ever put for it — the service-side index that stands in for the SQL twins'
|
|
76
|
-
* `WHERE checkpoint.session_id = ?` on NON-pending rows (core's File store enumerates pending only via
|
|
77
|
-
* listByScope; get(token) is the sole read for resolved/expired rows, so cross-status session queries need
|
|
78
|
-
* this registry). Registered BEFORE the inner put (same crash direction as the scopes registry: a stale
|
|
79
|
-
* index entry is a harmless empty lookup; an unindexed EXPIRED row would silently disable the
|
|
80
|
-
* failSuspendedWithExpiredCheckpoint leg — the bad direction). Persisted so a restart keeps the
|
|
81
|
-
* suspend→crash→expire-later lifecycle visible. Entries are never dropped while the inner row exists
|
|
82
|
-
* (a `resolved` row can be reopen()ed back to pending — pruning it would be wrong). */
|
|
83
25
|
sessionTokens = new Map();
|
|
84
26
|
opts;
|
|
85
27
|
constructor(root, opts = {}) {
|
|
@@ -100,18 +42,11 @@ export class LocalCheckpointStore {
|
|
|
100
42
|
this.scopes.add(s);
|
|
101
43
|
}
|
|
102
44
|
catch (e) {
|
|
103
|
-
// C4(clay 裁 2026-07-26,C 先行 + B 根治):此前这里静默吞 ⇒ 存量待决审批整体从枚举面隐身,
|
|
104
|
-
// 操作员看到的是**空列表而不是错误**,没人会对空列表排障。处置两层:
|
|
105
|
-
// ①fail-visible(C):warn 大声 + 坏文件改名隔离留证(也防下一次 put 的整写把证据盖掉);
|
|
106
|
-
// ②重建(B,core 1.424 `listScopes?` seam,[1800]② 索 → [1803] 交付):constructor 是同步的,
|
|
107
|
-
// 重建挂在读路径懒执行一次(见 ensureScopesRebuilt)—— 从数据本体枚举 pending scope 集,
|
|
108
|
-
// 枚举面完整恢复,不再依赖「下次 suspend 自愈」只救新件。
|
|
109
45
|
const quarantine = `${this.scopesPath}.corrupt-${Date.now()}`;
|
|
110
46
|
try {
|
|
111
47
|
renameSync(this.scopesPath, quarantine);
|
|
112
48
|
}
|
|
113
49
|
catch {
|
|
114
|
-
/* 移不动就留原地 —— warn 里已给路径,人工可查 */
|
|
115
50
|
}
|
|
116
51
|
this.scopeRegistryCorrupt = true;
|
|
117
52
|
console.warn(`LocalCheckpointStore: checkpoint-scopes.json is corrupt (${e instanceof Error ? e.message : String(e)}) — ` +
|
|
@@ -131,23 +66,17 @@ export class LocalCheckpointStore {
|
|
|
131
66
|
}
|
|
132
67
|
}
|
|
133
68
|
catch {
|
|
134
|
-
/* a corrupt index only narrows hasExpiredBySession (fail-safe: the reaper skips, cancel still works) */
|
|
135
69
|
}
|
|
136
70
|
}
|
|
137
71
|
}
|
|
138
|
-
/** Persist the session→tokens index (whole-file atomic replace — puts are per-suspend, low frequency). */
|
|
139
72
|
persistSessionTokens() {
|
|
140
73
|
atomicWriteFile(this.tmpDir, this.sessionTokensPath, JSON.stringify(Object.fromEntries([...this.sessionTokens].map(([sid, set]) => [sid, [...set]]))));
|
|
141
74
|
}
|
|
142
|
-
// ─────────── core CheckpointStore contract — delegated to core's File store (the engine authority) ───────────
|
|
143
75
|
async put(token, cp) {
|
|
144
|
-
// Register the scope BEFORE the inner commit: a crash between the two leaves a stale scope entry
|
|
145
|
-
// (harmless empty enumeration), never an unenumerable pending checkpoint (the bad direction).
|
|
146
76
|
if (!this.scopes.has(cp.scope)) {
|
|
147
77
|
this.scopes.add(cp.scope);
|
|
148
78
|
atomicWriteFile(this.tmpDir, this.scopesPath, JSON.stringify([...this.scopes]));
|
|
149
79
|
}
|
|
150
|
-
// [868] session→token index, same before-the-inner-put crash rationale (see the field doc).
|
|
151
80
|
if (!this.sessionTokens.get(cp.sessionId)?.has(token)) {
|
|
152
81
|
const set = this.sessionTokens.get(cp.sessionId) ?? new Set();
|
|
153
82
|
set.add(token);
|
|
@@ -165,10 +94,6 @@ export class LocalCheckpointStore {
|
|
|
165
94
|
reopen(token, scope, reason) {
|
|
166
95
|
return this.inner.reopen(token, scope, reason);
|
|
167
96
|
}
|
|
168
|
-
/** core 5.14.0(#147):入参是 `PendingSteerInput`(text/trusted + 可选 actor/inputId/priority),不是
|
|
169
|
-
* 两键单座 —— 形参窄成 `{text,trusted}` 会**静默丢掉**署名与幂等键(TS 的方法参数是双变的,窄形不
|
|
170
|
-
* 报错,只是把三个可选键从类型上抹掉,调用点再也传不进来)。队列语义与边界全在被包的 core
|
|
171
|
-
* `FileCheckpointStore` 里,本 wrapper 只做转发。 */
|
|
172
97
|
setPendingSteer(token, scope, steer) {
|
|
173
98
|
return this.inner.setPendingSteer(token, scope, steer);
|
|
174
99
|
}
|
|
@@ -184,17 +109,13 @@ export class LocalCheckpointStore {
|
|
|
184
109
|
close() {
|
|
185
110
|
this.inner.close();
|
|
186
111
|
}
|
|
187
|
-
// ─────────── service extensions (the SQL twins' operator/ops half) ───────────
|
|
188
|
-
/** C4-B:注册表损坏时从**数据本体**懒重建 scope 集(core 1.424 `listScopes?` = 「有 ≥1 条 pending 的
|
|
189
|
-
* distinct scope 集」,与 listByScope 可见面对齐 —— 恰好是本注册表要的内容)。一次性:重建后回写
|
|
190
|
-
* sidecar,恢复正常路径。seam 缺席(第三方旧 inner)⇒ 保持 C 案的可见降级,不装恢复了。 */
|
|
191
112
|
scopeRegistryCorrupt = false;
|
|
192
113
|
async ensureScopesRebuilt() {
|
|
193
114
|
if (!this.scopeRegistryCorrupt)
|
|
194
115
|
return;
|
|
195
116
|
const listScopes = this.inner.listScopes;
|
|
196
117
|
if (typeof listScopes !== "function")
|
|
197
|
-
return;
|
|
118
|
+
return;
|
|
198
119
|
const rebuilt = await listScopes.call(this.inner);
|
|
199
120
|
for (const s of rebuilt)
|
|
200
121
|
this.scopes.add(s);
|
|
@@ -202,7 +123,6 @@ export class LocalCheckpointStore {
|
|
|
202
123
|
atomicWriteFile(this.tmpDir, this.scopesPath, JSON.stringify([...this.scopes]));
|
|
203
124
|
console.warn(`LocalCheckpointStore: scope registry REBUILT from the data plane (${rebuilt.length} scope(s) with pending gates) — enumeration restored`);
|
|
204
125
|
}
|
|
205
|
-
/** All still-pending checkpoints across the registered scopes (summary + full row via get). */
|
|
206
126
|
async pendings(scope) {
|
|
207
127
|
await this.ensureScopesRebuilt();
|
|
208
128
|
const out = [];
|
|
@@ -216,7 +136,6 @@ export class LocalCheckpointStore {
|
|
|
216
136
|
}
|
|
217
137
|
return out;
|
|
218
138
|
}
|
|
219
|
-
/** Operator approval queue — the same NON-secret card as the SQL twins (never the token). */
|
|
220
139
|
async listPending(scope) {
|
|
221
140
|
const rows = [];
|
|
222
141
|
for (const { cp } of await this.pendings(scope)) {
|
|
@@ -228,32 +147,20 @@ export class LocalCheckpointStore {
|
|
|
228
147
|
scope: cp.scope,
|
|
229
148
|
toolName: pa?.toolName ?? null,
|
|
230
149
|
toolCallId,
|
|
231
|
-
// [1995]②: the D-1 binding fields are OMITTED when the park has no tool action (plan_review /
|
|
232
|
-
// resource_limit / task_done) — the wire contract says an absent nullable field is ABSENT, not `null`
|
|
233
|
-
// (a consumer branching on "key present ⇒ there is a binding" would echo `null` back into /decide).
|
|
234
150
|
...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
|
|
235
151
|
...(pa?.boundInputHash !== undefined ? { boundInputHash: pa.boundInputHash } : {}),
|
|
236
|
-
// [1995]③ additive: which gate this row is parked on, so a durable-recovery consumer can route
|
|
237
|
-
// plan / tool / resource off the SAME queue (toolName is null for every non-tool gate).
|
|
238
152
|
...(gate?.kind !== undefined ? { gateKind: gate.kind } : {}),
|
|
239
153
|
taskId: (await this.opts.getActiveTaskId?.(cp.sessionId)) ?? null,
|
|
240
154
|
input: boundedToolInput(pa?.args),
|
|
241
155
|
createdAt: cp.createdAt,
|
|
242
156
|
deadline: cp.deadline ?? null,
|
|
243
157
|
riskDescriptor: gate?.riskDescriptor ?? null,
|
|
244
|
-
// [3684]②:与 SQL 孪生**同一个** `boundedRuleSuggestions`(窄读+脱敏+截基数)。本地形没有列,
|
|
245
|
-
// 直接从 blob 上的 `pendingAction` 取——投影结果必须与两条 SQL 腿逐字同形,否则 local 档的运维
|
|
246
|
-
// 队列会给出一份别人没有的卡面。无供给 ⇒ 省键(绝不 null/[])。
|
|
247
|
-
// 🔴 这里**连计数一起**保留(不同于 SQL 腿的回读形 `{ countRedactions: false }` —— 那条腿脱敏
|
|
248
|
-
// **照跑**,关掉的只是计数):本地形每次读的是**未脱敏的原始 blob**,这一遍是它唯一的一次脱敏,
|
|
249
|
-
// 不是重扫已脱敏文本 ⇒ `redactions_applied_total` 记的是真命中,该计。
|
|
250
158
|
...(() => {
|
|
251
159
|
const rs = boundedRuleSuggestions(pa?.ruleSuggestions);
|
|
252
160
|
return rs !== undefined ? { ruleSuggestions: rs } : {};
|
|
253
161
|
})(),
|
|
254
162
|
});
|
|
255
163
|
}
|
|
256
|
-
// Same triage order as the SQL twins: severity DESC, then oldest-first within a tier.
|
|
257
164
|
return rows.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
258
165
|
}
|
|
259
166
|
async findPendingTokenBySession(sessionId, scope) {
|
|
@@ -262,14 +169,6 @@ export class LocalCheckpointStore {
|
|
|
262
169
|
return token;
|
|
263
170
|
return null;
|
|
264
171
|
}
|
|
265
|
-
/** [868] read-only probe face: does this session have an EXPIRED checkpoint row? The local twin of the SQL
|
|
266
|
-
* `EXISTS (SELECT 1 FROM checkpoint ce WHERE ce.session_id = ? AND ce.status = 'expired')` that drives the
|
|
267
|
-
* run stores' failSuspendedWithExpiredCheckpoint. Scans the session→token index (get(token) is the only
|
|
268
|
-
* cross-status read core's File store offers); an index entry whose inner row vanished is pruned lazily.
|
|
269
|
-
* Backed by a persisted registry, so it stays honest — never a guessed "true".
|
|
270
|
-
* ⚠️ 存量限制(codex M2,如实):索引随 put 增量建立,无升级迁移——本版本部署前已存在的 checkpoint 不在
|
|
271
|
-
* 索引里,其 expired 对本探针不可见(core File store 的 listByScope 是 pending-only,无法枚举历史行回填)。
|
|
272
|
-
* 旧存量锁死的恢复把手 = cancel verb([868]①,不依赖本索引);增量行全覆盖。 */
|
|
273
172
|
async hasExpiredBySession(sessionId) {
|
|
274
173
|
const tokens = this.sessionTokens.get(sessionId);
|
|
275
174
|
if (!tokens)
|
|
@@ -278,7 +177,7 @@ export class LocalCheckpointStore {
|
|
|
278
177
|
for (const t of [...tokens]) {
|
|
279
178
|
const cp = await this.inner.get(t);
|
|
280
179
|
if (!cp) {
|
|
281
|
-
tokens.delete(t);
|
|
180
|
+
tokens.delete(t);
|
|
282
181
|
pruned = true;
|
|
283
182
|
continue;
|
|
284
183
|
}
|
|
@@ -292,24 +191,16 @@ export class LocalCheckpointStore {
|
|
|
292
191
|
this.persistSessionTokens();
|
|
293
192
|
}
|
|
294
193
|
catch {
|
|
295
|
-
/* best-effort — a stale on-disk entry is re-pruned on the next scan */
|
|
296
194
|
}
|
|
297
195
|
}
|
|
298
196
|
return false;
|
|
299
197
|
}
|
|
300
|
-
/** `undefined` = no pending row; else the owner scope (local scopes are always strings — never null). */
|
|
301
198
|
async peekPendingScope(sessionId) {
|
|
302
199
|
for (const { cp } of await this.pendings())
|
|
303
200
|
if (cp.sessionId === sessionId)
|
|
304
201
|
return cp.scope;
|
|
305
202
|
return undefined;
|
|
306
203
|
}
|
|
307
|
-
/**
|
|
308
|
-
* design/80 D-D global expiry sweep — same kind-split as the TiDB twin: the deadline branch abort-expires
|
|
309
|
-
* every kind EXCEPT a human/irreversible_ask approval gate (those get the graceful resolve-DENY via
|
|
310
|
-
* listExpiredApprovalGates), with the AskUserQuestion carve-back (denying a question is incoherent → expire);
|
|
311
|
-
* the terminal_at_ms branch (derived read-time via the SAME formula the TiDB put stamps) abort-expires ANY kind.
|
|
312
|
-
*/
|
|
313
204
|
async reapExpired(cutoff) {
|
|
314
205
|
let n = 0;
|
|
315
206
|
for (const { token, cp } of await this.pendings()) {
|
|
@@ -324,7 +215,6 @@ export class LocalCheckpointStore {
|
|
|
324
215
|
}
|
|
325
216
|
return n;
|
|
326
217
|
}
|
|
327
|
-
/** design/80 D-D SLA deny-sweep input — pending human/irreversible_ask gates past deadline (excl. AskUserQuestion). */
|
|
328
218
|
async listExpiredApprovalGates(cutoff, limit = 100) {
|
|
329
219
|
const hits = [];
|
|
330
220
|
for (const { cp } of await this.pendings()) {
|
|
@@ -342,7 +232,6 @@ export class LocalCheckpointStore {
|
|
|
342
232
|
.slice(0, limit)
|
|
343
233
|
.map(({ sessionId, scope }) => ({ sessionId, scope }));
|
|
344
234
|
}
|
|
345
|
-
// ─────────── checkpoint ctx (resume rebuild inputs; sessionId-keyed, one JSON file per session) ───────────
|
|
346
235
|
async putCtx(sessionId, ctx) {
|
|
347
236
|
const rec = { sessionId, updatedAt: Date.now(), ctx };
|
|
348
237
|
atomicWriteFile(this.tmpDir, join(this.ctxDir, ctxFileOf(sessionId)), JSON.stringify(rec));
|
|
@@ -353,13 +242,12 @@ export class LocalCheckpointStore {
|
|
|
353
242
|
return null;
|
|
354
243
|
try {
|
|
355
244
|
const rec = JSON.parse(readFileSync(p, "utf8"));
|
|
356
|
-
return rec.sessionId === sessionId ? (rec.ctx ?? null) : null;
|
|
245
|
+
return rec.sessionId === sessionId ? (rec.ctx ?? null) : null;
|
|
357
246
|
}
|
|
358
247
|
catch {
|
|
359
|
-
return null;
|
|
248
|
+
return null;
|
|
360
249
|
}
|
|
361
250
|
}
|
|
362
|
-
/** GC ctx files whose lifecycle is over — same three guards as the SQL twins (stale ∧ no pending ∧ no active run). */
|
|
363
251
|
async reapCtx(cutoff) {
|
|
364
252
|
let n = 0;
|
|
365
253
|
for (const f of readdirSync(this.ctxDir)) {
|
|
@@ -371,7 +259,7 @@ export class LocalCheckpointStore {
|
|
|
371
259
|
rec = JSON.parse(readFileSync(p, "utf8"));
|
|
372
260
|
}
|
|
373
261
|
catch {
|
|
374
|
-
continue;
|
|
262
|
+
continue;
|
|
375
263
|
}
|
|
376
264
|
if (rec.updatedAt >= cutoff)
|
|
377
265
|
continue;
|
|
@@ -384,27 +272,20 @@ export class LocalCheckpointStore {
|
|
|
384
272
|
n++;
|
|
385
273
|
}
|
|
386
274
|
catch {
|
|
387
|
-
/* racing unlink is fine */
|
|
388
275
|
}
|
|
389
276
|
}
|
|
390
277
|
return n;
|
|
391
278
|
}
|
|
392
|
-
/**
|
|
393
|
-
* E21 session purge — functional contract: a deleted session has no resumable future. Expires every pending
|
|
394
|
-
* gate of the session (all scopes) + unlinks its ctx file. Owner guard mirrors the SQL EXISTS guard: with an
|
|
395
|
-
* `ownerOf` source the owner must null-safe-match; without one, only an ownerless call (owner=null) purges.
|
|
396
|
-
* Returns the number of gates expired. (Terminal ledger history stays on disk — local transcripts posture.)
|
|
397
|
-
*/
|
|
398
279
|
async deleteBySession(sessionId, owner) {
|
|
399
280
|
if (this.opts.ownerOf) {
|
|
400
281
|
const actual = await this.opts.ownerOf(sessionId);
|
|
401
282
|
if (actual === undefined)
|
|
402
|
-
return 0;
|
|
283
|
+
return 0;
|
|
403
284
|
if ((actual ?? null) !== (owner ?? null))
|
|
404
285
|
return 0;
|
|
405
286
|
}
|
|
406
287
|
else if (owner !== null) {
|
|
407
|
-
return 0;
|
|
288
|
+
return 0;
|
|
408
289
|
}
|
|
409
290
|
let n = 0;
|
|
410
291
|
for (const { token, cp } of await this.pendings()) {
|
|
@@ -417,7 +298,6 @@ export class LocalCheckpointStore {
|
|
|
417
298
|
unlinkSync(p);
|
|
418
299
|
}
|
|
419
300
|
catch {
|
|
420
|
-
/* best-effort ctx unlink */
|
|
421
301
|
}
|
|
422
302
|
return n;
|
|
423
303
|
}
|