@axiom-lattice/pg-stores 3.0.2 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +21 -0
- package/dist/index.d.mts +91 -11
- package/dist/index.d.ts +91 -11
- package/dist/index.js +619 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +614 -81
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/PostgreSQLCapabilityBundleStore.migrations.test.ts +67 -0
- package/src/__tests__/PostgreSQLCapabilityBundleStore.test.ts +383 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.integration.test.ts +33 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.test.ts +66 -1
- package/src/__tests__/ThreadMessageQueueStore.test.ts +96 -4
- package/src/__tests__/add_workspace_project_to_queue.test.ts +15 -0
- package/src/createPgStoreConfig.ts +11 -1
- package/src/index.ts +6 -0
- package/src/migrations/capability_bundle_migration.ts +20 -0
- package/src/migrations/task_work_items_migration.ts +16 -0
- package/src/stores/PostgreSQLCapabilityBundleStore.ts +306 -0
- package/src/stores/PostgreSQLProjectStore.ts +230 -50
- package/src/stores/PostgreSQLTaskWorkItemStore.ts +39 -0
- package/src/stores/ThreadMessageQueueStore.ts +97 -28
package/dist/index.js
CHANGED
|
@@ -35,10 +35,11 @@ __export(index_exports, {
|
|
|
35
35
|
MenuStore: () => MenuStore,
|
|
36
36
|
MigrationManager: () => MigrationManager,
|
|
37
37
|
PGVectorStoreProvider: () => PGVectorStoreProvider,
|
|
38
|
-
Pool: () =>
|
|
38
|
+
Pool: () => import_pg28.Pool,
|
|
39
39
|
PostgreSQLA2AApiKeyStore: () => PostgreSQLA2AApiKeyStore,
|
|
40
40
|
PostgreSQLAgentWebAppStore: () => PostgreSQLAgentWebAppStore,
|
|
41
41
|
PostgreSQLAssistantStore: () => PostgreSQLAssistantStore,
|
|
42
|
+
PostgreSQLCapabilityBundleStore: () => PostgreSQLCapabilityBundleStore,
|
|
42
43
|
PostgreSQLChannelInstallationStore: () => PostgreSQLChannelInstallationStore,
|
|
43
44
|
PostgreSQLCollectionStore: () => PostgreSQLCollectionStore,
|
|
44
45
|
PostgreSQLConnectionStore: () => PostgreSQLConnectionStore,
|
|
@@ -77,6 +78,7 @@ __export(index_exports, {
|
|
|
77
78
|
createA2AApiKeysTable: () => createA2AApiKeysTable,
|
|
78
79
|
createAgentWebAppsTable: () => createAgentWebAppsTable,
|
|
79
80
|
createAssistantsTable: () => createAssistantsTable,
|
|
81
|
+
createCapabilityBundlesTable: () => createCapabilityBundlesTable,
|
|
80
82
|
createChannelBindingsTable: () => createChannelBindingsTable,
|
|
81
83
|
createChannelIdentityMappingTables: () => createChannelIdentityMappingTables,
|
|
82
84
|
createChannelInstallationsTable: () => createChannelInstallationsTable,
|
|
@@ -109,10 +111,10 @@ __export(index_exports, {
|
|
|
109
111
|
safeParse: () => safeParse
|
|
110
112
|
});
|
|
111
113
|
module.exports = __toCommonJS(index_exports);
|
|
112
|
-
var
|
|
114
|
+
var import_pg28 = require("pg");
|
|
113
115
|
|
|
114
116
|
// src/createPgStoreConfig.ts
|
|
115
|
-
var
|
|
117
|
+
var import_pg25 = require("pg");
|
|
116
118
|
|
|
117
119
|
// src/migrations/migration.ts
|
|
118
120
|
var MigrationManager = class {
|
|
@@ -2382,6 +2384,7 @@ var PostgreSQLWorkspaceStore = class {
|
|
|
2382
2384
|
|
|
2383
2385
|
// src/stores/PostgreSQLProjectStore.ts
|
|
2384
2386
|
var import_pg7 = require("pg");
|
|
2387
|
+
var import_protocols = require("@axiom-lattice/protocols");
|
|
2385
2388
|
|
|
2386
2389
|
// src/migrations/project_migrations.ts
|
|
2387
2390
|
var createProjectsTable = {
|
|
@@ -2597,34 +2600,62 @@ var PostgreSQLProjectStore = class {
|
|
|
2597
2600
|
* Create a new project
|
|
2598
2601
|
*/
|
|
2599
2602
|
async createProject(tenantId, workspaceId, id, data) {
|
|
2603
|
+
(0, import_protocols.assertGenericProjectConfig)(data.config);
|
|
2600
2604
|
await this.ensureInitialized();
|
|
2601
2605
|
const now = /* @__PURE__ */ new Date();
|
|
2602
2606
|
const kind = data.kind || "business";
|
|
2603
|
-
await this.pool.
|
|
2604
|
-
|
|
2605
|
-
|
|
2607
|
+
const client = await this.pool.connect();
|
|
2608
|
+
try {
|
|
2609
|
+
await client.query("BEGIN");
|
|
2610
|
+
await client.query(
|
|
2611
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2612
|
+
[tenantId]
|
|
2613
|
+
);
|
|
2614
|
+
await client.query(
|
|
2615
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2616
|
+
[id, tenantId]
|
|
2617
|
+
);
|
|
2618
|
+
const existing = await client.query(
|
|
2619
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2620
|
+
[id, tenantId]
|
|
2621
|
+
);
|
|
2622
|
+
const ids = existing.rows[0]?.config?.capabilityBundleIds;
|
|
2623
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2624
|
+
if (bundleIds.length > 0) {
|
|
2625
|
+
await client.query(
|
|
2626
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2627
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2628
|
+
ORDER BY bundle_id`,
|
|
2629
|
+
[tenantId, bundleIds]
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
const result = await client.query(
|
|
2633
|
+
`INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
|
|
2606
2634
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
2607
2635
|
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
2608
2636
|
workspace_id = EXCLUDED.workspace_id,
|
|
2609
2637
|
name = EXCLUDED.name,
|
|
2610
2638
|
description = EXCLUDED.description,
|
|
2611
|
-
config =
|
|
2639
|
+
config = CASE
|
|
2640
|
+
WHEN lattice_projects.config ? 'capabilityBundleIds'
|
|
2641
|
+
THEN COALESCE(EXCLUDED.config, '{}'::jsonb)
|
|
2642
|
+
|| jsonb_build_object('capabilityBundleIds', lattice_projects.config->'capabilityBundleIds')
|
|
2643
|
+
ELSE EXCLUDED.config
|
|
2644
|
+
END,
|
|
2612
2645
|
kind = EXCLUDED.kind,
|
|
2613
2646
|
updated_at = EXCLUDED.updated_at
|
|
2647
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
|
|
2614
2648
|
`,
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
createdAt: now,
|
|
2626
|
-
updatedAt: now
|
|
2627
|
-
};
|
|
2649
|
+
[id, tenantId, workspaceId, data.name, data.description || null, data.config || null, kind, now, now]
|
|
2650
|
+
);
|
|
2651
|
+
await client.query("COMMIT");
|
|
2652
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
await client.query("ROLLBACK");
|
|
2655
|
+
throw error;
|
|
2656
|
+
} finally {
|
|
2657
|
+
client.release();
|
|
2658
|
+
}
|
|
2628
2659
|
}
|
|
2629
2660
|
/**
|
|
2630
2661
|
* Update an existing project
|
|
@@ -2634,11 +2665,8 @@ var PostgreSQLProjectStore = class {
|
|
|
2634
2665
|
* overwrites the existing config. To preserve the current config, omit this field.
|
|
2635
2666
|
*/
|
|
2636
2667
|
async updateProject(tenantId, id, updates) {
|
|
2668
|
+
(0, import_protocols.assertGenericProjectConfig)(updates.config);
|
|
2637
2669
|
await this.ensureInitialized();
|
|
2638
|
-
const existing = await this.getProjectById(tenantId, id);
|
|
2639
|
-
if (!existing) {
|
|
2640
|
-
return null;
|
|
2641
|
-
}
|
|
2642
2670
|
const updateFields = [];
|
|
2643
2671
|
const updateValues = [];
|
|
2644
2672
|
let paramIndex = 1;
|
|
@@ -2651,43 +2679,190 @@ var PostgreSQLProjectStore = class {
|
|
|
2651
2679
|
updateValues.push(updates.description || null);
|
|
2652
2680
|
}
|
|
2653
2681
|
if (updates.config !== void 0) {
|
|
2654
|
-
|
|
2655
|
-
|
|
2682
|
+
const configParam = `$${paramIndex++}`;
|
|
2683
|
+
updateFields.push(`config = CASE
|
|
2684
|
+
WHEN config ? 'capabilityBundleIds'
|
|
2685
|
+
THEN COALESCE(${configParam}::jsonb, '{}'::jsonb)
|
|
2686
|
+
|| jsonb_build_object('capabilityBundleIds', config->'capabilityBundleIds')
|
|
2687
|
+
ELSE ${configParam}::jsonb
|
|
2688
|
+
END`);
|
|
2689
|
+
updateValues.push(updates.config);
|
|
2656
2690
|
}
|
|
2657
2691
|
if (updates.kind !== void 0) {
|
|
2658
2692
|
updateFields.push(`kind = $${paramIndex++}`);
|
|
2659
2693
|
updateValues.push(updates.kind);
|
|
2660
2694
|
}
|
|
2661
2695
|
if (updateFields.length === 0) {
|
|
2662
|
-
return
|
|
2696
|
+
return await this.getProjectById(tenantId, id);
|
|
2663
2697
|
}
|
|
2664
2698
|
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
2665
2699
|
updateValues.push(/* @__PURE__ */ new Date());
|
|
2666
|
-
updateValues.push(id);
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2700
|
+
updateValues.push(id, tenantId);
|
|
2701
|
+
const client = await this.pool.connect();
|
|
2702
|
+
try {
|
|
2703
|
+
await client.query("BEGIN");
|
|
2704
|
+
await client.query(
|
|
2705
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2706
|
+
[tenantId]
|
|
2707
|
+
);
|
|
2708
|
+
await client.query(
|
|
2709
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2710
|
+
[id, tenantId]
|
|
2711
|
+
);
|
|
2712
|
+
const existing = await client.query(
|
|
2713
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2714
|
+
[id, tenantId]
|
|
2715
|
+
);
|
|
2716
|
+
if (!existing.rows[0]) {
|
|
2717
|
+
await client.query("COMMIT");
|
|
2718
|
+
return null;
|
|
2719
|
+
}
|
|
2720
|
+
const ids = existing.rows[0].config?.capabilityBundleIds;
|
|
2721
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2722
|
+
if (bundleIds.length > 0) {
|
|
2723
|
+
await client.query(
|
|
2724
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2725
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2726
|
+
ORDER BY bundle_id`,
|
|
2727
|
+
[tenantId, bundleIds]
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
const result = await client.query(
|
|
2731
|
+
`UPDATE lattice_projects SET ${updateFields.join(", ")}
|
|
2732
|
+
WHERE id = $${paramIndex} AND tenant_id = $${paramIndex + 1}
|
|
2733
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2734
|
+
updateValues
|
|
2735
|
+
);
|
|
2736
|
+
await client.query("COMMIT");
|
|
2737
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2738
|
+
} catch (error) {
|
|
2739
|
+
await client.query("ROLLBACK");
|
|
2740
|
+
throw error;
|
|
2741
|
+
} finally {
|
|
2742
|
+
client.release();
|
|
2743
|
+
}
|
|
2677
2744
|
}
|
|
2678
2745
|
/**
|
|
2679
2746
|
* Delete a project by ID
|
|
2680
2747
|
*/
|
|
2681
2748
|
async deleteProject(tenantId, id) {
|
|
2749
|
+
await this.ensureInitialized();
|
|
2750
|
+
const client = await this.pool.connect();
|
|
2751
|
+
try {
|
|
2752
|
+
await client.query("BEGIN");
|
|
2753
|
+
await client.query(
|
|
2754
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2755
|
+
[tenantId]
|
|
2756
|
+
);
|
|
2757
|
+
await client.query(
|
|
2758
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2759
|
+
[id, tenantId]
|
|
2760
|
+
);
|
|
2761
|
+
const selected = await client.query(
|
|
2762
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2763
|
+
[id, tenantId]
|
|
2764
|
+
);
|
|
2765
|
+
if (!selected.rows[0]) {
|
|
2766
|
+
await client.query("COMMIT");
|
|
2767
|
+
return false;
|
|
2768
|
+
}
|
|
2769
|
+
const ids = selected.rows[0].config?.capabilityBundleIds;
|
|
2770
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2771
|
+
if (bundleIds.length > 0) {
|
|
2772
|
+
await client.query(
|
|
2773
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2774
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2775
|
+
ORDER BY bundle_id`,
|
|
2776
|
+
[tenantId, bundleIds]
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
const result = await client.query(
|
|
2780
|
+
"DELETE FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2781
|
+
[id, tenantId]
|
|
2782
|
+
);
|
|
2783
|
+
await client.query("COMMIT");
|
|
2784
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
await client.query("ROLLBACK");
|
|
2787
|
+
throw error;
|
|
2788
|
+
} finally {
|
|
2789
|
+
client.release();
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
2793
|
+
await this.ensureInitialized();
|
|
2794
|
+
const client = await this.pool.connect();
|
|
2795
|
+
try {
|
|
2796
|
+
await client.query("BEGIN");
|
|
2797
|
+
await client.query(
|
|
2798
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2799
|
+
[tenantId]
|
|
2800
|
+
);
|
|
2801
|
+
await client.query(
|
|
2802
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2803
|
+
[projectId, tenantId]
|
|
2804
|
+
);
|
|
2805
|
+
await client.query(
|
|
2806
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2807
|
+
[projectId, tenantId]
|
|
2808
|
+
);
|
|
2809
|
+
if (bundleIds.length > 0) {
|
|
2810
|
+
await client.query(
|
|
2811
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2812
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2813
|
+
ORDER BY bundle_id`,
|
|
2814
|
+
[tenantId, [...bundleIds].sort()]
|
|
2815
|
+
);
|
|
2816
|
+
if (Object.keys(expectedRevisions).length > 0) {
|
|
2817
|
+
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]);
|
|
2818
|
+
if (revisions.rows.some((bundle) => expectedRevisions[bundle.id] !== void 0 && expectedRevisions[bundle.id] !== bundle.updated_at)) {
|
|
2819
|
+
await client.query("ROLLBACK");
|
|
2820
|
+
return { status: "bundle_conflict" };
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
const result = await client.query(
|
|
2825
|
+
`UPDATE lattice_projects
|
|
2826
|
+
SET config = COALESCE(config, '{}'::jsonb) || jsonb_build_object('capabilityBundleIds', $3::jsonb), updated_at = NOW()
|
|
2827
|
+
WHERE id = $1 AND tenant_id = $2
|
|
2828
|
+
AND NOT EXISTS (
|
|
2829
|
+
SELECT 1 FROM unnest($4::uuid[]) AS bundle_id
|
|
2830
|
+
WHERE NOT EXISTS (
|
|
2831
|
+
SELECT 1 FROM lattice_capability_bundles
|
|
2832
|
+
WHERE tenant_id = $2 AND id = bundle_id
|
|
2833
|
+
)
|
|
2834
|
+
)
|
|
2835
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2836
|
+
[projectId, tenantId, JSON.stringify(bundleIds), bundleIds]
|
|
2837
|
+
);
|
|
2838
|
+
if (result.rows[0]) {
|
|
2839
|
+
await client.query("COMMIT");
|
|
2840
|
+
return { status: "updated", project: this.mapRowToProject(result.rows[0]) };
|
|
2841
|
+
}
|
|
2842
|
+
const project = await client.query(
|
|
2843
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2844
|
+
[projectId, tenantId]
|
|
2845
|
+
);
|
|
2846
|
+
await client.query("COMMIT");
|
|
2847
|
+
return project.rows.length > 0 ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
await client.query("ROLLBACK");
|
|
2850
|
+
throw error;
|
|
2851
|
+
} finally {
|
|
2852
|
+
client.release();
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
2682
2856
|
await this.ensureInitialized();
|
|
2683
2857
|
const result = await this.pool.query(
|
|
2684
|
-
`
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2858
|
+
`SELECT 1 AS exists FROM lattice_projects
|
|
2859
|
+
WHERE tenant_id = $1
|
|
2860
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
2861
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
2862
|
+
LIMIT 1`,
|
|
2863
|
+
[tenantId, bundleId]
|
|
2689
2864
|
);
|
|
2690
|
-
return result.
|
|
2865
|
+
return result.rows.length > 0;
|
|
2691
2866
|
}
|
|
2692
2867
|
};
|
|
2693
2868
|
|
|
@@ -5170,10 +5345,48 @@ var ThreadMessageQueueStore = class {
|
|
|
5170
5345
|
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5171
5346
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
5172
5347
|
RETURNING *`,
|
|
5173
|
-
[id || import_crypto.default.randomUUID(), threadId, tenantId, assistantId, workspaceId
|
|
5348
|
+
[id || import_crypto.default.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]
|
|
5174
5349
|
);
|
|
5175
5350
|
return this.rowToMessage(result.rows[0]);
|
|
5176
5351
|
}
|
|
5352
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
5353
|
+
if (maxSize === Infinity) {
|
|
5354
|
+
await this.addMessage(params);
|
|
5355
|
+
return true;
|
|
5356
|
+
}
|
|
5357
|
+
const client = await this.pool.connect();
|
|
5358
|
+
try {
|
|
5359
|
+
await client.query("BEGIN");
|
|
5360
|
+
const scope = { tenantId: params.tenantId, assistantId: params.assistantId, workspaceId: params.workspaceId, projectId: params.projectId };
|
|
5361
|
+
const lockKey = `${params.tenantId}:${params.assistantId}:${params.threadId}:${params.workspaceId ?? ""}:${params.projectId ?? ""}`;
|
|
5362
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtext($1))", [lockKey]);
|
|
5363
|
+
const filter = scopeClause(scope, 2);
|
|
5364
|
+
const count = await client.query(
|
|
5365
|
+
`SELECT COUNT(*) as count FROM lattice_thread_message_queue WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5366
|
+
[params.threadId, ...filter.params]
|
|
5367
|
+
);
|
|
5368
|
+
if (parseInt(count.rows[0].count, 10) >= maxSize) {
|
|
5369
|
+
await client.query("ROLLBACK");
|
|
5370
|
+
return false;
|
|
5371
|
+
}
|
|
5372
|
+
const seq = await client.query(
|
|
5373
|
+
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5374
|
+
[params.threadId]
|
|
5375
|
+
);
|
|
5376
|
+
const result = await client.query(
|
|
5377
|
+
`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)
|
|
5378
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
|
|
5379
|
+
[params.id || import_crypto.default.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]
|
|
5380
|
+
);
|
|
5381
|
+
await client.query("COMMIT");
|
|
5382
|
+
return Boolean(result.rows[0]);
|
|
5383
|
+
} catch (error) {
|
|
5384
|
+
await client.query("ROLLBACK");
|
|
5385
|
+
throw error;
|
|
5386
|
+
} finally {
|
|
5387
|
+
client.release();
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5177
5390
|
/**
|
|
5178
5391
|
* Add message at head of queue (high priority, e.g., STEER/Command messages)
|
|
5179
5392
|
* Uses priority=100 to ensure message is processed first
|
|
@@ -5194,42 +5407,45 @@ var ThreadMessageQueueStore = class {
|
|
|
5194
5407
|
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5195
5408
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 100, $10, $11)
|
|
5196
5409
|
RETURNING *`,
|
|
5197
|
-
[id || import_crypto.default.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId
|
|
5410
|
+
[id || import_crypto.default.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]
|
|
5198
5411
|
);
|
|
5199
5412
|
return this.rowToMessage(result.rows[0]);
|
|
5200
5413
|
}
|
|
5201
5414
|
/**
|
|
5202
5415
|
* Get pending messages for thread
|
|
5203
5416
|
*/
|
|
5204
|
-
async getPendingMessages(threadId) {
|
|
5417
|
+
async getPendingMessages(threadId, scope) {
|
|
5418
|
+
const filter = scopeClause(scope, 2);
|
|
5205
5419
|
const result = await this.pool.query(
|
|
5206
|
-
`SELECT * FROM lattice_thread_message_queue
|
|
5207
|
-
|
|
5420
|
+
`SELECT * FROM lattice_thread_message_queue
|
|
5421
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}
|
|
5208
5422
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5209
|
-
[threadId]
|
|
5423
|
+
[threadId, ...filter.params]
|
|
5210
5424
|
);
|
|
5211
5425
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5212
5426
|
}
|
|
5213
5427
|
/**
|
|
5214
5428
|
* Get processing messages for a thread
|
|
5215
5429
|
*/
|
|
5216
|
-
async getProcessingMessages(threadId) {
|
|
5430
|
+
async getProcessingMessages(threadId, scope) {
|
|
5431
|
+
const filter = scopeClause(scope, 2);
|
|
5217
5432
|
const result = await this.pool.query(
|
|
5218
5433
|
`SELECT * FROM lattice_thread_message_queue
|
|
5219
|
-
|
|
5434
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5220
5435
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5221
|
-
[threadId]
|
|
5436
|
+
[threadId, ...filter.params]
|
|
5222
5437
|
);
|
|
5223
5438
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5224
5439
|
}
|
|
5225
5440
|
/**
|
|
5226
5441
|
* Get queue size
|
|
5227
5442
|
*/
|
|
5228
|
-
async getQueueSize(threadId) {
|
|
5443
|
+
async getQueueSize(threadId, scope) {
|
|
5444
|
+
const filter = scopeClause(scope, 2);
|
|
5229
5445
|
const result = await this.pool.query(
|
|
5230
5446
|
`SELECT COUNT(*) as count FROM lattice_thread_message_queue
|
|
5231
|
-
|
|
5232
|
-
[threadId]
|
|
5447
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5448
|
+
[threadId, ...filter.params]
|
|
5233
5449
|
);
|
|
5234
5450
|
return parseInt(result.rows[0].count, 10);
|
|
5235
5451
|
}
|
|
@@ -5238,58 +5454,70 @@ var ThreadMessageQueueStore = class {
|
|
|
5238
5454
|
*/
|
|
5239
5455
|
async getThreadsWithPendingMessages() {
|
|
5240
5456
|
const result = await this.pool.query(
|
|
5241
|
-
`SELECT DISTINCT ON (thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5457
|
+
`SELECT DISTINCT ON (tenant_id, assistant_id, workspace_id, project_id, thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5242
5458
|
FROM lattice_thread_message_queue
|
|
5243
5459
|
WHERE status IN ('pending', 'processing')
|
|
5244
|
-
|
|
5460
|
+
ORDER BY tenant_id, assistant_id, workspace_id, project_id, thread_id`
|
|
5245
5461
|
);
|
|
5246
5462
|
return result.rows.map((row) => ({
|
|
5247
5463
|
tenantId: row.tenant_id,
|
|
5248
5464
|
assistantId: row.assistant_id,
|
|
5249
5465
|
threadId: row.thread_id,
|
|
5250
|
-
workspaceId: row.workspace_id
|
|
5251
|
-
projectId: row.project_id
|
|
5466
|
+
workspaceId: row.workspace_id,
|
|
5467
|
+
projectId: row.project_id
|
|
5252
5468
|
}));
|
|
5253
5469
|
}
|
|
5254
5470
|
/**
|
|
5255
5471
|
* Remove message
|
|
5256
5472
|
*/
|
|
5257
|
-
async removeMessage(messageId) {
|
|
5473
|
+
async removeMessage(messageId, scope) {
|
|
5474
|
+
const filter = scopeClause(scope, 2);
|
|
5258
5475
|
const result = await this.pool.query(
|
|
5259
|
-
`DELETE FROM lattice_thread_message_queue WHERE id = $1 RETURNING id`,
|
|
5260
|
-
[messageId]
|
|
5476
|
+
`DELETE FROM lattice_thread_message_queue WHERE id = $1${filter.sql} RETURNING id`,
|
|
5477
|
+
[messageId, ...filter.params]
|
|
5261
5478
|
);
|
|
5262
5479
|
return (result.rowCount ?? 0) > 0;
|
|
5263
5480
|
}
|
|
5264
5481
|
/**
|
|
5265
5482
|
* Clear all messages for thread
|
|
5266
5483
|
*/
|
|
5267
|
-
async clearMessages(threadId) {
|
|
5484
|
+
async clearMessages(threadId, scope) {
|
|
5485
|
+
const filter = scopeClause(scope, 2);
|
|
5268
5486
|
await this.pool.query(
|
|
5269
|
-
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5270
|
-
[threadId]
|
|
5487
|
+
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1${filter.sql}`,
|
|
5488
|
+
[threadId, ...filter.params]
|
|
5271
5489
|
);
|
|
5272
5490
|
}
|
|
5273
5491
|
/**
|
|
5274
5492
|
* Mark message as processing
|
|
5275
5493
|
*/
|
|
5276
|
-
async markProcessing(messageId) {
|
|
5494
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
5495
|
+
if (customRunConfig !== void 0) {
|
|
5496
|
+
const filter2 = scopeClause(scope, 3);
|
|
5497
|
+
await this.pool.query(
|
|
5498
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing', custom_run_config = $2 WHERE id = $1${filter2.sql}`,
|
|
5499
|
+
[messageId, JSON.stringify(customRunConfig), ...filter2.params]
|
|
5500
|
+
);
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5503
|
+
const filter = scopeClause(scope, 2);
|
|
5277
5504
|
await this.pool.query(
|
|
5278
|
-
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1`,
|
|
5279
|
-
[messageId]
|
|
5505
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1${filter.sql}`,
|
|
5506
|
+
[messageId, ...filter.params]
|
|
5280
5507
|
);
|
|
5281
5508
|
}
|
|
5282
5509
|
/**
|
|
5283
5510
|
* Reset all processing messages to pending state for a thread
|
|
5284
5511
|
* Returns the number of messages reset
|
|
5285
5512
|
*/
|
|
5286
|
-
async resetProcessingToPending(threadId) {
|
|
5513
|
+
async resetProcessingToPending(threadId, scope) {
|
|
5514
|
+
const filter = scopeClause(scope, 2);
|
|
5287
5515
|
const result = await this.pool.query(
|
|
5288
5516
|
`UPDATE lattice_thread_message_queue
|
|
5289
5517
|
SET status = 'pending'
|
|
5290
|
-
|
|
5518
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5291
5519
|
RETURNING id`,
|
|
5292
|
-
[threadId]
|
|
5520
|
+
[threadId, ...filter.params]
|
|
5293
5521
|
);
|
|
5294
5522
|
return result.rowCount ?? 0;
|
|
5295
5523
|
}
|
|
@@ -5306,6 +5534,18 @@ var ThreadMessageQueueStore = class {
|
|
|
5306
5534
|
};
|
|
5307
5535
|
}
|
|
5308
5536
|
};
|
|
5537
|
+
function scopeClause(scope, start) {
|
|
5538
|
+
if (!scope) return { sql: "", params: [] };
|
|
5539
|
+
const params = [];
|
|
5540
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
5541
|
+
const sql = entries.map(([key, value]) => {
|
|
5542
|
+
const column = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
5543
|
+
if (value == null) return ` AND ${column} IS NULL`;
|
|
5544
|
+
params.push(value);
|
|
5545
|
+
return ` AND ${column} = $${start + params.length - 1}`;
|
|
5546
|
+
}).join("");
|
|
5547
|
+
return { sql, params };
|
|
5548
|
+
}
|
|
5309
5549
|
|
|
5310
5550
|
// src/stores/ChannelBindingStore.ts
|
|
5311
5551
|
var import_pg14 = require("pg");
|
|
@@ -6056,17 +6296,17 @@ var PostgreSQLA2AApiKeyStore = class {
|
|
|
6056
6296
|
const result = await this.pool.query(
|
|
6057
6297
|
`SELECT * FROM lattice_a2a_api_keys WHERE enabled = true`
|
|
6058
6298
|
);
|
|
6059
|
-
const
|
|
6299
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
6060
6300
|
for (const row of result.rows) {
|
|
6061
6301
|
const key = (0, import_core5.decrypt)(row.key_value);
|
|
6062
|
-
|
|
6302
|
+
map2.set(key, {
|
|
6063
6303
|
key,
|
|
6064
6304
|
tenantId: row.tenant_id,
|
|
6065
6305
|
projectId: row.project_id,
|
|
6066
6306
|
assistantIds: row.assistant_ids ?? void 0
|
|
6067
6307
|
});
|
|
6068
6308
|
}
|
|
6069
|
-
return
|
|
6309
|
+
return map2;
|
|
6070
6310
|
}
|
|
6071
6311
|
};
|
|
6072
6312
|
|
|
@@ -6296,7 +6536,7 @@ var PostgreSQLAgentWebAppStore = class {
|
|
|
6296
6536
|
|
|
6297
6537
|
// src/stores/PostgreSQLScheduleStorage.ts
|
|
6298
6538
|
var import_pg18 = require("pg");
|
|
6299
|
-
var
|
|
6539
|
+
var import_protocols2 = require("@axiom-lattice/protocols");
|
|
6300
6540
|
|
|
6301
6541
|
// src/migrations/schedule_migrations.ts
|
|
6302
6542
|
var createScheduledTasksTable = {
|
|
@@ -6710,7 +6950,7 @@ var PostgreSQLScheduleStorage = class {
|
|
|
6710
6950
|
await this.ensureInitialized();
|
|
6711
6951
|
const result = await this.pool.query(
|
|
6712
6952
|
`SELECT * FROM lattice_scheduled_tasks WHERE status IN ($1, $2) ORDER BY created_at ASC`,
|
|
6713
|
-
[
|
|
6953
|
+
[import_protocols2.ScheduledTaskStatus.PENDING, import_protocols2.ScheduledTaskStatus.PAUSED]
|
|
6714
6954
|
);
|
|
6715
6955
|
return result.rows.map((row) => this.mapRowToTask(row));
|
|
6716
6956
|
}
|
|
@@ -6869,9 +7109,9 @@ var PostgreSQLScheduleStorage = class {
|
|
|
6869
7109
|
AND updated_at < $4
|
|
6870
7110
|
`,
|
|
6871
7111
|
[
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
7112
|
+
import_protocols2.ScheduledTaskStatus.COMPLETED,
|
|
7113
|
+
import_protocols2.ScheduledTaskStatus.CANCELLED,
|
|
7114
|
+
import_protocols2.ScheduledTaskStatus.FAILED,
|
|
6875
7115
|
cutoff
|
|
6876
7116
|
]
|
|
6877
7117
|
);
|
|
@@ -7737,6 +7977,7 @@ var PostgreSQLTaskStore = class {
|
|
|
7737
7977
|
};
|
|
7738
7978
|
|
|
7739
7979
|
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
7980
|
+
var import_protocols3 = require("@axiom-lattice/protocols");
|
|
7740
7981
|
var import_uuid4 = require("uuid");
|
|
7741
7982
|
var PostgreSQLTaskWorkItemStore = class {
|
|
7742
7983
|
constructor(pool) {
|
|
@@ -7828,6 +8069,35 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7828
8069
|
const result = await this.pool.query(query, params);
|
|
7829
8070
|
return result.rows.map((row) => this.rowToItem(row));
|
|
7830
8071
|
}
|
|
8072
|
+
/** List pending execution results using one bounded PostgreSQL anti-join query. */
|
|
8073
|
+
async listPendingExecutionResults(params) {
|
|
8074
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > import_protocols3.MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
8075
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${import_protocols3.MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
8076
|
+
error.code = "INVALID_LIMIT";
|
|
8077
|
+
throw error;
|
|
8078
|
+
}
|
|
8079
|
+
if (params.limit === 0) return [];
|
|
8080
|
+
const result = await this.pool.query(
|
|
8081
|
+
`SELECT result.*
|
|
8082
|
+
FROM lattice_task_work_items AS result
|
|
8083
|
+
WHERE result.tenant_id = $1
|
|
8084
|
+
AND result.task_id = $2
|
|
8085
|
+
AND result.action = 'execution_result'
|
|
8086
|
+
AND result.event_key COLLATE "C" ~ '^execution-result:[A-Za-z0-9._:-]+$'
|
|
8087
|
+
AND NOT EXISTS (
|
|
8088
|
+
SELECT 1
|
|
8089
|
+
FROM lattice_task_work_items AS reconciled
|
|
8090
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
8091
|
+
AND reconciled.task_id = result.task_id
|
|
8092
|
+
AND reconciled.action = 'execution_reconciled'
|
|
8093
|
+
AND reconciled.detail ->> 'executionResultId' = result.event_key
|
|
8094
|
+
)
|
|
8095
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
8096
|
+
LIMIT $3`,
|
|
8097
|
+
[params.tenantId, params.taskId, params.limit]
|
|
8098
|
+
);
|
|
8099
|
+
return result.rows.map((row) => this.rowToItem(row));
|
|
8100
|
+
}
|
|
7831
8101
|
rowToItem(row) {
|
|
7832
8102
|
return {
|
|
7833
8103
|
id: row.id,
|
|
@@ -8855,10 +9125,268 @@ var addTaskWorkItemEventKeyMigration = {
|
|
|
8855
9125
|
`);
|
|
8856
9126
|
}
|
|
8857
9127
|
};
|
|
9128
|
+
var addTaskWorkItemPendingIndexesMigration = {
|
|
9129
|
+
version: 171,
|
|
9130
|
+
name: "add_task_work_item_pending_indexes",
|
|
9131
|
+
up: async (client) => {
|
|
9132
|
+
await client.query(`
|
|
9133
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
9134
|
+
ON lattice_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC)
|
|
9135
|
+
`);
|
|
9136
|
+
await client.query(`
|
|
9137
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
9138
|
+
ON lattice_task_work_items (tenant_id, task_id, (detail ->> 'executionResultId'))
|
|
9139
|
+
WHERE action = 'execution_reconciled'
|
|
9140
|
+
`);
|
|
9141
|
+
}
|
|
9142
|
+
};
|
|
9143
|
+
|
|
9144
|
+
// src/migrations/capability_bundle_migration.ts
|
|
9145
|
+
var createCapabilityBundlesTable = {
|
|
9146
|
+
version: 170,
|
|
9147
|
+
name: "create_capability_bundles_table",
|
|
9148
|
+
up: async (client) => {
|
|
9149
|
+
await client.query(`CREATE TABLE IF NOT EXISTS lattice_capability_bundles (
|
|
9150
|
+
id UUID PRIMARY KEY, tenant_id VARCHAR(255) NOT NULL, bundle_key VARCHAR(255) NOT NULL,
|
|
9151
|
+
name VARCHAR(255) NOT NULL, description TEXT, capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9152
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9153
|
+
UNIQUE (tenant_id, bundle_key)
|
|
9154
|
+
)`);
|
|
9155
|
+
await client.query("CREATE INDEX IF NOT EXISTS idx_lattice_capability_bundles_tenant ON lattice_capability_bundles(tenant_id)");
|
|
9156
|
+
},
|
|
9157
|
+
down: async (client) => {
|
|
9158
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_capability_bundles_tenant");
|
|
9159
|
+
await client.query("DROP TABLE IF EXISTS lattice_capability_bundles");
|
|
9160
|
+
}
|
|
9161
|
+
};
|
|
9162
|
+
|
|
9163
|
+
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
9164
|
+
var import_crypto4 = require("crypto");
|
|
9165
|
+
var import_pg24 = require("pg");
|
|
9166
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
9167
|
+
function map(row) {
|
|
9168
|
+
return {
|
|
9169
|
+
id: row.id,
|
|
9170
|
+
tenantId: row.tenant_id,
|
|
9171
|
+
key: row.bundle_key,
|
|
9172
|
+
name: row.name,
|
|
9173
|
+
description: row.description ?? void 0,
|
|
9174
|
+
capabilities: row.capabilities,
|
|
9175
|
+
createdAt: row.created_at.toISOString(),
|
|
9176
|
+
updatedAt: typeof row.updated_at === "string" ? row.updated_at : row.updated_at.toISOString()
|
|
9177
|
+
};
|
|
9178
|
+
}
|
|
9179
|
+
function isDuplicate(error) {
|
|
9180
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
9181
|
+
}
|
|
9182
|
+
var PostgreSQLCapabilityBundleStore = class {
|
|
9183
|
+
/**
|
|
9184
|
+
* Creates a PostgreSQL capability bundle store.
|
|
9185
|
+
*
|
|
9186
|
+
* @param options - Pool ownership, connection, and migration options.
|
|
9187
|
+
*/
|
|
9188
|
+
constructor(options) {
|
|
9189
|
+
this.initialized = false;
|
|
9190
|
+
this.ownsPool = true;
|
|
9191
|
+
this.initPromise = null;
|
|
9192
|
+
if (options.pool) {
|
|
9193
|
+
this.pool = options.pool;
|
|
9194
|
+
this.ownsPool = false;
|
|
9195
|
+
this.initialized = true;
|
|
9196
|
+
return;
|
|
9197
|
+
}
|
|
9198
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg24.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg24.Pool(options.poolConfig) : (() => {
|
|
9199
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
9200
|
+
})();
|
|
9201
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
9202
|
+
this.migrationManager.register(createCapabilityBundlesTable);
|
|
9203
|
+
if (options.autoMigrate !== false) {
|
|
9204
|
+
this.startInitialization();
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
/**
|
|
9208
|
+
* Applies pending migrations for an internally managed pool.
|
|
9209
|
+
*
|
|
9210
|
+
* @returns A shared promise that resolves when initialization completes.
|
|
9211
|
+
*/
|
|
9212
|
+
async initialize() {
|
|
9213
|
+
if (this.initialized) return;
|
|
9214
|
+
if (this.initPromise) return this.initPromise;
|
|
9215
|
+
return this.startInitialization();
|
|
9216
|
+
}
|
|
9217
|
+
startInitialization() {
|
|
9218
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
9219
|
+
this.initialized = true;
|
|
9220
|
+
});
|
|
9221
|
+
void this.initPromise.catch(() => void 0);
|
|
9222
|
+
return this.initPromise;
|
|
9223
|
+
}
|
|
9224
|
+
/**
|
|
9225
|
+
* Closes the pool when it was created by this store.
|
|
9226
|
+
*
|
|
9227
|
+
* @returns A promise that resolves after owned resources are released.
|
|
9228
|
+
*/
|
|
9229
|
+
async dispose() {
|
|
9230
|
+
if (this.ownsPool) await this.pool.end();
|
|
9231
|
+
}
|
|
9232
|
+
async ready() {
|
|
9233
|
+
if (!this.initialized) await this.initialize();
|
|
9234
|
+
}
|
|
9235
|
+
async listByTenant(tenantId) {
|
|
9236
|
+
await this.ready();
|
|
9237
|
+
const result = await this.pool.query(
|
|
9238
|
+
"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",
|
|
9239
|
+
[tenantId]
|
|
9240
|
+
);
|
|
9241
|
+
return result.rows.map(map);
|
|
9242
|
+
}
|
|
9243
|
+
async getById(tenantId, id) {
|
|
9244
|
+
await this.ready();
|
|
9245
|
+
const result = await this.pool.query(
|
|
9246
|
+
"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",
|
|
9247
|
+
[tenantId, id]
|
|
9248
|
+
);
|
|
9249
|
+
return result.rows[0] ? map(result.rows[0]) : null;
|
|
9250
|
+
}
|
|
9251
|
+
async getManyByIds(tenantId, ids) {
|
|
9252
|
+
await this.ready();
|
|
9253
|
+
if (ids.length === 0) return [];
|
|
9254
|
+
const result = await this.pool.query(
|
|
9255
|
+
"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[])",
|
|
9256
|
+
[tenantId, ids]
|
|
9257
|
+
);
|
|
9258
|
+
return result.rows.map(map);
|
|
9259
|
+
}
|
|
9260
|
+
async create(tenantId, input) {
|
|
9261
|
+
await this.ready();
|
|
9262
|
+
try {
|
|
9263
|
+
const result = await this.pool.query(
|
|
9264
|
+
`INSERT INTO lattice_capability_bundles
|
|
9265
|
+
(id, tenant_id, bundle_key, name, description, capabilities)
|
|
9266
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb)
|
|
9267
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9268
|
+
[
|
|
9269
|
+
(0, import_crypto4.randomUUID)(),
|
|
9270
|
+
tenantId,
|
|
9271
|
+
input.key,
|
|
9272
|
+
input.name,
|
|
9273
|
+
input.description ?? null,
|
|
9274
|
+
JSON.stringify(input.capabilities)
|
|
9275
|
+
]
|
|
9276
|
+
);
|
|
9277
|
+
return map(result.rows[0]);
|
|
9278
|
+
} catch (error) {
|
|
9279
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9280
|
+
throw error;
|
|
9281
|
+
}
|
|
9282
|
+
}
|
|
9283
|
+
async update(tenantId, id, input) {
|
|
9284
|
+
await this.ready();
|
|
9285
|
+
const fields = [];
|
|
9286
|
+
const values = [];
|
|
9287
|
+
const add = (field, value) => {
|
|
9288
|
+
values.push(value);
|
|
9289
|
+
fields.push(`${field} = $${values.length}`);
|
|
9290
|
+
};
|
|
9291
|
+
if (input.name !== void 0) add("name", input.name);
|
|
9292
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) {
|
|
9293
|
+
add("description", input.description ?? null);
|
|
9294
|
+
}
|
|
9295
|
+
if (input.capabilities !== void 0) {
|
|
9296
|
+
add("capabilities", JSON.stringify(input.capabilities));
|
|
9297
|
+
}
|
|
9298
|
+
if (fields.length === 0) {
|
|
9299
|
+
const expectedUpdatedAt2 = input.expectedUpdatedAt;
|
|
9300
|
+
const result = await this.pool.query(
|
|
9301
|
+
`SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at
|
|
9302
|
+
FROM lattice_capability_bundles
|
|
9303
|
+
WHERE tenant_id = $1 AND id = $2${expectedUpdatedAt2 === void 0 ? "" : " AND updated_at::text = $3"}`,
|
|
9304
|
+
expectedUpdatedAt2 === void 0 ? [tenantId, id] : [tenantId, id, expectedUpdatedAt2]
|
|
9305
|
+
);
|
|
9306
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9307
|
+
return expectedUpdatedAt2 === void 0 ? null : { status: "conflict" };
|
|
9308
|
+
}
|
|
9309
|
+
const expectedUpdatedAt = input.expectedUpdatedAt;
|
|
9310
|
+
values.push(tenantId, id);
|
|
9311
|
+
if (expectedUpdatedAt !== void 0) values.push(expectedUpdatedAt);
|
|
9312
|
+
const tenantParam = values.length - (expectedUpdatedAt === void 0 ? 1 : 2);
|
|
9313
|
+
const idParam = tenantParam + 1;
|
|
9314
|
+
const revisionPredicate = expectedUpdatedAt === void 0 ? "" : ` AND updated_at::text = $${values.length}`;
|
|
9315
|
+
try {
|
|
9316
|
+
const result = await this.pool.query(
|
|
9317
|
+
`UPDATE lattice_capability_bundles
|
|
9318
|
+
SET ${fields.join(", ")}, updated_at = GREATEST(updated_at + interval '1 microsecond', clock_timestamp())
|
|
9319
|
+
WHERE tenant_id = $${tenantParam} AND id = $${idParam}${revisionPredicate}
|
|
9320
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9321
|
+
values
|
|
9322
|
+
);
|
|
9323
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9324
|
+
return expectedUpdatedAt === void 0 ? null : { status: "conflict" };
|
|
9325
|
+
} catch (error) {
|
|
9326
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9327
|
+
throw error;
|
|
9328
|
+
}
|
|
9329
|
+
}
|
|
9330
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
9331
|
+
await this.ready();
|
|
9332
|
+
const client = await this.pool.connect();
|
|
9333
|
+
try {
|
|
9334
|
+
await client.query("BEGIN");
|
|
9335
|
+
await client.query(
|
|
9336
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
9337
|
+
[tenantId]
|
|
9338
|
+
);
|
|
9339
|
+
const projectIds = await client.query(
|
|
9340
|
+
`SELECT id FROM lattice_projects
|
|
9341
|
+
WHERE tenant_id = $1
|
|
9342
|
+
ORDER BY id`,
|
|
9343
|
+
[tenantId]
|
|
9344
|
+
);
|
|
9345
|
+
if (projectIds.rows.length > 0) {
|
|
9346
|
+
await client.query(
|
|
9347
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project:' || project_id, 0))
|
|
9348
|
+
FROM unnest($2::text[]) AS project_id
|
|
9349
|
+
ORDER BY project_id`,
|
|
9350
|
+
[tenantId, projectIds.rows.map((project) => project.id)]
|
|
9351
|
+
);
|
|
9352
|
+
}
|
|
9353
|
+
await client.query(
|
|
9354
|
+
`SELECT id FROM lattice_projects
|
|
9355
|
+
WHERE tenant_id = $1
|
|
9356
|
+
FOR UPDATE`,
|
|
9357
|
+
[tenantId]
|
|
9358
|
+
);
|
|
9359
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || $2, 0))", [tenantId, id]);
|
|
9360
|
+
const referenced = await client.query(
|
|
9361
|
+
`SELECT 1 FROM lattice_projects
|
|
9362
|
+
WHERE tenant_id = $1
|
|
9363
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
9364
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
9365
|
+
LIMIT 1`,
|
|
9366
|
+
[tenantId, id]
|
|
9367
|
+
);
|
|
9368
|
+
if (referenced.rows.length > 0) {
|
|
9369
|
+
await client.query("COMMIT");
|
|
9370
|
+
return "in_use";
|
|
9371
|
+
}
|
|
9372
|
+
const deleted = await client.query(
|
|
9373
|
+
"DELETE FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = $2 RETURNING id",
|
|
9374
|
+
[tenantId, id]
|
|
9375
|
+
);
|
|
9376
|
+
await client.query("COMMIT");
|
|
9377
|
+
return deleted.rows.length > 0 ? "deleted" : "not_found";
|
|
9378
|
+
} catch (error) {
|
|
9379
|
+
await client.query("ROLLBACK");
|
|
9380
|
+
throw error;
|
|
9381
|
+
} finally {
|
|
9382
|
+
client.release();
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
};
|
|
8858
9386
|
|
|
8859
9387
|
// src/createPgStoreConfig.ts
|
|
8860
9388
|
async function createPgStoreConfig(connectionString) {
|
|
8861
|
-
const pool = new
|
|
9389
|
+
const pool = new import_pg25.Pool({ connectionString });
|
|
8862
9390
|
const mm = new MigrationManager(pool);
|
|
8863
9391
|
mm.register(createThreadsTable);
|
|
8864
9392
|
mm.register(createScheduledTasksTable);
|
|
@@ -8913,6 +9441,8 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8913
9441
|
mm.register(addA2AKeyAssistantIds);
|
|
8914
9442
|
mm.register(addTaskWorkItemEventKeyMigration);
|
|
8915
9443
|
mm.register(createAgentWebAppsTable);
|
|
9444
|
+
mm.register(createCapabilityBundlesTable);
|
|
9445
|
+
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
8916
9446
|
await mm.migrate();
|
|
8917
9447
|
const checkpoint = import_langgraph_checkpoint_postgres.PostgresSaver.fromConnString(connectionString);
|
|
8918
9448
|
checkpoint.setup().catch((err) => {
|
|
@@ -8941,6 +9471,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8941
9471
|
taskWorkItem: taskWorkItemStore,
|
|
8942
9472
|
a2aApiKey: new PostgreSQLA2AApiKeyStore(opts),
|
|
8943
9473
|
agentWebApp: new PostgreSQLAgentWebAppStore(opts),
|
|
9474
|
+
capabilityBundle: new PostgreSQLCapabilityBundleStore(opts),
|
|
8944
9475
|
schedule: new PostgreSQLScheduleStorage(opts),
|
|
8945
9476
|
menu: new MenuStore(opts),
|
|
8946
9477
|
sharedResource: new PostgresSharedResourceStore(opts),
|
|
@@ -8951,7 +9482,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8951
9482
|
}
|
|
8952
9483
|
|
|
8953
9484
|
// src/stores/PostgreSQLSkillStore.ts
|
|
8954
|
-
var
|
|
9485
|
+
var import_pg26 = require("pg");
|
|
8955
9486
|
var PostgreSQLSkillStore = class {
|
|
8956
9487
|
constructor(options) {
|
|
8957
9488
|
this.initialized = false;
|
|
@@ -8964,9 +9495,9 @@ var PostgreSQLSkillStore = class {
|
|
|
8964
9495
|
return;
|
|
8965
9496
|
}
|
|
8966
9497
|
if (typeof options.poolConfig === "string") {
|
|
8967
|
-
this.pool = new
|
|
9498
|
+
this.pool = new import_pg26.Pool({ connectionString: options.poolConfig });
|
|
8968
9499
|
} else if (options.poolConfig) {
|
|
8969
|
-
this.pool = new
|
|
9500
|
+
this.pool = new import_pg26.Pool(options.poolConfig);
|
|
8970
9501
|
} else {
|
|
8971
9502
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8972
9503
|
}
|
|
@@ -9267,7 +9798,7 @@ var PostgreSQLSkillStore = class {
|
|
|
9267
9798
|
};
|
|
9268
9799
|
|
|
9269
9800
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
9270
|
-
var
|
|
9801
|
+
var import_pg27 = require("pg");
|
|
9271
9802
|
var ChannelIdentityMappingStore = class {
|
|
9272
9803
|
constructor(options) {
|
|
9273
9804
|
this.initialized = false;
|
|
@@ -9279,7 +9810,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
9279
9810
|
this.initialized = true;
|
|
9280
9811
|
return;
|
|
9281
9812
|
}
|
|
9282
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
9813
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg27.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg27.Pool(options.poolConfig) : (() => {
|
|
9283
9814
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9284
9815
|
})();
|
|
9285
9816
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9508,6 +10039,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9508
10039
|
PostgreSQLA2AApiKeyStore,
|
|
9509
10040
|
PostgreSQLAgentWebAppStore,
|
|
9510
10041
|
PostgreSQLAssistantStore,
|
|
10042
|
+
PostgreSQLCapabilityBundleStore,
|
|
9511
10043
|
PostgreSQLChannelInstallationStore,
|
|
9512
10044
|
PostgreSQLCollectionStore,
|
|
9513
10045
|
PostgreSQLConnectionStore,
|
|
@@ -9546,6 +10078,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9546
10078
|
createA2AApiKeysTable,
|
|
9547
10079
|
createAgentWebAppsTable,
|
|
9548
10080
|
createAssistantsTable,
|
|
10081
|
+
createCapabilityBundlesTable,
|
|
9549
10082
|
createChannelBindingsTable,
|
|
9550
10083
|
createChannelIdentityMappingTables,
|
|
9551
10084
|
createChannelInstallationsTable,
|