@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,23 +1,3 @@
1
- import type { Pool, PoolClient } from "pg";
2
- import { type TaskOutcome, type RunStatus } from "./tidb-outcome-ledger.js";
3
- import type { TaskOutcome as CoreTaskOutcome } from "@sema-agent/core";
4
- export declare const PG_OUTCOME_LEDGER_SCHEMA: string[];
5
- export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
6
- export declare class PgOutcomeLedger {
7
- private readonly pool;
8
- constructor(pool: Pool);
9
- record(o: TaskOutcome): Promise<string>;
10
- recordCore(o: CoreTaskOutcome): Promise<void>;
11
- summary(opts?: {
12
- taskSignature?: string;
13
- includeAborted?: boolean;
14
- }): Promise<Array<{
15
- taskSignature: string;
16
- model: string;
17
- n: number;
18
- passRate: number;
19
- meanCostMicroUsd: number;
20
- }>>;
21
- }
22
- export type { TaskOutcome, RunStatus };
1
+ export { PgOutcomeLedger, PG_OUTCOME_LEDGER_SCHEMA, ensurePgOutcomeLedgerSchema as ensureSchema, } from "./outcome-ledger-sql.js";
2
+ export type { TaskOutcome, RunStatus } from "./outcome-ledger-sql.js";
23
3
  //# sourceMappingURL=pg-outcome-ledger.d.ts.map
@@ -1,105 +1,2 @@
1
- import { randomUUID } from "node:crypto";
2
- import { taskSignature, deriveOutcome, } from "./tidb-outcome-ledger.js";
3
- import { coreOutcomeToLedgerRow } from "./tidb-outcome-ledger.js";
4
- import { pgSafeJsonStringify } from "./pg-safe-json.js";
5
- export const PG_OUTCOME_LEDGER_SCHEMA = [
6
- `CREATE TABLE IF NOT EXISTS outcome_ledger (
7
- id VARCHAR(64) NOT NULL,
8
- task_signature VARCHAR(190) NOT NULL,
9
- model VARCHAR(190) NOT NULL,
10
- task_id VARCHAR(64),
11
- run_status VARCHAR(20) NOT NULL DEFAULT 'completed-graded',
12
- route VARCHAR(16),
13
- worker_count INTEGER,
14
- oracle_passed INTEGER,
15
- oracle_total INTEGER,
16
- assemble_exit INTEGER,
17
- lint_exit INTEGER,
18
- merge_ok SMALLINT,
19
- replan_triggers JSONB,
20
- cost_micro_usd BIGINT,
21
- turns INTEGER,
22
- outcome VARCHAR(8),
23
- llm_assisted JSONB,
24
- signature_inputs JSONB,
25
- -- design/73 §1 bridge: the VERBATIM core TaskOutcome fact (oracleHadRedRun / status / oracle blob) as it
26
- -- came off core, kept alongside the derived mechanical columns. NULL when the caller recorded through the
27
- -- non-core \`record()\` path (no core fact to bridge).
28
- core_outcome JSONB,
29
- created_at TIMESTAMPTZ(3) NOT NULL,
30
- PRIMARY KEY (id)
31
- )`,
32
- `CREATE INDEX IF NOT EXISTS idx_outcome_ledger_sig_model ON outcome_ledger (task_signature, model, run_status)`,
33
- `CREATE INDEX IF NOT EXISTS idx_outcome_ledger_outcome ON outcome_ledger (outcome)`,
34
- `CREATE INDEX IF NOT EXISTS idx_outcome_ledger_created ON outcome_ledger (created_at)`,
35
- ];
36
- export async function ensureSchema(pool) {
37
- for (const stmt of PG_OUTCOME_LEDGER_SCHEMA)
38
- await pool.query(stmt);
39
- }
40
- export class PgOutcomeLedger {
41
- pool;
42
- constructor(pool) {
43
- this.pool = pool;
44
- }
45
- async record(o) {
46
- const id = randomUUID().replace(/-/g, "").slice(0, 32);
47
- const sig = taskSignature(o.signatureInputs);
48
- const m = o.mechanical;
49
- const runStatus = o.runStatus ?? "completed-graded";
50
- await this.pool.query(`INSERT INTO outcome_ledger
51
- (id, task_signature, model, task_id, run_status, route, worker_count, oracle_passed, oracle_total,
52
- assemble_exit, lint_exit, merge_ok, replan_triggers, cost_micro_usd, turns, outcome,
53
- llm_assisted, signature_inputs, core_outcome, created_at)
54
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13::jsonb,$14,$15,$16,$17::jsonb,$18::jsonb,$19::jsonb,$20)`, [
55
- id,
56
- sig,
57
- o.model,
58
- o.taskId ?? null,
59
- runStatus,
60
- m.route ?? null,
61
- m.workerCount ?? null,
62
- m.oraclePassed ?? null,
63
- m.oracleTotal ?? null,
64
- m.assembleExit ?? null,
65
- m.lintExit ?? null,
66
- m.mergeOk == null ? null : m.mergeOk ? 1 : 0,
67
- m.replanTriggers ? pgSafeJsonStringify(m.replanTriggers) : null,
68
- m.costMicroUsd ?? null,
69
- m.turns ?? null,
70
- deriveOutcome(m, runStatus),
71
- o.llmAssisted ? pgSafeJsonStringify(o.llmAssisted) : null,
72
- pgSafeJsonStringify(o.signatureInputs),
73
- o.core != null ? pgSafeJsonStringify(o.core) : null,
74
- new Date(),
75
- ]);
76
- return id;
77
- }
78
- async recordCore(o) {
79
- await this.record(coreOutcomeToLedgerRow(o));
80
- }
81
- async summary(opts = {}) {
82
- const clauses = [];
83
- const params = [];
84
- if (!opts.includeAborted)
85
- clauses.push("run_status = 'completed-graded'");
86
- if (opts.taskSignature) {
87
- params.push(opts.taskSignature);
88
- clauses.push(`task_signature = $${params.length}`);
89
- }
90
- const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
91
- const res = await this.pool.query(`SELECT task_signature, model, COUNT(*) n,
92
- AVG((outcome = 'pass')::int) pass_rate, AVG(cost_micro_usd) mean_cost
93
- FROM outcome_ledger ${where}
94
- GROUP BY task_signature, model
95
- ORDER BY task_signature, model`, params);
96
- return res.rows.map((r) => ({
97
- taskSignature: String(r.task_signature),
98
- model: String(r.model),
99
- n: Number(r.n),
100
- passRate: Number(r.pass_rate),
101
- meanCostMicroUsd: Number(r.mean_cost ?? 0),
102
- }));
103
- }
104
- }
1
+ export { PgOutcomeLedger, PG_OUTCOME_LEDGER_SCHEMA, ensurePgOutcomeLedgerSchema as ensureSchema, } from "./outcome-ledger-sql.js";
105
2
  //# sourceMappingURL=pg-outcome-ledger.js.map
