@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +21 -0
- package/dist/index.d.mts +53 -10
- package/dist/index.d.ts +53 -10
- package/dist/index.js +323 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +322 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalCapabilityBundleStore.test.ts +217 -0
- package/src/__tests__/LocalEvalStore.test.ts +41 -2
- 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/LocalEvalStore.ts +28 -2
- 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();
|
|
@@ -2236,8 +2285,34 @@ var LocalEvalStore = class {
|
|
|
2236
2285
|
return this.getProjectById(tenantId, id);
|
|
2237
2286
|
}
|
|
2238
2287
|
async deleteProject(tenantId, id) {
|
|
2239
|
-
const
|
|
2240
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
3179
|
-
params.projectId
|
|
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
|
|
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
|
|
3205
|
-
params.projectId
|
|
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
|
|
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
|
|
3242
|
-
projectId: r.project_id
|
|
3367
|
+
workspaceId: r.workspace_id,
|
|
3368
|
+
projectId: r.project_id
|
|
3243
3369
|
}));
|
|
3244
3370
|
}
|
|
3245
|
-
async removeMessage(messageId) {
|
|
3246
|
-
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);
|
|
3247
3374
|
return result.changes > 0;
|
|
3248
3375
|
}
|
|
3249
|
-
async clearMessages(threadId) {
|
|
3250
|
-
|
|
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
|
-
|
|
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,
|