@sema-agent/server 1.315.0 → 1.316.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.
Files changed (97) hide show
  1. package/dist/http/idempotency.d.ts +1 -0
  2. package/dist/http/idempotency.js +3 -0
  3. package/dist/http/principal-gate.d.ts +12 -0
  4. package/dist/http/principal-gate.js +52 -0
  5. package/dist/http/route-ctx.d.ts +67 -0
  6. package/dist/http/route-ctx.js +2 -0
  7. package/dist/http/routes/attachments.d.ts +4 -0
  8. package/dist/http/routes/attachments.js +106 -0
  9. package/dist/http/routes/capabilities.d.ts +4 -0
  10. package/dist/http/routes/capabilities.js +111 -0
  11. package/dist/http/routes/images.d.ts +20 -0
  12. package/dist/http/routes/images.js +572 -0
  13. package/dist/http/routes/memory-policy.d.ts +4 -0
  14. package/dist/http/routes/memory-policy.js +105 -0
  15. package/dist/http/routes/observability.d.ts +4 -0
  16. package/dist/http/routes/observability.js +63 -0
  17. package/dist/http/routes/session-sync.d.ts +36 -0
  18. package/dist/http/routes/session-sync.js +513 -0
  19. package/dist/http/routes/sessions-list.d.ts +4 -0
  20. package/dist/http/routes/sessions-list.js +117 -0
  21. package/dist/http/routes/sessions.d.ts +17 -0
  22. package/dist/http/routes/sessions.js +746 -0
  23. package/dist/http/send.d.ts +6 -0
  24. package/dist/http/send.js +19 -0
  25. package/dist/http/server.d.ts +6 -7
  26. package/dist/http/server.js +125 -2413
  27. package/dist/http/sse-log.d.ts +25 -0
  28. package/dist/http/sse-log.js +59 -0
  29. package/dist/http/workspace-content.d.ts +3 -0
  30. package/dist/http/workspace-content.js +20 -0
  31. package/dist/plugins/approval-store-sql.d.ts +52 -0
  32. package/dist/plugins/approval-store-sql.js +119 -0
  33. package/dist/plugins/breaker-state-sql.d.ts +29 -0
  34. package/dist/plugins/breaker-state-sql.js +104 -0
  35. package/dist/plugins/file-snapshot-store-sql.d.ts +38 -0
  36. package/dist/plugins/file-snapshot-store-sql.js +210 -0
  37. package/dist/plugins/image-index-sql.d.ts +33 -0
  38. package/dist/plugins/image-index-sql.js +328 -0
  39. package/dist/plugins/outcome-ledger-sql.d.ts +70 -0
  40. package/dist/plugins/outcome-ledger-sql.js +158 -0
  41. package/dist/plugins/pg-approval-store.d.ts +1 -27
  42. package/dist/plugins/pg-approval-store.js +1 -83
  43. package/dist/plugins/pg-breaker-state.d.ts +1 -19
  44. package/dist/plugins/pg-breaker-state.js +1 -88
  45. package/dist/plugins/pg-file-snapshot-store.d.ts +1 -27
  46. package/dist/plugins/pg-file-snapshot-store.js +1 -190
  47. package/dist/plugins/pg-image-index.d.ts +1 -21
  48. package/dist/plugins/pg-image-index.js +1 -217
  49. package/dist/plugins/pg-outcome-ledger.d.ts +2 -22
  50. package/dist/plugins/pg-outcome-ledger.js +1 -104
  51. package/dist/plugins/pg-resume-anchor-store.d.ts +1 -13
  52. package/dist/plugins/pg-resume-anchor-store.js +1 -22
  53. package/dist/plugins/pg-run-store.d.ts +1 -75
  54. package/dist/plugins/pg-run-store.js +1 -287
  55. package/dist/plugins/pg-session-policy-store.d.ts +1 -11
  56. package/dist/plugins/pg-session-policy-store.js +1 -90
  57. package/dist/plugins/pg-tool-result-store.d.ts +1 -15
  58. package/dist/plugins/pg-tool-result-store.js +1 -52
  59. package/dist/plugins/pg-workflow-journal-store.d.ts +1 -20
  60. package/dist/plugins/pg-workflow-journal-store.js +1 -34
  61. package/dist/plugins/pg-workflow-run-store.d.ts +1 -55
  62. package/dist/plugins/pg-workflow-run-store.js +1 -220
  63. package/dist/plugins/resume-anchor-store-sql.d.ts +23 -0
  64. package/dist/plugins/resume-anchor-store-sql.js +38 -0
  65. package/dist/plugins/run-store-sql.d.ts +89 -0
  66. package/dist/plugins/run-store-sql.js +397 -0
  67. package/dist/plugins/session-policy-store-sql.d.ts +22 -0
  68. package/dist/plugins/session-policy-store-sql.js +109 -0
  69. package/dist/plugins/tidb-approval-store.d.ts +1 -39
  70. package/dist/plugins/tidb-approval-store.js +1 -71
  71. package/dist/plugins/tidb-breaker-state.d.ts +1 -17
  72. package/dist/plugins/tidb-breaker-state.js +1 -74
  73. package/dist/plugins/tidb-file-snapshot-store.d.ts +1 -27
  74. package/dist/plugins/tidb-file-snapshot-store.js +1 -192
  75. package/dist/plugins/tidb-image-index.d.ts +1 -19
  76. package/dist/plugins/tidb-image-index.js +1 -149
  77. package/dist/plugins/tidb-outcome-ledger.d.ts +1 -57
  78. package/dist/plugins/tidb-outcome-ledger.js +1 -97
  79. package/dist/plugins/tidb-resume-anchor-store.d.ts +1 -13
  80. package/dist/plugins/tidb-resume-anchor-store.js +1 -22
  81. package/dist/plugins/tidb-run-store.d.ts +1 -75
  82. package/dist/plugins/tidb-run-store.js +1 -306
  83. package/dist/plugins/tidb-session-policy-store.d.ts +1 -11
  84. package/dist/plugins/tidb-session-policy-store.js +1 -86
  85. package/dist/plugins/tidb-tool-result-store.d.ts +1 -13
  86. package/dist/plugins/tidb-tool-result-store.js +1 -38
  87. package/dist/plugins/tidb-workflow-journal-store.d.ts +1 -20
  88. package/dist/plugins/tidb-workflow-journal-store.js +1 -34
  89. package/dist/plugins/tidb-workflow-run-store.d.ts +1 -61
  90. package/dist/plugins/tidb-workflow-run-store.js +1 -231
  91. package/dist/plugins/tool-result-store-sql.d.ts +25 -0
  92. package/dist/plugins/tool-result-store-sql.js +69 -0
  93. package/dist/plugins/workflow-journal-store-sql.d.ts +30 -0
  94. package/dist/plugins/workflow-journal-store-sql.js +53 -0
  95. package/dist/plugins/workflow-run-store-sql.d.ts +85 -0
  96. package/dist/plugins/workflow-run-store-sql.js +299 -0
  97. package/package.json +1 -1
