@sema-agent/server 7.7.1 → 7.8.1

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.
Files changed (36) hide show
  1. package/dist/approval-card.d.ts +2 -2
  2. package/dist/approval-card.js +2 -2
  3. package/dist/approval-deny-reasons.d.ts +1 -1
  4. package/dist/approval-deny-reasons.js +1 -1
  5. package/dist/approval-reconciler.js +2 -2
  6. package/dist/config-types.d.ts +1 -1
  7. package/dist/http/routes/runs.js +1 -1
  8. package/dist/http/server.js +6 -1
  9. package/dist/plugins/approval-ask-store-memory.d.ts +1 -1
  10. package/dist/plugins/approval-ask-store-memory.js +17 -17
  11. package/dist/plugins/approval-ask-store-sql.d.ts +12 -12
  12. package/dist/plugins/approval-ask-store-sql.js +58 -58
  13. package/dist/plugins/background-agent-store-sql.d.ts +1 -1
  14. package/dist/plugins/background-agent-store-sql.js +2 -2
  15. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  16. package/dist/plugins/checkpoint-store-sql.js +6 -6
  17. package/dist/plugins/mailbox-store-sql.d.ts +2 -2
  18. package/dist/plugins/mailbox-store-sql.js +2 -2
  19. package/dist/plugins/memory-engine-pg.d.ts +2 -2
  20. package/dist/plugins/memory-engine-pg.js +17 -17
  21. package/dist/plugins/memory-engine-tidb.d.ts +2 -2
  22. package/dist/plugins/memory-engine-tidb.js +16 -16
  23. package/dist/plugins/memory-sync-store-pg.d.ts +2 -2
  24. package/dist/plugins/memory-sync-store-pg.js +19 -19
  25. package/dist/plugins/memory-sync-store-tidb.d.ts +1 -1
  26. package/dist/plugins/memory-sync-store-tidb.js +21 -21
  27. package/dist/plugins/pg-pool.js +26 -26
  28. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  29. package/dist/plugins/task-list-store-sql.js +11 -11
  30. package/dist/plugins/tidb-pool.js +26 -26
  31. package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
  32. package/dist/plugins/workflow-journal-store-sql.js +4 -4
  33. package/dist/plugins/workflow-run-store-sql.d.ts +1 -1
  34. package/dist/plugins/workflow-run-store-sql.js +9 -9
  35. package/dist/security.js +9 -1
  36. package/package.json +2 -2
@@ -26,8 +26,8 @@ import { pgSafeJsonStringify, pgHasUnstorable } from "./pg-safe-json.js";
26
26
  /** Table names (single source). Deliberately DISJOINT from the legacy `agent_memory*` tables —
27
27
  * the retired MemoryStore plane and this entry plane must never cross-write. */
28
28
  export const PG_MEMORY_ENGINE_TABLES = {
29
- entries: "agent_memory_engine_entries",
30
- cursors: "agent_memory_engine_cursors",
29
+ entry: "agent_memory_engine_entry",
30
+ cursor: "agent_memory_engine_cursor",
31
31
  };
32
32
  /** Idempotent DDL for the entry plane. Call once at startup (or run through your migration tool). */
33
33
  export async function ensurePgMemoryEngineSchema(query, opts = {}) {
@@ -41,7 +41,7 @@ export async function ensurePgMemoryEngineSchema(query, opts = {}) {
41
41
  if (vec?.pgvector)
42
42
  await query("CREATE EXTENSION IF NOT EXISTS vector");
43
43
  const embeddingCol = vec?.pgvector ? `vector(${vec.dimensions})` : "jsonb";
44
- await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.entries} (
44
+ await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.entry} (
45
45
  id text COLLATE "C" PRIMARY KEY,
46
46
  scope text COLLATE "C" NOT NULL,
47
47
  slug text COLLATE "C" NOT NULL,
@@ -53,7 +53,7 @@ export async function ensurePgMemoryEngineSchema(query, opts = {}) {
53
53
  embedding ${embeddingCol}
54
54
  )`);
55
55
  try {
56
- await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_ENGINE_TABLES.entries}_scope ON ${PG_MEMORY_ENGINE_TABLES.entries} (scope)`);
56
+ await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_ENGINE_TABLES.entry}_scope ON ${PG_MEMORY_ENGINE_TABLES.entry} (scope)`);
57
57
  }
58
58
  catch {
59
59
  /* pg-mem: partial CREATE INDEX support — the index is a perf add-on, never correctness */
@@ -63,8 +63,8 @@ export async function ensurePgMemoryEngineSchema(query, opts = {}) {
63
63
  // insert, leaving two entries whose file projections overwrite each other at materialize. The DB
64
64
  // constraint is the only true arbiter; the add path catches the violation and retries the next
65
65
  // suffix. NOT wrapped in try/catch — if this constraint cannot be created, the backend is unsafe.
66
- await query(`CREATE UNIQUE INDEX IF NOT EXISTS uq_${PG_MEMORY_ENGINE_TABLES.entries}_scope_slug ON ${PG_MEMORY_ENGINE_TABLES.entries} (scope, slug)`);
67
- await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.cursors} (
66
+ await query(`CREATE UNIQUE INDEX IF NOT EXISTS uq_${PG_MEMORY_ENGINE_TABLES.entry}_scope_slug ON ${PG_MEMORY_ENGINE_TABLES.entry} (scope, slug)`);
67
+ await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.cursor} (
68
68
  scope text COLLATE "C" PRIMARY KEY,
69
69
  cursor text COLLATE "C" NOT NULL
70
70
  )`);
@@ -149,14 +149,14 @@ export class PgMemoryEngineBackend {
149
149
  const uniq = [...new Set(scopes)];
150
150
  if (uniq.length === 0)
151
151
  return [];
152
- const res = await this.query(`SELECT id, scope, slug, frontmatter, rev, mtime_ms, size_bytes FROM ${PG_MEMORY_ENGINE_TABLES.entries} WHERE scope IN (${inPlaceholders(1, uniq.length)}) ORDER BY id`, uniq);
152
+ const res = await this.query(`SELECT id, scope, slug, frontmatter, rev, mtime_ms, size_bytes FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE scope IN (${inPlaceholders(1, uniq.length)}) ORDER BY id`, uniq);
153
153
  return res.rows.map((r) => this.headerFromRow(r));
154
154
  }