@@ -1,14 +1,2 @@
1
- import type { Pool } from "pg";
2
- export declare class PgResumeAnchorStore {
3
- private readonly pool;
4
- constructor(pool: Pool);
5
- put(sessionId: string, eventId: string, entryId: string, owner: string | null): Promise<void>;
6
- resolve(sessionId: string, eventId: string, owner: string | null): Promise<string | undefined>;
7
- listBySession(sessionId: string): Promise<Array<{
8
- eventId: string;
9
- entryId: string;
10
- owner: string | null;
11
- }>>;
12
- deleteBySession(sessionId: string): Promise<number>;
13
- }
1
+ export { PgResumeAnchorStore } from "./resume-anchor-store-sql.js";
14
2
  //# sourceMappingURL=pg-resume-anchor-store.d.ts.map
@@ -1,23 +1,2 @@
1
- export class PgResumeAnchorStore {
2
- pool;
3
- constructor(pool) {
4
- this.pool = pool;
5
- }
6
- async put(sessionId, eventId, entryId, owner) {
7
- await this.pool.query("INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES ($1,$2,$3,$4,$5) " +
8
- "ON CONFLICT (session_id, event_id) DO UPDATE SET entry_id = EXCLUDED.entry_id, owner = EXCLUDED.owner", [sessionId, eventId, entryId, owner, new Date()]);
9
- }
10
- async resolve(sessionId, eventId, owner) {
11
- const r = await this.pool.query("SELECT entry_id FROM resume_anchor WHERE session_id = $1 AND event_id = $2 AND owner IS NOT DISTINCT FROM $3", [sessionId, eventId, owner]);
12
- return r.rows[0] ? String(r.rows[0].entry_id) : undefined;
13
- }
14
- async listBySession(sessionId) {
15
- const r = await this.pool.query("SELECT event_id, entry_id, owner FROM resume_anchor WHERE session_id = $1", [sessionId]);
16
- return r.rows.map((row) => ({ eventId: String(row.event_id), entryId: String(row.entry_id), owner: row.owner == null ? null : String(row.owner) }));
17
- }
18
- async deleteBySession(sessionId) {
19
- const r = await this.pool.query("DELETE FROM resume_anchor WHERE session_id = $1", [sessionId]);
20
- return r.rowCount ?? 0;
21
- }
22
- }
1
+ export { PgResumeAnchorStore } from "./resume-anchor-store-sql.js";
23
2
  //# sourceMappingURL=pg-resume-anchor-store.js.map
