@hasna/mementos 0.4.41 → 0.7.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 +2797 -1611
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/entities.d.ts.map +1 -1
- package/dist/db/memories.d.ts +1 -0
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/relations.d.ts.map +1 -1
- package/dist/db/webhook_hooks.d.ts +25 -0
- package/dist/db/webhook_hooks.d.ts.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2187 -1331
- package/dist/lib/auto-memory-queue.d.ts +46 -0
- package/dist/lib/auto-memory-queue.d.ts.map +1 -0
- package/dist/lib/auto-memory.d.ts +18 -0
- package/dist/lib/auto-memory.d.ts.map +1 -0
- package/dist/lib/built-in-hooks.d.ts +12 -0
- package/dist/lib/built-in-hooks.d.ts.map +1 -0
- package/dist/lib/dedup.d.ts +33 -0
- package/dist/lib/dedup.d.ts.map +1 -0
- package/dist/lib/focus.d.ts +58 -0
- package/dist/lib/focus.d.ts.map +1 -0
- package/dist/lib/hooks.d.ts +50 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/providers/anthropic.d.ts +21 -0
- package/dist/lib/providers/anthropic.d.ts.map +1 -0
- package/dist/lib/providers/base.d.ts +96 -0
- package/dist/lib/providers/base.d.ts.map +1 -0
- package/dist/lib/providers/cerebras.d.ts +20 -0
- package/dist/lib/providers/cerebras.d.ts.map +1 -0
- package/dist/lib/providers/grok.d.ts +19 -0
- package/dist/lib/providers/grok.d.ts.map +1 -0
- package/dist/lib/providers/index.d.ts +7 -0
- package/dist/lib/providers/index.d.ts.map +1 -0
- package/dist/lib/providers/openai-compat.d.ts +18 -0
- package/dist/lib/providers/openai-compat.d.ts.map +1 -0
- package/dist/lib/providers/openai.d.ts +20 -0
- package/dist/lib/providers/openai.d.ts.map +1 -0
- package/dist/lib/providers/registry.d.ts +38 -0
- package/dist/lib/providers/registry.d.ts.map +1 -0
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +6851 -5544
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2716 -1596
- package/dist/types/hooks.d.ts +136 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -2427,6 +2427,30 @@ var init_database = __esm(() => {
|
|
|
2427
2427
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
2428
2428
|
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
2429
2429
|
INSERT OR IGNORE INTO _migrations (id) VALUES (8);
|
|
2430
|
+
`,
|
|
2431
|
+
`
|
|
2432
|
+
ALTER TABLE memories ADD COLUMN recall_count INTEGER NOT NULL DEFAULT 0;
|
|
2433
|
+
CREATE INDEX IF NOT EXISTS idx_memories_recall_count ON memories(recall_count DESC);
|
|
2434
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (9);
|
|
2435
|
+
`,
|
|
2436
|
+
`
|
|
2437
|
+
CREATE TABLE IF NOT EXISTS webhook_hooks (
|
|
2438
|
+
id TEXT PRIMARY KEY,
|
|
2439
|
+
type TEXT NOT NULL,
|
|
2440
|
+
handler_url TEXT NOT NULL,
|
|
2441
|
+
priority INTEGER NOT NULL DEFAULT 50,
|
|
2442
|
+
blocking INTEGER NOT NULL DEFAULT 0,
|
|
2443
|
+
agent_id TEXT,
|
|
2444
|
+
project_id TEXT,
|
|
2445
|
+
description TEXT,
|
|
2446
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
2447
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2448
|
+
invocation_count INTEGER NOT NULL DEFAULT 0,
|
|
2449
|
+
failure_count INTEGER NOT NULL DEFAULT 0
|
|
2450
|
+
);
|
|
2451
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_type ON webhook_hooks(type);
|
|
2452
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_enabled ON webhook_hooks(enabled);
|
|
2453
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (10);
|
|
2430
2454
|
`
|
|
2431
2455
|
];
|
|
2432
2456
|
});
|
|
@@ -2506,481 +2530,600 @@ var init_redact = __esm(() => {
|
|
|
2506
2530
|
];
|
|
2507
2531
|
});
|
|
2508
2532
|
|
|
2509
|
-
// src/
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
role: row["role"] || null,
|
|
2517
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2518
|
-
active_project_id: row["active_project_id"] || null,
|
|
2519
|
-
created_at: row["created_at"],
|
|
2520
|
-
last_seen_at: row["last_seen_at"]
|
|
2521
|
-
};
|
|
2533
|
+
// src/lib/hooks.ts
|
|
2534
|
+
var exports_hooks = {};
|
|
2535
|
+
__export(exports_hooks, {
|
|
2536
|
+
hookRegistry: () => hookRegistry
|
|
2537
|
+
});
|
|
2538
|
+
function generateHookId() {
|
|
2539
|
+
return `hook_${++_idCounter}_${Date.now().toString(36)}`;
|
|
2522
2540
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
|
|
2536
|
-
throw new AgentConflictError({
|
|
2537
|
-
existing_id: existingId,
|
|
2538
|
-
existing_name: normalizedName,
|
|
2539
|
-
last_seen_at: existingLastSeen,
|
|
2540
|
-
session_hint: existingSessionId.slice(0, 8),
|
|
2541
|
-
working_dir: null
|
|
2542
|
-
});
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
|
|
2546
|
-
timestamp,
|
|
2547
|
-
sessionId ?? existingSessionId,
|
|
2548
|
-
existingId
|
|
2549
|
-
]);
|
|
2550
|
-
if (description) {
|
|
2551
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
|
|
2552
|
-
}
|
|
2553
|
-
if (role) {
|
|
2554
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
|
|
2555
|
-
}
|
|
2556
|
-
if (projectId !== undefined) {
|
|
2557
|
-
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
2558
|
-
}
|
|
2559
|
-
return getAgent(existingId, d);
|
|
2541
|
+
|
|
2542
|
+
class HookRegistry {
|
|
2543
|
+
hooks = new Map;
|
|
2544
|
+
register(reg) {
|
|
2545
|
+
const id = generateHookId();
|
|
2546
|
+
const hook = {
|
|
2547
|
+
...reg,
|
|
2548
|
+
id,
|
|
2549
|
+
priority: reg.priority ?? 50
|
|
2550
|
+
};
|
|
2551
|
+
this.hooks.set(id, hook);
|
|
2552
|
+
return id;
|
|
2560
2553
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
|
|
2593
|
-
if (existing) {
|
|
2594
|
-
throw new Error(`Agent name already taken: ${normalizedNewName}`);
|
|
2554
|
+
unregister(hookId) {
|
|
2555
|
+
const hook = this.hooks.get(hookId);
|
|
2556
|
+
if (!hook)
|
|
2557
|
+
return false;
|
|
2558
|
+
if (hook.builtin)
|
|
2559
|
+
return false;
|
|
2560
|
+
this.hooks.delete(hookId);
|
|
2561
|
+
return true;
|
|
2562
|
+
}
|
|
2563
|
+
list(type) {
|
|
2564
|
+
const all = [...this.hooks.values()];
|
|
2565
|
+
if (!type)
|
|
2566
|
+
return all;
|
|
2567
|
+
return all.filter((h) => h.type === type);
|
|
2568
|
+
}
|
|
2569
|
+
async runHooks(type, context) {
|
|
2570
|
+
const matching = this.getMatchingHooks(type, context);
|
|
2571
|
+
if (matching.length === 0)
|
|
2572
|
+
return true;
|
|
2573
|
+
matching.sort((a, b) => a.priority - b.priority);
|
|
2574
|
+
for (const hook of matching) {
|
|
2575
|
+
if (hook.blocking) {
|
|
2576
|
+
try {
|
|
2577
|
+
const result = await hook.handler(context);
|
|
2578
|
+
if (result === false)
|
|
2579
|
+
return false;
|
|
2580
|
+
} catch (err) {
|
|
2581
|
+
console.error(`[hooks] blocking hook ${hook.id} (${type}) threw:`, err);
|
|
2582
|
+
}
|
|
2583
|
+
} else {
|
|
2584
|
+
Promise.resolve().then(() => hook.handler(context)).catch((err) => console.error(`[hooks] non-blocking hook ${hook.id} (${type}) threw:`, err));
|
|
2595
2585
|
}
|
|
2596
|
-
d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
|
|
2597
2586
|
}
|
|
2587
|
+
return true;
|
|
2598
2588
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2589
|
+
getMatchingHooks(type, context) {
|
|
2590
|
+
const ctx = context;
|
|
2591
|
+
return [...this.hooks.values()].filter((hook) => {
|
|
2592
|
+
if (hook.type !== type)
|
|
2593
|
+
return false;
|
|
2594
|
+
if (hook.agentId && hook.agentId !== ctx.agentId)
|
|
2595
|
+
return false;
|
|
2596
|
+
if (hook.projectId && hook.projectId !== ctx.projectId)
|
|
2597
|
+
return false;
|
|
2598
|
+
return true;
|
|
2599
|
+
});
|
|
2607
2600
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2601
|
+
stats() {
|
|
2602
|
+
const all = [...this.hooks.values()];
|
|
2603
|
+
const byType = {};
|
|
2604
|
+
for (const hook of all) {
|
|
2605
|
+
byType[hook.type] = (byType[hook.type] ?? 0) + 1;
|
|
2606
|
+
}
|
|
2607
|
+
return {
|
|
2608
|
+
total: all.length,
|
|
2609
|
+
byType,
|
|
2610
|
+
blocking: all.filter((h) => h.blocking).length,
|
|
2611
|
+
nonBlocking: all.filter((h) => !h.blocking).length
|
|
2612
|
+
};
|
|
2610
2613
|
}
|
|
2611
|
-
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
|
|
2612
|
-
return getAgent(agent.id, d);
|
|
2613
2614
|
}
|
|
2614
|
-
var
|
|
2615
|
-
var
|
|
2616
|
-
|
|
2617
|
-
init_database();
|
|
2618
|
-
CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
2615
|
+
var _idCounter = 0, hookRegistry;
|
|
2616
|
+
var init_hooks = __esm(() => {
|
|
2617
|
+
hookRegistry = new HookRegistry;
|
|
2619
2618
|
});
|
|
2620
2619
|
|
|
2621
|
-
// src/db/
|
|
2622
|
-
function
|
|
2620
|
+
// src/db/entity-memories.ts
|
|
2621
|
+
function parseEntityMemoryRow(row) {
|
|
2623
2622
|
return {
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
memory_prefix: row["memory_prefix"] || null,
|
|
2629
|
-
created_at: row["created_at"],
|
|
2630
|
-
updated_at: row["updated_at"]
|
|
2623
|
+
entity_id: row["entity_id"],
|
|
2624
|
+
memory_id: row["memory_id"],
|
|
2625
|
+
role: row["role"],
|
|
2626
|
+
created_at: row["created_at"]
|
|
2631
2627
|
};
|
|
2632
2628
|
}
|
|
2633
|
-
function
|
|
2629
|
+
function linkEntityToMemory(entityId, memoryId, role = "context", db) {
|
|
2634
2630
|
const d = db || getDatabase();
|
|
2635
2631
|
const timestamp = now();
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
timestamp,
|
|
2641
|
-
existingId
|
|
2642
|
-
]);
|
|
2643
|
-
return parseProjectRow(existing);
|
|
2644
|
-
}
|
|
2645
|
-
const id = uuid();
|
|
2646
|
-
d.run("INSERT INTO projects (id, name, path, description, memory_prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, name, path, description || null, memoryPrefix || null, timestamp, timestamp]);
|
|
2647
|
-
return getProject(id, d);
|
|
2632
|
+
d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
|
|
2633
|
+
VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
|
|
2634
|
+
const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
|
|
2635
|
+
return parseEntityMemoryRow(row);
|
|
2648
2636
|
}
|
|
2649
|
-
function
|
|
2637
|
+
function unlinkEntityFromMemory(entityId, memoryId, db) {
|
|
2650
2638
|
const d = db || getDatabase();
|
|
2651
|
-
|
|
2652
|
-
if (row)
|
|
2653
|
-
return parseProjectRow(row);
|
|
2654
|
-
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
2655
|
-
if (row)
|
|
2656
|
-
return parseProjectRow(row);
|
|
2657
|
-
row = d.query("SELECT * FROM projects WHERE LOWER(name) = ?").get(idOrPath.toLowerCase());
|
|
2658
|
-
if (row)
|
|
2659
|
-
return parseProjectRow(row);
|
|
2660
|
-
return null;
|
|
2639
|
+
d.run("DELETE FROM entity_memories WHERE entity_id = ? AND memory_id = ?", [entityId, memoryId]);
|
|
2661
2640
|
}
|
|
2662
|
-
function
|
|
2641
|
+
function getMemoriesForEntity(entityId, db) {
|
|
2663
2642
|
const d = db || getDatabase();
|
|
2664
|
-
const rows = d.query(
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
});
|
|
2670
|
-
|
|
2671
|
-
// src/lib/extractor.ts
|
|
2672
|
-
function getSearchText(memory) {
|
|
2673
|
-
const parts = [memory.key, memory.value];
|
|
2674
|
-
if (memory.summary)
|
|
2675
|
-
parts.push(memory.summary);
|
|
2676
|
-
return parts.join(" ");
|
|
2643
|
+
const rows = d.query(`SELECT m.* FROM memories m
|
|
2644
|
+
INNER JOIN entity_memories em ON em.memory_id = m.id
|
|
2645
|
+
WHERE em.entity_id = ?
|
|
2646
|
+
ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
|
|
2647
|
+
return rows.map(parseMemoryRow);
|
|
2677
2648
|
}
|
|
2678
|
-
function
|
|
2679
|
-
const
|
|
2680
|
-
const
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
const existing = entityMap.get(normalized);
|
|
2686
|
-
if (!existing || existing.confidence < confidence) {
|
|
2687
|
-
entityMap.set(normalized, { name: normalized, type, confidence });
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
for (const match of text.matchAll(FILE_PATH_RE)) {
|
|
2691
|
-
add(match[1].trim(), "file", 0.9);
|
|
2692
|
-
}
|
|
2693
|
-
for (const match of text.matchAll(URL_RE)) {
|
|
2694
|
-
add(match[0], "api", 0.8);
|
|
2695
|
-
}
|
|
2696
|
-
for (const match of text.matchAll(NPM_PACKAGE_RE)) {
|
|
2697
|
-
add(match[0], "tool", 0.85);
|
|
2649
|
+
function getEntityMemoryLinks(entityId, memoryId, db) {
|
|
2650
|
+
const d = db || getDatabase();
|
|
2651
|
+
const conditions = [];
|
|
2652
|
+
const params = [];
|
|
2653
|
+
if (entityId) {
|
|
2654
|
+
conditions.push("entity_id = ?");
|
|
2655
|
+
params.push(entityId);
|
|
2698
2656
|
}
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
const textLower2 = text.toLowerCase();
|
|
2703
|
-
for (const agent of agents) {
|
|
2704
|
-
const nameLower = agent.name.toLowerCase();
|
|
2705
|
-
if (nameLower.length >= 3 && textLower2.includes(nameLower)) {
|
|
2706
|
-
add(agent.name, "person", 0.95);
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
} catch {}
|
|
2710
|
-
try {
|
|
2711
|
-
const d = db || getDatabase();
|
|
2712
|
-
const projects = listProjects(d);
|
|
2713
|
-
const textLower2 = text.toLowerCase();
|
|
2714
|
-
for (const project of projects) {
|
|
2715
|
-
const nameLower = project.name.toLowerCase();
|
|
2716
|
-
if (nameLower.length >= 3 && textLower2.includes(nameLower)) {
|
|
2717
|
-
add(project.name, "project", 0.95);
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
} catch {}
|
|
2721
|
-
const textLower = text.toLowerCase();
|
|
2722
|
-
for (const keyword of TECH_KEYWORDS) {
|
|
2723
|
-
const re = new RegExp(`\\b${keyword}\\b`, "i");
|
|
2724
|
-
if (re.test(textLower)) {
|
|
2725
|
-
add(keyword, "tool", 0.7);
|
|
2726
|
-
}
|
|
2657
|
+
if (memoryId) {
|
|
2658
|
+
conditions.push("memory_id = ?");
|
|
2659
|
+
params.push(memoryId);
|
|
2727
2660
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2661
|
+
let sql = "SELECT * FROM entity_memories";
|
|
2662
|
+
if (conditions.length > 0) {
|
|
2663
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
2730
2664
|
}
|
|
2731
|
-
|
|
2665
|
+
sql += " ORDER BY created_at DESC";
|
|
2666
|
+
const rows = d.query(sql).all(...params);
|
|
2667
|
+
return rows.map(parseEntityMemoryRow);
|
|
2732
2668
|
}
|
|
2733
|
-
var
|
|
2734
|
-
var init_extractor = __esm(() => {
|
|
2669
|
+
var init_entity_memories = __esm(() => {
|
|
2735
2670
|
init_database();
|
|
2736
|
-
|
|
2737
|
-
init_projects();
|
|
2738
|
-
TECH_KEYWORDS = new Set([
|
|
2739
|
-
"typescript",
|
|
2740
|
-
"javascript",
|
|
2741
|
-
"python",
|
|
2742
|
-
"rust",
|
|
2743
|
-
"go",
|
|
2744
|
-
"java",
|
|
2745
|
-
"ruby",
|
|
2746
|
-
"swift",
|
|
2747
|
-
"kotlin",
|
|
2748
|
-
"react",
|
|
2749
|
-
"vue",
|
|
2750
|
-
"angular",
|
|
2751
|
-
"svelte",
|
|
2752
|
-
"nextjs",
|
|
2753
|
-
"bun",
|
|
2754
|
-
"node",
|
|
2755
|
-
"deno",
|
|
2756
|
-
"sqlite",
|
|
2757
|
-
"postgres",
|
|
2758
|
-
"mysql",
|
|
2759
|
-
"redis",
|
|
2760
|
-
"docker",
|
|
2761
|
-
"kubernetes",
|
|
2762
|
-
"git",
|
|
2763
|
-
"npm",
|
|
2764
|
-
"yarn",
|
|
2765
|
-
"pnpm",
|
|
2766
|
-
"webpack",
|
|
2767
|
-
"vite",
|
|
2768
|
-
"tailwind",
|
|
2769
|
-
"prisma",
|
|
2770
|
-
"drizzle",
|
|
2771
|
-
"zod",
|
|
2772
|
-
"commander",
|
|
2773
|
-
"express",
|
|
2774
|
-
"fastify",
|
|
2775
|
-
"hono"
|
|
2776
|
-
]);
|
|
2777
|
-
FILE_PATH_RE = /(?:^|\s)((?:\/|\.\/|~\/)?(?:[\w.-]+\/)+[\w.-]+\.\w+)/g;
|
|
2778
|
-
URL_RE = /https?:\/\/[^\s)]+/g;
|
|
2779
|
-
NPM_PACKAGE_RE = /@[\w-]+\/[\w.-]+/g;
|
|
2780
|
-
PASCAL_CASE_RE = /\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g;
|
|
2671
|
+
init_memories();
|
|
2781
2672
|
});
|
|
2782
2673
|
|
|
2783
|
-
// src/db/
|
|
2784
|
-
|
|
2674
|
+
// src/db/memories.ts
|
|
2675
|
+
var exports_memories = {};
|
|
2676
|
+
__export(exports_memories, {
|
|
2677
|
+
updateMemory: () => updateMemory,
|
|
2678
|
+
touchMemory: () => touchMemory,
|
|
2679
|
+
parseMemoryRow: () => parseMemoryRow,
|
|
2680
|
+
listMemories: () => listMemories,
|
|
2681
|
+
incrementRecallCount: () => incrementRecallCount,
|
|
2682
|
+
getMemoryVersions: () => getMemoryVersions,
|
|
2683
|
+
getMemoryByKey: () => getMemoryByKey,
|
|
2684
|
+
getMemory: () => getMemory,
|
|
2685
|
+
getMemoriesByKey: () => getMemoriesByKey,
|
|
2686
|
+
deleteMemory: () => deleteMemory,
|
|
2687
|
+
createMemory: () => createMemory,
|
|
2688
|
+
cleanExpiredMemories: () => cleanExpiredMemories,
|
|
2689
|
+
bulkDeleteMemories: () => bulkDeleteMemories
|
|
2690
|
+
});
|
|
2691
|
+
function runEntityExtraction(_memory, _projectId, _d) {}
|
|
2692
|
+
function parseMemoryRow(row) {
|
|
2785
2693
|
return {
|
|
2786
2694
|
id: row["id"],
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2695
|
+
key: row["key"],
|
|
2696
|
+
value: row["value"],
|
|
2697
|
+
category: row["category"],
|
|
2698
|
+
scope: row["scope"],
|
|
2699
|
+
summary: row["summary"] || null,
|
|
2700
|
+
tags: JSON.parse(row["tags"] || "[]"),
|
|
2701
|
+
importance: row["importance"],
|
|
2702
|
+
source: row["source"],
|
|
2703
|
+
status: row["status"],
|
|
2704
|
+
pinned: !!row["pinned"],
|
|
2705
|
+
agent_id: row["agent_id"] || null,
|
|
2791
2706
|
project_id: row["project_id"] || null,
|
|
2707
|
+
session_id: row["session_id"] || null,
|
|
2708
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2709
|
+
access_count: row["access_count"],
|
|
2710
|
+
version: row["version"],
|
|
2711
|
+
expires_at: row["expires_at"] || null,
|
|
2792
2712
|
created_at: row["created_at"],
|
|
2793
|
-
updated_at: row["updated_at"]
|
|
2713
|
+
updated_at: row["updated_at"],
|
|
2714
|
+
accessed_at: row["accessed_at"] || null
|
|
2794
2715
|
};
|
|
2795
2716
|
}
|
|
2796
|
-
function
|
|
2717
|
+
function createMemory(input, dedupeMode = "merge", db) {
|
|
2797
2718
|
const d = db || getDatabase();
|
|
2798
2719
|
const timestamp = now();
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2720
|
+
let expiresAt = input.expires_at || null;
|
|
2721
|
+
if (input.ttl_ms && !expiresAt) {
|
|
2722
|
+
expiresAt = new Date(Date.now() + input.ttl_ms).toISOString();
|
|
2723
|
+
}
|
|
2724
|
+
const id = uuid();
|
|
2725
|
+
const tags = input.tags || [];
|
|
2726
|
+
const tagsJson = JSON.stringify(tags);
|
|
2727
|
+
const metadataJson = JSON.stringify(input.metadata || {});
|
|
2728
|
+
const safeValue = redactSecrets(input.value);
|
|
2729
|
+
const safeSummary = input.summary ? redactSecrets(input.summary) : null;
|
|
2730
|
+
if (dedupeMode === "merge") {
|
|
2731
|
+
const existing = d.query(`SELECT id, version FROM memories
|
|
2732
|
+
WHERE key = ? AND scope = ?
|
|
2733
|
+
AND COALESCE(agent_id, '') = ?
|
|
2734
|
+
AND COALESCE(project_id, '') = ?
|
|
2735
|
+
AND COALESCE(session_id, '') = ?`).get(input.key, input.scope || "private", input.agent_id || "", input.project_id || "", input.session_id || "");
|
|
2736
|
+
if (existing) {
|
|
2737
|
+
d.run(`UPDATE memories SET
|
|
2738
|
+
value = ?, category = ?, summary = ?, tags = ?,
|
|
2739
|
+
importance = ?, metadata = ?, expires_at = ?,
|
|
2740
|
+
pinned = COALESCE(pinned, 0),
|
|
2741
|
+
version = version + 1, updated_at = ?
|
|
2742
|
+
WHERE id = ?`, [
|
|
2743
|
+
safeValue,
|
|
2744
|
+
input.category || "knowledge",
|
|
2745
|
+
safeSummary,
|
|
2746
|
+
tagsJson,
|
|
2747
|
+
input.importance ?? 5,
|
|
2748
|
+
metadataJson,
|
|
2749
|
+
expiresAt,
|
|
2750
|
+
timestamp,
|
|
2751
|
+
existing.id
|
|
2752
|
+
]);
|
|
2753
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
2754
|
+
const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
2755
|
+
for (const tag of tags) {
|
|
2756
|
+
insertTag2.run(existing.id, tag);
|
|
2757
|
+
}
|
|
2758
|
+
const merged = getMemory(existing.id, d);
|
|
2759
|
+
try {
|
|
2760
|
+
const oldLinks = getEntityMemoryLinks(undefined, merged.id, d);
|
|
2761
|
+
for (const link of oldLinks) {
|
|
2762
|
+
unlinkEntityFromMemory(link.entity_id, merged.id, d);
|
|
2763
|
+
}
|
|
2764
|
+
runEntityExtraction(merged, input.project_id, d);
|
|
2765
|
+
} catch {}
|
|
2766
|
+
return merged;
|
|
2812
2767
|
}
|
|
2813
|
-
const existingId = existing["id"];
|
|
2814
|
-
params.push(existingId);
|
|
2815
|
-
d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2816
|
-
return getEntity(existingId, d);
|
|
2817
2768
|
}
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2769
|
+
d.run(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, metadata, access_count, version, expires_at, created_at, updated_at)
|
|
2770
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, 0, 1, ?, ?, ?)`, [
|
|
2821
2771
|
id,
|
|
2822
|
-
input.
|
|
2823
|
-
input.
|
|
2824
|
-
input.
|
|
2825
|
-
|
|
2772
|
+
input.key,
|
|
2773
|
+
input.value,
|
|
2774
|
+
input.category || "knowledge",
|
|
2775
|
+
input.scope || "private",
|
|
2776
|
+
input.summary || null,
|
|
2777
|
+
tagsJson,
|
|
2778
|
+
input.importance ?? 5,
|
|
2779
|
+
input.source || "agent",
|
|
2780
|
+
input.agent_id || null,
|
|
2826
2781
|
input.project_id || null,
|
|
2782
|
+
input.session_id || null,
|
|
2783
|
+
metadataJson,
|
|
2784
|
+
expiresAt,
|
|
2827
2785
|
timestamp,
|
|
2828
2786
|
timestamp
|
|
2829
2787
|
]);
|
|
2830
|
-
|
|
2788
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
2789
|
+
for (const tag of tags) {
|
|
2790
|
+
insertTag.run(id, tag);
|
|
2791
|
+
}
|
|
2792
|
+
const memory = getMemory(id, d);
|
|
2793
|
+
runEntityExtraction(memory, input.project_id, d);
|
|
2794
|
+
hookRegistry.runHooks("PostMemorySave", {
|
|
2795
|
+
memory,
|
|
2796
|
+
wasUpdated: false,
|
|
2797
|
+
agentId: input.agent_id,
|
|
2798
|
+
projectId: input.project_id,
|
|
2799
|
+
sessionId: input.session_id,
|
|
2800
|
+
timestamp: Date.now()
|
|
2801
|
+
});
|
|
2802
|
+
return memory;
|
|
2831
2803
|
}
|
|
2832
|
-
function
|
|
2804
|
+
function getMemory(id, db) {
|
|
2833
2805
|
const d = db || getDatabase();
|
|
2834
|
-
const row = d.query("SELECT * FROM
|
|
2806
|
+
const row = d.query("SELECT * FROM memories WHERE id = ?").get(id);
|
|
2835
2807
|
if (!row)
|
|
2836
|
-
|
|
2837
|
-
return
|
|
2808
|
+
return null;
|
|
2809
|
+
return parseMemoryRow(row);
|
|
2838
2810
|
}
|
|
2839
|
-
function
|
|
2811
|
+
function getMemoryByKey(key, scope, agentId, projectId, sessionId, db) {
|
|
2840
2812
|
const d = db || getDatabase();
|
|
2841
|
-
let sql = "SELECT * FROM
|
|
2842
|
-
const params = [
|
|
2843
|
-
if (
|
|
2844
|
-
sql += " AND
|
|
2845
|
-
params.push(
|
|
2813
|
+
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
2814
|
+
const params = [key];
|
|
2815
|
+
if (scope) {
|
|
2816
|
+
sql += " AND scope = ?";
|
|
2817
|
+
params.push(scope);
|
|
2846
2818
|
}
|
|
2847
|
-
if (
|
|
2819
|
+
if (agentId) {
|
|
2820
|
+
sql += " AND agent_id = ?";
|
|
2821
|
+
params.push(agentId);
|
|
2822
|
+
}
|
|
2823
|
+
if (projectId) {
|
|
2848
2824
|
sql += " AND project_id = ?";
|
|
2849
2825
|
params.push(projectId);
|
|
2850
2826
|
}
|
|
2851
|
-
|
|
2827
|
+
if (sessionId) {
|
|
2828
|
+
sql += " AND session_id = ?";
|
|
2829
|
+
params.push(sessionId);
|
|
2830
|
+
}
|
|
2831
|
+
sql += " AND status = 'active' ORDER BY importance DESC LIMIT 1";
|
|
2852
2832
|
const row = d.query(sql).get(...params);
|
|
2853
2833
|
if (!row)
|
|
2854
2834
|
return null;
|
|
2855
|
-
return
|
|
2835
|
+
return parseMemoryRow(row);
|
|
2856
2836
|
}
|
|
2857
|
-
function
|
|
2837
|
+
function getMemoriesByKey(key, scope, agentId, projectId, db) {
|
|
2858
2838
|
const d = db || getDatabase();
|
|
2859
|
-
|
|
2860
|
-
const params = [];
|
|
2861
|
-
if (
|
|
2862
|
-
|
|
2863
|
-
params.push(
|
|
2839
|
+
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
2840
|
+
const params = [key];
|
|
2841
|
+
if (scope) {
|
|
2842
|
+
sql += " AND scope = ?";
|
|
2843
|
+
params.push(scope);
|
|
2864
2844
|
}
|
|
2865
|
-
if (
|
|
2866
|
-
|
|
2867
|
-
params.push(
|
|
2845
|
+
if (agentId) {
|
|
2846
|
+
sql += " AND agent_id = ?";
|
|
2847
|
+
params.push(agentId);
|
|
2868
2848
|
}
|
|
2869
|
-
if (
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
params.push(term, term);
|
|
2849
|
+
if (projectId) {
|
|
2850
|
+
sql += " AND project_id = ?";
|
|
2851
|
+
params.push(projectId);
|
|
2873
2852
|
}
|
|
2874
|
-
|
|
2853
|
+
sql += " AND status = 'active' ORDER BY importance DESC";
|
|
2854
|
+
const rows = d.query(sql).all(...params);
|
|
2855
|
+
return rows.map(parseMemoryRow);
|
|
2856
|
+
}
|
|
2857
|
+
function listMemories(filter, db) {
|
|
2858
|
+
const d = db || getDatabase();
|
|
2859
|
+
const conditions = [];
|
|
2860
|
+
const params = [];
|
|
2861
|
+
if (filter) {
|
|
2862
|
+
if (filter.scope) {
|
|
2863
|
+
if (Array.isArray(filter.scope)) {
|
|
2864
|
+
conditions.push(`scope IN (${filter.scope.map(() => "?").join(",")})`);
|
|
2865
|
+
params.push(...filter.scope);
|
|
2866
|
+
} else {
|
|
2867
|
+
conditions.push("scope = ?");
|
|
2868
|
+
params.push(filter.scope);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
if (filter.category) {
|
|
2872
|
+
if (Array.isArray(filter.category)) {
|
|
2873
|
+
conditions.push(`category IN (${filter.category.map(() => "?").join(",")})`);
|
|
2874
|
+
params.push(...filter.category);
|
|
2875
|
+
} else {
|
|
2876
|
+
conditions.push("category = ?");
|
|
2877
|
+
params.push(filter.category);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
if (filter.source) {
|
|
2881
|
+
if (Array.isArray(filter.source)) {
|
|
2882
|
+
conditions.push(`source IN (${filter.source.map(() => "?").join(",")})`);
|
|
2883
|
+
params.push(...filter.source);
|
|
2884
|
+
} else {
|
|
2885
|
+
conditions.push("source = ?");
|
|
2886
|
+
params.push(filter.source);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (filter.status) {
|
|
2890
|
+
if (Array.isArray(filter.status)) {
|
|
2891
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
2892
|
+
params.push(...filter.status);
|
|
2893
|
+
} else {
|
|
2894
|
+
conditions.push("status = ?");
|
|
2895
|
+
params.push(filter.status);
|
|
2896
|
+
}
|
|
2897
|
+
} else {
|
|
2898
|
+
conditions.push("status = 'active'");
|
|
2899
|
+
}
|
|
2900
|
+
if (filter.project_id) {
|
|
2901
|
+
conditions.push("project_id = ?");
|
|
2902
|
+
params.push(filter.project_id);
|
|
2903
|
+
}
|
|
2904
|
+
if (filter.agent_id) {
|
|
2905
|
+
conditions.push("agent_id = ?");
|
|
2906
|
+
params.push(filter.agent_id);
|
|
2907
|
+
}
|
|
2908
|
+
if (filter.session_id) {
|
|
2909
|
+
conditions.push("session_id = ?");
|
|
2910
|
+
params.push(filter.session_id);
|
|
2911
|
+
}
|
|
2912
|
+
if (filter.min_importance) {
|
|
2913
|
+
conditions.push("importance >= ?");
|
|
2914
|
+
params.push(filter.min_importance);
|
|
2915
|
+
}
|
|
2916
|
+
if (filter.pinned !== undefined) {
|
|
2917
|
+
conditions.push("pinned = ?");
|
|
2918
|
+
params.push(filter.pinned ? 1 : 0);
|
|
2919
|
+
}
|
|
2920
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
2921
|
+
for (const tag of filter.tags) {
|
|
2922
|
+
conditions.push("id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
|
|
2923
|
+
params.push(tag);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
if (filter.search) {
|
|
2927
|
+
conditions.push("(key LIKE ? OR value LIKE ? OR summary LIKE ?)");
|
|
2928
|
+
const term = `%${filter.search}%`;
|
|
2929
|
+
params.push(term, term, term);
|
|
2930
|
+
}
|
|
2931
|
+
} else {
|
|
2932
|
+
conditions.push("status = 'active'");
|
|
2933
|
+
}
|
|
2934
|
+
let sql = "SELECT * FROM memories";
|
|
2875
2935
|
if (conditions.length > 0) {
|
|
2876
2936
|
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
2877
2937
|
}
|
|
2878
|
-
sql += " ORDER BY
|
|
2879
|
-
if (filter
|
|
2938
|
+
sql += " ORDER BY importance DESC, created_at DESC";
|
|
2939
|
+
if (filter?.limit) {
|
|
2880
2940
|
sql += " LIMIT ?";
|
|
2881
2941
|
params.push(filter.limit);
|
|
2882
2942
|
}
|
|
2883
|
-
if (filter
|
|
2943
|
+
if (filter?.offset) {
|
|
2884
2944
|
sql += " OFFSET ?";
|
|
2885
2945
|
params.push(filter.offset);
|
|
2886
2946
|
}
|
|
2887
2947
|
const rows = d.query(sql).all(...params);
|
|
2888
|
-
return rows.map(
|
|
2889
|
-
}
|
|
2890
|
-
function deleteEntity(id, db) {
|
|
2891
|
-
const d = db || getDatabase();
|
|
2892
|
-
const result = d.run("DELETE FROM entities WHERE id = ?", [id]);
|
|
2893
|
-
if (result.changes === 0)
|
|
2894
|
-
throw new EntityNotFoundError(id);
|
|
2948
|
+
return rows.map(parseMemoryRow);
|
|
2895
2949
|
}
|
|
2896
|
-
function
|
|
2950
|
+
function updateMemory(id, input, db) {
|
|
2897
2951
|
const d = db || getDatabase();
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2952
|
+
const existing = getMemory(id, d);
|
|
2953
|
+
if (!existing)
|
|
2954
|
+
throw new MemoryNotFoundError(id);
|
|
2955
|
+
if (existing.version !== input.version) {
|
|
2956
|
+
throw new VersionConflictError(id, input.version, existing.version);
|
|
2957
|
+
}
|
|
2958
|
+
try {
|
|
2959
|
+
d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, created_at)
|
|
2960
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2961
|
+
uuid(),
|
|
2962
|
+
existing.id,
|
|
2963
|
+
existing.version,
|
|
2964
|
+
existing.value,
|
|
2965
|
+
existing.importance,
|
|
2966
|
+
existing.scope,
|
|
2967
|
+
existing.category,
|
|
2968
|
+
JSON.stringify(existing.tags),
|
|
2969
|
+
existing.summary,
|
|
2970
|
+
existing.pinned ? 1 : 0,
|
|
2971
|
+
existing.status,
|
|
2972
|
+
existing.updated_at
|
|
2973
|
+
]);
|
|
2974
|
+
} catch {}
|
|
2975
|
+
const sets = ["version = version + 1", "updated_at = ?"];
|
|
2976
|
+
const params = [now()];
|
|
2977
|
+
if (input.value !== undefined) {
|
|
2978
|
+
sets.push("value = ?");
|
|
2979
|
+
params.push(redactSecrets(input.value));
|
|
2980
|
+
}
|
|
2981
|
+
if (input.category !== undefined) {
|
|
2982
|
+
sets.push("category = ?");
|
|
2983
|
+
params.push(input.category);
|
|
2984
|
+
}
|
|
2985
|
+
if (input.scope !== undefined) {
|
|
2986
|
+
sets.push("scope = ?");
|
|
2987
|
+
params.push(input.scope);
|
|
2988
|
+
}
|
|
2989
|
+
if (input.summary !== undefined) {
|
|
2990
|
+
sets.push("summary = ?");
|
|
2991
|
+
params.push(input.summary);
|
|
2992
|
+
}
|
|
2993
|
+
if (input.importance !== undefined) {
|
|
2994
|
+
sets.push("importance = ?");
|
|
2995
|
+
params.push(input.importance);
|
|
2996
|
+
}
|
|
2997
|
+
if (input.pinned !== undefined) {
|
|
2998
|
+
sets.push("pinned = ?");
|
|
2999
|
+
params.push(input.pinned ? 1 : 0);
|
|
3000
|
+
}
|
|
3001
|
+
if (input.status !== undefined) {
|
|
3002
|
+
sets.push("status = ?");
|
|
3003
|
+
params.push(input.status);
|
|
3004
|
+
}
|
|
3005
|
+
if (input.metadata !== undefined) {
|
|
3006
|
+
sets.push("metadata = ?");
|
|
3007
|
+
params.push(JSON.stringify(input.metadata));
|
|
3008
|
+
}
|
|
3009
|
+
if (input.expires_at !== undefined) {
|
|
3010
|
+
sets.push("expires_at = ?");
|
|
3011
|
+
params.push(input.expires_at);
|
|
3012
|
+
}
|
|
3013
|
+
if (input.tags !== undefined) {
|
|
3014
|
+
sets.push("tags = ?");
|
|
3015
|
+
params.push(JSON.stringify(input.tags));
|
|
3016
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [id]);
|
|
3017
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3018
|
+
for (const tag of input.tags) {
|
|
3019
|
+
insertTag.run(id, tag);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
params.push(id);
|
|
3023
|
+
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3024
|
+
const updated = getMemory(id, d);
|
|
3025
|
+
if (input.value !== undefined) {
|
|
3026
|
+
try {
|
|
3027
|
+
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
3028
|
+
for (const link of oldLinks) {
|
|
3029
|
+
unlinkEntityFromMemory(link.entity_id, updated.id, d);
|
|
3030
|
+
}
|
|
3031
|
+
} catch {}
|
|
3032
|
+
}
|
|
3033
|
+
hookRegistry.runHooks("PostMemoryUpdate", {
|
|
3034
|
+
memory: updated,
|
|
3035
|
+
previousValue: existing.value,
|
|
3036
|
+
agentId: existing.agent_id ?? undefined,
|
|
3037
|
+
projectId: existing.project_id ?? undefined,
|
|
3038
|
+
sessionId: existing.session_id ?? undefined,
|
|
3039
|
+
timestamp: Date.now()
|
|
3040
|
+
});
|
|
3041
|
+
return updated;
|
|
2911
3042
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
role: row["role"],
|
|
2923
|
-
created_at: row["created_at"]
|
|
2924
|
-
};
|
|
3043
|
+
function deleteMemory(id, db) {
|
|
3044
|
+
const d = db || getDatabase();
|
|
3045
|
+
const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
3046
|
+
if (result.changes > 0) {
|
|
3047
|
+
hookRegistry.runHooks("PostMemoryDelete", {
|
|
3048
|
+
memoryId: id,
|
|
3049
|
+
timestamp: Date.now()
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
return result.changes > 0;
|
|
2925
3053
|
}
|
|
2926
|
-
function
|
|
3054
|
+
function bulkDeleteMemories(ids, db) {
|
|
2927
3055
|
const d = db || getDatabase();
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
const
|
|
2932
|
-
|
|
3056
|
+
if (ids.length === 0)
|
|
3057
|
+
return 0;
|
|
3058
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
3059
|
+
const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...ids);
|
|
3060
|
+
const count = countRow.c;
|
|
3061
|
+
if (count > 0) {
|
|
3062
|
+
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, ids);
|
|
3063
|
+
}
|
|
3064
|
+
return count;
|
|
2933
3065
|
}
|
|
2934
|
-
function
|
|
3066
|
+
function touchMemory(id, db) {
|
|
2935
3067
|
const d = db || getDatabase();
|
|
2936
|
-
d.run("
|
|
3068
|
+
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
2937
3069
|
}
|
|
2938
|
-
function
|
|
3070
|
+
function incrementRecallCount(id, db) {
|
|
2939
3071
|
const d = db || getDatabase();
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
3072
|
+
try {
|
|
3073
|
+
d.run("UPDATE memories SET recall_count = recall_count + 1, access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
3074
|
+
const row = d.query("SELECT recall_count, importance FROM memories WHERE id = ?").get(id);
|
|
3075
|
+
if (!row)
|
|
3076
|
+
return;
|
|
3077
|
+
const promotions = Math.floor(row.recall_count / RECALL_PROMOTE_THRESHOLD);
|
|
3078
|
+
if (promotions > 0 && row.importance < 10) {
|
|
3079
|
+
const newImportance = Math.min(10, row.importance + 1);
|
|
3080
|
+
d.run("UPDATE memories SET importance = ? WHERE id = ? AND importance < 10", [newImportance, id]);
|
|
3081
|
+
}
|
|
3082
|
+
} catch {}
|
|
2945
3083
|
}
|
|
2946
|
-
function
|
|
3084
|
+
function cleanExpiredMemories(db) {
|
|
2947
3085
|
const d = db || getDatabase();
|
|
2948
|
-
const
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
}
|
|
2954
|
-
if (memoryId) {
|
|
2955
|
-
conditions.push("memory_id = ?");
|
|
2956
|
-
params.push(memoryId);
|
|
3086
|
+
const timestamp = now();
|
|
3087
|
+
const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
|
|
3088
|
+
const count = countRow.c;
|
|
3089
|
+
if (count > 0) {
|
|
3090
|
+
d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
|
|
2957
3091
|
}
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3092
|
+
return count;
|
|
3093
|
+
}
|
|
3094
|
+
function getMemoryVersions(memoryId, db) {
|
|
3095
|
+
const d = db || getDatabase();
|
|
3096
|
+
try {
|
|
3097
|
+
const rows = d.query("SELECT * FROM memory_versions WHERE memory_id = ? ORDER BY version ASC").all(memoryId);
|
|
3098
|
+
return rows.map((row) => ({
|
|
3099
|
+
id: row["id"],
|
|
3100
|
+
memory_id: row["memory_id"],
|
|
3101
|
+
version: row["version"],
|
|
3102
|
+
value: row["value"],
|
|
3103
|
+
importance: row["importance"],
|
|
3104
|
+
scope: row["scope"],
|
|
3105
|
+
category: row["category"],
|
|
3106
|
+
tags: JSON.parse(row["tags"] || "[]"),
|
|
3107
|
+
summary: row["summary"] || null,
|
|
3108
|
+
pinned: !!row["pinned"],
|
|
3109
|
+
status: row["status"],
|
|
3110
|
+
created_at: row["created_at"]
|
|
3111
|
+
}));
|
|
3112
|
+
} catch {
|
|
3113
|
+
return [];
|
|
2961
3114
|
}
|
|
2962
|
-
sql += " ORDER BY created_at DESC";
|
|
2963
|
-
const rows = d.query(sql).all(...params);
|
|
2964
|
-
return rows.map(parseEntityMemoryRow);
|
|
2965
3115
|
}
|
|
2966
|
-
var
|
|
3116
|
+
var RECALL_PROMOTE_THRESHOLD = 3;
|
|
3117
|
+
var init_memories = __esm(() => {
|
|
3118
|
+
init_types();
|
|
2967
3119
|
init_database();
|
|
2968
|
-
|
|
3120
|
+
init_redact();
|
|
3121
|
+
init_hooks();
|
|
3122
|
+
init_entity_memories();
|
|
2969
3123
|
});
|
|
2970
3124
|
|
|
2971
|
-
// src/db/
|
|
2972
|
-
function
|
|
2973
|
-
return {
|
|
2974
|
-
id: row["id"],
|
|
2975
|
-
source_entity_id: row["source_entity_id"],
|
|
2976
|
-
target_entity_id: row["target_entity_id"],
|
|
2977
|
-
relation_type: row["relation_type"],
|
|
2978
|
-
weight: row["weight"],
|
|
2979
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2980
|
-
created_at: row["created_at"]
|
|
2981
|
-
};
|
|
2982
|
-
}
|
|
2983
|
-
function parseEntityRow2(row) {
|
|
3125
|
+
// src/db/entities.ts
|
|
3126
|
+
function parseEntityRow(row) {
|
|
2984
3127
|
return {
|
|
2985
3128
|
id: row["id"],
|
|
2986
3129
|
name: row["name"],
|
|
@@ -2992,299 +3135,936 @@ function parseEntityRow2(row) {
|
|
|
2992
3135
|
updated_at: row["updated_at"]
|
|
2993
3136
|
};
|
|
2994
3137
|
}
|
|
2995
|
-
function
|
|
3138
|
+
function createEntity(input, db) {
|
|
2996
3139
|
const d = db || getDatabase();
|
|
2997
|
-
const id = shortUuid();
|
|
2998
3140
|
const timestamp = now();
|
|
2999
|
-
const
|
|
3000
|
-
const
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
}
|
|
3009
|
-
function listRelations(filter, db) {
|
|
3010
|
-
const d = db || getDatabase();
|
|
3011
|
-
const conditions = [];
|
|
3012
|
-
const params = [];
|
|
3013
|
-
if (filter.entity_id) {
|
|
3014
|
-
const dir = filter.direction || "both";
|
|
3015
|
-
if (dir === "outgoing") {
|
|
3016
|
-
conditions.push("source_entity_id = ?");
|
|
3017
|
-
params.push(filter.entity_id);
|
|
3018
|
-
} else if (dir === "incoming") {
|
|
3019
|
-
conditions.push("target_entity_id = ?");
|
|
3020
|
-
params.push(filter.entity_id);
|
|
3021
|
-
} else {
|
|
3022
|
-
conditions.push("(source_entity_id = ? OR target_entity_id = ?)");
|
|
3023
|
-
params.push(filter.entity_id, filter.entity_id);
|
|
3141
|
+
const metadataJson = JSON.stringify(input.metadata || {});
|
|
3142
|
+
const existing = d.query(`SELECT * FROM entities
|
|
3143
|
+
WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
|
|
3144
|
+
if (existing) {
|
|
3145
|
+
const sets = ["updated_at = ?"];
|
|
3146
|
+
const params = [timestamp];
|
|
3147
|
+
if (input.description !== undefined) {
|
|
3148
|
+
sets.push("description = ?");
|
|
3149
|
+
params.push(input.description);
|
|
3024
3150
|
}
|
|
3151
|
+
if (input.metadata !== undefined) {
|
|
3152
|
+
sets.push("metadata = ?");
|
|
3153
|
+
params.push(metadataJson);
|
|
3154
|
+
}
|
|
3155
|
+
const existingId = existing["id"];
|
|
3156
|
+
params.push(existingId);
|
|
3157
|
+
d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3158
|
+
return getEntity(existingId, d);
|
|
3025
3159
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3160
|
+
const id = shortUuid();
|
|
3161
|
+
d.run(`INSERT INTO entities (id, name, type, description, metadata, project_id, created_at, updated_at)
|
|
3162
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3163
|
+
id,
|
|
3164
|
+
input.name,
|
|
3165
|
+
input.type,
|
|
3166
|
+
input.description || null,
|
|
3167
|
+
metadataJson,
|
|
3168
|
+
input.project_id || null,
|
|
3169
|
+
timestamp,
|
|
3170
|
+
timestamp
|
|
3171
|
+
]);
|
|
3172
|
+
hookRegistry.runHooks("PostEntityCreate", {
|
|
3173
|
+
entityId: id,
|
|
3174
|
+
name: input.name,
|
|
3175
|
+
entityType: input.type,
|
|
3176
|
+
projectId: input.project_id,
|
|
3177
|
+
timestamp: Date.now()
|
|
3178
|
+
});
|
|
3179
|
+
return getEntity(id, d);
|
|
3039
3180
|
}
|
|
3040
|
-
function
|
|
3181
|
+
function getEntity(id, db) {
|
|
3041
3182
|
const d = db || getDatabase();
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
SELECT DISTINCT e.* FROM entities e
|
|
3047
|
-
JOIN relations r ON (
|
|
3048
|
-
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
3049
|
-
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
3050
|
-
)
|
|
3051
|
-
WHERE r.relation_type = ?
|
|
3052
|
-
`;
|
|
3053
|
-
params.push(entityId, entityId, relationType);
|
|
3054
|
-
} else {
|
|
3055
|
-
sql = `
|
|
3056
|
-
SELECT DISTINCT e.* FROM entities e
|
|
3057
|
-
JOIN relations r ON (
|
|
3058
|
-
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
3059
|
-
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
3060
|
-
)
|
|
3061
|
-
`;
|
|
3062
|
-
params.push(entityId, entityId);
|
|
3063
|
-
}
|
|
3064
|
-
const rows = d.query(sql).all(...params);
|
|
3065
|
-
return rows.map(parseEntityRow2);
|
|
3183
|
+
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
3184
|
+
if (!row)
|
|
3185
|
+
throw new EntityNotFoundError(id);
|
|
3186
|
+
return parseEntityRow(row);
|
|
3066
3187
|
}
|
|
3067
|
-
function
|
|
3188
|
+
function getEntityByName(name, type, projectId, db) {
|
|
3068
3189
|
const d = db || getDatabase();
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
WHERE g.depth < ?
|
|
3075
|
-
)
|
|
3076
|
-
SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
|
|
3077
|
-
const entities = entityRows.map(parseEntityRow2);
|
|
3078
|
-
const entityIds = new Set(entities.map((e) => e.id));
|
|
3079
|
-
if (entityIds.size === 0) {
|
|
3080
|
-
return { entities: [], relations: [] };
|
|
3190
|
+
let sql = "SELECT * FROM entities WHERE name = ?";
|
|
3191
|
+
const params = [name];
|
|
3192
|
+
if (type) {
|
|
3193
|
+
sql += " AND type = ?";
|
|
3194
|
+
params.push(type);
|
|
3081
3195
|
}
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3196
|
+
if (projectId !== undefined) {
|
|
3197
|
+
sql += " AND project_id = ?";
|
|
3198
|
+
params.push(projectId);
|
|
3199
|
+
}
|
|
3200
|
+
sql += " LIMIT 1";
|
|
3201
|
+
const row = d.query(sql).get(...params);
|
|
3202
|
+
if (!row)
|
|
3203
|
+
return null;
|
|
3204
|
+
return parseEntityRow(row);
|
|
3088
3205
|
}
|
|
3089
|
-
function
|
|
3206
|
+
function listEntities(filter = {}, db) {
|
|
3090
3207
|
const d = db || getDatabase();
|
|
3091
|
-
const
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
p.trail || ',' || CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
3097
|
-
p.depth + 1
|
|
3098
|
-
FROM relations r JOIN path p ON (r.source_entity_id = p.id OR r.target_entity_id = p.id)
|
|
3099
|
-
WHERE p.depth < ?
|
|
3100
|
-
AND INSTR(p.trail, CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END) = 0
|
|
3101
|
-
)
|
|
3102
|
-
SELECT trail FROM path WHERE id = ? ORDER BY depth ASC LIMIT 1`).get(fromEntityId, fromEntityId, maxDepth, toEntityId);
|
|
3103
|
-
if (!rows)
|
|
3104
|
-
return null;
|
|
3105
|
-
const ids = rows.trail.split(",");
|
|
3106
|
-
const entities = [];
|
|
3107
|
-
for (const id of ids) {
|
|
3108
|
-
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
3109
|
-
if (row)
|
|
3110
|
-
entities.push(parseEntityRow2(row));
|
|
3208
|
+
const conditions = [];
|
|
3209
|
+
const params = [];
|
|
3210
|
+
if (filter.type) {
|
|
3211
|
+
conditions.push("type = ?");
|
|
3212
|
+
params.push(filter.type);
|
|
3111
3213
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
init_database();
|
|
3116
|
-
});
|
|
3117
|
-
|
|
3118
|
-
// src/lib/config.ts
|
|
3119
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync, unlinkSync } from "fs";
|
|
3120
|
-
import { homedir } from "os";
|
|
3121
|
-
import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
3122
|
-
function deepMerge(target, source) {
|
|
3123
|
-
const result = { ...target };
|
|
3124
|
-
for (const key of Object.keys(source)) {
|
|
3125
|
-
const sourceVal = source[key];
|
|
3126
|
-
const targetVal = result[key];
|
|
3127
|
-
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
3128
|
-
result[key] = deepMerge(targetVal, sourceVal);
|
|
3129
|
-
} else {
|
|
3130
|
-
result[key] = sourceVal;
|
|
3131
|
-
}
|
|
3214
|
+
if (filter.project_id) {
|
|
3215
|
+
conditions.push("project_id = ?");
|
|
3216
|
+
params.push(filter.project_id);
|
|
3132
3217
|
}
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
}
|
|
3138
|
-
function isValidCategory(value) {
|
|
3139
|
-
return VALID_CATEGORIES.includes(value);
|
|
3140
|
-
}
|
|
3141
|
-
function loadConfig() {
|
|
3142
|
-
const configPath = join2(homedir(), ".mementos", "config.json");
|
|
3143
|
-
let fileConfig = {};
|
|
3144
|
-
if (existsSync2(configPath)) {
|
|
3145
|
-
try {
|
|
3146
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
3147
|
-
fileConfig = JSON.parse(raw);
|
|
3148
|
-
} catch {}
|
|
3218
|
+
if (filter.search) {
|
|
3219
|
+
conditions.push("(name LIKE ? OR description LIKE ?)");
|
|
3220
|
+
const term = `%${filter.search}%`;
|
|
3221
|
+
params.push(term, term);
|
|
3149
3222
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
merged.default_scope = envScope;
|
|
3223
|
+
let sql = "SELECT * FROM entities";
|
|
3224
|
+
if (conditions.length > 0) {
|
|
3225
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
3154
3226
|
}
|
|
3155
|
-
|
|
3156
|
-
if (
|
|
3157
|
-
|
|
3227
|
+
sql += " ORDER BY updated_at DESC";
|
|
3228
|
+
if (filter.limit) {
|
|
3229
|
+
sql += " LIMIT ?";
|
|
3230
|
+
params.push(filter.limit);
|
|
3158
3231
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
3163
|
-
merged.default_importance = parsed;
|
|
3164
|
-
}
|
|
3232
|
+
if (filter.offset) {
|
|
3233
|
+
sql += " OFFSET ?";
|
|
3234
|
+
params.push(filter.offset);
|
|
3165
3235
|
}
|
|
3166
|
-
|
|
3236
|
+
const rows = d.query(sql).all(...params);
|
|
3237
|
+
return rows.map(parseEntityRow);
|
|
3167
3238
|
}
|
|
3168
|
-
function
|
|
3169
|
-
|
|
3239
|
+
function deleteEntity(id, db) {
|
|
3240
|
+
const d = db || getDatabase();
|
|
3241
|
+
const result = d.run("DELETE FROM entities WHERE id = ?", [id]);
|
|
3242
|
+
if (result.changes === 0)
|
|
3243
|
+
throw new EntityNotFoundError(id);
|
|
3170
3244
|
}
|
|
3171
|
-
function
|
|
3172
|
-
|
|
3245
|
+
function mergeEntities(sourceId, targetId, db) {
|
|
3246
|
+
const d = db || getDatabase();
|
|
3247
|
+
getEntity(sourceId, d);
|
|
3248
|
+
getEntity(targetId, d);
|
|
3249
|
+
d.run(`UPDATE OR IGNORE relations SET source_entity_id = ? WHERE source_entity_id = ?`, [targetId, sourceId]);
|
|
3250
|
+
d.run(`UPDATE OR IGNORE relations SET target_entity_id = ? WHERE target_entity_id = ?`, [targetId, sourceId]);
|
|
3251
|
+
d.run("DELETE FROM relations WHERE source_entity_id = ? OR target_entity_id = ?", [
|
|
3252
|
+
sourceId,
|
|
3253
|
+
sourceId
|
|
3254
|
+
]);
|
|
3255
|
+
d.run(`UPDATE OR IGNORE entity_memories SET entity_id = ? WHERE entity_id = ?`, [targetId, sourceId]);
|
|
3256
|
+
d.run("DELETE FROM entity_memories WHERE entity_id = ?", [sourceId]);
|
|
3257
|
+
d.run("DELETE FROM entities WHERE id = ?", [sourceId]);
|
|
3258
|
+
d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), targetId]);
|
|
3259
|
+
return getEntity(targetId, d);
|
|
3173
3260
|
}
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3261
|
+
var init_entities = __esm(() => {
|
|
3262
|
+
init_database();
|
|
3263
|
+
init_types();
|
|
3264
|
+
init_hooks();
|
|
3265
|
+
});
|
|
3266
|
+
|
|
3267
|
+
// src/lib/search.ts
|
|
3268
|
+
function parseMemoryRow2(row) {
|
|
3269
|
+
return {
|
|
3270
|
+
id: row["id"],
|
|
3271
|
+
key: row["key"],
|
|
3272
|
+
value: row["value"],
|
|
3273
|
+
category: row["category"],
|
|
3274
|
+
scope: row["scope"],
|
|
3275
|
+
summary: row["summary"] || null,
|
|
3276
|
+
tags: JSON.parse(row["tags"] || "[]"),
|
|
3277
|
+
importance: row["importance"],
|
|
3278
|
+
source: row["source"],
|
|
3279
|
+
status: row["status"],
|
|
3280
|
+
pinned: !!row["pinned"],
|
|
3281
|
+
agent_id: row["agent_id"] || null,
|
|
3282
|
+
project_id: row["project_id"] || null,
|
|
3283
|
+
session_id: row["session_id"] || null,
|
|
3284
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
3285
|
+
access_count: row["access_count"],
|
|
3286
|
+
version: row["version"],
|
|
3287
|
+
expires_at: row["expires_at"] || null,
|
|
3288
|
+
created_at: row["created_at"],
|
|
3289
|
+
updated_at: row["updated_at"],
|
|
3290
|
+
accessed_at: row["accessed_at"] || null
|
|
3291
|
+
};
|
|
3183
3292
|
}
|
|
3184
|
-
function
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3293
|
+
function preprocessQuery(query) {
|
|
3294
|
+
let q = query.trim();
|
|
3295
|
+
q = q.replace(/\s+/g, " ");
|
|
3296
|
+
q = q.normalize("NFC");
|
|
3297
|
+
return q;
|
|
3188
3298
|
}
|
|
3189
|
-
function
|
|
3190
|
-
|
|
3191
|
-
if (envProfile)
|
|
3192
|
-
return envProfile.trim();
|
|
3193
|
-
const cfg = readGlobalConfig();
|
|
3194
|
-
return cfg["active_profile"] || null;
|
|
3299
|
+
function escapeLikePattern(s) {
|
|
3300
|
+
return s.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
3195
3301
|
}
|
|
3196
|
-
function
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3302
|
+
function removeStopWords(tokens) {
|
|
3303
|
+
if (tokens.length <= 1)
|
|
3304
|
+
return tokens;
|
|
3305
|
+
const filtered = tokens.filter((t) => !STOP_WORDS.has(t.toLowerCase()));
|
|
3306
|
+
return filtered.length > 0 ? filtered : tokens;
|
|
3307
|
+
}
|
|
3308
|
+
function extractHighlights(memory, queryLower) {
|
|
3309
|
+
const highlights = [];
|
|
3310
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
3311
|
+
for (const field of ["key", "value", "summary"]) {
|
|
3312
|
+
const text = field === "summary" ? memory.summary : memory[field];
|
|
3313
|
+
if (!text)
|
|
3314
|
+
continue;
|
|
3315
|
+
const textLower = text.toLowerCase();
|
|
3316
|
+
const searchTerms = [queryLower, ...tokens].filter(Boolean);
|
|
3317
|
+
for (const term of searchTerms) {
|
|
3318
|
+
const idx = textLower.indexOf(term);
|
|
3319
|
+
if (idx !== -1) {
|
|
3320
|
+
const start = Math.max(0, idx - 30);
|
|
3321
|
+
const end = Math.min(text.length, idx + term.length + 30);
|
|
3322
|
+
const prefix = start > 0 ? "..." : "";
|
|
3323
|
+
const suffix = end < text.length ? "..." : "";
|
|
3324
|
+
highlights.push({
|
|
3325
|
+
field,
|
|
3326
|
+
snippet: prefix + text.slice(start, end) + suffix
|
|
3327
|
+
});
|
|
3328
|
+
break;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
for (const tag of memory.tags) {
|
|
3333
|
+
if (tag.toLowerCase().includes(queryLower) || tokens.some((t) => tag.toLowerCase().includes(t))) {
|
|
3334
|
+
highlights.push({ field: "tag", snippet: tag });
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
return highlights;
|
|
3338
|
+
}
|
|
3339
|
+
function determineMatchType(memory, queryLower) {
|
|
3340
|
+
if (memory.key.toLowerCase() === queryLower)
|
|
3341
|
+
return "exact";
|
|
3342
|
+
if (memory.tags.some((t) => t.toLowerCase() === queryLower))
|
|
3343
|
+
return "tag";
|
|
3344
|
+
if (memory.tags.some((t) => t.toLowerCase().includes(queryLower)))
|
|
3345
|
+
return "tag";
|
|
3346
|
+
return "fuzzy";
|
|
3347
|
+
}
|
|
3348
|
+
function computeScore(memory, queryLower) {
|
|
3349
|
+
const fieldScores = [];
|
|
3350
|
+
const keyLower = memory.key.toLowerCase();
|
|
3351
|
+
if (keyLower === queryLower) {
|
|
3352
|
+
fieldScores.push(10);
|
|
3353
|
+
} else if (keyLower.includes(queryLower)) {
|
|
3354
|
+
fieldScores.push(7);
|
|
3355
|
+
}
|
|
3356
|
+
if (memory.tags.some((t) => t.toLowerCase() === queryLower)) {
|
|
3357
|
+
fieldScores.push(6);
|
|
3358
|
+
} else if (memory.tags.some((t) => t.toLowerCase().includes(queryLower))) {
|
|
3359
|
+
fieldScores.push(3);
|
|
3360
|
+
}
|
|
3361
|
+
if (memory.summary && memory.summary.toLowerCase().includes(queryLower)) {
|
|
3362
|
+
fieldScores.push(4);
|
|
3363
|
+
}
|
|
3364
|
+
if (memory.value.toLowerCase().includes(queryLower)) {
|
|
3365
|
+
fieldScores.push(3);
|
|
3366
|
+
}
|
|
3367
|
+
const metadataStr = JSON.stringify(memory.metadata).toLowerCase();
|
|
3368
|
+
if (metadataStr !== "{}" && metadataStr.includes(queryLower)) {
|
|
3369
|
+
fieldScores.push(2);
|
|
3370
|
+
}
|
|
3371
|
+
fieldScores.sort((a, b) => b - a);
|
|
3372
|
+
const diminishingMultipliers = [1, 0.5, 0.25, 0.15, 0.15];
|
|
3373
|
+
let score = 0;
|
|
3374
|
+
for (let i = 0;i < fieldScores.length; i++) {
|
|
3375
|
+
score += fieldScores[i] * (diminishingMultipliers[i] ?? 0.15);
|
|
3376
|
+
}
|
|
3377
|
+
const { phrases } = extractQuotedPhrases(queryLower);
|
|
3378
|
+
for (const phrase of phrases) {
|
|
3379
|
+
if (keyLower.includes(phrase))
|
|
3380
|
+
score += 8;
|
|
3381
|
+
if (memory.value.toLowerCase().includes(phrase))
|
|
3382
|
+
score += 5;
|
|
3383
|
+
if (memory.summary && memory.summary.toLowerCase().includes(phrase))
|
|
3384
|
+
score += 4;
|
|
3385
|
+
}
|
|
3386
|
+
const { remainder } = extractQuotedPhrases(queryLower);
|
|
3387
|
+
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
3388
|
+
if (tokens.length > 1) {
|
|
3389
|
+
let tokenScore = 0;
|
|
3390
|
+
for (const token of tokens) {
|
|
3391
|
+
if (keyLower === token) {
|
|
3392
|
+
tokenScore += 10 / tokens.length;
|
|
3393
|
+
} else if (keyLower.includes(token)) {
|
|
3394
|
+
tokenScore += 7 / tokens.length;
|
|
3395
|
+
}
|
|
3396
|
+
if (memory.tags.some((t) => t.toLowerCase() === token)) {
|
|
3397
|
+
tokenScore += 6 / tokens.length;
|
|
3398
|
+
} else if (memory.tags.some((t) => t.toLowerCase().includes(token))) {
|
|
3399
|
+
tokenScore += 3 / tokens.length;
|
|
3400
|
+
}
|
|
3401
|
+
if (memory.summary && memory.summary.toLowerCase().includes(token)) {
|
|
3402
|
+
tokenScore += 4 / tokens.length;
|
|
3403
|
+
}
|
|
3404
|
+
if (memory.value.toLowerCase().includes(token)) {
|
|
3405
|
+
tokenScore += 3 / tokens.length;
|
|
3406
|
+
}
|
|
3407
|
+
if (metadataStr !== "{}" && metadataStr.includes(token)) {
|
|
3408
|
+
tokenScore += 2 / tokens.length;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
if (score > 0) {
|
|
3412
|
+
score += tokenScore * 0.3;
|
|
3413
|
+
} else {
|
|
3414
|
+
score += tokenScore;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
return score;
|
|
3418
|
+
}
|
|
3419
|
+
function extractQuotedPhrases(query) {
|
|
3420
|
+
const phrases = [];
|
|
3421
|
+
const remainder = query.replace(/"([^"]+)"/g, (_match, phrase) => {
|
|
3422
|
+
phrases.push(phrase);
|
|
3423
|
+
return "";
|
|
3424
|
+
});
|
|
3425
|
+
return { phrases, remainder: remainder.trim() };
|
|
3426
|
+
}
|
|
3427
|
+
function escapeFts5Query(query) {
|
|
3428
|
+
const { phrases, remainder } = extractQuotedPhrases(query);
|
|
3429
|
+
const parts = [];
|
|
3430
|
+
for (const phrase of phrases) {
|
|
3431
|
+
parts.push(`"${phrase.replace(/"/g, '""')}"`);
|
|
3432
|
+
}
|
|
3433
|
+
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
3434
|
+
for (const t of tokens) {
|
|
3435
|
+
parts.push(`"${t.replace(/"/g, '""')}"`);
|
|
3436
|
+
}
|
|
3437
|
+
return parts.join(" ");
|
|
3438
|
+
}
|
|
3439
|
+
function hasFts5Table(d) {
|
|
3440
|
+
try {
|
|
3441
|
+
const row = d.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
3442
|
+
return !!row;
|
|
3443
|
+
} catch {
|
|
3444
|
+
return false;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
function buildFilterConditions(filter) {
|
|
3448
|
+
const conditions = [];
|
|
3449
|
+
const params = [];
|
|
3450
|
+
conditions.push("m.status = 'active'");
|
|
3451
|
+
conditions.push("(m.expires_at IS NULL OR m.expires_at >= datetime('now'))");
|
|
3452
|
+
if (!filter)
|
|
3453
|
+
return { conditions, params };
|
|
3454
|
+
if (filter.scope) {
|
|
3455
|
+
if (Array.isArray(filter.scope)) {
|
|
3456
|
+
conditions.push(`m.scope IN (${filter.scope.map(() => "?").join(",")})`);
|
|
3457
|
+
params.push(...filter.scope);
|
|
3458
|
+
} else {
|
|
3459
|
+
conditions.push("m.scope = ?");
|
|
3460
|
+
params.push(filter.scope);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
if (filter.category) {
|
|
3464
|
+
if (Array.isArray(filter.category)) {
|
|
3465
|
+
conditions.push(`m.category IN (${filter.category.map(() => "?").join(",")})`);
|
|
3466
|
+
params.push(...filter.category);
|
|
3467
|
+
} else {
|
|
3468
|
+
conditions.push("m.category = ?");
|
|
3469
|
+
params.push(filter.category);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
if (filter.source) {
|
|
3473
|
+
if (Array.isArray(filter.source)) {
|
|
3474
|
+
conditions.push(`m.source IN (${filter.source.map(() => "?").join(",")})`);
|
|
3475
|
+
params.push(...filter.source);
|
|
3476
|
+
} else {
|
|
3477
|
+
conditions.push("m.source = ?");
|
|
3478
|
+
params.push(filter.source);
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
if (filter.status) {
|
|
3482
|
+
conditions.shift();
|
|
3483
|
+
if (Array.isArray(filter.status)) {
|
|
3484
|
+
conditions.push(`m.status IN (${filter.status.map(() => "?").join(",")})`);
|
|
3485
|
+
params.push(...filter.status);
|
|
3486
|
+
} else {
|
|
3487
|
+
conditions.push("m.status = ?");
|
|
3488
|
+
params.push(filter.status);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
if (filter.project_id) {
|
|
3492
|
+
conditions.push("m.project_id = ?");
|
|
3493
|
+
params.push(filter.project_id);
|
|
3494
|
+
}
|
|
3495
|
+
if (filter.agent_id) {
|
|
3496
|
+
conditions.push("m.agent_id = ?");
|
|
3497
|
+
params.push(filter.agent_id);
|
|
3498
|
+
}
|
|
3499
|
+
if (filter.session_id) {
|
|
3500
|
+
conditions.push("m.session_id = ?");
|
|
3501
|
+
params.push(filter.session_id);
|
|
3502
|
+
}
|
|
3503
|
+
if (filter.min_importance) {
|
|
3504
|
+
conditions.push("m.importance >= ?");
|
|
3505
|
+
params.push(filter.min_importance);
|
|
3506
|
+
}
|
|
3507
|
+
if (filter.pinned !== undefined) {
|
|
3508
|
+
conditions.push("m.pinned = ?");
|
|
3509
|
+
params.push(filter.pinned ? 1 : 0);
|
|
3510
|
+
}
|
|
3511
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
3512
|
+
for (const tag of filter.tags) {
|
|
3513
|
+
conditions.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
|
|
3514
|
+
params.push(tag);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
return { conditions, params };
|
|
3518
|
+
}
|
|
3519
|
+
function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
|
|
3520
|
+
const ftsQuery = escapeFts5Query(query);
|
|
3521
|
+
if (!ftsQuery)
|
|
3522
|
+
return null;
|
|
3523
|
+
try {
|
|
3524
|
+
const { conditions, params } = buildFilterConditions(filter);
|
|
3525
|
+
const queryParam = `%${query}%`;
|
|
3526
|
+
const ftsCondition = `(m.rowid IN (SELECT f.rowid FROM memories_fts f WHERE memories_fts MATCH ?) ` + `OR m.id IN (SELECT memory_id FROM memory_tags WHERE tag LIKE ?) ` + `OR m.metadata LIKE ?)`;
|
|
3527
|
+
const allConditions = [ftsCondition, ...conditions];
|
|
3528
|
+
const allParams = [ftsQuery, queryParam, queryParam, ...params];
|
|
3529
|
+
const candidateSql = `SELECT m.* FROM memories m WHERE ${allConditions.join(" AND ")}`;
|
|
3530
|
+
const rows = d.query(candidateSql).all(...allParams);
|
|
3531
|
+
return scoreResults(rows, queryLower, graphBoostedIds);
|
|
3532
|
+
} catch {
|
|
3533
|
+
return null;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
function searchWithLike(d, query, queryLower, filter, graphBoostedIds) {
|
|
3537
|
+
const { conditions, params } = buildFilterConditions(filter);
|
|
3538
|
+
const rawTokens = query.trim().split(/\s+/).filter(Boolean);
|
|
3539
|
+
const tokens = removeStopWords(rawTokens);
|
|
3540
|
+
const escapedQuery = escapeLikePattern(query);
|
|
3541
|
+
const likePatterns = [`%${escapedQuery}%`];
|
|
3542
|
+
if (tokens.length > 1) {
|
|
3543
|
+
for (const t of tokens)
|
|
3544
|
+
likePatterns.push(`%${escapeLikePattern(t)}%`);
|
|
3545
|
+
}
|
|
3546
|
+
const fieldClauses = [];
|
|
3547
|
+
for (const pattern of likePatterns) {
|
|
3548
|
+
fieldClauses.push("m.key LIKE ? ESCAPE '\\'");
|
|
3549
|
+
params.push(pattern);
|
|
3550
|
+
fieldClauses.push("m.value LIKE ? ESCAPE '\\'");
|
|
3551
|
+
params.push(pattern);
|
|
3552
|
+
fieldClauses.push("m.summary LIKE ? ESCAPE '\\'");
|
|
3553
|
+
params.push(pattern);
|
|
3554
|
+
fieldClauses.push("m.metadata LIKE ? ESCAPE '\\'");
|
|
3555
|
+
params.push(pattern);
|
|
3556
|
+
fieldClauses.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag LIKE ? ESCAPE '\\')");
|
|
3557
|
+
params.push(pattern);
|
|
3558
|
+
}
|
|
3559
|
+
conditions.push(`(${fieldClauses.join(" OR ")})`);
|
|
3560
|
+
const sql = `SELECT DISTINCT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
|
|
3561
|
+
const rows = d.query(sql).all(...params);
|
|
3562
|
+
return scoreResults(rows, queryLower, graphBoostedIds);
|
|
3563
|
+
}
|
|
3564
|
+
function generateTrigrams(s) {
|
|
3565
|
+
const lower = s.toLowerCase();
|
|
3566
|
+
const trigrams = new Set;
|
|
3567
|
+
for (let i = 0;i <= lower.length - 3; i++) {
|
|
3568
|
+
trigrams.add(lower.slice(i, i + 3));
|
|
3569
|
+
}
|
|
3570
|
+
return trigrams;
|
|
3571
|
+
}
|
|
3572
|
+
function trigramSimilarity(a, b) {
|
|
3573
|
+
const triA = generateTrigrams(a);
|
|
3574
|
+
const triB = generateTrigrams(b);
|
|
3575
|
+
if (triA.size === 0 || triB.size === 0)
|
|
3576
|
+
return 0;
|
|
3577
|
+
let intersection = 0;
|
|
3578
|
+
for (const t of triA) {
|
|
3579
|
+
if (triB.has(t))
|
|
3580
|
+
intersection++;
|
|
3581
|
+
}
|
|
3582
|
+
const union = triA.size + triB.size - intersection;
|
|
3583
|
+
return union === 0 ? 0 : intersection / union;
|
|
3584
|
+
}
|
|
3585
|
+
function searchWithFuzzy(d, query, filter, graphBoostedIds) {
|
|
3586
|
+
const { conditions, params } = buildFilterConditions(filter);
|
|
3587
|
+
const sql = `SELECT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
|
|
3588
|
+
const rows = d.query(sql).all(...params);
|
|
3589
|
+
const MIN_SIMILARITY = 0.3;
|
|
3590
|
+
const results = [];
|
|
3591
|
+
for (const row of rows) {
|
|
3592
|
+
const memory = parseMemoryRow2(row);
|
|
3593
|
+
let bestSimilarity = 0;
|
|
3594
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.key));
|
|
3595
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.value.slice(0, 200)));
|
|
3596
|
+
if (memory.summary) {
|
|
3597
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.summary));
|
|
3598
|
+
}
|
|
3599
|
+
for (const tag of memory.tags) {
|
|
3600
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, tag));
|
|
3601
|
+
}
|
|
3602
|
+
if (bestSimilarity >= MIN_SIMILARITY) {
|
|
3603
|
+
const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
|
|
3604
|
+
const score = bestSimilarity * 5 * memory.importance / 10 + graphBoost;
|
|
3605
|
+
results.push({ memory, score, match_type: "fuzzy" });
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
results.sort((a, b) => b.score - a.score);
|
|
3609
|
+
return results;
|
|
3610
|
+
}
|
|
3611
|
+
function getGraphBoostedMemoryIds(query, d) {
|
|
3612
|
+
const boostedIds = new Set;
|
|
3613
|
+
try {
|
|
3614
|
+
const matchingEntities = listEntities({ search: query, limit: 10 }, d);
|
|
3615
|
+
const exactMatch = getEntityByName(query, undefined, undefined, d);
|
|
3616
|
+
if (exactMatch && !matchingEntities.find((e) => e.id === exactMatch.id)) {
|
|
3617
|
+
matchingEntities.push(exactMatch);
|
|
3618
|
+
}
|
|
3619
|
+
for (const entity of matchingEntities) {
|
|
3620
|
+
const memories = getMemoriesForEntity(entity.id, d);
|
|
3621
|
+
for (const mem of memories) {
|
|
3622
|
+
boostedIds.add(mem.id);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
} catch {}
|
|
3626
|
+
return boostedIds;
|
|
3627
|
+
}
|
|
3628
|
+
function computeRecencyBoost(memory) {
|
|
3629
|
+
if (memory.pinned)
|
|
3630
|
+
return 1;
|
|
3631
|
+
const mostRecent = memory.accessed_at || memory.updated_at;
|
|
3632
|
+
if (!mostRecent)
|
|
3633
|
+
return 0;
|
|
3634
|
+
const daysSinceAccess = (Date.now() - Date.parse(mostRecent)) / (1000 * 60 * 60 * 24);
|
|
3635
|
+
return Math.max(0, 1 - daysSinceAccess / 30);
|
|
3636
|
+
}
|
|
3637
|
+
function scoreResults(rows, queryLower, graphBoostedIds) {
|
|
3638
|
+
const scored = [];
|
|
3639
|
+
for (const row of rows) {
|
|
3640
|
+
const memory = parseMemoryRow2(row);
|
|
3641
|
+
const rawScore = computeScore(memory, queryLower);
|
|
3642
|
+
if (rawScore === 0)
|
|
3643
|
+
continue;
|
|
3644
|
+
const weightedScore = rawScore * memory.importance / 10;
|
|
3645
|
+
const recencyBoost = computeRecencyBoost(memory);
|
|
3646
|
+
const accessBoost = Math.min(memory.access_count / 20, 0.2);
|
|
3647
|
+
const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
|
|
3648
|
+
const finalScore = (weightedScore + graphBoost) * (1 + recencyBoost * 0.3) * (1 + accessBoost);
|
|
3649
|
+
const matchType = determineMatchType(memory, queryLower);
|
|
3650
|
+
scored.push({
|
|
3651
|
+
memory,
|
|
3652
|
+
score: finalScore,
|
|
3653
|
+
match_type: matchType,
|
|
3654
|
+
highlights: extractHighlights(memory, queryLower)
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
3657
|
+
scored.sort((a, b) => {
|
|
3658
|
+
if (b.score !== a.score)
|
|
3659
|
+
return b.score - a.score;
|
|
3660
|
+
return b.memory.importance - a.memory.importance;
|
|
3661
|
+
});
|
|
3662
|
+
return scored;
|
|
3663
|
+
}
|
|
3664
|
+
function searchMemories(query, filter, db) {
|
|
3665
|
+
const d = db || getDatabase();
|
|
3666
|
+
query = preprocessQuery(query);
|
|
3667
|
+
if (!query)
|
|
3668
|
+
return [];
|
|
3669
|
+
const queryLower = query.toLowerCase();
|
|
3670
|
+
const graphBoostedIds = getGraphBoostedMemoryIds(query, d);
|
|
3671
|
+
let scored;
|
|
3672
|
+
if (hasFts5Table(d)) {
|
|
3673
|
+
const ftsResult = searchWithFts5(d, query, queryLower, filter, graphBoostedIds);
|
|
3674
|
+
if (ftsResult !== null) {
|
|
3675
|
+
scored = ftsResult;
|
|
3676
|
+
} else {
|
|
3677
|
+
scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
|
|
3678
|
+
}
|
|
3679
|
+
} else {
|
|
3680
|
+
scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
|
|
3681
|
+
}
|
|
3682
|
+
if (scored.length < 3) {
|
|
3683
|
+
const fuzzyResults = searchWithFuzzy(d, query, filter, graphBoostedIds);
|
|
3684
|
+
const seenIds = new Set(scored.map((r) => r.memory.id));
|
|
3685
|
+
for (const fr of fuzzyResults) {
|
|
3686
|
+
if (!seenIds.has(fr.memory.id)) {
|
|
3687
|
+
scored.push(fr);
|
|
3688
|
+
seenIds.add(fr.memory.id);
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
scored.sort((a, b) => {
|
|
3692
|
+
if (b.score !== a.score)
|
|
3693
|
+
return b.score - a.score;
|
|
3694
|
+
return b.memory.importance - a.memory.importance;
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
const offset = filter?.offset ?? 0;
|
|
3698
|
+
const limit = filter?.limit ?? scored.length;
|
|
3699
|
+
const finalResults = scored.slice(offset, offset + limit);
|
|
3700
|
+
if (finalResults.length > 0 && scored.length > 0) {
|
|
3701
|
+
const topScore = scored[0]?.score ?? 0;
|
|
3702
|
+
const secondScore = scored[1]?.score ?? 0;
|
|
3703
|
+
const confidence = topScore > 0 ? Math.max(0, Math.min(1, (topScore - secondScore) / topScore)) : 0;
|
|
3704
|
+
finalResults[0] = { ...finalResults[0], confidence };
|
|
3705
|
+
}
|
|
3706
|
+
logSearchQuery(query, scored.length, filter?.agent_id, filter?.project_id, d);
|
|
3707
|
+
return finalResults;
|
|
3708
|
+
}
|
|
3709
|
+
function logSearchQuery(query, resultCount, agentId, projectId, db) {
|
|
3710
|
+
try {
|
|
3711
|
+
const d = db || getDatabase();
|
|
3712
|
+
const id = crypto.randomUUID().slice(0, 8);
|
|
3713
|
+
d.run("INSERT INTO search_history (id, query, result_count, agent_id, project_id) VALUES (?, ?, ?, ?, ?)", [id, query, resultCount, agentId || null, projectId || null]);
|
|
3714
|
+
} catch {}
|
|
3715
|
+
}
|
|
3716
|
+
function getSearchHistory(limit = 20, projectId, db) {
|
|
3717
|
+
const d = db || getDatabase();
|
|
3718
|
+
if (projectId) {
|
|
3719
|
+
return d.query("SELECT query, result_count, created_at FROM search_history WHERE project_id = ? ORDER BY created_at DESC LIMIT ?").all(projectId, limit);
|
|
3720
|
+
}
|
|
3721
|
+
return d.query("SELECT query, result_count, created_at FROM search_history ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
3722
|
+
}
|
|
3723
|
+
function getPopularSearches(limit = 10, projectId, db) {
|
|
3724
|
+
const d = db || getDatabase();
|
|
3725
|
+
if (projectId) {
|
|
3726
|
+
return d.query("SELECT query, COUNT(*) as count FROM search_history WHERE project_id = ? GROUP BY query ORDER BY count DESC LIMIT ?").all(projectId, limit);
|
|
3727
|
+
}
|
|
3728
|
+
return d.query("SELECT query, COUNT(*) as count FROM search_history GROUP BY query ORDER BY count DESC LIMIT ?").all(limit);
|
|
3729
|
+
}
|
|
3730
|
+
var STOP_WORDS;
|
|
3731
|
+
var init_search = __esm(() => {
|
|
3732
|
+
init_database();
|
|
3733
|
+
init_entities();
|
|
3734
|
+
init_entity_memories();
|
|
3735
|
+
STOP_WORDS = new Set([
|
|
3736
|
+
"a",
|
|
3737
|
+
"an",
|
|
3738
|
+
"the",
|
|
3739
|
+
"is",
|
|
3740
|
+
"are",
|
|
3741
|
+
"was",
|
|
3742
|
+
"were",
|
|
3743
|
+
"be",
|
|
3744
|
+
"been",
|
|
3745
|
+
"being",
|
|
3746
|
+
"have",
|
|
3747
|
+
"has",
|
|
3748
|
+
"had",
|
|
3749
|
+
"do",
|
|
3750
|
+
"does",
|
|
3751
|
+
"did",
|
|
3752
|
+
"will",
|
|
3753
|
+
"would",
|
|
3754
|
+
"could",
|
|
3755
|
+
"should",
|
|
3756
|
+
"may",
|
|
3757
|
+
"might",
|
|
3758
|
+
"shall",
|
|
3759
|
+
"can",
|
|
3760
|
+
"need",
|
|
3761
|
+
"dare",
|
|
3762
|
+
"ought",
|
|
3763
|
+
"used",
|
|
3764
|
+
"to",
|
|
3765
|
+
"of",
|
|
3766
|
+
"in",
|
|
3767
|
+
"for",
|
|
3768
|
+
"on",
|
|
3769
|
+
"with",
|
|
3770
|
+
"at",
|
|
3771
|
+
"by",
|
|
3772
|
+
"from",
|
|
3773
|
+
"as",
|
|
3774
|
+
"into",
|
|
3775
|
+
"through",
|
|
3776
|
+
"during",
|
|
3777
|
+
"before",
|
|
3778
|
+
"after",
|
|
3779
|
+
"above",
|
|
3780
|
+
"below",
|
|
3781
|
+
"between",
|
|
3782
|
+
"out",
|
|
3783
|
+
"off",
|
|
3784
|
+
"over",
|
|
3785
|
+
"under",
|
|
3786
|
+
"again",
|
|
3787
|
+
"further",
|
|
3788
|
+
"then",
|
|
3789
|
+
"once",
|
|
3790
|
+
"here",
|
|
3791
|
+
"there",
|
|
3792
|
+
"when",
|
|
3793
|
+
"where",
|
|
3794
|
+
"why",
|
|
3795
|
+
"how",
|
|
3796
|
+
"all",
|
|
3797
|
+
"each",
|
|
3798
|
+
"every",
|
|
3799
|
+
"both",
|
|
3800
|
+
"few",
|
|
3801
|
+
"more",
|
|
3802
|
+
"most",
|
|
3803
|
+
"other",
|
|
3804
|
+
"some",
|
|
3805
|
+
"such",
|
|
3806
|
+
"no",
|
|
3807
|
+
"not",
|
|
3808
|
+
"only",
|
|
3809
|
+
"own",
|
|
3810
|
+
"same",
|
|
3811
|
+
"so",
|
|
3812
|
+
"than",
|
|
3813
|
+
"too",
|
|
3814
|
+
"very",
|
|
3815
|
+
"just",
|
|
3816
|
+
"because",
|
|
3817
|
+
"but",
|
|
3818
|
+
"and",
|
|
3819
|
+
"or",
|
|
3820
|
+
"if",
|
|
3821
|
+
"while",
|
|
3822
|
+
"that",
|
|
3823
|
+
"this",
|
|
3824
|
+
"it"
|
|
3825
|
+
]);
|
|
3826
|
+
});
|
|
3827
|
+
|
|
3828
|
+
// src/db/relations.ts
|
|
3829
|
+
function parseRelationRow(row) {
|
|
3830
|
+
return {
|
|
3831
|
+
id: row["id"],
|
|
3832
|
+
source_entity_id: row["source_entity_id"],
|
|
3833
|
+
target_entity_id: row["target_entity_id"],
|
|
3834
|
+
relation_type: row["relation_type"],
|
|
3835
|
+
weight: row["weight"],
|
|
3836
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
3837
|
+
created_at: row["created_at"]
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3840
|
+
function parseEntityRow2(row) {
|
|
3841
|
+
return {
|
|
3842
|
+
id: row["id"],
|
|
3843
|
+
name: row["name"],
|
|
3844
|
+
type: row["type"],
|
|
3845
|
+
description: row["description"] || null,
|
|
3846
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
3847
|
+
project_id: row["project_id"] || null,
|
|
3848
|
+
created_at: row["created_at"],
|
|
3849
|
+
updated_at: row["updated_at"]
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
function createRelation(input, db) {
|
|
3853
|
+
const d = db || getDatabase();
|
|
3854
|
+
const id = shortUuid();
|
|
3855
|
+
const timestamp = now();
|
|
3856
|
+
const weight = input.weight ?? 1;
|
|
3857
|
+
const metadata = JSON.stringify(input.metadata ?? {});
|
|
3858
|
+
d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
|
|
3859
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
3860
|
+
ON CONFLICT(source_entity_id, target_entity_id, relation_type)
|
|
3861
|
+
DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
|
|
3862
|
+
const row = d.query(`SELECT * FROM relations
|
|
3863
|
+
WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
|
|
3864
|
+
const relation = parseRelationRow(row);
|
|
3865
|
+
hookRegistry.runHooks("PostRelationCreate", {
|
|
3866
|
+
relationId: relation.id,
|
|
3867
|
+
sourceEntityId: relation.source_entity_id,
|
|
3868
|
+
targetEntityId: relation.target_entity_id,
|
|
3869
|
+
relationType: relation.relation_type,
|
|
3870
|
+
timestamp: Date.now()
|
|
3871
|
+
});
|
|
3872
|
+
return relation;
|
|
3873
|
+
}
|
|
3874
|
+
function listRelations(filter, db) {
|
|
3875
|
+
const d = db || getDatabase();
|
|
3876
|
+
const conditions = [];
|
|
3877
|
+
const params = [];
|
|
3878
|
+
if (filter.entity_id) {
|
|
3879
|
+
const dir = filter.direction || "both";
|
|
3880
|
+
if (dir === "outgoing") {
|
|
3881
|
+
conditions.push("source_entity_id = ?");
|
|
3882
|
+
params.push(filter.entity_id);
|
|
3883
|
+
} else if (dir === "incoming") {
|
|
3884
|
+
conditions.push("target_entity_id = ?");
|
|
3885
|
+
params.push(filter.entity_id);
|
|
3886
|
+
} else {
|
|
3887
|
+
conditions.push("(source_entity_id = ? OR target_entity_id = ?)");
|
|
3888
|
+
params.push(filter.entity_id, filter.entity_id);
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
if (filter.relation_type) {
|
|
3892
|
+
conditions.push("relation_type = ?");
|
|
3893
|
+
params.push(filter.relation_type);
|
|
3894
|
+
}
|
|
3895
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3896
|
+
const rows = d.query(`SELECT * FROM relations ${where} ORDER BY created_at DESC`).all(...params);
|
|
3897
|
+
return rows.map(parseRelationRow);
|
|
3898
|
+
}
|
|
3899
|
+
function deleteRelation(id, db) {
|
|
3900
|
+
const d = db || getDatabase();
|
|
3901
|
+
const result = d.run("DELETE FROM relations WHERE id = ?", [id]);
|
|
3902
|
+
if (result.changes === 0)
|
|
3903
|
+
throw new Error(`Relation not found: ${id}`);
|
|
3904
|
+
}
|
|
3905
|
+
function getRelatedEntities(entityId, relationType, db) {
|
|
3906
|
+
const d = db || getDatabase();
|
|
3907
|
+
let sql;
|
|
3908
|
+
const params = [];
|
|
3909
|
+
if (relationType) {
|
|
3910
|
+
sql = `
|
|
3911
|
+
SELECT DISTINCT e.* FROM entities e
|
|
3912
|
+
JOIN relations r ON (
|
|
3913
|
+
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
3914
|
+
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
3915
|
+
)
|
|
3916
|
+
WHERE r.relation_type = ?
|
|
3917
|
+
`;
|
|
3918
|
+
params.push(entityId, entityId, relationType);
|
|
3200
3919
|
} else {
|
|
3201
|
-
|
|
3920
|
+
sql = `
|
|
3921
|
+
SELECT DISTINCT e.* FROM entities e
|
|
3922
|
+
JOIN relations r ON (
|
|
3923
|
+
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
3924
|
+
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
3925
|
+
)
|
|
3926
|
+
`;
|
|
3927
|
+
params.push(entityId, entityId);
|
|
3202
3928
|
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
function listProfiles() {
|
|
3206
|
-
const dir = profilesDir();
|
|
3207
|
-
if (!existsSync2(dir))
|
|
3208
|
-
return [];
|
|
3209
|
-
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
3929
|
+
const rows = d.query(sql).all(...params);
|
|
3930
|
+
return rows.map(parseEntityRow2);
|
|
3210
3931
|
}
|
|
3211
|
-
function
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3932
|
+
function getEntityGraph(entityId, depth = 2, db) {
|
|
3933
|
+
const d = db || getDatabase();
|
|
3934
|
+
const entityRows = d.query(`WITH RECURSIVE graph(id, depth) AS (
|
|
3935
|
+
VALUES(?, 0)
|
|
3936
|
+
UNION
|
|
3937
|
+
SELECT CASE WHEN r.source_entity_id = g.id THEN r.target_entity_id ELSE r.source_entity_id END, g.depth + 1
|
|
3938
|
+
FROM relations r JOIN graph g ON (r.source_entity_id = g.id OR r.target_entity_id = g.id)
|
|
3939
|
+
WHERE g.depth < ?
|
|
3940
|
+
)
|
|
3941
|
+
SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
|
|
3942
|
+
const entities = entityRows.map(parseEntityRow2);
|
|
3943
|
+
const entityIds = new Set(entities.map((e) => e.id));
|
|
3944
|
+
if (entityIds.size === 0) {
|
|
3945
|
+
return { entities: [], relations: [] };
|
|
3946
|
+
}
|
|
3947
|
+
const placeholders = Array.from(entityIds).map(() => "?").join(",");
|
|
3948
|
+
const relationRows = d.query(`SELECT * FROM relations
|
|
3949
|
+
WHERE source_entity_id IN (${placeholders})
|
|
3950
|
+
AND target_entity_id IN (${placeholders})`).all(...Array.from(entityIds), ...Array.from(entityIds));
|
|
3951
|
+
const relations = relationRows.map(parseRelationRow);
|
|
3952
|
+
return { entities, relations };
|
|
3219
3953
|
}
|
|
3220
|
-
function
|
|
3221
|
-
|
|
3222
|
-
|
|
3954
|
+
function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
|
|
3955
|
+
const d = db || getDatabase();
|
|
3956
|
+
const rows = d.query(`WITH RECURSIVE path(id, trail, depth) AS (
|
|
3957
|
+
SELECT ?, ?, 0
|
|
3958
|
+
UNION
|
|
3959
|
+
SELECT
|
|
3960
|
+
CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
3961
|
+
p.trail || ',' || CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
3962
|
+
p.depth + 1
|
|
3963
|
+
FROM relations r JOIN path p ON (r.source_entity_id = p.id OR r.target_entity_id = p.id)
|
|
3964
|
+
WHERE p.depth < ?
|
|
3965
|
+
AND INSTR(p.trail, CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END) = 0
|
|
3966
|
+
)
|
|
3967
|
+
SELECT trail FROM path WHERE id = ? ORDER BY depth ASC LIMIT 1`).get(fromEntityId, fromEntityId, maxDepth, toEntityId);
|
|
3968
|
+
if (!rows)
|
|
3969
|
+
return null;
|
|
3970
|
+
const ids = rows.trail.split(",");
|
|
3971
|
+
const entities = [];
|
|
3972
|
+
for (const id of ids) {
|
|
3973
|
+
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
3974
|
+
if (row)
|
|
3975
|
+
entities.push(parseEntityRow2(row));
|
|
3223
3976
|
}
|
|
3977
|
+
return entities.length > 0 ? entities : null;
|
|
3224
3978
|
}
|
|
3225
|
-
var
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
default_scope: "private",
|
|
3229
|
-
default_category: "knowledge",
|
|
3230
|
-
default_importance: 5,
|
|
3231
|
-
max_entries: 1000,
|
|
3232
|
-
max_entries_per_scope: {
|
|
3233
|
-
global: 500,
|
|
3234
|
-
shared: 300,
|
|
3235
|
-
private: 200
|
|
3236
|
-
},
|
|
3237
|
-
injection: {
|
|
3238
|
-
max_tokens: 500,
|
|
3239
|
-
min_importance: 5,
|
|
3240
|
-
categories: ["preference", "fact"],
|
|
3241
|
-
refresh_interval: 5
|
|
3242
|
-
},
|
|
3243
|
-
extraction: {
|
|
3244
|
-
enabled: true,
|
|
3245
|
-
min_confidence: 0.5
|
|
3246
|
-
},
|
|
3247
|
-
sync_agents: ["claude", "codex", "gemini"],
|
|
3248
|
-
auto_cleanup: {
|
|
3249
|
-
enabled: true,
|
|
3250
|
-
expired_check_interval: 3600,
|
|
3251
|
-
unused_archive_days: 7,
|
|
3252
|
-
stale_deprioritize_days: 14
|
|
3253
|
-
}
|
|
3254
|
-
};
|
|
3255
|
-
VALID_SCOPES = ["global", "shared", "private"];
|
|
3256
|
-
VALID_CATEGORIES = [
|
|
3257
|
-
"preference",
|
|
3258
|
-
"fact",
|
|
3259
|
-
"knowledge",
|
|
3260
|
-
"history"
|
|
3261
|
-
];
|
|
3979
|
+
var init_relations = __esm(() => {
|
|
3980
|
+
init_database();
|
|
3981
|
+
init_hooks();
|
|
3262
3982
|
});
|
|
3263
3983
|
|
|
3264
|
-
// src/
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
const
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
}
|
|
3984
|
+
// src/lib/poll.ts
|
|
3985
|
+
var exports_poll = {};
|
|
3986
|
+
__export(exports_poll, {
|
|
3987
|
+
startPolling: () => startPolling
|
|
3988
|
+
});
|
|
3989
|
+
function startPolling(opts) {
|
|
3990
|
+
const interval = opts.interval_ms ?? 500;
|
|
3991
|
+
const db = opts.db ?? getDatabase();
|
|
3992
|
+
let stopped = false;
|
|
3993
|
+
let inFlight = false;
|
|
3994
|
+
let lastSeen = null;
|
|
3995
|
+
const seedLastSeen = () => {
|
|
3996
|
+
try {
|
|
3997
|
+
const latest = listMemories({
|
|
3998
|
+
scope: opts.scope,
|
|
3999
|
+
category: opts.category,
|
|
4000
|
+
agent_id: opts.agent_id,
|
|
4001
|
+
project_id: opts.project_id,
|
|
4002
|
+
limit: 1
|
|
4003
|
+
}, db);
|
|
4004
|
+
if (latest.length > 0 && latest[0]) {
|
|
4005
|
+
lastSeen = latest[0].updated_at;
|
|
4006
|
+
}
|
|
4007
|
+
} catch (err) {
|
|
4008
|
+
opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
|
|
3284
4009
|
}
|
|
3285
|
-
}
|
|
4010
|
+
};
|
|
4011
|
+
const poll = () => {
|
|
4012
|
+
if (stopped || inFlight)
|
|
4013
|
+
return;
|
|
4014
|
+
inFlight = true;
|
|
4015
|
+
try {
|
|
4016
|
+
const conditions = ["status = 'active'"];
|
|
4017
|
+
const params = [];
|
|
4018
|
+
if (lastSeen) {
|
|
4019
|
+
conditions.push("(updated_at > ? OR created_at > ?)");
|
|
4020
|
+
params.push(lastSeen, lastSeen);
|
|
4021
|
+
}
|
|
4022
|
+
if (opts.scope) {
|
|
4023
|
+
conditions.push("scope = ?");
|
|
4024
|
+
params.push(opts.scope);
|
|
4025
|
+
}
|
|
4026
|
+
if (opts.category) {
|
|
4027
|
+
conditions.push("category = ?");
|
|
4028
|
+
params.push(opts.category);
|
|
4029
|
+
}
|
|
4030
|
+
if (opts.agent_id) {
|
|
4031
|
+
conditions.push("agent_id = ?");
|
|
4032
|
+
params.push(opts.agent_id);
|
|
4033
|
+
}
|
|
4034
|
+
if (opts.project_id) {
|
|
4035
|
+
conditions.push("project_id = ?");
|
|
4036
|
+
params.push(opts.project_id);
|
|
4037
|
+
}
|
|
4038
|
+
const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY updated_at ASC`;
|
|
4039
|
+
const rows = db.query(sql).all(...params);
|
|
4040
|
+
if (rows.length > 0) {
|
|
4041
|
+
const memories = rows.map(parseMemoryRow3);
|
|
4042
|
+
const lastRow = memories[memories.length - 1];
|
|
4043
|
+
if (lastRow) {
|
|
4044
|
+
lastSeen = lastRow.updated_at;
|
|
4045
|
+
}
|
|
4046
|
+
try {
|
|
4047
|
+
opts.on_memories(memories);
|
|
4048
|
+
} catch (err) {
|
|
4049
|
+
opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
} catch (err) {
|
|
4053
|
+
opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
|
|
4054
|
+
} finally {
|
|
4055
|
+
inFlight = false;
|
|
4056
|
+
}
|
|
4057
|
+
};
|
|
4058
|
+
seedLastSeen();
|
|
4059
|
+
const timer = setInterval(poll, interval);
|
|
4060
|
+
return {
|
|
4061
|
+
stop: () => {
|
|
4062
|
+
stopped = true;
|
|
4063
|
+
clearInterval(timer);
|
|
4064
|
+
}
|
|
4065
|
+
};
|
|
3286
4066
|
}
|
|
3287
|
-
function
|
|
4067
|
+
function parseMemoryRow3(row) {
|
|
3288
4068
|
return {
|
|
3289
4069
|
id: row["id"],
|
|
3290
4070
|
key: row["key"],
|
|
@@ -3307,496 +4087,947 @@ function parseMemoryRow(row) {
|
|
|
3307
4087
|
created_at: row["created_at"],
|
|
3308
4088
|
updated_at: row["updated_at"],
|
|
3309
4089
|
accessed_at: row["accessed_at"] || null
|
|
3310
|
-
};
|
|
3311
|
-
}
|
|
3312
|
-
function createMemory(input, dedupeMode = "merge", db) {
|
|
3313
|
-
const d = db || getDatabase();
|
|
3314
|
-
const timestamp = now();
|
|
3315
|
-
let expiresAt = input.expires_at || null;
|
|
3316
|
-
if (input.ttl_ms && !expiresAt) {
|
|
3317
|
-
expiresAt = new Date(Date.now() + input.ttl_ms).toISOString();
|
|
3318
|
-
}
|
|
3319
|
-
const id = uuid();
|
|
3320
|
-
const tags = input.tags || [];
|
|
3321
|
-
const tagsJson = JSON.stringify(tags);
|
|
3322
|
-
const metadataJson = JSON.stringify(input.metadata || {});
|
|
3323
|
-
const safeValue = redactSecrets(input.value);
|
|
3324
|
-
const safeSummary = input.summary ? redactSecrets(input.summary) : null;
|
|
3325
|
-
if (dedupeMode === "merge") {
|
|
3326
|
-
const existing = d.query(`SELECT id, version FROM memories
|
|
3327
|
-
WHERE key = ? AND scope = ?
|
|
3328
|
-
AND COALESCE(agent_id, '') = ?
|
|
3329
|
-
AND COALESCE(project_id, '') = ?
|
|
3330
|
-
AND COALESCE(session_id, '') = ?`).get(input.key, input.scope || "private", input.agent_id || "", input.project_id || "", input.session_id || "");
|
|
3331
|
-
if (existing) {
|
|
3332
|
-
d.run(`UPDATE memories SET
|
|
3333
|
-
value = ?, category = ?, summary = ?, tags = ?,
|
|
3334
|
-
importance = ?, metadata = ?, expires_at = ?,
|
|
3335
|
-
pinned = COALESCE(pinned, 0),
|
|
3336
|
-
version = version + 1, updated_at = ?
|
|
3337
|
-
WHERE id = ?`, [
|
|
3338
|
-
safeValue,
|
|
3339
|
-
input.category || "knowledge",
|
|
3340
|
-
safeSummary,
|
|
3341
|
-
tagsJson,
|
|
3342
|
-
input.importance ?? 5,
|
|
3343
|
-
metadataJson,
|
|
3344
|
-
expiresAt,
|
|
3345
|
-
timestamp,
|
|
3346
|
-
existing.id
|
|
3347
|
-
]);
|
|
3348
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
3349
|
-
const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3350
|
-
for (const tag of tags) {
|
|
3351
|
-
insertTag2.run(existing.id, tag);
|
|
3352
|
-
}
|
|
3353
|
-
const merged = getMemory(existing.id, d);
|
|
3354
|
-
try {
|
|
3355
|
-
const oldLinks = getEntityMemoryLinks(undefined, merged.id, d);
|
|
3356
|
-
for (const link of oldLinks) {
|
|
3357
|
-
unlinkEntityFromMemory(link.entity_id, merged.id, d);
|
|
3358
|
-
}
|
|
3359
|
-
runEntityExtraction(merged, input.project_id, d);
|
|
3360
|
-
} catch {}
|
|
3361
|
-
return merged;
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
d.run(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, metadata, access_count, version, expires_at, created_at, updated_at)
|
|
3365
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, 0, 1, ?, ?, ?)`, [
|
|
3366
|
-
id,
|
|
3367
|
-
input.key,
|
|
3368
|
-
input.value,
|
|
3369
|
-
input.category || "knowledge",
|
|
3370
|
-
input.scope || "private",
|
|
3371
|
-
input.summary || null,
|
|
3372
|
-
tagsJson,
|
|
3373
|
-
input.importance ?? 5,
|
|
3374
|
-
input.source || "agent",
|
|
3375
|
-
input.agent_id || null,
|
|
3376
|
-
input.project_id || null,
|
|
3377
|
-
input.session_id || null,
|
|
3378
|
-
metadataJson,
|
|
3379
|
-
expiresAt,
|
|
3380
|
-
timestamp,
|
|
3381
|
-
timestamp
|
|
3382
|
-
]);
|
|
3383
|
-
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3384
|
-
for (const tag of tags) {
|
|
3385
|
-
insertTag.run(id, tag);
|
|
3386
|
-
}
|
|
3387
|
-
const memory = getMemory(id, d);
|
|
3388
|
-
try {
|
|
3389
|
-
runEntityExtraction(memory, input.project_id, d);
|
|
3390
|
-
} catch {}
|
|
3391
|
-
return memory;
|
|
3392
|
-
}
|
|
3393
|
-
function getMemory(id, db) {
|
|
3394
|
-
const d = db || getDatabase();
|
|
3395
|
-
const row = d.query("SELECT * FROM memories WHERE id = ?").get(id);
|
|
3396
|
-
if (!row)
|
|
3397
|
-
return null;
|
|
3398
|
-
return parseMemoryRow(row);
|
|
4090
|
+
};
|
|
3399
4091
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
params.push(agentId);
|
|
3411
|
-
}
|
|
3412
|
-
if (projectId) {
|
|
3413
|
-
sql += " AND project_id = ?";
|
|
3414
|
-
params.push(projectId);
|
|
3415
|
-
}
|
|
3416
|
-
if (sessionId) {
|
|
3417
|
-
sql += " AND session_id = ?";
|
|
3418
|
-
params.push(sessionId);
|
|
4092
|
+
var init_poll = __esm(() => {
|
|
4093
|
+
init_memories();
|
|
4094
|
+
init_database();
|
|
4095
|
+
});
|
|
4096
|
+
|
|
4097
|
+
// src/lib/providers/base.ts
|
|
4098
|
+
class BaseProvider {
|
|
4099
|
+
config;
|
|
4100
|
+
constructor(config) {
|
|
4101
|
+
this.config = config;
|
|
3419
4102
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
const d = db || getDatabase();
|
|
3428
|
-
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
3429
|
-
const params = [key];
|
|
3430
|
-
if (scope) {
|
|
3431
|
-
sql += " AND scope = ?";
|
|
3432
|
-
params.push(scope);
|
|
4103
|
+
parseJSON(raw) {
|
|
4104
|
+
try {
|
|
4105
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim();
|
|
4106
|
+
return JSON.parse(cleaned);
|
|
4107
|
+
} catch {
|
|
4108
|
+
return null;
|
|
4109
|
+
}
|
|
3433
4110
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
4111
|
+
clampImportance(value) {
|
|
4112
|
+
const n = Number(value);
|
|
4113
|
+
if (isNaN(n))
|
|
4114
|
+
return 5;
|
|
4115
|
+
return Math.max(0, Math.min(10, Math.round(n)));
|
|
3437
4116
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
4117
|
+
normaliseMemory(raw) {
|
|
4118
|
+
if (!raw || typeof raw !== "object")
|
|
4119
|
+
return null;
|
|
4120
|
+
const m = raw;
|
|
4121
|
+
if (typeof m.content !== "string" || !m.content.trim())
|
|
4122
|
+
return null;
|
|
4123
|
+
const validScopes = ["private", "shared", "global"];
|
|
4124
|
+
const validCategories = [
|
|
4125
|
+
"preference",
|
|
4126
|
+
"fact",
|
|
4127
|
+
"knowledge",
|
|
4128
|
+
"history"
|
|
4129
|
+
];
|
|
4130
|
+
return {
|
|
4131
|
+
content: m.content.trim(),
|
|
4132
|
+
category: validCategories.includes(m.category) ? m.category : "knowledge",
|
|
4133
|
+
importance: this.clampImportance(m.importance),
|
|
4134
|
+
tags: Array.isArray(m.tags) ? m.tags.filter((t) => typeof t === "string").map((t) => t.toLowerCase()) : [],
|
|
4135
|
+
suggestedScope: validScopes.includes(m.suggestedScope) ? m.suggestedScope : "shared",
|
|
4136
|
+
reasoning: typeof m.reasoning === "string" ? m.reasoning : undefined
|
|
4137
|
+
};
|
|
3441
4138
|
}
|
|
3442
|
-
sql += " AND status = 'active' ORDER BY importance DESC";
|
|
3443
|
-
const rows = d.query(sql).all(...params);
|
|
3444
|
-
return rows.map(parseMemoryRow);
|
|
3445
4139
|
}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
4140
|
+
var DEFAULT_AUTO_MEMORY_CONFIG, MEMORY_EXTRACTION_SYSTEM_PROMPT = `You are a precise memory extraction engine for an AI agent.
|
|
4141
|
+
Given text, extract facts worth remembering as structured JSON.
|
|
4142
|
+
Focus on: decisions made, preferences revealed, corrections, architectural choices, established facts, user preferences.
|
|
4143
|
+
Ignore: greetings, filler, questions without answers, temporary states.
|
|
4144
|
+
Output ONLY a JSON array \u2014 no markdown, no explanation.`, MEMORY_EXTRACTION_USER_TEMPLATE = (text, context) => `Extract memories from this text.
|
|
4145
|
+
${context.projectName ? `Project: ${context.projectName}` : ""}
|
|
4146
|
+
${context.existingMemoriesSummary ? `Existing memories (avoid duplicates):
|
|
4147
|
+
${context.existingMemoriesSummary}` : ""}
|
|
4148
|
+
|
|
4149
|
+
Text:
|
|
4150
|
+
${text}
|
|
4151
|
+
|
|
4152
|
+
Return a JSON array of objects with these exact fields:
|
|
4153
|
+
- content: string (the memory, concise and specific)
|
|
4154
|
+
- category: "preference" | "fact" | "knowledge" | "history"
|
|
4155
|
+
- importance: number 0-10 (10 = critical, 0 = trivial)
|
|
4156
|
+
- tags: string[] (lowercase keywords)
|
|
4157
|
+
- suggestedScope: "private" | "shared" | "global"
|
|
4158
|
+
- reasoning: string (one sentence why this is worth remembering)
|
|
4159
|
+
|
|
4160
|
+
Return [] if nothing is worth remembering.`, ENTITY_EXTRACTION_SYSTEM_PROMPT = `You are a knowledge graph entity extractor.
|
|
4161
|
+
Given text, identify named entities and their relationships.
|
|
4162
|
+
Output ONLY valid JSON \u2014 no markdown, no explanation.`, ENTITY_EXTRACTION_USER_TEMPLATE = (text) => `Extract entities and relations from this text.
|
|
4163
|
+
|
|
4164
|
+
Text: ${text}
|
|
4165
|
+
|
|
4166
|
+
Return JSON with this exact shape:
|
|
4167
|
+
{
|
|
4168
|
+
"entities": [
|
|
4169
|
+
{ "name": string, "type": "person"|"project"|"tool"|"concept"|"file"|"api"|"pattern"|"organization", "confidence": 0-1 }
|
|
4170
|
+
],
|
|
4171
|
+
"relations": [
|
|
4172
|
+
{ "from": string, "to": string, "type": "uses"|"knows"|"depends_on"|"created_by"|"related_to"|"contradicts"|"part_of"|"implements" }
|
|
4173
|
+
]
|
|
4174
|
+
}`;
|
|
4175
|
+
var init_base = __esm(() => {
|
|
4176
|
+
DEFAULT_AUTO_MEMORY_CONFIG = {
|
|
4177
|
+
provider: "anthropic",
|
|
4178
|
+
model: "claude-haiku-4-5",
|
|
4179
|
+
enabled: true,
|
|
4180
|
+
minImportance: 4,
|
|
4181
|
+
autoEntityLink: true,
|
|
4182
|
+
fallback: ["cerebras", "openai"]
|
|
4183
|
+
};
|
|
4184
|
+
});
|
|
4185
|
+
|
|
4186
|
+
// src/lib/providers/anthropic.ts
|
|
4187
|
+
var ANTHROPIC_MODELS, AnthropicProvider;
|
|
4188
|
+
var init_anthropic = __esm(() => {
|
|
4189
|
+
init_base();
|
|
4190
|
+
ANTHROPIC_MODELS = {
|
|
4191
|
+
default: "claude-haiku-4-5",
|
|
4192
|
+
premium: "claude-sonnet-4-5"
|
|
4193
|
+
};
|
|
4194
|
+
AnthropicProvider = class AnthropicProvider extends BaseProvider {
|
|
4195
|
+
name = "anthropic";
|
|
4196
|
+
baseUrl = "https://api.anthropic.com/v1";
|
|
4197
|
+
constructor(config) {
|
|
4198
|
+
const apiKey = config?.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
4199
|
+
super({
|
|
4200
|
+
apiKey,
|
|
4201
|
+
model: config?.model ?? ANTHROPIC_MODELS.default,
|
|
4202
|
+
maxTokens: config?.maxTokens ?? 1024,
|
|
4203
|
+
temperature: config?.temperature ?? 0,
|
|
4204
|
+
timeoutMs: config?.timeoutMs ?? 15000
|
|
4205
|
+
});
|
|
3459
4206
|
}
|
|
3460
|
-
|
|
3461
|
-
if (
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
4207
|
+
async extractMemories(text, context) {
|
|
4208
|
+
if (!this.config.apiKey)
|
|
4209
|
+
return [];
|
|
4210
|
+
try {
|
|
4211
|
+
const response = await this.callAPI(MEMORY_EXTRACTION_SYSTEM_PROMPT, MEMORY_EXTRACTION_USER_TEMPLATE(text, context));
|
|
4212
|
+
const parsed = this.parseJSON(response);
|
|
4213
|
+
if (!Array.isArray(parsed))
|
|
4214
|
+
return [];
|
|
4215
|
+
return parsed.map((item) => this.normaliseMemory(item)).filter((m) => m !== null);
|
|
4216
|
+
} catch (err) {
|
|
4217
|
+
console.error("[anthropic] extractMemories failed:", err);
|
|
4218
|
+
return [];
|
|
3467
4219
|
}
|
|
3468
4220
|
}
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
4221
|
+
async extractEntities(text) {
|
|
4222
|
+
const empty = { entities: [], relations: [] };
|
|
4223
|
+
if (!this.config.apiKey)
|
|
4224
|
+
return empty;
|
|
4225
|
+
try {
|
|
4226
|
+
const response = await this.callAPI(ENTITY_EXTRACTION_SYSTEM_PROMPT, ENTITY_EXTRACTION_USER_TEMPLATE(text));
|
|
4227
|
+
const parsed = this.parseJSON(response);
|
|
4228
|
+
if (!parsed || typeof parsed !== "object")
|
|
4229
|
+
return empty;
|
|
4230
|
+
return {
|
|
4231
|
+
entities: Array.isArray(parsed.entities) ? parsed.entities : [],
|
|
4232
|
+
relations: Array.isArray(parsed.relations) ? parsed.relations : []
|
|
4233
|
+
};
|
|
4234
|
+
} catch (err) {
|
|
4235
|
+
console.error("[anthropic] extractEntities failed:", err);
|
|
4236
|
+
return empty;
|
|
3476
4237
|
}
|
|
3477
4238
|
}
|
|
3478
|
-
|
|
3479
|
-
if (
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
4239
|
+
async scoreImportance(content, _context) {
|
|
4240
|
+
if (!this.config.apiKey)
|
|
4241
|
+
return 5;
|
|
4242
|
+
try {
|
|
4243
|
+
const response = await this.callAPI("You are an importance scorer. Return only a single integer 0-10. No explanation.", `How important is this memory for an AI agent to retain long-term?
|
|
4244
|
+
|
|
4245
|
+
"${content}"
|
|
4246
|
+
|
|
4247
|
+
Return only a number 0-10.`);
|
|
4248
|
+
return this.clampImportance(response.trim());
|
|
4249
|
+
} catch {
|
|
4250
|
+
return 5;
|
|
3485
4251
|
}
|
|
3486
|
-
} else {
|
|
3487
|
-
conditions.push("status = 'active'");
|
|
3488
4252
|
}
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
4253
|
+
async callAPI(systemPrompt, userMessage) {
|
|
4254
|
+
const controller = new AbortController;
|
|
4255
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 15000);
|
|
4256
|
+
try {
|
|
4257
|
+
const res = await fetch(`${this.baseUrl}/messages`, {
|
|
4258
|
+
method: "POST",
|
|
4259
|
+
headers: {
|
|
4260
|
+
"Content-Type": "application/json",
|
|
4261
|
+
"x-api-key": this.config.apiKey,
|
|
4262
|
+
"anthropic-version": "2023-06-01"
|
|
4263
|
+
},
|
|
4264
|
+
body: JSON.stringify({
|
|
4265
|
+
model: this.config.model,
|
|
4266
|
+
max_tokens: this.config.maxTokens ?? 1024,
|
|
4267
|
+
temperature: this.config.temperature ?? 0,
|
|
4268
|
+
system: systemPrompt,
|
|
4269
|
+
messages: [{ role: "user", content: userMessage }]
|
|
4270
|
+
}),
|
|
4271
|
+
signal: controller.signal
|
|
4272
|
+
});
|
|
4273
|
+
if (!res.ok) {
|
|
4274
|
+
const body = await res.text().catch(() => "");
|
|
4275
|
+
throw new Error(`Anthropic API ${res.status}: ${body.slice(0, 200)}`);
|
|
4276
|
+
}
|
|
4277
|
+
const data = await res.json();
|
|
4278
|
+
return data.content?.[0]?.text ?? "";
|
|
4279
|
+
} finally {
|
|
4280
|
+
clearTimeout(timeout);
|
|
4281
|
+
}
|
|
3492
4282
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
4283
|
+
};
|
|
4284
|
+
});
|
|
4285
|
+
|
|
4286
|
+
// src/lib/providers/openai-compat.ts
|
|
4287
|
+
var OpenAICompatProvider;
|
|
4288
|
+
var init_openai_compat = __esm(() => {
|
|
4289
|
+
init_base();
|
|
4290
|
+
OpenAICompatProvider = class OpenAICompatProvider extends BaseProvider {
|
|
4291
|
+
constructor(config) {
|
|
4292
|
+
super(config);
|
|
4293
|
+
}
|
|
4294
|
+
async extractMemories(text, context) {
|
|
4295
|
+
if (!this.config.apiKey)
|
|
4296
|
+
return [];
|
|
4297
|
+
try {
|
|
4298
|
+
const response = await this.callWithRetry(MEMORY_EXTRACTION_SYSTEM_PROMPT, MEMORY_EXTRACTION_USER_TEMPLATE(text, context));
|
|
4299
|
+
const parsed = this.parseJSON(response);
|
|
4300
|
+
if (!Array.isArray(parsed))
|
|
4301
|
+
return [];
|
|
4302
|
+
return parsed.map((item) => this.normaliseMemory(item)).filter((m) => m !== null);
|
|
4303
|
+
} catch (err) {
|
|
4304
|
+
console.error(`[${this.name}] extractMemories failed:`, err);
|
|
4305
|
+
return [];
|
|
4306
|
+
}
|
|
3496
4307
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
4308
|
+
async extractEntities(text) {
|
|
4309
|
+
const empty = { entities: [], relations: [] };
|
|
4310
|
+
if (!this.config.apiKey)
|
|
4311
|
+
return empty;
|
|
4312
|
+
try {
|
|
4313
|
+
const response = await this.callWithRetry(ENTITY_EXTRACTION_SYSTEM_PROMPT, ENTITY_EXTRACTION_USER_TEMPLATE(text));
|
|
4314
|
+
const parsed = this.parseJSON(response);
|
|
4315
|
+
if (!parsed || typeof parsed !== "object")
|
|
4316
|
+
return empty;
|
|
4317
|
+
return {
|
|
4318
|
+
entities: Array.isArray(parsed.entities) ? parsed.entities : [],
|
|
4319
|
+
relations: Array.isArray(parsed.relations) ? parsed.relations : []
|
|
4320
|
+
};
|
|
4321
|
+
} catch (err) {
|
|
4322
|
+
console.error(`[${this.name}] extractEntities failed:`, err);
|
|
4323
|
+
return empty;
|
|
4324
|
+
}
|
|
3500
4325
|
}
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
4326
|
+
async scoreImportance(content, _context) {
|
|
4327
|
+
if (!this.config.apiKey)
|
|
4328
|
+
return 5;
|
|
4329
|
+
try {
|
|
4330
|
+
const response = await this.callWithRetry("You are an importance scorer. Return only a single integer 0-10. No explanation.", `How important is this memory for an AI agent to retain long-term?
|
|
4331
|
+
|
|
4332
|
+
"${content}"
|
|
4333
|
+
|
|
4334
|
+
Return only a number 0-10.`);
|
|
4335
|
+
return this.clampImportance(response.trim());
|
|
4336
|
+
} catch {
|
|
4337
|
+
return 5;
|
|
4338
|
+
}
|
|
3504
4339
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
4340
|
+
async callWithRetry(systemPrompt, userMessage, retries = 3) {
|
|
4341
|
+
let lastError = null;
|
|
4342
|
+
for (let attempt = 0;attempt < retries; attempt++) {
|
|
4343
|
+
try {
|
|
4344
|
+
return await this.callAPI(systemPrompt, userMessage);
|
|
4345
|
+
} catch (err) {
|
|
4346
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
4347
|
+
const isRateLimit = lastError.message.includes("429") || lastError.message.toLowerCase().includes("rate limit");
|
|
4348
|
+
if (!isRateLimit || attempt === retries - 1)
|
|
4349
|
+
throw lastError;
|
|
4350
|
+
await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
throw lastError ?? new Error("Unknown error");
|
|
3508
4354
|
}
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
4355
|
+
async callAPI(systemPrompt, userMessage) {
|
|
4356
|
+
const controller = new AbortController;
|
|
4357
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 15000);
|
|
4358
|
+
try {
|
|
4359
|
+
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
4360
|
+
method: "POST",
|
|
4361
|
+
headers: {
|
|
4362
|
+
"Content-Type": "application/json",
|
|
4363
|
+
[this.authHeader]: `Bearer ${this.config.apiKey}`
|
|
4364
|
+
},
|
|
4365
|
+
body: JSON.stringify({
|
|
4366
|
+
model: this.config.model,
|
|
4367
|
+
max_tokens: this.config.maxTokens ?? 1024,
|
|
4368
|
+
temperature: this.config.temperature ?? 0,
|
|
4369
|
+
messages: [
|
|
4370
|
+
{ role: "system", content: systemPrompt },
|
|
4371
|
+
{ role: "user", content: userMessage }
|
|
4372
|
+
]
|
|
4373
|
+
}),
|
|
4374
|
+
signal: controller.signal
|
|
4375
|
+
});
|
|
4376
|
+
if (!res.ok) {
|
|
4377
|
+
const body = await res.text().catch(() => "");
|
|
4378
|
+
throw new Error(`${this.name} API ${res.status}: ${body.slice(0, 200)}`);
|
|
4379
|
+
}
|
|
4380
|
+
const data = await res.json();
|
|
4381
|
+
return data.choices?.[0]?.message?.content ?? "";
|
|
4382
|
+
} finally {
|
|
4383
|
+
clearTimeout(timeout);
|
|
3513
4384
|
}
|
|
3514
4385
|
}
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
4386
|
+
};
|
|
4387
|
+
});
|
|
4388
|
+
|
|
4389
|
+
// src/lib/providers/openai.ts
|
|
4390
|
+
var OPENAI_MODELS, OpenAIProvider;
|
|
4391
|
+
var init_openai = __esm(() => {
|
|
4392
|
+
init_openai_compat();
|
|
4393
|
+
OPENAI_MODELS = {
|
|
4394
|
+
default: "gpt-4.1-nano",
|
|
4395
|
+
mini: "gpt-4.1-mini",
|
|
4396
|
+
full: "gpt-4.1"
|
|
4397
|
+
};
|
|
4398
|
+
OpenAIProvider = class OpenAIProvider extends OpenAICompatProvider {
|
|
4399
|
+
name = "openai";
|
|
4400
|
+
baseUrl = "https://api.openai.com/v1";
|
|
4401
|
+
authHeader = "Authorization";
|
|
4402
|
+
constructor(config) {
|
|
4403
|
+
super({
|
|
4404
|
+
apiKey: config?.apiKey ?? process.env.OPENAI_API_KEY ?? "",
|
|
4405
|
+
model: config?.model ?? OPENAI_MODELS.default,
|
|
4406
|
+
maxTokens: config?.maxTokens ?? 1024,
|
|
4407
|
+
temperature: config?.temperature ?? 0,
|
|
4408
|
+
timeoutMs: config?.timeoutMs ?? 15000
|
|
4409
|
+
});
|
|
3519
4410
|
}
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
4411
|
+
};
|
|
4412
|
+
});
|
|
4413
|
+
|
|
4414
|
+
// src/lib/providers/cerebras.ts
|
|
4415
|
+
var CEREBRAS_MODELS, CerebrasProvider;
|
|
4416
|
+
var init_cerebras = __esm(() => {
|
|
4417
|
+
init_openai_compat();
|
|
4418
|
+
CEREBRAS_MODELS = {
|
|
4419
|
+
default: "llama-3.3-70b",
|
|
4420
|
+
fast: "llama3.1-8b"
|
|
4421
|
+
};
|
|
4422
|
+
CerebrasProvider = class CerebrasProvider extends OpenAICompatProvider {
|
|
4423
|
+
name = "cerebras";
|
|
4424
|
+
baseUrl = "https://api.cerebras.ai/v1";
|
|
4425
|
+
authHeader = "Authorization";
|
|
4426
|
+
constructor(config) {
|
|
4427
|
+
super({
|
|
4428
|
+
apiKey: config?.apiKey ?? process.env.CEREBRAS_API_KEY ?? "",
|
|
4429
|
+
model: config?.model ?? CEREBRAS_MODELS.default,
|
|
4430
|
+
maxTokens: config?.maxTokens ?? 1024,
|
|
4431
|
+
temperature: config?.temperature ?? 0,
|
|
4432
|
+
timeoutMs: config?.timeoutMs ?? 1e4
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
};
|
|
4436
|
+
});
|
|
4437
|
+
|
|
4438
|
+
// src/lib/providers/grok.ts
|
|
4439
|
+
var GROK_MODELS, GrokProvider;
|
|
4440
|
+
var init_grok = __esm(() => {
|
|
4441
|
+
init_openai_compat();
|
|
4442
|
+
GROK_MODELS = {
|
|
4443
|
+
default: "grok-3-mini",
|
|
4444
|
+
premium: "grok-3"
|
|
4445
|
+
};
|
|
4446
|
+
GrokProvider = class GrokProvider extends OpenAICompatProvider {
|
|
4447
|
+
name = "grok";
|
|
4448
|
+
baseUrl = "https://api.x.ai/v1";
|
|
4449
|
+
authHeader = "Authorization";
|
|
4450
|
+
constructor(config) {
|
|
4451
|
+
super({
|
|
4452
|
+
apiKey: config?.apiKey ?? process.env.XAI_API_KEY ?? "",
|
|
4453
|
+
model: config?.model ?? GROK_MODELS.default,
|
|
4454
|
+
maxTokens: config?.maxTokens ?? 1024,
|
|
4455
|
+
temperature: config?.temperature ?? 0,
|
|
4456
|
+
timeoutMs: config?.timeoutMs ?? 15000
|
|
4457
|
+
});
|
|
4458
|
+
}
|
|
4459
|
+
};
|
|
4460
|
+
});
|
|
4461
|
+
|
|
4462
|
+
// src/lib/providers/registry.ts
|
|
4463
|
+
var exports_registry = {};
|
|
4464
|
+
__export(exports_registry, {
|
|
4465
|
+
providerRegistry: () => providerRegistry
|
|
4466
|
+
});
|
|
4467
|
+
|
|
4468
|
+
class ProviderRegistry {
|
|
4469
|
+
config = { ...DEFAULT_AUTO_MEMORY_CONFIG };
|
|
4470
|
+
_instances = new Map;
|
|
4471
|
+
configure(partial) {
|
|
4472
|
+
this.config = { ...this.config, ...partial };
|
|
4473
|
+
this._instances.clear();
|
|
4474
|
+
}
|
|
4475
|
+
getConfig() {
|
|
4476
|
+
return this.config;
|
|
4477
|
+
}
|
|
4478
|
+
getPrimary() {
|
|
4479
|
+
return this.getProvider(this.config.provider);
|
|
4480
|
+
}
|
|
4481
|
+
getFallbacks() {
|
|
4482
|
+
const fallbackNames = this.config.fallback ?? [];
|
|
4483
|
+
return fallbackNames.filter((n) => n !== this.config.provider).map((n) => this.getProvider(n)).filter((p) => p !== null);
|
|
4484
|
+
}
|
|
4485
|
+
getAvailable() {
|
|
4486
|
+
const primary = this.getPrimary();
|
|
4487
|
+
if (primary)
|
|
4488
|
+
return primary;
|
|
4489
|
+
const fallbacks = this.getFallbacks();
|
|
4490
|
+
return fallbacks[0] ?? null;
|
|
4491
|
+
}
|
|
4492
|
+
getProvider(name) {
|
|
4493
|
+
const cached = this._instances.get(name);
|
|
4494
|
+
if (cached)
|
|
4495
|
+
return cached;
|
|
4496
|
+
const provider = this.createProvider(name);
|
|
4497
|
+
if (!provider)
|
|
4498
|
+
return null;
|
|
4499
|
+
if (!provider.config.apiKey)
|
|
4500
|
+
return null;
|
|
4501
|
+
this._instances.set(name, provider);
|
|
4502
|
+
return provider;
|
|
4503
|
+
}
|
|
4504
|
+
health() {
|
|
4505
|
+
const providers = ["anthropic", "openai", "cerebras", "grok"];
|
|
4506
|
+
const result = {};
|
|
4507
|
+
for (const name of providers) {
|
|
4508
|
+
const p = this.createProvider(name);
|
|
4509
|
+
result[name] = {
|
|
4510
|
+
available: Boolean(p?.config.apiKey),
|
|
4511
|
+
model: p?.config.model ?? "unknown"
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4514
|
+
return result;
|
|
4515
|
+
}
|
|
4516
|
+
createProvider(name) {
|
|
4517
|
+
const modelOverride = name === this.config.provider ? this.config.model : undefined;
|
|
4518
|
+
switch (name) {
|
|
4519
|
+
case "anthropic":
|
|
4520
|
+
return new AnthropicProvider(modelOverride ? { model: modelOverride } : undefined);
|
|
4521
|
+
case "openai":
|
|
4522
|
+
return new OpenAIProvider(modelOverride ? { model: modelOverride } : undefined);
|
|
4523
|
+
case "cerebras":
|
|
4524
|
+
return new CerebrasProvider(modelOverride ? { model: modelOverride } : undefined);
|
|
4525
|
+
case "grok":
|
|
4526
|
+
return new GrokProvider(modelOverride ? { model: modelOverride } : undefined);
|
|
4527
|
+
default:
|
|
4528
|
+
return null;
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
function autoConfigureFromEnv() {
|
|
4533
|
+
const hasAnthropicKey = Boolean(process.env.ANTHROPIC_API_KEY);
|
|
4534
|
+
const hasCerebrasKey = Boolean(process.env.CEREBRAS_API_KEY);
|
|
4535
|
+
const hasOpenAIKey = Boolean(process.env.OPENAI_API_KEY);
|
|
4536
|
+
const hasGrokKey = Boolean(process.env.XAI_API_KEY);
|
|
4537
|
+
if (!hasAnthropicKey) {
|
|
4538
|
+
if (hasCerebrasKey) {
|
|
4539
|
+
providerRegistry.configure({ provider: "cerebras" });
|
|
4540
|
+
} else if (hasOpenAIKey) {
|
|
4541
|
+
providerRegistry.configure({ provider: "openai" });
|
|
4542
|
+
} else if (hasGrokKey) {
|
|
4543
|
+
providerRegistry.configure({ provider: "grok" });
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
const allProviders = ["anthropic", "cerebras", "openai", "grok"];
|
|
4547
|
+
const available = allProviders.filter((p) => {
|
|
4548
|
+
switch (p) {
|
|
4549
|
+
case "anthropic":
|
|
4550
|
+
return hasAnthropicKey;
|
|
4551
|
+
case "cerebras":
|
|
4552
|
+
return hasCerebrasKey;
|
|
4553
|
+
case "openai":
|
|
4554
|
+
return hasOpenAIKey;
|
|
4555
|
+
case "grok":
|
|
4556
|
+
return hasGrokKey;
|
|
4557
|
+
}
|
|
4558
|
+
});
|
|
4559
|
+
const primary = providerRegistry.getConfig().provider;
|
|
4560
|
+
const fallback = available.filter((p) => p !== primary);
|
|
4561
|
+
providerRegistry.configure({ fallback });
|
|
4562
|
+
}
|
|
4563
|
+
var providerRegistry;
|
|
4564
|
+
var init_registry = __esm(() => {
|
|
4565
|
+
init_anthropic();
|
|
4566
|
+
init_openai();
|
|
4567
|
+
init_cerebras();
|
|
4568
|
+
init_grok();
|
|
4569
|
+
init_base();
|
|
4570
|
+
providerRegistry = new ProviderRegistry;
|
|
4571
|
+
autoConfigureFromEnv();
|
|
4572
|
+
});
|
|
4573
|
+
|
|
4574
|
+
// src/lib/auto-memory-queue.ts
|
|
4575
|
+
class AutoMemoryQueue {
|
|
4576
|
+
queue = [];
|
|
4577
|
+
handler = null;
|
|
4578
|
+
running = false;
|
|
4579
|
+
activeCount = 0;
|
|
4580
|
+
stats = {
|
|
4581
|
+
pending: 0,
|
|
4582
|
+
processing: 0,
|
|
4583
|
+
processed: 0,
|
|
4584
|
+
failed: 0,
|
|
4585
|
+
dropped: 0
|
|
4586
|
+
};
|
|
4587
|
+
setHandler(handler) {
|
|
4588
|
+
this.handler = handler;
|
|
4589
|
+
if (!this.running)
|
|
4590
|
+
this.startLoop();
|
|
4591
|
+
}
|
|
4592
|
+
enqueue(job) {
|
|
4593
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
4594
|
+
this.queue.shift();
|
|
4595
|
+
this.stats.dropped++;
|
|
4596
|
+
this.stats.pending = Math.max(0, this.stats.pending - 1);
|
|
4597
|
+
}
|
|
4598
|
+
this.queue.push(job);
|
|
4599
|
+
this.stats.pending++;
|
|
4600
|
+
if (!this.running && this.handler)
|
|
4601
|
+
this.startLoop();
|
|
4602
|
+
}
|
|
4603
|
+
getStats() {
|
|
4604
|
+
return { ...this.stats, pending: this.queue.length };
|
|
4605
|
+
}
|
|
4606
|
+
startLoop() {
|
|
4607
|
+
this.running = true;
|
|
4608
|
+
this.loop();
|
|
4609
|
+
}
|
|
4610
|
+
async loop() {
|
|
4611
|
+
while (this.queue.length > 0 || this.activeCount > 0) {
|
|
4612
|
+
while (this.queue.length > 0 && this.activeCount < CONCURRENCY) {
|
|
4613
|
+
const job = this.queue.shift();
|
|
4614
|
+
if (!job)
|
|
4615
|
+
break;
|
|
4616
|
+
this.stats.pending = Math.max(0, this.stats.pending - 1);
|
|
4617
|
+
this.activeCount++;
|
|
4618
|
+
this.stats.processing = this.activeCount;
|
|
4619
|
+
this.processJob(job);
|
|
4620
|
+
}
|
|
4621
|
+
await new Promise((r) => setImmediate(r));
|
|
4622
|
+
}
|
|
4623
|
+
this.running = false;
|
|
3585
4624
|
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
4625
|
+
async processJob(job) {
|
|
4626
|
+
if (!this.handler) {
|
|
4627
|
+
this.activeCount--;
|
|
4628
|
+
this.stats.processing = this.activeCount;
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4631
|
+
try {
|
|
4632
|
+
await this.handler(job);
|
|
4633
|
+
this.stats.processed++;
|
|
4634
|
+
} catch (err) {
|
|
4635
|
+
this.stats.failed++;
|
|
4636
|
+
console.error("[auto-memory-queue] job failed:", err);
|
|
4637
|
+
} finally {
|
|
4638
|
+
this.activeCount--;
|
|
4639
|
+
this.stats.processing = this.activeCount;
|
|
4640
|
+
}
|
|
3589
4641
|
}
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
4642
|
+
}
|
|
4643
|
+
var MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue;
|
|
4644
|
+
var init_auto_memory_queue = __esm(() => {
|
|
4645
|
+
autoMemoryQueue = new AutoMemoryQueue;
|
|
4646
|
+
});
|
|
4647
|
+
|
|
4648
|
+
// src/lib/auto-memory.ts
|
|
4649
|
+
var exports_auto_memory = {};
|
|
4650
|
+
__export(exports_auto_memory, {
|
|
4651
|
+
processConversationTurn: () => processConversationTurn,
|
|
4652
|
+
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
4653
|
+
configureAutoMemory: () => configureAutoMemory
|
|
4654
|
+
});
|
|
4655
|
+
function isDuplicate(content, agentId, projectId) {
|
|
4656
|
+
try {
|
|
4657
|
+
const query = content.split(/\s+/).filter((w) => w.length > 3).slice(0, 10).join(" ");
|
|
4658
|
+
if (!query)
|
|
4659
|
+
return false;
|
|
4660
|
+
const results = searchMemories(query, {
|
|
4661
|
+
agent_id: agentId,
|
|
4662
|
+
project_id: projectId,
|
|
4663
|
+
limit: 3
|
|
4664
|
+
});
|
|
4665
|
+
if (results.length === 0)
|
|
4666
|
+
return false;
|
|
4667
|
+
const contentWords = new Set(content.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
|
|
4668
|
+
for (const result of results) {
|
|
4669
|
+
const existingWords = new Set(result.memory.value.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
|
|
4670
|
+
if (contentWords.size === 0 || existingWords.size === 0)
|
|
4671
|
+
continue;
|
|
4672
|
+
const intersection = [...contentWords].filter((w) => existingWords.has(w)).length;
|
|
4673
|
+
const union = new Set([...contentWords, ...existingWords]).size;
|
|
4674
|
+
const similarity = intersection / union;
|
|
4675
|
+
if (similarity >= DEDUP_SIMILARITY_THRESHOLD)
|
|
4676
|
+
return true;
|
|
4677
|
+
}
|
|
4678
|
+
return false;
|
|
4679
|
+
} catch {
|
|
4680
|
+
return false;
|
|
3593
4681
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
4682
|
+
}
|
|
4683
|
+
async function linkEntitiesToMemory(memoryId, content, _agentId, projectId) {
|
|
4684
|
+
const provider = providerRegistry.getAvailable();
|
|
4685
|
+
if (!provider)
|
|
4686
|
+
return;
|
|
4687
|
+
try {
|
|
4688
|
+
const { entities, relations } = await provider.extractEntities(content);
|
|
4689
|
+
const entityIdMap = new Map;
|
|
4690
|
+
for (const extracted of entities) {
|
|
4691
|
+
if (extracted.confidence < 0.6)
|
|
4692
|
+
continue;
|
|
4693
|
+
try {
|
|
4694
|
+
const existing = getEntityByName(extracted.name);
|
|
4695
|
+
const entityId = existing ? existing.id : createEntity({
|
|
4696
|
+
name: extracted.name,
|
|
4697
|
+
type: extracted.type,
|
|
4698
|
+
project_id: projectId
|
|
4699
|
+
}).id;
|
|
4700
|
+
entityIdMap.set(extracted.name, entityId);
|
|
4701
|
+
linkEntityToMemory(entityId, memoryId, "subject");
|
|
4702
|
+
} catch {}
|
|
4703
|
+
}
|
|
4704
|
+
for (const rel of relations) {
|
|
4705
|
+
const fromId = entityIdMap.get(rel.from);
|
|
4706
|
+
const toId = entityIdMap.get(rel.to);
|
|
4707
|
+
if (!fromId || !toId)
|
|
4708
|
+
continue;
|
|
4709
|
+
try {
|
|
4710
|
+
createRelation({
|
|
4711
|
+
source_entity_id: fromId,
|
|
4712
|
+
target_entity_id: toId,
|
|
4713
|
+
relation_type: rel.type
|
|
4714
|
+
});
|
|
4715
|
+
} catch {}
|
|
4716
|
+
}
|
|
4717
|
+
} catch (err) {
|
|
4718
|
+
console.error("[auto-memory] entity linking failed:", err);
|
|
3597
4719
|
}
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
4720
|
+
}
|
|
4721
|
+
async function saveExtractedMemory(extracted, context) {
|
|
4722
|
+
const minImportance = providerRegistry.getConfig().minImportance;
|
|
4723
|
+
if (extracted.importance < minImportance)
|
|
4724
|
+
return null;
|
|
4725
|
+
if (!extracted.content.trim())
|
|
4726
|
+
return null;
|
|
4727
|
+
if (isDuplicate(extracted.content, context.agentId, context.projectId)) {
|
|
4728
|
+
return null;
|
|
3601
4729
|
}
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
4730
|
+
try {
|
|
4731
|
+
const input = {
|
|
4732
|
+
key: extracted.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
|
|
4733
|
+
value: extracted.content,
|
|
4734
|
+
category: extracted.category,
|
|
4735
|
+
scope: extracted.suggestedScope,
|
|
4736
|
+
importance: extracted.importance,
|
|
4737
|
+
tags: [
|
|
4738
|
+
...extracted.tags,
|
|
4739
|
+
"auto-extracted",
|
|
4740
|
+
...context.sessionId ? [`session:${context.sessionId}`] : []
|
|
4741
|
+
],
|
|
4742
|
+
agent_id: context.agentId,
|
|
4743
|
+
project_id: context.projectId,
|
|
4744
|
+
session_id: context.sessionId,
|
|
4745
|
+
metadata: {
|
|
4746
|
+
reasoning: extracted.reasoning,
|
|
4747
|
+
auto_extracted: true,
|
|
4748
|
+
extracted_at: new Date().toISOString()
|
|
4749
|
+
}
|
|
4750
|
+
};
|
|
4751
|
+
const memory = createMemory(input, "merge");
|
|
4752
|
+
return memory.id;
|
|
4753
|
+
} catch (err) {
|
|
4754
|
+
console.error("[auto-memory] saveExtractedMemory failed:", err);
|
|
4755
|
+
return null;
|
|
3610
4756
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
4757
|
+
}
|
|
4758
|
+
async function processJob(job) {
|
|
4759
|
+
if (!providerRegistry.getConfig().enabled)
|
|
4760
|
+
return;
|
|
4761
|
+
const provider = providerRegistry.getAvailable();
|
|
4762
|
+
if (!provider)
|
|
4763
|
+
return;
|
|
4764
|
+
const context = {
|
|
4765
|
+
agentId: job.agentId,
|
|
4766
|
+
projectId: job.projectId,
|
|
4767
|
+
sessionId: job.sessionId
|
|
4768
|
+
};
|
|
4769
|
+
let extracted = [];
|
|
3614
4770
|
try {
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
4771
|
+
extracted = await provider.extractMemories(job.turn, context);
|
|
4772
|
+
} catch {
|
|
4773
|
+
const fallbacks = providerRegistry.getFallbacks();
|
|
4774
|
+
for (const fallback of fallbacks) {
|
|
4775
|
+
try {
|
|
4776
|
+
extracted = await fallback.extractMemories(job.turn, context);
|
|
4777
|
+
if (extracted.length > 0)
|
|
4778
|
+
break;
|
|
4779
|
+
} catch {
|
|
4780
|
+
continue;
|
|
3619
4781
|
}
|
|
3620
|
-
runEntityExtraction(updated, existing.project_id || undefined, d);
|
|
3621
4782
|
}
|
|
3622
|
-
}
|
|
3623
|
-
|
|
4783
|
+
}
|
|
4784
|
+
if (extracted.length === 0)
|
|
4785
|
+
return;
|
|
4786
|
+
for (const memory of extracted) {
|
|
4787
|
+
const memoryId = await saveExtractedMemory(memory, context);
|
|
4788
|
+
if (!memoryId)
|
|
4789
|
+
continue;
|
|
4790
|
+
if (providerRegistry.getConfig().autoEntityLink) {
|
|
4791
|
+
linkEntitiesToMemory(memoryId, memory.content, job.agentId, job.projectId);
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
3624
4794
|
}
|
|
3625
|
-
function
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
4795
|
+
function processConversationTurn(turn, context, source = "turn") {
|
|
4796
|
+
if (!turn?.trim())
|
|
4797
|
+
return;
|
|
4798
|
+
autoMemoryQueue.enqueue({
|
|
4799
|
+
...context,
|
|
4800
|
+
turn,
|
|
4801
|
+
timestamp: Date.now(),
|
|
4802
|
+
source
|
|
4803
|
+
});
|
|
3629
4804
|
}
|
|
3630
|
-
function
|
|
4805
|
+
function getAutoMemoryStats() {
|
|
4806
|
+
return autoMemoryQueue.getStats();
|
|
4807
|
+
}
|
|
4808
|
+
function configureAutoMemory(config) {
|
|
4809
|
+
providerRegistry.configure(config);
|
|
4810
|
+
}
|
|
4811
|
+
var DEDUP_SIMILARITY_THRESHOLD = 0.85;
|
|
4812
|
+
var init_auto_memory = __esm(() => {
|
|
4813
|
+
init_memories();
|
|
4814
|
+
init_search();
|
|
4815
|
+
init_entities();
|
|
4816
|
+
init_relations();
|
|
4817
|
+
init_entity_memories();
|
|
4818
|
+
init_registry();
|
|
4819
|
+
init_auto_memory_queue();
|
|
4820
|
+
autoMemoryQueue.setHandler(processJob);
|
|
4821
|
+
});
|
|
4822
|
+
|
|
4823
|
+
// src/db/webhook_hooks.ts
|
|
4824
|
+
var exports_webhook_hooks = {};
|
|
4825
|
+
__export(exports_webhook_hooks, {
|
|
4826
|
+
updateWebhookHook: () => updateWebhookHook,
|
|
4827
|
+
recordWebhookInvocation: () => recordWebhookInvocation,
|
|
4828
|
+
listWebhookHooks: () => listWebhookHooks,
|
|
4829
|
+
getWebhookHook: () => getWebhookHook,
|
|
4830
|
+
deleteWebhookHook: () => deleteWebhookHook,
|
|
4831
|
+
createWebhookHook: () => createWebhookHook
|
|
4832
|
+
});
|
|
4833
|
+
function parseRow(row) {
|
|
4834
|
+
return {
|
|
4835
|
+
id: row["id"],
|
|
4836
|
+
type: row["type"],
|
|
4837
|
+
handlerUrl: row["handler_url"],
|
|
4838
|
+
priority: row["priority"],
|
|
4839
|
+
blocking: Boolean(row["blocking"]),
|
|
4840
|
+
agentId: row["agent_id"] || undefined,
|
|
4841
|
+
projectId: row["project_id"] || undefined,
|
|
4842
|
+
description: row["description"] || undefined,
|
|
4843
|
+
enabled: Boolean(row["enabled"]),
|
|
4844
|
+
createdAt: row["created_at"],
|
|
4845
|
+
invocationCount: row["invocation_count"],
|
|
4846
|
+
failureCount: row["failure_count"]
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
function createWebhookHook(input, db) {
|
|
3631
4850
|
const d = db || getDatabase();
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
4851
|
+
const id = shortUuid();
|
|
4852
|
+
const timestamp = now();
|
|
4853
|
+
d.run(`INSERT INTO webhook_hooks
|
|
4854
|
+
(id, type, handler_url, priority, blocking, agent_id, project_id, description, enabled, created_at, invocation_count, failure_count)
|
|
4855
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, 0, 0)`, [
|
|
4856
|
+
id,
|
|
4857
|
+
input.type,
|
|
4858
|
+
input.handlerUrl,
|
|
4859
|
+
input.priority ?? 50,
|
|
4860
|
+
input.blocking ? 1 : 0,
|
|
4861
|
+
input.agentId ?? null,
|
|
4862
|
+
input.projectId ?? null,
|
|
4863
|
+
input.description ?? null,
|
|
4864
|
+
timestamp
|
|
4865
|
+
]);
|
|
4866
|
+
return getWebhookHook(id, d);
|
|
3641
4867
|
}
|
|
3642
|
-
function
|
|
4868
|
+
function getWebhookHook(id, db) {
|
|
3643
4869
|
const d = db || getDatabase();
|
|
3644
|
-
d.
|
|
4870
|
+
const row = d.query("SELECT * FROM webhook_hooks WHERE id = ?").get(id);
|
|
4871
|
+
return row ? parseRow(row) : null;
|
|
3645
4872
|
}
|
|
3646
|
-
function
|
|
4873
|
+
function listWebhookHooks(filter = {}, db) {
|
|
3647
4874
|
const d = db || getDatabase();
|
|
3648
|
-
const
|
|
3649
|
-
const
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
4875
|
+
const conditions = [];
|
|
4876
|
+
const params = [];
|
|
4877
|
+
if (filter.type) {
|
|
4878
|
+
conditions.push("type = ?");
|
|
4879
|
+
params.push(filter.type);
|
|
3653
4880
|
}
|
|
3654
|
-
|
|
4881
|
+
if (filter.enabled !== undefined) {
|
|
4882
|
+
conditions.push("enabled = ?");
|
|
4883
|
+
params.push(filter.enabled ? 1 : 0);
|
|
4884
|
+
}
|
|
4885
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
4886
|
+
const rows = d.query(`SELECT * FROM webhook_hooks ${where} ORDER BY priority ASC, created_at ASC`).all(...params);
|
|
4887
|
+
return rows.map(parseRow);
|
|
3655
4888
|
}
|
|
3656
|
-
function
|
|
4889
|
+
function updateWebhookHook(id, updates, db) {
|
|
3657
4890
|
const d = db || getDatabase();
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
return
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
} catch {
|
|
3675
|
-
return [];
|
|
4891
|
+
const existing = getWebhookHook(id, d);
|
|
4892
|
+
if (!existing)
|
|
4893
|
+
return null;
|
|
4894
|
+
const sets = [];
|
|
4895
|
+
const params = [];
|
|
4896
|
+
if (updates.enabled !== undefined) {
|
|
4897
|
+
sets.push("enabled = ?");
|
|
4898
|
+
params.push(updates.enabled ? 1 : 0);
|
|
4899
|
+
}
|
|
4900
|
+
if (updates.description !== undefined) {
|
|
4901
|
+
sets.push("description = ?");
|
|
4902
|
+
params.push(updates.description);
|
|
4903
|
+
}
|
|
4904
|
+
if (updates.priority !== undefined) {
|
|
4905
|
+
sets.push("priority = ?");
|
|
4906
|
+
params.push(updates.priority);
|
|
3676
4907
|
}
|
|
4908
|
+
if (sets.length > 0) {
|
|
4909
|
+
params.push(id);
|
|
4910
|
+
d.run(`UPDATE webhook_hooks SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
4911
|
+
}
|
|
4912
|
+
return getWebhookHook(id, d);
|
|
3677
4913
|
}
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
}
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
});
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
conditions.push("(updated_at > ? OR created_at > ?)");
|
|
3725
|
-
params.push(lastSeen, lastSeen);
|
|
3726
|
-
}
|
|
3727
|
-
if (opts.scope) {
|
|
3728
|
-
conditions.push("scope = ?");
|
|
3729
|
-
params.push(opts.scope);
|
|
3730
|
-
}
|
|
3731
|
-
if (opts.category) {
|
|
3732
|
-
conditions.push("category = ?");
|
|
3733
|
-
params.push(opts.category);
|
|
3734
|
-
}
|
|
3735
|
-
if (opts.agent_id) {
|
|
3736
|
-
conditions.push("agent_id = ?");
|
|
3737
|
-
params.push(opts.agent_id);
|
|
3738
|
-
}
|
|
3739
|
-
if (opts.project_id) {
|
|
3740
|
-
conditions.push("project_id = ?");
|
|
3741
|
-
params.push(opts.project_id);
|
|
3742
|
-
}
|
|
3743
|
-
const sql = `SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY updated_at ASC`;
|
|
3744
|
-
const rows = db.query(sql).all(...params);
|
|
3745
|
-
if (rows.length > 0) {
|
|
3746
|
-
const memories = rows.map(parseMemoryRow3);
|
|
3747
|
-
const lastRow = memories[memories.length - 1];
|
|
3748
|
-
if (lastRow) {
|
|
3749
|
-
lastSeen = lastRow.updated_at;
|
|
3750
|
-
}
|
|
3751
|
-
try {
|
|
3752
|
-
opts.on_memories(memories);
|
|
3753
|
-
} catch (err) {
|
|
3754
|
-
opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
|
|
3755
|
-
}
|
|
3756
|
-
}
|
|
3757
|
-
} catch (err) {
|
|
3758
|
-
opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
|
|
3759
|
-
} finally {
|
|
3760
|
-
inFlight = false;
|
|
4914
|
+
function deleteWebhookHook(id, db) {
|
|
4915
|
+
const d = db || getDatabase();
|
|
4916
|
+
const result = d.run("DELETE FROM webhook_hooks WHERE id = ?", [id]);
|
|
4917
|
+
return result.changes > 0;
|
|
4918
|
+
}
|
|
4919
|
+
function recordWebhookInvocation(id, success, db) {
|
|
4920
|
+
const d = db || getDatabase();
|
|
4921
|
+
if (success) {
|
|
4922
|
+
d.run("UPDATE webhook_hooks SET invocation_count = invocation_count + 1 WHERE id = ?", [id]);
|
|
4923
|
+
} else {
|
|
4924
|
+
d.run("UPDATE webhook_hooks SET invocation_count = invocation_count + 1, failure_count = failure_count + 1 WHERE id = ?", [id]);
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
var init_webhook_hooks = __esm(() => {
|
|
4928
|
+
init_database();
|
|
4929
|
+
});
|
|
4930
|
+
|
|
4931
|
+
// src/lib/built-in-hooks.ts
|
|
4932
|
+
var exports_built_in_hooks = {};
|
|
4933
|
+
__export(exports_built_in_hooks, {
|
|
4934
|
+
reloadWebhooks: () => reloadWebhooks,
|
|
4935
|
+
loadWebhooksFromDb: () => loadWebhooksFromDb
|
|
4936
|
+
});
|
|
4937
|
+
async function getAutoMemory() {
|
|
4938
|
+
if (!_processConversationTurn) {
|
|
4939
|
+
const mod = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
4940
|
+
_processConversationTurn = mod.processConversationTurn;
|
|
4941
|
+
}
|
|
4942
|
+
return _processConversationTurn;
|
|
4943
|
+
}
|
|
4944
|
+
function loadWebhooksFromDb() {
|
|
4945
|
+
if (_webhooksLoaded)
|
|
4946
|
+
return;
|
|
4947
|
+
_webhooksLoaded = true;
|
|
4948
|
+
try {
|
|
4949
|
+
const webhooks = listWebhookHooks({ enabled: true });
|
|
4950
|
+
for (const wh of webhooks) {
|
|
4951
|
+
hookRegistry.register({
|
|
4952
|
+
type: wh.type,
|
|
4953
|
+
blocking: wh.blocking,
|
|
4954
|
+
priority: wh.priority,
|
|
4955
|
+
agentId: wh.agentId,
|
|
4956
|
+
projectId: wh.projectId,
|
|
4957
|
+
description: wh.description ?? `Webhook: ${wh.handlerUrl}`,
|
|
4958
|
+
handler: makeWebhookHandler(wh.id, wh.handlerUrl)
|
|
4959
|
+
});
|
|
3761
4960
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
const timer = setInterval(poll, interval);
|
|
3765
|
-
return {
|
|
3766
|
-
stop: () => {
|
|
3767
|
-
stopped = true;
|
|
3768
|
-
clearInterval(timer);
|
|
4961
|
+
if (webhooks.length > 0) {
|
|
4962
|
+
console.log(`[hooks] Loaded ${webhooks.length} webhook(s) from DB`);
|
|
3769
4963
|
}
|
|
3770
|
-
}
|
|
4964
|
+
} catch (err) {
|
|
4965
|
+
console.error("[hooks] Failed to load webhooks from DB:", err);
|
|
4966
|
+
}
|
|
3771
4967
|
}
|
|
3772
|
-
function
|
|
3773
|
-
return {
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
agent_id: row["agent_id"] || null,
|
|
3786
|
-
project_id: row["project_id"] || null,
|
|
3787
|
-
session_id: row["session_id"] || null,
|
|
3788
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
3789
|
-
access_count: row["access_count"],
|
|
3790
|
-
version: row["version"],
|
|
3791
|
-
expires_at: row["expires_at"] || null,
|
|
3792
|
-
created_at: row["created_at"],
|
|
3793
|
-
updated_at: row["updated_at"],
|
|
3794
|
-
accessed_at: row["accessed_at"] || null
|
|
4968
|
+
function makeWebhookHandler(webhookId, url) {
|
|
4969
|
+
return async (context) => {
|
|
4970
|
+
try {
|
|
4971
|
+
const res = await fetch(url, {
|
|
4972
|
+
method: "POST",
|
|
4973
|
+
headers: { "Content-Type": "application/json" },
|
|
4974
|
+
body: JSON.stringify(context),
|
|
4975
|
+
signal: AbortSignal.timeout(1e4)
|
|
4976
|
+
});
|
|
4977
|
+
recordWebhookInvocation(webhookId, res.ok);
|
|
4978
|
+
} catch {
|
|
4979
|
+
recordWebhookInvocation(webhookId, false);
|
|
4980
|
+
}
|
|
3795
4981
|
};
|
|
3796
4982
|
}
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
4983
|
+
function reloadWebhooks() {
|
|
4984
|
+
_webhooksLoaded = false;
|
|
4985
|
+
loadWebhooksFromDb();
|
|
4986
|
+
}
|
|
4987
|
+
var _processConversationTurn = null, _webhooksLoaded = false;
|
|
4988
|
+
var init_built_in_hooks = __esm(() => {
|
|
4989
|
+
init_hooks();
|
|
4990
|
+
init_webhook_hooks();
|
|
4991
|
+
hookRegistry.register({
|
|
4992
|
+
type: "PostMemorySave",
|
|
4993
|
+
blocking: false,
|
|
4994
|
+
builtin: true,
|
|
4995
|
+
priority: 100,
|
|
4996
|
+
description: "Trigger async LLM entity extraction when a memory is saved",
|
|
4997
|
+
handler: async (ctx) => {
|
|
4998
|
+
if (ctx.wasUpdated)
|
|
4999
|
+
return;
|
|
5000
|
+
const processConversationTurn2 = await getAutoMemory();
|
|
5001
|
+
processConversationTurn2(`${ctx.memory.key}: ${ctx.memory.value}`, {
|
|
5002
|
+
agentId: ctx.agentId,
|
|
5003
|
+
projectId: ctx.projectId,
|
|
5004
|
+
sessionId: ctx.sessionId
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
});
|
|
5008
|
+
hookRegistry.register({
|
|
5009
|
+
type: "OnSessionStart",
|
|
5010
|
+
blocking: false,
|
|
5011
|
+
builtin: true,
|
|
5012
|
+
priority: 100,
|
|
5013
|
+
description: "Record session start as a history memory for analytics",
|
|
5014
|
+
handler: async (ctx) => {
|
|
5015
|
+
const { createMemory: createMemory2 } = await Promise.resolve().then(() => (init_memories(), exports_memories));
|
|
5016
|
+
try {
|
|
5017
|
+
createMemory2({
|
|
5018
|
+
key: `session-start-${ctx.agentId}`,
|
|
5019
|
+
value: `Agent ${ctx.agentId} started session on project ${ctx.projectId} at ${new Date(ctx.timestamp).toISOString()}`,
|
|
5020
|
+
category: "history",
|
|
5021
|
+
scope: "shared",
|
|
5022
|
+
importance: 3,
|
|
5023
|
+
source: "system",
|
|
5024
|
+
agent_id: ctx.agentId,
|
|
5025
|
+
project_id: ctx.projectId,
|
|
5026
|
+
session_id: ctx.sessionId
|
|
5027
|
+
});
|
|
5028
|
+
} catch {}
|
|
5029
|
+
}
|
|
5030
|
+
});
|
|
3800
5031
|
});
|
|
3801
5032
|
|
|
3802
5033
|
// node_modules/commander/esm.mjs
|
|
@@ -3818,569 +5049,315 @@ var {
|
|
|
3818
5049
|
// src/cli/index.tsx
|
|
3819
5050
|
init_database();
|
|
3820
5051
|
init_memories();
|
|
3821
|
-
init_agents();
|
|
3822
|
-
init_projects();
|
|
3823
5052
|
import chalk from "chalk";
|
|
3824
5053
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, accessSync, statSync, copyFileSync, mkdirSync as mkdirSync3, readdirSync as readdirSync2, constants as fsConstants } from "fs";
|
|
3825
5054
|
import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
|
|
3826
5055
|
import { homedir as homedir2 } from "os";
|
|
3827
5056
|
import { fileURLToPath } from "url";
|
|
3828
5057
|
|
|
3829
|
-
// src/
|
|
5058
|
+
// src/db/agents.ts
|
|
5059
|
+
init_types();
|
|
3830
5060
|
init_database();
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
function parseMemoryRow2(row) {
|
|
5061
|
+
var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
5062
|
+
function parseAgentRow(row) {
|
|
3834
5063
|
return {
|
|
3835
5064
|
id: row["id"],
|
|
3836
|
-
|
|
3837
|
-
value: row["value"],
|
|
3838
|
-
category: row["category"],
|
|
3839
|
-
scope: row["scope"],
|
|
3840
|
-
summary: row["summary"] || null,
|
|
3841
|
-
tags: JSON.parse(row["tags"] || "[]"),
|
|
3842
|
-
importance: row["importance"],
|
|
3843
|
-
source: row["source"],
|
|
3844
|
-
status: row["status"],
|
|
3845
|
-
pinned: !!row["pinned"],
|
|
3846
|
-
agent_id: row["agent_id"] || null,
|
|
3847
|
-
project_id: row["project_id"] || null,
|
|
5065
|
+
name: row["name"],
|
|
3848
5066
|
session_id: row["session_id"] || null,
|
|
5067
|
+
description: row["description"] || null,
|
|
5068
|
+
role: row["role"] || null,
|
|
3849
5069
|
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
"been",
|
|
3877
|
-
"being",
|
|
3878
|
-
"have",
|
|
3879
|
-
"has",
|
|
3880
|
-
"had",
|
|
3881
|
-
"do",
|
|
3882
|
-
"does",
|
|
3883
|
-
"did",
|
|
3884
|
-
"will",
|
|
3885
|
-
"would",
|
|
3886
|
-
"could",
|
|
3887
|
-
"should",
|
|
3888
|
-
"may",
|
|
3889
|
-
"might",
|
|
3890
|
-
"shall",
|
|
3891
|
-
"can",
|
|
3892
|
-
"need",
|
|
3893
|
-
"dare",
|
|
3894
|
-
"ought",
|
|
3895
|
-
"used",
|
|
3896
|
-
"to",
|
|
3897
|
-
"of",
|
|
3898
|
-
"in",
|
|
3899
|
-
"for",
|
|
3900
|
-
"on",
|
|
3901
|
-
"with",
|
|
3902
|
-
"at",
|
|
3903
|
-
"by",
|
|
3904
|
-
"from",
|
|
3905
|
-
"as",
|
|
3906
|
-
"into",
|
|
3907
|
-
"through",
|
|
3908
|
-
"during",
|
|
3909
|
-
"before",
|
|
3910
|
-
"after",
|
|
3911
|
-
"above",
|
|
3912
|
-
"below",
|
|
3913
|
-
"between",
|
|
3914
|
-
"out",
|
|
3915
|
-
"off",
|
|
3916
|
-
"over",
|
|
3917
|
-
"under",
|
|
3918
|
-
"again",
|
|
3919
|
-
"further",
|
|
3920
|
-
"then",
|
|
3921
|
-
"once",
|
|
3922
|
-
"here",
|
|
3923
|
-
"there",
|
|
3924
|
-
"when",
|
|
3925
|
-
"where",
|
|
3926
|
-
"why",
|
|
3927
|
-
"how",
|
|
3928
|
-
"all",
|
|
3929
|
-
"each",
|
|
3930
|
-
"every",
|
|
3931
|
-
"both",
|
|
3932
|
-
"few",
|
|
3933
|
-
"more",
|
|
3934
|
-
"most",
|
|
3935
|
-
"other",
|
|
3936
|
-
"some",
|
|
3937
|
-
"such",
|
|
3938
|
-
"no",
|
|
3939
|
-
"not",
|
|
3940
|
-
"only",
|
|
3941
|
-
"own",
|
|
3942
|
-
"same",
|
|
3943
|
-
"so",
|
|
3944
|
-
"than",
|
|
3945
|
-
"too",
|
|
3946
|
-
"very",
|
|
3947
|
-
"just",
|
|
3948
|
-
"because",
|
|
3949
|
-
"but",
|
|
3950
|
-
"and",
|
|
3951
|
-
"or",
|
|
3952
|
-
"if",
|
|
3953
|
-
"while",
|
|
3954
|
-
"that",
|
|
3955
|
-
"this",
|
|
3956
|
-
"it"
|
|
3957
|
-
]);
|
|
3958
|
-
function removeStopWords(tokens) {
|
|
3959
|
-
if (tokens.length <= 1)
|
|
3960
|
-
return tokens;
|
|
3961
|
-
const filtered = tokens.filter((t) => !STOP_WORDS.has(t.toLowerCase()));
|
|
3962
|
-
return filtered.length > 0 ? filtered : tokens;
|
|
3963
|
-
}
|
|
3964
|
-
function extractHighlights(memory, queryLower) {
|
|
3965
|
-
const highlights = [];
|
|
3966
|
-
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
3967
|
-
for (const field of ["key", "value", "summary"]) {
|
|
3968
|
-
const text = field === "summary" ? memory.summary : memory[field];
|
|
3969
|
-
if (!text)
|
|
3970
|
-
continue;
|
|
3971
|
-
const textLower = text.toLowerCase();
|
|
3972
|
-
const searchTerms = [queryLower, ...tokens].filter(Boolean);
|
|
3973
|
-
for (const term of searchTerms) {
|
|
3974
|
-
const idx = textLower.indexOf(term);
|
|
3975
|
-
if (idx !== -1) {
|
|
3976
|
-
const start = Math.max(0, idx - 30);
|
|
3977
|
-
const end = Math.min(text.length, idx + term.length + 30);
|
|
3978
|
-
const prefix = start > 0 ? "..." : "";
|
|
3979
|
-
const suffix = end < text.length ? "..." : "";
|
|
3980
|
-
highlights.push({
|
|
3981
|
-
field,
|
|
3982
|
-
snippet: prefix + text.slice(start, end) + suffix
|
|
3983
|
-
});
|
|
3984
|
-
break;
|
|
3985
|
-
}
|
|
3986
|
-
}
|
|
3987
|
-
}
|
|
3988
|
-
for (const tag of memory.tags) {
|
|
3989
|
-
if (tag.toLowerCase().includes(queryLower) || tokens.some((t) => tag.toLowerCase().includes(t))) {
|
|
3990
|
-
highlights.push({ field: "tag", snippet: tag });
|
|
3991
|
-
}
|
|
3992
|
-
}
|
|
3993
|
-
return highlights;
|
|
3994
|
-
}
|
|
3995
|
-
function determineMatchType(memory, queryLower) {
|
|
3996
|
-
if (memory.key.toLowerCase() === queryLower)
|
|
3997
|
-
return "exact";
|
|
3998
|
-
if (memory.tags.some((t) => t.toLowerCase() === queryLower))
|
|
3999
|
-
return "tag";
|
|
4000
|
-
if (memory.tags.some((t) => t.toLowerCase().includes(queryLower)))
|
|
4001
|
-
return "tag";
|
|
4002
|
-
return "fuzzy";
|
|
4003
|
-
}
|
|
4004
|
-
function computeScore(memory, queryLower) {
|
|
4005
|
-
const fieldScores = [];
|
|
4006
|
-
const keyLower = memory.key.toLowerCase();
|
|
4007
|
-
if (keyLower === queryLower) {
|
|
4008
|
-
fieldScores.push(10);
|
|
4009
|
-
} else if (keyLower.includes(queryLower)) {
|
|
4010
|
-
fieldScores.push(7);
|
|
4011
|
-
}
|
|
4012
|
-
if (memory.tags.some((t) => t.toLowerCase() === queryLower)) {
|
|
4013
|
-
fieldScores.push(6);
|
|
4014
|
-
} else if (memory.tags.some((t) => t.toLowerCase().includes(queryLower))) {
|
|
4015
|
-
fieldScores.push(3);
|
|
4016
|
-
}
|
|
4017
|
-
if (memory.summary && memory.summary.toLowerCase().includes(queryLower)) {
|
|
4018
|
-
fieldScores.push(4);
|
|
4019
|
-
}
|
|
4020
|
-
if (memory.value.toLowerCase().includes(queryLower)) {
|
|
4021
|
-
fieldScores.push(3);
|
|
4022
|
-
}
|
|
4023
|
-
const metadataStr = JSON.stringify(memory.metadata).toLowerCase();
|
|
4024
|
-
if (metadataStr !== "{}" && metadataStr.includes(queryLower)) {
|
|
4025
|
-
fieldScores.push(2);
|
|
4026
|
-
}
|
|
4027
|
-
fieldScores.sort((a, b) => b - a);
|
|
4028
|
-
const diminishingMultipliers = [1, 0.5, 0.25, 0.15, 0.15];
|
|
4029
|
-
let score = 0;
|
|
4030
|
-
for (let i = 0;i < fieldScores.length; i++) {
|
|
4031
|
-
score += fieldScores[i] * (diminishingMultipliers[i] ?? 0.15);
|
|
4032
|
-
}
|
|
4033
|
-
const { phrases } = extractQuotedPhrases(queryLower);
|
|
4034
|
-
for (const phrase of phrases) {
|
|
4035
|
-
if (keyLower.includes(phrase))
|
|
4036
|
-
score += 8;
|
|
4037
|
-
if (memory.value.toLowerCase().includes(phrase))
|
|
4038
|
-
score += 5;
|
|
4039
|
-
if (memory.summary && memory.summary.toLowerCase().includes(phrase))
|
|
4040
|
-
score += 4;
|
|
4041
|
-
}
|
|
4042
|
-
const { remainder } = extractQuotedPhrases(queryLower);
|
|
4043
|
-
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
4044
|
-
if (tokens.length > 1) {
|
|
4045
|
-
let tokenScore = 0;
|
|
4046
|
-
for (const token of tokens) {
|
|
4047
|
-
if (keyLower === token) {
|
|
4048
|
-
tokenScore += 10 / tokens.length;
|
|
4049
|
-
} else if (keyLower.includes(token)) {
|
|
4050
|
-
tokenScore += 7 / tokens.length;
|
|
4051
|
-
}
|
|
4052
|
-
if (memory.tags.some((t) => t.toLowerCase() === token)) {
|
|
4053
|
-
tokenScore += 6 / tokens.length;
|
|
4054
|
-
} else if (memory.tags.some((t) => t.toLowerCase().includes(token))) {
|
|
4055
|
-
tokenScore += 3 / tokens.length;
|
|
4056
|
-
}
|
|
4057
|
-
if (memory.summary && memory.summary.toLowerCase().includes(token)) {
|
|
4058
|
-
tokenScore += 4 / tokens.length;
|
|
4059
|
-
}
|
|
4060
|
-
if (memory.value.toLowerCase().includes(token)) {
|
|
4061
|
-
tokenScore += 3 / tokens.length;
|
|
4062
|
-
}
|
|
4063
|
-
if (metadataStr !== "{}" && metadataStr.includes(token)) {
|
|
4064
|
-
tokenScore += 2 / tokens.length;
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
if (score > 0) {
|
|
4068
|
-
score += tokenScore * 0.3;
|
|
4069
|
-
} else {
|
|
4070
|
-
score += tokenScore;
|
|
4071
|
-
}
|
|
4072
|
-
}
|
|
4073
|
-
return score;
|
|
4074
|
-
}
|
|
4075
|
-
function extractQuotedPhrases(query) {
|
|
4076
|
-
const phrases = [];
|
|
4077
|
-
const remainder = query.replace(/"([^"]+)"/g, (_match, phrase) => {
|
|
4078
|
-
phrases.push(phrase);
|
|
4079
|
-
return "";
|
|
4080
|
-
});
|
|
4081
|
-
return { phrases, remainder: remainder.trim() };
|
|
4082
|
-
}
|
|
4083
|
-
function escapeFts5Query(query) {
|
|
4084
|
-
const { phrases, remainder } = extractQuotedPhrases(query);
|
|
4085
|
-
const parts = [];
|
|
4086
|
-
for (const phrase of phrases) {
|
|
4087
|
-
parts.push(`"${phrase.replace(/"/g, '""')}"`);
|
|
4088
|
-
}
|
|
4089
|
-
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
4090
|
-
for (const t of tokens) {
|
|
4091
|
-
parts.push(`"${t.replace(/"/g, '""')}"`);
|
|
4092
|
-
}
|
|
4093
|
-
return parts.join(" ");
|
|
4094
|
-
}
|
|
4095
|
-
function hasFts5Table(d) {
|
|
4096
|
-
try {
|
|
4097
|
-
const row = d.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
4098
|
-
return !!row;
|
|
4099
|
-
} catch {
|
|
4100
|
-
return false;
|
|
4101
|
-
}
|
|
4102
|
-
}
|
|
4103
|
-
function buildFilterConditions(filter) {
|
|
4104
|
-
const conditions = [];
|
|
4105
|
-
const params = [];
|
|
4106
|
-
conditions.push("m.status = 'active'");
|
|
4107
|
-
conditions.push("(m.expires_at IS NULL OR m.expires_at >= datetime('now'))");
|
|
4108
|
-
if (!filter)
|
|
4109
|
-
return { conditions, params };
|
|
4110
|
-
if (filter.scope) {
|
|
4111
|
-
if (Array.isArray(filter.scope)) {
|
|
4112
|
-
conditions.push(`m.scope IN (${filter.scope.map(() => "?").join(",")})`);
|
|
4113
|
-
params.push(...filter.scope);
|
|
4114
|
-
} else {
|
|
4115
|
-
conditions.push("m.scope = ?");
|
|
4116
|
-
params.push(filter.scope);
|
|
5070
|
+
active_project_id: row["active_project_id"] || null,
|
|
5071
|
+
created_at: row["created_at"],
|
|
5072
|
+
last_seen_at: row["last_seen_at"]
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
function registerAgent(name, sessionId, description, role, projectId, db) {
|
|
5076
|
+
const d = db || getDatabase();
|
|
5077
|
+
const timestamp = now();
|
|
5078
|
+
const normalizedName = name.trim().toLowerCase();
|
|
5079
|
+
const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
5080
|
+
if (existing) {
|
|
5081
|
+
const existingId = existing["id"];
|
|
5082
|
+
const existingSessionId = existing["session_id"] || null;
|
|
5083
|
+
const existingLastSeen = existing["last_seen_at"];
|
|
5084
|
+
if (sessionId && existingSessionId && existingSessionId !== sessionId) {
|
|
5085
|
+
const lastSeenMs = new Date(existingLastSeen).getTime();
|
|
5086
|
+
const nowMs = Date.now();
|
|
5087
|
+
if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
|
|
5088
|
+
throw new AgentConflictError({
|
|
5089
|
+
existing_id: existingId,
|
|
5090
|
+
existing_name: normalizedName,
|
|
5091
|
+
last_seen_at: existingLastSeen,
|
|
5092
|
+
session_hint: existingSessionId.slice(0, 8),
|
|
5093
|
+
working_dir: null
|
|
5094
|
+
});
|
|
5095
|
+
}
|
|
4117
5096
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
params.push(filter.category);
|
|
5097
|
+
d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
|
|
5098
|
+
timestamp,
|
|
5099
|
+
sessionId ?? existingSessionId,
|
|
5100
|
+
existingId
|
|
5101
|
+
]);
|
|
5102
|
+
if (description) {
|
|
5103
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
|
|
4126
5104
|
}
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
if (Array.isArray(filter.source)) {
|
|
4130
|
-
conditions.push(`m.source IN (${filter.source.map(() => "?").join(",")})`);
|
|
4131
|
-
params.push(...filter.source);
|
|
4132
|
-
} else {
|
|
4133
|
-
conditions.push("m.source = ?");
|
|
4134
|
-
params.push(filter.source);
|
|
5105
|
+
if (role) {
|
|
5106
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
|
|
4135
5107
|
}
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
conditions.shift();
|
|
4139
|
-
if (Array.isArray(filter.status)) {
|
|
4140
|
-
conditions.push(`m.status IN (${filter.status.map(() => "?").join(",")})`);
|
|
4141
|
-
params.push(...filter.status);
|
|
4142
|
-
} else {
|
|
4143
|
-
conditions.push("m.status = ?");
|
|
4144
|
-
params.push(filter.status);
|
|
5108
|
+
if (projectId !== undefined) {
|
|
5109
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
4145
5110
|
}
|
|
5111
|
+
return getAgent(existingId, d);
|
|
4146
5112
|
}
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
5113
|
+
const id = shortUuid();
|
|
5114
|
+
d.run("INSERT INTO agents (id, name, session_id, description, role, active_project_id, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, normalizedName, sessionId ?? null, description || null, role || "agent", projectId ?? null, timestamp, timestamp]);
|
|
5115
|
+
return getAgent(id, d);
|
|
5116
|
+
}
|
|
5117
|
+
function getAgent(idOrName, db) {
|
|
5118
|
+
const d = db || getDatabase();
|
|
5119
|
+
let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
|
|
5120
|
+
if (row)
|
|
5121
|
+
return parseAgentRow(row);
|
|
5122
|
+
row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
|
|
5123
|
+
if (row)
|
|
5124
|
+
return parseAgentRow(row);
|
|
5125
|
+
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
5126
|
+
if (rows.length === 1)
|
|
5127
|
+
return parseAgentRow(rows[0]);
|
|
5128
|
+
return null;
|
|
5129
|
+
}
|
|
5130
|
+
function listAgents(db) {
|
|
5131
|
+
const d = db || getDatabase();
|
|
5132
|
+
const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
|
|
5133
|
+
return rows.map(parseAgentRow);
|
|
5134
|
+
}
|
|
5135
|
+
function updateAgent(id, updates, db) {
|
|
5136
|
+
const d = db || getDatabase();
|
|
5137
|
+
const agent = getAgent(id, d);
|
|
5138
|
+
if (!agent)
|
|
5139
|
+
return null;
|
|
5140
|
+
const timestamp = now();
|
|
5141
|
+
if (updates.name) {
|
|
5142
|
+
const normalizedNewName = updates.name.trim().toLowerCase();
|
|
5143
|
+
if (normalizedNewName !== agent.name) {
|
|
5144
|
+
const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
|
|
5145
|
+
if (existing) {
|
|
5146
|
+
throw new Error(`Agent name already taken: ${normalizedNewName}`);
|
|
5147
|
+
}
|
|
5148
|
+
d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
|
|
5149
|
+
}
|
|
4154
5150
|
}
|
|
4155
|
-
if (
|
|
4156
|
-
|
|
4157
|
-
params.push(filter.session_id);
|
|
5151
|
+
if (updates.description !== undefined) {
|
|
5152
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
|
|
4158
5153
|
}
|
|
4159
|
-
if (
|
|
4160
|
-
|
|
4161
|
-
params.push(filter.min_importance);
|
|
5154
|
+
if (updates.role !== undefined) {
|
|
5155
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
|
|
4162
5156
|
}
|
|
4163
|
-
if (
|
|
4164
|
-
|
|
4165
|
-
params.push(filter.pinned ? 1 : 0);
|
|
5157
|
+
if (updates.metadata !== undefined) {
|
|
5158
|
+
d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
|
|
4166
5159
|
}
|
|
4167
|
-
if (
|
|
4168
|
-
|
|
4169
|
-
conditions.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
|
|
4170
|
-
params.push(tag);
|
|
4171
|
-
}
|
|
5160
|
+
if ("active_project_id" in updates) {
|
|
5161
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [updates.active_project_id ?? null, agent.id]);
|
|
4172
5162
|
}
|
|
4173
|
-
|
|
5163
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
|
|
5164
|
+
return getAgent(agent.id, d);
|
|
4174
5165
|
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
} catch {
|
|
4189
|
-
return null;
|
|
4190
|
-
}
|
|
5166
|
+
|
|
5167
|
+
// src/db/projects.ts
|
|
5168
|
+
init_database();
|
|
5169
|
+
function parseProjectRow(row) {
|
|
5170
|
+
return {
|
|
5171
|
+
id: row["id"],
|
|
5172
|
+
name: row["name"],
|
|
5173
|
+
path: row["path"],
|
|
5174
|
+
description: row["description"] || null,
|
|
5175
|
+
memory_prefix: row["memory_prefix"] || null,
|
|
5176
|
+
created_at: row["created_at"],
|
|
5177
|
+
updated_at: row["updated_at"]
|
|
5178
|
+
};
|
|
4191
5179
|
}
|
|
4192
|
-
function
|
|
4193
|
-
const
|
|
4194
|
-
const
|
|
4195
|
-
const
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
for (const pattern of likePatterns) {
|
|
4204
|
-
fieldClauses.push("m.key LIKE ? ESCAPE '\\'");
|
|
4205
|
-
params.push(pattern);
|
|
4206
|
-
fieldClauses.push("m.value LIKE ? ESCAPE '\\'");
|
|
4207
|
-
params.push(pattern);
|
|
4208
|
-
fieldClauses.push("m.summary LIKE ? ESCAPE '\\'");
|
|
4209
|
-
params.push(pattern);
|
|
4210
|
-
fieldClauses.push("m.metadata LIKE ? ESCAPE '\\'");
|
|
4211
|
-
params.push(pattern);
|
|
4212
|
-
fieldClauses.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag LIKE ? ESCAPE '\\')");
|
|
4213
|
-
params.push(pattern);
|
|
5180
|
+
function registerProject(name, path, description, memoryPrefix, db) {
|
|
5181
|
+
const d = db || getDatabase();
|
|
5182
|
+
const timestamp = now();
|
|
5183
|
+
const existing = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
5184
|
+
if (existing) {
|
|
5185
|
+
const existingId = existing["id"];
|
|
5186
|
+
d.run("UPDATE projects SET updated_at = ? WHERE id = ?", [
|
|
5187
|
+
timestamp,
|
|
5188
|
+
existingId
|
|
5189
|
+
]);
|
|
5190
|
+
return parseProjectRow(existing);
|
|
4214
5191
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
return scoreResults(rows, queryLower, graphBoostedIds);
|
|
5192
|
+
const id = uuid();
|
|
5193
|
+
d.run("INSERT INTO projects (id, name, path, description, memory_prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, name, path, description || null, memoryPrefix || null, timestamp, timestamp]);
|
|
5194
|
+
return getProject(id, d);
|
|
4219
5195
|
}
|
|
4220
|
-
function
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
5196
|
+
function getProject(idOrPath, db) {
|
|
5197
|
+
const d = db || getDatabase();
|
|
5198
|
+
let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
|
|
5199
|
+
if (row)
|
|
5200
|
+
return parseProjectRow(row);
|
|
5201
|
+
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
5202
|
+
if (row)
|
|
5203
|
+
return parseProjectRow(row);
|
|
5204
|
+
row = d.query("SELECT * FROM projects WHERE LOWER(name) = ?").get(idOrPath.toLowerCase());
|
|
5205
|
+
if (row)
|
|
5206
|
+
return parseProjectRow(row);
|
|
5207
|
+
return null;
|
|
4227
5208
|
}
|
|
4228
|
-
function
|
|
4229
|
-
const
|
|
4230
|
-
const
|
|
4231
|
-
|
|
4232
|
-
return 0;
|
|
4233
|
-
let intersection = 0;
|
|
4234
|
-
for (const t of triA) {
|
|
4235
|
-
if (triB.has(t))
|
|
4236
|
-
intersection++;
|
|
4237
|
-
}
|
|
4238
|
-
const union = triA.size + triB.size - intersection;
|
|
4239
|
-
return union === 0 ? 0 : intersection / union;
|
|
5209
|
+
function listProjects(db) {
|
|
5210
|
+
const d = db || getDatabase();
|
|
5211
|
+
const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
|
|
5212
|
+
return rows.map(parseProjectRow);
|
|
4240
5213
|
}
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
5214
|
+
|
|
5215
|
+
// src/cli/index.tsx
|
|
5216
|
+
init_search();
|
|
5217
|
+
|
|
5218
|
+
// src/lib/config.ts
|
|
5219
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync, unlinkSync } from "fs";
|
|
5220
|
+
import { homedir } from "os";
|
|
5221
|
+
import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
5222
|
+
var DEFAULT_CONFIG = {
|
|
5223
|
+
default_scope: "private",
|
|
5224
|
+
default_category: "knowledge",
|
|
5225
|
+
default_importance: 5,
|
|
5226
|
+
max_entries: 1000,
|
|
5227
|
+
max_entries_per_scope: {
|
|
5228
|
+
global: 500,
|
|
5229
|
+
shared: 300,
|
|
5230
|
+
private: 200
|
|
5231
|
+
},
|
|
5232
|
+
injection: {
|
|
5233
|
+
max_tokens: 500,
|
|
5234
|
+
min_importance: 5,
|
|
5235
|
+
categories: ["preference", "fact"],
|
|
5236
|
+
refresh_interval: 5
|
|
5237
|
+
},
|
|
5238
|
+
extraction: {
|
|
5239
|
+
enabled: true,
|
|
5240
|
+
min_confidence: 0.5
|
|
5241
|
+
},
|
|
5242
|
+
sync_agents: ["claude", "codex", "gemini"],
|
|
5243
|
+
auto_cleanup: {
|
|
5244
|
+
enabled: true,
|
|
5245
|
+
expired_check_interval: 3600,
|
|
5246
|
+
unused_archive_days: 7,
|
|
5247
|
+
stale_deprioritize_days: 14
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
function deepMerge(target, source) {
|
|
5251
|
+
const result = { ...target };
|
|
5252
|
+
for (const key of Object.keys(source)) {
|
|
5253
|
+
const sourceVal = source[key];
|
|
5254
|
+
const targetVal = result[key];
|
|
5255
|
+
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
5256
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
5257
|
+
} else {
|
|
5258
|
+
result[key] = sourceVal;
|
|
4262
5259
|
}
|
|
4263
5260
|
}
|
|
4264
|
-
|
|
4265
|
-
return results;
|
|
5261
|
+
return result;
|
|
4266
5262
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
const memories = getMemoriesForEntity(entity.id, d);
|
|
4277
|
-
for (const mem of memories) {
|
|
4278
|
-
boostedIds.add(mem.id);
|
|
4279
|
-
}
|
|
4280
|
-
}
|
|
4281
|
-
} catch {}
|
|
4282
|
-
return boostedIds;
|
|
5263
|
+
var VALID_SCOPES = ["global", "shared", "private"];
|
|
5264
|
+
var VALID_CATEGORIES = [
|
|
5265
|
+
"preference",
|
|
5266
|
+
"fact",
|
|
5267
|
+
"knowledge",
|
|
5268
|
+
"history"
|
|
5269
|
+
];
|
|
5270
|
+
function isValidScope(value) {
|
|
5271
|
+
return VALID_SCOPES.includes(value);
|
|
4283
5272
|
}
|
|
4284
|
-
function
|
|
4285
|
-
|
|
4286
|
-
return 1;
|
|
4287
|
-
const mostRecent = memory.accessed_at || memory.updated_at;
|
|
4288
|
-
if (!mostRecent)
|
|
4289
|
-
return 0;
|
|
4290
|
-
const daysSinceAccess = (Date.now() - Date.parse(mostRecent)) / (1000 * 60 * 60 * 24);
|
|
4291
|
-
return Math.max(0, 1 - daysSinceAccess / 30);
|
|
5273
|
+
function isValidCategory(value) {
|
|
5274
|
+
return VALID_CATEGORIES.includes(value);
|
|
4292
5275
|
}
|
|
4293
|
-
function
|
|
4294
|
-
const
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
const recencyBoost = computeRecencyBoost(memory);
|
|
4302
|
-
const accessBoost = Math.min(memory.access_count / 20, 0.2);
|
|
4303
|
-
const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
|
|
4304
|
-
const finalScore = (weightedScore + graphBoost) * (1 + recencyBoost * 0.3) * (1 + accessBoost);
|
|
4305
|
-
const matchType = determineMatchType(memory, queryLower);
|
|
4306
|
-
scored.push({
|
|
4307
|
-
memory,
|
|
4308
|
-
score: finalScore,
|
|
4309
|
-
match_type: matchType,
|
|
4310
|
-
highlights: extractHighlights(memory, queryLower)
|
|
4311
|
-
});
|
|
5276
|
+
function loadConfig() {
|
|
5277
|
+
const configPath = join2(homedir(), ".mementos", "config.json");
|
|
5278
|
+
let fileConfig = {};
|
|
5279
|
+
if (existsSync2(configPath)) {
|
|
5280
|
+
try {
|
|
5281
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
5282
|
+
fileConfig = JSON.parse(raw);
|
|
5283
|
+
} catch {}
|
|
4312
5284
|
}
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
});
|
|
4318
|
-
return scored;
|
|
4319
|
-
}
|
|
4320
|
-
function searchMemories(query, filter, db) {
|
|
4321
|
-
const d = db || getDatabase();
|
|
4322
|
-
query = preprocessQuery(query);
|
|
4323
|
-
if (!query)
|
|
4324
|
-
return [];
|
|
4325
|
-
const queryLower = query.toLowerCase();
|
|
4326
|
-
const graphBoostedIds = getGraphBoostedMemoryIds(query, d);
|
|
4327
|
-
let scored;
|
|
4328
|
-
if (hasFts5Table(d)) {
|
|
4329
|
-
const ftsResult = searchWithFts5(d, query, queryLower, filter, graphBoostedIds);
|
|
4330
|
-
if (ftsResult !== null) {
|
|
4331
|
-
scored = ftsResult;
|
|
4332
|
-
} else {
|
|
4333
|
-
scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
|
|
4334
|
-
}
|
|
4335
|
-
} else {
|
|
4336
|
-
scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
|
|
5285
|
+
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
5286
|
+
const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
|
|
5287
|
+
if (envScope && isValidScope(envScope)) {
|
|
5288
|
+
merged.default_scope = envScope;
|
|
4337
5289
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
5290
|
+
const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
|
|
5291
|
+
if (envCategory && isValidCategory(envCategory)) {
|
|
5292
|
+
merged.default_category = envCategory;
|
|
5293
|
+
}
|
|
5294
|
+
const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
|
|
5295
|
+
if (envImportance) {
|
|
5296
|
+
const parsed = parseInt(envImportance, 10);
|
|
5297
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
5298
|
+
merged.default_importance = parsed;
|
|
4346
5299
|
}
|
|
4347
|
-
scored.sort((a, b) => {
|
|
4348
|
-
if (b.score !== a.score)
|
|
4349
|
-
return b.score - a.score;
|
|
4350
|
-
return b.memory.importance - a.memory.importance;
|
|
4351
|
-
});
|
|
4352
5300
|
}
|
|
4353
|
-
|
|
4354
|
-
const limit = filter?.limit ?? scored.length;
|
|
4355
|
-
const finalResults = scored.slice(offset, offset + limit);
|
|
4356
|
-
logSearchQuery(query, scored.length, filter?.agent_id, filter?.project_id, d);
|
|
4357
|
-
return finalResults;
|
|
5301
|
+
return merged;
|
|
4358
5302
|
}
|
|
4359
|
-
function
|
|
5303
|
+
function profilesDir() {
|
|
5304
|
+
return join2(homedir(), ".mementos", "profiles");
|
|
5305
|
+
}
|
|
5306
|
+
function globalConfigPath() {
|
|
5307
|
+
return join2(homedir(), ".mementos", "config.json");
|
|
5308
|
+
}
|
|
5309
|
+
function readGlobalConfig() {
|
|
5310
|
+
const p = globalConfigPath();
|
|
5311
|
+
if (!existsSync2(p))
|
|
5312
|
+
return {};
|
|
4360
5313
|
try {
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
}
|
|
5314
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
5315
|
+
} catch {
|
|
5316
|
+
return {};
|
|
5317
|
+
}
|
|
4365
5318
|
}
|
|
4366
|
-
function
|
|
4367
|
-
const
|
|
4368
|
-
|
|
4369
|
-
|
|
5319
|
+
function writeGlobalConfig(data) {
|
|
5320
|
+
const p = globalConfigPath();
|
|
5321
|
+
ensureDir2(dirname2(p));
|
|
5322
|
+
writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");
|
|
5323
|
+
}
|
|
5324
|
+
function getActiveProfile() {
|
|
5325
|
+
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
5326
|
+
if (envProfile)
|
|
5327
|
+
return envProfile.trim();
|
|
5328
|
+
const cfg = readGlobalConfig();
|
|
5329
|
+
return cfg["active_profile"] || null;
|
|
5330
|
+
}
|
|
5331
|
+
function setActiveProfile(name) {
|
|
5332
|
+
const cfg = readGlobalConfig();
|
|
5333
|
+
if (name === null) {
|
|
5334
|
+
delete cfg["active_profile"];
|
|
5335
|
+
} else {
|
|
5336
|
+
cfg["active_profile"] = name;
|
|
4370
5337
|
}
|
|
4371
|
-
|
|
5338
|
+
writeGlobalConfig(cfg);
|
|
4372
5339
|
}
|
|
4373
|
-
function
|
|
4374
|
-
const
|
|
4375
|
-
if (
|
|
4376
|
-
return
|
|
5340
|
+
function listProfiles() {
|
|
5341
|
+
const dir = profilesDir();
|
|
5342
|
+
if (!existsSync2(dir))
|
|
5343
|
+
return [];
|
|
5344
|
+
return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
|
|
5345
|
+
}
|
|
5346
|
+
function deleteProfile(name) {
|
|
5347
|
+
const dbPath = join2(profilesDir(), `${name}.db`);
|
|
5348
|
+
if (!existsSync2(dbPath))
|
|
5349
|
+
return false;
|
|
5350
|
+
unlinkSync(dbPath);
|
|
5351
|
+
if (getActiveProfile() === name)
|
|
5352
|
+
setActiveProfile(null);
|
|
5353
|
+
return true;
|
|
5354
|
+
}
|
|
5355
|
+
function ensureDir2(dir) {
|
|
5356
|
+
if (!existsSync2(dir)) {
|
|
5357
|
+
mkdirSync2(dir, { recursive: true });
|
|
4377
5358
|
}
|
|
4378
|
-
return d.query("SELECT query, COUNT(*) as count FROM search_history GROUP BY query ORDER BY count DESC LIMIT ?").all(limit);
|
|
4379
5359
|
}
|
|
4380
5360
|
|
|
4381
|
-
// src/cli/index.tsx
|
|
4382
|
-
init_config();
|
|
4383
|
-
|
|
4384
5361
|
// src/lib/retention.ts
|
|
4385
5362
|
init_database();
|
|
4386
5363
|
init_memories();
|
|
@@ -7449,4 +8426,213 @@ profileCmd.command("delete <name>").description("Delete a profile and its DB fil
|
|
|
7449
8426
|
process.exit(1);
|
|
7450
8427
|
}
|
|
7451
8428
|
});
|
|
8429
|
+
var autoMemory = program2.command("auto-memory").description("LLM-based auto-memory extraction pipeline");
|
|
8430
|
+
autoMemory.command("process <turn>").description("Enqueue text for async LLM memory extraction (fire-and-forget)").option("--agent <id>", "agent ID").option("--project <id>", "project ID").option("--session <id>", "session ID").option("--sync", "run synchronously and print extracted memories").action(async (turn, opts) => {
|
|
8431
|
+
const { processConversationTurn: processConversationTurn2, getAutoMemoryStats: getAutoMemoryStats2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
8432
|
+
const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
8433
|
+
if (opts.sync) {
|
|
8434
|
+
const provider = providerRegistry2.getAvailable();
|
|
8435
|
+
if (!provider) {
|
|
8436
|
+
console.error(chalk.red("No LLM provider configured. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, CEREBRAS_API_KEY, or XAI_API_KEY."));
|
|
8437
|
+
process.exit(1);
|
|
8438
|
+
}
|
|
8439
|
+
console.log(chalk.dim(`Using ${provider.name} / ${provider.config.model}...`));
|
|
8440
|
+
const memories = await provider.extractMemories(turn, {
|
|
8441
|
+
agentId: opts.agent,
|
|
8442
|
+
projectId: opts.project
|
|
8443
|
+
});
|
|
8444
|
+
if (memories.length === 0) {
|
|
8445
|
+
console.log(chalk.dim("Nothing worth remembering extracted."));
|
|
8446
|
+
} else {
|
|
8447
|
+
memories.forEach((m, i) => {
|
|
8448
|
+
console.log(chalk.bold(`
|
|
8449
|
+
[${i + 1}] ${m.category} \xB7 importance ${m.importance}/10 \xB7 ${m.suggestedScope}`));
|
|
8450
|
+
console.log(` ${m.content}`);
|
|
8451
|
+
if (m.tags.length)
|
|
8452
|
+
console.log(chalk.dim(` tags: ${m.tags.join(", ")}`));
|
|
8453
|
+
});
|
|
8454
|
+
}
|
|
8455
|
+
} else {
|
|
8456
|
+
processConversationTurn2(turn, {
|
|
8457
|
+
agentId: opts.agent,
|
|
8458
|
+
projectId: opts.project,
|
|
8459
|
+
sessionId: opts.session
|
|
8460
|
+
});
|
|
8461
|
+
const stats = getAutoMemoryStats2();
|
|
8462
|
+
console.log(chalk.green("\u2713 Queued for extraction"));
|
|
8463
|
+
console.log(chalk.dim(`Queue: ${stats.pending} pending \xB7 ${stats.processed} processed \xB7 ${stats.failed} failed`));
|
|
8464
|
+
}
|
|
8465
|
+
});
|
|
8466
|
+
autoMemory.command("status").description("Show auto-memory queue stats and provider health").action(async () => {
|
|
8467
|
+
const { getAutoMemoryStats: getAutoMemoryStats2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
8468
|
+
const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
8469
|
+
const stats = getAutoMemoryStats2();
|
|
8470
|
+
const config = providerRegistry2.getConfig();
|
|
8471
|
+
const health = providerRegistry2.health();
|
|
8472
|
+
console.log(chalk.bold("Auto-Memory Status"));
|
|
8473
|
+
console.log(` Provider: ${config.enabled ? chalk.green(config.provider) : chalk.red("disabled")} / ${config.model ?? "default"}`);
|
|
8474
|
+
console.log(` Queue: ${stats.pending} pending \xB7 ${stats.processing} processing \xB7 ${stats.processed} processed`);
|
|
8475
|
+
console.log(` Errors: ${stats.failed} failed \xB7 ${stats.dropped} dropped`);
|
|
8476
|
+
console.log(chalk.bold(`
|
|
8477
|
+
Provider Health`));
|
|
8478
|
+
for (const [name, info] of Object.entries(health)) {
|
|
8479
|
+
const icon = info.available ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
8480
|
+
console.log(` ${icon} ${name.padEnd(12)} ${info.model}`);
|
|
8481
|
+
}
|
|
8482
|
+
});
|
|
8483
|
+
autoMemory.command("config").description("Show or update auto-memory provider config").option("--provider <name>", "provider: anthropic | openai | cerebras | grok").option("--model <name>", "model name override").option("--min-importance <n>", "minimum importance threshold (0-10)").action(async (opts) => {
|
|
8484
|
+
const { configureAutoMemory: configureAutoMemory2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
8485
|
+
const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
8486
|
+
if (opts.provider || opts.model || opts.minImportance) {
|
|
8487
|
+
configureAutoMemory2({
|
|
8488
|
+
...opts.provider && { provider: opts.provider },
|
|
8489
|
+
...opts.model && { model: opts.model },
|
|
8490
|
+
...opts.minImportance && { minImportance: Number(opts.minImportance) }
|
|
8491
|
+
});
|
|
8492
|
+
console.log(chalk.green("\u2713 Config updated"));
|
|
8493
|
+
}
|
|
8494
|
+
const config = providerRegistry2.getConfig();
|
|
8495
|
+
console.log(chalk.bold("Auto-Memory Config"));
|
|
8496
|
+
console.log(JSON.stringify(config, null, 2));
|
|
8497
|
+
});
|
|
8498
|
+
autoMemory.command("test <turn>").description("Test extraction without saving (dry run)").option("--provider <name>", "force a specific provider").option("--agent <id>", "agent ID for context").option("--project <id>", "project ID for context").action(async (turn, opts) => {
|
|
8499
|
+
const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
8500
|
+
const provider = opts.provider ? providerRegistry2.getProvider(opts.provider) : providerRegistry2.getAvailable();
|
|
8501
|
+
if (!provider) {
|
|
8502
|
+
console.error(chalk.red("No LLM provider configured."));
|
|
8503
|
+
process.exit(1);
|
|
8504
|
+
}
|
|
8505
|
+
console.log(chalk.dim(`DRY RUN \u2014 ${provider.name} / ${provider.config.model} \u2014 nothing will be saved
|
|
8506
|
+
`));
|
|
8507
|
+
const memories = await provider.extractMemories(turn, {
|
|
8508
|
+
agentId: opts.agent,
|
|
8509
|
+
projectId: opts.project
|
|
8510
|
+
});
|
|
8511
|
+
if (memories.length === 0) {
|
|
8512
|
+
console.log(chalk.dim("Nothing extracted."));
|
|
8513
|
+
} else {
|
|
8514
|
+
memories.forEach((m, i) => {
|
|
8515
|
+
console.log(chalk.bold(`[${i + 1}] ${m.category.toUpperCase()} \xB7 importance ${m.importance}/10 \xB7 ${m.suggestedScope}`));
|
|
8516
|
+
console.log(` ${chalk.white(m.content)}`);
|
|
8517
|
+
if (m.tags.length)
|
|
8518
|
+
console.log(chalk.dim(` tags: ${m.tags.join(", ")}`));
|
|
8519
|
+
if (m.reasoning)
|
|
8520
|
+
console.log(chalk.dim(` why: ${m.reasoning}`));
|
|
8521
|
+
console.log();
|
|
8522
|
+
});
|
|
8523
|
+
}
|
|
8524
|
+
});
|
|
8525
|
+
autoMemory.command("enable").description("Enable auto-memory extraction").action(async () => {
|
|
8526
|
+
const { configureAutoMemory: configureAutoMemory2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
8527
|
+
configureAutoMemory2({ enabled: true });
|
|
8528
|
+
console.log(chalk.green("\u2713 Auto-memory enabled"));
|
|
8529
|
+
});
|
|
8530
|
+
autoMemory.command("disable").description("Disable auto-memory extraction").action(async () => {
|
|
8531
|
+
const { configureAutoMemory: configureAutoMemory2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
|
|
8532
|
+
configureAutoMemory2({ enabled: false });
|
|
8533
|
+
console.log(chalk.yellow("\u26A0 Auto-memory disabled"));
|
|
8534
|
+
});
|
|
8535
|
+
var hooksCmd = program2.command("hooks").description("Hook registry and webhook management");
|
|
8536
|
+
hooksCmd.command("list").description("List registered hooks in the in-memory registry").option("--type <type>", "Filter by hook type").action(async (opts) => {
|
|
8537
|
+
const { hookRegistry: hookRegistry2 } = await Promise.resolve().then(() => (init_hooks(), exports_hooks));
|
|
8538
|
+
const hooks = hookRegistry2.list(opts.type);
|
|
8539
|
+
if (hooks.length === 0) {
|
|
8540
|
+
console.log(chalk.gray("No hooks registered."));
|
|
8541
|
+
return;
|
|
8542
|
+
}
|
|
8543
|
+
for (const h of hooks) {
|
|
8544
|
+
const builtinTag = h.builtin ? chalk.blue(" [builtin]") : "";
|
|
8545
|
+
const blockingTag = h.blocking ? chalk.red(" [blocking]") : chalk.gray(" [non-blocking]");
|
|
8546
|
+
console.log(`${chalk.cyan(h.id)} ${chalk.bold(h.type)}${builtinTag}${blockingTag} priority=${h.priority}`);
|
|
8547
|
+
if (h.description)
|
|
8548
|
+
console.log(` ${chalk.gray(h.description)}`);
|
|
8549
|
+
}
|
|
8550
|
+
});
|
|
8551
|
+
hooksCmd.command("stats").description("Show hook registry statistics").action(async () => {
|
|
8552
|
+
const { hookRegistry: hookRegistry2 } = await Promise.resolve().then(() => (init_hooks(), exports_hooks));
|
|
8553
|
+
const stats = hookRegistry2.stats();
|
|
8554
|
+
console.log(chalk.bold("Hook Registry Stats"));
|
|
8555
|
+
console.log(` Total: ${chalk.cyan(stats.total)}`);
|
|
8556
|
+
console.log(` Blocking: ${chalk.red(stats.blocking)}`);
|
|
8557
|
+
console.log(` Non-blocking:${chalk.green(stats.nonBlocking)}`);
|
|
8558
|
+
if (Object.keys(stats.byType).length > 0) {
|
|
8559
|
+
console.log(chalk.bold(`
|
|
8560
|
+
By type:`));
|
|
8561
|
+
for (const [type, count] of Object.entries(stats.byType)) {
|
|
8562
|
+
console.log(` ${type}: ${count}`);
|
|
8563
|
+
}
|
|
8564
|
+
}
|
|
8565
|
+
});
|
|
8566
|
+
var webhooksCmd = hooksCmd.command("webhooks").alias("wh").description("Manage persistent HTTP webhook hooks");
|
|
8567
|
+
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) => {
|
|
8568
|
+
const { listWebhookHooks: listWebhookHooks2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
8569
|
+
const webhooks = listWebhookHooks2({
|
|
8570
|
+
type: opts.type,
|
|
8571
|
+
enabled: opts.disabled ? false : undefined
|
|
8572
|
+
});
|
|
8573
|
+
if (webhooks.length === 0) {
|
|
8574
|
+
console.log(chalk.gray("No webhooks registered."));
|
|
8575
|
+
return;
|
|
8576
|
+
}
|
|
8577
|
+
for (const wh of webhooks) {
|
|
8578
|
+
const enabledTag = wh.enabled ? chalk.green("enabled") : chalk.red("disabled");
|
|
8579
|
+
const blockingTag = wh.blocking ? chalk.red("blocking") : chalk.gray("non-blocking");
|
|
8580
|
+
console.log(`${chalk.cyan(wh.id)} [${enabledTag}] ${chalk.bold(wh.type)} \u2192 ${wh.handlerUrl}`);
|
|
8581
|
+
console.log(` ${blockingTag} | priority=${wh.priority} | invocations=${wh.invocationCount} failures=${wh.failureCount}`);
|
|
8582
|
+
if (wh.description)
|
|
8583
|
+
console.log(` ${chalk.gray(wh.description)}`);
|
|
8584
|
+
}
|
|
8585
|
+
});
|
|
8586
|
+
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) => {
|
|
8587
|
+
const { createWebhookHook: createWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
8588
|
+
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
8589
|
+
const wh = createWebhookHook2({
|
|
8590
|
+
type,
|
|
8591
|
+
handlerUrl: url,
|
|
8592
|
+
blocking: opts.blocking ?? false,
|
|
8593
|
+
priority: parseInt(opts.priority, 10),
|
|
8594
|
+
agentId: opts.agent,
|
|
8595
|
+
projectId: opts.project,
|
|
8596
|
+
description: opts.description
|
|
8597
|
+
});
|
|
8598
|
+
reloadWebhooks2();
|
|
8599
|
+
console.log(chalk.green("\u2713 Webhook created"));
|
|
8600
|
+
console.log(` ID: ${chalk.cyan(wh.id)}`);
|
|
8601
|
+
console.log(` Type: ${wh.type}`);
|
|
8602
|
+
console.log(` URL: ${wh.handlerUrl}`);
|
|
8603
|
+
});
|
|
8604
|
+
webhooksCmd.command("delete <id>").description("Delete a webhook by ID").action(async (id) => {
|
|
8605
|
+
const { deleteWebhookHook: deleteWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
8606
|
+
const deleted = deleteWebhookHook2(id);
|
|
8607
|
+
if (deleted) {
|
|
8608
|
+
console.log(chalk.green(`\u2713 Webhook ${id} deleted`));
|
|
8609
|
+
} else {
|
|
8610
|
+
console.error(chalk.red(`Webhook not found: ${id}`));
|
|
8611
|
+
process.exit(1);
|
|
8612
|
+
}
|
|
8613
|
+
});
|
|
8614
|
+
webhooksCmd.command("enable <id>").description("Enable a webhook").action(async (id) => {
|
|
8615
|
+
const { updateWebhookHook: updateWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
8616
|
+
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
8617
|
+
const updated = updateWebhookHook2(id, { enabled: true });
|
|
8618
|
+
if (updated) {
|
|
8619
|
+
reloadWebhooks2();
|
|
8620
|
+
console.log(chalk.green(`\u2713 Webhook ${id} enabled`));
|
|
8621
|
+
} else {
|
|
8622
|
+
console.error(chalk.red(`Webhook not found: ${id}`));
|
|
8623
|
+
process.exit(1);
|
|
8624
|
+
}
|
|
8625
|
+
});
|
|
8626
|
+
webhooksCmd.command("disable <id>").description("Disable a webhook (without deleting it)").action(async (id) => {
|
|
8627
|
+
const { updateWebhookHook: updateWebhookHook2 } = await Promise.resolve().then(() => (init_webhook_hooks(), exports_webhook_hooks));
|
|
8628
|
+
const { reloadWebhooks: reloadWebhooks2 } = await Promise.resolve().then(() => (init_built_in_hooks(), exports_built_in_hooks));
|
|
8629
|
+
const updated = updateWebhookHook2(id, { enabled: false });
|
|
8630
|
+
if (updated) {
|
|
8631
|
+
reloadWebhooks2();
|
|
8632
|
+
console.log(chalk.yellow(`\u2298 Webhook ${id} disabled`));
|
|
8633
|
+
} else {
|
|
8634
|
+
console.error(chalk.red(`Webhook not found: ${id}`));
|
|
8635
|
+
process.exit(1);
|
|
8636
|
+
}
|
|
8637
|
+
});
|
|
7452
8638
|
program2.parse(process.argv);
|