155
155
  async getByIds(ids) {
156
156
  const uniq = [...new Set(ids)];
157
157
  if (uniq.length === 0)
158
158
  return [];
159
- const res = await this.query(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${PG_MEMORY_ENGINE_TABLES.entries} WHERE id IN (${inPlaceholders(1, uniq.length)}) ORDER BY id`, uniq);
159
+ const res = await this.query(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id IN (${inPlaceholders(1, uniq.length)}) ORDER BY id`, uniq);
160
160
  return res.rows.map((r) => this.entryFromRow(r)); // unknown ids: silently absent
161
161
  }
162
162
  async search(query, scopes, opts) {
@@ -180,7 +180,7 @@ export class PgMemoryEngineBackend {
180
180
  if (Array.isArray(qv) && qv.length === this.embedder.dimensions) {
181
181
  const vecParam = scopes.length + 1;
182
182
  const res = await this.query(`SELECT id, scope, slug, frontmatter, rev, mtime_ms, size_bytes, embedding <=> $${vecParam}::vector AS dist
183
- FROM ${PG_MEMORY_ENGINE_TABLES.entries}
183
+ FROM ${PG_MEMORY_ENGINE_TABLES.entry}
184
184
  WHERE scope IN (${inPlaceholders(1, scopes.length)}) AND embedding IS NOT NULL
185
185
  ORDER BY embedding <=> $${vecParam}::vector ASC
186
186
  LIMIT ${Math.min(limit, 100_000)}`, [...scopes, vectorLiteral(qv)]);
@@ -188,7 +188,7 @@ export class PgMemoryEngineBackend {
188
188
  }
189
189
  // Wrong-dimension query vector (transient embedder fault) — fall open to the in-process path.
190
190
  }
191
- const res = await this.query(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes, embedding FROM ${PG_MEMORY_ENGINE_TABLES.entries} WHERE scope IN (${inPlaceholders(1, scopes.length)})`, [...scopes]);
191
+ const res = await this.query(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes, embedding FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE scope IN (${inPlaceholders(1, scopes.length)})`, [...scopes]);
192
192
  const queryVec = this.embedder ? await this.embedder.embed(query).catch(() => null) : null;
193
193
  const dims = this.embedder?.dimensions;
194
194
  const scored = [];
@@ -256,7 +256,7 @@ export class PgMemoryEngineBackend {
256
256
  return report;
257
257
  }
258
258
  async applyOne(patch, report, plannedSlugs, historyRows) {
259
- const T = PG_MEMORY_ENGINE_TABLES.entries;
259
+ const T = PG_MEMORY_ENGINE_TABLES.entry;
260
260
  // A patch whose entry carries a DIFFERENT id than the patch addresses is
261
261
  // malformed — the two backends would otherwise diverge (File writes the entry's id, Pg would
262
262
  // key the SELECT on patch.id but the write on entry.id). Refused up front, both backends alike.
@@ -354,7 +354,7 @@ export class PgMemoryEngineBackend {
354
354
  }
355
355
  // update/delete address an EXISTING entry by immutable id. (scope in the projection = the
356
356
  // history row's scope for delete — a tombstone can't be joined back to the deleted row.)
357
- const cur = await this.query(`SELECT slug, rev, scope, frontmatter FROM ${PG_MEMORY_ENGINE_TABLES.entries} WHERE id = $1`, [patch.id]);
357
+ const cur = await this.query(`SELECT slug, rev, scope, frontmatter FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1`, [patch.id]);
358
358
  if (cur.rows.length === 0) {
359
359
  report.conflicts.push({ op: patch.op, id: patch.id, reason: "unknown id (entry not found in backend)" });
360
360
  return;
@@ -395,7 +395,7 @@ export class PgMemoryEngineBackend {
395
395
  if (patch.op === "delete") {
396
396
  // Conditional on the rev we just judged — a write that lands between the SELECT and this DELETE
397
397
  // re-reports as the same CAS conflict instead of silently deleting the newer committed state.
398
- const res = await this.query(`DELETE FROM ${PG_MEMORY_ENGINE_TABLES.entries} WHERE id = $1 AND rev = $2 RETURNING id`, [patch.id, currentRev]);
398
+ const res = await this.query(`DELETE FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1 AND rev = $2 RETURNING id`, [patch.id, currentRev]);
399
399
  if (res.rows.length === 0) {
400
400
  report.conflicts.push({ op: "delete", id: patch.id, reason: "rev mismatch (concurrent change)", baseRev: patch.baseRev, currentRev: await this.freshRev(patch.id, currentRev) });
401
401
  return;
@@ -421,7 +421,7 @@ export class PgMemoryEngineBackend {
421
421
  const cEntry = entry; // R3 终形:拒绝式后恒干净,rev 对拍原文
422
422
  const rev = computeEntryRev(cEntry);
423
423
  const embedding = await this.embeddingParam(this.haystackOf(cEntry.slug, cEntry.frontmatter, cEntry.body));
424
- const res = await this.query(`UPDATE ${PG_MEMORY_ENGINE_TABLES.entries}
424
+ const res = await this.query(`UPDATE ${PG_MEMORY_ENGINE_TABLES.entry}
425
425
  SET scope = $2, slug = $3, frontmatter = $4, body = $5, rev = $6, mtime_ms = $7, size_bytes = $8, embedding = $9
426
426
  WHERE id = $1 AND rev = $10
427
427
  RETURNING id`, [cEntry.id, cEntry.scope, cEntry.slug, pgSafeJsonStringify(cEntry.frontmatter), cEntry.body, rev, this.clock(), Buffer.byteLength(serializeEntryFile(cEntry), "utf8"), embedding, currentRev]);
@@ -438,15 +438,15 @@ export class PgMemoryEngineBackend {
438
438
  * SELECT→write window), the report's `currentRev` must be the rev NOW — re-reporting the pre-race
439
439
  * SELECT value could claim `baseRev === currentRev` alongside a "rev mismatch" reason. */
440
440
  async freshRev(id, fallback) {
441
- const res = await this.query(`SELECT rev FROM ${PG_MEMORY_ENGINE_TABLES.entries} WHERE id = $1`, [id]);
441
+ const res = await this.query(`SELECT rev FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1`, [id]);
442
442
  return res.rows.length > 0 ? String(res.rows[0].rev) : fallback;
443
443
  }
444
444
  async getConsolidationCursor(scope) {
445
- const res = await this.query(`SELECT cursor FROM ${PG_MEMORY_ENGINE_TABLES.cursors} WHERE scope = $1`, [scope]);
445
+ const res = await this.query(`SELECT cursor FROM ${PG_MEMORY_ENGINE_TABLES.cursor} WHERE scope = $1`, [scope]);
446
446
  return res.rows.length > 0 ? String(res.rows[0].cursor) : undefined;
447
447
  }
448
448
  async setConsolidationCursor(scope, cursor) {
449
- await this.query(`INSERT INTO ${PG_MEMORY_ENGINE_TABLES.cursors} (scope, cursor) VALUES ($1, $2)
449
+ await this.query(`INSERT INTO ${PG_MEMORY_ENGINE_TABLES.cursor} (scope, cursor) VALUES ($1, $2)
450
450
  ON CONFLICT (scope) DO UPDATE SET cursor = $2`, [scope, cursor]);
451
451
  }
452
452
  }
@@ -5,8 +5,8 @@ import type { Pool } from "mysql2/promise";
5
5
  /** Table names (single source) — SAME names as PG_MEMORY_ENGINE_TABLES (the two dialects never share
6
6
  * one database), deliberately DISJOINT from the legacy `agent_memory*` MemoryStore plane. */
7
7
  export declare const TIDB_MEMORY_ENGINE_TABLES: {
8
- readonly entries: "agent_memory_engine_entries";
9
- readonly cursors: "agent_memory_engine_cursors";
8
+ readonly entry: "agent_memory_engine_entry";
9
+ readonly cursor: "agent_memory_engine_cursor";
10
10
  };
11
11
  /** Idempotent DDL for the entry plane (TiDB dialect). Call once at startup.
12
12
  * Key lengths: utf8mb4 = 4 bytes/char and the default index-key cap is 3072 bytes, so the
@@ -12,7 +12,7 @@
12
12
  // - add 路径 Pg 用 `INSERT … ON CONFLICT (id) DO UPDATE`(冲突靶=仅 PK)。MySQL 的 ON DUPLICATE KEY
13
13
  // UPDATE 会被 **任意** 唯一键触发——(scope,slug) 撞键时它会把竞争对手的行改写成本 entry 的 id(数据毁),
14
14
  // 不能用;拆成「不存在→INSERT / 已存在→UPDATE by id」,两条腿的 ER_DUP_ENTRY 都按键名分诊(PRIMARY=
15
- // 并发同 id add → 转 UPDATE 腿;uq_agent_memory_engine_entries_scope_slug=并发 slug 竞争 → 下一个 -n 后缀重试);
15
+ // 并发同 id add → 转 UPDATE 腿;uq_agent_memory_engine_entry_scope_slug=并发 slug 竞争 → 下一个 -n 后缀重试);
16
16
  // - unique violation 判定:ER_DUP_ENTRY(errno 1062),不是 Pg 的 23505;
17
17
  // - pg-mem 伪影 workaround(索引列 ANY 零行/重复占位符重复行)不抄——MySQL 本就走 IN-list,dedupe 保留
18
18
  // 是因为它同时是诚实语义(重复 key 问的是同一个 entry,契约锁死 once-per-entry);
@@ -27,14 +27,14 @@ import { pgHasUnstorable } from "./pg-safe-json.js";
27
27
  /** Table names (single source) — SAME names as PG_MEMORY_ENGINE_TABLES (the two dialects never share
28
28
  * one database), deliberately DISJOINT from the legacy `agent_memory*` MemoryStore plane. */
29
29
  export const TIDB_MEMORY_ENGINE_TABLES = {
30
- entries: "agent_memory_engine_entries",
31
- cursors: "agent_memory_engine_cursors",
30
+ entry: "agent_memory_engine_entry",
31
+ cursor: "agent_memory_engine_cursor",
32
32
  };
33
33
  /** Idempotent DDL for the entry plane (TiDB dialect). Call once at startup.
34
34
  * Key lengths: utf8mb4 = 4 bytes/char and the default index-key cap is 3072 bytes, so the
35
35
  * (scope, slug) unique key budget is scope 190 + slug 512 = 702 chars ⇒ 2808 bytes ≤ 3072. */
36
36
  export async function ensureTiDBMemoryEngineSchema(pool) {
37
- await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_ENGINE_TABLES.entries} (
37
+ await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_ENGINE_TABLES.entry} (
38
38
  id VARCHAR(64) NOT NULL,
39
39
  scope VARCHAR(190) NOT NULL,
40
40
  slug VARCHAR(512) NOT NULL,
@@ -45,13 +45,13 @@ export async function ensureTiDBMemoryEngineSchema(pool) {
45
45
  size_bytes INT NOT NULL,
46
46
  embedding JSON NULL,
47
47
  PRIMARY KEY (id),
48
- KEY idx_agent_memory_engine_entries_scope (scope),
49
- UNIQUE KEY uq_agent_memory_engine_entries_scope_slug (scope, slug)
48
+ KEY idx_agent_memory_engine_entry_scope (scope),
49
+ UNIQUE KEY uq_agent_memory_engine_entry_scope_slug (scope, slug)
50
50
  ) COLLATE utf8mb4_bin`);
51
51
  // The (scope, slug) UNIQUE key above is CORRECTNESS, not perf (Pg 版同注):two
52
52
  // instances racing an add of one scope+slug (different ids) can both probe "free" and both insert;
53
53
  // the DB constraint is the only true arbiter and the add path retries the next suffix on violation.
54
- await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_ENGINE_TABLES.cursors} (
54
+ await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_ENGINE_TABLES.cursor} (
55
55
  scope VARCHAR(190) NOT NULL,
56
56
  \`cursor\` TEXT NOT NULL,
57
57
  PRIMARY KEY (scope)
@@ -62,7 +62,7 @@ function isDupEntry(err) {
62
62
  return false;
63
63
  return err.code === "ER_DUP_ENTRY" || err.errno === 1062;
64
64
  }
65
- /** ER_DUP_ENTRY messages name the violated key (`… for key 'tbl.PRIMARY'` / `… for key 'tbl.uq_agent_memory_engine_entries_scope_slug'`)
65
+ /** ER_DUP_ENTRY messages name the violated key (`… for key 'tbl.PRIMARY'` / `… for key 'tbl.uq_agent_memory_engine_entry_scope_slug'`)
66
66
  * — the add path uses this to tell "id already exists (→ overwrite leg)" from "slug race (→ -n retry)". */
67
67
  function isPrimaryKeyDup(err) {
68
68
  return isDupEntry(err) && /for key '[^']*PRIMARY'/i.test(String(err.message ?? ""));
@@ -134,14 +134,14 @@ export class TiDBMemoryEngineBackend {
134
134
  const uniq = [...new Set(scopes)];
135
135
  if (uniq.length === 0)
136
136
  return [];
137
- const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entries} WHERE scope IN (${inPlaceholders(uniq.length)}) ORDER BY id`, uniq);
137
+ const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entry} WHERE scope IN (${inPlaceholders(uniq.length)}) ORDER BY id`, uniq);
138
138
  return rows.map((r) => this.headerFromRow(r));
139
139
  }
140
140
  async getByIds(ids) {
141
141
  const uniq = [...new Set(ids)];
142
142
  if (uniq.length === 0)
143
143
  return [];
144
- const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entries} WHERE id IN (${inPlaceholders(uniq.length)}) ORDER BY id`, uniq);
144
+ const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entry} WHERE id IN (${inPlaceholders(uniq.length)}) ORDER BY id`, uniq);
145
145
  return rows.map((r) => this.entryFromRow(r)); // unknown ids: silently absent
146
146
  }
147
147
  async search(query, scopes, opts) {
@@ -155,7 +155,7 @@ export class TiDBMemoryEngineBackend {
155
155
  if (q.size === 0)
156
156
  return [];
157
157
  const uniq = [...new Set(scopes)];
158
- const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entries} WHERE scope IN (${inPlaceholders(uniq.length)})`, uniq);
158
+ const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entry} WHERE scope IN (${inPlaceholders(uniq.length)})`, uniq);
159
159
  const scored = [];
160
160
  for (const raw of rows) {
161
161
  const r = raw;
@@ -212,7 +212,7 @@ export class TiDBMemoryEngineBackend {
212
212
  return report;
213
213
  }
214
214
  async applyOne(patch, report, plannedSlugs, historyRows) {
215
- const T = TIDB_MEMORY_ENGINE_TABLES.entries;
215
+ const T = TIDB_MEMORY_ENGINE_TABLES.entry;
216
216
  // A patch whose entry carries a DIFFERENT id than the patch addresses is malformed — refused up
217
217
  // front, all backends alike (Pg 版同注).
218
218
  if (patch.op !== "delete" && patch.entry !== undefined && patch.entry.id !== patch.id) {
@@ -263,7 +263,7 @@ export class TiDBMemoryEngineBackend {
263
263
  }
264
264
  }
265
265
  // Slug collision with a DIFFERENT entry → deterministic `-n` suffix (file-backend parity); the
266
- // probe is only an optimization — the write INSIDE the loop is arbitrated by uq_agent_memory_engine_entries_scope_slug, and
266
+ // probe is only an optimization — the write INSIDE the loop is arbitrated by uq_agent_memory_engine_entry_scope_slug, and
267
267
  // a concurrent loser retries the next suffix (Pg 版同注).
268
268
  let slug = entry.slug;
269
269
  for (let n = 2;; n++) {
@@ -419,7 +419,7 @@ export class TiDBMemoryEngineBackend {
419
419
  /** The row's rev NOW, or undefined if the row is gone. The existence distinction is the whole point
420
420
  * (codex lens-3): a concurrent delete must not be mistaken for an idempotent already-at-target write. */
421
421
  async freshRevOrMissing(id) {
422
- const rows = await this.selectRows(`SELECT rev FROM ${TIDB_MEMORY_ENGINE_TABLES.entries} WHERE id = ?`, [id]);
422
+ const rows = await this.selectRows(`SELECT rev FROM ${TIDB_MEMORY_ENGINE_TABLES.entry} WHERE id = ?`, [id]);
423
423
  return rows.length > 0 ? String(rows[0].rev) : undefined;
424
424
  }
425
425
  /** When the conditional write reports zero rows (a write landed inside the SELECT→write window), the
@@ -429,13 +429,13 @@ export class TiDBMemoryEngineBackend {
429
429
  return (await this.freshRevOrMissing(id)) ?? fallback;
430
430
  }
431
431
  async getConsolidationCursor(scope) {
432
- const rows = await this.selectRows(`SELECT \`cursor\` FROM ${TIDB_MEMORY_ENGINE_TABLES.cursors} WHERE scope = ?`, [scope]);
432
+ const rows = await this.selectRows(`SELECT \`cursor\` FROM ${TIDB_MEMORY_ENGINE_TABLES.cursor} WHERE scope = ?`, [scope]);
433
433
  return rows.length > 0 ? String(rows[0].cursor) : undefined;
434
434
  }
