@axiom-lattice/local-stores 3.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  InMemoryConversationStore: () => InMemoryConversationStore,
35
35
  LocalA2AApiKeyStore: () => LocalA2AApiKeyStore,
36
36
  LocalAssistantStore: () => LocalAssistantStore,
37
+ LocalCapabilityBundleStore: () => LocalCapabilityBundleStore,
37
38
  LocalChannelBindingStore: () => LocalChannelBindingStore,
38
39
  LocalChannelInstallationStore: () => LocalChannelInstallationStore,
39
40
  LocalConnectionStore: () => LocalConnectionStore,
@@ -656,6 +657,7 @@ function mapRowToWorkspace(row) {
656
657
  }
657
658
 
658
659
  // src/stores/LocalProjectStore.ts
660
+ var import_protocols = require("@axiom-lattice/protocols");
659
661
  var DDL4 = `
660
662
  CREATE TABLE IF NOT EXISTS lt_projects (
661
663
  id TEXT NOT NULL,
@@ -696,6 +698,7 @@ var LocalProjectStore = class {
696
698
  return row ? mapRowToProject(row) : null;
697
699
  }
698
700
  async createProject(tenantId, workspaceId, id, data) {
701
+ (0, import_protocols.assertGenericProjectConfig)(data.config);
699
702
  const now = nowISO();
700
703
  const kind = data.kind || "business";
701
704
  this.db.prepare(
@@ -705,23 +708,18 @@ var LocalProjectStore = class {
705
708
  workspace_id = excluded.workspace_id,
706
709
  name = excluded.name,
707
710
  description = excluded.description,
708
- config = excluded.config,
711
+ config = CASE
712
+ WHEN json_type(lt_projects.config, '$.capabilityBundleIds') IS NOT NULL
713
+ THEN json_set(COALESCE(excluded.config, '{}'), '$.capabilityBundleIds', json_extract(lt_projects.config, '$.capabilityBundleIds'))
714
+ ELSE excluded.config
715
+ END,
709
716
  kind = excluded.kind,
710
717
  updated_at = excluded.updated_at`
711
718
  ).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
- };
719
+ return await this.getProjectById(tenantId, id);
723
720
  }
724
721
  async updateProject(tenantId, id, updates) {
722
+ (0, import_protocols.assertGenericProjectConfig)(updates.config);
725
723
  const existing = await this.getProjectById(tenantId, id);
726
724
  if (!existing) return null;
727
725
  const setClauses = [];
@@ -735,8 +733,13 @@ var LocalProjectStore = class {
735
733
  values.push(updates.description || null);
736
734
  }
737
735
  if (updates.config !== void 0) {
738
- setClauses.push("config = ?");
739
- values.push(updates.config ? JSON.stringify(updates.config) : null);
736
+ setClauses.push(`config = CASE
737
+ WHEN json_type(config, '$.capabilityBundleIds') IS NOT NULL
738
+ THEN json_set(json(?), '$.capabilityBundleIds', json_extract(config, '$.capabilityBundleIds'))
739
+ ELSE ?
740
+ END`);
741
+ const config = JSON.stringify(updates.config ?? {});
742
+ values.push(config, config);
740
743
  }
741
744
  if (updates.kind !== void 0) {
742
745
  setClauses.push("kind = ?");
@@ -758,6 +761,52 @@ var LocalProjectStore = class {
758
761
  ).run(tenantId, id);
759
762
  return result.changes > 0;
760
763
  }
764
+ async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
765
+ const now = nowISO();
766
+ const result = this.db.prepare(
767
+ `UPDATE lt_projects
768
+ SET config = json_set(COALESCE(config, '{}'), '$.capabilityBundleIds', json(?)), updated_at = ?
769
+ WHERE tenant_id = ? AND id = ?
770
+ AND NOT EXISTS (
771
+ SELECT 1 FROM json_each(?) AS requested
772
+ WHERE NOT EXISTS (
773
+ SELECT 1 FROM lt_capability_bundles
774
+ WHERE tenant_id = ? AND id = requested.value
775
+ )
776
+ )
777
+ AND NOT EXISTS (
778
+ SELECT 1 FROM json_each(?) AS expected
779
+ WHERE EXISTS (
780
+ SELECT 1 FROM lt_capability_bundles
781
+ WHERE tenant_id = ? AND id = expected.key AND updated_at <> expected.value
782
+ )
783
+ )`
784
+ ).run(JSON.stringify(bundleIds), now, tenantId, projectId, JSON.stringify(bundleIds), tenantId, JSON.stringify(expectedRevisions), tenantId);
785
+ if (result.changes > 0) {
786
+ const project = await this.getProjectById(tenantId, projectId);
787
+ return project ? { status: "updated", project } : { status: "project_not_found" };
788
+ }
789
+ const revisionMismatch = this.db.prepare(
790
+ "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"
791
+ ).get(tenantId, JSON.stringify(expectedRevisions), JSON.stringify(expectedRevisions));
792
+ if (revisionMismatch) return { status: "bundle_conflict" };
793
+ return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
794
+ }
795
+ async isCapabilityBundleReferenced(tenantId, bundleId) {
796
+ const rows = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
797
+ return rows.some((row) => {
798
+ if (!row.config) return false;
799
+ let config;
800
+ try {
801
+ config = JSON.parse(row.config);
802
+ } catch {
803
+ return false;
804
+ }
805
+ if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
806
+ const ids = config.capabilityBundleIds;
807
+ return Array.isArray(ids) && ids.every((id) => typeof id === "string") && ids.includes(bundleId);
808
+ });
809
+ }
761
810
  /** Add a column if it does not exist (SQLite version compatible). */