@@ -1,53 +1,2 @@
1
- import { escapeLike } from "./sql-escape.js";
2
- import { pgSanitizeText } from "./pg-safe-json.js";
3
- export const PG_TOOL_RESULT_SCHEMA = [
4
- `CREATE TABLE IF NOT EXISTS tool_result (
5
- ref VARCHAR(190) NOT NULL,
6
- content TEXT NOT NULL,
7
- created_at TIMESTAMPTZ(3) NOT NULL,
8
- PRIMARY KEY (ref)
9
- )`,
10
- `CREATE INDEX IF NOT EXISTS idx_tool_result_created ON tool_result (created_at)`,
11
- ];
12
- export async function ensureSchema(pool) {
13
- for (const stmt of PG_TOOL_RESULT_SCHEMA)
14
- await pool.query(stmt);
15
- }
16
- export class PgToolResultStore {
17
- pool;
18
- constructor(pool) {
19
- this.pool = pool;
20
- }
21
- async put(ref, content) {
22
- const safe = pgSanitizeText(Buffer.from(content, "utf8").toString("utf8"));
23
- await this.pool.query("INSERT INTO tool_result (ref, content, created_at) VALUES ($1,$2,$3) ON CONFLICT (ref) DO NOTHING", [ref, safe, new Date()]);
24
- }
25
- async get(ref, opts = {}) {
26
- const offset = Math.max(0, Math.floor(opts.offset ?? 0));
27
- const hasLimit = opts.limit !== undefined && Number.isFinite(opts.limit);
28
- const limit = hasLimit ? Math.max(0, Math.floor(opts.limit)) : 0;
29
- const sql = hasLimit
30
- ? "SELECT SUBSTRING(content FROM $1::int FOR $2::int) AS slice, CHAR_LENGTH(content) AS total FROM tool_result WHERE ref = $3"
31
- : "SELECT SUBSTRING(content FROM $1::int) AS slice, CHAR_LENGTH(content) AS total FROM tool_result WHERE ref = $2";
32
- const params = hasLimit ? [offset + 1, limit, ref] : [offset + 1, ref];
33
- const res = await this.pool.query(sql, params);
34
- if (res.rows.length === 0)
35
- return undefined;
36
- return { content: String(res.rows[0].slice ?? ""), offset, totalChars: Number(res.rows[0].total) };
37
- }
38
- async reapOlderThan(cutoffMs) {
39
- try {
40
- const res = await this.pool.query("DELETE FROM tool_result WHERE created_at < $1", [new Date(cutoffMs)]);
41
- return res.rowCount ?? 0;
42
- }
43
- catch {
44
- return 0;
45
- }
46
- }
47
- async deleteBySession(sessionId) {
48
- const prefix = `${escapeLike(`tr_${sessionId}_`)}%`;
49
- const res = await this.pool.query("DELETE FROM tool_result WHERE ref LIKE $1 ESCAPE '\\'", [prefix]);
50
- return res.rowCount ?? 0;
51
- }
52
- }
1
+ export { PgToolResultStore, PG_TOOL_RESULT_SCHEMA, ensureSchema } from "./tool-result-store-sql.js";
53
2
  //# sourceMappingURL=pg-tool-result-store.js.map