@@ -1,76 +1,2 @@
1
- import type { TaskResult, TaskStatus } from "@sema-agent/core";
2
- import { type UsageRow } from "../usage-analytics.js";
3
- import type { Pool } from "pg";
4
- import type { RunRecord, RunEvent, SessionSummary } from "./store-contracts.js";
5
- export declare class PgRunStore {
6
- private readonly pool;
7
- constructor(pool: Pool);
8
- private tx;
9
- createRun(taskId: string, sessionId: string, owner: string | null, instanceId: string, meta?: {
10
- jobId?: string | null;
11
- source?: string | null;
12
- objectivePreview?: string | null;
13
- }): Promise<{
14
- ok: true;
15
- } | {
16
- ok: false;
17
- activeTaskId: string;
18
- }>;
19
- requestCancel(taskId: string, owner: string | null): Promise<boolean>;
20
- isCancelRequested(taskId: string, owner: string | null): Promise<boolean>;
21
- requestPreempt(taskId: string, owner: string | null): Promise<boolean>;
22
- isPreemptRequested(taskId: string, owner: string | null): Promise<boolean>;
23
- heartbeat(taskId: string, owner: string | null): Promise<void>;
24
- appendEvent(taskId: string, seq: number, type: string, data: unknown): Promise<void>;
25
- maxSeq(taskId: string): Promise<number>;
26
- setTerminal(taskId: string, status: TaskStatus, result: TaskResult | null, error: string | null): Promise<void>;
27
- setSuspended(taskId: string): Promise<void>;
28
- setNeedsReview(taskId: string): Promise<void>;
29
- markResuming(taskId: string): Promise<boolean>;
30
- getActiveTaskId(sessionId: string): Promise<string | undefined>;
31
- private rowToRun;
32
- getRun(taskId: string): Promise<RunRecord | undefined>;
33
- listRuns(opts: {
34
- status?: string;
35
- jobId?: string;
36
- source?: string;
37
- owner?: string;
38
- cursor?: {
39
- createdAt: string;
40
- taskId: string;
41
- };
42
- limit: number;
43
- }): Promise<RunRecord[]>;
44
- listSessions(opts: {
45
- owner?: string;
46
- cursor?: {
47
- lastActivityAt: string;
48
- sessionId: string;
49
- };
50
- limit: number;
51
- q?: string;
52
- }): Promise<SessionSummary[]>;
53
- deleteBySession(sessionId: string, owner: string | null): Promise<{
54
- removed: number;
55
- } | {
56
- active: string;
57
- }>;
58
- sourceSummary(sinceMs: number): Promise<Array<{
59
- source: string | null;
60
- status: string;
61
- count: number;
62
- }>>;
63
- usageScan(fromMs: number, toMs: number, opts?: {
64
- owner?: string;
65
- limit?: number;
66
- }): Promise<{
67
- rows: UsageRow[];
68
- truncated: boolean;
69
- }>;
70
- retainedFrom(taskId: string): Promise<number>;
71
- getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
72
- reapStale(olderThanMs: number): Promise<number>;
73
- reapSuspended(olderThanMs: number): Promise<number>;
74
- failSuspendedWithExpiredCheckpoint(): Promise<number>;
75
- }
1
+ export { PgRunStore } from "./run-store-sql.js";
76
2
  //# sourceMappingURL=pg-run-store.d.ts.map
