@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.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();
|
|
@@ -3055,11 +3103,11 @@ var LocalA2AApiKeyStore = class {
|
|
|
3055
3103
|
const rows = this.db.prepare(
|
|
3056
3104
|
`SELECT * FROM lt_a2a_api_keys WHERE enabled = 1`
|
|
3057
3105
|
).all();
|
|
3058
|
-
const
|
|
3106
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3059
3107
|
for (const row of rows) {
|
|
3060
3108
|
try {
|
|
3061
3109
|
const key = decrypt3(row.key_value);
|
|
3062
|
-
|
|
3110
|
+
map2.set(key, {
|
|
3063
3111
|
key,
|
|
3064
3112
|
tenantId: row.tenant_id,
|
|
3065
3113
|
projectId: row.project_id,
|
|
@@ -3068,7 +3116,7 @@ var LocalA2AApiKeyStore = class {
|
|
|
3068
3116
|
} catch {
|
|
3069
3117
|
}
|
|
3070
3118
|
}
|
|
3071
|
-
return
|
|
3119
|
+
return map2;
|
|
3072
3120
|
}
|
|
3073
3121
|
};
|
|
3074
3122
|
function mapRowToRecord(row) {
|
|
@@ -3115,6 +3163,7 @@ CREATE INDEX IF NOT EXISTS idx_lt_tmq_pending ON lt_thread_message_queue(status,
|
|
|
3115
3163
|
`;
|
|
3116
3164
|
var LocalThreadMessageQueueStore = class {
|
|
3117
3165
|
constructor(db) {
|
|
3166
|
+
this.capacityTail = Promise.resolve();
|
|
3118
3167
|
this.db = db;
|
|
3119
3168
|
ensureTable(db, DDL17);
|
|
3120
3169
|
}
|
|
@@ -3133,18 +3182,65 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3133
3182
|
params.threadId,
|
|
3134
3183
|
params.tenantId,
|
|
3135
3184
|
params.assistantId,
|
|
3136
|
-
params.workspaceId
|
|
3137
|
-
params.projectId
|
|
3185
|
+
params.workspaceId ?? null,
|
|
3186
|
+
params.projectId ?? null,
|
|
3138
3187
|
JSON.stringify(params.content),
|
|
3139
3188
|
params.type || "human",
|
|
3140
3189
|
nextSeq,
|
|
3141
|
-
params.priority
|
|
3190
|
+
params.priority ?? 0,
|
|
3142
3191
|
params.command ? JSON.stringify(params.command) : null,
|
|
3143
3192
|
params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
|
|
3144
3193
|
now
|
|
3145
3194
|
);
|
|
3146
3195
|
return this.getById(id);
|
|
3147
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
|
+
}
|
|
3148
3244
|
async addMessageAtHead(params) {
|
|
3149
3245
|
const now = nowISO();
|
|
3150
3246
|
const id = params.id || randomUUID4();
|
|
@@ -3159,8 +3255,8 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3159
3255
|
params.threadId,
|
|
3160
3256
|
params.tenantId,
|
|
3161
3257
|
params.assistantId,
|
|
3162
|
-
params.workspaceId
|
|
3163
|
-
params.projectId
|
|
3258
|
+
params.workspaceId ?? null,
|
|
3259
|
+
params.projectId ?? null,
|
|
3164
3260
|
JSON.stringify(params.content),
|
|
3165
3261
|
params.type || "human",
|
|
3166
3262
|
seqRow.next_seq,
|
|
@@ -3170,50 +3266,63 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3170
3266
|
);
|
|
3171
3267
|
return this.getById(id);
|
|
3172
3268
|
}
|
|
3173
|
-
async getPendingMessages(threadId) {
|
|
3269
|
+
async getPendingMessages(threadId, scope) {
|
|
3270
|
+
const filter = scopeClause(scope);
|
|
3174
3271
|
const rows = this.db.prepare(
|
|
3175
|
-
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending' ORDER BY priority DESC, sequence_order ASC`
|
|
3176
|
-
).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);
|
|
3177
3274
|
return rows.map(rowToMessage);
|
|
3178
3275
|
}
|
|
3179
|
-
async getProcessingMessages(threadId) {
|
|
3276
|
+
async getProcessingMessages(threadId, scope) {
|
|
3277
|
+
const filter = scopeClause(scope);
|
|
3180
3278
|
const rows = this.db.prepare(
|
|
3181
|
-
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'processing' ORDER BY priority DESC, sequence_order ASC`
|
|
3182
|
-
).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);
|
|
3183
3281
|
return rows.map(rowToMessage);
|
|
3184
3282
|
}
|
|
3185
|
-
async getQueueSize(threadId) {
|
|
3283
|
+
async getQueueSize(threadId, scope) {
|
|
3284
|
+
const filter = scopeClause(scope);
|
|
3186
3285
|
const row = this.db.prepare(
|
|
3187
|
-
`SELECT COUNT(*) as count FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'`
|
|
3188
|
-
).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);
|
|
3189
3288
|
return row.count;
|
|
3190
3289
|
}
|
|
3191
3290
|
async getThreadsWithPendingMessages() {
|
|
3192
3291
|
const rows = this.db.prepare(
|
|
3193
|
-
`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`
|
|
3194
3293
|
).all();
|
|
3195
3294
|
return rows.map((r) => ({
|
|
3196
3295
|
tenantId: r.tenant_id,
|
|
3197
3296
|
assistantId: r.assistant_id,
|
|
3198
3297
|
threadId: r.thread_id,
|
|
3199
|
-
workspaceId: r.workspace_id
|
|
3200
|
-
projectId: r.project_id
|
|
3298
|
+
workspaceId: r.workspace_id,
|
|
3299
|
+
projectId: r.project_id
|
|
3201
3300
|
}));
|
|
3202
3301
|
}
|
|
3203
|
-
async removeMessage(messageId) {
|
|
3204
|
-
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);
|
|
3205
3305
|
return result.changes > 0;
|
|
3206
3306
|
}
|
|
3207
|
-
async clearMessages(threadId) {
|
|
3208
|
-
|
|
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);
|
|
3209
3310
|
}
|
|
3210
|
-
async markProcessing(messageId) {
|
|
3211
|
-
|
|
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);
|
|
3212
3320
|
}
|
|
3213
|
-
async resetProcessingToPending(threadId) {
|
|
3321
|
+
async resetProcessingToPending(threadId, scope) {
|
|
3322
|
+
const filter = scopeClause(scope);
|
|
3214
3323
|
const result = this.db.prepare(
|
|
3215
|
-
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'`
|
|
3216
|
-
).run(threadId);
|
|
3324
|
+
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
|
|
3325
|
+
).run(threadId, ...filter.params);
|
|
3217
3326
|
return result.changes;
|
|
3218
3327
|
}
|
|
3219
3328
|
getById(id) {
|
|
@@ -3221,6 +3330,14 @@ var LocalThreadMessageQueueStore = class {
|
|
|
3221
3330
|
return rowToMessage(row);
|
|
3222
3331
|
}
|
|
3223
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
|
+
}
|
|
3224
3341
|
function rowToMessage(row) {
|
|
3225
3342
|
return {
|
|
3226
3343
|
id: row.id,
|
|
@@ -4375,6 +4492,7 @@ var LocalTaskStore = class {
|
|
|
4375
4492
|
};
|
|
4376
4493
|
|
|
4377
4494
|
// src/stores/LocalTaskWorkItemStore.ts
|
|
4495
|
+
import { MAX_PENDING_EXECUTION_RESULTS_LIMIT } from "@axiom-lattice/protocols";
|
|
4378
4496
|
import { v4 as uuid2 } from "uuid";
|
|
4379
4497
|
var DDL21 = `
|
|
4380
4498
|
CREATE TABLE IF NOT EXISTS lt_task_work_items (
|
|
@@ -4419,6 +4537,11 @@ var LocalTaskWorkItemStore = class {
|
|
|
4419
4537
|
this.ensureColumn("lt_task_work_items", "event_key", "event_key TEXT");
|
|
4420
4538
|
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
4421
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';`);
|
|
4422
4545
|
}
|
|
4423
4546
|
/** Add a column if it does not exist (SQLite version compatible). */
|
|
4424
4547
|
ensureColumn(table, column, ddl) {
|
|
@@ -4509,6 +4632,132 @@ var LocalTaskWorkItemStore = class {
|
|
|
4509
4632
|
).all(...params, limit, offset);
|
|
4510
4633
|
return rows.map(mapRowToWorkItem);
|
|
4511
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
|
+
}
|
|
4512
4761
|
};
|
|
4513
4762
|
|
|
4514
4763
|
// src/createLocalStoreConfig.ts
|
|
@@ -4537,6 +4786,7 @@ async function createLocalStoreConfig(options = {}) {
|
|
|
4537
4786
|
schedule: new LocalScheduleStorage(db),
|
|
4538
4787
|
task: new LocalTaskStore(db),
|
|
4539
4788
|
taskWorkItem: new LocalTaskWorkItemStore(db),
|
|
4789
|
+
capabilityBundle: new LocalCapabilityBundleStore(db),
|
|
4540
4790
|
checkpoint: SqliteSaver.fromConnString(dbPath)
|
|
4541
4791
|
};
|
|
4542
4792
|
}
|
|
@@ -4582,6 +4832,7 @@ export {
|
|
|
4582
4832
|
InMemoryConversationStore,
|
|
4583
4833
|
LocalA2AApiKeyStore,
|
|
4584
4834
|
LocalAssistantStore,
|
|
4835
|
+
LocalCapabilityBundleStore,
|
|
4585
4836
|
LocalChannelBindingStore,
|
|
4586
4837
|
LocalChannelInstallationStore,
|
|
4587
4838
|
LocalConnectionStore,
|