@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.mjs
CHANGED
|
@@ -588,6 +588,7 @@ function mapRowToWorkspace(row) {
|
|
|
588
588
|
}
|
|
589
589
|
|
|
590
590
|
// src/stores/LocalProjectStore.ts
|
|
591
|
+
import { assertGenericProjectConfig } from "@axiom-lattice/protocols";
|
|
591
592
|
var DDL4 = `
|
|
592
593
|
CREATE TABLE IF NOT EXISTS lt_projects (
|
|
593
594
|
id TEXT NOT NULL,
|
|
@@ -628,6 +629,7 @@ var LocalProjectStore = class {
|
|
|
628
629
|
return row ? mapRowToProject(row) : null;
|
|
629
630
|
}
|
|
630
631
|
async createProject(tenantId, workspaceId, id, data) {
|
|
632
|
+
assertGenericProjectConfig(data.config);
|
|
631
633
|
const now = nowISO();
|
|
632
634
|
const kind = data.kind || "business";
|
|
633
635
|
this.db.prepare(
|
|
@@ -637,23 +639,18 @@ var LocalProjectStore = class {
|
|
|
637
639
|
workspace_id = excluded.workspace_id,
|
|
638
640
|
name = excluded.name,
|
|
639
641
|
description = excluded.description,
|
|
640
|
-
config =
|
|
642
|
+
config = CASE
|
|
643
|
+
WHEN json_type(lt_projects.config, '$.capabilityBundleIds') IS NOT NULL
|
|
644
|
+
THEN json_set(COALESCE(excluded.config, '{}'), '$.capabilityBundleIds', json_extract(lt_projects.config, '$.capabilityBundleIds'))
|
|
645
|
+
ELSE excluded.config
|
|
646
|
+
END,
|
|
641
647
|
kind = excluded.kind,
|
|
642
648
|
updated_at = excluded.updated_at`
|
|
643
649
|
).run(id, tenantId, workspaceId, data.name, data.description || null, data.config ? JSON.stringify(data.config) : null, kind, now, now);
|
|
644
|
-
return
|
|
645
|
-
id,
|
|
646
|
-
tenantId,
|
|
647
|
-
workspaceId,
|
|
648
|
-
name: data.name,
|
|
649
|
-
description: data.description,
|
|
650
|
-
config: data.config,
|
|
651
|
-
kind,
|
|
652
|
-
createdAt: parseISO(now),
|
|
653
|
-
updatedAt: parseISO(now)
|
|
654
|
-
};
|
|
650
|
+
return await this.getProjectById(tenantId, id);
|
|
655
651
|
}
|
|
656
652
|
async updateProject(tenantId, id, updates) {
|
|
653
|
+
assertGenericProjectConfig(updates.config);
|
|
657
654
|
const existing = await this.getProjectById(tenantId, id);
|
|
658
655
|
if (!existing) return null;
|
|
659
656
|
const setClauses = [];
|
|
@@ -667,8 +664,13 @@ var LocalProjectStore = class {
|
|
|
667
664
|
values.push(updates.description || null);
|
|
668
665
|
}
|
|
669
666
|
if (updates.config !== void 0) {
|
|
670
|
-
setClauses.push(
|
|
671
|
-
|
|
667
|
+
setClauses.push(`config = CASE
|
|
668
|
+
WHEN json_type(config, '$.capabilityBundleIds') IS NOT NULL
|
|
669
|
+
THEN json_set(json(?), '$.capabilityBundleIds', json_extract(config, '$.capabilityBundleIds'))
|
|
670
|
+
ELSE ?
|
|
671
|
+
END`);
|
|
672
|
+
const config = JSON.stringify(updates.config ?? {});
|
|
673
|
+
values.push(config, config);
|
|
672
674
|
}
|
|
673
675
|
if (updates.kind !== void 0) {
|
|
674
676
|
setClauses.push("kind = ?");
|
|
@@ -690,6 +692,52 @@ var LocalProjectStore = class {
|
|
|
690
692
|
).run(tenantId, id);
|
|
691
693
|
return result.changes > 0;
|
|
692
694
|
}
|
|
695
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
696
|
+
const now = nowISO();
|
|
697
|
+
const result = this.db.prepare(
|
|
698
|
+
`UPDATE lt_projects
|
|
699
|
+
SET config = json_set(COALESCE(config, '{}'), '$.capabilityBundleIds', json(?)), updated_at = ?
|
|
700
|
+
WHERE tenant_id = ? AND id = ?
|
|
701
|
+
AND NOT EXISTS (
|
|
702
|
+
SELECT 1 FROM json_each(?) AS requested
|
|
703
|
+
WHERE NOT EXISTS (
|
|
704
|
+
SELECT 1 FROM lt_capability_bundles
|
|
705
|
+
WHERE tenant_id = ? AND id = requested.value
|
|
706
|
+
)
|
|
707
|
+
)
|
|
708
|
+
AND NOT EXISTS (
|
|
709
|
+
SELECT 1 FROM json_each(?) AS expected
|
|
710
|
+
WHERE EXISTS (
|
|
711
|
+
SELECT 1 FROM lt_capability_bundles
|
|
712
|
+
WHERE tenant_id = ? AND id = expected.key AND updated_at <> expected.value
|
|
713
|
+
)
|
|
714
|
+
)`
|
|
715
|
+
).run(JSON.stringify(bundleIds), now, tenantId, projectId, JSON.stringify(bundleIds), tenantId, JSON.stringify(expectedRevisions), tenantId);
|
|
716
|
+
if (result.changes > 0) {
|
|
717
|
+
const project = await this.getProjectById(tenantId, projectId);
|
|
718
|
+
return project ? { status: "updated", project } : { status: "project_not_found" };
|
|
719
|
+
}
|
|
720
|
+
const revisionMismatch = this.db.prepare(
|
|
721
|
+
"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"
|
|
722
|
+
).get(tenantId, JSON.stringify(expectedRevisions), JSON.stringify(expectedRevisions));
|
|
723
|
+
if (revisionMismatch) return { status: "bundle_conflict" };
|
|
724
|
+
return await this.getProjectById(tenantId, projectId) ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
725
|
+
}
|
|
726
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
727
|
+
const rows = this.db.prepare("SELECT config FROM lt_projects WHERE tenant_id = ?").all(tenantId);
|
|
728
|
+
return rows.some((row) => {
|
|
729
|
+
if (!row.config) return false;
|
|
730
|
+
let config;
|
|
731
|
+
try {
|
|
732
|
+
config = JSON.parse(row.config);
|
|
733
|
+
} catch {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
|
|
737
|
+
const ids = config.capabilityBundleIds;
|
|
738
|
+
return Array.isArray(ids) && ids.every((id) => typeof id === "string") && ids.includes(bundleId);
|
|
739
|
+
});
|
|
740
|
+
}
|
|
693
741
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
694
742
|
ensureColumn(table, column, ddl) {
|
|
695
743
|
const cols = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
@@ -2168,8 +2216,34 @@ var LocalEvalStore = class {
|
|
|
2168
2216
|
return this.getProjectById(tenantId, id);
|
|
2169
2217
|
}
|
|
2170
2218
|
async deleteProject(tenantId, id) {
|
|
2171
|
-
const
|
|
2172
|
-
|
|
2219
|
+
const db = this.db.getRawDb();
|
|
2220
|
+
db.run("BEGIN TRANSACTION");
|
|
2221
|
+
try {
|
|
2222
|
+
db.run(
|
|
2223
|
+
`DELETE FROM lt_eval_run_results
|
|
2224
|
+
WHERE run_id IN (
|
|
2225
|
+
SELECT id FROM lt_eval_runs WHERE tenant_id = ? AND project_id = ?
|
|
2226
|
+
)`,
|
|
2227
|
+
[tenantId, id]
|
|
2228
|
+
);
|
|
2229
|
+
db.run(
|
|
2230
|
+
`DELETE FROM lt_eval_cases
|
|
2231
|
+
WHERE tenant_id = ? AND suite_id IN (
|
|
2232
|
+
SELECT id FROM lt_eval_suites WHERE tenant_id = ? AND project_id = ?
|
|
2233
|
+
)`,
|
|
2234
|
+
[tenantId, tenantId, id]
|
|
2235
|
+
);
|
|
2236
|
+
db.run(`DELETE FROM lt_eval_runs WHERE tenant_id = ? AND project_id = ?`, [tenantId, id]);
|
|
2237
|
+
db.run(`DELETE FROM lt_eval_suites WHERE tenant_id = ? AND project_id = ?`, [tenantId, id]);
|
|
2238
|
+
db.run(`DELETE FROM lt_eval_projects WHERE tenant_id = ? AND id = ?`, [tenantId, id]);
|
|
2239
|
+
const deleted = db.getRowsModified() > 0;
|
|
2240
|
+
db.run("COMMIT");
|
|
2241
|
+
this.db.save();
|
|
2242
|
+
return deleted;
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
db.run("ROLLBACK");
|
|
2245
|
+
throw error;
|
|
2246
|
+
}
|
|
2173
2247
|
}
|
|
2174
2248
|
// -------------------------------------------------------------------------
|
|
2175
2249
|
// Suites
|
|
@@ -3029,11 +3103,11 @@ var LocalA2AApiKeyStore = class {
|
|
|
3029
3103
|
const rows = this.db.prepare(
|
|
3030
3104
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3031
3105
|
).all();
|
|
3032
|
-
const
|
|
3106
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3033
3107
|
for (const row of rows) {
|
|
3034
3108
|
try {
|
|
3035
3109
|
const key = decrypt3(row.key_value);
|
|
3036
|
-
|
|
3110
|
+
map2.set(key, {
|
|
3037
3111
|
key,
|
|
3038
3112
|
tenantId: row.tenant_id,
|
|
3039
3113
|
projectId: row.project_id,
|
|
@@ -3042,7 +3116,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3042
3116
|
} catch {
|
|
3043
3117
|
}
|
|
3044
3118
|
}
|
|
3045
|
-
return
|
|
3119
|
+
return map2;
|
|
3046
3120
|
}
|
|
3047
3121
|
};
|
|
3048
3122
|
function mapRowToRecord(row) {
|
|
@@ -3089,6 +3163,7 @@ CREATE INDEX IF NOT EXISTS idx_lt_tmq_pending ON lt_thread_message_queue(status,
|
|
|
3089
3163
|
`;
|
|
3090
3164
|
var LocalThreadMessageQueueStore = class {
|
|
3091
3165
|
constructor(db) {
|
|
3166
|
+
this.capacityTail = Promise.resolve();
|
|
3092
3167
|
this.db = db;
|
|
3093
3168
|
ensureTable(db, DDL17);
|
|
3094
3169
|
}
|
|
@@ -3107,18 +3182,65 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3107
3182
|
params.threadId,
|
|
3108
3183
|
params.tenantId,
|
|
3109
3184
|
params.assistantId,
|
|
3110
|
-
params.workspaceId
|
|
3111
|
-
params.projectId
|
|
3185
|
+
params.workspaceId ?? null,
|
|
3186
|
+
params.projectId ?? null,
|
|
3112
3187
|
JSON.stringify(params.content),
|
|
3113
3188
|
params.type || "human",
|
|
3114
3189
|
nextSeq,
|
|
3115
|
-
params.priority
|
|
3190
|
+
params.priority ?? 0,
|
|
3116
3191
|
params.command ? JSON.stringify(params.command) : null,
|
|
3117
3192
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3118
3193
|
now
|
|
3119
3194
|
);
|
|
3120
3195
|
return this.getById(id);
|
|
3121
3196
|
}
|
|
3197
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
3198
|
+
let release;
|
|
3199
|
+
const previous = this.capacityTail;
|
|
3200
|
+
this.capacityTail = new Promise((resolve) => {
|
|
3201
|
+
release = resolve;
|
|
3202
|
+
});
|
|
3203
|
+
await previous;
|
|
3204
|
+
try {
|
|
3205
|
+
const scope = {
|
|
3206
|
+
tenantId: params.tenantId,
|
|
3207
|
+
assistantId: params.assistantId,
|
|
3208
|
+
workspaceId: params.workspaceId,
|
|
3209
|
+
projectId: params.projectId
|
|
3210
|
+
};
|
|
3211
|
+
const filter = scopeClause(scope);
|
|
3212
|
+
const id = params.id || randomUUID4();
|
|
3213
|
+
const limit = maxSize === Infinity ? -1 : maxSize;
|
|
3214
|
+
const result = this.db.prepare(
|
|
3215
|
+
`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)
|
|
3216
|
+
SELECT ?, ?, ?, ?, ?, ?, ?, ?,
|
|
3217
|
+
(SELECT COALESCE(MAX(sequence_order), 0) + 1 FROM lt_thread_message_queue WHERE thread_id = ?),
|
|
3218
|
+
?, ?, ?, ?
|
|
3219
|
+
WHERE ? = -1 OR (SELECT COUNT(*) FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}) < ?`
|
|
3220
|
+
).run(
|
|
3221
|
+
id,
|
|
3222
|
+
params.threadId,
|
|
3223
|
+
params.tenantId,
|
|
3224
|
+
params.assistantId,
|
|
3225
|
+
params.workspaceId ?? null,
|
|
3226
|
+
params.projectId ?? null,
|
|
3227
|
+
JSON.stringify(params.content),
|
|
3228
|
+
params.type || "human",
|
|
3229
|
+
params.threadId,
|
|
3230
|
+
params.priority ?? 0,
|
|
3231
|
+
params.command ? JSON.stringify(params.command) : null,
|
|
3232
|
+
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3233
|
+
nowISO(),
|
|
3234
|
+
limit,
|
|
3235
|
+
params.threadId,
|
|
3236
|
+
...filter.params,
|
|
3237
|
+
limit
|
|
3238
|
+
);
|
|
3239
|
+
return result.changes > 0;
|
|
3240
|
+
} finally {
|
|
3241
|
+
release();
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3122
3244
|
async addMessageAtHead(params) {
|
|
3123
3245
|
const now = nowISO();
|
|
3124
3246
|
const id = params.id || randomUUID4();
|
|
@@ -3133,8 +3255,8 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3133
3255
|
params.threadId,
|
|
3134
3256
|
params.tenantId,
|
|
3135
3257
|
params.assistantId,
|
|
3136
|
-
params.workspaceId
|
|
3137
|
-
params.projectId
|
|
3258
|
+
params.workspaceId ?? null,
|
|
3259
|
+
params.projectId ?? null,
|
|
3138
3260
|
JSON.stringify(params.content),
|
|
3139
3261
|
params.type || "human",
|
|
3140
3262
|
seqRow.next_seq,
|
|
@@ -3144,50 +3266,63 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3144
3266
|
);
|
|
3145
3267
|
return this.getById(id);
|
|
3146
3268
|
}
|
|
3147
|
-
async getPendingMessages(threadId) {
|
|
3269
|
+
async getPendingMessages(threadId, scope) {
|
|
3270
|
+
const filter = scopeClause(scope);
|
|
3148
3271
|
const rows = this.db.prepare(
|
|
3149
|
-
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending' ORDER BY priority DESC, sequence_order ASC`
|
|
3150
|
-
).all(threadId);
|
|
3272
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
3273
|
+
).all(threadId, ...filter.params);
|
|
3151
3274
|
return rows.map(rowToMessage);
|
|
3152
3275
|
}
|
|
3153
|
-
async getProcessingMessages(threadId) {
|
|
3276
|
+
async getProcessingMessages(threadId, scope) {
|
|
3277
|
+
const filter = scopeClause(scope);
|
|
3154
3278
|
const rows = this.db.prepare(
|
|
3155
|
-
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing' ORDER BY priority DESC, sequence_order ASC`
|
|
3156
|
-
).all(threadId);
|
|
3279
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing'${filter.sql} ORDER BY priority DESC, sequence_order ASC`
|
|
3280
|
+
).all(threadId, ...filter.params);
|
|
3157
3281
|
return rows.map(rowToMessage);
|
|
3158
3282
|
}
|
|
3159
|
-
async getQueueSize(threadId) {
|
|
3283
|
+
async getQueueSize(threadId, scope) {
|
|
3284
|
+
const filter = scopeClause(scope);
|
|
3160
3285
|
const row = this.db.prepare(
|
|
3161
|
-
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'`
|
|
3162
|
-
).get(threadId);
|
|
3286
|
+
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql}`
|
|
3287
|
+
).get(threadId, ...filter.params);
|
|
3163
3288
|
return row.count;
|
|
3164
3289
|
}
|
|
3165
3290
|
async getThreadsWithPendingMessages() {
|
|
3166
3291
|
const rows = this.db.prepare(
|
|
3167
|
-
`SELECT
|
|
3292
|
+
`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`
|
|
3168
3293
|
).all();
|
|
3169
3294
|
return rows.map((r) => ({
|
|
3170
3295
|
tenantId: r.tenant_id,
|
|
3171
3296
|
assistantId: r.assistant_id,
|
|
3172
3297
|
threadId: r.thread_id,
|
|
3173
|
-
workspaceId: r.workspace_id
|
|
3174
|
-
projectId: r.project_id
|
|
3298
|
+
workspaceId: r.workspace_id,
|
|
3299
|
+
projectId: r.project_id
|
|
3175
3300
|
}));
|
|
3176
3301
|
}
|
|
3177
|
-
async removeMessage(messageId) {
|
|
3178
|
-
const
|
|
3302
|
+
async removeMessage(messageId, scope) {
|
|
3303
|
+
const filter = scopeClause(scope);
|
|
3304
|
+
const result = this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
|
|
3179
3305
|
return result.changes > 0;
|
|
3180
3306
|
}
|
|
3181
|
-
async clearMessages(threadId) {
|
|
3182
|
-
|
|
3307
|
+
async clearMessages(threadId, scope) {
|
|
3308
|
+
const filter = scopeClause(scope);
|
|
3309
|
+
this.db.prepare(`DELETE FROM lt_thread_message_queue WHERE thread_id = ?${filter.sql}`).run(threadId, ...filter.params);
|
|
3183
3310
|
}
|
|
3184
|
-
async markProcessing(messageId) {
|
|
3185
|
-
|
|
3311
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
3312
|
+
const filter = scopeClause(scope);
|
|
3313
|
+
if (customRunConfig === void 0) {
|
|
3314
|
+
this.db.prepare(`UPDATE lt_thread_message_queue SET status = 'processing' WHERE id = ?${filter.sql}`).run(messageId, ...filter.params);
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
this.db.prepare(
|
|
3318
|
+
`UPDATE lt_thread_message_queue SET status = 'processing', custom_run_config = ? WHERE id = ?${filter.sql}`
|
|
3319
|
+
).run(JSON.stringify(customRunConfig), messageId, ...filter.params);
|
|
3186
3320
|
}
|
|
3187
|
-
async resetProcessingToPending(threadId) {
|
|
3321
|
+
async resetProcessingToPending(threadId, scope) {
|
|
3322
|
+
const filter = scopeClause(scope);
|
|
3188
3323
|
const result = this.db.prepare(
|
|
3189
|
-
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'`
|
|
3190
|
-
).run(threadId);
|
|
3324
|
+
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
|
|
3325
|
+
).run(threadId, ...filter.params);
|
|
3191
3326
|
return result.changes;
|
|
3192
3327
|
}
|
|
3193
3328
|
getById(id) {
|
|
@@ -3195,6 +3330,14 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3195
3330
|
return rowToMessage(row);
|
|
3196
3331
|
}
|
|
3197
3332
|
};
|
|
3333
|
+
function scopeClause(scope) {
|
|
3334
|
+
if (!scope) return { sql: "", params: [] };
|
|
3335
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
3336
|
+
return {
|
|
3337
|
+
sql: entries.map(([key, value]) => ` AND ${key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)} ${value == null ? "IS NULL" : "= ?"}`).join(""),
|
|
3338
|
+
params: entries.flatMap(([, value]) => value == null ? [] : [value])
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3198
3341
|
function rowToMessage(row) {
|
|
3199
3342
|
return {
|
|
3200
3343
|
id: row.id,
|
|
@@ -4349,6 +4492,7 @@ var LocalTaskStore = class {
|
|
|
4349
4492
|
};
|
|
4350
4493
|
|
|
4351
4494
|
// src/stores/LocalTaskWorkItemStore.ts
|
|
4495
|
+
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
4352
4496
|
import { v4 as uuid2 } from "uuid";
|
|
4353
4497
|
var DDL21 = `
|
|
4354
4498
|
CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
@@ -4393,6 +4537,11 @@ var LocalTaskWorkItemStore = class {
|
|
|
4393
4537
|
this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
|
|
4394
4538
|
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
4395
4539
|
ON lt_task_work_items (tenant_id, task_id, event_key);`);
|
|
4540
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
4541
|
+
ON lt_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC);`);
|
|
4542
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
4543
|
+
ON lt_task_work_items (tenant_id, task_id, json_extract(detail, '$.executionResultId'))
|
|
4544
|
+
WHERE action = 'execution_reconciled';`);
|
|
4396
4545
|
}
|
|
4397
4546
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4398
4547
|
ensureColumn(table, column, ddl) {
|
|
@@ -4483,6 +4632,132 @@ var LocalTaskWorkItemStore = class {
|
|
|
4483
4632
|
).all(...params, limit, offset);
|
|
4484
4633
|
return rows.map(mapRowToWorkItem);
|
|
4485
4634
|
}
|
|
4635
|
+
/** List pending execution results using one bounded SQLite anti-join query. */
|
|
4636
|
+
async listPendingExecutionResults(params) {
|
|
4637
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
4638
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
4639
|
+
error.code = "INVALID_LIMIT";
|
|
4640
|
+
throw error;
|
|
4641
|
+
}
|
|
4642
|
+
if (params.limit === 0) return [];
|
|
4643
|
+
const rows = this.db.prepare(
|
|
4644
|
+
`SELECT result.*
|
|
4645
|
+
FROM lt_task_work_items AS result
|
|
4646
|
+
WHERE result.tenant_id = ?
|
|
4647
|
+
AND result.task_id = ?
|
|
4648
|
+
AND result.action = 'execution_result'
|
|
4649
|
+
AND substr(result.event_key, 1, 17) = 'execution-result:'
|
|
4650
|
+
AND length(result.event_key) > 17
|
|
4651
|
+
AND result.event_key NOT GLOB '*[^A-Za-z0-9._:-]*'
|
|
4652
|
+
AND NOT EXISTS (
|
|
4653
|
+
SELECT 1
|
|
4654
|
+
FROM lt_task_work_items AS reconciled
|
|
4655
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
4656
|
+
AND reconciled.task_id = result.task_id
|
|
4657
|
+
AND reconciled.action = 'execution_reconciled'
|
|
4658
|
+
AND json_extract(reconciled.detail, '$.executionResultId') = result.event_key
|
|
4659
|
+
)
|
|
4660
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
4661
|
+
LIMIT ?`
|
|
4662
|
+
).all(params.tenantId, params.taskId, params.limit);
|
|
4663
|
+
return rows.map(mapRowToWorkItem);
|
|
4664
|
+
}
|
|
4665
|
+
};
|
|
4666
|
+
|
|
4667
|
+
// src/stores/LocalCapabilityBundleStore.ts
|
|
4668
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
4669
|
+
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);`;
|
|
4670
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
4671
|
+
function map(row) {
|
|
4672
|
+
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() };
|
|
4673
|
+
}
|
|
4674
|
+
function isConstraint(error) {
|
|
4675
|
+
return error instanceof Error && /UNIQUE constraint failed/.test(error.message);
|
|
4676
|
+
}
|
|
4677
|
+
function nextTimestamp(previous) {
|
|
4678
|
+
const now = Date.now();
|
|
4679
|
+
const previousTime = previous ? Date.parse(previous) : Number.NaN;
|
|
4680
|
+
return new Date(Math.max(now, Number.isFinite(previousTime) ? previousTime + 1 : now)).toISOString();
|
|
4681
|
+
}
|
|
4682
|
+
var LocalCapabilityBundleStore = class {
|
|
4683
|
+
/**
|
|
4684
|
+
* Creates a capability bundle store over an initialized sql.js database.
|
|
4685
|
+
*
|
|
4686
|
+
* @param db - Shared local database wrapper used for persistence.
|
|
4687
|
+
*/
|
|
4688
|
+
constructor(db) {
|
|
4689
|
+
this.db = db;
|
|
4690
|
+
ensureTable(db, DDL22);
|
|
4691
|
+
}
|
|
4692
|
+
async listByTenant(tenantId) {
|
|
4693
|
+
return this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? ORDER BY created_at").all(tenantId).map(map);
|
|
4694
|
+
}
|
|
4695
|
+
async getById(tenantId, id) {
|
|
4696
|
+
const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
|
|
4697
|
+
return row ? map(row) : null;
|
|
4698
|
+
}
|
|
4699
|
+
async getManyByIds(tenantId, ids) {
|
|
4700
|
+
return ids.flatMap((id) => {
|
|
4701
|
+
const row = this.db.prepare("SELECT * FROM lt_capability_bundles WHERE tenant_id = ? AND id = ?").get(tenantId, id);
|
|
4702
|
+
return row ? [map(row)] : [];
|
|
4703
|
+
});
|
|
4704
|
+
}
|
|
4705
|
+
async create(tenantId, input) {
|
|
4706
|
+
const now = nextTimestamp();
|
|
4707
|
+
const id = randomUUID6();
|
|
4708
|
+
try {
|
|
4709
|
+
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);
|
|
4710
|
+
} catch (error) {
|
|
4711
|
+
if (isConstraint(error)) throw new Error(duplicateMessage);
|
|
4712
|
+
throw error;
|
|
4713
|
+
}
|
|
4714
|
+
return await this.getById(tenantId, id);
|
|
4715
|
+
}
|
|
4716
|
+
async update(tenantId, id, input) {
|
|
4717
|
+
const existing = await this.getById(tenantId, id);
|
|
4718
|
+
if (!existing) return null;
|
|
4719
|
+
if (input.expectedUpdatedAt !== void 0 && input.expectedUpdatedAt !== existing.updatedAt) return { status: "conflict" };
|
|
4720
|
+
const fields = [];
|
|
4721
|
+
const values = [];
|
|
4722
|
+
const add = (field, value) => {
|
|
4723
|
+
fields.push(`${field} = ?`);
|
|
4724
|
+
values.push(value);
|
|
4725
|
+
};
|
|
4726
|
+
if (input.name !== void 0) add("name", input.name);
|
|
4727
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) add("description", input.description ?? null);
|
|
4728
|
+
if (input.capabilities !== void 0) add("capabilities", JSON.stringify(input.capabilities));
|
|
4729
|
+
if (!fields.length) return existing;
|
|
4730
|
+
const now = nextTimestamp(existing.updatedAt);
|
|
4731
|
+
fields.push("updated_at = ?");
|
|
4732
|
+
values.push(now, tenantId, id);
|
|
4733
|
+
try {
|
|
4734
|
+
if (input.expectedUpdatedAt !== void 0) {
|
|
4735
|
+
const result = this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ? AND updated_at = ?`).run(...values, input.expectedUpdatedAt);
|
|
4736
|
+
if (result.changes === 0) return { status: "conflict" };
|
|
4737
|
+
} else {
|
|
4738
|
+
this.db.prepare(`UPDATE lt_capability_bundles SET ${fields.join(", ")} WHERE tenant_id = ? AND id = ?`).run(...values);
|
|
4739
|
+
}
|
|
4740
|
+
} catch (error) {
|
|
4741
|
+
if (isConstraint(error)) throw new Error(duplicateMessage);
|
|
4742
|
+
throw error;
|
|
4743
|
+
}
|
|
4744
|
+
return this.getById(tenantId, id);
|
|
4745
|
+
}
|
|
4746
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
4747
|
+
const result = this.db.prepare(
|
|
4748
|
+
`DELETE FROM lt_capability_bundles
|
|
4749
|
+
WHERE tenant_id = ? AND id = ?
|
|
4750
|
+
AND NOT EXISTS (
|
|
4751
|
+
SELECT 1 FROM lt_projects
|
|
4752
|
+
WHERE tenant_id = ?
|
|
4753
|
+
AND json_type(config, '$.capabilityBundleIds') = 'array'
|
|
4754
|
+
AND EXISTS (SELECT 1 FROM json_each(config, '$.capabilityBundleIds') WHERE value = ?)
|
|
4755
|
+
)`
|
|
4756
|
+
).run(tenantId, id, tenantId, id);
|
|
4757
|
+
if (result.changes > 0) return "deleted";
|
|
4758
|
+
if (await this.getById(tenantId, id)) return "in_use";
|
|
4759
|
+
return "not_found";
|
|
4760
|
+
}
|
|
4486
4761
|
};
|
|
4487
4762
|
|
|
4488
4763
|
// src/createLocalStoreConfig.ts
|
|
@@ -4511,6 +4786,7 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4511
4786
|
schedule: new LocalScheduleStorage(db),
|
|
4512
4787
|
task: new LocalTaskStore(db),
|
|
4513
4788
|
taskWorkItem: new LocalTaskWorkItemStore(db),
|
|
4789
|
+
capabilityBundle: new LocalCapabilityBundleStore(db),
|
|
4514
4790
|
checkpoint: SqliteSaver.fromConnString(dbPath)
|
|
4515
4791
|
};
|
|
4516
4792
|
}
|
|
@@ -4556,6 +4832,7 @@ export {
|
|
|
4556
4832
|
InMemoryConversationStore,
|
|
4557
4833
|
LocalA2AApiKeyStore,
|
|
4558
4834
|
LocalAssistantStore,
|
|
4835
|
+
LocalCapabilityBundleStore,
|
|
4559
4836
|
LocalChannelBindingStore,
|
|
4560
4837
|
LocalChannelInstallationStore,
|
|
4561
4838
|
LocalConnectionStore,
|