@@ -1,21 +1,2 @@
1
- import type { Pool } from "pg";
2
- import { type WorkflowJournalEntry, type WorkflowJournalStore } from "@sema-agent/core";
3
- export declare class PgWorkflowJournalStore implements WorkflowJournalStore {
4
- private readonly pool;
5
- constructor(pool: Pool);
6
- append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
7
- load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
8
- locator(runId: string, _scope: string): string;
9
- loadPage(runId: string, scope: string, opts: {
10
- offset: number;
11
- limit: number;
12
- maxResultBytes: number;
13
- }): Promise<Array<{
14
- callKey: string;
15
- resultJson: string | null;
16
- resultBytes: number;
17
- }>>;
18
- deleteByRun(runId: string): Promise<number>;
19
- reapExpired(now: number, maxAgeMs: number): Promise<number>;
20
- }
1
+ export { PgWorkflowJournalStore } from "./workflow-journal-store-sql.js";
21
2
  //# sourceMappingURL=pg-workflow-journal-store.d.ts.map
@@ -1,35 +1,2 @@
1
- import { callKeyOrdinal } from "@sema-agent/core";
2
- import { oversizeJournalResult } from "./workflow-journal-limits.js";
3
- export class PgWorkflowJournalStore {
4
- pool;
5
- constructor(pool) {
6
- this.pool = pool;
7
- }
8
- async append(runId, scope, entry) {
9
- const serialized = JSON.stringify(entry.result);
10
- if (oversizeJournalResult(serialized))
11
- return;
12
- await this.pool.query("INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at) VALUES ($1,$2,$3,$4,$5,$6) " +
13
- "ON CONFLICT (run_id, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result", [runId, callKeyOrdinal(entry.callKey), scope, entry.callKey, serialized, Date.now()]);
14
- }
15
- async load(runId, scope) {
16
- const r = await this.pool.query("SELECT call_key, result FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC", [runId, scope]);
17
- return r.rows.map((row) => ({ callKey: String(row.call_key), result: JSON.parse(String(row.result)) }));
18
- }
19
- locator(runId, _scope) {
20
- return `/v1/workflows/${runId}/journal`;
21
- }
22
- async loadPage(runId, scope, opts) {
23
- const r = await this.pool.query("SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE result END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5", [runId, scope, opts.maxResultBytes, opts.limit, opts.offset]);
24
- return r.rows.map((row) => ({ callKey: String(row.call_key), resultJson: row.result_json === null ? null : String(row.result_json), resultBytes: Number(row.result_bytes) }));
25
- }
26
- async deleteByRun(runId) {
27
- const r = await this.pool.query("DELETE FROM workflow_journal WHERE run_id = $1", [runId]);
28
- return r.rowCount ?? 0;
29
- }
30
- async reapExpired(now, maxAgeMs) {
31
- const r = await this.pool.query("DELETE FROM workflow_journal WHERE created_at < $1", [now - maxAgeMs]);
32
- return r.rowCount ?? 0;
33
- }
34
- }
1
+ export { PgWorkflowJournalStore } from "./workflow-journal-store-sql.js";
35
2
  //# sourceMappingURL=pg-workflow-journal-store.js.map
