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