@agentplat/rooms-postgres 0.2.0-beta.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.
@@ -0,0 +1,830 @@
1
+ import { AgentPlatError } from '@agentplat/core';
2
+ function iso(value) {
3
+ if (value instanceof Date)
4
+ return value.toISOString();
5
+ return String(value);
6
+ }
7
+ function optionalIso(value) {
8
+ return value === null || value === undefined ? undefined : iso(value);
9
+ }
10
+ function optionalString(value) {
11
+ return value === null || value === undefined ? undefined : String(value);
12
+ }
13
+ function json(value) {
14
+ return JSON.stringify(value);
15
+ }
16
+ function metadata(row) {
17
+ const value = row.metadata;
18
+ return value && Object.keys(value).length > 0 ? value : undefined;
19
+ }
20
+ function mapRoom(row) {
21
+ return {
22
+ tenantId: String(row.tenant_id),
23
+ id: String(row.id),
24
+ parentRoomId: optionalString(row.parent_room_id),
25
+ title: String(row.title),
26
+ goal: String(row.goal),
27
+ status: row.status,
28
+ metadata: metadata(row),
29
+ createdBy: optionalString(row.created_by),
30
+ createdAt: iso(row.created_at),
31
+ updatedAt: iso(row.updated_at),
32
+ completedAt: optionalIso(row.completed_at),
33
+ archivedAt: optionalIso(row.archived_at),
34
+ };
35
+ }
36
+ function mapParticipant(row) {
37
+ return {
38
+ tenantId: String(row.tenant_id),
39
+ id: String(row.id),
40
+ type: row.type,
41
+ displayName: String(row.display_name),
42
+ role: String(row.role),
43
+ authorityLevel: Number(row.authority_level),
44
+ permissions: row.permissions,
45
+ boundaries: row.boundaries,
46
+ memoryScope: row.memory_scope,
47
+ runtime: row.runtime,
48
+ metadata: metadata(row),
49
+ createdAt: iso(row.created_at),
50
+ updatedAt: iso(row.updated_at),
51
+ };
52
+ }
53
+ function mapMessage(row) {
54
+ return {
55
+ tenantId: String(row.tenant_id),
56
+ id: String(row.id),
57
+ roomId: String(row.room_id),
58
+ authorParticipantId: optionalString(row.author_participant_id),
59
+ role: row.role,
60
+ content: String(row.content),
61
+ metadata: metadata(row),
62
+ createdAt: iso(row.created_at),
63
+ };
64
+ }
65
+ function mapTask(row) {
66
+ return {
67
+ tenantId: String(row.tenant_id),
68
+ id: String(row.id),
69
+ roomId: String(row.room_id),
70
+ stepId: String(row.step_id),
71
+ assignedParticipantId: optionalString(row.assigned_participant_id),
72
+ assignedRole: optionalString(row.assigned_role),
73
+ instruction: String(row.instruction),
74
+ expectedOutput: String(row.expected_output),
75
+ expectedArtifactKind: String(row.expected_artifact_kind),
76
+ dependencies: row.dependencies,
77
+ acceptanceCriteria: row.acceptance_criteria,
78
+ actionLevel: row.action_level,
79
+ approvalRequired: Boolean(row.approval_required),
80
+ toolIds: row.tool_ids,
81
+ status: row.status,
82
+ errorMessage: optionalString(row.error_message),
83
+ metadata: metadata(row),
84
+ createdAt: iso(row.created_at),
85
+ updatedAt: iso(row.updated_at),
86
+ completedAt: optionalIso(row.completed_at),
87
+ };
88
+ }
89
+ function mapArtifact(row) {
90
+ return {
91
+ tenantId: String(row.tenant_id),
92
+ id: String(row.id),
93
+ roomId: String(row.room_id),
94
+ type: String(row.type),
95
+ title: String(row.title),
96
+ status: row.status,
97
+ currentVersion: Number(row.current_version),
98
+ authors: row.authors,
99
+ provenance: row.provenance,
100
+ assumptions: row.assumptions,
101
+ risks: row.risks,
102
+ metadata: metadata(row),
103
+ createdAt: iso(row.created_at),
104
+ updatedAt: iso(row.updated_at),
105
+ };
106
+ }
107
+ function mapArtifactVersion(row) {
108
+ return {
109
+ tenantId: String(row.tenant_id),
110
+ id: String(row.id),
111
+ artifactId: String(row.artifact_id),
112
+ version: Number(row.version),
113
+ content: row.content,
114
+ contentType: String(row.content_type),
115
+ createdBy: optionalString(row.created_by),
116
+ createdAt: iso(row.created_at),
117
+ };
118
+ }
119
+ function mapApproval(row) {
120
+ return {
121
+ tenantId: String(row.tenant_id),
122
+ id: String(row.id),
123
+ roomId: String(row.room_id),
124
+ targetType: row.target_type,
125
+ targetId: String(row.target_id),
126
+ targetVersion: row.target_version === null || row.target_version === undefined
127
+ ? undefined
128
+ : Number(row.target_version),
129
+ action: optionalString(row.action),
130
+ status: row.status,
131
+ requestedBy: optionalString(row.requested_by),
132
+ decidedBy: optionalString(row.decided_by),
133
+ comment: optionalString(row.comment),
134
+ createdAt: iso(row.created_at),
135
+ updatedAt: iso(row.updated_at),
136
+ decidedAt: optionalIso(row.decided_at),
137
+ };
138
+ }
139
+ function mapPolicy(row) {
140
+ return {
141
+ tenantId: String(row.tenant_id),
142
+ id: String(row.id),
143
+ roomId: String(row.room_id),
144
+ name: String(row.name),
145
+ allowedActions: row.allowed_actions,
146
+ deniedActions: row.denied_actions,
147
+ requiredApprovals: row.required_approvals,
148
+ escalationRules: row.escalation_rules,
149
+ toolPermissions: row.tool_permissions,
150
+ memoryAccessRules: row.memory_access_rules,
151
+ createdAt: iso(row.created_at),
152
+ updatedAt: iso(row.updated_at),
153
+ };
154
+ }
155
+ function mapMemory(row) {
156
+ return {
157
+ tenantId: String(row.tenant_id),
158
+ id: String(row.id),
159
+ roomId: optionalString(row.room_id),
160
+ scope: row.scope,
161
+ scopeId: optionalString(row.scope_id),
162
+ content: row.content,
163
+ source: String(row.source),
164
+ confidence: Number(row.confidence),
165
+ retention: row.retention,
166
+ retainUntil: optionalIso(row.retain_until),
167
+ provenance: row.provenance,
168
+ createdAt: iso(row.created_at),
169
+ };
170
+ }
171
+ function mapContextSnapshot(row) {
172
+ return {
173
+ tenantId: String(row.tenant_id),
174
+ id: String(row.id),
175
+ roomId: String(row.room_id),
176
+ taskId: String(row.task_id),
177
+ runId: String(row.run_id),
178
+ context: row.context,
179
+ createdAt: iso(row.created_at),
180
+ };
181
+ }
182
+ function mapRun(row) {
183
+ return {
184
+ tenantId: String(row.tenant_id),
185
+ id: String(row.id),
186
+ roomId: String(row.room_id),
187
+ taskId: String(row.task_id),
188
+ participantId: String(row.participant_id),
189
+ runtime: String(row.runtime),
190
+ status: row.status,
191
+ output: optionalString(row.output),
192
+ errorMessage: optionalString(row.error_message),
193
+ tokenUsage: row.token_usage,
194
+ latencyMs: row.latency_ms === null || row.latency_ms === undefined
195
+ ? undefined
196
+ : Number(row.latency_ms),
197
+ startedAt: iso(row.started_at),
198
+ leaseExpiresAt: iso(row.lease_expires_at),
199
+ completedAt: optionalIso(row.completed_at),
200
+ };
201
+ }
202
+ function mapToolCall(row) {
203
+ return {
204
+ tenantId: String(row.tenant_id),
205
+ id: String(row.id),
206
+ roomId: String(row.room_id),
207
+ runId: String(row.run_id),
208
+ toolId: String(row.tool_id),
209
+ input: row.input,
210
+ output: row.output,
211
+ status: row.status,
212
+ latencyMs: row.latency_ms === null || row.latency_ms === undefined
213
+ ? undefined
214
+ : Number(row.latency_ms),
215
+ createdAt: iso(row.created_at),
216
+ completedAt: optionalIso(row.completed_at),
217
+ };
218
+ }
219
+ function mapEvent(row) {
220
+ return {
221
+ tenantId: String(row.tenant_id),
222
+ id: String(row.id),
223
+ roomId: String(row.room_id),
224
+ type: row.type,
225
+ source: String(row.source),
226
+ subject: row.subject,
227
+ payload: row.payload,
228
+ metadata: metadata(row),
229
+ occurredAt: iso(row.occurred_at),
230
+ actorId: optionalString(row.actor_id),
231
+ };
232
+ }
233
+ function translatePostgresError(error) {
234
+ if (error instanceof AgentPlatError)
235
+ return error;
236
+ const code = typeof error === 'object' && error !== null && 'code' in error
237
+ ? String(error.code)
238
+ : undefined;
239
+ if (code === '23505') {
240
+ return new AgentPlatError('CONFLICT', 'The resource already exists', {
241
+ statusCode: 409,
242
+ details: error,
243
+ });
244
+ }
245
+ if (code === '23502' ||
246
+ code === '23503' ||
247
+ code === '23514' ||
248
+ code === '22P02') {
249
+ return new AgentPlatError('VALIDATION_ERROR', 'The persisted room data is invalid', {
250
+ statusCode: 400,
251
+ details: error,
252
+ });
253
+ }
254
+ return error;
255
+ }
256
+ class PostgresRoomReader {
257
+ database;
258
+ transactionTenantId;
259
+ lockRows;
260
+ constructor(database, transactionTenantId, lockRows = false) {
261
+ this.database = database;
262
+ this.transactionTenantId = transactionTenantId;
263
+ this.lockRows = lockRows;
264
+ }
265
+ get rowLock() {
266
+ return this.lockRows ? ' FOR UPDATE' : '';
267
+ }
268
+ assertTenant(tenantId) {
269
+ if (this.transactionTenantId !== undefined &&
270
+ tenantId !== this.transactionTenantId) {
271
+ throw new AgentPlatError('FORBIDDEN', 'A room transaction cannot access another tenant', { statusCode: 403 });
272
+ }
273
+ }
274
+ async getRoom(tenantId, roomId) {
275
+ this.assertTenant(tenantId);
276
+ const result = await this.database.query(`SELECT * FROM public.rooms WHERE tenant_id = $1 AND id = $2${this.rowLock}`, [tenantId, roomId]);
277
+ return result.rows[0] ? mapRoom(result.rows[0]) : undefined;
278
+ }
279
+ async listRooms(tenantId) {
280
+ this.assertTenant(tenantId);
281
+ const result = await this.database.query('SELECT * FROM public.rooms WHERE tenant_id = $1 ORDER BY created_at DESC, id', [tenantId]);
282
+ return result.rows.map(mapRoom);
283
+ }
284
+ async getParticipant(tenantId, participantId) {
285
+ this.assertTenant(tenantId);
286
+ const result = await this.database.query(`SELECT * FROM public.participants WHERE tenant_id = $1 AND id = $2${this.rowLock}`, [tenantId, participantId]);
287
+ return result.rows[0] ? mapParticipant(result.rows[0]) : undefined;
288
+ }
289
+ async getTask(tenantId, taskId) {
290
+ this.assertTenant(tenantId);
291
+ const result = await this.database.query(`SELECT * FROM public.tasks WHERE tenant_id = $1 AND id = $2${this.rowLock}`, [tenantId, taskId]);
292
+ return result.rows[0] ? mapTask(result.rows[0]) : undefined;
293
+ }
294
+ async getArtifact(tenantId, artifactId) {
295
+ this.assertTenant(tenantId);
296
+ const result = await this.database.query(`SELECT * FROM public.artifacts WHERE tenant_id = $1 AND id = $2${this.rowLock}`, [tenantId, artifactId]);
297
+ return result.rows[0] ? mapArtifact(result.rows[0]) : undefined;
298
+ }
299
+ async getApproval(tenantId, approvalId) {
300
+ this.assertTenant(tenantId);
301
+ const result = await this.database.query(`SELECT * FROM public.approvals WHERE tenant_id = $1 AND id = $2${this.rowLock}`, [tenantId, approvalId]);
302
+ return result.rows[0] ? mapApproval(result.rows[0]) : undefined;
303
+ }
304
+ async listEvents(tenantId, roomId) {
305
+ this.assertTenant(tenantId);
306
+ const result = await this.database.query(`SELECT * FROM public.events
307
+ WHERE tenant_id = $1 AND room_id = $2
308
+ ORDER BY sequence`, [tenantId, roomId]);
309
+ return result.rows.map(mapEvent);
310
+ }
311
+ async getRoomState(tenantId, roomId) {
312
+ this.assertTenant(tenantId);
313
+ const room = await this.getRoom(tenantId, roomId);
314
+ if (!room)
315
+ return undefined;
316
+ const participants = await this.database.query(`SELECT participant.*
317
+ FROM public.room_participants link
318
+ JOIN public.participants participant
319
+ ON participant.tenant_id = link.tenant_id
320
+ AND participant.id = link.participant_id
321
+ WHERE link.tenant_id = $1 AND link.room_id = $2
322
+ ORDER BY link.joined_at, participant.id`, [tenantId, roomId]);
323
+ const messages = await this.database.query(`SELECT * FROM public.messages
324
+ WHERE tenant_id = $1 AND room_id = $2
325
+ ORDER BY created_at, id`, [tenantId, roomId]);
326
+ const tasks = await this.database.query(`SELECT * FROM public.tasks
327
+ WHERE tenant_id = $1 AND room_id = $2
328
+ ORDER BY created_at, id`, [tenantId, roomId]);
329
+ const artifacts = await this.database.query(`SELECT * FROM public.artifacts
330
+ WHERE tenant_id = $1 AND room_id = $2
331
+ ORDER BY created_at, id`, [tenantId, roomId]);
332
+ const versions = await this.database.query(`SELECT version.*
333
+ FROM public.artifact_versions version
334
+ JOIN public.artifacts artifact
335
+ ON artifact.tenant_id = version.tenant_id
336
+ AND artifact.id = version.artifact_id
337
+ WHERE artifact.tenant_id = $1 AND artifact.room_id = $2
338
+ ORDER BY artifact.created_at, artifact.id, version.version`, [tenantId, roomId]);
339
+ const approvals = await this.database.query(`SELECT * FROM public.approvals
340
+ WHERE tenant_id = $1 AND room_id = $2
341
+ ORDER BY created_at, id`, [tenantId, roomId]);
342
+ const policies = await this.database.query(`SELECT * FROM public.policies
343
+ WHERE tenant_id = $1 AND room_id = $2
344
+ ORDER BY created_at, id`, [tenantId, roomId]);
345
+ const memory = await this.database.query(`SELECT * FROM public.memory_entries
346
+ WHERE tenant_id = $1
347
+ AND (room_id = $2 OR (scope = 'organization' AND scope_id = $1))
348
+ ORDER BY created_at, id`, [tenantId, roomId]);
349
+ const snapshots = await this.database.query(`SELECT * FROM public.context_snapshots
350
+ WHERE tenant_id = $1 AND room_id = $2
351
+ ORDER BY created_at, id`, [tenantId, roomId]);
352
+ const runs = await this.database.query(`SELECT * FROM public.runs
353
+ WHERE tenant_id = $1 AND room_id = $2
354
+ ORDER BY started_at, id`, [tenantId, roomId]);
355
+ const toolCalls = await this.database.query(`SELECT * FROM public.tool_calls
356
+ WHERE tenant_id = $1 AND room_id = $2
357
+ ORDER BY created_at, id`, [tenantId, roomId]);
358
+ const childRooms = await this.database.query(`SELECT * FROM public.rooms
359
+ WHERE tenant_id = $1 AND parent_room_id = $2
360
+ ORDER BY created_at, id`, [tenantId, roomId]);
361
+ const roomEvents = await this.listEvents(tenantId, roomId);
362
+ const mappedVersions = versions.rows.map(mapArtifactVersion);
363
+ return {
364
+ room,
365
+ participants: participants.rows.map(mapParticipant),
366
+ messages: messages.rows.map(mapMessage),
367
+ tasks: tasks.rows.map(mapTask),
368
+ artifacts: artifacts.rows.map((row) => {
369
+ const artifact = mapArtifact(row);
370
+ return {
371
+ ...artifact,
372
+ versions: mappedVersions.filter((version) => version.artifactId === artifact.id),
373
+ };
374
+ }),
375
+ approvals: approvals.rows.map(mapApproval),
376
+ policies: policies.rows.map(mapPolicy),
377
+ memory: memory.rows.map(mapMemory),
378
+ contextSnapshots: snapshots.rows.map(mapContextSnapshot),
379
+ runs: runs.rows.map(mapRun),
380
+ toolCalls: toolCalls.rows.map(mapToolCall),
381
+ events: roomEvents,
382
+ childRooms: childRooms.rows.map(mapRoom),
383
+ };
384
+ }
385
+ }
386
+ class PostgresRoomTransaction extends PostgresRoomReader {
387
+ tenantId;
388
+ constructor(client, tenantId) {
389
+ super(client, tenantId, true);
390
+ this.tenantId = tenantId;
391
+ }
392
+ assertEntityTenant(entity) {
393
+ this.assertTenant(entity.tenantId);
394
+ }
395
+ async assertUpdated(result, label, id) {
396
+ if ((result.rowCount ?? 0) === 0) {
397
+ throw new AgentPlatError('NOT_FOUND', `${label} "${id}" was not found`, {
398
+ statusCode: 404,
399
+ });
400
+ }
401
+ }
402
+ async insertRoom(room) {
403
+ this.assertEntityTenant(room);
404
+ await this.database.query(`INSERT INTO public.rooms (
405
+ tenant_id, id, parent_room_id, title, goal, status, metadata,
406
+ created_by, created_at, updated_at, completed_at, archived_at
407
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,$11,$12)`, [
408
+ room.tenantId,
409
+ room.id,
410
+ room.parentRoomId ?? null,
411
+ room.title,
412
+ room.goal,
413
+ room.status,
414
+ json(room.metadata ?? {}),
415
+ room.createdBy ?? null,
416
+ room.createdAt,
417
+ room.updatedAt,
418
+ room.completedAt ?? null,
419
+ room.archivedAt ?? null,
420
+ ]);
421
+ }
422
+ async updateRoom(room) {
423
+ this.assertEntityTenant(room);
424
+ const result = await this.database.query(`UPDATE public.rooms SET
425
+ parent_room_id = $3, title = $4, goal = $5, status = $6,
426
+ metadata = $7::jsonb, created_by = $8, created_at = $9,
427
+ updated_at = $10, completed_at = $11, archived_at = $12
428
+ WHERE tenant_id = $1 AND id = $2`, [
429
+ room.tenantId,
430
+ room.id,
431
+ room.parentRoomId ?? null,
432
+ room.title,
433
+ room.goal,
434
+ room.status,
435
+ json(room.metadata ?? {}),
436
+ room.createdBy ?? null,
437
+ room.createdAt,
438
+ room.updatedAt,
439
+ room.completedAt ?? null,
440
+ room.archivedAt ?? null,
441
+ ]);
442
+ await this.assertUpdated(result, 'Room', room.id);
443
+ }
444
+ async insertParticipant(participant) {
445
+ this.assertEntityTenant(participant);
446
+ await this.database.query(`INSERT INTO public.participants (
447
+ tenant_id, id, type, display_name, role, authority_level,
448
+ permissions, boundaries, memory_scope, runtime, metadata,
449
+ created_at, updated_at
450
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb,$9,$10::jsonb,$11::jsonb,$12,$13)`, [
451
+ participant.tenantId,
452
+ participant.id,
453
+ participant.type,
454
+ participant.displayName,
455
+ participant.role,
456
+ participant.authorityLevel,
457
+ json(participant.permissions),
458
+ json(participant.boundaries),
459
+ participant.memoryScope ?? null,
460
+ participant.runtime ? json(participant.runtime) : null,
461
+ json(participant.metadata ?? {}),
462
+ participant.createdAt,
463
+ participant.updatedAt,
464
+ ]);
465
+ }
466
+ async addRoomParticipant(link) {
467
+ this.assertEntityTenant(link);
468
+ await this.database.query(`INSERT INTO public.room_participants (
469
+ tenant_id, room_id, participant_id, joined_at
470
+ ) VALUES ($1,$2,$3,$4)`, [link.tenantId, link.roomId, link.participantId, link.joinedAt]);
471
+ }
472
+ async insertMessage(message) {
473
+ this.assertEntityTenant(message);
474
+ await this.database.query(`INSERT INTO public.messages (
475
+ tenant_id, id, room_id, author_participant_id, role,
476
+ content, metadata, created_at
477
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8)`, [
478
+ message.tenantId,
479
+ message.id,
480
+ message.roomId,
481
+ message.authorParticipantId ?? null,
482
+ message.role,
483
+ message.content,
484
+ json(message.metadata ?? {}),
485
+ message.createdAt,
486
+ ]);
487
+ }
488
+ async insertTask(task) {
489
+ this.assertEntityTenant(task);
490
+ await this.database.query(`INSERT INTO public.tasks (
491
+ tenant_id, id, room_id, step_id, assigned_participant_id,
492
+ assigned_role, instruction, expected_output, expected_artifact_kind,
493
+ dependencies, acceptance_criteria, action_level, approval_required,
494
+ tool_ids, status, error_message, metadata, created_at, updated_at,
495
+ completed_at
496
+ ) VALUES (
497
+ $1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11::jsonb,$12,$13,
498
+ $14::jsonb,$15,$16,$17::jsonb,$18,$19,$20
499
+ )`, this.taskValues(task));
500
+ }
501
+ async updateTask(task) {
502
+ this.assertEntityTenant(task);
503
+ const values = this.taskValues(task);
504
+ const result = await this.database.query(`UPDATE public.tasks SET
505
+ room_id = $3, step_id = $4, assigned_participant_id = $5,
506
+ assigned_role = $6, instruction = $7, expected_output = $8,
507
+ expected_artifact_kind = $9, dependencies = $10::jsonb,
508
+ acceptance_criteria = $11::jsonb, action_level = $12,
509
+ approval_required = $13, tool_ids = $14::jsonb, status = $15,
510
+ error_message = $16, metadata = $17::jsonb, created_at = $18,
511
+ updated_at = $19, completed_at = $20
512
+ WHERE tenant_id = $1 AND id = $2`, values);
513
+ await this.assertUpdated(result, 'Task', task.id);
514
+ }
515
+ taskValues(task) {
516
+ return [
517
+ task.tenantId,
518
+ task.id,
519
+ task.roomId,
520
+ task.stepId,
521
+ task.assignedParticipantId ?? null,
522
+ task.assignedRole ?? null,
523
+ task.instruction,
524
+ task.expectedOutput,
525
+ task.expectedArtifactKind,
526
+ json(task.dependencies),
527
+ json(task.acceptanceCriteria),
528
+ task.actionLevel,
529
+ task.approvalRequired,
530
+ json(task.toolIds),
531
+ task.status,
532
+ task.errorMessage ?? null,
533
+ json(task.metadata ?? {}),
534
+ task.createdAt,
535
+ task.updatedAt,
536
+ task.completedAt ?? null,
537
+ ];
538
+ }
539
+ async insertArtifact(artifact, version) {
540
+ this.assertEntityTenant(artifact);
541
+ this.assertEntityTenant(version);
542
+ if (version.artifactId !== artifact.id ||
543
+ version.version !== artifact.currentVersion) {
544
+ throw new AgentPlatError('VALIDATION_ERROR', 'The initial artifact version must match the artifact current version', { statusCode: 400 });
545
+ }
546
+ await this.database.query(`INSERT INTO public.artifacts (
547
+ tenant_id, id, room_id, type, title, status, current_version,
548
+ authors, provenance, assumptions, risks, metadata, created_at, updated_at
549
+ ) VALUES (
550
+ $1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9::jsonb,$10::jsonb,
551
+ $11::jsonb,$12::jsonb,$13,$14
552
+ )`, this.artifactValues(artifact));
553
+ await this.insertArtifactVersion(version);
554
+ }
555
+ async updateArtifact(artifact) {
556
+ this.assertEntityTenant(artifact);
557
+ const values = this.artifactValues(artifact);
558
+ const result = await this.database.query(`UPDATE public.artifacts SET
559
+ room_id = $3, type = $4, title = $5, status = $6,
560
+ current_version = $7, authors = $8::jsonb, provenance = $9::jsonb,
561
+ assumptions = $10::jsonb, risks = $11::jsonb,
562
+ metadata = $12::jsonb, created_at = $13, updated_at = $14
563
+ WHERE tenant_id = $1 AND id = $2`, values);
564
+ await this.assertUpdated(result, 'Artifact', artifact.id);
565
+ }
566
+ artifactValues(artifact) {
567
+ return [
568
+ artifact.tenantId,
569
+ artifact.id,
570
+ artifact.roomId,
571
+ artifact.type,
572
+ artifact.title,
573
+ artifact.status,
574
+ artifact.currentVersion,
575
+ json(artifact.authors),
576
+ json(artifact.provenance),
577
+ json(artifact.assumptions),
578
+ json(artifact.risks),
579
+ json(artifact.metadata ?? {}),
580
+ artifact.createdAt,
581
+ artifact.updatedAt,
582
+ ];
583
+ }
584
+ async insertArtifactVersion(version) {
585
+ this.assertEntityTenant(version);
586
+ await this.database.query(`INSERT INTO public.artifact_versions (
587
+ tenant_id, id, artifact_id, version, content, content_type,
588
+ created_by, created_at
589
+ ) VALUES ($1,$2,$3,$4,$5::jsonb,$6,$7,$8)`, [
590
+ version.tenantId,
591
+ version.id,
592
+ version.artifactId,
593
+ version.version,
594
+ json(version.content),
595
+ version.contentType,
596
+ version.createdBy ?? null,
597
+ version.createdAt,
598
+ ]);
599
+ }
600
+ async insertApproval(approval) {
601
+ this.assertEntityTenant(approval);
602
+ await this.database.query(`INSERT INTO public.approvals (
603
+ tenant_id, id, room_id, target_type, target_id, target_version,
604
+ action, status, requested_by, decided_by, comment, created_at,
605
+ updated_at, decided_at
606
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)`, this.approvalValues(approval));
607
+ }
608
+ async updateApproval(approval) {
609
+ this.assertEntityTenant(approval);
610
+ const values = this.approvalValues(approval);
611
+ const result = await this.database.query(`UPDATE public.approvals SET
612
+ room_id = $3, target_type = $4, target_id = $5,
613
+ target_version = $6, action = $7, status = $8,
614
+ requested_by = $9, decided_by = $10, comment = $11,
615
+ created_at = $12, updated_at = $13, decided_at = $14
616
+ WHERE tenant_id = $1 AND id = $2`, values);
617
+ await this.assertUpdated(result, 'Approval', approval.id);
618
+ }
619
+ approvalValues(approval) {
620
+ return [
621
+ approval.tenantId,
622
+ approval.id,
623
+ approval.roomId,
624
+ approval.targetType,
625
+ approval.targetId,
626
+ approval.targetVersion ?? null,
627
+ approval.action ?? null,
628
+ approval.status,
629
+ approval.requestedBy ?? null,
630
+ approval.decidedBy ?? null,
631
+ approval.comment ?? null,
632
+ approval.createdAt,
633
+ approval.updatedAt,
634
+ approval.decidedAt ?? null,
635
+ ];
636
+ }
637
+ async insertPolicy(policy) {
638
+ this.assertEntityTenant(policy);
639
+ await this.database.query(`INSERT INTO public.policies (
640
+ tenant_id, id, room_id, name, allowed_actions, denied_actions,
641
+ required_approvals, escalation_rules, tool_permissions,
642
+ memory_access_rules, created_at, updated_at
643
+ ) VALUES (
644
+ $1,$2,$3,$4,$5::jsonb,$6::jsonb,$7::jsonb,$8::jsonb,
645
+ $9::jsonb,$10::jsonb,$11,$12
646
+ )`, [
647
+ policy.tenantId,
648
+ policy.id,
649
+ policy.roomId,
650
+ policy.name,
651
+ json(policy.allowedActions),
652
+ json(policy.deniedActions),
653
+ json(policy.requiredApprovals),
654
+ json(policy.escalationRules),
655
+ json(policy.toolPermissions),
656
+ json(policy.memoryAccessRules),
657
+ policy.createdAt,
658
+ policy.updatedAt,
659
+ ]);
660
+ }
661
+ async insertMemory(entry) {
662
+ this.assertEntityTenant(entry);
663
+ await this.database.query(`INSERT INTO public.memory_entries (
664
+ tenant_id, id, room_id, scope, scope_id, content, source,
665
+ confidence, retention, retain_until, provenance, created_at
666
+ ) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10,$11::jsonb,$12)`, [
667
+ entry.tenantId,
668
+ entry.id,
669
+ entry.roomId ?? null,
670
+ entry.scope,
671
+ entry.scopeId ?? null,
672
+ json(entry.content),
673
+ entry.source,
674
+ entry.confidence,
675
+ entry.retention,
676
+ entry.retainUntil ?? null,
677
+ json(entry.provenance),
678
+ entry.createdAt,
679
+ ]);
680
+ }
681
+ async insertContextSnapshot(snapshot) {
682
+ this.assertEntityTenant(snapshot);
683
+ await this.database.query(`INSERT INTO public.context_snapshots (
684
+ tenant_id, id, room_id, task_id, run_id, context, created_at
685
+ ) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)`, [
686
+ snapshot.tenantId,
687
+ snapshot.id,
688
+ snapshot.roomId,
689
+ snapshot.taskId,
690
+ snapshot.runId,
691
+ json(snapshot.context),
692
+ snapshot.createdAt,
693
+ ]);
694
+ }
695
+ async insertRun(run) {
696
+ this.assertEntityTenant(run);
697
+ await this.database.query(`INSERT INTO public.runs (
698
+ tenant_id, id, room_id, task_id, participant_id, runtime, status,
699
+ output, error_message, token_usage, latency_ms, started_at,
700
+ lease_expires_at, completed_at
701
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11,$12,$13,$14)`, this.runValues(run));
702
+ }
703
+ async updateRun(run) {
704
+ this.assertEntityTenant(run);
705
+ const values = this.runValues(run);
706
+ const result = await this.database.query(`UPDATE public.runs SET
707
+ room_id = $3, task_id = $4, participant_id = $5, runtime = $6,
708
+ status = $7, output = $8, error_message = $9,
709
+ token_usage = $10::jsonb, latency_ms = $11, started_at = $12,
710
+ lease_expires_at = $13, completed_at = $14
711
+ WHERE tenant_id = $1 AND id = $2`, values);
712
+ await this.assertUpdated(result, 'Run', run.id);
713
+ }
714
+ runValues(run) {
715
+ return [
716
+ run.tenantId,
717
+ run.id,
718
+ run.roomId,
719
+ run.taskId,
720
+ run.participantId,
721
+ run.runtime,
722
+ run.status,
723
+ run.output ?? null,
724
+ run.errorMessage ?? null,
725
+ run.tokenUsage ? json(run.tokenUsage) : null,
726
+ run.latencyMs ?? null,
727
+ run.startedAt,
728
+ run.leaseExpiresAt,
729
+ run.completedAt ?? null,
730
+ ];
731
+ }
732
+ async insertToolCall(call) {
733
+ this.assertEntityTenant(call);
734
+ await this.database.query(`INSERT INTO public.tool_calls (
735
+ tenant_id, id, room_id, run_id, tool_id, input, output,
736
+ status, latency_ms, created_at, completed_at
737
+ ) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7::jsonb,$8,$9,$10,$11)`, this.toolCallValues(call));
738
+ }
739
+ async updateToolCall(call) {
740
+ this.assertEntityTenant(call);
741
+ const values = this.toolCallValues(call);
742
+ const result = await this.database.query(`UPDATE public.tool_calls SET
743
+ room_id = $3, run_id = $4, tool_id = $5, input = $6::jsonb,
744
+ output = $7::jsonb, status = $8, latency_ms = $9,
745
+ created_at = $10, completed_at = $11
746
+ WHERE tenant_id = $1 AND id = $2`, values);
747
+ await this.assertUpdated(result, 'Tool call', call.id);
748
+ }
749
+ toolCallValues(call) {
750
+ return [
751
+ call.tenantId,
752
+ call.id,
753
+ call.roomId,
754
+ call.runId,
755
+ call.toolId,
756
+ json(call.input),
757
+ call.output === undefined ? null : json(call.output),
758
+ call.status,
759
+ call.latencyMs ?? null,
760
+ call.createdAt,
761
+ call.completedAt ?? null,
762
+ ];
763
+ }
764
+ async appendEvent(event) {
765
+ this.assertEntityTenant(event);
766
+ if (event.roomId.length === 0) {
767
+ throw new AgentPlatError('VALIDATION_ERROR', 'Event roomId is required', {
768
+ statusCode: 400,
769
+ });
770
+ }
771
+ await this.database.query(`INSERT INTO public.events (
772
+ tenant_id, id, room_id, type, source, subject, payload,
773
+ metadata, occurred_at, actor_id
774
+ ) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7::jsonb,$8::jsonb,$9,$10)`, [
775
+ event.tenantId,
776
+ event.id,
777
+ event.roomId,
778
+ event.type,
779
+ event.source,
780
+ event.subject ? json(event.subject) : null,
781
+ json(event.payload),
782
+ json(event.metadata ?? {}),
783
+ event.occurredAt,
784
+ event.actorId ?? null,
785
+ ]);
786
+ }
787
+ }
788
+ export class PostgresRoomRepository extends PostgresRoomReader {
789
+ pool;
790
+ constructor(pool) {
791
+ super(pool);
792
+ this.pool = pool;
793
+ }
794
+ async getRoomState(tenantId, roomId) {
795
+ const client = await this.pool.connect();
796
+ try {
797
+ await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
798
+ const state = await new PostgresRoomReader(client, tenantId).getRoomState(tenantId, roomId);
799
+ await client.query('COMMIT');
800
+ return state;
801
+ }
802
+ catch (error) {
803
+ await client.query('ROLLBACK').catch(() => undefined);
804
+ throw error;
805
+ }
806
+ finally {
807
+ client.release();
808
+ }
809
+ }
810
+ async transaction(tenantId, work) {
811
+ if (!tenantId.trim()) {
812
+ throw new AgentPlatError('VALIDATION_ERROR', 'tenantId is required');
813
+ }
814
+ const client = await this.pool.connect();
815
+ try {
816
+ await client.query('BEGIN');
817
+ const result = await work(new PostgresRoomTransaction(client, tenantId));
818
+ await client.query('COMMIT');
819
+ return result;
820
+ }
821
+ catch (error) {
822
+ await client.query('ROLLBACK').catch(() => undefined);
823
+ throw translatePostgresError(error);
824
+ }
825
+ finally {
826
+ client.release();
827
+ }
828
+ }
829
+ }
830
+ //# sourceMappingURL=repository.js.map