@@ -1,56 +1,2 @@
1
- import type { Pool } from "pg";
2
- import { summarizeWorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
3
- import { type WorkflowCompletionInbox, type WorkflowCompletionInboxEntry } from "../orchestration/workflow-completion-inbox.js";
4
- import type { WorkflowNotifyJournalStore, WorkflowNotifyJournalEntry } from "../orchestration/workflow-notify-journal.js";
5
- import { type InboxWarn } from "./tidb-workflow-run-store.js";
6
- type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
7
- export declare class PgWorkflowRunStore implements WorkflowRunStore {
8
- private readonly pool;
9
- constructor(pool: Pool);
10
- put(id: string, run: WorkflowRun): Promise<void>;
11
- get(id: string): Promise<WorkflowRun | null>;
12
- update(id: string, scope: string, run: WorkflowRun, expect?: {
13
- rev: number;
14
- }): Promise<boolean>;
15
- listByScope(scope: string, opts?: {
16
- status?: WorkflowRun["status"];
17
- limit?: number;
18
- session?: string;
19
- }): Promise<ReturnType<typeof summarizeWorkflowRun>[]>;
20
- reapAllScopes(now: number, opts: {
21
- maxAgeMs?: number;
22
- keep?: number;
23
- }): Promise<number>;
24
- reap(scope: string, now: number, opts?: {
25
- maxAgeMs?: number;
26
- keep?: number;
27
- }): Promise<number>;
28
- }
29
- export declare class PgWorkflowCompletionInbox implements WorkflowCompletionInbox {
30
- private readonly pool;
31
- private readonly onWarn?;
32
- constructor(pool: Pool, onWarn?: InboxWarn | undefined);
33
- private sweepFences;
34
- enqueue(entry: WorkflowCompletionInboxEntry): Promise<void>;
35
- pending(sessionId: string): Promise<WorkflowCompletionInboxEntry[]>;
36
- ack(sessionId: string, runId: string): Promise<void>;
37
- purge(sessionId: string, purgedOwner?: string | null): Promise<void>;
38
- markTerminalServed(sessionId: string, runId: string): Promise<void>;
39
- }
40
- export declare class PgWorkflowNotifyJournalStore implements WorkflowNotifyJournalStore {
41
- private readonly pool;
42
- constructor(pool: Pool);
43
- record(input: {
44
- runId: string;
45
- scope: string;
46
- sourceTaskId?: string;
47
- principal?: string;
48
- createdAt: number;
49
- }): Promise<void>;
50
- ack(runId: string, ackedAt: number): Promise<void>;
51
- listPending(): Promise<WorkflowNotifyJournalEntry[]>;
52
- reapAcked(before: number): Promise<number>;
53
- get(runId: string): Promise<WorkflowNotifyJournalEntry | null>;
54
- }
55
- export {};
1
+ export { PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore } from "./workflow-run-store-sql.js";
56
2
  //# sourceMappingURL=pg-workflow-run-store.d.ts.map
@@ -1,221 +1,2 @@
1
- import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
2
- import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
3
- import { MAX_RUN_BLOB_BYTES, slimOversizeRun } from "./tidb-workflow-run-store.js";
4
- export class PgWorkflowRunStore {
5
- pool;
6
- constructor(pool) {
7
- this.pool = pool;
8
- }
9
- async put(id, run) {
10
- const stored = { ...run, id, rev: run.rev ?? 0 };
11
- try {
12
- await this.pool.query("INSERT INTO workflow_run (id, scope, status, run, rev, created_at, ended_at) VALUES ($1,$2,$3,$4,$5,$6,$7)", [id, run.scope, run.status, JSON.stringify(stored), stored.rev, run.createdAt, run.endedAt ?? null]);
13
- }
14
- catch (e) {
15
- if (e.code === "23505") {
16
- throw new WorkflowRunStoreError("workflow_run.already_exists", "workflow run id already exists");
17
- }
18
- throw e;
19
- }
20
- }
21
- async get(id) {
22
- const res = await this.pool.query("SELECT run, rev FROM workflow_run WHERE id = $1", [id]);
23
- const r = res.rows[0];
24
- if (!r)
25
- return null;
26
- const run = JSON.parse(String(r.run));
27
- run.rev = Number(r.rev);
28
- return run;
29
- }
30
- async update(id, scope, run, expect) {
31
- let blob = JSON.stringify({ ...run, id, scope });
32
- if (Buffer.byteLength(blob) > MAX_RUN_BLOB_BYTES) {
33
- const slim = slimOversizeRun({ ...run, id, scope }, MAX_RUN_BLOB_BYTES);
34
- if (slim === null)
35
- return false;
36
- blob = slim;
37
- }
38
- const res = await this.pool.query(`UPDATE workflow_run SET run = $1, rev = rev + 1, status = $2, ended_at = $3 WHERE id = $4 AND scope = $5${expect !== undefined ? " AND rev = $6" : ""}`, expect !== undefined ? [blob, run.status, run.endedAt ?? null, id, scope, expect.rev] : [blob, run.status, run.endedAt ?? null, id, scope]);
39
- return res.rowCount === 1;
40
- }
41
- async listByScope(scope, opts) {
42
- const params = [scope];
43
- let where = "scope = $1";
44
- if (opts?.status !== undefined) {
45
- params.push(opts.status);
46
- where += ` AND status = $${params.length}`;
47
- }
48
- if (opts?.session !== undefined) {
49
- params.push(opts.session);
50
- where += ` AND run::json->>'originatingSessionId' = $${params.length}`;
51
- }
52
- let limit = "";
53
- if (opts?.limit !== undefined) {
54
- params.push(Math.max(0, opts.limit));
55
- limit = ` LIMIT $${params.length}`;
56
- }
57
- const res = await this.pool.query(`SELECT run, rev FROM workflow_run WHERE ${where} ORDER BY created_at DESC, id DESC${limit}`, params);
58
- return res.rows.map((r) => {
59
- const run = JSON.parse(String(r.run));
60
- run.rev = Number(r.rev);
61
- return summarizeWorkflowRun(run);
62
- });
63
- }
64
- async reapAllScopes(now, opts) {
65
- const res = await this.pool.query("SELECT DISTINCT scope FROM workflow_run");
66
- let total = 0;
67
- for (const r of res.rows)
68
- total += await this.reap(r.scope, now, opts);
69
- return total;
70
- }
71
- async reap(scope, now, opts) {
72
- if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
73
- return 0;
74
- const res = await this.pool.query("SELECT id, status, created_at, ended_at FROM workflow_run WHERE scope = $1 ORDER BY created_at DESC, id DESC", [scope]);
75
- const rows = res.rows;
76
- const terminal = rows.filter((r) => isTerminalWorkflowStatus(r.status));
77
- const doomed = [];
78
- for (let i = 0; i < terminal.length; i++) {
79
- const r = terminal[i];
80
- const anchor = r.ended_at != null ? Number(r.ended_at) : Number(r.created_at);
81
- const tooOld = opts.maxAgeMs !== undefined && anchor < now - opts.maxAgeMs;
82
- const overKeep = opts.keep !== undefined && i >= opts.keep;
83
- if (tooOld || overKeep)
84
- doomed.push(r.id);
85
- }
86
- if (doomed.length === 0)
87
- return 0;
88
- const del = await this.pool.query(`DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])`, [scope, doomed]);
89
- return del.rowCount ?? 0;
90
- }
91
- }
92
- const KIND_SERVED = "served";
93
- const KIND_PURGE = "purge";
94
- export class PgWorkflowCompletionInbox {
95
- pool;
96
- onWarn;
97
- constructor(pool, onWarn) {
98
- this.pool = pool;
99
- this.onWarn = onWarn;
100
- }
101
- async sweepFences(now) {
102
- await this.pool.query("DELETE FROM workflow_inbox_fence WHERE until_ms < $1", [now]);
103
- }
104
- async enqueue(entry) {
105
- const now = Date.now();
106
- await this.sweepFences(now);
107
- const served = await this.pool.query("SELECT 1 FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id IN ($3, $4) AND until_ms >= $5", [entry.sessionId, KIND_SERVED, entry.runId, foldKeyFamily(entry.runId), now]);
108
- if ((served.rowCount ?? 0) > 0) {
109
- this.onWarn?.("workflow_completion_enqueue_after_poll_served", { sessionId: entry.sessionId, runId: entry.runId });
110
- return;
111
- }
112
- const purge = await this.pool.query("SELECT owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id = '' AND until_ms >= $3", [entry.sessionId, KIND_PURGE, now]);
113
- const pf = purge.rows[0];
114
- if (pf && (Number(pf.owner_known) === 0 || (pf.owner === null ? entry.owner === null : pf.owner === entry.owner))) {
115
- this.onWarn?.("workflow_completion_enqueue_after_purge", { sessionId: entry.sessionId, runId: entry.runId });
116
- return;
117
- }
118
- await this.pool.query("INSERT INTO workflow_completion_inbox (session_id, run_id, owner, status, summary, enqueued_at, kind, payload) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) " +
119
- "ON CONFLICT (session_id, run_id) DO UPDATE SET seq = nextval(pg_get_serial_sequence('workflow_completion_inbox','seq')), " +
120
- "owner = EXCLUDED.owner, status = EXCLUDED.status, summary = EXCLUDED.summary, enqueued_at = EXCLUDED.enqueued_at, kind = EXCLUDED.kind, payload = EXCLUDED.payload", [entry.sessionId, entry.runId, entry.owner, entry.status, entry.summary, entry.enqueuedAt, entry.kind ?? null, entry.payload ?? null]);
121
- const confirm = await this.pool.query("SELECT kind, owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND until_ms >= $2 AND (run_id IN ($3, $4) OR (kind = $5 AND run_id = ''))", [entry.sessionId, now, entry.runId, foldKeyFamily(entry.runId), KIND_PURGE]);
122
- for (const f of confirm.rows) {
123
- const isServed = f.kind === KIND_SERVED;
124
- const purgeFencesUs = !isServed && (Number(f.owner_known) === 0 || (f.owner === null ? entry.owner === null : f.owner === entry.owner));
125
- if (isServed || purgeFencesUs) {
126
- await this.ack(entry.sessionId, entry.runId);
127
- this.onWarn?.(isServed ? "workflow_completion_enqueue_after_poll_served" : "workflow_completion_enqueue_after_purge", {
128
- sessionId: entry.sessionId,
129
- runId: entry.runId,
130
- });
131
- return;
132
- }
133
- }
134
- const over = await this.pool.query("SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = $1 ORDER BY seq DESC OFFSET $2", [entry.sessionId, MAX_PENDING_PER_SESSION]);
135
- const overRows = over.rows;
136
- if (overRows.length > 0) {
137
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND seq = ANY($2::bigint[])", [
138
- entry.sessionId,
139
- overRows.map((r) => r.seq),
140
- ]);
141
- for (const r of overRows)
142
- this.onWarn?.("workflow_completion_inbox_overflow", { sessionId: entry.sessionId, droppedRunId: r.run_id });
143
- }
144
- }
145
- async pending(sessionId) {
146
- const res = await this.pool.query("SELECT i.run_id, i.owner, i.status, i.summary, i.enqueued_at, i.kind, i.payload FROM workflow_completion_inbox i " +
147
- "WHERE i.session_id = $1 AND NOT EXISTS (SELECT 1 FROM workflow_inbox_fence f WHERE f.session_id = i.session_id AND f.kind = $2 AND f.run_id IN (i.run_id, regexp_replace(i.run_id, '#[0-9]+$', '')) AND f.until_ms >= $3) " +
148
- "ORDER BY i.seq ASC", [sessionId, KIND_SERVED, Date.now()]);
149
- return res.rows.map((r) => ({
150
- sessionId,
151
- runId: r.run_id,
152
- owner: r.owner,
153
- status: r.status,
154
- summary: r.summary,
155
- enqueuedAt: Number(r.enqueued_at),
156
- ...(r.kind != null ? { kind: r.kind } : {}),
157
- ...(r.payload != null ? { payload: r.payload } : {}),
158
- }));
159
- }
160
- async ack(sessionId, runId) {
161
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND run_id = $2", [sessionId, runId]);
162
- }
163
- async purge(sessionId, purgedOwner) {
164
- const now = Date.now();
165
- await this.sweepFences(now);
166
- await this.pool.query("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,'',$3,$4,$5) " +
167
- "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET owner = EXCLUDED.owner, owner_known = EXCLUDED.owner_known, until_ms = EXCLUDED.until_ms", [sessionId, KIND_PURGE, purgedOwner ?? null, purgedOwner !== undefined ? 1 : 0, now + PURGE_FENCE_MS]);
168
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1", [sessionId]);
169
- }
170
- async markTerminalServed(sessionId, runId) {
171
- const now = Date.now();
172
- await this.sweepFences(now);
173
- await this.pool.query("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,$3,NULL,0,$4) " +
174
- "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET until_ms = EXCLUDED.until_ms", [sessionId, KIND_SERVED, runId, now + SERVED_FENCE_MS]);
175
- await this.ack(sessionId, runId);
176
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND run_id <> $2 AND regexp_replace(run_id, '#[0-9]+$', '') = $3", [sessionId, runId, runId]);
177
- }
178
- }
179
- export class PgWorkflowNotifyJournalStore {
180
- pool;
181
- constructor(pool) {
182
- this.pool = pool;
183
- }
184
- async record(input) {
185
- await this.pool.query("INSERT INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at) VALUES ($1,$2,0,$3,$4,$5) ON CONFLICT (run_id) DO NOTHING", [input.runId, input.scope, input.sourceTaskId ?? null, input.principal ?? null, input.createdAt]);
186
- }
187
- async ack(runId, ackedAt) {
188
- await this.pool.query("UPDATE workflow_notify_journal SET acked = 1, acked_at = $1 WHERE run_id = $2 AND acked = 0", [ackedAt, runId]);
189
- }
190
- async listPending() {
191
- const res = await this.pool.query("SELECT run_id, scope, source_task_id, principal, created_at FROM workflow_notify_journal WHERE acked = 0");
192
- return res.rows.map((r) => ({
193
- runId: r.run_id,
194
- scope: r.scope,
195
- acked: false,
196
- ...(r.source_task_id != null ? { sourceTaskId: r.source_task_id } : {}),
197
- ...(r.principal != null ? { principal: r.principal } : {}),
198
- createdAt: Number(r.created_at),
199
- }));
200
- }
201
- async reapAcked(before) {
202
- const res = await this.pool.query("DELETE FROM workflow_notify_journal WHERE acked = 1 AND acked_at < $1", [before]);
203
- return res.rowCount ?? 0;
204
- }
205
- async get(runId) {
206
- const res = await this.pool.query("SELECT run_id, scope, acked, source_task_id, principal, created_at, acked_at FROM workflow_notify_journal WHERE run_id = $1", [runId]);
207
- const r = res.rows[0];
208
- if (!r)
209
- return null;
210
- return {
211
- runId: r.run_id,
212
- scope: r.scope,
213
- acked: Number(r.acked) === 1,
214
- ...(r.source_task_id != null ? { sourceTaskId: r.source_task_id } : {}),
215
- ...(r.principal != null ? { principal: r.principal } : {}),
216
- createdAt: Number(r.created_at),
217
- ...(r.acked_at != null ? { ackedAt: Number(r.acked_at) } : {}),
218
- };
219
- }
220
- }
1
+ export { PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore } from "./workflow-run-store-sql.js";
221
2
  //# sourceMappingURL=pg-workflow-run-store.js.map