@@ -1,288 +1,2 @@
1
- import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
2
- import { escapeLike } from "./sql-escape.js";
3
- import { pgSafeJsonStringify, pgSanitizeText } from "./pg-safe-json.js";
4
- import { parseJsonLenient as parseJson, toIso as iso } from "./sql-row-helpers.js";
5
- function isDupKey(err) {
6
- return Boolean(err) && err.code === "23505";
7
- }
8
- export class PgRunStore {
9
- pool;
10
- constructor(pool) {
11
- this.pool = pool;
12
- }
13
- async tx(fn) {
14
- const c = await this.pool.connect();
15
- try {
16
- await c.query("BEGIN");
17
- const out = await fn(c);
18
- await c.query("COMMIT");
19
- return out;
20
- }
21
- catch (err) {
22
- try {
23
- await c.query("ROLLBACK");
24
- }
25
- catch {
26
- }
27
- throw err;
28
- }
29
- finally {
30
- c.release();
31
- }
32
- }
33
- async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
34
- const c = await this.pool.connect();
35
- try {
36
- await c.query("BEGIN");
37
- const now = new Date();
38
- try {
39
- await c.query("INSERT INTO task_active (session_id, task_id, since) VALUES ($1,$2,$3)", [sessionId, taskId, now]);
40
- }
41
- catch (err) {
42
- if (isDupKey(err)) {
43
- await c.query("ROLLBACK");
44
- const active = await c.query("SELECT task_id FROM task_active WHERE session_id = $1", [sessionId]);
45
- return { ok: false, activeTaskId: String(active.rows[0]?.task_id ?? "") };
46
- }
47
- throw err;
48
- }
49
- await c.query("INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, objective_preview, created_at, updated_at) " +
50
- "VALUES ($1,$2,$3,'running',$4,$5,$6,$7,$8,$9)", [taskId, sessionId, owner, instanceId, meta.jobId ?? null, meta.source ?? null, meta.objectivePreview == null ? null : pgSanitizeText(meta.objectivePreview), now, now]);
51
- await c.query("COMMIT");
52
- return { ok: true };
53
- }
54
- catch (err) {
55
- try {
56
- await c.query("ROLLBACK");
57
- }
58
- catch {
59
- }
60
- throw err;
61
- }
62
- finally {
63
- c.release();
64
- }
65
- }
66
- async requestCancel(taskId, owner) {
67
- const r = await this.pool.query("UPDATE task_run SET cancel_requested = 1 WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2 AND status = 'running'", [taskId, owner]);
68
- return (r.rowCount ?? 0) > 0;
69
- }
70
- async isCancelRequested(taskId, owner) {
71
- const r = await this.pool.query("SELECT cancel_requested FROM task_run WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2", [taskId, owner]);
72
- return Boolean(r.rows[0]?.cancel_requested);
73
- }
74
- async requestPreempt(taskId, owner) {
75
- const r = await this.pool.query("UPDATE task_run SET preempt_requested = 1 WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2 AND status = 'running'", [taskId, owner]);
76
- return (r.rowCount ?? 0) > 0;
77
- }
78
- async isPreemptRequested(taskId, owner) {
79
- const r = await this.pool.query("SELECT preempt_requested FROM task_run WHERE task_id = $1 AND owner IS NOT DISTINCT FROM $2", [taskId, owner]);
80
- return Boolean(r.rows[0]?.preempt_requested);
81
- }
82
- async heartbeat(taskId, owner) {
83
- await this.pool.query("UPDATE task_run SET updated_at = $1 WHERE task_id = $2 AND owner IS NOT DISTINCT FROM $3 AND status = 'running'", [new Date(), taskId, owner]);
84
- }
85
- async appendEvent(taskId, seq, type, data) {
86
- await this.pool.query("INSERT INTO task_event (task_id, seq, type, data, ts) VALUES ($1,$2,$3,$4::jsonb,$5)", [
87
- taskId,
88
- seq,
89
- type,
90
- data == null ? null : pgSafeJsonStringify(data),
91
- new Date(),
92
- ]);
93
- }
94
- async maxSeq(taskId) {
95
- const r = await this.pool.query("SELECT MAX(seq) AS m FROM task_event WHERE task_id = $1", [taskId]);
96
- const m = r.rows[0]?.m;
97
- return m == null ? 0 : Number(m);
98
- }
99
- async setTerminal(taskId, status, result, error) {
100
- const errorCode = result?.errorCode ?? null;
101
- await this.tx(async (c) => {
102
- await c.query("UPDATE task_run SET status = $1, result = $2::jsonb, error = $3, error_code = $4, updated_at = $5 WHERE task_id = $6 AND status IN ('running','suspended','needs_review')", [
103
- status,
104
- result == null ? null : pgSafeJsonStringify(result),
105
- error == null ? null : pgSanitizeText(error),
106
- errorCode == null ? null : pgSanitizeText(errorCode),
107
- new Date(),
108
- taskId,
109
- ]);
110
- await c.query("DELETE FROM task_active WHERE task_id = $1", [taskId]);
111
- });
112
- }
113
- async setSuspended(taskId) {
114
- await this.pool.query("UPDATE task_run SET status = 'suspended', updated_at = $1 WHERE task_id = $2 AND status IN ('running','suspended')", [new Date(), taskId]);
115
- }
116
- async setNeedsReview(taskId) {
117
- await this.pool.query("UPDATE task_run SET status = 'needs_review', updated_at = $1 WHERE task_id = $2 AND status IN ('running','needs_review')", [new Date(), taskId]);
118
- }
119
- async markResuming(taskId) {
120
- const r = await this.pool.query("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, updated_at = $1 WHERE task_id = $2 AND status IN ('suspended','needs_review')", [new Date(), taskId]);
121
- return (r.rowCount ?? 0) === 1;
122
- }
123
- async getActiveTaskId(sessionId) {
124
- const r = await this.pool.query("SELECT task_id FROM task_active WHERE session_id = $1", [sessionId]);
125
- return r.rows[0] ? String(r.rows[0].task_id) : undefined;
126
- }
127
- rowToRun(r) {
128
- return {
129
- taskId: String(r.task_id),
130
- sessionId: String(r.session_id),
131
- owner: r.owner ?? null,
132
- status: r.status,
133
- result: parseJson(r.result),
134
- error: r.error ?? null,
135
- errorCode: r.error_code ?? null,
136
- jobId: r.job_id ?? null,
137
- source: r.source ?? null,
138
- instanceId: r.instance_id ?? null,
139
- objectivePreview: r.objective_preview ?? null,
140
- createdAt: iso(r.created_at),
141
- updatedAt: iso(r.updated_at),
142
- };
143
- }
144
- async getRun(taskId) {
145
- const r = await this.pool.query("SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, instance_id, objective_preview, created_at, updated_at FROM task_run WHERE task_id = $1", [taskId]);
146
- return r.rows[0] ? this.rowToRun(r.rows[0]) : undefined;
147
- }
148
- async listRuns(opts) {
149
- if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
150
- return [];
151
- const where = [];
152
- const params = [];
153
- const p = (v) => {
154
- params.push(v);
155
- return `$${params.length}`;
156
- };
157
- if (opts.status)
158
- where.push(`status = ${p(opts.status)}`);
159
- if (opts.jobId)
160
- where.push(`job_id = ${p(opts.jobId)}`);
161
- if (opts.source)
162
- where.push(`source = ${p(opts.source)}`);
163
- if (opts.owner)
164
- where.push(`owner = ${p(opts.owner)}`);
165
- if (opts.cursor) {
166
- const a = p(new Date(opts.cursor.createdAt));
167
- const b = p(new Date(opts.cursor.createdAt));
168
- const t = p(opts.cursor.taskId);
169
- where.push(`(created_at < ${a} OR (created_at = ${b} AND task_id < ${t}))`);
170
- }
171
- const sql = "SELECT task_id, session_id, owner, status, result, error, error_code, job_id, source, objective_preview, created_at, updated_at FROM task_run" +
172
- (where.length ? " WHERE " + where.join(" AND ") : "") +
173
- ` ORDER BY created_at DESC, task_id DESC LIMIT ${p(opts.limit)}`;
174
- const r = await this.pool.query(sql, params);
175
- return r.rows.map((row) => this.rowToRun(row));
176
- }
177
- async listSessions(opts) {
178
- const params = [];
179
- const p = (v) => {
180
- params.push(v);
181
- return `$${params.length}`;
182
- };
183
- const innerWhere = opts.owner ? ` WHERE owner = ${p(opts.owner)}` : "";
184
- const outer = ["rn = 1"];
185
- if (opts.q)
186
- outer.push(`EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = t.session_id AND LOWER(tr.objective_preview) LIKE LOWER(${p(`%${escapeLike(opts.q)}%`)}) ESCAPE '\\')`);
187
- if (opts.cursor) {
188
- const a = p(new Date(opts.cursor.lastActivityAt));
189
- const b = p(new Date(opts.cursor.lastActivityAt));
190
- const sid = p(opts.cursor.sessionId);
191
- outer.push(`(last_activity < ${a} OR (last_activity = ${b} AND session_id < ${sid}))`);
192
- }
193
- const sql = "SELECT session_id, owner, task_id, last_activity, first_activity, run_count, objective_preview, status FROM (" +
194
- "SELECT session_id, owner, task_id, objective_preview, status," +
195
- " ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at DESC, task_id DESC) AS rn," +
196
- " MAX(created_at) OVER (PARTITION BY session_id) AS last_activity," +
197
- " MIN(created_at) OVER (PARTITION BY session_id) AS first_activity," +
198
- " COUNT(*) OVER (PARTITION BY session_id) AS run_count" +
199
- " FROM task_run" + innerWhere + ") t WHERE " + outer.join(" AND ") +
200
- ` ORDER BY last_activity DESC, session_id DESC LIMIT ${p(opts.limit)}`;
201
- const r = await this.pool.query(sql, params);
202
- return r.rows.map((row) => ({
203
- sessionId: String(row.session_id),
204
- owner: row.owner ?? null,
205
- lastActivityAt: iso(row.last_activity),
206
- firstActivityAt: iso(row.first_activity),
207
- runCount: Number(row.run_count),
208
- objectivePreview: row.objective_preview ?? null,
209
- lastStatus: String(row.status),
210
- lastRunId: row.task_id ?? null,
211
- title: null,
212
- }));
213
- }
214
- async deleteBySession(sessionId, owner) {
215
- return this.tx(async (c) => {
216
- const active = await c.query("SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE", [sessionId]);
217
- if (active.rows[0])
218
- return { active: String(active.rows[0].task_id) };
219
- await c.query("DELETE FROM task_event te USING task_run tr WHERE te.task_id = tr.task_id AND tr.session_id = $1 AND tr.owner IS NOT DISTINCT FROM $2", [sessionId, owner]);
220
- await c.query("DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.session_id = $1 AND tr.owner IS NOT DISTINCT FROM $2", [sessionId, owner]);
221
- const res = await c.query("DELETE FROM task_run WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2", [sessionId, owner]);
222
- return { removed: res.rowCount ?? 0 };
223
- });
224
- }
225
- async sourceSummary(sinceMs) {
226
- const r = await this.pool.query("SELECT source, status, COUNT(*) AS c FROM task_run WHERE created_at >= $1 GROUP BY source, status", [new Date(sinceMs)]);
227
- return r.rows.map((row) => ({ source: row.source ?? null, status: String(row.status), count: Number(row.c) }));
228
- }
229
- async usageScan(fromMs, toMs, opts) {
230
- const limit = Math.min(opts?.limit ?? USAGE_SCAN_LIMIT, USAGE_SCAN_LIMIT);
231
- const where = ["created_at >= $1", "created_at < $2"];
232
- const params = [new Date(fromMs), new Date(toMs)];
233
- if (opts?.owner !== undefined) {
234
- where.push("owner = $3");
235
- params.push(opts.owner);
236
- }
237
- const r = await this.pool.query(`SELECT owner, created_at, status, result FROM task_run WHERE ${where.join(" AND ")} ORDER BY created_at LIMIT ${limit + 1}`, params);
238
- const truncated = r.rows.length > limit;
239
- const page = truncated ? r.rows.slice(0, limit) : r.rows;
240
- return {
241
- truncated,
242
- rows: page.map((row) => ({
243
- owner: row.owner ?? null,
244
- createdAtMs: new Date(row.created_at).getTime(),
245
- status: String(row.status),
246
- ...(row.result != null ? { stats: projectUsageStats(row.result) } : {}),
247
- })),
248
- };
249
- }
250
- async retainedFrom(taskId) {
251
- const r = await this.pool.query("SELECT MIN(seq) AS m FROM task_event WHERE task_id = $1", [taskId]);
252
- const m = r.rows[0]?.m;
253
- return m == null ? 0 : Number(m);
254
- }
255
- async getEvents(taskId, afterSeq) {
256
- const r = await this.pool.query("SELECT seq, type, data, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC", [taskId, afterSeq]);
257
- return r.rows.map((row) => ({ seq: Number(row.seq), type: String(row.type), data: parseJson(row.data), ts: iso(row.ts) }));
258
- }
259
- async reapStale(olderThanMs) {
260
- const cutoff = new Date(Date.now() - olderThanMs);
261
- return this.tx(async (c) => {
262
- const res = await c.query("UPDATE task_run SET status = 'failed', error = 'run stalled (instance lost?)', updated_at = $1 WHERE status = 'running' AND updated_at < $2", [new Date(), cutoff]);
263
- await c.query("DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.status NOT IN ('running','suspended','needs_review')");
264
- return res.rowCount ?? 0;
265
- });
266
- }
267
- async reapSuspended(olderThanMs) {
268
- const cutoff = new Date(Date.now() - olderThanMs);
269
- return this.tx(async (c) => {
270
- const res = await c.query("UPDATE task_run AS tr SET status = 'failed', error = 'approval expired before decision', error_code = 'approval.expired', updated_at = $1 " +
271
- "WHERE tr.status = 'suspended' AND tr.updated_at < $2 " +
272
- "AND NOT EXISTS (SELECT 1 FROM checkpoint c WHERE c.session_id = tr.session_id AND c.status = 'pending')", [new Date(), cutoff]);
273
- await c.query("DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.status NOT IN ('running','suspended','needs_review')");
274
- return res.rowCount ?? 0;
275
- });
276
- }
277
- async failSuspendedWithExpiredCheckpoint() {
278
- return this.tx(async (c) => {
279
- const res = await c.query("UPDATE task_run AS tr SET status = 'failed', error = 'approval expired before decision', error_code = 'approval.expired', updated_at = $1 " +
280
- "WHERE tr.status IN ('suspended','needs_review') " +
281
- "AND EXISTS (SELECT 1 FROM checkpoint ce WHERE ce.session_id = tr.session_id AND ce.status = 'expired') " +
282
- "AND NOT EXISTS (SELECT 1 FROM checkpoint cp WHERE cp.session_id = tr.session_id AND cp.status = 'pending')", [new Date()]);
283
- await c.query("DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.status NOT IN ('running','suspended','needs_review')");
284
- return res.rowCount ?? 0;
285
- });
286
- }
287
- }
1
+ export { PgRunStore } from "./run-store-sql.js";
288
2
  //# sourceMappingURL=pg-run-store.js.map
