@sema-agent/server 7.137.0 → 7.139.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 +600 -142
- package/README.md +7 -3
- package/README.zh-CN.md +6 -3
- package/USAGE.md +132 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +23 -16
- package/dist/approval-card.js +1 -0
- package/dist/audit.js +1 -0
- package/dist/background-agent-name.d.ts +5 -0
- package/dist/background-agent-name.js +5 -0
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/budget-tracing.d.ts +1 -12
- package/dist/boot/deployment-mods.d.ts +46 -16
- package/dist/boot/deployment-mods.js +82 -6
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +16 -24
- package/dist/boot/execution-lane.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +12 -18
- package/dist/boot/governance-seams.js +10 -24
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/memory-consolidation.js +2 -2
- package/dist/boot/org-delivery.js +15 -2
- package/dist/boot/resolve-spec.d.ts +4 -1
- package/dist/boot/resolve-spec.js +29 -19
- package/dist/boot/retention-lane.d.ts +14 -8
- package/dist/boot/retention-lane.js +2 -8
- package/dist/boot/runner-deps.d.ts +10 -4
- package/dist/boot/runner-deps.js +2 -0
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -12
- package/dist/boot/stage-05-execution-env.d.ts +3 -12
- package/dist/boot/stage-05-execution-env.js +8 -5
- package/dist/boot/stage-06-runners.d.ts +3 -12
- package/dist/boot/stage-06-runners.js +5 -3
- package/dist/boot/stage-07-capability-layer.d.ts +3 -12
- package/dist/boot/stage-07-capability-layer.js +2 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -12
- package/dist/boot/stage-09-leader.d.ts +3 -12
- package/dist/boot/stage-09-leader.js +2 -3
- package/dist/boot/stage-10-http-server.d.ts +3 -12
- package/dist/boot/stage-10-http-server.js +5 -3
- package/dist/boot/stores.js +4 -4
- package/dist/boot/web-admin.d.ts +5 -2
- package/dist/boot/web-admin.js +5 -5
- package/dist/brain.js +5 -5
- package/dist/budget.d.ts +17 -15
- package/dist/budget.js +2 -2
- package/dist/capabilities/mcp-probe.d.ts +36 -11
- package/dist/capabilities/mcp-probe.js +44 -5
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +21 -7
- package/dist/capability-facts.js +13 -3
- package/dist/checkpoint-identity.d.ts +13 -6
- package/dist/config-catalog.js +14 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +19 -24
- package/dist/config-provider.js +27 -12
- package/dist/config-types.d.ts +25 -12
- package/dist/config.d.ts +33 -0
- package/dist/config.js +52 -20
- package/dist/delegated-session-owner.d.ts +2 -2
- package/dist/deployment-shape.d.ts +7 -7
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +19 -0
- package/dist/execution-lane-caps.js +10 -0
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/gate-settlement.d.ts +71 -0
- package/dist/gate-settlement.js +40 -0
- package/dist/hooks/classic-stations.d.ts +1 -0
- package/dist/hooks/classic-stations.js +1 -0
- package/dist/hooks/command-spawn.d.ts +2 -2
- package/dist/hooks/hook-runner.js +14 -14
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +10 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/dispatch.d.ts +4 -0
- package/dist/http/dispatch.js +3 -0
- package/dist/http/durable-run-leg.js +12 -6
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/resume-legs.d.ts +5 -11
- package/dist/http/resume-legs.js +179 -104
- package/dist/http/route-ctx.d.ts +44 -59
- package/dist/http/route-ctx.js +2 -5
- package/dist/http/routes/a2a-serve.js +10 -4
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +5 -1
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/capabilities.js +12 -10
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/mcp-reconnect.d.ts +3 -2
- package/dist/http/routes/mcp-reconnect.js +7 -5
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +3 -3
- package/dist/http/routes/runs.js +11 -7
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +119 -13
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +6 -8
- package/dist/http/routes/tasks.js +800 -782
- package/dist/http/server.d.ts +61 -39
- package/dist/http/server.js +11 -0
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +22 -18
- package/dist/identity-source.d.ts +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +7 -0
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +51 -40
- package/dist/mcp-assertion-header.js +10 -11
- package/dist/mcp-authorization-face.d.ts +27 -0
- package/dist/mcp-authorization-face.js +15 -0
- package/dist/memory-operator-faces.d.ts +13 -4
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-sync-client.d.ts +53 -14
- package/dist/memory-sync-client.js +187 -92
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/model-route-gap.d.ts +6 -22
- package/dist/model-route-gap.js +7 -5
- package/dist/mods-declaration.d.ts +110 -0
- package/dist/mods-declaration.js +109 -0
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/err-text.d.ts +5 -0
- package/dist/observability/err-text.js +9 -0
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/run-terminal.js +0 -1
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +49 -11
- package/dist/parked-decide.js +25 -26
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +156 -0
- package/dist/peer-seat.js +273 -0
- package/dist/permission-rule-vocab.d.ts +7 -7
- package/dist/permission-rule-vocab.js +3 -3
- package/dist/plain-string-record.d.ts +38 -0
- package/dist/plain-string-record.js +38 -0
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +30 -12
- package/dist/plugins/background-agent-store-sql.js +6 -3
- package/dist/plugins/caching-session-store.d.ts +11 -2
- package/dist/plugins/caching-session-store.js +15 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
- package/dist/plugins/checkpoint-store-sql.js +13 -15
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +49 -103
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +7 -6
- package/dist/plugins/file-history-store-sql.js +8 -7
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
- package/dist/plugins/k8s-pod-reaper.js +3 -3
- package/dist/plugins/local-checkpoint-store.d.ts +1 -11
- package/dist/plugins/local-checkpoint-store.js +0 -1
- package/dist/plugins/local-session-store.d.ts +183 -45
- package/dist/plugins/local-session-store.js +412 -105
- package/dist/plugins/mailbox-store-sql.d.ts +11 -3
- package/dist/plugins/mailbox-store-sql.js +103 -15
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
- package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -6
- package/dist/plugins/one-time-migrations.js +383 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
- package/dist/plugins/permission-rule-store-sql.js +34 -29
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +41 -22
- package/dist/plugins/pg-session-storage.js +136 -55
- package/dist/plugins/remote-env-adb.d.ts +22 -4
- package/dist/plugins/remote-env-adb.js +70 -62
- package/dist/plugins/remote-env-device.d.ts +32 -12
- package/dist/plugins/remote-env-device.js +18 -17
- package/dist/plugins/remote-env-e2b.d.ts +19 -3
- package/dist/plugins/remote-env-e2b.js +41 -42
- package/dist/plugins/remote-env-file-error.d.ts +2 -0
- package/dist/plugins/remote-env-file-error.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +23 -8
- package/dist/plugins/remote-env-host.js +66 -70
- package/dist/plugins/remote-env-k8s.d.ts +13 -16
- package/dist/plugins/remote-env-k8s.js +61 -54
- package/dist/plugins/remote-env-local-docker.d.ts +30 -5
- package/dist/plugins/remote-env-local-docker.js +46 -59
- package/dist/plugins/remote-env-ssh.d.ts +18 -4
- package/dist/plugins/remote-env-ssh.js +72 -56
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/retention-store-sql.d.ts +23 -8
- package/dist/plugins/retention-store-sql.js +13 -1
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/schema-consistency.d.ts +15 -5
- package/dist/plugins/schema-consistency.js +14 -8
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/session-fork.d.ts +63 -12
- package/dist/plugins/session-fork.js +38 -21
- package/dist/plugins/session-ownership.d.ts +7 -0
- package/dist/plugins/session-ownership.js +5 -0
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +17 -0
- package/dist/plugins/session-workspace-cell.js +38 -0
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-backend.js +26 -14
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-store.d.ts +74 -28
- package/dist/plugins/tidb-session-store.js +132 -53
- package/dist/plugins/tool-result-store-sql.d.ts +16 -8
- package/dist/plugins/tool-result-store-sql.js +26 -17
- package/dist/plugins/worktree-isolation.d.ts +3 -2
- package/dist/plugins/worktree-isolation.js +1 -7
- package/dist/project-identity.d.ts +1 -14
- package/dist/project-identity.js +0 -4
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/rules-consent.js +6 -4
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +36 -26
- package/dist/runs.d.ts +9 -2
- package/dist/runs.js +9 -2
- package/dist/sandbox-git-plane.d.ts +2 -2
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +15 -8
- package/dist/server-secret-env.js +4 -2
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +19 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/session-workspace.d.ts +130 -0
- package/dist/session-workspace.js +83 -0
- package/dist/spec-fields.d.ts +1 -1
- package/dist/standing-answer-grant.d.ts +2 -13
- package/dist/standing-answer-grant.js +0 -7
- package/dist/task-a2a.d.ts +44 -14
- package/dist/task-a2a.js +46 -20
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +33 -20
- package/dist/task-mcp.js +46 -22
- package/dist/tool-approval.d.ts +38 -8
- package/dist/tool-approval.js +57 -13
- package/dist/trace/core-keyset-guard.d.ts +33 -18
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +20 -1
- package/dist/trace/exit-seat.js +3 -0
- package/dist/trace/extension-call-face.d.ts +33 -0
- package/dist/trace/extension-call-face.js +39 -0
- package/dist/trace/project.d.ts +26 -7
- package/dist/trace/project.js +30 -3
- package/dist/trace/redact.js +42 -13
- package/dist/trace/self-minted-wire.d.ts +240 -12
- package/dist/trace/self-minted-wire.js +100 -4
- package/dist/trace/wire-projection-faces.d.ts +6 -6
- package/dist/trace/wire-projection-faces.js +6 -5
- package/dist/url-refusal.d.ts +1 -1
- package/dist/user-authored-text.d.ts +2 -2
- package/dist/user-authored-text.js +16 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/center-request.d.ts +3 -2
- package/dist/web-admin-client/center-request.js +2 -3
- package/dist/web-admin-client/skills-mcp.d.ts +8 -4
- package/dist/web-admin-client/skills-mcp.js +32 -18
- package/dist/web-admin-client/types.d.ts +11 -0
- package/package.json +3 -3
- package/wire-changes/server@7.138.0.json +446 -0
- package/wire-changes/server@7.139.0.json +348 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
- package/dist/http/mcp-remote-text.d.ts +0 -5
- package/dist/http/mcp-remote-text.js +0 -7
|
@@ -13,17 +13,20 @@
|
|
|
13
13
|
import { type AcquiredSession, type PlacedSessionRow, type SessionHead, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry } from "@sema-agent/core";
|
|
14
14
|
import type { Pool } from "mysql2/promise";
|
|
15
15
|
import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
16
|
-
import {
|
|
16
|
+
import type { SessionWorkspace } from "../session-workspace.js";
|
|
17
|
+
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
17
18
|
import type { SessionSummary } from "./store-contracts.js";
|
|
18
|
-
import type
|
|
19
|
+
import { type ClientSessionRowWrite, type ReplaySessionRowWrite, type ReplaySessionRowWriteOutcome, type SessionRowPatch, type SessionRowWriteOutcome } from "../session-row.js";
|
|
19
20
|
import { type OrphanOwnerLog } from "../delegated-session-owner.js";
|
|
20
21
|
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
21
22
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
23
|
+
import { type ForkTarget, type ForkToOutcome } from "./session-fork.js";
|
|
24
|
+
import { type ExecutionPlacementSeat } from "../execution-placement.js";
|
|
22
25
|
export declare class TiDBSessionStore implements SessionStore {
|
|
23
26
|
private readonly pool;
|
|
24
|
-
/**
|
|
27
|
+
/** 托管留存声明 = 装配交给它的词(后端按扫车道开不开给;不给 ⇒ `"none"`)。读法逐字见
|
|
25
28
|
* `retention-store-sql.ts` 的 {@link MANAGED_RETENTION}。 */
|
|
26
|
-
readonly retention:
|
|
29
|
+
readonly retention: RetentionDeclaration;
|
|
27
30
|
/** [ref] C18(core 5.48.0 [ref]):placement **声明**。六条义务的实装点逐条见
|
|
28
31
|
* {@link SQL_SESSION_PLACEMENTS} 顶注与本类的 acquire / listSessions / release / listPlaced /
|
|
29
32
|
* placementOf 各处的 `[ref]` 注。 */
|
|
@@ -36,8 +39,16 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
36
39
|
private readonly pending;
|
|
37
40
|
/** 委派子代孤行那一行 warn 的去处(`delegated-session-owner.ts`;装配给部署的 logger,缺席 ⇒ 缺省 logger,不静默)。 */
|
|
38
41
|
private readonly log;
|
|
42
|
+
/**
|
|
43
|
+
* 执行席 —— 根会话行上这一位住客的那一格的唯一读口与 place / markMirror 两只写口(规则在 `src/execution-placement.ts`;
|
|
44
|
+
* 实现与语句的唯一属主 = `execution-placement-sql.ts`,两孪生同一只实现,本店只交自己的池)。
|
|
45
|
+
*/
|
|
46
|
+
readonly executionPlacement: ExecutionPlacementSeat;
|
|
47
|
+
/** 本店的池包成的 SqlDriver:执行席与会话行格写原语的事务都经它借连接(连接初始化钉住事务读语义,`sql-driver.ts`)。 */
|
|
48
|
+
private readonly db;
|
|
39
49
|
constructor(pool: Pool, opts?: {
|
|
40
50
|
readonly log?: OrphanOwnerLog;
|
|
51
|
+
readonly retention?: RetentionDeclaration;
|
|
41
52
|
});
|
|
42
53
|
acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise<AcquiredSession>;
|
|
43
54
|
private load;
|
|
@@ -80,7 +91,7 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
80
91
|
}): Promise<PlacedSessionRow[]>;
|
|
81
92
|
/**
|
|
82
93
|
* core `SessionStore.headOf` —— 这一行的属主、住客、叶子,**一条 PK SELECT、一行**(两次独立读在删除 / 重登记的缝里能拼出混世代组合;
|
|
83
|
-
* 单行单读天然一致)。`undefined` = 无此会话。本店按 id 问这一行的**唯一**读口:授权口的属主、
|
|
94
|
+
* 单行单读天然一致)。`undefined` = 无此会话。本店按 id 问这一行的**唯一**读口:授权口的属主、k8s 工作区空间的住客、SSE 探针的叶子、
|
|
84
95
|
* resume-at 的叶子都是它的投影(此前的 `ownerOf` / `sessionFaceOf` / `incarnationOf` / `getLeafId` / `getHead` 五只读全部并进来;规则见
|
|
85
96
|
* `session-incarnation.ts` 头注)。化身那一格经 `headFromRow` 的 `absent` 读法(空 ⇒ 住客未知);要一位活住客的读者经 `sessionFaceOfHead` 响亮拒空。
|
|
86
97
|
* 只读、不取会话、不上锁、绕过暖缓存 —— 跑到一半读也安全(写的安全是追加时的 CAS)。
|
|
@@ -132,21 +143,24 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
132
143
|
* row of the list face (no such session / placed). The run-preview subqueries are scoped to the same id (an index read on
|
|
133
144
|
* `task_run.session_id`, not the whole-ledger aggregate the list pays). */
|
|
134
145
|
sessionSummaryOf(sessionId: string): Promise<SessionSummary | undefined>;
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
/** {@link sessionSummaryOf} 那一读 + 读到它时行上的住客(写原语的应答行要复核住客,`settleSessionRowWrite`)。 */
|
|
147
|
+
private listRowOf;
|
|
148
|
+
/**
|
|
149
|
+
* 会话行格写原语 —— 用户标题 / 置顶的唯一写口(规则与判序:`src/session-row.ts` 头注「写原语」;事务与语句:`session-row-sql.ts`,PG 孪生同一只实现)。
|
|
150
|
+
* 一只经 `SqlDriver.connect()` 的事务锁住根行、交 core 判官判行期望(属主 + 化身)→ 写者身份(客户端写在镜像上 ⇒ `mirror`)→ 标签 → 写;
|
|
151
|
+
* 应答行在提交 / 回滚之后另读(列表行同一只投影,含新 `rowTag`)、复核仍是判过的那一位住客。只写列表面上的行(委派分区行 ⇒ `absent`);
|
|
152
|
+
* `updated_at` 不动(改名不是活动)。重载按入口分:`replay` 的结局只有 `written` / `absent`。
|
|
153
|
+
*/
|
|
154
|
+
writeSessionRowCells(sessionId: string, cells: SessionRowPatch, how: ClientSessionRowWrite): Promise<SessionRowWriteOutcome<SessionSummary>>;
|
|
155
|
+
writeSessionRowCells(sessionId: string, cells: SessionRowPatch, how: ReplaySessionRowWrite): Promise<ReplaySessionRowWriteOutcome<SessionSummary>>;
|
|
144
156
|
/**
|
|
145
157
|
* The list projection's ONE SELECT + row mapper ({@link listSessions} and {@link sessionSummaryOf} both read through it).
|
|
146
158
|
* `runSessionId` scopes the two run-preview subqueries to one session (the single-row read); the list leaves them whole.
|
|
147
159
|
* Placeholder order follows the text: subquery params, then `where` params, then the LIMIT.
|
|
160
|
+
* `sm.incarnation` is read for the row tag only (`rowTag`, `sessionRowTagKey` — present-iff the row has a token) and the answer row's occupant
|
|
161
|
+
* check; it never enters the list row itself.
|
|
148
162
|
*/
|
|
149
|
-
private
|
|
163
|
+
private selectListRows;
|
|
150
164
|
/** Cheap pre-LLM probe: "none" = session_meta row not yet REGISTERED (createRun fires
|
|
151
165
|
* before the Runner's session acquire — the titler retries), "untitled" / "titled" as read. */
|
|
152
166
|
probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
|
|
@@ -165,6 +179,18 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
165
179
|
* 在期限内拒。⚠️ mysql2 的超时是客户端侧的 —— 拒之后那条连接仍被挂着的命令占到服务端结束(S-9 同形,如实披露)。
|
|
166
180
|
*/
|
|
167
181
|
recordInstructionSource(sessionId: string, expected: InstructionSourceBaseline, source: string | null): Promise<void>;
|
|
182
|
+
/**
|
|
183
|
+
* 会话行的位置与项目那一格(`session_meta.workspace`;域律 `src/session-workspace.ts`,列属主 `session-workspace-cell.ts`)。
|
|
184
|
+
* 一行 PK 读,按文本投影、恰好解一次;行不在 / 格为 NULL ⇒ `undefined`;格读不出 ⇒ 读作没有记录并计一次(F 类,见列属主头注)。
|
|
185
|
+
*/
|
|
186
|
+
readSessionWorkspace(sessionId: string): Promise<SessionWorkspace | undefined>;
|
|
187
|
+
/**
|
|
188
|
+
* 整格覆写(最后一帧胜;唯一调用方 = 腿的记录座,它在腿的流循环里一笔一笔等写完)。**住客围栏**:只写化身仍是 `incarnation` 的那一行 ——
|
|
189
|
+
* 行不在、或同 id 已换了住客 ⇒ 0 行、不抛(那一行不是这条腿的)。**不**碰 `updated_at`(那是会话活动时刻,留存腿按它判龄;记下站在哪里不是一次会话活动)。
|
|
190
|
+
* 走店的一般查询面(与腿循环里的账本追加同一形,不另设每查询的客户端超时):mysql2 的客户端超时只是不再等,服务端照把那条 UPDATE 跑完 ——
|
|
191
|
+
* 一笔「放弃等待却仍会落地」的写正是这条腿离开之后还能盖掉下一条腿的那一形。卡住的上界归服务端(锁等待超时)与部署的池策略。
|
|
192
|
+
*/
|
|
193
|
+
recordSessionWorkspace(sessionId: string, incarnation: string, workspace: SessionWorkspace): Promise<void>;
|
|
168
194
|
/**
|
|
169
195
|
* E17 — fork a session: copy its WHOLE durable history to a NEW uuidv7 session owned by `owner`, mirroring
|
|
170
196
|
* core's `PgSessionRepo.fork`. Entry ids / parent links are per-session and copy VERBATIM; only `seq` is
|
|
@@ -178,6 +204,17 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
178
204
|
* bypassed — and re-seq it dense from 0 into the new session, ids/parents/payload byte-verbatim.
|
|
179
205
|
*/
|
|
180
206
|
fork(sourceId: string, owner?: string | null): Promise<string | null>;
|
|
207
|
+
/**
|
|
208
|
+
* **fork 续写的店契约**(三店同一只私有体;core `fork` 是它的投影):把源会话的整份历史拷进调用方给的目标 id。
|
|
209
|
+
* 1. **原子目标创建**:事务首句 `INSERT INTO session_meta (…, fork_key) VALUES (:target, …)`,主键即排他(meta 先,随后拷事件、COMMIT);
|
|
210
|
+
* 2. **赢家分类**:撞上已在的目标 ⇒ 输方整笔回滚,在新视图里读目标、按属主与 fork 键分类({@link classifyForkTarget});读不到(赢方在分类前回滚或被删)
|
|
211
|
+
* ⇒ 整笔重跑一次,再撞而仍读不到 ⇒ {@link ForkTargetUnsettledError}(不猜);
|
|
212
|
+
* 3. 单事务 ⇒ 无中间态;`fork_key` 只由这里写(此后每个写携带:UPDATE 只碰点名列,整段换装经 `carriedRowCells` 带回)。
|
|
213
|
+
* 源不在 ⇒ `null`(先于目标判:源被删之后的同键重试答「源不在」)。新行 = 新住客(化身新铸),执行格 absent(INSERT 列名显式)。
|
|
214
|
+
*/
|
|
215
|
+
forkTo(sourceId: string, owner: string | null, target: ForkTarget): Promise<ForkToOutcome | null>;
|
|
216
|
+
/** {@link forkTo} 的一次尝试:`"unsettled"` = 撞上目标而回滚之后读不到它(交给外层重跑一次)。 */
|
|
217
|
+
private forkToOnce;
|
|
181
218
|
/**
|
|
182
219
|
* 2c session-sync — EXPORT a session's full durable entry log for cross-backend migration. This is the
|
|
183
220
|
* READ half of {@link fork}, surfaced as the core `SessionRepo.exportEntries` seam: the WHOLE `session_event` log,
|
|
@@ -224,29 +261,38 @@ export declare class TiDBSessionStore implements SessionStore {
|
|
|
224
261
|
* exactly-one-root / leaf-resolvable / structural refs present) — we persist its VERBATIM output and do NOT
|
|
225
262
|
* re-implement any of those checks in SQL ([[core-service-boundary]]; the same discipline fork's hand-SQL
|
|
226
263
|
* was warned about). A violation throws `SessionError("invalid_session")` and NOTHING is written.
|
|
227
|
-
* The write
|
|
228
|
-
*
|
|
229
|
-
*
|
|
264
|
+
* The write: re-seq dense from 0, entry_id/parent_id/payload/ts verbatim, in ONE txn (COMMIT is the sole commit point), written
|
|
265
|
+
* **lock-or-create, meta first** (读写序律,`execution-placement-sql.ts` 头注):the `session_meta` row is occupied first (an existing row ⇒
|
|
266
|
+
* refused before anything else about it is read), locked, the run claim on the root is read on THIS connection (a claim ⇒
|
|
267
|
+
* `SessionActiveRunError`, nothing written), then the entries. The leaf is the replayed leaf (core's `leafIdAfterEntry`), matching core's import.
|
|
230
268
|
*/
|
|
231
269
|
importEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
232
270
|
/**
|
|
233
271
|
* 2c session-sync (§7/§8) — IDEMPOTENT REPLACE: purge any existing conversation log for `sessionId` then re-import
|
|
234
|
-
* `entries` verbatim under `owner`, ALL in ONE txn. Unlike {@link importEntries} (
|
|
235
|
-
*
|
|
272
|
+
* `entries` verbatim under `owner`, ALL in ONE txn. Unlike {@link importEntries} (fresh-only: an existing session at the
|
|
273
|
+
* destination is refused), replace is safe to run OVER an existing session
|
|
236
274
|
* — the §7 fast-forward path and a §8 retry-to-completion both land on it. The validate gate runs FIRST (fail-closed,
|
|
237
|
-
* before any write), then
|
|
238
|
-
* — a wrong-owner caller can never replace another tenant's session
|
|
239
|
-
*
|
|
240
|
-
*
|
|
275
|
+
* before any write), then the OWNER-GUARDED meta delete (defense-in-depth, mirrors {@link deleteSession}
|
|
276
|
+
* — a wrong-owner caller can never replace another tenant's session: its delete matches nothing and the re-insert collides on the PK),
|
|
277
|
+
* then the SAME shared write body importEntries uses (session_meta first, the in-txn claim read, then the events swapped) — ONE source of
|
|
278
|
+
* the write logic, no copy-paste drift. COMMIT is the sole commit point: a mid-replace crash rolls the whole swap back.
|
|
241
279
|
*/
|
|
242
280
|
replaceEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
|
|
243
281
|
/**
|
|
244
|
-
* Shared
|
|
245
|
-
*
|
|
246
|
-
*
|
|
282
|
+
* Shared write half of {@link importEntries} / {@link replaceEntries} (ONE source of the write logic, no drift), in the global lock
|
|
283
|
+
* order (根 `session_meta` 先于 `task_*` 与 `session_event`):
|
|
284
|
+
* ① session_meta FIRST — `create` occupies the row (`ON DUPLICATE KEY UPDATE session_id = session_id`: an existing row is left as it is),
|
|
285
|
+
* `replace` is a plain INSERT after the caller's owner-guarded delete (a foreign row collides on the PK — the existing refusal);
|
|
286
|
+
* owner = the RE-STAMPED authenticated principal (never the bundle's); 化身与 fork 键按 `carriedRowCells`(或新铸),执行格 = mirror
|
|
287
|
+
* —— 导入落下的行是「写者在别处」(列名与值同出 `importedRowCells`);
|
|
288
|
+
* ② `create`: lock the row and judge it is the one this call occupied (化身 = 本次铸的)— a row that existed before ⇒ refused before
|
|
289
|
+
* anything else about it is read (no claim read on someone else's row);
|
|
290
|
+
* ③ 读写序律:the run claim on the root, read on THIS connection after the row lock (`lockRootAndReadClaim`) — a claim ⇒ `SessionActiveRunError`;
|
|
291
|
+
* ④ the validated entries re-seq dense from 0 (entry_id/parent_id/type/ts verbatim); `replace` drops the old log first.
|
|
292
|
+
* Runs inside the caller's open transaction (`conn`) so the caller owns begin/commit/rollback (COMMIT is the sole commit point).
|
|
247
293
|
* `validated` is the OUTPUT of {@link validateEntriesForImport} (the caller runs the gate before opening the txn).
|
|
248
294
|
*/
|
|
249
|
-
private
|
|
295
|
+
private writeImportedLog;
|
|
250
296
|
/**
|
|
251
297
|
* 2c session-sync P1d-β (PUSH streaming, §15) — begin a STAGED import into `realSessionId` under the shadow id
|
|
252
298
|
* `${realSessionId}#stg-${token}`. The route mints the uuidv7 `token` (the store never calls uuidv7/Date.now itself
|
|
@@ -4,24 +4,38 @@ import { decodeSessionEventPayload } from "./session-event-payload.js";
|
|
|
4
4
|
import { jsonColumnText } from "./sql-json-column.js";
|
|
5
5
|
import { escapeLike } from "./sql-escape.js";
|
|
6
6
|
import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
|
|
8
|
+
import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
9
|
+
import { carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
|
|
10
|
+
import { sessionRowTagKey } from "../session-row.js";
|
|
11
|
+
import { writeSessionRowCellsSql } from "./session-row-sql.js";
|
|
9
12
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
10
13
|
import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
|
|
11
14
|
import { createLogger } from "../observability/logger.js";
|
|
12
15
|
import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SQL_SESSION_PLACEMENTS, } from "./session-placement.js";
|
|
13
16
|
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
14
17
|
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue } from "./session-ownership.js";
|
|
15
|
-
import {
|
|
18
|
+
import { selectForkRows } from "@sema-agent/core";
|
|
19
|
+
import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "./session-fork.js";
|
|
20
|
+
import { isMysqlDupKeyError } from "./sql-errors.js";
|
|
21
|
+
import { mysqlDriver } from "./sql-driver.js";
|
|
22
|
+
import { MIRROR_ON_COMMIT_GUARD, MIRROR_ON_COMMIT_SET, SESSION_META_PLACEMENT_READ_COLUMNS, createSqlExecutionPlacementSeat, importedRowCells, lockRootAndReadClaim, placementCellFromRow, readPlacementRowStatement } from "./execution-placement-sql.js";
|
|
23
|
+
import { SessionActiveRunError, SessionWriterPresentError } from "../execution-placement.js";
|
|
24
|
+
const rowsOnConnection = (conn) => async (sql, values) => (await conn.query(sql, [...values]))[0];
|
|
16
25
|
export class TiDBSessionStore {
|
|
17
26
|
pool;
|
|
18
|
-
retention
|
|
27
|
+
retention;
|
|
19
28
|
placements = SQL_SESSION_PLACEMENTS;
|
|
20
29
|
pending = new Map();
|
|
21
30
|
log;
|
|
31
|
+
executionPlacement;
|
|
32
|
+
db;
|
|
22
33
|
constructor(pool, opts = {}) {
|
|
23
34
|
this.pool = pool;
|
|
24
35
|
this.log = opts.log ?? createLogger();
|
|
36
|
+
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
37
|
+
this.db = mysqlDriver(pool);
|
|
38
|
+
this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
|
|
25
39
|
}
|
|
26
40
|
async acquire(sessionId, opts) {
|
|
27
41
|
if (!sessionId) {
|
|
@@ -169,34 +183,21 @@ export class TiDBSessionStore {
|
|
|
169
183
|
where.push("(sm.updated_at < ? OR (sm.updated_at = ? AND sm.session_id < ?))");
|
|
170
184
|
params.push(new Date(opts.cursor.lastActivityAt), new Date(opts.cursor.lastActivityAt), opts.cursor.sessionId);
|
|
171
185
|
}
|
|
172
|
-
return this.
|
|
186
|
+
return (await this.selectListRows({ where, params, limit: opts.limit })).map((a) => a.row);
|
|
173
187
|
}
|
|
174
188
|
async sessionSummaryOf(sessionId) {
|
|
175
|
-
|
|
176
|
-
return rows[0];
|
|
189
|
+
return (await this.listRowOf(sessionId))?.row;
|
|
177
190
|
}
|
|
178
|
-
async
|
|
179
|
-
|
|
180
|
-
const params = [];
|
|
181
|
-
if (patch.title !== undefined) {
|
|
182
|
-
sets.push("title = ?");
|
|
183
|
-
params.push(patch.title);
|
|
184
|
-
}
|
|
185
|
-
if (patch.pinned !== undefined) {
|
|
186
|
-
sets.push("pinned = ?");
|
|
187
|
-
params.push(patch.pinned);
|
|
188
|
-
}
|
|
189
|
-
if (sets.length > 0) {
|
|
190
|
-
params.push(sessionId, face.incarnation);
|
|
191
|
-
await this.pool.query(`UPDATE session_meta SET ${sets.join(", ")} WHERE session_id = ? AND incarnation = ? AND placement_kind IS NULL`, params);
|
|
192
|
-
}
|
|
193
|
-
const rows = await this.selectSummaries({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?", "sm.incarnation = ?"], params: [sessionId, face.incarnation], limit: 1, runSessionId: sessionId });
|
|
194
|
-
return rows[0];
|
|
191
|
+
async listRowOf(sessionId) {
|
|
192
|
+
return (await this.selectListRows({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?"], params: [sessionId], limit: 1, runSessionId: sessionId }))[0];
|
|
195
193
|
}
|
|
196
|
-
async
|
|
194
|
+
async writeSessionRowCells(sessionId, cells, how) {
|
|
195
|
+
return writeSessionRowCellsSql(this.db, sessionId, cells, how, (id) => this.listRowOf(id));
|
|
196
|
+
}
|
|
197
|
+
async selectListRows(q) {
|
|
197
198
|
const runWhere = q.runSessionId !== undefined ? " WHERE session_id = ?" : "";
|
|
198
199
|
const runParams = q.runSessionId !== undefined ? [q.runSessionId, q.runSessionId] : [];
|
|
199
|
-
const sql = "SELECT sm.session_id, sm.owner, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.title, sm.pinned, " +
|
|
200
|
+
const sql = "SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.title, sm.pinned, " +
|
|
200
201
|
" COALESCE(r.run_count, 0) AS run_count, latest.objective_preview, latest.status, latest.task_id AS last_run_id, latest.last_task_run_id " +
|
|
201
202
|
"FROM session_meta sm " +
|
|
202
203
|
"LEFT JOIN (SELECT session_id, COUNT(*) AS run_count FROM task_run" + runWhere + " GROUP BY session_id) r ON r.session_id = sm.session_id " +
|
|
@@ -209,9 +210,14 @@ export class TiDBSessionStore {
|
|
|
209
210
|
const [rows] = await this.pool.query(sql, [...runParams, ...q.params, q.limit]);
|
|
210
211
|
return rows.map((r) => {
|
|
211
212
|
const runId = r.last_task_run_id;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
const sessionId = String(r.session_id);
|
|
214
|
+
const owner = r.owner ?? null;
|
|
215
|
+
const incarnation = incarnationFromCell(sessionId, r.incarnation, "absent");
|
|
216
|
+
const title = r.title ?? null;
|
|
217
|
+
const pinned = Number(r.pinned) === 1;
|
|
218
|
+
const row = {
|
|
219
|
+
sessionId,
|
|
220
|
+
owner,
|
|
215
221
|
lastActivityAt: r.last_activity instanceof Date ? r.last_activity.toISOString() : String(r.last_activity),
|
|
216
222
|
firstActivityAt: r.first_activity instanceof Date ? r.first_activity.toISOString() : String(r.first_activity),
|
|
217
223
|
runCount: Number(r.run_count),
|
|
@@ -219,9 +225,11 @@ export class TiDBSessionStore {
|
|
|
219
225
|
lastStatus: r.status == null ? "" : String(r.status),
|
|
220
226
|
lastRunId: r.last_run_id ?? null,
|
|
221
227
|
...(typeof runId === "string" && runId !== "" ? { lastTaskRunId: runId } : {}),
|
|
222
|
-
title
|
|
223
|
-
pinned
|
|
228
|
+
title,
|
|
229
|
+
pinned,
|
|
230
|
+
...sessionRowTagKey({ sessionId, owner, incarnation, cells: { title, pinned } }),
|
|
224
231
|
};
|
|
232
|
+
return { row, occupant: { owner, incarnation } };
|
|
225
233
|
});
|
|
226
234
|
}
|
|
227
235
|
async probeTitle(sessionId) {
|
|
@@ -247,7 +255,26 @@ export class TiDBSessionStore {
|
|
|
247
255
|
await this.pool.query({ sql: "UPDATE session_meta SET instruction_source = ? WHERE session_id = ? AND instruction_source IS NULL", timeout: BASELINE_IO_DEADLINE_MS }, [next, sessionId]);
|
|
248
256
|
}
|
|
249
257
|
}
|
|
258
|
+
async readSessionWorkspace(sessionId) {
|
|
259
|
+
const [rows] = await this.pool.query(`SELECT ${jsonColumnText("tidb", "workspace")} FROM session_meta WHERE session_id = ?`, [sessionId]);
|
|
260
|
+
const row = rows[0];
|
|
261
|
+
return row === undefined ? undefined : decodeSessionWorkspaceCell(row.workspace, sessionId);
|
|
262
|
+
}
|
|
263
|
+
async recordSessionWorkspace(sessionId, incarnation, workspace) {
|
|
264
|
+
await this.pool.query("UPDATE session_meta SET workspace = ? WHERE session_id = ? AND incarnation = ?", [encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId), sessionId, incarnation]);
|
|
265
|
+
}
|
|
250
266
|
async fork(sourceId, owner) {
|
|
267
|
+
return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner);
|
|
268
|
+
}
|
|
269
|
+
async forkTo(sourceId, owner, target) {
|
|
270
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
271
|
+
const out = await this.forkToOnce(sourceId, owner, target);
|
|
272
|
+
if (out !== "unsettled")
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
throw new ForkTargetUnsettledError(target.id);
|
|
276
|
+
}
|
|
277
|
+
async forkToOnce(sourceId, owner, target) {
|
|
251
278
|
const [metaRows] = await this.pool.query("SELECT leaf_id, owner FROM session_meta WHERE session_id = ?", [sourceId]);
|
|
252
279
|
if (!metaRows[0])
|
|
253
280
|
return null;
|
|
@@ -258,20 +285,30 @@ export class TiDBSessionStore {
|
|
|
258
285
|
return null;
|
|
259
286
|
const point = await forkPointOf(storage);
|
|
260
287
|
const [allRows] = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("tidb", "payload")}, ts FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [sourceId]);
|
|
261
|
-
const { rows: eventRows, leafId: forkLeafId } =
|
|
262
|
-
const newId = uuidv7();
|
|
288
|
+
const { rows: eventRows, leafId: forkLeafId } = selectForkRows(allRows, leafId, point, FORK_LOG_ROW_SHAPE);
|
|
263
289
|
const now = new Date();
|
|
264
290
|
const conn = await this.pool.getConnection();
|
|
291
|
+
let collided = false;
|
|
265
292
|
try {
|
|
266
293
|
await conn.beginTransaction();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const payload = String(r.payload);
|
|
270
|
-
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
|
|
294
|
+
try {
|
|
295
|
+
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, fork_key) VALUES (?,?,?,?,?,?,?,?)", [target.id, forkOwner, now, now, forkLeafId, eventRows.length, mintSessionIncarnation(), target.key ?? null]);
|
|
271
296
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
297
|
+
catch (err) {
|
|
298
|
+
if (!isMysqlDupKeyError(err))
|
|
299
|
+
throw err;
|
|
300
|
+
collided = true;
|
|
301
|
+
}
|
|
302
|
+
if (!collided) {
|
|
303
|
+
for (let seq = 0; seq < eventRows.length; seq++) {
|
|
304
|
+
const r = eventRows[seq];
|
|
305
|
+
const payload = String(r.payload);
|
|
306
|
+
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [target.id, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
|
|
307
|
+
}
|
|
308
|
+
await conn.commit();
|
|
309
|
+
return { outcome: "created", id: target.id };
|
|
310
|
+
}
|
|
311
|
+
await conn.rollback();
|
|
275
312
|
}
|
|
276
313
|
catch (err) {
|
|
277
314
|
try {
|
|
@@ -284,6 +321,9 @@ export class TiDBSessionStore {
|
|
|
284
321
|
finally {
|
|
285
322
|
conn.release();
|
|
286
323
|
}
|
|
324
|
+
const [existing] = await this.pool.query("SELECT owner, fork_key FROM session_meta WHERE session_id = ?", [target.id]);
|
|
325
|
+
const row = existing[0];
|
|
326
|
+
return row === undefined ? "unsettled" : classifyForkTarget(target, { owner: row.owner ?? null, forkKey: row.fork_key ?? null }, forkOwner);
|
|
287
327
|
}
|
|
288
328
|
async exportEntries(sessionId) {
|
|
289
329
|
const [metaRows] = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = ?", [sessionId]);
|
|
@@ -323,7 +363,7 @@ export class TiDBSessionStore {
|
|
|
323
363
|
const conn = await this.pool.getConnection();
|
|
324
364
|
try {
|
|
325
365
|
await conn.beginTransaction();
|
|
326
|
-
await this.
|
|
366
|
+
await this.writeImportedLog(conn, sessionId, owner, validated, { incarnation: mintSessionIncarnation(), forkKey: null }, "create");
|
|
327
367
|
await conn.commit();
|
|
328
368
|
}
|
|
329
369
|
catch (err) {
|
|
@@ -332,7 +372,7 @@ export class TiDBSessionStore {
|
|
|
332
372
|
}
|
|
333
373
|
catch {
|
|
334
374
|
}
|
|
335
|
-
if (err instanceof SessionError)
|
|
375
|
+
if (err instanceof SessionError || err instanceof SessionActiveRunError)
|
|
336
376
|
throw err;
|
|
337
377
|
throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
338
378
|
}
|
|
@@ -346,14 +386,21 @@ export class TiDBSessionStore {
|
|
|
346
386
|
const conn = await this.pool.getConnection();
|
|
347
387
|
try {
|
|
348
388
|
await conn.beginTransaction();
|
|
349
|
-
const [placedRows] = await conn.query(
|
|
350
|
-
|
|
389
|
+
const [placedRows] = await conn.query(`SELECT ${SESSION_META_PLACEMENT_READ_COLUMNS}, fork_key FROM session_meta WHERE session_id = ? FOR UPDATE`, [sessionId]);
|
|
390
|
+
const prior = placedRows[0];
|
|
391
|
+
if (prior?.placement_kind != null) {
|
|
351
392
|
await conn.rollback();
|
|
352
|
-
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${String(
|
|
393
|
+
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${String(prior.placement_kind)} placement partition — a session-sync replace cannot overwrite a delegation transcript`);
|
|
353
394
|
}
|
|
354
|
-
await conn.query("DELETE FROM
|
|
355
|
-
|
|
356
|
-
|
|
395
|
+
const [purged] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ? AND placement_kind IS NULL", [sessionId, owner]);
|
|
396
|
+
const swapped = purged.affectedRows === 1 ? prior : undefined;
|
|
397
|
+
const cells = carriedRowCells(sessionId, swapped === undefined ? undefined : { incarnation: swapped.incarnation, forkKey: swapped.fork_key });
|
|
398
|
+
const priorCell = swapped === undefined ? undefined : placementCellFromRow(sessionId, swapped);
|
|
399
|
+
if (priorCell?.role === "writer") {
|
|
400
|
+
await conn.rollback();
|
|
401
|
+
throw new SessionWriterPresentError(sessionId, priorCell.lane);
|
|
402
|
+
}
|
|
403
|
+
await this.writeImportedLog(conn, sessionId, owner, validated, cells, "replace");
|
|
357
404
|
await conn.commit();
|
|
358
405
|
}
|
|
359
406
|
catch (err) {
|
|
@@ -362,7 +409,7 @@ export class TiDBSessionStore {
|
|
|
362
409
|
}
|
|
363
410
|
catch {
|
|
364
411
|
}
|
|
365
|
-
if (err instanceof SessionError)
|
|
412
|
+
if (err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
|
|
366
413
|
throw err;
|
|
367
414
|
throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
368
415
|
}
|
|
@@ -370,16 +417,29 @@ export class TiDBSessionStore {
|
|
|
370
417
|
conn.release();
|
|
371
418
|
}
|
|
372
419
|
}
|
|
373
|
-
async
|
|
420
|
+
async writeImportedLog(conn, sessionId, owner, validated, cells, mode) {
|
|
374
421
|
let leafId = null;
|
|
375
422
|
for (const e of validated)
|
|
376
423
|
leafId = leafIdAfterEntry(e);
|
|
377
424
|
const now = new Date();
|
|
425
|
+
const imported = importedRowCells(cells.forkKey);
|
|
426
|
+
await conn.query(`INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, ${imported.columns}) VALUES (?,?,?,?,?,?,?,?,?)` +
|
|
427
|
+
(mode === "create" ? " ON DUPLICATE KEY UPDATE session_id = session_id" : ""), [sessionId, owner, now, now, leafId, validated.length, cells.incarnation, ...imported.values]);
|
|
428
|
+
if (mode === "create") {
|
|
429
|
+
const [held] = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
|
|
430
|
+
if (String(held[0]?.incarnation) !== cells.incarnation) {
|
|
431
|
+
throw new SessionError("storage", `import of session ${sessionId} failed`, new Error(`session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "tidb", sessionId);
|
|
435
|
+
if (claim.activeTaskId !== null)
|
|
436
|
+
throw new SessionActiveRunError(sessionId, claim.activeTaskId);
|
|
437
|
+
if (mode === "replace")
|
|
438
|
+
await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
|
|
378
439
|
for (let seq = 0; seq < validated.length; seq++) {
|
|
379
440
|
const e = validated[seq];
|
|
380
441
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [sessionId, seq, e.id, e.parentId ?? null, e.type, JSON.stringify(e), new Date(e.timestamp)]);
|
|
381
442
|
}
|
|
382
|
-
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES (?,?,?,?,?,?,?)", [sessionId, owner, now, now, leafId, validated.length, incarnation]);
|
|
383
443
|
}
|
|
384
444
|
beginImportStaging(realSessionId, token) {
|
|
385
445
|
return new TiDBStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
|
|
@@ -410,6 +470,11 @@ export class TiDBSessionStore {
|
|
|
410
470
|
const conn = await this.pool.getConnection();
|
|
411
471
|
try {
|
|
412
472
|
await conn.beginTransaction();
|
|
473
|
+
const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "tidb", sessionId);
|
|
474
|
+
if (claim.activeTaskId !== null) {
|
|
475
|
+
await conn.rollback();
|
|
476
|
+
throw new SessionActiveRunError(sessionId, claim.activeTaskId);
|
|
477
|
+
}
|
|
413
478
|
const [metaRes] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ?", [sessionId, owner]);
|
|
414
479
|
if (metaRes.affectedRows > 0)
|
|
415
480
|
await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
|
|
@@ -488,6 +553,11 @@ class TiDBStagingHandle {
|
|
|
488
553
|
throw new StagedCommitOwnerChangedError(this.realSessionId);
|
|
489
554
|
}
|
|
490
555
|
}
|
|
556
|
+
const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "tidb", this.realSessionId);
|
|
557
|
+
if (claim.activeTaskId !== null) {
|
|
558
|
+
await conn.rollback();
|
|
559
|
+
throw new SessionActiveRunError(this.realSessionId, claim.activeTaskId);
|
|
560
|
+
}
|
|
491
561
|
const [stagedRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
|
|
492
562
|
const [dstRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
|
|
493
563
|
const [dstMeta] = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = ?", [this.realSessionId]);
|
|
@@ -522,7 +592,16 @@ class TiDBStagingHandle {
|
|
|
522
592
|
let leafId = null;
|
|
523
593
|
if (lastRow[0])
|
|
524
594
|
leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRow[0].payload, this.realSessionId, "last staged row"));
|
|
525
|
-
await conn.query(
|
|
595
|
+
const [settle] = await conn.query(`UPDATE session_meta SET owner = ?, updated_at = ?, leaf_id = ?, leaf_seq = ?, ${MIRROR_ON_COMMIT_SET} WHERE session_id = ? AND ${MIRROR_ON_COMMIT_GUARD}`, [owner, now, leafId, srcIds.length, this.realSessionId]);
|
|
596
|
+
if (settle.affectedRows !== 1) {
|
|
597
|
+
const locked = readPlacementRowStatement("tidb", this.realSessionId, { lock: true });
|
|
598
|
+
const [lockedRows] = await conn.query(locked.text, [...locked.values]);
|
|
599
|
+
const cell = lockedRows[0] === undefined ? undefined : placementCellFromRow(this.realSessionId, lockedRows[0]);
|
|
600
|
+
await conn.rollback();
|
|
601
|
+
if (cell?.role === "writer")
|
|
602
|
+
throw new SessionWriterPresentError(this.realSessionId, cell.lane);
|
|
603
|
+
throw new SessionError("storage", `staged commit for ${this.realSessionId}: the commit point matched no row under a held row lock and the row is not a writer — refusing to guess`);
|
|
604
|
+
}
|
|
526
605
|
const [afterMeta] = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = ?", [this.realSessionId]);
|
|
527
606
|
if (afterMeta[0]?.placement_kind != null) {
|
|
528
607
|
await conn.rollback();
|
|
@@ -537,7 +616,7 @@ class TiDBStagingHandle {
|
|
|
537
616
|
await conn.rollback();
|
|
538
617
|
}
|
|
539
618
|
catch { }
|
|
540
|
-
if (err instanceof SyncConflictError || err instanceof SessionError)
|
|
619
|
+
if (err instanceof SyncConflictError || err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
|
|
541
620
|
throw err;
|
|
542
621
|
throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
543
622
|
}
|
|
@@ -2,6 +2,7 @@ import type { ToolResultProvenance, ToolResultStore, ToolResultSlice, ToolResult
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool, PoolClient as PgPoolClient } from "pg";
|
|
4
4
|
import { type SqlDriver } from "./sql-driver.js";
|
|
5
|
+
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
5
6
|
import { type IndexSpec } from "./ensure-index.js";
|
|
6
7
|
/** PG schema for the tool_result table — the PG translation of tidb-pool.ts's tool_result DDL
|
|
7
8
|
* (LONGTEXT→TEXT, DATETIME(3)→TIMESTAMPTZ(3), inline KEY→separate CREATE INDEX). Disjoint from other stores'
|
|
@@ -14,14 +15,15 @@ export declare function ensureSchema(pool: PgPool | PgPoolClient): Promise<void>
|
|
|
14
15
|
/**
|
|
15
16
|
* [ref] 升级前置断言 —— **拒启**,不是 warn(codex 复审 [high],已核真)。
|
|
16
17
|
*
|
|
17
|
-
* 病:本仓不发 `ALTER TABLE` 迁移(
|
|
18
|
+
* 病:本仓不发 `ALTER TABLE` 迁移(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。于是一台**没跟上**就升上来的
|
|
18
19
|
* 部署,`CREATE TABLE IF NOT EXISTS` 对它是空操作,旧表既没有出处两列也只有 `VARCHAR(190)` 的 ref 列。
|
|
19
20
|
* 那样跑起来的后果不是「少个功能」:每一次 offload 写都会撞 unknown column 报错,而 core 的 offload
|
|
20
21
|
* 失败臂会把错误吞成一条内联占位("[offload LOST at write time…]")—— 服务照跑,工具产物全丢,日志里只有
|
|
21
22
|
* 一句看不出根因的话。这正是「响亮或 fail-closed,禁静默降级」那条要挡的形态。
|
|
22
23
|
*
|
|
23
24
|
* 判据用**能力探测**而不是版本号/information_schema:发一条恒空的 `WHERE 1=0` 读,列不在就报错。
|
|
24
|
-
*
|
|
25
|
+
* 探针读抛且是**可证**的缺列 ⇒ 拒启(其余错误原样上抛,与兄弟探针同一条规则)。拒因的出路是**闭集三条**(与整库对账同一份,{@link describeProbedTableDrift} / {@link schemaWaysOut}),没有销毁性的一条:
|
|
26
|
+
* 缺的出处两列由对账同一套件逐条打印 `ALTER TABLE tool_result ADD COLUMN …`;键宽不够则第 (2) 条是部署者自己把 `ref` 加宽到本构建的定义。
|
|
25
27
|
*/
|
|
26
28
|
export declare function assertToolResultProvenanceSchema(query: (sql: string) => Promise<{
|
|
27
29
|
rows: Record<string, unknown>[];
|
|
@@ -29,10 +31,12 @@ export declare function assertToolResultProvenanceSchema(query: (sql: string) =>
|
|
|
29
31
|
/** Dual-dialect durable ToolResultStore. See the file header for the dialect-delta ledger. */
|
|
30
32
|
export declare class SqlToolResultStore implements ToolResultStore {
|
|
31
33
|
protected readonly db: SqlDriver;
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
readonly retention:
|
|
35
|
-
constructor(db: SqlDriver
|
|
34
|
+
/** 托管留存声明 = 装配交给它的词(后端按扫车道开不开给;不给 ⇒ `"none"`)。读法与「为什么 SQL 店答 managed 而三方法的实现体在
|
|
35
|
+
* `retention-store-sql.ts`」逐字见 {@link MANAGED_RETENTION}(扫车道开着时本店的 `tool_result` 行由那只聚合店按期清:随会话树删 + 孤儿腿)。 */
|
|
36
|
+
readonly retention: RetentionDeclaration;
|
|
37
|
+
constructor(db: SqlDriver, opts?: {
|
|
38
|
+
readonly retention?: RetentionDeclaration;
|
|
39
|
+
});
|
|
36
40
|
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
37
41
|
private q;
|
|
38
42
|
/** D1(试剂盒揪出,[ref] 语义):ref 是主键,而 `deleteBySession` 只认**记录下来的出处**与单射的
|
|
@@ -97,10 +101,14 @@ export declare class SqlToolResultStore implements ToolResultStore {
|
|
|
97
101
|
}
|
|
98
102
|
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
99
103
|
export declare class TiDBToolResultStore extends SqlToolResultStore {
|
|
100
|
-
constructor(pool: MySqlPool
|
|
104
|
+
constructor(pool: MySqlPool, opts?: {
|
|
105
|
+
readonly retention?: RetentionDeclaration;
|
|
106
|
+
});
|
|
101
107
|
}
|
|
102
108
|
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
103
109
|
export declare class PgToolResultStore extends SqlToolResultStore {
|
|
104
|
-
constructor(pool: PgPool
|
|
110
|
+
constructor(pool: PgPool, opts?: {
|
|
111
|
+
readonly retention?: RetentionDeclaration;
|
|
112
|
+
});
|
|
105
113
|
}
|
|
106
114
|
//# sourceMappingURL=tool-result-store-sql.d.ts.map
|