@axiom-lattice/local-stores 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +18 -0
- package/dist/index.d.mts +193 -22
- package/dist/index.d.ts +193 -22
- package/dist/index.js +1482 -242
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1479 -242
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalCapabilityBundleStore.test.ts +216 -0
- package/src/__tests__/LocalChannelBindingStore.test.ts +183 -0
- package/src/__tests__/LocalChannelInstallationStore.test.ts +23 -0
- package/src/__tests__/LocalProjectRoomStores.test.ts +356 -0
- package/src/__tests__/LocalProjectStore.test.ts +39 -2
- package/src/__tests__/LocalTaskStore.test.ts +58 -0
- package/src/__tests__/LocalTaskWorkItemStore.test.ts +199 -0
- package/src/__tests__/LocalThreadMessageQueueStore.test.ts +201 -0
- package/src/createLocalStoreConfig.ts +10 -0
- package/src/index.ts +5 -0
- package/src/migrations/migration.ts +1 -1
- package/src/stores/LocalCapabilityBundleStore.ts +53 -0
- package/src/stores/LocalChannelBindingStore.ts +75 -41
- package/src/stores/LocalChannelInstallationStore.ts +6 -5
- package/src/stores/LocalProjectBotMembershipStore.ts +105 -0
- package/src/stores/LocalProjectMembershipStore.ts +72 -0
- package/src/stores/LocalProjectRoomMessageStore.ts +63 -0
- package/src/stores/LocalProjectRoomStore.ts +37 -0
- package/src/stores/LocalProjectStore.ts +67 -14
- package/src/stores/LocalTaskStore.ts +82 -2
- package/src/stores/LocalTaskWorkItemStore.ts +136 -1
- package/src/stores/LocalThreadMessageQueueStore.ts +136 -28
- package/src/stores/project-room-schema.ts +243 -0
- package/src/stores/room-channel-binding-index.ts +60 -0
package/dist/index.mjs
CHANGED
|
@@ -81,11 +81,11 @@ var StatementWrapper = class {
|
|
|
81
81
|
if (params.length > 0) {
|
|
82
82
|
stmt.bind(params);
|
|
83
83
|
}
|
|
84
|
-
const
|
|
84
|
+
const rows2 = [];
|
|
85
85
|
while (stmt.step()) {
|
|
86
|
-
|
|
86
|
+
rows2.push(stmt.getAsObject());
|
|
87
87
|
}
|
|
88
|
-
return
|
|
88
|
+
return rows2;
|
|
89
89
|
} finally {
|
|
90
90
|
stmt.free();
|
|
91
91
|
}
|
|
@@ -160,8 +160,8 @@ function closeDatabase() {
|
|
|
160
160
|
_db = null;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
function ensureTable(db,
|
|
164
|
-
db.exec(
|
|
163
|
+
function ensureTable(db, ddl2) {
|
|
164
|
+
db.exec(ddl2);
|
|
165
165
|
}
|
|
166
166
|
function nowISO() {
|
|
167
167
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -205,7 +205,10 @@ var MigrationManager = class {
|
|
|
205
205
|
this.db.run(
|
|
206
206
|
`INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at)
|
|
207
207
|
VALUES (?, ?, ?, ?)`,
|
|
208
|
-
|
|
208
|
+
this.storeName,
|
|
209
|
+
m.name,
|
|
210
|
+
m.version,
|
|
211
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
209
212
|
);
|
|
210
213
|
}
|
|
211
214
|
}
|
|
@@ -255,10 +258,10 @@ var MigrationManager = class {
|
|
|
255
258
|
`);
|
|
256
259
|
}
|
|
257
260
|
getAppliedNames() {
|
|
258
|
-
const
|
|
261
|
+
const rows2 = this.db.prepare(
|
|
259
262
|
`SELECT name FROM lattice_schema_migrations WHERE store_name = ? ORDER BY version`
|
|
260
263
|
).all(this.storeName);
|
|
261
|
-
return new Set(
|
|
264
|
+
return new Set(rows2.map((r) => r.name));
|
|
262
265
|
}
|
|
263
266
|
};
|
|
264
267
|
|
|
@@ -293,10 +296,10 @@ var LocalThreadStore = class {
|
|
|
293
296
|
mm.migrate();
|
|
294
297
|
}
|
|
295
298
|
async getThreadsByAssistantId(tenantId, assistantId, metadataFilter) {
|
|
296
|
-
const
|
|
299
|
+
const rows2 = this.db.prepare(
|
|
297
300
|
`SELECT * FROM lt_threads WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
298
301
|
).all(tenantId, assistantId);
|
|
299
|
-
let threads =
|
|
302
|
+
let threads = rows2.map(mapRowToThread);
|
|
300
303
|
if (metadataFilter && Object.keys(metadataFilter).length > 0) {
|
|
301
304
|
threads = threads.filter(
|
|
302
305
|
(t) => Object.entries(metadataFilter).every(
|
|
@@ -393,10 +396,10 @@ var LocalAssistantStore = class {
|
|
|
393
396
|
ensureTable(db, DDL2);
|
|
394
397
|
}
|
|
395
398
|
async getAllAssistants(tenantId) {
|
|
396
|
-
const
|
|
399
|
+
const rows2 = this.db.prepare(
|
|
397
400
|
`SELECT * FROM lt_assistants WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
398
401
|
).all(tenantId);
|
|
399
|
-
return
|
|
402
|
+
return rows2.map(mapRowToAssistant);
|
|
400
403
|
}
|
|
401
404
|
async getAssistantById(tenantId, id) {
|
|
402
405
|
const row = this.db.prepare(
|
|
@@ -509,10 +512,10 @@ var LocalWorkspaceStore = class {
|
|
|
509
512
|
ensureTable(db, DDL3);
|
|
510
513
|
}
|
|
511
514
|
async getAllWorkspaces(tenantId) {
|
|
512
|
-
const
|
|
515
|
+
const rows2 = this.db.prepare(
|
|
513
516
|
`SELECT * FROM lt_workspaces WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
514
517
|
).all(tenantId);
|
|
515
|
-
return
|
|
518
|
+
return rows2.map(mapRowToWorkspace);
|
|
516
519
|
}
|
|
517
520
|
async getWorkspaceById(tenantId, id) {
|
|
518
521
|
const row = this.db.prepare(
|
|
@@ -588,6 +591,7 @@ function mapRowToWorkspace(row) {
|
|
|
588
591
|
}
|
|
589
592
|
|
|
590
593
|
// src/stores/LocalProjectStore.ts
|
|
594
|
+
import { assertGenericProjectConfig } from "@axiom-lattice/protocols";
|
|
591
595
|
var DDL4 = `
|
|
592
596
|
CREATE TABLE IF NOT EXISTS lt_projects (
|
|
593
597
|
id TEXT NOT NULL,
|
|
@@ -611,15 +615,15 @@ var LocalProjectStore = class {
|
|
|
611
615
|
}
|
|
612
616
|
async getProjectsByWorkspace(tenantId, workspaceId, filter) {
|
|
613
617
|
if (filter?.kind !== void 0) {
|
|
614
|
-
const
|
|
618
|
+
const rows3 = this.db.prepare(
|
|
615
619
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? AND kind = ? ORDER BY created_at DESC`
|
|
616
620
|
).all(tenantId, workspaceId, filter.kind);
|
|
617
|
-
return
|
|
621
|
+
return rows3.map(mapRowToProject);
|
|
618
622
|
}
|
|
619
|
-
const
|
|
623
|
+
const rows2 = this.db.prepare(
|
|
620
624
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? ORDER BY created_at DESC`
|
|
621
625
|
).all(tenantId, workspaceId);
|
|
622
|
-
return
|
|
626
|
+
return rows2.map(mapRowToProject);
|
|
623
627
|
}
|
|
624
628
|
async getProjectById(tenantId, id) {
|
|
625
629
|
const row = this.db.prepare(
|
|
@@ -628,6 +632,7 @@ var LocalProjectStore = class {
|
|
|
628
632
|
return row ? mapRowToProject(row) : null;
|
|
629
633
|
}
|
|
630
634
|
async createProject(tenantId, workspaceId, id, data) {
|
|
635
|
+
assertGenericProjectConfig(data.config);
|
|
631
636
|
const now = nowISO();
|
|
632
637
|
const kind = data.kind || "business";
|
|
633
638
|
this.db.prepare(
|
|
@@ -637,23 +642,18 @@ var LocalProjectStore = class {
|
|
|
637
642
|
workspace_id = excluded.workspace_id,
|
|
638
643
|
name = excluded.name,
|
|
639
644
|
description = excluded.description,
|
|
640
|
-
config =
|
|
645
|
+
config = CASE
|
|
646
|
+
WHEN json_type(lt_projects.config, '$.capabilityBundleIds') IS NOT NULL
|
|
647
|
+
THEN json_set(COALESCE(excluded.config, '{}'), '$.capabilityBundleIds', json_extract(lt_projects.config, '$.capabilityBundleIds'))
|
|
648
|
+
ELSE excluded.config
|
|
649
|
+
END,
|
|
641
650
|
kind = excluded.kind,
|
|
642
651
|
updated_at = excluded.updated_at`
|
|
643
652
|
).run(id, tenantId, workspaceId, data.name, data.description || null, data.config ? JSON.stringify(data.config) : null, kind, now, now);
|
|
644
|
-
return
|
|
645
|
-
id,
|
|
646
|
-
tenantId,
|
|
647
|
-
workspaceId,
|
|
648
|
-
name: data.name,
|
|
649
|
-
description: data.description,
|
|
650
|
-
config: data.config,
|
|
651
|
-
kind,
|
|
652
|
-
createdAt: parseISO(now),
|
|
653
|
-
updatedAt: parseISO(now)
|
|
654
|
-
};
|
|
653
|
+
return await this.getProjectById(tenantId, id);
|
|
655
654
|
}
|
|
656
655
|
async updateProject(tenantId, id, updates) {
|
|
656
|
+
assertGenericProjectConfig(updates.config);
|
|
657
657
|
const existing = await this.getProjectById(tenantId, id);
|
|
658
658
|
if (!existing) return null;
|
|
659
659
|
const setClauses = [];
|
|
@@ -667,8 +667,13 @@ var LocalProjectStore = class {
|
|
|
667
667
|
values.push(updates.description || null);
|
|
668
668
|
}
|
|
669
669
|
if (updates.config !== void 0) {
|
|
670
|
-
setClauses.push(
|
|
671
|
-
|
|
670
|
+
setClauses.push(`config = CASE
|
|
671
|
+
WHEN json_type(config, '$.capabilityBundleIds') IS NOT NULL
|
|
672
|
+
THEN json_set(json(?), '$.capabilityBundleIds', json_extract(config, '$.capabilityBundleIds'))
|
|
673
|
+
ELSE ?
|
|
674
|
+
END`);
|
|
675
|
+
const config = JSON.stringify(updates.config ?? {});
|
|
676
|
+
values.push(config, config);
|
|
672
677
|
}
|
|
673
678
|
if (updates.kind !== void 0) {
|
|
674
679
|
setClauses.push("kind = ?");
|
|
@@ -690,11 +695,57 @@ var LocalProjectStore = class {
|
|
|
690
695
|
).run(tenantId, id);
|
|
691
696
|
return result.changes > 0;
|
|
692
697
|
}
|
|
698
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
699
|
+
const now = nowISO();
|
|
700
|
+
const result = this.db.prepare(
|
|
701
|
+
`UPDATE lt_projects
|
|
702
|
+
SET config = json_set(COALESCE(config, '{}'), '$.capabilityBundleIds', json(?)), updated_at = ?
|
|
703
|
+
WHERE tenant_id = ? AND id = ?
|
|
704
|
+
AND NOT EXISTS (
|
|
705
|
+
SELECT 1 FROM json_each(?) AS requested
|
|
706
|
+
WHERE NOT EXISTS (
|
|
707
|
+
SELECT 1 FROM lt_capability_bundles
|
|
708
|
+
WHERE tenant_id = ? AND id = requested.value
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
AND NOT EXISTS (
|
|
712
|
+
SELECT 1 FROM json_each(?) AS expected
|
|
713
|
+
WHERE EXISTS (
|
|
714
|
+
SELECT 1 FROM lt_capability_bundles
|
|
715
|
+
WHERE tenant_id = ? AND id = expected.key AND updated_at <> expected.value
|
|
716
|
+
)
|
|
717
|
+
)`
|
|
718
|
+
).run(JSON.stringify(bundleIds), now, tenantId, projectId, JSON.stringify(bundleIds), tenantId, JSON.stringify(expectedRevisions), tenantId);
|
|
719
|
+
if (result.changes > 0) {
|
|
720
|
+
const project = await this.getProjectById(tenantId, projectId);
|
|
721
|
+
return project ? { status: "updated", project } : { status: "project_not_found" };
|
|
722
|
+
}
|
|
723
|
+
const revisionMismatch = this.db.prepare(
|
|
724
|
+
"SELECT 1 FROM lt_capability_bundles WHERE tenant_id = ? AND id IN (SELECT key FROM json_each(?)) AND updated_at <> (SELECT value FROM json_each(?) WHERE key = lt_capability_bundles.id) LIMIT 1"
|
|
725
|
+
).get(tenantId, JSON.stringify(expectedRevisions), JSON.stringify(expectedRevisions));
|
|
726
|
+
if (revisionMismatch) return { status: "bundle_conflict" };
|
|
727
|
+
return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
728
|
+
}
|
|
729
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
730
|
+
const rows2 = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
|
|
731
|
+
return rows2.some((row) => {
|
|
732
|
+
if (!row.config) return false;
|
|
733
|
+
let config;
|
|
734
|
+
try {
|
|
735
|
+
config = JSON.parse(row.config);
|
|
736
|
+
} catch {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
|
|
740
|
+
const ids = config.capabilityBundleIds;
|
|
741
|
+
return Array.isArray(ids) && ids.every((id) => typeof id === "string") && ids.includes(bundleId);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
693
744
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
694
|
-
ensureColumn(table, column,
|
|
745
|
+
ensureColumn(table, column, ddl2) {
|
|
695
746
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
696
747
|
if (!cols.some((c) => c.name === column)) {
|
|
697
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
748
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
698
749
|
}
|
|
699
750
|
}
|
|
700
751
|
};
|
|
@@ -712,6 +763,640 @@ function mapRowToProject(row) {
|
|
|
712
763
|
};
|
|
713
764
|
}
|
|
714
765
|
|
|
766
|
+
// src/stores/project-room-schema.ts
|
|
767
|
+
var schemaMarker = Symbol("projectRoomSchema");
|
|
768
|
+
var activeTransactions = /* @__PURE__ */ new WeakSet();
|
|
769
|
+
var migrationName = "create_project_room_tables";
|
|
770
|
+
var migrationTableDdl = `CREATE TABLE IF NOT EXISTS lattice_schema_migrations (
|
|
771
|
+
store_name TEXT NOT NULL, name TEXT NOT NULL, version INTEGER NOT NULL, applied_at TEXT NOT NULL,
|
|
772
|
+
PRIMARY KEY (store_name, name)
|
|
773
|
+
)`;
|
|
774
|
+
var ddl = `
|
|
775
|
+
CREATE TABLE IF NOT EXISTS lt_project_rooms (
|
|
776
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
777
|
+
type TEXT NOT NULL CHECK (type = 'main'), name TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
778
|
+
PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, type)
|
|
779
|
+
);
|
|
780
|
+
CREATE TABLE IF NOT EXISTS lt_project_memberships (
|
|
781
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, project_id TEXT NOT NULL, user_id TEXT NOT NULL,
|
|
782
|
+
role TEXT NOT NULL CHECK (role IN ('owner','admin','member','viewer')),
|
|
783
|
+
status TEXT NOT NULL CHECK (status IN ('active','removed')), joined_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
784
|
+
PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, user_id)
|
|
785
|
+
);
|
|
786
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_memberships_project ON lt_project_memberships (tenant_id, project_id);
|
|
787
|
+
CREATE TABLE IF NOT EXISTS lt_project_bot_memberships (
|
|
788
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
789
|
+
room_id TEXT NOT NULL, assistant_id TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('coordinator','specialist')),
|
|
790
|
+
title TEXT NOT NULL, responsibility TEXT, mention_name TEXT NOT NULL,
|
|
791
|
+
status TEXT NOT NULL CHECK (status IN ('active','paused','removed')), room_thread_id TEXT NOT NULL,
|
|
792
|
+
joined_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id),
|
|
793
|
+
UNIQUE (tenant_id, project_id, assistant_id)
|
|
794
|
+
);
|
|
795
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_coordinator
|
|
796
|
+
ON lt_project_bot_memberships (tenant_id, project_id) WHERE role = 'coordinator' AND status IN ('active','paused');
|
|
797
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_mention
|
|
798
|
+
ON lt_project_bot_memberships (tenant_id, room_id, mention_name) WHERE status IN ('active','paused');
|
|
799
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_project ON lt_project_bot_memberships (tenant_id, project_id);
|
|
800
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_room ON lt_project_bot_memberships (tenant_id, room_id);
|
|
801
|
+
CREATE TABLE IF NOT EXISTS lt_project_room_messages (
|
|
802
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL, room_id TEXT NOT NULL,
|
|
803
|
+
author TEXT NOT NULL, content TEXT NOT NULL, mentions TEXT NOT NULL DEFAULT '[]', reply_to_message_id TEXT,
|
|
804
|
+
source TEXT NOT NULL CHECK (source IN ('user','agent','task','routine','system')), source_id TEXT,
|
|
805
|
+
idempotency_key TEXT, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id)
|
|
806
|
+
);
|
|
807
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_room_messages_idempotency
|
|
808
|
+
ON lt_project_room_messages (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
809
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_room_messages_room_created
|
|
810
|
+
ON lt_project_room_messages (tenant_id, room_id, created_at DESC, id DESC);
|
|
811
|
+
`;
|
|
812
|
+
var tableColumns = {
|
|
813
|
+
lt_project_rooms: ["id", "tenant_id", "workspace_id", "project_id", "type", "name", "created_at", "updated_at"],
|
|
814
|
+
lt_project_memberships: ["id", "tenant_id", "project_id", "user_id", "role", "status", "joined_at", "updated_at"],
|
|
815
|
+
lt_project_bot_memberships: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "assistant_id", "role", "title", "responsibility", "mention_name", "status", "room_thread_id", "joined_at", "updated_at"],
|
|
816
|
+
lt_project_room_messages: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "author", "content", "mentions", "reply_to_message_id", "source", "source_id", "idempotency_key", "created_at"]
|
|
817
|
+
};
|
|
818
|
+
var indexes = {
|
|
819
|
+
idx_lt_project_memberships_project: { table: "lt_project_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
|
|
820
|
+
uq_lt_project_bot_memberships_coordinator: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: true, partial: true, sql: ["role = 'coordinator'", "status in ('active','paused')"] },
|
|
821
|
+
uq_lt_project_bot_memberships_mention: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id", "mention_name"], unique: true, partial: true, sql: ["status in ('active','paused')"] },
|
|
822
|
+
idx_lt_project_bot_memberships_project: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
|
|
823
|
+
idx_lt_project_bot_memberships_room: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id"], unique: false, partial: false },
|
|
824
|
+
uq_lt_project_room_messages_idempotency: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "idempotency_key"], unique: true, partial: true, sql: ["idempotency_key is not null"] },
|
|
825
|
+
idx_lt_project_room_messages_room_created: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "created_at", "id"], unique: false, partial: false, sql: ["created_at desc", "id desc"] }
|
|
826
|
+
};
|
|
827
|
+
var uniqueConstraints = {
|
|
828
|
+
lt_project_rooms: [["tenant_id", "id"], ["tenant_id", "project_id", "type"]],
|
|
829
|
+
lt_project_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "user_id"]],
|
|
830
|
+
lt_project_bot_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "assistant_id"]],
|
|
831
|
+
lt_project_room_messages: [["tenant_id", "id"]]
|
|
832
|
+
};
|
|
833
|
+
var checkConstraints = {
|
|
834
|
+
lt_project_rooms: [{ name: "type", fragment: "type='main'" }],
|
|
835
|
+
lt_project_memberships: [
|
|
836
|
+
{ name: "role", fragment: "role in ('owner','admin','member','viewer')" },
|
|
837
|
+
{ name: "status", fragment: "status in ('active','removed')" }
|
|
838
|
+
],
|
|
839
|
+
lt_project_bot_memberships: [
|
|
840
|
+
{ name: "role", fragment: "role in ('coordinator','specialist')" },
|
|
841
|
+
{ name: "status", fragment: "status in ('active','paused','removed')" }
|
|
842
|
+
],
|
|
843
|
+
lt_project_room_messages: [{ name: "source", fragment: "source in ('user','agent','task','routine','system')" }]
|
|
844
|
+
};
|
|
845
|
+
function rows(db, sql) {
|
|
846
|
+
const result = db.getRawDb().exec(sql);
|
|
847
|
+
if (!result[0]) return [];
|
|
848
|
+
return result[0].values.map((values) => Object.fromEntries(result[0].columns.map((column, index) => [column, values[index]])));
|
|
849
|
+
}
|
|
850
|
+
function normalizeSql(value) {
|
|
851
|
+
return value.toLowerCase().replace(/["`]/g, "").replace(/\[|\]/g, "").replace(/\s+/g, " ").replace(/\s*([=,])\s*/g, "$1").replace(/[()]/g, "").trim();
|
|
852
|
+
}
|
|
853
|
+
function extractCheckExpressions(sql) {
|
|
854
|
+
const checks = [];
|
|
855
|
+
let quoted = false;
|
|
856
|
+
for (let index = 0; index < sql.length; index += 1) {
|
|
857
|
+
if (sql[index] === "'") {
|
|
858
|
+
if (quoted && sql[index + 1] === "'") {
|
|
859
|
+
index += 1;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
quoted = !quoted;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (quoted || sql.slice(index, index + 5).toLowerCase() !== "check") continue;
|
|
866
|
+
const before = sql[index - 1];
|
|
867
|
+
const after = sql[index + 5];
|
|
868
|
+
if (before && /[a-z0-9_]/i.test(before) || after && /[a-z0-9_]/i.test(after)) continue;
|
|
869
|
+
let opening = index + 5;
|
|
870
|
+
while (/\s/.test(sql[opening] ?? "")) opening += 1;
|
|
871
|
+
if (sql[opening] !== "(") continue;
|
|
872
|
+
let depth = 1;
|
|
873
|
+
let expressionQuoted = false;
|
|
874
|
+
for (let cursor = opening + 1; cursor < sql.length; cursor += 1) {
|
|
875
|
+
if (sql[cursor] === "'") {
|
|
876
|
+
if (expressionQuoted && sql[cursor + 1] === "'") {
|
|
877
|
+
cursor += 1;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
expressionQuoted = !expressionQuoted;
|
|
881
|
+
} else if (!expressionQuoted && sql[cursor] === "(") {
|
|
882
|
+
depth += 1;
|
|
883
|
+
} else if (!expressionQuoted && sql[cursor] === ")") {
|
|
884
|
+
depth -= 1;
|
|
885
|
+
if (depth === 0) {
|
|
886
|
+
checks.push(sql.slice(opening + 1, cursor));
|
|
887
|
+
index = cursor;
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return checks;
|
|
894
|
+
}
|
|
895
|
+
function validateProjectRoomSchema(db) {
|
|
896
|
+
for (const [table, expected] of Object.entries(tableColumns)) {
|
|
897
|
+
const actual = rows(db, `PRAGMA table_info(${table})`);
|
|
898
|
+
if (actual.map((column) => column.name).join(",") !== expected.join(",") || actual.some((column) => column.type !== "TEXT") || actual.some((column) => column.name !== "responsibility" && column.name !== "reply_to_message_id" && column.name !== "source_id" && column.name !== "idempotency_key" && column.notnull !== 1)) {
|
|
899
|
+
throw new Error(`Invalid project room schema: table '${table}' has incompatible columns`);
|
|
900
|
+
}
|
|
901
|
+
const uniqueIndexes = rows(db, `PRAGMA index_list(${table})`).filter((index) => index.unique === 1);
|
|
902
|
+
const signatures = uniqueIndexes.map((index) => rows(db, `PRAGMA index_info(${String(index.name)})`).map((column) => column.name).join(","));
|
|
903
|
+
if (uniqueConstraints[table].some((constraint) => !signatures.includes(constraint.join(",")))) {
|
|
904
|
+
throw new Error(`Invalid project room schema: table '${table}' is missing required uniqueness`);
|
|
905
|
+
}
|
|
906
|
+
const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '${table}'`)[0]?.sql;
|
|
907
|
+
const checks = typeof definition === "string" ? extractCheckExpressions(definition).map(normalizeSql) : [];
|
|
908
|
+
const missingCheck = checkConstraints[table].find((constraint) => !checks.includes(normalizeSql(constraint.fragment)));
|
|
909
|
+
if (missingCheck) {
|
|
910
|
+
throw new Error(`Invalid project room schema: table '${table}' is missing required CHECK constraint '${missingCheck.name}'`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
for (const [name, expected] of Object.entries(indexes)) {
|
|
914
|
+
const listed = rows(db, `PRAGMA index_list(${expected.table})`).find((index) => index.name === name);
|
|
915
|
+
const columns5 = rows(db, `PRAGMA index_info(${name})`).map((column) => column.name);
|
|
916
|
+
const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'index' AND name = '${name}'`)[0]?.sql;
|
|
917
|
+
const normalized = typeof definition === "string" ? definition.toLowerCase().replace(/\s+/g, " ") : "";
|
|
918
|
+
if (!listed || listed.unique !== Number(expected.unique) || listed.partial !== Number(expected.partial) || columns5.join(",") !== expected.columns.join(",") || expected.sql?.some((fragment) => !normalized.includes(fragment))) {
|
|
919
|
+
throw new Error(`Invalid project room schema: index '${name}' is incompatible`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function ensureProjectRoomSchema(db) {
|
|
924
|
+
const marked = db;
|
|
925
|
+
if (marked[schemaMarker]) return;
|
|
926
|
+
const raw = db.getRawDb();
|
|
927
|
+
const hasMigrationTable = rows(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'lattice_schema_migrations'").length > 0;
|
|
928
|
+
const applied = hasMigrationTable ? rows(db, `SELECT name, version FROM lattice_schema_migrations WHERE store_name = 'project_room' AND name = '${migrationName}'`)[0] : void 0;
|
|
929
|
+
if (applied) {
|
|
930
|
+
if (applied.version !== 1) throw new Error(`Invalid project room schema migration version: ${String(applied.version)}`);
|
|
931
|
+
validateProjectRoomSchema(db);
|
|
932
|
+
marked[schemaMarker] = true;
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
raw.exec("BEGIN IMMEDIATE");
|
|
936
|
+
try {
|
|
937
|
+
raw.exec(migrationTableDdl);
|
|
938
|
+
raw.exec(ddl);
|
|
939
|
+
validateProjectRoomSchema(db);
|
|
940
|
+
raw.run("INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at) VALUES (?, ?, ?, ?)", ["project_room", migrationName, 1, (/* @__PURE__ */ new Date()).toISOString()]);
|
|
941
|
+
raw.exec("COMMIT");
|
|
942
|
+
} catch (error) {
|
|
943
|
+
try {
|
|
944
|
+
raw.exec("ROLLBACK");
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
throw error;
|
|
948
|
+
}
|
|
949
|
+
db.save();
|
|
950
|
+
marked[schemaMarker] = true;
|
|
951
|
+
}
|
|
952
|
+
function transaction(db, work) {
|
|
953
|
+
if (activeTransactions.has(db)) throw new Error("Nested local store transactions are not supported");
|
|
954
|
+
activeTransactions.add(db);
|
|
955
|
+
let began = false;
|
|
956
|
+
try {
|
|
957
|
+
db.exec("BEGIN IMMEDIATE");
|
|
958
|
+
began = true;
|
|
959
|
+
const result = work();
|
|
960
|
+
db.exec("COMMIT");
|
|
961
|
+
db.save();
|
|
962
|
+
return result;
|
|
963
|
+
} catch (error) {
|
|
964
|
+
if (began) {
|
|
965
|
+
try {
|
|
966
|
+
db.exec("ROLLBACK");
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
db.save();
|
|
970
|
+
}
|
|
971
|
+
throw error;
|
|
972
|
+
} finally {
|
|
973
|
+
activeTransactions.delete(db);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function write(db, sql, params) {
|
|
977
|
+
const raw = db.getRawDb();
|
|
978
|
+
raw.run(sql, params);
|
|
979
|
+
return raw.getRowsModified();
|
|
980
|
+
}
|
|
981
|
+
function timestamp(previous) {
|
|
982
|
+
const previousTime = previous === void 0 ? Number.NaN : Date.parse(previous);
|
|
983
|
+
const now = Date.now();
|
|
984
|
+
return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
|
|
985
|
+
}
|
|
986
|
+
function validDate(value) {
|
|
987
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
988
|
+
}
|
|
989
|
+
function isUniqueError(error) {
|
|
990
|
+
return error instanceof Error && error.message.includes("UNIQUE constraint failed");
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/stores/LocalProjectRoomStore.ts
|
|
994
|
+
var columns = "id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at";
|
|
995
|
+
function mapRow(row) {
|
|
996
|
+
const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
|
|
997
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
998
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || row.type !== "main" || typeof row.name !== "string" || !validDate(createdAt) || !validDate(updatedAt)) throw new Error("Invalid project room row");
|
|
999
|
+
return {
|
|
1000
|
+
id: row.id,
|
|
1001
|
+
tenantId: row.tenant_id,
|
|
1002
|
+
workspaceId: row.workspace_id,
|
|
1003
|
+
projectId: row.project_id,
|
|
1004
|
+
type: "main",
|
|
1005
|
+
name: row.name,
|
|
1006
|
+
createdAt,
|
|
1007
|
+
updatedAt
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
var LocalProjectRoomStore = class {
|
|
1011
|
+
constructor(db) {
|
|
1012
|
+
this.db = db;
|
|
1013
|
+
ensureProjectRoomSchema(db);
|
|
1014
|
+
}
|
|
1015
|
+
async ensureMainRoom(input) {
|
|
1016
|
+
return transaction(this.db, () => {
|
|
1017
|
+
const existing = this.getRow(input.tenantId, input.projectId);
|
|
1018
|
+
if (existing) return mapRow(existing);
|
|
1019
|
+
const now = timestamp();
|
|
1020
|
+
write(this.db, "INSERT INTO lt_project_rooms (id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at) VALUES (?, ?, ?, ?, 'main', ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.name, now, now]);
|
|
1021
|
+
return mapRow(this.getRow(input.tenantId, input.projectId));
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
async getMainRoom(tenantId, projectId) {
|
|
1025
|
+
const row = this.getRow(tenantId, projectId);
|
|
1026
|
+
return row ? mapRow(row) : null;
|
|
1027
|
+
}
|
|
1028
|
+
getRow(tenantId, projectId) {
|
|
1029
|
+
return this.db.prepare(`SELECT ${columns} FROM lt_project_rooms WHERE tenant_id = ? AND project_id = ? AND type = 'main'`).get(tenantId, projectId);
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// src/stores/LocalProjectMembershipStore.ts
|
|
1034
|
+
var columns2 = "id, tenant_id, project_id, user_id, role, status, joined_at, updated_at";
|
|
1035
|
+
var DuplicateProjectMembershipError = class extends Error {
|
|
1036
|
+
constructor() {
|
|
1037
|
+
super("Project membership already exists for tenant, project, and user");
|
|
1038
|
+
this.name = "DuplicateProjectMembershipError";
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
var ProjectMembershipIdConflictError = class extends Error {
|
|
1042
|
+
constructor() {
|
|
1043
|
+
super("Project membership ID already exists for tenant");
|
|
1044
|
+
this.name = "ProjectMembershipIdConflictError";
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
function role(value) {
|
|
1048
|
+
return value === "owner" || value === "admin" || value === "member" || value === "viewer";
|
|
1049
|
+
}
|
|
1050
|
+
function mapRow2(row) {
|
|
1051
|
+
const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
|
|
1052
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1053
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.project_id !== "string" || typeof row.user_id !== "string" || !role(row.role) || row.status !== "active" && row.status !== "removed" || !validDate(joinedAt) || !validDate(updatedAt)) throw new Error("Invalid project membership row");
|
|
1054
|
+
return {
|
|
1055
|
+
id: row.id,
|
|
1056
|
+
tenantId: row.tenant_id,
|
|
1057
|
+
projectId: row.project_id,
|
|
1058
|
+
userId: row.user_id,
|
|
1059
|
+
role: row.role,
|
|
1060
|
+
status: row.status,
|
|
1061
|
+
joinedAt,
|
|
1062
|
+
updatedAt
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
var LocalProjectMembershipStore = class {
|
|
1066
|
+
constructor(db) {
|
|
1067
|
+
this.db = db;
|
|
1068
|
+
ensureProjectRoomSchema(db);
|
|
1069
|
+
}
|
|
1070
|
+
async list(tenantId, projectId) {
|
|
1071
|
+
return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? ORDER BY joined_at ASC, id ASC`).all(tenantId, projectId).map(mapRow2);
|
|
1072
|
+
}
|
|
1073
|
+
async findByUser(tenantId, projectId, userId) {
|
|
1074
|
+
const row = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?`).get(tenantId, projectId, userId);
|
|
1075
|
+
return row ? mapRow2(row) : null;
|
|
1076
|
+
}
|
|
1077
|
+
async create(input) {
|
|
1078
|
+
return transaction(this.db, () => {
|
|
1079
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
|
|
1080
|
+
const now = timestamp();
|
|
1081
|
+
try {
|
|
1082
|
+
write(this.db, "INSERT INTO lt_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.projectId, input.userId, input.role, input.status, now, now]);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
return mapRow2(this.byId(input.tenantId, input.id));
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
async createInitialOwner(input) {
|
|
1091
|
+
return transaction(this.db, () => {
|
|
1092
|
+
const rows2 = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ?`).all(input.tenantId, input.projectId);
|
|
1093
|
+
if (rows2.length > 0) {
|
|
1094
|
+
const existing = rows2.map(mapRow2).find((item) => item.userId === input.userId && item.role === "owner" && item.status === "active");
|
|
1095
|
+
return existing ? { kind: "existing", membership: existing } : { kind: "already_initialized" };
|
|
1096
|
+
}
|
|
1097
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
|
|
1098
|
+
const now = timestamp();
|
|
1099
|
+
try {
|
|
1100
|
+
write(this.db, "INSERT INTO lt_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES (?, ?, ?, ?, 'owner', 'active', ?, ?)", [input.id, input.tenantId, input.projectId, input.userId, now, now]);
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
return { kind: "created", membership: mapRow2(this.byId(input.tenantId, input.id)) };
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
async updateRole(input) {
|
|
1109
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, input.role, false);
|
|
1110
|
+
}
|
|
1111
|
+
async remove(input) {
|
|
1112
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, void 0, true);
|
|
1113
|
+
}
|
|
1114
|
+
mutate(tenantId, id, expected, nextRole, remove) {
|
|
1115
|
+
return transaction(this.db, () => {
|
|
1116
|
+
const row = this.byId(tenantId, id);
|
|
1117
|
+
if (!row) return { kind: "not_found" };
|
|
1118
|
+
const current = mapRow2(row);
|
|
1119
|
+
if (!validDate(expected) || current.updatedAt.getTime() !== expected.getTime()) return { kind: "conflict" };
|
|
1120
|
+
if (current.role === "owner" && current.status === "active" && (remove || nextRole !== "owner")) {
|
|
1121
|
+
const other = this.db.prepare("SELECT 1 FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND id <> ? AND role = 'owner' AND status = 'active'").get(tenantId, current.projectId, id);
|
|
1122
|
+
if (!other) return { kind: "last_owner" };
|
|
1123
|
+
}
|
|
1124
|
+
const updatedAt = timestamp(row.updated_at);
|
|
1125
|
+
const changes = remove ? write(this.db, "UPDATE lt_project_memberships SET status = 'removed', updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [updatedAt, tenantId, id, row.updated_at]) : write(this.db, "UPDATE lt_project_memberships SET role = ?, updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [nextRole, updatedAt, tenantId, id, row.updated_at]);
|
|
1126
|
+
if (changes === 0) return { kind: "conflict" };
|
|
1127
|
+
return { kind: remove ? "removed" : "updated", membership: mapRow2(this.byId(tenantId, id)) };
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
byId(tenantId, id) {
|
|
1131
|
+
return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1132
|
+
}
|
|
1133
|
+
classifyUnique(tenantId, id, projectId, userId) {
|
|
1134
|
+
if (this.byId(tenantId, id)) return new ProjectMembershipIdConflictError();
|
|
1135
|
+
if (this.db.prepare("SELECT 1 FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?").get(tenantId, projectId, userId)) return new DuplicateProjectMembershipError();
|
|
1136
|
+
return new Error("Unclassified project membership uniqueness conflict");
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
// src/stores/LocalProjectBotMembershipStore.ts
|
|
1141
|
+
var columns3 = "id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at";
|
|
1142
|
+
var ProjectBotMembershipIdConflictError = class extends Error {
|
|
1143
|
+
constructor(tenantId, id) {
|
|
1144
|
+
super(`Project bot membership ID '${id}' already exists in tenant '${tenantId}'`);
|
|
1145
|
+
this.name = "ProjectBotMembershipIdConflictError";
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
function isRole(value) {
|
|
1149
|
+
return value === "coordinator" || value === "specialist";
|
|
1150
|
+
}
|
|
1151
|
+
function isStatus(value) {
|
|
1152
|
+
return value === "active" || value === "paused" || value === "removed";
|
|
1153
|
+
}
|
|
1154
|
+
function mapRow3(row) {
|
|
1155
|
+
const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
|
|
1156
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1157
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || typeof row.assistant_id !== "string" || !isRole(row.role) || typeof row.title !== "string" || row.responsibility !== null && typeof row.responsibility !== "string" || typeof row.mention_name !== "string" || !isStatus(row.status) || typeof row.room_thread_id !== "string" || !validDate(joinedAt) || !validDate(updatedAt)) throw new Error("Invalid project bot membership row");
|
|
1158
|
+
return {
|
|
1159
|
+
id: row.id,
|
|
1160
|
+
tenantId: row.tenant_id,
|
|
1161
|
+
workspaceId: row.workspace_id,
|
|
1162
|
+
projectId: row.project_id,
|
|
1163
|
+
roomId: row.room_id,
|
|
1164
|
+
assistantId: row.assistant_id,
|
|
1165
|
+
role: row.role,
|
|
1166
|
+
title: row.title,
|
|
1167
|
+
...row.responsibility === null ? {} : { responsibility: row.responsibility },
|
|
1168
|
+
mentionName: row.mention_name,
|
|
1169
|
+
status: row.status,
|
|
1170
|
+
roomThreadId: row.room_thread_id,
|
|
1171
|
+
joinedAt,
|
|
1172
|
+
updatedAt
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
var LocalProjectBotMembershipStore = class {
|
|
1176
|
+
constructor(db) {
|
|
1177
|
+
this.db = db;
|
|
1178
|
+
ensureProjectRoomSchema(db);
|
|
1179
|
+
}
|
|
1180
|
+
async list(tenantId, projectId) {
|
|
1181
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? ORDER BY joined_at ASC, id ASC`).all(tenantId, projectId).map(mapRow3);
|
|
1182
|
+
}
|
|
1183
|
+
async findById(tenantId, id) {
|
|
1184
|
+
const row = this.byId(tenantId, id);
|
|
1185
|
+
return row ? mapRow3(row) : null;
|
|
1186
|
+
}
|
|
1187
|
+
async findByAssistant(tenantId, projectId, assistantId) {
|
|
1188
|
+
const row = this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
|
|
1189
|
+
return row ? mapRow3(row) : null;
|
|
1190
|
+
}
|
|
1191
|
+
async save(input) {
|
|
1192
|
+
try {
|
|
1193
|
+
return transaction(this.db, () => {
|
|
1194
|
+
const existing = this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(input.tenantId, input.projectId, input.assistantId);
|
|
1195
|
+
if (existing) return this.reactivate(existing, input);
|
|
1196
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
1197
|
+
const conflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
|
|
1198
|
+
if (conflict) return { kind: conflict };
|
|
1199
|
+
const now = timestamp();
|
|
1200
|
+
try {
|
|
1201
|
+
write(this.db, "INSERT INTO lt_project_bot_memberships (id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.roomId, input.assistantId, input.role, input.title, input.responsibility ?? null, input.mentionName, input.status, input.roomThreadId, now, now]);
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
if (!isUniqueError(error)) throw error;
|
|
1204
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
1205
|
+
const raced = this.byAssistant(input.tenantId, input.projectId, input.assistantId);
|
|
1206
|
+
if (raced) return this.reactivate(raced, input);
|
|
1207
|
+
const racedConflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
|
|
1208
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1209
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1210
|
+
}
|
|
1211
|
+
return { kind: "created", membership: mapRow3(this.byId(input.tenantId, input.id)) };
|
|
1212
|
+
});
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
const conflict = this.conflict(error);
|
|
1215
|
+
if (conflict) return { kind: conflict };
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
async update(input) {
|
|
1220
|
+
try {
|
|
1221
|
+
return transaction(this.db, () => {
|
|
1222
|
+
const row = this.byId(input.tenantId, input.id);
|
|
1223
|
+
if (!row) return { kind: "not_found" };
|
|
1224
|
+
const existing = mapRow3(row);
|
|
1225
|
+
if (!validDate(input.expectedUpdatedAt) || existing.updatedAt.getTime() !== input.expectedUpdatedAt.getTime()) return { kind: "conflict" };
|
|
1226
|
+
const candidate = { ...existing, ...input.patch };
|
|
1227
|
+
const conflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
|
|
1228
|
+
if (conflict) return { kind: conflict };
|
|
1229
|
+
let changes;
|
|
1230
|
+
try {
|
|
1231
|
+
changes = write(this.db, "UPDATE lt_project_bot_memberships SET role = ?, title = ?, responsibility = ?, mention_name = ?, status = ?, updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [candidate.role, candidate.title, candidate.responsibility ?? null, candidate.mentionName, candidate.status, timestamp(row.updated_at), input.tenantId, input.id, row.updated_at]);
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
if (!isUniqueError(error)) throw error;
|
|
1234
|
+
const racedConflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
|
|
1235
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1236
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1237
|
+
}
|
|
1238
|
+
if (changes === 0) return { kind: "conflict" };
|
|
1239
|
+
return { kind: "updated", membership: mapRow3(this.byId(input.tenantId, input.id)) };
|
|
1240
|
+
});
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
const conflict = this.conflict(error);
|
|
1243
|
+
if (conflict) return { kind: conflict };
|
|
1244
|
+
throw error;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
byId(tenantId, id) {
|
|
1248
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1249
|
+
}
|
|
1250
|
+
byAssistant(tenantId, projectId, assistantId) {
|
|
1251
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
|
|
1252
|
+
}
|
|
1253
|
+
reactivate(existing, input) {
|
|
1254
|
+
const persisted = mapRow3(existing);
|
|
1255
|
+
const wasRemoved = persisted.status === "removed";
|
|
1256
|
+
const conflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
|
|
1257
|
+
if (conflict) return { kind: conflict };
|
|
1258
|
+
try {
|
|
1259
|
+
write(this.db, "UPDATE lt_project_bot_memberships SET role = ?, title = ?, responsibility = ?, mention_name = ?, status = 'active', updated_at = ? WHERE tenant_id = ? AND id = ?", [input.role, input.title, input.responsibility ?? null, input.mentionName, timestamp(existing.updated_at), input.tenantId, persisted.id]);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (!isUniqueError(error)) throw error;
|
|
1262
|
+
const racedConflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
|
|
1263
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1264
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1265
|
+
}
|
|
1266
|
+
return { kind: wasRemoved ? "reactivated" : "updated", membership: mapRow3(this.byId(input.tenantId, persisted.id)) };
|
|
1267
|
+
}
|
|
1268
|
+
preflight(tenantId, projectId, roomId, role2, mentionName, status, excludeId) {
|
|
1269
|
+
if (status === "removed") return void 0;
|
|
1270
|
+
if (role2 === "coordinator" && this.db.prepare("SELECT 1 FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND id <> ? AND role = 'coordinator' AND status IN ('active', 'paused')").get(tenantId, projectId, excludeId)) return "coordinator_conflict";
|
|
1271
|
+
if (this.db.prepare("SELECT 1 FROM lt_project_bot_memberships WHERE tenant_id = ? AND room_id = ? AND mention_name = ? AND id <> ? AND status IN ('active', 'paused')").get(tenantId, roomId, mentionName, excludeId)) return "mention_conflict";
|
|
1272
|
+
return void 0;
|
|
1273
|
+
}
|
|
1274
|
+
conflict(error) {
|
|
1275
|
+
if (!isUniqueError(error) || !(error instanceof Error)) return void 0;
|
|
1276
|
+
if (error.message.includes("tenant_id, lt_project_bot_memberships.project_id")) return "coordinator_conflict";
|
|
1277
|
+
if (error.message.includes("room_id") && error.message.includes("mention_name")) return "mention_conflict";
|
|
1278
|
+
return void 0;
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
// src/stores/LocalProjectRoomMessageStore.ts
|
|
1283
|
+
var columns4 = "id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at";
|
|
1284
|
+
var ProjectRoomMessageIdConflictError = class extends Error {
|
|
1285
|
+
constructor(tenantId, id) {
|
|
1286
|
+
super(`Project room message ID '${id}' already exists in tenant '${tenantId}'`);
|
|
1287
|
+
this.name = "ProjectRoomMessageIdConflictError";
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
var DuplicateProjectRoomMessageIdempotencyKeyError = class extends Error {
|
|
1291
|
+
constructor(tenantId, roomId, key) {
|
|
1292
|
+
super(`Project room message idempotency key '${key}' already exists in tenant '${tenantId}' room '${roomId}'`);
|
|
1293
|
+
this.name = "DuplicateProjectRoomMessageIdempotencyKeyError";
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
function record(value) {
|
|
1297
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1298
|
+
}
|
|
1299
|
+
function only(value, keys) {
|
|
1300
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
1301
|
+
}
|
|
1302
|
+
function parseJson(value) {
|
|
1303
|
+
if (typeof value !== "string") return void 0;
|
|
1304
|
+
try {
|
|
1305
|
+
return JSON.parse(value);
|
|
1306
|
+
} catch {
|
|
1307
|
+
return void 0;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function author(value) {
|
|
1311
|
+
if (!record(value)) return void 0;
|
|
1312
|
+
if (value.type === "human" && only(value, ["type", "userId"]) && typeof value.userId === "string") return { type: "human", userId: value.userId };
|
|
1313
|
+
if (value.type === "bot" && only(value, ["type", "membershipId", "assistantId"]) && typeof value.membershipId === "string" && typeof value.assistantId === "string") return { type: "bot", membershipId: value.membershipId, assistantId: value.assistantId };
|
|
1314
|
+
if (value.type === "system" && only(value, ["type"])) return { type: "system" };
|
|
1315
|
+
return void 0;
|
|
1316
|
+
}
|
|
1317
|
+
function mention(value) {
|
|
1318
|
+
if (!record(value)) return void 0;
|
|
1319
|
+
if (value.type === "bot" && only(value, ["type", "membershipId"]) && typeof value.membershipId === "string") return { type: "bot", membershipId: value.membershipId };
|
|
1320
|
+
if (value.type === "team" && only(value, ["type"])) return { type: "team" };
|
|
1321
|
+
return void 0;
|
|
1322
|
+
}
|
|
1323
|
+
function source(value) {
|
|
1324
|
+
return value === "user" || value === "agent" || value === "task" || value === "routine" || value === "system";
|
|
1325
|
+
}
|
|
1326
|
+
function mapRow4(row) {
|
|
1327
|
+
const mappedAuthor = author(parseJson(row.author));
|
|
1328
|
+
const content = parseJson(row.content);
|
|
1329
|
+
const rawMentions = parseJson(row.mentions);
|
|
1330
|
+
const mappedMentions = Array.isArray(rawMentions) ? rawMentions.map(mention) : void 0;
|
|
1331
|
+
const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
|
|
1332
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || !mappedAuthor || !record(content) || content.type !== "text" || typeof content.text !== "string" || !only(content, ["type", "text"]) || !mappedMentions || mappedMentions.some((item) => item === void 0) || row.reply_to_message_id !== null && typeof row.reply_to_message_id !== "string" || !source(row.source) || row.source_id !== null && typeof row.source_id !== "string" || row.idempotency_key !== null && typeof row.idempotency_key !== "string" || !validDate(createdAt)) throw new Error("Invalid project room message row");
|
|
1333
|
+
return {
|
|
1334
|
+
id: row.id,
|
|
1335
|
+
tenantId: row.tenant_id,
|
|
1336
|
+
workspaceId: row.workspace_id,
|
|
1337
|
+
projectId: row.project_id,
|
|
1338
|
+
roomId: row.room_id,
|
|
1339
|
+
author: mappedAuthor,
|
|
1340
|
+
content: { type: "text", text: content.text },
|
|
1341
|
+
mentions: mappedMentions,
|
|
1342
|
+
...row.reply_to_message_id === null ? {} : { replyToMessageId: row.reply_to_message_id },
|
|
1343
|
+
source: row.source,
|
|
1344
|
+
...row.source_id === null ? {} : { sourceId: row.source_id },
|
|
1345
|
+
...row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key },
|
|
1346
|
+
createdAt
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
var LocalProjectRoomMessageStore = class {
|
|
1350
|
+
constructor(db) {
|
|
1351
|
+
this.db = db;
|
|
1352
|
+
ensureProjectRoomSchema(db);
|
|
1353
|
+
}
|
|
1354
|
+
async create(input) {
|
|
1355
|
+
return this.insert(input, false);
|
|
1356
|
+
}
|
|
1357
|
+
async createIdempotent(input) {
|
|
1358
|
+
return this.insert(input, true);
|
|
1359
|
+
}
|
|
1360
|
+
async list(input) {
|
|
1361
|
+
if (input.before && !validDate(input.before.createdAt)) throw new RangeError("Project room message cursor date is invalid");
|
|
1362
|
+
const limit = Math.min(100, Math.max(1, Math.trunc(Number.isFinite(input.limit) ? input.limit : 1)));
|
|
1363
|
+
const rows2 = input.before ? this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND (created_at < ? OR (created_at = ? AND id < ?)) ORDER BY created_at DESC, id DESC LIMIT ?`).all(input.tenantId, input.roomId, input.before.createdAt.toISOString(), input.before.createdAt.toISOString(), input.before.id, limit) : this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? ORDER BY created_at DESC, id DESC LIMIT ?`).all(input.tenantId, input.roomId, limit);
|
|
1364
|
+
return rows2.map(mapRow4);
|
|
1365
|
+
}
|
|
1366
|
+
async findById(tenantId, id) {
|
|
1367
|
+
const row = this.byId(tenantId, id);
|
|
1368
|
+
return row ? mapRow4(row) : null;
|
|
1369
|
+
}
|
|
1370
|
+
insert(input, idempotent) {
|
|
1371
|
+
return transaction(this.db, () => {
|
|
1372
|
+
if (idempotent && input.idempotencyKey) {
|
|
1373
|
+
const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1374
|
+
if (canonical) return mapRow4(canonical);
|
|
1375
|
+
}
|
|
1376
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
1377
|
+
try {
|
|
1378
|
+
write(this.db, "INSERT INTO lt_project_room_messages (id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.roomId, JSON.stringify(input.author), JSON.stringify(input.content), JSON.stringify(input.mentions), input.replyToMessageId ?? null, input.source, input.sourceId ?? null, input.idempotencyKey ?? null, timestamp()]);
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
if (!isUniqueError(error)) throw error;
|
|
1381
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
1382
|
+
if (input.idempotencyKey) {
|
|
1383
|
+
const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1384
|
+
if (canonical && idempotent) return mapRow4(canonical);
|
|
1385
|
+
if (canonical) throw new DuplicateProjectRoomMessageIdempotencyKeyError(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1386
|
+
}
|
|
1387
|
+
throw new Error("Unclassified project room message uniqueness conflict");
|
|
1388
|
+
}
|
|
1389
|
+
return mapRow4(this.byId(input.tenantId, input.id));
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
byId(tenantId, id) {
|
|
1393
|
+
return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1394
|
+
}
|
|
1395
|
+
byKey(tenantId, roomId, key) {
|
|
1396
|
+
return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND idempotency_key = ?`).get(tenantId, roomId, key);
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
|
|
715
1400
|
// src/stores/LocalUserStore.ts
|
|
716
1401
|
var DDL5 = `
|
|
717
1402
|
CREATE TABLE IF NOT EXISTS lt_users (
|
|
@@ -731,10 +1416,10 @@ var LocalUserStore = class {
|
|
|
731
1416
|
ensureTable(db, DDL5);
|
|
732
1417
|
}
|
|
733
1418
|
async getAllUsers() {
|
|
734
|
-
const
|
|
1419
|
+
const rows2 = this.db.prepare(
|
|
735
1420
|
`SELECT * FROM lt_users ORDER BY created_at DESC`
|
|
736
1421
|
).all();
|
|
737
|
-
return
|
|
1422
|
+
return rows2.map(mapRowToUser);
|
|
738
1423
|
}
|
|
739
1424
|
async getUserById(id) {
|
|
740
1425
|
const row = this.db.prepare(
|
|
@@ -838,10 +1523,10 @@ var LocalTenantStore = class {
|
|
|
838
1523
|
ensureTable(db, DDL6);
|
|
839
1524
|
}
|
|
840
1525
|
async getAllTenants() {
|
|
841
|
-
const
|
|
1526
|
+
const rows2 = this.db.prepare(
|
|
842
1527
|
`SELECT * FROM lt_tenants ORDER BY created_at DESC`
|
|
843
1528
|
).all();
|
|
844
|
-
return
|
|
1529
|
+
return rows2.map(mapRowToTenant);
|
|
845
1530
|
}
|
|
846
1531
|
async getTenantById(id) {
|
|
847
1532
|
const row = this.db.prepare(
|
|
@@ -940,16 +1625,16 @@ var LocalUserTenantLinkStore = class {
|
|
|
940
1625
|
ensureTable(db, DDL7);
|
|
941
1626
|
}
|
|
942
1627
|
async getTenantsByUser(userId) {
|
|
943
|
-
const
|
|
1628
|
+
const rows2 = this.db.prepare(
|
|
944
1629
|
`SELECT * FROM lt_user_tenant_links WHERE user_id = ? ORDER BY joined_at DESC`
|
|
945
1630
|
).all(userId);
|
|
946
|
-
return
|
|
1631
|
+
return rows2.map(mapRowToLink);
|
|
947
1632
|
}
|
|
948
1633
|
async getUsersByTenant(tenantId) {
|
|
949
|
-
const
|
|
1634
|
+
const rows2 = this.db.prepare(
|
|
950
1635
|
`SELECT * FROM lt_user_tenant_links WHERE tenant_id = ? ORDER BY joined_at DESC`
|
|
951
1636
|
).all(tenantId);
|
|
952
|
-
return
|
|
1637
|
+
return rows2.map(mapRowToLink);
|
|
953
1638
|
}
|
|
954
1639
|
async getLink(userId, tenantId) {
|
|
955
1640
|
const row = this.db.prepare(
|
|
@@ -959,7 +1644,7 @@ var LocalUserTenantLinkStore = class {
|
|
|
959
1644
|
}
|
|
960
1645
|
async createLink(data) {
|
|
961
1646
|
const now = nowISO();
|
|
962
|
-
const
|
|
1647
|
+
const role2 = data.role || "member";
|
|
963
1648
|
const metadata = JSON.stringify(data.metadata || {});
|
|
964
1649
|
this.db.prepare(
|
|
965
1650
|
`INSERT INTO lt_user_tenant_links (user_id, tenant_id, role, joined_at, metadata)
|
|
@@ -967,11 +1652,11 @@ var LocalUserTenantLinkStore = class {
|
|
|
967
1652
|
ON CONFLICT(user_id, tenant_id) DO UPDATE SET
|
|
968
1653
|
role = excluded.role,
|
|
969
1654
|
metadata = excluded.metadata`
|
|
970
|
-
).run(data.userId, data.tenantId,
|
|
1655
|
+
).run(data.userId, data.tenantId, role2, now, metadata);
|
|
971
1656
|
return {
|
|
972
1657
|
userId: data.userId,
|
|
973
1658
|
tenantId: data.tenantId,
|
|
974
|
-
role,
|
|
1659
|
+
role: role2,
|
|
975
1660
|
joinedAt: parseISO(now),
|
|
976
1661
|
metadata: data.metadata
|
|
977
1662
|
};
|
|
@@ -1041,16 +1726,16 @@ var LocalDatabaseConfigStore = class {
|
|
|
1041
1726
|
ensureTable(db, DDL8);
|
|
1042
1727
|
}
|
|
1043
1728
|
async getAllConfigs(tenantId) {
|
|
1044
|
-
const
|
|
1729
|
+
const rows2 = this.db.prepare(
|
|
1045
1730
|
`SELECT * FROM lt_database_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1046
1731
|
).all(tenantId);
|
|
1047
|
-
return
|
|
1732
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1048
1733
|
}
|
|
1049
1734
|
async getAllConfigsWithoutTenant() {
|
|
1050
|
-
const
|
|
1735
|
+
const rows2 = this.db.prepare(
|
|
1051
1736
|
`SELECT * FROM lt_database_configs ORDER BY created_at DESC`
|
|
1052
1737
|
).all();
|
|
1053
|
-
return
|
|
1738
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1054
1739
|
}
|
|
1055
1740
|
async getConfigById(tenantId, id) {
|
|
1056
1741
|
const row = this.db.prepare(
|
|
@@ -1182,16 +1867,16 @@ var LocalConnectionStore = class {
|
|
|
1182
1867
|
ensureTable(db, DDL9);
|
|
1183
1868
|
}
|
|
1184
1869
|
async listByTenant(tenantId) {
|
|
1185
|
-
const
|
|
1870
|
+
const rows2 = this.db.prepare(
|
|
1186
1871
|
`SELECT * FROM connection_configs WHERE tenant_id = ? ORDER BY created_at`
|
|
1187
1872
|
).all(tenantId);
|
|
1188
|
-
return
|
|
1873
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1189
1874
|
}
|
|
1190
1875
|
async listByType(tenantId, type) {
|
|
1191
|
-
const
|
|
1876
|
+
const rows2 = this.db.prepare(
|
|
1192
1877
|
`SELECT * FROM connection_configs WHERE tenant_id = ? AND type = ? ORDER BY created_at`
|
|
1193
1878
|
).all(tenantId, type);
|
|
1194
|
-
return
|
|
1879
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1195
1880
|
}
|
|
1196
1881
|
async getByKey(tenantId, type, key) {
|
|
1197
1882
|
const row = this.db.prepare(
|
|
@@ -1277,16 +1962,16 @@ var LocalMetricsServerConfigStore = class {
|
|
|
1277
1962
|
ensureTable(db, DDL10);
|
|
1278
1963
|
}
|
|
1279
1964
|
async getAllConfigs(tenantId) {
|
|
1280
|
-
const
|
|
1965
|
+
const rows2 = this.db.prepare(
|
|
1281
1966
|
`SELECT * FROM lt_metrics_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1282
1967
|
).all(tenantId);
|
|
1283
|
-
return
|
|
1968
|
+
return rows2.map(mapRowToEntry2);
|
|
1284
1969
|
}
|
|
1285
1970
|
async getAllConfigsWithoutTenant() {
|
|
1286
|
-
const
|
|
1971
|
+
const rows2 = this.db.prepare(
|
|
1287
1972
|
`SELECT * FROM lt_metrics_configs ORDER BY created_at DESC`
|
|
1288
1973
|
).all();
|
|
1289
|
-
return
|
|
1974
|
+
return rows2.map(mapRowToEntry2);
|
|
1290
1975
|
}
|
|
1291
1976
|
async getConfigById(tenantId, id) {
|
|
1292
1977
|
const row = this.db.prepare(
|
|
@@ -1405,16 +2090,16 @@ var LocalMcpServerConfigStore = class {
|
|
|
1405
2090
|
ensureTable(db, DDL11);
|
|
1406
2091
|
}
|
|
1407
2092
|
async getAllConfigs(tenantId) {
|
|
1408
|
-
const
|
|
2093
|
+
const rows2 = this.db.prepare(
|
|
1409
2094
|
`SELECT * FROM lt_mcp_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1410
2095
|
).all(tenantId);
|
|
1411
|
-
return
|
|
2096
|
+
return rows2.map(mapRowToEntry3);
|
|
1412
2097
|
}
|
|
1413
2098
|
async getAllConfigsWithoutTenant() {
|
|
1414
|
-
const
|
|
2099
|
+
const rows2 = this.db.prepare(
|
|
1415
2100
|
`SELECT * FROM lt_mcp_configs ORDER BY created_at DESC`
|
|
1416
2101
|
).all();
|
|
1417
|
-
return
|
|
2102
|
+
return rows2.map(mapRowToEntry3);
|
|
1418
2103
|
}
|
|
1419
2104
|
async getConfigById(tenantId, id) {
|
|
1420
2105
|
const row = this.db.prepare(
|
|
@@ -1696,22 +2381,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1696
2381
|
this.db.prepare(`DELETE FROM lt_workflow_runs WHERE id = ?`).run(runId);
|
|
1697
2382
|
}
|
|
1698
2383
|
async getWorkflowRunsByThreadId(tenantId, threadId) {
|
|
1699
|
-
const
|
|
2384
|
+
const rows2 = this.db.prepare(
|
|
1700
2385
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND thread_id = ? ORDER BY created_at DESC`
|
|
1701
2386
|
).all(tenantId, threadId);
|
|
1702
|
-
return
|
|
2387
|
+
return rows2.map(mapRowToRun);
|
|
1703
2388
|
}
|
|
1704
2389
|
async getWorkflowRunsByAssistantId(tenantId, assistantId) {
|
|
1705
|
-
const
|
|
2390
|
+
const rows2 = this.db.prepare(
|
|
1706
2391
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
1707
2392
|
).all(tenantId, assistantId);
|
|
1708
|
-
return
|
|
2393
|
+
return rows2.map(mapRowToRun);
|
|
1709
2394
|
}
|
|
1710
2395
|
async getWorkflowRunsByTenantId(tenantId) {
|
|
1711
|
-
const
|
|
2396
|
+
const rows2 = this.db.prepare(
|
|
1712
2397
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1713
2398
|
).all(tenantId);
|
|
1714
|
-
return
|
|
2399
|
+
return rows2.map(mapRowToRun);
|
|
1715
2400
|
}
|
|
1716
2401
|
async queryWorkflowRuns(tenantId, options = {}) {
|
|
1717
2402
|
const conditions = ["tenant_id = ?"];
|
|
@@ -1740,8 +2425,8 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1740
2425
|
sql += " OFFSET ?";
|
|
1741
2426
|
selectParams.push(options.offset);
|
|
1742
2427
|
}
|
|
1743
|
-
const
|
|
1744
|
-
return { records:
|
|
2428
|
+
const rows2 = this.db.prepare(sql).all(...selectParams);
|
|
2429
|
+
return { records: rows2.map(mapRowToRun), total: countRow.count };
|
|
1745
2430
|
}
|
|
1746
2431
|
async createRunStep(request) {
|
|
1747
2432
|
const now = nowISO();
|
|
@@ -1833,22 +2518,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1833
2518
|
return this.getStepById(runId, stepId);
|
|
1834
2519
|
}
|
|
1835
2520
|
async getRunSteps(runId) {
|
|
1836
|
-
const
|
|
2521
|
+
const rows2 = this.db.prepare(
|
|
1837
2522
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? ORDER BY created_at ASC`
|
|
1838
2523
|
).all(runId);
|
|
1839
|
-
return
|
|
2524
|
+
return rows2.map(mapRowToStep);
|
|
1840
2525
|
}
|
|
1841
2526
|
async getRunStepsByType(runId, stepType) {
|
|
1842
|
-
const
|
|
2527
|
+
const rows2 = this.db.prepare(
|
|
1843
2528
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? ORDER BY created_at ASC`
|
|
1844
2529
|
).all(runId, stepType);
|
|
1845
|
-
return
|
|
2530
|
+
return rows2.map(mapRowToStep);
|
|
1846
2531
|
}
|
|
1847
2532
|
async getInterruptedSteps(runId) {
|
|
1848
|
-
const
|
|
2533
|
+
const rows2 = this.db.prepare(
|
|
1849
2534
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND status = 'interrupted' ORDER BY created_at ASC`
|
|
1850
2535
|
).all(runId);
|
|
1851
|
-
return
|
|
2536
|
+
return rows2.map(mapRowToStep);
|
|
1852
2537
|
}
|
|
1853
2538
|
getStepById(runId, id) {
|
|
1854
2539
|
const row = this.db.prepare(
|
|
@@ -1993,7 +2678,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_run_results (
|
|
|
1993
2678
|
PRIMARY KEY (run_id, id)
|
|
1994
2679
|
);
|
|
1995
2680
|
`;
|
|
1996
|
-
function
|
|
2681
|
+
function parseJson2(val, fallback) {
|
|
1997
2682
|
if (val == null) return fallback;
|
|
1998
2683
|
if (typeof val === "string") {
|
|
1999
2684
|
try {
|
|
@@ -2085,20 +2770,20 @@ var LocalEvalStore = class {
|
|
|
2085
2770
|
`);
|
|
2086
2771
|
}
|
|
2087
2772
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2088
|
-
ensureColumn(table, column,
|
|
2773
|
+
ensureColumn(table, column, ddl2) {
|
|
2089
2774
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2090
2775
|
if (!cols.some((c) => c.name === column)) {
|
|
2091
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
2776
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
2092
2777
|
}
|
|
2093
2778
|
}
|
|
2094
2779
|
// -------------------------------------------------------------------------
|
|
2095
2780
|
// Projects
|
|
2096
2781
|
// -------------------------------------------------------------------------
|
|
2097
2782
|
async getProjectsByTenant(tenantId) {
|
|
2098
|
-
const
|
|
2783
|
+
const rows2 = this.db.prepare(
|
|
2099
2784
|
`SELECT * FROM lt_eval_projects WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2100
2785
|
).all(tenantId);
|
|
2101
|
-
return
|
|
2786
|
+
return rows2.map(this.mapRowToProject);
|
|
2102
2787
|
}
|
|
2103
2788
|
async getProjectById(tenantId, id) {
|
|
2104
2789
|
const row = this.db.prepare(
|
|
@@ -2201,11 +2886,11 @@ var LocalEvalStore = class {
|
|
|
2201
2886
|
// Suites
|
|
2202
2887
|
// -------------------------------------------------------------------------
|
|
2203
2888
|
async getSuitesByProject(tenantId, projectId) {
|
|
2204
|
-
const
|
|
2889
|
+
const rows2 = this.db.prepare(
|
|
2205
2890
|
`SELECT s.*, (SELECT COUNT(*) FROM lt_eval_cases c WHERE c.suite_id = s.id AND c.tenant_id = s.tenant_id) as case_count
|
|
2206
2891
|
FROM lt_eval_suites s WHERE s.tenant_id = ? AND s.project_id = ? ORDER BY s.created_at DESC`
|
|
2207
2892
|
).all(tenantId, projectId);
|
|
2208
|
-
return
|
|
2893
|
+
return rows2.map(this.mapRowToSuite);
|
|
2209
2894
|
}
|
|
2210
2895
|
async getSuiteById(tenantId, id) {
|
|
2211
2896
|
const row = this.db.prepare(
|
|
@@ -2246,10 +2931,10 @@ var LocalEvalStore = class {
|
|
|
2246
2931
|
// Cases
|
|
2247
2932
|
// -------------------------------------------------------------------------
|
|
2248
2933
|
async getCasesBySuite(tenantId, suiteId) {
|
|
2249
|
-
const
|
|
2934
|
+
const rows2 = this.db.prepare(
|
|
2250
2935
|
`SELECT * FROM lt_eval_cases WHERE tenant_id = ? AND suite_id = ? ORDER BY created_at DESC`
|
|
2251
2936
|
).all(tenantId, suiteId);
|
|
2252
|
-
return
|
|
2937
|
+
return rows2.map(this.mapRowToCase);
|
|
2253
2938
|
}
|
|
2254
2939
|
async getCaseById(tenantId, id) {
|
|
2255
2940
|
const row = this.db.prepare(
|
|
@@ -2338,8 +3023,8 @@ var LocalEvalStore = class {
|
|
|
2338
3023
|
vals.push(opts.status);
|
|
2339
3024
|
}
|
|
2340
3025
|
query += ` ORDER BY created_at DESC`;
|
|
2341
|
-
const
|
|
2342
|
-
return
|
|
3026
|
+
const rows2 = this.db.prepare(query).all(...vals);
|
|
3027
|
+
return rows2.map(this.mapRowToRun);
|
|
2343
3028
|
}
|
|
2344
3029
|
async getRunById(tenantId, id) {
|
|
2345
3030
|
const row = this.db.prepare(
|
|
@@ -2402,12 +3087,12 @@ var LocalEvalStore = class {
|
|
|
2402
3087
|
// Run Results
|
|
2403
3088
|
// -------------------------------------------------------------------------
|
|
2404
3089
|
async getResultsByRun(tenantId, runId) {
|
|
2405
|
-
const
|
|
3090
|
+
const rows2 = this.db.prepare(
|
|
2406
3091
|
`SELECT rr.* FROM lt_eval_run_results rr
|
|
2407
3092
|
INNER JOIN lt_eval_runs r ON r.id = rr.run_id
|
|
2408
3093
|
WHERE r.tenant_id = ? AND rr.run_id = ? ORDER BY rr.created_at ASC`
|
|
2409
3094
|
).all(tenantId, runId);
|
|
2410
|
-
return
|
|
3095
|
+
return rows2.map(this.mapRowToRunResult);
|
|
2411
3096
|
}
|
|
2412
3097
|
async getRunResultById(tenantId, id) {
|
|
2413
3098
|
const row = this.db.prepare(
|
|
@@ -2525,8 +3210,8 @@ var LocalEvalStore = class {
|
|
|
2525
3210
|
name: row.name,
|
|
2526
3211
|
description: row.description,
|
|
2527
3212
|
version: row.version,
|
|
2528
|
-
judgeModelConfig:
|
|
2529
|
-
targetServerConfig:
|
|
3213
|
+
judgeModelConfig: parseJson2(row.judge_model_config, {}),
|
|
3214
|
+
targetServerConfig: parseJson2(row.target_server_config, {}),
|
|
2530
3215
|
concurrency: row.concurrency ?? 3,
|
|
2531
3216
|
reportConfig: parseOptionalJson(row.report_config),
|
|
2532
3217
|
createdAt: parseISO(row.created_at),
|
|
@@ -2551,7 +3236,7 @@ var LocalEvalStore = class {
|
|
|
2551
3236
|
suiteId: row.suite_id,
|
|
2552
3237
|
inputMessage: row.input_message,
|
|
2553
3238
|
inputFiles: parseOptionalJson(row.input_files),
|
|
2554
|
-
steps:
|
|
3239
|
+
steps: parseJson2(row.steps, []),
|
|
2555
3240
|
outputType: row.output_type || "message_content",
|
|
2556
3241
|
contentAssertion: row.content_assertion || "",
|
|
2557
3242
|
rubrics: parseOptionalJson(row.rubrics),
|
|
@@ -2603,7 +3288,54 @@ var LocalEvalStore = class {
|
|
|
2603
3288
|
};
|
|
2604
3289
|
|
|
2605
3290
|
// src/stores/LocalChannelBindingStore.ts
|
|
3291
|
+
import {
|
|
3292
|
+
DuplicateChannelBindingSubjectError
|
|
3293
|
+
} from "@axiom-lattice/protocols";
|
|
2606
3294
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3295
|
+
|
|
3296
|
+
// src/stores/room-channel-binding-index.ts
|
|
3297
|
+
import {
|
|
3298
|
+
ChannelBindingMigrationConflictError
|
|
3299
|
+
} from "@axiom-lattice/protocols";
|
|
3300
|
+
var UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_cb_subject_unique
|
|
3301
|
+
ON lt_channel_bindings(channel, channel_installation_id, tenant_id, sender_id)`;
|
|
3302
|
+
var LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve
|
|
3303
|
+
ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id)`;
|
|
3304
|
+
function ensureChannelBindingSubjectIndex(db) {
|
|
3305
|
+
db.exec("BEGIN");
|
|
3306
|
+
try {
|
|
3307
|
+
const conflicts = db.prepare(`SELECT tenant_id, channel, channel_installation_id, sender_id, COUNT(*) AS count
|
|
3308
|
+
FROM lt_channel_bindings
|
|
3309
|
+
GROUP BY channel, channel_installation_id, tenant_id, sender_id
|
|
3310
|
+
HAVING COUNT(*) > 1
|
|
3311
|
+
ORDER BY tenant_id, channel, channel_installation_id, sender_id`).all();
|
|
3312
|
+
if (conflicts.length > 0) {
|
|
3313
|
+
throw new ChannelBindingMigrationConflictError(conflicts.map(mapConflict));
|
|
3314
|
+
}
|
|
3315
|
+
db.exec("DROP INDEX IF EXISTS idx_lt_cb_room_subject");
|
|
3316
|
+
db.exec(UNIQUE_INDEX_SQL);
|
|
3317
|
+
db.exec(LOOKUP_INDEX_SQL);
|
|
3318
|
+
db.exec("COMMIT");
|
|
3319
|
+
} catch (error) {
|
|
3320
|
+
try {
|
|
3321
|
+
db.exec("ROLLBACK");
|
|
3322
|
+
} catch {
|
|
3323
|
+
}
|
|
3324
|
+
throw error;
|
|
3325
|
+
}
|
|
3326
|
+
db.save();
|
|
3327
|
+
}
|
|
3328
|
+
function mapConflict(row) {
|
|
3329
|
+
return {
|
|
3330
|
+
tenantId: row.tenant_id,
|
|
3331
|
+
channel: row.channel,
|
|
3332
|
+
channelInstallationId: row.channel_installation_id,
|
|
3333
|
+
senderId: row.sender_id,
|
|
3334
|
+
count: row.count
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// src/stores/LocalChannelBindingStore.ts
|
|
2607
3339
|
var DDL14 = `
|
|
2608
3340
|
CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
2609
3341
|
id TEXT PRIMARY KEY,
|
|
@@ -2622,12 +3354,23 @@ CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
|
2622
3354
|
created_at TEXT NOT NULL,
|
|
2623
3355
|
updated_at TEXT NOT NULL
|
|
2624
3356
|
);
|
|
2625
|
-
CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id);
|
|
2626
3357
|
`;
|
|
2627
3358
|
var LocalChannelBindingStore = class {
|
|
2628
3359
|
constructor(db) {
|
|
2629
3360
|
this.db = db;
|
|
2630
3361
|
ensureTable(db, DDL14);
|
|
3362
|
+
ensureChannelBindingSubjectIndex(db);
|
|
3363
|
+
}
|
|
3364
|
+
async findById(tenantId, id) {
|
|
3365
|
+
const row = this.db.prepare(
|
|
3366
|
+
"SELECT * FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?"
|
|
3367
|
+
).get(id, tenantId);
|
|
3368
|
+
return row ? mapRowToBinding(row) : null;
|
|
3369
|
+
}
|
|
3370
|
+
async findBySubject(params) {
|
|
3371
|
+
const row = this.db.prepare(`SELECT * FROM lt_channel_bindings
|
|
3372
|
+
WHERE tenant_id = ? AND channel = ? AND channel_installation_id = ? AND sender_id = ? LIMIT 1`).get(params.tenantId, params.channel, params.channelInstallationId, params.senderId);
|
|
3373
|
+
return row ? mapRowToBinding(row) : null;
|
|
2631
3374
|
}
|
|
2632
3375
|
async resolve(params) {
|
|
2633
3376
|
const row = this.db.prepare(
|
|
@@ -2640,35 +3383,41 @@ var LocalChannelBindingStore = class {
|
|
|
2640
3383
|
async create(input) {
|
|
2641
3384
|
const id = randomUUID2();
|
|
2642
3385
|
const now = nowISO();
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
3386
|
+
try {
|
|
3387
|
+
this.db.prepare(
|
|
3388
|
+
`INSERT INTO lt_channel_bindings
|
|
3389
|
+
(id, channel, channel_installation_id, tenant_id, sender_id, agent_id,
|
|
3390
|
+
thread_id, thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, enabled, created_at, updated_at)
|
|
3391
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3392
|
+
).run(
|
|
3393
|
+
id,
|
|
3394
|
+
input.channel,
|
|
3395
|
+
input.channelInstallationId,
|
|
3396
|
+
input.tenantId,
|
|
3397
|
+
input.senderId,
|
|
3398
|
+
input.agentId,
|
|
3399
|
+
input.threadId || null,
|
|
3400
|
+
input.threadMode || "fixed",
|
|
3401
|
+
input.senderDisplayName || null,
|
|
3402
|
+
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
3403
|
+
input.workspaceId || null,
|
|
3404
|
+
input.projectId || null,
|
|
3405
|
+
input.enabled ?? true ? 1 : 0,
|
|
3406
|
+
now,
|
|
3407
|
+
now
|
|
3408
|
+
);
|
|
3409
|
+
} catch (error) {
|
|
3410
|
+
if (error instanceof Error && /UNIQUE constraint failed/.test(error.message)) {
|
|
3411
|
+
throw new DuplicateChannelBindingSubjectError();
|
|
3412
|
+
}
|
|
3413
|
+
throw error;
|
|
3414
|
+
}
|
|
2663
3415
|
return await this.getById(id);
|
|
2664
3416
|
}
|
|
2665
|
-
async update(id, patch) {
|
|
2666
|
-
const existing = await this.
|
|
3417
|
+
async update(tenantId, id, patch) {
|
|
3418
|
+
const existing = await this.findById(tenantId, id);
|
|
2667
3419
|
if (!existing) throw new Error(`Binding ${id} not found`);
|
|
2668
3420
|
const updated = {
|
|
2669
|
-
channel: patch.channel ?? existing.channel,
|
|
2670
|
-
channelInstallationId: patch.channelInstallationId ?? existing.channelInstallationId,
|
|
2671
|
-
senderId: patch.senderId ?? existing.senderId,
|
|
2672
3421
|
agentId: patch.agentId ?? existing.agentId,
|
|
2673
3422
|
threadId: patch.threadId !== void 0 ? patch.threadId : existing.threadId,
|
|
2674
3423
|
workspaceId: patch.workspaceId !== void 0 ? patch.workspaceId : existing.workspaceId,
|
|
@@ -2681,14 +3430,10 @@ var LocalChannelBindingStore = class {
|
|
|
2681
3430
|
const now = nowISO();
|
|
2682
3431
|
this.db.prepare(
|
|
2683
3432
|
`UPDATE lt_channel_bindings SET
|
|
2684
|
-
|
|
2685
|
-
thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
3433
|
+
agent_id = ?, thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
2686
3434
|
sender_display_name = ?, sender_metadata = ?, enabled = ?, updated_at = ?
|
|
2687
|
-
WHERE id = ?`
|
|
3435
|
+
WHERE id = ? AND tenant_id = ?`
|
|
2688
3436
|
).run(
|
|
2689
|
-
updated.channel,
|
|
2690
|
-
updated.channelInstallationId,
|
|
2691
|
-
updated.senderId,
|
|
2692
3437
|
updated.agentId,
|
|
2693
3438
|
updated.threadId || null,
|
|
2694
3439
|
updated.workspaceId || null,
|
|
@@ -2698,12 +3443,13 @@ var LocalChannelBindingStore = class {
|
|
|
2698
3443
|
updated.senderMetadata ? JSON.stringify(updated.senderMetadata) : null,
|
|
2699
3444
|
updated.enabled ? 1 : 0,
|
|
2700
3445
|
now,
|
|
2701
|
-
id
|
|
3446
|
+
id,
|
|
3447
|
+
tenantId
|
|
2702
3448
|
);
|
|
2703
|
-
return await this.
|
|
3449
|
+
return await this.findById(tenantId, id);
|
|
2704
3450
|
}
|
|
2705
|
-
async delete(id) {
|
|
2706
|
-
this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ?`).run(id);
|
|
3451
|
+
async delete(tenantId, id) {
|
|
3452
|
+
this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?`).run(id, tenantId);
|
|
2707
3453
|
}
|
|
2708
3454
|
async list(params) {
|
|
2709
3455
|
const conditions = ["tenant_id = ?"];
|
|
@@ -2720,18 +3466,35 @@ var LocalChannelBindingStore = class {
|
|
|
2720
3466
|
conditions.push("channel_installation_id = ?");
|
|
2721
3467
|
values.push(params.channelInstallationId);
|
|
2722
3468
|
}
|
|
3469
|
+
if (params.excludeChannels?.length) {
|
|
3470
|
+
conditions.push(`channel NOT IN (${params.excludeChannels.map(() => "?").join(", ")})`);
|
|
3471
|
+
values.push(...params.excludeChannels);
|
|
3472
|
+
}
|
|
3473
|
+
for (const prefix of params.excludeInstallationIdPrefixes ?? []) {
|
|
3474
|
+
conditions.push("substr(channel_installation_id, 1, length(?)) <> ?");
|
|
3475
|
+
values.push(prefix, prefix);
|
|
3476
|
+
}
|
|
2723
3477
|
const limit = params.limit ?? 50;
|
|
2724
3478
|
const offset = params.offset ?? 0;
|
|
2725
3479
|
values.push(limit, offset);
|
|
2726
|
-
const
|
|
3480
|
+
const rows2 = this.db.prepare(
|
|
2727
3481
|
`SELECT * FROM lt_channel_bindings
|
|
2728
3482
|
WHERE ${conditions.join(" AND ")}
|
|
2729
3483
|
ORDER BY created_at DESC
|
|
2730
3484
|
LIMIT ? OFFSET ?`
|
|
2731
3485
|
).all(...values);
|
|
2732
|
-
return
|
|
3486
|
+
return rows2.map(mapRowToBinding);
|
|
2733
3487
|
}
|
|
2734
|
-
async import(bindings) {
|
|
3488
|
+
async import(tenantId, bindings) {
|
|
3489
|
+
if (bindings.some((binding) => binding.channel === "room")) {
|
|
3490
|
+
throw new Error("Room bindings cannot be imported through the public store API");
|
|
3491
|
+
}
|
|
3492
|
+
if (bindings.some((binding) => binding.channelInstallationId.startsWith("room-internal:"))) {
|
|
3493
|
+
throw new Error("Internal bindings cannot be imported through the public store API");
|
|
3494
|
+
}
|
|
3495
|
+
if (bindings.some((binding) => binding.tenantId !== tenantId)) {
|
|
3496
|
+
throw new Error("Binding import tenant mismatch");
|
|
3497
|
+
}
|
|
2735
3498
|
const result = [];
|
|
2736
3499
|
for (const input of bindings) {
|
|
2737
3500
|
result.push(await this.create(input));
|
|
@@ -2739,7 +3502,13 @@ var LocalChannelBindingStore = class {
|
|
|
2739
3502
|
return result;
|
|
2740
3503
|
}
|
|
2741
3504
|
async export(params) {
|
|
2742
|
-
return this.list({
|
|
3505
|
+
return this.list({
|
|
3506
|
+
tenantId: params.tenantId,
|
|
3507
|
+
excludeChannels: ["room"],
|
|
3508
|
+
excludeInstallationIdPrefixes: ["room-internal:"],
|
|
3509
|
+
limit: 1e4,
|
|
3510
|
+
offset: 0
|
|
3511
|
+
});
|
|
2743
3512
|
}
|
|
2744
3513
|
async getById(id) {
|
|
2745
3514
|
const row = this.db.prepare(
|
|
@@ -2793,33 +3562,33 @@ var LocalChannelInstallationStore = class {
|
|
|
2793
3562
|
const row = this.db.prepare(
|
|
2794
3563
|
`SELECT * FROM lt_channel_installations WHERE id = ?`
|
|
2795
3564
|
).get(installationId);
|
|
2796
|
-
return row ?
|
|
3565
|
+
return row ? mapRow5(row) : null;
|
|
2797
3566
|
}
|
|
2798
3567
|
async getInstallationsByTenant(tenantId, channel) {
|
|
2799
|
-
let
|
|
3568
|
+
let rows2;
|
|
2800
3569
|
if (channel) {
|
|
2801
|
-
|
|
3570
|
+
rows2 = this.db.prepare(
|
|
2802
3571
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? AND channel = ? ORDER BY created_at DESC`
|
|
2803
3572
|
).all(tenantId, channel);
|
|
2804
3573
|
} else {
|
|
2805
|
-
|
|
3574
|
+
rows2 = this.db.prepare(
|
|
2806
3575
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2807
3576
|
).all(tenantId);
|
|
2808
3577
|
}
|
|
2809
|
-
return
|
|
3578
|
+
return rows2.map(mapRow5);
|
|
2810
3579
|
}
|
|
2811
3580
|
async getAllInstallations(channel) {
|
|
2812
|
-
let
|
|
3581
|
+
let rows2;
|
|
2813
3582
|
if (channel) {
|
|
2814
|
-
|
|
3583
|
+
rows2 = this.db.prepare(
|
|
2815
3584
|
`SELECT * FROM lt_channel_installations WHERE channel = ? ORDER BY created_at DESC`
|
|
2816
3585
|
).all(channel);
|
|
2817
3586
|
} else {
|
|
2818
|
-
|
|
3587
|
+
rows2 = this.db.prepare(
|
|
2819
3588
|
`SELECT * FROM lt_channel_installations ORDER BY created_at DESC`
|
|
2820
3589
|
).all();
|
|
2821
3590
|
}
|
|
2822
|
-
return
|
|
3591
|
+
return rows2.map(mapRow5);
|
|
2823
3592
|
}
|
|
2824
3593
|
async createInstallation(tenantId, installationId, data) {
|
|
2825
3594
|
const now = nowISO();
|
|
@@ -2844,7 +3613,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2844
3613
|
tenantId,
|
|
2845
3614
|
channel: data.channel,
|
|
2846
3615
|
name: data.name,
|
|
2847
|
-
config: data.config,
|
|
3616
|
+
config: { ...data.config },
|
|
2848
3617
|
enabled: data.enabled !== false,
|
|
2849
3618
|
fallbackAgentId: data.fallbackAgentId,
|
|
2850
3619
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? false,
|
|
@@ -2854,7 +3623,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2854
3623
|
}
|
|
2855
3624
|
async updateInstallation(tenantId, installationId, updates) {
|
|
2856
3625
|
const existing = await this.getInstallationById(installationId);
|
|
2857
|
-
if (!existing) return null;
|
|
3626
|
+
if (!existing || existing.tenantId !== tenantId) return null;
|
|
2858
3627
|
const setClauses = [];
|
|
2859
3628
|
const values = [];
|
|
2860
3629
|
if (updates.name !== void 0) {
|
|
@@ -2881,9 +3650,10 @@ var LocalChannelInstallationStore = class {
|
|
|
2881
3650
|
const now = nowISO();
|
|
2882
3651
|
setClauses.push("updated_at = ?");
|
|
2883
3652
|
values.push(now);
|
|
3653
|
+
values.push(tenantId);
|
|
2884
3654
|
values.push(installationId);
|
|
2885
3655
|
this.db.prepare(
|
|
2886
|
-
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE id = ?`
|
|
3656
|
+
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`
|
|
2887
3657
|
).run(...values);
|
|
2888
3658
|
return this.getInstallationById(installationId);
|
|
2889
3659
|
}
|
|
@@ -2894,7 +3664,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2894
3664
|
return result.changes > 0;
|
|
2895
3665
|
}
|
|
2896
3666
|
};
|
|
2897
|
-
function
|
|
3667
|
+
function mapRow5(row) {
|
|
2898
3668
|
return {
|
|
2899
3669
|
id: row.id,
|
|
2900
3670
|
tenantId: row.tenant_id,
|
|
@@ -2945,18 +3715,18 @@ var LocalA2AApiKeyStore = class {
|
|
|
2945
3715
|
this.repairManagementIds();
|
|
2946
3716
|
}
|
|
2947
3717
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2948
|
-
ensureColumn(table, column,
|
|
3718
|
+
ensureColumn(table, column, ddl2) {
|
|
2949
3719
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2950
3720
|
if (!cols.some((c) => c.name === column)) {
|
|
2951
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
3721
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
2952
3722
|
}
|
|
2953
3723
|
}
|
|
2954
3724
|
repairManagementIds() {
|
|
2955
|
-
const
|
|
3725
|
+
const rows2 = this.db.prepare(
|
|
2956
3726
|
`SELECT rowid, id FROM lt_a2a_api_keys`
|
|
2957
3727
|
).all();
|
|
2958
3728
|
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2959
|
-
for (const row of
|
|
3729
|
+
for (const row of rows2) {
|
|
2960
3730
|
if (!row.id || !uuidPattern.test(row.id)) {
|
|
2961
3731
|
this.db.prepare(
|
|
2962
3732
|
`UPDATE lt_a2a_api_keys SET id = ? WHERE rowid = ?`
|
|
@@ -2965,10 +3735,10 @@ var LocalA2AApiKeyStore = class {
|
|
|
2965
3735
|
}
|
|
2966
3736
|
}
|
|
2967
3737
|
async findByKey(key) {
|
|
2968
|
-
const
|
|
3738
|
+
const rows2 = this.db.prepare(
|
|
2969
3739
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
2970
3740
|
).all();
|
|
2971
|
-
for (const row of
|
|
3741
|
+
for (const row of rows2) {
|
|
2972
3742
|
try {
|
|
2973
3743
|
if (decrypt3(row.key_value) === key) return mapRowToRecord(row);
|
|
2974
3744
|
} catch {
|
|
@@ -2985,17 +3755,17 @@ var LocalA2AApiKeyStore = class {
|
|
|
2985
3755
|
async list(params) {
|
|
2986
3756
|
const limit = params.limit || 100;
|
|
2987
3757
|
const offset = params.offset || 0;
|
|
2988
|
-
let
|
|
3758
|
+
let rows2;
|
|
2989
3759
|
if (params.tenantId) {
|
|
2990
|
-
|
|
3760
|
+
rows2 = this.db.prepare(
|
|
2991
3761
|
`SELECT * FROM lt_a2a_api_keys WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
2992
3762
|
).all(params.tenantId, limit, offset);
|
|
2993
3763
|
} else {
|
|
2994
|
-
|
|
3764
|
+
rows2 = this.db.prepare(
|
|
2995
3765
|
`SELECT * FROM lt_a2a_api_keys ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
2996
3766
|
).all(limit, offset);
|
|
2997
3767
|
}
|
|
2998
|
-
return
|
|
3768
|
+
return rows2.map(mapRowToRecord);
|
|
2999
3769
|
}
|
|
3000
3770
|
async create(input) {
|
|
3001
3771
|
const id = randomUUID3();
|
|
@@ -3005,7 +3775,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3005
3775
|
`INSERT INTO lt_a2a_api_keys (id, key_value, tenant_id, project_id, assistant_ids, label, created_at, updated_at)
|
|
3006
3776
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3007
3777
|
).run(id, encrypt3(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null, now, now);
|
|
3008
|
-
const
|
|
3778
|
+
const record2 = {
|
|
3009
3779
|
id,
|
|
3010
3780
|
key,
|
|
3011
3781
|
tenantId: input.tenantId,
|
|
@@ -3016,7 +3786,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3016
3786
|
createdAt: parseISO(now),
|
|
3017
3787
|
updatedAt: parseISO(now)
|
|
3018
3788
|
};
|
|
3019
|
-
return
|
|
3789
|
+
return record2;
|
|
3020
3790
|
}
|
|
3021
3791
|
async disable(id) {
|
|
3022
3792
|
const now = nowISO();
|
|
@@ -3044,22 +3814,22 @@ var LocalA2AApiKeyStore = class {
|
|
|
3044
3814
|
).run(encrypt3(key), now, id);
|
|
3045
3815
|
const row = this.db.prepare(`SELECT * FROM lt_a2a_api_keys WHERE id = ?`).get(id);
|
|
3046
3816
|
if (!row) throw new Error(`A2A API key not found: ${id}`);
|
|
3047
|
-
const
|
|
3048
|
-
|
|
3049
|
-
return
|
|
3817
|
+
const record2 = mapRowToRecord(row);
|
|
3818
|
+
record2.key = key;
|
|
3819
|
+
return record2;
|
|
3050
3820
|
}
|
|
3051
3821
|
async delete(id) {
|
|
3052
3822
|
this.db.prepare(`DELETE FROM lt_a2a_api_keys WHERE id = ?`).run(id);
|
|
3053
3823
|
}
|
|
3054
3824
|
async loadIntoMap() {
|
|
3055
|
-
const
|
|
3825
|
+
const rows2 = this.db.prepare(
|
|
3056
3826
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3057
3827
|
).all();
|
|
3058
|
-
const
|
|
3059
|
-
for (const row of
|
|
3828
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3829
|
+
for (const row of rows2) {
|
|
3060
3830
|
try {
|
|
3061
3831
|
const key = decrypt3(row.key_value);
|
|
3062
|
-
|
|
3832
|
+
map2.set(key, {
|
|
3063
3833
|
key,
|
|
3064
3834
|
tenantId: row.tenant_id,
|
|
3065
3835
|
projectId: row.project_id,
|
|
@@ -3068,7 +3838,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3068
3838
|
} catch {
|
|
3069
3839
|
}
|
|
3070
3840
|
}
|
|
3071
|
-
return
|
|
3841
|
+
return map2;
|
|
3072
3842
|
}
|
|
3073
3843
|
};
|
|
3074
3844
|
function mapRowToRecord(row) {
|
|
@@ -3092,6 +3862,10 @@ function mapRowToRecord(row) {
|
|
|
3092
3862
|
}
|
|
3093
3863
|
|
|
3094
3864
|
// src/stores/LocalThreadMessageQueueStore.ts
|
|
3865
|
+
import {
|
|
3866
|
+
parseQueuedExecutionMode,
|
|
3867
|
+
parseTrustedRunContext
|
|
3868
|
+
} from "@axiom-lattice/protocols";
|
|
3095
3869
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3096
3870
|
var DDL17 = `
|
|
3097
3871
|
CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
@@ -3108,6 +3882,8 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
|
3108
3882
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
3109
3883
|
command TEXT,
|
|
3110
3884
|
custom_run_config TEXT,
|
|
3885
|
+
trusted_run_context TEXT,
|
|
3886
|
+
execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
|
|
3111
3887
|
created_at TEXT NOT NULL
|
|
3112
3888
|
);
|
|
3113
3889
|
CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
|
|
@@ -3115,10 +3891,20 @@ CREATE INDEX IF NOT EXISTS idx_lt_tmq_pending ON lt_thread_message_queue(status,
|
|
|
3115
3891
|
`;
|
|
3116
3892
|
var LocalThreadMessageQueueStore = class {
|
|
3117
3893
|
constructor(db) {
|
|
3894
|
+
this.capacityTail = Promise.resolve();
|
|
3118
3895
|
this.db = db;
|
|
3119
3896
|
ensureTable(db, DDL17);
|
|
3897
|
+
const columns5 = db.prepare("PRAGMA table_info(lt_thread_message_queue)").all();
|
|
3898
|
+
const names = new Set(columns5.map(({ name }) => name));
|
|
3899
|
+
if (!names.has("trusted_run_context")) {
|
|
3900
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN trusted_run_context TEXT");
|
|
3901
|
+
}
|
|
3902
|
+
if (!names.has("execution_mode")) {
|
|
3903
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup')");
|
|
3904
|
+
}
|
|
3120
3905
|
}
|
|
3121
3906
|
async addMessage(params) {
|
|
3907
|
+
const trusted = validateQueueTrust(params);
|
|
3122
3908
|
const now = nowISO();
|
|
3123
3909
|
const id = params.id || randomUUID4();
|
|
3124
3910
|
const seqRow = this.db.prepare(
|
|
@@ -3126,94 +3912,163 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3126
3912
|
).get(params.threadId);
|
|
3127
3913
|
const nextSeq = seqRow.next_seq;
|
|
3128
3914
|
this.db.prepare(
|
|
3129
|
-
`INSERT INTO lt_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, created_at)
|
|
3130
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3915
|
+
`INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
|
|
3916
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3131
3917
|
).run(
|
|
3132
3918
|
id,
|
|
3133
3919
|
params.threadId,
|
|
3134
3920
|
params.tenantId,
|
|
3135
3921
|
params.assistantId,
|
|
3136
|
-
params.workspaceId
|
|
3137
|
-
params.projectId
|
|
3922
|
+
params.workspaceId ?? null,
|
|
3923
|
+
params.projectId ?? null,
|
|
3138
3924
|
JSON.stringify(params.content),
|
|
3139
3925
|
params.type || "human",
|
|
3140
3926
|
nextSeq,
|
|
3141
|
-
params.priority
|
|
3927
|
+
params.priority ?? 0,
|
|
3142
3928
|
params.command ? JSON.stringify(params.command) : null,
|
|
3143
3929
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3930
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
3931
|
+
trusted.executionMode ?? null,
|
|
3144
3932
|
now
|
|
3145
3933
|
);
|
|
3146
3934
|
return this.getById(id);
|
|
3147
3935
|
}
|
|
3936
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
3937
|
+
const trusted = validateQueueTrust(params);
|
|
3938
|
+
let release;
|
|
3939
|
+
const previous = this.capacityTail;
|
|
3940
|
+
this.capacityTail = new Promise((resolve) => {
|
|
3941
|
+
release = resolve;
|
|
3942
|
+
});
|
|
3943
|
+
await previous;
|
|
3944
|
+
try {
|
|
3945
|
+
const scope = {
|
|
3946
|
+
tenantId: params.tenantId,
|
|
3947
|
+
assistantId: params.assistantId,
|
|
3948
|
+
workspaceId: params.workspaceId,
|
|
3949
|
+
projectId: params.projectId
|
|
3950
|
+
};
|
|
3951
|
+
const filter = scopeClause(scope);
|
|
3952
|
+
const id = params.id || randomUUID4();
|
|
3953
|
+
const limit = maxSize === Infinity ? -1 : maxSize;
|
|
3954
|
+
const result = this.db.prepare(
|
|
3955
|
+
`INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
|
|
3956
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?,
|
|
3957
|
+
(SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
|
|
3958
|
+
?, ?, ?, ?, ?,
|
|
3959
|
+
?
|
|
3960
|
+
WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
|
|
3961
|
+
).run(
|
|
3962
|
+
id,
|
|
3963
|
+
params.threadId,
|
|
3964
|
+
params.tenantId,
|
|
3965
|
+
params.assistantId,
|
|
3966
|
+
params.workspaceId ?? null,
|
|
3967
|
+
params.projectId ?? null,
|
|
3968
|
+
JSON.stringify(params.content),
|
|
3969
|
+
params.type || "human",
|
|
3970
|
+
params.threadId,
|
|
3971
|
+
params.priority ?? 0,
|
|
3972
|
+
params.command ? JSON.stringify(params.command) : null,
|
|
3973
|
+
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3974
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
3975
|
+
trusted.executionMode ?? null,
|
|
3976
|
+
nowISO(),
|
|
3977
|
+
limit,
|
|
3978
|
+
params.threadId,
|
|
3979
|
+
...filter.params,
|
|
3980
|
+
limit
|
|
3981
|
+
);
|
|
3982
|
+
return result.changes > 0;
|
|
3983
|
+
} finally {
|
|
3984
|
+
release();
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3148
3987
|
async addMessageAtHead(params) {
|
|
3988
|
+
const trusted = validateQueueTrust(params);
|
|
3149
3989
|
const now = nowISO();
|
|
3150
3990
|
const id = params.id || randomUUID4();
|
|
3151
3991
|
const seqRow = this.db.prepare(
|
|
3152
3992
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lt_thread_message_queue WHERE thread_id = ?`
|
|
3153
3993
|
).get(params.threadId);
|
|
3154
3994
|
this.db.prepare(
|
|
3155
|
-
`INSERT INTO lt_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, created_at)
|
|
3156
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?)`
|
|
3995
|
+
`INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
|
|
3996
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?, ?, ?)`
|
|
3157
3997
|
).run(
|
|
3158
3998
|
id,
|
|
3159
3999
|
params.threadId,
|
|
3160
4000
|
params.tenantId,
|
|
3161
4001
|
params.assistantId,
|
|
3162
|
-
params.workspaceId
|
|
3163
|
-
params.projectId
|
|
4002
|
+
params.workspaceId ?? null,
|
|
4003
|
+
params.projectId ?? null,
|
|
3164
4004
|
JSON.stringify(params.content),
|
|
3165
4005
|
params.type || "human",
|
|
3166
4006
|
seqRow.next_seq,
|
|
3167
4007
|
params.command ? JSON.stringify(params.command) : null,
|
|
3168
4008
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4009
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4010
|
+
trusted.executionMode ?? null,
|
|
3169
4011
|
now
|
|
3170
4012
|
);
|
|
3171
4013
|
return this.getById(id);
|
|
3172
4014
|
}
|
|
3173
|
-
async getPendingMessages(threadId) {
|
|
3174
|
-
const
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
4015
|
+
async getPendingMessages(threadId, scope) {
|
|
4016
|
+
const filter = scopeClause(scope);
|
|
4017
|
+
const rows2 = this.db.prepare(
|
|
4018
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
4019
|
+
).all(threadId, ...filter.params);
|
|
4020
|
+
return rows2.map(rowToMessage);
|
|
4021
|
+
}
|
|
4022
|
+
async getProcessingMessages(threadId, scope) {
|
|
4023
|
+
const filter = scopeClause(scope);
|
|
4024
|
+
const rows2 = this.db.prepare(
|
|
4025
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
4026
|
+
).all(threadId, ...filter.params);
|
|
4027
|
+
return rows2.map(rowToMessage);
|
|
4028
|
+
}
|
|
4029
|
+
async getQueueSize(threadId, scope) {
|
|
4030
|
+
const filter = scopeClause(scope);
|
|
3186
4031
|
const row = this.db.prepare(
|
|
3187
|
-
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'`
|
|
3188
|
-
).get(threadId);
|
|
4032
|
+
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}`
|
|
4033
|
+
).get(threadId, ...filter.params);
|
|
3189
4034
|
return row.count;
|
|
3190
4035
|
}
|
|
3191
4036
|
async getThreadsWithPendingMessages() {
|
|
3192
|
-
const
|
|
3193
|
-
`SELECT
|
|
4037
|
+
const rows2 = this.db.prepare(
|
|
4038
|
+
`SELECT tenant_id, assistant_id, thread_id, workspace_id, project_id FROM lt_thread_message_queue WHERE status IN ('pending', 'processing') GROUP BY tenant_id, assistant_id, thread_id, workspace_id, project_id ORDER BY thread_id`
|
|
3194
4039
|
).all();
|
|
3195
|
-
return
|
|
4040
|
+
return rows2.map((r) => ({
|
|
3196
4041
|
tenantId: r.tenant_id,
|
|
3197
4042
|
assistantId: r.assistant_id,
|
|
3198
4043
|
threadId: r.thread_id,
|
|
3199
|
-
workspaceId: r.workspace_id
|
|
3200
|
-
projectId: r.project_id
|
|
4044
|
+
workspaceId: r.workspace_id,
|
|
4045
|
+
projectId: r.project_id
|
|
3201
4046
|
}));
|
|
3202
4047
|
}
|
|
3203
|
-
async removeMessage(messageId) {
|
|
3204
|
-
const
|
|
4048
|
+
async removeMessage(messageId, scope) {
|
|
4049
|
+
const filter = scopeClause(scope);
|
|
4050
|
+
const result = this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
|
|
3205
4051
|
return result.changes > 0;
|
|
3206
4052
|
}
|
|
3207
|
-
async clearMessages(threadId) {
|
|
3208
|
-
|
|
4053
|
+
async clearMessages(threadId, scope) {
|
|
4054
|
+
const filter = scopeClause(scope);
|
|
4055
|
+
this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE thread_id = ?${filter.sql}`).run(threadId, ...filter.params);
|
|
3209
4056
|
}
|
|
3210
|
-
async markProcessing(messageId) {
|
|
3211
|
-
|
|
4057
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
4058
|
+
const filter = scopeClause(scope);
|
|
4059
|
+
if (customRunConfig === void 0) {
|
|
4060
|
+
this.db.prepare(`UPDATE lt_thread_message_queue SET status = 'processing' WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
|
|
4061
|
+
return;
|
|
4062
|
+
}
|
|
4063
|
+
this.db.prepare(
|
|
4064
|
+
`UPDATE lt_thread_message_queue SET status = 'processing', custom_run_config = ? WHERE id = ?${filter.sql}`
|
|
4065
|
+
).run(JSON.stringify(customRunConfig), messageId, ...filter.params);
|
|
3212
4066
|
}
|
|
3213
|
-
async resetProcessingToPending(threadId) {
|
|
4067
|
+
async resetProcessingToPending(threadId, scope) {
|
|
4068
|
+
const filter = scopeClause(scope);
|
|
3214
4069
|
const result = this.db.prepare(
|
|
3215
|
-
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'`
|
|
3216
|
-
).run(threadId);
|
|
4070
|
+
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
|
|
4071
|
+
).run(threadId, ...filter.params);
|
|
3217
4072
|
return result.changes;
|
|
3218
4073
|
}
|
|
3219
4074
|
getById(id) {
|
|
@@ -3221,6 +4076,20 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3221
4076
|
return rowToMessage(row);
|
|
3222
4077
|
}
|
|
3223
4078
|
};
|
|
4079
|
+
function validateQueueTrust(params) {
|
|
4080
|
+
return {
|
|
4081
|
+
trustedRunContext: params.trusted_run_context === void 0 ? void 0 : parseTrustedRunContext(params.trusted_run_context),
|
|
4082
|
+
executionMode: params.execution_mode === void 0 ? void 0 : parseQueuedExecutionMode(params.execution_mode)
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
4085
|
+
function scopeClause(scope) {
|
|
4086
|
+
if (!scope) return { sql: "", params: [] };
|
|
4087
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
4088
|
+
return {
|
|
4089
|
+
sql: entries.map(([key, value]) => ` AND ${key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)} ${value == null ? "IS NULL" : "= ?"}`).join(""),
|
|
4090
|
+
params: entries.flatMap(([, value]) => value == null ? [] : [value])
|
|
4091
|
+
};
|
|
4092
|
+
}
|
|
3224
4093
|
function rowToMessage(row) {
|
|
3225
4094
|
return {
|
|
3226
4095
|
id: row.id,
|
|
@@ -3230,7 +4099,9 @@ function rowToMessage(row) {
|
|
|
3230
4099
|
createdAt: parseISO(row.created_at),
|
|
3231
4100
|
priority: row.priority || 0,
|
|
3232
4101
|
command: row.command ? JSON.parse(row.command) : void 0,
|
|
3233
|
-
custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0
|
|
4102
|
+
custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0,
|
|
4103
|
+
trusted_run_context: row.trusted_run_context ? parseTrustedRunContext(JSON.parse(row.trusted_run_context)) : void 0,
|
|
4104
|
+
execution_mode: row.execution_mode == null ? void 0 : parseQueuedExecutionMode(row.execution_mode)
|
|
3234
4105
|
};
|
|
3235
4106
|
}
|
|
3236
4107
|
|
|
@@ -3257,10 +4128,10 @@ var LocalSkillStore = class {
|
|
|
3257
4128
|
ensureTable(db, DDL18);
|
|
3258
4129
|
}
|
|
3259
4130
|
async getAllSkills(tenantId, _context) {
|
|
3260
|
-
const
|
|
4131
|
+
const rows2 = this.db.prepare(
|
|
3261
4132
|
`SELECT * FROM lt_skills WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
3262
4133
|
).all(tenantId);
|
|
3263
|
-
return
|
|
4134
|
+
return rows2.map(mapRowToSkill);
|
|
3264
4135
|
}
|
|
3265
4136
|
async getSkillById(tenantId, id, _context) {
|
|
3266
4137
|
const row = this.db.prepare(
|
|
@@ -3365,16 +4236,16 @@ var LocalSkillStore = class {
|
|
|
3365
4236
|
return all.filter((s) => s.metadata?.[metadataKey] === metadataValue);
|
|
3366
4237
|
}
|
|
3367
4238
|
async filterByCompatibility(tenantId, compatibility, _context) {
|
|
3368
|
-
const
|
|
4239
|
+
const rows2 = this.db.prepare(
|
|
3369
4240
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND compatibility = ? ORDER BY created_at DESC`
|
|
3370
4241
|
).all(tenantId, compatibility);
|
|
3371
|
-
return
|
|
4242
|
+
return rows2.map(mapRowToSkill);
|
|
3372
4243
|
}
|
|
3373
4244
|
async filterByLicense(tenantId, license, _context) {
|
|
3374
|
-
const
|
|
4245
|
+
const rows2 = this.db.prepare(
|
|
3375
4246
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND license = ? ORDER BY created_at DESC`
|
|
3376
4247
|
).all(tenantId, license);
|
|
3377
|
-
return
|
|
4248
|
+
return rows2.map(mapRowToSkill);
|
|
3378
4249
|
}
|
|
3379
4250
|
async getSubSkills(tenantId, parentSkillName, _context) {
|
|
3380
4251
|
const all = await this.getAllSkills(tenantId);
|
|
@@ -3586,40 +4457,40 @@ var LocalScheduleStorage = class {
|
|
|
3586
4457
|
this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
|
|
3587
4458
|
}
|
|
3588
4459
|
async getActiveTasks() {
|
|
3589
|
-
const
|
|
4460
|
+
const rows2 = this.db.prepare(
|
|
3590
4461
|
`SELECT * FROM lt_scheduled_tasks WHERE status IN ('pending', 'paused') ORDER BY created_at ASC`
|
|
3591
4462
|
).all();
|
|
3592
|
-
return
|
|
4463
|
+
return rows2.map(mapRowToTask);
|
|
3593
4464
|
}
|
|
3594
4465
|
async getTasksByType(taskType) {
|
|
3595
|
-
const
|
|
4466
|
+
const rows2 = this.db.prepare(
|
|
3596
4467
|
`SELECT * FROM lt_scheduled_tasks WHERE task_type = ? ORDER BY created_at DESC`
|
|
3597
4468
|
).all(taskType);
|
|
3598
|
-
return
|
|
4469
|
+
return rows2.map(mapRowToTask);
|
|
3599
4470
|
}
|
|
3600
4471
|
async getTasksByStatus(status) {
|
|
3601
|
-
const
|
|
4472
|
+
const rows2 = this.db.prepare(
|
|
3602
4473
|
`SELECT * FROM lt_scheduled_tasks WHERE status = ? ORDER BY created_at DESC`
|
|
3603
4474
|
).all(status);
|
|
3604
|
-
return
|
|
4475
|
+
return rows2.map(mapRowToTask);
|
|
3605
4476
|
}
|
|
3606
4477
|
async getTasksByExecutionType(executionType) {
|
|
3607
|
-
const
|
|
4478
|
+
const rows2 = this.db.prepare(
|
|
3608
4479
|
`SELECT * FROM lt_scheduled_tasks WHERE execution_type = ? ORDER BY created_at DESC`
|
|
3609
4480
|
).all(executionType);
|
|
3610
|
-
return
|
|
4481
|
+
return rows2.map(mapRowToTask);
|
|
3611
4482
|
}
|
|
3612
4483
|
async getTasksByAssistantId(assistantId) {
|
|
3613
|
-
const
|
|
4484
|
+
const rows2 = this.db.prepare(
|
|
3614
4485
|
`SELECT * FROM lt_scheduled_tasks WHERE assistant_id = ? ORDER BY created_at DESC`
|
|
3615
4486
|
).all(assistantId);
|
|
3616
|
-
return
|
|
4487
|
+
return rows2.map(mapRowToTask);
|
|
3617
4488
|
}
|
|
3618
4489
|
async getTasksByThreadId(threadId) {
|
|
3619
|
-
const
|
|
4490
|
+
const rows2 = this.db.prepare(
|
|
3620
4491
|
`SELECT * FROM lt_scheduled_tasks WHERE thread_id = ? ORDER BY created_at DESC`
|
|
3621
4492
|
).all(threadId);
|
|
3622
|
-
return
|
|
4493
|
+
return rows2.map(mapRowToTask);
|
|
3623
4494
|
}
|
|
3624
4495
|
async getAllTasks(filters) {
|
|
3625
4496
|
const conditions = [];
|
|
@@ -3659,8 +4530,8 @@ var LocalScheduleStorage = class {
|
|
|
3659
4530
|
query += ` OFFSET ?`;
|
|
3660
4531
|
values.push(filters.offset);
|
|
3661
4532
|
}
|
|
3662
|
-
const
|
|
3663
|
-
return
|
|
4533
|
+
const rows2 = this.db.prepare(query).all(...values);
|
|
4534
|
+
return rows2.map(mapRowToTask);
|
|
3664
4535
|
}
|
|
3665
4536
|
async countTasks(filters) {
|
|
3666
4537
|
const conditions = [];
|
|
@@ -3733,6 +4604,15 @@ function mapRowToTask(row) {
|
|
|
3733
4604
|
// src/stores/LocalTaskStore.ts
|
|
3734
4605
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3735
4606
|
var MAX_CANONICAL_TASK_TIME = "9999-12-31T23:59:59.999Z";
|
|
4607
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
4608
|
+
"pending",
|
|
4609
|
+
"in_progress",
|
|
4610
|
+
"review",
|
|
4611
|
+
"failed",
|
|
4612
|
+
"interrupted",
|
|
4613
|
+
"completed",
|
|
4614
|
+
"cancelled"
|
|
4615
|
+
]);
|
|
3736
4616
|
var NEXT_UPDATED_AT_SQL = `updated_at = CASE
|
|
3737
4617
|
WHEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) = updated_at AND updated_at >= ?
|
|
3738
4618
|
THEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds')
|
|
@@ -3805,6 +4685,9 @@ function nextISO(previous) {
|
|
|
3805
4685
|
const previousMs = new Date(previous).getTime();
|
|
3806
4686
|
return new Date(Math.max(Date.now(), previousMs + 1)).toISOString();
|
|
3807
4687
|
}
|
|
4688
|
+
function assertPage(limit, offset = 0) {
|
|
4689
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0) throw new RangeError("Invalid task page");
|
|
4690
|
+
}
|
|
3808
4691
|
var LocalTaskStore = class {
|
|
3809
4692
|
constructor(db) {
|
|
3810
4693
|
this.db = db;
|
|
@@ -3818,10 +4701,10 @@ var LocalTaskStore = class {
|
|
|
3818
4701
|
this.ensureColumn("lt_tasks", "files", "files TEXT");
|
|
3819
4702
|
}
|
|
3820
4703
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
3821
|
-
ensureColumn(table, column,
|
|
4704
|
+
ensureColumn(table, column, ddl2) {
|
|
3822
4705
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
3823
4706
|
if (!cols.some((c) => c.name === column)) {
|
|
3824
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
4707
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
3825
4708
|
}
|
|
3826
4709
|
}
|
|
3827
4710
|
async create(params) {
|
|
@@ -3887,7 +4770,8 @@ var LocalTaskStore = class {
|
|
|
3887
4770
|
conditions.push("workspace_id = ?");
|
|
3888
4771
|
params.push(filter.workspaceId);
|
|
3889
4772
|
}
|
|
3890
|
-
if (filter.projectId)
|
|
4773
|
+
if (filter.projectId === null) conditions.push("(project_id IS NULL OR project_id = '' OR project_id = 'default')");
|
|
4774
|
+
else if (filter.projectId !== void 0) {
|
|
3891
4775
|
conditions.push("project_id = ?");
|
|
3892
4776
|
params.push(filter.projectId);
|
|
3893
4777
|
}
|
|
@@ -3917,10 +4801,44 @@ var LocalTaskStore = class {
|
|
|
3917
4801
|
const where = conditions.join(" AND ");
|
|
3918
4802
|
const limit = filter.limit || 100;
|
|
3919
4803
|
const offset = filter.offset || 0;
|
|
3920
|
-
const
|
|
3921
|
-
`SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
4804
|
+
const rows2 = this.db.prepare(
|
|
4805
|
+
`SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`
|
|
3922
4806
|
).all(...params, limit, offset);
|
|
3923
|
-
return
|
|
4807
|
+
return rows2.map(mapRowToTask2);
|
|
4808
|
+
}
|
|
4809
|
+
/** Lists exact project tasks containing a string dependency. */
|
|
4810
|
+
async listDependents(query) {
|
|
4811
|
+
assertPage(query.limit, query.offset);
|
|
4812
|
+
if (query.statuses.length === 0 || query.statuses.some((status) => !TASK_STATUSES.has(status))) {
|
|
4813
|
+
throw new RangeError("Invalid task statuses");
|
|
4814
|
+
}
|
|
4815
|
+
const rows2 = this.db.prepare(
|
|
4816
|
+
`SELECT task.* FROM lt_tasks AS task
|
|
4817
|
+
WHERE task.tenant_id = ? AND task.workspace_id = ? AND task.project_id = ?
|
|
4818
|
+
AND task.status IN (${query.statuses.map(() => "?").join(", ")})
|
|
4819
|
+
AND task.dependencies IS NOT NULL
|
|
4820
|
+
AND json_valid(task.dependencies) = 1
|
|
4821
|
+
AND json_type(CASE WHEN json_valid(task.dependencies)=1
|
|
4822
|
+
THEN task.dependencies ELSE '[]' END) = 'array'
|
|
4823
|
+
AND EXISTS (
|
|
4824
|
+
SELECT 1 FROM json_each(
|
|
4825
|
+
CASE WHEN json_valid(task.dependencies)=1 THEN
|
|
4826
|
+
CASE WHEN json_type(task.dependencies)='array' THEN task.dependencies ELSE '[]' END
|
|
4827
|
+
ELSE '[]' END
|
|
4828
|
+
) AS dependency
|
|
4829
|
+
WHERE dependency.type = 'text' AND dependency.value = ?
|
|
4830
|
+
)
|
|
4831
|
+
ORDER BY task.created_at DESC, task.id DESC LIMIT ? OFFSET ?`
|
|
4832
|
+
).all(
|
|
4833
|
+
query.tenantId,
|
|
4834
|
+
query.workspaceId,
|
|
4835
|
+
query.projectId,
|
|
4836
|
+
...query.statuses,
|
|
4837
|
+
query.dependencyTaskId,
|
|
4838
|
+
query.limit,
|
|
4839
|
+
query.offset
|
|
4840
|
+
);
|
|
4841
|
+
return rows2.map(mapRowToTask2);
|
|
3924
4842
|
}
|
|
3925
4843
|
async update(tenantId, id, updates) {
|
|
3926
4844
|
const existing = await this.getById(tenantId, id);
|
|
@@ -4272,6 +5190,54 @@ var LocalTaskStore = class {
|
|
|
4272
5190
|
if (result.changes === 0) return null;
|
|
4273
5191
|
return this.getById(tenantId, id);
|
|
4274
5192
|
}
|
|
5193
|
+
/** Atomically update only while status, timestamp, owner, and Project scope match. */
|
|
5194
|
+
async updateIfSnapshot(tenantId, id, updates, snapshot) {
|
|
5195
|
+
const expectedIso = new Date(snapshot.updatedAt).toISOString();
|
|
5196
|
+
if (expectedIso === MAX_CANONICAL_TASK_TIME) return null;
|
|
5197
|
+
const setClauses = ["updated_at = ?"];
|
|
5198
|
+
const values = [nextISO(expectedIso)];
|
|
5199
|
+
const fields = [
|
|
5200
|
+
["title", "title"],
|
|
5201
|
+
["description", "description"],
|
|
5202
|
+
["status", "status"],
|
|
5203
|
+
["priority", "priority"],
|
|
5204
|
+
["dueDate", "due_date"],
|
|
5205
|
+
["metadata", "metadata", true],
|
|
5206
|
+
["parentId", "parent_id"],
|
|
5207
|
+
["sourceId", "source_id"],
|
|
5208
|
+
["context", "context", true],
|
|
5209
|
+
["ownerType", "owner_type"],
|
|
5210
|
+
["ownerId", "owner_id"],
|
|
5211
|
+
["requireReview", "require_review"],
|
|
5212
|
+
["dependencies", "dependencies", true],
|
|
5213
|
+
["result", "result"],
|
|
5214
|
+
["failureReason", "failure_reason"],
|
|
5215
|
+
["workspaceId", "workspace_id"],
|
|
5216
|
+
["projectId", "project_id"],
|
|
5217
|
+
["files", "files", true]
|
|
5218
|
+
];
|
|
5219
|
+
for (const [field, column, json] of fields) {
|
|
5220
|
+
const value = updates[field];
|
|
5221
|
+
if (value === void 0) continue;
|
|
5222
|
+
setClauses.push(`${column} = ?`);
|
|
5223
|
+
values.push(json && value !== null ? JSON.stringify(value) : field === "requireReview" ? value ? 1 : 0 : value);
|
|
5224
|
+
}
|
|
5225
|
+
values.push(
|
|
5226
|
+
tenantId,
|
|
5227
|
+
id,
|
|
5228
|
+
snapshot.status,
|
|
5229
|
+
expectedIso,
|
|
5230
|
+
snapshot.ownerType,
|
|
5231
|
+
snapshot.ownerId,
|
|
5232
|
+
snapshot.workspaceId,
|
|
5233
|
+
snapshot.projectId
|
|
5234
|
+
);
|
|
5235
|
+
const result = this.db.prepare(
|
|
5236
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND status = ?
|
|
5237
|
+
AND updated_at = ? AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
|
|
5238
|
+
).run(...values);
|
|
5239
|
+
return result.changes === 0 ? null : this.getById(tenantId, id);
|
|
5240
|
+
}
|
|
4275
5241
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
4276
5242
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
4277
5243
|
if (expectedStatuses.length === 0) return null;
|
|
@@ -4372,10 +5338,36 @@ var LocalTaskStore = class {
|
|
|
4372
5338
|
).run(tenantId, id);
|
|
4373
5339
|
return result.changes > 0;
|
|
4374
5340
|
}
|
|
5341
|
+
/** Atomically delete only while status, timestamp, owner, and Project scope match. */
|
|
5342
|
+
async deleteIfSnapshot(tenantId, id, snapshot) {
|
|
5343
|
+
const result = this.db.prepare(
|
|
5344
|
+
`DELETE FROM lt_tasks WHERE tenant_id = ? AND id = ? AND status = ? AND updated_at = ?
|
|
5345
|
+
AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
|
|
5346
|
+
).run(
|
|
5347
|
+
tenantId,
|
|
5348
|
+
id,
|
|
5349
|
+
snapshot.status,
|
|
5350
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
5351
|
+
snapshot.ownerType,
|
|
5352
|
+
snapshot.ownerId,
|
|
5353
|
+
snapshot.workspaceId,
|
|
5354
|
+
snapshot.projectId
|
|
5355
|
+
);
|
|
5356
|
+
return result.changes > 0;
|
|
5357
|
+
}
|
|
4375
5358
|
};
|
|
4376
5359
|
|
|
4377
5360
|
// src/stores/LocalTaskWorkItemStore.ts
|
|
5361
|
+
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
4378
5362
|
import { v4 as uuid2 } from "uuid";
|
|
5363
|
+
var PROJECT_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
5364
|
+
"in_progress",
|
|
5365
|
+
"interrupted",
|
|
5366
|
+
"failed",
|
|
5367
|
+
"completed",
|
|
5368
|
+
"cancelled",
|
|
5369
|
+
"reassigned"
|
|
5370
|
+
]);
|
|
4379
5371
|
var DDL21 = `
|
|
4380
5372
|
CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
4381
5373
|
id TEXT NOT NULL,
|
|
@@ -4393,6 +5385,15 @@ CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
|
4393
5385
|
PRIMARY KEY (tenant_id, id)
|
|
4394
5386
|
);
|
|
4395
5387
|
`;
|
|
5388
|
+
function parseDetail(raw) {
|
|
5389
|
+
if (!raw) return void 0;
|
|
5390
|
+
try {
|
|
5391
|
+
const parsed = JSON.parse(raw);
|
|
5392
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
5393
|
+
} catch {
|
|
5394
|
+
return void 0;
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
4396
5397
|
function mapRowToWorkItem(row) {
|
|
4397
5398
|
return {
|
|
4398
5399
|
id: row.id,
|
|
@@ -4402,7 +5403,7 @@ function mapRowToWorkItem(row) {
|
|
|
4402
5403
|
actor: row.actor,
|
|
4403
5404
|
threadId: row.thread_id ?? void 0,
|
|
4404
5405
|
summary: row.summary ?? void 0,
|
|
4405
|
-
detail:
|
|
5406
|
+
detail: parseDetail(row.detail),
|
|
4406
5407
|
attempt: row.attempt ?? void 0,
|
|
4407
5408
|
workspaceId: row.workspace_id ?? void 0,
|
|
4408
5409
|
projectId: row.project_id ?? void 0,
|
|
@@ -4410,6 +5411,34 @@ function mapRowToWorkItem(row) {
|
|
|
4410
5411
|
createdAt: parseISO(row.created_at)
|
|
4411
5412
|
};
|
|
4412
5413
|
}
|
|
5414
|
+
function assertAndCloneProjectLifecycleEvent(item) {
|
|
5415
|
+
let milliseconds;
|
|
5416
|
+
try {
|
|
5417
|
+
milliseconds = Date.prototype.getTime.call(item.createdAt);
|
|
5418
|
+
} catch {
|
|
5419
|
+
throw new Error("Invalid project lifecycle event row");
|
|
5420
|
+
}
|
|
5421
|
+
if (!Number.isFinite(milliseconds) || typeof item.id !== "string" || item.id.length === 0 || typeof item.eventKey !== "string" || item.eventKey.length === 0) {
|
|
5422
|
+
throw new Error("Invalid project lifecycle event row");
|
|
5423
|
+
}
|
|
5424
|
+
return { ...item, createdAt: new Date(milliseconds) };
|
|
5425
|
+
}
|
|
5426
|
+
function assertLifecyclePage(query) {
|
|
5427
|
+
if (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100 || query.actions.length === 0 || query.actions.some((action) => !PROJECT_LIFECYCLE_ACTIONS.has(action))) {
|
|
5428
|
+
throw new RangeError("Invalid project lifecycle event page");
|
|
5429
|
+
}
|
|
5430
|
+
if (!query.before) return null;
|
|
5431
|
+
let milliseconds;
|
|
5432
|
+
try {
|
|
5433
|
+
milliseconds = Date.prototype.getTime.call(query.before.createdAt);
|
|
5434
|
+
} catch {
|
|
5435
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
5436
|
+
}
|
|
5437
|
+
if (!Number.isFinite(milliseconds) || typeof query.before.id !== "string" || query.before.id.length === 0) {
|
|
5438
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
5439
|
+
}
|
|
5440
|
+
return milliseconds;
|
|
5441
|
+
}
|
|
4413
5442
|
var LocalTaskWorkItemStore = class {
|
|
4414
5443
|
constructor(db) {
|
|
4415
5444
|
this.db = db;
|
|
@@ -4419,12 +5448,20 @@ var LocalTaskWorkItemStore = class {
|
|
|
4419
5448
|
this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
|
|
4420
5449
|
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
4421
5450
|
ON lt_task_work_items (tenant_id, task_id, event_key);`);
|
|
5451
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
5452
|
+
ON lt_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC);`);
|
|
5453
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
5454
|
+
ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
|
|
5455
|
+
WHERE action = 'execution_reconciled';`);
|
|
5456
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_project_lifecycle
|
|
5457
|
+
ON lt_task_work_items (tenant_id, workspace_id, project_id, created_at DESC, id DESC)
|
|
5458
|
+
WHERE event_key IS NOT NULL;`);
|
|
4422
5459
|
}
|
|
4423
5460
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4424
|
-
ensureColumn(table, column,
|
|
5461
|
+
ensureColumn(table, column, ddl2) {
|
|
4425
5462
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
4426
5463
|
if (!cols.some((c) => c.name === column)) {
|
|
4427
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
5464
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
4428
5465
|
}
|
|
4429
5466
|
}
|
|
4430
5467
|
async create(params) {
|
|
@@ -4452,6 +5489,41 @@ var LocalTaskWorkItemStore = class {
|
|
|
4452
5489
|
).get(params.tenantId, id);
|
|
4453
5490
|
return mapRowToWorkItem(row);
|
|
4454
5491
|
}
|
|
5492
|
+
/** Atomically inserts a work item by selecting one exact task snapshot. */
|
|
5493
|
+
async createIfTaskSnapshot(params, snapshot) {
|
|
5494
|
+
const id = uuid2();
|
|
5495
|
+
const result = this.db.prepare(
|
|
5496
|
+
`INSERT INTO lt_task_work_items
|
|
5497
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
5498
|
+
SELECT ?, task.tenant_id, task.id, ?, ?, ?, ?, ?, ?, task.workspace_id, task.project_id, ?
|
|
5499
|
+
FROM lt_tasks AS task
|
|
5500
|
+
WHERE task.tenant_id = ? AND task.id = ? AND task.status = ? AND task.updated_at = ?
|
|
5501
|
+
AND task.owner_type = ? AND task.owner_id = ? AND task.workspace_id IS ? AND task.project_id IS ?
|
|
5502
|
+
AND ? IS task.workspace_id AND ? IS task.project_id`
|
|
5503
|
+
).run(
|
|
5504
|
+
id,
|
|
5505
|
+
params.action,
|
|
5506
|
+
params.actor,
|
|
5507
|
+
params.threadId || null,
|
|
5508
|
+
params.summary || null,
|
|
5509
|
+
params.detail ? JSON.stringify(params.detail) : null,
|
|
5510
|
+
params.attempt ?? null,
|
|
5511
|
+
nowISO(),
|
|
5512
|
+
params.tenantId,
|
|
5513
|
+
params.taskId,
|
|
5514
|
+
snapshot.status,
|
|
5515
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
5516
|
+
snapshot.ownerType,
|
|
5517
|
+
snapshot.ownerId,
|
|
5518
|
+
snapshot.workspaceId,
|
|
5519
|
+
snapshot.projectId,
|
|
5520
|
+
params.workspaceId,
|
|
5521
|
+
params.projectId
|
|
5522
|
+
);
|
|
5523
|
+
if (result.changes === 0) return null;
|
|
5524
|
+
const row = this.db.prepare(`SELECT * FROM lt_task_work_items WHERE tenant_id = ? AND id = ?`).get(params.tenantId, id);
|
|
5525
|
+
return row ? mapRowToWorkItem(row) : null;
|
|
5526
|
+
}
|
|
4455
5527
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
4456
5528
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
4457
5529
|
const row = this.db.prepare(
|
|
@@ -4504,10 +5576,160 @@ var LocalTaskWorkItemStore = class {
|
|
|
4504
5576
|
const limit = filter.limit || 100;
|
|
4505
5577
|
const offset = filter.offset || 0;
|
|
4506
5578
|
const order = filter.order === "asc" ? "ASC" : "DESC";
|
|
4507
|
-
const
|
|
5579
|
+
const rows2 = this.db.prepare(
|
|
4508
5580
|
`SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at ${order}, id ${order} LIMIT ? OFFSET ?`
|
|
4509
5581
|
).all(...params, limit, offset);
|
|
4510
|
-
return
|
|
5582
|
+
return rows2.map(mapRowToWorkItem);
|
|
5583
|
+
}
|
|
5584
|
+
/** List pending execution results using one bounded SQLite anti-join query. */
|
|
5585
|
+
async listPendingExecutionResults(params) {
|
|
5586
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
5587
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
5588
|
+
error.code = "INVALID_LIMIT";
|
|
5589
|
+
throw error;
|
|
5590
|
+
}
|
|
5591
|
+
if (params.limit === 0) return [];
|
|
5592
|
+
const rows2 = this.db.prepare(
|
|
5593
|
+
`SELECT result.*
|
|
5594
|
+
FROM lt_task_work_items AS result
|
|
5595
|
+
WHERE result.tenant_id = ?
|
|
5596
|
+
AND result.task_id = ?
|
|
5597
|
+
AND result.action = 'execution_result'
|
|
5598
|
+
AND substr(result.event_key, 1, 17) = 'execution-result:'
|
|
5599
|
+
AND length(result.event_key) > 17
|
|
5600
|
+
AND result.event_key NOT GLOB '*[^A-Za-z0-9._:-]*'
|
|
5601
|
+
AND NOT EXISTS (
|
|
5602
|
+
SELECT 1
|
|
5603
|
+
FROM lt_task_work_items AS reconciled
|
|
5604
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
5605
|
+
AND reconciled.task_id = result.task_id
|
|
5606
|
+
AND reconciled.action = 'execution_reconciled'
|
|
5607
|
+
AND json_extract(reconciled.detail, '$.executionResultId') = result.event_key
|
|
5608
|
+
)
|
|
5609
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
5610
|
+
LIMIT ?`
|
|
5611
|
+
).all(params.tenantId, params.taskId, params.limit);
|
|
5612
|
+
return rows2.map(mapRowToWorkItem);
|
|
5613
|
+
}
|
|
5614
|
+
/** Lists canonical project lifecycle events with an exclusive cursor. */
|
|
5615
|
+
async listProjectLifecycleEvents(query) {
|
|
5616
|
+
const cursorMilliseconds = assertLifecyclePage(query);
|
|
5617
|
+
const cursor = cursorMilliseconds === null ? null : new Date(cursorMilliseconds).toISOString();
|
|
5618
|
+
const rows2 = this.db.prepare(
|
|
5619
|
+
`SELECT * FROM lt_task_work_items
|
|
5620
|
+
WHERE tenant_id = ? AND workspace_id = ? AND project_id = ?
|
|
5621
|
+
AND action IN (${query.actions.map(() => "?").join(", ")})
|
|
5622
|
+
AND event_key IS NOT NULL AND event_key <> ''
|
|
5623
|
+
AND (? IS NULL OR created_at < ? OR (created_at = ? AND id < ?))
|
|
5624
|
+
ORDER BY created_at DESC, id DESC LIMIT ?`
|
|
5625
|
+
).all(
|
|
5626
|
+
query.tenantId,
|
|
5627
|
+
query.workspaceId,
|
|
5628
|
+
query.projectId,
|
|
5629
|
+
...query.actions,
|
|
5630
|
+
cursor,
|
|
5631
|
+
cursor,
|
|
5632
|
+
cursor,
|
|
5633
|
+
query.before?.id ?? null,
|
|
5634
|
+
query.limit
|
|
5635
|
+
);
|
|
5636
|
+
return rows2.map(mapRowToWorkItem).map(assertAndCloneProjectLifecycleEvent);
|
|
5637
|
+
}
|
|
5638
|
+
};
|
|
5639
|
+
|
|
5640
|
+
// src/stores/LocalCapabilityBundleStore.ts
|
|
5641
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5642
|
+
var DDL22 = `CREATE TABLE IF NOT EXISTS lt_capability_bundles (id TEXT NOT NULL, tenant_id TEXT NOT NULL, bundle_key TEXT NOT NULL, name TEXT NOT NULL, description TEXT, capabilities TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, bundle_key)); CREATE INDEX IF NOT EXISTS idx_lt_capability_bundles_tenant ON lt_capability_bundles(tenant_id);`;
|
|
5643
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
5644
|
+
function map(row) {
|
|
5645
|
+
return { id: row.id, tenantId: row.tenant_id, key: row.bundle_key, name: row.name, description: row.description ?? void 0, capabilities: JSON.parse(row.capabilities), createdAt: parseISO(row.created_at).toISOString(), updatedAt: parseISO(row.updated_at).toISOString() };
|
|
5646
|
+
}
|
|
5647
|
+
function isConstraint(error) {
|
|
5648
|
+
return error instanceof Error && /UNIQUE constraint failed/.test(error.message);
|
|
5649
|
+
}
|
|
5650
|
+
function nextTimestamp(previous) {
|
|
5651
|
+
const now = Date.now();
|
|
5652
|
+
const previousTime = previous ? Date.parse(previous) : Number.NaN;
|
|
5653
|
+
return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
|
|
5654
|
+
}
|
|
5655
|
+
var LocalCapabilityBundleStore = class {
|
|
5656
|
+
/**
|
|
5657
|
+
* Creates a capability bundle store over an initialized sql.js database.
|
|
5658
|
+
*
|
|
5659
|
+
* @param db - Shared local database wrapper used for persistence.
|
|
5660
|
+
*/
|
|
5661
|
+
constructor(db) {
|
|
5662
|
+
this.db = db;
|
|
5663
|
+
ensureTable(db, DDL22);
|
|
5664
|
+
}
|
|
5665
|
+
async listByTenant(tenantId) {
|
|
5666
|
+
return this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? ORDER BY created_at").all(tenantId).map(map);
|
|
5667
|
+
}
|
|
5668
|
+
async getById(tenantId, id) {
|
|
5669
|
+
const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
|
|
5670
|
+
return row ? map(row) : null;
|
|
5671
|
+
}
|
|
5672
|
+
async getManyByIds(tenantId, ids) {
|
|
5673
|
+
return ids.flatMap((id) => {
|
|
5674
|
+
const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
|
|
5675
|
+
return row ? [map(row)] : [];
|
|
5676
|
+
});
|
|
5677
|
+
}
|
|
5678
|
+
async create(tenantId, input) {
|
|
5679
|
+
const now = nextTimestamp();
|
|
5680
|
+
const id = randomUUID6();
|
|
5681
|
+
try {
|
|
5682
|
+
this.db.prepare("INSERT INTO lt_capability_bundles (id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(id, tenantId, input.key, input.name, input.description ?? null, JSON.stringify(input.capabilities), now, now);
|
|
5683
|
+
} catch (error) {
|
|
5684
|
+
if (isConstraint(error)) throw new Error(duplicateMessage);
|
|
5685
|
+
throw error;
|
|
5686
|
+
}
|
|
5687
|
+
return await this.getById(tenantId, id);
|
|
5688
|
+
}
|
|
5689
|
+
async update(tenantId, id, input) {
|
|
5690
|
+
const existing = await this.getById(tenantId, id);
|
|
5691
|
+
if (!existing) return null;
|
|
5692
|
+
if (input.expectedUpdatedAt !== void 0 && input.expectedUpdatedAt !== existing.updatedAt) return { status: "conflict" };
|
|
5693
|
+
const fields = [];
|
|
5694
|
+
const values = [];
|
|
5695
|
+
const add = (field, value) => {
|
|
5696
|
+
fields.push(`${field} = ?`);
|
|
5697
|
+
values.push(value);
|
|
5698
|
+
};
|
|
5699
|
+
if (input.name !== void 0) add("name", input.name);
|
|
5700
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) add("description", input.description ?? null);
|
|
5701
|
+
if (input.capabilities !== void 0) add("capabilities", JSON.stringify(input.capabilities));
|
|
5702
|
+
if (!fields.length) return existing;
|
|
5703
|
+
const now = nextTimestamp(existing.updatedAt);
|
|
5704
|
+
fields.push("updated_at = ?");
|
|
5705
|
+
values.push(now, tenantId, id);
|
|
5706
|
+
try {
|
|
5707
|
+
if (input.expectedUpdatedAt !== void 0) {
|
|
5708
|
+
const result = this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at = ?`).run(...values, input.expectedUpdatedAt);
|
|
5709
|
+
if (result.changes === 0) return { status: "conflict" };
|
|
5710
|
+
} else {
|
|
5711
|
+
this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ?`).run(...values);
|
|
5712
|
+
}
|
|
5713
|
+
} catch (error) {
|
|
5714
|
+
if (isConstraint(error)) throw new Error(duplicateMessage);
|
|
5715
|
+
throw error;
|
|
5716
|
+
}
|
|
5717
|
+
return this.getById(tenantId, id);
|
|
5718
|
+
}
|
|
5719
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
5720
|
+
const result = this.db.prepare(
|
|
5721
|
+
`DELETE FROM lt_capability_bundles
|
|
5722
|
+
WHERE tenant_id = ? AND id = ?
|
|
5723
|
+
AND NOT EXISTS (
|
|
5724
|
+
SELECT 1 FROM lt_projects
|
|
5725
|
+
WHERE tenant_id = ?
|
|
5726
|
+
AND json_type(config, '$.capabilityBundleIds') = 'array'
|
|
5727
|
+
AND EXISTS (SELECT 1 FROM json_each(config, '$.capabilityBundleIds') WHERE value = ?)
|
|
5728
|
+
)`
|
|
5729
|
+
).run(tenantId, id, tenantId, id);
|
|
5730
|
+
if (result.changes > 0) return "deleted";
|
|
5731
|
+
if (await this.getById(tenantId, id)) return "in_use";
|
|
5732
|
+
return "not_found";
|
|
4511
5733
|
}
|
|
4512
5734
|
};
|
|
4513
5735
|
|
|
@@ -4520,6 +5742,10 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4520
5742
|
assistant: new LocalAssistantStore(db),
|
|
4521
5743
|
workspace: new LocalWorkspaceStore(db),
|
|
4522
5744
|
project: new LocalProjectStore(db),
|
|
5745
|
+
projectRoom: new LocalProjectRoomStore(db),
|
|
5746
|
+
projectMembership: new LocalProjectMembershipStore(db),
|
|
5747
|
+
projectBotMembership: new LocalProjectBotMembershipStore(db),
|
|
5748
|
+
projectRoomMessage: new LocalProjectRoomMessageStore(db),
|
|
4523
5749
|
user: new LocalUserStore(db),
|
|
4524
5750
|
tenant: new LocalTenantStore(db),
|
|
4525
5751
|
userTenantLink: new LocalUserTenantLinkStore(db),
|
|
@@ -4537,6 +5763,7 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4537
5763
|
schedule: new LocalScheduleStorage(db),
|
|
4538
5764
|
task: new LocalTaskStore(db),
|
|
4539
5765
|
taskWorkItem: new LocalTaskWorkItemStore(db),
|
|
5766
|
+
capabilityBundle: new LocalCapabilityBundleStore(db),
|
|
4540
5767
|
checkpoint: SqliteSaver.fromConnString(dbPath)
|
|
4541
5768
|
};
|
|
4542
5769
|
}
|
|
@@ -4552,7 +5779,7 @@ var InMemoryConversationStore = class {
|
|
|
4552
5779
|
}
|
|
4553
5780
|
async createConversation(input) {
|
|
4554
5781
|
const now = isoNow();
|
|
4555
|
-
const
|
|
5782
|
+
const record2 = {
|
|
4556
5783
|
conversationId: input.conversationId,
|
|
4557
5784
|
tenantId: input.tenantId,
|
|
4558
5785
|
taskIds: [],
|
|
@@ -4561,17 +5788,17 @@ var InMemoryConversationStore = class {
|
|
|
4561
5788
|
createdAt: now,
|
|
4562
5789
|
updatedAt: now
|
|
4563
5790
|
};
|
|
4564
|
-
this.store.set(input.conversationId,
|
|
4565
|
-
return
|
|
5791
|
+
this.store.set(input.conversationId, record2);
|
|
5792
|
+
return record2;
|
|
4566
5793
|
}
|
|
4567
5794
|
async addTaskToConversation(conversationId, taskId) {
|
|
4568
|
-
const
|
|
4569
|
-
if (!
|
|
5795
|
+
const record2 = this.store.get(conversationId);
|
|
5796
|
+
if (!record2) {
|
|
4570
5797
|
throw new Error(`Conversation not found: ${conversationId}`);
|
|
4571
5798
|
}
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
return
|
|
5799
|
+
record2.taskIds.push(taskId);
|
|
5800
|
+
record2.updatedAt = isoNow();
|
|
5801
|
+
return record2;
|
|
4575
5802
|
}
|
|
4576
5803
|
async deleteConversation(conversationId) {
|
|
4577
5804
|
this.store.delete(conversationId);
|
|
@@ -4579,9 +5806,12 @@ var InMemoryConversationStore = class {
|
|
|
4579
5806
|
};
|
|
4580
5807
|
export {
|
|
4581
5808
|
DatabaseWrapper,
|
|
5809
|
+
DuplicateProjectMembershipError,
|
|
5810
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
4582
5811
|
InMemoryConversationStore,
|
|
4583
5812
|
LocalA2AApiKeyStore,
|
|
4584
5813
|
LocalAssistantStore,
|
|
5814
|
+
LocalCapabilityBundleStore,
|
|
4585
5815
|
LocalChannelBindingStore,
|
|
4586
5816
|
LocalChannelInstallationStore,
|
|
4587
5817
|
LocalConnectionStore,
|
|
@@ -4589,6 +5819,10 @@ export {
|
|
|
4589
5819
|
LocalEvalStore,
|
|
4590
5820
|
LocalMcpServerConfigStore,
|
|
4591
5821
|
LocalMetricsServerConfigStore,
|
|
5822
|
+
LocalProjectBotMembershipStore,
|
|
5823
|
+
LocalProjectMembershipStore,
|
|
5824
|
+
LocalProjectRoomMessageStore,
|
|
5825
|
+
LocalProjectRoomStore,
|
|
4592
5826
|
LocalProjectStore,
|
|
4593
5827
|
LocalScheduleStorage,
|
|
4594
5828
|
LocalSkillStore,
|
|
@@ -4602,6 +5836,9 @@ export {
|
|
|
4602
5836
|
LocalWorkflowTrackingStore,
|
|
4603
5837
|
LocalWorkspaceStore,
|
|
4604
5838
|
MigrationManager,
|
|
5839
|
+
ProjectBotMembershipIdConflictError,
|
|
5840
|
+
ProjectMembershipIdConflictError,
|
|
5841
|
+
ProjectRoomMessageIdConflictError,
|
|
4605
5842
|
RunResult,
|
|
4606
5843
|
StatementWrapper,
|
|
4607
5844
|
closeDatabase,
|