@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.mjs
CHANGED
|
@@ -81,11 +81,11 @@ var StatementWrapper = class {
|
|
|
81
81
|
if (params.length > 0) {
|
|
82
82
|
stmt.bind(params);
|
|
83
83
|
}
|
|
84
|
-
const
|
|
84
|
+
const rows2 = [];
|
|
85
85
|
while (stmt.step()) {
|
|
86
|
-
|
|
86
|
+
rows2.push(stmt.getAsObject());
|
|
87
87
|
}
|
|
88
|
-
return
|
|
88
|
+
return rows2;
|
|
89
89
|
} finally {
|
|
90
90
|
stmt.free();
|
|
91
91
|
}
|
|
@@ -160,8 +160,8 @@ function closeDatabase() {
|
|
|
160
160
|
_db = null;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
function ensureTable(db,
|
|
164
|
-
db.exec(
|
|
163
|
+
function ensureTable(db, ddl2) {
|
|
164
|
+
db.exec(ddl2);
|
|
165
165
|
}
|
|
166
166
|
function nowISO() {
|
|
167
167
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -205,7 +205,10 @@ var MigrationManager = class {
|
|
|
205
205
|
this.db.run(
|
|
206
206
|
`INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at)
|
|
207
207
|
VALUES (?, ?, ?, ?)`,
|
|
208
|
-
|
|
208
|
+
this.storeName,
|
|
209
|
+
m.name,
|
|
210
|
+
m.version,
|
|
211
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
209
212
|
);
|
|
210
213
|
}
|
|
211
214
|
}
|
|
@@ -255,10 +258,10 @@ var MigrationManager = class {
|
|
|
255
258
|
`);
|
|
256
259
|
}
|
|
257
260
|
getAppliedNames() {
|
|
258
|
-
const
|
|
261
|
+
const rows2 = this.db.prepare(
|
|
259
262
|
`SELECT name FROM lattice_schema_migrations WHERE store_name = ? ORDER BY version`
|
|
260
263
|
).all(this.storeName);
|
|
261
|
-
return new Set(
|
|
264
|
+
return new Set(rows2.map((r) => r.name));
|
|
262
265
|
}
|
|
263
266
|
};
|
|
264
267
|
|
|
@@ -293,10 +296,10 @@ var LocalThreadStore = class {
|
|
|
293
296
|
mm.migrate();
|
|
294
297
|
}
|
|
295
298
|
async getThreadsByAssistantId(tenantId, assistantId, metadataFilter) {
|
|
296
|
-
const
|
|
299
|
+
const rows2 = this.db.prepare(
|
|
297
300
|
`SELECT * FROM lt_threads WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
298
301
|
).all(tenantId, assistantId);
|
|
299
|
-
let threads =
|
|
302
|
+
let threads = rows2.map(mapRowToThread);
|
|
300
303
|
if (metadataFilter && Object.keys(metadataFilter).length > 0) {
|
|
301
304
|
threads = threads.filter(
|
|
302
305
|
(t) => Object.entries(metadataFilter).every(
|
|
@@ -393,10 +396,10 @@ var LocalAssistantStore = class {
|
|
|
393
396
|
ensureTable(db, DDL2);
|
|
394
397
|
}
|
|
395
398
|
async getAllAssistants(tenantId) {
|
|
396
|
-
const
|
|
399
|
+
const rows2 = this.db.prepare(
|
|
397
400
|
`SELECT * FROM lt_assistants WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
398
401
|
).all(tenantId);
|
|
399
|
-
return
|
|
402
|
+
return rows2.map(mapRowToAssistant);
|
|
400
403
|
}
|
|
401
404
|
async getAssistantById(tenantId, id) {
|
|
402
405
|
const row = this.db.prepare(
|
|
@@ -509,10 +512,10 @@ var LocalWorkspaceStore = class {
|
|
|
509
512
|
ensureTable(db, DDL3);
|
|
510
513
|
}
|
|
511
514
|
async getAllWorkspaces(tenantId) {
|
|
512
|
-
const
|
|
515
|
+
const rows2 = this.db.prepare(
|
|
513
516
|
`SELECT * FROM lt_workspaces WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
514
517
|
).all(tenantId);
|
|
515
|
-
return
|
|
518
|
+
return rows2.map(mapRowToWorkspace);
|
|
516
519
|
}
|
|
517
520
|
async getWorkspaceById(tenantId, id) {
|
|
518
521
|
const row = this.db.prepare(
|
|
@@ -612,15 +615,15 @@ var LocalProjectStore = class {
|
|
|
612
615
|
}
|
|
613
616
|
async getProjectsByWorkspace(tenantId, workspaceId, filter) {
|
|
614
617
|
if (filter?.kind !== void 0) {
|
|
615
|
-
const
|
|
618
|
+
const rows3 = this.db.prepare(
|
|
616
619
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? AND kind = ? ORDER BY created_at DESC`
|
|
617
620
|
).all(tenantId, workspaceId, filter.kind);
|
|
618
|
-
return
|
|
621
|
+
return rows3.map(mapRowToProject);
|
|
619
622
|
}
|
|
620
|
-
const
|
|
623
|
+
const rows2 = this.db.prepare(
|
|
621
624
|
`SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? ORDER BY created_at DESC`
|
|
622
625
|
).all(tenantId, workspaceId);
|
|
623
|
-
return
|
|
626
|
+
return rows2.map(mapRowToProject);
|
|
624
627
|
}
|
|
625
628
|
async getProjectById(tenantId, id) {
|
|
626
629
|
const row = this.db.prepare(
|
|
@@ -724,8 +727,8 @@ var LocalProjectStore = class {
|
|
|
724
727
|
return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
725
728
|
}
|
|
726
729
|
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
727
|
-
const
|
|
728
|
-
return
|
|
730
|
+
const rows2 = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
|
|
731
|
+
return rows2.some((row) => {
|
|
729
732
|
if (!row.config) return false;
|
|
730
733
|
let config;
|
|
731
734
|
try {
|
|
@@ -739,10 +742,10 @@ var LocalProjectStore = class {
|
|
|
739
742
|
});
|
|
740
743
|
}
|
|
741
744
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
742
|
-
ensureColumn(table, column,
|
|
745
|
+
ensureColumn(table, column, ddl2) {
|
|
743
746
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
744
747
|
if (!cols.some((c) => c.name === column)) {
|
|
745
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
748
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
746
749
|
}
|
|
747
750
|
}
|
|
748
751
|
};
|
|
@@ -760,6 +763,640 @@ function mapRowToProject(row) {
|
|
|
760
763
|
};
|
|
761
764
|
}
|
|
762
765
|
|
|
766
|
+
// src/stores/project-room-schema.ts
|
|
767
|
+
var schemaMarker = Symbol("projectRoomSchema");
|
|
768
|
+
var activeTransactions = /* @__PURE__ */ new WeakSet();
|
|
769
|
+
var migrationName = "create_project_room_tables";
|
|
770
|
+
var migrationTableDdl = `CREATE TABLE IF NOT EXISTS lattice_schema_migrations (
|
|
771
|
+
store_name TEXT NOT NULL, name TEXT NOT NULL, version INTEGER NOT NULL, applied_at TEXT NOT NULL,
|
|
772
|
+
PRIMARY KEY (store_name, name)
|
|
773
|
+
)`;
|
|
774
|
+
var ddl = `
|
|
775
|
+
CREATE TABLE IF NOT EXISTS lt_project_rooms (
|
|
776
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
777
|
+
type TEXT NOT NULL CHECK (type = 'main'), name TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
778
|
+
PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, type)
|
|
779
|
+
);
|
|
780
|
+
CREATE TABLE IF NOT EXISTS lt_project_memberships (
|
|
781
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, project_id TEXT NOT NULL, user_id TEXT NOT NULL,
|
|
782
|
+
role TEXT NOT NULL CHECK (role IN ('owner','admin','member','viewer')),
|
|
783
|
+
status TEXT NOT NULL CHECK (status IN ('active','removed')), joined_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
784
|
+
PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, user_id)
|
|
785
|
+
);
|
|
786
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_memberships_project ON lt_project_memberships (tenant_id, project_id);
|
|
787
|
+
CREATE TABLE IF NOT EXISTS lt_project_bot_memberships (
|
|
788
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
789
|
+
room_id TEXT NOT NULL, assistant_id TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('coordinator','specialist')),
|
|
790
|
+
title TEXT NOT NULL, responsibility TEXT, mention_name TEXT NOT NULL,
|
|
791
|
+
status TEXT NOT NULL CHECK (status IN ('active','paused','removed')), room_thread_id TEXT NOT NULL,
|
|
792
|
+
joined_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id),
|
|
793
|
+
UNIQUE (tenant_id, project_id, assistant_id)
|
|
794
|
+
);
|
|
795
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_coordinator
|
|
796
|
+
ON lt_project_bot_memberships (tenant_id, project_id) WHERE role = 'coordinator' AND status IN ('active','paused');
|
|
797
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_mention
|
|
798
|
+
ON lt_project_bot_memberships (tenant_id, room_id, mention_name) WHERE status IN ('active','paused');
|
|
799
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_project ON lt_project_bot_memberships (tenant_id, project_id);
|
|
800
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_room ON lt_project_bot_memberships (tenant_id, room_id);
|
|
801
|
+
CREATE TABLE IF NOT EXISTS lt_project_room_messages (
|
|
802
|
+
id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL, room_id TEXT NOT NULL,
|
|
803
|
+
author TEXT NOT NULL, content TEXT NOT NULL, mentions TEXT NOT NULL DEFAULT '[]', reply_to_message_id TEXT,
|
|
804
|
+
source TEXT NOT NULL CHECK (source IN ('user','agent','task','routine','system')), source_id TEXT,
|
|
805
|
+
idempotency_key TEXT, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id)
|
|
806
|
+
);
|
|
807
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_room_messages_idempotency
|
|
808
|
+
ON lt_project_room_messages (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
809
|
+
CREATE INDEX IF NOT EXISTS idx_lt_project_room_messages_room_created
|
|
810
|
+
ON lt_project_room_messages (tenant_id, room_id, created_at DESC, id DESC);
|
|
811
|
+
`;
|
|
812
|
+
var tableColumns = {
|
|
813
|
+
lt_project_rooms: ["id", "tenant_id", "workspace_id", "project_id", "type", "name", "created_at", "updated_at"],
|
|
814
|
+
lt_project_memberships: ["id", "tenant_id", "project_id", "user_id", "role", "status", "joined_at", "updated_at"],
|
|
815
|
+
lt_project_bot_memberships: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "assistant_id", "role", "title", "responsibility", "mention_name", "status", "room_thread_id", "joined_at", "updated_at"],
|
|
816
|
+
lt_project_room_messages: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "author", "content", "mentions", "reply_to_message_id", "source", "source_id", "idempotency_key", "created_at"]
|
|
817
|
+
};
|
|
818
|
+
var indexes = {
|
|
819
|
+
idx_lt_project_memberships_project: { table: "lt_project_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
|
|
820
|
+
uq_lt_project_bot_memberships_coordinator: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: true, partial: true, sql: ["role = 'coordinator'", "status in ('active','paused')"] },
|
|
821
|
+
uq_lt_project_bot_memberships_mention: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id", "mention_name"], unique: true, partial: true, sql: ["status in ('active','paused')"] },
|
|
822
|
+
idx_lt_project_bot_memberships_project: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
|
|
823
|
+
idx_lt_project_bot_memberships_room: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id"], unique: false, partial: false },
|
|
824
|
+
uq_lt_project_room_messages_idempotency: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "idempotency_key"], unique: true, partial: true, sql: ["idempotency_key is not null"] },
|
|
825
|
+
idx_lt_project_room_messages_room_created: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "created_at", "id"], unique: false, partial: false, sql: ["created_at desc", "id desc"] }
|
|
826
|
+
};
|
|
827
|
+
var uniqueConstraints = {
|
|
828
|
+
lt_project_rooms: [["tenant_id", "id"], ["tenant_id", "project_id", "type"]],
|
|
829
|
+
lt_project_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "user_id"]],
|
|
830
|
+
lt_project_bot_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "assistant_id"]],
|
|
831
|
+
lt_project_room_messages: [["tenant_id", "id"]]
|
|
832
|
+
};
|
|
833
|
+
var checkConstraints = {
|
|
834
|
+
lt_project_rooms: [{ name: "type", fragment: "type='main'" }],
|
|
835
|
+
lt_project_memberships: [
|
|
836
|
+
{ name: "role", fragment: "role in ('owner','admin','member','viewer')" },
|
|
837
|
+
{ name: "status", fragment: "status in ('active','removed')" }
|
|
838
|
+
],
|
|
839
|
+
lt_project_bot_memberships: [
|
|
840
|
+
{ name: "role", fragment: "role in ('coordinator','specialist')" },
|
|
841
|
+
{ name: "status", fragment: "status in ('active','paused','removed')" }
|
|
842
|
+
],
|
|
843
|
+
lt_project_room_messages: [{ name: "source", fragment: "source in ('user','agent','task','routine','system')" }]
|
|
844
|
+
};
|
|
845
|
+
function rows(db, sql) {
|
|
846
|
+
const result = db.getRawDb().exec(sql);
|
|
847
|
+
if (!result[0]) return [];
|
|
848
|
+
return result[0].values.map((values) => Object.fromEntries(result[0].columns.map((column, index) => [column, values[index]])));
|
|
849
|
+
}
|
|
850
|
+
function normalizeSql(value) {
|
|
851
|
+
return value.toLowerCase().replace(/["`]/g, "").replace(/\[|\]/g, "").replace(/\s+/g, " ").replace(/\s*([=,])\s*/g, "$1").replace(/[()]/g, "").trim();
|
|
852
|
+
}
|
|
853
|
+
function extractCheckExpressions(sql) {
|
|
854
|
+
const checks = [];
|
|
855
|
+
let quoted = false;
|
|
856
|
+
for (let index = 0; index < sql.length; index += 1) {
|
|
857
|
+
if (sql[index] === "'") {
|
|
858
|
+
if (quoted && sql[index + 1] === "'") {
|
|
859
|
+
index += 1;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
quoted = !quoted;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (quoted || sql.slice(index, index + 5).toLowerCase() !== "check") continue;
|
|
866
|
+
const before = sql[index - 1];
|
|
867
|
+
const after = sql[index + 5];
|
|
868
|
+
if (before && /[a-z0-9_]/i.test(before) || after && /[a-z0-9_]/i.test(after)) continue;
|
|
869
|
+
let opening = index + 5;
|
|
870
|
+
while (/\s/.test(sql[opening] ?? "")) opening += 1;
|
|
871
|
+
if (sql[opening] !== "(") continue;
|
|
872
|
+
let depth = 1;
|
|
873
|
+
let expressionQuoted = false;
|
|
874
|
+
for (let cursor = opening + 1; cursor < sql.length; cursor += 1) {
|
|
875
|
+
if (sql[cursor] === "'") {
|
|
876
|
+
if (expressionQuoted && sql[cursor + 1] === "'") {
|
|
877
|
+
cursor += 1;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
expressionQuoted = !expressionQuoted;
|
|
881
|
+
} else if (!expressionQuoted && sql[cursor] === "(") {
|
|
882
|
+
depth += 1;
|
|
883
|
+
} else if (!expressionQuoted && sql[cursor] === ")") {
|
|
884
|
+
depth -= 1;
|
|
885
|
+
if (depth === 0) {
|
|
886
|
+
checks.push(sql.slice(opening + 1, cursor));
|
|
887
|
+
index = cursor;
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return checks;
|
|
894
|
+
}
|
|
895
|
+
function validateProjectRoomSchema(db) {
|
|
896
|
+
for (const [table, expected] of Object.entries(tableColumns)) {
|
|
897
|
+
const actual = rows(db, `PRAGMA table_info(${table})`);
|
|
898
|
+
if (actual.map((column) => column.name).join(",") !== expected.join(",") || actual.some((column) => column.type !== "TEXT") || actual.some((column) => column.name !== "responsibility" && column.name !== "reply_to_message_id" && column.name !== "source_id" && column.name !== "idempotency_key" && column.notnull !== 1)) {
|
|
899
|
+
throw new Error(`Invalid project room schema: table '${table}' has incompatible columns`);
|
|
900
|
+
}
|
|
901
|
+
const uniqueIndexes = rows(db, `PRAGMA index_list(${table})`).filter((index) => index.unique === 1);
|
|
902
|
+
const signatures = uniqueIndexes.map((index) => rows(db, `PRAGMA index_info(${String(index.name)})`).map((column) => column.name).join(","));
|
|
903
|
+
if (uniqueConstraints[table].some((constraint) => !signatures.includes(constraint.join(",")))) {
|
|
904
|
+
throw new Error(`Invalid project room schema: table '${table}' is missing required uniqueness`);
|
|
905
|
+
}
|
|
906
|
+
const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '${table}'`)[0]?.sql;
|
|
907
|
+
const checks = typeof definition === "string" ? extractCheckExpressions(definition).map(normalizeSql) : [];
|
|
908
|
+
const missingCheck = checkConstraints[table].find((constraint) => !checks.includes(normalizeSql(constraint.fragment)));
|
|
909
|
+
if (missingCheck) {
|
|
910
|
+
throw new Error(`Invalid project room schema: table '${table}' is missing required CHECK constraint '${missingCheck.name}'`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
for (const [name, expected] of Object.entries(indexes)) {
|
|
914
|
+
const listed = rows(db, `PRAGMA index_list(${expected.table})`).find((index) => index.name === name);
|
|
915
|
+
const columns5 = rows(db, `PRAGMA index_info(${name})`).map((column) => column.name);
|
|
916
|
+
const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'index' AND name = '${name}'`)[0]?.sql;
|
|
917
|
+
const normalized = typeof definition === "string" ? definition.toLowerCase().replace(/\s+/g, " ") : "";
|
|
918
|
+
if (!listed || listed.unique !== Number(expected.unique) || listed.partial !== Number(expected.partial) || columns5.join(",") !== expected.columns.join(",") || expected.sql?.some((fragment) => !normalized.includes(fragment))) {
|
|
919
|
+
throw new Error(`Invalid project room schema: index '${name}' is incompatible`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function ensureProjectRoomSchema(db) {
|
|
924
|
+
const marked = db;
|
|
925
|
+
if (marked[schemaMarker]) return;
|
|
926
|
+
const raw = db.getRawDb();
|
|
927
|
+
const hasMigrationTable = rows(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'lattice_schema_migrations'").length > 0;
|
|
928
|
+
const applied = hasMigrationTable ? rows(db, `SELECT name, version FROM lattice_schema_migrations WHERE store_name = 'project_room' AND name = '${migrationName}'`)[0] : void 0;
|
|
929
|
+
if (applied) {
|
|
930
|
+
if (applied.version !== 1) throw new Error(`Invalid project room schema migration version: ${String(applied.version)}`);
|
|
931
|
+
validateProjectRoomSchema(db);
|
|
932
|
+
marked[schemaMarker] = true;
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
raw.exec("BEGIN IMMEDIATE");
|
|
936
|
+
try {
|
|
937
|
+
raw.exec(migrationTableDdl);
|
|
938
|
+
raw.exec(ddl);
|
|
939
|
+
validateProjectRoomSchema(db);
|
|
940
|
+
raw.run("INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at) VALUES (?, ?, ?, ?)", ["project_room", migrationName, 1, (/* @__PURE__ */ new Date()).toISOString()]);
|
|
941
|
+
raw.exec("COMMIT");
|
|
942
|
+
} catch (error) {
|
|
943
|
+
try {
|
|
944
|
+
raw.exec("ROLLBACK");
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
throw error;
|
|
948
|
+
}
|
|
949
|
+
db.save();
|
|
950
|
+
marked[schemaMarker] = true;
|
|
951
|
+
}
|
|
952
|
+
function transaction(db, work) {
|
|
953
|
+
if (activeTransactions.has(db)) throw new Error("Nested local store transactions are not supported");
|
|
954
|
+
activeTransactions.add(db);
|
|
955
|
+
let began = false;
|
|
956
|
+
try {
|
|
957
|
+
db.exec("BEGIN IMMEDIATE");
|
|
958
|
+
began = true;
|
|
959
|
+
const result = work();
|
|
960
|
+
db.exec("COMMIT");
|
|
961
|
+
db.save();
|
|
962
|
+
return result;
|
|
963
|
+
} catch (error) {
|
|
964
|
+
if (began) {
|
|
965
|
+
try {
|
|
966
|
+
db.exec("ROLLBACK");
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
db.save();
|
|
970
|
+
}
|
|
971
|
+
throw error;
|
|
972
|
+
} finally {
|
|
973
|
+
activeTransactions.delete(db);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function write(db, sql, params) {
|
|
977
|
+
const raw = db.getRawDb();
|
|
978
|
+
raw.run(sql, params);
|
|
979
|
+
return raw.getRowsModified();
|
|
980
|
+
}
|
|
981
|
+
function timestamp(previous) {
|
|
982
|
+
const previousTime = previous === void 0 ? Number.NaN : Date.parse(previous);
|
|
983
|
+
const now = Date.now();
|
|
984
|
+
return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
|
|
985
|
+
}
|
|
986
|
+
function validDate(value) {
|
|
987
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
988
|
+
}
|
|
989
|
+
function isUniqueError(error) {
|
|
990
|
+
return error instanceof Error && error.message.includes("UNIQUE constraint failed");
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/stores/LocalProjectRoomStore.ts
|
|
994
|
+
var columns = "id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at";
|
|
995
|
+
function mapRow(row) {
|
|
996
|
+
const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
|
|
997
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
998
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || row.type !== "main" || typeof row.name !== "string" || !validDate(createdAt) || !validDate(updatedAt)) throw new Error("Invalid project room row");
|
|
999
|
+
return {
|
|
1000
|
+
id: row.id,
|
|
1001
|
+
tenantId: row.tenant_id,
|
|
1002
|
+
workspaceId: row.workspace_id,
|
|
1003
|
+
projectId: row.project_id,
|
|
1004
|
+
type: "main",
|
|
1005
|
+
name: row.name,
|
|
1006
|
+
createdAt,
|
|
1007
|
+
updatedAt
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
var LocalProjectRoomStore = class {
|
|
1011
|
+
constructor(db) {
|
|
1012
|
+
this.db = db;
|
|
1013
|
+
ensureProjectRoomSchema(db);
|
|
1014
|
+
}
|
|
1015
|
+
async ensureMainRoom(input) {
|
|
1016
|
+
return transaction(this.db, () => {
|
|
1017
|
+
const existing = this.getRow(input.tenantId, input.projectId);
|
|
1018
|
+
if (existing) return mapRow(existing);
|
|
1019
|
+
const now = timestamp();
|
|
1020
|
+
write(this.db, "INSERT INTO lt_project_rooms (id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at) VALUES (?, ?, ?, ?, 'main', ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.name, now, now]);
|
|
1021
|
+
return mapRow(this.getRow(input.tenantId, input.projectId));
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
async getMainRoom(tenantId, projectId) {
|
|
1025
|
+
const row = this.getRow(tenantId, projectId);
|
|
1026
|
+
return row ? mapRow(row) : null;
|
|
1027
|
+
}
|
|
1028
|
+
getRow(tenantId, projectId) {
|
|
1029
|
+
return this.db.prepare(`SELECT ${columns} FROM lt_project_rooms WHERE tenant_id = ? AND project_id = ? AND type = 'main'`).get(tenantId, projectId);
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// src/stores/LocalProjectMembershipStore.ts
|
|
1034
|
+
var columns2 = "id, tenant_id, project_id, user_id, role, status, joined_at, updated_at";
|
|
1035
|
+
var DuplicateProjectMembershipError = class extends Error {
|
|
1036
|
+
constructor() {
|
|
1037
|
+
super("Project membership already exists for tenant, project, and user");
|
|
1038
|
+
this.name = "DuplicateProjectMembershipError";
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
var ProjectMembershipIdConflictError = class extends Error {
|
|
1042
|
+
constructor() {
|
|
1043
|
+
super("Project membership ID already exists for tenant");
|
|
1044
|
+
this.name = "ProjectMembershipIdConflictError";
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
function role(value) {
|
|
1048
|
+
return value === "owner" || value === "admin" || value === "member" || value === "viewer";
|
|
1049
|
+
}
|
|
1050
|
+
function mapRow2(row) {
|
|
1051
|
+
const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
|
|
1052
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1053
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.project_id !== "string" || typeof row.user_id !== "string" || !role(row.role) || row.status !== "active" && row.status !== "removed" || !validDate(joinedAt) || !validDate(updatedAt)) throw new Error("Invalid project membership row");
|
|
1054
|
+
return {
|
|
1055
|
+
id: row.id,
|
|
1056
|
+
tenantId: row.tenant_id,
|
|
1057
|
+
projectId: row.project_id,
|
|
1058
|
+
userId: row.user_id,
|
|
1059
|
+
role: row.role,
|
|
1060
|
+
status: row.status,
|
|
1061
|
+
joinedAt,
|
|
1062
|
+
updatedAt
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
var LocalProjectMembershipStore = class {
|
|
1066
|
+
constructor(db) {
|
|
1067
|
+
this.db = db;
|
|
1068
|
+
ensureProjectRoomSchema(db);
|
|
1069
|
+
}
|
|
1070
|
+
async list(tenantId, projectId) {
|
|
1071
|
+
return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? ORDER BY joined_at ASC, id ASC`).all(tenantId, projectId).map(mapRow2);
|
|
1072
|
+
}
|
|
1073
|
+
async findByUser(tenantId, projectId, userId) {
|
|
1074
|
+
const row = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?`).get(tenantId, projectId, userId);
|
|
1075
|
+
return row ? mapRow2(row) : null;
|
|
1076
|
+
}
|
|
1077
|
+
async create(input) {
|
|
1078
|
+
return transaction(this.db, () => {
|
|
1079
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
|
|
1080
|
+
const now = timestamp();
|
|
1081
|
+
try {
|
|
1082
|
+
write(this.db, "INSERT INTO lt_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.projectId, input.userId, input.role, input.status, now, now]);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
return mapRow2(this.byId(input.tenantId, input.id));
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
async createInitialOwner(input) {
|
|
1091
|
+
return transaction(this.db, () => {
|
|
1092
|
+
const rows2 = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ?`).all(input.tenantId, input.projectId);
|
|
1093
|
+
if (rows2.length > 0) {
|
|
1094
|
+
const existing = rows2.map(mapRow2).find((item) => item.userId === input.userId && item.role === "owner" && item.status === "active");
|
|
1095
|
+
return existing ? { kind: "existing", membership: existing } : { kind: "already_initialized" };
|
|
1096
|
+
}
|
|
1097
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
|
|
1098
|
+
const now = timestamp();
|
|
1099
|
+
try {
|
|
1100
|
+
write(this.db, "INSERT INTO lt_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES (?, ?, ?, ?, 'owner', 'active', ?, ?)", [input.id, input.tenantId, input.projectId, input.userId, now, now]);
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
return { kind: "created", membership: mapRow2(this.byId(input.tenantId, input.id)) };
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
async updateRole(input) {
|
|
1109
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, input.role, false);
|
|
1110
|
+
}
|
|
1111
|
+
async remove(input) {
|
|
1112
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, void 0, true);
|
|
1113
|
+
}
|
|
1114
|
+
mutate(tenantId, id, expected, nextRole, remove) {
|
|
1115
|
+
return transaction(this.db, () => {
|
|
1116
|
+
const row = this.byId(tenantId, id);
|
|
1117
|
+
if (!row) return { kind: "not_found" };
|
|
1118
|
+
const current = mapRow2(row);
|
|
1119
|
+
if (!validDate(expected) || current.updatedAt.getTime() !== expected.getTime()) return { kind: "conflict" };
|
|
1120
|
+
if (current.role === "owner" && current.status === "active" && (remove || nextRole !== "owner")) {
|
|
1121
|
+
const other = this.db.prepare("SELECT 1 FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND id <> ? AND role = 'owner' AND status = 'active'").get(tenantId, current.projectId, id);
|
|
1122
|
+
if (!other) return { kind: "last_owner" };
|
|
1123
|
+
}
|
|
1124
|
+
const updatedAt = timestamp(row.updated_at);
|
|
1125
|
+
const changes = remove ? write(this.db, "UPDATE lt_project_memberships SET status = 'removed', updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [updatedAt, tenantId, id, row.updated_at]) : write(this.db, "UPDATE lt_project_memberships SET role = ?, updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [nextRole, updatedAt, tenantId, id, row.updated_at]);
|
|
1126
|
+
if (changes === 0) return { kind: "conflict" };
|
|
1127
|
+
return { kind: remove ? "removed" : "updated", membership: mapRow2(this.byId(tenantId, id)) };
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
byId(tenantId, id) {
|
|
1131
|
+
return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1132
|
+
}
|
|
1133
|
+
classifyUnique(tenantId, id, projectId, userId) {
|
|
1134
|
+
if (this.byId(tenantId, id)) return new ProjectMembershipIdConflictError();
|
|
1135
|
+
if (this.db.prepare("SELECT 1 FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?").get(tenantId, projectId, userId)) return new DuplicateProjectMembershipError();
|
|
1136
|
+
return new Error("Unclassified project membership uniqueness conflict");
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
// src/stores/LocalProjectBotMembershipStore.ts
|
|
1141
|
+
var columns3 = "id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at";
|
|
1142
|
+
var ProjectBotMembershipIdConflictError = class extends Error {
|
|
1143
|
+
constructor(tenantId, id) {
|
|
1144
|
+
super(`Project bot membership ID '${id}' already exists in tenant '${tenantId}'`);
|
|
1145
|
+
this.name = "ProjectBotMembershipIdConflictError";
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
function isRole(value) {
|
|
1149
|
+
return value === "coordinator" || value === "specialist";
|
|
1150
|
+
}
|
|
1151
|
+
function isStatus(value) {
|
|
1152
|
+
return value === "active" || value === "paused" || value === "removed";
|
|
1153
|
+
}
|
|
1154
|
+
function mapRow3(row) {
|
|
1155
|
+
const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
|
|
1156
|
+
const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
|
|
1157
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || typeof row.assistant_id !== "string" || !isRole(row.role) || typeof row.title !== "string" || row.responsibility !== null && typeof row.responsibility !== "string" || typeof row.mention_name !== "string" || !isStatus(row.status) || typeof row.room_thread_id !== "string" || !validDate(joinedAt) || !validDate(updatedAt)) throw new Error("Invalid project bot membership row");
|
|
1158
|
+
return {
|
|
1159
|
+
id: row.id,
|
|
1160
|
+
tenantId: row.tenant_id,
|
|
1161
|
+
workspaceId: row.workspace_id,
|
|
1162
|
+
projectId: row.project_id,
|
|
1163
|
+
roomId: row.room_id,
|
|
1164
|
+
assistantId: row.assistant_id,
|
|
1165
|
+
role: row.role,
|
|
1166
|
+
title: row.title,
|
|
1167
|
+
...row.responsibility === null ? {} : { responsibility: row.responsibility },
|
|
1168
|
+
mentionName: row.mention_name,
|
|
1169
|
+
status: row.status,
|
|
1170
|
+
roomThreadId: row.room_thread_id,
|
|
1171
|
+
joinedAt,
|
|
1172
|
+
updatedAt
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
var LocalProjectBotMembershipStore = class {
|
|
1176
|
+
constructor(db) {
|
|
1177
|
+
this.db = db;
|
|
1178
|
+
ensureProjectRoomSchema(db);
|
|
1179
|
+
}
|
|
1180
|
+
async list(tenantId, projectId) {
|
|
1181
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? ORDER BY joined_at ASC, id ASC`).all(tenantId, projectId).map(mapRow3);
|
|
1182
|
+
}
|
|
1183
|
+
async findById(tenantId, id) {
|
|
1184
|
+
const row = this.byId(tenantId, id);
|
|
1185
|
+
return row ? mapRow3(row) : null;
|
|
1186
|
+
}
|
|
1187
|
+
async findByAssistant(tenantId, projectId, assistantId) {
|
|
1188
|
+
const row = this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
|
|
1189
|
+
return row ? mapRow3(row) : null;
|
|
1190
|
+
}
|
|
1191
|
+
async save(input) {
|
|
1192
|
+
try {
|
|
1193
|
+
return transaction(this.db, () => {
|
|
1194
|
+
const existing = this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(input.tenantId, input.projectId, input.assistantId);
|
|
1195
|
+
if (existing) return this.reactivate(existing, input);
|
|
1196
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
1197
|
+
const conflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
|
|
1198
|
+
if (conflict) return { kind: conflict };
|
|
1199
|
+
const now = timestamp();
|
|
1200
|
+
try {
|
|
1201
|
+
write(this.db, "INSERT INTO lt_project_bot_memberships (id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.roomId, input.assistantId, input.role, input.title, input.responsibility ?? null, input.mentionName, input.status, input.roomThreadId, now, now]);
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
if (!isUniqueError(error)) throw error;
|
|
1204
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
1205
|
+
const raced = this.byAssistant(input.tenantId, input.projectId, input.assistantId);
|
|
1206
|
+
if (raced) return this.reactivate(raced, input);
|
|
1207
|
+
const racedConflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
|
|
1208
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1209
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1210
|
+
}
|
|
1211
|
+
return { kind: "created", membership: mapRow3(this.byId(input.tenantId, input.id)) };
|
|
1212
|
+
});
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
const conflict = this.conflict(error);
|
|
1215
|
+
if (conflict) return { kind: conflict };
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
async update(input) {
|
|
1220
|
+
try {
|
|
1221
|
+
return transaction(this.db, () => {
|
|
1222
|
+
const row = this.byId(input.tenantId, input.id);
|
|
1223
|
+
if (!row) return { kind: "not_found" };
|
|
1224
|
+
const existing = mapRow3(row);
|
|
1225
|
+
if (!validDate(input.expectedUpdatedAt) || existing.updatedAt.getTime() !== input.expectedUpdatedAt.getTime()) return { kind: "conflict" };
|
|
1226
|
+
const candidate = { ...existing, ...input.patch };
|
|
1227
|
+
const conflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
|
|
1228
|
+
if (conflict) return { kind: conflict };
|
|
1229
|
+
let changes;
|
|
1230
|
+
try {
|
|
1231
|
+
changes = write(this.db, "UPDATE lt_project_bot_memberships SET role = ?, title = ?, responsibility = ?, mention_name = ?, status = ?, updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [candidate.role, candidate.title, candidate.responsibility ?? null, candidate.mentionName, candidate.status, timestamp(row.updated_at), input.tenantId, input.id, row.updated_at]);
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
if (!isUniqueError(error)) throw error;
|
|
1234
|
+
const racedConflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
|
|
1235
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1236
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1237
|
+
}
|
|
1238
|
+
if (changes === 0) return { kind: "conflict" };
|
|
1239
|
+
return { kind: "updated", membership: mapRow3(this.byId(input.tenantId, input.id)) };
|
|
1240
|
+
});
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
const conflict = this.conflict(error);
|
|
1243
|
+
if (conflict) return { kind: conflict };
|
|
1244
|
+
throw error;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
byId(tenantId, id) {
|
|
1248
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1249
|
+
}
|
|
1250
|
+
byAssistant(tenantId, projectId, assistantId) {
|
|
1251
|
+
return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
|
|
1252
|
+
}
|
|
1253
|
+
reactivate(existing, input) {
|
|
1254
|
+
const persisted = mapRow3(existing);
|
|
1255
|
+
const wasRemoved = persisted.status === "removed";
|
|
1256
|
+
const conflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
|
|
1257
|
+
if (conflict) return { kind: conflict };
|
|
1258
|
+
try {
|
|
1259
|
+
write(this.db, "UPDATE lt_project_bot_memberships SET role = ?, title = ?, responsibility = ?, mention_name = ?, status = 'active', updated_at = ? WHERE tenant_id = ? AND id = ?", [input.role, input.title, input.responsibility ?? null, input.mentionName, timestamp(existing.updated_at), input.tenantId, persisted.id]);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (!isUniqueError(error)) throw error;
|
|
1262
|
+
const racedConflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
|
|
1263
|
+
if (racedConflict) return { kind: racedConflict };
|
|
1264
|
+
throw new Error("Unclassified project bot membership uniqueness conflict");
|
|
1265
|
+
}
|
|
1266
|
+
return { kind: wasRemoved ? "reactivated" : "updated", membership: mapRow3(this.byId(input.tenantId, persisted.id)) };
|
|
1267
|
+
}
|
|
1268
|
+
preflight(tenantId, projectId, roomId, role2, mentionName, status, excludeId) {
|
|
1269
|
+
if (status === "removed") return void 0;
|
|
1270
|
+
if (role2 === "coordinator" && this.db.prepare("SELECT 1 FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND id <> ? AND role = 'coordinator' AND status IN ('active', 'paused')").get(tenantId, projectId, excludeId)) return "coordinator_conflict";
|
|
1271
|
+
if (this.db.prepare("SELECT 1 FROM lt_project_bot_memberships WHERE tenant_id = ? AND room_id = ? AND mention_name = ? AND id <> ? AND status IN ('active', 'paused')").get(tenantId, roomId, mentionName, excludeId)) return "mention_conflict";
|
|
1272
|
+
return void 0;
|
|
1273
|
+
}
|
|
1274
|
+
conflict(error) {
|
|
1275
|
+
if (!isUniqueError(error) || !(error instanceof Error)) return void 0;
|
|
1276
|
+
if (error.message.includes("tenant_id, lt_project_bot_memberships.project_id")) return "coordinator_conflict";
|
|
1277
|
+
if (error.message.includes("room_id") && error.message.includes("mention_name")) return "mention_conflict";
|
|
1278
|
+
return void 0;
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
// src/stores/LocalProjectRoomMessageStore.ts
|
|
1283
|
+
var columns4 = "id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at";
|
|
1284
|
+
var ProjectRoomMessageIdConflictError = class extends Error {
|
|
1285
|
+
constructor(tenantId, id) {
|
|
1286
|
+
super(`Project room message ID '${id}' already exists in tenant '${tenantId}'`);
|
|
1287
|
+
this.name = "ProjectRoomMessageIdConflictError";
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
var DuplicateProjectRoomMessageIdempotencyKeyError = class extends Error {
|
|
1291
|
+
constructor(tenantId, roomId, key) {
|
|
1292
|
+
super(`Project room message idempotency key '${key}' already exists in tenant '${tenantId}' room '${roomId}'`);
|
|
1293
|
+
this.name = "DuplicateProjectRoomMessageIdempotencyKeyError";
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
function record(value) {
|
|
1297
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1298
|
+
}
|
|
1299
|
+
function only(value, keys) {
|
|
1300
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
1301
|
+
}
|
|
1302
|
+
function parseJson(value) {
|
|
1303
|
+
if (typeof value !== "string") return void 0;
|
|
1304
|
+
try {
|
|
1305
|
+
return JSON.parse(value);
|
|
1306
|
+
} catch {
|
|
1307
|
+
return void 0;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function author(value) {
|
|
1311
|
+
if (!record(value)) return void 0;
|
|
1312
|
+
if (value.type === "human" && only(value, ["type", "userId"]) && typeof value.userId === "string") return { type: "human", userId: value.userId };
|
|
1313
|
+
if (value.type === "bot" && only(value, ["type", "membershipId", "assistantId"]) && typeof value.membershipId === "string" && typeof value.assistantId === "string") return { type: "bot", membershipId: value.membershipId, assistantId: value.assistantId };
|
|
1314
|
+
if (value.type === "system" && only(value, ["type"])) return { type: "system" };
|
|
1315
|
+
return void 0;
|
|
1316
|
+
}
|
|
1317
|
+
function mention(value) {
|
|
1318
|
+
if (!record(value)) return void 0;
|
|
1319
|
+
if (value.type === "bot" && only(value, ["type", "membershipId"]) && typeof value.membershipId === "string") return { type: "bot", membershipId: value.membershipId };
|
|
1320
|
+
if (value.type === "team" && only(value, ["type"])) return { type: "team" };
|
|
1321
|
+
return void 0;
|
|
1322
|
+
}
|
|
1323
|
+
function source(value) {
|
|
1324
|
+
return value === "user" || value === "agent" || value === "task" || value === "routine" || value === "system";
|
|
1325
|
+
}
|
|
1326
|
+
function mapRow4(row) {
|
|
1327
|
+
const mappedAuthor = author(parseJson(row.author));
|
|
1328
|
+
const content = parseJson(row.content);
|
|
1329
|
+
const rawMentions = parseJson(row.mentions);
|
|
1330
|
+
const mappedMentions = Array.isArray(rawMentions) ? rawMentions.map(mention) : void 0;
|
|
1331
|
+
const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
|
|
1332
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || !mappedAuthor || !record(content) || content.type !== "text" || typeof content.text !== "string" || !only(content, ["type", "text"]) || !mappedMentions || mappedMentions.some((item) => item === void 0) || row.reply_to_message_id !== null && typeof row.reply_to_message_id !== "string" || !source(row.source) || row.source_id !== null && typeof row.source_id !== "string" || row.idempotency_key !== null && typeof row.idempotency_key !== "string" || !validDate(createdAt)) throw new Error("Invalid project room message row");
|
|
1333
|
+
return {
|
|
1334
|
+
id: row.id,
|
|
1335
|
+
tenantId: row.tenant_id,
|
|
1336
|
+
workspaceId: row.workspace_id,
|
|
1337
|
+
projectId: row.project_id,
|
|
1338
|
+
roomId: row.room_id,
|
|
1339
|
+
author: mappedAuthor,
|
|
1340
|
+
content: { type: "text", text: content.text },
|
|
1341
|
+
mentions: mappedMentions,
|
|
1342
|
+
...row.reply_to_message_id === null ? {} : { replyToMessageId: row.reply_to_message_id },
|
|
1343
|
+
source: row.source,
|
|
1344
|
+
...row.source_id === null ? {} : { sourceId: row.source_id },
|
|
1345
|
+
...row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key },
|
|
1346
|
+
createdAt
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
var LocalProjectRoomMessageStore = class {
|
|
1350
|
+
constructor(db) {
|
|
1351
|
+
this.db = db;
|
|
1352
|
+
ensureProjectRoomSchema(db);
|
|
1353
|
+
}
|
|
1354
|
+
async create(input) {
|
|
1355
|
+
return this.insert(input, false);
|
|
1356
|
+
}
|
|
1357
|
+
async createIdempotent(input) {
|
|
1358
|
+
return this.insert(input, true);
|
|
1359
|
+
}
|
|
1360
|
+
async list(input) {
|
|
1361
|
+
if (input.before && !validDate(input.before.createdAt)) throw new RangeError("Project room message cursor date is invalid");
|
|
1362
|
+
const limit = Math.min(100, Math.max(1, Math.trunc(Number.isFinite(input.limit) ? input.limit : 1)));
|
|
1363
|
+
const rows2 = input.before ? this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND (created_at < ? OR (created_at = ? AND id < ?)) ORDER BY created_at DESC, id DESC LIMIT ?`).all(input.tenantId, input.roomId, input.before.createdAt.toISOString(), input.before.createdAt.toISOString(), input.before.id, limit) : this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? ORDER BY created_at DESC, id DESC LIMIT ?`).all(input.tenantId, input.roomId, limit);
|
|
1364
|
+
return rows2.map(mapRow4);
|
|
1365
|
+
}
|
|
1366
|
+
async findById(tenantId, id) {
|
|
1367
|
+
const row = this.byId(tenantId, id);
|
|
1368
|
+
return row ? mapRow4(row) : null;
|
|
1369
|
+
}
|
|
1370
|
+
insert(input, idempotent) {
|
|
1371
|
+
return transaction(this.db, () => {
|
|
1372
|
+
if (idempotent && input.idempotencyKey) {
|
|
1373
|
+
const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1374
|
+
if (canonical) return mapRow4(canonical);
|
|
1375
|
+
}
|
|
1376
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
1377
|
+
try {
|
|
1378
|
+
write(this.db, "INSERT INTO lt_project_room_messages (id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.roomId, JSON.stringify(input.author), JSON.stringify(input.content), JSON.stringify(input.mentions), input.replyToMessageId ?? null, input.source, input.sourceId ?? null, input.idempotencyKey ?? null, timestamp()]);
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
if (!isUniqueError(error)) throw error;
|
|
1381
|
+
if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
1382
|
+
if (input.idempotencyKey) {
|
|
1383
|
+
const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1384
|
+
if (canonical && idempotent) return mapRow4(canonical);
|
|
1385
|
+
if (canonical) throw new DuplicateProjectRoomMessageIdempotencyKeyError(input.tenantId, input.roomId, input.idempotencyKey);
|
|
1386
|
+
}
|
|
1387
|
+
throw new Error("Unclassified project room message uniqueness conflict");
|
|
1388
|
+
}
|
|
1389
|
+
return mapRow4(this.byId(input.tenantId, input.id));
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
byId(tenantId, id) {
|
|
1393
|
+
return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
|
|
1394
|
+
}
|
|
1395
|
+
byKey(tenantId, roomId, key) {
|
|
1396
|
+
return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND idempotency_key = ?`).get(tenantId, roomId, key);
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
|
|
763
1400
|
// src/stores/LocalUserStore.ts
|
|
764
1401
|
var DDL5 = `
|
|
765
1402
|
CREATE TABLE IF NOT EXISTS lt_users (
|
|
@@ -779,10 +1416,10 @@ var LocalUserStore = class {
|
|
|
779
1416
|
ensureTable(db, DDL5);
|
|
780
1417
|
}
|
|
781
1418
|
async getAllUsers() {
|
|
782
|
-
const
|
|
1419
|
+
const rows2 = this.db.prepare(
|
|
783
1420
|
`SELECT * FROM lt_users ORDER BY created_at DESC`
|
|
784
1421
|
).all();
|
|
785
|
-
return
|
|
1422
|
+
return rows2.map(mapRowToUser);
|
|
786
1423
|
}
|
|
787
1424
|
async getUserById(id) {
|
|
788
1425
|
const row = this.db.prepare(
|
|
@@ -886,10 +1523,10 @@ var LocalTenantStore = class {
|
|
|
886
1523
|
ensureTable(db, DDL6);
|
|
887
1524
|
}
|
|
888
1525
|
async getAllTenants() {
|
|
889
|
-
const
|
|
1526
|
+
const rows2 = this.db.prepare(
|
|
890
1527
|
`SELECT * FROM lt_tenants ORDER BY created_at DESC`
|
|
891
1528
|
).all();
|
|
892
|
-
return
|
|
1529
|
+
return rows2.map(mapRowToTenant);
|
|
893
1530
|
}
|
|
894
1531
|
async getTenantById(id) {
|
|
895
1532
|
const row = this.db.prepare(
|
|
@@ -988,16 +1625,16 @@ var LocalUserTenantLinkStore = class {
|
|
|
988
1625
|
ensureTable(db, DDL7);
|
|
989
1626
|
}
|
|
990
1627
|
async getTenantsByUser(userId) {
|
|
991
|
-
const
|
|
1628
|
+
const rows2 = this.db.prepare(
|
|
992
1629
|
`SELECT * FROM lt_user_tenant_links WHERE user_id = ? ORDER BY joined_at DESC`
|
|
993
1630
|
).all(userId);
|
|
994
|
-
return
|
|
1631
|
+
return rows2.map(mapRowToLink);
|
|
995
1632
|
}
|
|
996
1633
|
async getUsersByTenant(tenantId) {
|
|
997
|
-
const
|
|
1634
|
+
const rows2 = this.db.prepare(
|
|
998
1635
|
`SELECT * FROM lt_user_tenant_links WHERE tenant_id = ? ORDER BY joined_at DESC`
|
|
999
1636
|
).all(tenantId);
|
|
1000
|
-
return
|
|
1637
|
+
return rows2.map(mapRowToLink);
|
|
1001
1638
|
}
|
|
1002
1639
|
async getLink(userId, tenantId) {
|
|
1003
1640
|
const row = this.db.prepare(
|
|
@@ -1007,7 +1644,7 @@ var LocalUserTenantLinkStore = class {
|
|
|
1007
1644
|
}
|
|
1008
1645
|
async createLink(data) {
|
|
1009
1646
|
const now = nowISO();
|
|
1010
|
-
const
|
|
1647
|
+
const role2 = data.role || "member";
|
|
1011
1648
|
const metadata = JSON.stringify(data.metadata || {});
|
|
1012
1649
|
this.db.prepare(
|
|
1013
1650
|
`INSERT INTO lt_user_tenant_links (user_id, tenant_id, role, joined_at, metadata)
|
|
@@ -1015,11 +1652,11 @@ var LocalUserTenantLinkStore = class {
|
|
|
1015
1652
|
ON CONFLICT(user_id, tenant_id) DO UPDATE SET
|
|
1016
1653
|
role = excluded.role,
|
|
1017
1654
|
metadata = excluded.metadata`
|
|
1018
|
-
).run(data.userId, data.tenantId,
|
|
1655
|
+
).run(data.userId, data.tenantId, role2, now, metadata);
|
|
1019
1656
|
return {
|
|
1020
1657
|
userId: data.userId,
|
|
1021
1658
|
tenantId: data.tenantId,
|
|
1022
|
-
role,
|
|
1659
|
+
role: role2,
|
|
1023
1660
|
joinedAt: parseISO(now),
|
|
1024
1661
|
metadata: data.metadata
|
|
1025
1662
|
};
|
|
@@ -1089,16 +1726,16 @@ var LocalDatabaseConfigStore = class {
|
|
|
1089
1726
|
ensureTable(db, DDL8);
|
|
1090
1727
|
}
|
|
1091
1728
|
async getAllConfigs(tenantId) {
|
|
1092
|
-
const
|
|
1729
|
+
const rows2 = this.db.prepare(
|
|
1093
1730
|
`SELECT * FROM lt_database_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1094
1731
|
).all(tenantId);
|
|
1095
|
-
return
|
|
1732
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1096
1733
|
}
|
|
1097
1734
|
async getAllConfigsWithoutTenant() {
|
|
1098
|
-
const
|
|
1735
|
+
const rows2 = this.db.prepare(
|
|
1099
1736
|
`SELECT * FROM lt_database_configs ORDER BY created_at DESC`
|
|
1100
1737
|
).all();
|
|
1101
|
-
return
|
|
1738
|
+
return rows2.map((r) => mapRowToEntry(r));
|
|
1102
1739
|
}
|
|
1103
1740
|
async getConfigById(tenantId, id) {
|
|
1104
1741
|
const row = this.db.prepare(
|
|
@@ -1230,16 +1867,16 @@ var LocalConnectionStore = class {
|
|
|
1230
1867
|
ensureTable(db, DDL9);
|
|
1231
1868
|
}
|
|
1232
1869
|
async listByTenant(tenantId) {
|
|
1233
|
-
const
|
|
1870
|
+
const rows2 = this.db.prepare(
|
|
1234
1871
|
`SELECT * FROM connection_configs WHERE tenant_id = ? ORDER BY created_at`
|
|
1235
1872
|
).all(tenantId);
|
|
1236
|
-
return
|
|
1873
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1237
1874
|
}
|
|
1238
1875
|
async listByType(tenantId, type) {
|
|
1239
|
-
const
|
|
1876
|
+
const rows2 = this.db.prepare(
|
|
1240
1877
|
`SELECT * FROM connection_configs WHERE tenant_id = ? AND type = ? ORDER BY created_at`
|
|
1241
1878
|
).all(tenantId, type);
|
|
1242
|
-
return
|
|
1879
|
+
return rows2.map((r) => this.mapRow(r));
|
|
1243
1880
|
}
|
|
1244
1881
|
async getByKey(tenantId, type, key) {
|
|
1245
1882
|
const row = this.db.prepare(
|
|
@@ -1325,16 +1962,16 @@ var LocalMetricsServerConfigStore = class {
|
|
|
1325
1962
|
ensureTable(db, DDL10);
|
|
1326
1963
|
}
|
|
1327
1964
|
async getAllConfigs(tenantId) {
|
|
1328
|
-
const
|
|
1965
|
+
const rows2 = this.db.prepare(
|
|
1329
1966
|
`SELECT * FROM lt_metrics_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1330
1967
|
).all(tenantId);
|
|
1331
|
-
return
|
|
1968
|
+
return rows2.map(mapRowToEntry2);
|
|
1332
1969
|
}
|
|
1333
1970
|
async getAllConfigsWithoutTenant() {
|
|
1334
|
-
const
|
|
1971
|
+
const rows2 = this.db.prepare(
|
|
1335
1972
|
`SELECT * FROM lt_metrics_configs ORDER BY created_at DESC`
|
|
1336
1973
|
).all();
|
|
1337
|
-
return
|
|
1974
|
+
return rows2.map(mapRowToEntry2);
|
|
1338
1975
|
}
|
|
1339
1976
|
async getConfigById(tenantId, id) {
|
|
1340
1977
|
const row = this.db.prepare(
|
|
@@ -1453,16 +2090,16 @@ var LocalMcpServerConfigStore = class {
|
|
|
1453
2090
|
ensureTable(db, DDL11);
|
|
1454
2091
|
}
|
|
1455
2092
|
async getAllConfigs(tenantId) {
|
|
1456
|
-
const
|
|
2093
|
+
const rows2 = this.db.prepare(
|
|
1457
2094
|
`SELECT * FROM lt_mcp_configs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1458
2095
|
).all(tenantId);
|
|
1459
|
-
return
|
|
2096
|
+
return rows2.map(mapRowToEntry3);
|
|
1460
2097
|
}
|
|
1461
2098
|
async getAllConfigsWithoutTenant() {
|
|
1462
|
-
const
|
|
2099
|
+
const rows2 = this.db.prepare(
|
|
1463
2100
|
`SELECT * FROM lt_mcp_configs ORDER BY created_at DESC`
|
|
1464
2101
|
).all();
|
|
1465
|
-
return
|
|
2102
|
+
return rows2.map(mapRowToEntry3);
|
|
1466
2103
|
}
|
|
1467
2104
|
async getConfigById(tenantId, id) {
|
|
1468
2105
|
const row = this.db.prepare(
|
|
@@ -1744,22 +2381,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1744
2381
|
this.db.prepare(`DELETE FROM lt_workflow_runs WHERE id = ?`).run(runId);
|
|
1745
2382
|
}
|
|
1746
2383
|
async getWorkflowRunsByThreadId(tenantId, threadId) {
|
|
1747
|
-
const
|
|
2384
|
+
const rows2 = this.db.prepare(
|
|
1748
2385
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND thread_id = ? ORDER BY created_at DESC`
|
|
1749
2386
|
).all(tenantId, threadId);
|
|
1750
|
-
return
|
|
2387
|
+
return rows2.map(mapRowToRun);
|
|
1751
2388
|
}
|
|
1752
2389
|
async getWorkflowRunsByAssistantId(tenantId, assistantId) {
|
|
1753
|
-
const
|
|
2390
|
+
const rows2 = this.db.prepare(
|
|
1754
2391
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
|
|
1755
2392
|
).all(tenantId, assistantId);
|
|
1756
|
-
return
|
|
2393
|
+
return rows2.map(mapRowToRun);
|
|
1757
2394
|
}
|
|
1758
2395
|
async getWorkflowRunsByTenantId(tenantId) {
|
|
1759
|
-
const
|
|
2396
|
+
const rows2 = this.db.prepare(
|
|
1760
2397
|
`SELECT * FROM lt_workflow_runs WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
1761
2398
|
).all(tenantId);
|
|
1762
|
-
return
|
|
2399
|
+
return rows2.map(mapRowToRun);
|
|
1763
2400
|
}
|
|
1764
2401
|
async queryWorkflowRuns(tenantId, options = {}) {
|
|
1765
2402
|
const conditions = ["tenant_id = ?"];
|
|
@@ -1788,8 +2425,8 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1788
2425
|
sql += " OFFSET ?";
|
|
1789
2426
|
selectParams.push(options.offset);
|
|
1790
2427
|
}
|
|
1791
|
-
const
|
|
1792
|
-
return { records:
|
|
2428
|
+
const rows2 = this.db.prepare(sql).all(...selectParams);
|
|
2429
|
+
return { records: rows2.map(mapRowToRun), total: countRow.count };
|
|
1793
2430
|
}
|
|
1794
2431
|
async createRunStep(request) {
|
|
1795
2432
|
const now = nowISO();
|
|
@@ -1881,22 +2518,22 @@ var LocalWorkflowTrackingStore = class {
|
|
|
1881
2518
|
return this.getStepById(runId, stepId);
|
|
1882
2519
|
}
|
|
1883
2520
|
async getRunSteps(runId) {
|
|
1884
|
-
const
|
|
2521
|
+
const rows2 = this.db.prepare(
|
|
1885
2522
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? ORDER BY created_at ASC`
|
|
1886
2523
|
).all(runId);
|
|
1887
|
-
return
|
|
2524
|
+
return rows2.map(mapRowToStep);
|
|
1888
2525
|
}
|
|
1889
2526
|
async getRunStepsByType(runId, stepType) {
|
|
1890
|
-
const
|
|
2527
|
+
const rows2 = this.db.prepare(
|
|
1891
2528
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? ORDER BY created_at ASC`
|
|
1892
2529
|
).all(runId, stepType);
|
|
1893
|
-
return
|
|
2530
|
+
return rows2.map(mapRowToStep);
|
|
1894
2531
|
}
|
|
1895
2532
|
async getInterruptedSteps(runId) {
|
|
1896
|
-
const
|
|
2533
|
+
const rows2 = this.db.prepare(
|
|
1897
2534
|
`SELECT * FROM lt_workflow_steps WHERE run_id = ? AND status = 'interrupted' ORDER BY created_at ASC`
|
|
1898
2535
|
).all(runId);
|
|
1899
|
-
return
|
|
2536
|
+
return rows2.map(mapRowToStep);
|
|
1900
2537
|
}
|
|
1901
2538
|
getStepById(runId, id) {
|
|
1902
2539
|
const row = this.db.prepare(
|
|
@@ -2041,7 +2678,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_run_results (
|
|
|
2041
2678
|
PRIMARY KEY (run_id, id)
|
|
2042
2679
|
);
|
|
2043
2680
|
`;
|
|
2044
|
-
function
|
|
2681
|
+
function parseJson2(val, fallback) {
|
|
2045
2682
|
if (val == null) return fallback;
|
|
2046
2683
|
if (typeof val === "string") {
|
|
2047
2684
|
try {
|
|
@@ -2133,20 +2770,20 @@ var LocalEvalStore = class {
|
|
|
2133
2770
|
`);
|
|
2134
2771
|
}
|
|
2135
2772
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2136
|
-
ensureColumn(table, column,
|
|
2773
|
+
ensureColumn(table, column, ddl2) {
|
|
2137
2774
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2138
2775
|
if (!cols.some((c) => c.name === column)) {
|
|
2139
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
2776
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
2140
2777
|
}
|
|
2141
2778
|
}
|
|
2142
2779
|
// -------------------------------------------------------------------------
|
|
2143
2780
|
// Projects
|
|
2144
2781
|
// -------------------------------------------------------------------------
|
|
2145
2782
|
async getProjectsByTenant(tenantId) {
|
|
2146
|
-
const
|
|
2783
|
+
const rows2 = this.db.prepare(
|
|
2147
2784
|
`SELECT * FROM lt_eval_projects WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2148
2785
|
).all(tenantId);
|
|
2149
|
-
return
|
|
2786
|
+
return rows2.map(this.mapRowToProject);
|
|
2150
2787
|
}
|
|
2151
2788
|
async getProjectById(tenantId, id) {
|
|
2152
2789
|
const row = this.db.prepare(
|
|
@@ -2249,11 +2886,11 @@ var LocalEvalStore = class {
|
|
|
2249
2886
|
// Suites
|
|
2250
2887
|
// -------------------------------------------------------------------------
|
|
2251
2888
|
async getSuitesByProject(tenantId, projectId) {
|
|
2252
|
-
const
|
|
2889
|
+
const rows2 = this.db.prepare(
|
|
2253
2890
|
`SELECT s.*, (SELECT COUNT(*) FROM lt_eval_cases c WHERE c.suite_id = s.id AND c.tenant_id = s.tenant_id) as case_count
|
|
2254
2891
|
FROM lt_eval_suites s WHERE s.tenant_id = ? AND s.project_id = ? ORDER BY s.created_at DESC`
|
|
2255
2892
|
).all(tenantId, projectId);
|
|
2256
|
-
return
|
|
2893
|
+
return rows2.map(this.mapRowToSuite);
|
|
2257
2894
|
}
|
|
2258
2895
|
async getSuiteById(tenantId, id) {
|
|
2259
2896
|
const row = this.db.prepare(
|
|
@@ -2294,10 +2931,10 @@ var LocalEvalStore = class {
|
|
|
2294
2931
|
// Cases
|
|
2295
2932
|
// -------------------------------------------------------------------------
|
|
2296
2933
|
async getCasesBySuite(tenantId, suiteId) {
|
|
2297
|
-
const
|
|
2934
|
+
const rows2 = this.db.prepare(
|
|
2298
2935
|
`SELECT * FROM lt_eval_cases WHERE tenant_id = ? AND suite_id = ? ORDER BY created_at DESC`
|
|
2299
2936
|
).all(tenantId, suiteId);
|
|
2300
|
-
return
|
|
2937
|
+
return rows2.map(this.mapRowToCase);
|
|
2301
2938
|
}
|
|
2302
2939
|
async getCaseById(tenantId, id) {
|
|
2303
2940
|
const row = this.db.prepare(
|
|
@@ -2386,8 +3023,8 @@ var LocalEvalStore = class {
|
|
|
2386
3023
|
vals.push(opts.status);
|
|
2387
3024
|
}
|
|
2388
3025
|
query += ` ORDER BY created_at DESC`;
|
|
2389
|
-
const
|
|
2390
|
-
return
|
|
3026
|
+
const rows2 = this.db.prepare(query).all(...vals);
|
|
3027
|
+
return rows2.map(this.mapRowToRun);
|
|
2391
3028
|
}
|
|
2392
3029
|
async getRunById(tenantId, id) {
|
|
2393
3030
|
const row = this.db.prepare(
|
|
@@ -2450,12 +3087,12 @@ var LocalEvalStore = class {
|
|
|
2450
3087
|
// Run Results
|
|
2451
3088
|
// -------------------------------------------------------------------------
|
|
2452
3089
|
async getResultsByRun(tenantId, runId) {
|
|
2453
|
-
const
|
|
3090
|
+
const rows2 = this.db.prepare(
|
|
2454
3091
|
`SELECT rr.* FROM lt_eval_run_results rr
|
|
2455
3092
|
INNER JOIN lt_eval_runs r ON r.id = rr.run_id
|
|
2456
3093
|
WHERE r.tenant_id = ? AND rr.run_id = ? ORDER BY rr.created_at ASC`
|
|
2457
3094
|
).all(tenantId, runId);
|
|
2458
|
-
return
|
|
3095
|
+
return rows2.map(this.mapRowToRunResult);
|
|
2459
3096
|
}
|
|
2460
3097
|
async getRunResultById(tenantId, id) {
|
|
2461
3098
|
const row = this.db.prepare(
|
|
@@ -2573,8 +3210,8 @@ var LocalEvalStore = class {
|
|
|
2573
3210
|
name: row.name,
|
|
2574
3211
|
description: row.description,
|
|
2575
3212
|
version: row.version,
|
|
2576
|
-
judgeModelConfig:
|
|
2577
|
-
targetServerConfig:
|
|
3213
|
+
judgeModelConfig: parseJson2(row.judge_model_config, {}),
|
|
3214
|
+
targetServerConfig: parseJson2(row.target_server_config, {}),
|
|
2578
3215
|
concurrency: row.concurrency ?? 3,
|
|
2579
3216
|
reportConfig: parseOptionalJson(row.report_config),
|
|
2580
3217
|
createdAt: parseISO(row.created_at),
|
|
@@ -2599,7 +3236,7 @@ var LocalEvalStore = class {
|
|
|
2599
3236
|
suiteId: row.suite_id,
|
|
2600
3237
|
inputMessage: row.input_message,
|
|
2601
3238
|
inputFiles: parseOptionalJson(row.input_files),
|
|
2602
|
-
steps:
|
|
3239
|
+
steps: parseJson2(row.steps, []),
|
|
2603
3240
|
outputType: row.output_type || "message_content",
|
|
2604
3241
|
contentAssertion: row.content_assertion || "",
|
|
2605
3242
|
rubrics: parseOptionalJson(row.rubrics),
|
|
@@ -2651,7 +3288,54 @@ var LocalEvalStore = class {
|
|
|
2651
3288
|
};
|
|
2652
3289
|
|
|
2653
3290
|
// src/stores/LocalChannelBindingStore.ts
|
|
3291
|
+
import {
|
|
3292
|
+
DuplicateChannelBindingSubjectError
|
|
3293
|
+
} from "@axiom-lattice/protocols";
|
|
2654
3294
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3295
|
+
|
|
3296
|
+
// src/stores/room-channel-binding-index.ts
|
|
3297
|
+
import {
|
|
3298
|
+
ChannelBindingMigrationConflictError
|
|
3299
|
+
} from "@axiom-lattice/protocols";
|
|
3300
|
+
var UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_cb_subject_unique
|
|
3301
|
+
ON lt_channel_bindings(channel, channel_installation_id, tenant_id, sender_id)`;
|
|
3302
|
+
var LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve
|
|
3303
|
+
ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id)`;
|
|
3304
|
+
function ensureChannelBindingSubjectIndex(db) {
|
|
3305
|
+
db.exec("BEGIN");
|
|
3306
|
+
try {
|
|
3307
|
+
const conflicts = db.prepare(`SELECT tenant_id, channel, channel_installation_id, sender_id, COUNT(*) AS count
|
|
3308
|
+
FROM lt_channel_bindings
|
|
3309
|
+
GROUP BY channel, channel_installation_id, tenant_id, sender_id
|
|
3310
|
+
HAVING COUNT(*) > 1
|
|
3311
|
+
ORDER BY tenant_id, channel, channel_installation_id, sender_id`).all();
|
|
3312
|
+
if (conflicts.length > 0) {
|
|
3313
|
+
throw new ChannelBindingMigrationConflictError(conflicts.map(mapConflict));
|
|
3314
|
+
}
|
|
3315
|
+
db.exec("DROP INDEX IF EXISTS idx_lt_cb_room_subject");
|
|
3316
|
+
db.exec(UNIQUE_INDEX_SQL);
|
|
3317
|
+
db.exec(LOOKUP_INDEX_SQL);
|
|
3318
|
+
db.exec("COMMIT");
|
|
3319
|
+
} catch (error) {
|
|
3320
|
+
try {
|
|
3321
|
+
db.exec("ROLLBACK");
|
|
3322
|
+
} catch {
|
|
3323
|
+
}
|
|
3324
|
+
throw error;
|
|
3325
|
+
}
|
|
3326
|
+
db.save();
|
|
3327
|
+
}
|
|
3328
|
+
function mapConflict(row) {
|
|
3329
|
+
return {
|
|
3330
|
+
tenantId: row.tenant_id,
|
|
3331
|
+
channel: row.channel,
|
|
3332
|
+
channelInstallationId: row.channel_installation_id,
|
|
3333
|
+
senderId: row.sender_id,
|
|
3334
|
+
count: row.count
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// src/stores/LocalChannelBindingStore.ts
|
|
2655
3339
|
var DDL14 = `
|
|
2656
3340
|
CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
2657
3341
|
id TEXT PRIMARY KEY,
|
|
@@ -2670,12 +3354,23 @@ CREATE TABLE IF NOT EXISTS lt_channel_bindings (
|
|
|
2670
3354
|
created_at TEXT NOT NULL,
|
|
2671
3355
|
updated_at TEXT NOT NULL
|
|
2672
3356
|
);
|
|
2673
|
-
CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id);
|
|
2674
3357
|
`;
|
|
2675
3358
|
var LocalChannelBindingStore = class {
|
|
2676
3359
|
constructor(db) {
|
|
2677
3360
|
this.db = db;
|
|
2678
3361
|
ensureTable(db, DDL14);
|
|
3362
|
+
ensureChannelBindingSubjectIndex(db);
|
|
3363
|
+
}
|
|
3364
|
+
async findById(tenantId, id) {
|
|
3365
|
+
const row = this.db.prepare(
|
|
3366
|
+
"SELECT * FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?"
|
|
3367
|
+
).get(id, tenantId);
|
|
3368
|
+
return row ? mapRowToBinding(row) : null;
|
|
3369
|
+
}
|
|
3370
|
+
async findBySubject(params) {
|
|
3371
|
+
const row = this.db.prepare(`SELECT * FROM lt_channel_bindings
|
|
3372
|
+
WHERE tenant_id = ? AND channel = ? AND channel_installation_id = ? AND sender_id = ? LIMIT 1`).get(params.tenantId, params.channel, params.channelInstallationId, params.senderId);
|
|
3373
|
+
return row ? mapRowToBinding(row) : null;
|
|
2679
3374
|
}
|
|
2680
3375
|
async resolve(params) {
|
|
2681
3376
|
const row = this.db.prepare(
|
|
@@ -2688,35 +3383,41 @@ var LocalChannelBindingStore = class {
|
|
|
2688
3383
|
async create(input) {
|
|
2689
3384
|
const id = randomUUID2();
|
|
2690
3385
|
const now = nowISO();
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
3386
|
+
try {
|
|
3387
|
+
this.db.prepare(
|
|
3388
|
+
`INSERT INTO lt_channel_bindings
|
|
3389
|
+
(id, channel, channel_installation_id, tenant_id, sender_id, agent_id,
|
|
3390
|
+
thread_id, thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, enabled, created_at, updated_at)
|
|
3391
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3392
|
+
).run(
|
|
3393
|
+
id,
|
|
3394
|
+
input.channel,
|
|
3395
|
+
input.channelInstallationId,
|
|
3396
|
+
input.tenantId,
|
|
3397
|
+
input.senderId,
|
|
3398
|
+
input.agentId,
|
|
3399
|
+
input.threadId || null,
|
|
3400
|
+
input.threadMode || "fixed",
|
|
3401
|
+
input.senderDisplayName || null,
|
|
3402
|
+
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
3403
|
+
input.workspaceId || null,
|
|
3404
|
+
input.projectId || null,
|
|
3405
|
+
input.enabled ?? true ? 1 : 0,
|
|
3406
|
+
now,
|
|
3407
|
+
now
|
|
3408
|
+
);
|
|
3409
|
+
} catch (error) {
|
|
3410
|
+
if (error instanceof Error && /UNIQUE constraint failed/.test(error.message)) {
|
|
3411
|
+
throw new DuplicateChannelBindingSubjectError();
|
|
3412
|
+
}
|
|
3413
|
+
throw error;
|
|
3414
|
+
}
|
|
2711
3415
|
return await this.getById(id);
|
|
2712
3416
|
}
|
|
2713
|
-
async update(id, patch) {
|
|
2714
|
-
const existing = await this.
|
|
3417
|
+
async update(tenantId, id, patch) {
|
|
3418
|
+
const existing = await this.findById(tenantId, id);
|
|
2715
3419
|
if (!existing) throw new Error(`Binding ${id} not found`);
|
|
2716
3420
|
const updated = {
|
|
2717
|
-
channel: patch.channel ?? existing.channel,
|
|
2718
|
-
channelInstallationId: patch.channelInstallationId ?? existing.channelInstallationId,
|
|
2719
|
-
senderId: patch.senderId ?? existing.senderId,
|
|
2720
3421
|
agentId: patch.agentId ?? existing.agentId,
|
|
2721
3422
|
threadId: patch.threadId !== void 0 ? patch.threadId : existing.threadId,
|
|
2722
3423
|
workspaceId: patch.workspaceId !== void 0 ? patch.workspaceId : existing.workspaceId,
|
|
@@ -2729,14 +3430,10 @@ var LocalChannelBindingStore = class {
|
|
|
2729
3430
|
const now = nowISO();
|
|
2730
3431
|
this.db.prepare(
|
|
2731
3432
|
`UPDATE lt_channel_bindings SET
|
|
2732
|
-
|
|
2733
|
-
thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
3433
|
+
agent_id = ?, thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
|
|
2734
3434
|
sender_display_name = ?, sender_metadata = ?, enabled = ?, updated_at = ?
|
|
2735
|
-
WHERE id = ?`
|
|
3435
|
+
WHERE id = ? AND tenant_id = ?`
|
|
2736
3436
|
).run(
|
|
2737
|
-
updated.channel,
|
|
2738
|
-
updated.channelInstallationId,
|
|
2739
|
-
updated.senderId,
|
|
2740
3437
|
updated.agentId,
|
|
2741
3438
|
updated.threadId || null,
|
|
2742
3439
|
updated.workspaceId || null,
|
|
@@ -2746,12 +3443,13 @@ var LocalChannelBindingStore = class {
|
|
|
2746
3443
|
updated.senderMetadata ? JSON.stringify(updated.senderMetadata) : null,
|
|
2747
3444
|
updated.enabled ? 1 : 0,
|
|
2748
3445
|
now,
|
|
2749
|
-
id
|
|
3446
|
+
id,
|
|
3447
|
+
tenantId
|
|
2750
3448
|
);
|
|
2751
|
-
return await this.
|
|
3449
|
+
return await this.findById(tenantId, id);
|
|
2752
3450
|
}
|
|
2753
|
-
async delete(id) {
|
|
2754
|
-
this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ?`).run(id);
|
|
3451
|
+
async delete(tenantId, id) {
|
|
3452
|
+
this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?`).run(id, tenantId);
|
|
2755
3453
|
}
|
|
2756
3454
|
async list(params) {
|
|
2757
3455
|
const conditions = ["tenant_id = ?"];
|
|
@@ -2768,18 +3466,35 @@ var LocalChannelBindingStore = class {
|
|
|
2768
3466
|
conditions.push("channel_installation_id = ?");
|
|
2769
3467
|
values.push(params.channelInstallationId);
|
|
2770
3468
|
}
|
|
3469
|
+
if (params.excludeChannels?.length) {
|
|
3470
|
+
conditions.push(`channel NOT IN (${params.excludeChannels.map(() => "?").join(", ")})`);
|
|
3471
|
+
values.push(...params.excludeChannels);
|
|
3472
|
+
}
|
|
3473
|
+
for (const prefix of params.excludeInstallationIdPrefixes ?? []) {
|
|
3474
|
+
conditions.push("substr(channel_installation_id, 1, length(?)) <> ?");
|
|
3475
|
+
values.push(prefix, prefix);
|
|
3476
|
+
}
|
|
2771
3477
|
const limit = params.limit ?? 50;
|
|
2772
3478
|
const offset = params.offset ?? 0;
|
|
2773
3479
|
values.push(limit, offset);
|
|
2774
|
-
const
|
|
3480
|
+
const rows2 = this.db.prepare(
|
|
2775
3481
|
`SELECT * FROM lt_channel_bindings
|
|
2776
3482
|
WHERE ${conditions.join(" AND ")}
|
|
2777
3483
|
ORDER BY created_at DESC
|
|
2778
3484
|
LIMIT ? OFFSET ?`
|
|
2779
3485
|
).all(...values);
|
|
2780
|
-
return
|
|
3486
|
+
return rows2.map(mapRowToBinding);
|
|
2781
3487
|
}
|
|
2782
|
-
async import(bindings) {
|
|
3488
|
+
async import(tenantId, bindings) {
|
|
3489
|
+
if (bindings.some((binding) => binding.channel === "room")) {
|
|
3490
|
+
throw new Error("Room bindings cannot be imported through the public store API");
|
|
3491
|
+
}
|
|
3492
|
+
if (bindings.some((binding) => binding.channelInstallationId.startsWith("room-internal:"))) {
|
|
3493
|
+
throw new Error("Internal bindings cannot be imported through the public store API");
|
|
3494
|
+
}
|
|
3495
|
+
if (bindings.some((binding) => binding.tenantId !== tenantId)) {
|
|
3496
|
+
throw new Error("Binding import tenant mismatch");
|
|
3497
|
+
}
|
|
2783
3498
|
const result = [];
|
|
2784
3499
|
for (const input of bindings) {
|
|
2785
3500
|
result.push(await this.create(input));
|
|
@@ -2787,7 +3502,13 @@ var LocalChannelBindingStore = class {
|
|
|
2787
3502
|
return result;
|
|
2788
3503
|
}
|
|
2789
3504
|
async export(params) {
|
|
2790
|
-
return this.list({
|
|
3505
|
+
return this.list({
|
|
3506
|
+
tenantId: params.tenantId,
|
|
3507
|
+
excludeChannels: ["room"],
|
|
3508
|
+
excludeInstallationIdPrefixes: ["room-internal:"],
|
|
3509
|
+
limit: 1e4,
|
|
3510
|
+
offset: 0
|
|
3511
|
+
});
|
|
2791
3512
|
}
|
|
2792
3513
|
async getById(id) {
|
|
2793
3514
|
const row = this.db.prepare(
|
|
@@ -2841,33 +3562,33 @@ var LocalChannelInstallationStore = class {
|
|
|
2841
3562
|
const row = this.db.prepare(
|
|
2842
3563
|
`SELECT * FROM lt_channel_installations WHERE id = ?`
|
|
2843
3564
|
).get(installationId);
|
|
2844
|
-
return row ?
|
|
3565
|
+
return row ? mapRow5(row) : null;
|
|
2845
3566
|
}
|
|
2846
3567
|
async getInstallationsByTenant(tenantId, channel) {
|
|
2847
|
-
let
|
|
3568
|
+
let rows2;
|
|
2848
3569
|
if (channel) {
|
|
2849
|
-
|
|
3570
|
+
rows2 = this.db.prepare(
|
|
2850
3571
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? AND channel = ? ORDER BY created_at DESC`
|
|
2851
3572
|
).all(tenantId, channel);
|
|
2852
3573
|
} else {
|
|
2853
|
-
|
|
3574
|
+
rows2 = this.db.prepare(
|
|
2854
3575
|
`SELECT * FROM lt_channel_installations WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
2855
3576
|
).all(tenantId);
|
|
2856
3577
|
}
|
|
2857
|
-
return
|
|
3578
|
+
return rows2.map(mapRow5);
|
|
2858
3579
|
}
|
|
2859
3580
|
async getAllInstallations(channel) {
|
|
2860
|
-
let
|
|
3581
|
+
let rows2;
|
|
2861
3582
|
if (channel) {
|
|
2862
|
-
|
|
3583
|
+
rows2 = this.db.prepare(
|
|
2863
3584
|
`SELECT * FROM lt_channel_installations WHERE channel = ? ORDER BY created_at DESC`
|
|
2864
3585
|
).all(channel);
|
|
2865
3586
|
} else {
|
|
2866
|
-
|
|
3587
|
+
rows2 = this.db.prepare(
|
|
2867
3588
|
`SELECT * FROM lt_channel_installations ORDER BY created_at DESC`
|
|
2868
3589
|
).all();
|
|
2869
3590
|
}
|
|
2870
|
-
return
|
|
3591
|
+
return rows2.map(mapRow5);
|
|
2871
3592
|
}
|
|
2872
3593
|
async createInstallation(tenantId, installationId, data) {
|
|
2873
3594
|
const now = nowISO();
|
|
@@ -2892,7 +3613,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2892
3613
|
tenantId,
|
|
2893
3614
|
channel: data.channel,
|
|
2894
3615
|
name: data.name,
|
|
2895
|
-
config: data.config,
|
|
3616
|
+
config: { ...data.config },
|
|
2896
3617
|
enabled: data.enabled !== false,
|
|
2897
3618
|
fallbackAgentId: data.fallbackAgentId,
|
|
2898
3619
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? false,
|
|
@@ -2902,7 +3623,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2902
3623
|
}
|
|
2903
3624
|
async updateInstallation(tenantId, installationId, updates) {
|
|
2904
3625
|
const existing = await this.getInstallationById(installationId);
|
|
2905
|
-
if (!existing) return null;
|
|
3626
|
+
if (!existing || existing.tenantId !== tenantId) return null;
|
|
2906
3627
|
const setClauses = [];
|
|
2907
3628
|
const values = [];
|
|
2908
3629
|
if (updates.name !== void 0) {
|
|
@@ -2929,9 +3650,10 @@ var LocalChannelInstallationStore = class {
|
|
|
2929
3650
|
const now = nowISO();
|
|
2930
3651
|
setClauses.push("updated_at = ?");
|
|
2931
3652
|
values.push(now);
|
|
3653
|
+
values.push(tenantId);
|
|
2932
3654
|
values.push(installationId);
|
|
2933
3655
|
this.db.prepare(
|
|
2934
|
-
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE id = ?`
|
|
3656
|
+
`UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`
|
|
2935
3657
|
).run(...values);
|
|
2936
3658
|
return this.getInstallationById(installationId);
|
|
2937
3659
|
}
|
|
@@ -2942,7 +3664,7 @@ var LocalChannelInstallationStore = class {
|
|
|
2942
3664
|
return result.changes > 0;
|
|
2943
3665
|
}
|
|
2944
3666
|
};
|
|
2945
|
-
function
|
|
3667
|
+
function mapRow5(row) {
|
|
2946
3668
|
return {
|
|
2947
3669
|
id: row.id,
|
|
2948
3670
|
tenantId: row.tenant_id,
|
|
@@ -2993,18 +3715,18 @@ var LocalA2AApiKeyStore = class {
|
|
|
2993
3715
|
this.repairManagementIds();
|
|
2994
3716
|
}
|
|
2995
3717
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
2996
|
-
ensureColumn(table, column,
|
|
3718
|
+
ensureColumn(table, column, ddl2) {
|
|
2997
3719
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
2998
3720
|
if (!cols.some((c) => c.name === column)) {
|
|
2999
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
3721
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
3000
3722
|
}
|
|
3001
3723
|
}
|
|
3002
3724
|
repairManagementIds() {
|
|
3003
|
-
const
|
|
3725
|
+
const rows2 = this.db.prepare(
|
|
3004
3726
|
`SELECT rowid, id FROM lt_a2a_api_keys`
|
|
3005
3727
|
).all();
|
|
3006
3728
|
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3007
|
-
for (const row of
|
|
3729
|
+
for (const row of rows2) {
|
|
3008
3730
|
if (!row.id || !uuidPattern.test(row.id)) {
|
|
3009
3731
|
this.db.prepare(
|
|
3010
3732
|
`UPDATE lt_a2a_api_keys SET id = ? WHERE rowid = ?`
|
|
@@ -3013,10 +3735,10 @@ var LocalA2AApiKeyStore = class {
|
|
|
3013
3735
|
}
|
|
3014
3736
|
}
|
|
3015
3737
|
async findByKey(key) {
|
|
3016
|
-
const
|
|
3738
|
+
const rows2 = this.db.prepare(
|
|
3017
3739
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3018
3740
|
).all();
|
|
3019
|
-
for (const row of
|
|
3741
|
+
for (const row of rows2) {
|
|
3020
3742
|
try {
|
|
3021
3743
|
if (decrypt3(row.key_value) === key) return mapRowToRecord(row);
|
|
3022
3744
|
} catch {
|
|
@@ -3033,17 +3755,17 @@ var LocalA2AApiKeyStore = class {
|
|
|
3033
3755
|
async list(params) {
|
|
3034
3756
|
const limit = params.limit || 100;
|
|
3035
3757
|
const offset = params.offset || 0;
|
|
3036
|
-
let
|
|
3758
|
+
let rows2;
|
|
3037
3759
|
if (params.tenantId) {
|
|
3038
|
-
|
|
3760
|
+
rows2 = this.db.prepare(
|
|
3039
3761
|
`SELECT * FROM lt_a2a_api_keys WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
3040
3762
|
).all(params.tenantId, limit, offset);
|
|
3041
3763
|
} else {
|
|
3042
|
-
|
|
3764
|
+
rows2 = this.db.prepare(
|
|
3043
3765
|
`SELECT * FROM lt_a2a_api_keys ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
3044
3766
|
).all(limit, offset);
|
|
3045
3767
|
}
|
|
3046
|
-
return
|
|
3768
|
+
return rows2.map(mapRowToRecord);
|
|
3047
3769
|
}
|
|
3048
3770
|
async create(input) {
|
|
3049
3771
|
const id = randomUUID3();
|
|
@@ -3053,7 +3775,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3053
3775
|
`INSERT INTO lt_a2a_api_keys (id, key_value, tenant_id, project_id, assistant_ids, label, created_at, updated_at)
|
|
3054
3776
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3055
3777
|
).run(id, encrypt3(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null, now, now);
|
|
3056
|
-
const
|
|
3778
|
+
const record2 = {
|
|
3057
3779
|
id,
|
|
3058
3780
|
key,
|
|
3059
3781
|
tenantId: input.tenantId,
|
|
@@ -3064,7 +3786,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3064
3786
|
createdAt: parseISO(now),
|
|
3065
3787
|
updatedAt: parseISO(now)
|
|
3066
3788
|
};
|
|
3067
|
-
return
|
|
3789
|
+
return record2;
|
|
3068
3790
|
}
|
|
3069
3791
|
async disable(id) {
|
|
3070
3792
|
const now = nowISO();
|
|
@@ -3092,19 +3814,19 @@ var LocalA2AApiKeyStore = class {
|
|
|
3092
3814
|
).run(encrypt3(key), now, id);
|
|
3093
3815
|
const row = this.db.prepare(`SELECT * FROM lt_a2a_api_keys WHERE id = ?`).get(id);
|
|
3094
3816
|
if (!row) throw new Error(`A2A API key not found: ${id}`);
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
return
|
|
3817
|
+
const record2 = mapRowToRecord(row);
|
|
3818
|
+
record2.key = key;
|
|
3819
|
+
return record2;
|
|
3098
3820
|
}
|
|
3099
3821
|
async delete(id) {
|
|
3100
3822
|
this.db.prepare(`DELETE FROM lt_a2a_api_keys WHERE id = ?`).run(id);
|
|
3101
3823
|
}
|
|
3102
3824
|
async loadIntoMap() {
|
|
3103
|
-
const
|
|
3825
|
+
const rows2 = this.db.prepare(
|
|
3104
3826
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3105
3827
|
).all();
|
|
3106
3828
|
const map2 = /* @__PURE__ */ new Map();
|
|
3107
|
-
for (const row of
|
|
3829
|
+
for (const row of rows2) {
|
|
3108
3830
|
try {
|
|
3109
3831
|
const key = decrypt3(row.key_value);
|
|
3110
3832
|
map2.set(key, {
|
|
@@ -3140,6 +3862,10 @@ function mapRowToRecord(row) {
|
|
|
3140
3862
|
}
|
|
3141
3863
|
|
|
3142
3864
|
// src/stores/LocalThreadMessageQueueStore.ts
|
|
3865
|
+
import {
|
|
3866
|
+
parseQueuedExecutionMode,
|
|
3867
|
+
parseTrustedRunContext
|
|
3868
|
+
} from "@axiom-lattice/protocols";
|
|
3143
3869
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
3144
3870
|
var DDL17 = `
|
|
3145
3871
|
CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
@@ -3156,6 +3882,8 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
|
3156
3882
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
3157
3883
|
command TEXT,
|
|
3158
3884
|
custom_run_config TEXT,
|
|
3885
|
+
trusted_run_context TEXT,
|
|
3886
|
+
execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
|
|
3159
3887
|
created_at TEXT NOT NULL
|
|
3160
3888
|
);
|
|
3161
3889
|
CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
|
|
@@ -3166,8 +3894,17 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3166
3894
|
this.capacityTail = Promise.resolve();
|
|
3167
3895
|
this.db = db;
|
|
3168
3896
|
ensureTable(db, DDL17);
|
|
3897
|
+
const columns5 = db.prepare("PRAGMA table_info(lt_thread_message_queue)").all();
|
|
3898
|
+
const names = new Set(columns5.map(({ name }) => name));
|
|
3899
|
+
if (!names.has("trusted_run_context")) {
|
|
3900
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN trusted_run_context TEXT");
|
|
3901
|
+
}
|
|
3902
|
+
if (!names.has("execution_mode")) {
|
|
3903
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup')");
|
|
3904
|
+
}
|
|
3169
3905
|
}
|
|
3170
3906
|
async addMessage(params) {
|
|
3907
|
+
const trusted = validateQueueTrust(params);
|
|
3171
3908
|
const now = nowISO();
|
|
3172
3909
|
const id = params.id || randomUUID4();
|
|
3173
3910
|
const seqRow = this.db.prepare(
|
|
@@ -3175,8 +3912,8 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3175
3912
|
).get(params.threadId);
|
|
3176
3913
|
const nextSeq = seqRow.next_seq;
|
|
3177
3914
|
this.db.prepare(
|
|
3178
|
-
`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)
|
|
3179
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3915
|
+
`INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
|
|
3916
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3180
3917
|
).run(
|
|
3181
3918
|
id,
|
|
3182
3919
|
params.threadId,
|
|
@@ -3190,11 +3927,14 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3190
3927
|
params.priority ?? 0,
|
|
3191
3928
|
params.command ? JSON.stringify(params.command) : null,
|
|
3192
3929
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3930
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
3931
|
+
trusted.executionMode ?? null,
|
|
3193
3932
|
now
|
|
3194
3933
|
);
|
|
3195
3934
|
return this.getById(id);
|
|
3196
3935
|
}
|
|
3197
3936
|
async addMessageIfCapacity(params, maxSize) {
|
|
3937
|
+
const trusted = validateQueueTrust(params);
|
|
3198
3938
|
let release;
|
|
3199
3939
|
const previous = this.capacityTail;
|
|
3200
3940
|
this.capacityTail = new Promise((resolve) => {
|
|
@@ -3212,10 +3952,11 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3212
3952
|
const id = params.id || randomUUID4();
|
|
3213
3953
|
const limit = maxSize === Infinity ? -1 : maxSize;
|
|
3214
3954
|
const result = this.db.prepare(
|
|
3215
|
-
`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)
|
|
3955
|
+
`INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
|
|
3216
3956
|
SELECT ?, ?, ?, ?, ?, ?, ?, ?,
|
|
3217
3957
|
(SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
|
|
3218
|
-
?, ?, ?,
|
|
3958
|
+
?, ?, ?, ?, ?,
|
|
3959
|
+
?
|
|
3219
3960
|
WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
|
|
3220
3961
|
).run(
|
|
3221
3962
|
id,
|
|
@@ -3230,6 +3971,8 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3230
3971
|
params.priority ?? 0,
|
|
3231
3972
|
params.command ? JSON.stringify(params.command) : null,
|
|
3232
3973
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3974
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
3975
|
+
trusted.executionMode ?? null,
|
|
3233
3976
|
nowISO(),
|
|
3234
3977
|
limit,
|
|
3235
3978
|
params.threadId,
|
|
@@ -3242,14 +3985,15 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3242
3985
|
}
|
|
3243
3986
|
}
|
|
3244
3987
|
async addMessageAtHead(params) {
|
|
3988
|
+
const trusted = validateQueueTrust(params);
|
|
3245
3989
|
const now = nowISO();
|
|
3246
3990
|
const id = params.id || randomUUID4();
|
|
3247
3991
|
const seqRow = this.db.prepare(
|
|
3248
3992
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lt_thread_message_queue WHERE thread_id = ?`
|
|
3249
3993
|
).get(params.threadId);
|
|
3250
3994
|
this.db.prepare(
|
|
3251
|
-
`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)
|
|
3252
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?)`
|
|
3995
|
+
`INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
|
|
3996
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?, ?, ?)`
|
|
3253
3997
|
).run(
|
|
3254
3998
|
id,
|
|
3255
3999
|
params.threadId,
|
|
@@ -3262,23 +4006,25 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3262
4006
|
seqRow.next_seq,
|
|
3263
4007
|
params.command ? JSON.stringify(params.command) : null,
|
|
3264
4008
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
4009
|
+
trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
|
|
4010
|
+
trusted.executionMode ?? null,
|
|
3265
4011
|
now
|
|
3266
4012
|
);
|
|
3267
4013
|
return this.getById(id);
|
|
3268
4014
|
}
|
|
3269
4015
|
async getPendingMessages(threadId, scope) {
|
|
3270
4016
|
const filter = scopeClause(scope);
|
|
3271
|
-
const
|
|
4017
|
+
const rows2 = this.db.prepare(
|
|
3272
4018
|
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
3273
4019
|
).all(threadId, ...filter.params);
|
|
3274
|
-
return
|
|
4020
|
+
return rows2.map(rowToMessage);
|
|
3275
4021
|
}
|
|
3276
4022
|
async getProcessingMessages(threadId, scope) {
|
|
3277
4023
|
const filter = scopeClause(scope);
|
|
3278
|
-
const
|
|
4024
|
+
const rows2 = this.db.prepare(
|
|
3279
4025
|
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
3280
4026
|
).all(threadId, ...filter.params);
|
|
3281
|
-
return
|
|
4027
|
+
return rows2.map(rowToMessage);
|
|
3282
4028
|
}
|
|
3283
4029
|
async getQueueSize(threadId, scope) {
|
|
3284
4030
|
const filter = scopeClause(scope);
|
|
@@ -3288,10 +4034,10 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3288
4034
|
return row.count;
|
|
3289
4035
|
}
|
|
3290
4036
|
async getThreadsWithPendingMessages() {
|
|
3291
|
-
const
|
|
4037
|
+
const rows2 = this.db.prepare(
|
|
3292
4038
|
`SELECT tenant_id, assistant_id, thread_id, workspace_id, project_id FROM lt_thread_message_queue WHERE status IN ('pending', 'processing') GROUP BY tenant_id, assistant_id, thread_id, workspace_id, project_id ORDER BY thread_id`
|
|
3293
4039
|
).all();
|
|
3294
|
-
return
|
|
4040
|
+
return rows2.map((r) => ({
|
|
3295
4041
|
tenantId: r.tenant_id,
|
|
3296
4042
|
assistantId: r.assistant_id,
|
|
3297
4043
|
threadId: r.thread_id,
|
|
@@ -3330,6 +4076,12 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3330
4076
|
return rowToMessage(row);
|
|
3331
4077
|
}
|
|
3332
4078
|
};
|
|
4079
|
+
function validateQueueTrust(params) {
|
|
4080
|
+
return {
|
|
4081
|
+
trustedRunContext: params.trusted_run_context === void 0 ? void 0 : parseTrustedRunContext(params.trusted_run_context),
|
|
4082
|
+
executionMode: params.execution_mode === void 0 ? void 0 : parseQueuedExecutionMode(params.execution_mode)
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
3333
4085
|
function scopeClause(scope) {
|
|
3334
4086
|
if (!scope) return { sql: "", params: [] };
|
|
3335
4087
|
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
@@ -3347,7 +4099,9 @@ function rowToMessage(row) {
|
|
|
3347
4099
|
createdAt: parseISO(row.created_at),
|
|
3348
4100
|
priority: row.priority || 0,
|
|
3349
4101
|
command: row.command ? JSON.parse(row.command) : void 0,
|
|
3350
|
-
custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0
|
|
4102
|
+
custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0,
|
|
4103
|
+
trusted_run_context: row.trusted_run_context ? parseTrustedRunContext(JSON.parse(row.trusted_run_context)) : void 0,
|
|
4104
|
+
execution_mode: row.execution_mode == null ? void 0 : parseQueuedExecutionMode(row.execution_mode)
|
|
3351
4105
|
};
|
|
3352
4106
|
}
|
|
3353
4107
|
|
|
@@ -3374,10 +4128,10 @@ var LocalSkillStore = class {
|
|
|
3374
4128
|
ensureTable(db, DDL18);
|
|
3375
4129
|
}
|
|
3376
4130
|
async getAllSkills(tenantId, _context) {
|
|
3377
|
-
const
|
|
4131
|
+
const rows2 = this.db.prepare(
|
|
3378
4132
|
`SELECT * FROM lt_skills WHERE tenant_id = ? ORDER BY created_at DESC`
|
|
3379
4133
|
).all(tenantId);
|
|
3380
|
-
return
|
|
4134
|
+
return rows2.map(mapRowToSkill);
|
|
3381
4135
|
}
|
|
3382
4136
|
async getSkillById(tenantId, id, _context) {
|
|
3383
4137
|
const row = this.db.prepare(
|
|
@@ -3482,16 +4236,16 @@ var LocalSkillStore = class {
|
|
|
3482
4236
|
return all.filter((s) => s.metadata?.[metadataKey] === metadataValue);
|
|
3483
4237
|
}
|
|
3484
4238
|
async filterByCompatibility(tenantId, compatibility, _context) {
|
|
3485
|
-
const
|
|
4239
|
+
const rows2 = this.db.prepare(
|
|
3486
4240
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND compatibility = ? ORDER BY created_at DESC`
|
|
3487
4241
|
).all(tenantId, compatibility);
|
|
3488
|
-
return
|
|
4242
|
+
return rows2.map(mapRowToSkill);
|
|
3489
4243
|
}
|
|
3490
4244
|
async filterByLicense(tenantId, license, _context) {
|
|
3491
|
-
const
|
|
4245
|
+
const rows2 = this.db.prepare(
|
|
3492
4246
|
`SELECT * FROM lt_skills WHERE tenant_id = ? AND license = ? ORDER BY created_at DESC`
|
|
3493
4247
|
).all(tenantId, license);
|
|
3494
|
-
return
|
|
4248
|
+
return rows2.map(mapRowToSkill);
|
|
3495
4249
|
}
|
|
3496
4250
|
async getSubSkills(tenantId, parentSkillName, _context) {
|
|
3497
4251
|
const all = await this.getAllSkills(tenantId);
|
|
@@ -3703,40 +4457,40 @@ var LocalScheduleStorage = class {
|
|
|
3703
4457
|
this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
|
|
3704
4458
|
}
|
|
3705
4459
|
async getActiveTasks() {
|
|
3706
|
-
const
|
|
4460
|
+
const rows2 = this.db.prepare(
|
|
3707
4461
|
`SELECT * FROM lt_scheduled_tasks WHERE status IN ('pending', 'paused') ORDER BY created_at ASC`
|
|
3708
4462
|
).all();
|
|
3709
|
-
return
|
|
4463
|
+
return rows2.map(mapRowToTask);
|
|
3710
4464
|
}
|
|
3711
4465
|
async getTasksByType(taskType) {
|
|
3712
|
-
const
|
|
4466
|
+
const rows2 = this.db.prepare(
|
|
3713
4467
|
`SELECT * FROM lt_scheduled_tasks WHERE task_type = ? ORDER BY created_at DESC`
|
|
3714
4468
|
).all(taskType);
|
|
3715
|
-
return
|
|
4469
|
+
return rows2.map(mapRowToTask);
|
|
3716
4470
|
}
|
|
3717
4471
|
async getTasksByStatus(status) {
|
|
3718
|
-
const
|
|
4472
|
+
const rows2 = this.db.prepare(
|
|
3719
4473
|
`SELECT * FROM lt_scheduled_tasks WHERE status = ? ORDER BY created_at DESC`
|
|
3720
4474
|
).all(status);
|
|
3721
|
-
return
|
|
4475
|
+
return rows2.map(mapRowToTask);
|
|
3722
4476
|
}
|
|
3723
4477
|
async getTasksByExecutionType(executionType) {
|
|
3724
|
-
const
|
|
4478
|
+
const rows2 = this.db.prepare(
|
|
3725
4479
|
`SELECT * FROM lt_scheduled_tasks WHERE execution_type = ? ORDER BY created_at DESC`
|
|
3726
4480
|
).all(executionType);
|
|
3727
|
-
return
|
|
4481
|
+
return rows2.map(mapRowToTask);
|
|
3728
4482
|
}
|
|
3729
4483
|
async getTasksByAssistantId(assistantId) {
|
|
3730
|
-
const
|
|
4484
|
+
const rows2 = this.db.prepare(
|
|
3731
4485
|
`SELECT * FROM lt_scheduled_tasks WHERE assistant_id = ? ORDER BY created_at DESC`
|
|
3732
4486
|
).all(assistantId);
|
|
3733
|
-
return
|
|
4487
|
+
return rows2.map(mapRowToTask);
|
|
3734
4488
|
}
|
|
3735
4489
|
async getTasksByThreadId(threadId) {
|
|
3736
|
-
const
|
|
4490
|
+
const rows2 = this.db.prepare(
|
|
3737
4491
|
`SELECT * FROM lt_scheduled_tasks WHERE thread_id = ? ORDER BY created_at DESC`
|
|
3738
4492
|
).all(threadId);
|
|
3739
|
-
return
|
|
4493
|
+
return rows2.map(mapRowToTask);
|
|
3740
4494
|
}
|
|
3741
4495
|
async getAllTasks(filters) {
|
|
3742
4496
|
const conditions = [];
|
|
@@ -3776,8 +4530,8 @@ var LocalScheduleStorage = class {
|
|
|
3776
4530
|
query += ` OFFSET ?`;
|
|
3777
4531
|
values.push(filters.offset);
|
|
3778
4532
|
}
|
|
3779
|
-
const
|
|
3780
|
-
return
|
|
4533
|
+
const rows2 = this.db.prepare(query).all(...values);
|
|
4534
|
+
return rows2.map(mapRowToTask);
|
|
3781
4535
|
}
|
|
3782
4536
|
async countTasks(filters) {
|
|
3783
4537
|
const conditions = [];
|
|
@@ -3850,6 +4604,15 @@ function mapRowToTask(row) {
|
|
|
3850
4604
|
// src/stores/LocalTaskStore.ts
|
|
3851
4605
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
3852
4606
|
var MAX_CANONICAL_TASK_TIME = "9999-12-31T23:59:59.999Z";
|
|
4607
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
4608
|
+
"pending",
|
|
4609
|
+
"in_progress",
|
|
4610
|
+
"review",
|
|
4611
|
+
"failed",
|
|
4612
|
+
"interrupted",
|
|
4613
|
+
"completed",
|
|
4614
|
+
"cancelled"
|
|
4615
|
+
]);
|
|
3853
4616
|
var NEXT_UPDATED_AT_SQL = `updated_at = CASE
|
|
3854
4617
|
WHEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) = updated_at AND updated_at >= ?
|
|
3855
4618
|
THEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds')
|
|
@@ -3922,6 +4685,9 @@ function nextISO(previous) {
|
|
|
3922
4685
|
const previousMs = new Date(previous).getTime();
|
|
3923
4686
|
return new Date(Math.max(Date.now(), previousMs + 1)).toISOString();
|
|
3924
4687
|
}
|
|
4688
|
+
function assertPage(limit, offset = 0) {
|
|
4689
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0) throw new RangeError("Invalid task page");
|
|
4690
|
+
}
|
|
3925
4691
|
var LocalTaskStore = class {
|
|
3926
4692
|
constructor(db) {
|
|
3927
4693
|
this.db = db;
|
|
@@ -3935,10 +4701,10 @@ var LocalTaskStore = class {
|
|
|
3935
4701
|
this.ensureColumn("lt_tasks", "files", "files TEXT");
|
|
3936
4702
|
}
|
|
3937
4703
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
3938
|
-
ensureColumn(table, column,
|
|
4704
|
+
ensureColumn(table, column, ddl2) {
|
|
3939
4705
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
3940
4706
|
if (!cols.some((c) => c.name === column)) {
|
|
3941
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
4707
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
3942
4708
|
}
|
|
3943
4709
|
}
|
|
3944
4710
|
async create(params) {
|
|
@@ -4004,7 +4770,8 @@ var LocalTaskStore = class {
|
|
|
4004
4770
|
conditions.push("workspace_id = ?");
|
|
4005
4771
|
params.push(filter.workspaceId);
|
|
4006
4772
|
}
|
|
4007
|
-
if (filter.projectId)
|
|
4773
|
+
if (filter.projectId === null) conditions.push("(project_id IS NULL OR project_id = '' OR project_id = 'default')");
|
|
4774
|
+
else if (filter.projectId !== void 0) {
|
|
4008
4775
|
conditions.push("project_id = ?");
|
|
4009
4776
|
params.push(filter.projectId);
|
|
4010
4777
|
}
|
|
@@ -4034,10 +4801,44 @@ var LocalTaskStore = class {
|
|
|
4034
4801
|
const where = conditions.join(" AND ");
|
|
4035
4802
|
const limit = filter.limit || 100;
|
|
4036
4803
|
const offset = filter.offset || 0;
|
|
4037
|
-
const
|
|
4038
|
-
`SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
4804
|
+
const rows2 = this.db.prepare(
|
|
4805
|
+
`SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`
|
|
4039
4806
|
).all(...params, limit, offset);
|
|
4040
|
-
return
|
|
4807
|
+
return rows2.map(mapRowToTask2);
|
|
4808
|
+
}
|
|
4809
|
+
/** Lists exact project tasks containing a string dependency. */
|
|
4810
|
+
async listDependents(query) {
|
|
4811
|
+
assertPage(query.limit, query.offset);
|
|
4812
|
+
if (query.statuses.length === 0 || query.statuses.some((status) => !TASK_STATUSES.has(status))) {
|
|
4813
|
+
throw new RangeError("Invalid task statuses");
|
|
4814
|
+
}
|
|
4815
|
+
const rows2 = this.db.prepare(
|
|
4816
|
+
`SELECT task.* FROM lt_tasks AS task
|
|
4817
|
+
WHERE task.tenant_id = ? AND task.workspace_id = ? AND task.project_id = ?
|
|
4818
|
+
AND task.status IN (${query.statuses.map(() => "?").join(", ")})
|
|
4819
|
+
AND task.dependencies IS NOT NULL
|
|
4820
|
+
AND json_valid(task.dependencies) = 1
|
|
4821
|
+
AND json_type(CASE WHEN json_valid(task.dependencies)=1
|
|
4822
|
+
THEN task.dependencies ELSE '[]' END) = 'array'
|
|
4823
|
+
AND EXISTS (
|
|
4824
|
+
SELECT 1 FROM json_each(
|
|
4825
|
+
CASE WHEN json_valid(task.dependencies)=1 THEN
|
|
4826
|
+
CASE WHEN json_type(task.dependencies)='array' THEN task.dependencies ELSE '[]' END
|
|
4827
|
+
ELSE '[]' END
|
|
4828
|
+
) AS dependency
|
|
4829
|
+
WHERE dependency.type = 'text' AND dependency.value = ?
|
|
4830
|
+
)
|
|
4831
|
+
ORDER BY task.created_at DESC, task.id DESC LIMIT ? OFFSET ?`
|
|
4832
|
+
).all(
|
|
4833
|
+
query.tenantId,
|
|
4834
|
+
query.workspaceId,
|
|
4835
|
+
query.projectId,
|
|
4836
|
+
...query.statuses,
|
|
4837
|
+
query.dependencyTaskId,
|
|
4838
|
+
query.limit,
|
|
4839
|
+
query.offset
|
|
4840
|
+
);
|
|
4841
|
+
return rows2.map(mapRowToTask2);
|
|
4041
4842
|
}
|
|
4042
4843
|
async update(tenantId, id, updates) {
|
|
4043
4844
|
const existing = await this.getById(tenantId, id);
|
|
@@ -4389,6 +5190,54 @@ var LocalTaskStore = class {
|
|
|
4389
5190
|
if (result.changes === 0) return null;
|
|
4390
5191
|
return this.getById(tenantId, id);
|
|
4391
5192
|
}
|
|
5193
|
+
/** Atomically update only while status, timestamp, owner, and Project scope match. */
|
|
5194
|
+
async updateIfSnapshot(tenantId, id, updates, snapshot) {
|
|
5195
|
+
const expectedIso = new Date(snapshot.updatedAt).toISOString();
|
|
5196
|
+
if (expectedIso === MAX_CANONICAL_TASK_TIME) return null;
|
|
5197
|
+
const setClauses = ["updated_at = ?"];
|
|
5198
|
+
const values = [nextISO(expectedIso)];
|
|
5199
|
+
const fields = [
|
|
5200
|
+
["title", "title"],
|
|
5201
|
+
["description", "description"],
|
|
5202
|
+
["status", "status"],
|
|
5203
|
+
["priority", "priority"],
|
|
5204
|
+
["dueDate", "due_date"],
|
|
5205
|
+
["metadata", "metadata", true],
|
|
5206
|
+
["parentId", "parent_id"],
|
|
5207
|
+
["sourceId", "source_id"],
|
|
5208
|
+
["context", "context", true],
|
|
5209
|
+
["ownerType", "owner_type"],
|
|
5210
|
+
["ownerId", "owner_id"],
|
|
5211
|
+
["requireReview", "require_review"],
|
|
5212
|
+
["dependencies", "dependencies", true],
|
|
5213
|
+
["result", "result"],
|
|
5214
|
+
["failureReason", "failure_reason"],
|
|
5215
|
+
["workspaceId", "workspace_id"],
|
|
5216
|
+
["projectId", "project_id"],
|
|
5217
|
+
["files", "files", true]
|
|
5218
|
+
];
|
|
5219
|
+
for (const [field, column, json] of fields) {
|
|
5220
|
+
const value = updates[field];
|
|
5221
|
+
if (value === void 0) continue;
|
|
5222
|
+
setClauses.push(`${column} = ?`);
|
|
5223
|
+
values.push(json && value !== null ? JSON.stringify(value) : field === "requireReview" ? value ? 1 : 0 : value);
|
|
5224
|
+
}
|
|
5225
|
+
values.push(
|
|
5226
|
+
tenantId,
|
|
5227
|
+
id,
|
|
5228
|
+
snapshot.status,
|
|
5229
|
+
expectedIso,
|
|
5230
|
+
snapshot.ownerType,
|
|
5231
|
+
snapshot.ownerId,
|
|
5232
|
+
snapshot.workspaceId,
|
|
5233
|
+
snapshot.projectId
|
|
5234
|
+
);
|
|
5235
|
+
const result = this.db.prepare(
|
|
5236
|
+
`UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND status = ?
|
|
5237
|
+
AND updated_at = ? AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
|
|
5238
|
+
).run(...values);
|
|
5239
|
+
return result.changes === 0 ? null : this.getById(tenantId, id);
|
|
5240
|
+
}
|
|
4392
5241
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
4393
5242
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
4394
5243
|
if (expectedStatuses.length === 0) return null;
|
|
@@ -4489,11 +5338,36 @@ var LocalTaskStore = class {
|
|
|
4489
5338
|
).run(tenantId, id);
|
|
4490
5339
|
return result.changes > 0;
|
|
4491
5340
|
}
|
|
5341
|
+
/** Atomically delete only while status, timestamp, owner, and Project scope match. */
|
|
5342
|
+
async deleteIfSnapshot(tenantId, id, snapshot) {
|
|
5343
|
+
const result = this.db.prepare(
|
|
5344
|
+
`DELETE FROM lt_tasks WHERE tenant_id = ? AND id = ? AND status = ? AND updated_at = ?
|
|
5345
|
+
AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
|
|
5346
|
+
).run(
|
|
5347
|
+
tenantId,
|
|
5348
|
+
id,
|
|
5349
|
+
snapshot.status,
|
|
5350
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
5351
|
+
snapshot.ownerType,
|
|
5352
|
+
snapshot.ownerId,
|
|
5353
|
+
snapshot.workspaceId,
|
|
5354
|
+
snapshot.projectId
|
|
5355
|
+
);
|
|
5356
|
+
return result.changes > 0;
|
|
5357
|
+
}
|
|
4492
5358
|
};
|
|
4493
5359
|
|
|
4494
5360
|
// src/stores/LocalTaskWorkItemStore.ts
|
|
4495
5361
|
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
4496
5362
|
import { v4 as uuid2 } from "uuid";
|
|
5363
|
+
var PROJECT_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
5364
|
+
"in_progress",
|
|
5365
|
+
"interrupted",
|
|
5366
|
+
"failed",
|
|
5367
|
+
"completed",
|
|
5368
|
+
"cancelled",
|
|
5369
|
+
"reassigned"
|
|
5370
|
+
]);
|
|
4497
5371
|
var DDL21 = `
|
|
4498
5372
|
CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
4499
5373
|
id TEXT NOT NULL,
|
|
@@ -4511,6 +5385,15 @@ CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
|
4511
5385
|
PRIMARY KEY (tenant_id, id)
|
|
4512
5386
|
);
|
|
4513
5387
|
`;
|
|
5388
|
+
function parseDetail(raw) {
|
|
5389
|
+
if (!raw) return void 0;
|
|
5390
|
+
try {
|
|
5391
|
+
const parsed = JSON.parse(raw);
|
|
5392
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
5393
|
+
} catch {
|
|
5394
|
+
return void 0;
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
4514
5397
|
function mapRowToWorkItem(row) {
|
|
4515
5398
|
return {
|
|
4516
5399
|
id: row.id,
|
|
@@ -4520,7 +5403,7 @@ function mapRowToWorkItem(row) {
|
|
|
4520
5403
|
actor: row.actor,
|
|
4521
5404
|
threadId: row.thread_id ?? void 0,
|
|
4522
5405
|
summary: row.summary ?? void 0,
|
|
4523
|
-
detail:
|
|
5406
|
+
detail: parseDetail(row.detail),
|
|
4524
5407
|
attempt: row.attempt ?? void 0,
|
|
4525
5408
|
workspaceId: row.workspace_id ?? void 0,
|
|
4526
5409
|
projectId: row.project_id ?? void 0,
|
|
@@ -4528,6 +5411,34 @@ function mapRowToWorkItem(row) {
|
|
|
4528
5411
|
createdAt: parseISO(row.created_at)
|
|
4529
5412
|
};
|
|
4530
5413
|
}
|
|
5414
|
+
function assertAndCloneProjectLifecycleEvent(item) {
|
|
5415
|
+
let milliseconds;
|
|
5416
|
+
try {
|
|
5417
|
+
milliseconds = Date.prototype.getTime.call(item.createdAt);
|
|
5418
|
+
} catch {
|
|
5419
|
+
throw new Error("Invalid project lifecycle event row");
|
|
5420
|
+
}
|
|
5421
|
+
if (!Number.isFinite(milliseconds) || typeof item.id !== "string" || item.id.length === 0 || typeof item.eventKey !== "string" || item.eventKey.length === 0) {
|
|
5422
|
+
throw new Error("Invalid project lifecycle event row");
|
|
5423
|
+
}
|
|
5424
|
+
return { ...item, createdAt: new Date(milliseconds) };
|
|
5425
|
+
}
|
|
5426
|
+
function assertLifecyclePage(query) {
|
|
5427
|
+
if (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100 || query.actions.length === 0 || query.actions.some((action) => !PROJECT_LIFECYCLE_ACTIONS.has(action))) {
|
|
5428
|
+
throw new RangeError("Invalid project lifecycle event page");
|
|
5429
|
+
}
|
|
5430
|
+
if (!query.before) return null;
|
|
5431
|
+
let milliseconds;
|
|
5432
|
+
try {
|
|
5433
|
+
milliseconds = Date.prototype.getTime.call(query.before.createdAt);
|
|
5434
|
+
} catch {
|
|
5435
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
5436
|
+
}
|
|
5437
|
+
if (!Number.isFinite(milliseconds) || typeof query.before.id !== "string" || query.before.id.length === 0) {
|
|
5438
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
5439
|
+
}
|
|
5440
|
+
return milliseconds;
|
|
5441
|
+
}
|
|
4531
5442
|
var LocalTaskWorkItemStore = class {
|
|
4532
5443
|
constructor(db) {
|
|
4533
5444
|
this.db = db;
|
|
@@ -4542,12 +5453,15 @@ var LocalTaskWorkItemStore = class {
|
|
|
4542
5453
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
4543
5454
|
ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
|
|
4544
5455
|
WHERE action = 'execution_reconciled';`);
|
|
5456
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_project_lifecycle
|
|
5457
|
+
ON lt_task_work_items (tenant_id, workspace_id, project_id, created_at DESC, id DESC)
|
|
5458
|
+
WHERE event_key IS NOT NULL;`);
|
|
4545
5459
|
}
|
|
4546
5460
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4547
|
-
ensureColumn(table, column,
|
|
5461
|
+
ensureColumn(table, column, ddl2) {
|
|
4548
5462
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
4549
5463
|
if (!cols.some((c) => c.name === column)) {
|
|
4550
|
-
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${
|
|
5464
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
|
|
4551
5465
|
}
|
|
4552
5466
|
}
|
|
4553
5467
|
async create(params) {
|
|
@@ -4575,6 +5489,41 @@ var LocalTaskWorkItemStore = class {
|
|
|
4575
5489
|
).get(params.tenantId, id);
|
|
4576
5490
|
return mapRowToWorkItem(row);
|
|
4577
5491
|
}
|
|
5492
|
+
/** Atomically inserts a work item by selecting one exact task snapshot. */
|
|
5493
|
+
async createIfTaskSnapshot(params, snapshot) {
|
|
5494
|
+
const id = uuid2();
|
|
5495
|
+
const result = this.db.prepare(
|
|
5496
|
+
`INSERT INTO lt_task_work_items
|
|
5497
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
5498
|
+
SELECT ?, task.tenant_id, task.id, ?, ?, ?, ?, ?, ?, task.workspace_id, task.project_id, ?
|
|
5499
|
+
FROM lt_tasks AS task
|
|
5500
|
+
WHERE task.tenant_id = ? AND task.id = ? AND task.status = ? AND task.updated_at = ?
|
|
5501
|
+
AND task.owner_type = ? AND task.owner_id = ? AND task.workspace_id IS ? AND task.project_id IS ?
|
|
5502
|
+
AND ? IS task.workspace_id AND ? IS task.project_id`
|
|
5503
|
+
).run(
|
|
5504
|
+
id,
|
|
5505
|
+
params.action,
|
|
5506
|
+
params.actor,
|
|
5507
|
+
params.threadId || null,
|
|
5508
|
+
params.summary || null,
|
|
5509
|
+
params.detail ? JSON.stringify(params.detail) : null,
|
|
5510
|
+
params.attempt ?? null,
|
|
5511
|
+
nowISO(),
|
|
5512
|
+
params.tenantId,
|
|
5513
|
+
params.taskId,
|
|
5514
|
+
snapshot.status,
|
|
5515
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
5516
|
+
snapshot.ownerType,
|
|
5517
|
+
snapshot.ownerId,
|
|
5518
|
+
snapshot.workspaceId,
|
|
5519
|
+
snapshot.projectId,
|
|
5520
|
+
params.workspaceId,
|
|
5521
|
+
params.projectId
|
|
5522
|
+
);
|
|
5523
|
+
if (result.changes === 0) return null;
|
|
5524
|
+
const row = this.db.prepare(`SELECT * FROM lt_task_work_items WHERE tenant_id = ? AND id = ?`).get(params.tenantId, id);
|
|
5525
|
+
return row ? mapRowToWorkItem(row) : null;
|
|
5526
|
+
}
|
|
4578
5527
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
4579
5528
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
4580
5529
|
const row = this.db.prepare(
|
|
@@ -4627,10 +5576,10 @@ var LocalTaskWorkItemStore = class {
|
|
|
4627
5576
|
const limit = filter.limit || 100;
|
|
4628
5577
|
const offset = filter.offset || 0;
|
|
4629
5578
|
const order = filter.order === "asc" ? "ASC" : "DESC";
|
|
4630
|
-
const
|
|
5579
|
+
const rows2 = this.db.prepare(
|
|
4631
5580
|
`SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at ${order}, id ${order} LIMIT ? OFFSET ?`
|
|
4632
5581
|
).all(...params, limit, offset);
|
|
4633
|
-
return
|
|
5582
|
+
return rows2.map(mapRowToWorkItem);
|
|
4634
5583
|
}
|
|
4635
5584
|
/** List pending execution results using one bounded SQLite anti-join query. */
|
|
4636
5585
|
async listPendingExecutionResults(params) {
|
|
@@ -4640,7 +5589,7 @@ var LocalTaskWorkItemStore = class {
|
|
|
4640
5589
|
throw error;
|
|
4641
5590
|
}
|
|
4642
5591
|
if (params.limit === 0) return [];
|
|
4643
|
-
const
|
|
5592
|
+
const rows2 = this.db.prepare(
|
|
4644
5593
|
`SELECT result.*
|
|
4645
5594
|
FROM lt_task_work_items AS result
|
|
4646
5595
|
WHERE result.tenant_id = ?
|
|
@@ -4660,7 +5609,31 @@ var LocalTaskWorkItemStore = class {
|
|
|
4660
5609
|
ORDER BY result.created_at DESC, result.id DESC
|
|
4661
5610
|
LIMIT ?`
|
|
4662
5611
|
).all(params.tenantId, params.taskId, params.limit);
|
|
4663
|
-
return
|
|
5612
|
+
return rows2.map(mapRowToWorkItem);
|
|
5613
|
+
}
|
|
5614
|
+
/** Lists canonical project lifecycle events with an exclusive cursor. */
|
|
5615
|
+
async listProjectLifecycleEvents(query) {
|
|
5616
|
+
const cursorMilliseconds = assertLifecyclePage(query);
|
|
5617
|
+
const cursor = cursorMilliseconds === null ? null : new Date(cursorMilliseconds).toISOString();
|
|
5618
|
+
const rows2 = this.db.prepare(
|
|
5619
|
+
`SELECT * FROM lt_task_work_items
|
|
5620
|
+
WHERE tenant_id = ? AND workspace_id = ? AND project_id = ?
|
|
5621
|
+
AND action IN (${query.actions.map(() => "?").join(", ")})
|
|
5622
|
+
AND event_key IS NOT NULL AND event_key <> ''
|
|
5623
|
+
AND (? IS NULL OR created_at < ? OR (created_at = ? AND id < ?))
|
|
5624
|
+
ORDER BY created_at DESC, id DESC LIMIT ?`
|
|
5625
|
+
).all(
|
|
5626
|
+
query.tenantId,
|
|
5627
|
+
query.workspaceId,
|
|
5628
|
+
query.projectId,
|
|
5629
|
+
...query.actions,
|
|
5630
|
+
cursor,
|
|
5631
|
+
cursor,
|
|
5632
|
+
cursor,
|
|
5633
|
+
query.before?.id ?? null,
|
|
5634
|
+
query.limit
|
|
5635
|
+
);
|
|
5636
|
+
return rows2.map(mapRowToWorkItem).map(assertAndCloneProjectLifecycleEvent);
|
|
4664
5637
|
}
|
|
4665
5638
|
};
|
|
4666
5639
|
|
|
@@ -4769,6 +5742,10 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4769
5742
|
assistant: new LocalAssistantStore(db),
|
|
4770
5743
|
workspace: new LocalWorkspaceStore(db),
|
|
4771
5744
|
project: new LocalProjectStore(db),
|
|
5745
|
+
projectRoom: new LocalProjectRoomStore(db),
|
|
5746
|
+
projectMembership: new LocalProjectMembershipStore(db),
|
|
5747
|
+
projectBotMembership: new LocalProjectBotMembershipStore(db),
|
|
5748
|
+
projectRoomMessage: new LocalProjectRoomMessageStore(db),
|
|
4772
5749
|
user: new LocalUserStore(db),
|
|
4773
5750
|
tenant: new LocalTenantStore(db),
|
|
4774
5751
|
userTenantLink: new LocalUserTenantLinkStore(db),
|
|
@@ -4802,7 +5779,7 @@ var InMemoryConversationStore = class {
|
|
|
4802
5779
|
}
|
|
4803
5780
|
async createConversation(input) {
|
|
4804
5781
|
const now = isoNow();
|
|
4805
|
-
const
|
|
5782
|
+
const record2 = {
|
|
4806
5783
|
conversationId: input.conversationId,
|
|
4807
5784
|
tenantId: input.tenantId,
|
|
4808
5785
|
taskIds: [],
|
|
@@ -4811,17 +5788,17 @@ var InMemoryConversationStore = class {
|
|
|
4811
5788
|
createdAt: now,
|
|
4812
5789
|
updatedAt: now
|
|
4813
5790
|
};
|
|
4814
|
-
this.store.set(input.conversationId,
|
|
4815
|
-
return
|
|
5791
|
+
this.store.set(input.conversationId, record2);
|
|
5792
|
+
return record2;
|
|
4816
5793
|
}
|
|
4817
5794
|
async addTaskToConversation(conversationId, taskId) {
|
|
4818
|
-
const
|
|
4819
|
-
if (!
|
|
5795
|
+
const record2 = this.store.get(conversationId);
|
|
5796
|
+
if (!record2) {
|
|
4820
5797
|
throw new Error(`Conversation not found: ${conversationId}`);
|
|
4821
5798
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
return
|
|
5799
|
+
record2.taskIds.push(taskId);
|
|
5800
|
+
record2.updatedAt = isoNow();
|
|
5801
|
+
return record2;
|
|
4825
5802
|
}
|
|
4826
5803
|
async deleteConversation(conversationId) {
|
|
4827
5804
|
this.store.delete(conversationId);
|
|
@@ -4829,6 +5806,8 @@ var InMemoryConversationStore = class {
|
|
|
4829
5806
|
};
|
|
4830
5807
|
export {
|
|
4831
5808
|
DatabaseWrapper,
|
|
5809
|
+
DuplicateProjectMembershipError,
|
|
5810
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
4832
5811
|
InMemoryConversationStore,
|
|
4833
5812
|
LocalA2AApiKeyStore,
|
|
4834
5813
|
LocalAssistantStore,
|
|
@@ -4840,6 +5819,10 @@ export {
|
|
|
4840
5819
|
LocalEvalStore,
|
|
4841
5820
|
LocalMcpServerConfigStore,
|
|
4842
5821
|
LocalMetricsServerConfigStore,
|
|
5822
|
+
LocalProjectBotMembershipStore,
|
|
5823
|
+
LocalProjectMembershipStore,
|
|
5824
|
+
LocalProjectRoomMessageStore,
|
|
5825
|
+
LocalProjectRoomStore,
|
|
4843
5826
|
LocalProjectStore,
|
|
4844
5827
|
LocalScheduleStorage,
|
|
4845
5828
|
LocalSkillStore,
|
|
@@ -4853,6 +5836,9 @@ export {
|
|
|
4853
5836
|
LocalWorkflowTrackingStore,
|
|
4854
5837
|
LocalWorkspaceStore,
|
|
4855
5838
|
MigrationManager,
|
|
5839
|
+
ProjectBotMembershipIdConflictError,
|
|
5840
|
+
ProjectMembershipIdConflictError,
|
|
5841
|
+
ProjectRoomMessageIdConflictError,
|
|
4856
5842
|
RunResult,
|
|
4857
5843
|
StatementWrapper,
|
|
4858
5844
|
closeDatabase,
|