@axiom-lattice/pg-stores 3.1.1 → 3.1.2
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +9 -0
- package/dist/index.d.mts +195 -15
- package/dist/index.d.ts +195 -15
- package/dist/index.js +1309 -199
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1295 -191
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/ChannelBindingStore.test.ts +122 -0
- package/src/__tests__/PostgreSQLChannelInstallationStore.test.ts +16 -0
- package/src/__tests__/PostgreSQLProjectBotMembershipStore.test.ts +117 -0
- package/src/__tests__/PostgreSQLProjectMembershipStore.test.ts +235 -0
- package/src/__tests__/PostgreSQLProjectRoomMessageStore.test.ts +103 -0
- package/src/__tests__/PostgreSQLProjectRoomStore.migrations.test.ts +114 -0
- package/src/__tests__/PostgreSQLProjectRoomStore.test.ts +48 -0
- package/src/__tests__/PostgreSQLTaskStore.integration.test.ts +27 -0
- package/src/__tests__/PostgreSQLTaskStore.test.ts +57 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.integration.test.ts +74 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.migrations.test.ts +62 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.test.ts +96 -0
- package/src/__tests__/ThreadMessageQueueStore.migrations.test.ts +63 -0
- package/src/__tests__/ThreadMessageQueueStore.test.ts +113 -0
- package/src/__tests__/migration-name-version-compatibility.test.ts +67 -0
- package/src/__tests__/task-files.test.ts +4 -3
- package/src/createPgStoreConfig.ts +14 -0
- package/src/index.ts +7 -0
- package/src/migrations/add_trusted_run_context_column.ts +18 -0
- package/src/migrations/migration.ts +2 -1
- package/src/migrations/project_room_migration.ts +128 -0
- package/src/migrations/task_migration.ts +15 -0
- package/src/migrations/task_work_items_migration.ts +29 -1
- package/src/stores/ChannelBindingStore.ts +99 -59
- package/src/stores/PostgreSQLChannelInstallationStore.ts +12 -30
- package/src/stores/PostgreSQLProjectBotMembershipStore.ts +199 -0
- package/src/stores/PostgreSQLProjectMembershipStore.ts +140 -0
- package/src/stores/PostgreSQLProjectRoomMessageStore.ts +177 -0
- package/src/stores/PostgreSQLProjectRoomStore.ts +57 -0
- package/src/stores/PostgreSQLTaskStore.ts +89 -3
- package/src/stores/PostgreSQLTaskWorkItemStore.ts +159 -8
- package/src/stores/ThreadMessageQueueStore.ts +38 -9
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { Pool as
|
|
2
|
+
import { Pool as Pool29 } from "pg";
|
|
3
3
|
|
|
4
4
|
// src/createPgStoreConfig.ts
|
|
5
|
-
import { Pool as
|
|
5
|
+
import { Pool as Pool26 } from "pg";
|
|
6
6
|
|
|
7
7
|
// src/migrations/migration.ts
|
|
8
8
|
var MigrationManager = class {
|
|
@@ -33,7 +33,8 @@ var MigrationManager = class {
|
|
|
33
33
|
const tableExists = await client.query(`
|
|
34
34
|
SELECT EXISTS (
|
|
35
35
|
SELECT FROM information_schema.tables
|
|
36
|
-
WHERE
|
|
36
|
+
WHERE table_schema = current_schema()
|
|
37
|
+
AND table_name = 'lattice_schema_migrations'
|
|
37
38
|
)
|
|
38
39
|
`);
|
|
39
40
|
if (!tableExists.rows[0].exists) {
|
|
@@ -5032,6 +5033,10 @@ var PostgreSQLEvalStore = class {
|
|
|
5032
5033
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5033
5034
|
import { Pool as Pool13 } from "pg";
|
|
5034
5035
|
import crypto from "crypto";
|
|
5036
|
+
import {
|
|
5037
|
+
parseQueuedExecutionMode,
|
|
5038
|
+
parseTrustedRunContext
|
|
5039
|
+
} from "@axiom-lattice/protocols";
|
|
5035
5040
|
|
|
5036
5041
|
// src/migrations/thread_message_queue_migrations.ts
|
|
5037
5042
|
var createThreadMessageQueueTable = {
|
|
@@ -5168,6 +5173,23 @@ var addWorkspaceProjectToQueue = {
|
|
|
5168
5173
|
}
|
|
5169
5174
|
};
|
|
5170
5175
|
|
|
5176
|
+
// src/migrations/add_trusted_run_context_column.ts
|
|
5177
|
+
var addTrustedRunContextColumn = {
|
|
5178
|
+
version: 173,
|
|
5179
|
+
name: "add_thread_queue_trusted_run_context",
|
|
5180
|
+
up: async (client) => {
|
|
5181
|
+
await client.query(`ALTER TABLE lattice_thread_message_queue
|
|
5182
|
+
ADD COLUMN IF NOT EXISTS trusted_run_context JSONB,
|
|
5183
|
+
ADD COLUMN IF NOT EXISTS execution_mode VARCHAR(20)
|
|
5184
|
+
CHECK (execution_mode IS NULL OR execution_mode = 'followup')`);
|
|
5185
|
+
},
|
|
5186
|
+
down: async (client) => {
|
|
5187
|
+
await client.query(`ALTER TABLE lattice_thread_message_queue
|
|
5188
|
+
DROP COLUMN IF EXISTS execution_mode,
|
|
5189
|
+
DROP COLUMN IF EXISTS trusted_run_context`);
|
|
5190
|
+
}
|
|
5191
|
+
};
|
|
5192
|
+
|
|
5171
5193
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5172
5194
|
var ThreadMessageQueueStore = class {
|
|
5173
5195
|
constructor(options) {
|
|
@@ -5193,6 +5215,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5193
5215
|
this.migrationManager.register(addCustomRunConfigColumn);
|
|
5194
5216
|
this.migrationManager.register(alterMessageQueueIdColumn);
|
|
5195
5217
|
this.migrationManager.register(addWorkspaceProjectToQueue);
|
|
5218
|
+
this.migrationManager.register(addTrustedRunContextColumn);
|
|
5196
5219
|
if (options.autoMigrate !== false) {
|
|
5197
5220
|
this.initialize().catch((error) => {
|
|
5198
5221
|
console.error("Failed to initialize ThreadMessageQueueStore:", error);
|
|
@@ -5222,6 +5245,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5222
5245
|
* Add message to queue
|
|
5223
5246
|
*/
|
|
5224
5247
|
async addMessage(params) {
|
|
5248
|
+
const trusted = validateQueueTrust(params);
|
|
5225
5249
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", priority = 0, command, custom_run_config, id } = params;
|
|
5226
5250
|
const seqResult = await this.pool.query(
|
|
5227
5251
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq
|
|
@@ -5232,14 +5256,15 @@ var ThreadMessageQueueStore = class {
|
|
|
5232
5256
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5233
5257
|
const result = await this.pool.query(
|
|
5234
5258
|
`INSERT INTO lattice_thread_message_queue
|
|
5235
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5236
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
5259
|
+
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode)
|
|
5260
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
5237
5261
|
RETURNING *`,
|
|
5238
|
-
[id || crypto.randomUUID(), threadId, tenantId, assistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, priority, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null]
|
|
5262
|
+
[id || crypto.randomUUID(), threadId, tenantId, assistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, priority, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null, trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null, trusted.executionMode ?? null]
|
|
5239
5263
|
);
|
|
5240
5264
|
return this.rowToMessage(result.rows[0]);
|
|
5241
5265
|
}
|
|
5242
5266
|
async addMessageIfCapacity(params, maxSize) {
|
|
5267
|
+
const trusted = validateQueueTrust(params);
|
|
5243
5268
|
if (maxSize === Infinity) {
|
|
5244
5269
|
await this.addMessage(params);
|
|
5245
5270
|
return true;
|
|
@@ -5264,9 +5289,9 @@ var ThreadMessageQueueStore = class {
|
|
|
5264
5289
|
[params.threadId]
|
|
5265
5290
|
);
|
|
5266
5291
|
const result = await client.query(
|
|
5267
|
-
`INSERT INTO lattice_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5268
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
|
|
5269
|
-
[params.id || crypto.randomUUID(), params.threadId, params.tenantId, params.assistantId, params.workspaceId ?? null, params.projectId ?? null, JSON.stringify(params.content), params.type || "human", seq.rows[0].next_seq, params.priority ?? 0, params.command ? JSON.stringify(params.command) : null, params.custom_run_config ? JSON.stringify(params.custom_run_config) : null]
|
|
5292
|
+
`INSERT INTO lattice_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode)
|
|
5293
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *`,
|
|
5294
|
+
[params.id || crypto.randomUUID(), params.threadId, params.tenantId, params.assistantId, params.workspaceId ?? null, params.projectId ?? null, JSON.stringify(params.content), params.type || "human", seq.rows[0].next_seq, params.priority ?? 0, params.command ? JSON.stringify(params.command) : null, params.custom_run_config ? JSON.stringify(params.custom_run_config) : null, trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null, trusted.executionMode ?? null]
|
|
5270
5295
|
);
|
|
5271
5296
|
await client.query("COMMIT");
|
|
5272
5297
|
return Boolean(result.rows[0]);
|
|
@@ -5282,6 +5307,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5282
5307
|
* Uses priority=100 to ensure message is processed first
|
|
5283
5308
|
*/
|
|
5284
5309
|
async addMessageAtHead(params) {
|
|
5310
|
+
const trusted = validateQueueTrust(params);
|
|
5285
5311
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", command, custom_run_config, id } = params;
|
|
5286
5312
|
const resolvedTenantId = tenantId;
|
|
5287
5313
|
const resolvedAssistantId = assistantId;
|
|
@@ -5294,10 +5320,10 @@ var ThreadMessageQueueStore = class {
|
|
|
5294
5320
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5295
5321
|
const result = await this.pool.query(
|
|
5296
5322
|
`INSERT INTO lattice_thread_message_queue
|
|
5297
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5298
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 100, $10, $11)
|
|
5323
|
+
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode)
|
|
5324
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 100, $10, $11, $12, $13)
|
|
5299
5325
|
RETURNING *`,
|
|
5300
|
-
[id || crypto.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null]
|
|
5326
|
+
[id || crypto.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null, trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null, trusted.executionMode ?? null]
|
|
5301
5327
|
);
|
|
5302
5328
|
return this.rowToMessage(result.rows[0]);
|
|
5303
5329
|
}
|
|
@@ -5420,10 +5446,18 @@ var ThreadMessageQueueStore = class {
|
|
|
5420
5446
|
createdAt: new Date(row.created_at),
|
|
5421
5447
|
priority: row.priority || 0,
|
|
5422
5448
|
command: row.command ? typeof row.command === "string" ? JSON.parse(row.command) : row.command : void 0,
|
|
5423
|
-
custom_run_config: row.custom_run_config ? typeof row.custom_run_config === "string" ? JSON.parse(row.custom_run_config) : row.custom_run_config : void 0
|
|
5449
|
+
custom_run_config: row.custom_run_config ? typeof row.custom_run_config === "string" ? JSON.parse(row.custom_run_config) : row.custom_run_config : void 0,
|
|
5450
|
+
trusted_run_context: row.trusted_run_context ? parseTrustedRunContext(typeof row.trusted_run_context === "string" ? JSON.parse(row.trusted_run_context) : row.trusted_run_context) : void 0,
|
|
5451
|
+
execution_mode: row.execution_mode == null ? void 0 : parseQueuedExecutionMode(row.execution_mode)
|
|
5424
5452
|
};
|
|
5425
5453
|
}
|
|
5426
5454
|
};
|
|
5455
|
+
function validateQueueTrust(params) {
|
|
5456
|
+
return {
|
|
5457
|
+
trustedRunContext: params.trusted_run_context === void 0 ? void 0 : parseTrustedRunContext(params.trusted_run_context),
|
|
5458
|
+
executionMode: params.execution_mode === void 0 ? void 0 : parseQueuedExecutionMode(params.execution_mode)
|
|
5459
|
+
};
|
|
5460
|
+
}
|
|
5427
5461
|
function scopeClause(scope, start) {
|
|
5428
5462
|
if (!scope) return { sql: "", params: [] };
|
|
5429
5463
|
const params = [];
|
|
@@ -5439,6 +5473,9 @@ function scopeClause(scope, start) {
|
|
|
5439
5473
|
|
|
5440
5474
|
// src/stores/ChannelBindingStore.ts
|
|
5441
5475
|
import { Pool as Pool14 } from "pg";
|
|
5476
|
+
import {
|
|
5477
|
+
DuplicateChannelBindingSubjectError
|
|
5478
|
+
} from "@axiom-lattice/protocols";
|
|
5442
5479
|
|
|
5443
5480
|
// src/migrations/channel_bindings_migration.ts
|
|
5444
5481
|
var createChannelBindingsTable = {
|
|
@@ -5486,6 +5523,7 @@ var createChannelBindingsTable = {
|
|
|
5486
5523
|
};
|
|
5487
5524
|
|
|
5488
5525
|
// src/stores/ChannelBindingStore.ts
|
|
5526
|
+
var BINDING_SUBJECT_CONSTRAINT = "lattice_channel_bindings_channel_channel_installation_id_te_key";
|
|
5489
5527
|
var ChannelBindingStore = class {
|
|
5490
5528
|
constructor(options) {
|
|
5491
5529
|
this.initialized = false;
|
|
@@ -5542,80 +5580,91 @@ var ChannelBindingStore = class {
|
|
|
5542
5580
|
if (result.rows.length === 0) return null;
|
|
5543
5581
|
return this.mapRowToBinding(result.rows[0]);
|
|
5544
5582
|
}
|
|
5545
|
-
async
|
|
5583
|
+
async findById(tenantId, id) {
|
|
5546
5584
|
await this.ensureInitialized();
|
|
5547
5585
|
const result = await this.pool.query(
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
thread_mode, sender_display_name, sender_metadata, workspace_id, project_id)
|
|
5551
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
5552
|
-
RETURNING *`,
|
|
5553
|
-
[
|
|
5554
|
-
input.channel,
|
|
5555
|
-
input.channelInstallationId,
|
|
5556
|
-
input.tenantId,
|
|
5557
|
-
input.senderId,
|
|
5558
|
-
input.agentId,
|
|
5559
|
-
input.threadMode || "fixed",
|
|
5560
|
-
input.senderDisplayName || null,
|
|
5561
|
-
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
5562
|
-
input.workspaceId || null,
|
|
5563
|
-
input.projectId || null
|
|
5564
|
-
]
|
|
5586
|
+
"SELECT * FROM lattice_channel_bindings WHERE id = $1 AND tenant_id = $2",
|
|
5587
|
+
[id, tenantId]
|
|
5565
5588
|
);
|
|
5566
|
-
return this.mapRowToBinding(result.rows[0]);
|
|
5589
|
+
return result.rows[0] ? this.mapRowToBinding(result.rows[0]) : null;
|
|
5567
5590
|
}
|
|
5568
|
-
async
|
|
5591
|
+
async findBySubject(params) {
|
|
5569
5592
|
await this.ensureInitialized();
|
|
5570
|
-
const
|
|
5571
|
-
`SELECT * FROM lattice_channel_bindings
|
|
5572
|
-
|
|
5593
|
+
const result = await this.pool.query(
|
|
5594
|
+
`SELECT * FROM lattice_channel_bindings
|
|
5595
|
+
WHERE tenant_id = $1 AND channel = $2 AND channel_installation_id = $3 AND sender_id = $4 LIMIT 1`,
|
|
5596
|
+
[params.tenantId, params.channel, params.channelInstallationId, params.senderId]
|
|
5573
5597
|
);
|
|
5574
|
-
|
|
5575
|
-
|
|
5598
|
+
return result.rows[0] ? this.mapRowToBinding(result.rows[0]) : null;
|
|
5599
|
+
}
|
|
5600
|
+
async create(input) {
|
|
5601
|
+
await this.ensureInitialized();
|
|
5602
|
+
try {
|
|
5603
|
+
const result = await this.pool.query(
|
|
5604
|
+
`INSERT INTO lattice_channel_bindings
|
|
5605
|
+
(channel, channel_installation_id, tenant_id, sender_id, agent_id,
|
|
5606
|
+
thread_id, thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, enabled)
|
|
5607
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
5608
|
+
RETURNING *`,
|
|
5609
|
+
[
|
|
5610
|
+
input.channel,
|
|
5611
|
+
input.channelInstallationId,
|
|
5612
|
+
input.tenantId,
|
|
5613
|
+
input.senderId,
|
|
5614
|
+
input.agentId,
|
|
5615
|
+
input.threadId || null,
|
|
5616
|
+
input.threadMode || "fixed",
|
|
5617
|
+
input.senderDisplayName || null,
|
|
5618
|
+
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
5619
|
+
input.workspaceId || null,
|
|
5620
|
+
input.projectId || null,
|
|
5621
|
+
input.enabled ?? true
|
|
5622
|
+
]
|
|
5623
|
+
);
|
|
5624
|
+
return this.mapRowToBinding(result.rows[0]);
|
|
5625
|
+
} catch (error) {
|
|
5626
|
+
if (isBindingSubjectConflict(error)) {
|
|
5627
|
+
throw new DuplicateChannelBindingSubjectError();
|
|
5628
|
+
}
|
|
5629
|
+
throw error;
|
|
5576
5630
|
}
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
channel_installation_id: patch.channelInstallationId ?? row.channel_installation_id,
|
|
5581
|
-
sender_id: patch.senderId ?? row.sender_id,
|
|
5582
|
-
agent_id: patch.agentId ?? row.agent_id,
|
|
5583
|
-
thread_id: patch.threadId !== void 0 ? patch.threadId : row.thread_id,
|
|
5584
|
-
workspace_id: patch.workspaceId !== void 0 ? patch.workspaceId : row.workspace_id,
|
|
5585
|
-
project_id: patch.projectId !== void 0 ? patch.projectId : row.project_id,
|
|
5586
|
-
thread_mode: patch.threadMode ?? row.thread_mode,
|
|
5587
|
-
sender_display_name: patch.senderDisplayName !== void 0 ? patch.senderDisplayName : row.sender_display_name,
|
|
5588
|
-
sender_metadata: patch.senderMetadata !== void 0 ? patch.senderMetadata : row.sender_metadata,
|
|
5589
|
-
enabled: patch.enabled ?? row.enabled
|
|
5590
|
-
};
|
|
5631
|
+
}
|
|
5632
|
+
async update(tenantId, id, patch) {
|
|
5633
|
+
await this.ensureInitialized();
|
|
5591
5634
|
const result = await this.pool.query(
|
|
5592
5635
|
`UPDATE lattice_channel_bindings SET
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5636
|
+
agent_id = COALESCE($1, agent_id),
|
|
5637
|
+
thread_id = COALESCE($2, thread_id),
|
|
5638
|
+
workspace_id = COALESCE($3, workspace_id),
|
|
5639
|
+
project_id = COALESCE($4, project_id),
|
|
5640
|
+
thread_mode = COALESCE($5, thread_mode),
|
|
5641
|
+
sender_display_name = COALESCE($6, sender_display_name),
|
|
5642
|
+
sender_metadata = COALESCE($7, sender_metadata),
|
|
5643
|
+
enabled = COALESCE($8, enabled), updated_at = NOW()
|
|
5644
|
+
WHERE id = $9 AND tenant_id = $10
|
|
5598
5645
|
RETURNING *`,
|
|
5599
5646
|
[
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
updated.enabled,
|
|
5611
|
-
id
|
|
5647
|
+
patch.agentId ?? null,
|
|
5648
|
+
patch.threadId ?? null,
|
|
5649
|
+
patch.workspaceId ?? null,
|
|
5650
|
+
patch.projectId ?? null,
|
|
5651
|
+
patch.threadMode ?? null,
|
|
5652
|
+
patch.senderDisplayName ?? null,
|
|
5653
|
+
patch.senderMetadata ? JSON.stringify(patch.senderMetadata) : null,
|
|
5654
|
+
patch.enabled ?? null,
|
|
5655
|
+
id,
|
|
5656
|
+
tenantId
|
|
5612
5657
|
]
|
|
5613
5658
|
);
|
|
5659
|
+
if (!result.rows[0]) throw new Error(`Binding ${id} not found`);
|
|
5614
5660
|
return this.mapRowToBinding(result.rows[0]);
|
|
5615
5661
|
}
|
|
5616
|
-
async delete(id) {
|
|
5662
|
+
async delete(tenantId, id) {
|
|
5617
5663
|
await this.ensureInitialized();
|
|
5618
|
-
await this.pool.query(
|
|
5664
|
+
await this.pool.query(
|
|
5665
|
+
`DELETE FROM lattice_channel_bindings WHERE id = $1 AND tenant_id = $2`,
|
|
5666
|
+
[id, tenantId]
|
|
5667
|
+
);
|
|
5619
5668
|
}
|
|
5620
5669
|
async list(params) {
|
|
5621
5670
|
await this.ensureInitialized();
|
|
@@ -5634,6 +5683,14 @@ var ChannelBindingStore = class {
|
|
|
5634
5683
|
conditions.push(`channel_installation_id = $${idx++}`);
|
|
5635
5684
|
values.push(params.channelInstallationId);
|
|
5636
5685
|
}
|
|
5686
|
+
if (params.excludeChannels?.length) {
|
|
5687
|
+
conditions.push(`channel <> ALL($${idx++}::text[])`);
|
|
5688
|
+
values.push(params.excludeChannels);
|
|
5689
|
+
}
|
|
5690
|
+
for (const prefix of params.excludeInstallationIdPrefixes ?? []) {
|
|
5691
|
+
conditions.push(`channel_installation_id NOT LIKE $${idx++} ESCAPE '\\'`);
|
|
5692
|
+
values.push(`${escapeLikePattern(prefix)}%`);
|
|
5693
|
+
}
|
|
5637
5694
|
const limit = params.limit ?? 50;
|
|
5638
5695
|
const offset = params.offset ?? 0;
|
|
5639
5696
|
values.push(limit, offset);
|
|
@@ -5646,7 +5703,16 @@ var ChannelBindingStore = class {
|
|
|
5646
5703
|
);
|
|
5647
5704
|
return result.rows.map((r) => this.mapRowToBinding(r));
|
|
5648
5705
|
}
|
|
5649
|
-
async import(bindings) {
|
|
5706
|
+
async import(tenantId, bindings) {
|
|
5707
|
+
if (bindings.some((binding) => binding.channel === "room")) {
|
|
5708
|
+
throw new Error("Room bindings cannot be imported through the public store API");
|
|
5709
|
+
}
|
|
5710
|
+
if (bindings.some((binding) => binding.channelInstallationId.startsWith("room-internal:"))) {
|
|
5711
|
+
throw new Error("Internal bindings cannot be imported through the public store API");
|
|
5712
|
+
}
|
|
5713
|
+
if (bindings.some((binding) => binding.tenantId !== tenantId)) {
|
|
5714
|
+
throw new Error("Binding import tenant mismatch");
|
|
5715
|
+
}
|
|
5650
5716
|
const result = [];
|
|
5651
5717
|
for (const input of bindings) {
|
|
5652
5718
|
result.push(await this.create(input));
|
|
@@ -5654,7 +5720,13 @@ var ChannelBindingStore = class {
|
|
|
5654
5720
|
return result;
|
|
5655
5721
|
}
|
|
5656
5722
|
async export(params) {
|
|
5657
|
-
return this.list({
|
|
5723
|
+
return this.list({
|
|
5724
|
+
tenantId: params.tenantId,
|
|
5725
|
+
excludeChannels: ["room"],
|
|
5726
|
+
excludeInstallationIdPrefixes: ["room-internal:"],
|
|
5727
|
+
limit: 1e4,
|
|
5728
|
+
offset: 0
|
|
5729
|
+
});
|
|
5658
5730
|
}
|
|
5659
5731
|
async ensureInitialized() {
|
|
5660
5732
|
if (!this.initialized) {
|
|
@@ -5681,6 +5753,14 @@ var ChannelBindingStore = class {
|
|
|
5681
5753
|
};
|
|
5682
5754
|
}
|
|
5683
5755
|
};
|
|
5756
|
+
function isBindingSubjectConflict(error) {
|
|
5757
|
+
if (typeof error !== "object" || error === null) return false;
|
|
5758
|
+
const pgError = error;
|
|
5759
|
+
return pgError.code === "23505" && pgError.constraint === BINDING_SUBJECT_CONSTRAINT;
|
|
5760
|
+
}
|
|
5761
|
+
function escapeLikePattern(value) {
|
|
5762
|
+
return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
5763
|
+
}
|
|
5684
5764
|
|
|
5685
5765
|
// src/stores/PostgreSQLChannelInstallationStore.ts
|
|
5686
5766
|
import { Pool as Pool15 } from "pg";
|
|
@@ -5855,7 +5935,8 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5855
5935
|
async createInstallation(tenantId, installationId, data) {
|
|
5856
5936
|
await this.ensureInitialized();
|
|
5857
5937
|
const now = /* @__PURE__ */ new Date();
|
|
5858
|
-
const
|
|
5938
|
+
const config = { ...data.config };
|
|
5939
|
+
const encryptedConfig = this.encryptSecrets(config);
|
|
5859
5940
|
await this.pool.query(
|
|
5860
5941
|
`
|
|
5861
5942
|
INSERT INTO lattice_channel_installations (
|
|
@@ -5880,7 +5961,7 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5880
5961
|
tenantId,
|
|
5881
5962
|
channel: data.channel,
|
|
5882
5963
|
name: data.name,
|
|
5883
|
-
config
|
|
5964
|
+
config,
|
|
5884
5965
|
enabled: data.enabled ?? true,
|
|
5885
5966
|
fallbackAgentId: data.fallbackAgentId,
|
|
5886
5967
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? true,
|
|
@@ -5967,17 +6048,17 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5967
6048
|
encryptSecrets(config) {
|
|
5968
6049
|
return {
|
|
5969
6050
|
...config,
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
6051
|
+
...typeof config.appSecret === "string" ? { appSecret: encrypt4(config.appSecret) } : {},
|
|
6052
|
+
...typeof config.verificationToken === "string" ? { verificationToken: encrypt4(config.verificationToken) } : {},
|
|
6053
|
+
...typeof config.encryptKey === "string" ? { encryptKey: encrypt4(config.encryptKey) } : {}
|
|
5973
6054
|
};
|
|
5974
6055
|
}
|
|
5975
6056
|
decryptSecrets(config) {
|
|
5976
6057
|
return {
|
|
5977
6058
|
...config,
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
6059
|
+
...typeof config.appSecret === "string" ? { appSecret: decrypt4(config.appSecret) } : {},
|
|
6060
|
+
...typeof config.verificationToken === "string" ? { verificationToken: decrypt4(config.verificationToken) } : {},
|
|
6061
|
+
...typeof config.encryptKey === "string" ? { encryptKey: decrypt4(config.encryptKey) } : {}
|
|
5981
6062
|
};
|
|
5982
6063
|
}
|
|
5983
6064
|
};
|
|
@@ -7132,15 +7213,37 @@ var addFilesToTasks = {
|
|
|
7132
7213
|
`);
|
|
7133
7214
|
}
|
|
7134
7215
|
};
|
|
7216
|
+
var addTaskDependenciesGinIndex = {
|
|
7217
|
+
version: 174,
|
|
7218
|
+
name: "add_task_dependencies_gin_index",
|
|
7219
|
+
up: async (client) => {
|
|
7220
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_lattice_tasks_dependencies_gin
|
|
7221
|
+
ON lattice_tasks USING GIN (dependencies jsonb_path_ops)
|
|
7222
|
+
WHERE dependencies IS NOT NULL`);
|
|
7223
|
+
},
|
|
7224
|
+
down: async (client) => {
|
|
7225
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_tasks_dependencies_gin");
|
|
7226
|
+
}
|
|
7227
|
+
};
|
|
7135
7228
|
var taskMigrations = [
|
|
7136
7229
|
createTasksTable,
|
|
7137
7230
|
addTaskFieldsMigration,
|
|
7138
7231
|
addTaskProjectFieldsMigration,
|
|
7139
|
-
addFilesToTasks
|
|
7232
|
+
addFilesToTasks,
|
|
7233
|
+
addTaskDependenciesGinIndex
|
|
7140
7234
|
];
|
|
7141
7235
|
|
|
7142
7236
|
// src/stores/PostgreSQLTaskStore.ts
|
|
7143
7237
|
import { v4 as uuidv42 } from "uuid";
|
|
7238
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
7239
|
+
"pending",
|
|
7240
|
+
"in_progress",
|
|
7241
|
+
"review",
|
|
7242
|
+
"failed",
|
|
7243
|
+
"interrupted",
|
|
7244
|
+
"completed",
|
|
7245
|
+
"cancelled"
|
|
7246
|
+
]);
|
|
7144
7247
|
function nextUpdatedAtSql(column = "updated_at") {
|
|
7145
7248
|
return `to_char(
|
|
7146
7249
|
date_trunc('milliseconds', GREATEST(
|
|
@@ -7220,6 +7323,9 @@ function mapRowToTask(row) {
|
|
|
7220
7323
|
updatedAt: new Date(row.updated_at)
|
|
7221
7324
|
};
|
|
7222
7325
|
}
|
|
7326
|
+
function assertPage(limit, offset = 0) {
|
|
7327
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0) throw new RangeError("Invalid task page");
|
|
7328
|
+
}
|
|
7223
7329
|
var PostgreSQLTaskStore = class {
|
|
7224
7330
|
constructor(options) {
|
|
7225
7331
|
this.initialized = false;
|
|
@@ -7243,6 +7349,7 @@ var PostgreSQLTaskStore = class {
|
|
|
7243
7349
|
this.migrationManager.register(addTaskFieldsMigration);
|
|
7244
7350
|
this.migrationManager.register(addTaskProjectFieldsMigration);
|
|
7245
7351
|
this.migrationManager.register(addFilesToTasks);
|
|
7352
|
+
this.migrationManager.register(addTaskDependenciesGinIndex);
|
|
7246
7353
|
if (options.autoMigrate !== false) {
|
|
7247
7354
|
this.initialize().catch((error) => {
|
|
7248
7355
|
console.error("Failed to initialize PostgreSQLTaskStore:", error);
|
|
@@ -7338,7 +7445,9 @@ var PostgreSQLTaskStore = class {
|
|
|
7338
7445
|
conditions.push(`workspace_id = $${paramIndex++}`);
|
|
7339
7446
|
params.push(filter.workspaceId);
|
|
7340
7447
|
}
|
|
7341
|
-
if (filter.projectId) {
|
|
7448
|
+
if (filter.projectId === null) {
|
|
7449
|
+
conditions.push("(project_id IS NULL OR project_id = '' OR project_id = 'default')");
|
|
7450
|
+
} else if (filter.projectId !== void 0) {
|
|
7342
7451
|
conditions.push(`project_id = $${paramIndex++}`);
|
|
7343
7452
|
params.push(filter.projectId);
|
|
7344
7453
|
}
|
|
@@ -7363,11 +7472,35 @@ var PostgreSQLTaskStore = class {
|
|
|
7363
7472
|
const limit = filter.limit || 100;
|
|
7364
7473
|
const offset = filter.offset || 0;
|
|
7365
7474
|
const result = await this.pool.query(
|
|
7366
|
-
`SELECT * FROM lattice_tasks WHERE ${where} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
7475
|
+
`SELECT * FROM lattice_tasks WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
7367
7476
|
[...params, limit, offset]
|
|
7368
7477
|
);
|
|
7369
7478
|
return result.rows.map((r) => mapRowToTask(r));
|
|
7370
7479
|
}
|
|
7480
|
+
/** Lists exact project tasks containing a JSON string dependency. */
|
|
7481
|
+
async listDependents(query) {
|
|
7482
|
+
assertPage(query.limit, query.offset);
|
|
7483
|
+
if (query.statuses.length === 0 || query.statuses.some((status) => !TASK_STATUSES.has(status))) {
|
|
7484
|
+
throw new RangeError("Invalid task statuses");
|
|
7485
|
+
}
|
|
7486
|
+
await this.ensureInitialized();
|
|
7487
|
+
const result = await this.pool.query(
|
|
7488
|
+
`SELECT * FROM lattice_tasks
|
|
7489
|
+
WHERE tenant_id=$1 AND workspace_id=$2 AND project_id=$3
|
|
7490
|
+
AND dependencies @> $4::jsonb AND status = ANY($5::text[])
|
|
7491
|
+
ORDER BY created_at DESC, id DESC LIMIT $6 OFFSET $7`,
|
|
7492
|
+
[
|
|
7493
|
+
query.tenantId,
|
|
7494
|
+
query.workspaceId,
|
|
7495
|
+
query.projectId,
|
|
7496
|
+
JSON.stringify([query.dependencyTaskId]),
|
|
7497
|
+
query.statuses,
|
|
7498
|
+
query.limit,
|
|
7499
|
+
query.offset
|
|
7500
|
+
]
|
|
7501
|
+
);
|
|
7502
|
+
return result.rows.map(mapRowToTask);
|
|
7503
|
+
}
|
|
7371
7504
|
async update(tenantId, id, updates) {
|
|
7372
7505
|
await this.ensureInitialized();
|
|
7373
7506
|
const existing = await this.getById(tenantId, id);
|
|
@@ -7738,6 +7871,63 @@ var PostgreSQLTaskStore = class {
|
|
|
7738
7871
|
);
|
|
7739
7872
|
return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
|
|
7740
7873
|
}
|
|
7874
|
+
/** Atomically update only while status, timestamp, owner, and Project scope match. */
|
|
7875
|
+
async updateIfSnapshot(tenantId, id, updates, snapshot) {
|
|
7876
|
+
await this.ensureInitialized();
|
|
7877
|
+
const setClauses = [];
|
|
7878
|
+
const params = [];
|
|
7879
|
+
let index = 1;
|
|
7880
|
+
const fields = [
|
|
7881
|
+
["title", "title"],
|
|
7882
|
+
["description", "description"],
|
|
7883
|
+
["status", "status"],
|
|
7884
|
+
["priority", "priority"],
|
|
7885
|
+
["dueDate", "due_date"],
|
|
7886
|
+
["metadata", "metadata", true],
|
|
7887
|
+
["files", "files", true],
|
|
7888
|
+
["parentId", "parent_id"],
|
|
7889
|
+
["sourceId", "source_id"],
|
|
7890
|
+
["context", "context", true],
|
|
7891
|
+
["ownerType", "owner_type"],
|
|
7892
|
+
["ownerId", "owner_id"],
|
|
7893
|
+
["requireReview", "require_review"],
|
|
7894
|
+
["dependencies", "dependencies", true],
|
|
7895
|
+
["result", "result"],
|
|
7896
|
+
["failureReason", "failure_reason"],
|
|
7897
|
+
["workspaceId", "workspace_id"],
|
|
7898
|
+
["projectId", "project_id"]
|
|
7899
|
+
];
|
|
7900
|
+
for (const [field, column, json] of fields) {
|
|
7901
|
+
const value = updates[field];
|
|
7902
|
+
if (value === void 0) continue;
|
|
7903
|
+
setClauses.push(`${column} = $${index++}`);
|
|
7904
|
+
params.push(json && value !== null ? JSON.stringify(value) : value);
|
|
7905
|
+
}
|
|
7906
|
+
if (setClauses.length === 0) return this.getById(tenantId, id);
|
|
7907
|
+
setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
|
|
7908
|
+
const predicates = [
|
|
7909
|
+
tenantId,
|
|
7910
|
+
id,
|
|
7911
|
+
snapshot.status,
|
|
7912
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
7913
|
+
snapshot.ownerType,
|
|
7914
|
+
snapshot.ownerId,
|
|
7915
|
+
snapshot.workspaceId,
|
|
7916
|
+
snapshot.projectId
|
|
7917
|
+
];
|
|
7918
|
+
const placeholders = predicates.map(() => `$${index++}`);
|
|
7919
|
+
params.push(...predicates);
|
|
7920
|
+
const result = await this.pool.query(
|
|
7921
|
+
`UPDATE lattice_tasks SET ${setClauses.join(", ")}
|
|
7922
|
+
WHERE tenant_id = ${placeholders[0]} AND id = ${placeholders[1]} AND status = ${placeholders[2]}
|
|
7923
|
+
AND ${canonicalTimestampSnapshotSql("updated_at", placeholders[3])}
|
|
7924
|
+
AND owner_type = ${placeholders[4]} AND owner_id = ${placeholders[5]}
|
|
7925
|
+
AND workspace_id IS NOT DISTINCT FROM ${placeholders[6]}
|
|
7926
|
+
AND project_id IS NOT DISTINCT FROM ${placeholders[7]} RETURNING *`,
|
|
7927
|
+
params
|
|
7928
|
+
);
|
|
7929
|
+
return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
|
|
7930
|
+
}
|
|
7741
7931
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
7742
7932
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
7743
7933
|
await this.ensureInitialized();
|
|
@@ -7856,6 +8046,27 @@ var PostgreSQLTaskStore = class {
|
|
|
7856
8046
|
);
|
|
7857
8047
|
return (result.rowCount ?? 0) > 0;
|
|
7858
8048
|
}
|
|
8049
|
+
/** Atomically delete only while status, timestamp, owner, and Project scope match. */
|
|
8050
|
+
async deleteIfSnapshot(tenantId, id, snapshot) {
|
|
8051
|
+
await this.ensureInitialized();
|
|
8052
|
+
const result = await this.pool.query(
|
|
8053
|
+
`DELETE FROM lattice_tasks WHERE tenant_id = $1 AND id = $2 AND status = $3
|
|
8054
|
+
AND ${canonicalTimestampSnapshotSql("updated_at", "$4")}
|
|
8055
|
+
AND owner_type = $5 AND owner_id = $6
|
|
8056
|
+
AND workspace_id IS NOT DISTINCT FROM $7 AND project_id IS NOT DISTINCT FROM $8`,
|
|
8057
|
+
[
|
|
8058
|
+
tenantId,
|
|
8059
|
+
id,
|
|
8060
|
+
snapshot.status,
|
|
8061
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
8062
|
+
snapshot.ownerType,
|
|
8063
|
+
snapshot.ownerId,
|
|
8064
|
+
snapshot.workspaceId,
|
|
8065
|
+
snapshot.projectId
|
|
8066
|
+
]
|
|
8067
|
+
);
|
|
8068
|
+
return (result.rowCount ?? 0) > 0;
|
|
8069
|
+
}
|
|
7859
8070
|
async dispose() {
|
|
7860
8071
|
if (this.ownsPool && this.pool) {
|
|
7861
8072
|
await this.pool.end();
|
|
@@ -7869,18 +8080,175 @@ var PostgreSQLTaskStore = class {
|
|
|
7869
8080
|
};
|
|
7870
8081
|
|
|
7871
8082
|
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
8083
|
+
import { Pool as Pool20 } from "pg";
|
|
7872
8084
|
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
7873
8085
|
import { v4 } from "uuid";
|
|
8086
|
+
|
|
8087
|
+
// src/migrations/task_work_items_migration.ts
|
|
8088
|
+
var createTaskWorkItemsMigration = {
|
|
8089
|
+
version: 138,
|
|
8090
|
+
name: "create_task_work_items_table",
|
|
8091
|
+
up: async (client) => {
|
|
8092
|
+
await client.query(`
|
|
8093
|
+
CREATE TABLE IF NOT EXISTS lattice_task_work_items (
|
|
8094
|
+
id TEXT NOT NULL,
|
|
8095
|
+
tenant_id TEXT NOT NULL,
|
|
8096
|
+
task_id TEXT NOT NULL,
|
|
8097
|
+
action TEXT NOT NULL,
|
|
8098
|
+
actor TEXT NOT NULL,
|
|
8099
|
+
thread_id TEXT,
|
|
8100
|
+
summary TEXT,
|
|
8101
|
+
detail JSONB,
|
|
8102
|
+
attempt INTEGER,
|
|
8103
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT date_trunc('milliseconds', clock_timestamp()),
|
|
8104
|
+
PRIMARY KEY (tenant_id, id)
|
|
8105
|
+
)
|
|
8106
|
+
`);
|
|
8107
|
+
await client.query(`
|
|
8108
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_task_id
|
|
8109
|
+
ON lattice_task_work_items (tenant_id, task_id)
|
|
8110
|
+
`);
|
|
8111
|
+
await client.query(`
|
|
8112
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_action
|
|
8113
|
+
ON lattice_task_work_items (tenant_id, task_id, action)
|
|
8114
|
+
`);
|
|
8115
|
+
}
|
|
8116
|
+
};
|
|
8117
|
+
var addWorkItemProjectFieldsMigration = {
|
|
8118
|
+
version: 140,
|
|
8119
|
+
name: "add_task_work_item_project_fields",
|
|
8120
|
+
up: async (client) => {
|
|
8121
|
+
await client.query(`
|
|
8122
|
+
ALTER TABLE lattice_task_work_items
|
|
8123
|
+
ADD COLUMN IF NOT EXISTS workspace_id TEXT,
|
|
8124
|
+
ADD COLUMN IF NOT EXISTS project_id TEXT
|
|
8125
|
+
`);
|
|
8126
|
+
await client.query(`
|
|
8127
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_project
|
|
8128
|
+
ON lattice_task_work_items (tenant_id, project_id)
|
|
8129
|
+
`);
|
|
8130
|
+
}
|
|
8131
|
+
};
|
|
8132
|
+
var addTaskWorkItemEventKeyMigration = {
|
|
8133
|
+
version: 168,
|
|
8134
|
+
name: "add_task_work_item_event_key",
|
|
8135
|
+
up: async (client) => {
|
|
8136
|
+
await client.query(`
|
|
8137
|
+
ALTER TABLE lattice_task_work_items
|
|
8138
|
+
ADD COLUMN IF NOT EXISTS event_key TEXT
|
|
8139
|
+
`);
|
|
8140
|
+
await client.query(`
|
|
8141
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
8142
|
+
ON lattice_task_work_items (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
|
|
8143
|
+
`);
|
|
8144
|
+
}
|
|
8145
|
+
};
|
|
8146
|
+
var addTaskWorkItemPendingIndexesMigration = {
|
|
8147
|
+
version: 171,
|
|
8148
|
+
name: "add_task_work_item_pending_indexes",
|
|
8149
|
+
up: async (client) => {
|
|
8150
|
+
await client.query(`
|
|
8151
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
8152
|
+
ON lattice_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC)
|
|
8153
|
+
`);
|
|
8154
|
+
await client.query(`
|
|
8155
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
8156
|
+
ON lattice_task_work_items (tenant_id, task_id, (detail ->> 'executionResultId'))
|
|
8157
|
+
WHERE action = 'execution_reconciled'
|
|
8158
|
+
`);
|
|
8159
|
+
}
|
|
8160
|
+
};
|
|
8161
|
+
var addProjectLifecycleEventIndex = {
|
|
8162
|
+
version: 175,
|
|
8163
|
+
name: "add_project_lifecycle_event_index",
|
|
8164
|
+
up: async (client) => {
|
|
8165
|
+
await client.query(`UPDATE lattice_task_work_items
|
|
8166
|
+
SET created_at = date_trunc('milliseconds', created_at)
|
|
8167
|
+
WHERE created_at <> date_trunc('milliseconds', created_at)`);
|
|
8168
|
+
await client.query(`ALTER TABLE lattice_task_work_items
|
|
8169
|
+
ALTER COLUMN created_at SET DEFAULT date_trunc('milliseconds', clock_timestamp())`);
|
|
8170
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_task_work_items_project_lifecycle
|
|
8171
|
+
ON lattice_task_work_items (tenant_id, workspace_id, project_id, created_at DESC, id DESC)
|
|
8172
|
+
WHERE event_key IS NOT NULL`);
|
|
8173
|
+
},
|
|
8174
|
+
down: async (client) => {
|
|
8175
|
+
await client.query("DROP INDEX IF EXISTS idx_task_work_items_project_lifecycle");
|
|
8176
|
+
}
|
|
8177
|
+
};
|
|
8178
|
+
var taskWorkItemMigrations = [
|
|
8179
|
+
createTaskWorkItemsMigration,
|
|
8180
|
+
addWorkItemProjectFieldsMigration,
|
|
8181
|
+
addTaskWorkItemEventKeyMigration,
|
|
8182
|
+
addTaskWorkItemPendingIndexesMigration,
|
|
8183
|
+
addProjectLifecycleEventIndex
|
|
8184
|
+
];
|
|
8185
|
+
|
|
8186
|
+
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
8187
|
+
var PROJECT_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
8188
|
+
"in_progress",
|
|
8189
|
+
"interrupted",
|
|
8190
|
+
"failed",
|
|
8191
|
+
"completed",
|
|
8192
|
+
"cancelled",
|
|
8193
|
+
"reassigned"
|
|
8194
|
+
]);
|
|
8195
|
+
function isRecord2(value) {
|
|
8196
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8197
|
+
}
|
|
8198
|
+
function isPool(value) {
|
|
8199
|
+
return typeof value.query === "function";
|
|
8200
|
+
}
|
|
7874
8201
|
var PostgreSQLTaskWorkItemStore = class {
|
|
7875
|
-
constructor(
|
|
7876
|
-
this.
|
|
8202
|
+
constructor(poolOrOptions) {
|
|
8203
|
+
this.initialized = false;
|
|
8204
|
+
this.ownsPool = false;
|
|
8205
|
+
this.initPromise = null;
|
|
8206
|
+
if (isPool(poolOrOptions)) {
|
|
8207
|
+
this.pool = poolOrOptions;
|
|
8208
|
+
this.initialized = true;
|
|
8209
|
+
return;
|
|
8210
|
+
}
|
|
8211
|
+
const options = poolOrOptions;
|
|
8212
|
+
if (options.pool) {
|
|
8213
|
+
this.pool = options.pool;
|
|
8214
|
+
this.initialized = true;
|
|
8215
|
+
return;
|
|
8216
|
+
}
|
|
8217
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool20({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool20(options.poolConfig) : (() => {
|
|
8218
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
8219
|
+
})();
|
|
8220
|
+
this.ownsPool = true;
|
|
8221
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
8222
|
+
for (const migration of taskWorkItemMigrations) this.migrationManager.register(migration);
|
|
8223
|
+
if (options.autoMigrate !== false) this.startInitialization();
|
|
8224
|
+
}
|
|
8225
|
+
/** Applies the complete standalone TaskWorkItem migration chain once. */
|
|
8226
|
+
async initialize() {
|
|
8227
|
+
if (this.initialized) return;
|
|
8228
|
+
if (this.initPromise) return this.initPromise;
|
|
8229
|
+
return this.startInitialization();
|
|
8230
|
+
}
|
|
8231
|
+
/** Closes the pool only when this store created it from connection configuration. */
|
|
8232
|
+
async dispose() {
|
|
8233
|
+
if (this.ownsPool) await this.pool.end();
|
|
8234
|
+
}
|
|
8235
|
+
startInitialization() {
|
|
8236
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
8237
|
+
this.initialized = true;
|
|
8238
|
+
});
|
|
8239
|
+
void this.initPromise.catch(() => void 0);
|
|
8240
|
+
return this.initPromise;
|
|
8241
|
+
}
|
|
8242
|
+
async ensureInitialized() {
|
|
8243
|
+
if (!this.initialized) await this.initialize();
|
|
7877
8244
|
}
|
|
7878
8245
|
async create(params) {
|
|
8246
|
+
await this.ensureInitialized();
|
|
7879
8247
|
const id = v4();
|
|
7880
8248
|
const result = await this.pool.query(
|
|
7881
8249
|
`INSERT INTO lattice_task_work_items
|
|
7882
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id)
|
|
7883
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
8250
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
8251
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, date_trunc('milliseconds', clock_timestamp()))
|
|
7884
8252
|
RETURNING *`,
|
|
7885
8253
|
[
|
|
7886
8254
|
id,
|
|
@@ -7898,8 +8266,46 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7898
8266
|
);
|
|
7899
8267
|
return this.rowToItem(result.rows[0]);
|
|
7900
8268
|
}
|
|
8269
|
+
/** Atomically inserts a work item by selecting one exact task snapshot. */
|
|
8270
|
+
async createIfTaskSnapshot(params, snapshot) {
|
|
8271
|
+
await this.ensureInitialized();
|
|
8272
|
+
const result = await this.pool.query(
|
|
8273
|
+
`INSERT INTO lattice_task_work_items
|
|
8274
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
8275
|
+
SELECT $1, task.tenant_id, task.id, $2, $3, $4, $5, $6, $7, task.workspace_id, task.project_id,
|
|
8276
|
+
date_trunc('milliseconds', clock_timestamp())
|
|
8277
|
+
FROM lattice_tasks AS task
|
|
8278
|
+
WHERE task.tenant_id = $8 AND task.id = $9 AND task.status = $10
|
|
8279
|
+
AND date_trunc('milliseconds', task.updated_at::timestamptz) = $11::timestamptz
|
|
8280
|
+
AND task.owner_type = $12 AND task.owner_id = $13
|
|
8281
|
+
AND task.workspace_id IS NOT DISTINCT FROM $14 AND task.project_id IS NOT DISTINCT FROM $15
|
|
8282
|
+
AND $14 IS NOT DISTINCT FROM $16 AND $15 IS NOT DISTINCT FROM $17
|
|
8283
|
+
RETURNING *`,
|
|
8284
|
+
[
|
|
8285
|
+
v4(),
|
|
8286
|
+
params.action,
|
|
8287
|
+
params.actor,
|
|
8288
|
+
params.threadId || null,
|
|
8289
|
+
params.summary || null,
|
|
8290
|
+
params.detail ? JSON.stringify(params.detail) : null,
|
|
8291
|
+
params.attempt ?? null,
|
|
8292
|
+
params.tenantId,
|
|
8293
|
+
params.taskId,
|
|
8294
|
+
snapshot.status,
|
|
8295
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
8296
|
+
snapshot.ownerType,
|
|
8297
|
+
snapshot.ownerId,
|
|
8298
|
+
snapshot.workspaceId,
|
|
8299
|
+
snapshot.projectId,
|
|
8300
|
+
params.workspaceId,
|
|
8301
|
+
params.projectId
|
|
8302
|
+
]
|
|
8303
|
+
);
|
|
8304
|
+
return result.rows[0] ? this.rowToItem(result.rows[0]) : null;
|
|
8305
|
+
}
|
|
7901
8306
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
7902
8307
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
8308
|
+
await this.ensureInitialized();
|
|
7903
8309
|
const result = await this.pool.query(
|
|
7904
8310
|
`SELECT * FROM lattice_task_work_items
|
|
7905
8311
|
WHERE tenant_id = $1 AND task_id = $2 AND event_key = $3`,
|
|
@@ -7909,10 +8315,11 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7909
8315
|
}
|
|
7910
8316
|
/** Atomically return an existing event or create it once. */
|
|
7911
8317
|
async createIfAbsentByEventKey(params) {
|
|
8318
|
+
await this.ensureInitialized();
|
|
7912
8319
|
const result = await this.pool.query(
|
|
7913
8320
|
`INSERT INTO lattice_task_work_items
|
|
7914
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key)
|
|
7915
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
8321
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key, created_at)
|
|
8322
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, date_trunc('milliseconds', clock_timestamp()))
|
|
7916
8323
|
ON CONFLICT (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
|
|
7917
8324
|
DO UPDATE SET event_key = EXCLUDED.event_key
|
|
7918
8325
|
RETURNING *`,
|
|
@@ -7934,6 +8341,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7934
8341
|
return this.rowToItem(result.rows[0]);
|
|
7935
8342
|
}
|
|
7936
8343
|
async list(filter) {
|
|
8344
|
+
await this.ensureInitialized();
|
|
7937
8345
|
let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
|
|
7938
8346
|
const params = [filter.tenantId, filter.taskId];
|
|
7939
8347
|
if (filter.action) {
|
|
@@ -7990,27 +8398,76 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7990
8398
|
);
|
|
7991
8399
|
return result.rows.map((row) => this.rowToItem(row));
|
|
7992
8400
|
}
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8401
|
+
/** Lists canonical project lifecycle events with an exclusive cursor. */
|
|
8402
|
+
async listProjectLifecycleEvents(query) {
|
|
8403
|
+
let cursorMilliseconds = null;
|
|
8404
|
+
if (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100 || query.actions.length === 0 || query.actions.some((action) => !PROJECT_LIFECYCLE_ACTIONS.has(action))) {
|
|
8405
|
+
throw new RangeError("Invalid project lifecycle event page");
|
|
8406
|
+
}
|
|
8407
|
+
if (query.before) {
|
|
8408
|
+
try {
|
|
8409
|
+
cursorMilliseconds = Date.prototype.getTime.call(query.before.createdAt);
|
|
8410
|
+
} catch {
|
|
8411
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
8412
|
+
}
|
|
8413
|
+
if (!Number.isFinite(cursorMilliseconds) || typeof query.before.id !== "string" || query.before.id.length === 0) {
|
|
8414
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8417
|
+
const cursor = cursorMilliseconds === null ? null : new Date(cursorMilliseconds);
|
|
8418
|
+
await this.ensureInitialized();
|
|
8419
|
+
const result = await this.pool.query(
|
|
8420
|
+
`SELECT * FROM lattice_task_work_items
|
|
8421
|
+
WHERE tenant_id=$1 AND workspace_id=$2 AND project_id=$3
|
|
8422
|
+
AND action = ANY($4::text[]) AND event_key IS NOT NULL AND event_key <> ''
|
|
8423
|
+
AND ($5::timestamptz IS NULL OR created_at < $5::timestamptz
|
|
8424
|
+
OR (created_at = $5::timestamptz AND id < $6))
|
|
8425
|
+
ORDER BY created_at DESC, id DESC LIMIT $7`,
|
|
8426
|
+
[
|
|
8427
|
+
query.tenantId,
|
|
8428
|
+
query.workspaceId,
|
|
8429
|
+
query.projectId,
|
|
8430
|
+
query.actions,
|
|
8431
|
+
cursor,
|
|
8432
|
+
query.before?.id ?? null,
|
|
8433
|
+
query.limit
|
|
8434
|
+
]
|
|
8435
|
+
);
|
|
8436
|
+
return result.rows.map((row) => this.assertProjectLifecycleEvent(this.rowToItem(row)));
|
|
8437
|
+
}
|
|
8438
|
+
assertProjectLifecycleEvent(item) {
|
|
8439
|
+
let milliseconds;
|
|
8440
|
+
try {
|
|
8441
|
+
milliseconds = Date.prototype.getTime.call(item.createdAt);
|
|
8442
|
+
} catch {
|
|
8443
|
+
throw new Error("Invalid project lifecycle event row");
|
|
8444
|
+
}
|
|
8445
|
+
if (!Number.isFinite(milliseconds) || typeof item.id !== "string" || item.id.length === 0 || typeof item.eventKey !== "string" || item.eventKey.length === 0) {
|
|
8446
|
+
throw new Error("Invalid project lifecycle event row");
|
|
8447
|
+
}
|
|
8448
|
+
return { ...item, createdAt: new Date(milliseconds) };
|
|
8449
|
+
}
|
|
8450
|
+
rowToItem(row) {
|
|
8451
|
+
return {
|
|
8452
|
+
id: row.id,
|
|
8453
|
+
taskId: row.task_id,
|
|
8454
|
+
tenantId: row.tenant_id,
|
|
8455
|
+
action: row.action,
|
|
8456
|
+
actor: row.actor,
|
|
8457
|
+
threadId: row.thread_id,
|
|
8458
|
+
summary: row.summary,
|
|
8459
|
+
detail: isRecord2(row.detail) ? row.detail : void 0,
|
|
8460
|
+
attempt: row.attempt,
|
|
8461
|
+
workspaceId: row.workspace_id,
|
|
8462
|
+
projectId: row.project_id,
|
|
8463
|
+
eventKey: row.event_key == null ? void 0 : row.event_key,
|
|
8464
|
+
createdAt: new Date(row.created_at)
|
|
8008
8465
|
};
|
|
8009
8466
|
}
|
|
8010
8467
|
};
|
|
8011
8468
|
|
|
8012
8469
|
// src/stores/MenuStore.ts
|
|
8013
|
-
import { Pool as
|
|
8470
|
+
import { Pool as Pool21 } from "pg";
|
|
8014
8471
|
|
|
8015
8472
|
// src/migrations/menu_items_migration.ts
|
|
8016
8473
|
var createMenuItemsTable = {
|
|
@@ -8087,7 +8544,7 @@ var MenuStore = class {
|
|
|
8087
8544
|
this.initialized = true;
|
|
8088
8545
|
return;
|
|
8089
8546
|
}
|
|
8090
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
8547
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool21({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool21(options.poolConfig) : (() => {
|
|
8091
8548
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8092
8549
|
})();
|
|
8093
8550
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8242,7 +8699,7 @@ var MenuStore = class {
|
|
|
8242
8699
|
};
|
|
8243
8700
|
|
|
8244
8701
|
// src/stores/PostgresSharedResourceStore.ts
|
|
8245
|
-
import { Pool as
|
|
8702
|
+
import { Pool as Pool22 } from "pg";
|
|
8246
8703
|
|
|
8247
8704
|
// src/migrations/shared_resources_migration.ts
|
|
8248
8705
|
var createSharedResourcesTable = {
|
|
@@ -8296,7 +8753,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8296
8753
|
this.initialized = true;
|
|
8297
8754
|
return;
|
|
8298
8755
|
}
|
|
8299
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
8756
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool22({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool22(options.poolConfig) : (() => {
|
|
8300
8757
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8301
8758
|
})();
|
|
8302
8759
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8465,7 +8922,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8465
8922
|
};
|
|
8466
8923
|
|
|
8467
8924
|
// src/stores/PostgreSQLCollectionStore.ts
|
|
8468
|
-
import { Pool as
|
|
8925
|
+
import { Pool as Pool23 } from "pg";
|
|
8469
8926
|
var PostgreSQLCollectionStore = class {
|
|
8470
8927
|
constructor(options) {
|
|
8471
8928
|
this.initialized = false;
|
|
@@ -8478,9 +8935,9 @@ var PostgreSQLCollectionStore = class {
|
|
|
8478
8935
|
return;
|
|
8479
8936
|
}
|
|
8480
8937
|
if (typeof options.poolConfig === "string") {
|
|
8481
|
-
this.pool = new
|
|
8938
|
+
this.pool = new Pool23({ connectionString: options.poolConfig });
|
|
8482
8939
|
} else if (options.poolConfig) {
|
|
8483
|
-
this.pool = new
|
|
8940
|
+
this.pool = new Pool23(options.poolConfig);
|
|
8484
8941
|
} else {
|
|
8485
8942
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8486
8943
|
}
|
|
@@ -8595,7 +9052,7 @@ var PostgreSQLCollectionStore = class {
|
|
|
8595
9052
|
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
|
|
8596
9053
|
|
|
8597
9054
|
// src/PGVectorStoreProvider.ts
|
|
8598
|
-
import { Pool as
|
|
9055
|
+
import { Pool as Pool24 } from "pg";
|
|
8599
9056
|
import { PGVectorStore } from "@langchain/community/vectorstores/pgvector";
|
|
8600
9057
|
import { Document } from "@langchain/core/documents";
|
|
8601
9058
|
import { embeddingsLatticeManager } from "@axiom-lattice/core";
|
|
@@ -8669,7 +9126,7 @@ var PGVectorStoreProvider = class {
|
|
|
8669
9126
|
}
|
|
8670
9127
|
};
|
|
8671
9128
|
function createPGVectorStoreProvider(connectionString) {
|
|
8672
|
-
const pool = new
|
|
9129
|
+
const pool = new Pool24({ connectionString });
|
|
8673
9130
|
return {
|
|
8674
9131
|
provider: new PGVectorStoreProvider(pool, connectionString),
|
|
8675
9132
|
pool
|
|
@@ -8958,103 +9415,152 @@ var createCollectionsTable = {
|
|
|
8958
9415
|
}
|
|
8959
9416
|
};
|
|
8960
9417
|
|
|
8961
|
-
// src/migrations/
|
|
8962
|
-
var
|
|
8963
|
-
version:
|
|
8964
|
-
name: "
|
|
9418
|
+
// src/migrations/capability_bundle_migration.ts
|
|
9419
|
+
var createCapabilityBundlesTable = {
|
|
9420
|
+
version: 170,
|
|
9421
|
+
name: "create_capability_bundles_table",
|
|
9422
|
+
up: async (client) => {
|
|
9423
|
+
await client.query(`CREATE TABLE IF NOT EXISTS lattice_capability_bundles (
|
|
9424
|
+
id UUID PRIMARY KEY, tenant_id VARCHAR(255) NOT NULL, bundle_key VARCHAR(255) NOT NULL,
|
|
9425
|
+
name VARCHAR(255) NOT NULL, description TEXT, capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9426
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9427
|
+
UNIQUE (tenant_id, bundle_key)
|
|
9428
|
+
)`);
|
|
9429
|
+
await client.query("CREATE INDEX IF NOT EXISTS idx_lattice_capability_bundles_tenant ON lattice_capability_bundles(tenant_id)");
|
|
9430
|
+
},
|
|
9431
|
+
down: async (client) => {
|
|
9432
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_capability_bundles_tenant");
|
|
9433
|
+
await client.query("DROP TABLE IF EXISTS lattice_capability_bundles");
|
|
9434
|
+
}
|
|
9435
|
+
};
|
|
9436
|
+
|
|
9437
|
+
// src/migrations/project_room_migration.ts
|
|
9438
|
+
var createProjectRoomTables = {
|
|
9439
|
+
version: 172,
|
|
9440
|
+
name: "create_project_room_tables",
|
|
8965
9441
|
up: async (client) => {
|
|
8966
9442
|
await client.query(`
|
|
8967
|
-
CREATE TABLE IF NOT EXISTS
|
|
8968
|
-
id
|
|
8969
|
-
tenant_id
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
detail JSONB,
|
|
8976
|
-
attempt INTEGER,
|
|
9443
|
+
CREATE TABLE IF NOT EXISTS lattice_project_rooms (
|
|
9444
|
+
id VARCHAR(255) NOT NULL,
|
|
9445
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9446
|
+
workspace_id VARCHAR(255) NOT NULL,
|
|
9447
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9448
|
+
name VARCHAR(255) NOT NULL,
|
|
9449
|
+
type VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_rooms_type
|
|
9450
|
+
CHECK (type IN ('main')),
|
|
8977
9451
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8978
|
-
|
|
9452
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9453
|
+
PRIMARY KEY (tenant_id, id),
|
|
9454
|
+
UNIQUE (tenant_id, project_id, type)
|
|
8979
9455
|
)
|
|
8980
9456
|
`);
|
|
8981
9457
|
await client.query(`
|
|
8982
|
-
CREATE
|
|
8983
|
-
|
|
9458
|
+
CREATE TABLE IF NOT EXISTS lattice_project_memberships (
|
|
9459
|
+
id VARCHAR(255) NOT NULL,
|
|
9460
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9461
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9462
|
+
user_id VARCHAR(255) NOT NULL,
|
|
9463
|
+
role VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_memberships_role
|
|
9464
|
+
CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
|
|
9465
|
+
status VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_memberships_status
|
|
9466
|
+
CHECK (status IN ('active', 'removed')),
|
|
9467
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9468
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9469
|
+
PRIMARY KEY (tenant_id, id),
|
|
9470
|
+
UNIQUE (tenant_id, project_id, user_id)
|
|
9471
|
+
)
|
|
8984
9472
|
`);
|
|
8985
9473
|
await client.query(`
|
|
8986
|
-
CREATE INDEX IF NOT EXISTS
|
|
8987
|
-
|
|
9474
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_memberships_project
|
|
9475
|
+
ON lattice_project_memberships (tenant_id, project_id)
|
|
8988
9476
|
`);
|
|
8989
|
-
}
|
|
8990
|
-
};
|
|
8991
|
-
var addWorkItemProjectFieldsMigration = {
|
|
8992
|
-
version: 140,
|
|
8993
|
-
name: "add_task_work_item_project_fields",
|
|
8994
|
-
up: async (client) => {
|
|
8995
9477
|
await client.query(`
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
9478
|
+
CREATE TABLE IF NOT EXISTS lattice_project_bot_memberships (
|
|
9479
|
+
id VARCHAR(255) NOT NULL,
|
|
9480
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9481
|
+
workspace_id VARCHAR(255) NOT NULL,
|
|
9482
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9483
|
+
room_id VARCHAR(255) NOT NULL,
|
|
9484
|
+
assistant_id VARCHAR(255) NOT NULL,
|
|
9485
|
+
role VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_bot_memberships_role
|
|
9486
|
+
CHECK (role IN ('coordinator', 'specialist')),
|
|
9487
|
+
title VARCHAR(255) NOT NULL,
|
|
9488
|
+
responsibility TEXT,
|
|
9489
|
+
mention_name VARCHAR(255) NOT NULL,
|
|
9490
|
+
status VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_bot_memberships_status
|
|
9491
|
+
CHECK (status IN ('active', 'paused', 'removed')),
|
|
9492
|
+
room_thread_id VARCHAR(255) NOT NULL,
|
|
9493
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9494
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9495
|
+
PRIMARY KEY (tenant_id, id),
|
|
9496
|
+
UNIQUE (tenant_id, project_id, assistant_id)
|
|
9497
|
+
)
|
|
8999
9498
|
`);
|
|
9000
9499
|
await client.query(`
|
|
9001
|
-
CREATE INDEX IF NOT EXISTS
|
|
9002
|
-
|
|
9500
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_project_bot_memberships_coordinator
|
|
9501
|
+
ON lattice_project_bot_memberships (tenant_id, project_id)
|
|
9502
|
+
WHERE role = 'coordinator' AND status IN ('active', 'paused')
|
|
9003
9503
|
`);
|
|
9004
|
-
}
|
|
9005
|
-
};
|
|
9006
|
-
var addTaskWorkItemEventKeyMigration = {
|
|
9007
|
-
version: 168,
|
|
9008
|
-
name: "add_task_work_item_event_key",
|
|
9009
|
-
up: async (client) => {
|
|
9010
9504
|
await client.query(`
|
|
9011
|
-
|
|
9012
|
-
|
|
9505
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_project_bot_memberships_mention
|
|
9506
|
+
ON lattice_project_bot_memberships (tenant_id, room_id, mention_name)
|
|
9507
|
+
WHERE status IN ('active', 'paused')
|
|
9013
9508
|
`);
|
|
9014
9509
|
await client.query(`
|
|
9015
|
-
CREATE
|
|
9016
|
-
|
|
9510
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_project
|
|
9511
|
+
ON lattice_project_bot_memberships (tenant_id, project_id)
|
|
9017
9512
|
`);
|
|
9018
|
-
}
|
|
9019
|
-
};
|
|
9020
|
-
var addTaskWorkItemPendingIndexesMigration = {
|
|
9021
|
-
version: 171,
|
|
9022
|
-
name: "add_task_work_item_pending_indexes",
|
|
9023
|
-
up: async (client) => {
|
|
9024
9513
|
await client.query(`
|
|
9025
|
-
CREATE INDEX IF NOT EXISTS
|
|
9026
|
-
|
|
9514
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_room
|
|
9515
|
+
ON lattice_project_bot_memberships (tenant_id, room_id)
|
|
9027
9516
|
`);
|
|
9028
9517
|
await client.query(`
|
|
9029
|
-
CREATE
|
|
9030
|
-
|
|
9031
|
-
|
|
9518
|
+
CREATE TABLE IF NOT EXISTS lattice_project_room_messages (
|
|
9519
|
+
id VARCHAR(255) NOT NULL,
|
|
9520
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9521
|
+
workspace_id VARCHAR(255) NOT NULL,
|
|
9522
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9523
|
+
room_id VARCHAR(255) NOT NULL,
|
|
9524
|
+
author JSONB NOT NULL,
|
|
9525
|
+
content JSONB NOT NULL,
|
|
9526
|
+
mentions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9527
|
+
reply_to_message_id VARCHAR(255),
|
|
9528
|
+
source VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_room_messages_source
|
|
9529
|
+
CHECK (source IN ('user', 'agent', 'task', 'routine', 'system')),
|
|
9530
|
+
source_id VARCHAR(255),
|
|
9531
|
+
idempotency_key VARCHAR(255),
|
|
9532
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9533
|
+
PRIMARY KEY (tenant_id, id)
|
|
9534
|
+
)
|
|
9535
|
+
`);
|
|
9536
|
+
await client.query(`
|
|
9537
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_project_room_messages_idempotency
|
|
9538
|
+
ON lattice_project_room_messages (tenant_id, room_id, idempotency_key)
|
|
9539
|
+
WHERE idempotency_key IS NOT NULL
|
|
9540
|
+
`);
|
|
9541
|
+
await client.query(`
|
|
9542
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_room_messages_room_created
|
|
9543
|
+
ON lattice_project_room_messages (tenant_id, room_id, created_at DESC, id DESC)
|
|
9032
9544
|
`);
|
|
9033
|
-
}
|
|
9034
|
-
};
|
|
9035
|
-
|
|
9036
|
-
// src/migrations/capability_bundle_migration.ts
|
|
9037
|
-
var createCapabilityBundlesTable = {
|
|
9038
|
-
version: 170,
|
|
9039
|
-
name: "create_capability_bundles_table",
|
|
9040
|
-
up: async (client) => {
|
|
9041
|
-
await client.query(`CREATE TABLE IF NOT EXISTS lattice_capability_bundles (
|
|
9042
|
-
id UUID PRIMARY KEY, tenant_id VARCHAR(255) NOT NULL, bundle_key VARCHAR(255) NOT NULL,
|
|
9043
|
-
name VARCHAR(255) NOT NULL, description TEXT, capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9044
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9045
|
-
UNIQUE (tenant_id, bundle_key)
|
|
9046
|
-
)`);
|
|
9047
|
-
await client.query("CREATE INDEX IF NOT EXISTS idx_lattice_capability_bundles_tenant ON lattice_capability_bundles(tenant_id)");
|
|
9048
9545
|
},
|
|
9049
9546
|
down: async (client) => {
|
|
9050
|
-
await client.query("DROP INDEX IF EXISTS
|
|
9051
|
-
await client.query("DROP
|
|
9547
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_room_messages_room_created");
|
|
9548
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_project_room_messages_idempotency");
|
|
9549
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_room_messages");
|
|
9550
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_bot_memberships_room");
|
|
9551
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_bot_memberships_project");
|
|
9552
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_project_bot_memberships_mention");
|
|
9553
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_project_bot_memberships_coordinator");
|
|
9554
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_bot_memberships");
|
|
9555
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_memberships_project");
|
|
9556
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_memberships");
|
|
9557
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_rooms");
|
|
9052
9558
|
}
|
|
9053
9559
|
};
|
|
9054
9560
|
|
|
9055
9561
|
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
9056
9562
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9057
|
-
import { Pool as
|
|
9563
|
+
import { Pool as Pool25 } from "pg";
|
|
9058
9564
|
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
9059
9565
|
function map(row) {
|
|
9060
9566
|
return {
|
|
@@ -9087,7 +9593,7 @@ var PostgreSQLCapabilityBundleStore = class {
|
|
|
9087
9593
|
this.initialized = true;
|
|
9088
9594
|
return;
|
|
9089
9595
|
}
|
|
9090
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
9596
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool25({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool25(options.poolConfig) : (() => {
|
|
9091
9597
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9092
9598
|
})();
|
|
9093
9599
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9276,9 +9782,589 @@ var PostgreSQLCapabilityBundleStore = class {
|
|
|
9276
9782
|
}
|
|
9277
9783
|
};
|
|
9278
9784
|
|
|
9785
|
+
// src/stores/PostgreSQLProjectRoomStore.ts
|
|
9786
|
+
function isValidDate(value) {
|
|
9787
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
9788
|
+
}
|
|
9789
|
+
function mapRow2(row) {
|
|
9790
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || row.type !== "main" || typeof row.name !== "string" || !isValidDate(row.created_at) || !isValidDate(row.updated_at)) {
|
|
9791
|
+
throw new Error("Invalid project room row");
|
|
9792
|
+
}
|
|
9793
|
+
return {
|
|
9794
|
+
id: row.id,
|
|
9795
|
+
tenantId: row.tenant_id,
|
|
9796
|
+
workspaceId: row.workspace_id,
|
|
9797
|
+
projectId: row.project_id,
|
|
9798
|
+
type: "main",
|
|
9799
|
+
name: row.name,
|
|
9800
|
+
createdAt: row.created_at,
|
|
9801
|
+
updatedAt: row.updated_at
|
|
9802
|
+
};
|
|
9803
|
+
}
|
|
9804
|
+
var PostgreSQLProjectRoomStore = class {
|
|
9805
|
+
/** Creates a store using an externally managed pool; the pool is not migrated or closed. */
|
|
9806
|
+
constructor(options) {
|
|
9807
|
+
this.pool = options.pool;
|
|
9808
|
+
}
|
|
9809
|
+
/** Creates or returns the single persisted main room for a project. */
|
|
9810
|
+
async ensureMainRoom(input) {
|
|
9811
|
+
const result = await this.pool.query(
|
|
9812
|
+
`INSERT INTO lattice_project_rooms
|
|
9813
|
+
(id, tenant_id, workspace_id, project_id, type, name)
|
|
9814
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
9815
|
+
ON CONFLICT (tenant_id, project_id, type)
|
|
9816
|
+
DO UPDATE SET updated_at = lattice_project_rooms.updated_at
|
|
9817
|
+
RETURNING id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at`,
|
|
9818
|
+
[input.id, input.tenantId, input.workspaceId, input.projectId, "main", input.name]
|
|
9819
|
+
);
|
|
9820
|
+
return mapRow2(result.rows[0]);
|
|
9821
|
+
}
|
|
9822
|
+
/** Finds a tenant-scoped project's main room, or returns null. */
|
|
9823
|
+
async getMainRoom(tenantId, projectId) {
|
|
9824
|
+
const result = await this.pool.query(
|
|
9825
|
+
"SELECT id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at FROM lattice_project_rooms WHERE tenant_id = $1 AND project_id = $2 AND type = 'main'",
|
|
9826
|
+
[tenantId, projectId]
|
|
9827
|
+
);
|
|
9828
|
+
return result.rows[0] ? mapRow2(result.rows[0]) : null;
|
|
9829
|
+
}
|
|
9830
|
+
};
|
|
9831
|
+
|
|
9832
|
+
// src/stores/PostgreSQLProjectMembershipStore.ts
|
|
9833
|
+
var DuplicateProjectMembershipError = class extends Error {
|
|
9834
|
+
constructor() {
|
|
9835
|
+
super("Project membership already exists for tenant, project, and user");
|
|
9836
|
+
this.name = "DuplicateProjectMembershipError";
|
|
9837
|
+
}
|
|
9838
|
+
};
|
|
9839
|
+
var ProjectMembershipIdConflictError = class extends Error {
|
|
9840
|
+
constructor() {
|
|
9841
|
+
super("Project membership ID already exists for tenant");
|
|
9842
|
+
this.name = "ProjectMembershipIdConflictError";
|
|
9843
|
+
}
|
|
9844
|
+
};
|
|
9845
|
+
function isDate(value) {
|
|
9846
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
9847
|
+
}
|
|
9848
|
+
function isRole(value) {
|
|
9849
|
+
return value === "owner" || value === "admin" || value === "member" || value === "viewer";
|
|
9850
|
+
}
|
|
9851
|
+
function isStatus2(value) {
|
|
9852
|
+
return value === "active" || value === "removed";
|
|
9853
|
+
}
|
|
9854
|
+
function mapRow3(row) {
|
|
9855
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.project_id !== "string" || typeof row.user_id !== "string" || !isRole(row.role) || !isStatus2(row.status) || !isDate(row.joined_at) || !isDate(row.updated_at)) throw new Error("Invalid project membership row");
|
|
9856
|
+
return {
|
|
9857
|
+
id: row.id,
|
|
9858
|
+
tenantId: row.tenant_id,
|
|
9859
|
+
projectId: row.project_id,
|
|
9860
|
+
userId: row.user_id,
|
|
9861
|
+
role: row.role,
|
|
9862
|
+
status: row.status,
|
|
9863
|
+
joinedAt: row.joined_at,
|
|
9864
|
+
updatedAt: row.updated_at
|
|
9865
|
+
};
|
|
9866
|
+
}
|
|
9867
|
+
function isDuplicate2(error) {
|
|
9868
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
9869
|
+
}
|
|
9870
|
+
function mapDuplicate(error) {
|
|
9871
|
+
if (isDuplicate2(error) && typeof error.constraint === "string" && error.constraint.includes("project_id_user_id")) throw new DuplicateProjectMembershipError();
|
|
9872
|
+
if (isDuplicate2(error)) throw new ProjectMembershipIdConflictError();
|
|
9873
|
+
throw error;
|
|
9874
|
+
}
|
|
9875
|
+
var columns = "id, tenant_id, project_id, user_id, role, status, joined_at, updated_at";
|
|
9876
|
+
var PostgreSQLProjectMembershipStore = class {
|
|
9877
|
+
/** Creates a store using an externally managed pool; the pool is not migrated or closed. */
|
|
9878
|
+
constructor(options) {
|
|
9879
|
+
this.pool = options.pool;
|
|
9880
|
+
}
|
|
9881
|
+
/** Lists memberships in stable joined-time and ID order. */
|
|
9882
|
+
async list(tenantId, projectId) {
|
|
9883
|
+
const result = await this.pool.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 ORDER BY joined_at ASC, id ASC`, [tenantId, projectId]);
|
|
9884
|
+
return result.rows.map(mapRow3);
|
|
9885
|
+
}
|
|
9886
|
+
/** Finds a membership by exact tenant, project, and user identity. */
|
|
9887
|
+
async findByUser(tenantId, projectId, userId) {
|
|
9888
|
+
const result = await this.pool.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 AND user_id = $3`, [tenantId, projectId, userId]);
|
|
9889
|
+
return result.rows[0] ? mapRow3(result.rows[0]) : null;
|
|
9890
|
+
}
|
|
9891
|
+
/** Inserts a membership and maps database uniqueness errors to stable typed errors. */
|
|
9892
|
+
async create(input) {
|
|
9893
|
+
const client = await this.pool.connect();
|
|
9894
|
+
try {
|
|
9895
|
+
await client.query("BEGIN");
|
|
9896
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${input.projectId}`]);
|
|
9897
|
+
const result = await client.query(`INSERT INTO lattice_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, date_trunc('milliseconds', clock_timestamp()), date_trunc('milliseconds', clock_timestamp())) RETURNING ${columns}`, [input.id, input.tenantId, input.projectId, input.userId, input.role, input.status]);
|
|
9898
|
+
const membership = mapRow3(result.rows[0]);
|
|
9899
|
+
await client.query("COMMIT");
|
|
9900
|
+
return membership;
|
|
9901
|
+
} catch (error) {
|
|
9902
|
+
try {
|
|
9903
|
+
await client.query("ROLLBACK");
|
|
9904
|
+
} catch {
|
|
9905
|
+
}
|
|
9906
|
+
return mapDuplicate(error);
|
|
9907
|
+
} finally {
|
|
9908
|
+
client.release();
|
|
9909
|
+
}
|
|
9910
|
+
}
|
|
9911
|
+
/** Atomically creates the first active owner while serializing the project scope. */
|
|
9912
|
+
async createInitialOwner(input) {
|
|
9913
|
+
const client = await this.pool.connect();
|
|
9914
|
+
try {
|
|
9915
|
+
await client.query("BEGIN");
|
|
9916
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${input.projectId}`]);
|
|
9917
|
+
const existingResult = await client.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 FOR UPDATE`, [input.tenantId, input.projectId]);
|
|
9918
|
+
if (existingResult.rows.length > 0) {
|
|
9919
|
+
const existing = existingResult.rows.map(mapRow3).find((item) => item.userId === input.userId && item.role === "owner" && item.status === "active");
|
|
9920
|
+
await client.query("COMMIT");
|
|
9921
|
+
return existing ? { kind: "existing", membership: existing } : { kind: "already_initialized" };
|
|
9922
|
+
}
|
|
9923
|
+
const idResult = await client.query("SELECT id FROM lattice_project_memberships WHERE tenant_id = $1 AND id = $2", [input.tenantId, input.id]);
|
|
9924
|
+
if (idResult.rows.length > 0) throw new ProjectMembershipIdConflictError();
|
|
9925
|
+
const inserted = await client.query(`INSERT INTO lattice_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES ($1, $2, $3, $4, 'owner', 'active', date_trunc('milliseconds', clock_timestamp()), date_trunc('milliseconds', clock_timestamp())) RETURNING ${columns}`, [input.id, input.tenantId, input.projectId, input.userId]);
|
|
9926
|
+
const membership = mapRow3(inserted.rows[0]);
|
|
9927
|
+
await client.query("COMMIT");
|
|
9928
|
+
return { kind: "created", membership };
|
|
9929
|
+
} catch (error) {
|
|
9930
|
+
try {
|
|
9931
|
+
await client.query("ROLLBACK");
|
|
9932
|
+
} catch {
|
|
9933
|
+
}
|
|
9934
|
+
if (isDuplicate2(error)) return mapDuplicate(error);
|
|
9935
|
+
throw error;
|
|
9936
|
+
} finally {
|
|
9937
|
+
client.release();
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
/** Updates a role under a project lock with exact timestamp and owner safeguards. */
|
|
9941
|
+
async updateRole(input) {
|
|
9942
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, input.role, false);
|
|
9943
|
+
}
|
|
9944
|
+
/** Marks a membership removed under a project lock with exact timestamp and owner safeguards. */
|
|
9945
|
+
async remove(input) {
|
|
9946
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, void 0, true);
|
|
9947
|
+
}
|
|
9948
|
+
async mutate(tenantId, id, expected, role, remove) {
|
|
9949
|
+
const client = await this.pool.connect();
|
|
9950
|
+
try {
|
|
9951
|
+
await client.query("BEGIN");
|
|
9952
|
+
const identity = await client.query("SELECT project_id FROM lattice_project_memberships WHERE tenant_id = $1 AND id = $2", [tenantId, id]);
|
|
9953
|
+
if (!identity.rows[0] || typeof identity.rows[0].project_id !== "string") {
|
|
9954
|
+
await client.query("COMMIT");
|
|
9955
|
+
return { kind: "not_found" };
|
|
9956
|
+
}
|
|
9957
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${tenantId}:${identity.rows[0].project_id}`]);
|
|
9958
|
+
const activeRows = await client.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 AND status = 'active' FOR UPDATE`, [tenantId, identity.rows[0].project_id]);
|
|
9959
|
+
const lockedMemberships = activeRows.rows.map(mapRow3);
|
|
9960
|
+
let current = lockedMemberships.find((membership) => membership.id === id);
|
|
9961
|
+
if (!current) {
|
|
9962
|
+
const target = await client.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND id = $2 FOR UPDATE`, [tenantId, id]);
|
|
9963
|
+
if (!target.rows[0]) {
|
|
9964
|
+
await client.query("COMMIT");
|
|
9965
|
+
return { kind: "not_found" };
|
|
9966
|
+
}
|
|
9967
|
+
current = mapRow3(target.rows[0]);
|
|
9968
|
+
}
|
|
9969
|
+
if (current.updatedAt.getTime() !== expected.getTime()) {
|
|
9970
|
+
await client.query("COMMIT");
|
|
9971
|
+
return { kind: "conflict" };
|
|
9972
|
+
}
|
|
9973
|
+
if (current.role === "owner" && current.status === "active" && (remove || role !== "owner")) {
|
|
9974
|
+
const owners = lockedMemberships.filter((membership) => membership.id !== id && membership.role === "owner");
|
|
9975
|
+
if (owners.length === 0) {
|
|
9976
|
+
await client.query("COMMIT");
|
|
9977
|
+
return { kind: "last_owner" };
|
|
9978
|
+
}
|
|
9979
|
+
}
|
|
9980
|
+
const result = await client.query(remove ? `UPDATE lattice_project_memberships SET status = 'removed', updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp())) WHERE tenant_id = $1 AND id = $2 AND date_trunc('milliseconds', updated_at) = $3 RETURNING ${columns}` : `UPDATE lattice_project_memberships SET role = $4, updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp())) WHERE tenant_id = $1 AND id = $2 AND date_trunc('milliseconds', updated_at) = $3 RETURNING ${columns}`, remove ? [tenantId, id, expected] : [tenantId, id, expected, role]);
|
|
9981
|
+
if (!result.rows[0]) {
|
|
9982
|
+
await client.query("COMMIT");
|
|
9983
|
+
return { kind: "conflict" };
|
|
9984
|
+
}
|
|
9985
|
+
await client.query("COMMIT");
|
|
9986
|
+
return { kind: remove ? "removed" : "updated", membership: mapRow3(result.rows[0]) };
|
|
9987
|
+
} catch (error) {
|
|
9988
|
+
try {
|
|
9989
|
+
await client.query("ROLLBACK");
|
|
9990
|
+
} catch {
|
|
9991
|
+
}
|
|
9992
|
+
throw error;
|
|
9993
|
+
} finally {
|
|
9994
|
+
client.release();
|
|
9995
|
+
}
|
|
9996
|
+
}
|
|
9997
|
+
};
|
|
9998
|
+
|
|
9999
|
+
// src/stores/PostgreSQLProjectBotMembershipStore.ts
|
|
10000
|
+
var columns2 = "id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at";
|
|
10001
|
+
var ProjectBotMembershipIdConflictError = class extends Error {
|
|
10002
|
+
/** Creates an accurate tenant-scoped membership ID collision error. */
|
|
10003
|
+
constructor(tenantId, id) {
|
|
10004
|
+
super(`Project bot membership ID '${id}' already exists in tenant '${tenantId}'`);
|
|
10005
|
+
this.name = "ProjectBotMembershipIdConflictError";
|
|
10006
|
+
}
|
|
10007
|
+
};
|
|
10008
|
+
function isDate2(value) {
|
|
10009
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
10010
|
+
}
|
|
10011
|
+
function isRole2(value) {
|
|
10012
|
+
return value === "coordinator" || value === "specialist";
|
|
10013
|
+
}
|
|
10014
|
+
function isStatus3(value) {
|
|
10015
|
+
return value === "active" || value === "paused" || value === "removed";
|
|
10016
|
+
}
|
|
10017
|
+
function mapRow4(row) {
|
|
10018
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || typeof row.assistant_id !== "string" || !isRole2(row.role) || typeof row.title !== "string" || row.responsibility !== null && typeof row.responsibility !== "string" || typeof row.mention_name !== "string" || !isStatus3(row.status) || typeof row.room_thread_id !== "string" || !isDate2(row.joined_at) || !isDate2(row.updated_at)) {
|
|
10019
|
+
throw new Error("Invalid project bot membership row");
|
|
10020
|
+
}
|
|
10021
|
+
return {
|
|
10022
|
+
id: row.id,
|
|
10023
|
+
tenantId: row.tenant_id,
|
|
10024
|
+
workspaceId: row.workspace_id,
|
|
10025
|
+
projectId: row.project_id,
|
|
10026
|
+
roomId: row.room_id,
|
|
10027
|
+
assistantId: row.assistant_id,
|
|
10028
|
+
role: row.role,
|
|
10029
|
+
title: row.title,
|
|
10030
|
+
...row.responsibility === null ? {} : { responsibility: row.responsibility },
|
|
10031
|
+
mentionName: row.mention_name,
|
|
10032
|
+
status: row.status,
|
|
10033
|
+
roomThreadId: row.room_thread_id,
|
|
10034
|
+
joinedAt: row.joined_at,
|
|
10035
|
+
updatedAt: row.updated_at
|
|
10036
|
+
};
|
|
10037
|
+
}
|
|
10038
|
+
function isUniqueViolation(error) {
|
|
10039
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
10040
|
+
}
|
|
10041
|
+
function constraintConflict(error) {
|
|
10042
|
+
if (!isUniqueViolation(error) || typeof error.constraint !== "string") return void 0;
|
|
10043
|
+
if (error.constraint === "uq_lattice_project_bot_memberships_coordinator") return "coordinator_conflict";
|
|
10044
|
+
if (error.constraint === "uq_lattice_project_bot_memberships_mention") return "mention_conflict";
|
|
10045
|
+
return void 0;
|
|
10046
|
+
}
|
|
10047
|
+
async function rollback(client) {
|
|
10048
|
+
try {
|
|
10049
|
+
await client.query("ROLLBACK");
|
|
10050
|
+
} catch {
|
|
10051
|
+
}
|
|
10052
|
+
}
|
|
10053
|
+
var PostgreSQLProjectBotMembershipStore = class {
|
|
10054
|
+
/** Creates a store using an externally managed shared pool. */
|
|
10055
|
+
constructor(options) {
|
|
10056
|
+
this.pool = options.pool;
|
|
10057
|
+
}
|
|
10058
|
+
/** Lists retained memberships in stable join order. */
|
|
10059
|
+
async list(tenantId, projectId) {
|
|
10060
|
+
const result = await this.pool.query(
|
|
10061
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND project_id = $2 ORDER BY joined_at ASC, id ASC`,
|
|
10062
|
+
[tenantId, projectId]
|
|
10063
|
+
);
|
|
10064
|
+
return result.rows.map(mapRow4);
|
|
10065
|
+
}
|
|
10066
|
+
/** Finds a membership by tenant-scoped ID. */
|
|
10067
|
+
async findById(tenantId, id) {
|
|
10068
|
+
const result = await this.pool.query(
|
|
10069
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2`,
|
|
10070
|
+
[tenantId, id]
|
|
10071
|
+
);
|
|
10072
|
+
return result.rows[0] ? mapRow4(result.rows[0]) : null;
|
|
10073
|
+
}
|
|
10074
|
+
/** Finds an assistant's durable membership in a tenant-scoped project. */
|
|
10075
|
+
async findByAssistant(tenantId, projectId, assistantId) {
|
|
10076
|
+
const result = await this.pool.query(
|
|
10077
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND project_id = $2 AND assistant_id = $3`,
|
|
10078
|
+
[tenantId, projectId, assistantId]
|
|
10079
|
+
);
|
|
10080
|
+
return result.rows[0] ? mapRow4(result.rows[0]) : null;
|
|
10081
|
+
}
|
|
10082
|
+
/** Inserts a new membership or reactivates the assistant's durable membership atomically. */
|
|
10083
|
+
async save(input) {
|
|
10084
|
+
const client = await this.pool.connect();
|
|
10085
|
+
try {
|
|
10086
|
+
await client.query("BEGIN");
|
|
10087
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${input.projectId}`]);
|
|
10088
|
+
const existingResult = await client.query(
|
|
10089
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND project_id = $2 AND assistant_id = $3 FOR UPDATE`,
|
|
10090
|
+
[input.tenantId, input.projectId, input.assistantId]
|
|
10091
|
+
);
|
|
10092
|
+
const existing = existingResult.rows[0] ? mapRow4(existingResult.rows[0]) : void 0;
|
|
10093
|
+
let result;
|
|
10094
|
+
if (existing) {
|
|
10095
|
+
result = await client.query(
|
|
10096
|
+
`UPDATE lattice_project_bot_memberships
|
|
10097
|
+
SET role = $3, title = $4, responsibility = $5, mention_name = $6, status = 'active',
|
|
10098
|
+
updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp()))
|
|
10099
|
+
WHERE tenant_id = $1 AND id = $2 RETURNING ${columns2}`,
|
|
10100
|
+
[input.tenantId, existing.id, input.role, input.title, input.responsibility ?? null, input.mentionName]
|
|
10101
|
+
);
|
|
10102
|
+
} else {
|
|
10103
|
+
const idResult = await client.query(
|
|
10104
|
+
"SELECT id FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2",
|
|
10105
|
+
[input.tenantId, input.id]
|
|
10106
|
+
);
|
|
10107
|
+
if (idResult.rows.length > 0) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
10108
|
+
result = await client.query(
|
|
10109
|
+
`INSERT INTO lattice_project_bot_memberships
|
|
10110
|
+
(id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at)
|
|
10111
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'active', $11, date_trunc('milliseconds', clock_timestamp()), date_trunc('milliseconds', clock_timestamp()))
|
|
10112
|
+
RETURNING ${columns2}`,
|
|
10113
|
+
[
|
|
10114
|
+
input.id,
|
|
10115
|
+
input.tenantId,
|
|
10116
|
+
input.workspaceId,
|
|
10117
|
+
input.projectId,
|
|
10118
|
+
input.roomId,
|
|
10119
|
+
input.assistantId,
|
|
10120
|
+
input.role,
|
|
10121
|
+
input.title,
|
|
10122
|
+
input.responsibility ?? null,
|
|
10123
|
+
input.mentionName,
|
|
10124
|
+
input.roomThreadId
|
|
10125
|
+
]
|
|
10126
|
+
);
|
|
10127
|
+
}
|
|
10128
|
+
const membership = mapRow4(result.rows[0]);
|
|
10129
|
+
await client.query("COMMIT");
|
|
10130
|
+
return { kind: existing === void 0 ? "created" : existing.status === "removed" ? "reactivated" : "updated", membership };
|
|
10131
|
+
} catch (error) {
|
|
10132
|
+
await rollback(client);
|
|
10133
|
+
const conflict = constraintConflict(error);
|
|
10134
|
+
if (conflict) return { kind: conflict };
|
|
10135
|
+
if (isUniqueViolation(error) && error.constraint === "lattice_project_bot_memberships_pkey") {
|
|
10136
|
+
throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
10137
|
+
}
|
|
10138
|
+
throw error;
|
|
10139
|
+
} finally {
|
|
10140
|
+
client.release();
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
10143
|
+
/** Applies a mutable-field patch with project serialization and millisecond-safe optimistic concurrency. */
|
|
10144
|
+
async update(input) {
|
|
10145
|
+
const client = await this.pool.connect();
|
|
10146
|
+
try {
|
|
10147
|
+
await client.query("BEGIN");
|
|
10148
|
+
const identity = await client.query(
|
|
10149
|
+
"SELECT project_id FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2",
|
|
10150
|
+
[input.tenantId, input.id]
|
|
10151
|
+
);
|
|
10152
|
+
if (!identity.rows[0]) {
|
|
10153
|
+
await client.query("COMMIT");
|
|
10154
|
+
return { kind: "not_found" };
|
|
10155
|
+
}
|
|
10156
|
+
if (typeof identity.rows[0].project_id !== "string") throw new Error("Invalid project bot membership row");
|
|
10157
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${identity.rows[0].project_id}`]);
|
|
10158
|
+
const locked = await client.query(
|
|
10159
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2 FOR UPDATE`,
|
|
10160
|
+
[input.tenantId, input.id]
|
|
10161
|
+
);
|
|
10162
|
+
if (!locked.rows[0]) {
|
|
10163
|
+
await client.query("COMMIT");
|
|
10164
|
+
return { kind: "not_found" };
|
|
10165
|
+
}
|
|
10166
|
+
const existing = mapRow4(locked.rows[0]);
|
|
10167
|
+
if (existing.updatedAt.getTime() !== input.expectedUpdatedAt.getTime()) {
|
|
10168
|
+
await client.query("COMMIT");
|
|
10169
|
+
return { kind: "conflict" };
|
|
10170
|
+
}
|
|
10171
|
+
const candidate = { ...existing, ...input.patch };
|
|
10172
|
+
const updated = await client.query(
|
|
10173
|
+
`UPDATE lattice_project_bot_memberships
|
|
10174
|
+
SET role = $4, title = $5, responsibility = $6, mention_name = $7, status = $8,
|
|
10175
|
+
updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp()))
|
|
10176
|
+
WHERE tenant_id = $1 AND id = $2 AND date_trunc('milliseconds', updated_at) = $3
|
|
10177
|
+
RETURNING ${columns2}`,
|
|
10178
|
+
[
|
|
10179
|
+
input.tenantId,
|
|
10180
|
+
input.id,
|
|
10181
|
+
input.expectedUpdatedAt,
|
|
10182
|
+
candidate.role,
|
|
10183
|
+
candidate.title,
|
|
10184
|
+
candidate.responsibility ?? null,
|
|
10185
|
+
candidate.mentionName,
|
|
10186
|
+
candidate.status
|
|
10187
|
+
]
|
|
10188
|
+
);
|
|
10189
|
+
if (!updated.rows[0]) {
|
|
10190
|
+
await client.query("COMMIT");
|
|
10191
|
+
return { kind: "conflict" };
|
|
10192
|
+
}
|
|
10193
|
+
const membership = mapRow4(updated.rows[0]);
|
|
10194
|
+
await client.query("COMMIT");
|
|
10195
|
+
return { kind: "updated", membership };
|
|
10196
|
+
} catch (error) {
|
|
10197
|
+
await rollback(client);
|
|
10198
|
+
const conflict = constraintConflict(error);
|
|
10199
|
+
if (conflict) return { kind: conflict };
|
|
10200
|
+
throw error;
|
|
10201
|
+
} finally {
|
|
10202
|
+
client.release();
|
|
10203
|
+
}
|
|
10204
|
+
}
|
|
10205
|
+
};
|
|
10206
|
+
|
|
10207
|
+
// src/stores/PostgreSQLProjectRoomMessageStore.ts
|
|
10208
|
+
var columns3 = "id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at";
|
|
10209
|
+
var ProjectRoomMessageIdConflictError = class extends Error {
|
|
10210
|
+
/** Creates an accurate tenant-scoped message ID collision error. */
|
|
10211
|
+
constructor(tenantId, id) {
|
|
10212
|
+
super(`Project room message ID '${id}' already exists in tenant '${tenantId}'`);
|
|
10213
|
+
this.name = "ProjectRoomMessageIdConflictError";
|
|
10214
|
+
}
|
|
10215
|
+
};
|
|
10216
|
+
var DuplicateProjectRoomMessageIdempotencyKeyError = class extends Error {
|
|
10217
|
+
/** Creates an accurate tenant and room-scoped idempotency collision error. */
|
|
10218
|
+
constructor(tenantId, roomId, idempotencyKey) {
|
|
10219
|
+
super(`Project room message idempotency key '${idempotencyKey}' already exists in tenant '${tenantId}' room '${roomId}'`);
|
|
10220
|
+
this.name = "DuplicateProjectRoomMessageIdempotencyKeyError";
|
|
10221
|
+
}
|
|
10222
|
+
};
|
|
10223
|
+
function isRecord3(value) {
|
|
10224
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10225
|
+
}
|
|
10226
|
+
function hasOnlyKeys(value, keys) {
|
|
10227
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
10228
|
+
}
|
|
10229
|
+
function mapAuthor(value) {
|
|
10230
|
+
if (!isRecord3(value) || typeof value.type !== "string") return void 0;
|
|
10231
|
+
if (value.type === "human" && hasOnlyKeys(value, ["type", "userId"]) && typeof value.userId === "string") return { type: "human", userId: value.userId };
|
|
10232
|
+
if (value.type === "bot" && hasOnlyKeys(value, ["type", "membershipId", "assistantId"]) && typeof value.membershipId === "string" && typeof value.assistantId === "string") {
|
|
10233
|
+
return { type: "bot", membershipId: value.membershipId, assistantId: value.assistantId };
|
|
10234
|
+
}
|
|
10235
|
+
if (value.type === "system" && hasOnlyKeys(value, ["type"])) return { type: "system" };
|
|
10236
|
+
return void 0;
|
|
10237
|
+
}
|
|
10238
|
+
function mapMention(value) {
|
|
10239
|
+
if (!isRecord3(value) || typeof value.type !== "string") return void 0;
|
|
10240
|
+
if (value.type === "bot" && hasOnlyKeys(value, ["type", "membershipId"]) && typeof value.membershipId === "string") {
|
|
10241
|
+
return { type: "bot", membershipId: value.membershipId };
|
|
10242
|
+
}
|
|
10243
|
+
if (value.type === "team" && hasOnlyKeys(value, ["type"])) return { type: "team" };
|
|
10244
|
+
return void 0;
|
|
10245
|
+
}
|
|
10246
|
+
function isSource(value) {
|
|
10247
|
+
return value === "user" || value === "agent" || value === "task" || value === "routine" || value === "system";
|
|
10248
|
+
}
|
|
10249
|
+
function mapRow5(row) {
|
|
10250
|
+
const author = mapAuthor(row.author);
|
|
10251
|
+
const content = isRecord3(row.content) && row.content.type === "text" && typeof row.content.text === "string" && hasOnlyKeys(row.content, ["type", "text"]) ? { type: "text", text: row.content.text } : void 0;
|
|
10252
|
+
const mentions = Array.isArray(row.mentions) ? row.mentions.map(mapMention) : void 0;
|
|
10253
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || !author || !content || !mentions || mentions.some((mention) => mention === void 0) || row.reply_to_message_id !== null && typeof row.reply_to_message_id !== "string" || !isSource(row.source) || row.source_id !== null && typeof row.source_id !== "string" || row.idempotency_key !== null && typeof row.idempotency_key !== "string" || !(row.created_at instanceof Date) || Number.isNaN(row.created_at.getTime())) {
|
|
10254
|
+
throw new Error("Invalid project room message row");
|
|
10255
|
+
}
|
|
10256
|
+
return {
|
|
10257
|
+
id: row.id,
|
|
10258
|
+
tenantId: row.tenant_id,
|
|
10259
|
+
workspaceId: row.workspace_id,
|
|
10260
|
+
projectId: row.project_id,
|
|
10261
|
+
roomId: row.room_id,
|
|
10262
|
+
author,
|
|
10263
|
+
content,
|
|
10264
|
+
mentions,
|
|
10265
|
+
...row.reply_to_message_id === null ? {} : { replyToMessageId: row.reply_to_message_id },
|
|
10266
|
+
source: row.source,
|
|
10267
|
+
...row.source_id === null ? {} : { sourceId: row.source_id },
|
|
10268
|
+
...row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key },
|
|
10269
|
+
createdAt: row.created_at
|
|
10270
|
+
};
|
|
10271
|
+
}
|
|
10272
|
+
function isUniqueViolation2(error) {
|
|
10273
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
10274
|
+
}
|
|
10275
|
+
var PostgreSQLProjectRoomMessageStore = class {
|
|
10276
|
+
/** Creates a store using an externally managed shared pool. */
|
|
10277
|
+
constructor(options) {
|
|
10278
|
+
this.pool = options.pool;
|
|
10279
|
+
}
|
|
10280
|
+
/** Creates a room message and maps known uniqueness failures to typed errors. */
|
|
10281
|
+
async create(input) {
|
|
10282
|
+
try {
|
|
10283
|
+
const result = await this.pool.query(
|
|
10284
|
+
`INSERT INTO lattice_project_room_messages
|
|
10285
|
+
(id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at)
|
|
10286
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, date_trunc('milliseconds', clock_timestamp()))
|
|
10287
|
+
RETURNING ${columns3}`,
|
|
10288
|
+
this.parameters(input)
|
|
10289
|
+
);
|
|
10290
|
+
return mapRow5(result.rows[0]);
|
|
10291
|
+
} catch (error) {
|
|
10292
|
+
if (isUniqueViolation2(error) && error.constraint === "lattice_project_room_messages_pkey") {
|
|
10293
|
+
throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
10294
|
+
}
|
|
10295
|
+
if (isUniqueViolation2(error) && error.constraint === "uq_lattice_project_room_messages_idempotency") {
|
|
10296
|
+
throw new DuplicateProjectRoomMessageIdempotencyKeyError(input.tenantId, input.roomId, input.idempotencyKey ?? "");
|
|
10297
|
+
}
|
|
10298
|
+
throw error;
|
|
10299
|
+
}
|
|
10300
|
+
}
|
|
10301
|
+
/** Atomically creates or returns the canonical message for a room-scoped idempotency key. */
|
|
10302
|
+
async createIdempotent(input) {
|
|
10303
|
+
try {
|
|
10304
|
+
const result = await this.pool.query(
|
|
10305
|
+
`INSERT INTO lattice_project_room_messages
|
|
10306
|
+
(id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at)
|
|
10307
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, date_trunc('milliseconds', clock_timestamp()))
|
|
10308
|
+
ON CONFLICT (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL
|
|
10309
|
+
DO UPDATE SET idempotency_key = lattice_project_room_messages.idempotency_key
|
|
10310
|
+
RETURNING ${columns3}`,
|
|
10311
|
+
this.parameters(input)
|
|
10312
|
+
);
|
|
10313
|
+
return mapRow5(result.rows[0]);
|
|
10314
|
+
} catch (error) {
|
|
10315
|
+
if (isUniqueViolation2(error) && error.constraint === "lattice_project_room_messages_pkey") {
|
|
10316
|
+
throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
10317
|
+
}
|
|
10318
|
+
throw error;
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
/** Lists messages newest first using an exclusive stable cursor and a clamped limit. */
|
|
10322
|
+
async list(input) {
|
|
10323
|
+
if (input.before && (!(input.before.createdAt instanceof Date) || Number.isNaN(input.before.createdAt.getTime()))) {
|
|
10324
|
+
throw new RangeError("Project room message cursor date is invalid");
|
|
10325
|
+
}
|
|
10326
|
+
const limit = Math.min(100, Math.max(1, Math.trunc(Number.isFinite(input.limit) ? input.limit : 1)));
|
|
10327
|
+
const result = input.before ? await this.pool.query(
|
|
10328
|
+
`SELECT ${columns3} FROM lattice_project_room_messages
|
|
10329
|
+
WHERE tenant_id = $1 AND room_id = $2 AND (created_at < $3 OR (created_at = $3 AND id < $4))
|
|
10330
|
+
ORDER BY created_at DESC, id DESC LIMIT $5`,
|
|
10331
|
+
[input.tenantId, input.roomId, input.before.createdAt, input.before.id, limit]
|
|
10332
|
+
) : await this.pool.query(
|
|
10333
|
+
`SELECT ${columns3} FROM lattice_project_room_messages
|
|
10334
|
+
WHERE tenant_id = $1 AND room_id = $2 ORDER BY created_at DESC, id DESC LIMIT $3`,
|
|
10335
|
+
[input.tenantId, input.roomId, limit]
|
|
10336
|
+
);
|
|
10337
|
+
return result.rows.map(mapRow5);
|
|
10338
|
+
}
|
|
10339
|
+
/** Finds a message by tenant-scoped ID. */
|
|
10340
|
+
async findById(tenantId, id) {
|
|
10341
|
+
const result = await this.pool.query(
|
|
10342
|
+
`SELECT ${columns3} FROM lattice_project_room_messages WHERE tenant_id = $1 AND id = $2`,
|
|
10343
|
+
[tenantId, id]
|
|
10344
|
+
);
|
|
10345
|
+
return result.rows[0] ? mapRow5(result.rows[0]) : null;
|
|
10346
|
+
}
|
|
10347
|
+
parameters(input) {
|
|
10348
|
+
return [
|
|
10349
|
+
input.id,
|
|
10350
|
+
input.tenantId,
|
|
10351
|
+
input.workspaceId,
|
|
10352
|
+
input.projectId,
|
|
10353
|
+
input.roomId,
|
|
10354
|
+
JSON.stringify(input.author),
|
|
10355
|
+
JSON.stringify(input.content),
|
|
10356
|
+
JSON.stringify(input.mentions),
|
|
10357
|
+
input.replyToMessageId ?? null,
|
|
10358
|
+
input.source,
|
|
10359
|
+
input.sourceId ?? null,
|
|
10360
|
+
input.idempotencyKey ?? null
|
|
10361
|
+
];
|
|
10362
|
+
}
|
|
10363
|
+
};
|
|
10364
|
+
|
|
9279
10365
|
// src/createPgStoreConfig.ts
|
|
9280
10366
|
async function createPgStoreConfig(connectionString) {
|
|
9281
|
-
const pool = new
|
|
10367
|
+
const pool = new Pool26({ connectionString });
|
|
9282
10368
|
const mm = new MigrationManager(pool);
|
|
9283
10369
|
mm.register(createThreadsTable);
|
|
9284
10370
|
mm.register(createScheduledTasksTable);
|
|
@@ -9335,6 +10421,9 @@ async function createPgStoreConfig(connectionString) {
|
|
|
9335
10421
|
mm.register(createAgentWebAppsTable);
|
|
9336
10422
|
mm.register(createCapabilityBundlesTable);
|
|
9337
10423
|
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
10424
|
+
mm.register(createProjectRoomTables);
|
|
10425
|
+
mm.register(addTrustedRunContextColumn);
|
|
10426
|
+
mm.register(addProjectLifecycleEventIndex);
|
|
9338
10427
|
await mm.migrate();
|
|
9339
10428
|
const checkpoint = PostgresSaver.fromConnString(connectionString);
|
|
9340
10429
|
checkpoint.setup().catch((err) => {
|
|
@@ -9368,13 +10457,17 @@ async function createPgStoreConfig(connectionString) {
|
|
|
9368
10457
|
menu: new MenuStore(opts),
|
|
9369
10458
|
sharedResource: new PostgresSharedResourceStore(opts),
|
|
9370
10459
|
collection: new PostgreSQLCollectionStore(opts),
|
|
10460
|
+
projectRoom: new PostgreSQLProjectRoomStore(opts),
|
|
10461
|
+
projectMembership: new PostgreSQLProjectMembershipStore(opts),
|
|
10462
|
+
projectBotMembership: new PostgreSQLProjectBotMembershipStore(opts),
|
|
10463
|
+
projectRoomMessage: new PostgreSQLProjectRoomMessageStore(opts),
|
|
9371
10464
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
9372
10465
|
checkpoint
|
|
9373
10466
|
};
|
|
9374
10467
|
}
|
|
9375
10468
|
|
|
9376
10469
|
// src/stores/PostgreSQLSkillStore.ts
|
|
9377
|
-
import { Pool as
|
|
10470
|
+
import { Pool as Pool27 } from "pg";
|
|
9378
10471
|
var PostgreSQLSkillStore = class {
|
|
9379
10472
|
constructor(options) {
|
|
9380
10473
|
this.initialized = false;
|
|
@@ -9387,9 +10480,9 @@ var PostgreSQLSkillStore = class {
|
|
|
9387
10480
|
return;
|
|
9388
10481
|
}
|
|
9389
10482
|
if (typeof options.poolConfig === "string") {
|
|
9390
|
-
this.pool = new
|
|
10483
|
+
this.pool = new Pool27({ connectionString: options.poolConfig });
|
|
9391
10484
|
} else if (options.poolConfig) {
|
|
9392
|
-
this.pool = new
|
|
10485
|
+
this.pool = new Pool27(options.poolConfig);
|
|
9393
10486
|
} else {
|
|
9394
10487
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9395
10488
|
}
|
|
@@ -9690,7 +10783,7 @@ var PostgreSQLSkillStore = class {
|
|
|
9690
10783
|
};
|
|
9691
10784
|
|
|
9692
10785
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
9693
|
-
import { Pool as
|
|
10786
|
+
import { Pool as Pool28 } from "pg";
|
|
9694
10787
|
var ChannelIdentityMappingStore = class {
|
|
9695
10788
|
constructor(options) {
|
|
9696
10789
|
this.initialized = false;
|
|
@@ -9702,7 +10795,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
9702
10795
|
this.initialized = true;
|
|
9703
10796
|
return;
|
|
9704
10797
|
}
|
|
9705
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
10798
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool28({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool28(options.poolConfig) : (() => {
|
|
9706
10799
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9707
10800
|
})();
|
|
9708
10801
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9923,10 +11016,12 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9923
11016
|
export {
|
|
9924
11017
|
ChannelBindingStore,
|
|
9925
11018
|
ChannelIdentityMappingStore,
|
|
11019
|
+
DuplicateProjectMembershipError,
|
|
11020
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
9926
11021
|
MenuStore,
|
|
9927
11022
|
MigrationManager,
|
|
9928
11023
|
PGVectorStoreProvider,
|
|
9929
|
-
|
|
11024
|
+
Pool29 as Pool,
|
|
9930
11025
|
PostgreSQLA2AApiKeyStore,
|
|
9931
11026
|
PostgreSQLAgentWebAppStore,
|
|
9932
11027
|
PostgreSQLAssistantStore,
|
|
@@ -9938,9 +11033,14 @@ export {
|
|
|
9938
11033
|
PostgreSQLEvalStore,
|
|
9939
11034
|
PostgreSQLMcpServerConfigStore,
|
|
9940
11035
|
PostgreSQLMetricsServerConfigStore,
|
|
11036
|
+
PostgreSQLProjectBotMembershipStore,
|
|
11037
|
+
PostgreSQLProjectMembershipStore,
|
|
11038
|
+
PostgreSQLProjectRoomMessageStore,
|
|
11039
|
+
PostgreSQLProjectRoomStore,
|
|
9941
11040
|
PostgreSQLProjectStore,
|
|
9942
11041
|
PostgreSQLScheduleStorage,
|
|
9943
11042
|
PostgreSQLSkillStore,
|
|
11043
|
+
PostgreSQLTaskWorkItemStore,
|
|
9944
11044
|
PostgreSQLTenantStore,
|
|
9945
11045
|
PostgreSQLThreadStore,
|
|
9946
11046
|
PostgreSQLUserStore,
|
|
@@ -9948,6 +11048,9 @@ export {
|
|
|
9948
11048
|
PostgreSQLWorkflowTrackingStore,
|
|
9949
11049
|
PostgreSQLWorkspaceStore,
|
|
9950
11050
|
PostgresSharedResourceStore,
|
|
11051
|
+
ProjectBotMembershipIdConflictError,
|
|
11052
|
+
ProjectMembershipIdConflictError,
|
|
11053
|
+
ProjectRoomMessageIdConflictError,
|
|
9951
11054
|
ThreadMessageQueueStore,
|
|
9952
11055
|
addAssistantTenantId,
|
|
9953
11056
|
addEnvToEvalRuns,
|
|
@@ -9985,6 +11088,7 @@ export {
|
|
|
9985
11088
|
createMetricsConfigsTable,
|
|
9986
11089
|
createPGVectorStoreProvider,
|
|
9987
11090
|
createPgStoreConfig,
|
|
11091
|
+
createProjectRoomTables,
|
|
9988
11092
|
createProjectsTable,
|
|
9989
11093
|
createScheduledTasksTable,
|
|
9990
11094
|
createSharedResourcesTable,
|