@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.js
CHANGED
|
@@ -31,9 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
DatabaseWrapper: () => DatabaseWrapper,
|
|
34
|
+
DuplicateProjectMembershipError: () => DuplicateProjectMembershipError,
|
|
35
|
+
DuplicateProjectRoomMessageIdempotencyKeyError: () => DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
34
36
|
InMemoryConversationStore: () => InMemoryConversationStore,
|
|
35
37
|
LocalA2AApiKeyStore: () => LocalA2AApiKeyStore,
|
|
36
38
|
LocalAssistantStore: () => LocalAssistantStore,
|
|
39
|
+
LocalCapabilityBundleStore: () => LocalCapabilityBundleStore,
|
|
37
40
|
LocalChannelBindingStore: () => LocalChannelBindingStore,
|
|
38
41
|
LocalChannelInstallationStore: () => LocalChannelInstallationStore,
|
|
39
42
|
LocalConnectionStore: () => LocalConnectionStore,
|
|
@@ -41,6 +44,10 @@ __export(index_exports, {
|
|
|
41
44
|
LocalEvalStore: () => LocalEvalStore,
|
|
42
45
|
LocalMcpServerConfigStore: () => LocalMcpServerConfigStore,
|
|
43
46
|
LocalMetricsServerConfigStore: () => LocalMetricsServerConfigStore,
|
|
47
|
+
LocalProjectBotMembershipStore: () => LocalProjectBotMembershipStore,
|
|
48
|
+
LocalProjectMembershipStore: () => LocalProjectMembershipStore,
|
|
49
|
+
LocalProjectRoomMessageStore: () => LocalProjectRoomMessageStore,
|
|
50
|
+
LocalProjectRoomStore: () => LocalProjectRoomStore,
|
|
44
51
|
LocalProjectStore: () => LocalProjectStore,
|
|
45
52
|
LocalScheduleStorage: () => LocalScheduleStorage,
|
|
46
53
|
LocalSkillStore: () => LocalSkillStore,
|
|
@@ -54,6 +61,9 @@ __export(index_exports, {
|
|
|
54
61
|
LocalWorkflowTrackingStore: () => LocalWorkflowTrackingStore,
|
|
55
62
|
LocalWorkspaceStore: () => LocalWorkspaceStore,
|
|
56
63
|
MigrationManager: () => MigrationManager,
|
|
64
|
+
ProjectBotMembershipIdConflictError: () => ProjectBotMembershipIdConflictError,
|
|
65
|
+
ProjectMembershipIdConflictError: () => ProjectMembershipIdConflictError,
|
|
66
|
+
ProjectRoomMessageIdConflictError: () => ProjectRoomMessageIdConflictError,
|
|
57
67
|
RunResult: () => RunResult,
|
|
58
68
|
StatementWrapper: () => StatementWrapper,
|
|
59
69
|
closeDatabase: () => closeDatabase,
|
|
@@ -149,11 +159,11 @@ var StatementWrapper = class {
|
|
|
149
159
|
if (params.length > 0) {
|
|
150
160
|
stmt.bind(params);
|
|
151
161
|
}
|
|
152
|
-
const
|
|
162
|
+
const rows2 = [];
|
|
153
163
|
while (stmt.step()) {
|
|
154
|
-
|
|
164
|
+
rows2.push(stmt.getAsObject());
|
|
155
165
|
}
|
|
156
|
-
return
|
|
166
|
+
return rows2;
|
|
157
167
|
} finally {
|
|
158
168
|
stmt.free();
|
|
159
169
|
}
|
|
@@ -228,8 +238,8 @@ function closeDatabase() {
|
|
|
228
238
|
_db = null;
|
|
229
239
|
}
|
|
230
240
|
}
|
|
231
|
-
function ensureTable(db,
|
|
232
|
-
db.exec(
|
|
241
|
+
function ensureTable(db, ddl2) {
|
|
242
|
+
db.exec(ddl2);
|
|
233
243
|
}
|
|
234
244
|
function nowISO() {
|
|
235
245
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -273,7 +283,10 @@ var MigrationManager = class {
|
|
|
273
283
|
this.db.run(
|
|
274
284
|
`INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at)
|
|
275
285
|
VALUES (?, ?, ?, ?)`,
|
|
276
|
-
|
|
286
|
+
this.storeName,
|
|
287
|
+
m.name,
|
|
288
|
+
m.version,
|
|
289
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
277
290
|
);
|
|
278
291
|
}
|
|
279
292
|
}
|
|
@@ -323,10 +336,10 @@ var MigrationManager = class {
|
|
|
323
336
|
`);
|
|
324
337
|
}
|
|
325
338
|
getAppliedNames() {
|
|
326
|
-
const
|
|
339
|
+
const rows2 = this.db.prepare(
|
|
327
340
|
`SELECT name FROM lattice_schema_migrations WHERE store_name = ? ORDER BY version`
|
|
328
341
|
).all(this.storeName);
|
|
329
|
-
return new Set(
|
|
342
|
+
return new Set(rows2.map((r) => r.name));
|
|
330
343
|
}
|
|
331
344
|
};
|
|
332
345
|
|
|
@@ -361,10 +374,10 @@ var LocalThreadStore = class {
|
|
|
361
374
|
mm.migrate();
|
|
362
375
|
}
|
|
363
376
|
async getThreadsByAssistantId(tenantId, assistantId, metadataFilter) {
|
|
364
|
-
const
|
|
377
|
+
const rows2 = this.db.prepare(
|
|
365
378
|
`SELECT * FROM lt_threads WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
366
379
|
).all(tenantId, assistantId);
|
|
367
|
-
let threads =
|
|
380
|
+
let threads = rows2.map(mapRowToThread);
|
|
368
381
|
if (metadataFilter && Object.keys(metadataFilter).length > 0) {
|
|
369
382
|
threads = threads.filter(
|
|
370
383
|
(t) => Object.entries(metadataFilter).every(
|
|
@@ -461,10 +474,10 @@ var LocalAssistantStore = class {
|
|
|
461
474
|
ensureTable(db, DDL2);
|
|
462
475
|
}
|
|
463
476
|
async getAllAssistants(tenantId) {
|
|
464
|
-
const
|
|
477
|
+
const rows2 = this.db.prepare(
|
|
465
478
|
`SELECT * FROM lt_assistants WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
466
479
|
).all(tenantId);
|
|
467
|
-
return
|
|
480
|
+
return rows2.map(mapRowToAssistant);
|
|
468
481
|
}
|
|
469
482
|
async getAssistantById(tenantId, id) {
|
|
470
483
|
const row = this.db.prepare(
|
|
@@ -577,10 +590,10 @@ var LocalWorkspaceStore = class {
|
|
|
577
590
|
ensureTable(db, DDL3);
|
|
578
591
|
}
|
|
579
592
|
async getAllWorkspaces(tenantId) {
|
|
580
|
-
const
|
|
593
|
+
const rows2 = this.db.prepare(
|
|
581
594
|
`SELECT * FROM lt_workspaces WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
582
595
|
).all(tenantId);
|
|
583
|
-
return
|
|
596
|
+
return rows2.map(mapRowToWorkspace);
|
|
584
597
|
}
|
|
585
598
|
async getWorkspaceById(tenantId, id) {
|
|
586
599
|
const row = this.db.prepare(
|
|
@@ -656,6 +669,7 @@ function mapRowToWorkspace(row) {
|
|
|
656
669
|
}
|
|
657
670
|
|
|
658
671
|
// src/stores/LocalProjectStore.ts
|
|
672
|
+
var import_protocols = require("@axiom-lattice/protocols");
|
|
659
673
|
var DDL4 = `
|
|
660
674
|
CREATE TABLE IF NOT EXISTS lt_projects (
|
|
661
675
|
id TEXT NOT NULL,
|
|
@@ -679,15 +693,15 @@ var LocalProjectStore = class {
|
|
|
679
693
|
}
|
|
680
694
|
async getProjectsByWorkspace(tenantId, workspaceId, filter) {
|
|
681
695
|
if (filter?.kind !== void 0) {
|
|
682
|
-
const
|
|
696
|
+
const rows3 = this.db.prepare(
|
|
683
697
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? AND kind = ? ORDER BY created_at DESC`
|
|
684
698
|
).all(tenantId, workspaceId, filter.kind);
|
|
685
|
-
return
|
|
699
|
+
return rows3.map(mapRowToProject);
|
|
686
700
|
}
|
|
687
|
-
const
|
|
701
|
+
const rows2 = this.db.prepare(
|
|
688
702
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? ORDER BY created_at DESC`
|
|
689
703
|
).all(tenantId, workspaceId);
|
|
690
|
-
return
|
|
704
|
+
return rows2.map(mapRowToProject);
|
|
691
705
|
}
|
|
692
706
|
async getProjectById(tenantId, id) {
|
|
693
707
|
const row = this.db.prepare(
|
|
@@ -696,6 +710,7 @@ var LocalProjectStore = class {
|
|
|
696
710
|
return row ? mapRowToProject(row) : null;
|
|
697
711
|
}
|
|
698
712
|
async createProject(tenantId, workspaceId, id, data) {
|
|
713
|
+
(0, import_protocols.assertGenericProjectConfig)(data.config);
|
|
699
714
|
const now = nowISO();
|
|
700
715
|
const kind = data.kind || "business";
|
|
701
716
|
this.db.prepare(
|
|
@@ -705,23 +720,18 @@ var LocalProjectStore = class {
|
|
|
705
720
|
workspace_id = excluded.workspace_id,
|
|
706
721
|
name = excluded.name,
|
|
707
722
|
description = excluded.description,
|
|
708
|
-
config =
|
|
723
|
+
config = CASE
|
|
724
|
+
WHEN json_type(lt_projects.config, '$.capabilityBundleIds') IS NOT NULL
|
|
725
|
+
THEN json_set(COALESCE(excluded.config, '{}'), '$.capabilityBundleIds', json_extract(lt_projects.config, '$.capabilityBundleIds'))
|
|
726
|
+
ELSE excluded.config
|
|
727
|
+
END,
|
|
709
728
|
kind = excluded.kind,
|
|
710
729
|
updated_at = excluded.updated_at`
|
|
711
730
|
).run(id, tenantId, workspaceId, data.name, data.description || null, data.config ? JSON.stringify(data.config) : null, kind, now, now);
|
|
712
|
-
return
|
|
713
|
-
id,
|
|
714
|
-
tenantId,
|
|
715
|
-
workspaceId,
|
|
716
|
-
name: data.name,
|
|
717
|
-
description: data.description,
|
|
718
|
-
config: data.config,
|
|
719
|
-
kind,
|
|
720
|
-
createdAt: parseISO(now),
|
|
721
|
-
updatedAt: parseISO(now)
|
|
722
|
-
};
|
|
731
|
+
return await this.getProjectById(tenantId, id);
|
|
723
732
|
}
|
|
724
733
|
async updateProject(tenantId, id, updates) {
|
|
734
|
+
(0, import_protocols.assertGenericProjectConfig)(updates.config);
|
|
725
735
|
const existing = await this.getProjectById(tenantId, id);
|
|
726
736
|
if (!existing) return null;
|
|
727
737
|
const setClauses = [];
|
|
@@ -735,8 +745,13 @@ var LocalProjectStore = class {
|
|
|
735
745
|
values.push(updates.description || null);
|
|
736
746
|
}
|
|
737
747
|
if (updates.config !== void 0) {
|
|
738
|
-
setClauses.push(
|
|
739
|
-
|
|
748
|
+
setClauses.push(`config = CASE
|
|
749
|
+
WHEN json_type(config, '$.capabilityBundleIds') IS NOT NULL
|
|
750
|
+
THEN json_set(json(?), '$.capabilityBundleIds', json_extract(config, '$.capabilityBundleIds'))
|
|
751
|
+
ELSE ?
|
|
752
|
+
END`);
|
|
753
|
+
const config = JSON.stringify(updates.config ?? {});
|
|
754
|
+
values.push(config, config);
|
|
740
755
|
}
|
|
741
756
|
if (updates.kind !== void 0) {
|
|
742
757
|
setClauses.push("kind = ?");
|
|
@@ -758,11 +773,57 @@ var LocalProjectStore = class {
|
|
|
758
773
|
).run(tenantId, id);
|
|
759
774
|
return result.changes > 0;
|
|
760
775
|
}
|
|
776
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
777
|
+
const now = nowISO();
|
|
778
|
+
const result = this.db.prepare(
|
|
779
|
+
`UPDATE lt_projects
|
|
780
|
+
SET config = json_set(COALESCE(config, '{}'), '$.capabilityBundleIds', json(?)), updated_at = ?
|
|
781
|
+
WHERE tenant_id = ? AND id = ?
|
|
782
|
+
AND NOT EXISTS (
|
|
783
|
+
SELECT 1 FROM json_each(?) AS requested
|
|
784
|
+
WHERE NOT EXISTS (
|
|
785
|
+
SELECT 1 FROM lt_capability_bundles
|
|
786
|
+
WHERE tenant_id = ? AND id = requested.value
|
|
787
|
+
)
|
|
788
|
+
)
|
|
789
|
+
AND NOT EXISTS (
|
|
790
|
+
SELECT 1 FROM json_each(?) AS expected
|
|
791
|
+
WHERE EXISTS (
|
|
792
|
+
SELECT 1 FROM lt_capability_bundles
|
|
793
|
+
WHERE tenant_id = ? AND id = expected.key AND updated_at <> expected.value
|
|
794
|
+
)
|
|
795
|
+
)`
|
|
796
|
+
).run(JSON.stringify(bundleIds), now, tenantId, projectId, JSON.stringify(bundleIds), tenantId, JSON.stringify(expectedRevisions), tenantId);
|
|
797
|
+
if (result.changes > 0) {
|
|
798
|
+
const project = await this.getProjectById(tenantId, projectId);
|
|
799
|
+
return project ? { status: "updated", project } : { status: "project_not_found" };
|
|
800
|
+
}
|
|
801
|
+
const revisionMismatch = this.db.prepare(
|
|
802
|
+
"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"
|
|
803
|
+
).get(tenantId, JSON.stringify(expectedRevisions), JSON.stringify(expectedRevisions));
|
|
804
|
+
if (revisionMismatch) return { status: "bundle_conflict" };
|
|
805
|
+
return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
806
|
+
}
|
|
807
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
808
|
+
const rows2 = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
|
|
809
|
+
return rows2.some((row) => {
|
|
810
|
+
if (!row.config) return false;
|
|
811
|
+
let config;
|
|
812
|
+
try {
|
|
813
|
+
config = JSON.parse(row.config);
|
|
814
|
+
} catch {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
|
|
818
|
+
const ids = config.capabilityBundleIds;
|
|
819
|
+
return Array.isArray(ids) && ids.every((id) => typeof id === "string") && ids.includes(bundleId);
|
|
820
|
+
});
|
|
821
|
+
}
|
|
761
822
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
762
|
-
ensureColumn(table, column,
|
|
823
|
+
ensureColumn(table, column, ddl2) {
|
|
763
824
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
764
825
|
if (!cols.some((c) => c.name === column)) {
|
|
765
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
826
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
766
827
|
}
|
|
767
828
|
}
|
|
768
829
|
};
|
|
@@ -780,6 +841,640 @@ function mapRowToProject(row) {
|
|
|
780
841
|
};
|
|
781
842
|
}
|
|
782
843
|
|
|
844
|
+
// src/stores/project-room-schema.ts
|
|
845
|
+
var schemaMarker = Symbol("projectRoomSchema");
|
|
846
|
+
var activeTransactions = /* @__PURE__ */ new WeakSet();
|
|
847
|
+
var migrationName = "create_project_room_tables";
|
|
848
|
+
var migrationTableDdl = `CREATE TABLE IF NOT EXISTS lattice_schema_migrations (
|
|
849
|
+
store_name TEXT NOT NULL, name TEXT NOT NULL, version INTEGER NOT NULL, applied_at TEXT NOT NULL,
|
|
850
|
+
PRIMARY KEY (store_name, name)
|
|
851
|
+
)`;
|
|
852
|
+
var ddl = `
|
|
853
|
+
CREATE TABLE IF NOT EXISTS lt_project_rooms (
|
|
854
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
855
|
+
type TEXT NOT NULL CHECK (type = 'main'), name TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
856
|
+
PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, type)
|
|
857
|
+
);
|
|
858
|
+
CREATE TABLE IF NOT EXISTS lt_project_memberships (
|
|
859
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, project_id TEXT NOT NULL, user_id TEXT NOT NULL,
|
|
860
|
+
role TEXT NOT NULL CHECK (role IN ('owner','admin','member','viewer')),
|
|
861
|
+
status TEXT NOT NULL CHECK (status IN ('active','removed')), joined_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
862
|
+
PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, user_id)
|
|
863
|
+
);
|
|
864
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_memberships_project ON lt_project_memberships (tenant_id, project_id);
|
|
865
|
+
CREATE TABLE IF NOT EXISTS lt_project_bot_memberships (
|
|
866
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
867
|
+
room_id TEXT NOT NULL, assistant_id TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('coordinator','specialist')),
|
|
868
|
+
title TEXT NOT NULL, responsibility TEXT, mention_name TEXT NOT NULL,
|
|
869
|
+
status TEXT NOT NULL CHECK (status IN ('active','paused','removed')), room_thread_id TEXT NOT NULL,
|
|
870
|
+
joined_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id),
|
|
871
|
+
UNIQUE (tenant_id, project_id, assistant_id)
|
|
872
|
+
);
|
|
873
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_coordinator
|
|
874
|
+
ON lt_project_bot_memberships (tenant_id, project_id) WHERE role = 'coordinator' AND status IN ('active','paused');
|
|
875
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_mention
|
|
876
|
+
ON lt_project_bot_memberships (tenant_id, room_id, mention_name) WHERE status IN ('active','paused');
|
|
877
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_project ON lt_project_bot_memberships (tenant_id, project_id);
|
|
878
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_room ON lt_project_bot_memberships (tenant_id, room_id);
|
|
879
|
+
CREATE TABLE IF NOT EXISTS lt_project_room_messages (
|
|
880
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL, room_id TEXT NOT NULL,
|
|
881
|
+
author TEXT NOT NULL, content TEXT NOT NULL, mentions TEXT NOT NULL DEFAULT '[]', reply_to_message_id TEXT,
|
|
882
|
+
source TEXT NOT NULL CHECK (source IN ('user','agent','task','routine','system')), source_id TEXT,
|
|
883
|
+
idempotency_key TEXT, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id)
|
|
884
|
+
);
|
|
885
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_room_messages_idempotency
|
|
886
|
+
ON lt_project_room_messages (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
887
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_room_messages_room_created
|
|
888
|
+
ON lt_project_room_messages (tenant_id, room_id, created_at DESC, id DESC);
|
|
889
|
+
`;
|
|
890
|
+
var tableColumns = {
|
|
891
|
+
lt_project_rooms: ["id", "tenant_id", "workspace_id", "project_id", "type", "name", "created_at", "updated_at"],
|
|
892
|
+
lt_project_memberships: ["id", "tenant_id", "project_id", "user_id", "role", "status", "joined_at", "updated_at"],
|
|
893
|
+
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"],
|
|
894
|
+
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"]
|
|
895
|
+
};
|
|
896
|
+
var indexes = {
|
|
897
|
+
idx_lt_project_memberships_project: { table: "lt_project_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
|
|
898
|
+
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')"] },
|
|
899
|
+
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')"] },
|
|
900
|
+
idx_lt_project_bot_memberships_project: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
|
|
901
|
+
idx_lt_project_bot_memberships_room: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id"], unique: false, partial: false },
|
|
902
|
+
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"] },
|
|
903
|
+
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"] }
|
|
904
|
+
};
|
|
905
|
+
var uniqueConstraints = {
|
|
906
|
+
lt_project_rooms: [["tenant_id", "id"], ["tenant_id", "project_id", "type"]],
|
|
907
|
+
lt_project_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "user_id"]],
|
|
908
|
+
lt_project_bot_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "assistant_id"]],
|
|
909
|
+
lt_project_room_messages: [["tenant_id", "id"]]
|
|
910
|
+
};
|
|
911
|
+
var checkConstraints = {
|
|
912
|
+
lt_project_rooms: [{ name: "type", fragment: "type='main'" }],
|
|
913
|
+
lt_project_memberships: [
|
|
914
|
+
{ name: "role", fragment: "role in ('owner','admin','member','viewer')" },
|
|
915
|
+
{ name: "status", fragment: "status in ('active','removed')" }
|
|
916
|
+
],
|
|
917
|
+
lt_project_bot_memberships: [
|
|
918
|
+
{ name: "role", fragment: "role in ('coordinator','specialist')" },
|
|
919
|
+
{ name: "status", fragment: "status in ('active','paused','removed')" }
|
|
920
|
+
],
|
|
921
|
+
lt_project_room_messages: [{ name: "source", fragment: "source in ('user','agent','task','routine','system')" }]
|
|
922
|
+
};
|
|
923
|
+
function rows(db, sql) {
|
|
924
|
+
const result = db.getRawDb().exec(sql);
|
|
925
|
+
if (!result[0]) return [];
|
|
926
|
+
return result[0].values.map((values) => Object.fromEntries(result[0].columns.map((column, index) => [column, values[index]])));
|
|
927
|
+
}
|
|
928
|
+
function normalizeSql(value) {
|
|
929
|
+
return value.toLowerCase().replace(/["`]/g, "").replace(/\[|\]/g, "").replace(/\s+/g, " ").replace(/\s*([=,])\s*/g, "$1").replace(/[()]/g, "").trim();
|
|
930
|
+
}
|
|
931
|
+
function extractCheckExpressions(sql) {
|
|
932
|
+
const checks = [];
|
|
933
|
+
let quoted = false;
|
|
934
|
+
for (let index = 0; index < sql.length; index += 1) {
|
|
935
|
+
if (sql[index] === "'") {
|
|
936
|
+
if (quoted && sql[index + 1] === "'") {
|
|
937
|
+
index += 1;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
quoted = !quoted;
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (quoted || sql.slice(index, index + 5).toLowerCase() !== "check") continue;
|
|
944
|
+
const before = sql[index - 1];
|
|
945
|
+
const after = sql[index + 5];
|
|
946
|
+
if (before && /[a-z0-9_]/i.test(before) || after && /[a-z0-9_]/i.test(after)) continue;
|
|
947
|
+
let opening = index + 5;
|
|
948
|
+
while (/\s/.test(sql[opening] ?? "")) opening += 1;
|
|
949
|
+
if (sql[opening] !== "(") continue;
|
|
950
|
+
let depth = 1;
|
|
951
|
+
let expressionQuoted = false;
|
|
952
|
+
for (let cursor = opening + 1; cursor < sql.length; cursor += 1) {
|
|
953
|
+
if (sql[cursor] === "'") {
|
|
954
|
+
if (expressionQuoted && sql[cursor + 1] === "'") {
|
|
955
|
+
cursor += 1;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
expressionQuoted = !expressionQuoted;
|
|
959
|
+
} else if (!expressionQuoted && sql[cursor] === "(") {
|
|
960
|
+
depth += 1;
|
|
961
|
+
} else if (!expressionQuoted && sql[cursor] === ")") {
|
|
962
|
+
depth -= 1;
|
|
963
|
+
if (depth === 0) {
|
|
964
|
+
checks.push(sql.slice(opening + 1, cursor));
|
|
965
|
+
index = cursor;
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return checks;
|
|
972
|
+
}
|
|
973
|
+
function validateProjectRoomSchema(db) {
|
|
974
|
+
for (const [table, expected] of Object.entries(tableColumns)) {
|
|
975
|
+
const actual = rows(db, `PRAGMA table_info(${table})`);
|
|
976
|
+
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)) {
|
|
977
|
+
throw new Error(`Invalid project room schema: table '${table}' has incompatible columns`);
|
|
978
|
+
}
|
|
979
|
+
const uniqueIndexes = rows(db, `PRAGMA index_list(${table})`).filter((index) => index.unique === 1);
|
|
980
|
+
const signatures = uniqueIndexes.map((index) => rows(db, `PRAGMA index_info(${String(index.name)})`).map((column) => column.name).join(","));
|
|
981
|
+
if (uniqueConstraints[table].some((constraint) => !signatures.includes(constraint.join(",")))) {
|
|
982
|
+
throw new Error(`Invalid project room schema: table '${table}' is missing required uniqueness`);
|
|
983
|
+
}
|
|
984
|
+
const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '${table}'`)[0]?.sql;
|
|
985
|
+
const checks = typeof definition === "string" ? extractCheckExpressions(definition).map(normalizeSql) : [];
|
|
986
|
+
const missingCheck = checkConstraints[table].find((constraint) => !checks.includes(normalizeSql(constraint.fragment)));
|
|
987
|
+
if (missingCheck) {
|
|
988
|
+
throw new Error(`Invalid project room schema: table '${table}' is missing required CHECK constraint '${missingCheck.name}'`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
for (const [name, expected] of Object.entries(indexes)) {
|
|
992
|
+
const listed = rows(db, `PRAGMA index_list(${expected.table})`).find((index) => index.name === name);
|
|
993
|
+
const columns5 = rows(db, `PRAGMA index_info(${name})`).map((column) => column.name);
|
|
994
|
+
const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'index' AND name = '${name}'`)[0]?.sql;
|
|
995
|
+
const normalized = typeof definition === "string" ? definition.toLowerCase().replace(/\s+/g, " ") : "";
|
|
996
|
+
if (!listed || listed.unique !== Number(expected.unique) || listed.partial !== Number(expected.partial) || columns5.join(",") !== expected.columns.join(",") || expected.sql?.some((fragment) => !normalized.includes(fragment))) {
|
|
997
|
+
throw new Error(`Invalid project room schema: index '${name}' is incompatible`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function ensureProjectRoomSchema(db) {
|
|
1002
|
+
const marked = db;
|
|
1003
|
+
if (marked[schemaMarker]) return;
|
|
1004
|
+
const raw = db.getRawDb();
|
|
1005
|
+
const hasMigrationTable = rows(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'lattice_schema_migrations'").length > 0;
|
|
1006
|
+
const applied = hasMigrationTable ? rows(db, `SELECT name, version FROM lattice_schema_migrations WHERE store_name = 'project_room' AND name = '${migrationName}'`)[0] : void 0;
|
|
1007
|
+
if (applied) {
|
|
1008
|
+
if (applied.version !== 1) throw new Error(`Invalid project room schema migration version: ${String(applied.version)}`);
|
|
1009
|
+
validateProjectRoomSchema(db);
|
|
1010
|
+
marked[schemaMarker] = true;
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
raw.exec("BEGIN IMMEDIATE");
|
|
1014
|
+
try {
|
|
1015
|
+
raw.exec(migrationTableDdl);
|
|
1016
|
+
raw.exec(ddl);
|
|
1017
|
+
validateProjectRoomSchema(db);
|
|
1018
|
+
raw.run("INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at) VALUES (?, ?, ?, ?)", ["project_room", migrationName, 1, (/* @__PURE__ */ new Date()).toISOString()]);
|
|
1019
|
+
raw.exec("COMMIT");
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
try {
|
|
1022
|
+
raw.exec("ROLLBACK");
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
db.save();
|
|
1028
|
+
marked[schemaMarker] = true;
|
|
1029
|
+
}
|
|
1030
|
+
function transaction(db, work) {
|
|
1031
|
+
if (activeTransactions.has(db)) throw new Error("Nested local store transactions are not supported");
|
|
1032
|
+
activeTransactions.add(db);
|
|
1033
|
+
let began = false;
|
|
1034
|
+
try {
|
|
1035
|
+
db.exec("BEGIN IMMEDIATE");
|
|
1036
|
+
began = true;
|
|
1037
|
+
const result = work();
|
|
1038
|
+
db.exec("COMMIT");
|
|
1039
|
+
db.save();
|
|
1040
|
+
return result;
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
if (began) {
|
|
1043
|
+
try {
|
|
1044
|
+
db.exec("ROLLBACK");
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
db.save();
|
|
1048
|
+
}
|
|
1049
|
+
throw error;
|
|
1050
|
+
} finally {
|
|
1051
|
+
activeTransactions.delete(db);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function write(db, sql, params) {
|
|
1055
|
+
const raw = db.getRawDb();
|
|
1056
|
+
raw.run(sql, params);
|
|
1057
|
+
return raw.getRowsModified();
|
|
1058
|
+
}
|
|
1059
|
+
function timestamp(previous) {
|
|
1060
|
+
const previousTime = previous === void 0 ? Number.NaN : Date.parse(previous);
|
|
1061
|
+
const now = Date.now();
|
|
1062
|
+
return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
|
|
1063
|
+
}
|
|
1064
|
+
function validDate(value) {
|
|
1065
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
1066
|
+
}
|
|
1067
|
+
function isUniqueError(error) {
|
|
1068
|
+
return error instanceof Error && error.message.includes("UNIQUE constraint failed");
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/stores/LocalProjectRoomStore.ts
|
|
1072
|
+
var columns = "id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at";
|
|
1073
|
+
function mapRow(row) {
|
|
1074
|
+
const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
|
|
1075
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1076
|
+
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");
|
|
1077
|
+
return {
|
|
1078
|
+
id: row.id,
|
|
1079
|
+
tenantId: row.tenant_id,
|
|
1080
|
+
workspaceId: row.workspace_id,
|
|
1081
|
+
projectId: row.project_id,
|
|
1082
|
+
type: "main",
|
|
1083
|
+
name: row.name,
|
|
1084
|
+
createdAt,
|
|
1085
|
+
updatedAt
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
var LocalProjectRoomStore = class {
|
|
1089
|
+
constructor(db) {
|
|
1090
|
+
this.db = db;
|
|
1091
|
+
ensureProjectRoomSchema(db);
|
|
1092
|
+
}
|
|
1093
|
+
async ensureMainRoom(input) {
|
|
1094
|
+
return transaction(this.db, () => {
|
|
1095
|
+
const existing = this.getRow(input.tenantId, input.projectId);
|
|
1096
|
+
if (existing) return mapRow(existing);
|
|
1097
|
+
const now = timestamp();
|
|
1098
|
+
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]);
|
|
1099
|
+
return mapRow(this.getRow(input.tenantId, input.projectId));
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
async getMainRoom(tenantId, projectId) {
|
|
1103
|
+
const row = this.getRow(tenantId, projectId);
|
|
1104
|
+
return row ? mapRow(row) : null;
|
|
1105
|
+
}
|
|
1106
|
+
getRow(tenantId, projectId) {
|
|
1107
|
+
return this.db.prepare(`SELECT ${columns} FROM lt_project_rooms WHERE tenant_id = ? AND project_id = ? AND type = 'main'`).get(tenantId, projectId);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// src/stores/LocalProjectMembershipStore.ts
|
|
1112
|
+
var columns2 = "id, tenant_id, project_id, user_id, role, status, joined_at, updated_at";
|
|
1113
|
+
var DuplicateProjectMembershipError = class extends Error {
|
|
1114
|
+
constructor() {
|
|
1115
|
+
super("Project membership already exists for tenant, project, and user");
|
|
1116
|
+
this.name = "DuplicateProjectMembershipError";
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
var ProjectMembershipIdConflictError = class extends Error {
|
|
1120
|
+
constructor() {
|
|
1121
|
+
super("Project membership ID already exists for tenant");
|
|
1122
|
+
this.name = "ProjectMembershipIdConflictError";
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
function role(value) {
|
|
1126
|
+
return value === "owner" || value === "admin" || value === "member" || value === "viewer";
|
|
1127
|
+
}
|
|
1128
|
+
function mapRow2(row) {
|
|
1129
|
+
const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
|
|
1130
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1131
|
+
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");
|
|
1132
|
+
return {
|
|
1133
|
+
id: row.id,
|
|
1134
|
+
tenantId: row.tenant_id,
|
|
1135
|
+
projectId: row.project_id,
|
|
1136
|
+
userId: row.user_id,
|
|
1137
|
+
role: row.role,
|
|
1138
|
+
status: row.status,
|
|
1139
|
+
joinedAt,
|
|
1140
|
+
updatedAt
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
var LocalProjectMembershipStore = class {
|
|
1144
|
+
constructor(db) {
|
|
1145
|
+
this.db = db;
|
|
1146
|
+
ensureProjectRoomSchema(db);
|
|
1147
|
+
}
|
|
1148
|
+
async list(tenantId, projectId) {
|
|
1149
|
+
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);
|
|
1150
|
+
}
|
|
1151
|
+
async findByUser(tenantId, projectId, userId) {
|
|
1152
|
+
const row = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?`).get(tenantId, projectId, userId);
|
|
1153
|
+
return row ? mapRow2(row) : null;
|
|
1154
|
+
}
|
|
1155
|
+
async create(input) {
|
|
1156
|
+
return transaction(this.db, () => {
|
|
1157
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
|
|
1158
|
+
const now = timestamp();
|
|
1159
|
+
try {
|
|
1160
|
+
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]);
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
|
|
1163
|
+
throw error;
|
|
1164
|
+
}
|
|
1165
|
+
return mapRow2(this.byId(input.tenantId, input.id));
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async createInitialOwner(input) {
|
|
1169
|
+
return transaction(this.db, () => {
|
|
1170
|
+
const rows2 = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ?`).all(input.tenantId, input.projectId);
|
|
1171
|
+
if (rows2.length > 0) {
|
|
1172
|
+
const existing = rows2.map(mapRow2).find((item) => item.userId === input.userId && item.role === "owner" && item.status === "active");
|
|
1173
|
+
return existing ? { kind: "existing", membership: existing } : { kind: "already_initialized" };
|
|
1174
|
+
}
|
|
1175
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
|
|
1176
|
+
const now = timestamp();
|
|
1177
|
+
try {
|
|
1178
|
+
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]);
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
|
|
1181
|
+
throw error;
|
|
1182
|
+
}
|
|
1183
|
+
return { kind: "created", membership: mapRow2(this.byId(input.tenantId, input.id)) };
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
async updateRole(input) {
|
|
1187
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, input.role, false);
|
|
1188
|
+
}
|
|
1189
|
+
async remove(input) {
|
|
1190
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, void 0, true);
|
|
1191
|
+
}
|
|
1192
|
+
mutate(tenantId, id, expected, nextRole, remove) {
|
|
1193
|
+
return transaction(this.db, () => {
|
|
1194
|
+
const row = this.byId(tenantId, id);
|
|
1195
|
+
if (!row) return { kind: "not_found" };
|
|
1196
|
+
const current = mapRow2(row);
|
|
1197
|
+
if (!validDate(expected) || current.updatedAt.getTime() !== expected.getTime()) return { kind: "conflict" };
|
|
1198
|
+
if (current.role === "owner" && current.status === "active" && (remove || nextRole !== "owner")) {
|
|
1199
|
+
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);
|
|
1200
|
+
if (!other) return { kind: "last_owner" };
|
|
1201
|
+
}
|
|
1202
|
+
const updatedAt = timestamp(row.updated_at);
|
|
1203
|
+
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]);
|
|
1204
|
+
if (changes === 0) return { kind: "conflict" };
|
|
1205
|
+
return { kind: remove ? "removed" : "updated", membership: mapRow2(this.byId(tenantId, id)) };
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
byId(tenantId, id) {
|
|
1209
|
+
return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1210
|
+
}
|
|
1211
|
+
classifyUnique(tenantId, id, projectId, userId) {
|
|
1212
|
+
if (this.byId(tenantId, id)) return new ProjectMembershipIdConflictError();
|
|
1213
|
+
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();
|
|
1214
|
+
return new Error("Unclassified project membership uniqueness conflict");
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
// src/stores/LocalProjectBotMembershipStore.ts
|
|
1219
|
+
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";
|
|
1220
|
+
var ProjectBotMembershipIdConflictError = class extends Error {
|
|
1221
|
+
constructor(tenantId, id) {
|
|
1222
|
+
super(`Project bot membership ID '${id}' already exists in tenant '${tenantId}'`);
|
|
1223
|
+
this.name = "ProjectBotMembershipIdConflictError";
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
function isRole(value) {
|
|
1227
|
+
return value === "coordinator" || value === "specialist";
|
|
1228
|
+
}
|
|
1229
|
+
function isStatus(value) {
|
|
1230
|
+
return value === "active" || value === "paused" || value === "removed";
|
|
1231
|
+
}
|
|
1232
|
+
function mapRow3(row) {
|
|
1233
|
+
const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
|
|
1234
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1235
|
+
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");
|
|
1236
|
+
return {
|
|
1237
|
+
id: row.id,
|
|
1238
|
+
tenantId: row.tenant_id,
|
|
1239
|
+
workspaceId: row.workspace_id,
|
|
1240
|
+
projectId: row.project_id,
|
|
1241
|
+
roomId: row.room_id,
|
|
1242
|
+
assistantId: row.assistant_id,
|
|
1243
|
+
role: row.role,
|
|
1244
|
+
title: row.title,
|
|
1245
|
+
...row.responsibility === null ? {} : { responsibility: row.responsibility },
|
|
1246
|
+
mentionName: row.mention_name,
|
|
1247
|
+
status: row.status,
|
|
1248
|
+
roomThreadId: row.room_thread_id,
|
|
1249
|
+
joinedAt,
|
|
1250
|
+
updatedAt
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
var LocalProjectBotMembershipStore = class {
|
|
1254
|
+
constructor(db) {
|
|
1255
|
+
this.db = db;
|
|
1256
|
+
ensureProjectRoomSchema(db);
|
|
1257
|
+
}
|
|
1258
|
+
async list(tenantId, projectId) {
|
|
1259
|
+
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);
|
|
1260
|
+
}
|
|
1261
|
+
async findById(tenantId, id) {
|
|
1262
|
+
const row = this.byId(tenantId, id);
|
|
1263
|
+
return row ? mapRow3(row) : null;
|
|
1264
|
+
}
|
|
1265
|
+
async findByAssistant(tenantId, projectId, assistantId) {
|
|
1266
|
+
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);
|
|
1267
|
+
return row ? mapRow3(row) : null;
|
|
1268
|
+
}
|
|
1269
|
+
async save(input) {
|
|
1270
|
+
try {
|
|
1271
|
+
return transaction(this.db, () => {
|
|
1272
|
+
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);
|
|
1273
|
+
if (existing) return this.reactivate(existing, input);
|
|
1274
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
1275
|
+
const conflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
|
|
1276
|
+
if (conflict) return { kind: conflict };
|
|
1277
|
+
const now = timestamp();
|
|
1278
|
+
try {
|
|
1279
|
+
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]);
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
if (!isUniqueError(error)) throw error;
|
|
1282
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
1283
|
+
const raced = this.byAssistant(input.tenantId, input.projectId, input.assistantId);
|
|
1284
|
+
if (raced) return this.reactivate(raced, input);
|
|
1285
|
+
const racedConflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
|
|
1286
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1287
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1288
|
+
}
|
|
1289
|
+
return { kind: "created", membership: mapRow3(this.byId(input.tenantId, input.id)) };
|
|
1290
|
+
});
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
const conflict = this.conflict(error);
|
|
1293
|
+
if (conflict) return { kind: conflict };
|
|
1294
|
+
throw error;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
async update(input) {
|
|
1298
|
+
try {
|
|
1299
|
+
return transaction(this.db, () => {
|
|
1300
|
+
const row = this.byId(input.tenantId, input.id);
|
|
1301
|
+
if (!row) return { kind: "not_found" };
|
|
1302
|
+
const existing = mapRow3(row);
|
|
1303
|
+
if (!validDate(input.expectedUpdatedAt) || existing.updatedAt.getTime() !== input.expectedUpdatedAt.getTime()) return { kind: "conflict" };
|
|
1304
|
+
const candidate = { ...existing, ...input.patch };
|
|
1305
|
+
const conflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
|
|
1306
|
+
if (conflict) return { kind: conflict };
|
|
1307
|
+
let changes;
|
|
1308
|
+
try {
|
|
1309
|
+
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]);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
if (!isUniqueError(error)) throw error;
|
|
1312
|
+
const racedConflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
|
|
1313
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1314
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1315
|
+
}
|
|
1316
|
+
if (changes === 0) return { kind: "conflict" };
|
|
1317
|
+
return { kind: "updated", membership: mapRow3(this.byId(input.tenantId, input.id)) };
|
|
1318
|
+
});
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
const conflict = this.conflict(error);
|
|
1321
|
+
if (conflict) return { kind: conflict };
|
|
1322
|
+
throw error;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
byId(tenantId, id) {
|
|
1326
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1327
|
+
}
|
|
1328
|
+
byAssistant(tenantId, projectId, assistantId) {
|
|
1329
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
|
|
1330
|
+
}
|
|
1331
|
+
reactivate(existing, input) {
|
|
1332
|
+
const persisted = mapRow3(existing);
|
|
1333
|
+
const wasRemoved = persisted.status === "removed";
|
|
1334
|
+
const conflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
|
|
1335
|
+
if (conflict) return { kind: conflict };
|
|
1336
|
+
try {
|
|
1337
|
+
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]);
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
if (!isUniqueError(error)) throw error;
|
|
1340
|
+
const racedConflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
|
|
1341
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1342
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1343
|
+
}
|
|
1344
|
+
return { kind: wasRemoved ? "reactivated" : "updated", membership: mapRow3(this.byId(input.tenantId, persisted.id)) };
|
|
1345
|
+
}
|
|
1346
|
+
preflight(tenantId, projectId, roomId, role2, mentionName, status, excludeId) {
|
|
1347
|
+
if (status === "removed") return void 0;
|
|
1348
|
+
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";
|
|
1349
|
+
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";
|
|
1350
|
+
return void 0;
|
|
1351
|
+
}
|
|
1352
|
+
conflict(error) {
|
|
1353
|
+
if (!isUniqueError(error) || !(error instanceof Error)) return void 0;
|
|
1354
|
+
if (error.message.includes("tenant_id, lt_project_bot_memberships.project_id")) return "coordinator_conflict";
|
|
1355
|
+
if (error.message.includes("room_id") && error.message.includes("mention_name")) return "mention_conflict";
|
|
1356
|
+
return void 0;
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
// src/stores/LocalProjectRoomMessageStore.ts
|
|
1361
|
+
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";
|
|
1362
|
+
var ProjectRoomMessageIdConflictError = class extends Error {
|
|
1363
|
+
constructor(tenantId, id) {
|
|
1364
|
+
super(`Project room message ID '${id}' already exists in tenant '${tenantId}'`);
|
|
1365
|
+
this.name = "ProjectRoomMessageIdConflictError";
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
var DuplicateProjectRoomMessageIdempotencyKeyError = class extends Error {
|
|
1369
|
+
constructor(tenantId, roomId, key) {
|
|
1370
|
+
super(`Project room message idempotency key '${key}' already exists in tenant '${tenantId}' room '${roomId}'`);
|
|
1371
|
+
this.name = "DuplicateProjectRoomMessageIdempotencyKeyError";
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
function record(value) {
|
|
1375
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1376
|
+
}
|
|
1377
|
+
function only(value, keys) {
|
|
1378
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
1379
|
+
}
|
|
1380
|
+
function parseJson(value) {
|
|
1381
|
+
if (typeof value !== "string") return void 0;
|
|
1382
|
+
try {
|
|
1383
|
+
return JSON.parse(value);
|
|
1384
|
+
} catch {
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function author(value) {
|
|
1389
|
+
if (!record(value)) return void 0;
|
|
1390
|
+
if (value.type === "human" && only(value, ["type", "userId"]) && typeof value.userId === "string") return { type: "human", userId: value.userId };
|
|
1391
|
+
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 };
|
|
1392
|
+
if (value.type === "system" && only(value, ["type"])) return { type: "system" };
|
|
1393
|
+
return void 0;
|
|
1394
|
+
}
|
|
1395
|
+
function mention(value) {
|
|
1396
|
+
if (!record(value)) return void 0;
|
|
1397
|
+
if (value.type === "bot" && only(value, ["type", "membershipId"]) && typeof value.membershipId === "string") return { type: "bot", membershipId: value.membershipId };
|
|
1398
|
+
if (value.type === "team" && only(value, ["type"])) return { type: "team" };
|
|
1399
|
+
return void 0;
|
|
1400
|
+
}
|
|
1401
|
+
function source(value) {
|
|
1402
|
+
return value === "user" || value === "agent" || value === "task" || value === "routine" || value === "system";
|
|
1403
|
+
}
|
|
1404
|
+
function mapRow4(row) {
|
|
1405
|
+
const mappedAuthor = author(parseJson(row.author));
|
|
1406
|
+
const content = parseJson(row.content);
|
|
1407
|
+
const rawMentions = parseJson(row.mentions);
|
|
1408
|
+
const mappedMentions = Array.isArray(rawMentions) ? rawMentions.map(mention) : void 0;
|
|
1409
|
+
const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
|
|
1410
|
+
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");
|
|
1411
|
+
return {
|
|
1412
|
+
id: row.id,
|
|
1413
|
+
tenantId: row.tenant_id,
|
|
1414
|
+
workspaceId: row.workspace_id,
|
|
1415
|
+
projectId: row.project_id,
|
|
1416
|
+
roomId: row.room_id,
|
|
1417
|
+
author: mappedAuthor,
|
|
1418
|
+
content: { type: "text", text: content.text },
|
|
1419
|
+
mentions: mappedMentions,
|
|
1420
|
+
...row.reply_to_message_id === null ? {} : { replyToMessageId: row.reply_to_message_id },
|
|
1421
|
+
source: row.source,
|
|
1422
|
+
...row.source_id === null ? {} : { sourceId: row.source_id },
|
|
1423
|
+
...row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key },
|
|
1424
|
+
createdAt
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
var LocalProjectRoomMessageStore = class {
|
|
1428
|
+
constructor(db) {
|
|
1429
|
+
this.db = db;
|
|
1430
|
+
ensureProjectRoomSchema(db);
|
|
1431
|
+
}
|
|
1432
|
+
async create(input) {
|
|
1433
|
+
return this.insert(input, false);
|
|
1434
|
+
}
|
|
1435
|
+
async createIdempotent(input) {
|
|
1436
|
+
return this.insert(input, true);
|
|
1437
|
+
}
|
|
1438
|
+
async list(input) {
|
|
1439
|
+
if (input.before && !validDate(input.before.createdAt)) throw new RangeError("Project room message cursor date is invalid");
|
|
1440
|
+
const limit = Math.min(100, Math.max(1, Math.trunc(Number.isFinite(input.limit) ? input.limit : 1)));
|
|
1441
|
+
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);
|
|
1442
|
+
return rows2.map(mapRow4);
|
|
1443
|
+
}
|
|
1444
|
+
async findById(tenantId, id) {
|
|
1445
|
+
const row = this.byId(tenantId, id);
|
|
1446
|
+
return row ? mapRow4(row) : null;
|
|
1447
|
+
}
|
|
1448
|
+
insert(input, idempotent) {
|
|
1449
|
+
return transaction(this.db, () => {
|
|
1450
|
+
if (idempotent && input.idempotencyKey) {
|
|
1451
|
+
const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1452
|
+
if (canonical) return mapRow4(canonical);
|
|
1453
|
+
}
|
|
1454
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
1455
|
+
try {
|
|
1456
|
+
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()]);
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
if (!isUniqueError(error)) throw error;
|
|
1459
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
1460
|
+
if (input.idempotencyKey) {
|
|
1461
|
+
const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1462
|
+
if (canonical && idempotent) return mapRow4(canonical);
|
|
1463
|
+
if (canonical) throw new DuplicateProjectRoomMessageIdempotencyKeyError(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1464
|
+
}
|
|
1465
|
+
throw new Error("Unclassified project room message uniqueness conflict");
|
|
1466
|
+
}
|
|
1467
|
+
return mapRow4(this.byId(input.tenantId, input.id));
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
byId(tenantId, id) {
|
|
1471
|
+
return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1472
|
+
}
|
|
1473
|
+
byKey(tenantId, roomId, key) {
|
|
1474
|
+
return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND idempotency_key = ?`).get(tenantId, roomId, key);
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
|
|
783
1478
|
// src/stores/LocalUserStore.ts
|
|
784
1479
|
var DDL5 = `
|
|
785
1480
|
CREATE TABLE IF NOT EXISTS lt_users (
|
|
@@ -799,10 +1494,10 @@ var LocalUserStore = class {
|
|
|
799
1494
|
ensureTable(db, DDL5);
|
|
800
1495
|
}
|
|
801
1496
|
async getAllUsers() {
|
|
802
|
-
const
|
|
1497
|
+
const rows2 = this.db.prepare(
|
|
803
1498
|
`SELECT * FROM lt_users ORDER BY created_at DESC`
|
|
804
1499
|
).all();
|
|
805
|
-
return
|
|
1500
|
+
return rows2.map(mapRowToUser);
|
|
806
1501
|
}
|
|
807
1502
|
async getUserById(id) {
|
|
808
1503
|
const row = this.db.prepare(
|
|
@@ -906,10 +1601,10 @@ var LocalTenantStore = class {
|
|
|
906
1601
|
ensureTable(db, DDL6);
|
|
907
1602
|
}
|
|
908
1603
|
async getAllTenants() {
|
|
909
|
-
const
|
|
1604
|
+
const rows2 = this.db.prepare(
|
|
910
1605
|
`SELECT * FROM lt_tenants ORDER BY created_at DESC`
|
|
911
1606
|
).all();
|
|
912
|
-
return
|
|
1607
|
+
return rows2.map(mapRowToTenant);
|
|
913
1608
|
}
|
|
914
1609
|
async getTenantById(id) {
|
|
915
1610
|
const row = this.db.prepare(
|
|
@@ -1008,16 +1703,16 @@ var LocalUserTenantLinkStore = class {
|
|
|
1008
1703
|
ensureTable(db, DDL7);
|
|
1009
1704
|
}
|
|
1010
1705
|
async getTenantsByUser(userId) {
|
|
1011
|
-
const
|
|
1706
|
+
const rows2 = this.db.prepare(
|
|
1012
1707
|
`SELECT * FROM lt_user_tenant_links WHERE user_id = ? ORDER BY joined_at DESC`
|
|
1013
1708
|
).all(userId);
|
|
1014
|
-
return
|
|
1709
|
+
return rows2.map(mapRowToLink);
|
|
1015
1710
|
}
|
|
1016
1711
|
async getUsersByTenant(tenantId) {
|
|
1017
|
-
const
|
|
1712
|
+
const rows2 = this.db.prepare(
|
|
1018
1713
|
`SELECT * FROM lt_user_tenant_links WHERE tenant_id = ? ORDER BY joined_at DESC`
|
|
1019
1714
|
).all(tenantId);
|
|
1020
|
-
return
|
|
1715
|
+
return rows2.map(mapRowToLink);
|
|
1021
1716
|
}
|
|
1022
1717
|
async getLink(userId, tenantId) {
|
|
1023
1718
|
const row = this.db.prepare(
|
|
@@ -1027,7 +1722,7 @@ var LocalUserTenantLinkStore = class {
|
|
|
1027
1722
|
}
|
|
1028
1723
|
async createLink(data) {
|
|
1029
1724
|
const now = nowISO();
|
|
1030
|
-
const
|
|
1725
|
+
const role2 = data.role || "member";
|
|
1031
1726
|
const metadata = JSON.stringify(data.metadata || {});
|
|
1032
1727
|
this.db.prepare(
|
|
1033
1728
|
`INSERT INTO lt_user_tenant_links (user_id, tenant_id, role, joined_at, metadata)
|
|
@@ -1035,11 +1730,11 @@ var LocalUserTenantLinkStore = class {
|
|
|
1035
1730
|
ON CONFLICT(user_id, tenant_id) DO UPDATE SET
|
|
1036
1731
|
role = excluded.role,
|
|
1037
1732
|
metadata = excluded.metadata`
|
|
1038
|
-
).run(data.userId, data.tenantId,
|
|
1733
|
+
).run(data.userId, data.tenantId, role2, now, metadata);
|
|
1039
1734
|
return {
|
|
1040
1735
|
userId: data.userId,
|
|
1041
1736
|
tenantId: data.tenantId,
|
|
1042
|
-
role,
|
|
1737
|
+
role: role2,
|
|
1043
1738
|
joinedAt: parseISO(now),
|
|
1044
1739
|
metadata: data.metadata
|
|
1045
1740
|
};
|
|
@@ -1109,16 +1804,16 @@ var LocalDatabaseConfigStore = class {
|
|
|
1109
1804
|
ensureTable(db, DDL8);
|
|
1110
1805
|
}
|
|
1111
1806
|
async getAllConfigs(tenantId) {
|
|
1112
|
-
const
|
|
1807
|
+
const rows2 = this.db.prepare(
|
|
1113
1808
|
`SELECT * FROM lt_database_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1114
1809
|
).all(tenantId);
|
|
1115
|
-
return
|
|
1810
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1116
1811
|
}
|
|
1117
1812
|
async getAllConfigsWithoutTenant() {
|
|
1118
|
-
const
|
|
1813
|
+
const rows2 = this.db.prepare(
|
|
1119
1814
|
`SELECT * FROM lt_database_configs ORDER BY created_at DESC`
|
|
1120
1815
|
).all();
|
|
1121
|
-
return
|
|
1816
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1122
1817
|
}
|
|
1123
1818
|
async getConfigById(tenantId, id) {
|
|
1124
1819
|
const row = this.db.prepare(
|
|
@@ -1250,16 +1945,16 @@ var LocalConnectionStore = class {
|
|
|
1250
1945
|
ensureTable(db, DDL9);
|
|
1251
1946
|
}
|
|
1252
1947
|
async listByTenant(tenantId) {
|
|
1253
|
-
const
|
|
1948
|
+
const rows2 = this.db.prepare(
|
|
1254
1949
|
`SELECT * FROM connection_configs WHERE tenant_id = ? ORDER BY created_at`
|
|
1255
1950
|
).all(tenantId);
|
|
1256
|
-
return
|
|
1951
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1257
1952
|
}
|
|
1258
1953
|
async listByType(tenantId, type) {
|
|
1259
|
-
const
|
|
1954
|
+
const rows2 = this.db.prepare(
|
|
1260
1955
|
`SELECT * FROM connection_configs WHERE tenant_id = ? AND type = ? ORDER BY created_at`
|
|
1261
1956
|
).all(tenantId, type);
|
|
1262
|
-
return
|
|
1957
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1263
1958
|
}
|
|
1264
1959
|
async getByKey(tenantId, type, key) {
|
|
1265
1960
|
const row = this.db.prepare(
|
|
@@ -1345,16 +2040,16 @@ var LocalMetricsServerConfigStore = class {
|
|
|
1345
2040
|
ensureTable(db, DDL10);
|
|
1346
2041
|
}
|
|
1347
2042
|
async getAllConfigs(tenantId) {
|
|
1348
|
-
const
|
|
2043
|
+
const rows2 = this.db.prepare(
|
|
1349
2044
|
`SELECT * FROM lt_metrics_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1350
2045
|
).all(tenantId);
|
|
1351
|
-
return
|
|
2046
|
+
return rows2.map(mapRowToEntry2);
|
|
1352
2047
|
}
|
|
1353
2048
|
async getAllConfigsWithoutTenant() {
|
|
1354
|
-
const
|
|
2049
|
+
const rows2 = this.db.prepare(
|
|
1355
2050
|
`SELECT * FROM lt_metrics_configs ORDER BY created_at DESC`
|
|
1356
2051
|
).all();
|
|
1357
|
-
return
|
|
2052
|
+
return rows2.map(mapRowToEntry2);
|
|
1358
2053
|
}
|
|
1359
2054
|
async getConfigById(tenantId, id) {
|
|
1360
2055
|
const row = this.db.prepare(
|
|
@@ -1473,16 +2168,16 @@ var LocalMcpServerConfigStore = class {
|
|
|
1473
2168
|
ensureTable(db, DDL11);
|
|
1474
2169
|
}
|
|
1475
2170
|
async getAllConfigs(tenantId) {
|
|
1476
|
-
const
|
|
2171
|
+
const rows2 = this.db.prepare(
|
|
1477
2172
|
`SELECT * FROM lt_mcp_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1478
2173
|
).all(tenantId);
|
|
1479
|
-
return
|
|
2174
|
+
return rows2.map(mapRowToEntry3);
|
|
1480
2175
|
}
|
|
1481
2176
|
async getAllConfigsWithoutTenant() {
|
|
1482
|
-
const
|
|
2177
|
+
const rows2 = this.db.prepare(
|
|
1483
2178
|
`SELECT * FROM lt_mcp_configs ORDER BY created_at DESC`
|
|
1484
2179
|
).all();
|
|
1485
|
-
return
|
|
2180
|
+
return rows2.map(mapRowToEntry3);
|
|
1486
2181
|
}
|
|
1487
2182
|
async getConfigById(tenantId, id) {
|
|
1488
2183
|
const row = this.db.prepare(
|
|
@@ -1764,22 +2459,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1764
2459
|
this.db.prepare(`DELETE FROM lt_workflow_runs WHERE id = ?`).run(runId);
|
|
1765
2460
|
}
|
|
1766
2461
|
async getWorkflowRunsByThreadId(tenantId, threadId) {
|
|
1767
|
-
const
|
|
2462
|
+
const rows2 = this.db.prepare(
|
|
1768
2463
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND thread_id = ? ORDER BY created_at DESC`
|
|
1769
2464
|
).all(tenantId, threadId);
|
|
1770
|
-
return
|
|
2465
|
+
return rows2.map(mapRowToRun);
|
|
1771
2466
|
}
|
|
1772
2467
|
async getWorkflowRunsByAssistantId(tenantId, assistantId) {
|
|
1773
|
-
const
|
|
2468
|
+
const rows2 = this.db.prepare(
|
|
1774
2469
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
1775
2470
|
).all(tenantId, assistantId);
|
|
1776
|
-
return
|
|
2471
|
+
return rows2.map(mapRowToRun);
|
|
1777
2472
|
}
|
|
1778
2473
|
async getWorkflowRunsByTenantId(tenantId) {
|
|
1779
|
-
const
|
|
2474
|
+
const rows2 = this.db.prepare(
|
|
1780
2475
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1781
2476
|
).all(tenantId);
|
|
1782
|
-
return
|
|
2477
|
+
return rows2.map(mapRowToRun);
|
|
1783
2478
|
}
|
|
1784
2479
|
async queryWorkflowRuns(tenantId, options = {}) {
|
|
1785
2480
|
const conditions = ["tenant_id = ?"];
|
|
@@ -1808,8 +2503,8 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1808
2503
|
sql += " OFFSET ?";
|
|
1809
2504
|
selectParams.push(options.offset);
|
|
1810
2505
|
}
|
|
1811
|
-
const
|
|
1812
|
-
return { records:
|
|
2506
|
+
const rows2 = this.db.prepare(sql).all(...selectParams);
|
|
2507
|
+
return { records: rows2.map(mapRowToRun), total: countRow.count };
|
|
1813
2508
|
}
|
|
1814
2509
|
async createRunStep(request) {
|
|
1815
2510
|
const now = nowISO();
|
|
@@ -1901,22 +2596,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1901
2596
|
return this.getStepById(runId, stepId);
|
|
1902
2597
|
}
|
|
1903
2598
|
async getRunSteps(runId) {
|
|
1904
|
-
const
|
|
2599
|
+
const rows2 = this.db.prepare(
|
|
1905
2600
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? ORDER BY created_at ASC`
|
|
1906
2601
|
).all(runId);
|
|
1907
|
-
return
|
|
2602
|
+
return rows2.map(mapRowToStep);
|
|
1908
2603
|
}
|
|
1909
2604
|
async getRunStepsByType(runId, stepType) {
|
|
1910
|
-
const
|
|
2605
|
+
const rows2 = this.db.prepare(
|
|
1911
2606
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? ORDER BY created_at ASC`
|
|
1912
2607
|
).all(runId, stepType);
|
|
1913
|
-
return
|
|
2608
|
+
return rows2.map(mapRowToStep);
|
|
1914
2609
|
}
|
|
1915
2610
|
async getInterruptedSteps(runId) {
|
|
1916
|
-
const
|
|
2611
|
+
const rows2 = this.db.prepare(
|
|
1917
2612
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND status = 'interrupted' ORDER BY created_at ASC`
|
|
1918
2613
|
).all(runId);
|
|
1919
|
-
return
|
|
2614
|
+
return rows2.map(mapRowToStep);
|
|
1920
2615
|
}
|
|
1921
2616
|
getStepById(runId, id) {
|
|
1922
2617
|
const row = this.db.prepare(
|
|
@@ -2061,7 +2756,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_run_results (
|
|
|
2061
2756
|
PRIMARY KEY (run_id, id)
|
|
2062
2757
|
);
|
|
2063
2758
|
`;
|
|
2064
|
-
function
|
|
2759
|
+
function parseJson2(val, fallback) {
|
|
2065
2760
|
if (val == null) return fallback;
|
|
2066
2761
|
if (typeof val === "string") {
|
|
2067
2762
|
try {
|
|
@@ -2153,20 +2848,20 @@ var LocalEvalStore = class {
|
|
|
2153
2848
|
`);
|
|
2154
2849
|
}
|
|
2155
2850
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2156
|
-
ensureColumn(table, column,
|
|
2851
|
+
ensureColumn(table, column, ddl2) {
|
|
2157
2852
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2158
2853
|
if (!cols.some((c) => c.name === column)) {
|
|
2159
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
2854
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
2160
2855
|
}
|
|
2161
2856
|
}
|
|
2162
2857
|
// -------------------------------------------------------------------------
|
|
2163
2858
|
// Projects
|
|
2164
2859
|
// -------------------------------------------------------------------------
|
|
2165
2860
|
async getProjectsByTenant(tenantId) {
|
|
2166
|
-
const
|
|
2861
|
+
const rows2 = this.db.prepare(
|
|
2167
2862
|
`SELECT * FROM lt_eval_projects WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2168
2863
|
).all(tenantId);
|
|
2169
|
-
return
|
|
2864
|
+
return rows2.map(this.mapRowToProject);
|
|
2170
2865
|
}
|
|
2171
2866
|
async getProjectById(tenantId, id) {
|
|
2172
2867
|
const row = this.db.prepare(
|
|
@@ -2269,11 +2964,11 @@ var LocalEvalStore = class {
|
|
|
2269
2964
|
// Suites
|
|
2270
2965
|
// -------------------------------------------------------------------------
|
|
2271
2966
|
async getSuitesByProject(tenantId, projectId) {
|
|
2272
|
-
const
|
|
2967
|
+
const rows2 = this.db.prepare(
|
|
2273
2968
|
`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
|
|
2274
2969
|
FROM lt_eval_suites s WHERE s.tenant_id = ? AND s.project_id = ? ORDER BY s.created_at DESC`
|
|
2275
2970
|
).all(tenantId, projectId);
|
|
2276
|
-
return
|
|
2971
|
+
return rows2.map(this.mapRowToSuite);
|
|
2277
2972
|
}
|
|
2278
2973
|
async getSuiteById(tenantId, id) {
|
|
2279
2974
|
const row = this.db.prepare(
|
|
@@ -2314,10 +3009,10 @@ var LocalEvalStore = class {
|
|
|
2314
3009
|
// Cases
|
|
2315
3010
|
// -------------------------------------------------------------------------
|
|
2316
3011
|
async getCasesBySuite(tenantId, suiteId) {
|
|
2317
|
-
const
|
|
3012
|
+
const rows2 = this.db.prepare(
|
|
2318
3013
|
`SELECT * FROM lt_eval_cases WHERE tenant_id = ? AND suite_id = ? ORDER BY created_at DESC`
|
|
2319
3014
|
).all(tenantId, suiteId);
|
|
2320
|
-
return
|
|
3015
|
+
return rows2.map(this.mapRowToCase);
|
|
2321
3016
|
}
|
|
2322
3017
|
async getCaseById(tenantId, id) {
|
|
2323
3018
|
const row = this.db.prepare(
|
|
@@ -2406,8 +3101,8 @@ var LocalEvalStore = class {
|
|
|
2406
3101
|
vals.push(opts.status);
|
|
2407
3102
|
}
|
|
2408
3103
|
query += ` ORDER BY created_at DESC`;
|
|
2409
|
-
const
|
|
2410
|
-
return
|
|
3104
|
+
const rows2 = this.db.prepare(query).all(...vals);
|
|
3105
|
+
return rows2.map(this.mapRowToRun);
|
|
2411
3106
|
}
|
|
2412
3107
|
async getRunById(tenantId, id) {
|
|
2413
3108
|
const row = this.db.prepare(
|
|
@@ -2470,12 +3165,12 @@ var LocalEvalStore = class {
|
|
|
2470
3165
|
// Run Results
|
|
2471
3166
|
// -------------------------------------------------------------------------
|
|
2472
3167
|
async getResultsByRun(tenantId, runId) {
|
|
2473
|
-
const
|
|
3168
|
+
const rows2 = this.db.prepare(
|
|
2474
3169
|
`SELECT rr.* FROM lt_eval_run_results rr
|
|
2475
3170
|
INNER JOIN lt_eval_runs r ON r.id = rr.run_id
|
|
2476
3171
|
WHERE r.tenant_id = ? AND rr.run_id = ? ORDER BY rr.created_at ASC`
|
|
2477
3172
|
).all(tenantId, runId);
|
|
2478
|
-
return
|
|
3173
|
+
return rows2.map(this.mapRowToRunResult);
|
|
2479
3174
|
}
|
|
2480
3175
|
async getRunResultById(tenantId, id) {
|
|
2481
3176
|
const row = this.db.prepare(
|
|
@@ -2593,8 +3288,8 @@ var LocalEvalStore = class {
|
|
|
2593
3288
|
name: row.name,
|
|
2594
3289
|
description: row.description,
|
|
2595
3290
|
version: row.version,
|
|
2596
|
-
judgeModelConfig:
|
|
2597
|
-
targetServerConfig:
|
|
3291
|
+
judgeModelConfig: parseJson2(row.judge_model_config, {}),
|
|
3292
|
+
targetServerConfig: parseJson2(row.target_server_config, {}),
|
|
2598
3293
|
concurrency: row.concurrency ?? 3,
|
|
2599
3294
|
reportConfig: parseOptionalJson(row.report_config),
|
|
2600
3295
|
createdAt: parseISO(row.created_at),
|
|
@@ -2619,7 +3314,7 @@ var LocalEvalStore = class {
|
|
|
2619
3314
|
suiteId: row.suite_id,
|
|
2620
3315
|
inputMessage: row.input_message,
|
|
2621
3316
|
inputFiles: parseOptionalJson(row.input_files),
|
|
2622
|
-
steps:
|
|
3317
|
+
steps: parseJson2(row.steps, []),
|
|
2623
3318
|
outputType: row.output_type || "message_content",
|
|
2624
3319
|
contentAssertion: row.content_assertion || "",
|
|
2625
3320
|
rubrics: parseOptionalJson(row.rubrics),
|
|
@@ -2671,7 +3366,50 @@ var LocalEvalStore = class {
|
|
|
2671
3366
|
};
|
|
2672
3367
|
|
|
2673
3368
|
// src/stores/LocalChannelBindingStore.ts
|
|
3369
|
+
var import_protocols3 = require("@axiom-lattice/protocols");
|
|
2674
3370
|
var import_crypto2 = require("crypto");
|
|
3371
|
+
|
|
3372
|
+
// src/stores/room-channel-binding-index.ts
|
|
3373
|
+
var import_protocols2 = require("@axiom-lattice/protocols");
|
|
3374
|
+
var UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_cb_subject_unique
|
|
3375
|
+
ON lt_channel_bindings(channel, channel_installation_id, tenant_id, sender_id)`;
|
|
3376
|
+
var LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve
|
|
3377
|
+
ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id)`;
|
|
3378
|
+
function ensureChannelBindingSubjectIndex(db) {
|
|
3379
|
+
db.exec("BEGIN");
|
|
3380
|
+
try {
|
|
3381
|
+
const conflicts = db.prepare(`SELECT tenant_id, channel, channel_installation_id, sender_id, COUNT(*) AS count
|
|
3382
|
+
FROM lt_channel_bindings
|
|
3383
|
+
GROUP BY channel, channel_installation_id, tenant_id, sender_id
|
|
3384
|
+
HAVING COUNT(*) > 1
|
|
3385
|
+
ORDER BY tenant_id, channel, channel_installation_id, sender_id`).all();
|
|
3386
|
+
if (conflicts.length > 0) {
|
|
3387
|
+
throw new import_protocols2.ChannelBindingMigrationConflictError(conflicts.map(mapConflict));
|
|
3388
|
+
}
|
|
3389
|
+
db.exec("DROP INDEX IF EXISTS idx_lt_cb_room_subject");
|
|
3390
|
+
db.exec(UNIQUE_INDEX_SQL);
|
|
3391
|
+
db.exec(LOOKUP_INDEX_SQL);
|
|
3392
|
+
db.exec("COMMIT");
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
try {
|
|
3395
|
+
db.exec("ROLLBACK");
|
|
3396
|
+
} catch {
|
|
3397
|
+
}
|
|
3398
|
+
throw error;
|
|
3399
|
+
}
|
|
3400
|
+
db.save();
|
|
3401
|
+
}
|
|
3402
|
+
function mapConflict(row) {
|
|
3403
|
+
return {
|
|
3404
|
+
tenantId: row.tenant_id,
|
|
3405
|
+
channel: row.channel,
|
|
3406
|
+
channelInstallationId: row.channel_installation_id,
|
|
3407
|
+
senderId: row.sender_id,
|
|
3408
|
+
count: row.count
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// src/stores/LocalChannelBindingStore.ts
|
|
2675
3413
|
var DDL14 = `
|
|
2676
3414
|
CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
2677
3415
|
id TEXT PRIMARY KEY,
|
|
@@ -2690,12 +3428,23 @@ CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
|
2690
3428
|
created_at TEXT NOT NULL,
|
|
2691
3429
|
updated_at TEXT NOT NULL
|
|
2692
3430
|
);
|
|
2693
|
-
CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id);
|
|
2694
3431
|
`;
|
|
2695
3432
|
var LocalChannelBindingStore = class {
|
|
2696
3433
|
constructor(db) {
|
|
2697
3434
|
this.db = db;
|
|
2698
3435
|
ensureTable(db, DDL14);
|
|
3436
|
+
ensureChannelBindingSubjectIndex(db);
|
|
3437
|
+
}
|
|
3438
|
+
async findById(tenantId, id) {
|
|
3439
|
+
const row = this.db.prepare(
|
|
3440
|
+
"SELECT * FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?"
|
|
3441
|
+
).get(id, tenantId);
|
|
3442
|
+
return row ? mapRowToBinding(row) : null;
|
|
3443
|
+
}
|
|
3444
|
+
async findBySubject(params) {
|
|
3445
|
+
const row = this.db.prepare(`SELECT * FROM lt_channel_bindings
|
|
3446
|
+
WHERE tenant_id = ? AND channel = ? AND channel_installation_id = ? AND sender_id = ? LIMIT 1`).get(params.tenantId, params.channel, params.channelInstallationId, params.senderId);
|
|
3447
|
+
return row ? mapRowToBinding(row) : null;
|
|
2699
3448
|
}
|
|
2700
3449
|
async resolve(params) {
|
|
2701
3450
|
const row = this.db.prepare(
|
|
@@ -2708,35 +3457,41 @@ var LocalChannelBindingStore = class {
|
|
|
2708
3457
|
async create(input) {
|
|
2709
3458
|
const id = (0, import_crypto2.randomUUID)();
|
|
2710
3459
|
const now = nowISO();
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
3460
|
+
try {
|
|
3461
|
+
this.db.prepare(
|
|
3462
|
+
`INSERT INTO lt_channel_bindings
|
|
3463
|
+
(id, channel, channel_installation_id, tenant_id, sender_id, agent_id,
|
|
3464
|
+
thread_id, thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, enabled, created_at, updated_at)
|
|
3465
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3466
|
+
).run(
|
|
3467
|
+
id,
|
|
3468
|
+
input.channel,
|
|
3469
|
+
input.channelInstallationId,
|
|
3470
|
+
input.tenantId,
|
|
3471
|
+
input.senderId,
|
|
3472
|
+
input.agentId,
|
|
3473
|
+
input.threadId || null,
|
|
3474
|
+
input.threadMode || "fixed",
|
|
3475
|
+
input.senderDisplayName || null,
|
|
3476
|
+
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
3477
|
+
input.workspaceId || null,
|
|
3478
|
+
input.projectId || null,
|
|
3479
|
+
input.enabled ?? true ? 1 : 0,
|
|
3480
|
+
now,
|
|
3481
|
+
now
|
|
3482
|
+
);
|
|
3483
|
+
} catch (error) {
|
|
3484
|
+
if (error instanceof Error && /UNIQUE constraint failed/.test(error.message)) {
|
|
3485
|
+
throw new import_protocols3.DuplicateChannelBindingSubjectError();
|
|
3486
|
+
}
|
|
3487
|
+
throw error;
|
|
3488
|
+
}
|
|
2731
3489
|
return await this.getById(id);
|
|
2732
3490
|
}
|
|
2733
|
-
async update(id, patch) {
|
|
2734
|
-
const existing = await this.
|
|
3491
|
+
async update(tenantId, id, patch) {
|
|
3492
|
+
const existing = await this.findById(tenantId, id);
|
|
2735
3493
|
if (!existing) throw new Error(`Binding ${id} not found`);
|
|
2736
3494
|
const updated = {
|
|
2737
|
-
channel: patch.channel ?? existing.channel,
|
|
2738
|
-
channelInstallationId: patch.channelInstallationId ?? existing.channelInstallationId,
|
|
2739
|
-
senderId: patch.senderId ?? existing.senderId,
|
|
2740
3495
|
agentId: patch.agentId ?? existing.agentId,
|
|
2741
3496
|
threadId: patch.threadId !== void 0 ? patch.threadId : existing.threadId,
|
|
2742
3497
|
workspaceId: patch.workspaceId !== void 0 ? patch.workspaceId : existing.workspaceId,
|
|
@@ -2749,14 +3504,10 @@ var LocalChannelBindingStore = class {
|
|
|
2749
3504
|
const now = nowISO();
|
|
2750
3505
|
this.db.prepare(
|
|
2751
3506
|
`UPDATE lt_channel_bindings SET
|
|
2752
|
-
|
|
2753
|
-
thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
3507
|
+
agent_id = ?, thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
2754
3508
|
sender_display_name = ?, sender_metadata = ?, enabled = ?, updated_at = ?
|
|
2755
|
-
WHERE id = ?`
|
|
3509
|
+
WHERE id = ? AND tenant_id = ?`
|
|
2756
3510
|
).run(
|
|
2757
|
-
updated.channel,
|
|
2758
|
-
updated.channelInstallationId,
|
|
2759
|
-
updated.senderId,
|
|
2760
3511
|
updated.agentId,
|
|
2761
3512
|
updated.threadId || null,
|
|
2762
3513
|
updated.workspaceId || null,
|
|
@@ -2766,12 +3517,13 @@ var LocalChannelBindingStore = class {
|
|
|
2766
3517
|
updated.senderMetadata ? JSON.stringify(updated.senderMetadata) : null,
|
|
2767
3518
|
updated.enabled ? 1 : 0,
|
|
2768
3519
|
now,
|
|
2769
|
-
id
|
|
3520
|
+
id,
|
|
3521
|
+
tenantId
|
|
2770
3522
|
);
|
|
2771
|
-
return await this.
|
|
3523
|
+
return await this.findById(tenantId, id);
|
|
2772
3524
|
}
|
|
2773
|
-
async delete(id) {
|
|
2774
|
-
this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ?`).run(id);
|
|
3525
|
+
async delete(tenantId, id) {
|
|
3526
|
+
this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?`).run(id, tenantId);
|
|
2775
3527
|
}
|
|
2776
3528
|
async list(params) {
|
|
2777
3529
|
const conditions = ["tenant_id = ?"];
|
|
@@ -2788,18 +3540,35 @@ var LocalChannelBindingStore = class {
|
|
|
2788
3540
|
conditions.push("channel_installation_id = ?");
|
|
2789
3541
|
values.push(params.channelInstallationId);
|
|
2790
3542
|
}
|
|
3543
|
+
if (params.excludeChannels?.length) {
|
|
3544
|
+
conditions.push(`channel NOT IN (${params.excludeChannels.map(() => "?").join(", ")})`);
|
|
3545
|
+
values.push(...params.excludeChannels);
|
|
3546
|
+
}
|
|
3547
|
+
for (const prefix of params.excludeInstallationIdPrefixes ?? []) {
|
|
3548
|
+
conditions.push("substr(channel_installation_id, 1, length(?)) <> ?");
|
|
3549
|
+
values.push(prefix, prefix);
|
|
3550
|
+
}
|
|
2791
3551
|
const limit = params.limit ?? 50;
|
|
2792
3552
|
const offset = params.offset ?? 0;
|
|
2793
3553
|
values.push(limit, offset);
|
|
2794
|
-
const
|
|
3554
|
+
const rows2 = this.db.prepare(
|
|
2795
3555
|
`SELECT * FROM lt_channel_bindings
|
|
2796
3556
|
WHERE ${conditions.join(" AND ")}
|
|
2797
3557
|
ORDER BY created_at DESC
|
|
2798
3558
|
LIMIT ? OFFSET ?`
|
|
2799
3559
|
).all(...values);
|
|
2800
|
-
return
|
|
3560
|
+
return rows2.map(mapRowToBinding);
|
|
2801
3561
|
}
|
|
2802
|
-
async import(bindings) {
|
|
3562
|
+
async import(tenantId, bindings) {
|
|
3563
|
+
if (bindings.some((binding) => binding.channel === "room")) {
|
|
3564
|
+
throw new Error("Room bindings cannot be imported through the public store API");
|
|
3565
|
+
}
|
|
3566
|
+
if (bindings.some((binding) => binding.channelInstallationId.startsWith("room-internal:"))) {
|
|
3567
|
+
throw new Error("Internal bindings cannot be imported through the public store API");
|
|
3568
|
+
}
|
|
3569
|
+
if (bindings.some((binding) => binding.tenantId !== tenantId)) {
|
|
3570
|
+
throw new Error("Binding import tenant mismatch");
|
|
3571
|
+
}
|
|
2803
3572
|
const result = [];
|
|
2804
3573
|
for (const input of bindings) {
|
|
2805
3574
|
result.push(await this.create(input));
|
|
@@ -2807,7 +3576,13 @@ var LocalChannelBindingStore = class {
|
|
|
2807
3576
|
return result;
|
|
2808
3577
|
}
|
|
2809
3578
|
async export(params) {
|
|
2810
|
-
return this.list({
|
|
3579
|
+
return this.list({
|
|
3580
|
+
tenantId: params.tenantId,
|
|
3581
|
+
excludeChannels: ["room"],
|
|
3582
|
+
excludeInstallationIdPrefixes: ["room-internal:"],
|
|
3583
|
+
limit: 1e4,
|
|
3584
|
+
offset: 0
|
|
3585
|
+
});
|
|
2811
3586
|
}
|
|
2812
3587
|
async getById(id) {
|
|
2813
3588
|
const row = this.db.prepare(
|
|
@@ -2861,33 +3636,33 @@ var LocalChannelInstallationStore = class {
|
|
|
2861
3636
|
const row = this.db.prepare(
|
|
2862
3637
|
`SELECT * FROM lt_channel_installations WHERE id = ?`
|
|
2863
3638
|
).get(installationId);
|
|
2864
|
-
return row ?
|
|
3639
|
+
return row ? mapRow5(row) : null;
|
|
2865
3640
|
}
|
|
2866
3641
|
async getInstallationsByTenant(tenantId, channel) {
|
|
2867
|
-
let
|
|
3642
|
+
let rows2;
|
|
2868
3643
|
if (channel) {
|
|
2869
|
-
|
|
3644
|
+
rows2 = this.db.prepare(
|
|
2870
3645
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? AND channel = ? ORDER BY created_at DESC`
|
|
2871
3646
|
).all(tenantId, channel);
|
|
2872
3647
|
} else {
|
|
2873
|
-
|
|
3648
|
+
rows2 = this.db.prepare(
|
|
2874
3649
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2875
3650
|
).all(tenantId);
|
|
2876
3651
|
}
|
|
2877
|
-
return
|
|
3652
|
+
return rows2.map(mapRow5);
|
|
2878
3653
|
}
|
|
2879
3654
|
async getAllInstallations(channel) {
|
|
2880
|
-
let
|
|
3655
|
+
let rows2;
|
|
2881
3656
|
if (channel) {
|
|
2882
|
-
|
|
3657
|
+
rows2 = this.db.prepare(
|
|
2883
3658
|
`SELECT * FROM lt_channel_installations WHERE channel = ? ORDER BY created_at DESC`
|
|
2884
3659
|
).all(channel);
|
|
2885
3660
|
} else {
|
|
2886
|
-
|
|
3661
|
+
rows2 = this.db.prepare(
|
|
2887
3662
|
`SELECT * FROM lt_channel_installations ORDER BY created_at DESC`
|
|
2888
3663
|
).all();
|
|
2889
3664
|
}
|
|
2890
|
-
return
|
|
3665
|
+
return rows2.map(mapRow5);
|
|
2891
3666
|
}
|
|
2892
3667
|
async createInstallation(tenantId, installationId, data) {
|
|
2893
3668
|
const now = nowISO();
|
|
@@ -2912,7 +3687,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2912
3687
|
tenantId,
|
|
2913
3688
|
channel: data.channel,
|
|
2914
3689
|
name: data.name,
|
|
2915
|
-
config: data.config,
|
|
3690
|
+
config: { ...data.config },
|
|
2916
3691
|
enabled: data.enabled !== false,
|
|
2917
3692
|
fallbackAgentId: data.fallbackAgentId,
|
|
2918
3693
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? false,
|
|
@@ -2922,7 +3697,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2922
3697
|
}
|
|
2923
3698
|
async updateInstallation(tenantId, installationId, updates) {
|
|
2924
3699
|
const existing = await this.getInstallationById(installationId);
|
|
2925
|
-
if (!existing) return null;
|
|
3700
|
+
if (!existing || existing.tenantId !== tenantId) return null;
|
|
2926
3701
|
const setClauses = [];
|
|
2927
3702
|
const values = [];
|
|
2928
3703
|
if (updates.name !== void 0) {
|
|
@@ -2949,9 +3724,10 @@ var LocalChannelInstallationStore = class {
|
|
|
2949
3724
|
const now = nowISO();
|
|
2950
3725
|
setClauses.push("updated_at = ?");
|
|
2951
3726
|
values.push(now);
|
|
3727
|
+
values.push(tenantId);
|
|
2952
3728
|
values.push(installationId);
|
|
2953
3729
|
this.db.prepare(
|
|
2954
|
-
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE id = ?`
|
|
3730
|
+
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`
|
|
2955
3731
|
).run(...values);
|
|
2956
3732
|
return this.getInstallationById(installationId);
|
|
2957
3733
|
}
|
|
@@ -2962,7 +3738,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2962
3738
|
return result.changes > 0;
|
|
2963
3739
|
}
|
|
2964
3740
|
};
|
|
2965
|
-
function
|
|
3741
|
+
function mapRow5(row) {
|
|
2966
3742
|
return {
|
|
2967
3743
|
id: row.id,
|
|
2968
3744
|
tenantId: row.tenant_id,
|
|
@@ -3013,18 +3789,18 @@ var LocalA2AApiKeyStore = class {
|
|
|
3013
3789
|
this.repairManagementIds();
|
|
3014
3790
|
}
|
|
3015
3791
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
3016
|
-
ensureColumn(table, column,
|
|
3792
|
+
ensureColumn(table, column, ddl2) {
|
|
3017
3793
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
3018
3794
|
if (!cols.some((c) => c.name === column)) {
|
|
3019
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
3795
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
3020
3796
|
}
|
|
3021
3797
|
}
|
|
3022
3798
|
repairManagementIds() {
|
|
3023
|
-
const
|
|
3799
|
+
const rows2 = this.db.prepare(
|
|
3024
3800
|
`SELECT rowid, id FROM lt_a2a_api_keys`
|
|
3025
3801
|
).all();
|
|
3026
3802
|
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;
|
|
3027
|
-
for (const row of
|
|
3803
|
+
for (const row of rows2) {
|
|
3028
3804
|
if (!row.id || !uuidPattern.test(row.id)) {
|
|
3029
3805
|
this.db.prepare(
|
|
3030
3806
|
`UPDATE lt_a2a_api_keys SET id = ? WHERE rowid = ?`
|
|
@@ -3033,10 +3809,10 @@ var LocalA2AApiKeyStore = class {
|
|
|
3033
3809
|
}
|
|
3034
3810
|
}
|
|
3035
3811
|
async findByKey(key) {
|
|
3036
|
-
const
|
|
3812
|
+
const rows2 = this.db.prepare(
|
|
3037
3813
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3038
3814
|
).all();
|
|
3039
|
-
for (const row of
|
|
3815
|
+
for (const row of rows2) {
|
|
3040
3816
|
try {
|
|
3041
3817
|
if ((0, import_core3.decrypt)(row.key_value) === key) return mapRowToRecord(row);
|
|
3042
3818
|
} catch {
|
|
@@ -3053,17 +3829,17 @@ var LocalA2AApiKeyStore = class {
|
|
|
3053
3829
|
async list(params) {
|
|
3054
3830
|
const limit = params.limit || 100;
|
|
3055
3831
|
const offset = params.offset || 0;
|
|
3056
|
-
let
|
|
3832
|
+
let rows2;
|
|
3057
3833
|
if (params.tenantId) {
|
|
3058
|
-
|
|
3834
|
+
rows2 = this.db.prepare(
|
|
3059
3835
|
`SELECT * FROM lt_a2a_api_keys WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
3060
3836
|
).all(params.tenantId, limit, offset);
|
|
3061
3837
|
} else {
|
|
3062
|
-
|
|
3838
|
+
rows2 = this.db.prepare(
|
|
3063
3839
|
`SELECT * FROM lt_a2a_api_keys ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
3064
3840
|
).all(limit, offset);
|
|
3065
3841
|
}
|
|
3066
|
-
return
|
|
3842
|
+
return rows2.map(mapRowToRecord);
|
|
3067
3843
|
}
|
|
3068
3844
|
async create(input) {
|
|
3069
3845
|
const id = (0, import_crypto3.randomUUID)();
|
|
@@ -3073,7 +3849,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3073
3849
|
`INSERT INTO lt_a2a_api_keys (id, key_value, tenant_id, project_id, assistant_ids, label, created_at, updated_at)
|
|
3074
3850
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3075
3851
|
).run(id, (0, import_core3.encrypt)(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null, now, now);
|
|
3076
|
-
const
|
|
3852
|
+
const record2 = {
|
|
3077
3853
|
id,
|
|
3078
3854
|
key,
|
|
3079
3855
|
tenantId: input.tenantId,
|
|
@@ -3084,7 +3860,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3084
3860
|
createdAt: parseISO(now),
|
|
3085
3861
|
updatedAt: parseISO(now)
|
|
3086
3862
|
};
|
|
3087
|
-
return
|
|
3863
|
+
return record2;
|
|
3088
3864
|
}
|
|
3089
3865
|
async disable(id) {
|
|
3090
3866
|
const now = nowISO();
|
|
@@ -3112,22 +3888,22 @@ var LocalA2AApiKeyStore = class {
|
|
|
3112
3888
|
).run((0, import_core3.encrypt)(key), now, id);
|
|
3113
3889
|
const row = this.db.prepare(`SELECT * FROM lt_a2a_api_keys WHERE id = ?`).get(id);
|
|
3114
3890
|
if (!row) throw new Error(`A2A API key not found: ${id}`);
|
|
3115
|
-
const
|
|
3116
|
-
|
|
3117
|
-
return
|
|
3891
|
+
const record2 = mapRowToRecord(row);
|
|
3892
|
+
record2.key = key;
|
|
3893
|
+
return record2;
|
|
3118
3894
|
}
|
|
3119
3895
|
async delete(id) {
|
|
3120
3896
|
this.db.prepare(`DELETE FROM lt_a2a_api_keys WHERE id = ?`).run(id);
|
|
3121
3897
|
}
|
|
3122
3898
|
async loadIntoMap() {
|
|
3123
|
-
const
|
|
3899
|
+
const rows2 = this.db.prepare(
|
|
3124
3900
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3125
3901
|
).all();
|
|
3126
|
-
const
|
|
3127
|
-
for (const row of
|
|
3902
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3903
|
+
for (const row of rows2) {
|
|
3128
3904
|
try {
|
|
3129
3905
|
const key = (0, import_core3.decrypt)(row.key_value);
|
|
3130
|
-
|
|
3906
|
+
map2.set(key, {
|
|
3131
3907
|
key,
|
|
3132
3908
|
tenantId: row.tenant_id,
|
|
3133
3909
|
projectId: row.project_id,
|
|
@@ -3136,7 +3912,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3136
3912
|
} catch {
|
|
3137
3913
|
}
|
|
3138
3914
|
}
|
|
3139
|
-
return
|
|
3915
|
+
return map2;
|
|
3140
3916
|
}
|
|
3141
3917
|
};
|
|
3142
3918
|
function mapRowToRecord(row) {
|
|
@@ -3160,6 +3936,7 @@ function mapRowToRecord(row) {
|
|
|
3160
3936
|
}
|
|
3161
3937
|
|
|
3162
3938
|
// src/stores/LocalThreadMessageQueueStore.ts
|
|
3939
|
+
var import_protocols4 = require("@axiom-lattice/protocols");
|
|
3163
3940
|
var import_crypto4 = require("crypto");
|
|
3164
3941
|
var DDL17 = `
|
|
3165
3942
|
CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
@@ -3176,6 +3953,8 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
|
3176
3953
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
3177
3954
|
command TEXT,
|
|
3178
3955
|
custom_run_config TEXT,
|
|
3956
|
+
trusted_run_context TEXT,
|
|
3957
|
+
execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
|
|
3179
3958
|
created_at TEXT NOT NULL
|
|
3180
3959
|
);
|
|
3181
3960
|
CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
|
|
@@ -3183,10 +3962,20 @@ CREATE INDEX IF NOT EXISTS idx_lt_tmq_pending ON lt_thread_message_queue(status,
|
|
|
3183
3962
|
`;
|
|
3184
3963
|
var LocalThreadMessageQueueStore = class {
|
|
3185
3964
|
constructor(db) {
|
|
3965
|
+
this.capacityTail = Promise.resolve();
|
|
3186
3966
|
this.db = db;
|
|
3187
3967
|
ensureTable(db, DDL17);
|
|
3968
|
+
const columns5 = db.prepare("PRAGMA table_info(lt_thread_message_queue)").all();
|
|
3969
|
+
const names = new Set(columns5.map(({ name }) => name));
|
|
3970
|
+
if (!names.has("trusted_run_context")) {
|
|
3971
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN trusted_run_context TEXT");
|
|
3972
|
+
}
|
|
3973
|
+
if (!names.has("execution_mode")) {
|
|
3974
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup')");
|
|
3975
|
+
}
|
|
3188
3976
|
}
|
|
3189
3977
|
async addMessage(params) {
|
|
3978
|
+
const trusted = validateQueueTrust(params);
|
|
3190
3979
|
const now = nowISO();
|
|
3191
3980
|
const id = params.id || (0, import_crypto4.randomUUID)();
|
|
3192
3981
|
const seqRow = this.db.prepare(
|
|
@@ -3194,94 +3983,163 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3194
3983
|
).get(params.threadId);
|
|
3195
3984
|
const nextSeq = seqRow.next_seq;
|
|
3196
3985
|
this.db.prepare(
|
|
3197
|
-
`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)
|
|
3198
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3986
|
+
`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)
|
|
3987
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3199
3988
|
).run(
|
|
3200
3989
|
id,
|
|
3201
3990
|
params.threadId,
|
|
3202
3991
|
params.tenantId,
|
|
3203
3992
|
params.assistantId,
|
|
3204
|
-
params.workspaceId
|
|
3205
|
-
params.projectId
|
|
3993
|
+
params.workspaceId ?? null,
|
|
3994
|
+
params.projectId ?? null,
|
|
3206
3995
|
JSON.stringify(params.content),
|
|
3207
3996
|
params.type || "human",
|
|
3208
3997
|
nextSeq,
|
|
3209
|
-
params.priority
|
|
3998
|
+
params.priority ?? 0,
|
|
3210
3999
|
params.command ? JSON.stringify(params.command) : null,
|
|
3211
4000
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4001
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4002
|
+
trusted.executionMode ?? null,
|
|
3212
4003
|
now
|
|
3213
4004
|
);
|
|
3214
4005
|
return this.getById(id);
|
|
3215
4006
|
}
|
|
4007
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
4008
|
+
const trusted = validateQueueTrust(params);
|
|
4009
|
+
let release;
|
|
4010
|
+
const previous = this.capacityTail;
|
|
4011
|
+
this.capacityTail = new Promise((resolve) => {
|
|
4012
|
+
release = resolve;
|
|
4013
|
+
});
|
|
4014
|
+
await previous;
|
|
4015
|
+
try {
|
|
4016
|
+
const scope = {
|
|
4017
|
+
tenantId: params.tenantId,
|
|
4018
|
+
assistantId: params.assistantId,
|
|
4019
|
+
workspaceId: params.workspaceId,
|
|
4020
|
+
projectId: params.projectId
|
|
4021
|
+
};
|
|
4022
|
+
const filter = scopeClause(scope);
|
|
4023
|
+
const id = params.id || (0, import_crypto4.randomUUID)();
|
|
4024
|
+
const limit = maxSize === Infinity ? -1 : maxSize;
|
|
4025
|
+
const result = this.db.prepare(
|
|
4026
|
+
`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)
|
|
4027
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?,
|
|
4028
|
+
(SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
|
|
4029
|
+
?, ?, ?, ?, ?,
|
|
4030
|
+
?
|
|
4031
|
+
WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
|
|
4032
|
+
).run(
|
|
4033
|
+
id,
|
|
4034
|
+
params.threadId,
|
|
4035
|
+
params.tenantId,
|
|
4036
|
+
params.assistantId,
|
|
4037
|
+
params.workspaceId ?? null,
|
|
4038
|
+
params.projectId ?? null,
|
|
4039
|
+
JSON.stringify(params.content),
|
|
4040
|
+
params.type || "human",
|
|
4041
|
+
params.threadId,
|
|
4042
|
+
params.priority ?? 0,
|
|
4043
|
+
params.command ? JSON.stringify(params.command) : null,
|
|
4044
|
+
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4045
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4046
|
+
trusted.executionMode ?? null,
|
|
4047
|
+
nowISO(),
|
|
4048
|
+
limit,
|
|
4049
|
+
params.threadId,
|
|
4050
|
+
...filter.params,
|
|
4051
|
+
limit
|
|
4052
|
+
);
|
|
4053
|
+
return result.changes > 0;
|
|
4054
|
+
} finally {
|
|
4055
|
+
release();
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
3216
4058
|
async addMessageAtHead(params) {
|
|
4059
|
+
const trusted = validateQueueTrust(params);
|
|
3217
4060
|
const now = nowISO();
|
|
3218
4061
|
const id = params.id || (0, import_crypto4.randomUUID)();
|
|
3219
4062
|
const seqRow = this.db.prepare(
|
|
3220
4063
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lt_thread_message_queue WHERE thread_id = ?`
|
|
3221
4064
|
).get(params.threadId);
|
|
3222
4065
|
this.db.prepare(
|
|
3223
|
-
`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)
|
|
3224
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?)`
|
|
4066
|
+
`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)
|
|
4067
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?, ?, ?)`
|
|
3225
4068
|
).run(
|
|
3226
4069
|
id,
|
|
3227
4070
|
params.threadId,
|
|
3228
4071
|
params.tenantId,
|
|
3229
4072
|
params.assistantId,
|
|
3230
|
-
params.workspaceId
|
|
3231
|
-
params.projectId
|
|
4073
|
+
params.workspaceId ?? null,
|
|
4074
|
+
params.projectId ?? null,
|
|
3232
4075
|
JSON.stringify(params.content),
|
|
3233
4076
|
params.type || "human",
|
|
3234
4077
|
seqRow.next_seq,
|
|
3235
4078
|
params.command ? JSON.stringify(params.command) : null,
|
|
3236
4079
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4080
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4081
|
+
trusted.executionMode ?? null,
|
|
3237
4082
|
now
|
|
3238
4083
|
);
|
|
3239
4084
|
return this.getById(id);
|
|
3240
4085
|
}
|
|
3241
|
-
async getPendingMessages(threadId) {
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
4086
|
+
async getPendingMessages(threadId, scope) {
|
|
4087
|
+
const filter = scopeClause(scope);
|
|
4088
|
+
const rows2 = this.db.prepare(
|
|
4089
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
4090
|
+
).all(threadId, ...filter.params);
|
|
4091
|
+
return rows2.map(rowToMessage);
|
|
4092
|
+
}
|
|
4093
|
+
async getProcessingMessages(threadId, scope) {
|
|
4094
|
+
const filter = scopeClause(scope);
|
|
4095
|
+
const rows2 = this.db.prepare(
|
|
4096
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
4097
|
+
).all(threadId, ...filter.params);
|
|
4098
|
+
return rows2.map(rowToMessage);
|
|
4099
|
+
}
|
|
4100
|
+
async getQueueSize(threadId, scope) {
|
|
4101
|
+
const filter = scopeClause(scope);
|
|
3254
4102
|
const row = this.db.prepare(
|
|
3255
|
-
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'`
|
|
3256
|
-
).get(threadId);
|
|
4103
|
+
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}`
|
|
4104
|
+
).get(threadId, ...filter.params);
|
|
3257
4105
|
return row.count;
|
|
3258
4106
|
}
|
|
3259
4107
|
async getThreadsWithPendingMessages() {
|
|
3260
|
-
const
|
|
3261
|
-
`SELECT
|
|
4108
|
+
const rows2 = this.db.prepare(
|
|
4109
|
+
`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`
|
|
3262
4110
|
).all();
|
|
3263
|
-
return
|
|
4111
|
+
return rows2.map((r) => ({
|
|
3264
4112
|
tenantId: r.tenant_id,
|
|
3265
4113
|
assistantId: r.assistant_id,
|
|
3266
4114
|
threadId: r.thread_id,
|
|
3267
|
-
workspaceId: r.workspace_id
|
|
3268
|
-
projectId: r.project_id
|
|
4115
|
+
workspaceId: r.workspace_id,
|
|
4116
|
+
projectId: r.project_id
|
|
3269
4117
|
}));
|
|
3270
4118
|
}
|
|
3271
|
-
async removeMessage(messageId) {
|
|
3272
|
-
const
|
|
4119
|
+
async removeMessage(messageId, scope) {
|
|
4120
|
+
const filter = scopeClause(scope);
|
|
4121
|
+
const result = this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
|
|
3273
4122
|
return result.changes > 0;
|
|
3274
4123
|
}
|
|
3275
|
-
async clearMessages(threadId) {
|
|
3276
|
-
|
|
4124
|
+
async clearMessages(threadId, scope) {
|
|
4125
|
+
const filter = scopeClause(scope);
|
|
4126
|
+
this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE thread_id = ?${filter.sql}`).run(threadId, ...filter.params);
|
|
3277
4127
|
}
|
|
3278
|
-
async markProcessing(messageId) {
|
|
3279
|
-
|
|
4128
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
4129
|
+
const filter = scopeClause(scope);
|
|
4130
|
+
if (customRunConfig === void 0) {
|
|
4131
|
+
this.db.prepare(`UPDATE lt_thread_message_queue SET status = 'processing' WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4134
|
+
this.db.prepare(
|
|
4135
|
+
`UPDATE lt_thread_message_queue SET status = 'processing', custom_run_config = ? WHERE id = ?${filter.sql}`
|
|
4136
|
+
).run(JSON.stringify(customRunConfig), messageId, ...filter.params);
|
|
3280
4137
|
}
|
|
3281
|
-
async resetProcessingToPending(threadId) {
|
|
4138
|
+
async resetProcessingToPending(threadId, scope) {
|
|
4139
|
+
const filter = scopeClause(scope);
|
|
3282
4140
|
const result = this.db.prepare(
|
|
3283
|
-
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'`
|
|
3284
|
-
).run(threadId);
|
|
4141
|
+
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
|
|
4142
|
+
).run(threadId, ...filter.params);
|
|
3285
4143
|
return result.changes;
|
|
3286
4144
|
}
|
|
3287
4145
|
getById(id) {
|
|
@@ -3289,6 +4147,20 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3289
4147
|
return rowToMessage(row);
|
|
3290
4148
|
}
|
|
3291
4149
|
};
|
|
4150
|
+
function validateQueueTrust(params) {
|
|
4151
|
+
return {
|
|
4152
|
+
trustedRunContext: params.trusted_run_context === void 0 ? void 0 : (0, import_protocols4.parseTrustedRunContext)(params.trusted_run_context),
|
|
4153
|
+
executionMode: params.execution_mode === void 0 ? void 0 : (0, import_protocols4.parseQueuedExecutionMode)(params.execution_mode)
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
function scopeClause(scope) {
|
|
4157
|
+
if (!scope) return { sql: "", params: [] };
|
|
4158
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
4159
|
+
return {
|
|
4160
|
+
sql: entries.map(([key, value]) => ` AND ${key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)} ${value == null ? "IS NULL" : "= ?"}`).join(""),
|
|
4161
|
+
params: entries.flatMap(([, value]) => value == null ? [] : [value])
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
3292
4164
|
function rowToMessage(row) {
|
|
3293
4165
|
return {
|
|
3294
4166
|
id: row.id,
|
|
@@ -3298,7 +4170,9 @@ function rowToMessage(row) {
|
|
|
3298
4170
|
createdAt: parseISO(row.created_at),
|
|
3299
4171
|
priority: row.priority || 0,
|
|
3300
4172
|
command: row.command ? JSON.parse(row.command) : void 0,
|
|
3301
|
-
custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0
|
|
4173
|
+
custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0,
|
|
4174
|
+
trusted_run_context: row.trusted_run_context ? (0, import_protocols4.parseTrustedRunContext)(JSON.parse(row.trusted_run_context)) : void 0,
|
|
4175
|
+
execution_mode: row.execution_mode == null ? void 0 : (0, import_protocols4.parseQueuedExecutionMode)(row.execution_mode)
|
|
3302
4176
|
};
|
|
3303
4177
|
}
|
|
3304
4178
|
|
|
@@ -3325,10 +4199,10 @@ var LocalSkillStore = class {
|
|
|
3325
4199
|
ensureTable(db, DDL18);
|
|
3326
4200
|
}
|
|
3327
4201
|
async getAllSkills(tenantId, _context) {
|
|
3328
|
-
const
|
|
4202
|
+
const rows2 = this.db.prepare(
|
|
3329
4203
|
`SELECT * FROM lt_skills WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
3330
4204
|
).all(tenantId);
|
|
3331
|
-
return
|
|
4205
|
+
return rows2.map(mapRowToSkill);
|
|
3332
4206
|
}
|
|
3333
4207
|
async getSkillById(tenantId, id, _context) {
|
|
3334
4208
|
const row = this.db.prepare(
|
|
@@ -3433,16 +4307,16 @@ var LocalSkillStore = class {
|
|
|
3433
4307
|
return all.filter((s) => s.metadata?.[metadataKey] === metadataValue);
|
|
3434
4308
|
}
|
|
3435
4309
|
async filterByCompatibility(tenantId, compatibility, _context) {
|
|
3436
|
-
const
|
|
4310
|
+
const rows2 = this.db.prepare(
|
|
3437
4311
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND compatibility = ? ORDER BY created_at DESC`
|
|
3438
4312
|
).all(tenantId, compatibility);
|
|
3439
|
-
return
|
|
4313
|
+
return rows2.map(mapRowToSkill);
|
|
3440
4314
|
}
|
|
3441
4315
|
async filterByLicense(tenantId, license, _context) {
|
|
3442
|
-
const
|
|
4316
|
+
const rows2 = this.db.prepare(
|
|
3443
4317
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND license = ? ORDER BY created_at DESC`
|
|
3444
4318
|
).all(tenantId, license);
|
|
3445
|
-
return
|
|
4319
|
+
return rows2.map(mapRowToSkill);
|
|
3446
4320
|
}
|
|
3447
4321
|
async getSubSkills(tenantId, parentSkillName, _context) {
|
|
3448
4322
|
const all = await this.getAllSkills(tenantId);
|
|
@@ -3654,40 +4528,40 @@ var LocalScheduleStorage = class {
|
|
|
3654
4528
|
this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
|
|
3655
4529
|
}
|
|
3656
4530
|
async getActiveTasks() {
|
|
3657
|
-
const
|
|
4531
|
+
const rows2 = this.db.prepare(
|
|
3658
4532
|
`SELECT * FROM lt_scheduled_tasks WHERE status IN ('pending', 'paused') ORDER BY created_at ASC`
|
|
3659
4533
|
).all();
|
|
3660
|
-
return
|
|
4534
|
+
return rows2.map(mapRowToTask);
|
|
3661
4535
|
}
|
|
3662
4536
|
async getTasksByType(taskType) {
|
|
3663
|
-
const
|
|
4537
|
+
const rows2 = this.db.prepare(
|
|
3664
4538
|
`SELECT * FROM lt_scheduled_tasks WHERE task_type = ? ORDER BY created_at DESC`
|
|
3665
4539
|
).all(taskType);
|
|
3666
|
-
return
|
|
4540
|
+
return rows2.map(mapRowToTask);
|
|
3667
4541
|
}
|
|
3668
4542
|
async getTasksByStatus(status) {
|
|
3669
|
-
const
|
|
4543
|
+
const rows2 = this.db.prepare(
|
|
3670
4544
|
`SELECT * FROM lt_scheduled_tasks WHERE status = ? ORDER BY created_at DESC`
|
|
3671
4545
|
).all(status);
|
|
3672
|
-
return
|
|
4546
|
+
return rows2.map(mapRowToTask);
|
|
3673
4547
|
}
|
|
3674
4548
|
async getTasksByExecutionType(executionType) {
|
|
3675
|
-
const
|
|
4549
|
+
const rows2 = this.db.prepare(
|
|
3676
4550
|
`SELECT * FROM lt_scheduled_tasks WHERE execution_type = ? ORDER BY created_at DESC`
|
|
3677
4551
|
).all(executionType);
|
|
3678
|
-
return
|
|
4552
|
+
return rows2.map(mapRowToTask);
|
|
3679
4553
|
}
|
|
3680
4554
|
async getTasksByAssistantId(assistantId) {
|
|
3681
|
-
const
|
|
4555
|
+
const rows2 = this.db.prepare(
|
|
3682
4556
|
`SELECT * FROM lt_scheduled_tasks WHERE assistant_id = ? ORDER BY created_at DESC`
|
|
3683
4557
|
).all(assistantId);
|
|
3684
|
-
return
|
|
4558
|
+
return rows2.map(mapRowToTask);
|
|
3685
4559
|
}
|
|
3686
4560
|
async getTasksByThreadId(threadId) {
|
|
3687
|
-
const
|
|
4561
|
+
const rows2 = this.db.prepare(
|
|
3688
4562
|
`SELECT * FROM lt_scheduled_tasks WHERE thread_id = ? ORDER BY created_at DESC`
|
|
3689
4563
|
).all(threadId);
|
|
3690
|
-
return
|
|
4564
|
+
return rows2.map(mapRowToTask);
|
|
3691
4565
|
}
|
|
3692
4566
|
async getAllTasks(filters) {
|
|
3693
4567
|
const conditions = [];
|
|
@@ -3727,8 +4601,8 @@ var LocalScheduleStorage = class {
|
|
|
3727
4601
|
query += ` OFFSET ?`;
|
|
3728
4602
|
values.push(filters.offset);
|
|
3729
4603
|
}
|
|
3730
|
-
const
|
|
3731
|
-
return
|
|
4604
|
+
const rows2 = this.db.prepare(query).all(...values);
|
|
4605
|
+
return rows2.map(mapRowToTask);
|
|
3732
4606
|
}
|
|
3733
4607
|
async countTasks(filters) {
|
|
3734
4608
|
const conditions = [];
|
|
@@ -3801,6 +4675,15 @@ function mapRowToTask(row) {
|
|
|
3801
4675
|
// src/stores/LocalTaskStore.ts
|
|
3802
4676
|
var import_crypto5 = require("crypto");
|
|
3803
4677
|
var MAX_CANONICAL_TASK_TIME = "9999-12-31T23:59:59.999Z";
|
|
4678
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
4679
|
+
"pending",
|
|
4680
|
+
"in_progress",
|
|
4681
|
+
"review",
|
|
4682
|
+
"failed",
|
|
4683
|
+
"interrupted",
|
|
4684
|
+
"completed",
|
|
4685
|
+
"cancelled"
|
|
4686
|
+
]);
|
|
3804
4687
|
var NEXT_UPDATED_AT_SQL = `updated_at = CASE
|
|
3805
4688
|
WHEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) = updated_at AND updated_at >= ?
|
|
3806
4689
|
THEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds')
|
|
@@ -3873,6 +4756,9 @@ function nextISO(previous) {
|
|
|
3873
4756
|
const previousMs = new Date(previous).getTime();
|
|
3874
4757
|
return new Date(Math.max(Date.now(), previousMs + 1)).toISOString();
|
|
3875
4758
|
}
|
|
4759
|
+
function assertPage(limit, offset = 0) {
|
|
4760
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0) throw new RangeError("Invalid task page");
|
|
4761
|
+
}
|
|
3876
4762
|
var LocalTaskStore = class {
|
|
3877
4763
|
constructor(db) {
|
|
3878
4764
|
this.db = db;
|
|
@@ -3886,10 +4772,10 @@ var LocalTaskStore = class {
|
|
|
3886
4772
|
this.ensureColumn("lt_tasks", "files", "files TEXT");
|
|
3887
4773
|
}
|
|
3888
4774
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
3889
|
-
ensureColumn(table, column,
|
|
4775
|
+
ensureColumn(table, column, ddl2) {
|
|
3890
4776
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
3891
4777
|
if (!cols.some((c) => c.name === column)) {
|
|
3892
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
4778
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
3893
4779
|
}
|
|
3894
4780
|
}
|
|
3895
4781
|
async create(params) {
|
|
@@ -3955,7 +4841,8 @@ var LocalTaskStore = class {
|
|
|
3955
4841
|
conditions.push("workspace_id = ?");
|
|
3956
4842
|
params.push(filter.workspaceId);
|
|
3957
4843
|
}
|
|
3958
|
-
if (filter.projectId)
|
|
4844
|
+
if (filter.projectId === null) conditions.push("(project_id IS NULL OR project_id = '' OR project_id = 'default')");
|
|
4845
|
+
else if (filter.projectId !== void 0) {
|
|
3959
4846
|
conditions.push("project_id = ?");
|
|
3960
4847
|
params.push(filter.projectId);
|
|
3961
4848
|
}
|
|
@@ -3985,10 +4872,44 @@ var LocalTaskStore = class {
|
|
|
3985
4872
|
const where = conditions.join(" AND ");
|
|
3986
4873
|
const limit = filter.limit || 100;
|
|
3987
4874
|
const offset = filter.offset || 0;
|
|
3988
|
-
const
|
|
3989
|
-
`SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
4875
|
+
const rows2 = this.db.prepare(
|
|
4876
|
+
`SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`
|
|
3990
4877
|
).all(...params, limit, offset);
|
|
3991
|
-
return
|
|
4878
|
+
return rows2.map(mapRowToTask2);
|
|
4879
|
+
}
|
|
4880
|
+
/** Lists exact project tasks containing a string dependency. */
|
|
4881
|
+
async listDependents(query) {
|
|
4882
|
+
assertPage(query.limit, query.offset);
|
|
4883
|
+
if (query.statuses.length === 0 || query.statuses.some((status) => !TASK_STATUSES.has(status))) {
|
|
4884
|
+
throw new RangeError("Invalid task statuses");
|
|
4885
|
+
}
|
|
4886
|
+
const rows2 = this.db.prepare(
|
|
4887
|
+
`SELECT task.* FROM lt_tasks AS task
|
|
4888
|
+
WHERE task.tenant_id = ? AND task.workspace_id = ? AND task.project_id = ?
|
|
4889
|
+
AND task.status IN (${query.statuses.map(() => "?").join(", ")})
|
|
4890
|
+
AND task.dependencies IS NOT NULL
|
|
4891
|
+
AND json_valid(task.dependencies) = 1
|
|
4892
|
+
AND json_type(CASE WHEN json_valid(task.dependencies)=1
|
|
4893
|
+
THEN task.dependencies ELSE '[]' END) = 'array'
|
|
4894
|
+
AND EXISTS (
|
|
4895
|
+
SELECT 1 FROM json_each(
|
|
4896
|
+
CASE WHEN json_valid(task.dependencies)=1 THEN
|
|
4897
|
+
CASE WHEN json_type(task.dependencies)='array' THEN task.dependencies ELSE '[]' END
|
|
4898
|
+
ELSE '[]' END
|
|
4899
|
+
) AS dependency
|
|
4900
|
+
WHERE dependency.type = 'text' AND dependency.value = ?
|
|
4901
|
+
)
|
|
4902
|
+
ORDER BY task.created_at DESC, task.id DESC LIMIT ? OFFSET ?`
|
|
4903
|
+
).all(
|
|
4904
|
+
query.tenantId,
|
|
4905
|
+
query.workspaceId,
|
|
4906
|
+
query.projectId,
|
|
4907
|
+
...query.statuses,
|
|
4908
|
+
query.dependencyTaskId,
|
|
4909
|
+
query.limit,
|
|
4910
|
+
query.offset
|
|
4911
|
+
);
|
|
4912
|
+
return rows2.map(mapRowToTask2);
|
|
3992
4913
|
}
|
|
3993
4914
|
async update(tenantId, id, updates) {
|
|
3994
4915
|
const existing = await this.getById(tenantId, id);
|
|
@@ -4340,6 +5261,54 @@ var LocalTaskStore = class {
|
|
|
4340
5261
|
if (result.changes === 0) return null;
|
|
4341
5262
|
return this.getById(tenantId, id);
|
|
4342
5263
|
}
|
|
5264
|
+
/** Atomically update only while status, timestamp, owner, and Project scope match. */
|
|
5265
|
+
async updateIfSnapshot(tenantId, id, updates, snapshot) {
|
|
5266
|
+
const expectedIso = new Date(snapshot.updatedAt).toISOString();
|
|
5267
|
+
if (expectedIso === MAX_CANONICAL_TASK_TIME) return null;
|
|
5268
|
+
const setClauses = ["updated_at = ?"];
|
|
5269
|
+
const values = [nextISO(expectedIso)];
|
|
5270
|
+
const fields = [
|
|
5271
|
+
["title", "title"],
|
|
5272
|
+
["description", "description"],
|
|
5273
|
+
["status", "status"],
|
|
5274
|
+
["priority", "priority"],
|
|
5275
|
+
["dueDate", "due_date"],
|
|
5276
|
+
["metadata", "metadata", true],
|
|
5277
|
+
["parentId", "parent_id"],
|
|
5278
|
+
["sourceId", "source_id"],
|
|
5279
|
+
["context", "context", true],
|
|
5280
|
+
["ownerType", "owner_type"],
|
|
5281
|
+
["ownerId", "owner_id"],
|
|
5282
|
+
["requireReview", "require_review"],
|
|
5283
|
+
["dependencies", "dependencies", true],
|
|
5284
|
+
["result", "result"],
|
|
5285
|
+
["failureReason", "failure_reason"],
|
|
5286
|
+
["workspaceId", "workspace_id"],
|
|
5287
|
+
["projectId", "project_id"],
|
|
5288
|
+
["files", "files", true]
|
|
5289
|
+
];
|
|
5290
|
+
for (const [field, column, json] of fields) {
|
|
5291
|
+
const value = updates[field];
|
|
5292
|
+
if (value === void 0) continue;
|
|
5293
|
+
setClauses.push(`${column} = ?`);
|
|
5294
|
+
values.push(json && value !== null ? JSON.stringify(value) : field === "requireReview" ? value ? 1 : 0 : value);
|
|
5295
|
+
}
|
|
5296
|
+
values.push(
|
|
5297
|
+
tenantId,
|
|
5298
|
+
id,
|
|
5299
|
+
snapshot.status,
|
|
5300
|
+
expectedIso,
|
|
5301
|
+
snapshot.ownerType,
|
|
5302
|
+
snapshot.ownerId,
|
|
5303
|
+
snapshot.workspaceId,
|
|
5304
|
+
snapshot.projectId
|
|
5305
|
+
);
|
|
5306
|
+
const result = this.db.prepare(
|
|
5307
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND status = ?
|
|
5308
|
+
AND updated_at = ? AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
|
|
5309
|
+
).run(...values);
|
|
5310
|
+
return result.changes === 0 ? null : this.getById(tenantId, id);
|
|
5311
|
+
}
|
|
4343
5312
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
4344
5313
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
4345
5314
|
if (expectedStatuses.length === 0) return null;
|
|
@@ -4440,10 +5409,36 @@ var LocalTaskStore = class {
|
|
|
4440
5409
|
).run(tenantId, id);
|
|
4441
5410
|
return result.changes > 0;
|
|
4442
5411
|
}
|
|
5412
|
+
/** Atomically delete only while status, timestamp, owner, and Project scope match. */
|
|
5413
|
+
async deleteIfSnapshot(tenantId, id, snapshot) {
|
|
5414
|
+
const result = this.db.prepare(
|
|
5415
|
+
`DELETE FROM lt_tasks WHERE tenant_id = ? AND id = ? AND status = ? AND updated_at = ?
|
|
5416
|
+
AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
|
|
5417
|
+
).run(
|
|
5418
|
+
tenantId,
|
|
5419
|
+
id,
|
|
5420
|
+
snapshot.status,
|
|
5421
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
5422
|
+
snapshot.ownerType,
|
|
5423
|
+
snapshot.ownerId,
|
|
5424
|
+
snapshot.workspaceId,
|
|
5425
|
+
snapshot.projectId
|
|
5426
|
+
);
|
|
5427
|
+
return result.changes > 0;
|
|
5428
|
+
}
|
|
4443
5429
|
};
|
|
4444
5430
|
|
|
4445
5431
|
// src/stores/LocalTaskWorkItemStore.ts
|
|
5432
|
+
var import_protocols5 = require("@axiom-lattice/protocols");
|
|
4446
5433
|
var import_uuid2 = require("uuid");
|
|
5434
|
+
var PROJECT_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
5435
|
+
"in_progress",
|
|
5436
|
+
"interrupted",
|
|
5437
|
+
"failed",
|
|
5438
|
+
"completed",
|
|
5439
|
+
"cancelled",
|
|
5440
|
+
"reassigned"
|
|
5441
|
+
]);
|
|
4447
5442
|
var DDL21 = `
|
|
4448
5443
|
CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
4449
5444
|
id TEXT NOT NULL,
|
|
@@ -4461,6 +5456,15 @@ CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
|
4461
5456
|
PRIMARY KEY (tenant_id, id)
|
|
4462
5457
|
);
|
|
4463
5458
|
`;
|
|
5459
|
+
function parseDetail(raw) {
|
|
5460
|
+
if (!raw) return void 0;
|
|
5461
|
+
try {
|
|
5462
|
+
const parsed = JSON.parse(raw);
|
|
5463
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
5464
|
+
} catch {
|
|
5465
|
+
return void 0;
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
4464
5468
|
function mapRowToWorkItem(row) {
|
|
4465
5469
|
return {
|
|
4466
5470
|
id: row.id,
|
|
@@ -4470,7 +5474,7 @@ function mapRowToWorkItem(row) {
|
|
|
4470
5474
|
actor: row.actor,
|
|
4471
5475
|
threadId: row.thread_id ?? void 0,
|
|
4472
5476
|
summary: row.summary ?? void 0,
|
|
4473
|
-
detail:
|
|
5477
|
+
detail: parseDetail(row.detail),
|
|
4474
5478
|
attempt: row.attempt ?? void 0,
|
|
4475
5479
|
workspaceId: row.workspace_id ?? void 0,
|
|
4476
5480
|
projectId: row.project_id ?? void 0,
|
|
@@ -4478,6 +5482,34 @@ function mapRowToWorkItem(row) {
|
|
|
4478
5482
|
createdAt: parseISO(row.created_at)
|
|
4479
5483
|
};
|
|
4480
5484
|
}
|
|
5485
|
+
function assertAndCloneProjectLifecycleEvent(item) {
|
|
5486
|
+
let milliseconds;
|
|
5487
|
+
try {
|
|
5488
|
+
milliseconds = Date.prototype.getTime.call(item.createdAt);
|
|
5489
|
+
} catch {
|
|
5490
|
+
throw new Error("Invalid project lifecycle event row");
|
|
5491
|
+
}
|
|
5492
|
+
if (!Number.isFinite(milliseconds) || typeof item.id !== "string" || item.id.length === 0 || typeof item.eventKey !== "string" || item.eventKey.length === 0) {
|
|
5493
|
+
throw new Error("Invalid project lifecycle event row");
|
|
5494
|
+
}
|
|
5495
|
+
return { ...item, createdAt: new Date(milliseconds) };
|
|
5496
|
+
}
|
|
5497
|
+
function assertLifecyclePage(query) {
|
|
5498
|
+
if (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100 || query.actions.length === 0 || query.actions.some((action) => !PROJECT_LIFECYCLE_ACTIONS.has(action))) {
|
|
5499
|
+
throw new RangeError("Invalid project lifecycle event page");
|
|
5500
|
+
}
|
|
5501
|
+
if (!query.before) return null;
|
|
5502
|
+
let milliseconds;
|
|
5503
|
+
try {
|
|
5504
|
+
milliseconds = Date.prototype.getTime.call(query.before.createdAt);
|
|
5505
|
+
} catch {
|
|
5506
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
5507
|
+
}
|
|
5508
|
+
if (!Number.isFinite(milliseconds) || typeof query.before.id !== "string" || query.before.id.length === 0) {
|
|
5509
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
5510
|
+
}
|
|
5511
|
+
return milliseconds;
|
|
5512
|
+
}
|
|
4481
5513
|
var LocalTaskWorkItemStore = class {
|
|
4482
5514
|
constructor(db) {
|
|
4483
5515
|
this.db = db;
|
|
@@ -4487,12 +5519,20 @@ var LocalTaskWorkItemStore = class {
|
|
|
4487
5519
|
this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
|
|
4488
5520
|
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
4489
5521
|
ON lt_task_work_items (tenant_id, task_id, event_key);`);
|
|
5522
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
5523
|
+
ON lt_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC);`);
|
|
5524
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
5525
|
+
ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
|
|
5526
|
+
WHERE action = 'execution_reconciled';`);
|
|
5527
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_project_lifecycle
|
|
5528
|
+
ON lt_task_work_items (tenant_id, workspace_id, project_id, created_at DESC, id DESC)
|
|
5529
|
+
WHERE event_key IS NOT NULL;`);
|
|
4490
5530
|
}
|
|
4491
5531
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4492
|
-
ensureColumn(table, column,
|
|
5532
|
+
ensureColumn(table, column, ddl2) {
|
|
4493
5533
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
4494
5534
|
if (!cols.some((c) => c.name === column)) {
|
|
4495
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
5535
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
4496
5536
|
}
|
|
4497
5537
|
}
|
|
4498
5538
|
async create(params) {
|
|
@@ -4520,6 +5560,41 @@ var LocalTaskWorkItemStore = class {
|
|
|
4520
5560
|
).get(params.tenantId, id);
|
|
4521
5561
|
return mapRowToWorkItem(row);
|
|
4522
5562
|
}
|
|
5563
|
+
/** Atomically inserts a work item by selecting one exact task snapshot. */
|
|
5564
|
+
async createIfTaskSnapshot(params, snapshot) {
|
|
5565
|
+
const id = (0, import_uuid2.v4)();
|
|
5566
|
+
const result = this.db.prepare(
|
|
5567
|
+
`INSERT INTO lt_task_work_items
|
|
5568
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
5569
|
+
SELECT ?, task.tenant_id, task.id, ?, ?, ?, ?, ?, ?, task.workspace_id, task.project_id, ?
|
|
5570
|
+
FROM lt_tasks AS task
|
|
5571
|
+
WHERE task.tenant_id = ? AND task.id = ? AND task.status = ? AND task.updated_at = ?
|
|
5572
|
+
AND task.owner_type = ? AND task.owner_id = ? AND task.workspace_id IS ? AND task.project_id IS ?
|
|
5573
|
+
AND ? IS task.workspace_id AND ? IS task.project_id`
|
|
5574
|
+
).run(
|
|
5575
|
+
id,
|
|
5576
|
+
params.action,
|
|
5577
|
+
params.actor,
|
|
5578
|
+
params.threadId || null,
|
|
5579
|
+
params.summary || null,
|
|
5580
|
+
params.detail ? JSON.stringify(params.detail) : null,
|
|
5581
|
+
params.attempt ?? null,
|
|
5582
|
+
nowISO(),
|
|
5583
|
+
params.tenantId,
|
|
5584
|
+
params.taskId,
|
|
5585
|
+
snapshot.status,
|
|
5586
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
5587
|
+
snapshot.ownerType,
|
|
5588
|
+
snapshot.ownerId,
|
|
5589
|
+
snapshot.workspaceId,
|
|
5590
|
+
snapshot.projectId,
|
|
5591
|
+
params.workspaceId,
|
|
5592
|
+
params.projectId
|
|
5593
|
+
);
|
|
5594
|
+
if (result.changes === 0) return null;
|
|
5595
|
+
const row = this.db.prepare(`SELECT * FROM lt_task_work_items WHERE tenant_id = ? AND id = ?`).get(params.tenantId, id);
|
|
5596
|
+
return row ? mapRowToWorkItem(row) : null;
|
|
5597
|
+
}
|
|
4523
5598
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
4524
5599
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
4525
5600
|
const row = this.db.prepare(
|
|
@@ -4572,10 +5647,160 @@ var LocalTaskWorkItemStore = class {
|
|
|
4572
5647
|
const limit = filter.limit || 100;
|
|
4573
5648
|
const offset = filter.offset || 0;
|
|
4574
5649
|
const order = filter.order === "asc" ? "ASC" : "DESC";
|
|
4575
|
-
const
|
|
5650
|
+
const rows2 = this.db.prepare(
|
|
4576
5651
|
`SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at ${order}, id ${order} LIMIT ? OFFSET ?`
|
|
4577
5652
|
).all(...params, limit, offset);
|
|
4578
|
-
return
|
|
5653
|
+
return rows2.map(mapRowToWorkItem);
|
|
5654
|
+
}
|
|
5655
|
+
/** List pending execution results using one bounded SQLite anti-join query. */
|
|
5656
|
+
async listPendingExecutionResults(params) {
|
|
5657
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > import_protocols5.MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
5658
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${import_protocols5.MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
5659
|
+
error.code = "INVALID_LIMIT";
|
|
5660
|
+
throw error;
|
|
5661
|
+
}
|
|
5662
|
+
if (params.limit === 0) return [];
|
|
5663
|
+
const rows2 = this.db.prepare(
|
|
5664
|
+
`SELECT result.*
|
|
5665
|
+
FROM lt_task_work_items AS result
|
|
5666
|
+
WHERE result.tenant_id = ?
|
|
5667
|
+
AND result.task_id = ?
|
|
5668
|
+
AND result.action = 'execution_result'
|
|
5669
|
+
AND substr(result.event_key, 1, 17) = 'execution-result:'
|
|
5670
|
+
AND length(result.event_key) > 17
|
|
5671
|
+
AND result.event_key NOT GLOB '*[^A-Za-z0-9._:-]*'
|
|
5672
|
+
AND NOT EXISTS (
|
|
5673
|
+
SELECT 1
|
|
5674
|
+
FROM lt_task_work_items AS reconciled
|
|
5675
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
5676
|
+
AND reconciled.task_id = result.task_id
|
|
5677
|
+
AND reconciled.action = 'execution_reconciled'
|
|
5678
|
+
AND json_extract(reconciled.detail, '$.executionResultId') = result.event_key
|
|
5679
|
+
)
|
|
5680
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
5681
|
+
LIMIT ?`
|
|
5682
|
+
).all(params.tenantId, params.taskId, params.limit);
|
|
5683
|
+
return rows2.map(mapRowToWorkItem);
|
|
5684
|
+
}
|
|
5685
|
+
/** Lists canonical project lifecycle events with an exclusive cursor. */
|
|
5686
|
+
async listProjectLifecycleEvents(query) {
|
|
5687
|
+
const cursorMilliseconds = assertLifecyclePage(query);
|
|
5688
|
+
const cursor = cursorMilliseconds === null ? null : new Date(cursorMilliseconds).toISOString();
|
|
5689
|
+
const rows2 = this.db.prepare(
|
|
5690
|
+
`SELECT * FROM lt_task_work_items
|
|
5691
|
+
WHERE tenant_id = ? AND workspace_id = ? AND project_id = ?
|
|
5692
|
+
AND action IN (${query.actions.map(() => "?").join(", ")})
|
|
5693
|
+
AND event_key IS NOT NULL AND event_key <> ''
|
|
5694
|
+
AND (? IS NULL OR created_at < ? OR (created_at = ? AND id < ?))
|
|
5695
|
+
ORDER BY created_at DESC, id DESC LIMIT ?`
|
|
5696
|
+
).all(
|
|
5697
|
+
query.tenantId,
|
|
5698
|
+
query.workspaceId,
|
|
5699
|
+
query.projectId,
|
|
5700
|
+
...query.actions,
|
|
5701
|
+
cursor,
|
|
5702
|
+
cursor,
|
|
5703
|
+
cursor,
|
|
5704
|
+
query.before?.id ?? null,
|
|
5705
|
+
query.limit
|
|
5706
|
+
);
|
|
5707
|
+
return rows2.map(mapRowToWorkItem).map(assertAndCloneProjectLifecycleEvent);
|
|
5708
|
+
}
|
|
5709
|
+
};
|
|
5710
|
+
|
|
5711
|
+
// src/stores/LocalCapabilityBundleStore.ts
|
|
5712
|
+
var import_crypto6 = require("crypto");
|
|
5713
|
+
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);`;
|
|
5714
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
5715
|
+
function map(row) {
|
|
5716
|
+
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() };
|
|
5717
|
+
}
|
|
5718
|
+
function isConstraint(error) {
|
|
5719
|
+
return error instanceof Error && /UNIQUE constraint failed/.test(error.message);
|
|
5720
|
+
}
|
|
5721
|
+
function nextTimestamp(previous) {
|
|
5722
|
+
const now = Date.now();
|
|
5723
|
+
const previousTime = previous ? Date.parse(previous) : Number.NaN;
|
|
5724
|
+
return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
|
|
5725
|
+
}
|
|
5726
|
+
var LocalCapabilityBundleStore = class {
|
|
5727
|
+
/**
|
|
5728
|
+
* Creates a capability bundle store over an initialized sql.js database.
|
|
5729
|
+
*
|
|
5730
|
+
* @param db - Shared local database wrapper used for persistence.
|
|
5731
|
+
*/
|
|
5732
|
+
constructor(db) {
|
|
5733
|
+
this.db = db;
|
|
5734
|
+
ensureTable(db, DDL22);
|
|
5735
|
+
}
|
|
5736
|
+
async listByTenant(tenantId) {
|
|
5737
|
+
return this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? ORDER BY created_at").all(tenantId).map(map);
|
|
5738
|
+
}
|
|
5739
|
+
async getById(tenantId, id) {
|
|
5740
|
+
const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
|
|
5741
|
+
return row ? map(row) : null;
|
|
5742
|
+
}
|
|
5743
|
+
async getManyByIds(tenantId, ids) {
|
|
5744
|
+
return ids.flatMap((id) => {
|
|
5745
|
+
const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
|
|
5746
|
+
return row ? [map(row)] : [];
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
5749
|
+
async create(tenantId, input) {
|
|
5750
|
+
const now = nextTimestamp();
|
|
5751
|
+
const id = (0, import_crypto6.randomUUID)();
|
|
5752
|
+
try {
|
|
5753
|
+
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);
|
|
5754
|
+
} catch (error) {
|
|
5755
|
+
if (isConstraint(error)) throw new Error(duplicateMessage);
|
|
5756
|
+
throw error;
|
|
5757
|
+
}
|
|
5758
|
+
return await this.getById(tenantId, id);
|
|
5759
|
+
}
|
|
5760
|
+
async update(tenantId, id, input) {
|
|
5761
|
+
const existing = await this.getById(tenantId, id);
|
|
5762
|
+
if (!existing) return null;
|
|
5763
|
+
if (input.expectedUpdatedAt !== void 0 && input.expectedUpdatedAt !== existing.updatedAt) return { status: "conflict" };
|
|
5764
|
+
const fields = [];
|
|
5765
|
+
const values = [];
|
|
5766
|
+
const add = (field, value) => {
|
|
5767
|
+
fields.push(`${field} = ?`);
|
|
5768
|
+
values.push(value);
|
|
5769
|
+
};
|
|
5770
|
+
if (input.name !== void 0) add("name", input.name);
|
|
5771
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) add("description", input.description ?? null);
|
|
5772
|
+
if (input.capabilities !== void 0) add("capabilities", JSON.stringify(input.capabilities));
|
|
5773
|
+
if (!fields.length) return existing;
|
|
5774
|
+
const now = nextTimestamp(existing.updatedAt);
|
|
5775
|
+
fields.push("updated_at = ?");
|
|
5776
|
+
values.push(now, tenantId, id);
|
|
5777
|
+
try {
|
|
5778
|
+
if (input.expectedUpdatedAt !== void 0) {
|
|
5779
|
+
const result = this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at = ?`).run(...values, input.expectedUpdatedAt);
|
|
5780
|
+
if (result.changes === 0) return { status: "conflict" };
|
|
5781
|
+
} else {
|
|
5782
|
+
this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ?`).run(...values);
|
|
5783
|
+
}
|
|
5784
|
+
} catch (error) {
|
|
5785
|
+
if (isConstraint(error)) throw new Error(duplicateMessage);
|
|
5786
|
+
throw error;
|
|
5787
|
+
}
|
|
5788
|
+
return this.getById(tenantId, id);
|
|
5789
|
+
}
|
|
5790
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
5791
|
+
const result = this.db.prepare(
|
|
5792
|
+
`DELETE FROM lt_capability_bundles
|
|
5793
|
+
WHERE tenant_id = ? AND id = ?
|
|
5794
|
+
AND NOT EXISTS (
|
|
5795
|
+
SELECT 1 FROM lt_projects
|
|
5796
|
+
WHERE tenant_id = ?
|
|
5797
|
+
AND json_type(config, '$.capabilityBundleIds') = 'array'
|
|
5798
|
+
AND EXISTS (SELECT 1 FROM json_each(config, '$.capabilityBundleIds') WHERE value = ?)
|
|
5799
|
+
)`
|
|
5800
|
+
).run(tenantId, id, tenantId, id);
|
|
5801
|
+
if (result.changes > 0) return "deleted";
|
|
5802
|
+
if (await this.getById(tenantId, id)) return "in_use";
|
|
5803
|
+
return "not_found";
|
|
4579
5804
|
}
|
|
4580
5805
|
};
|
|
4581
5806
|
|
|
@@ -4588,6 +5813,10 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4588
5813
|
assistant: new LocalAssistantStore(db),
|
|
4589
5814
|
workspace: new LocalWorkspaceStore(db),
|
|
4590
5815
|
project: new LocalProjectStore(db),
|
|
5816
|
+
projectRoom: new LocalProjectRoomStore(db),
|
|
5817
|
+
projectMembership: new LocalProjectMembershipStore(db),
|
|
5818
|
+
projectBotMembership: new LocalProjectBotMembershipStore(db),
|
|
5819
|
+
projectRoomMessage: new LocalProjectRoomMessageStore(db),
|
|
4591
5820
|
user: new LocalUserStore(db),
|
|
4592
5821
|
tenant: new LocalTenantStore(db),
|
|
4593
5822
|
userTenantLink: new LocalUserTenantLinkStore(db),
|
|
@@ -4605,6 +5834,7 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4605
5834
|
schedule: new LocalScheduleStorage(db),
|
|
4606
5835
|
task: new LocalTaskStore(db),
|
|
4607
5836
|
taskWorkItem: new LocalTaskWorkItemStore(db),
|
|
5837
|
+
capabilityBundle: new LocalCapabilityBundleStore(db),
|
|
4608
5838
|
checkpoint: import_langgraph_checkpoint_sqlite.SqliteSaver.fromConnString(dbPath)
|
|
4609
5839
|
};
|
|
4610
5840
|
}
|
|
@@ -4620,7 +5850,7 @@ var InMemoryConversationStore = class {
|
|
|
4620
5850
|
}
|
|
4621
5851
|
async createConversation(input) {
|
|
4622
5852
|
const now = isoNow();
|
|
4623
|
-
const
|
|
5853
|
+
const record2 = {
|
|
4624
5854
|
conversationId: input.conversationId,
|
|
4625
5855
|
tenantId: input.tenantId,
|
|
4626
5856
|
taskIds: [],
|
|
@@ -4629,17 +5859,17 @@ var InMemoryConversationStore = class {
|
|
|
4629
5859
|
createdAt: now,
|
|
4630
5860
|
updatedAt: now
|
|
4631
5861
|
};
|
|
4632
|
-
this.store.set(input.conversationId,
|
|
4633
|
-
return
|
|
5862
|
+
this.store.set(input.conversationId, record2);
|
|
5863
|
+
return record2;
|
|
4634
5864
|
}
|
|
4635
5865
|
async addTaskToConversation(conversationId, taskId) {
|
|
4636
|
-
const
|
|
4637
|
-
if (!
|
|
5866
|
+
const record2 = this.store.get(conversationId);
|
|
5867
|
+
if (!record2) {
|
|
4638
5868
|
throw new Error(`Conversation not found: ${conversationId}`);
|
|
4639
5869
|
}
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
return
|
|
5870
|
+
record2.taskIds.push(taskId);
|
|
5871
|
+
record2.updatedAt = isoNow();
|
|
5872
|
+
return record2;
|
|
4643
5873
|
}
|
|
4644
5874
|
async deleteConversation(conversationId) {
|
|
4645
5875
|
this.store.delete(conversationId);
|
|
@@ -4648,9 +5878,12 @@ var InMemoryConversationStore = class {
|
|
|
4648
5878
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4649
5879
|
0 && (module.exports = {
|
|
4650
5880
|
DatabaseWrapper,
|
|
5881
|
+
DuplicateProjectMembershipError,
|
|
5882
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
4651
5883
|
InMemoryConversationStore,
|
|
4652
5884
|
LocalA2AApiKeyStore,
|
|
4653
5885
|
LocalAssistantStore,
|
|
5886
|
+
LocalCapabilityBundleStore,
|
|
4654
5887
|
LocalChannelBindingStore,
|
|
4655
5888
|
LocalChannelInstallationStore,
|
|
4656
5889
|
LocalConnectionStore,
|
|
@@ -4658,6 +5891,10 @@ var InMemoryConversationStore = class {
|
|
|
4658
5891
|
LocalEvalStore,
|
|
4659
5892
|
LocalMcpServerConfigStore,
|
|
4660
5893
|
LocalMetricsServerConfigStore,
|
|
5894
|
+
LocalProjectBotMembershipStore,
|
|
5895
|
+
LocalProjectMembershipStore,
|
|
5896
|
+
LocalProjectRoomMessageStore,
|
|
5897
|
+
LocalProjectRoomStore,
|
|
4661
5898
|
LocalProjectStore,
|
|
4662
5899
|
LocalScheduleStorage,
|
|
4663
5900
|
LocalSkillStore,
|
|
@@ -4671,6 +5908,9 @@ var InMemoryConversationStore = class {
|
|
|
4671
5908
|
LocalWorkflowTrackingStore,
|
|
4672
5909
|
LocalWorkspaceStore,
|
|
4673
5910
|
MigrationManager,
|
|
5911
|
+
ProjectBotMembershipIdConflictError,
|
|
5912
|
+
ProjectMembershipIdConflictError,
|
|
5913
|
+
ProjectRoomMessageIdConflictError,
|
|
4674
5914
|
RunResult,
|
|
4675
5915
|
StatementWrapper,
|
|
4676
5916
|
closeDatabase,
|