@axiom-lattice/local-stores 3.1.0 → 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();
@@ -3123,11 +3172,11 @@ var LocalA2AApiKeyStore = class {
3123
3172
  const rows = this.db.prepare(
3124
3173
  `SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
3125
3174
  ).all();
3126
- const map = /* @__PURE__ */ new Map();
3175
+ const map2 = /* @__PURE__ */ new Map();
3127
3176
  for (const row of rows) {
3128
3177
  try {
3129
3178
  const key = (0, import_core3.decrypt)(row.key_value);
3130
- map.set(key, {
3179
+ map2.set(key, {
3131
3180
  key,
3132
3181
  tenantId: row.tenant_id,
3133
3182
  projectId: row.project_id,
@@ -3136,7 +3185,7 @@ var LocalA2AApiKeyStore = class {
3136
3185
  } catch {
3137
3186
  }
3138
3187
  }
3139
- return map;
3188
+ return map2;
3140
3189
  }
3141
3190
  };
3142
3191
  function mapRowToRecord(row) {
@@ -3183,6 +3232,7 @@ CREATE INDEX IF NOT EXISTS idx_lt_tmq_pending ON lt_thread_message_queue(status,
3183
3232
  `;
3184
3233
  var LocalThreadMessageQueueStore = class {
3185
3234
  constructor(db) {
3235
+ this.capacityTail = Promise.resolve();
3186
3236
  this.db = db;
3187
3237
  ensureTable(db, DDL17);
3188
3238
  }
@@ -3201,18 +3251,65 @@ var LocalThreadMessageQueueStore = class {
3201
3251
  params.threadId,
3202
3252
  params.tenantId,
3203
3253
  params.assistantId,
3204
- params.workspaceId || null,
3205
- params.projectId || null,
3254
+ params.workspaceId ?? null,
3255
+ params.projectId ?? null,
3206
3256
  JSON.stringify(params.content),
3207
3257
  params.type || "human",
3208
3258
  nextSeq,
3209
- params.priority || 0,
3259
+ params.priority ?? 0,
3210
3260
  params.command ? JSON.stringify(params.command) : null,
3211
3261
  params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
3212
3262
  now
3213
3263
  );
3214
3264
  return this.getById(id);
3215
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
+ }
3216
3313
  async addMessageAtHead(params) {
3217
3314
  const now = nowISO();
3218
3315
  const id = params.id || (0, import_crypto4.randomUUID)();
@@ -3227,8 +3324,8 @@ var LocalThreadMessageQueueStore = class {
3227
3324
  params.threadId,
3228
3325
  params.tenantId,
3229
3326
  params.assistantId,
3230
- params.workspaceId || null,
3231
- params.projectId || null,
3327
+ params.workspaceId ?? null,
3328
+ params.projectId ?? null,
3232
3329
  JSON.stringify(params.content),
3233
3330
  params.type || "human",
3234
3331
  seqRow.next_seq,
@@ -3238,50 +3335,63 @@ var LocalThreadMessageQueueStore = class {
3238
3335
  );
3239
3336
  return this.getById(id);
3240
3337
  }
3241
- async getPendingMessages(threadId) {
3338
+ async getPendingMessages(threadId, scope) {
3339
+ const filter = scopeClause(scope);
3242
3340
  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);
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);
3245
3343
  return rows.map(rowToMessage);
3246
3344
  }
3247
- async getProcessingMessages(threadId) {
3345
+ async getProcessingMessages(threadId, scope) {
3346
+ const filter = scopeClause(scope);
3248
3347
  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);
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);
3251
3350
  return rows.map(rowToMessage);
3252
3351
  }
3253
- async getQueueSize(threadId) {
3352
+ async getQueueSize(threadId, scope) {
3353
+ const filter = scopeClause(scope);
3254
3354
  const row = this.db.prepare(
3255
- `SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'`
3256
- ).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);
3257
3357
  return row.count;
3258
3358
  }
3259
3359
  async getThreadsWithPendingMessages() {
3260
3360
  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`
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`
3262
3362
  ).all();
3263
3363
  return rows.map((r) => ({
3264
3364
  tenantId: r.tenant_id,
3265
3365
  assistantId: r.assistant_id,
3266
3366
  threadId: r.thread_id,
3267
- workspaceId: r.workspace_id || void 0,
3268
- projectId: r.project_id || void 0
3367
+ workspaceId: r.workspace_id,
3368
+ projectId: r.project_id
3269
3369
  }));
3270
3370
  }
3271
- async removeMessage(messageId) {
3272
- 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);
3273
3374
  return result.changes > 0;
3274
3375
  }
3275
- async clearMessages(threadId) {
3276
- 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);
3277
3379
  }
3278
- async markProcessing(messageId) {
3279
- 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);
3280
3389
  }
3281
- async resetProcessingToPending(threadId) {
3390
+ async resetProcessingToPending(threadId, scope) {
3391
+ const filter = scopeClause(scope);
3282
3392
  const result = this.db.prepare(
3283
- `UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'`
3284
- ).run(threadId);
3393
+ `UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
3394
+ ).run(threadId, ...filter.params);
3285
3395
  return result.changes;
3286
3396
  }
3287
3397
  getById(id) {
@@ -3289,6 +3399,14 @@ var LocalThreadMessageQueueStore = class {
3289
3399
  return rowToMessage(row);
3290
3400
  }
3291
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
+ }
3292
3410
  function rowToMessage(row) {
3293
3411
  return {
3294
3412
  id: row.id,
@@ -4443,6 +4561,7 @@ var LocalTaskStore = class {
4443
4561
  };
4444
4562
 
4445
4563
  // src/stores/LocalTaskWorkItemStore.ts
4564
+ var import_protocols2 = require("@axiom-lattice/protocols");
4446
4565
  var import_uuid2 = require("uuid");
4447
4566
  var DDL21 = `
4448
4567
  CREATE TABLE IF NOT EXISTS lt_task_work_items (
@@ -4487,6 +4606,11 @@ var LocalTaskWorkItemStore = class {
4487
4606
  this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
4488
4607
  this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
4489
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';`);
4490
4614
  }
4491
4615
  /** Add a column if it does not exist (SQLite version compatible). */
4492
4616
  ensureColumn(table, column, ddl) {
@@ -4577,6 +4701,132 @@ var LocalTaskWorkItemStore = class {
4577
4701
  ).all(...params, limit, offset);
4578
4702
  return rows.map(mapRowToWorkItem);
4579
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
+ }
4580
4830
  };
4581
4831
 
4582
4832
  // src/createLocalStoreConfig.ts
@@ -4605,6 +4855,7 @@ async function createLocalStoreConfig(options = {}) {
4605
4855
  schedule: new LocalScheduleStorage(db),
4606
4856
  task: new LocalTaskStore(db),
4607
4857
  taskWorkItem: new LocalTaskWorkItemStore(db),
4858
+ capabilityBundle: new LocalCapabilityBundleStore(db),
4608
4859
  checkpoint: import_langgraph_checkpoint_sqlite.SqliteSaver.fromConnString(dbPath)
4609
4860
  };
4610
4861
  }
@@ -4651,6 +4902,7 @@ var InMemoryConversationStore = class {
4651
4902
  InMemoryConversationStore,
4652
4903
  LocalA2AApiKeyStore,
4653
4904
  LocalAssistantStore,
4905
+ LocalCapabilityBundleStore,
4654
4906
  LocalChannelBindingStore,
4655
4907
  LocalChannelInstallationStore,
4656
4908
  LocalConnectionStore,