435
435
  async setConsolidationCursor(scope, cursor) {
436
436
  // Single unique key (the PK) on this table — ON DUPLICATE KEY UPDATE is unambiguous here,
437
437
  // unlike the two-unique-key entries table (see the file-head dialect note).
438
- await this.write(`INSERT INTO ${TIDB_MEMORY_ENGINE_TABLES.cursors} (scope, \`cursor\`) VALUES (?, ?) ON DUPLICATE KEY UPDATE \`cursor\` = VALUES(\`cursor\`)`, [scope, cursor]);
438
+ await this.write(`INSERT INTO ${TIDB_MEMORY_ENGINE_TABLES.cursor} (scope, \`cursor\`) VALUES (?, ?) ON DUPLICATE KEY UPDATE \`cursor\` = VALUES(\`cursor\`)`, [scope, cursor]);
439
439
  }
440
440
  }
441
441
  //# sourceMappingURL=memory-engine-tidb.js.map
@@ -2,7 +2,7 @@ import type { MemorySyncCursor } from "@sema-agent/core";
2
2
  import type { PgQueryFn } from "./pg-query.js";
3
3
  /** Table names (single source). SAME names as the TiDB twin (the two dialects never share one DB). */
4
4
  export declare const PG_MEMORY_SYNC_TABLES: {
5
- readonly syncCursors: "agent_memory_engine_sync_cursors";
5
+ readonly syncCursor: "agent_memory_engine_sync_cursor";
6
6
  readonly pushQueue: "agent_memory_engine_push_queue";
7
7
  readonly history: "agent_memory_engine_history";
8
8
  };
