@hasna/mementos 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2015 -170
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/entities.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/relations.d.ts.map +1 -1
- package/dist/db/synthesis.d.ts +101 -0
- package/dist/db/synthesis.d.ts.map +1 -0
- package/dist/db/webhook_hooks.d.ts +25 -0
- package/dist/db/webhook_hooks.d.ts.map +1 -0
- package/dist/index.js +229 -9
- package/dist/lib/built-in-hooks.d.ts +12 -0
- package/dist/lib/built-in-hooks.d.ts.map +1 -0
- package/dist/lib/focus.d.ts.map +1 -1
- package/dist/lib/hooks.d.ts +50 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/synthesis/corpus-builder.d.ts +30 -0
- package/dist/lib/synthesis/corpus-builder.d.ts.map +1 -0
- package/dist/lib/synthesis/executor.d.ts +14 -0
- package/dist/lib/synthesis/executor.d.ts.map +1 -0
- package/dist/lib/synthesis/index.d.ts +37 -0
- package/dist/lib/synthesis/index.d.ts.map +1 -0
- package/dist/lib/synthesis/llm-analyzer.d.ts +18 -0
- package/dist/lib/synthesis/llm-analyzer.d.ts.map +1 -0
- package/dist/lib/synthesis/metrics.d.ts +13 -0
- package/dist/lib/synthesis/metrics.d.ts.map +1 -0
- package/dist/lib/synthesis/scheduler.d.ts +18 -0
- package/dist/lib/synthesis/scheduler.d.ts.map +1 -0
- package/dist/lib/synthesis/validator.d.ts +19 -0
- package/dist/lib/synthesis/validator.d.ts.map +1 -0
- package/dist/mcp/index.js +8102 -6127
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3627 -1726
- package/dist/types/hooks.d.ts +136 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2432,6 +2432,94 @@ var init_database = __esm(() => {
|
|
|
2432
2432
|
ALTER TABLE memories ADD COLUMN recall_count INTEGER NOT NULL DEFAULT 0;
|
|
2433
2433
|
CREATE INDEX IF NOT EXISTS idx_memories_recall_count ON memories(recall_count DESC);
|
|
2434
2434
|
INSERT OR IGNORE INTO _migrations (id) VALUES (9);
|
|
2435
|
+
`,
|
|
2436
|
+
`
|
|
2437
|
+
CREATE TABLE IF NOT EXISTS synthesis_events (
|
|
2438
|
+
id TEXT PRIMARY KEY,
|
|
2439
|
+
event_type TEXT NOT NULL CHECK(event_type IN ('recalled','searched','saved','updated','deleted','injected')),
|
|
2440
|
+
memory_id TEXT,
|
|
2441
|
+
agent_id TEXT,
|
|
2442
|
+
project_id TEXT,
|
|
2443
|
+
session_id TEXT,
|
|
2444
|
+
query TEXT,
|
|
2445
|
+
importance_at_time INTEGER,
|
|
2446
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
2447
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2448
|
+
);
|
|
2449
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_events_memory ON synthesis_events(memory_id);
|
|
2450
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_events_project ON synthesis_events(project_id);
|
|
2451
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_events_type ON synthesis_events(event_type);
|
|
2452
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_events_created ON synthesis_events(created_at);
|
|
2453
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (11);
|
|
2454
|
+
`,
|
|
2455
|
+
`
|
|
2456
|
+
CREATE TABLE IF NOT EXISTS synthesis_runs (
|
|
2457
|
+
id TEXT PRIMARY KEY,
|
|
2458
|
+
triggered_by TEXT NOT NULL DEFAULT 'manual' CHECK(triggered_by IN ('scheduler','manual','threshold','hook')),
|
|
2459
|
+
project_id TEXT,
|
|
2460
|
+
agent_id TEXT,
|
|
2461
|
+
corpus_size INTEGER NOT NULL DEFAULT 0,
|
|
2462
|
+
proposals_generated INTEGER NOT NULL DEFAULT 0,
|
|
2463
|
+
proposals_accepted INTEGER NOT NULL DEFAULT 0,
|
|
2464
|
+
proposals_rejected INTEGER NOT NULL DEFAULT 0,
|
|
2465
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','running','completed','failed','rolled_back')),
|
|
2466
|
+
error TEXT,
|
|
2467
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2468
|
+
completed_at TEXT
|
|
2469
|
+
);
|
|
2470
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_runs_project ON synthesis_runs(project_id);
|
|
2471
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_runs_status ON synthesis_runs(status);
|
|
2472
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_runs_started ON synthesis_runs(started_at);
|
|
2473
|
+
|
|
2474
|
+
CREATE TABLE IF NOT EXISTS synthesis_proposals (
|
|
2475
|
+
id TEXT PRIMARY KEY,
|
|
2476
|
+
run_id TEXT NOT NULL REFERENCES synthesis_runs(id) ON DELETE CASCADE,
|
|
2477
|
+
proposal_type TEXT NOT NULL CHECK(proposal_type IN ('merge','archive','promote','update_value','add_tag','remove_duplicate')),
|
|
2478
|
+
memory_ids TEXT NOT NULL DEFAULT '[]',
|
|
2479
|
+
target_memory_id TEXT,
|
|
2480
|
+
proposed_changes TEXT NOT NULL DEFAULT '{}',
|
|
2481
|
+
reasoning TEXT,
|
|
2482
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
2483
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','accepted','rejected','rolled_back')),
|
|
2484
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2485
|
+
executed_at TEXT,
|
|
2486
|
+
rollback_data TEXT
|
|
2487
|
+
);
|
|
2488
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_proposals_run ON synthesis_proposals(run_id);
|
|
2489
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_proposals_status ON synthesis_proposals(status);
|
|
2490
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_proposals_type ON synthesis_proposals(proposal_type);
|
|
2491
|
+
|
|
2492
|
+
CREATE TABLE IF NOT EXISTS synthesis_metrics (
|
|
2493
|
+
id TEXT PRIMARY KEY,
|
|
2494
|
+
run_id TEXT NOT NULL REFERENCES synthesis_runs(id) ON DELETE CASCADE,
|
|
2495
|
+
metric_type TEXT NOT NULL,
|
|
2496
|
+
value REAL NOT NULL,
|
|
2497
|
+
baseline REAL,
|
|
2498
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
2499
|
+
);
|
|
2500
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_metrics_run ON synthesis_metrics(run_id);
|
|
2501
|
+
CREATE INDEX IF NOT EXISTS idx_synthesis_metrics_type ON synthesis_metrics(metric_type);
|
|
2502
|
+
|
|
2503
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (12);
|
|
2504
|
+
`,
|
|
2505
|
+
`
|
|
2506
|
+
CREATE TABLE IF NOT EXISTS webhook_hooks (
|
|
2507
|
+
id TEXT PRIMARY KEY,
|
|
2508
|
+
type TEXT NOT NULL,
|
|
2509
|
+
handler_url TEXT NOT NULL,
|
|
2510
|
+
priority INTEGER NOT NULL DEFAULT 50,
|
|
2511
|
+
blocking INTEGER NOT NULL DEFAULT 0,
|
|
2512
|
+
agent_id TEXT,
|
|
2513
|
+
project_id TEXT,
|
|
2514
|
+
description TEXT,
|
|
2515
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
2516
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2517
|
+
invocation_count INTEGER NOT NULL DEFAULT 0,
|
|
2518
|
+
failure_count INTEGER NOT NULL DEFAULT 0
|
|
2519
|
+
);
|
|
2520
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_type ON webhook_hooks(type);
|
|
2521
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_enabled ON webhook_hooks(enabled);
|
|
2522
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (10);
|
|
2435
2523
|
`
|
|
2436
2524
|
];
|
|
2437
2525
|
});
|
|
@@ -2511,6 +2599,93 @@ var init_redact = __esm(() => {
|
|
|
2511
2599
|
];
|
|
2512
2600
|
});
|
|
2513
2601
|
|
|
2602
|
+
// src/lib/hooks.ts
|
|
2603
|
+
var exports_hooks = {};
|
|
2604
|
+
__export(exports_hooks, {
|
|
2605
|
+
hookRegistry: () => hookRegistry
|
|
2606
|
+
});
|
|
2607
|
+
function generateHookId() {
|
|
2608
|
+
return `hook_${++_idCounter}_${Date.now().toString(36)}`;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
class HookRegistry {
|
|
2612
|
+
hooks = new Map;
|
|
2613
|
+
register(reg) {
|
|
2614
|
+
const id = generateHookId();
|
|
2615
|
+
const hook = {
|
|
2616
|
+
...reg,
|
|
2617
|
+
id,
|
|
2618
|
+
priority: reg.priority ?? 50
|
|
2619
|
+
};
|
|
2620
|
+
this.hooks.set(id, hook);
|
|
2621
|
+
return id;
|
|
2622
|
+
}
|
|
2623
|
+
unregister(hookId) {
|
|
2624
|
+
const hook = this.hooks.get(hookId);
|
|
2625
|
+
if (!hook)
|
|
2626
|
+
return false;
|
|
2627
|
+
if (hook.builtin)
|
|
2628
|
+
return false;
|
|
2629
|
+
this.hooks.delete(hookId);
|
|
2630
|
+
return true;
|
|
2631
|
+
}
|
|
2632
|
+
list(type) {
|
|
2633
|
+
const all = [...this.hooks.values()];
|
|
2634
|
+
if (!type)
|
|
2635
|
+
return all;
|
|
2636
|
+
return all.filter((h) => h.type === type);
|
|
2637
|
+
}
|
|
2638
|
+
async runHooks(type, context) {
|
|
2639
|
+
const matching = this.getMatchingHooks(type, context);
|
|
2640
|
+
if (matching.length === 0)
|
|
2641
|
+
return true;
|
|
2642
|
+
matching.sort((a, b) => a.priority - b.priority);
|
|
2643
|
+
for (const hook of matching) {
|
|
2644
|
+
if (hook.blocking) {
|
|
2645
|
+
try {
|
|
2646
|
+
const result = await hook.handler(context);
|
|
2647
|
+
if (result === false)
|
|
2648
|
+
return false;
|
|
2649
|
+
} catch (err) {
|
|
2650
|
+
console.error(`[hooks] blocking hook ${hook.id} (${type}) threw:`, err);
|
|
2651
|
+
}
|
|
2652
|
+
} else {
|
|
2653
|
+
Promise.resolve().then(() => hook.handler(context)).catch((err) => console.error(`[hooks] non-blocking hook ${hook.id} (${type}) threw:`, err));
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
return true;
|
|
2657
|
+
}
|
|
2658
|
+
getMatchingHooks(type, context) {
|
|
2659
|
+
const ctx = context;
|
|
2660
|
+
return [...this.hooks.values()].filter((hook) => {
|
|
2661
|
+
if (hook.type !== type)
|
|
2662
|
+
return false;
|
|
2663
|
+
if (hook.agentId && hook.agentId !== ctx.agentId)
|
|
2664
|
+
return false;
|
|
2665
|
+
if (hook.projectId && hook.projectId !== ctx.projectId)
|
|
2666
|
+
return false;
|
|
2667
|
+
return true;
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
stats() {
|
|
2671
|
+
const all = [...this.hooks.values()];
|
|
2672
|
+
const byType = {};
|
|
2673
|
+
for (const hook of all) {
|
|
2674
|
+
byType[hook.type] = (byType[hook.type] ?? 0) + 1;
|
|
2675
|
+
}
|
|
2676
|
+
return {
|
|
2677
|
+
total: all.length,
|
|
2678
|
+
byType,
|
|
2679
|
+
blocking: all.filter((h) => h.blocking).length,
|
|
2680
|
+
nonBlocking: all.filter((h) => !h.blocking).length
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
var _idCounter = 0, hookRegistry;
|
|
2685
|
+
var init_hooks = __esm(() => {
|
|
2686
|
+
hookRegistry = new HookRegistry;
|
|
2687
|
+
});
|
|
2688
|
+
|
|
2514
2689
|
// src/db/entity-memories.ts
|
|
2515
2690
|
function parseEntityMemoryRow(row) {
|
|
2516
2691
|
return {
|
|
@@ -2566,6 +2741,22 @@ var init_entity_memories = __esm(() => {
|
|
|
2566
2741
|
});
|
|
2567
2742
|
|
|
2568
2743
|
// src/db/memories.ts
|
|
2744
|
+
var exports_memories = {};
|
|
2745
|
+
__export(exports_memories, {
|
|
2746
|
+
updateMemory: () => updateMemory,
|
|
2747
|
+
touchMemory: () => touchMemory,
|
|
2748
|
+
parseMemoryRow: () => parseMemoryRow,
|
|
2749
|
+
listMemories: () => listMemories,
|
|
2750
|
+
incrementRecallCount: () => incrementRecallCount,
|
|
2751
|
+
getMemoryVersions: () => getMemoryVersions,
|
|
2752
|
+
getMemoryByKey: () => getMemoryByKey,
|
|
2753
|
+
getMemory: () => getMemory,
|
|
2754
|
+
getMemoriesByKey: () => getMemoriesByKey,
|
|
2755
|
+
deleteMemory: () => deleteMemory,
|
|
2756
|
+
createMemory: () => createMemory,
|
|
2757
|
+
cleanExpiredMemories: () => cleanExpiredMemories,
|
|
2758
|
+
bulkDeleteMemories: () => bulkDeleteMemories
|
|
2759
|
+
});
|
|
2569
2760
|
function runEntityExtraction(_memory, _projectId, _d) {}
|
|
2570
2761
|
function parseMemoryRow(row) {
|
|
2571
2762
|
return {
|
|
@@ -2668,9 +2859,15 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
2668
2859
|
insertTag.run(id, tag);
|
|
2669
2860
|
}
|
|
2670
2861
|
const memory = getMemory(id, d);
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2862
|
+
runEntityExtraction(memory, input.project_id, d);
|
|
2863
|
+
hookRegistry.runHooks("PostMemorySave", {
|
|
2864
|
+
memory,
|
|
2865
|
+
wasUpdated: false,
|
|
2866
|
+
agentId: input.agent_id,
|
|
2867
|
+
projectId: input.project_id,
|
|
2868
|
+
sessionId: input.session_id,
|
|
2869
|
+
timestamp: Date.now()
|
|
2870
|
+
});
|
|
2674
2871
|
return memory;
|
|
2675
2872
|
}
|
|
2676
2873
|
function getMemory(id, db) {
|
|
@@ -2894,20 +3091,33 @@ function updateMemory(id, input, db) {
|
|
|
2894
3091
|
params.push(id);
|
|
2895
3092
|
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2896
3093
|
const updated = getMemory(id, d);
|
|
2897
|
-
|
|
2898
|
-
|
|
3094
|
+
if (input.value !== undefined) {
|
|
3095
|
+
try {
|
|
2899
3096
|
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
2900
3097
|
for (const link of oldLinks) {
|
|
2901
3098
|
unlinkEntityFromMemory(link.entity_id, updated.id, d);
|
|
2902
3099
|
}
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
3100
|
+
} catch {}
|
|
3101
|
+
}
|
|
3102
|
+
hookRegistry.runHooks("PostMemoryUpdate", {
|
|
3103
|
+
memory: updated,
|
|
3104
|
+
previousValue: existing.value,
|
|
3105
|
+
agentId: existing.agent_id ?? undefined,
|
|
3106
|
+
projectId: existing.project_id ?? undefined,
|
|
3107
|
+
sessionId: existing.session_id ?? undefined,
|
|
3108
|
+
timestamp: Date.now()
|
|
3109
|
+
});
|
|
2906
3110
|
return updated;
|
|
2907
3111
|
}
|
|
2908
3112
|
function deleteMemory(id, db) {
|
|
2909
3113
|
const d = db || getDatabase();
|
|
2910
3114
|
const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
3115
|
+
if (result.changes > 0) {
|
|
3116
|
+
hookRegistry.runHooks("PostMemoryDelete", {
|
|
3117
|
+
memoryId: id,
|
|
3118
|
+
timestamp: Date.now()
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
2911
3121
|
return result.changes > 0;
|
|
2912
3122
|
}
|
|
2913
3123
|
function bulkDeleteMemories(ids, db) {
|
|
@@ -2926,6 +3136,20 @@ function touchMemory(id, db) {
|
|
|
2926
3136
|
const d = db || getDatabase();
|
|
2927
3137
|
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
2928
3138
|
}
|
|
3139
|
+
function incrementRecallCount(id, db) {
|
|
3140
|
+
const d = db || getDatabase();
|
|
3141
|
+
try {
|
|
3142
|
+
d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
3143
|
+
const row = d.query("SELECT recall_count, importance FROM memories WHERE id = ?").get(id);
|
|
3144
|
+
if (!row)
|
|
3145
|
+
return;
|
|
3146
|
+
const promotions = Math.floor(row.recall_count / RECALL_PROMOTE_THRESHOLD);
|
|
3147
|
+
if (promotions > 0 && row.importance < 10) {
|
|
3148
|
+
const newImportance = Math.min(10, row.importance + 1);
|
|
3149
|
+
d.run("UPDATE memories SET importance = ? WHERE id = ? AND importance < 10", [newImportance, id]);
|
|
3150
|
+
}
|
|
3151
|
+
} catch {}
|
|
3152
|
+
}
|
|
2929
3153
|
function cleanExpiredMemories(db) {
|
|
2930
3154
|
const d = db || getDatabase();
|
|
2931
3155
|
const timestamp = now();
|
|
@@ -2958,10 +3182,12 @@ function getMemoryVersions(memoryId, db) {
|
|
|
2958
3182
|
return [];
|
|
2959
3183
|
}
|
|
2960
3184
|
}
|
|
3185
|
+
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
2961
3186
|
var init_memories = __esm(() => {
|
|
2962
3187
|
init_types();
|
|
2963
3188
|
init_database();
|
|
2964
3189
|
init_redact();
|
|
3190
|
+
init_hooks();
|
|
2965
3191
|
init_entity_memories();
|
|
2966
3192
|
});
|
|
2967
3193
|
|
|
@@ -3012,6 +3238,13 @@ function createEntity(input, db) {
|
|
|
3012
3238
|
timestamp,
|
|
3013
3239
|
timestamp
|
|
3014
3240
|
]);
|
|
3241
|
+
hookRegistry.runHooks("PostEntityCreate", {
|
|
3242
|
+
entityId: id,
|
|
3243
|
+
name: input.name,
|
|
3244
|
+
entityType: input.type,
|
|
3245
|
+
projectId: input.project_id,
|
|
3246
|
+
timestamp: Date.now()
|
|
3247
|
+
});
|
|
3015
3248
|
return getEntity(id, d);
|
|
3016
3249
|
}
|
|
3017
3250
|
function getEntity(id, db) {
|
|
@@ -3097,6 +3330,7 @@ function mergeEntities(sourceId, targetId, db) {
|
|
|
3097
3330
|
var init_entities = __esm(() => {
|
|
3098
3331
|
init_database();
|
|
3099
3332
|
init_types();
|
|
3333
|
+
init_hooks();
|
|
3100
3334
|
});
|
|
3101
3335
|
|
|
3102
3336
|
// src/lib/search.ts
|
|
@@ -3696,7 +3930,15 @@ function createRelation(input, db) {
|
|
|
3696
3930
|
DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
|
|
3697
3931
|
const row = d.query(`SELECT * FROM relations
|
|
3698
3932
|
WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
|
|
3699
|
-
|
|
3933
|
+
const relation = parseRelationRow(row);
|
|
3934
|
+
hookRegistry.runHooks("PostRelationCreate", {
|
|
3935
|
+
relationId: relation.id,
|
|
3936
|
+
sourceEntityId: relation.source_entity_id,
|
|
3937
|
+
targetEntityId: relation.target_entity_id,
|
|
3938
|
+
relationType: relation.relation_type,
|
|
3939
|
+
timestamp: Date.now()
|
|
3940
|
+
});
|
|
3941
|
+
return relation;
|
|
3700
3942
|
}
|
|
3701
3943
|
function listRelations(filter, db) {
|
|
3702
3944
|
const d = db || getDatabase();
|
|
@@ -3805,6 +4047,7 @@ function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
|
|
|
3805
4047
|
}
|
|
3806
4048
|
var init_relations = __esm(() => {
|
|
3807
4049
|
init_database();
|
|
4050
|
+
init_hooks();
|
|
3808
4051
|
});
|
|
3809
4052
|
|
|
3810
4053
|
// src/lib/poll.ts
|
|
@@ -4466,184 +4709,1640 @@ class AutoMemoryQueue {
|
|
|
4466
4709
|
}
|
|
4467
4710
|
}
|
|
4468
4711
|
}
|
|
4469
|
-
var MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue;
|
|
4470
|
-
var init_auto_memory_queue = __esm(() => {
|
|
4471
|
-
autoMemoryQueue = new AutoMemoryQueue;
|
|
4712
|
+
var MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue;
|
|
4713
|
+
var init_auto_memory_queue = __esm(() => {
|
|
4714
|
+
autoMemoryQueue = new AutoMemoryQueue;
|
|
4715
|
+
});
|
|
4716
|
+
|
|
4717
|
+
// src/lib/auto-memory.ts
|
|
4718
|
+
var exports_auto_memory = {};
|
|
4719
|
+
__export(exports_auto_memory, {
|
|
4720
|
+
processConversationTurn: () => processConversationTurn,
|
|
4721
|
+
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
4722
|
+
configureAutoMemory: () => configureAutoMemory
|
|
4723
|
+
});
|
|
4724
|
+
function isDuplicate(content, agentId, projectId) {
|
|
4725
|
+
try {
|
|
4726
|
+
const query = content.split(/\s+/).filter((w) => w.length > 3).slice(0, 10).join(" ");
|
|
4727
|
+
if (!query)
|
|
4728
|
+
return false;
|
|
4729
|
+
const results = searchMemories(query, {
|
|
4730
|
+
agent_id: agentId,
|
|
4731
|
+
project_id: projectId,
|
|
4732
|
+
limit: 3
|
|
4733
|
+
});
|
|
4734
|
+
if (results.length === 0)
|
|
4735
|
+
return false;
|
|
4736
|
+
const contentWords = new Set(content.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
|
|
4737
|
+
for (const result of results) {
|
|
4738
|
+
const existingWords = new Set(result.memory.value.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
|
|
4739
|
+
if (contentWords.size === 0 || existingWords.size === 0)
|
|
4740
|
+
continue;
|
|
4741
|
+
const intersection = [...contentWords].filter((w) => existingWords.has(w)).length;
|
|
4742
|
+
const union = new Set([...contentWords, ...existingWords]).size;
|
|
4743
|
+
const similarity = intersection / union;
|
|
4744
|
+
if (similarity >= DEDUP_SIMILARITY_THRESHOLD)
|
|
4745
|
+
return true;
|
|
4746
|
+
}
|
|
4747
|
+
return false;
|
|
4748
|
+
} catch {
|
|
4749
|
+
return false;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
async function linkEntitiesToMemory(memoryId, content, _agentId, projectId) {
|
|
4753
|
+
const provider = providerRegistry.getAvailable();
|
|
4754
|
+
if (!provider)
|
|
4755
|
+
return;
|
|
4756
|
+
try {
|
|
4757
|
+
const { entities, relations } = await provider.extractEntities(content);
|
|
4758
|
+
const entityIdMap = new Map;
|
|
4759
|
+
for (const extracted of entities) {
|
|
4760
|
+
if (extracted.confidence < 0.6)
|
|
4761
|
+
continue;
|
|
4762
|
+
try {
|
|
4763
|
+
const existing = getEntityByName(extracted.name);
|
|
4764
|
+
const entityId = existing ? existing.id : createEntity({
|
|
4765
|
+
name: extracted.name,
|
|
4766
|
+
type: extracted.type,
|
|
4767
|
+
project_id: projectId
|
|
4768
|
+
}).id;
|
|
4769
|
+
entityIdMap.set(extracted.name, entityId);
|
|
4770
|
+
linkEntityToMemory(entityId, memoryId, "subject");
|
|
4771
|
+
} catch {}
|
|
4772
|
+
}
|
|
4773
|
+
for (const rel of relations) {
|
|
4774
|
+
const fromId = entityIdMap.get(rel.from);
|
|
4775
|
+
const toId = entityIdMap.get(rel.to);
|
|
4776
|
+
if (!fromId || !toId)
|
|
4777
|
+
continue;
|
|
4778
|
+
try {
|
|
4779
|
+
createRelation({
|
|
4780
|
+
source_entity_id: fromId,
|
|
4781
|
+
target_entity_id: toId,
|
|
4782
|
+
relation_type: rel.type
|
|
4783
|
+
});
|
|
4784
|
+
} catch {}
|
|
4785
|
+
}
|
|
4786
|
+
} catch (err) {
|
|
4787
|
+
console.error("[auto-memory] entity linking failed:", err);
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
async function saveExtractedMemory(extracted, context) {
|
|
4791
|
+
const minImportance = providerRegistry.getConfig().minImportance;
|
|
4792
|
+
if (extracted.importance < minImportance)
|
|
4793
|
+
return null;
|
|
4794
|
+
if (!extracted.content.trim())
|
|
4795
|
+
return null;
|
|
4796
|
+
if (isDuplicate(extracted.content, context.agentId, context.projectId)) {
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
try {
|
|
4800
|
+
const input = {
|
|
4801
|
+
key: extracted.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
|
|
4802
|
+
value: extracted.content,
|
|
4803
|
+
category: extracted.category,
|
|
4804
|
+
scope: extracted.suggestedScope,
|
|
4805
|
+
importance: extracted.importance,
|
|
4806
|
+
tags: [
|
|
4807
|
+
...extracted.tags,
|
|
4808
|
+
"auto-extracted",
|
|
4809
|
+
...context.sessionId ? [`session:${context.sessionId}`] : []
|
|
4810
|
+
],
|
|
4811
|
+
agent_id: context.agentId,
|
|
4812
|
+
project_id: context.projectId,
|
|
4813
|
+
session_id: context.sessionId,
|
|
4814
|
+
metadata: {
|
|
4815
|
+
reasoning: extracted.reasoning,
|
|
4816
|
+
auto_extracted: true,
|
|
4817
|
+
extracted_at: new Date().toISOString()
|
|
4818
|
+
}
|
|
4819
|
+
};
|
|
4820
|
+
const memory = createMemory(input, "merge");
|
|
4821
|
+
return memory.id;
|
|
4822
|
+
} catch (err) {
|
|
4823
|
+
console.error("[auto-memory] saveExtractedMemory failed:", err);
|
|
4824
|
+
return null;
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
async function processJob(job) {
|
|
4828
|
+
if (!providerRegistry.getConfig().enabled)
|
|
4829
|
+
return;
|
|
4830
|
+
const provider = providerRegistry.getAvailable();
|
|
4831
|
+
if (!provider)
|
|
4832
|
+
return;
|
|
4833
|
+
const context = {
|
|
4834
|
+
agentId: job.agentId,
|
|
4835
|
+
projectId: job.projectId,
|
|
4836
|
+
sessionId: job.sessionId
|
|
4837
|
+
};
|
|
4838
|
+
let extracted = [];
|
|
4839
|
+
try {
|
|
4840
|
+
extracted = await provider.extractMemories(job.turn, context);
|
|
4841
|
+
} catch {
|
|
4842
|
+
const fallbacks = providerRegistry.getFallbacks();
|
|
4843
|
+
for (const fallback of fallbacks) {
|
|
4844
|
+
try {
|
|
4845
|
+
extracted = await fallback.extractMemories(job.turn, context);
|
|
4846
|
+
if (extracted.length > 0)
|
|
4847
|
+
break;
|
|
4848
|
+
} catch {
|
|
4849
|
+
continue;
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
if (extracted.length === 0)
|
|
4854
|
+
return;
|
|
4855
|
+
for (const memory of extracted) {
|
|
4856
|
+
const memoryId = await saveExtractedMemory(memory, context);
|
|
4857
|
+
if (!memoryId)
|
|
4858
|
+
continue;
|
|
4859
|
+
if (providerRegistry.getConfig().autoEntityLink) {
|
|
4860
|
+
linkEntitiesToMemory(memoryId, memory.content, job.agentId, job.projectId);
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
function processConversationTurn(turn, context, source = "turn") {
|
|
4865
|
+
if (!turn?.trim())
|
|
4866
|
+
return;
|
|
4867
|
+
autoMemoryQueue.enqueue({
|
|
4868
|
+
...context,
|
|
4869
|
+
turn,
|
|
4870
|
+
timestamp: Date.now(),
|
|
4871
|
+
source
|
|
4872
|
+
});
|
|
4873
|
+
}
|
|
4874
|
+
function getAutoMemoryStats() {
|
|
4875
|
+
return autoMemoryQueue.getStats();
|
|
4876
|
+
}
|
|
4877
|
+
function configureAutoMemory(config) {
|
|
4878
|
+
providerRegistry.configure(config);
|
|
4879
|
+
}
|
|
4880
|
+
var DEDUP_SIMILARITY_THRESHOLD = 0.85;
|
|
4881
|
+
var init_auto_memory = __esm(() => {
|
|
4882
|
+
init_memories();
|
|
4883
|
+
init_search();
|
|
4884
|
+
init_entities();
|
|
4885
|
+
init_relations();
|
|
4886
|
+
init_entity_memories();
|
|
4887
|
+
init_registry();
|
|
4888
|
+
init_auto_memory_queue();
|
|
4889
|
+
autoMemoryQueue.setHandler(processJob);
|
|
4890
|
+
});
|
|
4891
|
+
|
|
4892
|
+
// src/db/webhook_hooks.ts
|
|
4893
|
+
var exports_webhook_hooks = {};
|
|
4894
|
+
__export(exports_webhook_hooks, {
|
|
4895
|
+
updateWebhookHook: () => updateWebhookHook,
|
|
4896
|
+
recordWebhookInvocation: () => recordWebhookInvocation,
|
|
4897
|
+
listWebhookHooks: () => listWebhookHooks,
|
|
4898
|
+
getWebhookHook: () => getWebhookHook,
|
|
4899
|
+
deleteWebhookHook: () => deleteWebhookHook,
|
|
4900
|
+
createWebhookHook: () => createWebhookHook
|
|
4901
|
+
});
|
|
4902
|
+
function parseRow(row) {
|
|
4903
|
+
return {
|
|
4904
|
+
id: row["id"],
|
|
4905
|
+
type: row["type"],
|
|
4906
|
+
handlerUrl: row["handler_url"],
|
|
4907
|
+
priority: row["priority"],
|
|
4908
|
+
blocking: Boolean(row["blocking"]),
|
|
4909
|
+
agentId: row["agent_id"] || undefined,
|
|
4910
|
+
projectId: row["project_id"] || undefined,
|
|
4911
|
+
description: row["description"] || undefined,
|
|
4912
|
+
enabled: Boolean(row["enabled"]),
|
|
4913
|
+
createdAt: row["created_at"],
|
|
4914
|
+
invocationCount: row["invocation_count"],
|
|
4915
|
+
failureCount: row["failure_count"]
|
|
4916
|
+
};
|
|
4917
|
+
}
|
|
4918
|
+
function createWebhookHook(input, db) {
|
|
4919
|
+
const d = db || getDatabase();
|
|
4920
|
+
const id = shortUuid();
|
|
4921
|
+
const timestamp = now();
|
|
4922
|
+
d.run(`INSERT INTO webhook_hooks
|
|
4923
|
+
(id, type, handler_url, priority, blocking, agent_id, project_id, description, enabled, created_at, invocation_count, failure_count)
|
|
4924
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, 0, 0)`, [
|
|
4925
|
+
id,
|
|
4926
|
+
input.type,
|
|
4927
|
+
input.handlerUrl,
|
|
4928
|
+
input.priority ?? 50,
|
|
4929
|
+
input.blocking ? 1 : 0,
|
|
4930
|
+
input.agentId ?? null,
|
|
4931
|
+
input.projectId ?? null,
|
|
4932
|
+
input.description ?? null,
|
|
4933
|
+
timestamp
|
|
4934
|
+
]);
|
|
4935
|
+
return getWebhookHook(id, d);
|
|
4936
|
+
}
|
|
4937
|
+
function getWebhookHook(id, db) {
|
|
4938
|
+
const d = db || getDatabase();
|
|
4939
|
+
const row = d.query("SELECT * FROM webhook_hooks WHERE id = ?").get(id);
|
|
4940
|
+
return row ? parseRow(row) : null;
|
|
4941
|
+
}
|
|
4942
|
+
function listWebhookHooks(filter = {}, db) {
|
|
4943
|
+
const d = db || getDatabase();
|
|
4944
|
+
const conditions = [];
|
|
4945
|
+
const params = [];
|
|
4946
|
+
if (filter.type) {
|
|
4947
|
+
conditions.push("type = ?");
|
|
4948
|
+
params.push(filter.type);
|
|
4949
|
+
}
|
|
4950
|
+
if (filter.enabled !== undefined) {
|
|
4951
|
+
conditions.push("enabled = ?");
|
|
4952
|
+
params.push(filter.enabled ? 1 : 0);
|
|
4953
|
+
}
|
|
4954
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
4955
|
+
const rows = d.query(`SELECT * FROM webhook_hooks ${where} ORDER BY priority ASC, created_at ASC`).all(...params);
|
|
4956
|
+
return rows.map(parseRow);
|
|
4957
|
+
}
|
|
4958
|
+
function updateWebhookHook(id, updates, db) {
|
|
4959
|
+
const d = db || getDatabase();
|
|
4960
|
+
const existing = getWebhookHook(id, d);
|
|
4961
|
+
if (!existing)
|
|
4962
|
+
return null;
|
|
4963
|
+
const sets = [];
|
|
4964
|
+
const params = [];
|
|
4965
|
+
if (updates.enabled !== undefined) {
|
|
4966
|
+
sets.push("enabled = ?");
|
|
4967
|
+
params.push(updates.enabled ? 1 : 0);
|
|
4968
|
+
}
|
|
4969
|
+
if (updates.description !== undefined) {
|
|
4970
|
+
sets.push("description = ?");
|
|
4971
|
+
params.push(updates.description);
|
|
4972
|
+
}
|
|
4973
|
+
if (updates.priority !== undefined) {
|
|
4974
|
+
sets.push("priority = ?");
|
|
4975
|
+
params.push(updates.priority);
|
|
4976
|
+
}
|
|
4977
|
+
if (sets.length > 0) {
|
|
4978
|
+
params.push(id);
|
|
4979
|
+
d.run(`UPDATE webhook_hooks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
4980
|
+
}
|
|
4981
|
+
return getWebhookHook(id, d);
|
|
4982
|
+
}
|
|
4983
|
+
function deleteWebhookHook(id, db) {
|
|
4984
|
+
const d = db || getDatabase();
|
|
4985
|
+
const result = d.run("DELETE FROM webhook_hooks WHERE id = ?", [id]);
|
|
4986
|
+
return result.changes > 0;
|
|
4987
|
+
}
|
|
4988
|
+
function recordWebhookInvocation(id, success, db) {
|
|
4989
|
+
const d = db || getDatabase();
|
|
4990
|
+
if (success) {
|
|
4991
|
+
d.run("UPDATE webhook_hooks SET invocation_count = invocation_count + 1 WHERE id = ?", [id]);
|
|
4992
|
+
} else {
|
|
4993
|
+
d.run("UPDATE webhook_hooks SET invocation_count = invocation_count + 1, failure_count = failure_count + 1 WHERE id = ?", [id]);
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
var init_webhook_hooks = __esm(() => {
|
|
4997
|
+
init_database();
|
|
4998
|
+
});
|
|
4999
|
+
|
|
5000
|
+
// src/db/synthesis.ts
|
|
5001
|
+
var exports_synthesis = {};
|
|
5002
|
+
__export(exports_synthesis, {
|
|
5003
|
+
updateSynthesisRun: () => updateSynthesisRun,
|
|
5004
|
+
updateProposal: () => updateProposal,
|
|
5005
|
+
recordSynthesisEvent: () => recordSynthesisEvent,
|
|
5006
|
+
listSynthesisRuns: () => listSynthesisRuns,
|
|
5007
|
+
listSynthesisEvents: () => listSynthesisEvents,
|
|
5008
|
+
listProposals: () => listProposals,
|
|
5009
|
+
listMetrics: () => listMetrics,
|
|
5010
|
+
getSynthesisRun: () => getSynthesisRun,
|
|
5011
|
+
getProposal: () => getProposal,
|
|
5012
|
+
createSynthesisRun: () => createSynthesisRun,
|
|
5013
|
+
createProposal: () => createProposal,
|
|
5014
|
+
createMetric: () => createMetric
|
|
5015
|
+
});
|
|
5016
|
+
function parseRunRow(row) {
|
|
5017
|
+
return {
|
|
5018
|
+
id: row["id"],
|
|
5019
|
+
triggered_by: row["triggered_by"],
|
|
5020
|
+
project_id: row["project_id"] || null,
|
|
5021
|
+
agent_id: row["agent_id"] || null,
|
|
5022
|
+
corpus_size: row["corpus_size"],
|
|
5023
|
+
proposals_generated: row["proposals_generated"],
|
|
5024
|
+
proposals_accepted: row["proposals_accepted"],
|
|
5025
|
+
proposals_rejected: row["proposals_rejected"],
|
|
5026
|
+
status: row["status"],
|
|
5027
|
+
error: row["error"] || null,
|
|
5028
|
+
started_at: row["started_at"],
|
|
5029
|
+
completed_at: row["completed_at"] || null
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
function parseProposalRow(row) {
|
|
5033
|
+
return {
|
|
5034
|
+
id: row["id"],
|
|
5035
|
+
run_id: row["run_id"],
|
|
5036
|
+
proposal_type: row["proposal_type"],
|
|
5037
|
+
memory_ids: JSON.parse(row["memory_ids"] || "[]"),
|
|
5038
|
+
target_memory_id: row["target_memory_id"] || null,
|
|
5039
|
+
proposed_changes: JSON.parse(row["proposed_changes"] || "{}"),
|
|
5040
|
+
reasoning: row["reasoning"] || null,
|
|
5041
|
+
confidence: row["confidence"],
|
|
5042
|
+
status: row["status"],
|
|
5043
|
+
created_at: row["created_at"],
|
|
5044
|
+
executed_at: row["executed_at"] || null,
|
|
5045
|
+
rollback_data: row["rollback_data"] ? JSON.parse(row["rollback_data"]) : null
|
|
5046
|
+
};
|
|
5047
|
+
}
|
|
5048
|
+
function parseMetricRow(row) {
|
|
5049
|
+
return {
|
|
5050
|
+
id: row["id"],
|
|
5051
|
+
run_id: row["run_id"],
|
|
5052
|
+
metric_type: row["metric_type"],
|
|
5053
|
+
value: row["value"],
|
|
5054
|
+
baseline: row["baseline"] != null ? row["baseline"] : null,
|
|
5055
|
+
created_at: row["created_at"]
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
function parseEventRow(row) {
|
|
5059
|
+
return {
|
|
5060
|
+
id: row["id"],
|
|
5061
|
+
event_type: row["event_type"],
|
|
5062
|
+
memory_id: row["memory_id"] || null,
|
|
5063
|
+
agent_id: row["agent_id"] || null,
|
|
5064
|
+
project_id: row["project_id"] || null,
|
|
5065
|
+
session_id: row["session_id"] || null,
|
|
5066
|
+
query: row["query"] || null,
|
|
5067
|
+
importance_at_time: row["importance_at_time"] != null ? row["importance_at_time"] : null,
|
|
5068
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
5069
|
+
created_at: row["created_at"]
|
|
5070
|
+
};
|
|
5071
|
+
}
|
|
5072
|
+
function createSynthesisRun(input, db) {
|
|
5073
|
+
const d = db || getDatabase();
|
|
5074
|
+
const id = shortUuid();
|
|
5075
|
+
const timestamp = now();
|
|
5076
|
+
d.run(`INSERT INTO synthesis_runs (id, triggered_by, project_id, agent_id, corpus_size, proposals_generated, proposals_accepted, proposals_rejected, status, started_at)
|
|
5077
|
+
VALUES (?, ?, ?, ?, ?, 0, 0, 0, 'pending', ?)`, [
|
|
5078
|
+
id,
|
|
5079
|
+
input.triggered_by,
|
|
5080
|
+
input.project_id ?? null,
|
|
5081
|
+
input.agent_id ?? null,
|
|
5082
|
+
input.corpus_size ?? 0,
|
|
5083
|
+
timestamp
|
|
5084
|
+
]);
|
|
5085
|
+
return getSynthesisRun(id, d);
|
|
5086
|
+
}
|
|
5087
|
+
function getSynthesisRun(id, db) {
|
|
5088
|
+
const d = db || getDatabase();
|
|
5089
|
+
const row = d.query("SELECT * FROM synthesis_runs WHERE id = ?").get(id);
|
|
5090
|
+
if (!row)
|
|
5091
|
+
return null;
|
|
5092
|
+
return parseRunRow(row);
|
|
5093
|
+
}
|
|
5094
|
+
function listSynthesisRuns(filter, db) {
|
|
5095
|
+
const d = db || getDatabase();
|
|
5096
|
+
const conditions = [];
|
|
5097
|
+
const params = [];
|
|
5098
|
+
if (filter.project_id !== undefined) {
|
|
5099
|
+
if (filter.project_id === null) {
|
|
5100
|
+
conditions.push("project_id IS NULL");
|
|
5101
|
+
} else {
|
|
5102
|
+
conditions.push("project_id = ?");
|
|
5103
|
+
params.push(filter.project_id);
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
if (filter.status) {
|
|
5107
|
+
conditions.push("status = ?");
|
|
5108
|
+
params.push(filter.status);
|
|
5109
|
+
}
|
|
5110
|
+
let sql = "SELECT * FROM synthesis_runs";
|
|
5111
|
+
if (conditions.length > 0) {
|
|
5112
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
5113
|
+
}
|
|
5114
|
+
sql += " ORDER BY started_at DESC";
|
|
5115
|
+
if (filter.limit) {
|
|
5116
|
+
sql += " LIMIT ?";
|
|
5117
|
+
params.push(filter.limit);
|
|
5118
|
+
}
|
|
5119
|
+
const rows = d.query(sql).all(...params);
|
|
5120
|
+
return rows.map(parseRunRow);
|
|
5121
|
+
}
|
|
5122
|
+
function updateSynthesisRun(id, updates, db) {
|
|
5123
|
+
const d = db || getDatabase();
|
|
5124
|
+
const sets = [];
|
|
5125
|
+
const params = [];
|
|
5126
|
+
if (updates.status !== undefined) {
|
|
5127
|
+
sets.push("status = ?");
|
|
5128
|
+
params.push(updates.status);
|
|
5129
|
+
}
|
|
5130
|
+
if (updates.error !== undefined) {
|
|
5131
|
+
sets.push("error = ?");
|
|
5132
|
+
params.push(updates.error);
|
|
5133
|
+
}
|
|
5134
|
+
if (updates.corpus_size !== undefined) {
|
|
5135
|
+
sets.push("corpus_size = ?");
|
|
5136
|
+
params.push(updates.corpus_size);
|
|
5137
|
+
}
|
|
5138
|
+
if (updates.proposals_generated !== undefined) {
|
|
5139
|
+
sets.push("proposals_generated = ?");
|
|
5140
|
+
params.push(updates.proposals_generated);
|
|
5141
|
+
}
|
|
5142
|
+
if (updates.proposals_accepted !== undefined) {
|
|
5143
|
+
sets.push("proposals_accepted = ?");
|
|
5144
|
+
params.push(updates.proposals_accepted);
|
|
5145
|
+
}
|
|
5146
|
+
if (updates.proposals_rejected !== undefined) {
|
|
5147
|
+
sets.push("proposals_rejected = ?");
|
|
5148
|
+
params.push(updates.proposals_rejected);
|
|
5149
|
+
}
|
|
5150
|
+
if (updates.completed_at !== undefined) {
|
|
5151
|
+
sets.push("completed_at = ?");
|
|
5152
|
+
params.push(updates.completed_at);
|
|
5153
|
+
}
|
|
5154
|
+
if (sets.length === 0)
|
|
5155
|
+
return getSynthesisRun(id, d);
|
|
5156
|
+
params.push(id);
|
|
5157
|
+
d.run(`UPDATE synthesis_runs SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5158
|
+
return getSynthesisRun(id, d);
|
|
5159
|
+
}
|
|
5160
|
+
function createProposal(input, db) {
|
|
5161
|
+
const d = db || getDatabase();
|
|
5162
|
+
const id = shortUuid();
|
|
5163
|
+
const timestamp = now();
|
|
5164
|
+
d.run(`INSERT INTO synthesis_proposals (id, run_id, proposal_type, memory_ids, target_memory_id, proposed_changes, reasoning, confidence, status, created_at)
|
|
5165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)`, [
|
|
5166
|
+
id,
|
|
5167
|
+
input.run_id,
|
|
5168
|
+
input.proposal_type,
|
|
5169
|
+
JSON.stringify(input.memory_ids),
|
|
5170
|
+
input.target_memory_id ?? null,
|
|
5171
|
+
JSON.stringify(input.proposed_changes),
|
|
5172
|
+
input.reasoning ?? null,
|
|
5173
|
+
input.confidence,
|
|
5174
|
+
timestamp
|
|
5175
|
+
]);
|
|
5176
|
+
return getProposal(id, d);
|
|
5177
|
+
}
|
|
5178
|
+
function getProposal(id, db) {
|
|
5179
|
+
const d = db || getDatabase();
|
|
5180
|
+
const row = d.query("SELECT * FROM synthesis_proposals WHERE id = ?").get(id);
|
|
5181
|
+
if (!row)
|
|
5182
|
+
return null;
|
|
5183
|
+
return parseProposalRow(row);
|
|
5184
|
+
}
|
|
5185
|
+
function listProposals(run_id, filter, db) {
|
|
5186
|
+
const d = db || getDatabase();
|
|
5187
|
+
const params = [run_id];
|
|
5188
|
+
let sql = "SELECT * FROM synthesis_proposals WHERE run_id = ?";
|
|
5189
|
+
if (filter?.status) {
|
|
5190
|
+
sql += " AND status = ?";
|
|
5191
|
+
params.push(filter.status);
|
|
5192
|
+
}
|
|
5193
|
+
sql += " ORDER BY created_at ASC";
|
|
5194
|
+
const rows = d.query(sql).all(...params);
|
|
5195
|
+
return rows.map(parseProposalRow);
|
|
5196
|
+
}
|
|
5197
|
+
function updateProposal(id, updates, db) {
|
|
5198
|
+
const d = db || getDatabase();
|
|
5199
|
+
const sets = [];
|
|
5200
|
+
const params = [];
|
|
5201
|
+
if (updates.status !== undefined) {
|
|
5202
|
+
sets.push("status = ?");
|
|
5203
|
+
params.push(updates.status);
|
|
5204
|
+
}
|
|
5205
|
+
if (updates.executed_at !== undefined) {
|
|
5206
|
+
sets.push("executed_at = ?");
|
|
5207
|
+
params.push(updates.executed_at);
|
|
5208
|
+
}
|
|
5209
|
+
if (updates.rollback_data !== undefined) {
|
|
5210
|
+
sets.push("rollback_data = ?");
|
|
5211
|
+
params.push(JSON.stringify(updates.rollback_data));
|
|
5212
|
+
}
|
|
5213
|
+
if (sets.length === 0)
|
|
5214
|
+
return getProposal(id, d);
|
|
5215
|
+
params.push(id);
|
|
5216
|
+
d.run(`UPDATE synthesis_proposals SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
5217
|
+
return getProposal(id, d);
|
|
5218
|
+
}
|
|
5219
|
+
function createMetric(input, db) {
|
|
5220
|
+
const d = db || getDatabase();
|
|
5221
|
+
const id = shortUuid();
|
|
5222
|
+
const timestamp = now();
|
|
5223
|
+
d.run(`INSERT INTO synthesis_metrics (id, run_id, metric_type, value, baseline, created_at)
|
|
5224
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [id, input.run_id, input.metric_type, input.value, input.baseline ?? null, timestamp]);
|
|
5225
|
+
return { id, run_id: input.run_id, metric_type: input.metric_type, value: input.value, baseline: input.baseline ?? null, created_at: timestamp };
|
|
5226
|
+
}
|
|
5227
|
+
function listMetrics(run_id, db) {
|
|
5228
|
+
const d = db || getDatabase();
|
|
5229
|
+
const rows = d.query("SELECT * FROM synthesis_metrics WHERE run_id = ? ORDER BY created_at ASC").all(run_id);
|
|
5230
|
+
return rows.map(parseMetricRow);
|
|
5231
|
+
}
|
|
5232
|
+
function recordSynthesisEvent(input, db) {
|
|
5233
|
+
try {
|
|
5234
|
+
const d = db || getDatabase();
|
|
5235
|
+
const id = shortUuid();
|
|
5236
|
+
const timestamp = now();
|
|
5237
|
+
d.run(`INSERT INTO synthesis_events (id, event_type, memory_id, agent_id, project_id, session_id, query, importance_at_time, metadata, created_at)
|
|
5238
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
5239
|
+
id,
|
|
5240
|
+
input.event_type,
|
|
5241
|
+
input.memory_id ?? null,
|
|
5242
|
+
input.agent_id ?? null,
|
|
5243
|
+
input.project_id ?? null,
|
|
5244
|
+
input.session_id ?? null,
|
|
5245
|
+
input.query ?? null,
|
|
5246
|
+
input.importance_at_time ?? null,
|
|
5247
|
+
JSON.stringify(input.metadata ?? {}),
|
|
5248
|
+
timestamp
|
|
5249
|
+
]);
|
|
5250
|
+
} catch {}
|
|
5251
|
+
}
|
|
5252
|
+
function listSynthesisEvents(filter, db) {
|
|
5253
|
+
const d = db || getDatabase();
|
|
5254
|
+
const conditions = [];
|
|
5255
|
+
const params = [];
|
|
5256
|
+
if (filter.memory_id) {
|
|
5257
|
+
conditions.push("memory_id = ?");
|
|
5258
|
+
params.push(filter.memory_id);
|
|
5259
|
+
}
|
|
5260
|
+
if (filter.project_id) {
|
|
5261
|
+
conditions.push("project_id = ?");
|
|
5262
|
+
params.push(filter.project_id);
|
|
5263
|
+
}
|
|
5264
|
+
if (filter.event_type) {
|
|
5265
|
+
conditions.push("event_type = ?");
|
|
5266
|
+
params.push(filter.event_type);
|
|
5267
|
+
}
|
|
5268
|
+
if (filter.since) {
|
|
5269
|
+
conditions.push("created_at >= ?");
|
|
5270
|
+
params.push(filter.since);
|
|
5271
|
+
}
|
|
5272
|
+
let sql = "SELECT * FROM synthesis_events";
|
|
5273
|
+
if (conditions.length > 0) {
|
|
5274
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
5275
|
+
}
|
|
5276
|
+
sql += " ORDER BY created_at DESC";
|
|
5277
|
+
if (filter.limit) {
|
|
5278
|
+
sql += " LIMIT ?";
|
|
5279
|
+
params.push(filter.limit);
|
|
5280
|
+
}
|
|
5281
|
+
const rows = d.query(sql).all(...params);
|
|
5282
|
+
return rows.map(parseEventRow);
|
|
5283
|
+
}
|
|
5284
|
+
var init_synthesis = __esm(() => {
|
|
5285
|
+
init_database();
|
|
5286
|
+
});
|
|
5287
|
+
|
|
5288
|
+
// src/lib/built-in-hooks.ts
|
|
5289
|
+
var exports_built_in_hooks = {};
|
|
5290
|
+
__export(exports_built_in_hooks, {
|
|
5291
|
+
reloadWebhooks: () => reloadWebhooks,
|
|
5292
|
+
loadWebhooksFromDb: () => loadWebhooksFromDb
|
|
5293
|
+
});
|
|
5294
|
+
async function getAutoMemory() {
|
|
5295
|
+
if (!_processConversationTurn) {
|
|
5296
|
+
const mod = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
5297
|
+
_processConversationTurn = mod.processConversationTurn;
|
|
5298
|
+
}
|
|
5299
|
+
return _processConversationTurn;
|
|
5300
|
+
}
|
|
5301
|
+
function loadWebhooksFromDb() {
|
|
5302
|
+
if (_webhooksLoaded)
|
|
5303
|
+
return;
|
|
5304
|
+
_webhooksLoaded = true;
|
|
5305
|
+
try {
|
|
5306
|
+
const webhooks = listWebhookHooks({ enabled: true });
|
|
5307
|
+
for (const wh of webhooks) {
|
|
5308
|
+
hookRegistry.register({
|
|
5309
|
+
type: wh.type,
|
|
5310
|
+
blocking: wh.blocking,
|
|
5311
|
+
priority: wh.priority,
|
|
5312
|
+
agentId: wh.agentId,
|
|
5313
|
+
projectId: wh.projectId,
|
|
5314
|
+
description: wh.description ?? `Webhook: ${wh.handlerUrl}`,
|
|
5315
|
+
handler: makeWebhookHandler(wh.id, wh.handlerUrl)
|
|
5316
|
+
});
|
|
5317
|
+
}
|
|
5318
|
+
if (webhooks.length > 0) {
|
|
5319
|
+
console.log(`[hooks] Loaded ${webhooks.length} webhook(s) from DB`);
|
|
5320
|
+
}
|
|
5321
|
+
} catch (err) {
|
|
5322
|
+
console.error("[hooks] Failed to load webhooks from DB:", err);
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
function makeWebhookHandler(webhookId, url) {
|
|
5326
|
+
return async (context) => {
|
|
5327
|
+
try {
|
|
5328
|
+
const res = await fetch(url, {
|
|
5329
|
+
method: "POST",
|
|
5330
|
+
headers: { "Content-Type": "application/json" },
|
|
5331
|
+
body: JSON.stringify(context),
|
|
5332
|
+
signal: AbortSignal.timeout(1e4)
|
|
5333
|
+
});
|
|
5334
|
+
recordWebhookInvocation(webhookId, res.ok);
|
|
5335
|
+
} catch {
|
|
5336
|
+
recordWebhookInvocation(webhookId, false);
|
|
5337
|
+
}
|
|
5338
|
+
};
|
|
5339
|
+
}
|
|
5340
|
+
function reloadWebhooks() {
|
|
5341
|
+
_webhooksLoaded = false;
|
|
5342
|
+
loadWebhooksFromDb();
|
|
5343
|
+
}
|
|
5344
|
+
var _processConversationTurn = null, _webhooksLoaded = false;
|
|
5345
|
+
var init_built_in_hooks = __esm(() => {
|
|
5346
|
+
init_hooks();
|
|
5347
|
+
init_webhook_hooks();
|
|
5348
|
+
hookRegistry.register({
|
|
5349
|
+
type: "PostMemorySave",
|
|
5350
|
+
blocking: false,
|
|
5351
|
+
builtin: true,
|
|
5352
|
+
priority: 100,
|
|
5353
|
+
description: "Trigger async LLM entity extraction when a memory is saved",
|
|
5354
|
+
handler: async (ctx) => {
|
|
5355
|
+
if (ctx.wasUpdated)
|
|
5356
|
+
return;
|
|
5357
|
+
const processConversationTurn2 = await getAutoMemory();
|
|
5358
|
+
processConversationTurn2(`${ctx.memory.key}: ${ctx.memory.value}`, {
|
|
5359
|
+
agentId: ctx.agentId,
|
|
5360
|
+
projectId: ctx.projectId,
|
|
5361
|
+
sessionId: ctx.sessionId
|
|
5362
|
+
});
|
|
5363
|
+
}
|
|
5364
|
+
});
|
|
5365
|
+
hookRegistry.register({
|
|
5366
|
+
type: "OnSessionStart",
|
|
5367
|
+
blocking: false,
|
|
5368
|
+
builtin: true,
|
|
5369
|
+
priority: 100,
|
|
5370
|
+
description: "Record session start as a history memory for analytics",
|
|
5371
|
+
handler: async (ctx) => {
|
|
5372
|
+
const { createMemory: createMemory2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
5373
|
+
try {
|
|
5374
|
+
createMemory2({
|
|
5375
|
+
key: `session-start-${ctx.agentId}`,
|
|
5376
|
+
value: `Agent ${ctx.agentId} started session on project ${ctx.projectId} at ${new Date(ctx.timestamp).toISOString()}`,
|
|
5377
|
+
category: "history",
|
|
5378
|
+
scope: "shared",
|
|
5379
|
+
importance: 3,
|
|
5380
|
+
source: "system",
|
|
5381
|
+
agent_id: ctx.agentId,
|
|
5382
|
+
project_id: ctx.projectId,
|
|
5383
|
+
session_id: ctx.sessionId
|
|
5384
|
+
});
|
|
5385
|
+
} catch {}
|
|
5386
|
+
}
|
|
5387
|
+
});
|
|
5388
|
+
hookRegistry.register({
|
|
5389
|
+
type: "PostMemorySave",
|
|
5390
|
+
blocking: false,
|
|
5391
|
+
builtin: true,
|
|
5392
|
+
priority: 200,
|
|
5393
|
+
description: "Record memory save event for synthesis analytics",
|
|
5394
|
+
handler: async (ctx) => {
|
|
5395
|
+
const { recordSynthesisEvent: recordSynthesisEvent2 } = await Promise.resolve().then(() => (init_synthesis(), exports_synthesis));
|
|
5396
|
+
recordSynthesisEvent2({
|
|
5397
|
+
event_type: "saved",
|
|
5398
|
+
memory_id: ctx.memory.id,
|
|
5399
|
+
agent_id: ctx.agentId,
|
|
5400
|
+
project_id: ctx.projectId,
|
|
5401
|
+
session_id: ctx.sessionId,
|
|
5402
|
+
importance_at_time: ctx.memory.importance
|
|
5403
|
+
});
|
|
5404
|
+
}
|
|
5405
|
+
});
|
|
5406
|
+
hookRegistry.register({
|
|
5407
|
+
type: "PostMemoryInject",
|
|
5408
|
+
blocking: false,
|
|
5409
|
+
builtin: true,
|
|
5410
|
+
priority: 200,
|
|
5411
|
+
description: "Record injection event for synthesis analytics",
|
|
5412
|
+
handler: async (ctx) => {
|
|
5413
|
+
const { recordSynthesisEvent: recordSynthesisEvent2 } = await Promise.resolve().then(() => (init_synthesis(), exports_synthesis));
|
|
5414
|
+
recordSynthesisEvent2({
|
|
5415
|
+
event_type: "injected",
|
|
5416
|
+
agent_id: ctx.agentId,
|
|
5417
|
+
project_id: ctx.projectId,
|
|
5418
|
+
session_id: ctx.sessionId,
|
|
5419
|
+
metadata: { count: ctx.memoriesCount, format: ctx.format }
|
|
5420
|
+
});
|
|
5421
|
+
}
|
|
5422
|
+
});
|
|
5423
|
+
});
|
|
5424
|
+
|
|
5425
|
+
// src/lib/synthesis/corpus-builder.ts
|
|
5426
|
+
function extractTerms(text) {
|
|
5427
|
+
const stopWords = new Set([
|
|
5428
|
+
"a",
|
|
5429
|
+
"an",
|
|
5430
|
+
"the",
|
|
5431
|
+
"is",
|
|
5432
|
+
"it",
|
|
5433
|
+
"in",
|
|
5434
|
+
"of",
|
|
5435
|
+
"for",
|
|
5436
|
+
"to",
|
|
5437
|
+
"and",
|
|
5438
|
+
"or",
|
|
5439
|
+
"but",
|
|
5440
|
+
"not",
|
|
5441
|
+
"with",
|
|
5442
|
+
"this",
|
|
5443
|
+
"that",
|
|
5444
|
+
"are",
|
|
5445
|
+
"was",
|
|
5446
|
+
"be",
|
|
5447
|
+
"by",
|
|
5448
|
+
"at",
|
|
5449
|
+
"as",
|
|
5450
|
+
"on",
|
|
5451
|
+
"has",
|
|
5452
|
+
"have",
|
|
5453
|
+
"had",
|
|
5454
|
+
"do",
|
|
5455
|
+
"did",
|
|
5456
|
+
"does",
|
|
5457
|
+
"will",
|
|
5458
|
+
"would",
|
|
5459
|
+
"can",
|
|
5460
|
+
"could",
|
|
5461
|
+
"should",
|
|
5462
|
+
"may",
|
|
5463
|
+
"might",
|
|
5464
|
+
"shall",
|
|
5465
|
+
"from",
|
|
5466
|
+
"into",
|
|
5467
|
+
"then",
|
|
5468
|
+
"than",
|
|
5469
|
+
"so",
|
|
5470
|
+
"if",
|
|
5471
|
+
"up",
|
|
5472
|
+
"out",
|
|
5473
|
+
"about",
|
|
5474
|
+
"its",
|
|
5475
|
+
"my",
|
|
5476
|
+
"we",
|
|
5477
|
+
"i",
|
|
5478
|
+
"you"
|
|
5479
|
+
]);
|
|
5480
|
+
return new Set(text.toLowerCase().replace(/[^a-z0-9\s\-_]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w)));
|
|
5481
|
+
}
|
|
5482
|
+
function jaccardSimilarity(a, b) {
|
|
5483
|
+
if (a.size === 0 && b.size === 0)
|
|
5484
|
+
return 0;
|
|
5485
|
+
const intersection = new Set([...a].filter((x) => b.has(x)));
|
|
5486
|
+
const union = new Set([...a, ...b]);
|
|
5487
|
+
return intersection.size / union.size;
|
|
5488
|
+
}
|
|
5489
|
+
function isStale(memory) {
|
|
5490
|
+
if (memory.status !== "active")
|
|
5491
|
+
return false;
|
|
5492
|
+
if (memory.importance >= 7)
|
|
5493
|
+
return false;
|
|
5494
|
+
if (!memory.accessed_at)
|
|
5495
|
+
return true;
|
|
5496
|
+
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
5497
|
+
return new Date(memory.accessed_at).getTime() < thirtyDaysAgo;
|
|
5498
|
+
}
|
|
5499
|
+
async function buildCorpus(options) {
|
|
5500
|
+
const d = options.db || getDatabase();
|
|
5501
|
+
const limit = options.limit ?? 500;
|
|
5502
|
+
const projectId = options.projectId ?? null;
|
|
5503
|
+
const memories = listMemories({
|
|
5504
|
+
status: "active",
|
|
5505
|
+
project_id: options.projectId,
|
|
5506
|
+
agent_id: options.agentId,
|
|
5507
|
+
limit
|
|
5508
|
+
}, d);
|
|
5509
|
+
const recallEvents = listSynthesisEvents({
|
|
5510
|
+
event_type: "recalled",
|
|
5511
|
+
project_id: options.projectId
|
|
5512
|
+
}, d);
|
|
5513
|
+
const recallCounts = new Map;
|
|
5514
|
+
const lastRecalledMap = new Map;
|
|
5515
|
+
for (const event of recallEvents) {
|
|
5516
|
+
if (!event.memory_id)
|
|
5517
|
+
continue;
|
|
5518
|
+
recallCounts.set(event.memory_id, (recallCounts.get(event.memory_id) ?? 0) + 1);
|
|
5519
|
+
const existing = lastRecalledMap.get(event.memory_id);
|
|
5520
|
+
if (!existing || event.created_at > existing) {
|
|
5521
|
+
lastRecalledMap.set(event.memory_id, event.created_at);
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
const searchEvents = listSynthesisEvents({
|
|
5525
|
+
event_type: "searched",
|
|
5526
|
+
project_id: options.projectId
|
|
5527
|
+
}, d);
|
|
5528
|
+
const searchHits = new Map;
|
|
5529
|
+
for (const event of searchEvents) {
|
|
5530
|
+
if (!event.memory_id)
|
|
5531
|
+
continue;
|
|
5532
|
+
searchHits.set(event.memory_id, (searchHits.get(event.memory_id) ?? 0) + 1);
|
|
5533
|
+
}
|
|
5534
|
+
const termSets = new Map;
|
|
5535
|
+
for (const m of memories) {
|
|
5536
|
+
termSets.set(m.id, extractTerms(`${m.key} ${m.value} ${m.summary ?? ""}`));
|
|
5537
|
+
}
|
|
5538
|
+
const duplicateCandidates = [];
|
|
5539
|
+
const similarityThreshold = 0.5;
|
|
5540
|
+
const similarIds = new Map;
|
|
5541
|
+
for (let i = 0;i < memories.length; i++) {
|
|
5542
|
+
const a = memories[i];
|
|
5543
|
+
const termsA = termSets.get(a.id);
|
|
5544
|
+
const aSimIds = [];
|
|
5545
|
+
for (let j = i + 1;j < memories.length; j++) {
|
|
5546
|
+
const b = memories[j];
|
|
5547
|
+
const termsB = termSets.get(b.id);
|
|
5548
|
+
const sim = jaccardSimilarity(termsA, termsB);
|
|
5549
|
+
if (sim >= similarityThreshold) {
|
|
5550
|
+
duplicateCandidates.push({ a, b, similarity: sim });
|
|
5551
|
+
aSimIds.push(b.id);
|
|
5552
|
+
const bSimIds = similarIds.get(b.id) ?? [];
|
|
5553
|
+
bSimIds.push(a.id);
|
|
5554
|
+
similarIds.set(b.id, bSimIds);
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
if (aSimIds.length > 0) {
|
|
5558
|
+
const existing = similarIds.get(a.id) ?? [];
|
|
5559
|
+
similarIds.set(a.id, [...existing, ...aSimIds]);
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
duplicateCandidates.sort((a, b) => b.similarity - a.similarity);
|
|
5563
|
+
const items = memories.map((memory) => ({
|
|
5564
|
+
memory,
|
|
5565
|
+
recallCount: recallCounts.get(memory.id) ?? 0,
|
|
5566
|
+
lastRecalled: lastRecalledMap.get(memory.id) ?? null,
|
|
5567
|
+
searchHits: searchHits.get(memory.id) ?? 0,
|
|
5568
|
+
similarMemoryIds: similarIds.get(memory.id) ?? []
|
|
5569
|
+
}));
|
|
5570
|
+
const staleMemories = memories.filter(isStale);
|
|
5571
|
+
const lowImportanceHighRecall = memories.filter((m) => m.importance < 5 && (recallCounts.get(m.id) ?? 0) > 3);
|
|
5572
|
+
const highImportanceLowRecall = memories.filter((m) => m.importance > 7 && (recallCounts.get(m.id) ?? 0) === 0);
|
|
5573
|
+
return {
|
|
5574
|
+
projectId,
|
|
5575
|
+
totalMemories: memories.length,
|
|
5576
|
+
items,
|
|
5577
|
+
staleMemories,
|
|
5578
|
+
duplicateCandidates,
|
|
5579
|
+
lowImportanceHighRecall,
|
|
5580
|
+
highImportanceLowRecall,
|
|
5581
|
+
generatedAt: now()
|
|
5582
|
+
};
|
|
5583
|
+
}
|
|
5584
|
+
var init_corpus_builder = __esm(() => {
|
|
5585
|
+
init_database();
|
|
5586
|
+
init_memories();
|
|
5587
|
+
init_synthesis();
|
|
5588
|
+
});
|
|
5589
|
+
|
|
5590
|
+
// src/lib/synthesis/llm-analyzer.ts
|
|
5591
|
+
function buildCorpusPrompt(corpus, maxProposals) {
|
|
5592
|
+
const lines = [];
|
|
5593
|
+
lines.push(`## Memory Corpus Analysis`);
|
|
5594
|
+
lines.push(`Project: ${corpus.projectId ?? "(global)"}`);
|
|
5595
|
+
lines.push(`Total active memories: ${corpus.totalMemories}`);
|
|
5596
|
+
lines.push(`Analysis generated at: ${corpus.generatedAt}`);
|
|
5597
|
+
lines.push("");
|
|
5598
|
+
if (corpus.staleMemories.length > 0) {
|
|
5599
|
+
lines.push(`## Stale Memories (not accessed in 30+ days, importance < 7)`);
|
|
5600
|
+
lines.push(`Count: ${corpus.staleMemories.length}`);
|
|
5601
|
+
for (const m of corpus.staleMemories.slice(0, 30)) {
|
|
5602
|
+
const accessed = m.accessed_at ?? "never";
|
|
5603
|
+
lines.push(`- id:${m.id} key="${m.key}" importance=${m.importance} lastAccessed=${accessed}`);
|
|
5604
|
+
lines.push(` value: ${m.value.slice(0, 120)}`);
|
|
5605
|
+
}
|
|
5606
|
+
lines.push("");
|
|
5607
|
+
}
|
|
5608
|
+
if (corpus.duplicateCandidates.length > 0) {
|
|
5609
|
+
lines.push(`## Potential Duplicates (term overlap \u2265 50%)`);
|
|
5610
|
+
lines.push(`Count: ${corpus.duplicateCandidates.length} pairs`);
|
|
5611
|
+
for (const { a, b, similarity } of corpus.duplicateCandidates.slice(0, 20)) {
|
|
5612
|
+
lines.push(`- similarity=${similarity.toFixed(2)}`);
|
|
5613
|
+
lines.push(` A id:${a.id} key="${a.key}" importance=${a.importance}: ${a.value.slice(0, 80)}`);
|
|
5614
|
+
lines.push(` B id:${b.id} key="${b.key}" importance=${b.importance}: ${b.value.slice(0, 80)}`);
|
|
5615
|
+
}
|
|
5616
|
+
lines.push("");
|
|
5617
|
+
}
|
|
5618
|
+
if (corpus.lowImportanceHighRecall.length > 0) {
|
|
5619
|
+
lines.push(`## Low Importance But High Recall (candidates for importance promotion)`);
|
|
5620
|
+
for (const m of corpus.lowImportanceHighRecall.slice(0, 20)) {
|
|
5621
|
+
lines.push(`- id:${m.id} key="${m.key}" importance=${m.importance} recall_count=${m.access_count}`);
|
|
5622
|
+
}
|
|
5623
|
+
lines.push("");
|
|
5624
|
+
}
|
|
5625
|
+
if (corpus.highImportanceLowRecall.length > 0) {
|
|
5626
|
+
lines.push(`## High Importance But Never Recalled (may need value update)`);
|
|
5627
|
+
for (const m of corpus.highImportanceLowRecall.slice(0, 20)) {
|
|
5628
|
+
lines.push(`- id:${m.id} key="${m.key}" importance=${m.importance}: ${m.value.slice(0, 80)}`);
|
|
5629
|
+
}
|
|
5630
|
+
lines.push("");
|
|
5631
|
+
}
|
|
5632
|
+
lines.push(`## Instructions`);
|
|
5633
|
+
lines.push(`Generate up to ${maxProposals} proposals as a JSON array. Each proposal must have:`);
|
|
5634
|
+
lines.push(` - type: "merge" | "archive" | "promote" | "update_value" | "add_tag" | "remove_duplicate"`);
|
|
5635
|
+
lines.push(` - memory_ids: string[] (IDs this proposal acts on)`);
|
|
5636
|
+
lines.push(` - target_memory_id: string (optional \u2014 used for merge/remove_duplicate)`);
|
|
5637
|
+
lines.push(` - proposed_changes: object (e.g. {new_importance:8} or {new_value:"..."} or {tags:["x"]})`);
|
|
5638
|
+
lines.push(` - reasoning: string (one sentence)`);
|
|
5639
|
+
lines.push(` - confidence: number 0.0-1.0`);
|
|
5640
|
+
lines.push("");
|
|
5641
|
+
lines.push(`Return ONLY the JSON array. No markdown, no explanation.`);
|
|
5642
|
+
return lines.join(`
|
|
5643
|
+
`);
|
|
5644
|
+
}
|
|
5645
|
+
async function analyzeCorpus(corpus, options) {
|
|
5646
|
+
const startMs = Date.now();
|
|
5647
|
+
const maxProposals = options?.maxProposals ?? 20;
|
|
5648
|
+
const empty = {
|
|
5649
|
+
proposals: [],
|
|
5650
|
+
summary: "No LLM provider available.",
|
|
5651
|
+
analysisDurationMs: 0
|
|
5652
|
+
};
|
|
5653
|
+
let provider = options?.provider ? providerRegistry.getProvider(options.provider) : providerRegistry.getAvailable();
|
|
5654
|
+
if (!provider) {
|
|
5655
|
+
return { ...empty, analysisDurationMs: Date.now() - startMs };
|
|
5656
|
+
}
|
|
5657
|
+
const userPrompt = buildCorpusPrompt(corpus, maxProposals);
|
|
5658
|
+
try {
|
|
5659
|
+
const rawResponse = await callProviderRaw(provider, SYNTHESIS_SYSTEM_PROMPT, userPrompt);
|
|
5660
|
+
if (!rawResponse) {
|
|
5661
|
+
return { ...empty, analysisDurationMs: Date.now() - startMs };
|
|
5662
|
+
}
|
|
5663
|
+
const parsed = parseProposalsResponse(rawResponse);
|
|
5664
|
+
const analysisDurationMs = Date.now() - startMs;
|
|
5665
|
+
const validProposals = parsed.slice(0, maxProposals);
|
|
5666
|
+
return {
|
|
5667
|
+
proposals: validProposals,
|
|
5668
|
+
summary: buildSummary(corpus, validProposals.length),
|
|
5669
|
+
analysisDurationMs
|
|
5670
|
+
};
|
|
5671
|
+
} catch {
|
|
5672
|
+
return { ...empty, analysisDurationMs: Date.now() - startMs };
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
async function callProviderRaw(provider, systemPrompt, userPrompt) {
|
|
5676
|
+
if (!provider)
|
|
5677
|
+
return null;
|
|
5678
|
+
const { config, name } = provider;
|
|
5679
|
+
if (!config.apiKey)
|
|
5680
|
+
return null;
|
|
5681
|
+
const timeoutMs = config.timeoutMs ?? 30000;
|
|
5682
|
+
try {
|
|
5683
|
+
if (name === "anthropic") {
|
|
5684
|
+
return await callAnthropic(config.apiKey, config.model, systemPrompt, userPrompt, timeoutMs);
|
|
5685
|
+
} else if (name === "openai" || name === "cerebras" || name === "grok") {
|
|
5686
|
+
return await callOpenAICompat(name, config.apiKey, config.model, systemPrompt, userPrompt, timeoutMs);
|
|
5687
|
+
}
|
|
5688
|
+
} catch {}
|
|
5689
|
+
return null;
|
|
5690
|
+
}
|
|
5691
|
+
async function callAnthropic(apiKey, model, systemPrompt, userPrompt, timeoutMs) {
|
|
5692
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
5693
|
+
method: "POST",
|
|
5694
|
+
headers: {
|
|
5695
|
+
"x-api-key": apiKey,
|
|
5696
|
+
"anthropic-version": "2023-06-01",
|
|
5697
|
+
"content-type": "application/json"
|
|
5698
|
+
},
|
|
5699
|
+
body: JSON.stringify({
|
|
5700
|
+
model,
|
|
5701
|
+
max_tokens: 2048,
|
|
5702
|
+
system: systemPrompt,
|
|
5703
|
+
messages: [{ role: "user", content: userPrompt }]
|
|
5704
|
+
}),
|
|
5705
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
5706
|
+
});
|
|
5707
|
+
if (!response.ok)
|
|
5708
|
+
return null;
|
|
5709
|
+
const data = await response.json();
|
|
5710
|
+
return data.content?.[0]?.text ?? null;
|
|
5711
|
+
}
|
|
5712
|
+
function getBaseUrlForProvider(providerName) {
|
|
5713
|
+
switch (providerName) {
|
|
5714
|
+
case "openai":
|
|
5715
|
+
return "https://api.openai.com/v1";
|
|
5716
|
+
case "cerebras":
|
|
5717
|
+
return "https://api.cerebras.ai/v1";
|
|
5718
|
+
case "grok":
|
|
5719
|
+
return "https://api.x.ai/v1";
|
|
5720
|
+
default:
|
|
5721
|
+
return "https://api.openai.com/v1";
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
async function callOpenAICompat(providerName, apiKey, model, systemPrompt, userPrompt, timeoutMs) {
|
|
5725
|
+
const baseUrl = getBaseUrlForProvider(providerName);
|
|
5726
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
5727
|
+
method: "POST",
|
|
5728
|
+
headers: {
|
|
5729
|
+
authorization: `Bearer ${apiKey}`,
|
|
5730
|
+
"content-type": "application/json"
|
|
5731
|
+
},
|
|
5732
|
+
body: JSON.stringify({
|
|
5733
|
+
model,
|
|
5734
|
+
max_tokens: 2048,
|
|
5735
|
+
temperature: 0,
|
|
5736
|
+
messages: [
|
|
5737
|
+
{ role: "system", content: systemPrompt },
|
|
5738
|
+
{ role: "user", content: userPrompt }
|
|
5739
|
+
]
|
|
5740
|
+
}),
|
|
5741
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
5742
|
+
});
|
|
5743
|
+
if (!response.ok)
|
|
5744
|
+
return null;
|
|
5745
|
+
const data = await response.json();
|
|
5746
|
+
return data.choices?.[0]?.message?.content ?? null;
|
|
5747
|
+
}
|
|
5748
|
+
function parseProposalsResponse(raw) {
|
|
5749
|
+
try {
|
|
5750
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim();
|
|
5751
|
+
const parsed = JSON.parse(cleaned);
|
|
5752
|
+
if (!Array.isArray(parsed))
|
|
5753
|
+
return [];
|
|
5754
|
+
const validTypes = new Set([
|
|
5755
|
+
"merge",
|
|
5756
|
+
"archive",
|
|
5757
|
+
"promote",
|
|
5758
|
+
"update_value",
|
|
5759
|
+
"add_tag",
|
|
5760
|
+
"remove_duplicate"
|
|
5761
|
+
]);
|
|
5762
|
+
return parsed.filter((item) => {
|
|
5763
|
+
if (!item || typeof item !== "object")
|
|
5764
|
+
return false;
|
|
5765
|
+
const p = item;
|
|
5766
|
+
return typeof p["type"] === "string" && validTypes.has(p["type"]) && Array.isArray(p["memory_ids"]) && typeof p["proposed_changes"] === "object" && typeof p["reasoning"] === "string" && typeof p["confidence"] === "number";
|
|
5767
|
+
}).map((p) => ({
|
|
5768
|
+
type: p.type,
|
|
5769
|
+
memory_ids: p.memory_ids.filter((id) => typeof id === "string"),
|
|
5770
|
+
target_memory_id: typeof p.target_memory_id === "string" ? p.target_memory_id : undefined,
|
|
5771
|
+
proposed_changes: p.proposed_changes ?? {},
|
|
5772
|
+
reasoning: p.reasoning,
|
|
5773
|
+
confidence: Math.max(0, Math.min(1, p.confidence))
|
|
5774
|
+
}));
|
|
5775
|
+
} catch {
|
|
5776
|
+
return [];
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
function buildSummary(corpus, proposalCount) {
|
|
5780
|
+
return `Analyzed ${corpus.totalMemories} memories. ` + `Found ${corpus.staleMemories.length} stale, ` + `${corpus.duplicateCandidates.length} duplicate pairs. ` + `Generated ${proposalCount} proposals.`;
|
|
5781
|
+
}
|
|
5782
|
+
var SYNTHESIS_SYSTEM_PROMPT = "You are a memory synthesizer. Analyze this agent's memory corpus and propose consolidations to improve quality and reduce redundancy. Return ONLY a JSON array of proposal objects.";
|
|
5783
|
+
var init_llm_analyzer = __esm(() => {
|
|
5784
|
+
init_registry();
|
|
5785
|
+
});
|
|
5786
|
+
|
|
5787
|
+
// src/lib/synthesis/validator.ts
|
|
5788
|
+
function validateProposals(proposals, corpus, config) {
|
|
5789
|
+
const cfg = { ...DEFAULT_SAFETY_CONFIG, ...config };
|
|
5790
|
+
const memoryMap = new Map(corpus.items.map((item) => [item.memory.id, item.memory]));
|
|
5791
|
+
const rejectedProposals = [];
|
|
5792
|
+
const warnings = [];
|
|
5793
|
+
let archiveCount = 0;
|
|
5794
|
+
let mergeCount = 0;
|
|
5795
|
+
for (const proposal of proposals) {
|
|
5796
|
+
if (proposal.type === "archive" || proposal.type === "remove_duplicate") {
|
|
5797
|
+
archiveCount++;
|
|
5798
|
+
}
|
|
5799
|
+
if (proposal.type === "merge") {
|
|
5800
|
+
mergeCount++;
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
const corpusSize = corpus.totalMemories;
|
|
5804
|
+
const archivePercent = corpusSize > 0 ? archiveCount / corpusSize * 100 : 0;
|
|
5805
|
+
const mergePercent = corpusSize > 0 ? mergeCount / corpusSize * 100 : 0;
|
|
5806
|
+
if (archivePercent > cfg.maxArchivePercent) {
|
|
5807
|
+
warnings.push(`Archive proposals (${archiveCount}) exceed ${cfg.maxArchivePercent}% of corpus (${archivePercent.toFixed(1)}%). Some will be rejected.`);
|
|
5808
|
+
}
|
|
5809
|
+
if (mergePercent > cfg.maxMergePercent) {
|
|
5810
|
+
warnings.push(`Merge proposals (${mergeCount}) exceed ${cfg.maxMergePercent}% of corpus (${mergePercent.toFixed(1)}%). Some will be rejected.`);
|
|
5811
|
+
}
|
|
5812
|
+
let remainingArchiveSlots = Math.floor(cfg.maxArchivePercent / 100 * corpusSize);
|
|
5813
|
+
let remainingMergeSlots = Math.floor(cfg.maxMergePercent / 100 * corpusSize);
|
|
5814
|
+
for (let i = 0;i < proposals.length; i++) {
|
|
5815
|
+
const proposal = proposals[i];
|
|
5816
|
+
const proposalRef = `proposal[${i}]`;
|
|
5817
|
+
if (proposal.confidence < cfg.minConfidence) {
|
|
5818
|
+
rejectedProposals.push({
|
|
5819
|
+
proposalId: proposalRef,
|
|
5820
|
+
reason: `Confidence ${proposal.confidence.toFixed(2)} below minimum ${cfg.minConfidence}`
|
|
5821
|
+
});
|
|
5822
|
+
continue;
|
|
5823
|
+
}
|
|
5824
|
+
const missingIds = proposal.memory_ids.filter((id) => !memoryMap.has(id));
|
|
5825
|
+
if (missingIds.length > 0) {
|
|
5826
|
+
rejectedProposals.push({
|
|
5827
|
+
proposalId: proposalRef,
|
|
5828
|
+
reason: `References unknown memory IDs: ${missingIds.join(", ")}`
|
|
5829
|
+
});
|
|
5830
|
+
continue;
|
|
5831
|
+
}
|
|
5832
|
+
if (cfg.protectPinned) {
|
|
5833
|
+
const pinnedIds = proposal.memory_ids.filter((id) => {
|
|
5834
|
+
const m = memoryMap.get(id);
|
|
5835
|
+
return m?.pinned === true;
|
|
5836
|
+
});
|
|
5837
|
+
if (pinnedIds.length > 0) {
|
|
5838
|
+
rejectedProposals.push({
|
|
5839
|
+
proposalId: proposalRef,
|
|
5840
|
+
reason: `Attempts to modify pinned memories: ${pinnedIds.join(", ")}`
|
|
5841
|
+
});
|
|
5842
|
+
continue;
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
if (proposal.type === "archive" || proposal.type === "remove_duplicate" || proposal.type === "merge") {
|
|
5846
|
+
const highImportanceIds = proposal.memory_ids.filter((id) => {
|
|
5847
|
+
const m = memoryMap.get(id);
|
|
5848
|
+
return m && m.importance >= cfg.protectHighImportance;
|
|
5849
|
+
});
|
|
5850
|
+
if (highImportanceIds.length > 0) {
|
|
5851
|
+
rejectedProposals.push({
|
|
5852
|
+
proposalId: proposalRef,
|
|
5853
|
+
reason: `Attempts to archive/merge memories with importance \u2265 ${cfg.protectHighImportance}: ${highImportanceIds.join(", ")}`
|
|
5854
|
+
});
|
|
5855
|
+
continue;
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
if (proposal.type === "archive" || proposal.type === "remove_duplicate") {
|
|
5859
|
+
if (remainingArchiveSlots <= 0) {
|
|
5860
|
+
rejectedProposals.push({
|
|
5861
|
+
proposalId: proposalRef,
|
|
5862
|
+
reason: `Archive quota exhausted (max ${cfg.maxArchivePercent}% of corpus)`
|
|
5863
|
+
});
|
|
5864
|
+
continue;
|
|
5865
|
+
}
|
|
5866
|
+
remainingArchiveSlots--;
|
|
5867
|
+
}
|
|
5868
|
+
if (proposal.type === "merge") {
|
|
5869
|
+
if (remainingMergeSlots <= 0) {
|
|
5870
|
+
rejectedProposals.push({
|
|
5871
|
+
proposalId: proposalRef,
|
|
5872
|
+
reason: `Merge quota exhausted (max ${cfg.maxMergePercent}% of corpus)`
|
|
5873
|
+
});
|
|
5874
|
+
continue;
|
|
5875
|
+
}
|
|
5876
|
+
remainingMergeSlots--;
|
|
5877
|
+
}
|
|
5878
|
+
if (proposal.target_memory_id && !memoryMap.has(proposal.target_memory_id)) {
|
|
5879
|
+
rejectedProposals.push({
|
|
5880
|
+
proposalId: proposalRef,
|
|
5881
|
+
reason: `target_memory_id "${proposal.target_memory_id}" does not exist in corpus`
|
|
5882
|
+
});
|
|
5883
|
+
continue;
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
return {
|
|
5887
|
+
valid: rejectedProposals.length === 0,
|
|
5888
|
+
rejectedProposals,
|
|
5889
|
+
warnings
|
|
5890
|
+
};
|
|
5891
|
+
}
|
|
5892
|
+
var DEFAULT_SAFETY_CONFIG;
|
|
5893
|
+
var init_validator = __esm(() => {
|
|
5894
|
+
DEFAULT_SAFETY_CONFIG = {
|
|
5895
|
+
maxArchivePercent: 20,
|
|
5896
|
+
maxMergePercent: 30,
|
|
5897
|
+
minConfidence: 0.6,
|
|
5898
|
+
protectPinned: true,
|
|
5899
|
+
protectHighImportance: 9
|
|
5900
|
+
};
|
|
5901
|
+
});
|
|
5902
|
+
|
|
5903
|
+
// src/lib/synthesis/executor.ts
|
|
5904
|
+
async function executeProposals(runId, proposals, db) {
|
|
5905
|
+
const d = db || getDatabase();
|
|
5906
|
+
let executed = 0;
|
|
5907
|
+
let failed = 0;
|
|
5908
|
+
const rollbackData = {};
|
|
5909
|
+
for (const proposal of proposals) {
|
|
5910
|
+
try {
|
|
5911
|
+
const rollback = executeProposal(proposal, d);
|
|
5912
|
+
updateProposal(proposal.id, {
|
|
5913
|
+
status: "accepted",
|
|
5914
|
+
executed_at: now(),
|
|
5915
|
+
rollback_data: rollback
|
|
5916
|
+
}, d);
|
|
5917
|
+
rollbackData[proposal.id] = rollback;
|
|
5918
|
+
executed++;
|
|
5919
|
+
} catch (err) {
|
|
5920
|
+
try {
|
|
5921
|
+
updateProposal(proposal.id, { status: "rejected" }, d);
|
|
5922
|
+
} catch {}
|
|
5923
|
+
failed++;
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5926
|
+
return { runId, executed, failed, rollbackData };
|
|
5927
|
+
}
|
|
5928
|
+
function executeProposal(proposal, d) {
|
|
5929
|
+
let rollbackData = {};
|
|
5930
|
+
d.transaction(() => {
|
|
5931
|
+
switch (proposal.proposal_type) {
|
|
5932
|
+
case "archive":
|
|
5933
|
+
rollbackData = executeArchive(proposal, d);
|
|
5934
|
+
break;
|
|
5935
|
+
case "promote":
|
|
5936
|
+
rollbackData = executePromote(proposal, d);
|
|
5937
|
+
break;
|
|
5938
|
+
case "update_value":
|
|
5939
|
+
rollbackData = executeUpdateValue(proposal, d);
|
|
5940
|
+
break;
|
|
5941
|
+
case "add_tag":
|
|
5942
|
+
rollbackData = executeAddTag(proposal, d);
|
|
5943
|
+
break;
|
|
5944
|
+
case "merge":
|
|
5945
|
+
rollbackData = executeMerge(proposal, d);
|
|
5946
|
+
break;
|
|
5947
|
+
case "remove_duplicate":
|
|
5948
|
+
rollbackData = executeRemoveDuplicate(proposal, d);
|
|
5949
|
+
break;
|
|
5950
|
+
default:
|
|
5951
|
+
throw new Error(`Unknown proposal type: ${proposal.proposal_type}`);
|
|
5952
|
+
}
|
|
5953
|
+
})();
|
|
5954
|
+
return rollbackData;
|
|
5955
|
+
}
|
|
5956
|
+
function executeArchive(proposal, d) {
|
|
5957
|
+
const rollback = {};
|
|
5958
|
+
for (const memId of proposal.memory_ids) {
|
|
5959
|
+
const mem = getMemory(memId, d);
|
|
5960
|
+
if (!mem)
|
|
5961
|
+
continue;
|
|
5962
|
+
rollback[memId] = mem.status;
|
|
5963
|
+
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
|
|
5964
|
+
}
|
|
5965
|
+
return { old_status: rollback };
|
|
5966
|
+
}
|
|
5967
|
+
function executePromote(proposal, d) {
|
|
5968
|
+
const newImportance = proposal.proposed_changes["new_importance"];
|
|
5969
|
+
if (typeof newImportance !== "number") {
|
|
5970
|
+
throw new Error("promote proposal missing new_importance in proposed_changes");
|
|
5971
|
+
}
|
|
5972
|
+
const rollback = {};
|
|
5973
|
+
for (const memId of proposal.memory_ids) {
|
|
5974
|
+
const mem = getMemory(memId, d);
|
|
5975
|
+
if (!mem)
|
|
5976
|
+
continue;
|
|
5977
|
+
rollback[memId] = mem.importance;
|
|
5978
|
+
d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [Math.max(1, Math.min(10, Math.round(newImportance))), now(), memId]);
|
|
5979
|
+
}
|
|
5980
|
+
return { old_importance: rollback };
|
|
5981
|
+
}
|
|
5982
|
+
function executeUpdateValue(proposal, d) {
|
|
5983
|
+
const newValue = proposal.proposed_changes["new_value"];
|
|
5984
|
+
if (typeof newValue !== "string") {
|
|
5985
|
+
throw new Error("update_value proposal missing new_value in proposed_changes");
|
|
5986
|
+
}
|
|
5987
|
+
const rollback = {};
|
|
5988
|
+
const memId = proposal.memory_ids[0];
|
|
5989
|
+
if (!memId)
|
|
5990
|
+
throw new Error("update_value proposal has no memory_ids");
|
|
5991
|
+
const mem = getMemory(memId, d);
|
|
5992
|
+
if (!mem)
|
|
5993
|
+
throw new Error(`Memory ${memId} not found`);
|
|
5994
|
+
rollback[memId] = { value: mem.value, version: mem.version };
|
|
5995
|
+
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [newValue, now(), memId]);
|
|
5996
|
+
return { old_state: rollback };
|
|
5997
|
+
}
|
|
5998
|
+
function executeAddTag(proposal, d) {
|
|
5999
|
+
const tagsToAdd = proposal.proposed_changes["tags"];
|
|
6000
|
+
if (!Array.isArray(tagsToAdd)) {
|
|
6001
|
+
throw new Error("add_tag proposal missing tags array in proposed_changes");
|
|
6002
|
+
}
|
|
6003
|
+
const rollback = {};
|
|
6004
|
+
for (const memId of proposal.memory_ids) {
|
|
6005
|
+
const mem = getMemory(memId, d);
|
|
6006
|
+
if (!mem)
|
|
6007
|
+
continue;
|
|
6008
|
+
rollback[memId] = [...mem.tags];
|
|
6009
|
+
const newTags = Array.from(new Set([...mem.tags, ...tagsToAdd]));
|
|
6010
|
+
d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(newTags), now(), memId]);
|
|
6011
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
6012
|
+
for (const tag of tagsToAdd) {
|
|
6013
|
+
insertTag.run(memId, tag);
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
return { old_tags: rollback };
|
|
6017
|
+
}
|
|
6018
|
+
function executeMerge(proposal, d) {
|
|
6019
|
+
const targetId = proposal.target_memory_id;
|
|
6020
|
+
if (!targetId)
|
|
6021
|
+
throw new Error("merge proposal missing target_memory_id");
|
|
6022
|
+
const target = getMemory(targetId, d);
|
|
6023
|
+
if (!target)
|
|
6024
|
+
throw new Error(`Target memory ${targetId} not found`);
|
|
6025
|
+
const rollback = {
|
|
6026
|
+
target_old_value: target.value,
|
|
6027
|
+
target_old_version: target.version,
|
|
6028
|
+
archived_memories: {}
|
|
6029
|
+
};
|
|
6030
|
+
const sourceValues = [];
|
|
6031
|
+
for (const memId of proposal.memory_ids) {
|
|
6032
|
+
if (memId === targetId)
|
|
6033
|
+
continue;
|
|
6034
|
+
const mem = getMemory(memId, d);
|
|
6035
|
+
if (!mem)
|
|
6036
|
+
continue;
|
|
6037
|
+
sourceValues.push(mem.value);
|
|
6038
|
+
rollback.archived_memories[memId] = mem.status;
|
|
6039
|
+
}
|
|
6040
|
+
const mergedValue = proposal.proposed_changes["merged_value"] ?? [target.value, ...sourceValues].join(`
|
|
6041
|
+
---
|
|
6042
|
+
`);
|
|
6043
|
+
d.run("UPDATE memories SET value = ?, version = version + 1, updated_at = ? WHERE id = ?", [mergedValue, now(), targetId]);
|
|
6044
|
+
for (const memId of proposal.memory_ids) {
|
|
6045
|
+
if (memId === targetId)
|
|
6046
|
+
continue;
|
|
6047
|
+
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), memId]);
|
|
6048
|
+
}
|
|
6049
|
+
return rollback;
|
|
6050
|
+
}
|
|
6051
|
+
function executeRemoveDuplicate(proposal, d) {
|
|
6052
|
+
const memories = proposal.memory_ids.map((id) => getMemory(id, d)).filter((m) => m !== null);
|
|
6053
|
+
if (memories.length < 2) {
|
|
6054
|
+
throw new Error("remove_duplicate requires at least 2 memories");
|
|
6055
|
+
}
|
|
6056
|
+
memories.sort((a, b) => b.importance - a.importance || a.created_at.localeCompare(b.created_at));
|
|
6057
|
+
const keepId = proposal.target_memory_id ?? memories[0].id;
|
|
6058
|
+
const rollback = {};
|
|
6059
|
+
for (const mem of memories) {
|
|
6060
|
+
if (mem.id === keepId)
|
|
6061
|
+
continue;
|
|
6062
|
+
rollback[mem.id] = mem.status;
|
|
6063
|
+
d.run("UPDATE memories SET status = 'archived', updated_at = ? WHERE id = ?", [now(), mem.id]);
|
|
6064
|
+
}
|
|
6065
|
+
return { old_status: rollback, kept_id: keepId };
|
|
6066
|
+
}
|
|
6067
|
+
async function rollbackRun(runId, db) {
|
|
6068
|
+
const d = db || getDatabase();
|
|
6069
|
+
const proposals = listProposals(runId, { status: "accepted" }, d);
|
|
6070
|
+
let rolledBack = 0;
|
|
6071
|
+
const errors = [];
|
|
6072
|
+
for (const proposal of proposals) {
|
|
6073
|
+
try {
|
|
6074
|
+
rollbackProposal(proposal, d);
|
|
6075
|
+
updateProposal(proposal.id, { status: "rolled_back" }, d);
|
|
6076
|
+
rolledBack++;
|
|
6077
|
+
} catch (err) {
|
|
6078
|
+
errors.push(`Proposal ${proposal.id} (${proposal.proposal_type}): ${err instanceof Error ? err.message : String(err)}`);
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
return { rolled_back: rolledBack, errors };
|
|
6082
|
+
}
|
|
6083
|
+
function rollbackProposal(proposal, d) {
|
|
6084
|
+
const rb = proposal.rollback_data;
|
|
6085
|
+
if (!rb)
|
|
6086
|
+
return;
|
|
6087
|
+
d.transaction(() => {
|
|
6088
|
+
switch (proposal.proposal_type) {
|
|
6089
|
+
case "archive":
|
|
6090
|
+
case "remove_duplicate": {
|
|
6091
|
+
const oldStatus = rb["old_status"];
|
|
6092
|
+
if (!oldStatus)
|
|
6093
|
+
break;
|
|
6094
|
+
for (const [memId, status] of Object.entries(oldStatus)) {
|
|
6095
|
+
d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now(), memId]);
|
|
6096
|
+
}
|
|
6097
|
+
break;
|
|
6098
|
+
}
|
|
6099
|
+
case "promote": {
|
|
6100
|
+
const oldImportance = rb["old_importance"];
|
|
6101
|
+
if (!oldImportance)
|
|
6102
|
+
break;
|
|
6103
|
+
for (const [memId, importance] of Object.entries(oldImportance)) {
|
|
6104
|
+
d.run("UPDATE memories SET importance = ?, updated_at = ? WHERE id = ?", [importance, now(), memId]);
|
|
6105
|
+
}
|
|
6106
|
+
break;
|
|
6107
|
+
}
|
|
6108
|
+
case "update_value": {
|
|
6109
|
+
const oldState = rb["old_state"];
|
|
6110
|
+
if (!oldState)
|
|
6111
|
+
break;
|
|
6112
|
+
for (const [memId, state] of Object.entries(oldState)) {
|
|
6113
|
+
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [state.value, state.version, now(), memId]);
|
|
6114
|
+
}
|
|
6115
|
+
break;
|
|
6116
|
+
}
|
|
6117
|
+
case "add_tag": {
|
|
6118
|
+
const oldTags = rb["old_tags"];
|
|
6119
|
+
if (!oldTags)
|
|
6120
|
+
break;
|
|
6121
|
+
for (const [memId, tags] of Object.entries(oldTags)) {
|
|
6122
|
+
d.run("UPDATE memories SET tags = ?, updated_at = ? WHERE id = ?", [JSON.stringify(tags), now(), memId]);
|
|
6123
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [memId]);
|
|
6124
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
6125
|
+
for (const tag of tags) {
|
|
6126
|
+
insertTag.run(memId, tag);
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
break;
|
|
6130
|
+
}
|
|
6131
|
+
case "merge": {
|
|
6132
|
+
const targetOldValue = rb["target_old_value"];
|
|
6133
|
+
const targetOldVersion = rb["target_old_version"];
|
|
6134
|
+
const archivedMemories = rb["archived_memories"];
|
|
6135
|
+
const targetId = proposal.target_memory_id;
|
|
6136
|
+
if (targetId && targetOldValue !== undefined && targetOldVersion !== undefined) {
|
|
6137
|
+
d.run("UPDATE memories SET value = ?, version = ?, updated_at = ? WHERE id = ?", [targetOldValue, targetOldVersion, now(), targetId]);
|
|
6138
|
+
}
|
|
6139
|
+
if (archivedMemories) {
|
|
6140
|
+
for (const [memId, status] of Object.entries(archivedMemories)) {
|
|
6141
|
+
d.run("UPDATE memories SET status = ?, updated_at = ? WHERE id = ?", [status, now(), memId]);
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
break;
|
|
6145
|
+
}
|
|
6146
|
+
default:
|
|
6147
|
+
break;
|
|
6148
|
+
}
|
|
6149
|
+
})();
|
|
6150
|
+
}
|
|
6151
|
+
var init_executor = __esm(() => {
|
|
6152
|
+
init_database();
|
|
6153
|
+
init_memories();
|
|
6154
|
+
init_synthesis();
|
|
4472
6155
|
});
|
|
4473
6156
|
|
|
4474
|
-
// src/lib/
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
6157
|
+
// src/lib/synthesis/metrics.ts
|
|
6158
|
+
async function measureEffectiveness(runId, preCorpus, db) {
|
|
6159
|
+
const d = db || getDatabase();
|
|
6160
|
+
const postMemories = listMemories({
|
|
6161
|
+
status: "active",
|
|
6162
|
+
project_id: preCorpus.projectId ?? undefined
|
|
6163
|
+
}, d);
|
|
6164
|
+
const postCount = postMemories.length;
|
|
6165
|
+
const preCount = preCorpus.totalMemories;
|
|
6166
|
+
const corpusReduction = preCount > 0 ? (preCount - postCount) / preCount * 100 : 0;
|
|
6167
|
+
const preAvgImportance = preCorpus.items.length > 0 ? preCorpus.items.reduce((sum, i) => sum + i.memory.importance, 0) / preCorpus.items.length : 0;
|
|
6168
|
+
const postAvgImportance = postMemories.length > 0 ? postMemories.reduce((sum, m) => sum + m.importance, 0) / postMemories.length : 0;
|
|
6169
|
+
const importanceDrift = postAvgImportance - preAvgImportance;
|
|
6170
|
+
const preDuplicatePairs = preCorpus.duplicateCandidates.length;
|
|
6171
|
+
const acceptedProposals = listProposals(runId, { status: "accepted" }, d);
|
|
6172
|
+
const deduplicationProposals = acceptedProposals.filter((p) => p.proposal_type === "remove_duplicate" || p.proposal_type === "merge");
|
|
6173
|
+
const deduplicationRate = preDuplicatePairs > 0 ? deduplicationProposals.length / preDuplicatePairs * 100 : 0;
|
|
6174
|
+
const recallWeight = {
|
|
6175
|
+
corpusReduction: 0.3,
|
|
6176
|
+
importanceDrift: 0.4,
|
|
6177
|
+
deduplicationRate: 0.3
|
|
6178
|
+
};
|
|
6179
|
+
const estimatedRecallImprovement = Math.max(0, Math.min(100, corpusReduction * recallWeight.corpusReduction + Math.max(0, importanceDrift * 10) * recallWeight.importanceDrift + deduplicationRate * recallWeight.deduplicationRate));
|
|
6180
|
+
const metricDefs = [
|
|
6181
|
+
{ metric_type: "corpus_reduction_pct", value: corpusReduction, baseline: 0 },
|
|
6182
|
+
{ metric_type: "importance_drift", value: importanceDrift, baseline: 0 },
|
|
6183
|
+
{ metric_type: "deduplication_rate_pct", value: deduplicationRate, baseline: 0 },
|
|
6184
|
+
{ metric_type: "estimated_recall_improvement_pct", value: estimatedRecallImprovement, baseline: 0 },
|
|
6185
|
+
{ metric_type: "pre_corpus_size", value: preCount, baseline: preCount },
|
|
6186
|
+
{ metric_type: "post_corpus_size", value: postCount, baseline: preCount },
|
|
6187
|
+
{ metric_type: "proposals_accepted", value: acceptedProposals.length, baseline: 0 },
|
|
6188
|
+
{ metric_type: "pre_avg_importance", value: preAvgImportance, baseline: preAvgImportance },
|
|
6189
|
+
{ metric_type: "post_avg_importance", value: postAvgImportance, baseline: preAvgImportance }
|
|
6190
|
+
];
|
|
6191
|
+
for (const def of metricDefs) {
|
|
6192
|
+
createMetric({
|
|
6193
|
+
run_id: runId,
|
|
6194
|
+
metric_type: def.metric_type,
|
|
6195
|
+
value: def.value,
|
|
6196
|
+
baseline: def.baseline ?? null
|
|
6197
|
+
}, d);
|
|
6198
|
+
}
|
|
6199
|
+
const metrics = listMetrics(runId, d);
|
|
6200
|
+
return {
|
|
6201
|
+
runId,
|
|
6202
|
+
corpusReduction,
|
|
6203
|
+
importanceDrift,
|
|
6204
|
+
deduplicationRate,
|
|
6205
|
+
estimatedRecallImprovement,
|
|
6206
|
+
metrics
|
|
6207
|
+
};
|
|
6208
|
+
}
|
|
6209
|
+
var init_metrics = __esm(() => {
|
|
6210
|
+
init_database();
|
|
6211
|
+
init_synthesis();
|
|
6212
|
+
init_memories();
|
|
4480
6213
|
});
|
|
4481
|
-
|
|
6214
|
+
|
|
6215
|
+
// src/lib/synthesis/index.ts
|
|
6216
|
+
var exports_synthesis2 = {};
|
|
6217
|
+
__export(exports_synthesis2, {
|
|
6218
|
+
runSynthesis: () => runSynthesis,
|
|
6219
|
+
rollbackSynthesis: () => rollbackSynthesis,
|
|
6220
|
+
getSynthesisStatus: () => getSynthesisStatus
|
|
6221
|
+
});
|
|
6222
|
+
async function runSynthesis(options = {}) {
|
|
6223
|
+
const d = options.db || getDatabase();
|
|
6224
|
+
const projectId = options.projectId ?? null;
|
|
6225
|
+
const agentId = options.agentId ?? null;
|
|
6226
|
+
const dryRun = options.dryRun ?? false;
|
|
6227
|
+
const run = createSynthesisRun({
|
|
6228
|
+
triggered_by: "manual",
|
|
6229
|
+
project_id: projectId,
|
|
6230
|
+
agent_id: agentId
|
|
6231
|
+
}, d);
|
|
6232
|
+
updateSynthesisRun(run.id, { status: "running" }, d);
|
|
4482
6233
|
try {
|
|
4483
|
-
const
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
agent_id: agentId,
|
|
4488
|
-
project_id: projectId,
|
|
4489
|
-
limit: 3
|
|
6234
|
+
const corpus = await buildCorpus({
|
|
6235
|
+
projectId: projectId ?? undefined,
|
|
6236
|
+
agentId: agentId ?? undefined,
|
|
6237
|
+
db: d
|
|
4490
6238
|
});
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
const
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
}
|
|
6239
|
+
updateSynthesisRun(run.id, { corpus_size: corpus.totalMemories }, d);
|
|
6240
|
+
const analysisResult = await analyzeCorpus(corpus, {
|
|
6241
|
+
provider: options.provider,
|
|
6242
|
+
maxProposals: options.maxProposals ?? 20
|
|
6243
|
+
});
|
|
6244
|
+
const validation = validateProposals(analysisResult.proposals, corpus, options.safetyConfig);
|
|
6245
|
+
const rejectedIndices = new Set(validation.rejectedProposals.map((r) => {
|
|
6246
|
+
const match = r.proposalId.match(/\[(\d+)\]/);
|
|
6247
|
+
return match ? parseInt(match[1], 10) : -1;
|
|
6248
|
+
}));
|
|
6249
|
+
const validProposals = analysisResult.proposals.filter((_, idx) => !rejectedIndices.has(idx));
|
|
6250
|
+
const savedProposals = [];
|
|
6251
|
+
for (const p of validProposals) {
|
|
6252
|
+
const saved = createProposal({
|
|
6253
|
+
run_id: run.id,
|
|
6254
|
+
proposal_type: p.type,
|
|
6255
|
+
memory_ids: p.memory_ids,
|
|
6256
|
+
target_memory_id: p.target_memory_id ?? null,
|
|
6257
|
+
proposed_changes: p.proposed_changes,
|
|
6258
|
+
reasoning: p.reasoning,
|
|
6259
|
+
confidence: p.confidence
|
|
6260
|
+
}, d);
|
|
6261
|
+
savedProposals.push(saved);
|
|
6262
|
+
}
|
|
6263
|
+
updateSynthesisRun(run.id, {
|
|
6264
|
+
proposals_generated: analysisResult.proposals.length,
|
|
6265
|
+
proposals_rejected: validation.rejectedProposals.length
|
|
6266
|
+
}, d);
|
|
6267
|
+
let executedCount = 0;
|
|
6268
|
+
if (!dryRun && savedProposals.length > 0) {
|
|
6269
|
+
const execResult = await executeProposals(run.id, savedProposals, d);
|
|
6270
|
+
executedCount = execResult.executed;
|
|
6271
|
+
updateSynthesisRun(run.id, {
|
|
6272
|
+
proposals_accepted: execResult.executed,
|
|
6273
|
+
proposals_rejected: validation.rejectedProposals.length + execResult.failed,
|
|
6274
|
+
status: "completed",
|
|
6275
|
+
completed_at: now()
|
|
6276
|
+
}, d);
|
|
6277
|
+
} else {
|
|
6278
|
+
updateSynthesisRun(run.id, {
|
|
6279
|
+
status: "completed",
|
|
6280
|
+
completed_at: now()
|
|
6281
|
+
}, d);
|
|
4529
6282
|
}
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
const toId = entityIdMap.get(rel.to);
|
|
4533
|
-
if (!fromId || !toId)
|
|
4534
|
-
continue;
|
|
6283
|
+
let effectivenessReport = null;
|
|
6284
|
+
if (!dryRun && executedCount > 0) {
|
|
4535
6285
|
try {
|
|
4536
|
-
|
|
4537
|
-
source_entity_id: fromId,
|
|
4538
|
-
target_entity_id: toId,
|
|
4539
|
-
relation_type: rel.type
|
|
4540
|
-
});
|
|
6286
|
+
effectivenessReport = await measureEffectiveness(run.id, corpus, d);
|
|
4541
6287
|
} catch {}
|
|
4542
6288
|
}
|
|
6289
|
+
const finalRun = listSynthesisRuns({ project_id: projectId, limit: 1 }, d)[0] ?? run;
|
|
6290
|
+
const finalProposals = listProposals(run.id, undefined, d);
|
|
6291
|
+
return {
|
|
6292
|
+
run: finalRun,
|
|
6293
|
+
proposals: finalProposals,
|
|
6294
|
+
executed: executedCount,
|
|
6295
|
+
metrics: effectivenessReport,
|
|
6296
|
+
dryRun
|
|
6297
|
+
};
|
|
4543
6298
|
} catch (err) {
|
|
4544
|
-
|
|
6299
|
+
updateSynthesisRun(run.id, {
|
|
6300
|
+
status: "failed",
|
|
6301
|
+
error: err instanceof Error ? err.message : String(err),
|
|
6302
|
+
completed_at: now()
|
|
6303
|
+
}, d);
|
|
6304
|
+
const failedRun = listSynthesisRuns({ project_id: projectId, limit: 1 }, d)[0] ?? run;
|
|
6305
|
+
return {
|
|
6306
|
+
run: failedRun,
|
|
6307
|
+
proposals: [],
|
|
6308
|
+
executed: 0,
|
|
6309
|
+
metrics: null,
|
|
6310
|
+
dryRun
|
|
6311
|
+
};
|
|
4545
6312
|
}
|
|
4546
6313
|
}
|
|
4547
|
-
async function
|
|
4548
|
-
const
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
return null;
|
|
4553
|
-
if (isDuplicate(extracted.content, context.agentId, context.projectId)) {
|
|
4554
|
-
return null;
|
|
6314
|
+
async function rollbackSynthesis(runId, db) {
|
|
6315
|
+
const d = db || getDatabase();
|
|
6316
|
+
const result = await rollbackRun(runId, d);
|
|
6317
|
+
if (result.errors.length === 0) {
|
|
6318
|
+
updateSynthesisRun(runId, { status: "rolled_back", completed_at: now() }, d);
|
|
4555
6319
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
...context.sessionId ? [`session:${context.sessionId}`] : []
|
|
4567
|
-
],
|
|
4568
|
-
agent_id: context.agentId,
|
|
4569
|
-
project_id: context.projectId,
|
|
4570
|
-
session_id: context.sessionId,
|
|
4571
|
-
metadata: {
|
|
4572
|
-
reasoning: extracted.reasoning,
|
|
4573
|
-
auto_extracted: true,
|
|
4574
|
-
extracted_at: new Date().toISOString()
|
|
4575
|
-
}
|
|
6320
|
+
return result;
|
|
6321
|
+
}
|
|
6322
|
+
function getSynthesisStatus(runId, projectId, db) {
|
|
6323
|
+
const d = db || getDatabase();
|
|
6324
|
+
if (runId) {
|
|
6325
|
+
const recentRuns2 = listSynthesisRuns({ project_id: projectId ?? null, limit: 10 }, d);
|
|
6326
|
+
const specificRun = recentRuns2.find((r) => r.id === runId) ?? null;
|
|
6327
|
+
return {
|
|
6328
|
+
lastRun: specificRun ?? recentRuns2[0] ?? null,
|
|
6329
|
+
recentRuns: recentRuns2
|
|
4576
6330
|
};
|
|
4577
|
-
const memory = createMemory(input, "merge");
|
|
4578
|
-
return memory.id;
|
|
4579
|
-
} catch (err) {
|
|
4580
|
-
console.error("[auto-memory] saveExtractedMemory failed:", err);
|
|
4581
|
-
return null;
|
|
4582
6331
|
}
|
|
4583
|
-
}
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
const provider = providerRegistry.getAvailable();
|
|
4588
|
-
if (!provider)
|
|
4589
|
-
return;
|
|
4590
|
-
const context = {
|
|
4591
|
-
agentId: job.agentId,
|
|
4592
|
-
projectId: job.projectId,
|
|
4593
|
-
sessionId: job.sessionId
|
|
6332
|
+
const recentRuns = listSynthesisRuns({ project_id: projectId ?? null, limit: 10 }, d);
|
|
6333
|
+
return {
|
|
6334
|
+
lastRun: recentRuns[0] ?? null,
|
|
6335
|
+
recentRuns
|
|
4594
6336
|
};
|
|
4595
|
-
let extracted = [];
|
|
4596
|
-
try {
|
|
4597
|
-
extracted = await provider.extractMemories(job.turn, context);
|
|
4598
|
-
} catch {
|
|
4599
|
-
const fallbacks = providerRegistry.getFallbacks();
|
|
4600
|
-
for (const fallback of fallbacks) {
|
|
4601
|
-
try {
|
|
4602
|
-
extracted = await fallback.extractMemories(job.turn, context);
|
|
4603
|
-
if (extracted.length > 0)
|
|
4604
|
-
break;
|
|
4605
|
-
} catch {
|
|
4606
|
-
continue;
|
|
4607
|
-
}
|
|
4608
|
-
}
|
|
4609
|
-
}
|
|
4610
|
-
if (extracted.length === 0)
|
|
4611
|
-
return;
|
|
4612
|
-
for (const memory of extracted) {
|
|
4613
|
-
const memoryId = await saveExtractedMemory(memory, context);
|
|
4614
|
-
if (!memoryId)
|
|
4615
|
-
continue;
|
|
4616
|
-
if (providerRegistry.getConfig().autoEntityLink) {
|
|
4617
|
-
linkEntitiesToMemory(memoryId, memory.content, job.agentId, job.projectId);
|
|
4618
|
-
}
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
function processConversationTurn(turn, context, source = "turn") {
|
|
4622
|
-
if (!turn?.trim())
|
|
4623
|
-
return;
|
|
4624
|
-
autoMemoryQueue.enqueue({
|
|
4625
|
-
...context,
|
|
4626
|
-
turn,
|
|
4627
|
-
timestamp: Date.now(),
|
|
4628
|
-
source
|
|
4629
|
-
});
|
|
4630
|
-
}
|
|
4631
|
-
function getAutoMemoryStats() {
|
|
4632
|
-
return autoMemoryQueue.getStats();
|
|
4633
|
-
}
|
|
4634
|
-
function configureAutoMemory(config) {
|
|
4635
|
-
providerRegistry.configure(config);
|
|
4636
6337
|
}
|
|
4637
|
-
var
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
init_auto_memory_queue();
|
|
4646
|
-
autoMemoryQueue.setHandler(processJob);
|
|
6338
|
+
var init_synthesis2 = __esm(() => {
|
|
6339
|
+
init_database();
|
|
6340
|
+
init_synthesis();
|
|
6341
|
+
init_corpus_builder();
|
|
6342
|
+
init_llm_analyzer();
|
|
6343
|
+
init_validator();
|
|
6344
|
+
init_executor();
|
|
6345
|
+
init_metrics();
|
|
4647
6346
|
});
|
|
4648
6347
|
|
|
4649
6348
|
// node_modules/commander/esm.mjs
|
|
@@ -8148,4 +9847,150 @@ autoMemory.command("disable").description("Disable auto-memory extraction").acti
|
|
|
8148
9847
|
configureAutoMemory2({ enabled: false });
|
|
8149
9848
|
console.log(chalk.yellow("\u26A0 Auto-memory disabled"));
|
|
8150
9849
|
});
|
|
9850
|
+
var hooksCmd = program2.command("hooks").description("Hook registry and webhook management");
|
|
9851
|
+
hooksCmd.command("list").description("List registered hooks in the in-memory registry").option("--type <type>", "Filter by hook type").action(async (opts) => {
|
|
9852
|
+
const { hookRegistry: hookRegistry2 } = await Promise.resolve().then(() => (init_hooks(), exports_hooks));
|
|
9853
|
+
const hooks = hookRegistry2.list(opts.type);
|
|
9854
|
+
if (hooks.length === 0) {
|
|
9855
|
+
console.log(chalk.gray("No hooks registered."));
|
|
9856
|
+
return;
|
|
9857
|
+
}
|
|
9858
|
+
for (const h of hooks) {
|
|
9859
|
+
const builtinTag = h.builtin ? chalk.blue(" [builtin]") : "";
|
|
9860
|
+
const blockingTag = h.blocking ? chalk.red(" [blocking]") : chalk.gray(" [non-blocking]");
|
|
9861
|
+
console.log(`${chalk.cyan(h.id)} ${chalk.bold(h.type)}${builtinTag}${blockingTag} priority=${h.priority}`);
|
|
9862
|
+
if (h.description)
|
|
9863
|
+
console.log(` ${chalk.gray(h.description)}`);
|
|
9864
|
+
}
|
|
9865
|
+
});
|
|
9866
|
+
hooksCmd.command("stats").description("Show hook registry statistics").action(async () => {
|
|
9867
|
+
const { hookRegistry: hookRegistry2 } = await Promise.resolve().then(() => (init_hooks(), exports_hooks));
|
|
9868
|
+
const stats = hookRegistry2.stats();
|
|
9869
|
+
console.log(chalk.bold("Hook Registry Stats"));
|
|
9870
|
+
console.log(` Total: ${chalk.cyan(stats.total)}`);
|
|
9871
|
+
console.log(` Blocking: ${chalk.red(stats.blocking)}`);
|
|
9872
|
+
console.log(` Non-blocking:${chalk.green(stats.nonBlocking)}`);
|
|
9873
|
+
if (Object.keys(stats.byType).length > 0) {
|
|
9874
|
+
console.log(chalk.bold(`
|
|
9875
|
+
By type:`));
|
|
9876
|
+
for (const [type, count] of Object.entries(stats.byType)) {
|
|
9877
|
+
console.log(` ${type}: ${count}`);
|
|
9878
|
+
}
|
|
9879
|
+
}
|
|
9880
|
+
});
|
|
9881
|
+
var webhooksCmd = hooksCmd.command("webhooks").alias("wh").description("Manage persistent HTTP webhook hooks");
|
|
9882
|
+
webhooksCmd.command("list").description("List all persisted webhook hooks").option("--type <type>", "Filter by hook type").option("--disabled", "Show only disabled webhooks").action(async (opts) => {
|
|
9883
|
+
const { listWebhookHooks: listWebhookHooks2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
9884
|
+
const webhooks = listWebhookHooks2({
|
|
9885
|
+
type: opts.type,
|
|
9886
|
+
enabled: opts.disabled ? false : undefined
|
|
9887
|
+
});
|
|
9888
|
+
if (webhooks.length === 0) {
|
|
9889
|
+
console.log(chalk.gray("No webhooks registered."));
|
|
9890
|
+
return;
|
|
9891
|
+
}
|
|
9892
|
+
for (const wh of webhooks) {
|
|
9893
|
+
const enabledTag = wh.enabled ? chalk.green("enabled") : chalk.red("disabled");
|
|
9894
|
+
const blockingTag = wh.blocking ? chalk.red("blocking") : chalk.gray("non-blocking");
|
|
9895
|
+
console.log(`${chalk.cyan(wh.id)} [${enabledTag}] ${chalk.bold(wh.type)} \u2192 ${wh.handlerUrl}`);
|
|
9896
|
+
console.log(` ${blockingTag} | priority=${wh.priority} | invocations=${wh.invocationCount} failures=${wh.failureCount}`);
|
|
9897
|
+
if (wh.description)
|
|
9898
|
+
console.log(` ${chalk.gray(wh.description)}`);
|
|
9899
|
+
}
|
|
9900
|
+
});
|
|
9901
|
+
webhooksCmd.command("create <type> <url>").description("Create a persistent webhook hook").option("--blocking", "Block the operation until the webhook responds").option("--priority <n>", "Hook priority (default 50)", "50").option("--agent <id>", "Scope to specific agent").option("--project <id>", "Scope to specific project").option("--description <text>", "Human-readable description").action(async (type, url, opts) => {
|
|
9902
|
+
const { createWebhookHook: createWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
9903
|
+
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
9904
|
+
const wh = createWebhookHook2({
|
|
9905
|
+
type,
|
|
9906
|
+
handlerUrl: url,
|
|
9907
|
+
blocking: opts.blocking ?? false,
|
|
9908
|
+
priority: parseInt(opts.priority, 10),
|
|
9909
|
+
agentId: opts.agent,
|
|
9910
|
+
projectId: opts.project,
|
|
9911
|
+
description: opts.description
|
|
9912
|
+
});
|
|
9913
|
+
reloadWebhooks2();
|
|
9914
|
+
console.log(chalk.green("\u2713 Webhook created"));
|
|
9915
|
+
console.log(` ID: ${chalk.cyan(wh.id)}`);
|
|
9916
|
+
console.log(` Type: ${wh.type}`);
|
|
9917
|
+
console.log(` URL: ${wh.handlerUrl}`);
|
|
9918
|
+
});
|
|
9919
|
+
webhooksCmd.command("delete <id>").description("Delete a webhook by ID").action(async (id) => {
|
|
9920
|
+
const { deleteWebhookHook: deleteWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
9921
|
+
const deleted = deleteWebhookHook2(id);
|
|
9922
|
+
if (deleted) {
|
|
9923
|
+
console.log(chalk.green(`\u2713 Webhook ${id} deleted`));
|
|
9924
|
+
} else {
|
|
9925
|
+
console.error(chalk.red(`Webhook not found: ${id}`));
|
|
9926
|
+
process.exit(1);
|
|
9927
|
+
}
|
|
9928
|
+
});
|
|
9929
|
+
webhooksCmd.command("enable <id>").description("Enable a webhook").action(async (id) => {
|
|
9930
|
+
const { updateWebhookHook: updateWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
9931
|
+
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
9932
|
+
const updated = updateWebhookHook2(id, { enabled: true });
|
|
9933
|
+
if (updated) {
|
|
9934
|
+
reloadWebhooks2();
|
|
9935
|
+
console.log(chalk.green(`\u2713 Webhook ${id} enabled`));
|
|
9936
|
+
} else {
|
|
9937
|
+
console.error(chalk.red(`Webhook not found: ${id}`));
|
|
9938
|
+
process.exit(1);
|
|
9939
|
+
}
|
|
9940
|
+
});
|
|
9941
|
+
webhooksCmd.command("disable <id>").description("Disable a webhook (without deleting it)").action(async (id) => {
|
|
9942
|
+
const { updateWebhookHook: updateWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
9943
|
+
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
9944
|
+
const updated = updateWebhookHook2(id, { enabled: false });
|
|
9945
|
+
if (updated) {
|
|
9946
|
+
reloadWebhooks2();
|
|
9947
|
+
console.log(chalk.yellow(`\u2298 Webhook ${id} disabled`));
|
|
9948
|
+
} else {
|
|
9949
|
+
console.error(chalk.red(`Webhook not found: ${id}`));
|
|
9950
|
+
process.exit(1);
|
|
9951
|
+
}
|
|
9952
|
+
});
|
|
9953
|
+
var synthesisCmd = program2.command("synthesis").alias("synth").description("ALMA memory synthesis \u2014 analyze and consolidate memories");
|
|
9954
|
+
synthesisCmd.command("run").description("Run memory synthesis on the corpus").option("--project <id>", "Project ID to synthesize").option("--dry-run", "Preview proposals without applying them").option("--max-proposals <n>", "Maximum proposals to generate", "20").option("--provider <name>", "LLM provider (anthropic, openai, cerebras, grok)").action(async (opts) => {
|
|
9955
|
+
const { runSynthesis: runSynthesis2 } = await Promise.resolve().then(() => (init_synthesis2(), exports_synthesis2));
|
|
9956
|
+
console.log(chalk.blue("Running memory synthesis..."));
|
|
9957
|
+
const result = await runSynthesis2({
|
|
9958
|
+
projectId: opts.project,
|
|
9959
|
+
dryRun: opts.dryRun ?? false,
|
|
9960
|
+
maxProposals: opts.maxProposals ? parseInt(opts.maxProposals) : 20,
|
|
9961
|
+
provider: opts.provider
|
|
9962
|
+
});
|
|
9963
|
+
if (result.dryRun) {
|
|
9964
|
+
console.log(chalk.yellow(`DRY RUN \u2014 ${result.proposals.length} proposals generated (not applied)`));
|
|
9965
|
+
} else {
|
|
9966
|
+
console.log(chalk.green(`\u2713 Synthesis complete`));
|
|
9967
|
+
console.log(` Corpus: ${result.run.corpus_size} memories`);
|
|
9968
|
+
console.log(` Proposals: ${result.run.proposals_generated} generated, ${result.run.proposals_accepted} applied`);
|
|
9969
|
+
}
|
|
9970
|
+
if (result.metrics) {
|
|
9971
|
+
console.log(` Reduction: ${(result.metrics.corpusReduction * 100).toFixed(1)}%`);
|
|
9972
|
+
}
|
|
9973
|
+
console.log(` Run ID: ${chalk.cyan(result.run.id)}`);
|
|
9974
|
+
});
|
|
9975
|
+
synthesisCmd.command("status").description("Show recent synthesis runs").option("--project <id>", "Filter by project").action(async (opts) => {
|
|
9976
|
+
const { listSynthesisRuns: listSynthesisRuns2 } = await Promise.resolve().then(() => (init_synthesis(), exports_synthesis));
|
|
9977
|
+
const runs = listSynthesisRuns2({ project_id: opts.project, limit: 10 });
|
|
9978
|
+
if (runs.length === 0) {
|
|
9979
|
+
console.log(chalk.gray("No synthesis runs found."));
|
|
9980
|
+
return;
|
|
9981
|
+
}
|
|
9982
|
+
for (const run of runs) {
|
|
9983
|
+
const statusColor = run.status === "completed" ? chalk.green : run.status === "failed" ? chalk.red : chalk.yellow;
|
|
9984
|
+
console.log(`${chalk.cyan(run.id)} [${statusColor(run.status)}] corpus=${run.corpus_size} accepted=${run.proposals_accepted}/${run.proposals_generated} ${run.started_at.slice(0, 10)}`);
|
|
9985
|
+
}
|
|
9986
|
+
});
|
|
9987
|
+
synthesisCmd.command("rollback <runId>").description("Roll back a synthesis run").action(async (runId) => {
|
|
9988
|
+
const { rollbackSynthesis: rollbackSynthesis2 } = await Promise.resolve().then(() => (init_synthesis2(), exports_synthesis2));
|
|
9989
|
+
console.log(chalk.yellow(`Rolling back synthesis run ${runId}...`));
|
|
9990
|
+
const result = await rollbackSynthesis2(runId);
|
|
9991
|
+
console.log(chalk.green(`\u2713 Rolled back ${result.rolled_back} proposals`));
|
|
9992
|
+
if (result.errors.length > 0) {
|
|
9993
|
+
console.log(chalk.red(` Errors: ${result.errors.join(", ")}`));
|
|
9994
|
+
}
|
|
9995
|
+
});
|
|
8151
9996
|
program2.parse(process.argv);
|