@sema-agent/server 7.138.0 → 7.140.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +457 -7
- package/README.md +7 -4
- package/README.zh-CN.md +6 -4
- package/USAGE.md +91 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +26 -26
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +28 -34
- package/dist/boot/execution-lane.d.ts +9 -1
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/governance-seams.d.ts +3 -2
- package/dist/boot/governance-seams.js +9 -11
- package/dist/boot/memory-boundary.d.ts +14 -51
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +15 -11
- package/dist/boot/memory-consolidation.js +25 -14
- package/dist/boot/resolve-spec.d.ts +4 -6
- package/dist/boot/resolve-spec.js +10 -13
- package/dist/boot/retention-lane.d.ts +5 -5
- package/dist/boot/retention-lane.js +0 -6
- package/dist/boot/runner-deps.d.ts +6 -4
- package/dist/boot/runner-deps.js +3 -1
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-05-execution-env.js +2 -2
- package/dist/boot/stage-06-runners.d.ts +2 -6
- package/dist/boot/stage-06-runners.js +2 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -6
- package/dist/boot/stage-08-reapers.d.ts +2 -6
- package/dist/boot/stage-09-leader.d.ts +2 -6
- package/dist/boot/stage-09-leader.js +1 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -6
- package/dist/boot/stage-10-http-server.js +7 -6
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +27 -40
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +37 -11
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/capabilities/mcp-probe.js +1 -1
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +15 -7
- package/dist/capability-facts.js +8 -3
- package/dist/config-catalog.js +9 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +33 -37
- package/dist/config-provider.js +51 -30
- package/dist/config-types.d.ts +21 -7
- package/dist/config.d.ts +33 -0
- package/dist/config.js +46 -16
- package/dist/delegated-session-owner.d.ts +4 -3
- package/dist/delegated-session-owner.js +1 -1
- package/dist/deployment-shape.d.ts +10 -9
- package/dist/deployment-shape.js +4 -4
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +115 -105
- package/dist/execution-lane-caps.js +76 -82
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +8 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/durable-run-leg.js +9 -5
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +63 -36
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/route-ctx.d.ts +14 -8
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +8 -1
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +5 -0
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +143 -22
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +4 -5
- package/dist/http/routes/tasks.js +41 -33
- package/dist/http/server.d.ts +40 -44
- package/dist/http/server.js +13 -2
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +8 -12
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +29 -5
- package/dist/memory-scope.js +16 -4
- package/dist/memory-sync-client.d.ts +70 -18
- package/dist/memory-sync-client.js +215 -117
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/fail-open.d.ts +12 -0
- package/dist/observability/fail-open.js +12 -0
- package/dist/observability/run-terminal.js +0 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +34 -9
- package/dist/parked-decide.js +12 -19
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +13 -7
- package/dist/peer-seat.js +37 -3
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +44 -26
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/caching-session-store.d.ts +6 -2
- package/dist/plugins/caching-session-store.js +9 -3
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
- package/dist/plugins/checkpoint-store-sql.js +21 -23
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +68 -122
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +9 -8
- package/dist/plugins/file-history-store-sql.js +9 -9
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/local-session-store.d.ts +149 -50
- package/dist/plugins/local-session-store.js +382 -107
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -8
- package/dist/plugins/one-time-migrations.js +408 -38
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +17 -19
- package/dist/plugins/permission-rule-store-sql.js +29 -32
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +31 -20
- package/dist/plugins/pg-session-storage.js +120 -52
- package/dist/plugins/remote-env-adb.d.ts +18 -19
- package/dist/plugins/remote-env-adb.js +91 -66
- package/dist/plugins/remote-env-device.d.ts +23 -31
- package/dist/plugins/remote-env-device.js +19 -17
- package/dist/plugins/remote-env-e2b.d.ts +27 -21
- package/dist/plugins/remote-env-e2b.js +91 -45
- package/dist/plugins/remote-env-file-error.d.ts +3 -1
- package/dist/plugins/remote-env-file-error.js +9 -3
- package/dist/plugins/remote-env-host.d.ts +31 -34
- package/dist/plugins/remote-env-host.js +91 -72
- package/dist/plugins/remote-env-k8s.d.ts +13 -29
- package/dist/plugins/remote-env-k8s.js +63 -55
- package/dist/plugins/remote-env-local-docker.d.ts +24 -12
- package/dist/plugins/remote-env-local-docker.js +49 -61
- package/dist/plugins/remote-env-ssh.d.ts +11 -19
- package/dist/plugins/remote-env-ssh.js +74 -57
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +10 -7
- package/dist/plugins/retention-store-sql.js +13 -7
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +20 -15
- package/dist/plugins/schema-consistency.d.ts +21 -11
- package/dist/plugins/schema-consistency.js +15 -9
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-fork.d.ts +54 -0
- package/dist/plugins/session-fork.js +38 -1
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +5 -7
- package/dist/plugins/store-backend.js +23 -11
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +13 -7
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.d.ts +57 -26
- package/dist/plugins/tidb-session-store.js +127 -59
- package/dist/plugins/tool-result-store-sql.d.ts +5 -4
- package/dist/plugins/tool-result-store-sql.js +21 -13
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +34 -35
- package/dist/runs.d.ts +10 -5
- package/dist/runs.js +52 -66
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +9 -5
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +20 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-a2a.js +5 -6
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +22 -9
- package/dist/task-mcp.js +17 -9
- package/dist/terminal.d.ts +27 -0
- package/dist/terminal.js +6 -0
- package/dist/tool-approval.d.ts +6 -0
- package/dist/tool-approval.js +8 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +1 -0
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +80 -28
- package/dist/trace/self-minted-wire.d.ts +169 -2
- package/dist/trace/self-minted-wire.js +54 -1
- package/dist/url-refusal.d.ts +1 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/dist/web-admin-client/skills-mcp.js +3 -2
- package/dist/web-admin-client/types.d.ts +6 -0
- package/package.json +3 -3
- package/wire-changes/server@7.139.0.json +348 -0
- package/wire-changes/server@7.140.0.json +184 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
|
@@ -6,7 +6,9 @@ import { escapeLike } from "./sql-escape.js";
|
|
|
6
6
|
import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
|
|
7
7
|
import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
|
|
8
8
|
import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
9
|
-
import {
|
|
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";
|
|
10
12
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
11
13
|
import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
|
|
12
14
|
import { createLogger } from "../observability/logger.js";
|
|
@@ -14,17 +16,26 @@ import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, pla
|
|
|
14
16
|
import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
15
17
|
import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue } from "./session-ownership.js";
|
|
16
18
|
import { selectForkRows } from "@sema-agent/core";
|
|
17
|
-
import { FORK_LOG_ROW_SHAPE, forkPointOf } from "./session-fork.js";
|
|
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];
|
|
18
25
|
export class TiDBSessionStore {
|
|
19
26
|
pool;
|
|
20
27
|
retention;
|
|
21
28
|
placements = SQL_SESSION_PLACEMENTS;
|
|
22
29
|
pending = new Map();
|
|
23
30
|
log;
|
|
31
|
+
executionPlacement;
|
|
32
|
+
db;
|
|
24
33
|
constructor(pool, opts = {}) {
|
|
25
34
|
this.pool = pool;
|
|
26
35
|
this.log = opts.log ?? createLogger();
|
|
27
36
|
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
37
|
+
this.db = mysqlDriver(pool);
|
|
38
|
+
this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
|
|
28
39
|
}
|
|
29
40
|
async acquire(sessionId, opts) {
|
|
30
41
|
if (!sessionId) {
|
|
@@ -78,7 +89,7 @@ export class TiDBSessionStore {
|
|
|
78
89
|
}, this.log);
|
|
79
90
|
}
|
|
80
91
|
async claimOwnerless(sessionId, incarnation, owner) {
|
|
81
|
-
const [res] = await this.pool.query(CLAIM_OWNERLESS_PLACED_SQL.
|
|
92
|
+
const [res] = await this.pool.query(CLAIM_OWNERLESS_PLACED_SQL.mysql, [...claimOwnerlessParams(sessionId, incarnation, owner)]);
|
|
82
93
|
return res.affectedRows > 0;
|
|
83
94
|
}
|
|
84
95
|
async sourceFaces(ids) {
|
|
@@ -172,34 +183,21 @@ export class TiDBSessionStore {
|
|
|
172
183
|
where.push("(sm.updated_at < ? OR (sm.updated_at = ? AND sm.session_id < ?))");
|
|
173
184
|
params.push(new Date(opts.cursor.lastActivityAt), new Date(opts.cursor.lastActivityAt), opts.cursor.sessionId);
|
|
174
185
|
}
|
|
175
|
-
return this.
|
|
186
|
+
return (await this.selectListRows({ where, params, limit: opts.limit })).map((a) => a.row);
|
|
176
187
|
}
|
|
177
188
|
async sessionSummaryOf(sessionId) {
|
|
178
|
-
|
|
179
|
-
return rows[0];
|
|
189
|
+
return (await this.listRowOf(sessionId))?.row;
|
|
180
190
|
}
|
|
181
|
-
async
|
|
182
|
-
|
|
183
|
-
const params = [];
|
|
184
|
-
if (patch.title !== undefined) {
|
|
185
|
-
sets.push("title = ?");
|
|
186
|
-
params.push(patch.title);
|
|
187
|
-
}
|
|
188
|
-
if (patch.pinned !== undefined) {
|
|
189
|
-
sets.push("pinned = ?");
|
|
190
|
-
params.push(patch.pinned);
|
|
191
|
-
}
|
|
192
|
-
if (sets.length > 0) {
|
|
193
|
-
params.push(sessionId, face.incarnation);
|
|
194
|
-
await this.pool.query(`UPDATE session_meta SET ${sets.join(", ")} WHERE session_id = ? AND incarnation = ? AND placement_kind IS NULL`, params);
|
|
195
|
-
}
|
|
196
|
-
const rows = await this.selectSummaries({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?", "sm.incarnation = ?"], params: [sessionId, face.incarnation], limit: 1, runSessionId: sessionId });
|
|
197
|
-
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];
|
|
198
193
|
}
|
|
199
|
-
async
|
|
194
|
+
async writeSessionRowCells(sessionId, cells, how) {
|
|
195
|
+
return writeSessionRowCellsSql(this.db, sessionId, cells, how, (id) => this.listRowOf(id));
|
|
196
|
+
}
|
|
197
|
+
async selectListRows(q) {
|
|
200
198
|
const runWhere = q.runSessionId !== undefined ? " WHERE session_id = ?" : "";
|
|
201
199
|
const runParams = q.runSessionId !== undefined ? [q.runSessionId, q.runSessionId] : [];
|
|
202
|
-
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, " +
|
|
203
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 " +
|
|
204
202
|
"FROM session_meta sm " +
|
|
205
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 " +
|
|
@@ -212,9 +210,14 @@ export class TiDBSessionStore {
|
|
|
212
210
|
const [rows] = await this.pool.query(sql, [...runParams, ...q.params, q.limit]);
|
|
213
211
|
return rows.map((r) => {
|
|
214
212
|
const runId = r.last_task_run_id;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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,
|
|
218
221
|
lastActivityAt: r.last_activity instanceof Date ? r.last_activity.toISOString() : String(r.last_activity),
|
|
219
222
|
firstActivityAt: r.first_activity instanceof Date ? r.first_activity.toISOString() : String(r.first_activity),
|
|
220
223
|
runCount: Number(r.run_count),
|
|
@@ -222,9 +225,11 @@ export class TiDBSessionStore {
|
|
|
222
225
|
lastStatus: r.status == null ? "" : String(r.status),
|
|
223
226
|
lastRunId: r.last_run_id ?? null,
|
|
224
227
|
...(typeof runId === "string" && runId !== "" ? { lastTaskRunId: runId } : {}),
|
|
225
|
-
title
|
|
226
|
-
pinned
|
|
228
|
+
title,
|
|
229
|
+
pinned,
|
|
230
|
+
...sessionRowTagKey({ sessionId, owner, incarnation, cells: { title, pinned } }),
|
|
227
231
|
};
|
|
232
|
+
return { row, occupant: { owner, incarnation } };
|
|
228
233
|
});
|
|
229
234
|
}
|
|
230
235
|
async probeTitle(sessionId) {
|
|
@@ -251,7 +256,7 @@ export class TiDBSessionStore {
|
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
async readSessionWorkspace(sessionId) {
|
|
254
|
-
const [rows] = await this.pool.query(`SELECT ${jsonColumnText("
|
|
259
|
+
const [rows] = await this.pool.query(`SELECT ${jsonColumnText("mysql", "workspace")} FROM session_meta WHERE session_id = ?`, [sessionId]);
|
|
255
260
|
const row = rows[0];
|
|
256
261
|
return row === undefined ? undefined : decodeSessionWorkspaceCell(row.workspace, sessionId);
|
|
257
262
|
}
|
|
@@ -259,6 +264,17 @@ export class TiDBSessionStore {
|
|
|
259
264
|
await this.pool.query("UPDATE session_meta SET workspace = ? WHERE session_id = ? AND incarnation = ?", [encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId), sessionId, incarnation]);
|
|
260
265
|
}
|
|
261
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) {
|
|
262
278
|
const [metaRows] = await this.pool.query("SELECT leaf_id, owner FROM session_meta WHERE session_id = ?", [sourceId]);
|
|
263
279
|
if (!metaRows[0])
|
|
264
280
|
return null;
|
|
@@ -268,21 +284,31 @@ export class TiDBSessionStore {
|
|
|
268
284
|
if (storage === null)
|
|
269
285
|
return null;
|
|
270
286
|
const point = await forkPointOf(storage);
|
|
271
|
-
const [allRows] = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("
|
|
287
|
+
const [allRows] = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("mysql", "payload")}, ts FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [sourceId]);
|
|
272
288
|
const { rows: eventRows, leafId: forkLeafId } = selectForkRows(allRows, leafId, point, FORK_LOG_ROW_SHAPE);
|
|
273
|
-
const newId = uuidv7();
|
|
274
289
|
const now = new Date();
|
|
275
290
|
const conn = await this.pool.getConnection();
|
|
291
|
+
let collided = false;
|
|
276
292
|
try {
|
|
277
293
|
await conn.beginTransaction();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const payload = String(r.payload);
|
|
281
|
-
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]);
|
|
282
296
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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();
|
|
286
312
|
}
|
|
287
313
|
catch (err) {
|
|
288
314
|
try {
|
|
@@ -295,12 +321,15 @@ export class TiDBSessionStore {
|
|
|
295
321
|
finally {
|
|
296
322
|
conn.release();
|
|
297
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);
|
|
298
327
|
}
|
|
299
328
|
async exportEntries(sessionId) {
|
|
300
329
|
const [metaRows] = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = ?", [sessionId]);
|
|
301
330
|
if (!metaRows[0])
|
|
302
331
|
return null;
|
|
303
|
-
const [eventRows] = await this.pool.query(`SELECT seq, entry_id, parent_id, type, ${jsonColumnText("
|
|
332
|
+
const [eventRows] = await this.pool.query(`SELECT seq, entry_id, parent_id, type, ${jsonColumnText("mysql", "payload")}, ts FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [sessionId]);
|
|
304
333
|
return eventRows.map((r) => decodeSessionEventPayload(r.payload, sessionId, r.seq));
|
|
305
334
|
}
|
|
306
335
|
async listEntryIds(sessionId) {
|
|
@@ -319,7 +348,7 @@ export class TiDBSessionStore {
|
|
|
319
348
|
async function* gen() {
|
|
320
349
|
let cursor = opts?.afterSeq ?? -1;
|
|
321
350
|
for (;;) {
|
|
322
|
-
const [rows] = await pool.query(`SELECT seq, ${jsonColumnText("
|
|
351
|
+
const [rows] = await pool.query(`SELECT seq, ${jsonColumnText("mysql", "payload")} FROM session_event WHERE session_id = ? AND seq > ? ORDER BY seq ASC LIMIT ?`, [sessionId, cursor, batchSize]);
|
|
323
352
|
for (const r of rows)
|
|
324
353
|
yield decodeSessionEventPayload(r.payload, sessionId, r.seq);
|
|
325
354
|
if (rows.length < batchSize)
|
|
@@ -334,7 +363,7 @@ export class TiDBSessionStore {
|
|
|
334
363
|
const conn = await this.pool.getConnection();
|
|
335
364
|
try {
|
|
336
365
|
await conn.beginTransaction();
|
|
337
|
-
await this.
|
|
366
|
+
await this.writeImportedLog(conn, sessionId, owner, validated, { incarnation: mintSessionIncarnation(), forkKey: null }, "create");
|
|
338
367
|
await conn.commit();
|
|
339
368
|
}
|
|
340
369
|
catch (err) {
|
|
@@ -343,7 +372,7 @@ export class TiDBSessionStore {
|
|
|
343
372
|
}
|
|
344
373
|
catch {
|
|
345
374
|
}
|
|
346
|
-
if (err instanceof SessionError)
|
|
375
|
+
if (err instanceof SessionError || err instanceof SessionActiveRunError)
|
|
347
376
|
throw err;
|
|
348
377
|
throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
349
378
|
}
|
|
@@ -357,14 +386,21 @@ export class TiDBSessionStore {
|
|
|
357
386
|
const conn = await this.pool.getConnection();
|
|
358
387
|
try {
|
|
359
388
|
await conn.beginTransaction();
|
|
360
|
-
const [placedRows] = await conn.query(
|
|
361
|
-
|
|
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) {
|
|
362
392
|
await conn.rollback();
|
|
363
|
-
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`);
|
|
364
394
|
}
|
|
365
|
-
await conn.query("DELETE FROM
|
|
366
|
-
|
|
367
|
-
|
|
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");
|
|
368
404
|
await conn.commit();
|
|
369
405
|
}
|
|
370
406
|
catch (err) {
|
|
@@ -373,7 +409,7 @@ export class TiDBSessionStore {
|
|
|
373
409
|
}
|
|
374
410
|
catch {
|
|
375
411
|
}
|
|
376
|
-
if (err instanceof SessionError)
|
|
412
|
+
if (err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
|
|
377
413
|
throw err;
|
|
378
414
|
throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
|
|
379
415
|
}
|
|
@@ -381,16 +417,29 @@ export class TiDBSessionStore {
|
|
|
381
417
|
conn.release();
|
|
382
418
|
}
|
|
383
419
|
}
|
|
384
|
-
async
|
|
420
|
+
async writeImportedLog(conn, sessionId, owner, validated, cells, mode) {
|
|
385
421
|
let leafId = null;
|
|
386
422
|
for (const e of validated)
|
|
387
423
|
leafId = leafIdAfterEntry(e);
|
|
388
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), "mysql", 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]);
|
|
389
439
|
for (let seq = 0; seq < validated.length; seq++) {
|
|
390
440
|
const e = validated[seq];
|
|
391
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)]);
|
|
392
442
|
}
|
|
393
|
-
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]);
|
|
394
443
|
}
|
|
395
444
|
beginImportStaging(realSessionId, token) {
|
|
396
445
|
return new TiDBStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
|
|
@@ -402,7 +451,7 @@ export class TiDBSessionStore {
|
|
|
402
451
|
}
|
|
403
452
|
}
|
|
404
453
|
async readStagedEntries(stagingId) {
|
|
405
|
-
const [rows] = await this.pool.query(`SELECT seq, ${jsonColumnText("
|
|
454
|
+
const [rows] = await this.pool.query(`SELECT seq, ${jsonColumnText("mysql", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [stagingId]);
|
|
406
455
|
return rows.map((r) => decodeSessionEventPayload(r.payload, stagingId, r.seq));
|
|
407
456
|
}
|
|
408
457
|
async sweepStagingSessions() {
|
|
@@ -421,6 +470,11 @@ export class TiDBSessionStore {
|
|
|
421
470
|
const conn = await this.pool.getConnection();
|
|
422
471
|
try {
|
|
423
472
|
await conn.beginTransaction();
|
|
473
|
+
const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "mysql", sessionId);
|
|
474
|
+
if (claim.activeTaskId !== null) {
|
|
475
|
+
await conn.rollback();
|
|
476
|
+
throw new SessionActiveRunError(sessionId, claim.activeTaskId);
|
|
477
|
+
}
|
|
424
478
|
const [metaRes] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ?", [sessionId, owner]);
|
|
425
479
|
if (metaRes.affectedRows > 0)
|
|
426
480
|
await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
|
|
@@ -499,6 +553,11 @@ class TiDBStagingHandle {
|
|
|
499
553
|
throw new StagedCommitOwnerChangedError(this.realSessionId);
|
|
500
554
|
}
|
|
501
555
|
}
|
|
556
|
+
const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "mysql", this.realSessionId);
|
|
557
|
+
if (claim.activeTaskId !== null) {
|
|
558
|
+
await conn.rollback();
|
|
559
|
+
throw new SessionActiveRunError(this.realSessionId, claim.activeTaskId);
|
|
560
|
+
}
|
|
502
561
|
const [stagedRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
|
|
503
562
|
const [dstRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
|
|
504
563
|
const [dstMeta] = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = ?", [this.realSessionId]);
|
|
@@ -514,8 +573,8 @@ class TiDBStagingHandle {
|
|
|
514
573
|
throw new SyncConflictError(this.realSessionId, rel);
|
|
515
574
|
}
|
|
516
575
|
if (rel.relation === "fast_forward" && opts.resolution !== "overwrite-dst" && dstIds !== null && dstIds.length > 0) {
|
|
517
|
-
const [dstFull] = await conn.query(`SELECT seq, ${jsonColumnText("
|
|
518
|
-
const [stagedFull] = await conn.query(`SELECT entry_id, ${jsonColumnText("
|
|
576
|
+
const [dstFull] = await conn.query(`SELECT seq, ${jsonColumnText("mysql", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE`, [this.realSessionId]);
|
|
577
|
+
const [stagedFull] = await conn.query(`SELECT entry_id, ${jsonColumnText("mysql", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE`, [this.stagingId]);
|
|
519
578
|
const dstSet = new Set(dstIds);
|
|
520
579
|
const dstEntries = dstFull.map((r) => decodeSessionEventPayload(r.payload, this.realSessionId, r.seq));
|
|
521
580
|
const stagedShared = stagedFull
|
|
@@ -529,11 +588,20 @@ class TiDBStagingHandle {
|
|
|
529
588
|
}
|
|
530
589
|
await conn.query("DELETE FROM session_event WHERE session_id = ?", [this.realSessionId]);
|
|
531
590
|
await conn.query("UPDATE session_event SET session_id = ? WHERE session_id = ?", [this.realSessionId, this.stagingId]);
|
|
532
|
-
const [lastRow] = await conn.query(`SELECT ${jsonColumnText("
|
|
591
|
+
const [lastRow] = await conn.query(`SELECT ${jsonColumnText("mysql", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq DESC LIMIT 1 FOR UPDATE`, [this.realSessionId]);
|
|
533
592
|
let leafId = null;
|
|
534
593
|
if (lastRow[0])
|
|
535
594
|
leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRow[0].payload, this.realSessionId, "last staged row"));
|
|
536
|
-
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("mysql", 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
|
+
}
|
|
537
605
|
const [afterMeta] = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = ?", [this.realSessionId]);
|
|
538
606
|
if (afterMeta[0]?.placement_kind != null) {
|
|
539
607
|
await conn.rollback();
|
|
@@ -548,7 +616,7 @@ class TiDBStagingHandle {
|
|
|
548
616
|
await conn.rollback();
|
|
549
617
|
}
|
|
550
618
|
catch { }
|
|
551
|
-
if (err instanceof SyncConflictError || err instanceof SessionError)
|
|
619
|
+
if (err instanceof SyncConflictError || err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
|
|
552
620
|
throw err;
|
|
553
621
|
throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
|
|
554
622
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ToolResultProvenance, ToolResultStore, ToolResultSlice, ToolResultDeletionReport } from "@sema-agent/core";
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool, PoolClient as PgPoolClient } from "pg";
|
|
4
|
-
import { type SqlDriver } from "./sql-driver.js";
|
|
4
|
+
import { type SqlDriver, type SqlDialect } from "./sql-driver.js";
|
|
5
5
|
import type { RetentionDeclaration } from "@sema-agent/core";
|
|
6
6
|
import { type IndexSpec } from "./ensure-index.js";
|
|
7
7
|
/** PG schema for the tool_result table — the PG translation of tidb-pool.ts's tool_result DDL
|
|
@@ -15,18 +15,19 @@ export declare function ensureSchema(pool: PgPool | PgPoolClient): Promise<void>
|
|
|
15
15
|
/**
|
|
16
16
|
* [ref] 升级前置断言 —— **拒启**,不是 warn(codex 复审 [high],已核真)。
|
|
17
17
|
*
|
|
18
|
-
* 病:本仓不发 `ALTER TABLE` 迁移(
|
|
18
|
+
* 病:本仓不发 `ALTER TABLE` 迁移(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。于是一台**没跟上**就升上来的
|
|
19
19
|
* 部署,`CREATE TABLE IF NOT EXISTS` 对它是空操作,旧表既没有出处两列也只有 `VARCHAR(190)` 的 ref 列。
|
|
20
20
|
* 那样跑起来的后果不是「少个功能」:每一次 offload 写都会撞 unknown column 报错,而 core 的 offload
|
|
21
21
|
* 失败臂会把错误吞成一条内联占位("[offload LOST at write time…]")—— 服务照跑,工具产物全丢,日志里只有
|
|
22
22
|
* 一句看不出根因的话。这正是「响亮或 fail-closed,禁静默降级」那条要挡的形态。
|
|
23
23
|
*
|
|
24
24
|
* 判据用**能力探测**而不是版本号/information_schema:发一条恒空的 `WHERE 1=0` 读,列不在就报错。
|
|
25
|
-
*
|
|
25
|
+
* 探针读抛且是**可证**的缺列 ⇒ 拒启(其余错误原样上抛,与兄弟探针同一条规则)。拒因的出路是**闭集三条**(与整库对账同一份,{@link describeProbedTableDrift} / {@link schemaWaysOut}),没有销毁性的一条:
|
|
26
|
+
* 缺的出处两列由对账同一套件逐条打印 `ALTER TABLE tool_result ADD COLUMN …`;键宽不够则第 (2) 条是部署者自己把 `ref` 加宽到本构建的定义。
|
|
26
27
|
*/
|
|
27
28
|
export declare function assertToolResultProvenanceSchema(query: (sql: string) => Promise<{
|
|
28
29
|
rows: Record<string, unknown>[];
|
|
29
|
-
}>, dialect:
|
|
30
|
+
}>, dialect: SqlDialect): Promise<void>;
|
|
30
31
|
/** Dual-dialect durable ToolResultStore. See the file header for the dialect-delta ledger. */
|
|
31
32
|
export declare class SqlToolResultStore implements ToolResultStore {
|
|
32
33
|
protected readonly db: SqlDriver;
|
|
@@ -3,6 +3,8 @@ import { pgSanitizeText } from "./pg-safe-json.js";
|
|
|
3
3
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
4
4
|
import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
|
|
5
5
|
import { ensureIndexes } from "./ensure-index.js";
|
|
6
|
+
import { describeProbedTableDrift, schemaWaysOut } from "./schema-drift-sentence.js";
|
|
7
|
+
import { isMissingColumnError } from "./sql-errors.js";
|
|
6
8
|
export const PG_TOOL_RESULT_SCHEMA = [
|
|
7
9
|
`CREATE TABLE IF NOT EXISTS tool_result (
|
|
8
10
|
ref VARCHAR(518) COLLATE "C" NOT NULL,
|
|
@@ -22,29 +24,35 @@ export async function ensureSchema(pool) {
|
|
|
22
24
|
await ensureIndexes((t, p) => pool.query(t, p), "pg", TOOL_RESULT_INDEXES);
|
|
23
25
|
}
|
|
24
26
|
export async function assertToolResultProvenanceSchema(query, dialect) {
|
|
25
|
-
const widthRow = await query(dialect === "
|
|
27
|
+
const widthRow = await query(dialect === "mysql"
|
|
26
28
|
? "SELECT CHARACTER_MAXIMUM_LENGTH AS len FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'tool_result' AND column_name = 'ref'"
|
|
27
29
|
: "SELECT character_maximum_length AS len FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'tool_result' AND column_name = 'ref'").catch(() => ({ rows: [] }));
|
|
28
30
|
const width = Number(widthRow.rows[0]?.len ?? NaN);
|
|
29
31
|
if (Number.isFinite(width) && width < MAX_MINTED_TOOL_RESULT_REF_CHARS) {
|
|
30
32
|
throw new Error(`tool_result.ref is VARCHAR(${width}) but core 5.26.0 mints refs up to ${MAX_MINTED_TOOL_RESULT_REF_CHARS} characters ` +
|
|
31
|
-
`(#119 made the ref injective: four \`~\`-separated segments). A truncating primary key aliases two distinct refs onto ` +
|
|
32
|
-
`one row — one run's tool output reads back from another run's ref.
|
|
33
|
-
`
|
|
34
|
-
`
|
|
33
|
+
`(#119 made the ref injective: four \`~\`-separated segments) — refusing to start. A truncating primary key aliases two distinct refs onto ` +
|
|
34
|
+
`one row — one run's tool output reads back from another run's ref. ` +
|
|
35
|
+
`The ${dialect === "pg" ? "PostgreSQL" : "MySQL-protocol"} database is behind this build on that key column; offloaded results are a recoverable-window cache, ` +
|
|
36
|
+
`inline previews in transcripts are unaffected.\n\n` +
|
|
37
|
+
` table tool_result: column ref is VARCHAR(${width}); this build needs at least VARCHAR(${MAX_MINTED_TOOL_RESULT_REF_CHARS}) — the database is OLDER than this build.\n\n` +
|
|
38
|
+
schemaWaysOut({
|
|
39
|
+
when: "a key column is narrower than this build declares",
|
|
40
|
+
action: "run an ALTER TABLE that widens tool_result.ref to this build's definition yourself",
|
|
41
|
+
}).join("\n"));
|
|
35
42
|
}
|
|
36
43
|
try {
|
|
37
44
|
await query("SELECT owner_session_id, owner_task_id FROM tool_result WHERE 1=0");
|
|
38
45
|
}
|
|
39
46
|
catch (err) {
|
|
47
|
+
if (!isMissingColumnError(err, dialect))
|
|
48
|
+
throw err;
|
|
40
49
|
throw new Error(`tool_result is missing the #119 provenance columns (owner_session_id / owner_task_id) — refusing to start. ` +
|
|
41
50
|
`This release picks up @sema-agent/core 5.26.0, which changed how an offloaded tool result is addressed ` +
|
|
42
|
-
`(the ref grew past the old VARCHAR(190) key) and made the store record who wrote each row.
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
`
|
|
46
|
-
|
|
47
|
-
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
|
|
51
|
+
`(the ref grew past the old VARCHAR(190) key) and made the store record who wrote each row. Without the columns every offload write ` +
|
|
52
|
+
`fails with unknown column and core's failure arm turns it into an inline placeholder — the service keeps running and the tool output is lost. ` +
|
|
53
|
+
`Offloaded tool results are a recoverable-window cache (the inline previews in transcripts are unaffected).\n` +
|
|
54
|
+
`Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
|
|
55
|
+
(await describeProbedTableDrift(query, dialect, ["tool_result"])));
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
export class SqlToolResultStore {
|
|
@@ -54,8 +62,8 @@ export class SqlToolResultStore {
|
|
|
54
62
|
this.db = db;
|
|
55
63
|
this.retention = opts.retention ?? UNMANAGED_RETENTION;
|
|
56
64
|
}
|
|
57
|
-
q(
|
|
58
|
-
return this.db.dialect === "
|
|
65
|
+
q(mysql, pg) {
|
|
66
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
59
67
|
}
|
|
60
68
|
isUnsafeRef(ref) {
|
|
61
69
|
try {
|
|
@@ -15,8 +15,8 @@ export class SqlWorkflowJournalStore {
|
|
|
15
15
|
this.db = db;
|
|
16
16
|
this.resumeClaimTtlMs = opts?.resumeClaimTtlMs ?? DEFAULT_RESUME_CLAIM_TTL_MS;
|
|
17
17
|
}
|
|
18
|
-
q(
|
|
19
|
-
return this.db.dialect === "
|
|
18
|
+
q(mysql, pg) {
|
|
19
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
20
20
|
}
|
|
21
21
|
async append(runId, scope, entry) {
|
|
22
22
|
assertJournalEntryFits(entry);
|
|
@@ -35,7 +35,7 @@ export class SqlWorkflowJournalStore {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
async load(runId, scope) {
|
|
38
|
-
const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("
|
|
38
|
+
const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("mysql", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC`, `SELECT call_key, ${jsonColumnText("pg", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC`), [runId, scope]);
|
|
39
39
|
return rows.map((r) => {
|
|
40
40
|
const callKey = String(r.call_key);
|
|
41
41
|
return journalEntryOfCell(runId, callKey, r.result, `workflow_journal.result for run ${runId} entry ${callKey}`);
|
|
@@ -69,10 +69,10 @@ export class SqlWorkflowJournalStore {
|
|
|
69
69
|
return row !== undefined && row.ordinal === ordinal && row.resultJson !== null ? row.resultJson : undefined;
|
|
70
70
|
}
|
|
71
71
|
async readRows(runId, scope, from, limit, inlineBytes) {
|
|
72
|
-
const params = this.db.dialect === "
|
|
72
|
+
const params = this.db.dialect === "mysql"
|
|
73
73
|
? [inlineBytes, runId, scope, from, limit]
|
|
74
74
|
: [runId, scope, inlineBytes, from, limit];
|
|
75
|
-
const { rows } = await this.db.query(this.q(`SELECT ordinal, call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("
|
|
75
|
+
const { rows } = await this.db.query(this.q(`SELECT ordinal, call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("mysql", "result")} END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? AND ordinal >= ? ORDER BY ordinal ASC LIMIT ?`, `SELECT ordinal, call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE ${jsonColumnTextExpr("pg", "result")} END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 AND ordinal >= $4 ORDER BY ordinal ASC LIMIT $5`), params);
|
|
76
76
|
return rows.map((r) => ({ ordinal: Number(r.ordinal), callKey: String(r.call_key), resultJson: r.result_json === null ? null : String(r.result_json), resultBytes: Number(r.result_bytes) }));
|
|
77
77
|
}
|
|
78
78
|
async deleteByRun(runId) {
|