@axiom-lattice/local-stores 3.1.0 → 3.1.2

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