@axiom-lattice/local-stores 3.1.1 → 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 +9 -0
- package/dist/index.d.mts +142 -14
- package/dist/index.d.ts +142 -14
- package/dist/index.js +1194 -206
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1189 -203
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalCapabilityBundleStore.test.ts +1 -2
- 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__/LocalTaskStore.test.ts +58 -0
- package/src/__tests__/LocalTaskWorkItemStore.test.ts +92 -0
- package/src/__tests__/LocalThreadMessageQueueStore.test.ts +126 -3
- package/src/createLocalStoreConfig.ts +8 -0
- package/src/index.ts +4 -0
- package/src/migrations/migration.ts +1 -1
- 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/LocalTaskStore.ts +82 -2
- package/src/stores/LocalTaskWorkItemStore.ts +91 -1
- package/src/stores/LocalThreadMessageQueueStore.ts +52 -6
- 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,6 +31,8 @@ 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,
|
|
@@ -42,6 +44,10 @@ __export(index_exports, {
|
|
|
42
44
|
LocalEvalStore: () => LocalEvalStore,
|
|
43
45
|
LocalMcpServerConfigStore: () => LocalMcpServerConfigStore,
|
|
44
46
|
LocalMetricsServerConfigStore: () => LocalMetricsServerConfigStore,
|
|
47
|
+
LocalProjectBotMembershipStore: () => LocalProjectBotMembershipStore,
|
|
48
|
+
LocalProjectMembershipStore: () => LocalProjectMembershipStore,
|
|
49
|
+
LocalProjectRoomMessageStore: () => LocalProjectRoomMessageStore,
|
|
50
|
+
LocalProjectRoomStore: () => LocalProjectRoomStore,
|
|
45
51
|
LocalProjectStore: () => LocalProjectStore,
|
|
46
52
|
LocalScheduleStorage: () => LocalScheduleStorage,
|
|
47
53
|
LocalSkillStore: () => LocalSkillStore,
|
|
@@ -55,6 +61,9 @@ __export(index_exports, {
|
|
|
55
61
|
LocalWorkflowTrackingStore: () => LocalWorkflowTrackingStore,
|
|
56
62
|
LocalWorkspaceStore: () => LocalWorkspaceStore,
|
|
57
63
|
MigrationManager: () => MigrationManager,
|
|
64
|
+
ProjectBotMembershipIdConflictError: () => ProjectBotMembershipIdConflictError,
|
|
65
|
+
ProjectMembershipIdConflictError: () => ProjectMembershipIdConflictError,
|
|
66
|
+
ProjectRoomMessageIdConflictError: () => ProjectRoomMessageIdConflictError,
|
|
58
67
|
RunResult: () => RunResult,
|
|
59
68
|
StatementWrapper: () => StatementWrapper,
|
|
60
69
|
closeDatabase: () => closeDatabase,
|
|
@@ -150,11 +159,11 @@ var StatementWrapper = class {
|
|
|
150
159
|
if (params.length > 0) {
|
|
151
160
|
stmt.bind(params);
|
|
152
161
|
}
|
|
153
|
-
const
|
|
162
|
+
const rows2 = [];
|
|
154
163
|
while (stmt.step()) {
|
|
155
|
-
|
|
164
|
+
rows2.push(stmt.getAsObject());
|
|
156
165
|
}
|
|
157
|
-
return
|
|
166
|
+
return rows2;
|
|
158
167
|
} finally {
|
|
159
168
|
stmt.free();
|
|
160
169
|
}
|
|
@@ -229,8 +238,8 @@ function closeDatabase() {
|
|
|
229
238
|
_db = null;
|
|
230
239
|
}
|
|
231
240
|
}
|
|
232
|
-
function ensureTable(db,
|
|
233
|
-
db.exec(
|
|
241
|
+
function ensureTable(db, ddl2) {
|
|
242
|
+
db.exec(ddl2);
|
|
234
243
|
}
|
|
235
244
|
function nowISO() {
|
|
236
245
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -274,7 +283,10 @@ var MigrationManager = class {
|
|
|
274
283
|
this.db.run(
|
|
275
284
|
`INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at)
|
|
276
285
|
VALUES (?, ?, ?, ?)`,
|
|
277
|
-
|
|
286
|
+
this.storeName,
|
|
287
|
+
m.name,
|
|
288
|
+
m.version,
|
|
289
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
278
290
|
);
|
|
279
291
|
}
|
|
280
292
|
}
|
|
@@ -324,10 +336,10 @@ var MigrationManager = class {
|
|
|
324
336
|
`);
|
|
325
337
|
}
|
|
326
338
|
getAppliedNames() {
|
|
327
|
-
const
|
|
339
|
+
const rows2 = this.db.prepare(
|
|
328
340
|
`SELECT name FROM lattice_schema_migrations WHERE store_name = ? ORDER BY version`
|
|
329
341
|
).all(this.storeName);
|
|
330
|
-
return new Set(
|
|
342
|
+
return new Set(rows2.map((r) => r.name));
|
|
331
343
|
}
|
|
332
344
|
};
|
|
333
345
|
|
|
@@ -362,10 +374,10 @@ var LocalThreadStore = class {
|
|
|
362
374
|
mm.migrate();
|
|
363
375
|
}
|
|
364
376
|
async getThreadsByAssistantId(tenantId, assistantId, metadataFilter) {
|
|
365
|
-
const
|
|
377
|
+
const rows2 = this.db.prepare(
|
|
366
378
|
`SELECT * FROM lt_threads WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
367
379
|
).all(tenantId, assistantId);
|
|
368
|
-
let threads =
|
|
380
|
+
let threads = rows2.map(mapRowToThread);
|
|
369
381
|
if (metadataFilter && Object.keys(metadataFilter).length > 0) {
|
|
370
382
|
threads = threads.filter(
|
|
371
383
|
(t) => Object.entries(metadataFilter).every(
|
|
@@ -462,10 +474,10 @@ var LocalAssistantStore = class {
|
|
|
462
474
|
ensureTable(db, DDL2);
|
|
463
475
|
}
|
|
464
476
|
async getAllAssistants(tenantId) {
|
|
465
|
-
const
|
|
477
|
+
const rows2 = this.db.prepare(
|
|
466
478
|
`SELECT * FROM lt_assistants WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
467
479
|
).all(tenantId);
|
|
468
|
-
return
|
|
480
|
+
return rows2.map(mapRowToAssistant);
|
|
469
481
|
}
|
|
470
482
|
async getAssistantById(tenantId, id) {
|
|
471
483
|
const row = this.db.prepare(
|
|
@@ -578,10 +590,10 @@ var LocalWorkspaceStore = class {
|
|
|
578
590
|
ensureTable(db, DDL3);
|
|
579
591
|
}
|
|
580
592
|
async getAllWorkspaces(tenantId) {
|
|
581
|
-
const
|
|
593
|
+
const rows2 = this.db.prepare(
|
|
582
594
|
`SELECT * FROM lt_workspaces WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
583
595
|
).all(tenantId);
|
|
584
|
-
return
|
|
596
|
+
return rows2.map(mapRowToWorkspace);
|
|
585
597
|
}
|
|
586
598
|
async getWorkspaceById(tenantId, id) {
|
|
587
599
|
const row = this.db.prepare(
|
|
@@ -681,15 +693,15 @@ var LocalProjectStore = class {
|
|
|
681
693
|
}
|
|
682
694
|
async getProjectsByWorkspace(tenantId, workspaceId, filter) {
|
|
683
695
|
if (filter?.kind !== void 0) {
|
|
684
|
-
const
|
|
696
|
+
const rows3 = this.db.prepare(
|
|
685
697
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? AND kind = ? ORDER BY created_at DESC`
|
|
686
698
|
).all(tenantId, workspaceId, filter.kind);
|
|
687
|
-
return
|
|
699
|
+
return rows3.map(mapRowToProject);
|
|
688
700
|
}
|
|
689
|
-
const
|
|
701
|
+
const rows2 = this.db.prepare(
|
|
690
702
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? ORDER BY created_at DESC`
|
|
691
703
|
).all(tenantId, workspaceId);
|
|
692
|
-
return
|
|
704
|
+
return rows2.map(mapRowToProject);
|
|
693
705
|
}
|
|
694
706
|
async getProjectById(tenantId, id) {
|
|
695
707
|
const row = this.db.prepare(
|
|
@@ -793,8 +805,8 @@ var LocalProjectStore = class {
|
|
|
793
805
|
return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
794
806
|
}
|
|
795
807
|
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
796
|
-
const
|
|
797
|
-
return
|
|
808
|
+
const rows2 = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
|
|
809
|
+
return rows2.some((row) => {
|
|
798
810
|
if (!row.config) return false;
|
|
799
811
|
let config;
|
|
800
812
|
try {
|
|
@@ -808,10 +820,10 @@ var LocalProjectStore = class {
|
|
|
808
820
|
});
|
|
809
821
|
}
|
|
810
822
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
811
|
-
ensureColumn(table, column,
|
|
823
|
+
ensureColumn(table, column, ddl2) {
|
|
812
824
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
813
825
|
if (!cols.some((c) => c.name === column)) {
|
|
814
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
826
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
815
827
|
}
|
|
816
828
|
}
|
|
817
829
|
};
|
|
@@ -829,6 +841,640 @@ function mapRowToProject(row) {
|
|
|
829
841
|
};
|
|
830
842
|
}
|
|
831
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
|
+
|
|
832
1478
|
// src/stores/LocalUserStore.ts
|
|
833
1479
|
var DDL5 = `
|
|
834
1480
|
CREATE TABLE IF NOT EXISTS lt_users (
|
|
@@ -848,10 +1494,10 @@ var LocalUserStore = class {
|
|
|
848
1494
|
ensureTable(db, DDL5);
|
|
849
1495
|
}
|
|
850
1496
|
async getAllUsers() {
|
|
851
|
-
const
|
|
1497
|
+
const rows2 = this.db.prepare(
|
|
852
1498
|
`SELECT * FROM lt_users ORDER BY created_at DESC`
|
|
853
1499
|
).all();
|
|
854
|
-
return
|
|
1500
|
+
return rows2.map(mapRowToUser);
|
|
855
1501
|
}
|
|
856
1502
|
async getUserById(id) {
|
|
857
1503
|
const row = this.db.prepare(
|
|
@@ -955,10 +1601,10 @@ var LocalTenantStore = class {
|
|
|
955
1601
|
ensureTable(db, DDL6);
|
|
956
1602
|
}
|
|
957
1603
|
async getAllTenants() {
|
|
958
|
-
const
|
|
1604
|
+
const rows2 = this.db.prepare(
|
|
959
1605
|
`SELECT * FROM lt_tenants ORDER BY created_at DESC`
|
|
960
1606
|
).all();
|
|
961
|
-
return
|
|
1607
|
+
return rows2.map(mapRowToTenant);
|
|
962
1608
|
}
|
|
963
1609
|
async getTenantById(id) {
|
|
964
1610
|
const row = this.db.prepare(
|
|
@@ -1057,16 +1703,16 @@ var LocalUserTenantLinkStore = class {
|
|
|
1057
1703
|
ensureTable(db, DDL7);
|
|
1058
1704
|
}
|
|
1059
1705
|
async getTenantsByUser(userId) {
|
|
1060
|
-
const
|
|
1706
|
+
const rows2 = this.db.prepare(
|
|
1061
1707
|
`SELECT * FROM lt_user_tenant_links WHERE user_id = ? ORDER BY joined_at DESC`
|
|
1062
1708
|
).all(userId);
|
|
1063
|
-
return
|
|
1709
|
+
return rows2.map(mapRowToLink);
|
|
1064
1710
|
}
|
|
1065
1711
|
async getUsersByTenant(tenantId) {
|
|
1066
|
-
const
|
|
1712
|
+
const rows2 = this.db.prepare(
|
|
1067
1713
|
`SELECT * FROM lt_user_tenant_links WHERE tenant_id = ? ORDER BY joined_at DESC`
|
|
1068
1714
|
).all(tenantId);
|
|
1069
|
-
return
|
|
1715
|
+
return rows2.map(mapRowToLink);
|
|
1070
1716
|
}
|
|
1071
1717
|
async getLink(userId, tenantId) {
|
|
1072
1718
|
const row = this.db.prepare(
|
|
@@ -1076,7 +1722,7 @@ var LocalUserTenantLinkStore = class {
|
|
|
1076
1722
|
}
|
|
1077
1723
|
async createLink(data) {
|
|
1078
1724
|
const now = nowISO();
|
|
1079
|
-
const
|
|
1725
|
+
const role2 = data.role || "member";
|
|
1080
1726
|
const metadata = JSON.stringify(data.metadata || {});
|
|
1081
1727
|
this.db.prepare(
|
|
1082
1728
|
`INSERT INTO lt_user_tenant_links (user_id, tenant_id, role, joined_at, metadata)
|
|
@@ -1084,11 +1730,11 @@ var LocalUserTenantLinkStore = class {
|
|
|
1084
1730
|
ON CONFLICT(user_id, tenant_id) DO UPDATE SET
|
|
1085
1731
|
role = excluded.role,
|
|
1086
1732
|
metadata = excluded.metadata`
|
|
1087
|
-
).run(data.userId, data.tenantId,
|
|
1733
|
+
).run(data.userId, data.tenantId, role2, now, metadata);
|
|
1088
1734
|
return {
|
|
1089
1735
|
userId: data.userId,
|
|
1090
1736
|
tenantId: data.tenantId,
|
|
1091
|
-
role,
|
|
1737
|
+
role: role2,
|
|
1092
1738
|
joinedAt: parseISO(now),
|
|
1093
1739
|
metadata: data.metadata
|
|
1094
1740
|
};
|
|
@@ -1158,16 +1804,16 @@ var LocalDatabaseConfigStore = class {
|
|
|
1158
1804
|
ensureTable(db, DDL8);
|
|
1159
1805
|
}
|
|
1160
1806
|
async getAllConfigs(tenantId) {
|
|
1161
|
-
const
|
|
1807
|
+
const rows2 = this.db.prepare(
|
|
1162
1808
|
`SELECT * FROM lt_database_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1163
1809
|
).all(tenantId);
|
|
1164
|
-
return
|
|
1810
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1165
1811
|
}
|
|
1166
1812
|
async getAllConfigsWithoutTenant() {
|
|
1167
|
-
const
|
|
1813
|
+
const rows2 = this.db.prepare(
|
|
1168
1814
|
`SELECT * FROM lt_database_configs ORDER BY created_at DESC`
|
|
1169
1815
|
).all();
|
|
1170
|
-
return
|
|
1816
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1171
1817
|
}
|
|
1172
1818
|
async getConfigById(tenantId, id) {
|
|
1173
1819
|
const row = this.db.prepare(
|
|
@@ -1299,16 +1945,16 @@ var LocalConnectionStore = class {
|
|
|
1299
1945
|
ensureTable(db, DDL9);
|
|
1300
1946
|
}
|
|
1301
1947
|
async listByTenant(tenantId) {
|
|
1302
|
-
const
|
|
1948
|
+
const rows2 = this.db.prepare(
|
|
1303
1949
|
`SELECT * FROM connection_configs WHERE tenant_id = ? ORDER BY created_at`
|
|
1304
1950
|
).all(tenantId);
|
|
1305
|
-
return
|
|
1951
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1306
1952
|
}
|
|
1307
1953
|
async listByType(tenantId, type) {
|
|
1308
|
-
const
|
|
1954
|
+
const rows2 = this.db.prepare(
|
|
1309
1955
|
`SELECT * FROM connection_configs WHERE tenant_id = ? AND type = ? ORDER BY created_at`
|
|
1310
1956
|
).all(tenantId, type);
|
|
1311
|
-
return
|
|
1957
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1312
1958
|
}
|
|
1313
1959
|
async getByKey(tenantId, type, key) {
|
|
1314
1960
|
const row = this.db.prepare(
|
|
@@ -1394,16 +2040,16 @@ var LocalMetricsServerConfigStore = class {
|
|
|
1394
2040
|
ensureTable(db, DDL10);
|
|
1395
2041
|
}
|
|
1396
2042
|
async getAllConfigs(tenantId) {
|
|
1397
|
-
const
|
|
2043
|
+
const rows2 = this.db.prepare(
|
|
1398
2044
|
`SELECT * FROM lt_metrics_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1399
2045
|
).all(tenantId);
|
|
1400
|
-
return
|
|
2046
|
+
return rows2.map(mapRowToEntry2);
|
|
1401
2047
|
}
|
|
1402
2048
|
async getAllConfigsWithoutTenant() {
|
|
1403
|
-
const
|
|
2049
|
+
const rows2 = this.db.prepare(
|
|
1404
2050
|
`SELECT * FROM lt_metrics_configs ORDER BY created_at DESC`
|
|
1405
2051
|
).all();
|
|
1406
|
-
return
|
|
2052
|
+
return rows2.map(mapRowToEntry2);
|
|
1407
2053
|
}
|
|
1408
2054
|
async getConfigById(tenantId, id) {
|
|
1409
2055
|
const row = this.db.prepare(
|
|
@@ -1522,16 +2168,16 @@ var LocalMcpServerConfigStore = class {
|
|
|
1522
2168
|
ensureTable(db, DDL11);
|
|
1523
2169
|
}
|
|
1524
2170
|
async getAllConfigs(tenantId) {
|
|
1525
|
-
const
|
|
2171
|
+
const rows2 = this.db.prepare(
|
|
1526
2172
|
`SELECT * FROM lt_mcp_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1527
2173
|
).all(tenantId);
|
|
1528
|
-
return
|
|
2174
|
+
return rows2.map(mapRowToEntry3);
|
|
1529
2175
|
}
|
|
1530
2176
|
async getAllConfigsWithoutTenant() {
|
|
1531
|
-
const
|
|
2177
|
+
const rows2 = this.db.prepare(
|
|
1532
2178
|
`SELECT * FROM lt_mcp_configs ORDER BY created_at DESC`
|
|
1533
2179
|
).all();
|
|
1534
|
-
return
|
|
2180
|
+
return rows2.map(mapRowToEntry3);
|
|
1535
2181
|
}
|
|
1536
2182
|
async getConfigById(tenantId, id) {
|
|
1537
2183
|
const row = this.db.prepare(
|
|
@@ -1813,22 +2459,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1813
2459
|
this.db.prepare(`DELETE FROM lt_workflow_runs WHERE id = ?`).run(runId);
|
|
1814
2460
|
}
|
|
1815
2461
|
async getWorkflowRunsByThreadId(tenantId, threadId) {
|
|
1816
|
-
const
|
|
2462
|
+
const rows2 = this.db.prepare(
|
|
1817
2463
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND thread_id = ? ORDER BY created_at DESC`
|
|
1818
2464
|
).all(tenantId, threadId);
|
|
1819
|
-
return
|
|
2465
|
+
return rows2.map(mapRowToRun);
|
|
1820
2466
|
}
|
|
1821
2467
|
async getWorkflowRunsByAssistantId(tenantId, assistantId) {
|
|
1822
|
-
const
|
|
2468
|
+
const rows2 = this.db.prepare(
|
|
1823
2469
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
1824
2470
|
).all(tenantId, assistantId);
|
|
1825
|
-
return
|
|
2471
|
+
return rows2.map(mapRowToRun);
|
|
1826
2472
|
}
|
|
1827
2473
|
async getWorkflowRunsByTenantId(tenantId) {
|
|
1828
|
-
const
|
|
2474
|
+
const rows2 = this.db.prepare(
|
|
1829
2475
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1830
2476
|
).all(tenantId);
|
|
1831
|
-
return
|
|
2477
|
+
return rows2.map(mapRowToRun);
|
|
1832
2478
|
}
|
|
1833
2479
|
async queryWorkflowRuns(tenantId, options = {}) {
|
|
1834
2480
|
const conditions = ["tenant_id = ?"];
|
|
@@ -1857,8 +2503,8 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1857
2503
|
sql += " OFFSET ?";
|
|
1858
2504
|
selectParams.push(options.offset);
|
|
1859
2505
|
}
|
|
1860
|
-
const
|
|
1861
|
-
return { records:
|
|
2506
|
+
const rows2 = this.db.prepare(sql).all(...selectParams);
|
|
2507
|
+
return { records: rows2.map(mapRowToRun), total: countRow.count };
|
|
1862
2508
|
}
|
|
1863
2509
|
async createRunStep(request) {
|
|
1864
2510
|
const now = nowISO();
|
|
@@ -1950,22 +2596,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1950
2596
|
return this.getStepById(runId, stepId);
|
|
1951
2597
|
}
|
|
1952
2598
|
async getRunSteps(runId) {
|
|
1953
|
-
const
|
|
2599
|
+
const rows2 = this.db.prepare(
|
|
1954
2600
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? ORDER BY created_at ASC`
|
|
1955
2601
|
).all(runId);
|
|
1956
|
-
return
|
|
2602
|
+
return rows2.map(mapRowToStep);
|
|
1957
2603
|
}
|
|
1958
2604
|
async getRunStepsByType(runId, stepType) {
|
|
1959
|
-
const
|
|
2605
|
+
const rows2 = this.db.prepare(
|
|
1960
2606
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? ORDER BY created_at ASC`
|
|
1961
2607
|
).all(runId, stepType);
|
|
1962
|
-
return
|
|
2608
|
+
return rows2.map(mapRowToStep);
|
|
1963
2609
|
}
|
|
1964
2610
|
async getInterruptedSteps(runId) {
|
|
1965
|
-
const
|
|
2611
|
+
const rows2 = this.db.prepare(
|
|
1966
2612
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND status = 'interrupted' ORDER BY created_at ASC`
|
|
1967
2613
|
).all(runId);
|
|
1968
|
-
return
|
|
2614
|
+
return rows2.map(mapRowToStep);
|
|
1969
2615
|
}
|
|
1970
2616
|
getStepById(runId, id) {
|
|
1971
2617
|
const row = this.db.prepare(
|
|
@@ -2110,7 +2756,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_run_results (
|
|
|
2110
2756
|
PRIMARY KEY (run_id, id)
|
|
2111
2757
|
);
|
|
2112
2758
|
`;
|
|
2113
|
-
function
|
|
2759
|
+
function parseJson2(val, fallback) {
|
|
2114
2760
|
if (val == null) return fallback;
|
|
2115
2761
|
if (typeof val === "string") {
|
|
2116
2762
|
try {
|
|
@@ -2202,20 +2848,20 @@ var LocalEvalStore = class {
|
|
|
2202
2848
|
`);
|
|
2203
2849
|
}
|
|
2204
2850
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2205
|
-
ensureColumn(table, column,
|
|
2851
|
+
ensureColumn(table, column, ddl2) {
|
|
2206
2852
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2207
2853
|
if (!cols.some((c) => c.name === column)) {
|
|
2208
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
2854
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
2209
2855
|
}
|
|
2210
2856
|
}
|
|
2211
2857
|
// -------------------------------------------------------------------------
|
|
2212
2858
|
// Projects
|
|
2213
2859
|
// -------------------------------------------------------------------------
|
|
2214
2860
|
async getProjectsByTenant(tenantId) {
|
|
2215
|
-
const
|
|
2861
|
+
const rows2 = this.db.prepare(
|
|
2216
2862
|
`SELECT * FROM lt_eval_projects WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2217
2863
|
).all(tenantId);
|
|
2218
|
-
return
|
|
2864
|
+
return rows2.map(this.mapRowToProject);
|
|
2219
2865
|
}
|
|
2220
2866
|
async getProjectById(tenantId, id) {
|
|
2221
2867
|
const row = this.db.prepare(
|
|
@@ -2318,11 +2964,11 @@ var LocalEvalStore = class {
|
|
|
2318
2964
|
// Suites
|
|
2319
2965
|
// -------------------------------------------------------------------------
|
|
2320
2966
|
async getSuitesByProject(tenantId, projectId) {
|
|
2321
|
-
const
|
|
2967
|
+
const rows2 = this.db.prepare(
|
|
2322
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
|
|
2323
2969
|
FROM lt_eval_suites s WHERE s.tenant_id = ? AND s.project_id = ? ORDER BY s.created_at DESC`
|
|
2324
2970
|
).all(tenantId, projectId);
|
|
2325
|
-
return
|
|
2971
|
+
return rows2.map(this.mapRowToSuite);
|
|
2326
2972
|
}
|
|
2327
2973
|
async getSuiteById(tenantId, id) {
|
|
2328
2974
|
const row = this.db.prepare(
|
|
@@ -2363,10 +3009,10 @@ var LocalEvalStore = class {
|
|
|
2363
3009
|
// Cases
|
|
2364
3010
|
// -------------------------------------------------------------------------
|
|
2365
3011
|
async getCasesBySuite(tenantId, suiteId) {
|
|
2366
|
-
const
|
|
3012
|
+
const rows2 = this.db.prepare(
|
|
2367
3013
|
`SELECT * FROM lt_eval_cases WHERE tenant_id = ? AND suite_id = ? ORDER BY created_at DESC`
|
|
2368
3014
|
).all(tenantId, suiteId);
|
|
2369
|
-
return
|
|
3015
|
+
return rows2.map(this.mapRowToCase);
|
|
2370
3016
|
}
|
|
2371
3017
|
async getCaseById(tenantId, id) {
|
|
2372
3018
|
const row = this.db.prepare(
|
|
@@ -2455,8 +3101,8 @@ var LocalEvalStore = class {
|
|
|
2455
3101
|
vals.push(opts.status);
|
|
2456
3102
|
}
|
|
2457
3103
|
query += ` ORDER BY created_at DESC`;
|
|
2458
|
-
const
|
|
2459
|
-
return
|
|
3104
|
+
const rows2 = this.db.prepare(query).all(...vals);
|
|
3105
|
+
return rows2.map(this.mapRowToRun);
|
|
2460
3106
|
}
|
|
2461
3107
|
async getRunById(tenantId, id) {
|
|
2462
3108
|
const row = this.db.prepare(
|
|
@@ -2519,12 +3165,12 @@ var LocalEvalStore = class {
|
|
|
2519
3165
|
// Run Results
|
|
2520
3166
|
// -------------------------------------------------------------------------
|
|
2521
3167
|
async getResultsByRun(tenantId, runId) {
|
|
2522
|
-
const
|
|
3168
|
+
const rows2 = this.db.prepare(
|
|
2523
3169
|
`SELECT rr.* FROM lt_eval_run_results rr
|
|
2524
3170
|
INNER JOIN lt_eval_runs r ON r.id = rr.run_id
|
|
2525
3171
|
WHERE r.tenant_id = ? AND rr.run_id = ? ORDER BY rr.created_at ASC`
|
|
2526
3172
|
).all(tenantId, runId);
|
|
2527
|
-
return
|
|
3173
|
+
return rows2.map(this.mapRowToRunResult);
|
|
2528
3174
|
}
|
|
2529
3175
|
async getRunResultById(tenantId, id) {
|
|
2530
3176
|
const row = this.db.prepare(
|
|
@@ -2642,8 +3288,8 @@ var LocalEvalStore = class {
|
|
|
2642
3288
|
name: row.name,
|
|
2643
3289
|
description: row.description,
|
|
2644
3290
|
version: row.version,
|
|
2645
|
-
judgeModelConfig:
|
|
2646
|
-
targetServerConfig:
|
|
3291
|
+
judgeModelConfig: parseJson2(row.judge_model_config, {}),
|
|
3292
|
+
targetServerConfig: parseJson2(row.target_server_config, {}),
|
|
2647
3293
|
concurrency: row.concurrency ?? 3,
|
|
2648
3294
|
reportConfig: parseOptionalJson(row.report_config),
|
|
2649
3295
|
createdAt: parseISO(row.created_at),
|
|
@@ -2668,7 +3314,7 @@ var LocalEvalStore = class {
|
|
|
2668
3314
|
suiteId: row.suite_id,
|
|
2669
3315
|
inputMessage: row.input_message,
|
|
2670
3316
|
inputFiles: parseOptionalJson(row.input_files),
|
|
2671
|
-
steps:
|
|
3317
|
+
steps: parseJson2(row.steps, []),
|
|
2672
3318
|
outputType: row.output_type || "message_content",
|
|
2673
3319
|
contentAssertion: row.content_assertion || "",
|
|
2674
3320
|
rubrics: parseOptionalJson(row.rubrics),
|
|
@@ -2720,7 +3366,50 @@ var LocalEvalStore = class {
|
|
|
2720
3366
|
};
|
|
2721
3367
|
|
|
2722
3368
|
// src/stores/LocalChannelBindingStore.ts
|
|
3369
|
+
var import_protocols3 = require("@axiom-lattice/protocols");
|
|
2723
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
|
|
2724
3413
|
var DDL14 = `
|
|
2725
3414
|
CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
2726
3415
|
id TEXT PRIMARY KEY,
|
|
@@ -2739,12 +3428,23 @@ CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
|
2739
3428
|
created_at TEXT NOT NULL,
|
|
2740
3429
|
updated_at TEXT NOT NULL
|
|
2741
3430
|
);
|
|
2742
|
-
CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id);
|
|
2743
3431
|
`;
|
|
2744
3432
|
var LocalChannelBindingStore = class {
|
|
2745
3433
|
constructor(db) {
|
|
2746
3434
|
this.db = db;
|
|
2747
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;
|
|
2748
3448
|
}
|
|
2749
3449
|
async resolve(params) {
|
|
2750
3450
|
const row = this.db.prepare(
|
|
@@ -2757,35 +3457,41 @@ var LocalChannelBindingStore = class {
|
|
|
2757
3457
|
async create(input) {
|
|
2758
3458
|
const id = (0, import_crypto2.randomUUID)();
|
|
2759
3459
|
const now = nowISO();
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
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
|
+
}
|
|
2780
3489
|
return await this.getById(id);
|
|
2781
3490
|
}
|
|
2782
|
-
async update(id, patch) {
|
|
2783
|
-
const existing = await this.
|
|
3491
|
+
async update(tenantId, id, patch) {
|
|
3492
|
+
const existing = await this.findById(tenantId, id);
|
|
2784
3493
|
if (!existing) throw new Error(`Binding ${id} not found`);
|
|
2785
3494
|
const updated = {
|
|
2786
|
-
channel: patch.channel ?? existing.channel,
|
|
2787
|
-
channelInstallationId: patch.channelInstallationId ?? existing.channelInstallationId,
|
|
2788
|
-
senderId: patch.senderId ?? existing.senderId,
|
|
2789
3495
|
agentId: patch.agentId ?? existing.agentId,
|
|
2790
3496
|
threadId: patch.threadId !== void 0 ? patch.threadId : existing.threadId,
|
|
2791
3497
|
workspaceId: patch.workspaceId !== void 0 ? patch.workspaceId : existing.workspaceId,
|
|
@@ -2798,14 +3504,10 @@ var LocalChannelBindingStore = class {
|
|
|
2798
3504
|
const now = nowISO();
|
|
2799
3505
|
this.db.prepare(
|
|
2800
3506
|
`UPDATE lt_channel_bindings SET
|
|
2801
|
-
|
|
2802
|
-
thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
3507
|
+
agent_id = ?, thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
2803
3508
|
sender_display_name = ?, sender_metadata = ?, enabled = ?, updated_at = ?
|
|
2804
|
-
WHERE id = ?`
|
|
3509
|
+
WHERE id = ? AND tenant_id = ?`
|
|
2805
3510
|
).run(
|
|
2806
|
-
updated.channel,
|
|
2807
|
-
updated.channelInstallationId,
|
|
2808
|
-
updated.senderId,
|
|
2809
3511
|
updated.agentId,
|
|
2810
3512
|
updated.threadId || null,
|
|
2811
3513
|
updated.workspaceId || null,
|
|
@@ -2815,12 +3517,13 @@ var LocalChannelBindingStore = class {
|
|
|
2815
3517
|
updated.senderMetadata ? JSON.stringify(updated.senderMetadata) : null,
|
|
2816
3518
|
updated.enabled ? 1 : 0,
|
|
2817
3519
|
now,
|
|
2818
|
-
id
|
|
3520
|
+
id,
|
|
3521
|
+
tenantId
|
|
2819
3522
|
);
|
|
2820
|
-
return await this.
|
|
3523
|
+
return await this.findById(tenantId, id);
|
|
2821
3524
|
}
|
|
2822
|
-
async delete(id) {
|
|
2823
|
-
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);
|
|
2824
3527
|
}
|
|
2825
3528
|
async list(params) {
|
|
2826
3529
|
const conditions = ["tenant_id = ?"];
|
|
@@ -2837,18 +3540,35 @@ var LocalChannelBindingStore = class {
|
|
|
2837
3540
|
conditions.push("channel_installation_id = ?");
|
|
2838
3541
|
values.push(params.channelInstallationId);
|
|
2839
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
|
+
}
|
|
2840
3551
|
const limit = params.limit ?? 50;
|
|
2841
3552
|
const offset = params.offset ?? 0;
|
|
2842
3553
|
values.push(limit, offset);
|
|
2843
|
-
const
|
|
3554
|
+
const rows2 = this.db.prepare(
|
|
2844
3555
|
`SELECT * FROM lt_channel_bindings
|
|
2845
3556
|
WHERE ${conditions.join(" AND ")}
|
|
2846
3557
|
ORDER BY created_at DESC
|
|
2847
3558
|
LIMIT ? OFFSET ?`
|
|
2848
3559
|
).all(...values);
|
|
2849
|
-
return
|
|
3560
|
+
return rows2.map(mapRowToBinding);
|
|
2850
3561
|
}
|
|
2851
|
-
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
|
+
}
|
|
2852
3572
|
const result = [];
|
|
2853
3573
|
for (const input of bindings) {
|
|
2854
3574
|
result.push(await this.create(input));
|
|
@@ -2856,7 +3576,13 @@ var LocalChannelBindingStore = class {
|
|
|
2856
3576
|
return result;
|
|
2857
3577
|
}
|
|
2858
3578
|
async export(params) {
|
|
2859
|
-
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
|
+
});
|
|
2860
3586
|
}
|
|
2861
3587
|
async getById(id) {
|
|
2862
3588
|
const row = this.db.prepare(
|
|
@@ -2910,33 +3636,33 @@ var LocalChannelInstallationStore = class {
|
|
|
2910
3636
|
const row = this.db.prepare(
|
|
2911
3637
|
`SELECT * FROM lt_channel_installations WHERE id = ?`
|
|
2912
3638
|
).get(installationId);
|
|
2913
|
-
return row ?
|
|
3639
|
+
return row ? mapRow5(row) : null;
|
|
2914
3640
|
}
|
|
2915
3641
|
async getInstallationsByTenant(tenantId, channel) {
|
|
2916
|
-
let
|
|
3642
|
+
let rows2;
|
|
2917
3643
|
if (channel) {
|
|
2918
|
-
|
|
3644
|
+
rows2 = this.db.prepare(
|
|
2919
3645
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? AND channel = ? ORDER BY created_at DESC`
|
|
2920
3646
|
).all(tenantId, channel);
|
|
2921
3647
|
} else {
|
|
2922
|
-
|
|
3648
|
+
rows2 = this.db.prepare(
|
|
2923
3649
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2924
3650
|
).all(tenantId);
|
|
2925
3651
|
}
|
|
2926
|
-
return
|
|
3652
|
+
return rows2.map(mapRow5);
|
|
2927
3653
|
}
|
|
2928
3654
|
async getAllInstallations(channel) {
|
|
2929
|
-
let
|
|
3655
|
+
let rows2;
|
|
2930
3656
|
if (channel) {
|
|
2931
|
-
|
|
3657
|
+
rows2 = this.db.prepare(
|
|
2932
3658
|
`SELECT * FROM lt_channel_installations WHERE channel = ? ORDER BY created_at DESC`
|
|
2933
3659
|
).all(channel);
|
|
2934
3660
|
} else {
|
|
2935
|
-
|
|
3661
|
+
rows2 = this.db.prepare(
|
|
2936
3662
|
`SELECT * FROM lt_channel_installations ORDER BY created_at DESC`
|
|
2937
3663
|
).all();
|
|
2938
3664
|
}
|
|
2939
|
-
return
|
|
3665
|
+
return rows2.map(mapRow5);
|
|
2940
3666
|
}
|
|
2941
3667
|
async createInstallation(tenantId, installationId, data) {
|
|
2942
3668
|
const now = nowISO();
|
|
@@ -2961,7 +3687,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2961
3687
|
tenantId,
|
|
2962
3688
|
channel: data.channel,
|
|
2963
3689
|
name: data.name,
|
|
2964
|
-
config: data.config,
|
|
3690
|
+
config: { ...data.config },
|
|
2965
3691
|
enabled: data.enabled !== false,
|
|
2966
3692
|
fallbackAgentId: data.fallbackAgentId,
|
|
2967
3693
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? false,
|
|
@@ -2971,7 +3697,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2971
3697
|
}
|
|
2972
3698
|
async updateInstallation(tenantId, installationId, updates) {
|
|
2973
3699
|
const existing = await this.getInstallationById(installationId);
|
|
2974
|
-
if (!existing) return null;
|
|
3700
|
+
if (!existing || existing.tenantId !== tenantId) return null;
|
|
2975
3701
|
const setClauses = [];
|
|
2976
3702
|
const values = [];
|
|
2977
3703
|
if (updates.name !== void 0) {
|
|
@@ -2998,9 +3724,10 @@ var LocalChannelInstallationStore = class {
|
|
|
2998
3724
|
const now = nowISO();
|
|
2999
3725
|
setClauses.push("updated_at = ?");
|
|
3000
3726
|
values.push(now);
|
|
3727
|
+
values.push(tenantId);
|
|
3001
3728
|
values.push(installationId);
|
|
3002
3729
|
this.db.prepare(
|
|
3003
|
-
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE id = ?`
|
|
3730
|
+
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`
|
|
3004
3731
|
).run(...values);
|
|
3005
3732
|
return this.getInstallationById(installationId);
|
|
3006
3733
|
}
|
|
@@ -3011,7 +3738,7 @@ var LocalChannelInstallationStore = class {
|
|
|
3011
3738
|
return result.changes > 0;
|
|
3012
3739
|
}
|
|
3013
3740
|
};
|
|
3014
|
-
function
|
|
3741
|
+
function mapRow5(row) {
|
|
3015
3742
|
return {
|
|
3016
3743
|
id: row.id,
|
|
3017
3744
|
tenantId: row.tenant_id,
|
|
@@ -3062,18 +3789,18 @@ var LocalA2AApiKeyStore = class {
|
|
|
3062
3789
|
this.repairManagementIds();
|
|
3063
3790
|
}
|
|
3064
3791
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
3065
|
-
ensureColumn(table, column,
|
|
3792
|
+
ensureColumn(table, column, ddl2) {
|
|
3066
3793
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
3067
3794
|
if (!cols.some((c) => c.name === column)) {
|
|
3068
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
3795
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
3069
3796
|
}
|
|
3070
3797
|
}
|
|
3071
3798
|
repairManagementIds() {
|
|
3072
|
-
const
|
|
3799
|
+
const rows2 = this.db.prepare(
|
|
3073
3800
|
`SELECT rowid, id FROM lt_a2a_api_keys`
|
|
3074
3801
|
).all();
|
|
3075
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;
|
|
3076
|
-
for (const row of
|
|
3803
|
+
for (const row of rows2) {
|
|
3077
3804
|
if (!row.id || !uuidPattern.test(row.id)) {
|
|
3078
3805
|
this.db.prepare(
|
|
3079
3806
|
`UPDATE lt_a2a_api_keys SET id = ? WHERE rowid = ?`
|
|
@@ -3082,10 +3809,10 @@ var LocalA2AApiKeyStore = class {
|
|
|
3082
3809
|
}
|
|
3083
3810
|
}
|
|
3084
3811
|
async findByKey(key) {
|
|
3085
|
-
const
|
|
3812
|
+
const rows2 = this.db.prepare(
|
|
3086
3813
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3087
3814
|
).all();
|
|
3088
|
-
for (const row of
|
|
3815
|
+
for (const row of rows2) {
|
|
3089
3816
|
try {
|
|
3090
3817
|
if ((0, import_core3.decrypt)(row.key_value) === key) return mapRowToRecord(row);
|
|
3091
3818
|
} catch {
|
|
@@ -3102,17 +3829,17 @@ var LocalA2AApiKeyStore = class {
|
|
|
3102
3829
|
async list(params) {
|
|
3103
3830
|
const limit = params.limit || 100;
|
|
3104
3831
|
const offset = params.offset || 0;
|
|
3105
|
-
let
|
|
3832
|
+
let rows2;
|
|
3106
3833
|
if (params.tenantId) {
|
|
3107
|
-
|
|
3834
|
+
rows2 = this.db.prepare(
|
|
3108
3835
|
`SELECT * FROM lt_a2a_api_keys WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
3109
3836
|
).all(params.tenantId, limit, offset);
|
|
3110
3837
|
} else {
|
|
3111
|
-
|
|
3838
|
+
rows2 = this.db.prepare(
|
|
3112
3839
|
`SELECT * FROM lt_a2a_api_keys ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
3113
3840
|
).all(limit, offset);
|
|
3114
3841
|
}
|
|
3115
|
-
return
|
|
3842
|
+
return rows2.map(mapRowToRecord);
|
|
3116
3843
|
}
|
|
3117
3844
|
async create(input) {
|
|
3118
3845
|
const id = (0, import_crypto3.randomUUID)();
|
|
@@ -3122,7 +3849,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3122
3849
|
`INSERT INTO lt_a2a_api_keys (id, key_value, tenant_id, project_id, assistant_ids, label, created_at, updated_at)
|
|
3123
3850
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3124
3851
|
).run(id, (0, import_core3.encrypt)(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null, now, now);
|
|
3125
|
-
const
|
|
3852
|
+
const record2 = {
|
|
3126
3853
|
id,
|
|
3127
3854
|
key,
|
|
3128
3855
|
tenantId: input.tenantId,
|
|
@@ -3133,7 +3860,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3133
3860
|
createdAt: parseISO(now),
|
|
3134
3861
|
updatedAt: parseISO(now)
|
|
3135
3862
|
};
|
|
3136
|
-
return
|
|
3863
|
+
return record2;
|
|
3137
3864
|
}
|
|
3138
3865
|
async disable(id) {
|
|
3139
3866
|
const now = nowISO();
|
|
@@ -3161,19 +3888,19 @@ var LocalA2AApiKeyStore = class {
|
|
|
3161
3888
|
).run((0, import_core3.encrypt)(key), now, id);
|
|
3162
3889
|
const row = this.db.prepare(`SELECT * FROM lt_a2a_api_keys WHERE id = ?`).get(id);
|
|
3163
3890
|
if (!row) throw new Error(`A2A API key not found: ${id}`);
|
|
3164
|
-
const
|
|
3165
|
-
|
|
3166
|
-
return
|
|
3891
|
+
const record2 = mapRowToRecord(row);
|
|
3892
|
+
record2.key = key;
|
|
3893
|
+
return record2;
|
|
3167
3894
|
}
|
|
3168
3895
|
async delete(id) {
|
|
3169
3896
|
this.db.prepare(`DELETE FROM lt_a2a_api_keys WHERE id = ?`).run(id);
|
|
3170
3897
|
}
|
|
3171
3898
|
async loadIntoMap() {
|
|
3172
|
-
const
|
|
3899
|
+
const rows2 = this.db.prepare(
|
|
3173
3900
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3174
3901
|
).all();
|
|
3175
3902
|
const map2 = /* @__PURE__ */ new Map();
|
|
3176
|
-
for (const row of
|
|
3903
|
+
for (const row of rows2) {
|
|
3177
3904
|
try {
|
|
3178
3905
|
const key = (0, import_core3.decrypt)(row.key_value);
|
|
3179
3906
|
map2.set(key, {
|
|
@@ -3209,6 +3936,7 @@ function mapRowToRecord(row) {
|
|
|
3209
3936
|
}
|
|
3210
3937
|
|
|
3211
3938
|
// src/stores/LocalThreadMessageQueueStore.ts
|
|
3939
|
+
var import_protocols4 = require("@axiom-lattice/protocols");
|
|
3212
3940
|
var import_crypto4 = require("crypto");
|
|
3213
3941
|
var DDL17 = `
|
|
3214
3942
|
CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
@@ -3225,6 +3953,8 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
|
3225
3953
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
3226
3954
|
command TEXT,
|
|
3227
3955
|
custom_run_config TEXT,
|
|
3956
|
+
trusted_run_context TEXT,
|
|
3957
|
+
execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
|
|
3228
3958
|
created_at TEXT NOT NULL
|
|
3229
3959
|
);
|
|
3230
3960
|
CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
|
|
@@ -3235,8 +3965,17 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3235
3965
|
this.capacityTail = Promise.resolve();
|
|
3236
3966
|
this.db = db;
|
|
3237
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
|
+
}
|
|
3238
3976
|
}
|
|
3239
3977
|
async addMessage(params) {
|
|
3978
|
+
const trusted = validateQueueTrust(params);
|
|
3240
3979
|
const now = nowISO();
|
|
3241
3980
|
const id = params.id || (0, import_crypto4.randomUUID)();
|
|
3242
3981
|
const seqRow = this.db.prepare(
|
|
@@ -3244,8 +3983,8 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3244
3983
|
).get(params.threadId);
|
|
3245
3984
|
const nextSeq = seqRow.next_seq;
|
|
3246
3985
|
this.db.prepare(
|
|
3247
|
-
`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)
|
|
3248
|
-
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3249
3988
|
).run(
|
|
3250
3989
|
id,
|
|
3251
3990
|
params.threadId,
|
|
@@ -3259,11 +3998,14 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3259
3998
|
params.priority ?? 0,
|
|
3260
3999
|
params.command ? JSON.stringify(params.command) : null,
|
|
3261
4000
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4001
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4002
|
+
trusted.executionMode ?? null,
|
|
3262
4003
|
now
|
|
3263
4004
|
);
|
|
3264
4005
|
return this.getById(id);
|
|
3265
4006
|
}
|
|
3266
4007
|
async addMessageIfCapacity(params, maxSize) {
|
|
4008
|
+
const trusted = validateQueueTrust(params);
|
|
3267
4009
|
let release;
|
|
3268
4010
|
const previous = this.capacityTail;
|
|
3269
4011
|
this.capacityTail = new Promise((resolve) => {
|
|
@@ -3281,10 +4023,11 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3281
4023
|
const id = params.id || (0, import_crypto4.randomUUID)();
|
|
3282
4024
|
const limit = maxSize === Infinity ? -1 : maxSize;
|
|
3283
4025
|
const result = this.db.prepare(
|
|
3284
|
-
`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)
|
|
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)
|
|
3285
4027
|
SELECT ?, ?, ?, ?, ?, ?, ?, ?,
|
|
3286
4028
|
(SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
|
|
3287
|
-
?, ?, ?,
|
|
4029
|
+
?, ?, ?, ?, ?,
|
|
4030
|
+
?
|
|
3288
4031
|
WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
|
|
3289
4032
|
).run(
|
|
3290
4033
|
id,
|
|
@@ -3299,6 +4042,8 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3299
4042
|
params.priority ?? 0,
|
|
3300
4043
|
params.command ? JSON.stringify(params.command) : null,
|
|
3301
4044
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4045
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4046
|
+
trusted.executionMode ?? null,
|
|
3302
4047
|
nowISO(),
|
|
3303
4048
|
limit,
|
|
3304
4049
|
params.threadId,
|
|
@@ -3311,14 +4056,15 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3311
4056
|
}
|
|
3312
4057
|
}
|
|
3313
4058
|
async addMessageAtHead(params) {
|
|
4059
|
+
const trusted = validateQueueTrust(params);
|
|
3314
4060
|
const now = nowISO();
|
|
3315
4061
|
const id = params.id || (0, import_crypto4.randomUUID)();
|
|
3316
4062
|
const seqRow = this.db.prepare(
|
|
3317
4063
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lt_thread_message_queue WHERE thread_id = ?`
|
|
3318
4064
|
).get(params.threadId);
|
|
3319
4065
|
this.db.prepare(
|
|
3320
|
-
`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)
|
|
3321
|
-
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, ?, ?, ?, ?, ?)`
|
|
3322
4068
|
).run(
|
|
3323
4069
|
id,
|
|
3324
4070
|
params.threadId,
|
|
@@ -3331,23 +4077,25 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3331
4077
|
seqRow.next_seq,
|
|
3332
4078
|
params.command ? JSON.stringify(params.command) : null,
|
|
3333
4079
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4080
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4081
|
+
trusted.executionMode ?? null,
|
|
3334
4082
|
now
|
|
3335
4083
|
);
|
|
3336
4084
|
return this.getById(id);
|
|
3337
4085
|
}
|
|
3338
4086
|
async getPendingMessages(threadId, scope) {
|
|
3339
4087
|
const filter = scopeClause(scope);
|
|
3340
|
-
const
|
|
4088
|
+
const rows2 = this.db.prepare(
|
|
3341
4089
|
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
3342
4090
|
).all(threadId, ...filter.params);
|
|
3343
|
-
return
|
|
4091
|
+
return rows2.map(rowToMessage);
|
|
3344
4092
|
}
|
|
3345
4093
|
async getProcessingMessages(threadId, scope) {
|
|
3346
4094
|
const filter = scopeClause(scope);
|
|
3347
|
-
const
|
|
4095
|
+
const rows2 = this.db.prepare(
|
|
3348
4096
|
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
3349
4097
|
).all(threadId, ...filter.params);
|
|
3350
|
-
return
|
|
4098
|
+
return rows2.map(rowToMessage);
|
|
3351
4099
|
}
|
|
3352
4100
|
async getQueueSize(threadId, scope) {
|
|
3353
4101
|
const filter = scopeClause(scope);
|
|
@@ -3357,10 +4105,10 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3357
4105
|
return row.count;
|
|
3358
4106
|
}
|
|
3359
4107
|
async getThreadsWithPendingMessages() {
|
|
3360
|
-
const
|
|
4108
|
+
const rows2 = this.db.prepare(
|
|
3361
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`
|
|
3362
4110
|
).all();
|
|
3363
|
-
return
|
|
4111
|
+
return rows2.map((r) => ({
|
|
3364
4112
|
tenantId: r.tenant_id,
|
|
3365
4113
|
assistantId: r.assistant_id,
|
|
3366
4114
|
threadId: r.thread_id,
|
|
@@ -3399,6 +4147,12 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3399
4147
|
return rowToMessage(row);
|
|
3400
4148
|
}
|
|
3401
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
|
+
}
|
|
3402
4156
|
function scopeClause(scope) {
|
|
3403
4157
|
if (!scope) return { sql: "", params: [] };
|
|
3404
4158
|
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
@@ -3416,7 +4170,9 @@ function rowToMessage(row) {
|
|
|
3416
4170
|
createdAt: parseISO(row.created_at),
|
|
3417
4171
|
priority: row.priority || 0,
|
|
3418
4172
|
command: row.command ? JSON.parse(row.command) : void 0,
|
|
3419
|
-
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)
|
|
3420
4176
|
};
|
|
3421
4177
|
}
|
|
3422
4178
|
|
|
@@ -3443,10 +4199,10 @@ var LocalSkillStore = class {
|
|
|
3443
4199
|
ensureTable(db, DDL18);
|
|
3444
4200
|
}
|
|
3445
4201
|
async getAllSkills(tenantId, _context) {
|
|
3446
|
-
const
|
|
4202
|
+
const rows2 = this.db.prepare(
|
|
3447
4203
|
`SELECT * FROM lt_skills WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
3448
4204
|
).all(tenantId);
|
|
3449
|
-
return
|
|
4205
|
+
return rows2.map(mapRowToSkill);
|
|
3450
4206
|
}
|
|
3451
4207
|
async getSkillById(tenantId, id, _context) {
|
|
3452
4208
|
const row = this.db.prepare(
|
|
@@ -3551,16 +4307,16 @@ var LocalSkillStore = class {
|
|
|
3551
4307
|
return all.filter((s) => s.metadata?.[metadataKey] === metadataValue);
|
|
3552
4308
|
}
|
|
3553
4309
|
async filterByCompatibility(tenantId, compatibility, _context) {
|
|
3554
|
-
const
|
|
4310
|
+
const rows2 = this.db.prepare(
|
|
3555
4311
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND compatibility = ? ORDER BY created_at DESC`
|
|
3556
4312
|
).all(tenantId, compatibility);
|
|
3557
|
-
return
|
|
4313
|
+
return rows2.map(mapRowToSkill);
|
|
3558
4314
|
}
|
|
3559
4315
|
async filterByLicense(tenantId, license, _context) {
|
|
3560
|
-
const
|
|
4316
|
+
const rows2 = this.db.prepare(
|
|
3561
4317
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND license = ? ORDER BY created_at DESC`
|
|
3562
4318
|
).all(tenantId, license);
|
|
3563
|
-
return
|
|
4319
|
+
return rows2.map(mapRowToSkill);
|
|
3564
4320
|
}
|
|
3565
4321
|
async getSubSkills(tenantId, parentSkillName, _context) {
|
|
3566
4322
|
const all = await this.getAllSkills(tenantId);
|
|
@@ -3772,40 +4528,40 @@ var LocalScheduleStorage = class {
|
|
|
3772
4528
|
this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
|
|
3773
4529
|
}
|
|
3774
4530
|
async getActiveTasks() {
|
|
3775
|
-
const
|
|
4531
|
+
const rows2 = this.db.prepare(
|
|
3776
4532
|
`SELECT * FROM lt_scheduled_tasks WHERE status IN ('pending', 'paused') ORDER BY created_at ASC`
|
|
3777
4533
|
).all();
|
|
3778
|
-
return
|
|
4534
|
+
return rows2.map(mapRowToTask);
|
|
3779
4535
|
}
|
|
3780
4536
|
async getTasksByType(taskType) {
|
|
3781
|
-
const
|
|
4537
|
+
const rows2 = this.db.prepare(
|
|
3782
4538
|
`SELECT * FROM lt_scheduled_tasks WHERE task_type = ? ORDER BY created_at DESC`
|
|
3783
4539
|
).all(taskType);
|
|
3784
|
-
return
|
|
4540
|
+
return rows2.map(mapRowToTask);
|
|
3785
4541
|
}
|
|
3786
4542
|
async getTasksByStatus(status) {
|
|
3787
|
-
const
|
|
4543
|
+
const rows2 = this.db.prepare(
|
|
3788
4544
|
`SELECT * FROM lt_scheduled_tasks WHERE status = ? ORDER BY created_at DESC`
|
|
3789
4545
|
).all(status);
|
|
3790
|
-
return
|
|
4546
|
+
return rows2.map(mapRowToTask);
|
|
3791
4547
|
}
|
|
3792
4548
|
async getTasksByExecutionType(executionType) {
|
|
3793
|
-
const
|
|
4549
|
+
const rows2 = this.db.prepare(
|
|
3794
4550
|
`SELECT * FROM lt_scheduled_tasks WHERE execution_type = ? ORDER BY created_at DESC`
|
|
3795
4551
|
).all(executionType);
|
|
3796
|
-
return
|
|
4552
|
+
return rows2.map(mapRowToTask);
|
|
3797
4553
|
}
|
|
3798
4554
|
async getTasksByAssistantId(assistantId) {
|
|
3799
|
-
const
|
|
4555
|
+
const rows2 = this.db.prepare(
|
|
3800
4556
|
`SELECT * FROM lt_scheduled_tasks WHERE assistant_id = ? ORDER BY created_at DESC`
|
|
3801
4557
|
).all(assistantId);
|
|
3802
|
-
return
|
|
4558
|
+
return rows2.map(mapRowToTask);
|
|
3803
4559
|
}
|
|
3804
4560
|
async getTasksByThreadId(threadId) {
|
|
3805
|
-
const
|
|
4561
|
+
const rows2 = this.db.prepare(
|
|
3806
4562
|
`SELECT * FROM lt_scheduled_tasks WHERE thread_id = ? ORDER BY created_at DESC`
|
|
3807
4563
|
).all(threadId);
|
|
3808
|
-
return
|
|
4564
|
+
return rows2.map(mapRowToTask);
|
|
3809
4565
|
}
|
|
3810
4566
|
async getAllTasks(filters) {
|
|
3811
4567
|
const conditions = [];
|
|
@@ -3845,8 +4601,8 @@ var LocalScheduleStorage = class {
|
|
|
3845
4601
|
query += ` OFFSET ?`;
|
|
3846
4602
|
values.push(filters.offset);
|
|
3847
4603
|
}
|
|
3848
|
-
const
|
|
3849
|
-
return
|
|
4604
|
+
const rows2 = this.db.prepare(query).all(...values);
|
|
4605
|
+
return rows2.map(mapRowToTask);
|
|
3850
4606
|
}
|
|
3851
4607
|
async countTasks(filters) {
|
|
3852
4608
|
const conditions = [];
|
|
@@ -3919,6 +4675,15 @@ function mapRowToTask(row) {
|
|
|
3919
4675
|
// src/stores/LocalTaskStore.ts
|
|
3920
4676
|
var import_crypto5 = require("crypto");
|
|
3921
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
|
+
]);
|
|
3922
4687
|
var NEXT_UPDATED_AT_SQL = `updated_at = CASE
|
|
3923
4688
|
WHEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) = updated_at AND updated_at >= ?
|
|
3924
4689
|
THEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds')
|
|
@@ -3991,6 +4756,9 @@ function nextISO(previous) {
|
|
|
3991
4756
|
const previousMs = new Date(previous).getTime();
|
|
3992
4757
|
return new Date(Math.max(Date.now(), previousMs + 1)).toISOString();
|
|
3993
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
|
+
}
|
|
3994
4762
|
var LocalTaskStore = class {
|
|
3995
4763
|
constructor(db) {
|
|
3996
4764
|
this.db = db;
|
|
@@ -4004,10 +4772,10 @@ var LocalTaskStore = class {
|
|
|
4004
4772
|
this.ensureColumn("lt_tasks", "files", "files TEXT");
|
|
4005
4773
|
}
|
|
4006
4774
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4007
|
-
ensureColumn(table, column,
|
|
4775
|
+
ensureColumn(table, column, ddl2) {
|
|
4008
4776
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
4009
4777
|
if (!cols.some((c) => c.name === column)) {
|
|
4010
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
4778
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
4011
4779
|
}
|
|
4012
4780
|
}
|
|
4013
4781
|
async create(params) {
|
|
@@ -4073,7 +4841,8 @@ var LocalTaskStore = class {
|
|
|
4073
4841
|
conditions.push("workspace_id = ?");
|
|
4074
4842
|
params.push(filter.workspaceId);
|
|
4075
4843
|
}
|
|
4076
|
-
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) {
|
|
4077
4846
|
conditions.push("project_id = ?");
|
|
4078
4847
|
params.push(filter.projectId);
|
|
4079
4848
|
}
|
|
@@ -4103,10 +4872,44 @@ var LocalTaskStore = class {
|
|
|
4103
4872
|
const where = conditions.join(" AND ");
|
|
4104
4873
|
const limit = filter.limit || 100;
|
|
4105
4874
|
const offset = filter.offset || 0;
|
|
4106
|
-
const
|
|
4107
|
-
`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 ?`
|
|
4108
4877
|
).all(...params, limit, offset);
|
|
4109
|
-
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);
|
|
4110
4913
|
}
|
|
4111
4914
|
async update(tenantId, id, updates) {
|
|
4112
4915
|
const existing = await this.getById(tenantId, id);
|
|
@@ -4458,6 +5261,54 @@ var LocalTaskStore = class {
|
|
|
4458
5261
|
if (result.changes === 0) return null;
|
|
4459
5262
|
return this.getById(tenantId, id);
|
|
4460
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
|
+
}
|
|
4461
5312
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
4462
5313
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
4463
5314
|
if (expectedStatuses.length === 0) return null;
|
|
@@ -4558,11 +5409,36 @@ var LocalTaskStore = class {
|
|
|
4558
5409
|
).run(tenantId, id);
|
|
4559
5410
|
return result.changes > 0;
|
|
4560
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
|
+
}
|
|
4561
5429
|
};
|
|
4562
5430
|
|
|
4563
5431
|
// src/stores/LocalTaskWorkItemStore.ts
|
|
4564
|
-
var
|
|
5432
|
+
var import_protocols5 = require("@axiom-lattice/protocols");
|
|
4565
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
|
+
]);
|
|
4566
5442
|
var DDL21 = `
|
|
4567
5443
|
CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
4568
5444
|
id TEXT NOT NULL,
|
|
@@ -4580,6 +5456,15 @@ CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
|
4580
5456
|
PRIMARY KEY (tenant_id, id)
|
|
4581
5457
|
);
|
|
4582
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
|
+
}
|
|
4583
5468
|
function mapRowToWorkItem(row) {
|
|
4584
5469
|
return {
|
|
4585
5470
|
id: row.id,
|
|
@@ -4589,7 +5474,7 @@ function mapRowToWorkItem(row) {
|
|
|
4589
5474
|
actor: row.actor,
|
|
4590
5475
|
threadId: row.thread_id ?? void 0,
|
|
4591
5476
|
summary: row.summary ?? void 0,
|
|
4592
|
-
detail:
|
|
5477
|
+
detail: parseDetail(row.detail),
|
|
4593
5478
|
attempt: row.attempt ?? void 0,
|
|
4594
5479
|
workspaceId: row.workspace_id ?? void 0,
|
|
4595
5480
|
projectId: row.project_id ?? void 0,
|
|
@@ -4597,6 +5482,34 @@ function mapRowToWorkItem(row) {
|
|
|
4597
5482
|
createdAt: parseISO(row.created_at)
|
|
4598
5483
|
};
|
|
4599
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
|
+
}
|
|
4600
5513
|
var LocalTaskWorkItemStore = class {
|
|
4601
5514
|
constructor(db) {
|
|
4602
5515
|
this.db = db;
|
|
@@ -4611,12 +5524,15 @@ var LocalTaskWorkItemStore = class {
|
|
|
4611
5524
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
4612
5525
|
ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
|
|
4613
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;`);
|
|
4614
5530
|
}
|
|
4615
5531
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4616
|
-
ensureColumn(table, column,
|
|
5532
|
+
ensureColumn(table, column, ddl2) {
|
|
4617
5533
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
4618
5534
|
if (!cols.some((c) => c.name === column)) {
|
|
4619
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
5535
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
4620
5536
|
}
|
|
4621
5537
|
}
|
|
4622
5538
|
async create(params) {
|
|
@@ -4644,6 +5560,41 @@ var LocalTaskWorkItemStore = class {
|
|
|
4644
5560
|
).get(params.tenantId, id);
|
|
4645
5561
|
return mapRowToWorkItem(row);
|
|
4646
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
|
+
}
|
|
4647
5598
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
4648
5599
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
4649
5600
|
const row = this.db.prepare(
|
|
@@ -4696,20 +5647,20 @@ var LocalTaskWorkItemStore = class {
|
|
|
4696
5647
|
const limit = filter.limit || 100;
|
|
4697
5648
|
const offset = filter.offset || 0;
|
|
4698
5649
|
const order = filter.order === "asc" ? "ASC" : "DESC";
|
|
4699
|
-
const
|
|
5650
|
+
const rows2 = this.db.prepare(
|
|
4700
5651
|
`SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at ${order}, id ${order} LIMIT ? OFFSET ?`
|
|
4701
5652
|
).all(...params, limit, offset);
|
|
4702
|
-
return
|
|
5653
|
+
return rows2.map(mapRowToWorkItem);
|
|
4703
5654
|
}
|
|
4704
5655
|
/** List pending execution results using one bounded SQLite anti-join query. */
|
|
4705
5656
|
async listPendingExecutionResults(params) {
|
|
4706
|
-
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit >
|
|
4707
|
-
const error = new RangeError(`limit must be a safe integer between 0 and ${
|
|
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}`);
|
|
4708
5659
|
error.code = "INVALID_LIMIT";
|
|
4709
5660
|
throw error;
|
|
4710
5661
|
}
|
|
4711
5662
|
if (params.limit === 0) return [];
|
|
4712
|
-
const
|
|
5663
|
+
const rows2 = this.db.prepare(
|
|
4713
5664
|
`SELECT result.*
|
|
4714
5665
|
FROM lt_task_work_items AS result
|
|
4715
5666
|
WHERE result.tenant_id = ?
|
|
@@ -4729,7 +5680,31 @@ var LocalTaskWorkItemStore = class {
|
|
|
4729
5680
|
ORDER BY result.created_at DESC, result.id DESC
|
|
4730
5681
|
LIMIT ?`
|
|
4731
5682
|
).all(params.tenantId, params.taskId, params.limit);
|
|
4732
|
-
return
|
|
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);
|
|
4733
5708
|
}
|
|
4734
5709
|
};
|
|
4735
5710
|
|
|
@@ -4838,6 +5813,10 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4838
5813
|
assistant: new LocalAssistantStore(db),
|
|
4839
5814
|
workspace: new LocalWorkspaceStore(db),
|
|
4840
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),
|
|
4841
5820
|
user: new LocalUserStore(db),
|
|
4842
5821
|
tenant: new LocalTenantStore(db),
|
|
4843
5822
|
userTenantLink: new LocalUserTenantLinkStore(db),
|
|
@@ -4871,7 +5850,7 @@ var InMemoryConversationStore = class {
|
|
|
4871
5850
|
}
|
|
4872
5851
|
async createConversation(input) {
|
|
4873
5852
|
const now = isoNow();
|
|
4874
|
-
const
|
|
5853
|
+
const record2 = {
|
|
4875
5854
|
conversationId: input.conversationId,
|
|
4876
5855
|
tenantId: input.tenantId,
|
|
4877
5856
|
taskIds: [],
|
|
@@ -4880,17 +5859,17 @@ var InMemoryConversationStore = class {
|
|
|
4880
5859
|
createdAt: now,
|
|
4881
5860
|
updatedAt: now
|
|
4882
5861
|
};
|
|
4883
|
-
this.store.set(input.conversationId,
|
|
4884
|
-
return
|
|
5862
|
+
this.store.set(input.conversationId, record2);
|
|
5863
|
+
return record2;
|
|
4885
5864
|
}
|
|
4886
5865
|
async addTaskToConversation(conversationId, taskId) {
|
|
4887
|
-
const
|
|
4888
|
-
if (!
|
|
5866
|
+
const record2 = this.store.get(conversationId);
|
|
5867
|
+
if (!record2) {
|
|
4889
5868
|
throw new Error(`Conversation not found: ${conversationId}`);
|
|
4890
5869
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
return
|
|
5870
|
+
record2.taskIds.push(taskId);
|
|
5871
|
+
record2.updatedAt = isoNow();
|
|
5872
|
+
return record2;
|
|
4894
5873
|
}
|
|
4895
5874
|
async deleteConversation(conversationId) {
|
|
4896
5875
|
this.store.delete(conversationId);
|
|
@@ -4899,6 +5878,8 @@ var InMemoryConversationStore = class {
|
|
|
4899
5878
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4900
5879
|
0 && (module.exports = {
|
|
4901
5880
|
DatabaseWrapper,
|
|
5881
|
+
DuplicateProjectMembershipError,
|
|
5882
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
4902
5883
|
InMemoryConversationStore,
|
|
4903
5884
|
LocalA2AApiKeyStore,
|
|
4904
5885
|
LocalAssistantStore,
|
|
@@ -4910,6 +5891,10 @@ var InMemoryConversationStore = class {
|
|
|
4910
5891
|
LocalEvalStore,
|
|
4911
5892
|
LocalMcpServerConfigStore,
|
|
4912
5893
|
LocalMetricsServerConfigStore,
|
|
5894
|
+
LocalProjectBotMembershipStore,
|
|
5895
|
+
LocalProjectMembershipStore,
|
|
5896
|
+
LocalProjectRoomMessageStore,
|
|
5897
|
+
LocalProjectRoomStore,
|
|
4913
5898
|
LocalProjectStore,
|
|
4914
5899
|
LocalScheduleStorage,
|
|
4915
5900
|
LocalSkillStore,
|
|
@@ -4923,6 +5908,9 @@ var InMemoryConversationStore = class {
|
|
|
4923
5908
|
LocalWorkflowTrackingStore,
|
|
4924
5909
|
LocalWorkspaceStore,
|
|
4925
5910
|
MigrationManager,
|
|
5911
|
+
ProjectBotMembershipIdConflictError,
|
|
5912
|
+
ProjectMembershipIdConflictError,
|
|
5913
|
+
ProjectRoomMessageIdConflictError,
|
|
4926
5914
|
RunResult,
|
|
4927
5915
|
StatementWrapper,
|
|
4928
5916
|
closeDatabase,
|