@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.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { Pool as
|
|
2
|
+
import { Pool as Pool28 } from "pg";
|
|
3
3
|
|
|
4
4
|
// src/createPgStoreConfig.ts
|
|
5
|
-
import { Pool as
|
|
5
|
+
import { Pool as Pool25 } from "pg";
|
|
6
6
|
|
|
7
7
|
// src/migrations/migration.ts
|
|
8
8
|
var MigrationManager = class {
|
|
@@ -2272,6 +2272,9 @@ var PostgreSQLWorkspaceStore = class {
|
|
|
2272
2272
|
|
|
2273
2273
|
// src/stores/PostgreSQLProjectStore.ts
|
|
2274
2274
|
import { Pool as Pool7 } from "pg";
|
|
2275
|
+
import {
|
|
2276
|
+
assertGenericProjectConfig
|
|
2277
|
+
} from "@axiom-lattice/protocols";
|
|
2275
2278
|
|
|
2276
2279
|
// src/migrations/project_migrations.ts
|
|
2277
2280
|
var createProjectsTable = {
|
|
@@ -2487,34 +2490,62 @@ var PostgreSQLProjectStore = class {
|
|
|
2487
2490
|
* Create a new project
|
|
2488
2491
|
*/
|
|
2489
2492
|
async createProject(tenantId, workspaceId, id, data) {
|
|
2493
|
+
assertGenericProjectConfig(data.config);
|
|
2490
2494
|
await this.ensureInitialized();
|
|
2491
2495
|
const now = /* @__PURE__ */ new Date();
|
|
2492
2496
|
const kind = data.kind || "business";
|
|
2493
|
-
await this.pool.
|
|
2494
|
-
|
|
2495
|
-
|
|
2497
|
+
const client = await this.pool.connect();
|
|
2498
|
+
try {
|
|
2499
|
+
await client.query("BEGIN");
|
|
2500
|
+
await client.query(
|
|
2501
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2502
|
+
[tenantId]
|
|
2503
|
+
);
|
|
2504
|
+
await client.query(
|
|
2505
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2506
|
+
[id, tenantId]
|
|
2507
|
+
);
|
|
2508
|
+
const existing = await client.query(
|
|
2509
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2510
|
+
[id, tenantId]
|
|
2511
|
+
);
|
|
2512
|
+
const ids = existing.rows[0]?.config?.capabilityBundleIds;
|
|
2513
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2514
|
+
if (bundleIds.length > 0) {
|
|
2515
|
+
await client.query(
|
|
2516
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2517
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2518
|
+
ORDER BY bundle_id`,
|
|
2519
|
+
[tenantId, bundleIds]
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
const result = await client.query(
|
|
2523
|
+
`INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
|
|
2496
2524
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
2497
2525
|
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
2498
2526
|
workspace_id = EXCLUDED.workspace_id,
|
|
2499
2527
|
name = EXCLUDED.name,
|
|
2500
2528
|
description = EXCLUDED.description,
|
|
2501
|
-
config =
|
|
2529
|
+
config = CASE
|
|
2530
|
+
WHEN lattice_projects.config ? 'capabilityBundleIds'
|
|
2531
|
+
THEN COALESCE(EXCLUDED.config, '{}'::jsonb)
|
|
2532
|
+
|| jsonb_build_object('capabilityBundleIds', lattice_projects.config->'capabilityBundleIds')
|
|
2533
|
+
ELSE EXCLUDED.config
|
|
2534
|
+
END,
|
|
2502
2535
|
kind = EXCLUDED.kind,
|
|
2503
2536
|
updated_at = EXCLUDED.updated_at
|
|
2537
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
|
|
2504
2538
|
`,
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
createdAt: now,
|
|
2516
|
-
updatedAt: now
|
|
2517
|
-
};
|
|
2539
|
+
[id, tenantId, workspaceId, data.name, data.description || null, data.config || null, kind, now, now]
|
|
2540
|
+
);
|
|
2541
|
+
await client.query("COMMIT");
|
|
2542
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
await client.query("ROLLBACK");
|
|
2545
|
+
throw error;
|
|
2546
|
+
} finally {
|
|
2547
|
+
client.release();
|
|
2548
|
+
}
|
|
2518
2549
|
}
|
|
2519
2550
|
/**
|
|
2520
2551
|
* Update an existing project
|
|
@@ -2524,11 +2555,8 @@ var PostgreSQLProjectStore = class {
|
|
|
2524
2555
|
* overwrites the existing config. To preserve the current config, omit this field.
|
|
2525
2556
|
*/
|
|
2526
2557
|
async updateProject(tenantId, id, updates) {
|
|
2558
|
+
assertGenericProjectConfig(updates.config);
|
|
2527
2559
|
await this.ensureInitialized();
|
|
2528
|
-
const existing = await this.getProjectById(tenantId, id);
|
|
2529
|
-
if (!existing) {
|
|
2530
|
-
return null;
|
|
2531
|
-
}
|
|
2532
2560
|
const updateFields = [];
|
|
2533
2561
|
const updateValues = [];
|
|
2534
2562
|
let paramIndex = 1;
|
|
@@ -2541,43 +2569,190 @@ var PostgreSQLProjectStore = class {
|
|
|
2541
2569
|
updateValues.push(updates.description || null);
|
|
2542
2570
|
}
|
|
2543
2571
|
if (updates.config !== void 0) {
|
|
2544
|
-
|
|
2545
|
-
|
|
2572
|
+
const configParam = `$${paramIndex++}`;
|
|
2573
|
+
updateFields.push(`config = CASE
|
|
2574
|
+
WHEN config ? 'capabilityBundleIds'
|
|
2575
|
+
THEN COALESCE(${configParam}::jsonb, '{}'::jsonb)
|
|
2576
|
+
|| jsonb_build_object('capabilityBundleIds', config->'capabilityBundleIds')
|
|
2577
|
+
ELSE ${configParam}::jsonb
|
|
2578
|
+
END`);
|
|
2579
|
+
updateValues.push(updates.config);
|
|
2546
2580
|
}
|
|
2547
2581
|
if (updates.kind !== void 0) {
|
|
2548
2582
|
updateFields.push(`kind = $${paramIndex++}`);
|
|
2549
2583
|
updateValues.push(updates.kind);
|
|
2550
2584
|
}
|
|
2551
2585
|
if (updateFields.length === 0) {
|
|
2552
|
-
return
|
|
2586
|
+
return await this.getProjectById(tenantId, id);
|
|
2553
2587
|
}
|
|
2554
2588
|
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
2555
2589
|
updateValues.push(/* @__PURE__ */ new Date());
|
|
2556
|
-
updateValues.push(id);
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2590
|
+
updateValues.push(id, tenantId);
|
|
2591
|
+
const client = await this.pool.connect();
|
|
2592
|
+
try {
|
|
2593
|
+
await client.query("BEGIN");
|
|
2594
|
+
await client.query(
|
|
2595
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2596
|
+
[tenantId]
|
|
2597
|
+
);
|
|
2598
|
+
await client.query(
|
|
2599
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2600
|
+
[id, tenantId]
|
|
2601
|
+
);
|
|
2602
|
+
const existing = await client.query(
|
|
2603
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2604
|
+
[id, tenantId]
|
|
2605
|
+
);
|
|
2606
|
+
if (!existing.rows[0]) {
|
|
2607
|
+
await client.query("COMMIT");
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
const ids = existing.rows[0].config?.capabilityBundleIds;
|
|
2611
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2612
|
+
if (bundleIds.length > 0) {
|
|
2613
|
+
await client.query(
|
|
2614
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2615
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2616
|
+
ORDER BY bundle_id`,
|
|
2617
|
+
[tenantId, bundleIds]
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
const result = await client.query(
|
|
2621
|
+
`UPDATE lattice_projects SET ${updateFields.join(", ")}
|
|
2622
|
+
WHERE id = $${paramIndex} AND tenant_id = $${paramIndex + 1}
|
|
2623
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2624
|
+
updateValues
|
|
2625
|
+
);
|
|
2626
|
+
await client.query("COMMIT");
|
|
2627
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2628
|
+
} catch (error) {
|
|
2629
|
+
await client.query("ROLLBACK");
|
|
2630
|
+
throw error;
|
|
2631
|
+
} finally {
|
|
2632
|
+
client.release();
|
|
2633
|
+
}
|
|
2567
2634
|
}
|
|
2568
2635
|
/**
|
|
2569
2636
|
* Delete a project by ID
|
|
2570
2637
|
*/
|
|
2571
2638
|
async deleteProject(tenantId, id) {
|
|
2639
|
+
await this.ensureInitialized();
|
|
2640
|
+
const client = await this.pool.connect();
|
|
2641
|
+
try {
|
|
2642
|
+
await client.query("BEGIN");
|
|
2643
|
+
await client.query(
|
|
2644
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2645
|
+
[tenantId]
|
|
2646
|
+
);
|
|
2647
|
+
await client.query(
|
|
2648
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2649
|
+
[id, tenantId]
|
|
2650
|
+
);
|
|
2651
|
+
const selected = await client.query(
|
|
2652
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2653
|
+
[id, tenantId]
|
|
2654
|
+
);
|
|
2655
|
+
if (!selected.rows[0]) {
|
|
2656
|
+
await client.query("COMMIT");
|
|
2657
|
+
return false;
|
|
2658
|
+
}
|
|
2659
|
+
const ids = selected.rows[0].config?.capabilityBundleIds;
|
|
2660
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2661
|
+
if (bundleIds.length > 0) {
|
|
2662
|
+
await client.query(
|
|
2663
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2664
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2665
|
+
ORDER BY bundle_id`,
|
|
2666
|
+
[tenantId, bundleIds]
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
const result = await client.query(
|
|
2670
|
+
"DELETE FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2671
|
+
[id, tenantId]
|
|
2672
|
+
);
|
|
2673
|
+
await client.query("COMMIT");
|
|
2674
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
2675
|
+
} catch (error) {
|
|
2676
|
+
await client.query("ROLLBACK");
|
|
2677
|
+
throw error;
|
|
2678
|
+
} finally {
|
|
2679
|
+
client.release();
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
2683
|
+
await this.ensureInitialized();
|
|
2684
|
+
const client = await this.pool.connect();
|
|
2685
|
+
try {
|
|
2686
|
+
await client.query("BEGIN");
|
|
2687
|
+
await client.query(
|
|
2688
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2689
|
+
[tenantId]
|
|
2690
|
+
);
|
|
2691
|
+
await client.query(
|
|
2692
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2693
|
+
[projectId, tenantId]
|
|
2694
|
+
);
|
|
2695
|
+
await client.query(
|
|
2696
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2697
|
+
[projectId, tenantId]
|
|
2698
|
+
);
|
|
2699
|
+
if (bundleIds.length > 0) {
|
|
2700
|
+
await client.query(
|
|
2701
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2702
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2703
|
+
ORDER BY bundle_id`,
|
|
2704
|
+
[tenantId, [...bundleIds].sort()]
|
|
2705
|
+
);
|
|
2706
|
+
if (Object.keys(expectedRevisions).length > 0) {
|
|
2707
|
+
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]);
|
|
2708
|
+
if (revisions.rows.some((bundle) => expectedRevisions[bundle.id] !== void 0 && expectedRevisions[bundle.id] !== bundle.updated_at)) {
|
|
2709
|
+
await client.query("ROLLBACK");
|
|
2710
|
+
return { status: "bundle_conflict" };
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
const result = await client.query(
|
|
2715
|
+
`UPDATE lattice_projects
|
|
2716
|
+
SET config = COALESCE(config, '{}'::jsonb) || jsonb_build_object('capabilityBundleIds', $3::jsonb), updated_at = NOW()
|
|
2717
|
+
WHERE id = $1 AND tenant_id = $2
|
|
2718
|
+
AND NOT EXISTS (
|
|
2719
|
+
SELECT 1 FROM unnest($4::uuid[]) AS bundle_id
|
|
2720
|
+
WHERE NOT EXISTS (
|
|
2721
|
+
SELECT 1 FROM lattice_capability_bundles
|
|
2722
|
+
WHERE tenant_id = $2 AND id = bundle_id
|
|
2723
|
+
)
|
|
2724
|
+
)
|
|
2725
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2726
|
+
[projectId, tenantId, JSON.stringify(bundleIds), bundleIds]
|
|
2727
|
+
);
|
|
2728
|
+
if (result.rows[0]) {
|
|
2729
|
+
await client.query("COMMIT");
|
|
2730
|
+
return { status: "updated", project: this.mapRowToProject(result.rows[0]) };
|
|
2731
|
+
}
|
|
2732
|
+
const project = await client.query(
|
|
2733
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2734
|
+
[projectId, tenantId]
|
|
2735
|
+
);
|
|
2736
|
+
await client.query("COMMIT");
|
|
2737
|
+
return project.rows.length > 0 ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
2738
|
+
} catch (error) {
|
|
2739
|
+
await client.query("ROLLBACK");
|
|
2740
|
+
throw error;
|
|
2741
|
+
} finally {
|
|
2742
|
+
client.release();
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
2572
2746
|
await this.ensureInitialized();
|
|
2573
2747
|
const result = await this.pool.query(
|
|
2574
|
-
`
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2748
|
+
`SELECT 1 AS exists FROM lattice_projects
|
|
2749
|
+
WHERE tenant_id = $1
|
|
2750
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
2751
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
2752
|
+
LIMIT 1`,
|
|
2753
|
+
[tenantId, bundleId]
|
|
2579
2754
|
);
|
|
2580
|
-
return result.
|
|
2755
|
+
return result.rows.length > 0;
|
|
2581
2756
|
}
|
|
2582
2757
|
};
|
|
2583
2758
|
|
|
@@ -5060,10 +5235,48 @@ var ThreadMessageQueueStore = class {
|
|
|
5060
5235
|
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5061
5236
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
5062
5237
|
RETURNING *`,
|
|
5063
|
-
[id || crypto.randomUUID(), threadId, tenantId, assistantId, workspaceId
|
|
5238
|
+
[id || crypto.randomUUID(), threadId, tenantId, assistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, priority, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null]
|
|
5064
5239
|
);
|
|
5065
5240
|
return this.rowToMessage(result.rows[0]);
|
|
5066
5241
|
}
|
|
5242
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
5243
|
+
if (maxSize === Infinity) {
|
|
5244
|
+
await this.addMessage(params);
|
|
5245
|
+
return true;
|
|
5246
|
+
}
|
|
5247
|
+
const client = await this.pool.connect();
|
|
5248
|
+
try {
|
|
5249
|
+
await client.query("BEGIN");
|
|
5250
|
+
const scope = { tenantId: params.tenantId, assistantId: params.assistantId, workspaceId: params.workspaceId, projectId: params.projectId };
|
|
5251
|
+
const lockKey = `${params.tenantId}:${params.assistantId}:${params.threadId}:${params.workspaceId ?? ""}:${params.projectId ?? ""}`;
|
|
5252
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtext($1))", [lockKey]);
|
|
5253
|
+
const filter = scopeClause(scope, 2);
|
|
5254
|
+
const count = await client.query(
|
|
5255
|
+
`SELECT COUNT(*) as count FROM lattice_thread_message_queue WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5256
|
+
[params.threadId, ...filter.params]
|
|
5257
|
+
);
|
|
5258
|
+
if (parseInt(count.rows[0].count, 10) >= maxSize) {
|
|
5259
|
+
await client.query("ROLLBACK");
|
|
5260
|
+
return false;
|
|
5261
|
+
}
|
|
5262
|
+
const seq = await client.query(
|
|
5263
|
+
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5264
|
+
[params.threadId]
|
|
5265
|
+
);
|
|
5266
|
+
const result = await client.query(
|
|
5267
|
+
`INSERT INTO lattice_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5268
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
|
|
5269
|
+
[params.id || crypto.randomUUID(), params.threadId, params.tenantId, params.assistantId, params.workspaceId ?? null, params.projectId ?? null, JSON.stringify(params.content), params.type || "human", seq.rows[0].next_seq, params.priority ?? 0, params.command ? JSON.stringify(params.command) : null, params.custom_run_config ? JSON.stringify(params.custom_run_config) : null]
|
|
5270
|
+
);
|
|
5271
|
+
await client.query("COMMIT");
|
|
5272
|
+
return Boolean(result.rows[0]);
|
|
5273
|
+
} catch (error) {
|
|
5274
|
+
await client.query("ROLLBACK");
|
|
5275
|
+
throw error;
|
|
5276
|
+
} finally {
|
|
5277
|
+
client.release();
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5067
5280
|
/**
|
|
5068
5281
|
* Add message at head of queue (high priority, e.g., STEER/Command messages)
|
|
5069
5282
|
* Uses priority=100 to ensure message is processed first
|
|
@@ -5084,42 +5297,45 @@ var ThreadMessageQueueStore = class {
|
|
|
5084
5297
|
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5085
5298
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 100, $10, $11)
|
|
5086
5299
|
RETURNING *`,
|
|
5087
|
-
[id || crypto.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId
|
|
5300
|
+
[id || crypto.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null]
|
|
5088
5301
|
);
|
|
5089
5302
|
return this.rowToMessage(result.rows[0]);
|
|
5090
5303
|
}
|
|
5091
5304
|
/**
|
|
5092
5305
|
* Get pending messages for thread
|
|
5093
5306
|
*/
|
|
5094
|
-
async getPendingMessages(threadId) {
|
|
5307
|
+
async getPendingMessages(threadId, scope) {
|
|
5308
|
+
const filter = scopeClause(scope, 2);
|
|
5095
5309
|
const result = await this.pool.query(
|
|
5096
|
-
`SELECT * FROM lattice_thread_message_queue
|
|
5097
|
-
|
|
5310
|
+
`SELECT * FROM lattice_thread_message_queue
|
|
5311
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}
|
|
5098
5312
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5099
|
-
[threadId]
|
|
5313
|
+
[threadId, ...filter.params]
|
|
5100
5314
|
);
|
|
5101
5315
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5102
5316
|
}
|
|
5103
5317
|
/**
|
|
5104
5318
|
* Get processing messages for a thread
|
|
5105
5319
|
*/
|
|
5106
|
-
async getProcessingMessages(threadId) {
|
|
5320
|
+
async getProcessingMessages(threadId, scope) {
|
|
5321
|
+
const filter = scopeClause(scope, 2);
|
|
5107
5322
|
const result = await this.pool.query(
|
|
5108
5323
|
`SELECT * FROM lattice_thread_message_queue
|
|
5109
|
-
|
|
5324
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5110
5325
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5111
|
-
[threadId]
|
|
5326
|
+
[threadId, ...filter.params]
|
|
5112
5327
|
);
|
|
5113
5328
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5114
5329
|
}
|
|
5115
5330
|
/**
|
|
5116
5331
|
* Get queue size
|
|
5117
5332
|
*/
|
|
5118
|
-
async getQueueSize(threadId) {
|
|
5333
|
+
async getQueueSize(threadId, scope) {
|
|
5334
|
+
const filter = scopeClause(scope, 2);
|
|
5119
5335
|
const result = await this.pool.query(
|
|
5120
5336
|
`SELECT COUNT(*) as count FROM lattice_thread_message_queue
|
|
5121
|
-
|
|
5122
|
-
[threadId]
|
|
5337
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5338
|
+
[threadId, ...filter.params]
|
|
5123
5339
|
);
|
|
5124
5340
|
return parseInt(result.rows[0].count, 10);
|
|
5125
5341
|
}
|
|
@@ -5128,58 +5344,70 @@ var ThreadMessageQueueStore = class {
|
|
|
5128
5344
|
*/
|
|
5129
5345
|
async getThreadsWithPendingMessages() {
|
|
5130
5346
|
const result = await this.pool.query(
|
|
5131
|
-
`SELECT DISTINCT ON (thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5347
|
+
`SELECT DISTINCT ON (tenant_id, assistant_id, workspace_id, project_id, thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5132
5348
|
FROM lattice_thread_message_queue
|
|
5133
5349
|
WHERE status IN ('pending', 'processing')
|
|
5134
|
-
|
|
5350
|
+
ORDER BY tenant_id, assistant_id, workspace_id, project_id, thread_id`
|
|
5135
5351
|
);
|
|
5136
5352
|
return result.rows.map((row) => ({
|
|
5137
5353
|
tenantId: row.tenant_id,
|
|
5138
5354
|
assistantId: row.assistant_id,
|
|
5139
5355
|
threadId: row.thread_id,
|
|
5140
|
-
workspaceId: row.workspace_id
|
|
5141
|
-
projectId: row.project_id
|
|
5356
|
+
workspaceId: row.workspace_id,
|
|
5357
|
+
projectId: row.project_id
|
|
5142
5358
|
}));
|
|
5143
5359
|
}
|
|
5144
5360
|
/**
|
|
5145
5361
|
* Remove message
|
|
5146
5362
|
*/
|
|
5147
|
-
async removeMessage(messageId) {
|
|
5363
|
+
async removeMessage(messageId, scope) {
|
|
5364
|
+
const filter = scopeClause(scope, 2);
|
|
5148
5365
|
const result = await this.pool.query(
|
|
5149
|
-
`DELETE FROM lattice_thread_message_queue WHERE id = $1 RETURNING id`,
|
|
5150
|
-
[messageId]
|
|
5366
|
+
`DELETE FROM lattice_thread_message_queue WHERE id = $1${filter.sql} RETURNING id`,
|
|
5367
|
+
[messageId, ...filter.params]
|
|
5151
5368
|
);
|
|
5152
5369
|
return (result.rowCount ?? 0) > 0;
|
|
5153
5370
|
}
|
|
5154
5371
|
/**
|
|
5155
5372
|
* Clear all messages for thread
|
|
5156
5373
|
*/
|
|
5157
|
-
async clearMessages(threadId) {
|
|
5374
|
+
async clearMessages(threadId, scope) {
|
|
5375
|
+
const filter = scopeClause(scope, 2);
|
|
5158
5376
|
await this.pool.query(
|
|
5159
|
-
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5160
|
-
[threadId]
|
|
5377
|
+
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1${filter.sql}`,
|
|
5378
|
+
[threadId, ...filter.params]
|
|
5161
5379
|
);
|
|
5162
5380
|
}
|
|
5163
5381
|
/**
|
|
5164
5382
|
* Mark message as processing
|
|
5165
5383
|
*/
|
|
5166
|
-
async markProcessing(messageId) {
|
|
5384
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
5385
|
+
if (customRunConfig !== void 0) {
|
|
5386
|
+
const filter2 = scopeClause(scope, 3);
|
|
5387
|
+
await this.pool.query(
|
|
5388
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing', custom_run_config = $2 WHERE id = $1${filter2.sql}`,
|
|
5389
|
+
[messageId, JSON.stringify(customRunConfig), ...filter2.params]
|
|
5390
|
+
);
|
|
5391
|
+
return;
|
|
5392
|
+
}
|
|
5393
|
+
const filter = scopeClause(scope, 2);
|
|
5167
5394
|
await this.pool.query(
|
|
5168
|
-
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1`,
|
|
5169
|
-
[messageId]
|
|
5395
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1${filter.sql}`,
|
|
5396
|
+
[messageId, ...filter.params]
|
|
5170
5397
|
);
|
|
5171
5398
|
}
|
|
5172
5399
|
/**
|
|
5173
5400
|
* Reset all processing messages to pending state for a thread
|
|
5174
5401
|
* Returns the number of messages reset
|
|
5175
5402
|
*/
|
|
5176
|
-
async resetProcessingToPending(threadId) {
|
|
5403
|
+
async resetProcessingToPending(threadId, scope) {
|
|
5404
|
+
const filter = scopeClause(scope, 2);
|
|
5177
5405
|
const result = await this.pool.query(
|
|
5178
5406
|
`UPDATE lattice_thread_message_queue
|
|
5179
5407
|
SET status = 'pending'
|
|
5180
|
-
|
|
5408
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5181
5409
|
RETURNING id`,
|
|
5182
|
-
[threadId]
|
|
5410
|
+
[threadId, ...filter.params]
|
|
5183
5411
|
);
|
|
5184
5412
|
return result.rowCount ?? 0;
|
|
5185
5413
|
}
|
|
@@ -5196,6 +5424,18 @@ var ThreadMessageQueueStore = class {
|
|
|
5196
5424
|
};
|
|
5197
5425
|
}
|
|
5198
5426
|
};
|
|
5427
|
+
function scopeClause(scope, start) {
|
|
5428
|
+
if (!scope) return { sql: "", params: [] };
|
|
5429
|
+
const params = [];
|
|
5430
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
5431
|
+
const sql = entries.map(([key, value]) => {
|
|
5432
|
+
const column = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
5433
|
+
if (value == null) return ` AND ${column} IS NULL`;
|
|
5434
|
+
params.push(value);
|
|
5435
|
+
return ` AND ${column} = $${start + params.length - 1}`;
|
|
5436
|
+
}).join("");
|
|
5437
|
+
return { sql, params };
|
|
5438
|
+
}
|
|
5199
5439
|
|
|
5200
5440
|
// src/stores/ChannelBindingStore.ts
|
|
5201
5441
|
import { Pool as Pool14 } from "pg";
|
|
@@ -5946,17 +6186,17 @@ var PostgreSQLA2AApiKeyStore = class {
|
|
|
5946
6186
|
const result = await this.pool.query(
|
|
5947
6187
|
`SELECT * FROM lattice_a2a_api_keys WHERE enabled = true`
|
|
5948
6188
|
);
|
|
5949
|
-
const
|
|
6189
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
5950
6190
|
for (const row of result.rows) {
|
|
5951
6191
|
const key = decrypt5(row.key_value);
|
|
5952
|
-
|
|
6192
|
+
map2.set(key, {
|
|
5953
6193
|
key,
|
|
5954
6194
|
tenantId: row.tenant_id,
|
|
5955
6195
|
projectId: row.project_id,
|
|
5956
6196
|
assistantIds: row.assistant_ids ?? void 0
|
|
5957
6197
|
});
|
|
5958
6198
|
}
|
|
5959
|
-
return
|
|
6199
|
+
return map2;
|
|
5960
6200
|
}
|
|
5961
6201
|
};
|
|
5962
6202
|
|
|
@@ -7629,6 +7869,7 @@ var PostgreSQLTaskStore = class {
|
|
|
7629
7869
|
};
|
|
7630
7870
|
|
|
7631
7871
|
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
7872
|
+
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
7632
7873
|
import { v4 } from "uuid";
|
|
7633
7874
|
var PostgreSQLTaskWorkItemStore = class {
|
|
7634
7875
|
constructor(pool) {
|
|
@@ -7720,6 +7961,35 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7720
7961
|
const result = await this.pool.query(query, params);
|
|
7721
7962
|
return result.rows.map((row) => this.rowToItem(row));
|
|
7722
7963
|
}
|
|
7964
|
+
/** List pending execution results using one bounded PostgreSQL anti-join query. */
|
|
7965
|
+
async listPendingExecutionResults(params) {
|
|
7966
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
7967
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
7968
|
+
error.code = "INVALID_LIMIT";
|
|
7969
|
+
throw error;
|
|
7970
|
+
}
|
|
7971
|
+
if (params.limit === 0) return [];
|
|
7972
|
+
const result = await this.pool.query(
|
|
7973
|
+
`SELECT result.*
|
|
7974
|
+
FROM lattice_task_work_items AS result
|
|
7975
|
+
WHERE result.tenant_id = $1
|
|
7976
|
+
AND result.task_id = $2
|
|
7977
|
+
AND result.action = 'execution_result'
|
|
7978
|
+
AND result.event_key COLLATE "C" ~ '^execution-result:[A-Za-z0-9._:-]+$'
|
|
7979
|
+
AND NOT EXISTS (
|
|
7980
|
+
SELECT 1
|
|
7981
|
+
FROM lattice_task_work_items AS reconciled
|
|
7982
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
7983
|
+
AND reconciled.task_id = result.task_id
|
|
7984
|
+
AND reconciled.action = 'execution_reconciled'
|
|
7985
|
+
AND reconciled.detail ->> 'executionResultId' = result.event_key
|
|
7986
|
+
)
|
|
7987
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
7988
|
+
LIMIT $3`,
|
|
7989
|
+
[params.tenantId, params.taskId, params.limit]
|
|
7990
|
+
);
|
|
7991
|
+
return result.rows.map((row) => this.rowToItem(row));
|
|
7992
|
+
}
|
|
7723
7993
|
rowToItem(row) {
|
|
7724
7994
|
return {
|
|
7725
7995
|
id: row.id,
|
|
@@ -8747,10 +9017,268 @@ var addTaskWorkItemEventKeyMigration = {
|
|
|
8747
9017
|
`);
|
|
8748
9018
|
}
|
|
8749
9019
|
};
|
|
9020
|
+
var addTaskWorkItemPendingIndexesMigration = {
|
|
9021
|
+
version: 171,
|
|
9022
|
+
name: "add_task_work_item_pending_indexes",
|
|
9023
|
+
up: async (client) => {
|
|
9024
|
+
await client.query(`
|
|
9025
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
9026
|
+
ON lattice_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC)
|
|
9027
|
+
`);
|
|
9028
|
+
await client.query(`
|
|
9029
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
9030
|
+
ON lattice_task_work_items (tenant_id, task_id, (detail ->> 'executionResultId'))
|
|
9031
|
+
WHERE action = 'execution_reconciled'
|
|
9032
|
+
`);
|
|
9033
|
+
}
|
|
9034
|
+
};
|
|
9035
|
+
|
|
9036
|
+
// src/migrations/capability_bundle_migration.ts
|
|
9037
|
+
var createCapabilityBundlesTable = {
|
|
9038
|
+
version: 170,
|
|
9039
|
+
name: "create_capability_bundles_table",
|
|
9040
|
+
up: async (client) => {
|
|
9041
|
+
await client.query(`CREATE TABLE IF NOT EXISTS lattice_capability_bundles (
|
|
9042
|
+
id UUID PRIMARY KEY, tenant_id VARCHAR(255) NOT NULL, bundle_key VARCHAR(255) NOT NULL,
|
|
9043
|
+
name VARCHAR(255) NOT NULL, description TEXT, capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9044
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9045
|
+
UNIQUE (tenant_id, bundle_key)
|
|
9046
|
+
)`);
|
|
9047
|
+
await client.query("CREATE INDEX IF NOT EXISTS idx_lattice_capability_bundles_tenant ON lattice_capability_bundles(tenant_id)");
|
|
9048
|
+
},
|
|
9049
|
+
down: async (client) => {
|
|
9050
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_capability_bundles_tenant");
|
|
9051
|
+
await client.query("DROP TABLE IF EXISTS lattice_capability_bundles");
|
|
9052
|
+
}
|
|
9053
|
+
};
|
|
9054
|
+
|
|
9055
|
+
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
9056
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
9057
|
+
import { Pool as Pool24 } from "pg";
|
|
9058
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
9059
|
+
function map(row) {
|
|
9060
|
+
return {
|
|
9061
|
+
id: row.id,
|
|
9062
|
+
tenantId: row.tenant_id,
|
|
9063
|
+
key: row.bundle_key,
|
|
9064
|
+
name: row.name,
|
|
9065
|
+
description: row.description ?? void 0,
|
|
9066
|
+
capabilities: row.capabilities,
|
|
9067
|
+
createdAt: row.created_at.toISOString(),
|
|
9068
|
+
updatedAt: typeof row.updated_at === "string" ? row.updated_at : row.updated_at.toISOString()
|
|
9069
|
+
};
|
|
9070
|
+
}
|
|
9071
|
+
function isDuplicate(error) {
|
|
9072
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
9073
|
+
}
|
|
9074
|
+
var PostgreSQLCapabilityBundleStore = class {
|
|
9075
|
+
/**
|
|
9076
|
+
* Creates a PostgreSQL capability bundle store.
|
|
9077
|
+
*
|
|
9078
|
+
* @param options - Pool ownership, connection, and migration options.
|
|
9079
|
+
*/
|
|
9080
|
+
constructor(options) {
|
|
9081
|
+
this.initialized = false;
|
|
9082
|
+
this.ownsPool = true;
|
|
9083
|
+
this.initPromise = null;
|
|
9084
|
+
if (options.pool) {
|
|
9085
|
+
this.pool = options.pool;
|
|
9086
|
+
this.ownsPool = false;
|
|
9087
|
+
this.initialized = true;
|
|
9088
|
+
return;
|
|
9089
|
+
}
|
|
9090
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool24({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool24(options.poolConfig) : (() => {
|
|
9091
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
9092
|
+
})();
|
|
9093
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
9094
|
+
this.migrationManager.register(createCapabilityBundlesTable);
|
|
9095
|
+
if (options.autoMigrate !== false) {
|
|
9096
|
+
this.startInitialization();
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
/**
|
|
9100
|
+
* Applies pending migrations for an internally managed pool.
|
|
9101
|
+
*
|
|
9102
|
+
* @returns A shared promise that resolves when initialization completes.
|
|
9103
|
+
*/
|
|
9104
|
+
async initialize() {
|
|
9105
|
+
if (this.initialized) return;
|
|
9106
|
+
if (this.initPromise) return this.initPromise;
|
|
9107
|
+
return this.startInitialization();
|
|
9108
|
+
}
|
|
9109
|
+
startInitialization() {
|
|
9110
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
9111
|
+
this.initialized = true;
|
|
9112
|
+
});
|
|
9113
|
+
void this.initPromise.catch(() => void 0);
|
|
9114
|
+
return this.initPromise;
|
|
9115
|
+
}
|
|
9116
|
+
/**
|
|
9117
|
+
* Closes the pool when it was created by this store.
|
|
9118
|
+
*
|
|
9119
|
+
* @returns A promise that resolves after owned resources are released.
|
|
9120
|
+
*/
|
|
9121
|
+
async dispose() {
|
|
9122
|
+
if (this.ownsPool) await this.pool.end();
|
|
9123
|
+
}
|
|
9124
|
+
async ready() {
|
|
9125
|
+
if (!this.initialized) await this.initialize();
|
|
9126
|
+
}
|
|
9127
|
+
async listByTenant(tenantId) {
|
|
9128
|
+
await this.ready();
|
|
9129
|
+
const result = await this.pool.query(
|
|
9130
|
+
"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",
|
|
9131
|
+
[tenantId]
|
|
9132
|
+
);
|
|
9133
|
+
return result.rows.map(map);
|
|
9134
|
+
}
|
|
9135
|
+
async getById(tenantId, id) {
|
|
9136
|
+
await this.ready();
|
|
9137
|
+
const result = await this.pool.query(
|
|
9138
|
+
"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",
|
|
9139
|
+
[tenantId, id]
|
|
9140
|
+
);
|
|
9141
|
+
return result.rows[0] ? map(result.rows[0]) : null;
|
|
9142
|
+
}
|
|
9143
|
+
async getManyByIds(tenantId, ids) {
|
|
9144
|
+
await this.ready();
|
|
9145
|
+
if (ids.length === 0) return [];
|
|
9146
|
+
const result = await this.pool.query(
|
|
9147
|
+
"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[])",
|
|
9148
|
+
[tenantId, ids]
|
|
9149
|
+
);
|
|
9150
|
+
return result.rows.map(map);
|
|
9151
|
+
}
|
|
9152
|
+
async create(tenantId, input) {
|
|
9153
|
+
await this.ready();
|
|
9154
|
+
try {
|
|
9155
|
+
const result = await this.pool.query(
|
|
9156
|
+
`INSERT INTO lattice_capability_bundles
|
|
9157
|
+
(id, tenant_id, bundle_key, name, description, capabilities)
|
|
9158
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb)
|
|
9159
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9160
|
+
[
|
|
9161
|
+
randomUUID3(),
|
|
9162
|
+
tenantId,
|
|
9163
|
+
input.key,
|
|
9164
|
+
input.name,
|
|
9165
|
+
input.description ?? null,
|
|
9166
|
+
JSON.stringify(input.capabilities)
|
|
9167
|
+
]
|
|
9168
|
+
);
|
|
9169
|
+
return map(result.rows[0]);
|
|
9170
|
+
} catch (error) {
|
|
9171
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9172
|
+
throw error;
|
|
9173
|
+
}
|
|
9174
|
+
}
|
|
9175
|
+
async update(tenantId, id, input) {
|
|
9176
|
+
await this.ready();
|
|
9177
|
+
const fields = [];
|
|
9178
|
+
const values = [];
|
|
9179
|
+
const add = (field, value) => {
|
|
9180
|
+
values.push(value);
|
|
9181
|
+
fields.push(`${field} = $${values.length}`);
|
|
9182
|
+
};
|
|
9183
|
+
if (input.name !== void 0) add("name", input.name);
|
|
9184
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) {
|
|
9185
|
+
add("description", input.description ?? null);
|
|
9186
|
+
}
|
|
9187
|
+
if (input.capabilities !== void 0) {
|
|
9188
|
+
add("capabilities", JSON.stringify(input.capabilities));
|
|
9189
|
+
}
|
|
9190
|
+
if (fields.length === 0) {
|
|
9191
|
+
const expectedUpdatedAt2 = input.expectedUpdatedAt;
|
|
9192
|
+
const result = await this.pool.query(
|
|
9193
|
+
`SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at
|
|
9194
|
+
FROM lattice_capability_bundles
|
|
9195
|
+
WHERE tenant_id = $1 AND id = $2${expectedUpdatedAt2 === void 0 ? "" : " AND updated_at::text = $3"}`,
|
|
9196
|
+
expectedUpdatedAt2 === void 0 ? [tenantId, id] : [tenantId, id, expectedUpdatedAt2]
|
|
9197
|
+
);
|
|
9198
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9199
|
+
return expectedUpdatedAt2 === void 0 ? null : { status: "conflict" };
|
|
9200
|
+
}
|
|
9201
|
+
const expectedUpdatedAt = input.expectedUpdatedAt;
|
|
9202
|
+
values.push(tenantId, id);
|
|
9203
|
+
if (expectedUpdatedAt !== void 0) values.push(expectedUpdatedAt);
|
|
9204
|
+
const tenantParam = values.length - (expectedUpdatedAt === void 0 ? 1 : 2);
|
|
9205
|
+
const idParam = tenantParam + 1;
|
|
9206
|
+
const revisionPredicate = expectedUpdatedAt === void 0 ? "" : ` AND updated_at::text = $${values.length}`;
|
|
9207
|
+
try {
|
|
9208
|
+
const result = await this.pool.query(
|
|
9209
|
+
`UPDATE lattice_capability_bundles
|
|
9210
|
+
SET ${fields.join(", ")}, updated_at = GREATEST(updated_at + interval '1 microsecond', clock_timestamp())
|
|
9211
|
+
WHERE tenant_id = $${tenantParam} AND id = $${idParam}${revisionPredicate}
|
|
9212
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9213
|
+
values
|
|
9214
|
+
);
|
|
9215
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9216
|
+
return expectedUpdatedAt === void 0 ? null : { status: "conflict" };
|
|
9217
|
+
} catch (error) {
|
|
9218
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9219
|
+
throw error;
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
9223
|
+
await this.ready();
|
|
9224
|
+
const client = await this.pool.connect();
|
|
9225
|
+
try {
|
|
9226
|
+
await client.query("BEGIN");
|
|
9227
|
+
await client.query(
|
|
9228
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
9229
|
+
[tenantId]
|
|
9230
|
+
);
|
|
9231
|
+
const projectIds = await client.query(
|
|
9232
|
+
`SELECT id FROM lattice_projects
|
|
9233
|
+
WHERE tenant_id = $1
|
|
9234
|
+
ORDER BY id`,
|
|
9235
|
+
[tenantId]
|
|
9236
|
+
);
|
|
9237
|
+
if (projectIds.rows.length > 0) {
|
|
9238
|
+
await client.query(
|
|
9239
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project:' || project_id, 0))
|
|
9240
|
+
FROM unnest($2::text[]) AS project_id
|
|
9241
|
+
ORDER BY project_id`,
|
|
9242
|
+
[tenantId, projectIds.rows.map((project) => project.id)]
|
|
9243
|
+
);
|
|
9244
|
+
}
|
|
9245
|
+
await client.query(
|
|
9246
|
+
`SELECT id FROM lattice_projects
|
|
9247
|
+
WHERE tenant_id = $1
|
|
9248
|
+
FOR UPDATE`,
|
|
9249
|
+
[tenantId]
|
|
9250
|
+
);
|
|
9251
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || $2, 0))", [tenantId, id]);
|
|
9252
|
+
const referenced = await client.query(
|
|
9253
|
+
`SELECT 1 FROM lattice_projects
|
|
9254
|
+
WHERE tenant_id = $1
|
|
9255
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
9256
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
9257
|
+
LIMIT 1`,
|
|
9258
|
+
[tenantId, id]
|
|
9259
|
+
);
|
|
9260
|
+
if (referenced.rows.length > 0) {
|
|
9261
|
+
await client.query("COMMIT");
|
|
9262
|
+
return "in_use";
|
|
9263
|
+
}
|
|
9264
|
+
const deleted = await client.query(
|
|
9265
|
+
"DELETE FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = $2 RETURNING id",
|
|
9266
|
+
[tenantId, id]
|
|
9267
|
+
);
|
|
9268
|
+
await client.query("COMMIT");
|
|
9269
|
+
return deleted.rows.length > 0 ? "deleted" : "not_found";
|
|
9270
|
+
} catch (error) {
|
|
9271
|
+
await client.query("ROLLBACK");
|
|
9272
|
+
throw error;
|
|
9273
|
+
} finally {
|
|
9274
|
+
client.release();
|
|
9275
|
+
}
|
|
9276
|
+
}
|
|
9277
|
+
};
|
|
8750
9278
|
|
|
8751
9279
|
// src/createPgStoreConfig.ts
|
|
8752
9280
|
async function createPgStoreConfig(connectionString) {
|
|
8753
|
-
const pool = new
|
|
9281
|
+
const pool = new Pool25({ connectionString });
|
|
8754
9282
|
const mm = new MigrationManager(pool);
|
|
8755
9283
|
mm.register(createThreadsTable);
|
|
8756
9284
|
mm.register(createScheduledTasksTable);
|
|
@@ -8805,6 +9333,8 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8805
9333
|
mm.register(addA2AKeyAssistantIds);
|
|
8806
9334
|
mm.register(addTaskWorkItemEventKeyMigration);
|
|
8807
9335
|
mm.register(createAgentWebAppsTable);
|
|
9336
|
+
mm.register(createCapabilityBundlesTable);
|
|
9337
|
+
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
8808
9338
|
await mm.migrate();
|
|
8809
9339
|
const checkpoint = PostgresSaver.fromConnString(connectionString);
|
|
8810
9340
|
checkpoint.setup().catch((err) => {
|
|
@@ -8833,6 +9363,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8833
9363
|
taskWorkItem: taskWorkItemStore,
|
|
8834
9364
|
a2aApiKey: new PostgreSQLA2AApiKeyStore(opts),
|
|
8835
9365
|
agentWebApp: new PostgreSQLAgentWebAppStore(opts),
|
|
9366
|
+
capabilityBundle: new PostgreSQLCapabilityBundleStore(opts),
|
|
8836
9367
|
schedule: new PostgreSQLScheduleStorage(opts),
|
|
8837
9368
|
menu: new MenuStore(opts),
|
|
8838
9369
|
sharedResource: new PostgresSharedResourceStore(opts),
|
|
@@ -8843,7 +9374,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8843
9374
|
}
|
|
8844
9375
|
|
|
8845
9376
|
// src/stores/PostgreSQLSkillStore.ts
|
|
8846
|
-
import { Pool as
|
|
9377
|
+
import { Pool as Pool26 } from "pg";
|
|
8847
9378
|
var PostgreSQLSkillStore = class {
|
|
8848
9379
|
constructor(options) {
|
|
8849
9380
|
this.initialized = false;
|
|
@@ -8856,9 +9387,9 @@ var PostgreSQLSkillStore = class {
|
|
|
8856
9387
|
return;
|
|
8857
9388
|
}
|
|
8858
9389
|
if (typeof options.poolConfig === "string") {
|
|
8859
|
-
this.pool = new
|
|
9390
|
+
this.pool = new Pool26({ connectionString: options.poolConfig });
|
|
8860
9391
|
} else if (options.poolConfig) {
|
|
8861
|
-
this.pool = new
|
|
9392
|
+
this.pool = new Pool26(options.poolConfig);
|
|
8862
9393
|
} else {
|
|
8863
9394
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8864
9395
|
}
|
|
@@ -9159,7 +9690,7 @@ var PostgreSQLSkillStore = class {
|
|
|
9159
9690
|
};
|
|
9160
9691
|
|
|
9161
9692
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
9162
|
-
import { Pool as
|
|
9693
|
+
import { Pool as Pool27 } from "pg";
|
|
9163
9694
|
var ChannelIdentityMappingStore = class {
|
|
9164
9695
|
constructor(options) {
|
|
9165
9696
|
this.initialized = false;
|
|
@@ -9171,7 +9702,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
9171
9702
|
this.initialized = true;
|
|
9172
9703
|
return;
|
|
9173
9704
|
}
|
|
9174
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
9705
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool27({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool27(options.poolConfig) : (() => {
|
|
9175
9706
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9176
9707
|
})();
|
|
9177
9708
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9395,10 +9926,11 @@ export {
|
|
|
9395
9926
|
MenuStore,
|
|
9396
9927
|
MigrationManager,
|
|
9397
9928
|
PGVectorStoreProvider,
|
|
9398
|
-
|
|
9929
|
+
Pool28 as Pool,
|
|
9399
9930
|
PostgreSQLA2AApiKeyStore,
|
|
9400
9931
|
PostgreSQLAgentWebAppStore,
|
|
9401
9932
|
PostgreSQLAssistantStore,
|
|
9933
|
+
PostgreSQLCapabilityBundleStore,
|
|
9402
9934
|
PostgreSQLChannelInstallationStore,
|
|
9403
9935
|
PostgreSQLCollectionStore,
|
|
9404
9936
|
PostgreSQLConnectionStore,
|
|
@@ -9437,6 +9969,7 @@ export {
|
|
|
9437
9969
|
createA2AApiKeysTable,
|
|
9438
9970
|
createAgentWebAppsTable,
|
|
9439
9971
|
createAssistantsTable,
|
|
9972
|
+
createCapabilityBundlesTable,
|
|
9440
9973
|
createChannelBindingsTable,
|
|
9441
9974
|
createChannelIdentityMappingTables,
|
|
9442
9975
|
createChannelInstallationsTable,
|