@@ -37,7 +37,7 @@ export interface MemorySyncStore {
37
37
  /** Whole-row replace (round semantics — baseRevs is swapped as one map, never merged). */
38
38
  putCursor(cursor: MemorySyncCursor): Promise<void>;
39
39
  enqueue(item: PushQueueItem): Promise<void>;
40
- /** Lease-claim up to `limit` due rows (next_attempt_at <= nowMs), oldest-due first. Claimed rows
40
+ /** Lease-claim up to `limit` due rows (next_attempt_at_ms <= nowMs), oldest-due first. Claimed rows
41
41
  * are invisible to other claimers until `complete`/`fail` or lease expiry (crash self-heal). */
42
42
  claimDue(nowMs: number, limit: number): Promise<ClaimedPushQueueItem[]>;
43
43
  /** Success = delete the row (outbox posture: the queue only holds outstanding work). */
@@ -1,13 +1,13 @@
1
1
  import { pgSafeJsonStringify, pgProtocolJsonStringify } from "./pg-safe-json.js";
2
2
  /** Table names (single source). SAME names as the TiDB twin (the two dialects never share one DB). */
3
3
  export const PG_MEMORY_SYNC_TABLES = {
4
- syncCursors: "agent_memory_engine_sync_cursors",
4
+ syncCursor: "agent_memory_engine_sync_cursor",
5
5
  pushQueue: "agent_memory_engine_push_queue",
6
6
  history: "agent_memory_engine_history",
7
7
  };
8
8
  /** Idempotent DDL for the sync plane (PG dialect). Call once at startup. */
9
9
  export async function ensurePgMemorySyncSchema(query) {
10
- await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.syncCursors} (
10
+ await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.syncCursor} (
11
11
  scope varchar(190) COLLATE "C" NOT NULL,
12
12
  peer varchar(190) COLLATE "C" NOT NULL,
13
13
  base_revs jsonb NOT NULL,
@@ -15,16 +15,16 @@ export async function ensurePgMemorySyncSchema(query) {
15
15
  PRIMARY KEY (scope, peer)
16
16
  )`);
17
17
  await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.pushQueue} (
18
- id char(36) COLLATE "C" PRIMARY KEY,
19
- scope varchar(190) COLLATE "C" NOT NULL,
20
- entry_id char(36) COLLATE "C" NOT NULL,
21
- payload jsonb NOT NULL,
22
- attempts integer NOT NULL DEFAULT 0,
23
- next_attempt_at bigint NOT NULL,
24
- last_error text COLLATE "C" NULL
18
+ id char(36) COLLATE "C" PRIMARY KEY,
19
+ scope varchar(190) COLLATE "C" NOT NULL,
20
+ entry_id char(36) COLLATE "C" NOT NULL,
21
+ payload jsonb NOT NULL,
22
+ attempts integer NOT NULL DEFAULT 0,
23
+ next_attempt_at_ms bigint NOT NULL,
24
+ last_error text COLLATE "C" NULL
25
25
  )`);
26
26
  // Scan face (§1.2): the reaper/consumer sweeps by due time. Perf add-on, never correctness.
27
- await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_SYNC_TABLES.pushQueue}_next_attempt ON ${PG_MEMORY_SYNC_TABLES.pushQueue} (next_attempt_at)`);
27
+ await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_SYNC_TABLES.pushQueue}_next_attempt_ms ON ${PG_MEMORY_SYNC_TABLES.pushQueue} (next_attempt_at_ms)`);
28
28
  }
