@axiom-lattice/pg-stores 3.1.0 → 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 +18 -0
- package/dist/index.d.mts +283 -23
- package/dist/index.d.ts +283 -23
- package/dist/index.js +1867 -224
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1854 -217
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/ChannelBindingStore.test.ts +122 -0
- package/src/__tests__/PostgreSQLCapabilityBundleStore.migrations.test.ts +67 -0
- package/src/__tests__/PostgreSQLCapabilityBundleStore.test.ts +383 -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 +107 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.migrations.test.ts +62 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.test.ts +162 -1
- package/src/__tests__/ThreadMessageQueueStore.migrations.test.ts +63 -0
- package/src/__tests__/ThreadMessageQueueStore.test.ts +209 -4
- package/src/__tests__/add_workspace_project_to_queue.test.ts +15 -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 +25 -1
- package/src/index.ts +13 -0
- package/src/migrations/add_trusted_run_context_column.ts +18 -0
- package/src/migrations/capability_bundle_migration.ts +20 -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 +45 -1
- package/src/stores/ChannelBindingStore.ts +99 -59
- package/src/stores/PostgreSQLCapabilityBundleStore.ts +306 -0
- 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/PostgreSQLProjectStore.ts +230 -50
- package/src/stores/PostgreSQLTaskStore.ts +89 -3
- package/src/stores/PostgreSQLTaskWorkItemStore.ts +198 -8
- package/src/stores/ThreadMessageQueueStore.ts +130 -32
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) {
|
|
@@ -2272,6 +2273,9 @@ var PostgreSQLWorkspaceStore = class {
|
|
|
2272
2273
|
|
|
2273
2274
|
// src/stores/PostgreSQLProjectStore.ts
|
|
2274
2275
|
import { Pool as Pool7 } from "pg";
|
|
2276
|
+
import {
|
|
2277
|
+
assertGenericProjectConfig
|
|
2278
|
+
} from "@axiom-lattice/protocols";
|
|
2275
2279
|
|
|
2276
2280
|
// src/migrations/project_migrations.ts
|
|
2277
2281
|
var createProjectsTable = {
|
|
@@ -2487,34 +2491,62 @@ var PostgreSQLProjectStore = class {
|
|
|
2487
2491
|
* Create a new project
|
|
2488
2492
|
*/
|
|
2489
2493
|
async createProject(tenantId, workspaceId, id, data) {
|
|
2494
|
+
assertGenericProjectConfig(data.config);
|
|
2490
2495
|
await this.ensureInitialized();
|
|
2491
2496
|
const now = /* @__PURE__ */ new Date();
|
|
2492
2497
|
const kind = data.kind || "business";
|
|
2493
|
-
await this.pool.
|
|
2494
|
-
|
|
2495
|
-
|
|
2498
|
+
const client = await this.pool.connect();
|
|
2499
|
+
try {
|
|
2500
|
+
await client.query("BEGIN");
|
|
2501
|
+
await client.query(
|
|
2502
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2503
|
+
[tenantId]
|
|
2504
|
+
);
|
|
2505
|
+
await client.query(
|
|
2506
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2507
|
+
[id, tenantId]
|
|
2508
|
+
);
|
|
2509
|
+
const existing = await client.query(
|
|
2510
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2511
|
+
[id, tenantId]
|
|
2512
|
+
);
|
|
2513
|
+
const ids = existing.rows[0]?.config?.capabilityBundleIds;
|
|
2514
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2515
|
+
if (bundleIds.length > 0) {
|
|
2516
|
+
await client.query(
|
|
2517
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2518
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2519
|
+
ORDER BY bundle_id`,
|
|
2520
|
+
[tenantId, bundleIds]
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
const result = await client.query(
|
|
2524
|
+
`INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
|
|
2496
2525
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
2497
2526
|
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
2498
2527
|
workspace_id = EXCLUDED.workspace_id,
|
|
2499
2528
|
name = EXCLUDED.name,
|
|
2500
2529
|
description = EXCLUDED.description,
|
|
2501
|
-
config =
|
|
2530
|
+
config = CASE
|
|
2531
|
+
WHEN lattice_projects.config ? 'capabilityBundleIds'
|
|
2532
|
+
THEN COALESCE(EXCLUDED.config, '{}'::jsonb)
|
|
2533
|
+
|| jsonb_build_object('capabilityBundleIds', lattice_projects.config->'capabilityBundleIds')
|
|
2534
|
+
ELSE EXCLUDED.config
|
|
2535
|
+
END,
|
|
2502
2536
|
kind = EXCLUDED.kind,
|
|
2503
2537
|
updated_at = EXCLUDED.updated_at
|
|
2538
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
|
|
2504
2539
|
`,
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
createdAt: now,
|
|
2516
|
-
updatedAt: now
|
|
2517
|
-
};
|
|
2540
|
+
[id, tenantId, workspaceId, data.name, data.description || null, data.config || null, kind, now, now]
|
|
2541
|
+
);
|
|
2542
|
+
await client.query("COMMIT");
|
|
2543
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2544
|
+
} catch (error) {
|
|
2545
|
+
await client.query("ROLLBACK");
|
|
2546
|
+
throw error;
|
|
2547
|
+
} finally {
|
|
2548
|
+
client.release();
|
|
2549
|
+
}
|
|
2518
2550
|
}
|
|
2519
2551
|
/**
|
|
2520
2552
|
* Update an existing project
|
|
@@ -2524,11 +2556,8 @@ var PostgreSQLProjectStore = class {
|
|
|
2524
2556
|
* overwrites the existing config. To preserve the current config, omit this field.
|
|
2525
2557
|
*/
|
|
2526
2558
|
async updateProject(tenantId, id, updates) {
|
|
2559
|
+
assertGenericProjectConfig(updates.config);
|
|
2527
2560
|
await this.ensureInitialized();
|
|
2528
|
-
const existing = await this.getProjectById(tenantId, id);
|
|
2529
|
-
if (!existing) {
|
|
2530
|
-
return null;
|
|
2531
|
-
}
|
|
2532
2561
|
const updateFields = [];
|
|
2533
2562
|
const updateValues = [];
|
|
2534
2563
|
let paramIndex = 1;
|
|
@@ -2541,43 +2570,190 @@ var PostgreSQLProjectStore = class {
|
|
|
2541
2570
|
updateValues.push(updates.description || null);
|
|
2542
2571
|
}
|
|
2543
2572
|
if (updates.config !== void 0) {
|
|
2544
|
-
|
|
2545
|
-
|
|
2573
|
+
const configParam = `$${paramIndex++}`;
|
|
2574
|
+
updateFields.push(`config = CASE
|
|
2575
|
+
WHEN config ? 'capabilityBundleIds'
|
|
2576
|
+
THEN COALESCE(${configParam}::jsonb, '{}'::jsonb)
|
|
2577
|
+
|| jsonb_build_object('capabilityBundleIds', config->'capabilityBundleIds')
|
|
2578
|
+
ELSE ${configParam}::jsonb
|
|
2579
|
+
END`);
|
|
2580
|
+
updateValues.push(updates.config);
|
|
2546
2581
|
}
|
|
2547
2582
|
if (updates.kind !== void 0) {
|
|
2548
2583
|
updateFields.push(`kind = $${paramIndex++}`);
|
|
2549
2584
|
updateValues.push(updates.kind);
|
|
2550
2585
|
}
|
|
2551
2586
|
if (updateFields.length === 0) {
|
|
2552
|
-
return
|
|
2587
|
+
return await this.getProjectById(tenantId, id);
|
|
2553
2588
|
}
|
|
2554
2589
|
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
2555
2590
|
updateValues.push(/* @__PURE__ */ new Date());
|
|
2556
|
-
updateValues.push(id);
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2591
|
+
updateValues.push(id, tenantId);
|
|
2592
|
+
const client = await this.pool.connect();
|
|
2593
|
+
try {
|
|
2594
|
+
await client.query("BEGIN");
|
|
2595
|
+
await client.query(
|
|
2596
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2597
|
+
[tenantId]
|
|
2598
|
+
);
|
|
2599
|
+
await client.query(
|
|
2600
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2601
|
+
[id, tenantId]
|
|
2602
|
+
);
|
|
2603
|
+
const existing = await client.query(
|
|
2604
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2605
|
+
[id, tenantId]
|
|
2606
|
+
);
|
|
2607
|
+
if (!existing.rows[0]) {
|
|
2608
|
+
await client.query("COMMIT");
|
|
2609
|
+
return null;
|
|
2610
|
+
}
|
|
2611
|
+
const ids = existing.rows[0].config?.capabilityBundleIds;
|
|
2612
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2613
|
+
if (bundleIds.length > 0) {
|
|
2614
|
+
await client.query(
|
|
2615
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2616
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2617
|
+
ORDER BY bundle_id`,
|
|
2618
|
+
[tenantId, bundleIds]
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
const result = await client.query(
|
|
2622
|
+
`UPDATE lattice_projects SET ${updateFields.join(", ")}
|
|
2623
|
+
WHERE id = $${paramIndex} AND tenant_id = $${paramIndex + 1}
|
|
2624
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2625
|
+
updateValues
|
|
2626
|
+
);
|
|
2627
|
+
await client.query("COMMIT");
|
|
2628
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
await client.query("ROLLBACK");
|
|
2631
|
+
throw error;
|
|
2632
|
+
} finally {
|
|
2633
|
+
client.release();
|
|
2634
|
+
}
|
|
2567
2635
|
}
|
|
2568
2636
|
/**
|
|
2569
2637
|
* Delete a project by ID
|
|
2570
2638
|
*/
|
|
2571
2639
|
async deleteProject(tenantId, id) {
|
|
2640
|
+
await this.ensureInitialized();
|
|
2641
|
+
const client = await this.pool.connect();
|
|
2642
|
+
try {
|
|
2643
|
+
await client.query("BEGIN");
|
|
2644
|
+
await client.query(
|
|
2645
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2646
|
+
[tenantId]
|
|
2647
|
+
);
|
|
2648
|
+
await client.query(
|
|
2649
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2650
|
+
[id, tenantId]
|
|
2651
|
+
);
|
|
2652
|
+
const selected = await client.query(
|
|
2653
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2654
|
+
[id, tenantId]
|
|
2655
|
+
);
|
|
2656
|
+
if (!selected.rows[0]) {
|
|
2657
|
+
await client.query("COMMIT");
|
|
2658
|
+
return false;
|
|
2659
|
+
}
|
|
2660
|
+
const ids = selected.rows[0].config?.capabilityBundleIds;
|
|
2661
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2662
|
+
if (bundleIds.length > 0) {
|
|
2663
|
+
await client.query(
|
|
2664
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2665
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2666
|
+
ORDER BY bundle_id`,
|
|
2667
|
+
[tenantId, bundleIds]
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
const result = await client.query(
|
|
2671
|
+
"DELETE FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2672
|
+
[id, tenantId]
|
|
2673
|
+
);
|
|
2674
|
+
await client.query("COMMIT");
|
|
2675
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
2676
|
+
} catch (error) {
|
|
2677
|
+
await client.query("ROLLBACK");
|
|
2678
|
+
throw error;
|
|
2679
|
+
} finally {
|
|
2680
|
+
client.release();
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
2684
|
+
await this.ensureInitialized();
|
|
2685
|
+
const client = await this.pool.connect();
|
|
2686
|
+
try {
|
|
2687
|
+
await client.query("BEGIN");
|
|
2688
|
+
await client.query(
|
|
2689
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2690
|
+
[tenantId]
|
|
2691
|
+
);
|
|
2692
|
+
await client.query(
|
|
2693
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2694
|
+
[projectId, tenantId]
|
|
2695
|
+
);
|
|
2696
|
+
await client.query(
|
|
2697
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2698
|
+
[projectId, tenantId]
|
|
2699
|
+
);
|
|
2700
|
+
if (bundleIds.length > 0) {
|
|
2701
|
+
await client.query(
|
|
2702
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2703
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2704
|
+
ORDER BY bundle_id`,
|
|
2705
|
+
[tenantId, [...bundleIds].sort()]
|
|
2706
|
+
);
|
|
2707
|
+
if (Object.keys(expectedRevisions).length > 0) {
|
|
2708
|
+
const revisions = await client.query("SELECT id, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = ANY($2::uuid[]) FOR UPDATE", [tenantId, bundleIds]);
|
|
2709
|
+
if (revisions.rows.some((bundle) => expectedRevisions[bundle.id] !== void 0 && expectedRevisions[bundle.id] !== bundle.updated_at)) {
|
|
2710
|
+
await client.query("ROLLBACK");
|
|
2711
|
+
return { status: "bundle_conflict" };
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
const result = await client.query(
|
|
2716
|
+
`UPDATE lattice_projects
|
|
2717
|
+
SET config = COALESCE(config, '{}'::jsonb) || jsonb_build_object('capabilityBundleIds', $3::jsonb), updated_at = NOW()
|
|
2718
|
+
WHERE id = $1 AND tenant_id = $2
|
|
2719
|
+
AND NOT EXISTS (
|
|
2720
|
+
SELECT 1 FROM unnest($4::uuid[]) AS bundle_id
|
|
2721
|
+
WHERE NOT EXISTS (
|
|
2722
|
+
SELECT 1 FROM lattice_capability_bundles
|
|
2723
|
+
WHERE tenant_id = $2 AND id = bundle_id
|
|
2724
|
+
)
|
|
2725
|
+
)
|
|
2726
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2727
|
+
[projectId, tenantId, JSON.stringify(bundleIds), bundleIds]
|
|
2728
|
+
);
|
|
2729
|
+
if (result.rows[0]) {
|
|
2730
|
+
await client.query("COMMIT");
|
|
2731
|
+
return { status: "updated", project: this.mapRowToProject(result.rows[0]) };
|
|
2732
|
+
}
|
|
2733
|
+
const project = await client.query(
|
|
2734
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2735
|
+
[projectId, tenantId]
|
|
2736
|
+
);
|
|
2737
|
+
await client.query("COMMIT");
|
|
2738
|
+
return project.rows.length > 0 ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
await client.query("ROLLBACK");
|
|
2741
|
+
throw error;
|
|
2742
|
+
} finally {
|
|
2743
|
+
client.release();
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
2572
2747
|
await this.ensureInitialized();
|
|
2573
2748
|
const result = await this.pool.query(
|
|
2574
|
-
`
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2749
|
+
`SELECT 1 AS exists FROM lattice_projects
|
|
2750
|
+
WHERE tenant_id = $1
|
|
2751
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
2752
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
2753
|
+
LIMIT 1`,
|
|
2754
|
+
[tenantId, bundleId]
|
|
2579
2755
|
);
|
|
2580
|
-
return result.
|
|
2756
|
+
return result.rows.length > 0;
|
|
2581
2757
|
}
|
|
2582
2758
|
};
|
|
2583
2759
|
|
|
@@ -4857,6 +5033,10 @@ var PostgreSQLEvalStore = class {
|
|
|
4857
5033
|
// src/stores/ThreadMessageQueueStore.ts
|
|
4858
5034
|
import { Pool as Pool13 } from "pg";
|
|
4859
5035
|
import crypto from "crypto";
|
|
5036
|
+
import {
|
|
5037
|
+
parseQueuedExecutionMode,
|
|
5038
|
+
parseTrustedRunContext
|
|
5039
|
+
} from "@axiom-lattice/protocols";
|
|
4860
5040
|
|
|
4861
5041
|
// src/migrations/thread_message_queue_migrations.ts
|
|
4862
5042
|
var createThreadMessageQueueTable = {
|
|
@@ -4993,6 +5173,23 @@ var addWorkspaceProjectToQueue = {
|
|
|
4993
5173
|
}
|
|
4994
5174
|
};
|
|
4995
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
|
+
|
|
4996
5193
|
// src/stores/ThreadMessageQueueStore.ts
|
|
4997
5194
|
var ThreadMessageQueueStore = class {
|
|
4998
5195
|
constructor(options) {
|
|
@@ -5018,6 +5215,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5018
5215
|
this.migrationManager.register(addCustomRunConfigColumn);
|
|
5019
5216
|
this.migrationManager.register(alterMessageQueueIdColumn);
|
|
5020
5217
|
this.migrationManager.register(addWorkspaceProjectToQueue);
|
|
5218
|
+
this.migrationManager.register(addTrustedRunContextColumn);
|
|
5021
5219
|
if (options.autoMigrate !== false) {
|
|
5022
5220
|
this.initialize().catch((error) => {
|
|
5023
5221
|
console.error("Failed to initialize ThreadMessageQueueStore:", error);
|
|
@@ -5047,6 +5245,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5047
5245
|
* Add message to queue
|
|
5048
5246
|
*/
|
|
5049
5247
|
async addMessage(params) {
|
|
5248
|
+
const trusted = validateQueueTrust(params);
|
|
5050
5249
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", priority = 0, command, custom_run_config, id } = params;
|
|
5051
5250
|
const seqResult = await this.pool.query(
|
|
5052
5251
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq
|
|
@@ -5057,18 +5256,58 @@ var ThreadMessageQueueStore = class {
|
|
|
5057
5256
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5058
5257
|
const result = await this.pool.query(
|
|
5059
5258
|
`INSERT INTO lattice_thread_message_queue
|
|
5060
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5061
|
-
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)
|
|
5062
5261
|
RETURNING *`,
|
|
5063
|
-
[id || crypto.randomUUID(), threadId, tenantId, assistantId, workspaceId
|
|
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]
|
|
5064
5263
|
);
|
|
5065
5264
|
return this.rowToMessage(result.rows[0]);
|
|
5066
5265
|
}
|
|
5266
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
5267
|
+
const trusted = validateQueueTrust(params);
|
|
5268
|
+
if (maxSize === Infinity) {
|
|
5269
|
+
await this.addMessage(params);
|
|
5270
|
+
return true;
|
|
5271
|
+
}
|
|
5272
|
+
const client = await this.pool.connect();
|
|
5273
|
+
try {
|
|
5274
|
+
await client.query("BEGIN");
|
|
5275
|
+
const scope = { tenantId: params.tenantId, assistantId: params.assistantId, workspaceId: params.workspaceId, projectId: params.projectId };
|
|
5276
|
+
const lockKey = `${params.tenantId}:${params.assistantId}:${params.threadId}:${params.workspaceId ?? ""}:${params.projectId ?? ""}`;
|
|
5277
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtext($1))", [lockKey]);
|
|
5278
|
+
const filter = scopeClause(scope, 2);
|
|
5279
|
+
const count = await client.query(
|
|
5280
|
+
`SELECT COUNT(*) as count FROM lattice_thread_message_queue WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5281
|
+
[params.threadId, ...filter.params]
|
|
5282
|
+
);
|
|
5283
|
+
if (parseInt(count.rows[0].count, 10) >= maxSize) {
|
|
5284
|
+
await client.query("ROLLBACK");
|
|
5285
|
+
return false;
|
|
5286
|
+
}
|
|
5287
|
+
const seq = await client.query(
|
|
5288
|
+
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5289
|
+
[params.threadId]
|
|
5290
|
+
);
|
|
5291
|
+
const result = await client.query(
|
|
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]
|
|
5295
|
+
);
|
|
5296
|
+
await client.query("COMMIT");
|
|
5297
|
+
return Boolean(result.rows[0]);
|
|
5298
|
+
} catch (error) {
|
|
5299
|
+
await client.query("ROLLBACK");
|
|
5300
|
+
throw error;
|
|
5301
|
+
} finally {
|
|
5302
|
+
client.release();
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5067
5305
|
/**
|
|
5068
5306
|
* Add message at head of queue (high priority, e.g., STEER/Command messages)
|
|
5069
5307
|
* Uses priority=100 to ensure message is processed first
|
|
5070
5308
|
*/
|
|
5071
5309
|
async addMessageAtHead(params) {
|
|
5310
|
+
const trusted = validateQueueTrust(params);
|
|
5072
5311
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", command, custom_run_config, id } = params;
|
|
5073
5312
|
const resolvedTenantId = tenantId;
|
|
5074
5313
|
const resolvedAssistantId = assistantId;
|
|
@@ -5081,45 +5320,48 @@ var ThreadMessageQueueStore = class {
|
|
|
5081
5320
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5082
5321
|
const result = await this.pool.query(
|
|
5083
5322
|
`INSERT INTO lattice_thread_message_queue
|
|
5084
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5085
|
-
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)
|
|
5086
5325
|
RETURNING *`,
|
|
5087
|
-
[id || crypto.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId
|
|
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]
|
|
5088
5327
|
);
|
|
5089
5328
|
return this.rowToMessage(result.rows[0]);
|
|
5090
5329
|
}
|
|
5091
5330
|
/**
|
|
5092
5331
|
* Get pending messages for thread
|
|
5093
5332
|
*/
|
|
5094
|
-
async getPendingMessages(threadId) {
|
|
5333
|
+
async getPendingMessages(threadId, scope) {
|
|
5334
|
+
const filter = scopeClause(scope, 2);
|
|
5095
5335
|
const result = await this.pool.query(
|
|
5096
|
-
`SELECT * FROM lattice_thread_message_queue
|
|
5097
|
-
|
|
5336
|
+
`SELECT * FROM lattice_thread_message_queue
|
|
5337
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}
|
|
5098
5338
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5099
|
-
[threadId]
|
|
5339
|
+
[threadId, ...filter.params]
|
|
5100
5340
|
);
|
|
5101
5341
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5102
5342
|
}
|
|
5103
5343
|
/**
|
|
5104
5344
|
* Get processing messages for a thread
|
|
5105
5345
|
*/
|
|
5106
|
-
async getProcessingMessages(threadId) {
|
|
5346
|
+
async getProcessingMessages(threadId, scope) {
|
|
5347
|
+
const filter = scopeClause(scope, 2);
|
|
5107
5348
|
const result = await this.pool.query(
|
|
5108
5349
|
`SELECT * FROM lattice_thread_message_queue
|
|
5109
|
-
|
|
5350
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5110
5351
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5111
|
-
[threadId]
|
|
5352
|
+
[threadId, ...filter.params]
|
|
5112
5353
|
);
|
|
5113
5354
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5114
5355
|
}
|
|
5115
5356
|
/**
|
|
5116
5357
|
* Get queue size
|
|
5117
5358
|
*/
|
|
5118
|
-
async getQueueSize(threadId) {
|
|
5359
|
+
async getQueueSize(threadId, scope) {
|
|
5360
|
+
const filter = scopeClause(scope, 2);
|
|
5119
5361
|
const result = await this.pool.query(
|
|
5120
5362
|
`SELECT COUNT(*) as count FROM lattice_thread_message_queue
|
|
5121
|
-
|
|
5122
|
-
[threadId]
|
|
5363
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5364
|
+
[threadId, ...filter.params]
|
|
5123
5365
|
);
|
|
5124
5366
|
return parseInt(result.rows[0].count, 10);
|
|
5125
5367
|
}
|
|
@@ -5128,58 +5370,70 @@ var ThreadMessageQueueStore = class {
|
|
|
5128
5370
|
*/
|
|
5129
5371
|
async getThreadsWithPendingMessages() {
|
|
5130
5372
|
const result = await this.pool.query(
|
|
5131
|
-
`SELECT DISTINCT ON (thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5373
|
+
`SELECT DISTINCT ON (tenant_id, assistant_id, workspace_id, project_id, thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5132
5374
|
FROM lattice_thread_message_queue
|
|
5133
5375
|
WHERE status IN ('pending', 'processing')
|
|
5134
|
-
|
|
5376
|
+
ORDER BY tenant_id, assistant_id, workspace_id, project_id, thread_id`
|
|
5135
5377
|
);
|
|
5136
5378
|
return result.rows.map((row) => ({
|
|
5137
5379
|
tenantId: row.tenant_id,
|
|
5138
5380
|
assistantId: row.assistant_id,
|
|
5139
5381
|
threadId: row.thread_id,
|
|
5140
|
-
workspaceId: row.workspace_id
|
|
5141
|
-
projectId: row.project_id
|
|
5382
|
+
workspaceId: row.workspace_id,
|
|
5383
|
+
projectId: row.project_id
|
|
5142
5384
|
}));
|
|
5143
5385
|
}
|
|
5144
5386
|
/**
|
|
5145
5387
|
* Remove message
|
|
5146
5388
|
*/
|
|
5147
|
-
async removeMessage(messageId) {
|
|
5389
|
+
async removeMessage(messageId, scope) {
|
|
5390
|
+
const filter = scopeClause(scope, 2);
|
|
5148
5391
|
const result = await this.pool.query(
|
|
5149
|
-
`DELETE FROM lattice_thread_message_queue WHERE id = $1 RETURNING id`,
|
|
5150
|
-
[messageId]
|
|
5392
|
+
`DELETE FROM lattice_thread_message_queue WHERE id = $1${filter.sql} RETURNING id`,
|
|
5393
|
+
[messageId, ...filter.params]
|
|
5151
5394
|
);
|
|
5152
5395
|
return (result.rowCount ?? 0) > 0;
|
|
5153
5396
|
}
|
|
5154
5397
|
/**
|
|
5155
5398
|
* Clear all messages for thread
|
|
5156
5399
|
*/
|
|
5157
|
-
async clearMessages(threadId) {
|
|
5400
|
+
async clearMessages(threadId, scope) {
|
|
5401
|
+
const filter = scopeClause(scope, 2);
|
|
5158
5402
|
await this.pool.query(
|
|
5159
|
-
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5160
|
-
[threadId]
|
|
5403
|
+
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1${filter.sql}`,
|
|
5404
|
+
[threadId, ...filter.params]
|
|
5161
5405
|
);
|
|
5162
5406
|
}
|
|
5163
5407
|
/**
|
|
5164
5408
|
* Mark message as processing
|
|
5165
5409
|
*/
|
|
5166
|
-
async markProcessing(messageId) {
|
|
5410
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
5411
|
+
if (customRunConfig !== void 0) {
|
|
5412
|
+
const filter2 = scopeClause(scope, 3);
|
|
5413
|
+
await this.pool.query(
|
|
5414
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing', custom_run_config = $2 WHERE id = $1${filter2.sql}`,
|
|
5415
|
+
[messageId, JSON.stringify(customRunConfig), ...filter2.params]
|
|
5416
|
+
);
|
|
5417
|
+
return;
|
|
5418
|
+
}
|
|
5419
|
+
const filter = scopeClause(scope, 2);
|
|
5167
5420
|
await this.pool.query(
|
|
5168
|
-
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1`,
|
|
5169
|
-
[messageId]
|
|
5421
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1${filter.sql}`,
|
|
5422
|
+
[messageId, ...filter.params]
|
|
5170
5423
|
);
|
|
5171
5424
|
}
|
|
5172
5425
|
/**
|
|
5173
5426
|
* Reset all processing messages to pending state for a thread
|
|
5174
5427
|
* Returns the number of messages reset
|
|
5175
5428
|
*/
|
|
5176
|
-
async resetProcessingToPending(threadId) {
|
|
5429
|
+
async resetProcessingToPending(threadId, scope) {
|
|
5430
|
+
const filter = scopeClause(scope, 2);
|
|
5177
5431
|
const result = await this.pool.query(
|
|
5178
5432
|
`UPDATE lattice_thread_message_queue
|
|
5179
5433
|
SET status = 'pending'
|
|
5180
|
-
|
|
5434
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5181
5435
|
RETURNING id`,
|
|
5182
|
-
[threadId]
|
|
5436
|
+
[threadId, ...filter.params]
|
|
5183
5437
|
);
|
|
5184
5438
|
return result.rowCount ?? 0;
|
|
5185
5439
|
}
|
|
@@ -5192,13 +5446,36 @@ var ThreadMessageQueueStore = class {
|
|
|
5192
5446
|
createdAt: new Date(row.created_at),
|
|
5193
5447
|
priority: row.priority || 0,
|
|
5194
5448
|
command: row.command ? typeof row.command === "string" ? JSON.parse(row.command) : row.command : void 0,
|
|
5195
|
-
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)
|
|
5196
5452
|
};
|
|
5197
5453
|
}
|
|
5198
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
|
+
}
|
|
5461
|
+
function scopeClause(scope, start) {
|
|
5462
|
+
if (!scope) return { sql: "", params: [] };
|
|
5463
|
+
const params = [];
|
|
5464
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
5465
|
+
const sql = entries.map(([key, value]) => {
|
|
5466
|
+
const column = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
5467
|
+
if (value == null) return ` AND ${column} IS NULL`;
|
|
5468
|
+
params.push(value);
|
|
5469
|
+
return ` AND ${column} = $${start + params.length - 1}`;
|
|
5470
|
+
}).join("");
|
|
5471
|
+
return { sql, params };
|
|
5472
|
+
}
|
|
5199
5473
|
|
|
5200
5474
|
// src/stores/ChannelBindingStore.ts
|
|
5201
5475
|
import { Pool as Pool14 } from "pg";
|
|
5476
|
+
import {
|
|
5477
|
+
DuplicateChannelBindingSubjectError
|
|
5478
|
+
} from "@axiom-lattice/protocols";
|
|
5202
5479
|
|
|
5203
5480
|
// src/migrations/channel_bindings_migration.ts
|
|
5204
5481
|
var createChannelBindingsTable = {
|
|
@@ -5246,6 +5523,7 @@ var createChannelBindingsTable = {
|
|
|
5246
5523
|
};
|
|
5247
5524
|
|
|
5248
5525
|
// src/stores/ChannelBindingStore.ts
|
|
5526
|
+
var BINDING_SUBJECT_CONSTRAINT = "lattice_channel_bindings_channel_channel_installation_id_te_key";
|
|
5249
5527
|
var ChannelBindingStore = class {
|
|
5250
5528
|
constructor(options) {
|
|
5251
5529
|
this.initialized = false;
|
|
@@ -5302,80 +5580,91 @@ var ChannelBindingStore = class {
|
|
|
5302
5580
|
if (result.rows.length === 0) return null;
|
|
5303
5581
|
return this.mapRowToBinding(result.rows[0]);
|
|
5304
5582
|
}
|
|
5305
|
-
async
|
|
5583
|
+
async findById(tenantId, id) {
|
|
5306
5584
|
await this.ensureInitialized();
|
|
5307
5585
|
const result = await this.pool.query(
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
thread_mode, sender_display_name, sender_metadata, workspace_id, project_id)
|
|
5311
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
5312
|
-
RETURNING *`,
|
|
5313
|
-
[
|
|
5314
|
-
input.channel,
|
|
5315
|
-
input.channelInstallationId,
|
|
5316
|
-
input.tenantId,
|
|
5317
|
-
input.senderId,
|
|
5318
|
-
input.agentId,
|
|
5319
|
-
input.threadMode || "fixed",
|
|
5320
|
-
input.senderDisplayName || null,
|
|
5321
|
-
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
5322
|
-
input.workspaceId || null,
|
|
5323
|
-
input.projectId || null
|
|
5324
|
-
]
|
|
5586
|
+
"SELECT * FROM lattice_channel_bindings WHERE id = $1 AND tenant_id = $2",
|
|
5587
|
+
[id, tenantId]
|
|
5325
5588
|
);
|
|
5326
|
-
return this.mapRowToBinding(result.rows[0]);
|
|
5589
|
+
return result.rows[0] ? this.mapRowToBinding(result.rows[0]) : null;
|
|
5327
5590
|
}
|
|
5328
|
-
async
|
|
5591
|
+
async findBySubject(params) {
|
|
5329
5592
|
await this.ensureInitialized();
|
|
5330
|
-
const
|
|
5331
|
-
`SELECT * FROM lattice_channel_bindings
|
|
5332
|
-
|
|
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]
|
|
5333
5597
|
);
|
|
5334
|
-
|
|
5335
|
-
|
|
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;
|
|
5336
5630
|
}
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
channel_installation_id: patch.channelInstallationId ?? row.channel_installation_id,
|
|
5341
|
-
sender_id: patch.senderId ?? row.sender_id,
|
|
5342
|
-
agent_id: patch.agentId ?? row.agent_id,
|
|
5343
|
-
thread_id: patch.threadId !== void 0 ? patch.threadId : row.thread_id,
|
|
5344
|
-
workspace_id: patch.workspaceId !== void 0 ? patch.workspaceId : row.workspace_id,
|
|
5345
|
-
project_id: patch.projectId !== void 0 ? patch.projectId : row.project_id,
|
|
5346
|
-
thread_mode: patch.threadMode ?? row.thread_mode,
|
|
5347
|
-
sender_display_name: patch.senderDisplayName !== void 0 ? patch.senderDisplayName : row.sender_display_name,
|
|
5348
|
-
sender_metadata: patch.senderMetadata !== void 0 ? patch.senderMetadata : row.sender_metadata,
|
|
5349
|
-
enabled: patch.enabled ?? row.enabled
|
|
5350
|
-
};
|
|
5631
|
+
}
|
|
5632
|
+
async update(tenantId, id, patch) {
|
|
5633
|
+
await this.ensureInitialized();
|
|
5351
5634
|
const result = await this.pool.query(
|
|
5352
5635
|
`UPDATE lattice_channel_bindings SET
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
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
|
|
5358
5645
|
RETURNING *`,
|
|
5359
5646
|
[
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
updated.enabled,
|
|
5371
|
-
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
|
|
5372
5657
|
]
|
|
5373
5658
|
);
|
|
5659
|
+
if (!result.rows[0]) throw new Error(`Binding ${id} not found`);
|
|
5374
5660
|
return this.mapRowToBinding(result.rows[0]);
|
|
5375
5661
|
}
|
|
5376
|
-
async delete(id) {
|
|
5662
|
+
async delete(tenantId, id) {
|
|
5377
5663
|
await this.ensureInitialized();
|
|
5378
|
-
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
|
+
);
|
|
5379
5668
|
}
|
|
5380
5669
|
async list(params) {
|
|
5381
5670
|
await this.ensureInitialized();
|
|
@@ -5394,6 +5683,14 @@ var ChannelBindingStore = class {
|
|
|
5394
5683
|
conditions.push(`channel_installation_id = $${idx++}`);
|
|
5395
5684
|
values.push(params.channelInstallationId);
|
|
5396
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
|
+
}
|
|
5397
5694
|
const limit = params.limit ?? 50;
|
|
5398
5695
|
const offset = params.offset ?? 0;
|
|
5399
5696
|
values.push(limit, offset);
|
|
@@ -5406,7 +5703,16 @@ var ChannelBindingStore = class {
|
|
|
5406
5703
|
);
|
|
5407
5704
|
return result.rows.map((r) => this.mapRowToBinding(r));
|
|
5408
5705
|
}
|
|
5409
|
-
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
|
+
}
|
|
5410
5716
|
const result = [];
|
|
5411
5717
|
for (const input of bindings) {
|
|
5412
5718
|
result.push(await this.create(input));
|
|
@@ -5414,7 +5720,13 @@ var ChannelBindingStore = class {
|
|
|
5414
5720
|
return result;
|
|
5415
5721
|
}
|
|
5416
5722
|
async export(params) {
|
|
5417
|
-
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
|
+
});
|
|
5418
5730
|
}
|
|
5419
5731
|
async ensureInitialized() {
|
|
5420
5732
|
if (!this.initialized) {
|
|
@@ -5441,6 +5753,14 @@ var ChannelBindingStore = class {
|
|
|
5441
5753
|
};
|
|
5442
5754
|
}
|
|
5443
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
|
+
}
|
|
5444
5764
|
|
|
5445
5765
|
// src/stores/PostgreSQLChannelInstallationStore.ts
|
|
5446
5766
|
import { Pool as Pool15 } from "pg";
|
|
@@ -5615,7 +5935,8 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5615
5935
|
async createInstallation(tenantId, installationId, data) {
|
|
5616
5936
|
await this.ensureInitialized();
|
|
5617
5937
|
const now = /* @__PURE__ */ new Date();
|
|
5618
|
-
const
|
|
5938
|
+
const config = { ...data.config };
|
|
5939
|
+
const encryptedConfig = this.encryptSecrets(config);
|
|
5619
5940
|
await this.pool.query(
|
|
5620
5941
|
`
|
|
5621
5942
|
INSERT INTO lattice_channel_installations (
|
|
@@ -5640,7 +5961,7 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5640
5961
|
tenantId,
|
|
5641
5962
|
channel: data.channel,
|
|
5642
5963
|
name: data.name,
|
|
5643
|
-
config
|
|
5964
|
+
config,
|
|
5644
5965
|
enabled: data.enabled ?? true,
|
|
5645
5966
|
fallbackAgentId: data.fallbackAgentId,
|
|
5646
5967
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? true,
|
|
@@ -5727,17 +6048,17 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5727
6048
|
encryptSecrets(config) {
|
|
5728
6049
|
return {
|
|
5729
6050
|
...config,
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
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) } : {}
|
|
5733
6054
|
};
|
|
5734
6055
|
}
|
|
5735
6056
|
decryptSecrets(config) {
|
|
5736
6057
|
return {
|
|
5737
6058
|
...config,
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
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) } : {}
|
|
5741
6062
|
};
|
|
5742
6063
|
}
|
|
5743
6064
|
};
|
|
@@ -5946,17 +6267,17 @@ var PostgreSQLA2AApiKeyStore = class {
|
|
|
5946
6267
|
const result = await this.pool.query(
|
|
5947
6268
|
`SELECT * FROM lattice_a2a_api_keys WHERE enabled = true`
|
|
5948
6269
|
);
|
|
5949
|
-
const
|
|
6270
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
5950
6271
|
for (const row of result.rows) {
|
|
5951
6272
|
const key = decrypt5(row.key_value);
|
|
5952
|
-
|
|
6273
|
+
map2.set(key, {
|
|
5953
6274
|
key,
|
|
5954
6275
|
tenantId: row.tenant_id,
|
|
5955
6276
|
projectId: row.project_id,
|
|
5956
6277
|
assistantIds: row.assistant_ids ?? void 0
|
|
5957
6278
|
});
|
|
5958
6279
|
}
|
|
5959
|
-
return
|
|
6280
|
+
return map2;
|
|
5960
6281
|
}
|
|
5961
6282
|
};
|
|
5962
6283
|
|
|
@@ -6892,15 +7213,37 @@ var addFilesToTasks = {
|
|
|
6892
7213
|
`);
|
|
6893
7214
|
}
|
|
6894
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
|
+
};
|
|
6895
7228
|
var taskMigrations = [
|
|
6896
7229
|
createTasksTable,
|
|
6897
7230
|
addTaskFieldsMigration,
|
|
6898
7231
|
addTaskProjectFieldsMigration,
|
|
6899
|
-
addFilesToTasks
|
|
7232
|
+
addFilesToTasks,
|
|
7233
|
+
addTaskDependenciesGinIndex
|
|
6900
7234
|
];
|
|
6901
7235
|
|
|
6902
7236
|
// src/stores/PostgreSQLTaskStore.ts
|
|
6903
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
|
+
]);
|
|
6904
7247
|
function nextUpdatedAtSql(column = "updated_at") {
|
|
6905
7248
|
return `to_char(
|
|
6906
7249
|
date_trunc('milliseconds', GREATEST(
|
|
@@ -6980,6 +7323,9 @@ function mapRowToTask(row) {
|
|
|
6980
7323
|
updatedAt: new Date(row.updated_at)
|
|
6981
7324
|
};
|
|
6982
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
|
+
}
|
|
6983
7329
|
var PostgreSQLTaskStore = class {
|
|
6984
7330
|
constructor(options) {
|
|
6985
7331
|
this.initialized = false;
|
|
@@ -7003,6 +7349,7 @@ var PostgreSQLTaskStore = class {
|
|
|
7003
7349
|
this.migrationManager.register(addTaskFieldsMigration);
|
|
7004
7350
|
this.migrationManager.register(addTaskProjectFieldsMigration);
|
|
7005
7351
|
this.migrationManager.register(addFilesToTasks);
|
|
7352
|
+
this.migrationManager.register(addTaskDependenciesGinIndex);
|
|
7006
7353
|
if (options.autoMigrate !== false) {
|
|
7007
7354
|
this.initialize().catch((error) => {
|
|
7008
7355
|
console.error("Failed to initialize PostgreSQLTaskStore:", error);
|
|
@@ -7098,7 +7445,9 @@ var PostgreSQLTaskStore = class {
|
|
|
7098
7445
|
conditions.push(`workspace_id = $${paramIndex++}`);
|
|
7099
7446
|
params.push(filter.workspaceId);
|
|
7100
7447
|
}
|
|
7101
|
-
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) {
|
|
7102
7451
|
conditions.push(`project_id = $${paramIndex++}`);
|
|
7103
7452
|
params.push(filter.projectId);
|
|
7104
7453
|
}
|
|
@@ -7123,11 +7472,35 @@ var PostgreSQLTaskStore = class {
|
|
|
7123
7472
|
const limit = filter.limit || 100;
|
|
7124
7473
|
const offset = filter.offset || 0;
|
|
7125
7474
|
const result = await this.pool.query(
|
|
7126
|
-
`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++}`,
|
|
7127
7476
|
[...params, limit, offset]
|
|
7128
7477
|
);
|
|
7129
7478
|
return result.rows.map((r) => mapRowToTask(r));
|
|
7130
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
|
+
}
|
|
7131
7504
|
async update(tenantId, id, updates) {
|
|
7132
7505
|
await this.ensureInitialized();
|
|
7133
7506
|
const existing = await this.getById(tenantId, id);
|
|
@@ -7498,6 +7871,63 @@ var PostgreSQLTaskStore = class {
|
|
|
7498
7871
|
);
|
|
7499
7872
|
return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
|
|
7500
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
|
+
}
|
|
7501
7931
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
7502
7932
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
7503
7933
|
await this.ensureInitialized();
|
|
@@ -7616,6 +8046,27 @@ var PostgreSQLTaskStore = class {
|
|
|
7616
8046
|
);
|
|
7617
8047
|
return (result.rowCount ?? 0) > 0;
|
|
7618
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
|
+
}
|
|
7619
8070
|
async dispose() {
|
|
7620
8071
|
if (this.ownsPool && this.pool) {
|
|
7621
8072
|
await this.pool.end();
|
|
@@ -7629,17 +8080,175 @@ var PostgreSQLTaskStore = class {
|
|
|
7629
8080
|
};
|
|
7630
8081
|
|
|
7631
8082
|
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
8083
|
+
import { Pool as Pool20 } from "pg";
|
|
8084
|
+
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
7632
8085
|
import { v4 } from "uuid";
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
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
|
+
}
|
|
8201
|
+
var PostgreSQLTaskWorkItemStore = class {
|
|
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();
|
|
7636
8244
|
}
|
|
7637
8245
|
async create(params) {
|
|
8246
|
+
await this.ensureInitialized();
|
|
7638
8247
|
const id = v4();
|
|
7639
8248
|
const result = await this.pool.query(
|
|
7640
8249
|
`INSERT INTO lattice_task_work_items
|
|
7641
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id)
|
|
7642
|
-
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()))
|
|
7643
8252
|
RETURNING *`,
|
|
7644
8253
|
[
|
|
7645
8254
|
id,
|
|
@@ -7657,8 +8266,46 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7657
8266
|
);
|
|
7658
8267
|
return this.rowToItem(result.rows[0]);
|
|
7659
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
|
+
}
|
|
7660
8306
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
7661
8307
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
8308
|
+
await this.ensureInitialized();
|
|
7662
8309
|
const result = await this.pool.query(
|
|
7663
8310
|
`SELECT * FROM lattice_task_work_items
|
|
7664
8311
|
WHERE tenant_id = $1 AND task_id = $2 AND event_key = $3`,
|
|
@@ -7668,10 +8315,11 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7668
8315
|
}
|
|
7669
8316
|
/** Atomically return an existing event or create it once. */
|
|
7670
8317
|
async createIfAbsentByEventKey(params) {
|
|
8318
|
+
await this.ensureInitialized();
|
|
7671
8319
|
const result = await this.pool.query(
|
|
7672
8320
|
`INSERT INTO lattice_task_work_items
|
|
7673
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key)
|
|
7674
|
-
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()))
|
|
7675
8323
|
ON CONFLICT (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
|
|
7676
8324
|
DO UPDATE SET event_key = EXCLUDED.event_key
|
|
7677
8325
|
RETURNING *`,
|
|
@@ -7693,6 +8341,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7693
8341
|
return this.rowToItem(result.rows[0]);
|
|
7694
8342
|
}
|
|
7695
8343
|
async list(filter) {
|
|
8344
|
+
await this.ensureInitialized();
|
|
7696
8345
|
let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
|
|
7697
8346
|
const params = [filter.tenantId, filter.taskId];
|
|
7698
8347
|
if (filter.action) {
|
|
@@ -7720,6 +8369,84 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7720
8369
|
const result = await this.pool.query(query, params);
|
|
7721
8370
|
return result.rows.map((row) => this.rowToItem(row));
|
|
7722
8371
|
}
|
|
8372
|
+
/** List pending execution results using one bounded PostgreSQL anti-join query. */
|
|
8373
|
+
async listPendingExecutionResults(params) {
|
|
8374
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
8375
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
8376
|
+
error.code = "INVALID_LIMIT";
|
|
8377
|
+
throw error;
|
|
8378
|
+
}
|
|
8379
|
+
if (params.limit === 0) return [];
|
|
8380
|
+
const result = await this.pool.query(
|
|
8381
|
+
`SELECT result.*
|
|
8382
|
+
FROM lattice_task_work_items AS result
|
|
8383
|
+
WHERE result.tenant_id = $1
|
|
8384
|
+
AND result.task_id = $2
|
|
8385
|
+
AND result.action = 'execution_result'
|
|
8386
|
+
AND result.event_key COLLATE "C" ~ '^execution-result:[A-Za-z0-9._:-]+$'
|
|
8387
|
+
AND NOT EXISTS (
|
|
8388
|
+
SELECT 1
|
|
8389
|
+
FROM lattice_task_work_items AS reconciled
|
|
8390
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
8391
|
+
AND reconciled.task_id = result.task_id
|
|
8392
|
+
AND reconciled.action = 'execution_reconciled'
|
|
8393
|
+
AND reconciled.detail ->> 'executionResultId' = result.event_key
|
|
8394
|
+
)
|
|
8395
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
8396
|
+
LIMIT $3`,
|
|
8397
|
+
[params.tenantId, params.taskId, params.limit]
|
|
8398
|
+
);
|
|
8399
|
+
return result.rows.map((row) => this.rowToItem(row));
|
|
8400
|
+
}
|
|
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
|
+
}
|
|
7723
8450
|
rowToItem(row) {
|
|
7724
8451
|
return {
|
|
7725
8452
|
id: row.id,
|
|
@@ -7729,7 +8456,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7729
8456
|
actor: row.actor,
|
|
7730
8457
|
threadId: row.thread_id,
|
|
7731
8458
|
summary: row.summary,
|
|
7732
|
-
detail: row.detail,
|
|
8459
|
+
detail: isRecord2(row.detail) ? row.detail : void 0,
|
|
7733
8460
|
attempt: row.attempt,
|
|
7734
8461
|
workspaceId: row.workspace_id,
|
|
7735
8462
|
projectId: row.project_id,
|
|
@@ -7740,7 +8467,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7740
8467
|
};
|
|
7741
8468
|
|
|
7742
8469
|
// src/stores/MenuStore.ts
|
|
7743
|
-
import { Pool as
|
|
8470
|
+
import { Pool as Pool21 } from "pg";
|
|
7744
8471
|
|
|
7745
8472
|
// src/migrations/menu_items_migration.ts
|
|
7746
8473
|
var createMenuItemsTable = {
|
|
@@ -7817,7 +8544,7 @@ var MenuStore = class {
|
|
|
7817
8544
|
this.initialized = true;
|
|
7818
8545
|
return;
|
|
7819
8546
|
}
|
|
7820
|
-
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) : (() => {
|
|
7821
8548
|
throw new Error("Either pool or poolConfig must be provided");
|
|
7822
8549
|
})();
|
|
7823
8550
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -7972,7 +8699,7 @@ var MenuStore = class {
|
|
|
7972
8699
|
};
|
|
7973
8700
|
|
|
7974
8701
|
// src/stores/PostgresSharedResourceStore.ts
|
|
7975
|
-
import { Pool as
|
|
8702
|
+
import { Pool as Pool22 } from "pg";
|
|
7976
8703
|
|
|
7977
8704
|
// src/migrations/shared_resources_migration.ts
|
|
7978
8705
|
var createSharedResourcesTable = {
|
|
@@ -8026,7 +8753,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8026
8753
|
this.initialized = true;
|
|
8027
8754
|
return;
|
|
8028
8755
|
}
|
|
8029
|
-
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) : (() => {
|
|
8030
8757
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8031
8758
|
})();
|
|
8032
8759
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8195,7 +8922,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8195
8922
|
};
|
|
8196
8923
|
|
|
8197
8924
|
// src/stores/PostgreSQLCollectionStore.ts
|
|
8198
|
-
import { Pool as
|
|
8925
|
+
import { Pool as Pool23 } from "pg";
|
|
8199
8926
|
var PostgreSQLCollectionStore = class {
|
|
8200
8927
|
constructor(options) {
|
|
8201
8928
|
this.initialized = false;
|
|
@@ -8208,9 +8935,9 @@ var PostgreSQLCollectionStore = class {
|
|
|
8208
8935
|
return;
|
|
8209
8936
|
}
|
|
8210
8937
|
if (typeof options.poolConfig === "string") {
|
|
8211
|
-
this.pool = new
|
|
8938
|
+
this.pool = new Pool23({ connectionString: options.poolConfig });
|
|
8212
8939
|
} else if (options.poolConfig) {
|
|
8213
|
-
this.pool = new
|
|
8940
|
+
this.pool = new Pool23(options.poolConfig);
|
|
8214
8941
|
} else {
|
|
8215
8942
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8216
8943
|
}
|
|
@@ -8325,7 +9052,7 @@ var PostgreSQLCollectionStore = class {
|
|
|
8325
9052
|
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
|
|
8326
9053
|
|
|
8327
9054
|
// src/PGVectorStoreProvider.ts
|
|
8328
|
-
import { Pool as
|
|
9055
|
+
import { Pool as Pool24 } from "pg";
|
|
8329
9056
|
import { PGVectorStore } from "@langchain/community/vectorstores/pgvector";
|
|
8330
9057
|
import { Document } from "@langchain/core/documents";
|
|
8331
9058
|
import { embeddingsLatticeManager } from "@axiom-lattice/core";
|
|
@@ -8399,7 +9126,7 @@ var PGVectorStoreProvider = class {
|
|
|
8399
9126
|
}
|
|
8400
9127
|
};
|
|
8401
9128
|
function createPGVectorStoreProvider(connectionString) {
|
|
8402
|
-
const pool = new
|
|
9129
|
+
const pool = new Pool24({ connectionString });
|
|
8403
9130
|
return {
|
|
8404
9131
|
provider: new PGVectorStoreProvider(pool, connectionString),
|
|
8405
9132
|
pool
|
|
@@ -8688,69 +9415,956 @@ var createCollectionsTable = {
|
|
|
8688
9415
|
}
|
|
8689
9416
|
};
|
|
8690
9417
|
|
|
8691
|
-
// src/migrations/
|
|
8692
|
-
var
|
|
8693
|
-
version:
|
|
8694
|
-
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",
|
|
8695
9441
|
up: async (client) => {
|
|
8696
9442
|
await client.query(`
|
|
8697
|
-
CREATE TABLE IF NOT EXISTS
|
|
8698
|
-
id
|
|
8699
|
-
tenant_id
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
detail JSONB,
|
|
8706
|
-
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')),
|
|
8707
9451
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8708
|
-
|
|
9452
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9453
|
+
PRIMARY KEY (tenant_id, id),
|
|
9454
|
+
UNIQUE (tenant_id, project_id, type)
|
|
8709
9455
|
)
|
|
8710
9456
|
`);
|
|
8711
9457
|
await client.query(`
|
|
8712
|
-
CREATE
|
|
8713
|
-
|
|
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
|
+
)
|
|
8714
9472
|
`);
|
|
8715
9473
|
await client.query(`
|
|
8716
|
-
CREATE INDEX IF NOT EXISTS
|
|
8717
|
-
|
|
9474
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_memberships_project
|
|
9475
|
+
ON lattice_project_memberships (tenant_id, project_id)
|
|
8718
9476
|
`);
|
|
8719
|
-
}
|
|
8720
|
-
};
|
|
8721
|
-
var addWorkItemProjectFieldsMigration = {
|
|
8722
|
-
version: 140,
|
|
8723
|
-
name: "add_task_work_item_project_fields",
|
|
8724
|
-
up: async (client) => {
|
|
8725
9477
|
await client.query(`
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
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
|
+
)
|
|
8729
9498
|
`);
|
|
8730
9499
|
await client.query(`
|
|
8731
|
-
CREATE INDEX IF NOT EXISTS
|
|
8732
|
-
|
|
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')
|
|
8733
9503
|
`);
|
|
8734
|
-
}
|
|
8735
|
-
};
|
|
8736
|
-
var addTaskWorkItemEventKeyMigration = {
|
|
8737
|
-
version: 168,
|
|
8738
|
-
name: "add_task_work_item_event_key",
|
|
8739
|
-
up: async (client) => {
|
|
8740
9504
|
await client.query(`
|
|
8741
|
-
|
|
8742
|
-
|
|
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')
|
|
8743
9508
|
`);
|
|
8744
9509
|
await client.query(`
|
|
8745
|
-
CREATE
|
|
8746
|
-
|
|
9510
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_project
|
|
9511
|
+
ON lattice_project_bot_memberships (tenant_id, project_id)
|
|
9512
|
+
`);
|
|
9513
|
+
await client.query(`
|
|
9514
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_room
|
|
9515
|
+
ON lattice_project_bot_memberships (tenant_id, room_id)
|
|
9516
|
+
`);
|
|
9517
|
+
await client.query(`
|
|
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)
|
|
8747
9544
|
`);
|
|
9545
|
+
},
|
|
9546
|
+
down: async (client) => {
|
|
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");
|
|
8748
9558
|
}
|
|
8749
9559
|
};
|
|
8750
9560
|
|
|
8751
|
-
// src/
|
|
8752
|
-
|
|
8753
|
-
|
|
9561
|
+
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
9562
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
9563
|
+
import { Pool as Pool25 } from "pg";
|
|
9564
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
9565
|
+
function map(row) {
|
|
9566
|
+
return {
|
|
9567
|
+
id: row.id,
|
|
9568
|
+
tenantId: row.tenant_id,
|
|
9569
|
+
key: row.bundle_key,
|
|
9570
|
+
name: row.name,
|
|
9571
|
+
description: row.description ?? void 0,
|
|
9572
|
+
capabilities: row.capabilities,
|
|
9573
|
+
createdAt: row.created_at.toISOString(),
|
|
9574
|
+
updatedAt: typeof row.updated_at === "string" ? row.updated_at : row.updated_at.toISOString()
|
|
9575
|
+
};
|
|
9576
|
+
}
|
|
9577
|
+
function isDuplicate(error) {
|
|
9578
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
9579
|
+
}
|
|
9580
|
+
var PostgreSQLCapabilityBundleStore = class {
|
|
9581
|
+
/**
|
|
9582
|
+
* Creates a PostgreSQL capability bundle store.
|
|
9583
|
+
*
|
|
9584
|
+
* @param options - Pool ownership, connection, and migration options.
|
|
9585
|
+
*/
|
|
9586
|
+
constructor(options) {
|
|
9587
|
+
this.initialized = false;
|
|
9588
|
+
this.ownsPool = true;
|
|
9589
|
+
this.initPromise = null;
|
|
9590
|
+
if (options.pool) {
|
|
9591
|
+
this.pool = options.pool;
|
|
9592
|
+
this.ownsPool = false;
|
|
9593
|
+
this.initialized = true;
|
|
9594
|
+
return;
|
|
9595
|
+
}
|
|
9596
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool25({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool25(options.poolConfig) : (() => {
|
|
9597
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
9598
|
+
})();
|
|
9599
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
9600
|
+
this.migrationManager.register(createCapabilityBundlesTable);
|
|
9601
|
+
if (options.autoMigrate !== false) {
|
|
9602
|
+
this.startInitialization();
|
|
9603
|
+
}
|
|
9604
|
+
}
|
|
9605
|
+
/**
|
|
9606
|
+
* Applies pending migrations for an internally managed pool.
|
|
9607
|
+
*
|
|
9608
|
+
* @returns A shared promise that resolves when initialization completes.
|
|
9609
|
+
*/
|
|
9610
|
+
async initialize() {
|
|
9611
|
+
if (this.initialized) return;
|
|
9612
|
+
if (this.initPromise) return this.initPromise;
|
|
9613
|
+
return this.startInitialization();
|
|
9614
|
+
}
|
|
9615
|
+
startInitialization() {
|
|
9616
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
9617
|
+
this.initialized = true;
|
|
9618
|
+
});
|
|
9619
|
+
void this.initPromise.catch(() => void 0);
|
|
9620
|
+
return this.initPromise;
|
|
9621
|
+
}
|
|
9622
|
+
/**
|
|
9623
|
+
* Closes the pool when it was created by this store.
|
|
9624
|
+
*
|
|
9625
|
+
* @returns A promise that resolves after owned resources are released.
|
|
9626
|
+
*/
|
|
9627
|
+
async dispose() {
|
|
9628
|
+
if (this.ownsPool) await this.pool.end();
|
|
9629
|
+
}
|
|
9630
|
+
async ready() {
|
|
9631
|
+
if (!this.initialized) await this.initialize();
|
|
9632
|
+
}
|
|
9633
|
+
async listByTenant(tenantId) {
|
|
9634
|
+
await this.ready();
|
|
9635
|
+
const result = await this.pool.query(
|
|
9636
|
+
"SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 ORDER BY created_at",
|
|
9637
|
+
[tenantId]
|
|
9638
|
+
);
|
|
9639
|
+
return result.rows.map(map);
|
|
9640
|
+
}
|
|
9641
|
+
async getById(tenantId, id) {
|
|
9642
|
+
await this.ready();
|
|
9643
|
+
const result = await this.pool.query(
|
|
9644
|
+
"SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = $2",
|
|
9645
|
+
[tenantId, id]
|
|
9646
|
+
);
|
|
9647
|
+
return result.rows[0] ? map(result.rows[0]) : null;
|
|
9648
|
+
}
|
|
9649
|
+
async getManyByIds(tenantId, ids) {
|
|
9650
|
+
await this.ready();
|
|
9651
|
+
if (ids.length === 0) return [];
|
|
9652
|
+
const result = await this.pool.query(
|
|
9653
|
+
"SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = ANY($2::uuid[])",
|
|
9654
|
+
[tenantId, ids]
|
|
9655
|
+
);
|
|
9656
|
+
return result.rows.map(map);
|
|
9657
|
+
}
|
|
9658
|
+
async create(tenantId, input) {
|
|
9659
|
+
await this.ready();
|
|
9660
|
+
try {
|
|
9661
|
+
const result = await this.pool.query(
|
|
9662
|
+
`INSERT INTO lattice_capability_bundles
|
|
9663
|
+
(id, tenant_id, bundle_key, name, description, capabilities)
|
|
9664
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb)
|
|
9665
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9666
|
+
[
|
|
9667
|
+
randomUUID3(),
|
|
9668
|
+
tenantId,
|
|
9669
|
+
input.key,
|
|
9670
|
+
input.name,
|
|
9671
|
+
input.description ?? null,
|
|
9672
|
+
JSON.stringify(input.capabilities)
|
|
9673
|
+
]
|
|
9674
|
+
);
|
|
9675
|
+
return map(result.rows[0]);
|
|
9676
|
+
} catch (error) {
|
|
9677
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9678
|
+
throw error;
|
|
9679
|
+
}
|
|
9680
|
+
}
|
|
9681
|
+
async update(tenantId, id, input) {
|
|
9682
|
+
await this.ready();
|
|
9683
|
+
const fields = [];
|
|
9684
|
+
const values = [];
|
|
9685
|
+
const add = (field, value) => {
|
|
9686
|
+
values.push(value);
|
|
9687
|
+
fields.push(`${field} = $${values.length}`);
|
|
9688
|
+
};
|
|
9689
|
+
if (input.name !== void 0) add("name", input.name);
|
|
9690
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) {
|
|
9691
|
+
add("description", input.description ?? null);
|
|
9692
|
+
}
|
|
9693
|
+
if (input.capabilities !== void 0) {
|
|
9694
|
+
add("capabilities", JSON.stringify(input.capabilities));
|
|
9695
|
+
}
|
|
9696
|
+
if (fields.length === 0) {
|
|
9697
|
+
const expectedUpdatedAt2 = input.expectedUpdatedAt;
|
|
9698
|
+
const result = await this.pool.query(
|
|
9699
|
+
`SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at
|
|
9700
|
+
FROM lattice_capability_bundles
|
|
9701
|
+
WHERE tenant_id = $1 AND id = $2${expectedUpdatedAt2 === void 0 ? "" : " AND updated_at::text = $3"}`,
|
|
9702
|
+
expectedUpdatedAt2 === void 0 ? [tenantId, id] : [tenantId, id, expectedUpdatedAt2]
|
|
9703
|
+
);
|
|
9704
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9705
|
+
return expectedUpdatedAt2 === void 0 ? null : { status: "conflict" };
|
|
9706
|
+
}
|
|
9707
|
+
const expectedUpdatedAt = input.expectedUpdatedAt;
|
|
9708
|
+
values.push(tenantId, id);
|
|
9709
|
+
if (expectedUpdatedAt !== void 0) values.push(expectedUpdatedAt);
|
|
9710
|
+
const tenantParam = values.length - (expectedUpdatedAt === void 0 ? 1 : 2);
|
|
9711
|
+
const idParam = tenantParam + 1;
|
|
9712
|
+
const revisionPredicate = expectedUpdatedAt === void 0 ? "" : ` AND updated_at::text = $${values.length}`;
|
|
9713
|
+
try {
|
|
9714
|
+
const result = await this.pool.query(
|
|
9715
|
+
`UPDATE lattice_capability_bundles
|
|
9716
|
+
SET ${fields.join(", ")}, updated_at = GREATEST(updated_at + interval '1 microsecond', clock_timestamp())
|
|
9717
|
+
WHERE tenant_id = $${tenantParam} AND id = $${idParam}${revisionPredicate}
|
|
9718
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9719
|
+
values
|
|
9720
|
+
);
|
|
9721
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9722
|
+
return expectedUpdatedAt === void 0 ? null : { status: "conflict" };
|
|
9723
|
+
} catch (error) {
|
|
9724
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9725
|
+
throw error;
|
|
9726
|
+
}
|
|
9727
|
+
}
|
|
9728
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
9729
|
+
await this.ready();
|
|
9730
|
+
const client = await this.pool.connect();
|
|
9731
|
+
try {
|
|
9732
|
+
await client.query("BEGIN");
|
|
9733
|
+
await client.query(
|
|
9734
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
9735
|
+
[tenantId]
|
|
9736
|
+
);
|
|
9737
|
+
const projectIds = await client.query(
|
|
9738
|
+
`SELECT id FROM lattice_projects
|
|
9739
|
+
WHERE tenant_id = $1
|
|
9740
|
+
ORDER BY id`,
|
|
9741
|
+
[tenantId]
|
|
9742
|
+
);
|
|
9743
|
+
if (projectIds.rows.length > 0) {
|
|
9744
|
+
await client.query(
|
|
9745
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project:' || project_id, 0))
|
|
9746
|
+
FROM unnest($2::text[]) AS project_id
|
|
9747
|
+
ORDER BY project_id`,
|
|
9748
|
+
[tenantId, projectIds.rows.map((project) => project.id)]
|
|
9749
|
+
);
|
|
9750
|
+
}
|
|
9751
|
+
await client.query(
|
|
9752
|
+
`SELECT id FROM lattice_projects
|
|
9753
|
+
WHERE tenant_id = $1
|
|
9754
|
+
FOR UPDATE`,
|
|
9755
|
+
[tenantId]
|
|
9756
|
+
);
|
|
9757
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || $2, 0))", [tenantId, id]);
|
|
9758
|
+
const referenced = await client.query(
|
|
9759
|
+
`SELECT 1 FROM lattice_projects
|
|
9760
|
+
WHERE tenant_id = $1
|
|
9761
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
9762
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
9763
|
+
LIMIT 1`,
|
|
9764
|
+
[tenantId, id]
|
|
9765
|
+
);
|
|
9766
|
+
if (referenced.rows.length > 0) {
|
|
9767
|
+
await client.query("COMMIT");
|
|
9768
|
+
return "in_use";
|
|
9769
|
+
}
|
|
9770
|
+
const deleted = await client.query(
|
|
9771
|
+
"DELETE FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = $2 RETURNING id",
|
|
9772
|
+
[tenantId, id]
|
|
9773
|
+
);
|
|
9774
|
+
await client.query("COMMIT");
|
|
9775
|
+
return deleted.rows.length > 0 ? "deleted" : "not_found";
|
|
9776
|
+
} catch (error) {
|
|
9777
|
+
await client.query("ROLLBACK");
|
|
9778
|
+
throw error;
|
|
9779
|
+
} finally {
|
|
9780
|
+
client.release();
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
};
|
|
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
|
+
|
|
10365
|
+
// src/createPgStoreConfig.ts
|
|
10366
|
+
async function createPgStoreConfig(connectionString) {
|
|
10367
|
+
const pool = new Pool26({ connectionString });
|
|
8754
10368
|
const mm = new MigrationManager(pool);
|
|
8755
10369
|
mm.register(createThreadsTable);
|
|
8756
10370
|
mm.register(createScheduledTasksTable);
|
|
@@ -8805,6 +10419,11 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8805
10419
|
mm.register(addA2AKeyAssistantIds);
|
|
8806
10420
|
mm.register(addTaskWorkItemEventKeyMigration);
|
|
8807
10421
|
mm.register(createAgentWebAppsTable);
|
|
10422
|
+
mm.register(createCapabilityBundlesTable);
|
|
10423
|
+
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
10424
|
+
mm.register(createProjectRoomTables);
|
|
10425
|
+
mm.register(addTrustedRunContextColumn);
|
|
10426
|
+
mm.register(addProjectLifecycleEventIndex);
|
|
8808
10427
|
await mm.migrate();
|
|
8809
10428
|
const checkpoint = PostgresSaver.fromConnString(connectionString);
|
|
8810
10429
|
checkpoint.setup().catch((err) => {
|
|
@@ -8833,17 +10452,22 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8833
10452
|
taskWorkItem: taskWorkItemStore,
|
|
8834
10453
|
a2aApiKey: new PostgreSQLA2AApiKeyStore(opts),
|
|
8835
10454
|
agentWebApp: new PostgreSQLAgentWebAppStore(opts),
|
|
10455
|
+
capabilityBundle: new PostgreSQLCapabilityBundleStore(opts),
|
|
8836
10456
|
schedule: new PostgreSQLScheduleStorage(opts),
|
|
8837
10457
|
menu: new MenuStore(opts),
|
|
8838
10458
|
sharedResource: new PostgresSharedResourceStore(opts),
|
|
8839
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),
|
|
8840
10464
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
8841
10465
|
checkpoint
|
|
8842
10466
|
};
|
|
8843
10467
|
}
|
|
8844
10468
|
|
|
8845
10469
|
// src/stores/PostgreSQLSkillStore.ts
|
|
8846
|
-
import { Pool as
|
|
10470
|
+
import { Pool as Pool27 } from "pg";
|
|
8847
10471
|
var PostgreSQLSkillStore = class {
|
|
8848
10472
|
constructor(options) {
|
|
8849
10473
|
this.initialized = false;
|
|
@@ -8856,9 +10480,9 @@ var PostgreSQLSkillStore = class {
|
|
|
8856
10480
|
return;
|
|
8857
10481
|
}
|
|
8858
10482
|
if (typeof options.poolConfig === "string") {
|
|
8859
|
-
this.pool = new
|
|
10483
|
+
this.pool = new Pool27({ connectionString: options.poolConfig });
|
|
8860
10484
|
} else if (options.poolConfig) {
|
|
8861
|
-
this.pool = new
|
|
10485
|
+
this.pool = new Pool27(options.poolConfig);
|
|
8862
10486
|
} else {
|
|
8863
10487
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8864
10488
|
}
|
|
@@ -9159,7 +10783,7 @@ var PostgreSQLSkillStore = class {
|
|
|
9159
10783
|
};
|
|
9160
10784
|
|
|
9161
10785
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
9162
|
-
import { Pool as
|
|
10786
|
+
import { Pool as Pool28 } from "pg";
|
|
9163
10787
|
var ChannelIdentityMappingStore = class {
|
|
9164
10788
|
constructor(options) {
|
|
9165
10789
|
this.initialized = false;
|
|
@@ -9171,7 +10795,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
9171
10795
|
this.initialized = true;
|
|
9172
10796
|
return;
|
|
9173
10797
|
}
|
|
9174
|
-
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) : (() => {
|
|
9175
10799
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9176
10800
|
})();
|
|
9177
10801
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9392,13 +11016,16 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9392
11016
|
export {
|
|
9393
11017
|
ChannelBindingStore,
|
|
9394
11018
|
ChannelIdentityMappingStore,
|
|
11019
|
+
DuplicateProjectMembershipError,
|
|
11020
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
9395
11021
|
MenuStore,
|
|
9396
11022
|
MigrationManager,
|
|
9397
11023
|
PGVectorStoreProvider,
|
|
9398
|
-
|
|
11024
|
+
Pool29 as Pool,
|
|
9399
11025
|
PostgreSQLA2AApiKeyStore,
|
|
9400
11026
|
PostgreSQLAgentWebAppStore,
|
|
9401
11027
|
PostgreSQLAssistantStore,
|
|
11028
|
+
PostgreSQLCapabilityBundleStore,
|
|
9402
11029
|
PostgreSQLChannelInstallationStore,
|
|
9403
11030
|
PostgreSQLCollectionStore,
|
|
9404
11031
|
PostgreSQLConnectionStore,
|
|
@@ -9406,9 +11033,14 @@ export {
|
|
|
9406
11033
|
PostgreSQLEvalStore,
|
|
9407
11034
|
PostgreSQLMcpServerConfigStore,
|
|
9408
11035
|
PostgreSQLMetricsServerConfigStore,
|
|
11036
|
+
PostgreSQLProjectBotMembershipStore,
|
|
11037
|
+
PostgreSQLProjectMembershipStore,
|
|
11038
|
+
PostgreSQLProjectRoomMessageStore,
|
|
11039
|
+
PostgreSQLProjectRoomStore,
|
|
9409
11040
|
PostgreSQLProjectStore,
|
|
9410
11041
|
PostgreSQLScheduleStorage,
|
|
9411
11042
|
PostgreSQLSkillStore,
|
|
11043
|
+
PostgreSQLTaskWorkItemStore,
|
|
9412
11044
|
PostgreSQLTenantStore,
|
|
9413
11045
|
PostgreSQLThreadStore,
|
|
9414
11046
|
PostgreSQLUserStore,
|
|
@@ -9416,6 +11048,9 @@ export {
|
|
|
9416
11048
|
PostgreSQLWorkflowTrackingStore,
|
|
9417
11049
|
PostgreSQLWorkspaceStore,
|
|
9418
11050
|
PostgresSharedResourceStore,
|
|
11051
|
+
ProjectBotMembershipIdConflictError,
|
|
11052
|
+
ProjectMembershipIdConflictError,
|
|
11053
|
+
ProjectRoomMessageIdConflictError,
|
|
9419
11054
|
ThreadMessageQueueStore,
|
|
9420
11055
|
addAssistantTenantId,
|
|
9421
11056
|
addEnvToEvalRuns,
|
|
@@ -9437,6 +11072,7 @@ export {
|
|
|
9437
11072
|
createA2AApiKeysTable,
|
|
9438
11073
|
createAgentWebAppsTable,
|
|
9439
11074
|
createAssistantsTable,
|
|
11075
|
+
createCapabilityBundlesTable,
|
|
9440
11076
|
createChannelBindingsTable,
|
|
9441
11077
|
createChannelIdentityMappingTables,
|
|
9442
11078
|
createChannelInstallationsTable,
|
|
@@ -9452,6 +11088,7 @@ export {
|
|
|
9452
11088
|
createMetricsConfigsTable,
|
|
9453
11089
|
createPGVectorStoreProvider,
|
|
9454
11090
|
createPgStoreConfig,
|
|
11091
|
+
createProjectRoomTables,
|
|
9455
11092
|
createProjectsTable,
|
|
9456
11093
|
createScheduledTasksTable,
|
|
9457
11094
|
createSharedResourcesTable,
|