762
811
  ensureColumn(table, column, ddl) {
763
812
  const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
@@ -2236,8 +2285,34 @@ var LocalEvalStore = class {
2236
2285
  return this.getProjectById(tenantId, id);
2237
2286
  }
2238
2287
  async deleteProject(tenantId, id) {
2239
- const result = this.db.prepare(`DELETE FROM lt_eval_projects WHERE tenant_id = ? AND id = ?`).run(tenantId, id);
2240
- return result.changes > 0;
2288
+ const db = this.db.getRawDb();
2289
+ db.run("BEGIN TRANSACTION");
2290
+ try {
2291
+ db.run(
2292
+ `DELETE FROM lt_eval_run_results
2293
+ WHERE run_id IN (
2294
+ SELECT id FROM lt_eval_runs WHERE tenant_id = ? AND project_id = ?
2295
+ )`,
2296
+ [tenantId, id]
2297
+ );
2298
+ db.run(
2299
+ `DELETE FROM lt_eval_cases
2300
+ WHERE tenant_id = ? AND suite_id IN (
2301
+ SELECT id FROM lt_eval_suites WHERE tenant_id = ? AND project_id = ?
2302
+ )`,
2303
+ [tenantId, tenantId, id]
2304
+ );
2305
+ db.run(`DELETE FROM lt_eval_runs WHERE tenant_id = ? AND project_id = ?`, [tenantId, id]);
2306
+ db.run(`DELETE FROM lt_eval_suites WHERE tenant_id = ? AND project_id = ?`, [tenantId, id]);
2307
+ db.run(`DELETE FROM lt_eval_projects WHERE tenant_id = ? AND id = ?`, [tenantId, id]);
2308
+ const deleted = db.getRowsModified() > 0;
2309
+ db.run("COMMIT");
2310
+ this.db.save();
2311
+ return deleted;
2312
+ } catch (error) {
2313
+ db.run("ROLLBACK");
2314
+ throw error;
2315
+ }
2241
2316
  }
2242
2317
  // -------------------------------------------------------------------------
2243
2318
  // Suites
@@ -3097,11 +3172,11 @@ var LocalA2AApiKeyStore = class {
3097
3172
  const rows = this.db.prepare(
3098
3173
  `SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
3099
3174
  ).all();
3100
- const map = /* @__PURE__ */ new Map();
3175
+ const map2 = /* @__PURE__ */ new Map();
3101
3176
  for (const row of rows) {
3102
3177
  try {
3103
3178
  const key = (0, import_core3.decrypt)(row.key_value);
3104
- map.set(key, {
3179
+ map2.set(key, {
3105
3180
  key,
3106
3181
  tenantId: row.tenant_id,
3107
3182
  projectId: row.project_id,
@@ -3110,7 +3185,7 @@ var LocalA2AApiKeyStore = class {
3110
3185
  } catch {
3111
3186
  }
3112
3187
  }
3113
- return map;
3188
+ return map2;
3114
3189
  }
3115
3190
  };
3116
3191
  function mapRowToRecord(row) {
@@ -3157,6 +3232,7 @@ CREATE INDEX IF NOT EXISTS idx_lt_tmq_pending ON lt_thread_message_queue(status,
3157
3232
  `;
3158
3233
  var LocalThreadMessageQueueStore = class {
3159
3234
  constructor(db) {
3235
+ this.capacityTail = Promise.resolve();
3160
3236
  this.db = db;
3161
3237
  ensureTable(db, DDL17);
3162
3238
  }
@@ -3175,18 +3251,65 @@ var LocalThreadMessageQueueStore = class {
3175
3251
  params.threadId,
3176
3252
  params.tenantId,
3177
3253
  params.assistantId,
3178
- params.workspaceId || null,
3179
- params.projectId || null,
3254
+ params.workspaceId ?? null,
3255
+ params.projectId ?? null,
3180
3256
  JSON.stringify(params.content),
3181
3257
  params.type || "human",
3182
3258
  nextSeq,
3183
- params.priority || 0,
3259
+ params.priority ?? 0,
3184
3260
  params.command ? JSON.stringify(params.command) : null,
3185
3261
  params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
3186
3262
  now
3187
3263
  );
3188
3264
  return this.getById(id);
3189
3265
  }
3266
+ async addMessageIfCapacity(params, maxSize) {
3267
+ let release;
3268
+ const previous = this.capacityTail;
3269
+ this.capacityTail = new Promise((resolve) => {
3270
+ release = resolve;
3271
+ });
3272
+ await previous;
3273
+ try {
3274
+ const scope = {
3275
+ tenantId: params.tenantId,
3276
+ assistantId: params.assistantId,
3277
+ workspaceId: params.workspaceId,
3278
+ projectId: params.projectId
3279
+ };
3280
+ const filter = scopeClause(scope);
3281
+ const id = params.id || (0, import_crypto4.randomUUID)();
3282
+ const limit = maxSize === Infinity ? -1 : maxSize;
3283
+ const result = this.db.prepare(
3284
+ `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
3285
+ SELECT ?, ?, ?, ?, ?, ?, ?, ?,
3286
+ (SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
3287
+ ?, ?, ?, ?
3288
+ WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
3289
+ ).run(
3290
+ id,
3291
+ params.threadId,
3292
+ params.tenantId,
3293
+ params.assistantId,
3294
+ params.workspaceId ?? null,
3295
+ params.projectId ?? null,
3296
+ JSON.stringify(params.content),
3297
+ params.type || "human",
3298
+ params.threadId,
3299
+ params.priority ?? 0,
3300
+ params.command ? JSON.stringify(params.command) : null,
3301
+ params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
3302
+ nowISO(),
3303
+ limit,
3304
+ params.threadId,
3305
+ ...filter.params,
3306
+ limit
3307
+ );
3308
+ return result.changes > 0;
3309
+ } finally {
3310
+ release();
3311
+ }
3312
+ }
3190
3313
  async addMessageAtHead(params) {
3191
3314
  const now = nowISO();
3192
3315
  const id = params.id || (0, import_crypto4.randomUUID)();
@@ -3201,8 +3324,8 @@ var LocalThreadMessageQueueStore = class {
3201
3324
  params.threadId,
3202
3325
  params.tenantId,
3203
3326
  params.assistantId,
3204
- params.workspaceId || null,
3205
- params.projectId || null,
3327
+ params.workspaceId ?? null,
3328
+ params.projectId ?? null,
3206
3329
  JSON.stringify(params.content),
3207
3330
  params.type || "human",
3208
3331
  seqRow.next_seq,
@@ -3212,50 +3335,63 @@ var LocalThreadMessageQueueStore = class {
3212
3335
  );
3213
3336
  return this.getById(id);
3214
3337
  }
3215
- async getPendingMessages(threadId) {
3338
+ async getPendingMessages(threadId, scope) {
3339
+ const filter = scopeClause(scope);
3216
3340
  const rows = this.db.prepare(
3217
- `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending' ORDER BY priority DESC, sequence_order ASC`
3218
- ).all(threadId);
3341
+ `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
3342
+ ).all(threadId, ...filter.params);
3219
3343
  return rows.map(rowToMessage);
3220
3344
  }
3221
- async getProcessingMessages(threadId) {
3345
+ async getProcessingMessages(threadId, scope) {
3346
+ const filter = scopeClause(scope);
3222
3347
  const rows = this.db.prepare(
3223
- `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing' ORDER BY priority DESC, sequence_order ASC`
3224
- ).all(threadId);
3348
+ `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
3349
+ ).all(threadId, ...filter.params);
3225
3350
  return rows.map(rowToMessage);
3226
3351
  }
3227
- async getQueueSize(threadId) {
3352
+ async getQueueSize(threadId, scope) {
3353
+ const filter = scopeClause(scope);
3228
3354
  const row = this.db.prepare(
3229
- `SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'`
3230
- ).get(threadId);
3355
+ `SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}`
3356
+ ).get(threadId, ...filter.params);
3231
3357
  return row.count;
3232
3358
  }
3233
3359
  async getThreadsWithPendingMessages() {
3234
3360
  const rows = this.db.prepare(
3235
- `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`
3361
+ `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`
3236
3362
  ).all();
3237
3363
  return rows.map((r) => ({
3238
3364
  tenantId: r.tenant_id,
3239
3365
  assistantId: r.assistant_id,
3240
3366
  threadId: r.thread_id,
3241
- workspaceId: r.workspace_id || void 0,
3242
- projectId: r.project_id || void 0
3367
+ workspaceId: r.workspace_id,
3368
+ projectId: r.project_id
3243
3369
  }));
3244
3370
  }
3245
- async removeMessage(messageId) {
3246
- const result = this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE id = ?`).run(messageId);
3371
+ async removeMessage(messageId, scope) {
3372
+ const filter = scopeClause(scope);
3373
+ const result = this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
3247
3374
  return result.changes > 0;
3248
3375
  }
3249
- async clearMessages(threadId) {
3250
- this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE thread_id = ?`).run(threadId);
3376
+ async clearMessages(threadId, scope) {
3377
+ const filter = scopeClause(scope);
3378
+ this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE thread_id = ?${filter.sql}`).run(threadId, ...filter.params);
3251
3379
  }
3252
- async markProcessing(messageId) {
3253
- this.db.prepare(`UPDATE lt_thread_message_queue SET status = 'processing' WHERE id = ?`).run(messageId);
3380
+ async markProcessing(messageId, customRunConfig, scope) {
3381
+ const filter = scopeClause(scope);
3382
+ if (customRunConfig === void 0) {
3383
+ this.db.prepare(`UPDATE lt_thread_message_queue SET status = 'processing' WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
3384
+ return;
3385
+ }
3386
+ this.db.prepare(
3387
+ `UPDATE lt_thread_message_queue SET status = 'processing', custom_run_config = ? WHERE id = ?${filter.sql}`
3388
+ ).run(JSON.stringify(customRunConfig), messageId, ...filter.params);
3254
3389
  }