29
29
  /** jsonb defense: node-pg hands back parsed objects, but a text-typed round-trip (or a foreign
30
30
  * writer) may yield a string — same posture as parseJson in run-store-sql.ts. */
@@ -51,7 +51,7 @@ export class PgMemorySyncStore {
51
51
  }
52
52
  // ── cursors ────────────────────────────────────────────────────────────────
53
53
  async getCursor(scope, peer) {
54
- const res = await this.query(`SELECT base_revs, updated_at_ms FROM ${PG_MEMORY_SYNC_TABLES.syncCursors} WHERE scope = $1 AND peer = $2`, [scope, peer]);
54
+ const res = await this.query(`SELECT base_revs, updated_at_ms FROM ${PG_MEMORY_SYNC_TABLES.syncCursor} WHERE scope = $1 AND peer = $2`, [scope, peer]);
55
55
  const row = res.rows[0];
56
56
  if (!row)
57
57
  return undefined;
@@ -65,7 +65,7 @@ export class PgMemorySyncStore {
65
65
  async putCursor(cursor) {
66
66
  // PK (scope,peer) is the table's ONLY unique key ⇒ ON CONFLICT has exactly one target, no
67
67
  // hijack face (纪律:the entries-table ban on blind upserts does NOT apply here).
68
- await this.query(`INSERT INTO ${PG_MEMORY_SYNC_TABLES.syncCursors} (scope, peer, base_revs, updated_at_ms)
68
+ await this.query(`INSERT INTO ${PG_MEMORY_SYNC_TABLES.syncCursor} (scope, peer, base_revs, updated_at_ms)
69
69
  VALUES ($1, $2, $3::jsonb, $4)
70
70
  ON CONFLICT (scope, peer) DO UPDATE SET base_revs = EXCLUDED.base_revs, updated_at_ms = EXCLUDED.updated_at_ms`,
71
71
  // codex R11:baseRevs=对账基线(entry id→rev,协议数据)——有损改写=peer 收到的与库里存的
@@ -76,7 +76,7 @@ export class PgMemorySyncStore {
76
76
  async enqueue(item) {
77
77
  // Plain INSERT, fail-loud on a duplicate id: the caller mints uuidv7 per enqueue, so a dup is a
78
78
  // caller bug (or a replay the caller must decide about) — never silently absorbed here.
79
- await this.query(`INSERT INTO ${PG_MEMORY_SYNC_TABLES.pushQueue} (id, scope, entry_id, payload, attempts, next_attempt_at, last_error)
79
+ await this.query(`INSERT INTO ${PG_MEMORY_SYNC_TABLES.pushQueue} (id, scope, entry_id, payload, attempts, next_attempt_at_ms, last_error)
80
80
  VALUES ($1, $2, $3, $4::jsonb, 0, $5, NULL)`,
81
81
  // codex R10:payload=协议数据(文档明言 verbatim;rev=内容哈希)——jsonb 列存不了脏值,但有损
82
82
  // 改写=payload 与 rev 内部矛盾(消费端永拒/应用脏内容)。协议纪律:拒绝式(与 memory add/update
@@ -88,9 +88,9 @@ export class PgMemorySyncStore {
88
88
  return [];
89
89
  const leasedUntilMs = nowMs + this.claimLeaseMs;
90
90
  // ONE statement = one implicit transaction: SKIP LOCKED excludes claimers overlapping in time,
91
- // the next_attempt_at bump (lease) excludes everyone arriving after we commit. RETURNING gives
91
+ // the next_attempt_at_ms bump (lease) excludes everyone arriving after we commit. RETURNING gives
92
92
  // the post-update row, so attempts/last_error are the pre-claim values (fail() is the only
93
- // incrementer) and next_attempt_at is the lease we just wrote.
93
+ // incrementer) and next_attempt_at_ms is the lease we just wrote.
94
94
  // ⚠️ MATERIALIZED CTE, NOT `WHERE id IN (SELECT … LIMIT n FOR UPDATE SKIP LOCKED)`: the IN-subquery
95
95
  // form lets the planner RESCAN the locking subquery (e.g. as a rescanned semi-join inner), and each
96
96
  // rescan skips the rows the previous pass locked and picks the NEXT due ones — an over-claim beyond
@@ -99,12 +99,12 @@ export class PgMemorySyncStore {
99
99
  // (PG12+) pins one evaluation; UPDATE … FROM joins the pinned id set.
100
100
  const res = await this.query(`WITH due AS MATERIALIZED (
101
101
  SELECT id FROM ${PG_MEMORY_SYNC_TABLES.pushQueue}
102
- WHERE next_attempt_at <= $1
103
- ORDER BY next_attempt_at, id
102
+ WHERE next_attempt_at_ms <= $1
103
+ ORDER BY next_attempt_at_ms, id
104
104
  LIMIT $2
105
105
  FOR UPDATE SKIP LOCKED
106
106
  )
107
- UPDATE ${PG_MEMORY_SYNC_TABLES.pushQueue} q SET next_attempt_at = $3
107
+ UPDATE ${PG_MEMORY_SYNC_TABLES.pushQueue} q SET next_attempt_at_ms = $3
108
108
  FROM due WHERE q.id = due.id
109
109
  RETURNING q.id, q.scope, q.entry_id, q.payload, q.attempts, q.last_error`, [nowMs, limit, leasedUntilMs]);
110
110
  return res.rows.map((r) => ({
@@ -124,7 +124,7 @@ export class PgMemorySyncStore {
124
124
  // attempts = attempts + 1 is SQL-atomic — a read-modify-write in JS would race a concurrent
125
125
  // failer and lose increments (the attempt cap is the fail-loud stop, it must count honestly).
126
126
  await this.query(`UPDATE ${PG_MEMORY_SYNC_TABLES.pushQueue}
127
- SET attempts = attempts + 1, last_error = $2, next_attempt_at = $3
127
+ SET attempts = attempts + 1, last_error = $2, next_attempt_at_ms = $3
128
128
  WHERE id = $1`, [id, error, nextAttemptAtMs]);
129
129
  }
130
130
  }
@@ -4,7 +4,7 @@ import type { Pool } from "mysql2/promise";
4
4
  /** Table names (single source) — SAME names as PG_MEMORY_SYNC_TABLES (the two dialects never share
5
5
  * one database). */
6
6
  export declare const TIDB_MEMORY_SYNC_TABLES: {
7
- readonly syncCursors: "agent_memory_engine_sync_cursors";
7
+ readonly syncCursor: "agent_memory_engine_sync_cursor";
8
8
  readonly pushQueue: "agent_memory_engine_push_queue";
9
9
  readonly history: "agent_memory_engine_history";
10
10
  };
@@ -2,14 +2,14 @@ import { historyRowFrom } from "./memory-sync-store-pg.js";
2
2
  /** Table names (single source) — SAME names as PG_MEMORY_SYNC_TABLES (the two dialects never share
3
3
  * one database). */
4
4
  export const TIDB_MEMORY_SYNC_TABLES = {
5
- syncCursors: "agent_memory_engine_sync_cursors",
5
+ syncCursor: "agent_memory_engine_sync_cursor",
6
6
  pushQueue: "agent_memory_engine_push_queue",
7
7
  history: "agent_memory_engine_history",
8
8
  };
9
9
  /** Idempotent DDL for the sync plane (TiDB dialect). Call once at startup.
10
10
  * Key budget: utf8mb4 = 4 bytes/char, index cap 3072 bytes ⇒ PK(scope,peer) = 380 chars = 1520 B ✓. */
11
11
  export async function ensureTiDBMemorySyncSchema(pool) {
12
- await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_SYNC_TABLES.syncCursors} (
12
+ await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_SYNC_TABLES.syncCursor} (
13
13
  scope VARCHAR(190) NOT NULL,
14
14
  peer VARCHAR(190) NOT NULL,
15
15
  base_revs JSON NOT NULL,
@@ -17,15 +17,15 @@ export async function ensureTiDBMemorySyncSchema(pool) {
17
17
  PRIMARY KEY (scope, peer)
18
18
  ) COLLATE utf8mb4_bin`);
19
19
  await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_SYNC_TABLES.pushQueue} (
20
- id CHAR(36) NOT NULL,
21
- scope VARCHAR(190) NOT NULL,
22
- entry_id CHAR(36) NOT NULL,
23
- payload JSON NOT NULL,
24
- attempts INT NOT NULL DEFAULT 0,
25
- next_attempt_at BIGINT NOT NULL,
26
- last_error TEXT NULL,
20
+ id CHAR(36) NOT NULL,
21
+ scope VARCHAR(190) NOT NULL,
22
+ entry_id CHAR(36) NOT NULL,
23
+ payload JSON NOT NULL,
24
+ attempts INT NOT NULL DEFAULT 0,
25
+ next_attempt_at_ms BIGINT NOT NULL,
26
+ last_error TEXT NULL,
27
27
  PRIMARY KEY (id),
28
- KEY idx_agent_memory_engine_push_queue_next_attempt (next_attempt_at)
28
+ KEY idx_agent_memory_engine_push_queue_next_attempt_ms (next_attempt_at_ms)
29
29
  ) COLLATE utf8mb4_bin`);
30
30
  }
31
31
  /** JSON-column defense: mysql2 normally hands back parsed objects for JSON columns, but string
@@ -52,7 +52,7 @@ export class TiDBMemorySyncStore {
52
52
  }
53
53
  // ── cursors ────────────────────────────────────────────────────────────────
54
54
  async getCursor(scope, peer) {
55
- const [rows] = await this.pool.query(`SELECT base_revs, updated_at_ms FROM ${TIDB_MEMORY_SYNC_TABLES.syncCursors} WHERE scope = ? AND peer = ?`, [scope, peer]);
55
+ const [rows] = await this.pool.query(`SELECT base_revs, updated_at_ms FROM ${TIDB_MEMORY_SYNC_TABLES.syncCursor} WHERE scope = ? AND peer = ?`, [scope, peer]);
56
56
  const row = rows[0];
57
57
  if (!row)
58
58
  return undefined;
@@ -66,7 +66,7 @@ export class TiDBMemorySyncStore {
66
66
  async putCursor(cursor) {
67
67
  // The table's ONLY unique key is PK(scope,peer) ⇒ ON DUPLICATE KEY UPDATE has exactly one
68
68
  // possible trigger — no hijack face (the entries-table ban does NOT apply; see header).
69
- await this.pool.query(`INSERT INTO ${TIDB_MEMORY_SYNC_TABLES.syncCursors} (scope, peer, base_revs, updated_at_ms)
69
+ await this.pool.query(`INSERT INTO ${TIDB_MEMORY_SYNC_TABLES.syncCursor} (scope, peer, base_revs, updated_at_ms)
70
70
  VALUES (?, ?, ?, ?)
71
71
  ON DUPLICATE KEY UPDATE base_revs = VALUES(base_revs), updated_at_ms = VALUES(updated_at_ms)`, [cursor.scope, cursor.peer, JSON.stringify(cursor.baseRevs), cursor.updatedAtMs]);
72
72
  }
@@ -74,7 +74,7 @@ export class TiDBMemorySyncStore {
74
74
  async enqueue(item) {
75
75
  // Plain INSERT, fail-loud on a duplicate id (ER_DUP_ENTRY 1062) — caller mints uuidv7 per
76
76
  // enqueue, a dup is a caller bug, never silently absorbed. Same posture as the PG twin.
77
- await this.pool.query(`INSERT INTO ${TIDB_MEMORY_SYNC_TABLES.pushQueue} (id, scope, entry_id, payload, attempts, next_attempt_at, last_error)
77
+ await this.pool.query(`INSERT INTO ${TIDB_MEMORY_SYNC_TABLES.pushQueue} (id, scope, entry_id, payload, attempts, next_attempt_at_ms, last_error)
78
78
  VALUES (?, ?, ?, ?, 0, ?, NULL)`, [item.id, item.scope, item.entryId, JSON.stringify(item.payload), item.nextAttemptAtMs]);
79
79
  }
80
80
  async claimDue(nowMs, limit) {
@@ -82,21 +82,21 @@ export class TiDBMemorySyncStore {
82
82
  return [];
83
83
  const leasedUntilMs = nowMs + this.claimLeaseMs;
84
84
  // Optimistic CAS claim (header rationale): read the due candidates, then win each row with a
85
- // conditional UPDATE on the exact next_attempt_at we read. Losers (another worker claimed, or
85
+ // conditional UPDATE on the exact next_attempt_at_ms we read. Losers (another worker claimed, or
86
86
  // fail()/a re-schedule moved the row) see affectedRows=0 and are skipped — the SKIP LOCKED
87
87
  // equivalent, without pessimistic locks or a held connection.
88
- const [candidates] = await this.pool.query(`SELECT id, scope, entry_id, payload, attempts, next_attempt_at, last_error
88
+ const [candidates] = await this.pool.query(`SELECT id, scope, entry_id, payload, attempts, next_attempt_at_ms, last_error
89
89
  FROM ${TIDB_MEMORY_SYNC_TABLES.pushQueue}
90
- WHERE next_attempt_at <= ?
91
- ORDER BY next_attempt_at, id
90
+ WHERE next_attempt_at_ms <= ?
91
+ ORDER BY next_attempt_at_ms, id
92
92
  LIMIT ?`, [nowMs, limit]);
93
93
  const claimed = [];
94
94
  for (const row of candidates) {
95
- // affectedRows=CHANGED 语义坑防御:lease = nowMs + claimLeaseMs > nowMs ≥ 读到的 next_attempt_at
95
+ // affectedRows=CHANGED 语义坑防御:lease = nowMs + claimLeaseMs > nowMs ≥ 读到的 next_attempt_at_ms
96
96
  // (WHERE 已保证),新值严格大于旧值 ⇒ 匹配必 change ⇒ affectedRows=1 恒等于「真的赢了」。
97
97
  const [res] = await this.pool.query(`UPDATE ${TIDB_MEMORY_SYNC_TABLES.pushQueue}
98
- SET next_attempt_at = ?
99
- WHERE id = ? AND next_attempt_at = ?`, [leasedUntilMs, row.id, toMs(row.next_attempt_at)]);
98
+ SET next_attempt_at_ms = ?
99
+ WHERE id = ? AND next_attempt_at_ms = ?`, [leasedUntilMs, row.id, toMs(row.next_attempt_at_ms)]);
100
100
  if (res.affectedRows !== 1)
101
101
  continue; // lost the race — skip, never block
102
102
  claimed.push({
@@ -119,7 +119,7 @@ export class TiDBMemorySyncStore {
119
119
  // guarantees the row CHANGES — the claim verdict is honest under BOTH affectedRows semantics
120
120
  // (mysql2 default = FOUND_ROWS/matched; changed-only would also work here).
121
121
  await this.pool.query(`UPDATE ${TIDB_MEMORY_SYNC_TABLES.pushQueue}
122
- SET attempts = attempts + 1, last_error = ?, next_attempt_at = ?
122
+ SET attempts = attempts + 1, last_error = ?, next_attempt_at_ms = ?
123
123
  WHERE id = ?`, [error, nextAttemptAtMs, id]);
124
124
  }
125
125
  }
@@ -116,9 +116,9 @@ export const PG_SCHEMA_STATEMENTS = [
116
116
  `CREATE INDEX IF NOT EXISTS idx_checkpoint_status_deadline ON checkpoint (status, deadline)`,
117
117
  `CREATE INDEX IF NOT EXISTS idx_checkpoint_session_status ON checkpoint (session_id, status)`,
118
118
  `CREATE TABLE IF NOT EXISTS checkpoint_ctx (
119
- session_id VARCHAR(190) COLLATE "C" NOT NULL,
120
- ctx JSONB NOT NULL,
121
- updated_at BIGINT NOT NULL,
119
+ session_id VARCHAR(190) COLLATE "C" NOT NULL,
120
+ ctx JSONB NOT NULL,
121
+ updated_at_ms BIGINT NOT NULL,
122
122
  PRIMARY KEY (session_id)
123
123
  )`,
124
124
  // E18 resume-at anchor map (the PG twin of the TiDB resume_anchor table — eventId→entryId; see tidb-pool.ts).
@@ -203,17 +203,17 @@ export const PG_SCHEMA_STATEMENTS = [
203
203
  // is TEXT, NOT JSONB — the byte-match contract is "string in, string out" (the store JSON.parses on load); JSONB would
204
204
  // make `pg` auto-parse the row back to an object and break the twin's String()→JSON.parse path. See workflow-journal-store-sql.ts.
205
205
  `CREATE TABLE IF NOT EXISTS workflow_journal (
206
- run_id VARCHAR(191) COLLATE "C" NOT NULL,
207
- ordinal INT NOT NULL,
206
+ run_id VARCHAR(191) COLLATE "C" NOT NULL,
207
+ ordinal INT NOT NULL,
208
208
  -- SVC-2 / CORE-9 audit BLOCKER: cross-tenant resume isolation (the WorkflowJournalStore seam takes a scope
209
209
  -- param). NOT NULL DEFAULT '' — a row written without a scope lands in the '' bucket, which is
210
210
  -- CROSS-SCOPE-ORPHANED but harmless: a real resume always filters \`WHERE scope = <caller>\`, so '' rows are
211
211
  -- never handed to a tenant. Keep the DEFAULT: it is what makes a scope-less write land in that dead bucket
212
212
  -- instead of failing or leaking into a real tenant's scope.
213
- scope VARCHAR(190) COLLATE "C" NOT NULL DEFAULT '',
214
- call_key VARCHAR(255) COLLATE "C" NOT NULL,
215
- result TEXT COLLATE "C" NOT NULL,
216
- created_at BIGINT NOT NULL,
213
+ scope VARCHAR(190) COLLATE "C" NOT NULL DEFAULT '',
214
+ call_key VARCHAR(255) COLLATE "C" NOT NULL,
215
+ result TEXT COLLATE "C" NOT NULL,
216
+ created_at_ms BIGINT NOT NULL,
217
217
  PRIMARY KEY (run_id, ordinal)
218
218
  )`,
219
219
  // RB-242 / WF2 ([1981] core 拍板 a 形):cross-replica workflow resume admission lease — the PG twin of
@@ -228,30 +228,30 @@ export const PG_SCHEMA_STATEMENTS = [
228
228
  // P1 (fleet failover): WorkflowRunStore + completion-inbox PG twins (design doc in workflow-run-store-sql.ts).
229
229
  // `run` is TEXT not jsonb (node-pg auto-parse would break the shared String()→JSON.parse path).
230
230
  `CREATE TABLE IF NOT EXISTS workflow_run (
231
- id VARCHAR(191) COLLATE "C" NOT NULL,
232
- scope VARCHAR(190) COLLATE "C" NOT NULL,
233
- status VARCHAR(16) COLLATE "C" NOT NULL,
234
- run TEXT COLLATE "C" NOT NULL,
235
- rev INTEGER NOT NULL DEFAULT 0,
236
- created_at BIGINT NOT NULL,
237
- ended_at BIGINT,
231
+ id VARCHAR(191) COLLATE "C" NOT NULL,
232
+ scope VARCHAR(190) COLLATE "C" NOT NULL,
233
+ status VARCHAR(16) COLLATE "C" NOT NULL,
234
+ run TEXT COLLATE "C" NOT NULL,
235
+ rev INTEGER NOT NULL DEFAULT 0,
236
+ created_at_ms BIGINT NOT NULL,
237
+ ended_at BIGINT,
238
238
  PRIMARY KEY (id)
239
239
  )`,
240
- `CREATE INDEX IF NOT EXISTS idx_wfrun_scope_created ON workflow_run (scope, created_at)`,
240
+ `CREATE INDEX IF NOT EXISTS idx_wfrun_scope_created ON workflow_run (scope, created_at_ms)`,
241
241
  `CREATE INDEX IF NOT EXISTS idx_wfrun_scope_status ON workflow_run (scope, status)`,
242
242
  `CREATE TABLE IF NOT EXISTS workflow_completion_inbox (
243
- seq BIGSERIAL,
244
- session_id VARCHAR(190) COLLATE "C" NOT NULL,
245
- run_id VARCHAR(191) COLLATE "C" NOT NULL,
246
- owner VARCHAR(190) COLLATE "C",
247
- status VARCHAR(16) COLLATE "C" NOT NULL,
248
- summary TEXT COLLATE "C" NOT NULL,
249
- enqueued_at BIGINT NOT NULL,
243
+ seq BIGSERIAL,
244
+ session_id VARCHAR(190) COLLATE "C" NOT NULL,
245
+ run_id VARCHAR(191) COLLATE "C" NOT NULL,
246
+ owner VARCHAR(190) COLLATE "C",
247
+ status VARCHAR(16) COLLATE "C" NOT NULL,
248
+ summary TEXT COLLATE "C" NOT NULL,
249
+ enqueued_at_ms BIGINT NOT NULL,
250
250
  -- 1.109 delivery envelope. kind NULL = a LEGACY \`workflow_complete\` row (the shape that predates the
251
251
  -- envelope); readers must keep treating NULL as that kind, it is not "unknown". payload = the kind's
252
252
  -- body, NULL for the legacy shape.
253
- kind VARCHAR(24) COLLATE "C",
254
- payload TEXT COLLATE "C",
253
+ kind VARCHAR(24) COLLATE "C",
254
+ payload TEXT COLLATE "C",
255
255
  PRIMARY KEY (session_id, run_id),
256
256
  -- Twin-alignment with TiDB's \`UNIQUE KEY uq_wfinbox_seq (seq)\` (same constraint name on purpose, so the two
257
257
  -- schemas can be diffed by name). This is NOT a correctness fix: seq is BIGSERIAL, so it is already unique in
@@ -27,7 +27,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
27
27
  import type { Pool as PgPool } from "pg";
28
28
  import { type TaskListStore } from "@sema-agent/core";
29
29
  export declare const TASK_LIST_META_TABLE = "task_list_meta";
30
- export declare const TASK_LIST_ITEMS_TABLE = "task_list_items";
30
+ export declare const TASK_LIST_ITEM_TABLE = "task_list_item";
31
31
  export declare function ensureTiDBTaskListSchema(pool: MySqlPool): Promise<void>;
32
32
  export declare function ensurePgTaskListSchema(q: (text: string, params?: unknown[]) => Promise<unknown>): Promise<void>;
33
33
  export declare function createTiDBTaskListStore(pool: MySqlPool, listKey: string): TaskListStore;