@sema-agent/server 7.125.1 → 7.127.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 +229 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +346 -145
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +57 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +32 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -2
- package/deploy/sema-up/chart/templates/server.yaml +2 -1
- package/deploy/sema-up/chart/values.yaml +22 -0
- package/deploy/sema-up/cluster-up.sh +10 -10
- package/deploy/sema-up/kube-up.sh +20 -13
- package/deploy/sema-up/lib/dataroot.sh +1 -1
- package/deploy/sema-up/lib/envf.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +5 -5
- package/deploy/sema-up/lib/lanes.py +1 -1
- package/deploy/sema-up/lib/node-addrs.sh +42 -0
- package/deploy/sema-up/lib/parse-machines.py +2 -2
- package/deploy/sema-up/lib/shell-access.sh +1 -1
- package/deploy/sema-up/preflight.sh +4 -4
- package/deploy/sema-up/sema-up.sh +33 -33
- package/deploy/sema-up/smoke.sh +3 -3
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +26 -3
- package/dist/approval-card.d.ts +20 -4
- package/dist/approval-content-kind.d.ts +38 -22
- package/dist/approval-content-kind.js +20 -4
- package/dist/audit.js +3 -0
- package/dist/bench/s1/arms.d.ts +9 -37
- package/dist/bench/s1/arms.js +4 -61
- package/dist/bench/s1/live-deps.d.ts +4 -10
- package/dist/bench/s1/live-deps.js +1 -222
- package/dist/bench/s1/oracle.d.ts +1 -1
- package/dist/bench/s1/reviewer.d.ts +1 -1
- package/dist/bench/s1/row.d.ts +5 -7
- package/dist/bench/s1/row.js +0 -2
- package/dist/bench/s1/run-firm.d.ts +2 -2
- package/dist/bench/s1/run-firm.js +1 -4
- package/dist/bench/s1/runner-ctx.d.ts +17 -29
- package/dist/bench/s1/runner-ctx.js +3 -7
- package/dist/bench/s1/tasks.d.ts +9 -46
- package/dist/bench/s1/tasks.js +4 -63
- package/dist/boot/execution-env.d.ts +38 -26
- package/dist/boot/execution-env.js +87 -41
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/memory-boundary.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +37 -3
- package/dist/boot/memory-consolidation.js +18 -2
- package/dist/boot/parked-revive-gate.d.ts +0 -3
- package/dist/boot/parked-revive-gate.js +2 -2
- package/dist/boot/resolve-spec.d.ts +0 -2
- package/dist/boot/resolve-spec.js +12 -17
- package/dist/boot/retention-lane.d.ts +12 -1
- package/dist/boot/retention-lane.js +2 -0
- package/dist/boot/runner-deps.d.ts +7 -6
- package/dist/boot/runner-deps.js +5 -16
- package/dist/boot/session-faces.d.ts +7 -2
- package/dist/boot/session-faces.js +37 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.js +7 -4
- package/dist/boot/stage-06-runners.d.ts +2 -3
- package/dist/boot/stage-06-runners.js +13 -9
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +3 -4
- package/dist/boot/stage-08-reapers.d.ts +2 -3
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +4 -3
- package/dist/boot/web-admin.d.ts +5 -0
- package/dist/boot/web-admin.js +28 -14
- package/dist/brain.d.ts +2 -2
- package/dist/brain.js +1 -1
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +27 -22
- package/dist/capabilities/memory-notice.js +15 -17
- package/dist/capabilities/scenarios.d.ts +9 -14
- package/dist/capabilities/scenarios.js +5 -50
- package/dist/capability-facts.d.ts +22 -11
- package/dist/capability-facts.js +13 -6
- package/dist/config-catalog.js +14 -5
- package/dist/config-provider.d.ts +5 -1
- package/dist/config-provider.js +23 -4
- package/dist/config-types.d.ts +19 -2
- package/dist/config.js +41 -6
- package/dist/deployment-governance.d.ts +19 -21
- package/dist/deployment-governance.js +7 -8
- package/dist/env-name-allowlist-knobs.d.ts +5 -5
- package/dist/env-name-allowlist-knobs.js +1 -0
- package/dist/execution-lane-caps.d.ts +12 -0
- package/dist/execution-lane-caps.js +18 -4
- package/dist/fleet/subagent-tail-bus.js +15 -5
- package/dist/hooks/hook-runner.d.ts +84 -30
- package/dist/hooks/hook-runner.js +74 -41
- package/dist/http/admission.js +17 -10
- package/dist/http/durable-run-leg.d.ts +5 -4
- package/dist/http/durable-run-leg.js +2 -4
- package/dist/http/idempotency.d.ts +28 -6
- package/dist/http/idempotency.js +10 -2
- package/dist/http/principal-gate.d.ts +14 -0
- package/dist/http/principal-gate.js +7 -0
- package/dist/http/request-guards.d.ts +2 -1
- package/dist/http/request-guards.js +14 -14
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +20 -10
- package/dist/http/route-ctx.d.ts +15 -7
- package/dist/http/routes/a2a-serve.js +11 -7
- package/dist/http/routes/approvals-assistant.js +18 -3
- package/dist/http/routes/background.d.ts +1 -1
- package/dist/http/routes/background.js +1 -1
- package/dist/http/routes/capabilities.js +10 -2
- package/dist/http/routes/mcp-reconnect.js +1 -1
- package/dist/http/routes/runs.d.ts +4 -3
- package/dist/http/routes/runs.js +35 -14
- package/dist/http/routes/session-sync.js +14 -11
- package/dist/http/routes/sessions.js +9 -9
- package/dist/http/routes/side-query.js +6 -19
- package/dist/http/routes/tasks.js +29 -19
- package/dist/http/routes/workflows.js +8 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/http/server.js +3 -5
- package/dist/http/submit-replay.d.ts +15 -8
- package/dist/http/submit-replay.js +6 -0
- package/dist/http/wire-types.d.ts +7 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal-run-env.d.ts +30 -0
- package/dist/internal-run-env.js +11 -0
- package/dist/leader/wire.js +3 -2
- package/dist/lsp/manager.js +1 -1
- package/dist/memory-scope.d.ts +19 -7
- package/dist/memory-scope.js +25 -8
- package/dist/observability/fail-open.d.ts +31 -3
- package/dist/observability/fail-open.js +31 -3
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/metrics.js +1 -4
- package/dist/observability/run-terminal.js +72 -4
- package/dist/permission-rule-vocab.d.ts +3 -2
- package/dist/permission-rule-vocab.js +1 -1
- package/dist/plan-cache-probe.d.ts +3 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
- package/dist/plugins/caching-session-store.d.ts +42 -22
- package/dist/plugins/caching-session-store.js +19 -9
- package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
- package/dist/plugins/checkpoint-store-sql.js +57 -11
- package/dist/plugins/file-run-store.d.ts +25 -21
- package/dist/plugins/file-run-store.js +70 -34
- package/dist/plugins/k8s-exec-protocol.js +3 -0
- package/dist/plugins/local-checkpoint-store.d.ts +8 -5
- package/dist/plugins/local-checkpoint-store.js +5 -15
- package/dist/plugins/local-session-store.d.ts +66 -7
- package/dist/plugins/local-session-store.js +168 -101
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
- package/dist/plugins/memory-run-store.d.ts +19 -3
- package/dist/plugins/memory-run-store.js +38 -3
- package/dist/plugins/one-time-migrations.d.ts +165 -39
- package/dist/plugins/one-time-migrations.js +51 -144
- package/dist/plugins/pg-pool.js +11 -1
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +9 -5
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +1 -0
- package/dist/plugins/remote-env-e2b.d.ts +4 -0
- package/dist/plugins/remote-env-e2b.js +2 -1
- package/dist/plugins/remote-env-host.d.ts +23 -1
- package/dist/plugins/remote-env-host.js +20 -2
- package/dist/plugins/remote-env-k8s.d.ts +264 -41
- package/dist/plugins/remote-env-k8s.js +551 -82
- package/dist/plugins/remote-env-local-docker.d.ts +4 -0
- package/dist/plugins/remote-env-local-docker.js +2 -1
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/retention-store-sql.d.ts +4 -1
- package/dist/plugins/retention-store-sql.js +11 -9
- package/dist/plugins/run-store-sql.d.ts +44 -6
- package/dist/plugins/run-store-sql.js +92 -27
- package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
- package/dist/plugins/session-store.d.ts +4 -3
- package/dist/plugins/session-store.js +1 -1
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/store-backend.d.ts +12 -5
- package/dist/plugins/store-backend.js +13 -4
- package/dist/plugins/store-contracts.d.ts +20 -3
- package/dist/plugins/store-open-wait.js +3 -0
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-storage.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -0
- package/dist/plugins/tidb-session-store.js +6 -2
- package/dist/request-key-closure.d.ts +32 -7
- package/dist/request-key-closure.js +25 -9
- package/dist/router/route-orchestration.d.ts +3 -11
- package/dist/router/route-orchestration.js +0 -4
- package/dist/rule-entry-list.js +0 -4
- package/dist/rules-consent.js +3 -0
- package/dist/run-acceptance.d.ts +51 -0
- package/dist/run-acceptance.js +27 -0
- package/dist/run-local.d.ts +5 -4
- package/dist/run-local.js +23 -17
- package/dist/runs.d.ts +7 -11
- package/dist/runs.js +6 -16
- package/dist/sandbox-egress-policy.d.ts +61 -0
- package/dist/sandbox-egress-policy.js +235 -0
- package/dist/security.d.ts +7 -0
- package/dist/security.js +5 -0
- package/dist/server-secret-env.d.ts +10 -4
- package/dist/server-secret-env.js +4 -1
- package/dist/session-error-wire.d.ts +2 -2
- package/dist/session-incarnation.d.ts +46 -2
- package/dist/session-incarnation.js +14 -3
- package/dist/session-sync-kernel.d.ts +10 -3
- package/dist/session-titler.d.ts +5 -3
- package/dist/spec-fields.d.ts +24 -8
- package/dist/spec-fields.js +21 -2
- package/dist/task-workflow.d.ts +6 -4
- package/dist/task-workflow.js +3 -2
- package/dist/tool-approval.d.ts +78 -6
- package/dist/tool-approval.js +7 -3
- package/dist/trace/core-keyset-guard.d.ts +6 -5
- package/dist/trace/engine-notice-wire.d.ts +26 -1
- package/dist/trace/engine-notice-wire.js +7 -2
- package/dist/trace/ledger-events.d.ts +9 -3
- package/dist/trace/ledger-sink.d.ts +7 -0
- package/dist/trace/ledger-sink.js +14 -4
- package/dist/trace/project.d.ts +57 -11
- package/dist/trace/project.js +54 -8
- package/dist/trace/self-minted-wire.d.ts +94 -2
- package/dist/trace/self-minted-wire.js +28 -0
- package/dist/trace/tool-output-media.d.ts +26 -0
- package/dist/trace/tool-output-media.js +57 -0
- package/dist/trace/wire-projection-faces.d.ts +7 -1
- package/dist/trace/wire-projection-faces.js +2 -1
- package/dist/web-admin-client/apply-effective.d.ts +13 -7
- package/dist/web-admin-client/apply-effective.js +11 -26
- package/dist/web-admin-client/apply-ledger.d.ts +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +39 -4
- package/dist/web-admin-client/candidate-gate.js +9 -2
- package/dist/web-admin-client/facade.d.ts +2 -2
- package/dist/web-admin-client/http-client.d.ts +3 -1
- package/dist/web-admin-client/http-client.js +4 -3
- package/dist/web-admin-client/read-warnings.d.ts +33 -5
- package/dist/web-admin-client/read-warnings.js +41 -6
- package/dist/web-admin-client/types.d.ts +11 -5
- package/dist/workspace-cache.d.ts +45 -0
- package/dist/workspace-cache.js +30 -0
- package/dist/workspace-space-commit.d.ts +116 -0
- package/dist/workspace-space-commit.js +117 -0
- package/dist/workspace-space.d.ts +75 -9
- package/dist/workspace-space.js +52 -1
- package/package.json +4 -4
- package/dist/boot/session-incarnation-seat.d.ts +0 -41
- package/dist/boot/session-incarnation-seat.js +0 -69
- package/dist/capabilities/code-review-council.d.ts +0 -49
- package/dist/capabilities/code-review-council.js +0 -111
- package/dist/capabilities/collab-wire.d.ts +0 -36
- package/dist/capabilities/collab-wire.js +0 -2
- package/dist/capabilities/collab-workflows.d.ts +0 -47
- package/dist/capabilities/collab-workflows.js +0 -90
- package/dist/capabilities/prompts/code-review.d.ts +0 -25
- package/dist/capabilities/prompts/code-review.js +0 -39
- package/dist/capabilities/prompts/team.d.ts +0 -10
- package/dist/capabilities/prompts/team.js +0 -6
- package/dist/capabilities/team.d.ts +0 -113
- package/dist/capabilities/team.js +0 -70
|
@@ -8,24 +8,36 @@ export interface CachingOptions {
|
|
|
8
8
|
/** Idle seconds a woken session is kept warm. 0 disables caching (pure passthrough). */
|
|
9
9
|
ttlSec: number;
|
|
10
10
|
sweepIntervalMs?: number;
|
|
11
|
-
/**
|
|
12
|
-
* the
|
|
13
|
-
|
|
11
|
+
/** Called once per warm hit that turned out stale (the store's head no longer names the cached leaf, or the session is
|
|
12
|
+
* gone): the entry was dropped and the session woken from the store again. Not an error — the operator signal for
|
|
13
|
+
* "requests for one session alternate between replicas within the TTL" (the hit rate affinity would buy back). */
|
|
14
|
+
onStaleHit?: (sessionId: string) => void;
|
|
14
15
|
}
|
|
16
|
+
/** The store's current head for one session — the one read every warm hit is checked against (`undefined` = no such
|
|
17
|
+
* session). The same single-row read `OwnerAwareSessionStore.getHead` names; this cache requires its inner store to have it. */
|
|
18
|
+
export type SessionHeadReader = Required<Pick<OwnerAwareSessionStore, "getHead">>;
|
|
15
19
|
/**
|
|
16
|
-
* Warm
|
|
20
|
+
* Warm cache in front of a durable {@link SessionStore}.
|
|
17
21
|
*
|
|
18
|
-
* Keeps a woken session in memory and **reuses the same storage instance across turns**, so
|
|
19
|
-
*
|
|
20
|
-
* active). Idle sessions expire after `ttlSec`; `release` evicts immediately.
|
|
22
|
+
* Keeps a woken session in memory and **reuses the same storage instance across turns**, so an active session is not
|
|
23
|
+
* woken from the store (meta row + event log) on every short turn. Idle sessions expire after `ttlSec`; `release` evicts.
|
|
21
24
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* **One rule: a warm entry is served only while the store's head for that session still names the leaf the entry
|
|
26
|
+
* holds.** Every hit reads the head first (`getHead` — one primary-key row read on the SQL twins); head gone or leaf
|
|
27
|
+
* moved ⇒ the entry is dropped and the session is woken from the store again ({@link CachingOptions.onStaleHit}). The
|
|
28
|
+
* leaf is the token because it is exactly what the stores' conditional writes compare (`expectedLeafId`): a handle this
|
|
29
|
+
* cache hands out is never behind the store's leaf, so those writes fail only on a genuinely concurrent write — two runs
|
|
30
|
+
* writing one session at the same time. Any number of replicas may share one store under any routing; session affinity
|
|
31
|
+
* only raises the hit rate.
|
|
32
|
+
*
|
|
33
|
+
* Open item (the same before this rule): a write that rewrites history while leaving the final leaf id where it was —
|
|
34
|
+
* a session-sync replace / `overwrite-dst` staged commit on ANOTHER replica that changes entry payloads under the same
|
|
35
|
+
* ids — moves no leaf, so a warm entry elsewhere keeps its old payloads until it expires or is evicted. Its conditional
|
|
36
|
+
* writes still succeed (the leaf matches); what is stale is the content of the entries under it. Closing it needs a
|
|
37
|
+
* store revision bumped by every write path (append, leaf move, replace, staged commit) and carried by the woken handle.
|
|
38
|
+
*
|
|
39
|
+
* Writes that go around the cached handle through THIS wrapper (import / replace / staged commit / delete) also drop
|
|
40
|
+
* the entry at the write site, so the next acquire wakes from the written state.
|
|
29
41
|
*
|
|
30
42
|
* Orthogonal to the F3 branch-floor change: floor bounds the *cold wake* size; this cache removes
|
|
31
43
|
* *repeated* re-reads of an active session.
|
|
@@ -36,12 +48,16 @@ export declare class CachingSessionStore implements SessionStore {
|
|
|
36
48
|
private readonly pending;
|
|
37
49
|
private readonly ttlMs;
|
|
38
50
|
private readonly timer?;
|
|
51
|
+
private readonly onStaleHit?;
|
|
39
52
|
/** Delegated tenant-ownership ops, present only if the inner store exposes them (the TiDB store does). */
|
|
40
53
|
readonly ownerOf?: (sessionId: string) => Promise<string | null | undefined>;
|
|
41
54
|
/** S-790:属主 + 化身一次读,纯转发(不经暖缓存 —— 化身判的就是「此刻坐在座位上的是谁」,缓存的旧答案正是要消灭的那一形)。
|
|
42
55
|
* 🔴 必须转发(与 retention / placements / instruction-source 同一条教训):生产默认把真店包进本类,不转发 = parked 决定腿
|
|
43
56
|
* 与记忆座在默认部署上读不到化身。 */
|
|
44
57
|
readonly sessionFaceOf?: SessionFaceReader;
|
|
58
|
+
/** core `SessionStore.incarnationOf`(按 id 读住客)—— 同一条纯转发、同一条理由:答的是行上那一格,绝不从暖缓存里那只会话的元数据答
|
|
59
|
+
* (缓存的会话活得比「另一只店删掉又重建了这一行」长)。🔴 必须转发:k8s 工作区空间的每 run 读与起服门读的就是本类。 */
|
|
60
|
+
readonly incarnationOf?: SessionStore["incarnationOf"];
|
|
45
61
|
readonly register?: (sessionId: string, owner: string | null) => Promise<void>;
|
|
46
62
|
/** Delegated SessionRepo seam (§0.5) — list/fork/delete operate on the DURABLE tables directly (they bypass the
|
|
47
63
|
* warm cache by design; a fork/delete must read/write durable state, not a possibly-stale cached view). */
|
|
@@ -73,11 +89,8 @@ export declare class CachingSessionStore implements SessionStore {
|
|
|
73
89
|
/** E18 — delegate the leaf read straight to inner (a cache-bypassing single SELECT; the resume-at turn-capture
|
|
74
90
|
* reads the DURABLE leaf, not a possibly-stale cached view — same discipline as the §0.5 repo ops above). */
|
|
75
91
|
readonly getLeafId?: (sessionId: string) => Promise<string | null>;
|
|
76
|
-
/** [ref] 六轮复审:owner+leafId 原子快照透传(cache-bypassing,getLeafId 同姿势)。 */
|
|
77
|
-
readonly getHead
|
|
78
|
-
owner: string | null;
|
|
79
|
-
leafId: string | null;
|
|
80
|
-
} | undefined>;
|
|
92
|
+
/** [ref] 六轮复审:owner+leafId 原子快照透传(cache-bypassing,getLeafId 同姿势)。内层店必有(本类的暖命中按它核新鲜)。 */
|
|
93
|
+
readonly getHead: SessionHeadReader["getHead"];
|
|
81
94
|
/** 2c session-sync — delegated export/import seam, DURABLE (bypasses the warm cache exactly like
|
|
82
95
|
* list/fork: an export reads the full durable log, an import writes durable state). */
|
|
83
96
|
readonly exportEntries?: (sessionId: string) => Promise<SessionTreeEntry[] | null>;
|
|
@@ -96,7 +109,7 @@ export declare class CachingSessionStore implements SessionStore {
|
|
|
96
109
|
* 转发白名单里 ⇒ 默认部署(SESSION_CACHE_TTL_SEC=300 把真店包进本类)PUSH Phase A 恒 501
|
|
97
110
|
* `capability.session_store_required`,而 capabilities().sessionSync 谓词三项(backend/exportEntries/
|
|
98
111
|
* fileHistoryStore)都被转发了 ⇒ 照报 true(能力面谎)。转发形:handle 的 `commit` 是唯一写真 id 的
|
|
99
|
-
* 时刻,成功后逐出该 id 的暖 handle
|
|
112
|
+
* 时刻,成功后逐出该 id 的暖 handle(importEntries/replaceEntries 同纪律);`abort` 只丢
|
|
100
113
|
* staging 行、不碰真 id ⇒ 不逐出。 */
|
|
101
114
|
readonly beginImportStaging?: (realSessionId: string, token: string) => StagingHandle;
|
|
102
115
|
/** Staging 清扫面(kernel `ImportStagingStore.sweepStagingSessions`)。当下 reaper 走 `backend.session()`
|
|
@@ -133,11 +146,18 @@ export declare class CachingSessionStore implements SessionStore {
|
|
|
133
146
|
scope?: string;
|
|
134
147
|
}) => Promise<PlacedSessionRow[]>;
|
|
135
148
|
readonly placementOf?: (sessionId: string) => Promise<SessionPlacementRecord | undefined>;
|
|
136
|
-
constructor(inner: SessionStore, opts: CachingOptions);
|
|
149
|
+
constructor(inner: SessionStore & SessionHeadReader, opts: CachingOptions);
|
|
137
150
|
acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise<AcquiredSession>;
|
|
138
151
|
touch(sessionId: string): Promise<void>;
|
|
139
152
|
release(sessionId: string): Promise<void>;
|
|
140
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* The cache's one rule: the warm entry for `sessionId`, if the store's head still names the leaf it holds; otherwise
|
|
155
|
+
* the entry is dropped (counted once via `onStaleHit`) and `undefined` sends the caller to the store. The head is read
|
|
156
|
+
* first, the cached leaf second: a write this replica commits in between can only turn a fresh verdict into a stale
|
|
157
|
+
* one (an extra wake), never the reverse.
|
|
158
|
+
*/
|
|
159
|
+
private freshHit;
|
|
160
|
+
/** Drop a session from the warm cache. */
|
|
141
161
|
evict(sessionId: string): void;
|
|
142
162
|
get size(): number;
|
|
143
163
|
dispose(): void;
|
|
@@ -5,8 +5,10 @@ export class CachingSessionStore {
|
|
|
5
5
|
pending = new Map();
|
|
6
6
|
ttlMs;
|
|
7
7
|
timer;
|
|
8
|
+
onStaleHit;
|
|
8
9
|
ownerOf;
|
|
9
10
|
sessionFaceOf;
|
|
11
|
+
incarnationOf;
|
|
10
12
|
register;
|
|
11
13
|
listSessions;
|
|
12
14
|
fork;
|
|
@@ -34,6 +36,8 @@ export class CachingSessionStore {
|
|
|
34
36
|
constructor(inner, opts) {
|
|
35
37
|
this.inner = inner;
|
|
36
38
|
this.ttlMs = Math.max(0, opts.ttlSec) * 1000;
|
|
39
|
+
this.onStaleHit = opts.onStaleHit;
|
|
40
|
+
this.getHead = inner.getHead.bind(inner);
|
|
37
41
|
const declared = inner.retention;
|
|
38
42
|
if (declared !== undefined)
|
|
39
43
|
this.retention = declared;
|
|
@@ -49,6 +53,8 @@ export class CachingSessionStore {
|
|
|
49
53
|
this.ownerOf = ownerAware.ownerOf.bind(inner);
|
|
50
54
|
if (ownerAware.sessionFaceOf)
|
|
51
55
|
this.sessionFaceOf = ownerAware.sessionFaceOf.bind(inner);
|
|
56
|
+
if (typeof inner.incarnationOf === "function")
|
|
57
|
+
this.incarnationOf = inner.incarnationOf.bind(inner);
|
|
52
58
|
if (ownerAware.register)
|
|
53
59
|
this.register = ownerAware.register.bind(inner);
|
|
54
60
|
if (ownerAware.listSessions)
|
|
@@ -69,8 +75,6 @@ export class CachingSessionStore {
|
|
|
69
75
|
this.fork = ownerAware.fork.bind(inner);
|
|
70
76
|
if (ownerAware.getLeafId)
|
|
71
77
|
this.getLeafId = ownerAware.getLeafId.bind(inner);
|
|
72
|
-
if (ownerAware.getHead)
|
|
73
|
-
this.getHead = ownerAware.getHead.bind(inner);
|
|
74
78
|
if (ownerAware.exportEntries)
|
|
75
79
|
this.exportEntries = ownerAware.exportEntries.bind(inner);
|
|
76
80
|
if (ownerAware.listEntryIds)
|
|
@@ -85,8 +89,6 @@ export class CachingSessionStore {
|
|
|
85
89
|
const imp = ownerAware.importEntries.bind(inner);
|
|
86
90
|
this.importEntries = async (sessionId, owner, entries) => {
|
|
87
91
|
await imp(sessionId, owner, entries);
|
|
88
|
-
if (this.cache.has(sessionId))
|
|
89
|
-
opts.onConflictEvict?.(sessionId);
|
|
90
92
|
this.evict(sessionId);
|
|
91
93
|
};
|
|
92
94
|
}
|
|
@@ -94,8 +96,6 @@ export class CachingSessionStore {
|
|
|
94
96
|
const rep = ownerAware.replaceEntries.bind(inner);
|
|
95
97
|
this.replaceEntries = async (sessionId, owner, entries) => {
|
|
96
98
|
await rep(sessionId, owner, entries);
|
|
97
|
-
if (this.cache.has(sessionId))
|
|
98
|
-
opts.onConflictEvict?.(sessionId);
|
|
99
99
|
this.evict(sessionId);
|
|
100
100
|
};
|
|
101
101
|
}
|
|
@@ -109,8 +109,6 @@ export class CachingSessionStore {
|
|
|
109
109
|
appendBatch: (entries) => h.appendBatch(entries),
|
|
110
110
|
commit: async (owner, o) => {
|
|
111
111
|
const r = await h.commit(owner, o);
|
|
112
|
-
if (this.cache.has(realSessionId))
|
|
113
|
-
opts.onConflictEvict?.(realSessionId);
|
|
114
112
|
this.evict(realSessionId);
|
|
115
113
|
return r;
|
|
116
114
|
},
|
|
@@ -141,7 +139,7 @@ export class CachingSessionStore {
|
|
|
141
139
|
this.store(fresh);
|
|
142
140
|
return fresh;
|
|
143
141
|
}
|
|
144
|
-
const hit = this.
|
|
142
|
+
const hit = await this.freshHit(sessionId);
|
|
145
143
|
if (hit && opts?.requireExisting !== true && this.placements !== undefined && this.placementOf !== undefined) {
|
|
146
144
|
assertPlacementAdmission(sessionId, await this.placementOf(sessionId), opts);
|
|
147
145
|
}
|
|
@@ -182,6 +180,18 @@ export class CachingSessionStore {
|
|
|
182
180
|
this.cache.delete(sessionId);
|
|
183
181
|
await this.inner.release(sessionId);
|
|
184
182
|
}
|
|
183
|
+
async freshHit(sessionId) {
|
|
184
|
+
const hit = this.cache.get(sessionId);
|
|
185
|
+
if (hit === undefined)
|
|
186
|
+
return undefined;
|
|
187
|
+
const head = await this.inner.getHead(sessionId);
|
|
188
|
+
if (head !== undefined && head.leafId === (await hit.acquired.session.getLeafId()))
|
|
189
|
+
return hit;
|
|
190
|
+
if (this.cache.get(sessionId) === hit)
|
|
191
|
+
this.cache.delete(sessionId);
|
|
192
|
+
this.onStaleHit?.(sessionId);
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
185
195
|
evict(sessionId) {
|
|
186
196
|
this.cache.delete(sessionId);
|
|
187
197
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
|
-
import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
|
|
3
|
+
import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
|
|
4
4
|
import { type ParkRowFacts, type RuleOfferProjection } from "../approval-card.js";
|
|
5
5
|
import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
|
|
6
6
|
export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
@@ -127,17 +127,14 @@ export interface PendingCheckpoint extends ParkRowFacts {
|
|
|
127
127
|
*/
|
|
128
128
|
hasBidiControls?: true;
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* `
|
|
135
|
-
*
|
|
136
|
-
* (普通工具门 / 非工具 park / toolName 缺席的存量行)。展示/分诊用,永不参与 resume / gate / CAS。
|
|
137
|
-
* 两条 durable 读面(`GET /v1/approvals` 行 + `/stream` `pending` 帧)经同一投影同时携带;live 腿的
|
|
138
|
-
* 同名键在 `LivePendingRow.contentKind`(同一词属主)。
|
|
130
|
+
* core 7.49.0 #1414:**这一行渲在哪张卡上** —— 门上引擎铸的 `gate.approvalCard`(闭集 core `TOOL_APPROVAL_CARDS`;`question` = 问用户的
|
|
131
|
+
* 问题,不是副作用工具),即 core `summarizeCheckpoint` 投给 `CheckpointSummary.approvalCard` 的同一个词。读法单点 = `approval-content-kind.ts`
|
|
132
|
+
* 的 `parkedApprovalCard`(只在审批门型上读、闭集窄读;两只店同调它:SQL 孪生读 `checkpoint` 列的 `gate` 子树,LOCAL 孪生读 blob 上的门)。
|
|
133
|
+
* 🔴 缺席即缺席([ref]② OMIT 纪律):非审批门、7.49.0 之前停驻的存量行(门上无卡 —— core 的读法「无卡 = 不是一问」,本仓**不**从
|
|
134
|
+
* 工具名回推)。两条 durable 读面(`GET /v1/approvals` 行 + `/stream` `pending` 帧)经同一投影同时携带;live 腿的同名键在
|
|
135
|
+
* `LivePendingRow.approvalCard`。展示/分诊用,永不参与 resume / gate / CAS。(此前此处是按 `tool_name` 列派生的 `contentKind`,随 #1414 删。)
|
|
139
136
|
*/
|
|
140
|
-
|
|
137
|
+
approvalCard?: ToolApprovalCard;
|
|
141
138
|
}
|
|
142
139
|
/**
|
|
143
140
|
* [ref]([ref] / DEBTS [ref]):session 键读口(`findPendingTokenBySession` / `peekPendingScope`)的**行绑定**。
|
|
@@ -416,8 +413,8 @@ interface SteerColumnRead {
|
|
|
416
413
|
* **一列一形**:`pending_steer_queue` 存 `PendingSteerEntry[]`。
|
|
417
414
|
*
|
|
418
415
|
* 🔴 7.74.0 起只剩这**一**列。队列化之前的裸单座列 `pending_steer` 随它的席位一起退役了(core 7.17.0
|
|
419
|
-
* [ref] d:引擎对带席的行 PRE-CAS 拒 `seat_retired`)
|
|
420
|
-
* (`plugins/one-time-migrations.ts
|
|
416
|
+
* [ref] d:引擎对带席的行 PRE-CAS 拒 `seat_retired`),存量列 7.74.0 起由启动期的一次性迁移删掉;迁移窗关后删码,
|
|
417
|
+
* 仍带着这一列的跳版升级库起服**响亮拒**(`plugins/one-time-migrations.ts` 的退役 schema 拒启臂)。
|
|
421
418
|
* ⇒ 这里刻意**没有**第二条读腿:双读臂的寿命就是那一次迁移,不是永远。
|
|
422
419
|
*
|
|
423
420
|
* 列有内容却读不出 ⇒ `lossy`(两个调用点对它的处置见 {@link SteerColumnRead})。
|
|
@@ -623,29 +620,46 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
623
620
|
*
|
|
624
621
|
* [ref] D-D: the deadline-branch EXPIRES (≈ abort) every kind EXCEPT a tool-approval human/irreversible_ask
|
|
625
622
|
* gate — those are resolve-DENIED gracefully by the SLA deny-sweep (listExpiredApprovalGates →
|
|
626
|
-
* resumeCheckpoint(deny)), so expiring them here too would race + lose the graceful denial.
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
623
|
+
* resumeCheckpoint(deny)), so expiring them here too would race + lose the graceful denial. Legacy rows (gate_kind
|
|
624
|
+
* NULL) stay on the expire path. The terminal_at_ms-branch is the crash-safe backstop for ANY kind (incl. a human
|
|
625
|
+
* gate whose deny-resume keeps failing) — it always abort-expires past the absolute cap (which is now STRICTLY
|
|
626
|
+
* after the deadline, so it never races the deny-sweep at the deadline instant).
|
|
627
|
+
*
|
|
628
|
+
* core 7.49.0 #1414 / #1468 A-S4: among the approval kinds, which rows the deny-sweep owns has ONE judge — core
|
|
629
|
+
* `facetOf(gate)`. A gate carrying the `question` card is facet `B` (a question's deadline voids it; denying a question
|
|
630
|
+
* is incoherent), so step ② expires it here row by row (token CAS, `status='pending'` guarded — a concurrent decide or
|
|
631
|
+
* another replica wins cleanly). The card lives only in the `checkpoint` blob's `gate` subtree, so the split cannot be
|
|
632
|
+
* one SQL predicate without writing core's judgment a second time in SQL; the old `tool_name` literal arm is gone. A
|
|
633
|
+
* gate with no card (parked before 7.49.0) is no question — core's reading — and stays with the deny-sweep.
|
|
633
634
|
*/
|
|
634
635
|
reapExpired(cutoff: number): Promise<number>;
|
|
636
|
+
/** reapExpired ②'s position in the overdue approval rows, per store instance (see there). Absent = start from the earliest deadline. */
|
|
637
|
+
private reapCursor;
|
|
635
638
|
/**
|
|
636
|
-
* [ref] D-D (SLA-timer): the sessions of pending
|
|
637
|
-
*
|
|
638
|
-
* with the denial) — graceful, vs the abort that expire() gives
|
|
639
|
-
* rows (gate_kind NULL — they predate the split and fall through to
|
|
640
|
-
* the per-tick deny-resume work (each is a model run).
|
|
641
|
-
* 🔴
|
|
642
|
-
*
|
|
643
|
-
*
|
|
639
|
+
* [ref] D-D (SLA-timer): the sessions of pending approval-kind checkpoints past their `deadline` (the per-approval
|
|
640
|
+
* SLA) that the deny-sweep owns ({@link denySweepOwns} — core `facetOf` reads them as facet `A`) — the reaper resolve-DENIES
|
|
641
|
+
* each (re-resume with decision=deny → the model continues with the denial) — graceful, vs the abort that expire() gives
|
|
642
|
+
* resource_limit/needs_review. Excludes legacy rows (gate_kind NULL — they predate the split and fall through to
|
|
643
|
+
* reapExpired's expire path). `limit` bounds the per-tick deny-resume work (each is a model run).
|
|
644
|
+
* 🔴 core 7.49.0 #1414 / #1468 A-S4: a gate carrying the `question` card (facet `B`) is NOT deny-swept — core refuses
|
|
645
|
+
* `sla_timeout` on it pre-CAS — reapExpired voids it instead. The card lives only in the blob's `gate` subtree and the
|
|
646
|
+
* judge is core's (the split is not one SQL predicate — see {@link reapExpired}), so the filter runs on the rows read;
|
|
647
|
+
* the read is therefore PAGED, filter first and `limit` after: pages of {@link REAP_CLASSIFY_BATCH} rows in the stable
|
|
648
|
+
* (deadline, token) order, the cursor advancing, until `limit` rows are collected or the overdue rows run out — the same
|
|
649
|
+
* "filter, then take `limit`" the LOCAL twin does. A backlog of question rows ahead of the deny-sweep's rows costs extra
|
|
650
|
+
* bounded pages, never a hidden deny-sweep row (they used to hold slots of a `LIMIT limit` window until voided).
|
|
644
651
|
*/
|
|
645
652
|
listExpiredApprovalGates(cutoff: number, limit?: number): Promise<Array<{
|
|
646
653
|
sessionId: string;
|
|
647
654
|
scope: string;
|
|
648
655
|
}>>;
|
|
656
|
+
/**
|
|
657
|
+
* core 7.49.0 #1414 / #1468 A-S4 —— 审批门型里过了 deadline 的 pending 行(deadline 升序),每行带 core 对它的到期读法
|
|
658
|
+
* (`denySweepOwns` = 审批门型 ∧ core `facetOf` 读作 `A`,门型取列、卡取 `gate` 子树)。到期两条腿(reapExpired 的 ②、拒扫腿的选行)读同一只:
|
|
659
|
+
* 对审批门型「谁拒扫、谁作废」本仓不写第二份判据。读恒有界(每次一页 `limit` 行),序 = (deadline, token) 稳定序;`after` = 从这一位置之后读
|
|
660
|
+
* (reapExpired 的跨拍游标;拒扫腿选行一次之内的翻页游标)。
|
|
661
|
+
*/
|
|
662
|
+
private overdueApprovalRows;
|
|
649
663
|
/** Pending checkpoints for the operator queue — NON-secret fields only (never the token). `taskId` is
|
|
650
664
|
* joined from task_active (the JOIN KEY to the run/trace — a suspended run KEEPS its
|
|
651
665
|
* session claim, so the join is live for every pending row; null only in pathological windows). */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parkedApprovalCard, parkedFacetOf } from "../approval-content-kind.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { APPROVAL_GATE_KINDS_SQL_IN } from "../tool-approval.js";
|
|
3
|
+
import { APPROVAL_GATE_KINDS_SQL_IN, denySweepOwns } from "../tool-approval.js";
|
|
4
4
|
import { CheckpointError, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, checkpointVersionOf, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
5
5
|
import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
6
6
|
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, RuleOfferRawSchema, backfillLegacyBatchMemberKind, parkRowFacts, screenUncoveredDetailSeat } from "../approval-card.js";
|
|
@@ -10,6 +10,7 @@ import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.
|
|
|
10
10
|
import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
|
|
11
11
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
12
12
|
import { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
13
|
+
const REAP_CLASSIFY_BATCH = 200;
|
|
13
14
|
export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
|
|
14
15
|
const MAX_TOOL_INPUT_CHARS = 8192;
|
|
15
16
|
export const TERMINAL_BACKSTOP_MS = Math.max(60_000, (Number.isFinite(Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS)) ? Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS) : 0) || 30 * 86_400_000);
|
|
@@ -182,6 +183,22 @@ function readParkRowFactsCell(cell) {
|
|
|
182
183
|
return parkRowFacts(pendingAction);
|
|
183
184
|
}
|
|
184
185
|
const PARK_ROW_PENDING_ACTION = { key: "pendingAction", as: "pending_action" };
|
|
186
|
+
const PARK_ROW_GATE = { key: "gate", as: "park_gate" };
|
|
187
|
+
function readParkGateCell(cell) {
|
|
188
|
+
try {
|
|
189
|
+
return decodeNullableJsonColumn(cell, readParkGateMemberBody, "checkpoint.checkpoint $.gate (approval card / expiry facet)");
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
if (!isJsonColumnBodyProblem(e))
|
|
193
|
+
throw e;
|
|
194
|
+
recordFailOpen("server.approvals.park-row-blob-unreadable", "member=gate");
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const readParkGateMemberBody = createObjectColumnReader();
|
|
199
|
+
function parkedGateOfRow(r) {
|
|
200
|
+
return { kind: r.gate_kind ?? undefined, approvalCard: readParkGateCell(r[PARK_ROW_GATE.as])?.approvalCard };
|
|
201
|
+
}
|
|
185
202
|
const readPendingActionMemberBody = createObjectColumnReader();
|
|
186
203
|
export function boundedToolInput(args) {
|
|
187
204
|
if (args === undefined)
|
|
@@ -518,21 +535,49 @@ export class SqlCheckpointStore {
|
|
|
518
535
|
}
|
|
519
536
|
async reapExpired(cutoff) {
|
|
520
537
|
const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'expired', decided_at_ms = ? " +
|
|
521
|
-
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND (gate_kind IS NULL OR gate_kind NOT IN ${APPROVAL_GATE_KINDS_SQL_IN}
|
|
522
|
-
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= $2 AND (gate_kind IS NULL OR gate_kind NOT IN ${APPROVAL_GATE_KINDS_SQL_IN}
|
|
523
|
-
|
|
538
|
+
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND (gate_kind IS NULL OR gate_kind NOT IN ${APPROVAL_GATE_KINDS_SQL_IN})) OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= ?))`, "UPDATE checkpoint SET status = 'expired', decided_at_ms = $1 " +
|
|
539
|
+
`WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= $2 AND (gate_kind IS NULL OR gate_kind NOT IN ${APPROVAL_GATE_KINDS_SQL_IN})) OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= $3))`), [Date.now(), cutoff, cutoff]);
|
|
540
|
+
let n = res.affected;
|
|
541
|
+
const batch = await this.overdueApprovalRows(cutoff, REAP_CLASSIFY_BATCH, this.reapCursor);
|
|
542
|
+
for (const row of batch) {
|
|
543
|
+
if (!row.denySweeps && (await this.expire(row.token, row.scope)))
|
|
544
|
+
n++;
|
|
545
|
+
}
|
|
546
|
+
const last = batch.at(-1);
|
|
547
|
+
this.reapCursor = batch.length < REAP_CLASSIFY_BATCH || last === undefined ? undefined : { deadline: last.deadline, token: last.token };
|
|
548
|
+
return n;
|
|
524
549
|
}
|
|
550
|
+
reapCursor;
|
|
525
551
|
async listExpiredApprovalGates(cutoff, limit = 100) {
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
552
|
+
const hits = [];
|
|
553
|
+
let after;
|
|
554
|
+
while (hits.length < limit) {
|
|
555
|
+
const page = await this.overdueApprovalRows(cutoff, REAP_CLASSIFY_BATCH, after);
|
|
556
|
+
for (const r of page) {
|
|
557
|
+
if (hits.length < limit && r.denySweeps)
|
|
558
|
+
hits.push({ sessionId: r.sessionId, scope: r.scope });
|
|
559
|
+
}
|
|
560
|
+
const last = page.at(-1);
|
|
561
|
+
if (page.length < REAP_CLASSIFY_BATCH || last === undefined)
|
|
562
|
+
break;
|
|
563
|
+
after = { deadline: last.deadline, token: last.token };
|
|
564
|
+
}
|
|
565
|
+
return hits;
|
|
566
|
+
}
|
|
567
|
+
async overdueApprovalRows(cutoff, limit, after) {
|
|
568
|
+
const head = `SELECT c.token, c.session_id, c.scope, c.gate_kind, c.deadline, ${jsonColumnText(this.db.dialect, "checkpoint", PARK_ROW_GATE)} FROM checkpoint c ` +
|
|
569
|
+
`WHERE c.status = 'pending' AND c.gate_kind IN ${APPROVAL_GATE_KINDS_SQL_IN} AND c.deadline IS NOT NULL AND `;
|
|
570
|
+
const { rows } = await this.db.query(after === undefined
|
|
571
|
+
? head + this.q("c.deadline <= ? ORDER BY c.deadline ASC, c.token ASC LIMIT ?", "c.deadline <= $1 ORDER BY c.deadline ASC, c.token ASC LIMIT $2")
|
|
572
|
+
: head +
|
|
573
|
+
this.q("c.deadline <= ? AND (c.deadline > ? OR (c.deadline = ? AND c.token > ?)) ORDER BY c.deadline ASC, c.token ASC LIMIT ?", "c.deadline <= $1 AND (c.deadline > $2 OR (c.deadline = $3 AND c.token > $4)) ORDER BY c.deadline ASC, c.token ASC LIMIT $5"), after === undefined ? [cutoff, limit] : [cutoff, after.deadline, after.deadline, after.token, limit]);
|
|
574
|
+
return rows.map((r) => ({ token: String(r.token), sessionId: String(r.session_id), scope: String(r.scope), deadline: Number(r.deadline), denySweeps: denySweepOwns(parkedGateOfRow(r)) }));
|
|
530
575
|
}
|
|
531
576
|
async listPending(scope) {
|
|
532
577
|
const d = this.db.dialect;
|
|
533
578
|
const base = `SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
|
|
534
579
|
`${jsonColumnText(d, "risk_descriptor")}, ${jsonColumnText(d, "rule_suggestions")}, c.has_bidi_controls, c.created_at_ms, c.deadline, c.gate_kind, ta.task_id, ` +
|
|
535
|
-
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)} ` +
|
|
580
|
+
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)} ` +
|
|
536
581
|
"FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
|
|
537
582
|
const { rows } = scope
|
|
538
583
|
? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
|
|
@@ -543,6 +588,7 @@ export class SqlCheckpointStore {
|
|
|
543
588
|
const boundInputHash = r.bound_input_hash ?? null;
|
|
544
589
|
const gateKind = r.gate_kind ?? null;
|
|
545
590
|
const ruleOffers = readRuleOffersCell(r.rule_suggestions);
|
|
591
|
+
const approvalCard = parkedApprovalCard(parkedGateOfRow(r));
|
|
546
592
|
return {
|
|
547
593
|
sessionId: String(r.session_id),
|
|
548
594
|
scope: String(r.scope),
|
|
@@ -558,7 +604,7 @@ export class SqlCheckpointStore {
|
|
|
558
604
|
riskDescriptor: readRiskDescriptorCell(r.risk_descriptor),
|
|
559
605
|
...(ruleOffers !== undefined ? { ruleOffers } : {}),
|
|
560
606
|
...(Number(r.has_bidi_controls) === 1 ? { hasBidiControls: true } : {}),
|
|
561
|
-
...(
|
|
607
|
+
...(approvalCard !== undefined ? { approvalCard } : {}),
|
|
562
608
|
...readParkRowFactsCell(r[PARK_ROW_PENDING_ACTION.as]),
|
|
563
609
|
};
|
|
564
610
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import { StoreBootRefusalError, type CreateRunOutcome, type NewRunMeta, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
3
|
+
import { StoreBootRefusalError, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
|
|
5
5
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
6
|
+
import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
|
|
6
7
|
/**
|
|
7
8
|
* `RUN_STORE_STRICT_HYDRATE=on` 下 hydrate 列不出 `runs/` 时的**拒启**类型([ref] 件B;codex 对抗复审
|
|
8
9
|
* R1-[high] 验真后从裸 `Error` 升成具名类型)。
|
|
@@ -25,6 +26,11 @@ export declare class FileRunStore {
|
|
|
25
26
|
private readonly runsDir;
|
|
26
27
|
private readonly activeDir;
|
|
27
28
|
private readonly tmpDir;
|
|
29
|
+
/** `<root>/space-commits/` — the twin of `workspace_space_commit`: one record per space key (file name = the key,
|
|
30
|
+
* checked by `sanitizePathComponent`; written by `atomicWriteFile`). */
|
|
31
|
+
private readonly spaceCommitsDir;
|
|
32
|
+
/** The k8s workspace space's commit fence (the rule: `workspace-space-commit.ts`) over the record files. */
|
|
33
|
+
private readonly spaceCommits;
|
|
28
34
|
/** taskId → RunRow (the run registry index). */
|
|
29
35
|
private readonly runs;
|
|
30
36
|
/** sessionId → taskId — the single-active-run claim (the task_active table; index of active/*). */
|
|
@@ -55,6 +61,15 @@ export declare class FileRunStore {
|
|
|
55
61
|
private runJsonPath;
|
|
56
62
|
private eventsPath;
|
|
57
63
|
private activePath;
|
|
64
|
+
private spaceCommitPath;
|
|
65
|
+
/** One space's commit record, or `undefined` (no file). A record that is not this store's shape is a loud read failure
|
|
66
|
+
* (the reader — the next run's connect — refuses; it never guesses that no commit is in flight). */
|
|
67
|
+
private loadSpaceCommit;
|
|
68
|
+
/** The k8s workspace space's commit fence rows (SQL twin: `workspace_space_commit`; the rule: `workspace-space-commit.ts`). */
|
|
69
|
+
readSpaceCommit(spaceKey: string): Promise<SpaceCommitReading | undefined>;
|
|
70
|
+
claimSpaceCommit(spaceKey: string, taskId: string, state: SpaceCommitState, claim: SpaceCommitClaim): Promise<boolean>;
|
|
71
|
+
endSpaceCommit(spaceKey: string, taskId: string): Promise<void>;
|
|
72
|
+
abandonSpaceCommit(spaceKey: string, taskId: string): Promise<boolean>;
|
|
58
73
|
private toRow;
|
|
59
74
|
private toDisk;
|
|
60
75
|
private toRecord;
|
|
@@ -120,6 +135,9 @@ export declare class FileRunStore {
|
|
|
120
135
|
/** [ref] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
|
|
121
136
|
* [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
|
|
122
137
|
setTerminal(taskId: string, status: TaskStatus, result: PersistedTaskResult | null, error: string | null): Promise<void>;
|
|
138
|
+
/** The terminal write's one synchronous section (SQL twin: one transaction): the k8s space's pending commit record (when
|
|
139
|
+
* offered) → the terminal row → the active-run claim release. Returns whether the pending record is now this run's. */
|
|
140
|
+
private writeTerminal;
|
|
123
141
|
/** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard).
|
|
124
142
|
* S-173:同时落 `result` —— 封口脱敏后的投影(`sealRunRowResult`,凭据不落;`null` = 没观察到 park 结果),SQL 孪生同注。 */
|
|
125
143
|
setSuspended(taskId: string, result: PersistedTaskResult | null): Promise<void>;
|
|
@@ -131,11 +149,15 @@ export declare class FileRunStore {
|
|
|
131
149
|
markResuming(taskId: string, instanceId: string): Promise<boolean>;
|
|
132
150
|
getActiveTaskId(sessionId: string): Promise<string | undefined>;
|
|
133
151
|
getRun(taskId: string): Promise<RunRecord | undefined>;
|
|
134
|
-
/** Undo THIS submission's
|
|
152
|
+
/** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
|
|
135
153
|
* before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, a device-placement reject):
|
|
136
154
|
* the error answer invites a retry, and a retry with the same key must run, not replay the refusal. The row stays (it
|
|
137
|
-
* is the refusal's record); only the
|
|
155
|
+
* is the refusal's record); only the binding is freed. */
|
|
138
156
|
releaseIdemKey(taskId: string): Promise<void>;
|
|
157
|
+
/** The body fingerprint this row's binding of `idem.key` holds — binding `idem.bodyFingerprint` first when it holds none (first
|
|
158
|
+
* arrival binds and is persisted, everyone after reads; the file twin of the SQL conditional UPDATE — one writer per data root,
|
|
159
|
+
* single-threaded between the check and the write). `null` = no such row, or the row is not bound to this key (never written). */
|
|
160
|
+
bindIdemBodyFingerprint(taskId: string, idem: RunIdemBinding): Promise<string | null>;
|
|
139
161
|
/** The run a submission's scoped `Idempotency-Key` names (the file twin of `uq_task_run_idem`), if any. */
|
|
140
162
|
getRunByIdem(idemKey: string): Promise<RunRecord | undefined>;
|
|
141
163
|
/** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
|
|
@@ -312,24 +334,6 @@ export declare class FileRunStore {
|
|
|
312
334
|
private releaseClaim;
|
|
313
335
|
/** Drop a run entirely (registry + event log + open fd + on-disk dir) — used by deleteBySession. */
|
|
314
336
|
private dropRun;
|
|
315
|
-
/**
|
|
316
|
-
* 🔴 **一次性迁移 M3 的 local 腿(窗口见 `one-time-migrations.ts` 登记表,随 M3 一起删)** —— 退役词旧拼法的账本改写,
|
|
317
|
-
* SQL 两孪生那条整表 UPDATE 的 local 形。逐条走**同一只**纯改写函数,所以三条车道改出来的字节一致。
|
|
318
|
-
*
|
|
319
|
-
* 为什么是 store 上的一只动词、而不是外面一个文件走查:本店的账本有**两份**在场 —— 盘上的 jsonl
|
|
320
|
-
* 与 hydrate 进来的内存索引。外部走查只改得到前者,读腿读的却是后者(`getEvents` 从 Map 取),
|
|
321
|
-
* 于是迁移在本次进程里完全不生效、下次重启才"忽然"生效。两份一起改的唯一正确位置就是这里。
|
|
322
|
-
*
|
|
323
|
-
* 写盘用整文件原子替换(不是追加):改的是**已有行**,而 `AppendLog` 只会往后写。替换之前把**全部**
|
|
324
|
-
* append fd 撤掉(rename 之后旧 fd 指向的是被 unlink 的 inode,继续往那儿追加 = 之后的事件全部写进一个
|
|
325
|
-
* 没人看得见的文件),下一次 `appendEvent` 会按惰性规则重新开。撤 fd 复用**既有的那一只** {@link dispose}
|
|
326
|
-
* —— 刻意不在这里另写一段 close+catch:那会是本文件第二处同形的兜底臂,而规则要变少不要变多。
|
|
327
|
-
* 常态下这一撤是空操作:本动词跑在 boot 期,那时一个 append fd 都还没惰性开出来。
|
|
328
|
-
*
|
|
329
|
-
* 幂等:零命中 ⇒ 一个字节都不写、一只 fd 都不撤(第二跑零 I/O)。
|
|
330
|
-
* @returns 被改写的账本**行**数(不是 run 数)。
|
|
331
|
-
*/
|
|
332
|
-
migrateRetiredAskOriginRows(): Promise<number>;
|
|
333
337
|
/**
|
|
334
338
|
* 一次性迁移 **M6 的 local 腿**(窗、删法与删后谁接住:窗口见 `plugins/one-time-migrations.ts` 登记表):把 ≤7.115.x 写下的死亡判决行
|
|
335
339
|
* (`failed` ∧ 码列空 ∧ 句子是那两句,判据 = `readLegacyHolderDeathCode`)补上机读码,盘上(`run.json` 整份原子替换,同 `persist`)与内存同改。
|