@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/dist/index.js CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DatabaseWrapper: () => DatabaseWrapper,
34
+ DuplicateProjectMembershipError: () => DuplicateProjectMembershipError,
35
+ DuplicateProjectRoomMessageIdempotencyKeyError: () => DuplicateProjectRoomMessageIdempotencyKeyError,
34
36
  InMemoryConversationStore: () => InMemoryConversationStore,
35
37
  LocalA2AApiKeyStore: () => LocalA2AApiKeyStore,
36
38
  LocalAssistantStore: () => LocalAssistantStore,
@@ -42,6 +44,10 @@ __export(index_exports, {
42
44
  LocalEvalStore: () => LocalEvalStore,
43
45
  LocalMcpServerConfigStore: () => LocalMcpServerConfigStore,
44
46
  LocalMetricsServerConfigStore: () => LocalMetricsServerConfigStore,
47
+ LocalProjectBotMembershipStore: () => LocalProjectBotMembershipStore,
48
+ LocalProjectMembershipStore: () => LocalProjectMembershipStore,
49
+ LocalProjectRoomMessageStore: () => LocalProjectRoomMessageStore,
50
+ LocalProjectRoomStore: () => LocalProjectRoomStore,
45
51
  LocalProjectStore: () => LocalProjectStore,
46
52
  LocalScheduleStorage: () => LocalScheduleStorage,
47
53
  LocalSkillStore: () => LocalSkillStore,
@@ -55,6 +61,9 @@ __export(index_exports, {
55
61
  LocalWorkflowTrackingStore: () => LocalWorkflowTrackingStore,
56
62
  LocalWorkspaceStore: () => LocalWorkspaceStore,
57
63
  MigrationManager: () => MigrationManager,
64
+ ProjectBotMembershipIdConflictError: () => ProjectBotMembershipIdConflictError,
65
+ ProjectMembershipIdConflictError: () => ProjectMembershipIdConflictError,
66
+ ProjectRoomMessageIdConflictError: () => ProjectRoomMessageIdConflictError,
58
67
  RunResult: () => RunResult,
59
68
  StatementWrapper: () => StatementWrapper,
60
69
  closeDatabase: () => closeDatabase,
@@ -150,11 +159,11 @@ var StatementWrapper = class {
150
159
  if (params.length > 0) {
151
160
  stmt.bind(params);
152
161
  }
153
- const rows = [];
162
+ const rows2 = [];
154
163
  while (stmt.step()) {
155
- rows.push(stmt.getAsObject());
164
+ rows2.push(stmt.getAsObject());
156
165
  }
157
- return rows;
166
+ return rows2;
158
167
  } finally {
159
168
  stmt.free();
160
169
  }
@@ -229,8 +238,8 @@ function closeDatabase() {
229
238
  _db = null;
230
239
  }
231
240
  }
232
- function ensureTable(db, ddl) {
233
- db.exec(ddl);
241
+ function ensureTable(db, ddl2) {
242
+ db.exec(ddl2);
234
243
  }
235
244
  function nowISO() {
236
245
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -274,7 +283,10 @@ var MigrationManager = class {
274
283
  this.db.run(
275
284
  `INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at)
276
285
  VALUES (?, ?, ?, ?)`,
277
- [this.storeName, m.name, m.version, (/* @__PURE__ */ new Date()).toISOString()]
286
+ this.storeName,
287
+ m.name,
288
+ m.version,
289
+ (/* @__PURE__ */ new Date()).toISOString()
278
290
  );
279
291
  }
280
292
  }
@@ -324,10 +336,10 @@ var MigrationManager = class {
324
336
  `);
325
337
  }
326
338
  getAppliedNames() {
327
- const rows = this.db.prepare(
339
+ const rows2 = this.db.prepare(
328
340
  `SELECT name FROM lattice_schema_migrations WHERE store_name = ? ORDER BY version`
329
341
  ).all(this.storeName);
330
- return new Set(rows.map((r) => r.name));
342
+ return new Set(rows2.map((r) => r.name));
331
343
  }
332
344
  };
333
345
 
@@ -362,10 +374,10 @@ var LocalThreadStore = class {
362
374
  mm.migrate();
363
375
  }
364
376
  async getThreadsByAssistantId(tenantId, assistantId, metadataFilter) {
365
- const rows = this.db.prepare(
377
+ const rows2 = this.db.prepare(
366
378
  `SELECT * FROM lt_threads WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
367
379
  ).all(tenantId, assistantId);
368
- let threads = rows.map(mapRowToThread);
380
+ let threads = rows2.map(mapRowToThread);
369
381
  if (metadataFilter && Object.keys(metadataFilter).length > 0) {
370
382
  threads = threads.filter(
371
383
  (t) => Object.entries(metadataFilter).every(
@@ -462,10 +474,10 @@ var LocalAssistantStore = class {
462
474
  ensureTable(db, DDL2);
463
475
  }
464
476
  async getAllAssistants(tenantId) {
465
- const rows = this.db.prepare(
477
+ const rows2 = this.db.prepare(
466
478
  `SELECT * FROM lt_assistants WHERE tenant_id = ? ORDER BY created_at DESC`
467
479
  ).all(tenantId);
468
- return rows.map(mapRowToAssistant);
480
+ return rows2.map(mapRowToAssistant);
469
481
  }
470
482
  async getAssistantById(tenantId, id) {
471
483
  const row = this.db.prepare(
@@ -578,10 +590,10 @@ var LocalWorkspaceStore = class {
578
590
  ensureTable(db, DDL3);
579
591
  }
580
592
  async getAllWorkspaces(tenantId) {
581
- const rows = this.db.prepare(
593
+ const rows2 = this.db.prepare(
582
594
  `SELECT * FROM lt_workspaces WHERE tenant_id = ? ORDER BY created_at DESC`
583
595
  ).all(tenantId);
584
- return rows.map(mapRowToWorkspace);
596
+ return rows2.map(mapRowToWorkspace);
585
597
  }
586
598
  async getWorkspaceById(tenantId, id) {
587
599
  const row = this.db.prepare(
@@ -681,15 +693,15 @@ var LocalProjectStore = class {
681
693
  }
682
694
  async getProjectsByWorkspace(tenantId, workspaceId, filter) {
683
695
  if (filter?.kind !== void 0) {
684
- const rows2 = this.db.prepare(
696
+ const rows3 = this.db.prepare(
685
697
  `SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? AND kind = ? ORDER BY created_at DESC`
686
698
  ).all(tenantId, workspaceId, filter.kind);
687
- return rows2.map(mapRowToProject);
699
+ return rows3.map(mapRowToProject);
688
700
  }
689
- const rows = this.db.prepare(
701
+ const rows2 = this.db.prepare(
690
702
  `SELECT * FROM lt_projects WHERE tenant_id = ? AND workspace_id = ? ORDER BY created_at DESC`
691
703
  ).all(tenantId, workspaceId);
692
- return rows.map(mapRowToProject);
704
+ return rows2.map(mapRowToProject);
693
705
  }
694
706
  async getProjectById(tenantId, id) {
695
707
  const row = this.db.prepare(
@@ -793,8 +805,8 @@ var LocalProjectStore = class {
793
805
  return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
794
806
  }
795
807
  async isCapabilityBundleReferenced(tenantId, bundleId) {
796
- const rows = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
797
- return rows.some((row) => {
808
+ const rows2 = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
809
+ return rows2.some((row) => {
798
810
  if (!row.config) return false;
799
811
  let config;
800
812
  try {
@@ -808,10 +820,10 @@ var LocalProjectStore = class {
808
820
  });
809
821
  }
810
822
  /** Add a column if it does not exist (SQLite version compatible). */
811
- ensureColumn(table, column, ddl) {
823
+ ensureColumn(table, column, ddl2) {
812
824
  const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
813
825
  if (!cols.some((c) => c.name === column)) {
814
- this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
826
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
815
827
  }
816
828
  }
817
829
  };
@@ -829,6 +841,640 @@ function mapRowToProject(row) {
829
841
  };
830
842
  }
831
843
 
844
+ // src/stores/project-room-schema.ts
845
+ var schemaMarker = Symbol("projectRoomSchema");
846
+ var activeTransactions = /* @__PURE__ */ new WeakSet();
847
+ var migrationName = "create_project_room_tables";
848
+ var migrationTableDdl = `CREATE TABLE IF NOT EXISTS lattice_schema_migrations (
849
+ store_name TEXT NOT NULL, name TEXT NOT NULL, version INTEGER NOT NULL, applied_at TEXT NOT NULL,
850
+ PRIMARY KEY (store_name, name)
851
+ )`;
852
+ var ddl = `
853
+ CREATE TABLE IF NOT EXISTS lt_project_rooms (
854
+ id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
855
+ type TEXT NOT NULL CHECK (type = 'main'), name TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
856
+ PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, type)
857
+ );
858
+ CREATE TABLE IF NOT EXISTS lt_project_memberships (
859
+ id TEXT NOT NULL, tenant_id TEXT NOT NULL, project_id TEXT NOT NULL, user_id TEXT NOT NULL,
860
+ role TEXT NOT NULL CHECK (role IN ('owner','admin','member','viewer')),
861
+ status TEXT NOT NULL CHECK (status IN ('active','removed')), joined_at TEXT NOT NULL, updated_at TEXT NOT NULL,
862
+ PRIMARY KEY (tenant_id, id), UNIQUE (tenant_id, project_id, user_id)
863
+ );
864
+ CREATE INDEX IF NOT EXISTS idx_lt_project_memberships_project ON lt_project_memberships (tenant_id, project_id);
865
+ CREATE TABLE IF NOT EXISTS lt_project_bot_memberships (
866
+ id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL,
867
+ room_id TEXT NOT NULL, assistant_id TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('coordinator','specialist')),
868
+ title TEXT NOT NULL, responsibility TEXT, mention_name TEXT NOT NULL,
869
+ status TEXT NOT NULL CHECK (status IN ('active','paused','removed')), room_thread_id TEXT NOT NULL,
870
+ joined_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id),
871
+ UNIQUE (tenant_id, project_id, assistant_id)
872
+ );
873
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_coordinator
874
+ ON lt_project_bot_memberships (tenant_id, project_id) WHERE role = 'coordinator' AND status IN ('active','paused');
875
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_bot_memberships_mention
876
+ ON lt_project_bot_memberships (tenant_id, room_id, mention_name) WHERE status IN ('active','paused');
877
+ CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_project ON lt_project_bot_memberships (tenant_id, project_id);
878
+ CREATE INDEX IF NOT EXISTS idx_lt_project_bot_memberships_room ON lt_project_bot_memberships (tenant_id, room_id);
879
+ CREATE TABLE IF NOT EXISTS lt_project_room_messages (
880
+ id TEXT NOT NULL, tenant_id TEXT NOT NULL, workspace_id TEXT NOT NULL, project_id TEXT NOT NULL, room_id TEXT NOT NULL,
881
+ author TEXT NOT NULL, content TEXT NOT NULL, mentions TEXT NOT NULL DEFAULT '[]', reply_to_message_id TEXT,
882
+ source TEXT NOT NULL CHECK (source IN ('user','agent','task','routine','system')), source_id TEXT,
883
+ idempotency_key TEXT, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, id)
884
+ );
885
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_room_messages_idempotency
886
+ ON lt_project_room_messages (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
887
+ CREATE INDEX IF NOT EXISTS idx_lt_project_room_messages_room_created
888
+ ON lt_project_room_messages (tenant_id, room_id, created_at DESC, id DESC);
889
+ `;
890
+ var tableColumns = {
891
+ lt_project_rooms: ["id", "tenant_id", "workspace_id", "project_id", "type", "name", "created_at", "updated_at"],
892
+ lt_project_memberships: ["id", "tenant_id", "project_id", "user_id", "role", "status", "joined_at", "updated_at"],
893
+ lt_project_bot_memberships: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "assistant_id", "role", "title", "responsibility", "mention_name", "status", "room_thread_id", "joined_at", "updated_at"],
894
+ lt_project_room_messages: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "author", "content", "mentions", "reply_to_message_id", "source", "source_id", "idempotency_key", "created_at"]
895
+ };
896
+ var indexes = {
897
+ idx_lt_project_memberships_project: { table: "lt_project_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
898
+ uq_lt_project_bot_memberships_coordinator: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: true, partial: true, sql: ["role = 'coordinator'", "status in ('active','paused')"] },
899
+ uq_lt_project_bot_memberships_mention: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id", "mention_name"], unique: true, partial: true, sql: ["status in ('active','paused')"] },
900
+ idx_lt_project_bot_memberships_project: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
901
+ idx_lt_project_bot_memberships_room: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id"], unique: false, partial: false },
902
+ uq_lt_project_room_messages_idempotency: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "idempotency_key"], unique: true, partial: true, sql: ["idempotency_key is not null"] },
903
+ idx_lt_project_room_messages_room_created: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "created_at", "id"], unique: false, partial: false, sql: ["created_at desc", "id desc"] }
904
+ };
905
+ var uniqueConstraints = {
906
+ lt_project_rooms: [["tenant_id", "id"], ["tenant_id", "project_id", "type"]],
907
+ lt_project_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "user_id"]],
908
+ lt_project_bot_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "assistant_id"]],
909
+ lt_project_room_messages: [["tenant_id", "id"]]
910
+ };
911
+ var checkConstraints = {
912
+ lt_project_rooms: [{ name: "type", fragment: "type='main'" }],
913
+ lt_project_memberships: [
914
+ { name: "role", fragment: "role in ('owner','admin','member','viewer')" },
915
+ { name: "status", fragment: "status in ('active','removed')" }
916
+ ],
917
+ lt_project_bot_memberships: [
918
+ { name: "role", fragment: "role in ('coordinator','specialist')" },
919
+ { name: "status", fragment: "status in ('active','paused','removed')" }
920
+ ],
921
+ lt_project_room_messages: [{ name: "source", fragment: "source in ('user','agent','task','routine','system')" }]
922
+ };
923
+ function rows(db, sql) {
924
+ const result = db.getRawDb().exec(sql);
925
+ if (!result[0]) return [];
926
+ return result[0].values.map((values) => Object.fromEntries(result[0].columns.map((column, index) => [column, values[index]])));
927
+ }
928
+ function normalizeSql(value) {
929
+ return value.toLowerCase().replace(/["`]/g, "").replace(/\[|\]/g, "").replace(/\s+/g, " ").replace(/\s*([=,])\s*/g, "$1").replace(/[()]/g, "").trim();
930
+ }
931
+ function extractCheckExpressions(sql) {
932
+ const checks = [];
933
+ let quoted = false;
934
+ for (let index = 0; index < sql.length; index += 1) {
935
+ if (sql[index] === "'") {
936
+ if (quoted && sql[index + 1] === "'") {
937
+ index += 1;
938
+ continue;
939
+ }
940
+ quoted = !quoted;
941
+ continue;
942
+ }
943
+ if (quoted || sql.slice(index, index + 5).toLowerCase() !== "check") continue;
944
+ const before = sql[index - 1];
945
+ const after = sql[index + 5];
946
+ if (before && /[a-z0-9_]/i.test(before) || after && /[a-z0-9_]/i.test(after)) continue;
947
+ let opening = index + 5;
948
+ while (/\s/.test(sql[opening] ?? "")) opening += 1;
949
+ if (sql[opening] !== "(") continue;
950
+ let depth = 1;
951
+ let expressionQuoted = false;
952
+ for (let cursor = opening + 1; cursor < sql.length; cursor += 1) {
953
+ if (sql[cursor] === "'") {
954
+ if (expressionQuoted && sql[cursor + 1] === "'") {
955
+ cursor += 1;
956
+ continue;
957
+ }
958
+ expressionQuoted = !expressionQuoted;
959
+ } else if (!expressionQuoted && sql[cursor] === "(") {
960
+ depth += 1;
961
+ } else if (!expressionQuoted && sql[cursor] === ")") {
962
+ depth -= 1;
963
+ if (depth === 0) {
964
+ checks.push(sql.slice(opening + 1, cursor));
965
+ index = cursor;
966
+ break;
967
+ }
968
+ }
969
+ }
970
+ }
971
+ return checks;
972
+ }
973
+ function validateProjectRoomSchema(db) {
974
+ for (const [table, expected] of Object.entries(tableColumns)) {
975
+ const actual = rows(db, `PRAGMA table_info(${table})`);
976
+ if (actual.map((column) => column.name).join(",") !== expected.join(",") || actual.some((column) => column.type !== "TEXT") || actual.some((column) => column.name !== "responsibility" && column.name !== "reply_to_message_id" && column.name !== "source_id" && column.name !== "idempotency_key" && column.notnull !== 1)) {
977
+ throw new Error(`Invalid project room schema: table '${table}' has incompatible columns`);
978
+ }
979
+ const uniqueIndexes = rows(db, `PRAGMA index_list(${table})`).filter((index) => index.unique === 1);
980
+ const signatures = uniqueIndexes.map((index) => rows(db, `PRAGMA index_info(${String(index.name)})`).map((column) => column.name).join(","));
981
+ if (uniqueConstraints[table].some((constraint) => !signatures.includes(constraint.join(",")))) {
982
+ throw new Error(`Invalid project room schema: table '${table}' is missing required uniqueness`);
983
+ }
984
+ const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '${table}'`)[0]?.sql;
985
+ const checks = typeof definition === "string" ? extractCheckExpressions(definition).map(normalizeSql) : [];
986
+ const missingCheck = checkConstraints[table].find((constraint) => !checks.includes(normalizeSql(constraint.fragment)));
987
+ if (missingCheck) {
988
+ throw new Error(`Invalid project room schema: table '${table}' is missing required CHECK constraint '${missingCheck.name}'`);
989
+ }
990
+ }
991
+ for (const [name, expected] of Object.entries(indexes)) {
992
+ const listed = rows(db, `PRAGMA index_list(${expected.table})`).find((index) => index.name === name);
993
+ const columns5 = rows(db, `PRAGMA index_info(${name})`).map((column) => column.name);
994
+ const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'index' AND name = '${name}'`)[0]?.sql;
995
+ const normalized = typeof definition === "string" ? definition.toLowerCase().replace(/\s+/g, " ") : "";
996
+ if (!listed || listed.unique !== Number(expected.unique) || listed.partial !== Number(expected.partial) || columns5.join(",") !== expected.columns.join(",") || expected.sql?.some((fragment) => !normalized.includes(fragment))) {
997
+ throw new Error(`Invalid project room schema: index '${name}' is incompatible`);
998
+ }
999
+ }
1000
+ }
1001
+ function ensureProjectRoomSchema(db) {
1002
+ const marked = db;
1003
+ if (marked[schemaMarker]) return;
1004
+ const raw = db.getRawDb();
1005
+ const hasMigrationTable = rows(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'lattice_schema_migrations'").length > 0;
1006
+ const applied = hasMigrationTable ? rows(db, `SELECT name, version FROM lattice_schema_migrations WHERE store_name = 'project_room' AND name = '${migrationName}'`)[0] : void 0;
1007
+ if (applied) {
1008
+ if (applied.version !== 1) throw new Error(`Invalid project room schema migration version: ${String(applied.version)}`);
1009
+ validateProjectRoomSchema(db);
1010
+ marked[schemaMarker] = true;
1011
+ return;
1012
+ }
1013
+ raw.exec("BEGIN IMMEDIATE");
1014
+ try {
1015
+ raw.exec(migrationTableDdl);
1016
+ raw.exec(ddl);
1017
+ validateProjectRoomSchema(db);
1018
+ raw.run("INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at) VALUES (?, ?, ?, ?)", ["project_room", migrationName, 1, (/* @__PURE__ */ new Date()).toISOString()]);
1019
+ raw.exec("COMMIT");
1020
+ } catch (error) {
1021
+ try {
1022
+ raw.exec("ROLLBACK");
1023
+ } catch {
1024
+ }
1025
+ throw error;
1026
+ }
1027
+ db.save();
1028
+ marked[schemaMarker] = true;
1029
+ }
1030
+ function transaction(db, work) {
1031
+ if (activeTransactions.has(db)) throw new Error("Nested local store transactions are not supported");
1032
+ activeTransactions.add(db);
1033
+ let began = false;
1034
+ try {
1035
+ db.exec("BEGIN IMMEDIATE");
1036
+ began = true;
1037
+ const result = work();
1038
+ db.exec("COMMIT");
1039
+ db.save();
1040
+ return result;
1041
+ } catch (error) {
1042
+ if (began) {
1043
+ try {
1044
+ db.exec("ROLLBACK");
1045
+ } catch {
1046
+ }
1047
+ db.save();
1048
+ }
1049
+ throw error;
1050
+ } finally {
1051
+ activeTransactions.delete(db);
1052
+ }
1053
+ }
1054
+ function write(db, sql, params) {
1055
+ const raw = db.getRawDb();
1056
+ raw.run(sql, params);
1057
+ return raw.getRowsModified();
1058
+ }
1059
+ function timestamp(previous) {
1060
+ const previousTime = previous === void 0 ? Number.NaN : Date.parse(previous);
1061
+ const now = Date.now();
1062
+ return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
1063
+ }
1064
+ function validDate(value) {
1065
+ return value instanceof Date && !Number.isNaN(value.getTime());
1066
+ }
1067
+ function isUniqueError(error) {
1068
+ return error instanceof Error && error.message.includes("UNIQUE constraint failed");
1069
+ }
1070
+
1071
+ // src/stores/LocalProjectRoomStore.ts
1072
+ var columns = "id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at";
1073
+ function mapRow(row) {
1074
+ const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
1075
+ const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
1076
+ if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || row.type !== "main" || typeof row.name !== "string" || !validDate(createdAt) || !validDate(updatedAt)) throw new Error("Invalid project room row");
1077
+ return {
1078
+ id: row.id,
1079
+ tenantId: row.tenant_id,
1080
+ workspaceId: row.workspace_id,
1081
+ projectId: row.project_id,
1082
+ type: "main",
1083
+ name: row.name,
1084
+ createdAt,
1085
+ updatedAt
1086
+ };
1087
+ }
1088
+ var LocalProjectRoomStore = class {
1089
+ constructor(db) {
1090
+ this.db = db;
1091
+ ensureProjectRoomSchema(db);
1092
+ }
1093
+ async ensureMainRoom(input) {
1094
+ return transaction(this.db, () => {
1095
+ const existing = this.getRow(input.tenantId, input.projectId);
1096
+ if (existing) return mapRow(existing);
1097
+ const now = timestamp();
1098
+ write(this.db, "INSERT INTO lt_project_rooms (id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at) VALUES (?, ?, ?, ?, 'main', ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.name, now, now]);
1099
+ return mapRow(this.getRow(input.tenantId, input.projectId));
1100
+ });
1101
+ }
1102
+ async getMainRoom(tenantId, projectId) {
1103
+ const row = this.getRow(tenantId, projectId);
1104
+ return row ? mapRow(row) : null;
1105
+ }
1106
+ getRow(tenantId, projectId) {
1107
+ return this.db.prepare(`SELECT ${columns} FROM lt_project_rooms WHERE tenant_id = ? AND project_id = ? AND type = 'main'`).get(tenantId, projectId);
1108
+ }
1109
+ };
1110
+
1111
+ // src/stores/LocalProjectMembershipStore.ts
1112
+ var columns2 = "id, tenant_id, project_id, user_id, role, status, joined_at, updated_at";
1113
+ var DuplicateProjectMembershipError = class extends Error {
1114
+ constructor() {
1115
+ super("Project membership already exists for tenant, project, and user");
1116
+ this.name = "DuplicateProjectMembershipError";
1117
+ }
1118
+ };
1119
+ var ProjectMembershipIdConflictError = class extends Error {
1120
+ constructor() {
1121
+ super("Project membership ID already exists for tenant");
1122
+ this.name = "ProjectMembershipIdConflictError";
1123
+ }
1124
+ };
1125
+ function role(value) {
1126
+ return value === "owner" || value === "admin" || value === "member" || value === "viewer";
1127
+ }
1128
+ function mapRow2(row) {
1129
+ const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
1130
+ const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
1131
+ if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.project_id !== "string" || typeof row.user_id !== "string" || !role(row.role) || row.status !== "active" && row.status !== "removed" || !validDate(joinedAt) || !validDate(updatedAt)) throw new Error("Invalid project membership row");
1132
+ return {
1133
+ id: row.id,
1134
+ tenantId: row.tenant_id,
1135
+ projectId: row.project_id,
1136
+ userId: row.user_id,
1137
+ role: row.role,
1138
+ status: row.status,
1139
+ joinedAt,
1140
+ updatedAt
1141
+ };
1142
+ }
1143
+ var LocalProjectMembershipStore = class {
1144
+ constructor(db) {
1145
+ this.db = db;
1146
+ ensureProjectRoomSchema(db);
1147
+ }
1148
+ async list(tenantId, projectId) {
1149
+ return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? ORDER BY joined_at ASC, id ASC`).all(tenantId, projectId).map(mapRow2);
1150
+ }
1151
+ async findByUser(tenantId, projectId, userId) {
1152
+ const row = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?`).get(tenantId, projectId, userId);
1153
+ return row ? mapRow2(row) : null;
1154
+ }
1155
+ async create(input) {
1156
+ return transaction(this.db, () => {
1157
+ if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
1158
+ const now = timestamp();
1159
+ try {
1160
+ write(this.db, "INSERT INTO lt_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.projectId, input.userId, input.role, input.status, now, now]);
1161
+ } catch (error) {
1162
+ if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
1163
+ throw error;
1164
+ }
1165
+ return mapRow2(this.byId(input.tenantId, input.id));
1166
+ });
1167
+ }
1168
+ async createInitialOwner(input) {
1169
+ return transaction(this.db, () => {
1170
+ const rows2 = this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ?`).all(input.tenantId, input.projectId);
1171
+ if (rows2.length > 0) {
1172
+ const existing = rows2.map(mapRow2).find((item) => item.userId === input.userId && item.role === "owner" && item.status === "active");
1173
+ return existing ? { kind: "existing", membership: existing } : { kind: "already_initialized" };
1174
+ }
1175
+ if (this.byId(input.tenantId, input.id)) throw new ProjectMembershipIdConflictError();
1176
+ const now = timestamp();
1177
+ try {
1178
+ write(this.db, "INSERT INTO lt_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES (?, ?, ?, ?, 'owner', 'active', ?, ?)", [input.id, input.tenantId, input.projectId, input.userId, now, now]);
1179
+ } catch (error) {
1180
+ if (isUniqueError(error)) throw this.classifyUnique(input.tenantId, input.id, input.projectId, input.userId);
1181
+ throw error;
1182
+ }
1183
+ return { kind: "created", membership: mapRow2(this.byId(input.tenantId, input.id)) };
1184
+ });
1185
+ }
1186
+ async updateRole(input) {
1187
+ return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, input.role, false);
1188
+ }
1189
+ async remove(input) {
1190
+ return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, void 0, true);
1191
+ }
1192
+ mutate(tenantId, id, expected, nextRole, remove) {
1193
+ return transaction(this.db, () => {
1194
+ const row = this.byId(tenantId, id);
1195
+ if (!row) return { kind: "not_found" };
1196
+ const current = mapRow2(row);
1197
+ if (!validDate(expected) || current.updatedAt.getTime() !== expected.getTime()) return { kind: "conflict" };
1198
+ if (current.role === "owner" && current.status === "active" && (remove || nextRole !== "owner")) {
1199
+ const other = this.db.prepare("SELECT 1 FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND id <> ? AND role = 'owner' AND status = 'active'").get(tenantId, current.projectId, id);
1200
+ if (!other) return { kind: "last_owner" };
1201
+ }
1202
+ const updatedAt = timestamp(row.updated_at);
1203
+ const changes = remove ? write(this.db, "UPDATE lt_project_memberships SET status = 'removed', updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [updatedAt, tenantId, id, row.updated_at]) : write(this.db, "UPDATE lt_project_memberships SET role = ?, updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [nextRole, updatedAt, tenantId, id, row.updated_at]);
1204
+ if (changes === 0) return { kind: "conflict" };
1205
+ return { kind: remove ? "removed" : "updated", membership: mapRow2(this.byId(tenantId, id)) };
1206
+ });
1207
+ }
1208
+ byId(tenantId, id) {
1209
+ return this.db.prepare(`SELECT ${columns2} FROM lt_project_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
1210
+ }
1211
+ classifyUnique(tenantId, id, projectId, userId) {
1212
+ if (this.byId(tenantId, id)) return new ProjectMembershipIdConflictError();
1213
+ if (this.db.prepare("SELECT 1 FROM lt_project_memberships WHERE tenant_id = ? AND project_id = ? AND user_id = ?").get(tenantId, projectId, userId)) return new DuplicateProjectMembershipError();
1214
+ return new Error("Unclassified project membership uniqueness conflict");
1215
+ }
1216
+ };
1217
+
1218
+ // src/stores/LocalProjectBotMembershipStore.ts
1219
+ var columns3 = "id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at";
1220
+ var ProjectBotMembershipIdConflictError = class extends Error {
1221
+ constructor(tenantId, id) {
1222
+ super(`Project bot membership ID '${id}' already exists in tenant '${tenantId}'`);
1223
+ this.name = "ProjectBotMembershipIdConflictError";
1224
+ }
1225
+ };
1226
+ function isRole(value) {
1227
+ return value === "coordinator" || value === "specialist";
1228
+ }
1229
+ function isStatus(value) {
1230
+ return value === "active" || value === "paused" || value === "removed";
1231
+ }
1232
+ function mapRow3(row) {
1233
+ const joinedAt = typeof row.joined_at === "string" ? new Date(row.joined_at) : void 0;
1234
+ const updatedAt = typeof row.updated_at === "string" ? new Date(row.updated_at) : void 0;
1235
+ if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || typeof row.assistant_id !== "string" || !isRole(row.role) || typeof row.title !== "string" || row.responsibility !== null && typeof row.responsibility !== "string" || typeof row.mention_name !== "string" || !isStatus(row.status) || typeof row.room_thread_id !== "string" || !validDate(joinedAt) || !validDate(updatedAt)) throw new Error("Invalid project bot membership row");
1236
+ return {
1237
+ id: row.id,
1238
+ tenantId: row.tenant_id,
1239
+ workspaceId: row.workspace_id,
1240
+ projectId: row.project_id,
1241
+ roomId: row.room_id,
1242
+ assistantId: row.assistant_id,
1243
+ role: row.role,
1244
+ title: row.title,
1245
+ ...row.responsibility === null ? {} : { responsibility: row.responsibility },
1246
+ mentionName: row.mention_name,
1247
+ status: row.status,
1248
+ roomThreadId: row.room_thread_id,
1249
+ joinedAt,
1250
+ updatedAt
1251
+ };
1252
+ }
1253
+ var LocalProjectBotMembershipStore = class {
1254
+ constructor(db) {
1255
+ this.db = db;
1256
+ ensureProjectRoomSchema(db);
1257
+ }
1258
+ async list(tenantId, projectId) {
1259
+ return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? ORDER BY joined_at ASC, id ASC`).all(tenantId, projectId).map(mapRow3);
1260
+ }
1261
+ async findById(tenantId, id) {
1262
+ const row = this.byId(tenantId, id);
1263
+ return row ? mapRow3(row) : null;
1264
+ }
1265
+ async findByAssistant(tenantId, projectId, assistantId) {
1266
+ const row = this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
1267
+ return row ? mapRow3(row) : null;
1268
+ }
1269
+ async save(input) {
1270
+ try {
1271
+ return transaction(this.db, () => {
1272
+ const existing = this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(input.tenantId, input.projectId, input.assistantId);
1273
+ if (existing) return this.reactivate(existing, input);
1274
+ if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
1275
+ const conflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
1276
+ if (conflict) return { kind: conflict };
1277
+ const now = timestamp();
1278
+ try {
1279
+ write(this.db, "INSERT INTO lt_project_bot_memberships (id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.roomId, input.assistantId, input.role, input.title, input.responsibility ?? null, input.mentionName, input.status, input.roomThreadId, now, now]);
1280
+ } catch (error) {
1281
+ if (!isUniqueError(error)) throw error;
1282
+ if (this.byId(input.tenantId, input.id)) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
1283
+ const raced = this.byAssistant(input.tenantId, input.projectId, input.assistantId);
1284
+ if (raced) return this.reactivate(raced, input);
1285
+ const racedConflict = this.preflight(input.tenantId, input.projectId, input.roomId, input.role, input.mentionName, input.status, input.id);
1286
+ if (racedConflict) return { kind: racedConflict };
1287
+ throw new Error("Unclassified project bot membership uniqueness conflict");
1288
+ }
1289
+ return { kind: "created", membership: mapRow3(this.byId(input.tenantId, input.id)) };
1290
+ });
1291
+ } catch (error) {
1292
+ const conflict = this.conflict(error);
1293
+ if (conflict) return { kind: conflict };
1294
+ throw error;
1295
+ }
1296
+ }
1297
+ async update(input) {
1298
+ try {
1299
+ return transaction(this.db, () => {
1300
+ const row = this.byId(input.tenantId, input.id);
1301
+ if (!row) return { kind: "not_found" };
1302
+ const existing = mapRow3(row);
1303
+ if (!validDate(input.expectedUpdatedAt) || existing.updatedAt.getTime() !== input.expectedUpdatedAt.getTime()) return { kind: "conflict" };
1304
+ const candidate = { ...existing, ...input.patch };
1305
+ const conflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
1306
+ if (conflict) return { kind: conflict };
1307
+ let changes;
1308
+ try {
1309
+ changes = write(this.db, "UPDATE lt_project_bot_memberships SET role = ?, title = ?, responsibility = ?, mention_name = ?, status = ?, updated_at = ? WHERE tenant_id = ? AND id = ? AND updated_at = ?", [candidate.role, candidate.title, candidate.responsibility ?? null, candidate.mentionName, candidate.status, timestamp(row.updated_at), input.tenantId, input.id, row.updated_at]);
1310
+ } catch (error) {
1311
+ if (!isUniqueError(error)) throw error;
1312
+ const racedConflict = this.preflight(candidate.tenantId, candidate.projectId, candidate.roomId, candidate.role, candidate.mentionName, candidate.status, candidate.id);
1313
+ if (racedConflict) return { kind: racedConflict };
1314
+ throw new Error("Unclassified project bot membership uniqueness conflict");
1315
+ }
1316
+ if (changes === 0) return { kind: "conflict" };
1317
+ return { kind: "updated", membership: mapRow3(this.byId(input.tenantId, input.id)) };
1318
+ });
1319
+ } catch (error) {
1320
+ const conflict = this.conflict(error);
1321
+ if (conflict) return { kind: conflict };
1322
+ throw error;
1323
+ }
1324
+ }
1325
+ byId(tenantId, id) {
1326
+ return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
1327
+ }
1328
+ byAssistant(tenantId, projectId, assistantId) {
1329
+ return this.db.prepare(`SELECT ${columns3} FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND assistant_id = ?`).get(tenantId, projectId, assistantId);
1330
+ }
1331
+ reactivate(existing, input) {
1332
+ const persisted = mapRow3(existing);
1333
+ const wasRemoved = persisted.status === "removed";
1334
+ const conflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
1335
+ if (conflict) return { kind: conflict };
1336
+ try {
1337
+ write(this.db, "UPDATE lt_project_bot_memberships SET role = ?, title = ?, responsibility = ?, mention_name = ?, status = 'active', updated_at = ? WHERE tenant_id = ? AND id = ?", [input.role, input.title, input.responsibility ?? null, input.mentionName, timestamp(existing.updated_at), input.tenantId, persisted.id]);
1338
+ } catch (error) {
1339
+ if (!isUniqueError(error)) throw error;
1340
+ const racedConflict = this.preflight(input.tenantId, input.projectId, persisted.roomId, input.role, input.mentionName, "active", persisted.id);
1341
+ if (racedConflict) return { kind: racedConflict };
1342
+ throw new Error("Unclassified project bot membership uniqueness conflict");
1343
+ }
1344
+ return { kind: wasRemoved ? "reactivated" : "updated", membership: mapRow3(this.byId(input.tenantId, persisted.id)) };
1345
+ }
1346
+ preflight(tenantId, projectId, roomId, role2, mentionName, status, excludeId) {
1347
+ if (status === "removed") return void 0;
1348
+ if (role2 === "coordinator" && this.db.prepare("SELECT 1 FROM lt_project_bot_memberships WHERE tenant_id = ? AND project_id = ? AND id <> ? AND role = 'coordinator' AND status IN ('active', 'paused')").get(tenantId, projectId, excludeId)) return "coordinator_conflict";
1349
+ if (this.db.prepare("SELECT 1 FROM lt_project_bot_memberships WHERE tenant_id = ? AND room_id = ? AND mention_name = ? AND id <> ? AND status IN ('active', 'paused')").get(tenantId, roomId, mentionName, excludeId)) return "mention_conflict";
1350
+ return void 0;
1351
+ }
1352
+ conflict(error) {
1353
+ if (!isUniqueError(error) || !(error instanceof Error)) return void 0;
1354
+ if (error.message.includes("tenant_id, lt_project_bot_memberships.project_id")) return "coordinator_conflict";
1355
+ if (error.message.includes("room_id") && error.message.includes("mention_name")) return "mention_conflict";
1356
+ return void 0;
1357
+ }
1358
+ };
1359
+
1360
+ // src/stores/LocalProjectRoomMessageStore.ts
1361
+ var columns4 = "id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at";
1362
+ var ProjectRoomMessageIdConflictError = class extends Error {
1363
+ constructor(tenantId, id) {
1364
+ super(`Project room message ID '${id}' already exists in tenant '${tenantId}'`);
1365
+ this.name = "ProjectRoomMessageIdConflictError";
1366
+ }
1367
+ };
1368
+ var DuplicateProjectRoomMessageIdempotencyKeyError = class extends Error {
1369
+ constructor(tenantId, roomId, key) {
1370
+ super(`Project room message idempotency key '${key}' already exists in tenant '${tenantId}' room '${roomId}'`);
1371
+ this.name = "DuplicateProjectRoomMessageIdempotencyKeyError";
1372
+ }
1373
+ };
1374
+ function record(value) {
1375
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1376
+ }
1377
+ function only(value, keys) {
1378
+ return Object.keys(value).every((key) => keys.includes(key));
1379
+ }
1380
+ function parseJson(value) {
1381
+ if (typeof value !== "string") return void 0;
1382
+ try {
1383
+ return JSON.parse(value);
1384
+ } catch {
1385
+ return void 0;
1386
+ }
1387
+ }
1388
+ function author(value) {
1389
+ if (!record(value)) return void 0;
1390
+ if (value.type === "human" && only(value, ["type", "userId"]) && typeof value.userId === "string") return { type: "human", userId: value.userId };
1391
+ if (value.type === "bot" && only(value, ["type", "membershipId", "assistantId"]) && typeof value.membershipId === "string" && typeof value.assistantId === "string") return { type: "bot", membershipId: value.membershipId, assistantId: value.assistantId };
1392
+ if (value.type === "system" && only(value, ["type"])) return { type: "system" };
1393
+ return void 0;
1394
+ }
1395
+ function mention(value) {
1396
+ if (!record(value)) return void 0;
1397
+ if (value.type === "bot" && only(value, ["type", "membershipId"]) && typeof value.membershipId === "string") return { type: "bot", membershipId: value.membershipId };
1398
+ if (value.type === "team" && only(value, ["type"])) return { type: "team" };
1399
+ return void 0;
1400
+ }
1401
+ function source(value) {
1402
+ return value === "user" || value === "agent" || value === "task" || value === "routine" || value === "system";
1403
+ }
1404
+ function mapRow4(row) {
1405
+ const mappedAuthor = author(parseJson(row.author));
1406
+ const content = parseJson(row.content);
1407
+ const rawMentions = parseJson(row.mentions);
1408
+ const mappedMentions = Array.isArray(rawMentions) ? rawMentions.map(mention) : void 0;
1409
+ const createdAt = typeof row.created_at === "string" ? new Date(row.created_at) : void 0;
1410
+ if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || !mappedAuthor || !record(content) || content.type !== "text" || typeof content.text !== "string" || !only(content, ["type", "text"]) || !mappedMentions || mappedMentions.some((item) => item === void 0) || row.reply_to_message_id !== null && typeof row.reply_to_message_id !== "string" || !source(row.source) || row.source_id !== null && typeof row.source_id !== "string" || row.idempotency_key !== null && typeof row.idempotency_key !== "string" || !validDate(createdAt)) throw new Error("Invalid project room message row");
1411
+ return {
1412
+ id: row.id,
1413
+ tenantId: row.tenant_id,
1414
+ workspaceId: row.workspace_id,
1415
+ projectId: row.project_id,
1416
+ roomId: row.room_id,
1417
+ author: mappedAuthor,
1418
+ content: { type: "text", text: content.text },
1419
+ mentions: mappedMentions,
1420
+ ...row.reply_to_message_id === null ? {} : { replyToMessageId: row.reply_to_message_id },
1421
+ source: row.source,
1422
+ ...row.source_id === null ? {} : { sourceId: row.source_id },
1423
+ ...row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key },
1424
+ createdAt
1425
+ };
1426
+ }
1427
+ var LocalProjectRoomMessageStore = class {
1428
+ constructor(db) {
1429
+ this.db = db;
1430
+ ensureProjectRoomSchema(db);
1431
+ }
1432
+ async create(input) {
1433
+ return this.insert(input, false);
1434
+ }
1435
+ async createIdempotent(input) {
1436
+ return this.insert(input, true);
1437
+ }
1438
+ async list(input) {
1439
+ if (input.before && !validDate(input.before.createdAt)) throw new RangeError("Project room message cursor date is invalid");
1440
+ const limit = Math.min(100, Math.max(1, Math.trunc(Number.isFinite(input.limit) ? input.limit : 1)));
1441
+ const rows2 = input.before ? this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND (created_at < ? OR (created_at = ? AND id < ?)) ORDER BY created_at DESC, id DESC LIMIT ?`).all(input.tenantId, input.roomId, input.before.createdAt.toISOString(), input.before.createdAt.toISOString(), input.before.id, limit) : this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? ORDER BY created_at DESC, id DESC LIMIT ?`).all(input.tenantId, input.roomId, limit);
1442
+ return rows2.map(mapRow4);
1443
+ }
1444
+ async findById(tenantId, id) {
1445
+ const row = this.byId(tenantId, id);
1446
+ return row ? mapRow4(row) : null;
1447
+ }
1448
+ insert(input, idempotent) {
1449
+ return transaction(this.db, () => {
1450
+ if (idempotent && input.idempotencyKey) {
1451
+ const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
1452
+ if (canonical) return mapRow4(canonical);
1453
+ }
1454
+ if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
1455
+ try {
1456
+ write(this.db, "INSERT INTO lt_project_room_messages (id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [input.id, input.tenantId, input.workspaceId, input.projectId, input.roomId, JSON.stringify(input.author), JSON.stringify(input.content), JSON.stringify(input.mentions), input.replyToMessageId ?? null, input.source, input.sourceId ?? null, input.idempotencyKey ?? null, timestamp()]);
1457
+ } catch (error) {
1458
+ if (!isUniqueError(error)) throw error;
1459
+ if (this.byId(input.tenantId, input.id)) throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
1460
+ if (input.idempotencyKey) {
1461
+ const canonical = this.byKey(input.tenantId, input.roomId, input.idempotencyKey);
1462
+ if (canonical && idempotent) return mapRow4(canonical);
1463
+ if (canonical) throw new DuplicateProjectRoomMessageIdempotencyKeyError(input.tenantId, input.roomId, input.idempotencyKey);
1464
+ }
1465
+ throw new Error("Unclassified project room message uniqueness conflict");
1466
+ }
1467
+ return mapRow4(this.byId(input.tenantId, input.id));
1468
+ });
1469
+ }
1470
+ byId(tenantId, id) {
1471
+ return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND id = ?`).get(tenantId, id);
1472
+ }
1473
+ byKey(tenantId, roomId, key) {
1474
+ return this.db.prepare(`SELECT ${columns4} FROM lt_project_room_messages WHERE tenant_id = ? AND room_id = ? AND idempotency_key = ?`).get(tenantId, roomId, key);
1475
+ }
1476
+ };
1477
+
832
1478
  // src/stores/LocalUserStore.ts
833
1479
  var DDL5 = `
834
1480
  CREATE TABLE IF NOT EXISTS lt_users (
@@ -848,10 +1494,10 @@ var LocalUserStore = class {
848
1494
  ensureTable(db, DDL5);
849
1495
  }
850
1496
  async getAllUsers() {
851
- const rows = this.db.prepare(
1497
+ const rows2 = this.db.prepare(
852
1498
  `SELECT * FROM lt_users ORDER BY created_at DESC`
853
1499
  ).all();
854
- return rows.map(mapRowToUser);
1500
+ return rows2.map(mapRowToUser);
855
1501
  }
856
1502
  async getUserById(id) {
857
1503
  const row = this.db.prepare(
@@ -955,10 +1601,10 @@ var LocalTenantStore = class {
955
1601
  ensureTable(db, DDL6);
956
1602
  }
957
1603
  async getAllTenants() {
958
- const rows = this.db.prepare(
1604
+ const rows2 = this.db.prepare(
959
1605
  `SELECT * FROM lt_tenants ORDER BY created_at DESC`
960
1606
  ).all();
961
- return rows.map(mapRowToTenant);
1607
+ return rows2.map(mapRowToTenant);
962
1608
  }
963
1609
  async getTenantById(id) {
964
1610
  const row = this.db.prepare(
@@ -1057,16 +1703,16 @@ var LocalUserTenantLinkStore = class {
1057
1703
  ensureTable(db, DDL7);
1058
1704
  }
1059
1705
  async getTenantsByUser(userId) {
1060
- const rows = this.db.prepare(
1706
+ const rows2 = this.db.prepare(
1061
1707
  `SELECT * FROM lt_user_tenant_links WHERE user_id = ? ORDER BY joined_at DESC`
1062
1708
  ).all(userId);
1063
- return rows.map(mapRowToLink);
1709
+ return rows2.map(mapRowToLink);
1064
1710
  }
1065
1711
  async getUsersByTenant(tenantId) {
1066
- const rows = this.db.prepare(
1712
+ const rows2 = this.db.prepare(
1067
1713
  `SELECT * FROM lt_user_tenant_links WHERE tenant_id = ? ORDER BY joined_at DESC`
1068
1714
  ).all(tenantId);
1069
- return rows.map(mapRowToLink);
1715
+ return rows2.map(mapRowToLink);
1070
1716
  }
1071
1717
  async getLink(userId, tenantId) {
1072
1718
  const row = this.db.prepare(
@@ -1076,7 +1722,7 @@ var LocalUserTenantLinkStore = class {
1076
1722
  }
1077
1723
  async createLink(data) {
1078
1724
  const now = nowISO();
1079
- const role = data.role || "member";
1725
+ const role2 = data.role || "member";
1080
1726
  const metadata = JSON.stringify(data.metadata || {});
1081
1727
  this.db.prepare(
1082
1728
  `INSERT INTO lt_user_tenant_links (user_id, tenant_id, role, joined_at, metadata)
@@ -1084,11 +1730,11 @@ var LocalUserTenantLinkStore = class {
1084
1730
  ON CONFLICT(user_id, tenant_id) DO UPDATE SET
1085
1731
  role = excluded.role,
1086
1732
  metadata = excluded.metadata`
1087
- ).run(data.userId, data.tenantId, role, now, metadata);
1733
+ ).run(data.userId, data.tenantId, role2, now, metadata);
1088
1734
  return {
1089
1735
  userId: data.userId,
1090
1736
  tenantId: data.tenantId,
1091
- role,
1737
+ role: role2,
1092
1738
  joinedAt: parseISO(now),
1093
1739
  metadata: data.metadata
1094
1740
  };
@@ -1158,16 +1804,16 @@ var LocalDatabaseConfigStore = class {
1158
1804
  ensureTable(db, DDL8);
1159
1805
  }
1160
1806
  async getAllConfigs(tenantId) {
1161
- const rows = this.db.prepare(
1807
+ const rows2 = this.db.prepare(
1162
1808
  `SELECT * FROM lt_database_configs WHERE tenant_id = ? ORDER BY created_at DESC`
1163
1809
  ).all(tenantId);
1164
- return rows.map((r) => mapRowToEntry(r));
1810
+ return rows2.map((r) => mapRowToEntry(r));
1165
1811
  }
1166
1812
  async getAllConfigsWithoutTenant() {
1167
- const rows = this.db.prepare(
1813
+ const rows2 = this.db.prepare(
1168
1814
  `SELECT * FROM lt_database_configs ORDER BY created_at DESC`
1169
1815
  ).all();
1170
- return rows.map((r) => mapRowToEntry(r));
1816
+ return rows2.map((r) => mapRowToEntry(r));
1171
1817
  }
1172
1818
  async getConfigById(tenantId, id) {
1173
1819
  const row = this.db.prepare(
@@ -1299,16 +1945,16 @@ var LocalConnectionStore = class {
1299
1945
  ensureTable(db, DDL9);
1300
1946
  }
1301
1947
  async listByTenant(tenantId) {
1302
- const rows = this.db.prepare(
1948
+ const rows2 = this.db.prepare(
1303
1949
  `SELECT * FROM connection_configs WHERE tenant_id = ? ORDER BY created_at`
1304
1950
  ).all(tenantId);
1305
- return rows.map((r) => this.mapRow(r));
1951
+ return rows2.map((r) => this.mapRow(r));
1306
1952
  }
1307
1953
  async listByType(tenantId, type) {
1308
- const rows = this.db.prepare(
1954
+ const rows2 = this.db.prepare(
1309
1955
  `SELECT * FROM connection_configs WHERE tenant_id = ? AND type = ? ORDER BY created_at`
1310
1956
  ).all(tenantId, type);
1311
- return rows.map((r) => this.mapRow(r));
1957
+ return rows2.map((r) => this.mapRow(r));
1312
1958
  }
1313
1959
  async getByKey(tenantId, type, key) {
1314
1960
  const row = this.db.prepare(
@@ -1394,16 +2040,16 @@ var LocalMetricsServerConfigStore = class {
1394
2040
  ensureTable(db, DDL10);
1395
2041
  }
1396
2042
  async getAllConfigs(tenantId) {
1397
- const rows = this.db.prepare(
2043
+ const rows2 = this.db.prepare(
1398
2044
  `SELECT * FROM lt_metrics_configs WHERE tenant_id = ? ORDER BY created_at DESC`
1399
2045
  ).all(tenantId);
1400
- return rows.map(mapRowToEntry2);
2046
+ return rows2.map(mapRowToEntry2);
1401
2047
  }
1402
2048
  async getAllConfigsWithoutTenant() {
1403
- const rows = this.db.prepare(
2049
+ const rows2 = this.db.prepare(
1404
2050
  `SELECT * FROM lt_metrics_configs ORDER BY created_at DESC`
1405
2051
  ).all();
1406
- return rows.map(mapRowToEntry2);
2052
+ return rows2.map(mapRowToEntry2);
1407
2053
  }
1408
2054
  async getConfigById(tenantId, id) {
1409
2055
  const row = this.db.prepare(
@@ -1522,16 +2168,16 @@ var LocalMcpServerConfigStore = class {
1522
2168
  ensureTable(db, DDL11);
1523
2169
  }
1524
2170
  async getAllConfigs(tenantId) {
1525
- const rows = this.db.prepare(
2171
+ const rows2 = this.db.prepare(
1526
2172
  `SELECT * FROM lt_mcp_configs WHERE tenant_id = ? ORDER BY created_at DESC`
1527
2173
  ).all(tenantId);
1528
- return rows.map(mapRowToEntry3);
2174
+ return rows2.map(mapRowToEntry3);
1529
2175
  }
1530
2176
  async getAllConfigsWithoutTenant() {
1531
- const rows = this.db.prepare(
2177
+ const rows2 = this.db.prepare(
1532
2178
  `SELECT * FROM lt_mcp_configs ORDER BY created_at DESC`
1533
2179
  ).all();
1534
- return rows.map(mapRowToEntry3);
2180
+ return rows2.map(mapRowToEntry3);
1535
2181
  }
1536
2182
  async getConfigById(tenantId, id) {
1537
2183
  const row = this.db.prepare(
@@ -1813,22 +2459,22 @@ var LocalWorkflowTrackingStore = class {
1813
2459
  this.db.prepare(`DELETE FROM lt_workflow_runs WHERE id = ?`).run(runId);
1814
2460
  }
1815
2461
  async getWorkflowRunsByThreadId(tenantId, threadId) {
1816
- const rows = this.db.prepare(
2462
+ const rows2 = this.db.prepare(
1817
2463
  `SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND thread_id = ? ORDER BY created_at DESC`
1818
2464
  ).all(tenantId, threadId);
1819
- return rows.map(mapRowToRun);
2465
+ return rows2.map(mapRowToRun);
1820
2466
  }
1821
2467
  async getWorkflowRunsByAssistantId(tenantId, assistantId) {
1822
- const rows = this.db.prepare(
2468
+ const rows2 = this.db.prepare(
1823
2469
  `SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`
1824
2470
  ).all(tenantId, assistantId);
1825
- return rows.map(mapRowToRun);
2471
+ return rows2.map(mapRowToRun);
1826
2472
  }
1827
2473
  async getWorkflowRunsByTenantId(tenantId) {
1828
- const rows = this.db.prepare(
2474
+ const rows2 = this.db.prepare(
1829
2475
  `SELECT * FROM lt_workflow_runs WHERE tenant_id = ? ORDER BY created_at DESC`
1830
2476
  ).all(tenantId);
1831
- return rows.map(mapRowToRun);
2477
+ return rows2.map(mapRowToRun);
1832
2478
  }
1833
2479
  async queryWorkflowRuns(tenantId, options = {}) {
1834
2480
  const conditions = ["tenant_id = ?"];
@@ -1857,8 +2503,8 @@ var LocalWorkflowTrackingStore = class {
1857
2503
  sql += " OFFSET ?";
1858
2504
  selectParams.push(options.offset);
1859
2505
  }
1860
- const rows = this.db.prepare(sql).all(...selectParams);
1861
- return { records: rows.map(mapRowToRun), total: countRow.count };
2506
+ const rows2 = this.db.prepare(sql).all(...selectParams);
2507
+ return { records: rows2.map(mapRowToRun), total: countRow.count };
1862
2508
  }
1863
2509
  async createRunStep(request) {
1864
2510
  const now = nowISO();
@@ -1950,22 +2596,22 @@ var LocalWorkflowTrackingStore = class {
1950
2596
  return this.getStepById(runId, stepId);
1951
2597
  }
1952
2598
  async getRunSteps(runId) {
1953
- const rows = this.db.prepare(
2599
+ const rows2 = this.db.prepare(
1954
2600
  `SELECT * FROM lt_workflow_steps WHERE run_id = ? ORDER BY created_at ASC`
1955
2601
  ).all(runId);
1956
- return rows.map(mapRowToStep);
2602
+ return rows2.map(mapRowToStep);
1957
2603
  }
1958
2604
  async getRunStepsByType(runId, stepType) {
1959
- const rows = this.db.prepare(
2605
+ const rows2 = this.db.prepare(
1960
2606
  `SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? ORDER BY created_at ASC`
1961
2607
  ).all(runId, stepType);
1962
- return rows.map(mapRowToStep);
2608
+ return rows2.map(mapRowToStep);
1963
2609
  }
1964
2610
  async getInterruptedSteps(runId) {
1965
- const rows = this.db.prepare(
2611
+ const rows2 = this.db.prepare(
1966
2612
  `SELECT * FROM lt_workflow_steps WHERE run_id = ? AND status = 'interrupted' ORDER BY created_at ASC`
1967
2613
  ).all(runId);
1968
- return rows.map(mapRowToStep);
2614
+ return rows2.map(mapRowToStep);
1969
2615
  }
1970
2616
  getStepById(runId, id) {
1971
2617
  const row = this.db.prepare(
@@ -2110,7 +2756,7 @@ CREATE TABLE IF NOT EXISTS lt_eval_run_results (
2110
2756
  PRIMARY KEY (run_id, id)
2111
2757
  );
2112
2758
  `;
2113
- function parseJson(val, fallback) {
2759
+ function parseJson2(val, fallback) {
2114
2760
  if (val == null) return fallback;
2115
2761
  if (typeof val === "string") {
2116
2762
  try {
@@ -2202,20 +2848,20 @@ var LocalEvalStore = class {
2202
2848
  `);
2203
2849
  }
2204
2850
  /** Add a column if it does not exist (SQLite version compatible). */
2205
- ensureColumn(table, column, ddl) {
2851
+ ensureColumn(table, column, ddl2) {
2206
2852
  const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
2207
2853
  if (!cols.some((c) => c.name === column)) {
2208
- this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
2854
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
2209
2855
  }
2210
2856
  }
2211
2857
  // -------------------------------------------------------------------------
2212
2858
  // Projects
2213
2859
  // -------------------------------------------------------------------------
2214
2860
  async getProjectsByTenant(tenantId) {
2215
- const rows = this.db.prepare(
2861
+ const rows2 = this.db.prepare(
2216
2862
  `SELECT * FROM lt_eval_projects WHERE tenant_id = ? ORDER BY created_at DESC`
2217
2863
  ).all(tenantId);
2218
- return rows.map(this.mapRowToProject);
2864
+ return rows2.map(this.mapRowToProject);
2219
2865
  }
2220
2866
  async getProjectById(tenantId, id) {
2221
2867
  const row = this.db.prepare(
@@ -2318,11 +2964,11 @@ var LocalEvalStore = class {
2318
2964
  // Suites
2319
2965
  // -------------------------------------------------------------------------
2320
2966
  async getSuitesByProject(tenantId, projectId) {
2321
- const rows = this.db.prepare(
2967
+ const rows2 = this.db.prepare(
2322
2968
  `SELECT s.*, (SELECT COUNT(*) FROM lt_eval_cases c WHERE c.suite_id = s.id AND c.tenant_id = s.tenant_id) as case_count
2323
2969
  FROM lt_eval_suites s WHERE s.tenant_id = ? AND s.project_id = ? ORDER BY s.created_at DESC`
2324
2970
  ).all(tenantId, projectId);
2325
- return rows.map(this.mapRowToSuite);
2971
+ return rows2.map(this.mapRowToSuite);
2326
2972
  }
2327
2973
  async getSuiteById(tenantId, id) {
2328
2974
  const row = this.db.prepare(
@@ -2363,10 +3009,10 @@ var LocalEvalStore = class {
2363
3009
  // Cases
2364
3010
  // -------------------------------------------------------------------------
2365
3011
  async getCasesBySuite(tenantId, suiteId) {
2366
- const rows = this.db.prepare(
3012
+ const rows2 = this.db.prepare(
2367
3013
  `SELECT * FROM lt_eval_cases WHERE tenant_id = ? AND suite_id = ? ORDER BY created_at DESC`
2368
3014
  ).all(tenantId, suiteId);
2369
- return rows.map(this.mapRowToCase);
3015
+ return rows2.map(this.mapRowToCase);
2370
3016
  }
2371
3017
  async getCaseById(tenantId, id) {
2372
3018
  const row = this.db.prepare(
@@ -2455,8 +3101,8 @@ var LocalEvalStore = class {
2455
3101
  vals.push(opts.status);
2456
3102
  }
2457
3103
  query += ` ORDER BY created_at DESC`;
2458
- const rows = this.db.prepare(query).all(...vals);
2459
- return rows.map(this.mapRowToRun);
3104
+ const rows2 = this.db.prepare(query).all(...vals);
3105
+ return rows2.map(this.mapRowToRun);
2460
3106
  }
2461
3107
  async getRunById(tenantId, id) {
2462
3108
  const row = this.db.prepare(
@@ -2519,12 +3165,12 @@ var LocalEvalStore = class {
2519
3165
  // Run Results
2520
3166
  // -------------------------------------------------------------------------
2521
3167
  async getResultsByRun(tenantId, runId) {
2522
- const rows = this.db.prepare(
3168
+ const rows2 = this.db.prepare(
2523
3169
  `SELECT rr.* FROM lt_eval_run_results rr
2524
3170
  INNER JOIN lt_eval_runs r ON r.id = rr.run_id
2525
3171
  WHERE r.tenant_id = ? AND rr.run_id = ? ORDER BY rr.created_at ASC`
2526
3172
  ).all(tenantId, runId);
2527
- return rows.map(this.mapRowToRunResult);
3173
+ return rows2.map(this.mapRowToRunResult);
2528
3174
  }
2529
3175
  async getRunResultById(tenantId, id) {
2530
3176
  const row = this.db.prepare(
@@ -2642,8 +3288,8 @@ var LocalEvalStore = class {
2642
3288
  name: row.name,
2643
3289
  description: row.description,
2644
3290
  version: row.version,
2645
- judgeModelConfig: parseJson(row.judge_model_config, {}),
2646
- targetServerConfig: parseJson(row.target_server_config, {}),
3291
+ judgeModelConfig: parseJson2(row.judge_model_config, {}),
3292
+ targetServerConfig: parseJson2(row.target_server_config, {}),
2647
3293
  concurrency: row.concurrency ?? 3,
2648
3294
  reportConfig: parseOptionalJson(row.report_config),
2649
3295
  createdAt: parseISO(row.created_at),
@@ -2668,7 +3314,7 @@ var LocalEvalStore = class {
2668
3314
  suiteId: row.suite_id,
2669
3315
  inputMessage: row.input_message,
2670
3316
  inputFiles: parseOptionalJson(row.input_files),
2671
- steps: parseJson(row.steps, []),
3317
+ steps: parseJson2(row.steps, []),
2672
3318
  outputType: row.output_type || "message_content",
2673
3319
  contentAssertion: row.content_assertion || "",
2674
3320
  rubrics: parseOptionalJson(row.rubrics),
@@ -2720,7 +3366,50 @@ var LocalEvalStore = class {
2720
3366
  };
2721
3367
 
2722
3368
  // src/stores/LocalChannelBindingStore.ts
3369
+ var import_protocols3 = require("@axiom-lattice/protocols");
2723
3370
  var import_crypto2 = require("crypto");
3371
+
3372
+ // src/stores/room-channel-binding-index.ts
3373
+ var import_protocols2 = require("@axiom-lattice/protocols");
3374
+ var UNIQUE_INDEX_SQL = `CREATE UNIQUE INDEX IF NOT EXISTS idx_lt_cb_subject_unique
3375
+ ON lt_channel_bindings(channel, channel_installation_id, tenant_id, sender_id)`;
3376
+ var LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve
3377
+ ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id)`;
3378
+ function ensureChannelBindingSubjectIndex(db) {
3379
+ db.exec("BEGIN");
3380
+ try {
3381
+ const conflicts = db.prepare(`SELECT tenant_id, channel, channel_installation_id, sender_id, COUNT(*) AS count
3382
+ FROM lt_channel_bindings
3383
+ GROUP BY channel, channel_installation_id, tenant_id, sender_id
3384
+ HAVING COUNT(*) > 1
3385
+ ORDER BY tenant_id, channel, channel_installation_id, sender_id`).all();
3386
+ if (conflicts.length > 0) {
3387
+ throw new import_protocols2.ChannelBindingMigrationConflictError(conflicts.map(mapConflict));
3388
+ }
3389
+ db.exec("DROP INDEX IF EXISTS idx_lt_cb_room_subject");
3390
+ db.exec(UNIQUE_INDEX_SQL);
3391
+ db.exec(LOOKUP_INDEX_SQL);
3392
+ db.exec("COMMIT");
3393
+ } catch (error) {
3394
+ try {
3395
+ db.exec("ROLLBACK");
3396
+ } catch {
3397
+ }
3398
+ throw error;
3399
+ }
3400
+ db.save();
3401
+ }
3402
+ function mapConflict(row) {
3403
+ return {
3404
+ tenantId: row.tenant_id,
3405
+ channel: row.channel,
3406
+ channelInstallationId: row.channel_installation_id,
3407
+ senderId: row.sender_id,
3408
+ count: row.count
3409
+ };
3410
+ }
3411
+
3412
+ // src/stores/LocalChannelBindingStore.ts
2724
3413
  var DDL14 = `
2725
3414
  CREATE TABLE IF NOT EXISTS lt_channel_bindings (
2726
3415
  id TEXT PRIMARY KEY,
@@ -2739,12 +3428,23 @@ CREATE TABLE IF NOT EXISTS lt_channel_bindings (
2739
3428
  created_at TEXT NOT NULL,
2740
3429
  updated_at TEXT NOT NULL
2741
3430
  );
2742
- CREATE INDEX IF NOT EXISTS idx_lt_cb_resolve ON lt_channel_bindings(channel, sender_id, channel_installation_id, tenant_id);
2743
3431
  `;
2744
3432
  var LocalChannelBindingStore = class {
2745
3433
  constructor(db) {
2746
3434
  this.db = db;
2747
3435
  ensureTable(db, DDL14);
3436
+ ensureChannelBindingSubjectIndex(db);
3437
+ }
3438
+ async findById(tenantId, id) {
3439
+ const row = this.db.prepare(
3440
+ "SELECT * FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?"
3441
+ ).get(id, tenantId);
3442
+ return row ? mapRowToBinding(row) : null;
3443
+ }
3444
+ async findBySubject(params) {
3445
+ const row = this.db.prepare(`SELECT * FROM lt_channel_bindings
3446
+ WHERE tenant_id = ? AND channel = ? AND channel_installation_id = ? AND sender_id = ? LIMIT 1`).get(params.tenantId, params.channel, params.channelInstallationId, params.senderId);
3447
+ return row ? mapRowToBinding(row) : null;
2748
3448
  }
2749
3449
  async resolve(params) {
2750
3450
  const row = this.db.prepare(
@@ -2757,35 +3457,41 @@ var LocalChannelBindingStore = class {
2757
3457
  async create(input) {
2758
3458
  const id = (0, import_crypto2.randomUUID)();
2759
3459
  const now = nowISO();
2760
- this.db.prepare(
2761
- `INSERT INTO lt_channel_bindings
2762
- (id, channel, channel_installation_id, tenant_id, sender_id, agent_id,
2763
- thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, created_at, updated_at)
2764
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
2765
- ).run(
2766
- id,
2767
- input.channel,
2768
- input.channelInstallationId,
2769
- input.tenantId,
2770
- input.senderId,
2771
- input.agentId,
2772
- input.threadMode || "fixed",
2773
- input.senderDisplayName || null,
2774
- input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
2775
- input.workspaceId || null,
2776
- input.projectId || null,
2777
- now,
2778
- now
2779
- );
3460
+ try {
3461
+ this.db.prepare(
3462
+ `INSERT INTO lt_channel_bindings
3463
+ (id, channel, channel_installation_id, tenant_id, sender_id, agent_id,
3464
+ thread_id, thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, enabled, created_at, updated_at)
3465
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
3466
+ ).run(
3467
+ id,
3468
+ input.channel,
3469
+ input.channelInstallationId,
3470
+ input.tenantId,
3471
+ input.senderId,
3472
+ input.agentId,
3473
+ input.threadId || null,
3474
+ input.threadMode || "fixed",
3475
+ input.senderDisplayName || null,
3476
+ input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
3477
+ input.workspaceId || null,
3478
+ input.projectId || null,
3479
+ input.enabled ?? true ? 1 : 0,
3480
+ now,
3481
+ now
3482
+ );
3483
+ } catch (error) {
3484
+ if (error instanceof Error && /UNIQUE constraint failed/.test(error.message)) {
3485
+ throw new import_protocols3.DuplicateChannelBindingSubjectError();
3486
+ }
3487
+ throw error;
3488
+ }
2780
3489
  return await this.getById(id);
2781
3490
  }
2782
- async update(id, patch) {
2783
- const existing = await this.getById(id);
3491
+ async update(tenantId, id, patch) {
3492
+ const existing = await this.findById(tenantId, id);
2784
3493
  if (!existing) throw new Error(`Binding ${id} not found`);
2785
3494
  const updated = {
2786
- channel: patch.channel ?? existing.channel,
2787
- channelInstallationId: patch.channelInstallationId ?? existing.channelInstallationId,
2788
- senderId: patch.senderId ?? existing.senderId,
2789
3495
  agentId: patch.agentId ?? existing.agentId,
2790
3496
  threadId: patch.threadId !== void 0 ? patch.threadId : existing.threadId,
2791
3497
  workspaceId: patch.workspaceId !== void 0 ? patch.workspaceId : existing.workspaceId,
@@ -2798,14 +3504,10 @@ var LocalChannelBindingStore = class {
2798
3504
  const now = nowISO();
2799
3505
  this.db.prepare(
2800
3506
  `UPDATE lt_channel_bindings SET
2801
- channel = ?, channel_installation_id = ?, sender_id = ?, agent_id = ?,
2802
- thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
3507
+ agent_id = ?, thread_id = ?, workspace_id = ?, project_id = ?, thread_mode = ?,
2803
3508
  sender_display_name = ?, sender_metadata = ?, enabled = ?, updated_at = ?
2804
- WHERE id = ?`
3509
+ WHERE id = ? AND tenant_id = ?`
2805
3510
  ).run(
2806
- updated.channel,
2807
- updated.channelInstallationId,
2808
- updated.senderId,
2809
3511
  updated.agentId,
2810
3512
  updated.threadId || null,
2811
3513
  updated.workspaceId || null,
@@ -2815,12 +3517,13 @@ var LocalChannelBindingStore = class {
2815
3517
  updated.senderMetadata ? JSON.stringify(updated.senderMetadata) : null,
2816
3518
  updated.enabled ? 1 : 0,
2817
3519
  now,
2818
- id
3520
+ id,
3521
+ tenantId
2819
3522
  );
2820
- return await this.getById(id);
3523
+ return await this.findById(tenantId, id);
2821
3524
  }
2822
- async delete(id) {
2823
- this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ?`).run(id);
3525
+ async delete(tenantId, id) {
3526
+ this.db.prepare(`DELETE FROM lt_channel_bindings WHERE id = ? AND tenant_id = ?`).run(id, tenantId);
2824
3527
  }
2825
3528
  async list(params) {
2826
3529
  const conditions = ["tenant_id = ?"];
@@ -2837,18 +3540,35 @@ var LocalChannelBindingStore = class {
2837
3540
  conditions.push("channel_installation_id = ?");
2838
3541
  values.push(params.channelInstallationId);
2839
3542
  }
3543
+ if (params.excludeChannels?.length) {
3544
+ conditions.push(`channel NOT IN (${params.excludeChannels.map(() => "?").join(", ")})`);
3545
+ values.push(...params.excludeChannels);
3546
+ }
3547
+ for (const prefix of params.excludeInstallationIdPrefixes ?? []) {
3548
+ conditions.push("substr(channel_installation_id, 1, length(?)) <> ?");
3549
+ values.push(prefix, prefix);
3550
+ }
2840
3551
  const limit = params.limit ?? 50;
2841
3552
  const offset = params.offset ?? 0;
2842
3553
  values.push(limit, offset);
2843
- const rows = this.db.prepare(
3554
+ const rows2 = this.db.prepare(
2844
3555
  `SELECT * FROM lt_channel_bindings
2845
3556
  WHERE ${conditions.join(" AND ")}
2846
3557
  ORDER BY created_at DESC
2847
3558
  LIMIT ? OFFSET ?`
2848
3559
  ).all(...values);
2849
- return rows.map(mapRowToBinding);
3560
+ return rows2.map(mapRowToBinding);
2850
3561
  }
2851
- async import(bindings) {
3562
+ async import(tenantId, bindings) {
3563
+ if (bindings.some((binding) => binding.channel === "room")) {
3564
+ throw new Error("Room bindings cannot be imported through the public store API");
3565
+ }
3566
+ if (bindings.some((binding) => binding.channelInstallationId.startsWith("room-internal:"))) {
3567
+ throw new Error("Internal bindings cannot be imported through the public store API");
3568
+ }
3569
+ if (bindings.some((binding) => binding.tenantId !== tenantId)) {
3570
+ throw new Error("Binding import tenant mismatch");
3571
+ }
2852
3572
  const result = [];
2853
3573
  for (const input of bindings) {
2854
3574
  result.push(await this.create(input));
@@ -2856,7 +3576,13 @@ var LocalChannelBindingStore = class {
2856
3576
  return result;
2857
3577
  }
2858
3578
  async export(params) {
2859
- return this.list({ tenantId: params.tenantId, limit: 1e4, offset: 0 });
3579
+ return this.list({
3580
+ tenantId: params.tenantId,
3581
+ excludeChannels: ["room"],
3582
+ excludeInstallationIdPrefixes: ["room-internal:"],
3583
+ limit: 1e4,
3584
+ offset: 0
3585
+ });
2860
3586
  }
2861
3587
  async getById(id) {
2862
3588
  const row = this.db.prepare(
@@ -2910,33 +3636,33 @@ var LocalChannelInstallationStore = class {
2910
3636
  const row = this.db.prepare(
2911
3637
  `SELECT * FROM lt_channel_installations WHERE id = ?`
2912
3638
  ).get(installationId);
2913
- return row ? mapRow(row) : null;
3639
+ return row ? mapRow5(row) : null;
2914
3640
  }
2915
3641
  async getInstallationsByTenant(tenantId, channel) {
2916
- let rows;
3642
+ let rows2;
2917
3643
  if (channel) {
2918
- rows = this.db.prepare(
3644
+ rows2 = this.db.prepare(
2919
3645
  `SELECT * FROM lt_channel_installations WHERE tenant_id = ? AND channel = ? ORDER BY created_at DESC`
2920
3646
  ).all(tenantId, channel);
2921
3647
  } else {
2922
- rows = this.db.prepare(
3648
+ rows2 = this.db.prepare(
2923
3649
  `SELECT * FROM lt_channel_installations WHERE tenant_id = ? ORDER BY created_at DESC`
2924
3650
  ).all(tenantId);
2925
3651
  }
2926
- return rows.map(mapRow);
3652
+ return rows2.map(mapRow5);
2927
3653
  }
2928
3654
  async getAllInstallations(channel) {
2929
- let rows;
3655
+ let rows2;
2930
3656
  if (channel) {
2931
- rows = this.db.prepare(
3657
+ rows2 = this.db.prepare(
2932
3658
  `SELECT * FROM lt_channel_installations WHERE channel = ? ORDER BY created_at DESC`
2933
3659
  ).all(channel);
2934
3660
  } else {
2935
- rows = this.db.prepare(
3661
+ rows2 = this.db.prepare(
2936
3662
  `SELECT * FROM lt_channel_installations ORDER BY created_at DESC`
2937
3663
  ).all();
2938
3664
  }
2939
- return rows.map(mapRow);
3665
+ return rows2.map(mapRow5);
2940
3666
  }
2941
3667
  async createInstallation(tenantId, installationId, data) {
2942
3668
  const now = nowISO();
@@ -2961,7 +3687,7 @@ var LocalChannelInstallationStore = class {
2961
3687
  tenantId,
2962
3688
  channel: data.channel,
2963
3689
  name: data.name,
2964
- config: data.config,
3690
+ config: { ...data.config },
2965
3691
  enabled: data.enabled !== false,
2966
3692
  fallbackAgentId: data.fallbackAgentId,
2967
3693
  rejectWhenNoBinding: data.rejectWhenNoBinding ?? false,
@@ -2971,7 +3697,7 @@ var LocalChannelInstallationStore = class {
2971
3697
  }
2972
3698
  async updateInstallation(tenantId, installationId, updates) {
2973
3699
  const existing = await this.getInstallationById(installationId);
2974
- if (!existing) return null;
3700
+ if (!existing || existing.tenantId !== tenantId) return null;
2975
3701
  const setClauses = [];
2976
3702
  const values = [];
2977
3703
  if (updates.name !== void 0) {
@@ -2998,9 +3724,10 @@ var LocalChannelInstallationStore = class {
2998
3724
  const now = nowISO();
2999
3725
  setClauses.push("updated_at = ?");
3000
3726
  values.push(now);
3727
+ values.push(tenantId);
3001
3728
  values.push(installationId);
3002
3729
  this.db.prepare(
3003
- `UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE id = ?`
3730
+ `UPDATE lt_channel_installations SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`
3004
3731
  ).run(...values);
3005
3732
  return this.getInstallationById(installationId);
3006
3733
  }
@@ -3011,7 +3738,7 @@ var LocalChannelInstallationStore = class {
3011
3738
  return result.changes > 0;
3012
3739
  }
3013
3740
  };
3014
- function mapRow(row) {
3741
+ function mapRow5(row) {
3015
3742
  return {
3016
3743
  id: row.id,
3017
3744
  tenantId: row.tenant_id,
@@ -3062,18 +3789,18 @@ var LocalA2AApiKeyStore = class {
3062
3789
  this.repairManagementIds();
3063
3790
  }
3064
3791
  /** Add a column if it does not exist (SQLite version compatible). */
3065
- ensureColumn(table, column, ddl) {
3792
+ ensureColumn(table, column, ddl2) {
3066
3793
  const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
3067
3794
  if (!cols.some((c) => c.name === column)) {
3068
- this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
3795
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
3069
3796
  }
3070
3797
  }
3071
3798
  repairManagementIds() {
3072
- const rows = this.db.prepare(
3799
+ const rows2 = this.db.prepare(
3073
3800
  `SELECT rowid, id FROM lt_a2a_api_keys`
3074
3801
  ).all();
3075
3802
  const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3076
- for (const row of rows) {
3803
+ for (const row of rows2) {
3077
3804
  if (!row.id || !uuidPattern.test(row.id)) {
3078
3805
  this.db.prepare(
3079
3806
  `UPDATE lt_a2a_api_keys SET id = ? WHERE rowid = ?`
@@ -3082,10 +3809,10 @@ var LocalA2AApiKeyStore = class {
3082
3809
  }
3083
3810
  }
3084
3811
  async findByKey(key) {
3085
- const rows = this.db.prepare(
3812
+ const rows2 = this.db.prepare(
3086
3813
  `SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
3087
3814
  ).all();
3088
- for (const row of rows) {
3815
+ for (const row of rows2) {
3089
3816
  try {
3090
3817
  if ((0, import_core3.decrypt)(row.key_value) === key) return mapRowToRecord(row);
3091
3818
  } catch {
@@ -3102,17 +3829,17 @@ var LocalA2AApiKeyStore = class {
3102
3829
  async list(params) {
3103
3830
  const limit = params.limit || 100;
3104
3831
  const offset = params.offset || 0;
3105
- let rows;
3832
+ let rows2;
3106
3833
  if (params.tenantId) {
3107
- rows = this.db.prepare(
3834
+ rows2 = this.db.prepare(
3108
3835
  `SELECT * FROM lt_a2a_api_keys WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?`
3109
3836
  ).all(params.tenantId, limit, offset);
3110
3837
  } else {
3111
- rows = this.db.prepare(
3838
+ rows2 = this.db.prepare(
3112
3839
  `SELECT * FROM lt_a2a_api_keys ORDER BY created_at DESC LIMIT ? OFFSET ?`
3113
3840
  ).all(limit, offset);
3114
3841
  }
3115
- return rows.map(mapRowToRecord);
3842
+ return rows2.map(mapRowToRecord);
3116
3843
  }
3117
3844
  async create(input) {
3118
3845
  const id = (0, import_crypto3.randomUUID)();
@@ -3122,7 +3849,7 @@ var LocalA2AApiKeyStore = class {
3122
3849
  `INSERT INTO lt_a2a_api_keys (id, key_value, tenant_id, project_id, assistant_ids, label, created_at, updated_at)
3123
3850
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
3124
3851
  ).run(id, (0, import_core3.encrypt)(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null, now, now);
3125
- const record = {
3852
+ const record2 = {
3126
3853
  id,
3127
3854
  key,
3128
3855
  tenantId: input.tenantId,
@@ -3133,7 +3860,7 @@ var LocalA2AApiKeyStore = class {
3133
3860
  createdAt: parseISO(now),
3134
3861
  updatedAt: parseISO(now)
3135
3862
  };
3136
- return record;
3863
+ return record2;
3137
3864
  }
3138
3865
  async disable(id) {
3139
3866
  const now = nowISO();
@@ -3161,19 +3888,19 @@ var LocalA2AApiKeyStore = class {
3161
3888
  ).run((0, import_core3.encrypt)(key), now, id);
3162
3889
  const row = this.db.prepare(`SELECT * FROM lt_a2a_api_keys WHERE id = ?`).get(id);
3163
3890
  if (!row) throw new Error(`A2A API key not found: ${id}`);
3164
- const record = mapRowToRecord(row);
3165
- record.key = key;
3166
- return record;
3891
+ const record2 = mapRowToRecord(row);
3892
+ record2.key = key;
3893
+ return record2;
3167
3894
  }
3168
3895
  async delete(id) {
3169
3896
  this.db.prepare(`DELETE FROM lt_a2a_api_keys WHERE id = ?`).run(id);
3170
3897
  }
3171
3898
  async loadIntoMap() {
3172
- const rows = this.db.prepare(
3899
+ const rows2 = this.db.prepare(
3173
3900
  `SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
3174
3901
  ).all();
3175
3902
  const map2 = /* @__PURE__ */ new Map();
3176
- for (const row of rows) {
3903
+ for (const row of rows2) {
3177
3904
  try {
3178
3905
  const key = (0, import_core3.decrypt)(row.key_value);
3179
3906
  map2.set(key, {
@@ -3209,6 +3936,7 @@ function mapRowToRecord(row) {
3209
3936
  }
3210
3937
 
3211
3938
  // src/stores/LocalThreadMessageQueueStore.ts
3939
+ var import_protocols4 = require("@axiom-lattice/protocols");
3212
3940
  var import_crypto4 = require("crypto");
3213
3941
  var DDL17 = `
3214
3942
  CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
@@ -3225,6 +3953,8 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
3225
3953
  status TEXT NOT NULL DEFAULT 'pending',
3226
3954
  command TEXT,
3227
3955
  custom_run_config TEXT,
3956
+ trusted_run_context TEXT,
3957
+ execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
3228
3958
  created_at TEXT NOT NULL
3229
3959
  );
3230
3960
  CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
@@ -3235,8 +3965,17 @@ var LocalThreadMessageQueueStore = class {
3235
3965
  this.capacityTail = Promise.resolve();
3236
3966
  this.db = db;
3237
3967
  ensureTable(db, DDL17);
3968
+ const columns5 = db.prepare("PRAGMA table_info(lt_thread_message_queue)").all();
3969
+ const names = new Set(columns5.map(({ name }) => name));
3970
+ if (!names.has("trusted_run_context")) {
3971
+ db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN trusted_run_context TEXT");
3972
+ }
3973
+ if (!names.has("execution_mode")) {
3974
+ db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup')");
3975
+ }
3238
3976
  }
3239
3977
  async addMessage(params) {
3978
+ const trusted = validateQueueTrust(params);
3240
3979
  const now = nowISO();
3241
3980
  const id = params.id || (0, import_crypto4.randomUUID)();
3242
3981
  const seqRow = this.db.prepare(
@@ -3244,8 +3983,8 @@ var LocalThreadMessageQueueStore = class {
3244
3983
  ).get(params.threadId);
3245
3984
  const nextSeq = seqRow.next_seq;
3246
3985
  this.db.prepare(
3247
- `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
3248
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
3986
+ `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
3987
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
3249
3988
  ).run(
3250
3989
  id,
3251
3990
  params.threadId,
@@ -3259,11 +3998,14 @@ var LocalThreadMessageQueueStore = class {
3259
3998
  params.priority ?? 0,
3260
3999
  params.command ? JSON.stringify(params.command) : null,
3261
4000
  params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
4001
+ trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
4002
+ trusted.executionMode ?? null,
3262
4003
  now
3263
4004
  );
3264
4005
  return this.getById(id);
3265
4006
  }
3266
4007
  async addMessageIfCapacity(params, maxSize) {
4008
+ const trusted = validateQueueTrust(params);
3267
4009
  let release;
3268
4010
  const previous = this.capacityTail;
3269
4011
  this.capacityTail = new Promise((resolve) => {
@@ -3281,10 +4023,11 @@ var LocalThreadMessageQueueStore = class {
3281
4023
  const id = params.id || (0, import_crypto4.randomUUID)();
3282
4024
  const limit = maxSize === Infinity ? -1 : maxSize;
3283
4025
  const result = this.db.prepare(
3284
- `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
4026
+ `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
3285
4027
  SELECT ?, ?, ?, ?, ?, ?, ?, ?,
3286
4028
  (SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
3287
- ?, ?, ?, ?
4029
+ ?, ?, ?, ?, ?,
4030
+ ?
3288
4031
  WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
3289
4032
  ).run(
3290
4033
  id,
@@ -3299,6 +4042,8 @@ var LocalThreadMessageQueueStore = class {
3299
4042
  params.priority ?? 0,
3300
4043
  params.command ? JSON.stringify(params.command) : null,
3301
4044
  params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
4045
+ trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
4046
+ trusted.executionMode ?? null,
3302
4047
  nowISO(),
3303
4048
  limit,
3304
4049
  params.threadId,
@@ -3311,14 +4056,15 @@ var LocalThreadMessageQueueStore = class {
3311
4056
  }
3312
4057
  }
3313
4058
  async addMessageAtHead(params) {
4059
+ const trusted = validateQueueTrust(params);
3314
4060
  const now = nowISO();
3315
4061
  const id = params.id || (0, import_crypto4.randomUUID)();
3316
4062
  const seqRow = this.db.prepare(
3317
4063
  `SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lt_thread_message_queue WHERE thread_id = ?`
3318
4064
  ).get(params.threadId);
3319
4065
  this.db.prepare(
3320
- `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
3321
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?)`
4066
+ `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode, created_at)
4067
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?, ?, ?)`
3322
4068
  ).run(
3323
4069
  id,
3324
4070
  params.threadId,
@@ -3331,23 +4077,25 @@ var LocalThreadMessageQueueStore = class {
3331
4077
  seqRow.next_seq,
3332
4078
  params.command ? JSON.stringify(params.command) : null,
3333
4079
  params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
4080
+ trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null,
4081
+ trusted.executionMode ?? null,
3334
4082
  now
3335
4083
  );
3336
4084
  return this.getById(id);
3337
4085
  }
3338
4086
  async getPendingMessages(threadId, scope) {
3339
4087
  const filter = scopeClause(scope);
3340
- const rows = this.db.prepare(
4088
+ const rows2 = this.db.prepare(
3341
4089
  `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
3342
4090
  ).all(threadId, ...filter.params);
3343
- return rows.map(rowToMessage);
4091
+ return rows2.map(rowToMessage);
3344
4092
  }
3345
4093
  async getProcessingMessages(threadId, scope) {
3346
4094
  const filter = scopeClause(scope);
3347
- const rows = this.db.prepare(
4095
+ const rows2 = this.db.prepare(
3348
4096
  `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
3349
4097
  ).all(threadId, ...filter.params);
3350
- return rows.map(rowToMessage);
4098
+ return rows2.map(rowToMessage);
3351
4099
  }
3352
4100
  async getQueueSize(threadId, scope) {
3353
4101
  const filter = scopeClause(scope);
@@ -3357,10 +4105,10 @@ var LocalThreadMessageQueueStore = class {
3357
4105
  return row.count;
3358
4106
  }
3359
4107
  async getThreadsWithPendingMessages() {
3360
- const rows = this.db.prepare(
4108
+ const rows2 = this.db.prepare(
3361
4109
  `SELECT tenant_id, assistant_id, thread_id, workspace_id, project_id FROM lt_thread_message_queue WHERE status IN ('pending', 'processing') GROUP BY tenant_id, assistant_id, thread_id, workspace_id, project_id ORDER BY thread_id`
3362
4110
  ).all();
3363
- return rows.map((r) => ({
4111
+ return rows2.map((r) => ({
3364
4112
  tenantId: r.tenant_id,
3365
4113
  assistantId: r.assistant_id,
3366
4114
  threadId: r.thread_id,
@@ -3399,6 +4147,12 @@ var LocalThreadMessageQueueStore = class {
3399
4147
  return rowToMessage(row);
3400
4148
  }
3401
4149
  };
4150
+ function validateQueueTrust(params) {
4151
+ return {
4152
+ trustedRunContext: params.trusted_run_context === void 0 ? void 0 : (0, import_protocols4.parseTrustedRunContext)(params.trusted_run_context),
4153
+ executionMode: params.execution_mode === void 0 ? void 0 : (0, import_protocols4.parseQueuedExecutionMode)(params.execution_mode)
4154
+ };
4155
+ }
3402
4156
  function scopeClause(scope) {
3403
4157
  if (!scope) return { sql: "", params: [] };
3404
4158
  const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
@@ -3416,7 +4170,9 @@ function rowToMessage(row) {
3416
4170
  createdAt: parseISO(row.created_at),
3417
4171
  priority: row.priority || 0,
3418
4172
  command: row.command ? JSON.parse(row.command) : void 0,
3419
- custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0
4173
+ custom_run_config: row.custom_run_config ? JSON.parse(row.custom_run_config) : void 0,
4174
+ trusted_run_context: row.trusted_run_context ? (0, import_protocols4.parseTrustedRunContext)(JSON.parse(row.trusted_run_context)) : void 0,
4175
+ execution_mode: row.execution_mode == null ? void 0 : (0, import_protocols4.parseQueuedExecutionMode)(row.execution_mode)
3420
4176
  };
3421
4177
  }
3422
4178
 
@@ -3443,10 +4199,10 @@ var LocalSkillStore = class {
3443
4199
  ensureTable(db, DDL18);
3444
4200
  }
3445
4201
  async getAllSkills(tenantId, _context) {
3446
- const rows = this.db.prepare(
4202
+ const rows2 = this.db.prepare(
3447
4203
  `SELECT * FROM lt_skills WHERE tenant_id = ? ORDER BY created_at DESC`
3448
4204
  ).all(tenantId);
3449
- return rows.map(mapRowToSkill);
4205
+ return rows2.map(mapRowToSkill);
3450
4206
  }
3451
4207
  async getSkillById(tenantId, id, _context) {
3452
4208
  const row = this.db.prepare(
@@ -3551,16 +4307,16 @@ var LocalSkillStore = class {
3551
4307
  return all.filter((s) => s.metadata?.[metadataKey] === metadataValue);
3552
4308
  }
3553
4309
  async filterByCompatibility(tenantId, compatibility, _context) {
3554
- const rows = this.db.prepare(
4310
+ const rows2 = this.db.prepare(
3555
4311
  `SELECT * FROM lt_skills WHERE tenant_id = ? AND compatibility = ? ORDER BY created_at DESC`
3556
4312
  ).all(tenantId, compatibility);
3557
- return rows.map(mapRowToSkill);
4313
+ return rows2.map(mapRowToSkill);
3558
4314
  }
3559
4315
  async filterByLicense(tenantId, license, _context) {
3560
- const rows = this.db.prepare(
4316
+ const rows2 = this.db.prepare(
3561
4317
  `SELECT * FROM lt_skills WHERE tenant_id = ? AND license = ? ORDER BY created_at DESC`
3562
4318
  ).all(tenantId, license);
3563
- return rows.map(mapRowToSkill);
4319
+ return rows2.map(mapRowToSkill);
3564
4320
  }
3565
4321
  async getSubSkills(tenantId, parentSkillName, _context) {
3566
4322
  const all = await this.getAllSkills(tenantId);
@@ -3772,40 +4528,40 @@ var LocalScheduleStorage = class {
3772
4528
  this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
3773
4529
  }
3774
4530
  async getActiveTasks() {
3775
- const rows = this.db.prepare(
4531
+ const rows2 = this.db.prepare(
3776
4532
  `SELECT * FROM lt_scheduled_tasks WHERE status IN ('pending', 'paused') ORDER BY created_at ASC`
3777
4533
  ).all();
3778
- return rows.map(mapRowToTask);
4534
+ return rows2.map(mapRowToTask);
3779
4535
  }
3780
4536
  async getTasksByType(taskType) {
3781
- const rows = this.db.prepare(
4537
+ const rows2 = this.db.prepare(
3782
4538
  `SELECT * FROM lt_scheduled_tasks WHERE task_type = ? ORDER BY created_at DESC`
3783
4539
  ).all(taskType);
3784
- return rows.map(mapRowToTask);
4540
+ return rows2.map(mapRowToTask);
3785
4541
  }
3786
4542
  async getTasksByStatus(status) {
3787
- const rows = this.db.prepare(
4543
+ const rows2 = this.db.prepare(
3788
4544
  `SELECT * FROM lt_scheduled_tasks WHERE status = ? ORDER BY created_at DESC`
3789
4545
  ).all(status);
3790
- return rows.map(mapRowToTask);
4546
+ return rows2.map(mapRowToTask);
3791
4547
  }
3792
4548
  async getTasksByExecutionType(executionType) {
3793
- const rows = this.db.prepare(
4549
+ const rows2 = this.db.prepare(
3794
4550
  `SELECT * FROM lt_scheduled_tasks WHERE execution_type = ? ORDER BY created_at DESC`
3795
4551
  ).all(executionType);
3796
- return rows.map(mapRowToTask);
4552
+ return rows2.map(mapRowToTask);
3797
4553
  }
3798
4554
  async getTasksByAssistantId(assistantId) {
3799
- const rows = this.db.prepare(
4555
+ const rows2 = this.db.prepare(
3800
4556
  `SELECT * FROM lt_scheduled_tasks WHERE assistant_id = ? ORDER BY created_at DESC`
3801
4557
  ).all(assistantId);
3802
- return rows.map(mapRowToTask);
4558
+ return rows2.map(mapRowToTask);
3803
4559
  }
3804
4560
  async getTasksByThreadId(threadId) {
3805
- const rows = this.db.prepare(
4561
+ const rows2 = this.db.prepare(
3806
4562
  `SELECT * FROM lt_scheduled_tasks WHERE thread_id = ? ORDER BY created_at DESC`
3807
4563
  ).all(threadId);
3808
- return rows.map(mapRowToTask);
4564
+ return rows2.map(mapRowToTask);
3809
4565
  }
3810
4566
  async getAllTasks(filters) {
3811
4567
  const conditions = [];
@@ -3845,8 +4601,8 @@ var LocalScheduleStorage = class {
3845
4601
  query += ` OFFSET ?`;
3846
4602
  values.push(filters.offset);
3847
4603
  }
3848
- const rows = this.db.prepare(query).all(...values);
3849
- return rows.map(mapRowToTask);
4604
+ const rows2 = this.db.prepare(query).all(...values);
4605
+ return rows2.map(mapRowToTask);
3850
4606
  }
3851
4607
  async countTasks(filters) {
3852
4608
  const conditions = [];
@@ -3919,6 +4675,15 @@ function mapRowToTask(row) {
3919
4675
  // src/stores/LocalTaskStore.ts
3920
4676
  var import_crypto5 = require("crypto");
3921
4677
  var MAX_CANONICAL_TASK_TIME = "9999-12-31T23:59:59.999Z";
4678
+ var TASK_STATUSES = /* @__PURE__ */ new Set([
4679
+ "pending",
4680
+ "in_progress",
4681
+ "review",
4682
+ "failed",
4683
+ "interrupted",
4684
+ "completed",
4685
+ "cancelled"
4686
+ ]);
3922
4687
  var NEXT_UPDATED_AT_SQL = `updated_at = CASE
3923
4688
  WHEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) = updated_at AND updated_at >= ?
3924
4689
  THEN strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds')
@@ -3991,6 +4756,9 @@ function nextISO(previous) {
3991
4756
  const previousMs = new Date(previous).getTime();
3992
4757
  return new Date(Math.max(Date.now(), previousMs + 1)).toISOString();
3993
4758
  }
4759
+ function assertPage(limit, offset = 0) {
4760
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0) throw new RangeError("Invalid task page");
4761
+ }
3994
4762
  var LocalTaskStore = class {
3995
4763
  constructor(db) {
3996
4764
  this.db = db;
@@ -4004,10 +4772,10 @@ var LocalTaskStore = class {
4004
4772
  this.ensureColumn("lt_tasks", "files", "files TEXT");
4005
4773
  }
4006
4774
  /** Add a column if it does not exist (SQLite version compatible). */
4007
- ensureColumn(table, column, ddl) {
4775
+ ensureColumn(table, column, ddl2) {
4008
4776
  const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
4009
4777
  if (!cols.some((c) => c.name === column)) {
4010
- this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
4778
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
4011
4779
  }
4012
4780
  }
4013
4781
  async create(params) {
@@ -4073,7 +4841,8 @@ var LocalTaskStore = class {
4073
4841
  conditions.push("workspace_id = ?");
4074
4842
  params.push(filter.workspaceId);
4075
4843
  }
4076
- if (filter.projectId) {
4844
+ if (filter.projectId === null) conditions.push("(project_id IS NULL OR project_id = '' OR project_id = 'default')");
4845
+ else if (filter.projectId !== void 0) {
4077
4846
  conditions.push("project_id = ?");
4078
4847
  params.push(filter.projectId);
4079
4848
  }
@@ -4103,10 +4872,44 @@ var LocalTaskStore = class {
4103
4872
  const where = conditions.join(" AND ");
4104
4873
  const limit = filter.limit || 100;
4105
4874
  const offset = filter.offset || 0;
4106
- const rows = this.db.prepare(
4107
- `SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
4875
+ const rows2 = this.db.prepare(
4876
+ `SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?`
4108
4877
  ).all(...params, limit, offset);
4109
- return rows.map(mapRowToTask2);
4878
+ return rows2.map(mapRowToTask2);
4879
+ }
4880
+ /** Lists exact project tasks containing a string dependency. */
4881
+ async listDependents(query) {
4882
+ assertPage(query.limit, query.offset);
4883
+ if (query.statuses.length === 0 || query.statuses.some((status) => !TASK_STATUSES.has(status))) {
4884
+ throw new RangeError("Invalid task statuses");
4885
+ }
4886
+ const rows2 = this.db.prepare(
4887
+ `SELECT task.* FROM lt_tasks AS task
4888
+ WHERE task.tenant_id = ? AND task.workspace_id = ? AND task.project_id = ?
4889
+ AND task.status IN (${query.statuses.map(() => "?").join(", ")})
4890
+ AND task.dependencies IS NOT NULL
4891
+ AND json_valid(task.dependencies) = 1
4892
+ AND json_type(CASE WHEN json_valid(task.dependencies)=1
4893
+ THEN task.dependencies ELSE '[]' END) = 'array'
4894
+ AND EXISTS (
4895
+ SELECT 1 FROM json_each(
4896
+ CASE WHEN json_valid(task.dependencies)=1 THEN
4897
+ CASE WHEN json_type(task.dependencies)='array' THEN task.dependencies ELSE '[]' END
4898
+ ELSE '[]' END
4899
+ ) AS dependency
4900
+ WHERE dependency.type = 'text' AND dependency.value = ?
4901
+ )
4902
+ ORDER BY task.created_at DESC, task.id DESC LIMIT ? OFFSET ?`
4903
+ ).all(
4904
+ query.tenantId,
4905
+ query.workspaceId,
4906
+ query.projectId,
4907
+ ...query.statuses,
4908
+ query.dependencyTaskId,
4909
+ query.limit,
4910
+ query.offset
4911
+ );
4912
+ return rows2.map(mapRowToTask2);
4110
4913
  }
4111
4914
  async update(tenantId, id, updates) {
4112
4915
  const existing = await this.getById(tenantId, id);
@@ -4458,6 +5261,54 @@ var LocalTaskStore = class {
4458
5261
  if (result.changes === 0) return null;
4459
5262
  return this.getById(tenantId, id);
4460
5263
  }
5264
+ /** Atomically update only while status, timestamp, owner, and Project scope match. */
5265
+ async updateIfSnapshot(tenantId, id, updates, snapshot) {
5266
+ const expectedIso = new Date(snapshot.updatedAt).toISOString();
5267
+ if (expectedIso === MAX_CANONICAL_TASK_TIME) return null;
5268
+ const setClauses = ["updated_at = ?"];
5269
+ const values = [nextISO(expectedIso)];
5270
+ const fields = [
5271
+ ["title", "title"],
5272
+ ["description", "description"],
5273
+ ["status", "status"],
5274
+ ["priority", "priority"],
5275
+ ["dueDate", "due_date"],
5276
+ ["metadata", "metadata", true],
5277
+ ["parentId", "parent_id"],
5278
+ ["sourceId", "source_id"],
5279
+ ["context", "context", true],
5280
+ ["ownerType", "owner_type"],
5281
+ ["ownerId", "owner_id"],
5282
+ ["requireReview", "require_review"],
5283
+ ["dependencies", "dependencies", true],
5284
+ ["result", "result"],
5285
+ ["failureReason", "failure_reason"],
5286
+ ["workspaceId", "workspace_id"],
5287
+ ["projectId", "project_id"],
5288
+ ["files", "files", true]
5289
+ ];
5290
+ for (const [field, column, json] of fields) {
5291
+ const value = updates[field];
5292
+ if (value === void 0) continue;
5293
+ setClauses.push(`${column} = ?`);
5294
+ values.push(json && value !== null ? JSON.stringify(value) : field === "requireReview" ? value ? 1 : 0 : value);
5295
+ }
5296
+ values.push(
5297
+ tenantId,
5298
+ id,
5299
+ snapshot.status,
5300
+ expectedIso,
5301
+ snapshot.ownerType,
5302
+ snapshot.ownerId,
5303
+ snapshot.workspaceId,
5304
+ snapshot.projectId
5305
+ );
5306
+ const result = this.db.prepare(
5307
+ `UPDATE lt_tasks SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ? AND status = ?
5308
+ AND updated_at = ? AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
5309
+ ).run(...values);
5310
+ return result.changes === 0 ? null : this.getById(tenantId, id);
5311
+ }
4461
5312
  /** Atomically update a child only when both child and parent snapshots match. */
4462
5313
  async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
4463
5314
  if (expectedStatuses.length === 0) return null;
@@ -4558,11 +5409,36 @@ var LocalTaskStore = class {
4558
5409
  ).run(tenantId, id);
4559
5410
  return result.changes > 0;
4560
5411
  }
5412
+ /** Atomically delete only while status, timestamp, owner, and Project scope match. */
5413
+ async deleteIfSnapshot(tenantId, id, snapshot) {
5414
+ const result = this.db.prepare(
5415
+ `DELETE FROM lt_tasks WHERE tenant_id = ? AND id = ? AND status = ? AND updated_at = ?
5416
+ AND owner_type = ? AND owner_id = ? AND workspace_id IS ? AND project_id IS ?`
5417
+ ).run(
5418
+ tenantId,
5419
+ id,
5420
+ snapshot.status,
5421
+ new Date(snapshot.updatedAt).toISOString(),
5422
+ snapshot.ownerType,
5423
+ snapshot.ownerId,
5424
+ snapshot.workspaceId,
5425
+ snapshot.projectId
5426
+ );
5427
+ return result.changes > 0;
5428
+ }
4561
5429
  };
4562
5430
 
4563
5431
  // src/stores/LocalTaskWorkItemStore.ts
4564
- var import_protocols2 = require("@axiom-lattice/protocols");
5432
+ var import_protocols5 = require("@axiom-lattice/protocols");
4565
5433
  var import_uuid2 = require("uuid");
5434
+ var PROJECT_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
5435
+ "in_progress",
5436
+ "interrupted",
5437
+ "failed",
5438
+ "completed",
5439
+ "cancelled",
5440
+ "reassigned"
5441
+ ]);
4566
5442
  var DDL21 = `
4567
5443
  CREATE TABLE IF NOT EXISTS lt_task_work_items (
4568
5444
  id TEXT NOT NULL,
@@ -4580,6 +5456,15 @@ CREATE TABLE IF NOT EXISTS lt_task_work_items (
4580
5456
  PRIMARY KEY (tenant_id, id)
4581
5457
  );
4582
5458
  `;
5459
+ function parseDetail(raw) {
5460
+ if (!raw) return void 0;
5461
+ try {
5462
+ const parsed = JSON.parse(raw);
5463
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
5464
+ } catch {
5465
+ return void 0;
5466
+ }
5467
+ }
4583
5468
  function mapRowToWorkItem(row) {
4584
5469
  return {
4585
5470
  id: row.id,
@@ -4589,7 +5474,7 @@ function mapRowToWorkItem(row) {
4589
5474
  actor: row.actor,
4590
5475
  threadId: row.thread_id ?? void 0,
4591
5476
  summary: row.summary ?? void 0,
4592
- detail: row.detail ? JSON.parse(row.detail) : void 0,
5477
+ detail: parseDetail(row.detail),
4593
5478
  attempt: row.attempt ?? void 0,
4594
5479
  workspaceId: row.workspace_id ?? void 0,
4595
5480
  projectId: row.project_id ?? void 0,
@@ -4597,6 +5482,34 @@ function mapRowToWorkItem(row) {
4597
5482
  createdAt: parseISO(row.created_at)
4598
5483
  };
4599
5484
  }
5485
+ function assertAndCloneProjectLifecycleEvent(item) {
5486
+ let milliseconds;
5487
+ try {
5488
+ milliseconds = Date.prototype.getTime.call(item.createdAt);
5489
+ } catch {
5490
+ throw new Error("Invalid project lifecycle event row");
5491
+ }
5492
+ if (!Number.isFinite(milliseconds) || typeof item.id !== "string" || item.id.length === 0 || typeof item.eventKey !== "string" || item.eventKey.length === 0) {
5493
+ throw new Error("Invalid project lifecycle event row");
5494
+ }
5495
+ return { ...item, createdAt: new Date(milliseconds) };
5496
+ }
5497
+ function assertLifecyclePage(query) {
5498
+ if (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100 || query.actions.length === 0 || query.actions.some((action) => !PROJECT_LIFECYCLE_ACTIONS.has(action))) {
5499
+ throw new RangeError("Invalid project lifecycle event page");
5500
+ }
5501
+ if (!query.before) return null;
5502
+ let milliseconds;
5503
+ try {
5504
+ milliseconds = Date.prototype.getTime.call(query.before.createdAt);
5505
+ } catch {
5506
+ throw new RangeError("Invalid project lifecycle event cursor");
5507
+ }
5508
+ if (!Number.isFinite(milliseconds) || typeof query.before.id !== "string" || query.before.id.length === 0) {
5509
+ throw new RangeError("Invalid project lifecycle event cursor");
5510
+ }
5511
+ return milliseconds;
5512
+ }
4600
5513
  var LocalTaskWorkItemStore = class {
4601
5514
  constructor(db) {
4602
5515
  this.db = db;
@@ -4611,12 +5524,15 @@ var LocalTaskWorkItemStore = class {
4611
5524
  this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
4612
5525
  ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
4613
5526
  WHERE action = 'execution_reconciled';`);
5527
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_project_lifecycle
5528
+ ON lt_task_work_items (tenant_id, workspace_id, project_id, created_at DESC, id DESC)
5529
+ WHERE event_key IS NOT NULL;`);
4614
5530
  }
4615
5531
  /** Add a column if it does not exist (SQLite version compatible). */
4616
- ensureColumn(table, column, ddl) {
5532
+ ensureColumn(table, column, ddl2) {
4617
5533
  const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
4618
5534
  if (!cols.some((c) => c.name === column)) {
4619
- this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
5535
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl2}`);
4620
5536
  }
4621
5537
  }
4622
5538
  async create(params) {
@@ -4644,6 +5560,41 @@ var LocalTaskWorkItemStore = class {
4644
5560
  ).get(params.tenantId, id);
4645
5561
  return mapRowToWorkItem(row);
4646
5562
  }
5563
+ /** Atomically inserts a work item by selecting one exact task snapshot. */
5564
+ async createIfTaskSnapshot(params, snapshot) {
5565
+ const id = (0, import_uuid2.v4)();
5566
+ const result = this.db.prepare(
5567
+ `INSERT INTO lt_task_work_items
5568
+ (id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
5569
+ SELECT ?, task.tenant_id, task.id, ?, ?, ?, ?, ?, ?, task.workspace_id, task.project_id, ?
5570
+ FROM lt_tasks AS task
5571
+ WHERE task.tenant_id = ? AND task.id = ? AND task.status = ? AND task.updated_at = ?
5572
+ AND task.owner_type = ? AND task.owner_id = ? AND task.workspace_id IS ? AND task.project_id IS ?
5573
+ AND ? IS task.workspace_id AND ? IS task.project_id`
5574
+ ).run(
5575
+ id,
5576
+ params.action,
5577
+ params.actor,
5578
+ params.threadId || null,
5579
+ params.summary || null,
5580
+ params.detail ? JSON.stringify(params.detail) : null,
5581
+ params.attempt ?? null,
5582
+ nowISO(),
5583
+ params.tenantId,
5584
+ params.taskId,
5585
+ snapshot.status,
5586
+ new Date(snapshot.updatedAt).toISOString(),
5587
+ snapshot.ownerType,
5588
+ snapshot.ownerId,
5589
+ snapshot.workspaceId,
5590
+ snapshot.projectId,
5591
+ params.workspaceId,
5592
+ params.projectId
5593
+ );
5594
+ if (result.changes === 0) return null;
5595
+ const row = this.db.prepare(`SELECT * FROM lt_task_work_items WHERE tenant_id = ? AND id = ?`).get(params.tenantId, id);
5596
+ return row ? mapRowToWorkItem(row) : null;
5597
+ }
4647
5598
  /** Find an event by its tenant- and task-scoped key without pagination. */
4648
5599
  async findByEventKey(tenantId, taskId, eventKey) {
4649
5600
  const row = this.db.prepare(
@@ -4696,20 +5647,20 @@ var LocalTaskWorkItemStore = class {
4696
5647
  const limit = filter.limit || 100;
4697
5648
  const offset = filter.offset || 0;
4698
5649
  const order = filter.order === "asc" ? "ASC" : "DESC";
4699
- const rows = this.db.prepare(
5650
+ const rows2 = this.db.prepare(
4700
5651
  `SELECT * FROM lt_task_work_items WHERE ${where} ORDER BY created_at ${order}, id ${order} LIMIT ? OFFSET ?`
4701
5652
  ).all(...params, limit, offset);
4702
- return rows.map(mapRowToWorkItem);
5653
+ return rows2.map(mapRowToWorkItem);
4703
5654
  }
4704
5655
  /** List pending execution results using one bounded SQLite anti-join query. */
4705
5656
  async listPendingExecutionResults(params) {
4706
- if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > import_protocols2.MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
4707
- const error = new RangeError(`limit must be a safe integer between 0 and ${import_protocols2.MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
5657
+ if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > import_protocols5.MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
5658
+ const error = new RangeError(`limit must be a safe integer between 0 and ${import_protocols5.MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
4708
5659
  error.code = "INVALID_LIMIT";
4709
5660
  throw error;
4710
5661
  }
4711
5662
  if (params.limit === 0) return [];
4712
- const rows = this.db.prepare(
5663
+ const rows2 = this.db.prepare(
4713
5664
  `SELECT result.*
4714
5665
  FROM lt_task_work_items AS result
4715
5666
  WHERE result.tenant_id = ?
@@ -4729,7 +5680,31 @@ var LocalTaskWorkItemStore = class {
4729
5680
  ORDER BY result.created_at DESC, result.id DESC
4730
5681
  LIMIT ?`
4731
5682
  ).all(params.tenantId, params.taskId, params.limit);
4732
- return rows.map(mapRowToWorkItem);
5683
+ return rows2.map(mapRowToWorkItem);
5684
+ }
5685
+ /** Lists canonical project lifecycle events with an exclusive cursor. */
5686
+ async listProjectLifecycleEvents(query) {
5687
+ const cursorMilliseconds = assertLifecyclePage(query);
5688
+ const cursor = cursorMilliseconds === null ? null : new Date(cursorMilliseconds).toISOString();
5689
+ const rows2 = this.db.prepare(
5690
+ `SELECT * FROM lt_task_work_items
5691
+ WHERE tenant_id = ? AND workspace_id = ? AND project_id = ?
5692
+ AND action IN (${query.actions.map(() => "?").join(", ")})
5693
+ AND event_key IS NOT NULL AND event_key <> ''
5694
+ AND (? IS NULL OR created_at < ? OR (created_at = ? AND id < ?))
5695
+ ORDER BY created_at DESC, id DESC LIMIT ?`
5696
+ ).all(
5697
+ query.tenantId,
5698
+ query.workspaceId,
5699
+ query.projectId,
5700
+ ...query.actions,
5701
+ cursor,
5702
+ cursor,
5703
+ cursor,
5704
+ query.before?.id ?? null,
5705
+ query.limit
5706
+ );
5707
+ return rows2.map(mapRowToWorkItem).map(assertAndCloneProjectLifecycleEvent);
4733
5708
  }
4734
5709
  };
4735
5710
 
@@ -4838,6 +5813,10 @@ async function createLocalStoreConfig(options = {}) {
4838
5813
  assistant: new LocalAssistantStore(db),
4839
5814
  workspace: new LocalWorkspaceStore(db),
4840
5815
  project: new LocalProjectStore(db),
5816
+ projectRoom: new LocalProjectRoomStore(db),
5817
+ projectMembership: new LocalProjectMembershipStore(db),
5818
+ projectBotMembership: new LocalProjectBotMembershipStore(db),
5819
+ projectRoomMessage: new LocalProjectRoomMessageStore(db),
4841
5820
  user: new LocalUserStore(db),
4842
5821
  tenant: new LocalTenantStore(db),
4843
5822
  userTenantLink: new LocalUserTenantLinkStore(db),
@@ -4871,7 +5850,7 @@ var InMemoryConversationStore = class {
4871
5850
  }
4872
5851
  async createConversation(input) {
4873
5852
  const now = isoNow();
4874
- const record = {
5853
+ const record2 = {
4875
5854
  conversationId: input.conversationId,
4876
5855
  tenantId: input.tenantId,
4877
5856
  taskIds: [],
@@ -4880,17 +5859,17 @@ var InMemoryConversationStore = class {
4880
5859
  createdAt: now,
4881
5860
  updatedAt: now
4882
5861
  };
4883
- this.store.set(input.conversationId, record);
4884
- return record;
5862
+ this.store.set(input.conversationId, record2);
5863
+ return record2;
4885
5864
  }
4886
5865
  async addTaskToConversation(conversationId, taskId) {
4887
- const record = this.store.get(conversationId);
4888
- if (!record) {
5866
+ const record2 = this.store.get(conversationId);
5867
+ if (!record2) {
4889
5868
  throw new Error(`Conversation not found: ${conversationId}`);
4890
5869
  }
4891
- record.taskIds.push(taskId);
4892
- record.updatedAt = isoNow();
4893
- return record;
5870
+ record2.taskIds.push(taskId);
5871
+ record2.updatedAt = isoNow();
5872
+ return record2;
4894
5873
  }
4895
5874
  async deleteConversation(conversationId) {
4896
5875
  this.store.delete(conversationId);
@@ -4899,6 +5878,8 @@ var InMemoryConversationStore = class {
4899
5878
  // Annotate the CommonJS export names for ESM import in node:
4900
5879
  0 && (module.exports = {
4901
5880
  DatabaseWrapper,
5881
+ DuplicateProjectMembershipError,
5882
+ DuplicateProjectRoomMessageIdempotencyKeyError,
4902
5883
  InMemoryConversationStore,
4903
5884
  LocalA2AApiKeyStore,
4904
5885
  LocalAssistantStore,
@@ -4910,6 +5891,10 @@ var InMemoryConversationStore = class {
4910
5891
  LocalEvalStore,
4911
5892
  LocalMcpServerConfigStore,
4912
5893
  LocalMetricsServerConfigStore,
5894
+ LocalProjectBotMembershipStore,
5895
+ LocalProjectMembershipStore,
5896
+ LocalProjectRoomMessageStore,
5897
+ LocalProjectRoomStore,
4913
5898
  LocalProjectStore,
4914
5899
  LocalScheduleStorage,
4915
5900
  LocalSkillStore,
@@ -4923,6 +5908,9 @@ var InMemoryConversationStore = class {
4923
5908
  LocalWorkflowTrackingStore,
4924
5909
  LocalWorkspaceStore,
4925
5910
  MigrationManager,
5911
+ ProjectBotMembershipIdConflictError,
5912
+ ProjectMembershipIdConflictError,
5913
+ ProjectRoomMessageIdConflictError,
4926
5914
  RunResult,
4927
5915
  StatementWrapper,
4928
5916
  closeDatabase,