@sema-agent/server 7.143.0 → 7.145.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 +295 -0
- package/README.md +3 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +34 -16
- package/dist/approval-card.d.ts +31 -14
- package/dist/approval-card.js +8 -8
- package/dist/audit.d.ts +2 -21
- package/dist/audit.js +0 -9
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/runner-deps.d.ts +10 -5
- package/dist/boot/runner-deps.js +5 -4
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +3 -3
- package/dist/boot/shutdown.d.ts +1 -1
- package/dist/boot/shutdown.js +4 -2
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-07-capability-layer.js +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +4 -9
- package/dist/capabilities/scenarios.d.ts +14 -18
- package/dist/capabilities/scenarios.js +1 -2
- package/dist/config-d-migration.d.ts +3 -0
- package/dist/delegated-session-owner.d.ts +33 -151
- package/dist/delegated-session-owner.js +1 -83
- package/dist/entry-mode-clamp-notice.d.ts +1 -1
- package/dist/execution-placement.d.ts +7 -4
- package/dist/fleet/fleet-bus.d.ts +13 -3
- package/dist/fleet/fleet-bus.js +17 -3
- package/dist/hooks/classic-stations.d.ts +3 -1
- package/dist/hooks/classic-stations.js +2 -0
- package/dist/http/admission.js +2 -1
- package/dist/http/checkpoint-ctx.d.ts +1 -0
- package/dist/http/checkpoint-ctx.js +1 -0
- package/dist/http/durable-run-leg.js +7 -6
- package/dist/http/principal-gate.d.ts +1 -1
- package/dist/http/refusal-body.d.ts +7 -3
- package/dist/http/refusal-body.js +1 -0
- package/dist/http/resume-legs.js +16 -7
- package/dist/http/route-ctx.d.ts +15 -0
- package/dist/http/routes/approvals-assistant.d.ts +20 -1
- package/dist/http/routes/approvals-assistant.js +28 -6
- package/dist/http/routes/rules.d.ts +1 -1
- package/dist/http/routes/rules.js +5 -2
- package/dist/http/routes/session-sync.js +12 -0
- package/dist/http/routes/tasks.js +12 -9
- package/dist/http/run-meta.d.ts +14 -7
- package/dist/http/run-meta.js +5 -2
- package/dist/http/server.d.ts +13 -5
- package/dist/http/server.js +3 -0
- package/dist/http/submit-ctx.d.ts +12 -0
- package/dist/http/submit-ctx.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lane-home-probe.d.ts +15 -0
- package/dist/lane-home-probe.js +4 -0
- package/dist/observability/corrupt-read-seat.d.ts +5 -9
- package/dist/observability/corrupt-read-seat.js +1 -2
- package/dist/observability/fail-open.d.ts +26 -10
- package/dist/observability/fail-open.js +26 -10
- package/dist/observability/run-terminal.d.ts +1 -1
- package/dist/observability/run-terminal.js +2 -2
- package/dist/parked-revive-surface.d.ts +6 -7
- package/dist/peer-inbound-face.d.ts +29 -34
- package/dist/peer-inbound-face.js +28 -63
- package/dist/plugins/approval-ask-store-file.d.ts +16 -2
- package/dist/plugins/approval-ask-store-file.js +21 -1
- package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
- package/dist/plugins/approval-ask-store-memory.js +11 -8
- package/dist/plugins/caching-session-store.d.ts +3 -3
- package/dist/plugins/caching-session-store.js +3 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +62 -37
- package/dist/plugins/checkpoint-store-sql.js +123 -40
- package/dist/plugins/file-run-store.d.ts +13 -1
- package/dist/plugins/file-run-store.js +11 -1
- package/dist/plugins/fork-routing-session-store.d.ts +8 -1
- package/dist/plugins/fork-routing-session-store.js +6 -0
- package/dist/plugins/local-checkpoint-store.d.ts +17 -8
- package/dist/plugins/local-checkpoint-store.js +26 -7
- package/dist/plugins/local-session-store.d.ts +105 -34
- package/dist/plugins/local-session-store.js +168 -65
- package/dist/plugins/local-session-window.d.ts +4 -2
- package/dist/plugins/memory-run-store.d.ts +2 -0
- package/dist/plugins/memory-run-store.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +12 -12
- package/dist/plugins/one-time-migrations.js +13 -13
- package/dist/plugins/pg-session-storage.d.ts +1 -9
- package/dist/plugins/pg-session-storage.js +2 -19
- package/dist/plugins/remote-env-host.d.ts +8 -4
- package/dist/plugins/remote-env-host.js +7 -9
- package/dist/plugins/remote-shell.js +12 -2
- package/dist/plugins/run-store-sql.d.ts +4 -0
- package/dist/plugins/run-store-sql.js +3 -0
- package/dist/plugins/schema-consistency.d.ts +5 -1
- package/dist/plugins/session-fork.d.ts +1 -1
- package/dist/plugins/session-ownership.d.ts +9 -0
- package/dist/plugins/session-ownership.js +6 -0
- package/dist/plugins/session-store.d.ts +13 -16
- package/dist/plugins/session-store.js +9 -10
- package/dist/plugins/sql-json-column.d.ts +24 -2
- package/dist/plugins/sql-json-column.js +38 -3
- package/dist/plugins/store-backend.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -14
- package/dist/plugins/tidb-session-store.js +2 -20
- package/dist/prompt-screen.d.ts +21 -0
- package/dist/prompt-screen.js +8 -0
- package/dist/rules-consent.d.ts +63 -51
- package/dist/rules-consent.js +6 -10
- package/dist/run-acceptance.d.ts +40 -10
- package/dist/run-acceptance.js +38 -5
- package/dist/run-local.js +2 -1
- package/dist/runs.js +5 -1
- package/dist/security.d.ts +10 -7
- package/dist/session-agent.d.ts +39 -0
- package/dist/session-agent.js +16 -0
- package/dist/session-error-wire.d.ts +25 -26
- package/dist/session-error-wire.js +4 -5
- package/dist/session-incarnation.d.ts +2 -2
- package/dist/session-notify-door.d.ts +2 -2
- package/dist/session-notify-door.js +5 -1
- package/dist/tool-approval.d.ts +3 -1
- package/dist/tool-approval.js +47 -18
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -1
- package/dist/trace/ledger-sink.js +12 -4
- package/dist/trace/model-segment.d.ts +26 -6
- package/dist/trace/model-segment.js +58 -9
- package/dist/trace/project.d.ts +8 -2
- package/dist/trace/project.js +6 -4
- package/dist/trace/redact.d.ts +77 -2
- package/dist/trace/redact.js +234 -99
- package/dist/trace/self-minted-wire.d.ts +160 -9
- package/dist/trace/self-minted-wire.js +52 -3
- package/package.json +3 -3
- package/wire-changes/server@7.144.0.json +291 -0
- package/wire-changes/server@7.145.0.json +146 -0
- package/dist/session-records-skipped-notice.d.ts +0 -21
- package/dist/session-records-skipped-notice.js +0 -27
|
@@ -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, type ToolApprovalCard } from "@sema-agent/core";
|
|
3
|
+
import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointRowVerdict, 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, type SkillCardFacts } from "../approval-card.js";
|
|
5
5
|
import type { KindlessBatchRowSource, KindlessBatchScan } from "../approval-batch-census.js";
|
|
6
6
|
import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
|
|
@@ -174,6 +174,16 @@ export interface PendingCheckpoint extends ParkRowFacts, SkillCardFacts {
|
|
|
174
174
|
* `LivePendingRow.approvalCard`。展示/分诊用,永不参与 resume / gate / CAS。(此前此处是按 `tool_name` 列派生的 `contentKind`,随 [ref] 删。)
|
|
175
175
|
*/
|
|
176
176
|
approvalCard?: ToolApprovalCard;
|
|
177
|
+
/**
|
|
178
|
+
* 这一行**本引擎续不续得了** —— core 判官 `judgeCheckpointRow` 对这一行盘上字节的判决(core 7.62.0 [ref];与助手收件箱行的 `rowVerdict`、
|
|
179
|
+
* 续跑在第一级读到的拒同一个读者、同一答),逐字投、本服务不重算不筛。判官要整行:SQL 孪生按 (库内算的 `checkpoint` 文本 sha256, 引擎构建)
|
|
180
|
+
* 记忆({@link StoredRowProjectionMemo}),未命中才按令牌批读整行;LOCAL 孪生取 core 店摘要上的同一位。
|
|
181
|
+
* 盘上字节读不成一只检查点的行(体是 `{}`、缺 `state`、JSON 字面 `null` / 标量 / 数组 / 非 JSON)同样判:判官对任何值都答,这种行答 `damaged`,
|
|
182
|
+
* 行照列(计 `server.approvals.park-row-blob-unreadable`)—— 一行坏字节只坏它自己那一行,见 {@link SqlCheckpointStore.readStoredRow}。
|
|
183
|
+
* **在场 ⇔ 这一次列表判过这一行**:缺席只在 SQL 孪生上出现 —— 两次读之间行已离开待决,或两次读之间行的字节变了(判过的是另一份,
|
|
184
|
+
* 不拼到这一行上;下一次列表即带)。缺席**不是** `ok`。非 `ok` 的行照列(人要看到它停在哪)。
|
|
185
|
+
*/
|
|
186
|
+
rowVerdict?: CheckpointRowVerdict;
|
|
177
187
|
}
|
|
178
188
|
/**
|
|
179
189
|
* [ref]([ref] / DEBTS [ref]):session 键读口(`findPendingTokenBySession` / `peekPendingScope`)的**行绑定**。
|
|
@@ -422,24 +432,8 @@ export declare const DECIDED_REPLAY_SCAN_CAP = 8;
|
|
|
422
432
|
* (plan_review / dry_run_review)的判词,那些门的决议入口不是本腿,回放它们等于跨门作答。
|
|
423
433
|
*/
|
|
424
434
|
export declare function approvalDecisionOfWinner(winner: ResolvedOutcome | undefined, boundCallId: string): "approve" | "deny" | null;
|
|
425
|
-
/**
|
|
426
|
-
|
|
427
|
-
*
|
|
428
|
-
* 🔴 三态、**不是**两态(真双库实跑抓到的缺陷,2026-08-06):「读不出」与「读出来了、但这条 park 本来就
|
|
429
|
-
* 没有 toolCallId」是两件事,压成同一个 `undefined` 会把每一条 `plan_review`/`task_done`/`resource_limit`
|
|
430
|
-
* 腿(core 的 `PendingAction` 联合里三个成员结构上就没有 toolCallId)都误报成坏行。前者应标 `unparseable`
|
|
431
|
-
* 交给收敛器当「不确定」,后者是**确定的不匹配**——干净地不是候选。
|
|
432
|
-
* - `{ readable: false }` —— JSON 坏 / 形状根本不是对象:真的读不出。
|
|
433
|
-
* - `{ readable: true, toolCallId: null }` —— 读出来了,这条 park 无工具动作:确定不匹配。
|
|
434
|
-
* - `{ readable: true, toolCallId: "…" }` —— 读出来了,拿去比。
|
|
435
|
-
*
|
|
436
|
-
* 逐层 `typeof` 收窄,不做裸 `as` 断言:blob 是持久层读回来的 `unknown`,是信任边界(宪法 [ref])。
|
|
437
|
-
*
|
|
438
|
-
* 🔴 [ref]:入参是**已经解过码的体**,不是一格列文本 —— 本函数不再自己 `JSON.parse`(SQL 车道的解码
|
|
439
|
-
* 唯一属主是 `sql-json-column.ts`,见 {@link pendingActionOfColumn};local 孪生本来就递一只对象进来)。
|
|
440
|
-
* 于是「串就再解一次」这条读法在这里按构造消失,两条车道递进来的东西同形。
|
|
441
|
-
*/
|
|
442
|
-
export declare function pendingActionToolCallId(body: unknown): {
|
|
435
|
+
/** 收敛器读口对一行的身份判词(三态 + 读不出;读法见 {@link pendingActionToolCallId} / {@link candidateIdentityOf})。 */
|
|
436
|
+
export type CandidateIdentity = {
|
|
443
437
|
readable: true;
|
|
444
438
|
toolCallId: string | null;
|
|
445
439
|
boundInputHash: string | null;
|
|
@@ -448,6 +442,14 @@ export declare function pendingActionToolCallId(body: unknown): {
|
|
|
448
442
|
} | {
|
|
449
443
|
readable: false;
|
|
450
444
|
};
|
|
445
|
+
/**
|
|
446
|
+
* 收敛器读口的**身份判词**(两只孪生同一只;`findCheckpointCandidatesForAsk` 的 SQL / LOCAL 两形都只经这里):先问 core 判官 `judgeCheckpointRow`
|
|
447
|
+
* (core 7.62.0 [ref],[ref])—— 判 `lacking`(更新的引擎能续它、本引擎缺读法:格式版本超上限,或要求表点名了本引擎不认的读法词)⇒ `{ readable: false }`:
|
|
448
|
+
* 本副本读不准这一行,交给收敛器当「不确定」,留给能续它的副本去绑(修前 SQL 孪生按 `version` 列比 `MAX_SUPPORTED_CHECKPOINT_VERSION`,
|
|
449
|
+
* 射程只有版本一轴,LOCAL 孪生没有这一臂)。`damaged` **不是**这一臂:本引擎不续这些字节(别的副本续不续,引擎没有说、本服务不替它们断言),身份读得出就照常判 —— 绑上之后续跑由判官给那句固定的拒,
|
|
450
|
+
* 比让 ask 停在 PARKING 诚实。其余三态见 {@link pendingActionToolCallId}。
|
|
451
|
+
*/
|
|
452
|
+
export declare function candidateIdentityOf(cp: Checkpoint): CandidateIdentity;
|
|
451
453
|
/** `pending_steer_queue` 列承载的 CheckpointState 字段(= core `appendPendingSteer` / `readPendingSteerQueue`
|
|
452
454
|
* 的入参形)。列是**唯一**权威(suspend 时写的 blob 从不带它)。 */
|
|
453
455
|
type SteerColumnState = Pick<CheckpointState, "pendingSteerQueue">;
|
|
@@ -536,6 +538,8 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
|
|
|
536
538
|
/** ctx 改写的认领栅栏({@link SqlCheckpointStore.rewriteCtxWhileClaimed}):后端装配绑**同一只池**上的 run 店(`plugins/store-backend.ts` 的 `checkpoint()`)。
|
|
537
539
|
* 缺席 ⇒ 那一口响亮抛(装配缺陷),其余口不读它。 */
|
|
538
540
|
private readonly claims;
|
|
541
|
+
/** 停驻行投影记忆([ref] ④;运维队列与调度视图共用,见 {@link StoredRowProjectionMemo})。进程内、按店实例。 */
|
|
542
|
+
private readonly storedRowMemo;
|
|
539
543
|
constructor(db: SqlDriver, logger?: {
|
|
540
544
|
info?(msg: string, meta?: unknown): void;
|
|
541
545
|
} | undefined, opts?: SqlCheckpointStoreOptions);
|
|
@@ -711,29 +715,50 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
|
|
|
711
715
|
*/
|
|
712
716
|
listPending(scope?: string): Promise<PendingCheckpoint[]>;
|
|
713
717
|
/**
|
|
714
|
-
*
|
|
715
|
-
* (授予绑定那一臂 —— 摘要 / 缺席词 —— 不在这里:[ref] 起由 {@link parkRowFacts} 读子树投出,本读口失败时它照在。)
|
|
718
|
+
* 运维队列 / 调度视图要的**整行事实**({@link StoredRowProjection}:判官的判决 + 技能卡两位),按令牌交回。
|
|
716
719
|
*
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
+
* 为什么要整行:判官 `judgeCheckpointRow` 读要求表要整只用(词集 + 摘要重算读整个 `state` 的表键路径,core [ref] ③);技能卡**清单**
|
|
721
|
+
* `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」才发布它,校验读口不在 core 的公开导出面上 —— 两者都只能对整只
|
|
722
|
+
* {@link Checkpoint} 跑 core 的函数(摘要 `summarizeCheckpoint` 一次给两样)。
|
|
720
723
|
*
|
|
721
|
-
* 读放大(如实)
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
+
* 读放大(如实):记忆命中零整行读;未命中的行按令牌**批**读(`IN (…)`,每批 ≤ {@link STORED_ROW_READ_BATCH}),每份字节每个进程一次 ——
|
|
725
|
+
* 起服后第一次列表把全部待决行读一遍,此后只有字节变了 / 被挤出记忆的行再读(修前:普通卡零整行读,技能卡每次列表都读;`/stream` 每连接每 3 s 一拍)。
|
|
726
|
+
* 判过的按**读回的那份字节**的摘要入记忆;两次读之间字节变了(批读的摘要 ≠ 主读交来的那一份)⇒ 判过的那一份照入记忆,但**不交回**给这一行 ——
|
|
727
|
+
* 交回的事实只许出自主读读到的同一份字节(一行一个字节版本;修前按令牌拼,B 的判决与清单配 A 的行),这一行本次不带判决与清单(与「离开待决」同一种缺席),
|
|
728
|
+
* 下一次列表主读读到新字节即命中记忆。
|
|
724
729
|
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
730
|
+
* 失败方向 = 本店停驻行读者的那一条律({@link SqlCheckpointStore.readStoredRow}):摘要读不动的行(字节解不出 / 体是对象但 core 的摘要读不动)
|
|
731
|
+
* 判决照给、清单与成因词缺席,计 `server.approvals.park-row-blob-unreadable`,不进记忆(下一次列表重读、再计)。行已离开待决(两次读之间被决 / 过期)⇒ 不在返回里。
|
|
732
|
+
* `not-text`(读腿漏了投影)与摘要格形不对原样抛(码坏)。授予绑定那一臂(摘要 / 缺席词)不在这里:由 {@link parkRowFacts} 读子树投出。
|
|
727
733
|
*/
|
|
728
|
-
private
|
|
734
|
+
private storedRowProjections;
|
|
735
|
+
/** 一行整行读回 → 它的 {@link StoredRowProjection}(摘要读得动的入记忆;失败方向见 {@link SqlCheckpointStore.readStoredRow})。 */
|
|
736
|
+
private projectStoredRow;
|
|
737
|
+
/**
|
|
738
|
+
* 🔴 **本店停驻行读者的那一条律:一行的字节只坏它自己那一行**(运维队列 / 订阅流的整行读与收件箱 / 任务视图的 `listByScope` 同经这里)。
|
|
739
|
+
*
|
|
740
|
+
* 一行停驻行的整行读 = `checkpoint` 列那一份字节 → core 判官 `judgeCheckpointRow` 的判决 + (读得动时)core 的摘要 `summarizeCheckpoint`。
|
|
741
|
+
* 判决**恒在**:判官对任何值都答(core:pure and total),字节读不成一只检查点(JSON 字面 `null` / 标量 / 数组 / 非 JSON)⇒ 交给它的是这一格**原样的文本**
|
|
742
|
+
* (不是本店编的一行),它答 `damaged`;体是对象而摘要读不动(`{}`、缺 `state` / `pendingAction` / `gate` 的手改行)⇒ 判官对这只对象答 ——
|
|
743
|
+
* 🚧 判官与摘要对「缺 `pendingAction`」不同答(摘要抛、判官答 `ok`:core 判官不查这一位),这一形的行照列而判决是 `ok`;本店不另判(判官的缺口归 core)。
|
|
744
|
+
* 摘要读不动的两种都计 `server.approvals.park-row-blob-unreadable`(每次列表都计)、不进记忆;**不抛** —— 修前收件箱 / 任务视图对这种行整张拒
|
|
745
|
+
* (`{}` / 缺 `state` 落 503「请重试」,坏行不修同路径恒失败),运维队列对字节解不出的行不带判决:同一张坏行两个读者两个方向。
|
|
746
|
+
* 读腿漂(`not-text`:SELECT 没投影这一列)与摘要格形不对照旧原样抛 —— 那是码坏,不是一行数据坏。
|
|
747
|
+
*
|
|
748
|
+
* 判决按 (这份字节, 引擎构建) 记忆({@link StoredRowProjectionMemo}):命中交给摘要不重判;摘要读得动的那一份由调用方 / 这里记下。
|
|
749
|
+
*/
|
|
750
|
+
private readStoredRow;
|
|
729
751
|
/**
|
|
730
752
|
* [ref] #2 (scheduler seam, core 1.105): a scope's PENDING checkpoints as lightweight
|
|
731
753
|
* {@link CheckpointSummary} rows in ONE query — the cross-task triage view a supervisor scheduler needs
|
|
732
754
|
* (gate kind + risk severity + budget spent + deadline per suspended task), no N+1 `get`s. The projection is
|
|
733
755
|
* core's shared {@link summarizeCheckpoint} run over the persisted blob (the `checkpoint` column = the same full
|
|
734
|
-
* {@link Checkpoint} `get()` parses), so
|
|
735
|
-
* created_at_ms ASC; callers (the inbox/scheduler) sort by severity.
|
|
736
|
-
*
|
|
756
|
+
* {@link Checkpoint} `get()` parses), so a readable row stays byte-identical to core's InMemory/Pg/File impls. Order =
|
|
757
|
+
* created_at_ms ASC; callers (the inbox/scheduler) sort by severity.
|
|
758
|
+
*
|
|
759
|
+
* 一行读不出摘要(字节解不出 / 体是对象而摘要读不动)⇒ 这一行**照列**,判决由判官给(缺 `pendingAction` 那一形判官答 `ok`,见 {@link SqlCheckpointStore.readStoredRow}),其余几位取本店在字节**之外**握着的事实:
|
|
760
|
+
* 令牌 / 会话 / scope / 建行时刻 / 截止 / 门型(`gate_kind` 列,与运维队列同一只判官 {@link queueRowGateKind}:读不出 ⇒ 不列、计数)/
|
|
761
|
+
* `checkpointId` 成员(是串才带;入 wire 前过信任边界由路由判)。失败方向与运维队列同一条律({@link SqlCheckpointStore.readStoredRow})。
|
|
737
762
|
*
|
|
738
763
|
* LIMIT bounds the fan-out (review w16yqkkxv): a triage view never needs more than a few — 500 is a generous
|
|
739
764
|
* ceiling that still protects memory/latency if a scope ever accumulates pathologically many pending gates.
|
|
@@ -772,14 +797,14 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
|
|
|
772
797
|
* 若只看 pending 就会把「park 成功过」这个既成事实读成「没 park 过」⇒ 又是一条假阴性 DENIED
|
|
773
798
|
* (对账三约束③)。park 成功是事实,gate 的活性由 `status` 表达,交给收敛器自己判。
|
|
774
799
|
* - **单行读不出 ⇒ 标记返回,不抛**:`unparseable` 的行由收敛器视同「不匹配」,一条坏行不许打断整段
|
|
775
|
-
* 扫描(§8 C-6)。什么算读不出:
|
|
776
|
-
*
|
|
800
|
+
* 扫描(§8 C-6)。什么算读不出:core 判官对这一行答 `lacking`(更新的引擎能续它 —— 本副本不绑,见 {@link candidateIdentityOf};
|
|
801
|
+
* 这里不能 throw,否则一条超前行会让整个 session 的对账永久卡死)、或 blob JSON 坏。
|
|
777
802
|
*
|
|
778
803
|
* 匹配是两段的:`tool_call_id` **列**是 `put()` 从 `pendingAction.toolCallId` 盖下来的权威投影,SQL 谓词
|
|
779
804
|
* 先按它(或 NULL)收窄;列为 NULL 的行(无工具动作的 park,或列存在之前的旧行)靠解 blob 补判——解得出
|
|
780
805
|
* 且相等才算候选,解不出就标 `unparseable`。
|
|
781
806
|
* 🔴 **没有「列命中即零解析」的快路径**(原注写过,已作废,别照它优化):函数体对**每一行**无条件解
|
|
782
|
-
* blob,原因有二 ——(a)
|
|
807
|
+
* blob,原因有二 ——(a) 判官的 `lacking` 臂与 blob 可读性门必须门在**所有**命中路径之前(codex F3 + 确认轮:
|
|
783
808
|
* 列长得对不代表 blob 读得出,放行一条读不出的行去 `bindBatch` 会把 ask 钉成 PARKED + 一张本进程读不出
|
|
784
809
|
* 的 resume 坐标,而 PARKED 不可回滚);(b) `[ref]` 之后 blob 顶层的 `sourceSessionId` 是和解三元组的第一
|
|
785
810
|
* 维,列命中行结构上也必须解 blob 才拿得到它。
|
|
@@ -835,7 +860,7 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
|
|
|
835
860
|
*
|
|
836
861
|
* 刻意**不带** `status='pending'` 谓词:判据是「这一行是不是你的」,与它此刻处在哪一态无关(`get()`
|
|
837
862
|
* 自己也是裸 `WHERE token = ?`);加了反而在「刚被并发消费」的窗里把属主自己的诊断吞掉。
|
|
838
|
-
* 只 SELECT `scope` 一列 ⇒
|
|
863
|
+
* 只 SELECT `scope` 一列 ⇒ 坏 blob 影响不到它(那是 `get()` 读 `checkpoint` 的事;7.144.0 起 `get()` 不再按版本列拒,本引擎续不续由 core 判官在续跑时答)。
|
|
839
864
|
*/
|
|
840
865
|
peekScopeByToken(token: CheckpointToken): Promise<string | null | undefined>;
|
|
841
866
|
peekPendingScope(sessionId: string, binding?: PendingRowBinding): Promise<string | null | undefined>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { parkedApprovalCard } from "../approval-content-kind.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
|
|
4
|
-
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf,
|
|
4
|
+
import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, judgeCheckpointRow, CONTRACT_KIT_ENGINE_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
|
|
5
5
|
import { redactDeep, redactSecrets } from "../trace/redact.js";
|
|
6
|
-
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, countKindlessBatchOffers,
|
|
7
|
-
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
6
|
+
import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, countKindlessBatchOffers, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
|
|
7
|
+
import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, jsonColumnTextSha256, readJsonArrayColumn, readJsonColumnTextSha256, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
9
|
import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
|
|
10
10
|
import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
|
|
@@ -21,6 +21,35 @@ export const TERMINAL_GRACE_MS = 3_600_000;
|
|
|
21
21
|
export function tokenFingerprint(token) {
|
|
22
22
|
return "sha256:" + createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
23
23
|
}
|
|
24
|
+
const STORED_ROW_MEMO_CAP = 8192;
|
|
25
|
+
class StoredRowProjectionMemo {
|
|
26
|
+
rows = new Map();
|
|
27
|
+
keyOf(sha256) {
|
|
28
|
+
return `${sha256}@${CONTRACT_KIT_ENGINE_VERSION}`;
|
|
29
|
+
}
|
|
30
|
+
get(sha256) {
|
|
31
|
+
const key = this.keyOf(sha256);
|
|
32
|
+
const hit = this.rows.get(key);
|
|
33
|
+
if (hit !== undefined) {
|
|
34
|
+
this.rows.delete(key);
|
|
35
|
+
this.rows.set(key, hit);
|
|
36
|
+
}
|
|
37
|
+
return hit;
|
|
38
|
+
}
|
|
39
|
+
set(sha256, projection) {
|
|
40
|
+
const key = this.keyOf(sha256);
|
|
41
|
+
this.rows.delete(key);
|
|
42
|
+
this.rows.set(key, projection);
|
|
43
|
+
while (this.rows.size > STORED_ROW_MEMO_CAP) {
|
|
44
|
+
const oldest = this.rows.keys().next().value;
|
|
45
|
+
if (oldest === undefined)
|
|
46
|
+
break;
|
|
47
|
+
this.rows.delete(oldest);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const STORED_ROW_READ_BATCH = 100;
|
|
52
|
+
const CHECKPOINT_SHA256_AS = "checkpoint_sha256";
|
|
24
53
|
const GATE_KIND_IS_LIVE_RUN_PARK = {
|
|
25
54
|
human: true,
|
|
26
55
|
irreversible_ask: true,
|
|
@@ -124,6 +153,9 @@ const readRiskDescriptorBody = createObjectColumnReader();
|
|
|
124
153
|
function decodeCheckpointColumn(cell, where) {
|
|
125
154
|
return decodeJsonColumn(cell, readCheckpointBlobBody, `checkpoint.checkpoint ${where}`);
|
|
126
155
|
}
|
|
156
|
+
function verdictOfUnreadableBody(cell) {
|
|
157
|
+
return judgeCheckpointRow(cell);
|
|
158
|
+
}
|
|
127
159
|
function decodeOutcomeColumn(cell, where) {
|
|
128
160
|
return decodeNullableJsonColumn(cell, readResumeOutcomeBody, `checkpoint.outcome ${where}`);
|
|
129
161
|
}
|
|
@@ -236,7 +268,7 @@ export function approvalDecisionOfWinner(winner, boundCallId) {
|
|
|
236
268
|
}
|
|
237
269
|
}
|
|
238
270
|
}
|
|
239
|
-
|
|
271
|
+
function pendingActionToolCallId(body) {
|
|
240
272
|
if (typeof body !== "object" || body === null)
|
|
241
273
|
return { readable: false };
|
|
242
274
|
if ("sourceTaskId" in body)
|
|
@@ -265,17 +297,22 @@ export function pendingActionToolCallId(body) {
|
|
|
265
297
|
return { readable: false };
|
|
266
298
|
return { readable: true, toolCallId: id, boundInputHash: bih, kind, sourceSessionId };
|
|
267
299
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
300
|
+
export function candidateIdentityOf(cp) {
|
|
301
|
+
if (judgeCheckpointRow(cp).kind === "lacking")
|
|
302
|
+
return { readable: false };
|
|
303
|
+
return pendingActionToolCallId(cp);
|
|
304
|
+
}
|
|
305
|
+
function candidateIdentityOfColumn(cell, where) {
|
|
306
|
+
let cp;
|
|
270
307
|
try {
|
|
271
|
-
|
|
308
|
+
cp = decodeCheckpointColumn(cell, where);
|
|
272
309
|
}
|
|
273
310
|
catch (e) {
|
|
274
311
|
if (!isJsonColumnBodyProblem(e))
|
|
275
312
|
throw e;
|
|
276
313
|
return { readable: false };
|
|
277
314
|
}
|
|
278
|
-
return
|
|
315
|
+
return candidateIdentityOf(cp);
|
|
279
316
|
}
|
|
280
317
|
function readActorAssertion(v) {
|
|
281
318
|
if (typeof v !== "object" || v === null)
|
|
@@ -346,6 +383,7 @@ export class SqlCheckpointStore {
|
|
|
346
383
|
fidelity = "json";
|
|
347
384
|
execution = { outcome: true };
|
|
348
385
|
claims;
|
|
386
|
+
storedRowMemo = new StoredRowProjectionMemo();
|
|
349
387
|
constructor(db, logger, opts = {}) {
|
|
350
388
|
this.db = db;
|
|
351
389
|
this.logger = logger;
|
|
@@ -433,16 +471,12 @@ export class SqlCheckpointStore {
|
|
|
433
471
|
}
|
|
434
472
|
}
|
|
435
473
|
async get(token) {
|
|
436
|
-
const { rows } = await this.db.query(this.q(`SELECT
|
|
437
|
-
`${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT
|
|
474
|
+
const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnText("mysql", "outcome")}, reopen_reason, rev, ` +
|
|
475
|
+
`${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT status, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnText("pg", "outcome")}, reopen_reason, rev, ` +
|
|
438
476
|
`${jsonColumnText("pg", "pending_steer_queue")}, ${jsonColumnText("pg", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = $1`), [token]);
|
|
439
477
|
const r = rows[0];
|
|
440
478
|
if (!r)
|
|
441
479
|
return null;
|
|
442
|
-
const version = Number(r.version);
|
|
443
|
-
if (version > MAX_SUPPORTED_CHECKPOINT_VERSION) {
|
|
444
|
-
throw new Error(`checkpoint format version ${version} is newer than this build supports (max ${MAX_SUPPORTED_CHECKPOINT_VERSION}) — upgrade the service to resume it`);
|
|
445
|
-
}
|
|
446
480
|
const cp = refusing("a checkpoint nobody can resume", () => decodeCheckpointColumn(r.checkpoint, `for token ${tokenFingerprint(token)}`));
|
|
447
481
|
cp.status = r.status;
|
|
448
482
|
const outcomeRaw = refusing("a decision record nobody can read", () => decodeOutcomeColumn(r.outcome, `for token ${tokenFingerprint(token)}`));
|
|
@@ -468,7 +502,6 @@ export class SqlCheckpointStore {
|
|
|
468
502
|
this.logger?.info?.("checkpoint_get", {
|
|
469
503
|
tokenFp: tokenFingerprint(token),
|
|
470
504
|
status: cp.status,
|
|
471
|
-
version,
|
|
472
505
|
hasWorkspaceHandle: !!cp.state?.workspaceHandle,
|
|
473
506
|
snapshotId: cp.state?.workspaceHandle?.snapshotId,
|
|
474
507
|
});
|
|
@@ -558,7 +591,8 @@ export class SqlCheckpointStore {
|
|
|
558
591
|
const d = this.db.dialect;
|
|
559
592
|
const base = `SELECT c.session_id, c.token, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
|
|
560
593
|
`${jsonColumnText(d, "risk_descriptor")}, ${jsonColumnText(d, "rule_suggestions")}, c.has_bidi_controls, c.created_at_ms, c.deadline, c.gate_kind, c.approval_card, ta.task_id, ` +
|
|
561
|
-
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)} ` +
|
|
594
|
+
`${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)}, ` +
|
|
595
|
+
`${jsonColumnTextSha256(d, "checkpoint", CHECKPOINT_SHA256_AS)} ` +
|
|
562
596
|
"FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
|
|
563
597
|
const { rows } = scope
|
|
564
598
|
? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
|
|
@@ -592,43 +626,96 @@ export class SqlCheckpointStore {
|
|
|
592
626
|
...(approvalCard !== undefined ? { approvalCard } : {}),
|
|
593
627
|
...parkRowFacts(pendingAction),
|
|
594
628
|
};
|
|
595
|
-
return [{ row, token: String(r.token),
|
|
629
|
+
return [{ row, token: String(r.token), sha256: readJsonColumnTextSha256(r[CHECKPOINT_SHA256_AS], "checkpoint.checkpoint_sha256 (operator queue row)") }];
|
|
630
|
+
});
|
|
631
|
+
const projections = await this.storedRowProjections(entries);
|
|
632
|
+
const out = entries.map((e) => {
|
|
633
|
+
const p = projections.get(e.token);
|
|
634
|
+
return p === undefined ? e.row : { ...e.row, ...p.skillCardFacts, rowVerdict: p.rowVerdict };
|
|
596
635
|
});
|
|
597
|
-
const out = [];
|
|
598
|
-
for (const e of entries)
|
|
599
|
-
out.push(e.readSkillCard ? { ...e.row, ...(await this.readParkedSkillCardFacts(e.token)) } : e.row);
|
|
600
636
|
return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
|
|
601
637
|
}
|
|
602
|
-
async
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
638
|
+
async storedRowProjections(rows) {
|
|
639
|
+
const out = new Map();
|
|
640
|
+
const misses = [];
|
|
641
|
+
const expected = new Map();
|
|
642
|
+
for (const { token, sha256 } of rows) {
|
|
643
|
+
expected.set(token, sha256);
|
|
644
|
+
const hit = this.storedRowMemo.get(sha256);
|
|
645
|
+
if (hit === undefined)
|
|
646
|
+
misses.push(token);
|
|
647
|
+
else
|
|
648
|
+
out.set(token, hit);
|
|
649
|
+
}
|
|
650
|
+
for (let i = 0; i < misses.length; i += STORED_ROW_READ_BATCH) {
|
|
651
|
+
const batch = misses.slice(i, i + STORED_ROW_READ_BATCH);
|
|
652
|
+
const { rows: read } = await this.db.query(this.q(`SELECT token, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnTextSha256("mysql", "checkpoint", CHECKPOINT_SHA256_AS)} FROM checkpoint ` +
|
|
653
|
+
`WHERE status = 'pending' AND token IN (${batch.map(() => "?").join(", ")})`, `SELECT token, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnTextSha256("pg", "checkpoint", CHECKPOINT_SHA256_AS)} FROM checkpoint ` +
|
|
654
|
+
`WHERE status = 'pending' AND token IN (${batch.map((_, j) => `$${j + 1}`).join(", ")})`), batch);
|
|
655
|
+
for (const r of read) {
|
|
656
|
+
const token = String(r.token);
|
|
657
|
+
const sha256 = readJsonColumnTextSha256(r[CHECKPOINT_SHA256_AS], "checkpoint.checkpoint_sha256 (whole-row read)");
|
|
658
|
+
const projection = this.projectStoredRow(r, sha256);
|
|
659
|
+
if (sha256 === expected.get(token))
|
|
660
|
+
out.set(token, projection);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return out;
|
|
664
|
+
}
|
|
665
|
+
projectStoredRow(r, sha256) {
|
|
666
|
+
const read = this.readStoredRow(r, "(operator queue row, whole-row read)", sha256);
|
|
667
|
+
return read.summary === undefined ? { rowVerdict: read.rowVerdict, skillCardFacts: {} } : { rowVerdict: read.rowVerdict, skillCardFacts: skillCardFacts(read.summary) };
|
|
668
|
+
}
|
|
669
|
+
readStoredRow(r, where, sha256, discloseFor) {
|
|
670
|
+
const memo = this.storedRowMemo.get(sha256);
|
|
607
671
|
let cp;
|
|
608
672
|
try {
|
|
609
|
-
cp = decodeCheckpointColumn(r.checkpoint,
|
|
673
|
+
cp = decodeCheckpointColumn(r.checkpoint, where);
|
|
610
674
|
}
|
|
611
675
|
catch (e) {
|
|
612
676
|
if (!isJsonColumnBodyProblem(e))
|
|
613
677
|
throw e;
|
|
614
678
|
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
615
|
-
return {};
|
|
679
|
+
return { rowVerdict: verdictOfUnreadableBody(r.checkpoint) };
|
|
616
680
|
}
|
|
681
|
+
let summary;
|
|
617
682
|
try {
|
|
618
|
-
|
|
683
|
+
summary = summarizeCheckpoint(cp, discloseFor?.(cp), memo?.rowVerdict);
|
|
619
684
|
}
|
|
620
685
|
catch {
|
|
621
686
|
recordFailOpen("server.approvals.park-row-blob-unreadable");
|
|
622
|
-
return {};
|
|
687
|
+
return { rowVerdict: judgeCheckpointRow(cp) };
|
|
623
688
|
}
|
|
689
|
+
if (memo === undefined)
|
|
690
|
+
this.storedRowMemo.set(sha256, { rowVerdict: summary.rowVerdict, skillCardFacts: skillCardFacts(summary) });
|
|
691
|
+
return { rowVerdict: summary.rowVerdict, summary };
|
|
624
692
|
}
|
|
625
693
|
async listByScope(scope) {
|
|
626
|
-
const
|
|
694
|
+
const d = this.db.dialect;
|
|
695
|
+
const { rows } = await this.db.query(`SELECT ${jsonColumnText(d, "checkpoint")}, ${jsonColumnTextSha256(d, "checkpoint", CHECKPOINT_SHA256_AS)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)}, ` +
|
|
696
|
+
`token, session_id, scope, created_at_ms, deadline, gate_kind FROM checkpoint WHERE status = 'pending' AND scope = ${this.q("?", "$1")} ORDER BY created_at_ms ASC LIMIT 500`, [scope]);
|
|
627
697
|
const out = [];
|
|
628
698
|
for (const r of rows) {
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
699
|
+
const sha256 = readJsonColumnTextSha256(r[CHECKPOINT_SHA256_AS], `checkpoint.checkpoint_sha256 in the ${scope} scheduler view`);
|
|
700
|
+
const read = this.readStoredRow(r, `in the ${scope} scheduler view`, sha256, (cp) => (sentence) => recordFailOpen("server.checkpoint.inbox-risk-axes-damaged", withoutCheckpointToken(sentence, cp.token)));
|
|
701
|
+
if (read.summary !== undefined) {
|
|
702
|
+
out.push(read.summary);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
const gateKind = queueRowGateKind(r.gate_kind);
|
|
706
|
+
if (gateKind === undefined)
|
|
707
|
+
continue;
|
|
708
|
+
const checkpointId = readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]);
|
|
709
|
+
out.push({
|
|
710
|
+
token: String(r.token),
|
|
711
|
+
rowVerdict: read.rowVerdict,
|
|
712
|
+
...(typeof checkpointId === "string" ? { checkpointId } : {}),
|
|
713
|
+
sessionId: String(r.session_id),
|
|
714
|
+
scope: String(r.scope),
|
|
715
|
+
gateKind,
|
|
716
|
+
createdAt: Number(r.created_at_ms),
|
|
717
|
+
...(r.deadline == null ? {} : { deadline: Number(r.deadline) }),
|
|
718
|
+
});
|
|
632
719
|
}
|
|
633
720
|
return out;
|
|
634
721
|
}
|
|
@@ -642,8 +729,8 @@ export class SqlCheckpointStore {
|
|
|
642
729
|
return rows[0] ? String(rows[0].token) : null;
|
|
643
730
|
}
|
|
644
731
|
async findCheckpointCandidatesForAsk(scope, sessionId, toolCallId, sinceMs) {
|
|
645
|
-
const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash,
|
|
646
|
-
"WHERE scope=? AND session_id=? AND created_at_ms>=? AND (tool_call_id=? OR tool_call_id IS NULL) ORDER BY created_at_ms ASC", `SELECT token, status, created_at_ms, tool_call_id, bound_input_hash,
|
|
732
|
+
const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, ${jsonColumnText("mysql", "checkpoint")} FROM checkpoint ` +
|
|
733
|
+
"WHERE scope=? AND session_id=? AND created_at_ms>=? AND (tool_call_id=? OR tool_call_id IS NULL) ORDER BY created_at_ms ASC", `SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, ${jsonColumnText("pg", "checkpoint")} FROM checkpoint ` +
|
|
647
734
|
"WHERE scope=$1 AND session_id=$2 AND created_at_ms>=$3 AND (tool_call_id=$4 OR tool_call_id IS NULL) ORDER BY created_at_ms ASC"), [scope, sessionId, sinceMs, toolCallId]);
|
|
648
735
|
const out = [];
|
|
649
736
|
for (const r of rows) {
|
|
@@ -654,11 +741,7 @@ export class SqlCheckpointStore {
|
|
|
654
741
|
boundInputHash: r.bound_input_hash == null ? null : String(r.bound_input_hash),
|
|
655
742
|
sourceSessionId: null,
|
|
656
743
|
};
|
|
657
|
-
|
|
658
|
-
out.push({ ...base, boundCallId: null, unparseable: true });
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
const derived = pendingActionOfColumn(r.checkpoint, `for token ${tokenFingerprint(String(r.token))}`);
|
|
744
|
+
const derived = candidateIdentityOfColumn(r.checkpoint, `for token ${tokenFingerprint(String(r.token))}`);
|
|
662
745
|
if (!derived.readable) {
|
|
663
746
|
out.push({ ...base, boundCallId: null, unparseable: true });
|
|
664
747
|
continue;
|
|
@@ -118,7 +118,13 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
118
118
|
*/
|
|
119
119
|
private refuseIfUnreadable;
|
|
120
120
|
private hydrate;
|
|
121
|
-
/**
|
|
121
|
+
/** run.json 的整文件原子替换 —— {@link persist} 的落盘那一半,不碰索引。 */
|
|
122
|
+
private writeRunFile;
|
|
123
|
+
/** Commit a RunRow to disk (whole-file atomic replace) AND the index — the single write path for every status change.
|
|
124
|
+
* 落盘在前、`runs.set` 在后:落盘抛 ⇒ 索引原样。要「全有或全无」的写口因此不在索引里那只行上原地先改再调 —— 原地改过的行,落盘一抛就是「内存已改、盘上没改」。
|
|
125
|
+
* 两种不原地的写法:对**副本**调本口(索引里换成副本:`releaseIdemKey` / `bindIdemBodyFingerprint` / `takeOverClaim`);或副本只走 {@link writeRunFile},
|
|
126
|
+
* 落成之后把改动**合回索引里的同一只行**(`fillObjectivePreview`)——后一种用在会撞进收割腿 await 窗的写口上:`reapSuspended` 等收割腿在 probe 的 await 之间手里持着索引里的行对象,
|
|
127
|
+
* await 之后的复核读的就是这只对象,索引里的行被换成副本,它们就看不见之后的认领 / 取消了。 */
|
|
122
128
|
private persist;
|
|
123
129
|
private eventLogFor;
|
|
124
130
|
/** Create a run iff the session has no active run (the task_active unique-key claim) AND no submission key of this
|
|
@@ -136,6 +142,12 @@ export declare class FileRunStore implements InProcessClaimFence {
|
|
|
136
142
|
* Synchronous from the key check to the index write ⇒ two same-key creates in this process (different sessions,
|
|
137
143
|
* different claim locks) cannot both pass the check: the loser observes the winner's row. */
|
|
138
144
|
private commitNewRun;
|
|
145
|
+
/** run 行预览的写一次补写(SQL 孪生同义:列仍是 null 才写,建行写过 / 已补过的不动;行不在 ⇒ 无事)。
|
|
146
|
+
* **副本判改 → 落盘 → 合回**:先把改动落在副本上写盘,落盘抛 ⇒ 内存里的行一字未动(预览仍是 null)、盘上也原样,下一次补写照常再试;
|
|
147
|
+
* 落成之后再把预览合回索引里的**同一只**行对象(不换对象:跨 await 持着行的收割腿复核读的是手上这只对象)。
|
|
148
|
+
* 原地先改再落盘,抛的那一拍就把内存和盘劈成两半(内存带预览、盘上 null),且「列已有值」的早退让这一分叉再也补不回来。
|
|
149
|
+
* 本体同步执行到底,中间没有 await ⇒ 副本落盘与合回之间没有别的写者插进来。 */
|
|
150
|
+
fillObjectivePreview(taskId: string, preview: string): Promise<void>;
|
|
139
151
|
requestCancel(taskId: string, owner: string | null): Promise<boolean>;
|
|
140
152
|
isCancelRequested(taskId: string, owner: string | null): Promise<boolean>;
|
|
141
153
|
requestPreempt(taskId: string, owner: string | null): Promise<boolean>;
|
|
@@ -352,9 +352,12 @@ export class FileRunStore {
|
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
-
|
|
355
|
+
writeRunFile(r) {
|
|
356
356
|
mkdirSync(this.runDir(r.taskId), { recursive: true });
|
|
357
357
|
atomicWriteFile(this.tmpDir, this.runJsonPath(r.taskId), `${JSON.stringify(this.toDisk(r))}\n`);
|
|
358
|
+
}
|
|
359
|
+
persist(r) {
|
|
360
|
+
this.writeRunFile(r);
|
|
358
361
|
this.runs.set(r.taskId, r);
|
|
359
362
|
}
|
|
360
363
|
eventLogFor(taskId) {
|
|
@@ -427,6 +430,13 @@ export class FileRunStore {
|
|
|
427
430
|
this.byIdem.set(idemKey, taskId);
|
|
428
431
|
return { ok: true };
|
|
429
432
|
}
|
|
433
|
+
async fillObjectivePreview(taskId, preview) {
|
|
434
|
+
const r = this.runs.get(taskId);
|
|
435
|
+
if (!r || r.objectivePreview !== null)
|
|
436
|
+
return;
|
|
437
|
+
this.writeRunFile({ ...r, objectivePreview: preview });
|
|
438
|
+
r.objectivePreview = preview;
|
|
439
|
+
}
|
|
430
440
|
async requestCancel(taskId, owner) {
|
|
431
441
|
const r = this.runs.get(taskId);
|
|
432
442
|
if (!r || !ownerEq(r.owner, owner) || r.status !== "running")
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* 路由表 LRU 上限 {@link ROUTE_MAX}:满额逐最老(refresh-on-use);被逐后下次 `acquire` 按上述规则重derive
|
|
33
33
|
* (requireExisting 的 host-first 重试仍落对店),中途 touch/noteTaskRun 缺路由=落 transient(no-op 级)。
|
|
34
34
|
*/
|
|
35
|
-
import { type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
|
|
35
|
+
import { type SessionStore, type AcquiredSession, type SessionHead, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
|
|
36
36
|
import type { ExecutionPlacementSeat } from "../execution-placement.js";
|
|
37
37
|
import type { ForkToFn } from "./session-fork.js";
|
|
38
38
|
/** acquire 选项形(core 5.48 SessionStore 契约):[ref] placement=受信 spawner 的分区建会话声明。 */
|
|
@@ -63,6 +63,13 @@ export declare class ForkRoutingSessionStore implements SessionStore {
|
|
|
63
63
|
readonly executionPlacement?: ExecutionPlacementSeat;
|
|
64
64
|
/** fork 续写的店契约透传(host 有才有):目标在 host 店原子新建,新 id 与源一并记 host 路由(与 {@link fork} 的 host 腿同)。 */
|
|
65
65
|
readonly forkTo?: ForkToFn;
|
|
66
|
+
/**
|
|
67
|
+
* core `SessionStore.headOf`(按 id 读一行:属主 / 住客 / 叶子)—— **按 acquire 的路由转发**({@link pick}:placed 名册 → 路由表 → 未知落 transient,
|
|
68
|
+
* 与 acquire 对同一只 id 落的店同一只),所以答的就是 acquire 交出的那只会话所在的行(core 7.62.0 [ref] 盒义务 7:声明 placements 的店答 `headOf`,
|
|
69
|
+
* 住客与 acquire 交出的同源;引擎在子 Runner 上据它读「没人派生的 run」自己那一行定委派树属主)。两只店都答 `headOf` 才声明:有一边答不了 ⇒ 不声明
|
|
70
|
+
* (半只读口会把那一边的行答成「不在」)。
|
|
71
|
+
*/
|
|
72
|
+
readonly headOf?: (sessionId: string) => Promise<SessionHead | undefined>;
|
|
66
73
|
constructor(host: SessionStore, transient: SessionStore);
|
|
67
74
|
private remember;
|
|
68
75
|
private pick;
|
|
@@ -11,6 +11,7 @@ export class ForkRoutingSessionStore {
|
|
|
11
11
|
listPlaced;
|
|
12
12
|
executionPlacement;
|
|
13
13
|
forkTo;
|
|
14
|
+
headOf;
|
|
14
15
|
constructor(host, transient) {
|
|
15
16
|
this.host = host;
|
|
16
17
|
this.transient = transient;
|
|
@@ -23,6 +24,11 @@ export class ForkRoutingSessionStore {
|
|
|
23
24
|
if (host.fork === undefined && transient.fork === undefined) {
|
|
24
25
|
this.fork = undefined;
|
|
25
26
|
}
|
|
27
|
+
const hostHeadOf = host.headOf;
|
|
28
|
+
const transientHeadOf = transient.headOf;
|
|
29
|
+
if (typeof hostHeadOf === "function" && typeof transientHeadOf === "function") {
|
|
30
|
+
this.headOf = (id) => (this.pick(id) === host ? hostHeadOf.call(host, id) : transientHeadOf.call(transient, id));
|
|
31
|
+
}
|
|
26
32
|
const hostFaces = host;
|
|
27
33
|
if (hostFaces.executionPlacement !== undefined)
|
|
28
34
|
this.executionPlacement = hostFaces.executionPlacement;
|
|
@@ -91,6 +91,15 @@ export declare class LocalCheckpointStore {
|
|
|
91
91
|
private ensureScopesRebuilt;
|
|
92
92
|
/** All still-pending checkpoints across the registered scopes (summary + full row via get). */
|
|
93
93
|
private pendings;
|
|
94
|
+
/**
|
|
95
|
+
* 一条会话的待决行 —— **只读这条会话自己的行**(本店停驻行读者的那一条律:一行的字节只坏它自己那一行;决断不许因为别的会话的一行坏行而失败)。
|
|
96
|
+
* 读口 = core 店的 `listBySession`(按会话取行,一行读不出的行答 `unreadable` 而不是整页失败、也不是跳过;它不经 scope 注册表、不投摘要);
|
|
97
|
+
* `unreadable` 的行经 `get(token)` 取原行(core 店的 `get` 原样拷出,不判形)—— 它仍是这条会话的一张待决行,选不选它由下游的门型绑定判
|
|
98
|
+
* ({@link pickPendingRow}:门型读不出 ⇒ 不命中,与 SQL 孪生 `gate_kind IN (…)` 对 NULL 同向),续不续由续跑的第一级(core 判官)答。
|
|
99
|
+
* 修前三个按会话的读口(`findPendingTokenBySession` / `peekPendingScope` / `deleteBySession`)走 {@link pendings} 的整店枚举,而那一路经 core 的
|
|
100
|
+
* `listByScope` 投摘要 —— 同库任何一行摘要读不动(缺 `state`)⇒ 整只枚举抛,别人那张好卡的决断也 500。
|
|
101
|
+
*/
|
|
102
|
+
private sessionPendings;
|
|
94
103
|
/** Operator approval queue — the same NON-secret card as the SQL twins (never the token). */
|
|
95
104
|
/** 每一只待决 checkpoint 的会话(去重;不按门型过滤 —— 理由与 SQL 孪生 `listPendingSessionIds` 逐字同,见那里)。 */
|
|
96
105
|
listPendingSessionIds(): Promise<string[]>;
|
|
@@ -147,11 +156,9 @@ export declare class LocalCheckpointStore {
|
|
|
147
156
|
* · **没有列,所以没有列/blob 一致性门**:SQL 孪生的两道矛盾门(`tool_call_id` 与 `bound_input_hash`
|
|
148
157
|
* 的列 vs blob)在本形结构上不存在 —— 本形只有 blob 这一个真源,谈不上两份互相矛盾。判别力因此**不弱**:
|
|
149
158
|
* 那两道门防的是「外部改库 / schema 偏斜让列与 blob 分家」,而本形没有可分家的第二份。
|
|
150
|
-
* ·
|
|
151
|
-
*
|
|
152
|
-
* `
|
|
153
|
-
* · **窄读器是同一只** `pendingActionToolCallId`(与 SQL 孪生共用,零第二份手抄):身份三元组的第一维
|
|
154
|
-
* `sourceSessionId`、`boundInputHash`、以及「读不出 vs 确定不匹配」的三态,全部由它一家判。
|
|
159
|
+
* · **身份判词是同一只** `candidateIdentityOf`(与 SQL 孪生共用,零第二份手抄):先问 core 判官(答 `lacking` ⇒ `unparseable`,
|
|
160
|
+
* 7.144.0 起两只孪生同一臂 —— 修前只有 SQL 孪生按版本列比,本形没有对应物),再由窄读器判身份三元组的第一维 `sourceSessionId`、
|
|
161
|
+
* `boundInputHash`、以及「读不出 vs 确定不匹配」的三态。
|
|
155
162
|
*/
|
|
156
163
|
findCheckpointCandidatesForAsk(scope: string, sessionId: string, toolCallId: string, sinceMs: number): Promise<CheckpointAskCandidate[]>;
|
|
157
164
|
/**
|
|
@@ -159,9 +166,11 @@ export declare class LocalCheckpointStore {
|
|
|
159
166
|
* 语义与存在理由的唯一属主 = SQL 侧 `peekScopeByToken` 顶注。
|
|
160
167
|
*
|
|
161
168
|
* ⚠️ 形差如实记:SQL 孪生那口是真「轻读」(只 SELECT scope 一列),本形没有列可选 —— core 的 File 店
|
|
162
|
-
* 只给 `get(token)`,所以这里读的是**整行**。后果对本形无害:本形的 `get()`
|
|
163
|
-
*
|
|
164
|
-
*
|
|
169
|
+
* 只给 `get(token)`,所以这里读的是**整行**。后果对本形无害:本形的 `get()` 没有行级拒、也不判形(两只孪生 7.144.0 起都不按版本拒 ——
|
|
170
|
+
* 本引擎续不续由 core 判官在续跑时答),原样拷出一行。读不动的形有两层:整店级 = 账本重放抛(认不出的事件种类等 ⇒ 整只店装不起来);
|
|
171
|
+
* 单行级也存在 —— 按会话的读口({@link sessionPendings})走 core 店的 `listBySession`,摘要读不出的那一行答 `unreadable`(读口不抛,行经 `get(token)` 取原行)。
|
|
172
|
+
* 即便如此,「行体读得出而 scope 读不出」这一支在本形仍结构上不存在:`scope` 是账本重放认一行的门(`token` / `scope` / `status` 皆字符串)里的成员,
|
|
173
|
+
* 门不过的行重放时被跳过并披露、根本不进店;进了店的行,行体其余成员损成什么样,`get(token)?.scope` 都是字符串。哪天本形补了行级拒、或 core 放宽重放的那道门,这里要跟着改成不碰行体的读。
|
|
165
174
|
*/
|
|
166
175
|
peekScopeByToken(token: CheckpointToken): Promise<string | null | undefined>;
|
|
167
176
|
/** `undefined` = no pending row; else the owner scope (local scopes are always strings — never null).
|