@@ -0,0 +1,23 @@
1
+ import type { Pool as MySqlPool } from "mysql2/promise";
2
+ import type { Pool as PgPool } from "pg";
3
+ import { type SqlDriver } from "./sql-driver.js";
4
+ export declare class SqlResumeAnchorStore {
5
+ protected readonly db: SqlDriver;
6
+ constructor(db: SqlDriver);
7
+ private q;
8
+ put(sessionId: string, eventId: string, entryId: string, owner: string | null): Promise<void>;
9
+ resolve(sessionId: string, eventId: string, owner: string | null): Promise<string | undefined>;
10
+ listBySession(sessionId: string): Promise<Array<{
11
+ eventId: string;
12
+ entryId: string;
13
+ owner: string | null;
14
+ }>>;
15
+ deleteBySession(sessionId: string): Promise<number>;
16
+ }
17
+ export declare class TiDBResumeAnchorStore extends SqlResumeAnchorStore {
18
+ constructor(pool: MySqlPool);
19
+ }
20
+ export declare class PgResumeAnchorStore extends SqlResumeAnchorStore {
21
+ constructor(pool: PgPool);
22
+ }
23
+ //# sourceMappingURL=resume-anchor-store-sql.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { mysqlDriver, pgDriver } from "./sql-driver.js";
2
+ export class SqlResumeAnchorStore {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ q(tidb, pg) {
8
+ return this.db.dialect === "tidb" ? tidb : pg;
9
+ }
10
+ async put(sessionId, eventId, entryId, owner) {
11
+ await this.db.query(this.q("INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES (?,?,?,?,?) " +
12
+ "ON DUPLICATE KEY UPDATE entry_id = VALUES(entry_id), owner = VALUES(owner)", "INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES ($1,$2,$3,$4,$5) " +
13
+ "ON CONFLICT (session_id, event_id) DO UPDATE SET entry_id = EXCLUDED.entry_id, owner = EXCLUDED.owner"), [sessionId, eventId, entryId, owner, new Date()]);
14
+ }
15
+ async resolve(sessionId, eventId, owner) {
16
+ const { rows } = await this.db.query(this.q("SELECT entry_id FROM resume_anchor WHERE session_id = ? AND event_id = ? AND owner <=> ?", "SELECT entry_id FROM resume_anchor WHERE session_id = $1 AND event_id = $2 AND owner IS NOT DISTINCT FROM $3"), [sessionId, eventId, owner]);
17
+ return rows[0] ? String(rows[0].entry_id) : undefined;
18
+ }
19
+ async listBySession(sessionId) {
20
+ const { rows } = await this.db.query(this.q("SELECT event_id, entry_id, owner FROM resume_anchor WHERE session_id = ?", "SELECT event_id, entry_id, owner FROM resume_anchor WHERE session_id = $1"), [sessionId]);
21
+ return rows.map((r) => ({ eventId: String(r.event_id), entryId: String(r.entry_id), owner: r.owner == null ? null : String(r.owner) }));
22
+ }
23
+ async deleteBySession(sessionId) {
24
+ const res = await this.db.query(this.q("DELETE FROM resume_anchor WHERE session_id = ?", "DELETE FROM resume_anchor WHERE session_id = $1"), [sessionId]);
25
+ return res.affected;
26
+ }
27
+ }
28
+ export class TiDBResumeAnchorStore extends SqlResumeAnchorStore {
29
+ constructor(pool) {
30
+ super(mysqlDriver(pool));
31
+ }
32
+ }
33
+ export class PgResumeAnchorStore extends SqlResumeAnchorStore {
34
+ constructor(pool) {
35
+ super(pgDriver(pool));
36
+ }
37
+ }
38
+ //# sourceMappingURL=resume-anchor-store-sql.js.map
@@ -0,0 +1,89 @@
1
+ import type { TaskResult, TaskStatus } from "@sema-agent/core";
2
+ import type { Pool as MySqlPool } from "mysql2/promise";
3
+ import type { Pool as PgPool } from "pg";
4
+ import { type UsageRow } from "../usage-analytics.js";
5
+ import type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
6
+ import { type SqlDriver } from "./sql-driver.js";
7
+ export type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
8
+ export declare class SqlRunStore {
9
+ protected readonly db: SqlDriver;
10
+ constructor(db: SqlDriver);
11
+ private q;
12
+ private isDupKey;
13
+ private json;
14
+ private tx;
15
+ createRun(taskId: string, sessionId: string, owner: string | null, instanceId: string, meta?: {
16
+ jobId?: string | null;
17
+ source?: string | null;
18
+ objectivePreview?: string | null;
19
+ }): Promise<{
20
+ ok: true;
21
+ } | {
22
+ ok: false;
23
+ activeTaskId: string;
24
+ }>;
25
+ requestCancel(taskId: string, owner: string | null): Promise<boolean>;
26
+ isCancelRequested(taskId: string, owner: string | null): Promise<boolean>;
27
+ requestPreempt(taskId: string, owner: string | null): Promise<boolean>;
28
+ isPreemptRequested(taskId: string, owner: string | null): Promise<boolean>;
29
+ heartbeat(taskId: string, owner: string | null): Promise<void>;
30
+ appendEvent(taskId: string, seq: number, type: string, data: unknown): Promise<void>;
31
+ maxSeq(taskId: string): Promise<number>;
32
+ setTerminal(taskId: string, status: TaskStatus, result: TaskResult | null, error: string | null): Promise<void>;
33
+ setSuspended(taskId: string): Promise<void>;
34
+ setNeedsReview(taskId: string): Promise<void>;
35
+ markResuming(taskId: string): Promise<boolean>;
36
+ getActiveTaskId(sessionId: string): Promise<string | undefined>;
37
+ private rowToRun;
38
+ getRun(taskId: string): Promise<RunRecord | undefined>;
39
+ listRuns(opts: {
40
+ status?: string;
41
+ jobId?: string;
42
+ source?: string;
43
+ owner?: string;
44
+ cursor?: {
45
+ createdAt: string;
46
+ taskId: string;
47
+ };
48
+ limit: number;
49
+ }): Promise<RunRecord[]>;
50
+ listSessions(opts: {
51
+ owner?: string;
52
+ cursor?: {
53
+ lastActivityAt: string;
54
+ sessionId: string;
55
+ };
56
+ limit: number;
57
+ q?: string;
58
+ }): Promise<SessionSummary[]>;
59
+ private rowToSession;
60
+ deleteBySession(sessionId: string, owner: string | null): Promise<{
61
+ removed: number;
62
+ } | {
63
+ active: string;
64
+ }>;
65
+ sourceSummary(sinceMs: number): Promise<Array<{
66
+ source: string | null;
67
+ status: string;
68
+ count: number;
69
+ }>>;
70
+ usageScan(fromMs: number, toMs: number, opts?: {
71
+ owner?: string;
72
+ limit?: number;
73
+ }): Promise<{
74
+ rows: UsageRow[];
75
+ truncated: boolean;
76
+ }>;
77
+ retainedFrom(taskId: string): Promise<number>;
78
+ getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
79
+ reapStale(olderThanMs: number): Promise<number>;
80
+ reapSuspended(olderThanMs: number): Promise<number>;
81
+ failSuspendedWithExpiredCheckpoint(): Promise<number>;
82
+ }
83
+ export declare class TiDBRunStore extends SqlRunStore {
84
+ constructor(pool: MySqlPool);
85
+ }
86
+ export declare class PgRunStore extends SqlRunStore {
87
+ constructor(pool: PgPool);
88
+ }
89
+ //# sourceMappingURL=run-store-sql.d.ts.map