@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +9 -0
- package/dist/index.d.mts +53 -10
- package/dist/index.d.ts +53 -10
- package/dist/index.js +295 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +294 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalCapabilityBundleStore.test.ts +217 -0
- package/src/__tests__/LocalProjectStore.test.ts +39 -2
- package/src/__tests__/LocalTaskWorkItemStore.test.ts +107 -0
- package/src/__tests__/LocalThreadMessageQueueStore.test.ts +78 -0
- package/src/createLocalStoreConfig.ts +2 -0
- package/src/index.ts +1 -0
- package/src/stores/LocalCapabilityBundleStore.ts +53 -0
- package/src/stores/LocalProjectStore.ts +67 -14
- package/src/stores/LocalTaskWorkItemStore.ts +45 -0
- package/src/stores/LocalThreadMessageQueueStore.ts +86 -24
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 =
|
|
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(
|
|
739
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
3205
|
-
params.projectId
|
|
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
|
|
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
|
|
3231
|
-
params.projectId
|
|
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
|
|
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
|
|
3268
|
-
projectId: r.project_id
|
|
3367
|
+
workspaceId: r.workspace_id,
|
|
3368
|
+
projectId: r.project_id
|
|
3269
3369
|
}));
|
|
3270
3370
|
}
|
|
3271
|
-
async removeMessage(messageId) {
|
|
3272
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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,
|