@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.
- package/LICENSE +202 -0
- package/README.md +76 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +7 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +58 -0
- package/dist/migrations.js.map +1 -0
- package/dist/pool.d.ts +8 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +9 -0
- package/dist/pool.js.map +1 -0
- package/dist/repository.d.ts +28 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +830 -0
- package/dist/repository.js.map +1 -0
- package/migrations/001_agent_rooms.down.sql +25 -0
- package/migrations/001_agent_rooms.up.sql +360 -0
- package/package.json +46 -0
|
@@ -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
|