3255
- async resetProcessingToPending(threadId) {
3390
+ async resetProcessingToPending(threadId, scope) {
3391
+ const filter = scopeClause(scope);
3256
3392
  const result = this.db.prepare(
3257
- `UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'`
3258
- ).run(threadId);
3393
+ `UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
3394
+ ).run(threadId, ...filter.params);
3259
3395
  return result.changes;
3260
3396
  }
3261
3397
  getById(id) {
@@ -3263,6 +3399,14 @@ var LocalThreadMessageQueueStore = class {
3263
3399
  return rowToMessage(row);
3264
3400
  }
3265
3401
  };
3402
+ function scopeClause(scope) {
3403
+ if (!scope) return { sql: "", params: [] };
3404
+ const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
3405
+ return {
3406
+ sql: entries.map(([key, value]) => ` AND ${key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)} ${value == null ? "IS NULL" : "= ?"}`).join(""),
3407
+ params: entries.flatMap(([, value]) => value == null ? [] : [value])
3408
+ };
3409
+ }
3266
3410
  function rowToMessage(row) {
3267
3411
  return {
3268
3412
  id: row.id,
@@ -4417,6 +4561,7 @@ var LocalTaskStore = class {
4417
4561
  };
4418
4562
 
4419
4563
  // src/stores/LocalTaskWorkItemStore.ts
4564
+ var import_protocols2 = require("@axiom-lattice/protocols");
4420
4565
  var import_uuid2 = require("uuid");
4421
4566
  var DDL21 = `
4422
4567
  CREATE TABLE IF NOT EXISTS lt_task_work_items (
@@ -4461,6 +4606,11 @@ var LocalTaskWorkItemStore = class {
4461
4606
  this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
4462
4607
  this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
4463
4608
  ON lt_task_work_items (tenant_id, task_id, event_key);`);
4609
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
4610
+ ON lt_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC);`);
4611
+ this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
4612
+ ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
4613
+ WHERE action = 'execution_reconciled';`);
4464
4614
  }
4465
4615
  /** Add a column if it does not exist (SQLite version compatible). */
4466
4616
  ensureColumn(table, column, ddl) {
@@ -4551,6 +4701,132 @@ var LocalTaskWorkItemStore = class {
4551
4701
  ).all(...params, limit, offset);
4552
4702
  return rows.map(mapRowToWorkItem);
4553
4703
  }
4704
+ /** List pending execution results using one bounded SQLite anti-join query. */
4705
+ async listPendingExecutionResults(params) {
4706
+ if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > import_protocols2.MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
4707
+ const error = new RangeError(`limit must be a safe integer between 0 and ${import_protocols2.MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
4708
+ error.code = "INVALID_LIMIT";
4709
+ throw error;
4710
+ }
4711
+ if (params.limit === 0) return [];
4712
+ const rows = this.db.prepare(
4713
+ `SELECT result.*
4714
+ FROM lt_task_work_items AS result
4715
+ WHERE result.tenant_id = ?
4716
+ AND result.task_id = ?
4717
+ AND result.action = 'execution_result'
4718
+ AND substr(result.event_key, 1, 17) = 'execution-result:'
4719
+ AND length(result.event_key) > 17
4720
+ AND result.event_key NOT GLOB '*[^A-Za-z0-9._:-]*'
4721
+ AND NOT EXISTS (
4722
+ SELECT 1
4723
+ FROM lt_task_work_items AS reconciled
4724
+ WHERE reconciled.tenant_id = result.tenant_id
4725
+ AND reconciled.task_id = result.task_id
4726
+ AND reconciled.action = 'execution_reconciled'
4727
+ AND json_extract(reconciled.detail, '$.executionResultId') = result.event_key
4728
+ )
4729
+ ORDER BY result.created_at DESC, result.id DESC
4730
+ LIMIT ?`
4731
+ ).all(params.tenantId, params.taskId, params.limit);
4732
+ return rows.map(mapRowToWorkItem);
4733
+ }
4734
+ };
4735
+
4736
+ // src/stores/LocalCapabilityBundleStore.ts
4737
+ var import_crypto6 = require("crypto");
4738
+ 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);`;
4739
+ var duplicateMessage = "Capability bundle key already exists for tenant";
4740
+ function map(row) {
4741
+ 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() };
4742
+ }
4743
+ function isConstraint(error) {
4744
+ return error instanceof Error && /UNIQUE constraint failed/.test(error.message);
4745
+ }
4746
+ function nextTimestamp(previous) {
4747
+ const now = Date.now();
4748
+ const previousTime = previous ? Date.parse(previous) : Number.NaN;
4749
+ return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
4750
+ }
4751
+ var LocalCapabilityBundleStore = class {
4752
+ /**
4753
+ * Creates a capability bundle store over an initialized sql.js database.
4754
+ *
4755
+ * @param db - Shared local database wrapper used for persistence.
4756
+ */
4757
+ constructor(db) {
4758
+ this.db = db;
4759
+ ensureTable(db, DDL22);
4760
+ }
4761
+ async listByTenant(tenantId) {
4762
+ return this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? ORDER BY created_at").all(tenantId).map(map);
4763
+ }
4764
+ async getById(tenantId, id) {
4765
+ const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
4766
+ return row ? map(row) : null;
4767
+ }
4768
+ async getManyByIds(tenantId, ids) {
4769
+ return ids.flatMap((id) => {
4770
+ const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
4771
+ return row ? [map(row)] : [];
4772
+ });
4773
+ }
4774
+ async create(tenantId, input) {
4775
+ const now = nextTimestamp();
4776
+ const id = (0, import_crypto6.randomUUID)();
4777
+ try {
4778
+ 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);
4779
+ } catch (error) {
4780
+ if (isConstraint(error)) throw new Error(duplicateMessage);
4781
+ throw error;
4782
+ }
4783
+ return await this.getById(tenantId, id);
4784
+ }
4785
+ async update(tenantId, id, input) {
4786
+ const existing = await this.getById(tenantId, id);
4787
+ if (!existing) return null;
4788
+ if (input.expectedUpdatedAt !== void 0 && input.expectedUpdatedAt !== existing.updatedAt) return { status: "conflict" };
4789
+ const fields = [];
4790
+ const values = [];
4791
+ const add = (field, value) => {
4792
+ fields.push(`${field} = ?`);
4793
+ values.push(value);
4794
+ };
4795
+ if (input.name !== void 0) add("name", input.name);
4796
+ if (Object.prototype.hasOwnProperty.call(input, "description")) add("description", input.description ?? null);
4797
+ if (input.capabilities !== void 0) add("capabilities", JSON.stringify(input.capabilities));
4798
+ if (!fields.length) return existing;
4799
+ const now = nextTimestamp(existing.updatedAt);
4800
+ fields.push("updated_at = ?");
4801
+ values.push(now, tenantId, id);
4802
+ try {
4803
+ if (input.expectedUpdatedAt !== void 0) {
4804
+ const result = this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at = ?`).run(...values, input.expectedUpdatedAt);
4805
+ if (result.changes === 0) return { status: "conflict" };
4806
+ } else {
4807
+ this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ?`).run(...values);
4808
+ }
4809
+ } catch (error) {
4810
+ if (isConstraint(error)) throw new Error(duplicateMessage);
4811
+ throw error;
4812
+ }
4813
+ return this.getById(tenantId, id);
4814
+ }
4815
+ async deleteIfUnreferenced(tenantId, id) {
4816
+ const result = this.db.prepare(
4817
+ `DELETE FROM lt_capability_bundles
4818
+ WHERE tenant_id = ? AND id = ?
4819
+ AND NOT EXISTS (
4820
+ SELECT 1 FROM lt_projects
4821
+ WHERE tenant_id = ?
4822
+ AND json_type(config, '$.capabilityBundleIds') = 'array'
4823
+ AND EXISTS (SELECT 1 FROM json_each(config, '$.capabilityBundleIds') WHERE value = ?)
4824
+ )`
4825
+ ).run(tenantId, id, tenantId, id);
4826
+ if (result.changes > 0) return "deleted";
4827
+ if (await this.getById(tenantId, id)) return "in_use";
4828
+ return "not_found";
4829
+ }
4554
4830
  };
4555
4831
 
4556
4832
  // src/createLocalStoreConfig.ts
@@ -4579,6 +4855,7 @@ async function createLocalStoreConfig(options = {}) {
4579
4855
  schedule: new LocalScheduleStorage(db),
4580
4856
  task: new LocalTaskStore(db),
4581
4857
  taskWorkItem: new LocalTaskWorkItemStore(db),
4858
+ capabilityBundle: new LocalCapabilityBundleStore(db),
4582
4859
  checkpoint: import_langgraph_checkpoint_sqlite.SqliteSaver.fromConnString(dbPath)
4583
4860
  };
4584
4861
  }
@@ -4625,6 +4902,7 @@ var InMemoryConversationStore = class {
4625
4902
  InMemoryConversationStore,
4626
4903
  LocalA2AApiKeyStore,
4627
4904
  LocalAssistantStore,
4905
+ LocalCapabilityBundleStore,
4628
4906
  LocalChannelBindingStore,
4629
4907
  LocalChannelInstallationStore,
4630
4908
  LocalConnectionStore,