@@ -1,12 +1,2 @@
1
- import { type SessionPolicyStore, type SessionPermissionRules, type StoredSessionRules, type SessionRulesRecord, type PutRulesOptions } from "@sema-agent/core";
2
- import type { Pool } from "pg";
3
- export declare class PgSessionPolicyStore implements SessionPolicyStore {
4
- private readonly pool;
5
- constructor(pool: Pool);
6
- getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
7
- listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
8
- putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
9
- deleteBySession(sessionId: string): Promise<void>;
10
- private tx;
11
- }
1
+ export { PgSessionPolicyStore } from "./session-policy-store-sql.js";
12
2
  //# sourceMappingURL=pg-session-policy-store.d.ts.map
@@ -1,91 +1,2 @@
1
- import { createHash } from "node:crypto";
2
- import { loosenReasons, SessionPolicyError, normalizeRules, stripRev } from "@sema-agent/core";
3
- import { pgHasUnstorable } from "./pg-safe-json.js";
4
- const policyKey = (sessionId, principal) => createHash("sha256").update(JSON.stringify([sessionId, principal ?? null])).digest("hex");
5
- const parseRules = (v) => (typeof v === "string" ? JSON.parse(v) : v);
6
- const isDupKey = (e) => e?.code === "23505";
7
- export class PgSessionPolicyStore {
8
- pool;
9
- constructor(pool) {
10
- this.pool = pool;
11
- }
12
- async getRules(sessionId, principal) {
13
- const r = await this.pool.query("SELECT rules, rev FROM session_policy WHERE policy_key = $1", [policyKey(sessionId, principal)]);
14
- if (!r.rows[0])
15
- return null;
16
- return { ...parseRules(r.rows[0].rules), rev: Number(r.rows[0].rev) };
17
- }
18
- async listBySession(sessionId) {
19
- const r = await this.pool.query("SELECT principal, rules, rev FROM session_policy WHERE session_id = $1", [sessionId]);
20
- return r.rows.map((row) => ({
21
- principal: row.principal == null ? undefined : String(row.principal),
22
- rules: { ...parseRules(row.rules), rev: Number(row.rev) },
23
- }));
24
- }
25
- async putRules(sessionId, principal, rules, opts) {
26
- const key = policyKey(sessionId, principal);
27
- for (let attempt = 0;; attempt++) {
28
- try {
29
- return await this.tx(async (c) => {
30
- const r = await c.query("SELECT rules, rev FROM session_policy WHERE policy_key = $1 FOR UPDATE", [key]);
31
- const prior = r.rows[0] ? { rules: parseRules(r.rows[0].rules), rev: Number(r.rows[0].rev) } : undefined;
32
- const priorRev = prior?.rev ?? 0;
33
- if (opts?.expectedRev !== undefined && priorRev !== opts.expectedRev) {
34
- throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts.expectedRev}, have ${priorRev})`);
35
- }
36
- const clean = normalizeRules(stripRev(rules));
37
- if (prior && opts?.operator !== true) {
38
- const loosens = loosenReasons(prior.rules, clean);
39
- if (loosens.length > 0) {
40
- throw new SessionPolicyError("loosen_forbidden", `a non-operator session-rule write may only tighten; refused: ${loosens.join("; ")}`);
41
- }
42
- }
43
- const nextRev = priorRev + 1;
44
- if (pgHasUnstorable(clean)) {
45
- throw new SessionPolicyError("loosen_forbidden", "rules contain unstorable bytes (NUL / lone surrogate) — refused; strip them at the source (authorization rules are never rewritten by the store)");
46
- }
47
- const json = JSON.stringify(clean);
48
- const now = new Date();
49
- if (prior) {
50
- const u = await c.query("UPDATE session_policy SET principal = $1, rules = $2::jsonb, rev = $3, updated_at = $4 WHERE policy_key = $5 AND rev = $6", [principal ?? null, json, nextRev, now, key, priorRev]);
51
- if ((u.rowCount ?? 0) !== 1)
52
- throw new SessionPolicyError("conflict", `session rules rev mismatch (expected ${opts?.expectedRev ?? priorRev}, have ${priorRev})`);
53
- }
54
- else {
55
- await c.query("INSERT INTO session_policy (policy_key, session_id, principal, rules, rev, created_at, updated_at) VALUES ($1,$2,$3,$4::jsonb,$5,$6,$7)", [key, sessionId, principal ?? null, json, nextRev, now, now]);
56
- }
57
- return structuredClone({ ...clean, rev: nextRev });
58
- });
59
- }
60
- catch (e) {
61
- if (isDupKey(e) && attempt < 4)
62
- continue;
63
- throw e;
64
- }
65
- }
66
- }
67
- async deleteBySession(sessionId) {
68
- await this.pool.query("DELETE FROM session_policy WHERE session_id = $1", [sessionId]);
69
- }
70
- async tx(fn) {
71
- const c = await this.pool.connect();
72
- try {
73
- await c.query("BEGIN");
74
- const out = await fn(c);
75
- await c.query("COMMIT");
76
- return out;
77
- }
78
- catch (err) {
79
- try {
80
- await c.query("ROLLBACK");
81
- }
82
- catch {
83
- }
84
- throw err;
85
- }
86
- finally {
87
- c.release();
88
- }
89
- }
90
- }
1
+ export { PgSessionPolicyStore } from "./session-policy-store-sql.js";
91
2
  //# sourceMappingURL=pg-session-policy-store.js.map
@@ -1,16 +1,2 @@
1
- import type { ToolResultStore, ToolResultSlice } from "@sema-agent/core";
2
- import type { Pool, PoolClient } from "pg";
3
- export declare const PG_TOOL_RESULT_SCHEMA: string[];
4
- export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
5
- export declare class PgToolResultStore implements ToolResultStore {
6
- private readonly pool;
7
- constructor(pool: Pool);
8
- put(ref: string, content: string): Promise<void>;
9
- get(ref: string, opts?: {
10
- offset?: number;
11
- limit?: number;
12
- }): Promise<ToolResultSlice | undefined>;
13
- reapOlderThan(cutoffMs: number): Promise<number>;
14
- deleteBySession(sessionId: string): Promise<number>;
15
- }
1
+ export { PgToolResultStore, PG_TOOL_RESULT_SCHEMA, ensureSchema } from "./tool-result-store-sql.js";
16
2
  //# sourceMappingURL=pg-tool-result-store.d.ts.map