@hasna/mementos 0.4.41 → 0.6.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.
Files changed (38) hide show
  1. package/dist/cli/index.js +2247 -1548
  2. package/dist/db/database.d.ts.map +1 -1
  3. package/dist/db/memories.d.ts +1 -0
  4. package/dist/db/memories.d.ts.map +1 -1
  5. package/dist/index.d.ts +6 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2041 -1336
  8. package/dist/lib/auto-memory-queue.d.ts +46 -0
  9. package/dist/lib/auto-memory-queue.d.ts.map +1 -0
  10. package/dist/lib/auto-memory.d.ts +18 -0
  11. package/dist/lib/auto-memory.d.ts.map +1 -0
  12. package/dist/lib/dedup.d.ts +33 -0
  13. package/dist/lib/dedup.d.ts.map +1 -0
  14. package/dist/lib/focus.d.ts +58 -0
  15. package/dist/lib/focus.d.ts.map +1 -0
  16. package/dist/lib/providers/anthropic.d.ts +21 -0
  17. package/dist/lib/providers/anthropic.d.ts.map +1 -0
  18. package/dist/lib/providers/base.d.ts +96 -0
  19. package/dist/lib/providers/base.d.ts.map +1 -0
  20. package/dist/lib/providers/cerebras.d.ts +20 -0
  21. package/dist/lib/providers/cerebras.d.ts.map +1 -0
  22. package/dist/lib/providers/grok.d.ts +19 -0
  23. package/dist/lib/providers/grok.d.ts.map +1 -0
  24. package/dist/lib/providers/index.d.ts +7 -0
  25. package/dist/lib/providers/index.d.ts.map +1 -0
  26. package/dist/lib/providers/openai-compat.d.ts +18 -0
  27. package/dist/lib/providers/openai-compat.d.ts.map +1 -0
  28. package/dist/lib/providers/openai.d.ts +20 -0
  29. package/dist/lib/providers/openai.d.ts.map +1 -0
  30. package/dist/lib/providers/registry.d.ts +38 -0
  31. package/dist/lib/providers/registry.d.ts.map +1 -0
  32. package/dist/lib/search.d.ts.map +1 -1
  33. package/dist/mcp/index.js +1781 -1101
  34. package/dist/server/index.d.ts.map +1 -1
  35. package/dist/server/index.js +1480 -941
  36. package/dist/types/index.d.ts +7 -0
  37. package/dist/types/index.d.ts.map +1 -1
  38. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -2427,6 +2427,11 @@ 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);
2430
2435
  `
2431
2436
  ];
2432
2437
  });
@@ -2506,481 +2511,462 @@ var init_redact = __esm(() => {
2506
2511
  ];
2507
2512
  });
2508
2513
 
2509
- // src/db/agents.ts
2510
- function parseAgentRow(row) {
2514
+ // src/db/entity-memories.ts
2515
+ function parseEntityMemoryRow(row) {
2511
2516
  return {
2512
- id: row["id"],
2513
- name: row["name"],
2514
- session_id: row["session_id"] || null,
2515
- description: row["description"] || null,
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"]
2517
+ entity_id: row["entity_id"],
2518
+ memory_id: row["memory_id"],
2519
+ role: row["role"],
2520
+ created_at: row["created_at"]
2521
2521
  };
2522
2522
  }
2523
- function registerAgent(name, sessionId, description, role, projectId, db) {
2523
+ function linkEntityToMemory(entityId, memoryId, role = "context", db) {
2524
2524
  const d = db || getDatabase();
2525
2525
  const timestamp = now();
2526
- const normalizedName = name.trim().toLowerCase();
2527
- const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
2528
- if (existing) {
2529
- const existingId = existing["id"];
2530
- const existingSessionId = existing["session_id"] || null;
2531
- const existingLastSeen = existing["last_seen_at"];
2532
- if (sessionId && existingSessionId && existingSessionId !== sessionId) {
2533
- const lastSeenMs = new Date(existingLastSeen).getTime();
2534
- const nowMs = Date.now();
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);
2560
- }
2561
- const id = shortUuid();
2562
- 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]);
2563
- return getAgent(id, d);
2526
+ d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
2527
+ VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
2528
+ const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
2529
+ return parseEntityMemoryRow(row);
2564
2530
  }
2565
- function getAgent(idOrName, db) {
2531
+ function unlinkEntityFromMemory(entityId, memoryId, db) {
2566
2532
  const d = db || getDatabase();
2567
- let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
2568
- if (row)
2569
- return parseAgentRow(row);
2570
- row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
2571
- if (row)
2572
- return parseAgentRow(row);
2573
- const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
2574
- if (rows.length === 1)
2575
- return parseAgentRow(rows[0]);
2576
- return null;
2533
+ d.run("DELETE FROM entity_memories WHERE entity_id = ? AND memory_id = ?", [entityId, memoryId]);
2577
2534
  }
2578
- function listAgents(db) {
2535
+ function getMemoriesForEntity(entityId, db) {
2579
2536
  const d = db || getDatabase();
2580
- const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
2581
- return rows.map(parseAgentRow);
2537
+ const rows = d.query(`SELECT m.* FROM memories m
2538
+ INNER JOIN entity_memories em ON em.memory_id = m.id
2539
+ WHERE em.entity_id = ?
2540
+ ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
2541
+ return rows.map(parseMemoryRow);
2582
2542
  }
2583
- function updateAgent(id, updates, db) {
2543
+ function getEntityMemoryLinks(entityId, memoryId, db) {
2584
2544
  const d = db || getDatabase();
2585
- const agent = getAgent(id, d);
2586
- if (!agent)
2587
- return null;
2588
- const timestamp = now();
2589
- if (updates.name) {
2590
- const normalizedNewName = updates.name.trim().toLowerCase();
2591
- if (normalizedNewName !== agent.name) {
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}`);
2595
- }
2596
- d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
2597
- }
2598
- }
2599
- if (updates.description !== undefined) {
2600
- d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
2601
- }
2602
- if (updates.role !== undefined) {
2603
- d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
2545
+ const conditions = [];
2546
+ const params = [];
2547
+ if (entityId) {
2548
+ conditions.push("entity_id = ?");
2549
+ params.push(entityId);
2604
2550
  }
2605
- if (updates.metadata !== undefined) {
2606
- d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
2551
+ if (memoryId) {
2552
+ conditions.push("memory_id = ?");
2553
+ params.push(memoryId);
2607
2554
  }
2608
- if ("active_project_id" in updates) {
2609
- d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [updates.active_project_id ?? null, agent.id]);
2555
+ let sql = "SELECT * FROM entity_memories";
2556
+ if (conditions.length > 0) {
2557
+ sql += ` WHERE ${conditions.join(" AND ")}`;
2610
2558
  }
2611
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
2612
- return getAgent(agent.id, d);
2559
+ sql += " ORDER BY created_at DESC";
2560
+ const rows = d.query(sql).all(...params);
2561
+ return rows.map(parseEntityMemoryRow);
2613
2562
  }
2614
- var CONFLICT_WINDOW_MS;
2615
- var init_agents = __esm(() => {
2616
- init_types();
2563
+ var init_entity_memories = __esm(() => {
2617
2564
  init_database();
2618
- CONFLICT_WINDOW_MS = 30 * 60 * 1000;
2565
+ init_memories();
2619
2566
  });
2620
2567
 
2621
- // src/db/projects.ts
2622
- function parseProjectRow(row) {
2568
+ // src/db/memories.ts
2569
+ function runEntityExtraction(_memory, _projectId, _d) {}
2570
+ function parseMemoryRow(row) {
2623
2571
  return {
2624
2572
  id: row["id"],
2625
- name: row["name"],
2626
- path: row["path"],
2627
- description: row["description"] || null,
2628
- memory_prefix: row["memory_prefix"] || null,
2573
+ key: row["key"],
2574
+ value: row["value"],
2575
+ category: row["category"],
2576
+ scope: row["scope"],
2577
+ summary: row["summary"] || null,
2578
+ tags: JSON.parse(row["tags"] || "[]"),
2579
+ importance: row["importance"],
2580
+ source: row["source"],
2581
+ status: row["status"],
2582
+ pinned: !!row["pinned"],
2583
+ agent_id: row["agent_id"] || null,
2584
+ project_id: row["project_id"] || null,
2585
+ session_id: row["session_id"] || null,
2586
+ metadata: JSON.parse(row["metadata"] || "{}"),
2587
+ access_count: row["access_count"],
2588
+ version: row["version"],
2589
+ expires_at: row["expires_at"] || null,
2629
2590
  created_at: row["created_at"],
2630
- updated_at: row["updated_at"]
2591
+ updated_at: row["updated_at"],
2592
+ accessed_at: row["accessed_at"] || null
2631
2593
  };
2632
2594
  }
2633
- function registerProject(name, path, description, memoryPrefix, db) {
2595
+ function createMemory(input, dedupeMode = "merge", db) {
2634
2596
  const d = db || getDatabase();
2635
2597
  const timestamp = now();
2636
- const existing = d.query("SELECT * FROM projects WHERE path = ?").get(path);
2637
- if (existing) {
2638
- const existingId = existing["id"];
2639
- d.run("UPDATE projects SET updated_at = ? WHERE id = ?", [
2640
- timestamp,
2641
- existingId
2642
- ]);
2643
- return parseProjectRow(existing);
2598
+ let expiresAt = input.expires_at || null;
2599
+ if (input.ttl_ms && !expiresAt) {
2600
+ expiresAt = new Date(Date.now() + input.ttl_ms).toISOString();
2644
2601
  }
2645
2602
  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);
2648
- }
2649
- function getProject(idOrPath, db) {
2650
- const d = db || getDatabase();
2651
- let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
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;
2661
- }
2662
- function listProjects(db) {
2663
- const d = db || getDatabase();
2664
- const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
2665
- return rows.map(parseProjectRow);
2666
- }
2667
- var init_projects = __esm(() => {
2668
- init_database();
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(" ");
2677
- }
2678
- function extractEntities(memory, db) {
2679
- const text = getSearchText(memory);
2680
- const entityMap = new Map;
2681
- function add(name, type, confidence) {
2682
- const normalized = name.toLowerCase();
2683
- if (normalized.length < 3)
2684
- return;
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);
2698
- }
2699
- try {
2700
- const d = db || getDatabase();
2701
- const agents = listAgents(d);
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);
2603
+ const tags = input.tags || [];
2604
+ const tagsJson = JSON.stringify(tags);
2605
+ const metadataJson = JSON.stringify(input.metadata || {});
2606
+ const safeValue = redactSecrets(input.value);
2607
+ const safeSummary = input.summary ? redactSecrets(input.summary) : null;
2608
+ if (dedupeMode === "merge") {
2609
+ const existing = d.query(`SELECT id, version FROM memories
2610
+ WHERE key = ? AND scope = ?
2611
+ AND COALESCE(agent_id, '') = ?
2612
+ AND COALESCE(project_id, '') = ?
2613
+ AND COALESCE(session_id, '') = ?`).get(input.key, input.scope || "private", input.agent_id || "", input.project_id || "", input.session_id || "");
2614
+ if (existing) {
2615
+ d.run(`UPDATE memories SET
2616
+ value = ?, category = ?, summary = ?, tags = ?,
2617
+ importance = ?, metadata = ?, expires_at = ?,
2618
+ pinned = COALESCE(pinned, 0),
2619
+ version = version + 1, updated_at = ?
2620
+ WHERE id = ?`, [
2621
+ safeValue,
2622
+ input.category || "knowledge",
2623
+ safeSummary,
2624
+ tagsJson,
2625
+ input.importance ?? 5,
2626
+ metadataJson,
2627
+ expiresAt,
2628
+ timestamp,
2629
+ existing.id
2630
+ ]);
2631
+ d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
2632
+ const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
2633
+ for (const tag of tags) {
2634
+ insertTag2.run(existing.id, tag);
2718
2635
  }
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);
2636
+ const merged = getMemory(existing.id, d);
2637
+ try {
2638
+ const oldLinks = getEntityMemoryLinks(undefined, merged.id, d);
2639
+ for (const link of oldLinks) {
2640
+ unlinkEntityFromMemory(link.entity_id, merged.id, d);
2641
+ }
2642
+ runEntityExtraction(merged, input.project_id, d);
2643
+ } catch {}
2644
+ return merged;
2726
2645
  }
2727
2646
  }
2728
- for (const match of text.matchAll(PASCAL_CASE_RE)) {
2729
- add(match[1], "concept", 0.5);
2730
- }
2731
- return Array.from(entityMap.values()).sort((a, b) => b.confidence - a.confidence);
2732
- }
2733
- var TECH_KEYWORDS, FILE_PATH_RE, URL_RE, NPM_PACKAGE_RE, PASCAL_CASE_RE;
2734
- var init_extractor = __esm(() => {
2735
- init_database();
2736
- init_agents();
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;
2781
- });
2782
-
2783
- // src/db/entities.ts
2784
- function parseEntityRow(row) {
2785
- return {
2786
- id: row["id"],
2787
- name: row["name"],
2788
- type: row["type"],
2789
- description: row["description"] || null,
2790
- metadata: JSON.parse(row["metadata"] || "{}"),
2791
- project_id: row["project_id"] || null,
2792
- created_at: row["created_at"],
2793
- updated_at: row["updated_at"]
2794
- };
2795
- }
2796
- function createEntity(input, db) {
2797
- const d = db || getDatabase();
2798
- const timestamp = now();
2799
- const metadataJson = JSON.stringify(input.metadata || {});
2800
- const existing = d.query(`SELECT * FROM entities
2801
- WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
2802
- if (existing) {
2803
- const sets = ["updated_at = ?"];
2804
- const params = [timestamp];
2805
- if (input.description !== undefined) {
2806
- sets.push("description = ?");
2807
- params.push(input.description);
2808
- }
2809
- if (input.metadata !== undefined) {
2810
- sets.push("metadata = ?");
2811
- params.push(metadataJson);
2812
- }
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
- }
2818
- const id = shortUuid();
2819
- d.run(`INSERT INTO entities (id, name, type, description, metadata, project_id, created_at, updated_at)
2820
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
2647
+ 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)
2648
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, 0, 1, ?, ?, ?)`, [
2821
2649
  id,
2822
- input.name,
2823
- input.type,
2824
- input.description || null,
2825
- metadataJson,
2650
+ input.key,
2651
+ input.value,
2652
+ input.category || "knowledge",
2653
+ input.scope || "private",
2654
+ input.summary || null,
2655
+ tagsJson,
2656
+ input.importance ?? 5,
2657
+ input.source || "agent",
2658
+ input.agent_id || null,
2826
2659
  input.project_id || null,
2660
+ input.session_id || null,
2661
+ metadataJson,
2662
+ expiresAt,
2827
2663
  timestamp,
2828
2664
  timestamp
2829
2665
  ]);
2830
- return getEntity(id, d);
2666
+ const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
2667
+ for (const tag of tags) {
2668
+ insertTag.run(id, tag);
2669
+ }
2670
+ const memory = getMemory(id, d);
2671
+ try {
2672
+ runEntityExtraction(memory, input.project_id, d);
2673
+ } catch {}
2674
+ return memory;
2831
2675
  }
2832
- function getEntity(id, db) {
2676
+ function getMemory(id, db) {
2833
2677
  const d = db || getDatabase();
2834
- const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
2678
+ const row = d.query("SELECT * FROM memories WHERE id = ?").get(id);
2835
2679
  if (!row)
2836
- throw new EntityNotFoundError(id);
2837
- return parseEntityRow(row);
2680
+ return null;
2681
+ return parseMemoryRow(row);
2838
2682
  }
2839
- function getEntityByName(name, type, projectId, db) {
2683
+ function getMemoryByKey(key, scope, agentId, projectId, sessionId, db) {
2840
2684
  const d = db || getDatabase();
2841
- let sql = "SELECT * FROM entities WHERE name = ?";
2842
- const params = [name];
2843
- if (type) {
2844
- sql += " AND type = ?";
2845
- params.push(type);
2685
+ let sql = "SELECT * FROM memories WHERE key = ?";
2686
+ const params = [key];
2687
+ if (scope) {
2688
+ sql += " AND scope = ?";
2689
+ params.push(scope);
2846
2690
  }
2847
- if (projectId !== undefined) {
2691
+ if (agentId) {
2692
+ sql += " AND agent_id = ?";
2693
+ params.push(agentId);
2694
+ }
2695
+ if (projectId) {
2848
2696
  sql += " AND project_id = ?";
2849
2697
  params.push(projectId);
2850
2698
  }
2851
- sql += " LIMIT 1";
2699
+ if (sessionId) {
2700
+ sql += " AND session_id = ?";
2701
+ params.push(sessionId);
2702
+ }
2703
+ sql += " AND status = 'active' ORDER BY importance DESC LIMIT 1";
2852
2704
  const row = d.query(sql).get(...params);
2853
2705
  if (!row)
2854
2706
  return null;
2855
- return parseEntityRow(row);
2707
+ return parseMemoryRow(row);
2856
2708
  }
2857
- function listEntities(filter = {}, db) {
2709
+ function getMemoriesByKey(key, scope, agentId, projectId, db) {
2858
2710
  const d = db || getDatabase();
2859
- const conditions = [];
2860
- const params = [];
2861
- if (filter.type) {
2862
- conditions.push("type = ?");
2863
- params.push(filter.type);
2711
+ let sql = "SELECT * FROM memories WHERE key = ?";
2712
+ const params = [key];
2713
+ if (scope) {
2714
+ sql += " AND scope = ?";
2715
+ params.push(scope);
2864
2716
  }
2865
- if (filter.project_id) {
2866
- conditions.push("project_id = ?");
2867
- params.push(filter.project_id);
2717
+ if (agentId) {
2718
+ sql += " AND agent_id = ?";
2719
+ params.push(agentId);
2868
2720
  }
2869
- if (filter.search) {
2870
- conditions.push("(name LIKE ? OR description LIKE ?)");
2871
- const term = `%${filter.search}%`;
2872
- params.push(term, term);
2721
+ if (projectId) {
2722
+ sql += " AND project_id = ?";
2723
+ params.push(projectId);
2873
2724
  }
2874
- let sql = "SELECT * FROM entities";
2725
+ sql += " AND status = 'active' ORDER BY importance DESC";
2726
+ const rows = d.query(sql).all(...params);
2727
+ return rows.map(parseMemoryRow);
2728
+ }
2729
+ function listMemories(filter, db) {
2730
+ const d = db || getDatabase();
2731
+ const conditions = [];
2732
+ const params = [];
2733
+ if (filter) {
2734
+ if (filter.scope) {
2735
+ if (Array.isArray(filter.scope)) {
2736
+ conditions.push(`scope IN (${filter.scope.map(() => "?").join(",")})`);
2737
+ params.push(...filter.scope);
2738
+ } else {
2739
+ conditions.push("scope = ?");
2740
+ params.push(filter.scope);
2741
+ }
2742
+ }
2743
+ if (filter.category) {
2744
+ if (Array.isArray(filter.category)) {
2745
+ conditions.push(`category IN (${filter.category.map(() => "?").join(",")})`);
2746
+ params.push(...filter.category);
2747
+ } else {
2748
+ conditions.push("category = ?");
2749
+ params.push(filter.category);
2750
+ }
2751
+ }
2752
+ if (filter.source) {
2753
+ if (Array.isArray(filter.source)) {
2754
+ conditions.push(`source IN (${filter.source.map(() => "?").join(",")})`);
2755
+ params.push(...filter.source);
2756
+ } else {
2757
+ conditions.push("source = ?");
2758
+ params.push(filter.source);
2759
+ }
2760
+ }
2761
+ if (filter.status) {
2762
+ if (Array.isArray(filter.status)) {
2763
+ conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
2764
+ params.push(...filter.status);
2765
+ } else {
2766
+ conditions.push("status = ?");
2767
+ params.push(filter.status);
2768
+ }
2769
+ } else {
2770
+ conditions.push("status = 'active'");
2771
+ }
2772
+ if (filter.project_id) {
2773
+ conditions.push("project_id = ?");
2774
+ params.push(filter.project_id);
2775
+ }
2776
+ if (filter.agent_id) {
2777
+ conditions.push("agent_id = ?");
2778
+ params.push(filter.agent_id);
2779
+ }
2780
+ if (filter.session_id) {
2781
+ conditions.push("session_id = ?");
2782
+ params.push(filter.session_id);
2783
+ }
2784
+ if (filter.min_importance) {
2785
+ conditions.push("importance >= ?");
2786
+ params.push(filter.min_importance);
2787
+ }
2788
+ if (filter.pinned !== undefined) {
2789
+ conditions.push("pinned = ?");
2790
+ params.push(filter.pinned ? 1 : 0);
2791
+ }
2792
+ if (filter.tags && filter.tags.length > 0) {
2793
+ for (const tag of filter.tags) {
2794
+ conditions.push("id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
2795
+ params.push(tag);
2796
+ }
2797
+ }
2798
+ if (filter.search) {
2799
+ conditions.push("(key LIKE ? OR value LIKE ? OR summary LIKE ?)");
2800
+ const term = `%${filter.search}%`;
2801
+ params.push(term, term, term);
2802
+ }
2803
+ } else {
2804
+ conditions.push("status = 'active'");
2805
+ }
2806
+ let sql = "SELECT * FROM memories";
2875
2807
  if (conditions.length > 0) {
2876
2808
  sql += ` WHERE ${conditions.join(" AND ")}`;
2877
2809
  }
2878
- sql += " ORDER BY updated_at DESC";
2879
- if (filter.limit) {
2810
+ sql += " ORDER BY importance DESC, created_at DESC";
2811
+ if (filter?.limit) {
2880
2812
  sql += " LIMIT ?";
2881
2813
  params.push(filter.limit);
2882
2814
  }
2883
- if (filter.offset) {
2815
+ if (filter?.offset) {
2884
2816
  sql += " OFFSET ?";
2885
2817
  params.push(filter.offset);
2886
2818
  }
2887
2819
  const rows = d.query(sql).all(...params);
2888
- return rows.map(parseEntityRow);
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);
2895
- }
2896
- function mergeEntities(sourceId, targetId, db) {
2897
- const d = db || getDatabase();
2898
- getEntity(sourceId, d);
2899
- getEntity(targetId, d);
2900
- d.run(`UPDATE OR IGNORE relations SET source_entity_id = ? WHERE source_entity_id = ?`, [targetId, sourceId]);
2901
- d.run(`UPDATE OR IGNORE relations SET target_entity_id = ? WHERE target_entity_id = ?`, [targetId, sourceId]);
2902
- d.run("DELETE FROM relations WHERE source_entity_id = ? OR target_entity_id = ?", [
2903
- sourceId,
2904
- sourceId
2905
- ]);
2906
- d.run(`UPDATE OR IGNORE entity_memories SET entity_id = ? WHERE entity_id = ?`, [targetId, sourceId]);
2907
- d.run("DELETE FROM entity_memories WHERE entity_id = ?", [sourceId]);
2908
- d.run("DELETE FROM entities WHERE id = ?", [sourceId]);
2909
- d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), targetId]);
2910
- return getEntity(targetId, d);
2911
- }
2912
- var init_entities = __esm(() => {
2913
- init_database();
2914
- init_types();
2915
- });
2916
-
2917
- // src/db/entity-memories.ts
2918
- function parseEntityMemoryRow(row) {
2919
- return {
2920
- entity_id: row["entity_id"],
2921
- memory_id: row["memory_id"],
2922
- role: row["role"],
2923
- created_at: row["created_at"]
2924
- };
2820
+ return rows.map(parseMemoryRow);
2925
2821
  }
2926
- function linkEntityToMemory(entityId, memoryId, role = "context", db) {
2822
+ function updateMemory(id, input, db) {
2927
2823
  const d = db || getDatabase();
2928
- const timestamp = now();
2929
- d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
2930
- VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
2931
- const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
2932
- return parseEntityMemoryRow(row);
2824
+ const existing = getMemory(id, d);
2825
+ if (!existing)
2826
+ throw new MemoryNotFoundError(id);
2827
+ if (existing.version !== input.version) {
2828
+ throw new VersionConflictError(id, input.version, existing.version);
2829
+ }
2830
+ try {
2831
+ d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, created_at)
2832
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2833
+ uuid(),
2834
+ existing.id,
2835
+ existing.version,
2836
+ existing.value,
2837
+ existing.importance,
2838
+ existing.scope,
2839
+ existing.category,
2840
+ JSON.stringify(existing.tags),
2841
+ existing.summary,
2842
+ existing.pinned ? 1 : 0,
2843
+ existing.status,
2844
+ existing.updated_at
2845
+ ]);
2846
+ } catch {}
2847
+ const sets = ["version = version + 1", "updated_at = ?"];
2848
+ const params = [now()];
2849
+ if (input.value !== undefined) {
2850
+ sets.push("value = ?");
2851
+ params.push(redactSecrets(input.value));
2852
+ }
2853
+ if (input.category !== undefined) {
2854
+ sets.push("category = ?");
2855
+ params.push(input.category);
2856
+ }
2857
+ if (input.scope !== undefined) {
2858
+ sets.push("scope = ?");
2859
+ params.push(input.scope);
2860
+ }
2861
+ if (input.summary !== undefined) {
2862
+ sets.push("summary = ?");
2863
+ params.push(input.summary);
2864
+ }
2865
+ if (input.importance !== undefined) {
2866
+ sets.push("importance = ?");
2867
+ params.push(input.importance);
2868
+ }
2869
+ if (input.pinned !== undefined) {
2870
+ sets.push("pinned = ?");
2871
+ params.push(input.pinned ? 1 : 0);
2872
+ }
2873
+ if (input.status !== undefined) {
2874
+ sets.push("status = ?");
2875
+ params.push(input.status);
2876
+ }
2877
+ if (input.metadata !== undefined) {
2878
+ sets.push("metadata = ?");
2879
+ params.push(JSON.stringify(input.metadata));
2880
+ }
2881
+ if (input.expires_at !== undefined) {
2882
+ sets.push("expires_at = ?");
2883
+ params.push(input.expires_at);
2884
+ }
2885
+ if (input.tags !== undefined) {
2886
+ sets.push("tags = ?");
2887
+ params.push(JSON.stringify(input.tags));
2888
+ d.run("DELETE FROM memory_tags WHERE memory_id = ?", [id]);
2889
+ const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
2890
+ for (const tag of input.tags) {
2891
+ insertTag.run(id, tag);
2892
+ }
2893
+ }
2894
+ params.push(id);
2895
+ d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
2896
+ const updated = getMemory(id, d);
2897
+ try {
2898
+ if (input.value !== undefined) {
2899
+ const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
2900
+ for (const link of oldLinks) {
2901
+ unlinkEntityFromMemory(link.entity_id, updated.id, d);
2902
+ }
2903
+ runEntityExtraction(updated, existing.project_id || undefined, d);
2904
+ }
2905
+ } catch {}
2906
+ return updated;
2933
2907
  }
2934
- function unlinkEntityFromMemory(entityId, memoryId, db) {
2908
+ function deleteMemory(id, db) {
2935
2909
  const d = db || getDatabase();
2936
- d.run("DELETE FROM entity_memories WHERE entity_id = ? AND memory_id = ?", [entityId, memoryId]);
2910
+ const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
2911
+ return result.changes > 0;
2937
2912
  }
2938
- function getMemoriesForEntity(entityId, db) {
2913
+ function bulkDeleteMemories(ids, db) {
2939
2914
  const d = db || getDatabase();
2940
- const rows = d.query(`SELECT m.* FROM memories m
2941
- INNER JOIN entity_memories em ON em.memory_id = m.id
2942
- WHERE em.entity_id = ?
2943
- ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
2944
- return rows.map(parseMemoryRow);
2915
+ if (ids.length === 0)
2916
+ return 0;
2917
+ const placeholders = ids.map(() => "?").join(",");
2918
+ const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...ids);
2919
+ const count = countRow.c;
2920
+ if (count > 0) {
2921
+ d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, ids);
2922
+ }
2923
+ return count;
2945
2924
  }
2946
- function getEntityMemoryLinks(entityId, memoryId, db) {
2925
+ function touchMemory(id, db) {
2947
2926
  const d = db || getDatabase();
2948
- const conditions = [];
2949
- const params = [];
2950
- if (entityId) {
2951
- conditions.push("entity_id = ?");
2952
- params.push(entityId);
2953
- }
2954
- if (memoryId) {
2955
- conditions.push("memory_id = ?");
2956
- params.push(memoryId);
2927
+ d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
2928
+ }
2929
+ function cleanExpiredMemories(db) {
2930
+ const d = db || getDatabase();
2931
+ const timestamp = now();
2932
+ const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
2933
+ const count = countRow.c;
2934
+ if (count > 0) {
2935
+ d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
2957
2936
  }
2958
- let sql = "SELECT * FROM entity_memories";
2959
- if (conditions.length > 0) {
2960
- sql += ` WHERE ${conditions.join(" AND ")}`;
2937
+ return count;
2938
+ }
2939
+ function getMemoryVersions(memoryId, db) {
2940
+ const d = db || getDatabase();
2941
+ try {
2942
+ const rows = d.query("SELECT * FROM memory_versions WHERE memory_id = ? ORDER BY version ASC").all(memoryId);
2943
+ return rows.map((row) => ({
2944
+ id: row["id"],
2945
+ memory_id: row["memory_id"],
2946
+ version: row["version"],
2947
+ value: row["value"],
2948
+ importance: row["importance"],
2949
+ scope: row["scope"],
2950
+ category: row["category"],
2951
+ tags: JSON.parse(row["tags"] || "[]"),
2952
+ summary: row["summary"] || null,
2953
+ pinned: !!row["pinned"],
2954
+ status: row["status"],
2955
+ created_at: row["created_at"]
2956
+ }));
2957
+ } catch {
2958
+ return [];
2961
2959
  }
2962
- sql += " ORDER BY created_at DESC";
2963
- const rows = d.query(sql).all(...params);
2964
- return rows.map(parseEntityMemoryRow);
2965
2960
  }
2966
- var init_entity_memories = __esm(() => {
2961
+ var init_memories = __esm(() => {
2962
+ init_types();
2967
2963
  init_database();
2968
- init_memories();
2964
+ init_redact();
2965
+ init_entity_memories();
2969
2966
  });
2970
2967
 
2971
- // src/db/relations.ts
2972
- function parseRelationRow(row) {
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) {
2968
+ // src/db/entities.ts
2969
+ function parseEntityRow(row) {
2984
2970
  return {
2985
2971
  id: row["id"],
2986
2972
  name: row["name"],
@@ -2992,299 +2978,129 @@ function parseEntityRow2(row) {
2992
2978
  updated_at: row["updated_at"]
2993
2979
  };
2994
2980
  }
2995
- function createRelation(input, db) {
2981
+ function createEntity(input, db) {
2996
2982
  const d = db || getDatabase();
2997
- const id = shortUuid();
2998
2983
  const timestamp = now();
2999
- const weight = input.weight ?? 1;
3000
- const metadata = JSON.stringify(input.metadata ?? {});
3001
- d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
3002
- VALUES (?, ?, ?, ?, ?, ?, ?)
3003
- ON CONFLICT(source_entity_id, target_entity_id, relation_type)
3004
- DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
3005
- const row = d.query(`SELECT * FROM relations
3006
- WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
3007
- return parseRelationRow(row);
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);
2984
+ const metadataJson = JSON.stringify(input.metadata || {});
2985
+ const existing = d.query(`SELECT * FROM entities
2986
+ WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
2987
+ if (existing) {
2988
+ const sets = ["updated_at = ?"];
2989
+ const params = [timestamp];
2990
+ if (input.description !== undefined) {
2991
+ sets.push("description = ?");
2992
+ params.push(input.description);
3024
2993
  }
2994
+ if (input.metadata !== undefined) {
2995
+ sets.push("metadata = ?");
2996
+ params.push(metadataJson);
2997
+ }
2998
+ const existingId = existing["id"];
2999
+ params.push(existingId);
3000
+ d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
3001
+ return getEntity(existingId, d);
3025
3002
  }
3026
- if (filter.relation_type) {
3027
- conditions.push("relation_type = ?");
3028
- params.push(filter.relation_type);
3029
- }
3030
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3031
- const rows = d.query(`SELECT * FROM relations ${where} ORDER BY created_at DESC`).all(...params);
3032
- return rows.map(parseRelationRow);
3003
+ const id = shortUuid();
3004
+ d.run(`INSERT INTO entities (id, name, type, description, metadata, project_id, created_at, updated_at)
3005
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
3006
+ id,
3007
+ input.name,
3008
+ input.type,
3009
+ input.description || null,
3010
+ metadataJson,
3011
+ input.project_id || null,
3012
+ timestamp,
3013
+ timestamp
3014
+ ]);
3015
+ return getEntity(id, d);
3033
3016
  }
3034
- function deleteRelation(id, db) {
3017
+ function getEntity(id, db) {
3035
3018
  const d = db || getDatabase();
3036
- const result = d.run("DELETE FROM relations WHERE id = ?", [id]);
3037
- if (result.changes === 0)
3038
- throw new Error(`Relation not found: ${id}`);
3019
+ const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
3020
+ if (!row)
3021
+ throw new EntityNotFoundError(id);
3022
+ return parseEntityRow(row);
3039
3023
  }
3040
- function getRelatedEntities(entityId, relationType, db) {
3024
+ function getEntityByName(name, type, projectId, db) {
3041
3025
  const d = db || getDatabase();
3042
- let sql;
3043
- const params = [];
3044
- if (relationType) {
3045
- sql = `
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);
3026
+ let sql = "SELECT * FROM entities WHERE name = ?";
3027
+ const params = [name];
3028
+ if (type) {
3029
+ sql += " AND type = ?";
3030
+ params.push(type);
3063
3031
  }
3064
- const rows = d.query(sql).all(...params);
3065
- return rows.map(parseEntityRow2);
3066
- }
3067
- function getEntityGraph(entityId, depth = 2, db) {
3068
- const d = db || getDatabase();
3069
- const entityRows = d.query(`WITH RECURSIVE graph(id, depth) AS (
3070
- VALUES(?, 0)
3071
- UNION
3072
- SELECT CASE WHEN r.source_entity_id = g.id THEN r.target_entity_id ELSE r.source_entity_id END, g.depth + 1
3073
- FROM relations r JOIN graph g ON (r.source_entity_id = g.id OR r.target_entity_id = g.id)
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: [] };
3032
+ if (projectId !== undefined) {
3033
+ sql += " AND project_id = ?";
3034
+ params.push(projectId);
3081
3035
  }
3082
- const placeholders = Array.from(entityIds).map(() => "?").join(",");
3083
- const relationRows = d.query(`SELECT * FROM relations
3084
- WHERE source_entity_id IN (${placeholders})
3085
- AND target_entity_id IN (${placeholders})`).all(...Array.from(entityIds), ...Array.from(entityIds));
3086
- const relations = relationRows.map(parseRelationRow);
3087
- return { entities, relations };
3088
- }
3089
- function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
3090
- const d = db || getDatabase();
3091
- const rows = d.query(`WITH RECURSIVE path(id, trail, depth) AS (
3092
- SELECT ?, ?, 0
3093
- UNION
3094
- SELECT
3095
- CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
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)
3036
+ sql += " LIMIT 1";
3037
+ const row = d.query(sql).get(...params);
3038
+ if (!row)
3104
3039
  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));
3111
- }
3112
- return entities.length > 0 ? entities : null;
3113
- }
3114
- var init_relations = __esm(() => {
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
- }
3132
- }
3133
- return result;
3134
- }
3135
- function isValidScope(value) {
3136
- return VALID_SCOPES.includes(value);
3137
- }
3138
- function isValidCategory(value) {
3139
- return VALID_CATEGORIES.includes(value);
3040
+ return parseEntityRow(row);
3140
3041
  }
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 {}
3042
+ function listEntities(filter = {}, db) {
3043
+ const d = db || getDatabase();
3044
+ const conditions = [];
3045
+ const params = [];
3046
+ if (filter.type) {
3047
+ conditions.push("type = ?");
3048
+ params.push(filter.type);
3149
3049
  }
3150
- const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
3151
- const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
3152
- if (envScope && isValidScope(envScope)) {
3153
- merged.default_scope = envScope;
3050
+ if (filter.project_id) {
3051
+ conditions.push("project_id = ?");
3052
+ params.push(filter.project_id);
3154
3053
  }
3155
- const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
3156
- if (envCategory && isValidCategory(envCategory)) {
3157
- merged.default_category = envCategory;
3054
+ if (filter.search) {
3055
+ conditions.push("(name LIKE ? OR description LIKE ?)");
3056
+ const term = `%${filter.search}%`;
3057
+ params.push(term, term);
3158
3058
  }
3159
- const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
3160
- if (envImportance) {
3161
- const parsed = parseInt(envImportance, 10);
3162
- if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
3163
- merged.default_importance = parsed;
3164
- }
3059
+ let sql = "SELECT * FROM entities";
3060
+ if (conditions.length > 0) {
3061
+ sql += ` WHERE ${conditions.join(" AND ")}`;
3165
3062
  }
3166
- return merged;
3167
- }
3168
- function profilesDir() {
3169
- return join2(homedir(), ".mementos", "profiles");
3170
- }
3171
- function globalConfigPath() {
3172
- return join2(homedir(), ".mementos", "config.json");
3173
- }
3174
- function readGlobalConfig() {
3175
- const p = globalConfigPath();
3176
- if (!existsSync2(p))
3177
- return {};
3178
- try {
3179
- return JSON.parse(readFileSync(p, "utf-8"));
3180
- } catch {
3181
- return {};
3063
+ sql += " ORDER BY updated_at DESC";
3064
+ if (filter.limit) {
3065
+ sql += " LIMIT ?";
3066
+ params.push(filter.limit);
3182
3067
  }
3183
- }
3184
- function writeGlobalConfig(data) {
3185
- const p = globalConfigPath();
3186
- ensureDir2(dirname2(p));
3187
- writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");
3188
- }
3189
- function getActiveProfile() {
3190
- const envProfile = process.env["MEMENTOS_PROFILE"];
3191
- if (envProfile)
3192
- return envProfile.trim();
3193
- const cfg = readGlobalConfig();
3194
- return cfg["active_profile"] || null;
3195
- }
3196
- function setActiveProfile(name) {
3197
- const cfg = readGlobalConfig();
3198
- if (name === null) {
3199
- delete cfg["active_profile"];
3200
- } else {
3201
- cfg["active_profile"] = name;
3068
+ if (filter.offset) {
3069
+ sql += " OFFSET ?";
3070
+ params.push(filter.offset);
3202
3071
  }
3203
- writeGlobalConfig(cfg);
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();
3072
+ const rows = d.query(sql).all(...params);
3073
+ return rows.map(parseEntityRow);
3210
3074
  }
3211
- function deleteProfile(name) {
3212
- const dbPath = join2(profilesDir(), `${name}.db`);
3213
- if (!existsSync2(dbPath))
3214
- return false;
3215
- unlinkSync(dbPath);
3216
- if (getActiveProfile() === name)
3217
- setActiveProfile(null);
3218
- return true;
3075
+ function deleteEntity(id, db) {
3076
+ const d = db || getDatabase();
3077
+ const result = d.run("DELETE FROM entities WHERE id = ?", [id]);
3078
+ if (result.changes === 0)
3079
+ throw new EntityNotFoundError(id);
3219
3080
  }
3220
- function ensureDir2(dir) {
3221
- if (!existsSync2(dir)) {
3222
- mkdirSync2(dir, { recursive: true });
3223
- }
3081
+ function mergeEntities(sourceId, targetId, db) {
3082
+ const d = db || getDatabase();
3083
+ getEntity(sourceId, d);
3084
+ getEntity(targetId, d);
3085
+ d.run(`UPDATE OR IGNORE relations SET source_entity_id = ? WHERE source_entity_id = ?`, [targetId, sourceId]);
3086
+ d.run(`UPDATE OR IGNORE relations SET target_entity_id = ? WHERE target_entity_id = ?`, [targetId, sourceId]);
3087
+ d.run("DELETE FROM relations WHERE source_entity_id = ? OR target_entity_id = ?", [
3088
+ sourceId,
3089
+ sourceId
3090
+ ]);
3091
+ d.run(`UPDATE OR IGNORE entity_memories SET entity_id = ? WHERE entity_id = ?`, [targetId, sourceId]);
3092
+ d.run("DELETE FROM entity_memories WHERE entity_id = ?", [sourceId]);
3093
+ d.run("DELETE FROM entities WHERE id = ?", [sourceId]);
3094
+ d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), targetId]);
3095
+ return getEntity(targetId, d);
3224
3096
  }
3225
- var DEFAULT_CONFIG, VALID_SCOPES, VALID_CATEGORIES;
3226
- var init_config = __esm(() => {
3227
- DEFAULT_CONFIG = {
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
- ];
3097
+ var init_entities = __esm(() => {
3098
+ init_database();
3099
+ init_types();
3262
3100
  });
3263
3101
 
3264
- // src/db/memories.ts
3265
- function runEntityExtraction(memory, projectId, d) {
3266
- const config = loadConfig();
3267
- if (config.extraction?.enabled === false)
3268
- return;
3269
- const extracted = extractEntities(memory, d);
3270
- const minConfidence = config.extraction?.min_confidence ?? 0.5;
3271
- const entityIds = [];
3272
- for (const ext of extracted) {
3273
- if (ext.confidence >= minConfidence) {
3274
- const entity = createEntity({ name: ext.name, type: ext.type, project_id: projectId }, d);
3275
- linkEntityToMemory(entity.id, memory.id, "context", d);
3276
- entityIds.push(entity.id);
3277
- }
3278
- }
3279
- for (let i = 0;i < entityIds.length; i++) {
3280
- for (let j = i + 1;j < entityIds.length; j++) {
3281
- try {
3282
- createRelation({ source_entity_id: entityIds[i], target_entity_id: entityIds[j], relation_type: "related_to" }, d);
3283
- } catch {}
3284
- }
3285
- }
3286
- }
3287
- function parseMemoryRow(row) {
3102
+ // src/lib/search.ts
3103
+ function parseMemoryRow2(row) {
3288
3104
  return {
3289
3105
  id: row["id"],
3290
3106
  key: row["key"],
@@ -3309,381 +3125,686 @@ function parseMemoryRow(row) {
3309
3125
  accessed_at: row["accessed_at"] || null
3310
3126
  };
3311
3127
  }
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);
3128
+ function preprocessQuery(query) {
3129
+ let q = query.trim();
3130
+ q = q.replace(/\s+/g, " ");
3131
+ q = q.normalize("NFC");
3132
+ return q;
3133
+ }
3134
+ function escapeLikePattern(s) {
3135
+ return s.replace(/%/g, "\\%").replace(/_/g, "\\_");
3136
+ }
3137
+ function removeStopWords(tokens) {
3138
+ if (tokens.length <= 1)
3139
+ return tokens;
3140
+ const filtered = tokens.filter((t) => !STOP_WORDS.has(t.toLowerCase()));
3141
+ return filtered.length > 0 ? filtered : tokens;
3142
+ }
3143
+ function extractHighlights(memory, queryLower) {
3144
+ const highlights = [];
3145
+ const tokens = queryLower.split(/\s+/).filter(Boolean);
3146
+ for (const field of ["key", "value", "summary"]) {
3147
+ const text = field === "summary" ? memory.summary : memory[field];
3148
+ if (!text)
3149
+ continue;
3150
+ const textLower = text.toLowerCase();
3151
+ const searchTerms = [queryLower, ...tokens].filter(Boolean);
3152
+ for (const term of searchTerms) {
3153
+ const idx = textLower.indexOf(term);
3154
+ if (idx !== -1) {
3155
+ const start = Math.max(0, idx - 30);
3156
+ const end = Math.min(text.length, idx + term.length + 30);
3157
+ const prefix = start > 0 ? "..." : "";
3158
+ const suffix = end < text.length ? "..." : "";
3159
+ highlights.push({
3160
+ field,
3161
+ snippet: prefix + text.slice(start, end) + suffix
3162
+ });
3163
+ break;
3352
3164
  }
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
3165
  }
3363
3166
  }
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);
3167
+ for (const tag of memory.tags) {
3168
+ if (tag.toLowerCase().includes(queryLower) || tokens.some((t) => tag.toLowerCase().includes(t))) {
3169
+ highlights.push({ field: "tag", snippet: tag });
3170
+ }
3386
3171
  }
3387
- const memory = getMemory(id, d);
3388
- try {
3389
- runEntityExtraction(memory, input.project_id, d);
3390
- } catch {}
3391
- return memory;
3172
+ return highlights;
3392
3173
  }
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);
3174
+ function determineMatchType(memory, queryLower) {
3175
+ if (memory.key.toLowerCase() === queryLower)
3176
+ return "exact";
3177
+ if (memory.tags.some((t) => t.toLowerCase() === queryLower))
3178
+ return "tag";
3179
+ if (memory.tags.some((t) => t.toLowerCase().includes(queryLower)))
3180
+ return "tag";
3181
+ return "fuzzy";
3399
3182
  }
3400
- function getMemoryByKey(key, scope, agentId, projectId, sessionId, db) {
3401
- const d = db || getDatabase();
3402
- let sql = "SELECT * FROM memories WHERE key = ?";
3403
- const params = [key];
3404
- if (scope) {
3405
- sql += " AND scope = ?";
3406
- params.push(scope);
3183
+ function computeScore(memory, queryLower) {
3184
+ const fieldScores = [];
3185
+ const keyLower = memory.key.toLowerCase();
3186
+ if (keyLower === queryLower) {
3187
+ fieldScores.push(10);
3188
+ } else if (keyLower.includes(queryLower)) {
3189
+ fieldScores.push(7);
3407
3190
  }
3408
- if (agentId) {
3409
- sql += " AND agent_id = ?";
3410
- params.push(agentId);
3191
+ if (memory.tags.some((t) => t.toLowerCase() === queryLower)) {
3192
+ fieldScores.push(6);
3193
+ } else if (memory.tags.some((t) => t.toLowerCase().includes(queryLower))) {
3194
+ fieldScores.push(3);
3411
3195
  }
3412
- if (projectId) {
3413
- sql += " AND project_id = ?";
3414
- params.push(projectId);
3196
+ if (memory.summary && memory.summary.toLowerCase().includes(queryLower)) {
3197
+ fieldScores.push(4);
3415
3198
  }
3416
- if (sessionId) {
3417
- sql += " AND session_id = ?";
3418
- params.push(sessionId);
3199
+ if (memory.value.toLowerCase().includes(queryLower)) {
3200
+ fieldScores.push(3);
3419
3201
  }
3420
- sql += " AND status = 'active' ORDER BY importance DESC LIMIT 1";
3421
- const row = d.query(sql).get(...params);
3422
- if (!row)
3423
- return null;
3424
- return parseMemoryRow(row);
3425
- }
3426
- function getMemoriesByKey(key, scope, agentId, projectId, db) {
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);
3202
+ const metadataStr = JSON.stringify(memory.metadata).toLowerCase();
3203
+ if (metadataStr !== "{}" && metadataStr.includes(queryLower)) {
3204
+ fieldScores.push(2);
3433
3205
  }
3434
- if (agentId) {
3435
- sql += " AND agent_id = ?";
3436
- params.push(agentId);
3206
+ fieldScores.sort((a, b) => b - a);
3207
+ const diminishingMultipliers = [1, 0.5, 0.25, 0.15, 0.15];
3208
+ let score = 0;
3209
+ for (let i = 0;i < fieldScores.length; i++) {
3210
+ score += fieldScores[i] * (diminishingMultipliers[i] ?? 0.15);
3437
3211
  }
3438
- if (projectId) {
3439
- sql += " AND project_id = ?";
3440
- params.push(projectId);
3212
+ const { phrases } = extractQuotedPhrases(queryLower);
3213
+ for (const phrase of phrases) {
3214
+ if (keyLower.includes(phrase))
3215
+ score += 8;
3216
+ if (memory.value.toLowerCase().includes(phrase))
3217
+ score += 5;
3218
+ if (memory.summary && memory.summary.toLowerCase().includes(phrase))
3219
+ score += 4;
3441
3220
  }
3442
- sql += " AND status = 'active' ORDER BY importance DESC";
3443
- const rows = d.query(sql).all(...params);
3444
- return rows.map(parseMemoryRow);
3445
- }
3446
- function listMemories(filter, db) {
3447
- const d = db || getDatabase();
3448
- const conditions = [];
3449
- const params = [];
3450
- if (filter) {
3451
- if (filter.scope) {
3452
- if (Array.isArray(filter.scope)) {
3453
- conditions.push(`scope IN (${filter.scope.map(() => "?").join(",")})`);
3454
- params.push(...filter.scope);
3455
- } else {
3456
- conditions.push("scope = ?");
3457
- params.push(filter.scope);
3221
+ const { remainder } = extractQuotedPhrases(queryLower);
3222
+ const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
3223
+ if (tokens.length > 1) {
3224
+ let tokenScore = 0;
3225
+ for (const token of tokens) {
3226
+ if (keyLower === token) {
3227
+ tokenScore += 10 / tokens.length;
3228
+ } else if (keyLower.includes(token)) {
3229
+ tokenScore += 7 / tokens.length;
3458
3230
  }
3459
- }
3460
- if (filter.category) {
3461
- if (Array.isArray(filter.category)) {
3462
- conditions.push(`category IN (${filter.category.map(() => "?").join(",")})`);
3463
- params.push(...filter.category);
3464
- } else {
3465
- conditions.push("category = ?");
3466
- params.push(filter.category);
3231
+ if (memory.tags.some((t) => t.toLowerCase() === token)) {
3232
+ tokenScore += 6 / tokens.length;
3233
+ } else if (memory.tags.some((t) => t.toLowerCase().includes(token))) {
3234
+ tokenScore += 3 / tokens.length;
3467
3235
  }
3468
- }
3469
- if (filter.source) {
3470
- if (Array.isArray(filter.source)) {
3471
- conditions.push(`source IN (${filter.source.map(() => "?").join(",")})`);
3472
- params.push(...filter.source);
3473
- } else {
3474
- conditions.push("source = ?");
3475
- params.push(filter.source);
3236
+ if (memory.summary && memory.summary.toLowerCase().includes(token)) {
3237
+ tokenScore += 4 / tokens.length;
3476
3238
  }
3477
- }
3478
- if (filter.status) {
3479
- if (Array.isArray(filter.status)) {
3480
- conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
3481
- params.push(...filter.status);
3482
- } else {
3483
- conditions.push("status = ?");
3484
- params.push(filter.status);
3239
+ if (memory.value.toLowerCase().includes(token)) {
3240
+ tokenScore += 3 / tokens.length;
3241
+ }
3242
+ if (metadataStr !== "{}" && metadataStr.includes(token)) {
3243
+ tokenScore += 2 / tokens.length;
3485
3244
  }
3486
- } else {
3487
- conditions.push("status = 'active'");
3488
- }
3489
- if (filter.project_id) {
3490
- conditions.push("project_id = ?");
3491
- params.push(filter.project_id);
3492
3245
  }
3493
- if (filter.agent_id) {
3494
- conditions.push("agent_id = ?");
3495
- params.push(filter.agent_id);
3246
+ if (score > 0) {
3247
+ score += tokenScore * 0.3;
3248
+ } else {
3249
+ score += tokenScore;
3496
3250
  }
3497
- if (filter.session_id) {
3498
- conditions.push("session_id = ?");
3499
- params.push(filter.session_id);
3500
- }
3501
- if (filter.min_importance) {
3502
- conditions.push("importance >= ?");
3503
- params.push(filter.min_importance);
3504
- }
3505
- if (filter.pinned !== undefined) {
3506
- conditions.push("pinned = ?");
3507
- params.push(filter.pinned ? 1 : 0);
3508
- }
3509
- if (filter.tags && filter.tags.length > 0) {
3510
- for (const tag of filter.tags) {
3511
- conditions.push("id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
3512
- params.push(tag);
3513
- }
3514
- }
3515
- if (filter.search) {
3516
- conditions.push("(key LIKE ? OR value LIKE ? OR summary LIKE ?)");
3517
- const term = `%${filter.search}%`;
3518
- params.push(term, term, term);
3519
- }
3520
- } else {
3521
- conditions.push("status = 'active'");
3522
- }
3523
- let sql = "SELECT * FROM memories";
3524
- if (conditions.length > 0) {
3525
- sql += ` WHERE ${conditions.join(" AND ")}`;
3526
3251
  }
3527
- sql += " ORDER BY importance DESC, created_at DESC";
3528
- if (filter?.limit) {
3529
- sql += " LIMIT ?";
3530
- params.push(filter.limit);
3252
+ return score;
3253
+ }
3254
+ function extractQuotedPhrases(query) {
3255
+ const phrases = [];
3256
+ const remainder = query.replace(/"([^"]+)"/g, (_match, phrase) => {
3257
+ phrases.push(phrase);
3258
+ return "";
3259
+ });
3260
+ return { phrases, remainder: remainder.trim() };
3261
+ }
3262
+ function escapeFts5Query(query) {
3263
+ const { phrases, remainder } = extractQuotedPhrases(query);
3264
+ const parts = [];
3265
+ for (const phrase of phrases) {
3266
+ parts.push(`"${phrase.replace(/"/g, '""')}"`);
3531
3267
  }
3532
- if (filter?.offset) {
3533
- sql += " OFFSET ?";
3534
- params.push(filter.offset);
3268
+ const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
3269
+ for (const t of tokens) {
3270
+ parts.push(`"${t.replace(/"/g, '""')}"`);
3535
3271
  }
3536
- const rows = d.query(sql).all(...params);
3537
- return rows.map(parseMemoryRow);
3272
+ return parts.join(" ");
3538
3273
  }
3539
- function updateMemory(id, input, db) {
3540
- const d = db || getDatabase();
3541
- const existing = getMemory(id, d);
3542
- if (!existing)
3543
- throw new MemoryNotFoundError(id);
3544
- if (existing.version !== input.version) {
3545
- throw new VersionConflictError(id, input.version, existing.version);
3546
- }
3274
+ function hasFts5Table(d) {
3547
3275
  try {
3548
- d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, created_at)
3549
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
3550
- uuid(),
3551
- existing.id,
3552
- existing.version,
3553
- existing.value,
3554
- existing.importance,
3555
- existing.scope,
3556
- existing.category,
3557
- JSON.stringify(existing.tags),
3558
- existing.summary,
3559
- existing.pinned ? 1 : 0,
3560
- existing.status,
3561
- existing.updated_at
3562
- ]);
3563
- } catch {}
3564
- const sets = ["version = version + 1", "updated_at = ?"];
3565
- const params = [now()];
3566
- if (input.value !== undefined) {
3567
- sets.push("value = ?");
3568
- params.push(redactSecrets(input.value));
3276
+ const row = d.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
3277
+ return !!row;
3278
+ } catch {
3279
+ return false;
3569
3280
  }
3570
- if (input.category !== undefined) {
3571
- sets.push("category = ?");
3572
- params.push(input.category);
3281
+ }
3282
+ function buildFilterConditions(filter) {
3283
+ const conditions = [];
3284
+ const params = [];
3285
+ conditions.push("m.status = 'active'");
3286
+ conditions.push("(m.expires_at IS NULL OR m.expires_at >= datetime('now'))");
3287
+ if (!filter)
3288
+ return { conditions, params };
3289
+ if (filter.scope) {
3290
+ if (Array.isArray(filter.scope)) {
3291
+ conditions.push(`m.scope IN (${filter.scope.map(() => "?").join(",")})`);
3292
+ params.push(...filter.scope);
3293
+ } else {
3294
+ conditions.push("m.scope = ?");
3295
+ params.push(filter.scope);
3296
+ }
3573
3297
  }
3574
- if (input.scope !== undefined) {
3575
- sets.push("scope = ?");
3576
- params.push(input.scope);
3298
+ if (filter.category) {
3299
+ if (Array.isArray(filter.category)) {
3300
+ conditions.push(`m.category IN (${filter.category.map(() => "?").join(",")})`);
3301
+ params.push(...filter.category);
3302
+ } else {
3303
+ conditions.push("m.category = ?");
3304
+ params.push(filter.category);
3305
+ }
3577
3306
  }
3578
- if (input.summary !== undefined) {
3579
- sets.push("summary = ?");
3580
- params.push(input.summary);
3307
+ if (filter.source) {
3308
+ if (Array.isArray(filter.source)) {
3309
+ conditions.push(`m.source IN (${filter.source.map(() => "?").join(",")})`);
3310
+ params.push(...filter.source);
3311
+ } else {
3312
+ conditions.push("m.source = ?");
3313
+ params.push(filter.source);
3314
+ }
3581
3315
  }
3582
- if (input.importance !== undefined) {
3583
- sets.push("importance = ?");
3584
- params.push(input.importance);
3316
+ if (filter.status) {
3317
+ conditions.shift();
3318
+ if (Array.isArray(filter.status)) {
3319
+ conditions.push(`m.status IN (${filter.status.map(() => "?").join(",")})`);
3320
+ params.push(...filter.status);
3321
+ } else {
3322
+ conditions.push("m.status = ?");
3323
+ params.push(filter.status);
3324
+ }
3585
3325
  }
3586
- if (input.pinned !== undefined) {
3587
- sets.push("pinned = ?");
3588
- params.push(input.pinned ? 1 : 0);
3326
+ if (filter.project_id) {
3327
+ conditions.push("m.project_id = ?");
3328
+ params.push(filter.project_id);
3589
3329
  }
3590
- if (input.status !== undefined) {
3591
- sets.push("status = ?");
3592
- params.push(input.status);
3330
+ if (filter.agent_id) {
3331
+ conditions.push("m.agent_id = ?");
3332
+ params.push(filter.agent_id);
3593
3333
  }
3594
- if (input.metadata !== undefined) {
3595
- sets.push("metadata = ?");
3596
- params.push(JSON.stringify(input.metadata));
3334
+ if (filter.session_id) {
3335
+ conditions.push("m.session_id = ?");
3336
+ params.push(filter.session_id);
3597
3337
  }
3598
- if (input.expires_at !== undefined) {
3599
- sets.push("expires_at = ?");
3600
- params.push(input.expires_at);
3338
+ if (filter.min_importance) {
3339
+ conditions.push("m.importance >= ?");
3340
+ params.push(filter.min_importance);
3601
3341
  }
3602
- if (input.tags !== undefined) {
3603
- sets.push("tags = ?");
3604
- params.push(JSON.stringify(input.tags));
3605
- d.run("DELETE FROM memory_tags WHERE memory_id = ?", [id]);
3606
- const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
3607
- for (const tag of input.tags) {
3608
- insertTag.run(id, tag);
3609
- }
3342
+ if (filter.pinned !== undefined) {
3343
+ conditions.push("m.pinned = ?");
3344
+ params.push(filter.pinned ? 1 : 0);
3610
3345
  }
3611
- params.push(id);
3612
- d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
3613
- const updated = getMemory(id, d);
3614
- try {
3615
- if (input.value !== undefined) {
3616
- const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
3617
- for (const link of oldLinks) {
3618
- unlinkEntityFromMemory(link.entity_id, updated.id, d);
3619
- }
3620
- runEntityExtraction(updated, existing.project_id || undefined, d);
3346
+ if (filter.tags && filter.tags.length > 0) {
3347
+ for (const tag of filter.tags) {
3348
+ conditions.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
3349
+ params.push(tag);
3621
3350
  }
3622
- } catch {}
3623
- return updated;
3624
- }
3625
- function deleteMemory(id, db) {
3626
- const d = db || getDatabase();
3627
- const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
3628
- return result.changes > 0;
3629
- }
3630
- function bulkDeleteMemories(ids, db) {
3631
- const d = db || getDatabase();
3632
- if (ids.length === 0)
3633
- return 0;
3634
- const placeholders = ids.map(() => "?").join(",");
3635
- const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...ids);
3636
- const count = countRow.c;
3637
- if (count > 0) {
3638
- d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, ids);
3639
- }
3640
- return count;
3641
- }
3642
- function touchMemory(id, db) {
3643
- const d = db || getDatabase();
3644
- d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
3645
- }
3646
- function cleanExpiredMemories(db) {
3647
- const d = db || getDatabase();
3648
- const timestamp = now();
3649
- const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
3650
- const count = countRow.c;
3651
- if (count > 0) {
3652
- d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
3653
3351
  }
3654
- return count;
3352
+ return { conditions, params };
3655
3353
  }
3656
- function getMemoryVersions(memoryId, db) {
3657
- const d = db || getDatabase();
3354
+ function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
3355
+ const ftsQuery = escapeFts5Query(query);
3356
+ if (!ftsQuery)
3357
+ return null;
3658
3358
  try {
3659
- const rows = d.query("SELECT * FROM memory_versions WHERE memory_id = ? ORDER BY version ASC").all(memoryId);
3660
- return rows.map((row) => ({
3661
- id: row["id"],
3662
- memory_id: row["memory_id"],
3663
- version: row["version"],
3664
- value: row["value"],
3665
- importance: row["importance"],
3666
- scope: row["scope"],
3667
- category: row["category"],
3668
- tags: JSON.parse(row["tags"] || "[]"),
3669
- summary: row["summary"] || null,
3670
- pinned: !!row["pinned"],
3671
- status: row["status"],
3672
- created_at: row["created_at"]
3673
- }));
3359
+ const { conditions, params } = buildFilterConditions(filter);
3360
+ const queryParam = `%${query}%`;
3361
+ 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 ?)`;
3362
+ const allConditions = [ftsCondition, ...conditions];
3363
+ const allParams = [ftsQuery, queryParam, queryParam, ...params];
3364
+ const candidateSql = `SELECT m.* FROM memories m WHERE ${allConditions.join(" AND ")}`;
3365
+ const rows = d.query(candidateSql).all(...allParams);
3366
+ return scoreResults(rows, queryLower, graphBoostedIds);
3674
3367
  } catch {
3675
- return [];
3368
+ return null;
3676
3369
  }
3677
3370
  }
3678
- var init_memories = __esm(() => {
3679
- init_types();
3371
+ function searchWithLike(d, query, queryLower, filter, graphBoostedIds) {
3372
+ const { conditions, params } = buildFilterConditions(filter);
3373
+ const rawTokens = query.trim().split(/\s+/).filter(Boolean);
3374
+ const tokens = removeStopWords(rawTokens);
3375
+ const escapedQuery = escapeLikePattern(query);
3376
+ const likePatterns = [`%${escapedQuery}%`];
3377
+ if (tokens.length > 1) {
3378
+ for (const t of tokens)
3379
+ likePatterns.push(`%${escapeLikePattern(t)}%`);
3380
+ }
3381
+ const fieldClauses = [];
3382
+ for (const pattern of likePatterns) {
3383
+ fieldClauses.push("m.key LIKE ? ESCAPE '\\'");
3384
+ params.push(pattern);
3385
+ fieldClauses.push("m.value LIKE ? ESCAPE '\\'");
3386
+ params.push(pattern);
3387
+ fieldClauses.push("m.summary LIKE ? ESCAPE '\\'");
3388
+ params.push(pattern);
3389
+ fieldClauses.push("m.metadata LIKE ? ESCAPE '\\'");
3390
+ params.push(pattern);
3391
+ fieldClauses.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag LIKE ? ESCAPE '\\')");
3392
+ params.push(pattern);
3393
+ }
3394
+ conditions.push(`(${fieldClauses.join(" OR ")})`);
3395
+ const sql = `SELECT DISTINCT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
3396
+ const rows = d.query(sql).all(...params);
3397
+ return scoreResults(rows, queryLower, graphBoostedIds);
3398
+ }
3399
+ function generateTrigrams(s) {
3400
+ const lower = s.toLowerCase();
3401
+ const trigrams = new Set;
3402
+ for (let i = 0;i <= lower.length - 3; i++) {
3403
+ trigrams.add(lower.slice(i, i + 3));
3404
+ }
3405
+ return trigrams;
3406
+ }
3407
+ function trigramSimilarity(a, b) {
3408
+ const triA = generateTrigrams(a);
3409
+ const triB = generateTrigrams(b);
3410
+ if (triA.size === 0 || triB.size === 0)
3411
+ return 0;
3412
+ let intersection = 0;
3413
+ for (const t of triA) {
3414
+ if (triB.has(t))
3415
+ intersection++;
3416
+ }
3417
+ const union = triA.size + triB.size - intersection;
3418
+ return union === 0 ? 0 : intersection / union;
3419
+ }
3420
+ function searchWithFuzzy(d, query, filter, graphBoostedIds) {
3421
+ const { conditions, params } = buildFilterConditions(filter);
3422
+ const sql = `SELECT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
3423
+ const rows = d.query(sql).all(...params);
3424
+ const MIN_SIMILARITY = 0.3;
3425
+ const results = [];
3426
+ for (const row of rows) {
3427
+ const memory = parseMemoryRow2(row);
3428
+ let bestSimilarity = 0;
3429
+ bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.key));
3430
+ bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.value.slice(0, 200)));
3431
+ if (memory.summary) {
3432
+ bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.summary));
3433
+ }
3434
+ for (const tag of memory.tags) {
3435
+ bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, tag));
3436
+ }
3437
+ if (bestSimilarity >= MIN_SIMILARITY) {
3438
+ const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
3439
+ const score = bestSimilarity * 5 * memory.importance / 10 + graphBoost;
3440
+ results.push({ memory, score, match_type: "fuzzy" });
3441
+ }
3442
+ }
3443
+ results.sort((a, b) => b.score - a.score);
3444
+ return results;
3445
+ }
3446
+ function getGraphBoostedMemoryIds(query, d) {
3447
+ const boostedIds = new Set;
3448
+ try {
3449
+ const matchingEntities = listEntities({ search: query, limit: 10 }, d);
3450
+ const exactMatch = getEntityByName(query, undefined, undefined, d);
3451
+ if (exactMatch && !matchingEntities.find((e) => e.id === exactMatch.id)) {
3452
+ matchingEntities.push(exactMatch);
3453
+ }
3454
+ for (const entity of matchingEntities) {
3455
+ const memories = getMemoriesForEntity(entity.id, d);
3456
+ for (const mem of memories) {
3457
+ boostedIds.add(mem.id);
3458
+ }
3459
+ }
3460
+ } catch {}
3461
+ return boostedIds;
3462
+ }
3463
+ function computeRecencyBoost(memory) {
3464
+ if (memory.pinned)
3465
+ return 1;
3466
+ const mostRecent = memory.accessed_at || memory.updated_at;
3467
+ if (!mostRecent)
3468
+ return 0;
3469
+ const daysSinceAccess = (Date.now() - Date.parse(mostRecent)) / (1000 * 60 * 60 * 24);
3470
+ return Math.max(0, 1 - daysSinceAccess / 30);
3471
+ }
3472
+ function scoreResults(rows, queryLower, graphBoostedIds) {
3473
+ const scored = [];
3474
+ for (const row of rows) {
3475
+ const memory = parseMemoryRow2(row);
3476
+ const rawScore = computeScore(memory, queryLower);
3477
+ if (rawScore === 0)
3478
+ continue;
3479
+ const weightedScore = rawScore * memory.importance / 10;
3480
+ const recencyBoost = computeRecencyBoost(memory);
3481
+ const accessBoost = Math.min(memory.access_count / 20, 0.2);
3482
+ const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
3483
+ const finalScore = (weightedScore + graphBoost) * (1 + recencyBoost * 0.3) * (1 + accessBoost);
3484
+ const matchType = determineMatchType(memory, queryLower);
3485
+ scored.push({
3486
+ memory,
3487
+ score: finalScore,
3488
+ match_type: matchType,
3489
+ highlights: extractHighlights(memory, queryLower)
3490
+ });
3491
+ }
3492
+ scored.sort((a, b) => {
3493
+ if (b.score !== a.score)
3494
+ return b.score - a.score;
3495
+ return b.memory.importance - a.memory.importance;
3496
+ });
3497
+ return scored;
3498
+ }
3499
+ function searchMemories(query, filter, db) {
3500
+ const d = db || getDatabase();
3501
+ query = preprocessQuery(query);
3502
+ if (!query)
3503
+ return [];
3504
+ const queryLower = query.toLowerCase();
3505
+ const graphBoostedIds = getGraphBoostedMemoryIds(query, d);
3506
+ let scored;
3507
+ if (hasFts5Table(d)) {
3508
+ const ftsResult = searchWithFts5(d, query, queryLower, filter, graphBoostedIds);
3509
+ if (ftsResult !== null) {
3510
+ scored = ftsResult;
3511
+ } else {
3512
+ scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
3513
+ }
3514
+ } else {
3515
+ scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
3516
+ }
3517
+ if (scored.length < 3) {
3518
+ const fuzzyResults = searchWithFuzzy(d, query, filter, graphBoostedIds);
3519
+ const seenIds = new Set(scored.map((r) => r.memory.id));
3520
+ for (const fr of fuzzyResults) {
3521
+ if (!seenIds.has(fr.memory.id)) {
3522
+ scored.push(fr);
3523
+ seenIds.add(fr.memory.id);
3524
+ }
3525
+ }
3526
+ scored.sort((a, b) => {
3527
+ if (b.score !== a.score)
3528
+ return b.score - a.score;
3529
+ return b.memory.importance - a.memory.importance;
3530
+ });
3531
+ }
3532
+ const offset = filter?.offset ?? 0;
3533
+ const limit = filter?.limit ?? scored.length;
3534
+ const finalResults = scored.slice(offset, offset + limit);
3535
+ if (finalResults.length > 0 && scored.length > 0) {
3536
+ const topScore = scored[0]?.score ?? 0;
3537
+ const secondScore = scored[1]?.score ?? 0;
3538
+ const confidence = topScore > 0 ? Math.max(0, Math.min(1, (topScore - secondScore) / topScore)) : 0;
3539
+ finalResults[0] = { ...finalResults[0], confidence };
3540
+ }
3541
+ logSearchQuery(query, scored.length, filter?.agent_id, filter?.project_id, d);
3542
+ return finalResults;
3543
+ }
3544
+ function logSearchQuery(query, resultCount, agentId, projectId, db) {
3545
+ try {
3546
+ const d = db || getDatabase();
3547
+ const id = crypto.randomUUID().slice(0, 8);
3548
+ d.run("INSERT INTO search_history (id, query, result_count, agent_id, project_id) VALUES (?, ?, ?, ?, ?)", [id, query, resultCount, agentId || null, projectId || null]);
3549
+ } catch {}
3550
+ }
3551
+ function getSearchHistory(limit = 20, projectId, db) {
3552
+ const d = db || getDatabase();
3553
+ if (projectId) {
3554
+ return d.query("SELECT query, result_count, created_at FROM search_history WHERE project_id = ? ORDER BY created_at DESC LIMIT ?").all(projectId, limit);
3555
+ }
3556
+ return d.query("SELECT query, result_count, created_at FROM search_history ORDER BY created_at DESC LIMIT ?").all(limit);
3557
+ }
3558
+ function getPopularSearches(limit = 10, projectId, db) {
3559
+ const d = db || getDatabase();
3560
+ if (projectId) {
3561
+ 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);
3562
+ }
3563
+ return d.query("SELECT query, COUNT(*) as count FROM search_history GROUP BY query ORDER BY count DESC LIMIT ?").all(limit);
3564
+ }
3565
+ var STOP_WORDS;
3566
+ var init_search = __esm(() => {
3680
3567
  init_database();
3681
- init_redact();
3682
- init_extractor();
3683
3568
  init_entities();
3684
3569
  init_entity_memories();
3685
- init_relations();
3686
- init_config();
3570
+ STOP_WORDS = new Set([
3571
+ "a",
3572
+ "an",
3573
+ "the",
3574
+ "is",
3575
+ "are",
3576
+ "was",
3577
+ "were",
3578
+ "be",
3579
+ "been",
3580
+ "being",
3581
+ "have",
3582
+ "has",
3583
+ "had",
3584
+ "do",
3585
+ "does",
3586
+ "did",
3587
+ "will",
3588
+ "would",
3589
+ "could",
3590
+ "should",
3591
+ "may",
3592
+ "might",
3593
+ "shall",
3594
+ "can",
3595
+ "need",
3596
+ "dare",
3597
+ "ought",
3598
+ "used",
3599
+ "to",
3600
+ "of",
3601
+ "in",
3602
+ "for",
3603
+ "on",
3604
+ "with",
3605
+ "at",
3606
+ "by",
3607
+ "from",
3608
+ "as",
3609
+ "into",
3610
+ "through",
3611
+ "during",
3612
+ "before",
3613
+ "after",
3614
+ "above",
3615
+ "below",
3616
+ "between",
3617
+ "out",
3618
+ "off",
3619
+ "over",
3620
+ "under",
3621
+ "again",
3622
+ "further",
3623
+ "then",
3624
+ "once",
3625
+ "here",
3626
+ "there",
3627
+ "when",
3628
+ "where",
3629
+ "why",
3630
+ "how",
3631
+ "all",
3632
+ "each",
3633
+ "every",
3634
+ "both",
3635
+ "few",
3636
+ "more",
3637
+ "most",
3638
+ "other",
3639
+ "some",
3640
+ "such",
3641
+ "no",
3642
+ "not",
3643
+ "only",
3644
+ "own",
3645
+ "same",
3646
+ "so",
3647
+ "than",
3648
+ "too",
3649
+ "very",
3650
+ "just",
3651
+ "because",
3652
+ "but",
3653
+ "and",
3654
+ "or",
3655
+ "if",
3656
+ "while",
3657
+ "that",
3658
+ "this",
3659
+ "it"
3660
+ ]);
3661
+ });
3662
+
3663
+ // src/db/relations.ts
3664
+ function parseRelationRow(row) {
3665
+ return {
3666
+ id: row["id"],
3667
+ source_entity_id: row["source_entity_id"],
3668
+ target_entity_id: row["target_entity_id"],
3669
+ relation_type: row["relation_type"],
3670
+ weight: row["weight"],
3671
+ metadata: JSON.parse(row["metadata"] || "{}"),
3672
+ created_at: row["created_at"]
3673
+ };
3674
+ }
3675
+ function parseEntityRow2(row) {
3676
+ return {
3677
+ id: row["id"],
3678
+ name: row["name"],
3679
+ type: row["type"],
3680
+ description: row["description"] || null,
3681
+ metadata: JSON.parse(row["metadata"] || "{}"),
3682
+ project_id: row["project_id"] || null,
3683
+ created_at: row["created_at"],
3684
+ updated_at: row["updated_at"]
3685
+ };
3686
+ }
3687
+ function createRelation(input, db) {
3688
+ const d = db || getDatabase();
3689
+ const id = shortUuid();
3690
+ const timestamp = now();
3691
+ const weight = input.weight ?? 1;
3692
+ const metadata = JSON.stringify(input.metadata ?? {});
3693
+ d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
3694
+ VALUES (?, ?, ?, ?, ?, ?, ?)
3695
+ ON CONFLICT(source_entity_id, target_entity_id, relation_type)
3696
+ DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
3697
+ const row = d.query(`SELECT * FROM relations
3698
+ WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
3699
+ return parseRelationRow(row);
3700
+ }
3701
+ function listRelations(filter, db) {
3702
+ const d = db || getDatabase();
3703
+ const conditions = [];
3704
+ const params = [];
3705
+ if (filter.entity_id) {
3706
+ const dir = filter.direction || "both";
3707
+ if (dir === "outgoing") {
3708
+ conditions.push("source_entity_id = ?");
3709
+ params.push(filter.entity_id);
3710
+ } else if (dir === "incoming") {
3711
+ conditions.push("target_entity_id = ?");
3712
+ params.push(filter.entity_id);
3713
+ } else {
3714
+ conditions.push("(source_entity_id = ? OR target_entity_id = ?)");
3715
+ params.push(filter.entity_id, filter.entity_id);
3716
+ }
3717
+ }
3718
+ if (filter.relation_type) {
3719
+ conditions.push("relation_type = ?");
3720
+ params.push(filter.relation_type);
3721
+ }
3722
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3723
+ const rows = d.query(`SELECT * FROM relations ${where} ORDER BY created_at DESC`).all(...params);
3724
+ return rows.map(parseRelationRow);
3725
+ }
3726
+ function deleteRelation(id, db) {
3727
+ const d = db || getDatabase();
3728
+ const result = d.run("DELETE FROM relations WHERE id = ?", [id]);
3729
+ if (result.changes === 0)
3730
+ throw new Error(`Relation not found: ${id}`);
3731
+ }
3732
+ function getRelatedEntities(entityId, relationType, db) {
3733
+ const d = db || getDatabase();
3734
+ let sql;
3735
+ const params = [];
3736
+ if (relationType) {
3737
+ sql = `
3738
+ SELECT DISTINCT e.* FROM entities e
3739
+ JOIN relations r ON (
3740
+ (r.source_entity_id = ? AND r.target_entity_id = e.id)
3741
+ OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
3742
+ )
3743
+ WHERE r.relation_type = ?
3744
+ `;
3745
+ params.push(entityId, entityId, relationType);
3746
+ } else {
3747
+ sql = `
3748
+ SELECT DISTINCT e.* FROM entities e
3749
+ JOIN relations r ON (
3750
+ (r.source_entity_id = ? AND r.target_entity_id = e.id)
3751
+ OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
3752
+ )
3753
+ `;
3754
+ params.push(entityId, entityId);
3755
+ }
3756
+ const rows = d.query(sql).all(...params);
3757
+ return rows.map(parseEntityRow2);
3758
+ }
3759
+ function getEntityGraph(entityId, depth = 2, db) {
3760
+ const d = db || getDatabase();
3761
+ const entityRows = d.query(`WITH RECURSIVE graph(id, depth) AS (
3762
+ VALUES(?, 0)
3763
+ UNION
3764
+ SELECT CASE WHEN r.source_entity_id = g.id THEN r.target_entity_id ELSE r.source_entity_id END, g.depth + 1
3765
+ FROM relations r JOIN graph g ON (r.source_entity_id = g.id OR r.target_entity_id = g.id)
3766
+ WHERE g.depth < ?
3767
+ )
3768
+ SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
3769
+ const entities = entityRows.map(parseEntityRow2);
3770
+ const entityIds = new Set(entities.map((e) => e.id));
3771
+ if (entityIds.size === 0) {
3772
+ return { entities: [], relations: [] };
3773
+ }
3774
+ const placeholders = Array.from(entityIds).map(() => "?").join(",");
3775
+ const relationRows = d.query(`SELECT * FROM relations
3776
+ WHERE source_entity_id IN (${placeholders})
3777
+ AND target_entity_id IN (${placeholders})`).all(...Array.from(entityIds), ...Array.from(entityIds));
3778
+ const relations = relationRows.map(parseRelationRow);
3779
+ return { entities, relations };
3780
+ }
3781
+ function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
3782
+ const d = db || getDatabase();
3783
+ const rows = d.query(`WITH RECURSIVE path(id, trail, depth) AS (
3784
+ SELECT ?, ?, 0
3785
+ UNION
3786
+ SELECT
3787
+ CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
3788
+ p.trail || ',' || CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
3789
+ p.depth + 1
3790
+ FROM relations r JOIN path p ON (r.source_entity_id = p.id OR r.target_entity_id = p.id)
3791
+ WHERE p.depth < ?
3792
+ AND INSTR(p.trail, CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END) = 0
3793
+ )
3794
+ SELECT trail FROM path WHERE id = ? ORDER BY depth ASC LIMIT 1`).get(fromEntityId, fromEntityId, maxDepth, toEntityId);
3795
+ if (!rows)
3796
+ return null;
3797
+ const ids = rows.trail.split(",");
3798
+ const entities = [];
3799
+ for (const id of ids) {
3800
+ const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
3801
+ if (row)
3802
+ entities.push(parseEntityRow2(row));
3803
+ }
3804
+ return entities.length > 0 ? entities : null;
3805
+ }
3806
+ var init_relations = __esm(() => {
3807
+ init_database();
3687
3808
  });
3688
3809
 
3689
3810
  // src/lib/poll.ts
@@ -3755,48 +3876,774 @@ function startPolling(opts) {
3755
3876
  }
3756
3877
  }
3757
3878
  } catch (err) {
3758
- opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
3879
+ opts.on_error?.(err instanceof Error ? err : new Error(String(err)));
3880
+ } finally {
3881
+ inFlight = false;
3882
+ }
3883
+ };
3884
+ seedLastSeen();
3885
+ const timer = setInterval(poll, interval);
3886
+ return {
3887
+ stop: () => {
3888
+ stopped = true;
3889
+ clearInterval(timer);
3890
+ }
3891
+ };
3892
+ }
3893
+ function parseMemoryRow3(row) {
3894
+ return {
3895
+ id: row["id"],
3896
+ key: row["key"],
3897
+ value: row["value"],
3898
+ category: row["category"],
3899
+ scope: row["scope"],
3900
+ summary: row["summary"] || null,
3901
+ tags: JSON.parse(row["tags"] || "[]"),
3902
+ importance: row["importance"],
3903
+ source: row["source"],
3904
+ status: row["status"],
3905
+ pinned: !!row["pinned"],
3906
+ agent_id: row["agent_id"] || null,
3907
+ project_id: row["project_id"] || null,
3908
+ session_id: row["session_id"] || null,
3909
+ metadata: JSON.parse(row["metadata"] || "{}"),
3910
+ access_count: row["access_count"],
3911
+ version: row["version"],
3912
+ expires_at: row["expires_at"] || null,
3913
+ created_at: row["created_at"],
3914
+ updated_at: row["updated_at"],
3915
+ accessed_at: row["accessed_at"] || null
3916
+ };
3917
+ }
3918
+ var init_poll = __esm(() => {
3919
+ init_memories();
3920
+ init_database();
3921
+ });
3922
+
3923
+ // src/lib/providers/base.ts
3924
+ class BaseProvider {
3925
+ config;
3926
+ constructor(config) {
3927
+ this.config = config;
3928
+ }
3929
+ parseJSON(raw) {
3930
+ try {
3931
+ const cleaned = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim();
3932
+ return JSON.parse(cleaned);
3933
+ } catch {
3934
+ return null;
3935
+ }
3936
+ }
3937
+ clampImportance(value) {
3938
+ const n = Number(value);
3939
+ if (isNaN(n))
3940
+ return 5;
3941
+ return Math.max(0, Math.min(10, Math.round(n)));
3942
+ }
3943
+ normaliseMemory(raw) {
3944
+ if (!raw || typeof raw !== "object")
3945
+ return null;
3946
+ const m = raw;
3947
+ if (typeof m.content !== "string" || !m.content.trim())
3948
+ return null;
3949
+ const validScopes = ["private", "shared", "global"];
3950
+ const validCategories = [
3951
+ "preference",
3952
+ "fact",
3953
+ "knowledge",
3954
+ "history"
3955
+ ];
3956
+ return {
3957
+ content: m.content.trim(),
3958
+ category: validCategories.includes(m.category) ? m.category : "knowledge",
3959
+ importance: this.clampImportance(m.importance),
3960
+ tags: Array.isArray(m.tags) ? m.tags.filter((t) => typeof t === "string").map((t) => t.toLowerCase()) : [],
3961
+ suggestedScope: validScopes.includes(m.suggestedScope) ? m.suggestedScope : "shared",
3962
+ reasoning: typeof m.reasoning === "string" ? m.reasoning : undefined
3963
+ };
3964
+ }
3965
+ }
3966
+ var DEFAULT_AUTO_MEMORY_CONFIG, MEMORY_EXTRACTION_SYSTEM_PROMPT = `You are a precise memory extraction engine for an AI agent.
3967
+ Given text, extract facts worth remembering as structured JSON.
3968
+ Focus on: decisions made, preferences revealed, corrections, architectural choices, established facts, user preferences.
3969
+ Ignore: greetings, filler, questions without answers, temporary states.
3970
+ Output ONLY a JSON array \u2014 no markdown, no explanation.`, MEMORY_EXTRACTION_USER_TEMPLATE = (text, context) => `Extract memories from this text.
3971
+ ${context.projectName ? `Project: ${context.projectName}` : ""}
3972
+ ${context.existingMemoriesSummary ? `Existing memories (avoid duplicates):
3973
+ ${context.existingMemoriesSummary}` : ""}
3974
+
3975
+ Text:
3976
+ ${text}
3977
+
3978
+ Return a JSON array of objects with these exact fields:
3979
+ - content: string (the memory, concise and specific)
3980
+ - category: "preference" | "fact" | "knowledge" | "history"
3981
+ - importance: number 0-10 (10 = critical, 0 = trivial)
3982
+ - tags: string[] (lowercase keywords)
3983
+ - suggestedScope: "private" | "shared" | "global"
3984
+ - reasoning: string (one sentence why this is worth remembering)
3985
+
3986
+ Return [] if nothing is worth remembering.`, ENTITY_EXTRACTION_SYSTEM_PROMPT = `You are a knowledge graph entity extractor.
3987
+ Given text, identify named entities and their relationships.
3988
+ Output ONLY valid JSON \u2014 no markdown, no explanation.`, ENTITY_EXTRACTION_USER_TEMPLATE = (text) => `Extract entities and relations from this text.
3989
+
3990
+ Text: ${text}
3991
+
3992
+ Return JSON with this exact shape:
3993
+ {
3994
+ "entities": [
3995
+ { "name": string, "type": "person"|"project"|"tool"|"concept"|"file"|"api"|"pattern"|"organization", "confidence": 0-1 }
3996
+ ],
3997
+ "relations": [
3998
+ { "from": string, "to": string, "type": "uses"|"knows"|"depends_on"|"created_by"|"related_to"|"contradicts"|"part_of"|"implements" }
3999
+ ]
4000
+ }`;
4001
+ var init_base = __esm(() => {
4002
+ DEFAULT_AUTO_MEMORY_CONFIG = {
4003
+ provider: "anthropic",
4004
+ model: "claude-haiku-4-5",
4005
+ enabled: true,
4006
+ minImportance: 4,
4007
+ autoEntityLink: true,
4008
+ fallback: ["cerebras", "openai"]
4009
+ };
4010
+ });
4011
+
4012
+ // src/lib/providers/anthropic.ts
4013
+ var ANTHROPIC_MODELS, AnthropicProvider;
4014
+ var init_anthropic = __esm(() => {
4015
+ init_base();
4016
+ ANTHROPIC_MODELS = {
4017
+ default: "claude-haiku-4-5",
4018
+ premium: "claude-sonnet-4-5"
4019
+ };
4020
+ AnthropicProvider = class AnthropicProvider extends BaseProvider {
4021
+ name = "anthropic";
4022
+ baseUrl = "https://api.anthropic.com/v1";
4023
+ constructor(config) {
4024
+ const apiKey = config?.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
4025
+ super({
4026
+ apiKey,
4027
+ model: config?.model ?? ANTHROPIC_MODELS.default,
4028
+ maxTokens: config?.maxTokens ?? 1024,
4029
+ temperature: config?.temperature ?? 0,
4030
+ timeoutMs: config?.timeoutMs ?? 15000
4031
+ });
4032
+ }
4033
+ async extractMemories(text, context) {
4034
+ if (!this.config.apiKey)
4035
+ return [];
4036
+ try {
4037
+ const response = await this.callAPI(MEMORY_EXTRACTION_SYSTEM_PROMPT, MEMORY_EXTRACTION_USER_TEMPLATE(text, context));
4038
+ const parsed = this.parseJSON(response);
4039
+ if (!Array.isArray(parsed))
4040
+ return [];
4041
+ return parsed.map((item) => this.normaliseMemory(item)).filter((m) => m !== null);
4042
+ } catch (err) {
4043
+ console.error("[anthropic] extractMemories failed:", err);
4044
+ return [];
4045
+ }
4046
+ }
4047
+ async extractEntities(text) {
4048
+ const empty = { entities: [], relations: [] };
4049
+ if (!this.config.apiKey)
4050
+ return empty;
4051
+ try {
4052
+ const response = await this.callAPI(ENTITY_EXTRACTION_SYSTEM_PROMPT, ENTITY_EXTRACTION_USER_TEMPLATE(text));
4053
+ const parsed = this.parseJSON(response);
4054
+ if (!parsed || typeof parsed !== "object")
4055
+ return empty;
4056
+ return {
4057
+ entities: Array.isArray(parsed.entities) ? parsed.entities : [],
4058
+ relations: Array.isArray(parsed.relations) ? parsed.relations : []
4059
+ };
4060
+ } catch (err) {
4061
+ console.error("[anthropic] extractEntities failed:", err);
4062
+ return empty;
4063
+ }
4064
+ }
4065
+ async scoreImportance(content, _context) {
4066
+ if (!this.config.apiKey)
4067
+ return 5;
4068
+ try {
4069
+ 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?
4070
+
4071
+ "${content}"
4072
+
4073
+ Return only a number 0-10.`);
4074
+ return this.clampImportance(response.trim());
4075
+ } catch {
4076
+ return 5;
4077
+ }
4078
+ }
4079
+ async callAPI(systemPrompt, userMessage) {
4080
+ const controller = new AbortController;
4081
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 15000);
4082
+ try {
4083
+ const res = await fetch(`${this.baseUrl}/messages`, {
4084
+ method: "POST",
4085
+ headers: {
4086
+ "Content-Type": "application/json",
4087
+ "x-api-key": this.config.apiKey,
4088
+ "anthropic-version": "2023-06-01"
4089
+ },
4090
+ body: JSON.stringify({
4091
+ model: this.config.model,
4092
+ max_tokens: this.config.maxTokens ?? 1024,
4093
+ temperature: this.config.temperature ?? 0,
4094
+ system: systemPrompt,
4095
+ messages: [{ role: "user", content: userMessage }]
4096
+ }),
4097
+ signal: controller.signal
4098
+ });
4099
+ if (!res.ok) {
4100
+ const body = await res.text().catch(() => "");
4101
+ throw new Error(`Anthropic API ${res.status}: ${body.slice(0, 200)}`);
4102
+ }
4103
+ const data = await res.json();
4104
+ return data.content?.[0]?.text ?? "";
4105
+ } finally {
4106
+ clearTimeout(timeout);
4107
+ }
4108
+ }
4109
+ };
4110
+ });
4111
+
4112
+ // src/lib/providers/openai-compat.ts
4113
+ var OpenAICompatProvider;
4114
+ var init_openai_compat = __esm(() => {
4115
+ init_base();
4116
+ OpenAICompatProvider = class OpenAICompatProvider extends BaseProvider {
4117
+ constructor(config) {
4118
+ super(config);
4119
+ }
4120
+ async extractMemories(text, context) {
4121
+ if (!this.config.apiKey)
4122
+ return [];
4123
+ try {
4124
+ const response = await this.callWithRetry(MEMORY_EXTRACTION_SYSTEM_PROMPT, MEMORY_EXTRACTION_USER_TEMPLATE(text, context));
4125
+ const parsed = this.parseJSON(response);
4126
+ if (!Array.isArray(parsed))
4127
+ return [];
4128
+ return parsed.map((item) => this.normaliseMemory(item)).filter((m) => m !== null);
4129
+ } catch (err) {
4130
+ console.error(`[${this.name}] extractMemories failed:`, err);
4131
+ return [];
4132
+ }
4133
+ }
4134
+ async extractEntities(text) {
4135
+ const empty = { entities: [], relations: [] };
4136
+ if (!this.config.apiKey)
4137
+ return empty;
4138
+ try {
4139
+ const response = await this.callWithRetry(ENTITY_EXTRACTION_SYSTEM_PROMPT, ENTITY_EXTRACTION_USER_TEMPLATE(text));
4140
+ const parsed = this.parseJSON(response);
4141
+ if (!parsed || typeof parsed !== "object")
4142
+ return empty;
4143
+ return {
4144
+ entities: Array.isArray(parsed.entities) ? parsed.entities : [],
4145
+ relations: Array.isArray(parsed.relations) ? parsed.relations : []
4146
+ };
4147
+ } catch (err) {
4148
+ console.error(`[${this.name}] extractEntities failed:`, err);
4149
+ return empty;
4150
+ }
4151
+ }
4152
+ async scoreImportance(content, _context) {
4153
+ if (!this.config.apiKey)
4154
+ return 5;
4155
+ try {
4156
+ 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?
4157
+
4158
+ "${content}"
4159
+
4160
+ Return only a number 0-10.`);
4161
+ return this.clampImportance(response.trim());
4162
+ } catch {
4163
+ return 5;
4164
+ }
4165
+ }
4166
+ async callWithRetry(systemPrompt, userMessage, retries = 3) {
4167
+ let lastError = null;
4168
+ for (let attempt = 0;attempt < retries; attempt++) {
4169
+ try {
4170
+ return await this.callAPI(systemPrompt, userMessage);
4171
+ } catch (err) {
4172
+ lastError = err instanceof Error ? err : new Error(String(err));
4173
+ const isRateLimit = lastError.message.includes("429") || lastError.message.toLowerCase().includes("rate limit");
4174
+ if (!isRateLimit || attempt === retries - 1)
4175
+ throw lastError;
4176
+ await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
4177
+ }
4178
+ }
4179
+ throw lastError ?? new Error("Unknown error");
4180
+ }
4181
+ async callAPI(systemPrompt, userMessage) {
4182
+ const controller = new AbortController;
4183
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 15000);
4184
+ try {
4185
+ const res = await fetch(`${this.baseUrl}/chat/completions`, {
4186
+ method: "POST",
4187
+ headers: {
4188
+ "Content-Type": "application/json",
4189
+ [this.authHeader]: `Bearer ${this.config.apiKey}`
4190
+ },
4191
+ body: JSON.stringify({
4192
+ model: this.config.model,
4193
+ max_tokens: this.config.maxTokens ?? 1024,
4194
+ temperature: this.config.temperature ?? 0,
4195
+ messages: [
4196
+ { role: "system", content: systemPrompt },
4197
+ { role: "user", content: userMessage }
4198
+ ]
4199
+ }),
4200
+ signal: controller.signal
4201
+ });
4202
+ if (!res.ok) {
4203
+ const body = await res.text().catch(() => "");
4204
+ throw new Error(`${this.name} API ${res.status}: ${body.slice(0, 200)}`);
4205
+ }
4206
+ const data = await res.json();
4207
+ return data.choices?.[0]?.message?.content ?? "";
4208
+ } finally {
4209
+ clearTimeout(timeout);
4210
+ }
4211
+ }
4212
+ };
4213
+ });
4214
+
4215
+ // src/lib/providers/openai.ts
4216
+ var OPENAI_MODELS, OpenAIProvider;
4217
+ var init_openai = __esm(() => {
4218
+ init_openai_compat();
4219
+ OPENAI_MODELS = {
4220
+ default: "gpt-4.1-nano",
4221
+ mini: "gpt-4.1-mini",
4222
+ full: "gpt-4.1"
4223
+ };
4224
+ OpenAIProvider = class OpenAIProvider extends OpenAICompatProvider {
4225
+ name = "openai";
4226
+ baseUrl = "https://api.openai.com/v1";
4227
+ authHeader = "Authorization";
4228
+ constructor(config) {
4229
+ super({
4230
+ apiKey: config?.apiKey ?? process.env.OPENAI_API_KEY ?? "",
4231
+ model: config?.model ?? OPENAI_MODELS.default,
4232
+ maxTokens: config?.maxTokens ?? 1024,
4233
+ temperature: config?.temperature ?? 0,
4234
+ timeoutMs: config?.timeoutMs ?? 15000
4235
+ });
4236
+ }
4237
+ };
4238
+ });
4239
+
4240
+ // src/lib/providers/cerebras.ts
4241
+ var CEREBRAS_MODELS, CerebrasProvider;
4242
+ var init_cerebras = __esm(() => {
4243
+ init_openai_compat();
4244
+ CEREBRAS_MODELS = {
4245
+ default: "llama-3.3-70b",
4246
+ fast: "llama3.1-8b"
4247
+ };
4248
+ CerebrasProvider = class CerebrasProvider extends OpenAICompatProvider {
4249
+ name = "cerebras";
4250
+ baseUrl = "https://api.cerebras.ai/v1";
4251
+ authHeader = "Authorization";
4252
+ constructor(config) {
4253
+ super({
4254
+ apiKey: config?.apiKey ?? process.env.CEREBRAS_API_KEY ?? "",
4255
+ model: config?.model ?? CEREBRAS_MODELS.default,
4256
+ maxTokens: config?.maxTokens ?? 1024,
4257
+ temperature: config?.temperature ?? 0,
4258
+ timeoutMs: config?.timeoutMs ?? 1e4
4259
+ });
4260
+ }
4261
+ };
4262
+ });
4263
+
4264
+ // src/lib/providers/grok.ts
4265
+ var GROK_MODELS, GrokProvider;
4266
+ var init_grok = __esm(() => {
4267
+ init_openai_compat();
4268
+ GROK_MODELS = {
4269
+ default: "grok-3-mini",
4270
+ premium: "grok-3"
4271
+ };
4272
+ GrokProvider = class GrokProvider extends OpenAICompatProvider {
4273
+ name = "grok";
4274
+ baseUrl = "https://api.x.ai/v1";
4275
+ authHeader = "Authorization";
4276
+ constructor(config) {
4277
+ super({
4278
+ apiKey: config?.apiKey ?? process.env.XAI_API_KEY ?? "",
4279
+ model: config?.model ?? GROK_MODELS.default,
4280
+ maxTokens: config?.maxTokens ?? 1024,
4281
+ temperature: config?.temperature ?? 0,
4282
+ timeoutMs: config?.timeoutMs ?? 15000
4283
+ });
4284
+ }
4285
+ };
4286
+ });
4287
+
4288
+ // src/lib/providers/registry.ts
4289
+ var exports_registry = {};
4290
+ __export(exports_registry, {
4291
+ providerRegistry: () => providerRegistry
4292
+ });
4293
+
4294
+ class ProviderRegistry {
4295
+ config = { ...DEFAULT_AUTO_MEMORY_CONFIG };
4296
+ _instances = new Map;
4297
+ configure(partial) {
4298
+ this.config = { ...this.config, ...partial };
4299
+ this._instances.clear();
4300
+ }
4301
+ getConfig() {
4302
+ return this.config;
4303
+ }
4304
+ getPrimary() {
4305
+ return this.getProvider(this.config.provider);
4306
+ }
4307
+ getFallbacks() {
4308
+ const fallbackNames = this.config.fallback ?? [];
4309
+ return fallbackNames.filter((n) => n !== this.config.provider).map((n) => this.getProvider(n)).filter((p) => p !== null);
4310
+ }
4311
+ getAvailable() {
4312
+ const primary = this.getPrimary();
4313
+ if (primary)
4314
+ return primary;
4315
+ const fallbacks = this.getFallbacks();
4316
+ return fallbacks[0] ?? null;
4317
+ }
4318
+ getProvider(name) {
4319
+ const cached = this._instances.get(name);
4320
+ if (cached)
4321
+ return cached;
4322
+ const provider = this.createProvider(name);
4323
+ if (!provider)
4324
+ return null;
4325
+ if (!provider.config.apiKey)
4326
+ return null;
4327
+ this._instances.set(name, provider);
4328
+ return provider;
4329
+ }
4330
+ health() {
4331
+ const providers = ["anthropic", "openai", "cerebras", "grok"];
4332
+ const result = {};
4333
+ for (const name of providers) {
4334
+ const p = this.createProvider(name);
4335
+ result[name] = {
4336
+ available: Boolean(p?.config.apiKey),
4337
+ model: p?.config.model ?? "unknown"
4338
+ };
4339
+ }
4340
+ return result;
4341
+ }
4342
+ createProvider(name) {
4343
+ const modelOverride = name === this.config.provider ? this.config.model : undefined;
4344
+ switch (name) {
4345
+ case "anthropic":
4346
+ return new AnthropicProvider(modelOverride ? { model: modelOverride } : undefined);
4347
+ case "openai":
4348
+ return new OpenAIProvider(modelOverride ? { model: modelOverride } : undefined);
4349
+ case "cerebras":
4350
+ return new CerebrasProvider(modelOverride ? { model: modelOverride } : undefined);
4351
+ case "grok":
4352
+ return new GrokProvider(modelOverride ? { model: modelOverride } : undefined);
4353
+ default:
4354
+ return null;
4355
+ }
4356
+ }
4357
+ }
4358
+ function autoConfigureFromEnv() {
4359
+ const hasAnthropicKey = Boolean(process.env.ANTHROPIC_API_KEY);
4360
+ const hasCerebrasKey = Boolean(process.env.CEREBRAS_API_KEY);
4361
+ const hasOpenAIKey = Boolean(process.env.OPENAI_API_KEY);
4362
+ const hasGrokKey = Boolean(process.env.XAI_API_KEY);
4363
+ if (!hasAnthropicKey) {
4364
+ if (hasCerebrasKey) {
4365
+ providerRegistry.configure({ provider: "cerebras" });
4366
+ } else if (hasOpenAIKey) {
4367
+ providerRegistry.configure({ provider: "openai" });
4368
+ } else if (hasGrokKey) {
4369
+ providerRegistry.configure({ provider: "grok" });
4370
+ }
4371
+ }
4372
+ const allProviders = ["anthropic", "cerebras", "openai", "grok"];
4373
+ const available = allProviders.filter((p) => {
4374
+ switch (p) {
4375
+ case "anthropic":
4376
+ return hasAnthropicKey;
4377
+ case "cerebras":
4378
+ return hasCerebrasKey;
4379
+ case "openai":
4380
+ return hasOpenAIKey;
4381
+ case "grok":
4382
+ return hasGrokKey;
4383
+ }
4384
+ });
4385
+ const primary = providerRegistry.getConfig().provider;
4386
+ const fallback = available.filter((p) => p !== primary);
4387
+ providerRegistry.configure({ fallback });
4388
+ }
4389
+ var providerRegistry;
4390
+ var init_registry = __esm(() => {
4391
+ init_anthropic();
4392
+ init_openai();
4393
+ init_cerebras();
4394
+ init_grok();
4395
+ init_base();
4396
+ providerRegistry = new ProviderRegistry;
4397
+ autoConfigureFromEnv();
4398
+ });
4399
+
4400
+ // src/lib/auto-memory-queue.ts
4401
+ class AutoMemoryQueue {
4402
+ queue = [];
4403
+ handler = null;
4404
+ running = false;
4405
+ activeCount = 0;
4406
+ stats = {
4407
+ pending: 0,
4408
+ processing: 0,
4409
+ processed: 0,
4410
+ failed: 0,
4411
+ dropped: 0
4412
+ };
4413
+ setHandler(handler) {
4414
+ this.handler = handler;
4415
+ if (!this.running)
4416
+ this.startLoop();
4417
+ }
4418
+ enqueue(job) {
4419
+ if (this.queue.length >= MAX_QUEUE_SIZE) {
4420
+ this.queue.shift();
4421
+ this.stats.dropped++;
4422
+ this.stats.pending = Math.max(0, this.stats.pending - 1);
4423
+ }
4424
+ this.queue.push(job);
4425
+ this.stats.pending++;
4426
+ if (!this.running && this.handler)
4427
+ this.startLoop();
4428
+ }
4429
+ getStats() {
4430
+ return { ...this.stats, pending: this.queue.length };
4431
+ }
4432
+ startLoop() {
4433
+ this.running = true;
4434
+ this.loop();
4435
+ }
4436
+ async loop() {
4437
+ while (this.queue.length > 0 || this.activeCount > 0) {
4438
+ while (this.queue.length > 0 && this.activeCount < CONCURRENCY) {
4439
+ const job = this.queue.shift();
4440
+ if (!job)
4441
+ break;
4442
+ this.stats.pending = Math.max(0, this.stats.pending - 1);
4443
+ this.activeCount++;
4444
+ this.stats.processing = this.activeCount;
4445
+ this.processJob(job);
4446
+ }
4447
+ await new Promise((r) => setImmediate(r));
4448
+ }
4449
+ this.running = false;
4450
+ }
4451
+ async processJob(job) {
4452
+ if (!this.handler) {
4453
+ this.activeCount--;
4454
+ this.stats.processing = this.activeCount;
4455
+ return;
4456
+ }
4457
+ try {
4458
+ await this.handler(job);
4459
+ this.stats.processed++;
4460
+ } catch (err) {
4461
+ this.stats.failed++;
4462
+ console.error("[auto-memory-queue] job failed:", err);
3759
4463
  } finally {
3760
- inFlight = false;
4464
+ this.activeCount--;
4465
+ this.stats.processing = this.activeCount;
3761
4466
  }
3762
- };
3763
- seedLastSeen();
3764
- const timer = setInterval(poll, interval);
3765
- return {
3766
- stop: () => {
3767
- stopped = true;
3768
- clearInterval(timer);
4467
+ }
4468
+ }
4469
+ var MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue;
4470
+ var init_auto_memory_queue = __esm(() => {
4471
+ autoMemoryQueue = new AutoMemoryQueue;
4472
+ });
4473
+
4474
+ // src/lib/auto-memory.ts
4475
+ var exports_auto_memory = {};
4476
+ __export(exports_auto_memory, {
4477
+ processConversationTurn: () => processConversationTurn,
4478
+ getAutoMemoryStats: () => getAutoMemoryStats,
4479
+ configureAutoMemory: () => configureAutoMemory
4480
+ });
4481
+ function isDuplicate(content, agentId, projectId) {
4482
+ try {
4483
+ const query = content.split(/\s+/).filter((w) => w.length > 3).slice(0, 10).join(" ");
4484
+ if (!query)
4485
+ return false;
4486
+ const results = searchMemories(query, {
4487
+ agent_id: agentId,
4488
+ project_id: projectId,
4489
+ limit: 3
4490
+ });
4491
+ if (results.length === 0)
4492
+ return false;
4493
+ const contentWords = new Set(content.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
4494
+ for (const result of results) {
4495
+ const existingWords = new Set(result.memory.value.toLowerCase().split(/\W+/).filter((w) => w.length > 3));
4496
+ if (contentWords.size === 0 || existingWords.size === 0)
4497
+ continue;
4498
+ const intersection = [...contentWords].filter((w) => existingWords.has(w)).length;
4499
+ const union = new Set([...contentWords, ...existingWords]).size;
4500
+ const similarity = intersection / union;
4501
+ if (similarity >= DEDUP_SIMILARITY_THRESHOLD)
4502
+ return true;
3769
4503
  }
3770
- };
4504
+ return false;
4505
+ } catch {
4506
+ return false;
4507
+ }
3771
4508
  }
3772
- function parseMemoryRow3(row) {
3773
- return {
3774
- id: row["id"],
3775
- key: row["key"],
3776
- value: row["value"],
3777
- category: row["category"],
3778
- scope: row["scope"],
3779
- summary: row["summary"] || null,
3780
- tags: JSON.parse(row["tags"] || "[]"),
3781
- importance: row["importance"],
3782
- source: row["source"],
3783
- status: row["status"],
3784
- pinned: !!row["pinned"],
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
4509
+ async function linkEntitiesToMemory(memoryId, content, _agentId, projectId) {
4510
+ const provider = providerRegistry.getAvailable();
4511
+ if (!provider)
4512
+ return;
4513
+ try {
4514
+ const { entities, relations } = await provider.extractEntities(content);
4515
+ const entityIdMap = new Map;
4516
+ for (const extracted of entities) {
4517
+ if (extracted.confidence < 0.6)
4518
+ continue;
4519
+ try {
4520
+ const existing = getEntityByName(extracted.name);
4521
+ const entityId = existing ? existing.id : createEntity({
4522
+ name: extracted.name,
4523
+ type: extracted.type,
4524
+ project_id: projectId
4525
+ }).id;
4526
+ entityIdMap.set(extracted.name, entityId);
4527
+ linkEntityToMemory(entityId, memoryId, "subject");
4528
+ } catch {}
4529
+ }
4530
+ for (const rel of relations) {
4531
+ const fromId = entityIdMap.get(rel.from);
4532
+ const toId = entityIdMap.get(rel.to);
4533
+ if (!fromId || !toId)
4534
+ continue;
4535
+ try {
4536
+ createRelation({
4537
+ source_entity_id: fromId,
4538
+ target_entity_id: toId,
4539
+ relation_type: rel.type
4540
+ });
4541
+ } catch {}
4542
+ }
4543
+ } catch (err) {
4544
+ console.error("[auto-memory] entity linking failed:", err);
4545
+ }
4546
+ }
4547
+ async function saveExtractedMemory(extracted, context) {
4548
+ const minImportance = providerRegistry.getConfig().minImportance;
4549
+ if (extracted.importance < minImportance)
4550
+ return null;
4551
+ if (!extracted.content.trim())
4552
+ return null;
4553
+ if (isDuplicate(extracted.content, context.agentId, context.projectId)) {
4554
+ return null;
4555
+ }
4556
+ try {
4557
+ const input = {
4558
+ key: extracted.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
4559
+ value: extracted.content,
4560
+ category: extracted.category,
4561
+ scope: extracted.suggestedScope,
4562
+ importance: extracted.importance,
4563
+ tags: [
4564
+ ...extracted.tags,
4565
+ "auto-extracted",
4566
+ ...context.sessionId ? [`session:${context.sessionId}`] : []
4567
+ ],
4568
+ agent_id: context.agentId,
4569
+ project_id: context.projectId,
4570
+ session_id: context.sessionId,
4571
+ metadata: {
4572
+ reasoning: extracted.reasoning,
4573
+ auto_extracted: true,
4574
+ extracted_at: new Date().toISOString()
4575
+ }
4576
+ };
4577
+ const memory = createMemory(input, "merge");
4578
+ return memory.id;
4579
+ } catch (err) {
4580
+ console.error("[auto-memory] saveExtractedMemory failed:", err);
4581
+ return null;
4582
+ }
4583
+ }
4584
+ async function processJob(job) {
4585
+ if (!providerRegistry.getConfig().enabled)
4586
+ return;
4587
+ const provider = providerRegistry.getAvailable();
4588
+ if (!provider)
4589
+ return;
4590
+ const context = {
4591
+ agentId: job.agentId,
4592
+ projectId: job.projectId,
4593
+ sessionId: job.sessionId
3795
4594
  };
4595
+ let extracted = [];
4596
+ try {
4597
+ extracted = await provider.extractMemories(job.turn, context);
4598
+ } catch {
4599
+ const fallbacks = providerRegistry.getFallbacks();
4600
+ for (const fallback of fallbacks) {
4601
+ try {
4602
+ extracted = await fallback.extractMemories(job.turn, context);
4603
+ if (extracted.length > 0)
4604
+ break;
4605
+ } catch {
4606
+ continue;
4607
+ }
4608
+ }
4609
+ }
4610
+ if (extracted.length === 0)
4611
+ return;
4612
+ for (const memory of extracted) {
4613
+ const memoryId = await saveExtractedMemory(memory, context);
4614
+ if (!memoryId)
4615
+ continue;
4616
+ if (providerRegistry.getConfig().autoEntityLink) {
4617
+ linkEntitiesToMemory(memoryId, memory.content, job.agentId, job.projectId);
4618
+ }
4619
+ }
3796
4620
  }
3797
- var init_poll = __esm(() => {
4621
+ function processConversationTurn(turn, context, source = "turn") {
4622
+ if (!turn?.trim())
4623
+ return;
4624
+ autoMemoryQueue.enqueue({
4625
+ ...context,
4626
+ turn,
4627
+ timestamp: Date.now(),
4628
+ source
4629
+ });
4630
+ }
4631
+ function getAutoMemoryStats() {
4632
+ return autoMemoryQueue.getStats();
4633
+ }
4634
+ function configureAutoMemory(config) {
4635
+ providerRegistry.configure(config);
4636
+ }
4637
+ var DEDUP_SIMILARITY_THRESHOLD = 0.85;
4638
+ var init_auto_memory = __esm(() => {
3798
4639
  init_memories();
3799
- init_database();
4640
+ init_search();
4641
+ init_entities();
4642
+ init_relations();
4643
+ init_entity_memories();
4644
+ init_registry();
4645
+ init_auto_memory_queue();
4646
+ autoMemoryQueue.setHandler(processJob);
3800
4647
  });
3801
4648
 
3802
4649
  // node_modules/commander/esm.mjs
@@ -3818,569 +4665,315 @@ var {
3818
4665
  // src/cli/index.tsx
3819
4666
  init_database();
3820
4667
  init_memories();
3821
- init_agents();
3822
- init_projects();
3823
4668
  import chalk from "chalk";
3824
4669
  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
4670
  import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
3826
4671
  import { homedir as homedir2 } from "os";
3827
4672
  import { fileURLToPath } from "url";
3828
4673
 
3829
- // src/lib/search.ts
4674
+ // src/db/agents.ts
4675
+ init_types();
3830
4676
  init_database();
3831
- init_entities();
3832
- init_entity_memories();
3833
- function parseMemoryRow2(row) {
4677
+ var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
4678
+ function parseAgentRow(row) {
3834
4679
  return {
3835
4680
  id: row["id"],
3836
- key: row["key"],
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,
4681
+ name: row["name"],
3848
4682
  session_id: row["session_id"] || null,
4683
+ description: row["description"] || null,
4684
+ role: row["role"] || null,
3849
4685
  metadata: JSON.parse(row["metadata"] || "{}"),
3850
- access_count: row["access_count"],
3851
- version: row["version"],
3852
- expires_at: row["expires_at"] || null,
4686
+ active_project_id: row["active_project_id"] || null,
3853
4687
  created_at: row["created_at"],
3854
- updated_at: row["updated_at"],
3855
- accessed_at: row["accessed_at"] || null
4688
+ last_seen_at: row["last_seen_at"]
3856
4689
  };
3857
4690
  }
3858
- function preprocessQuery(query) {
3859
- let q = query.trim();
3860
- q = q.replace(/\s+/g, " ");
3861
- q = q.normalize("NFC");
3862
- return q;
3863
- }
3864
- function escapeLikePattern(s) {
3865
- return s.replace(/%/g, "\\%").replace(/_/g, "\\_");
3866
- }
3867
- var STOP_WORDS = new Set([
3868
- "a",
3869
- "an",
3870
- "the",
3871
- "is",
3872
- "are",
3873
- "was",
3874
- "were",
3875
- "be",
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;
4691
+ function registerAgent(name, sessionId, description, role, projectId, db) {
4692
+ const d = db || getDatabase();
4693
+ const timestamp = now();
4694
+ const normalizedName = name.trim().toLowerCase();
4695
+ const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
4696
+ if (existing) {
4697
+ const existingId = existing["id"];
4698
+ const existingSessionId = existing["session_id"] || null;
4699
+ const existingLastSeen = existing["last_seen_at"];
4700
+ if (sessionId && existingSessionId && existingSessionId !== sessionId) {
4701
+ const lastSeenMs = new Date(existingLastSeen).getTime();
4702
+ const nowMs = Date.now();
4703
+ if (nowMs - lastSeenMs < CONFLICT_WINDOW_MS) {
4704
+ throw new AgentConflictError({
4705
+ existing_id: existingId,
4706
+ existing_name: normalizedName,
4707
+ last_seen_at: existingLastSeen,
4708
+ session_hint: existingSessionId.slice(0, 8),
4709
+ working_dir: null
4710
+ });
4065
4711
  }
4066
4712
  }
4067
- if (score > 0) {
4068
- score += tokenScore * 0.3;
4069
- } else {
4070
- score += tokenScore;
4713
+ d.run("UPDATE agents SET last_seen_at = ?, session_id = ? WHERE id = ?", [
4714
+ timestamp,
4715
+ sessionId ?? existingSessionId,
4716
+ existingId
4717
+ ]);
4718
+ if (description) {
4719
+ d.run("UPDATE agents SET description = ? WHERE id = ?", [description, existingId]);
4720
+ }
4721
+ if (role) {
4722
+ d.run("UPDATE agents SET role = ? WHERE id = ?", [role, existingId]);
4071
4723
  }
4724
+ if (projectId !== undefined) {
4725
+ d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
4726
+ }
4727
+ return getAgent(existingId, d);
4072
4728
  }
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() };
4729
+ const id = shortUuid();
4730
+ 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]);
4731
+ return getAgent(id, d);
4082
4732
  }
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(" ");
4733
+ function getAgent(idOrName, db) {
4734
+ const d = db || getDatabase();
4735
+ let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
4736
+ if (row)
4737
+ return parseAgentRow(row);
4738
+ row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
4739
+ if (row)
4740
+ return parseAgentRow(row);
4741
+ const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
4742
+ if (rows.length === 1)
4743
+ return parseAgentRow(rows[0]);
4744
+ return null;
4094
4745
  }
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
- }
4746
+ function listAgents(db) {
4747
+ const d = db || getDatabase();
4748
+ const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
4749
+ return rows.map(parseAgentRow);
4102
4750
  }
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);
4117
- }
4118
- }
4119
- if (filter.category) {
4120
- if (Array.isArray(filter.category)) {
4121
- conditions.push(`m.category IN (${filter.category.map(() => "?").join(",")})`);
4122
- params.push(...filter.category);
4123
- } else {
4124
- conditions.push("m.category = ?");
4125
- params.push(filter.category);
4126
- }
4127
- }
4128
- if (filter.source) {
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);
4135
- }
4136
- }
4137
- if (filter.status) {
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);
4751
+ function updateAgent(id, updates, db) {
4752
+ const d = db || getDatabase();
4753
+ const agent = getAgent(id, d);
4754
+ if (!agent)
4755
+ return null;
4756
+ const timestamp = now();
4757
+ if (updates.name) {
4758
+ const normalizedNewName = updates.name.trim().toLowerCase();
4759
+ if (normalizedNewName !== agent.name) {
4760
+ const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
4761
+ if (existing) {
4762
+ throw new Error(`Agent name already taken: ${normalizedNewName}`);
4763
+ }
4764
+ d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
4145
4765
  }
4146
4766
  }
4147
- if (filter.project_id) {
4148
- conditions.push("m.project_id = ?");
4149
- params.push(filter.project_id);
4150
- }
4151
- if (filter.agent_id) {
4152
- conditions.push("m.agent_id = ?");
4153
- params.push(filter.agent_id);
4154
- }
4155
- if (filter.session_id) {
4156
- conditions.push("m.session_id = ?");
4157
- params.push(filter.session_id);
4767
+ if (updates.description !== undefined) {
4768
+ d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
4158
4769
  }
4159
- if (filter.min_importance) {
4160
- conditions.push("m.importance >= ?");
4161
- params.push(filter.min_importance);
4770
+ if (updates.role !== undefined) {
4771
+ d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
4162
4772
  }
4163
- if (filter.pinned !== undefined) {
4164
- conditions.push("m.pinned = ?");
4165
- params.push(filter.pinned ? 1 : 0);
4773
+ if (updates.metadata !== undefined) {
4774
+ d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
4166
4775
  }
4167
- if (filter.tags && filter.tags.length > 0) {
4168
- for (const tag of filter.tags) {
4169
- conditions.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
4170
- params.push(tag);
4171
- }
4776
+ if ("active_project_id" in updates) {
4777
+ d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [updates.active_project_id ?? null, agent.id]);
4172
4778
  }
4173
- return { conditions, params };
4779
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
4780
+ return getAgent(agent.id, d);
4174
4781
  }
4175
- function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
4176
- const ftsQuery = escapeFts5Query(query);
4177
- if (!ftsQuery)
4178
- return null;
4179
- try {
4180
- const { conditions, params } = buildFilterConditions(filter);
4181
- const queryParam = `%${query}%`;
4182
- 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 ?)`;
4183
- const allConditions = [ftsCondition, ...conditions];
4184
- const allParams = [ftsQuery, queryParam, queryParam, ...params];
4185
- const candidateSql = `SELECT m.* FROM memories m WHERE ${allConditions.join(" AND ")}`;
4186
- const rows = d.query(candidateSql).all(...allParams);
4187
- return scoreResults(rows, queryLower, graphBoostedIds);
4188
- } catch {
4189
- return null;
4782
+
4783
+ // src/db/projects.ts
4784
+ init_database();
4785
+ function parseProjectRow(row) {
4786
+ return {
4787
+ id: row["id"],
4788
+ name: row["name"],
4789
+ path: row["path"],
4790
+ description: row["description"] || null,
4791
+ memory_prefix: row["memory_prefix"] || null,
4792
+ created_at: row["created_at"],
4793
+ updated_at: row["updated_at"]
4794
+ };
4795
+ }
4796
+ function registerProject(name, path, description, memoryPrefix, db) {
4797
+ const d = db || getDatabase();
4798
+ const timestamp = now();
4799
+ const existing = d.query("SELECT * FROM projects WHERE path = ?").get(path);
4800
+ if (existing) {
4801
+ const existingId = existing["id"];
4802
+ d.run("UPDATE projects SET updated_at = ? WHERE id = ?", [
4803
+ timestamp,
4804
+ existingId
4805
+ ]);
4806
+ return parseProjectRow(existing);
4190
4807
  }
4808
+ const id = uuid();
4809
+ 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]);
4810
+ return getProject(id, d);
4191
4811
  }
4192
- function searchWithLike(d, query, queryLower, filter, graphBoostedIds) {
4193
- const { conditions, params } = buildFilterConditions(filter);
4194
- const rawTokens = query.trim().split(/\s+/).filter(Boolean);
4195
- const tokens = removeStopWords(rawTokens);
4196
- const escapedQuery = escapeLikePattern(query);
4197
- const likePatterns = [`%${escapedQuery}%`];
4198
- if (tokens.length > 1) {
4199
- for (const t of tokens)
4200
- likePatterns.push(`%${escapeLikePattern(t)}%`);
4812
+ function getProject(idOrPath, db) {
4813
+ const d = db || getDatabase();
4814
+ let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
4815
+ if (row)
4816
+ return parseProjectRow(row);
4817
+ row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
4818
+ if (row)
4819
+ return parseProjectRow(row);
4820
+ row = d.query("SELECT * FROM projects WHERE LOWER(name) = ?").get(idOrPath.toLowerCase());
4821
+ if (row)
4822
+ return parseProjectRow(row);
4823
+ return null;
4824
+ }
4825
+ function listProjects(db) {
4826
+ const d = db || getDatabase();
4827
+ const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
4828
+ return rows.map(parseProjectRow);
4829
+ }
4830
+
4831
+ // src/cli/index.tsx
4832
+ init_search();
4833
+
4834
+ // src/lib/config.ts
4835
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync, unlinkSync } from "fs";
4836
+ import { homedir } from "os";
4837
+ import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
4838
+ var DEFAULT_CONFIG = {
4839
+ default_scope: "private",
4840
+ default_category: "knowledge",
4841
+ default_importance: 5,
4842
+ max_entries: 1000,
4843
+ max_entries_per_scope: {
4844
+ global: 500,
4845
+ shared: 300,
4846
+ private: 200
4847
+ },
4848
+ injection: {
4849
+ max_tokens: 500,
4850
+ min_importance: 5,
4851
+ categories: ["preference", "fact"],
4852
+ refresh_interval: 5
4853
+ },
4854
+ extraction: {
4855
+ enabled: true,
4856
+ min_confidence: 0.5
4857
+ },
4858
+ sync_agents: ["claude", "codex", "gemini"],
4859
+ auto_cleanup: {
4860
+ enabled: true,
4861
+ expired_check_interval: 3600,
4862
+ unused_archive_days: 7,
4863
+ stale_deprioritize_days: 14
4201
4864
  }
4202
- const fieldClauses = [];
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);
4865
+ };
4866
+ function deepMerge(target, source) {
4867
+ const result = { ...target };
4868
+ for (const key of Object.keys(source)) {
4869
+ const sourceVal = source[key];
4870
+ const targetVal = result[key];
4871
+ if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
4872
+ result[key] = deepMerge(targetVal, sourceVal);
4873
+ } else {
4874
+ result[key] = sourceVal;
4875
+ }
4214
4876
  }
4215
- conditions.push(`(${fieldClauses.join(" OR ")})`);
4216
- const sql = `SELECT DISTINCT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
4217
- const rows = d.query(sql).all(...params);
4218
- return scoreResults(rows, queryLower, graphBoostedIds);
4877
+ return result;
4219
4878
  }
4220
- function generateTrigrams(s) {
4221
- const lower = s.toLowerCase();
4222
- const trigrams = new Set;
4223
- for (let i = 0;i <= lower.length - 3; i++) {
4224
- trigrams.add(lower.slice(i, i + 3));
4225
- }
4226
- return trigrams;
4879
+ var VALID_SCOPES = ["global", "shared", "private"];
4880
+ var VALID_CATEGORIES = [
4881
+ "preference",
4882
+ "fact",
4883
+ "knowledge",
4884
+ "history"
4885
+ ];
4886
+ function isValidScope(value) {
4887
+ return VALID_SCOPES.includes(value);
4227
4888
  }
4228
- function trigramSimilarity(a, b) {
4229
- const triA = generateTrigrams(a);
4230
- const triB = generateTrigrams(b);
4231
- if (triA.size === 0 || triB.size === 0)
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;
4889
+ function isValidCategory(value) {
4890
+ return VALID_CATEGORIES.includes(value);
4240
4891
  }
4241
- function searchWithFuzzy(d, query, filter, graphBoostedIds) {
4242
- const { conditions, params } = buildFilterConditions(filter);
4243
- const sql = `SELECT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
4244
- const rows = d.query(sql).all(...params);
4245
- const MIN_SIMILARITY = 0.3;
4246
- const results = [];
4247
- for (const row of rows) {
4248
- const memory = parseMemoryRow2(row);
4249
- let bestSimilarity = 0;
4250
- bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.key));
4251
- bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.value.slice(0, 200)));
4252
- if (memory.summary) {
4253
- bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.summary));
4254
- }
4255
- for (const tag of memory.tags) {
4256
- bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, tag));
4257
- }
4258
- if (bestSimilarity >= MIN_SIMILARITY) {
4259
- const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
4260
- const score = bestSimilarity * 5 * memory.importance / 10 + graphBoost;
4261
- results.push({ memory, score, match_type: "fuzzy" });
4892
+ function loadConfig() {
4893
+ const configPath = join2(homedir(), ".mementos", "config.json");
4894
+ let fileConfig = {};
4895
+ if (existsSync2(configPath)) {
4896
+ try {
4897
+ const raw = readFileSync(configPath, "utf-8");
4898
+ fileConfig = JSON.parse(raw);
4899
+ } catch {}
4900
+ }
4901
+ const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
4902
+ const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
4903
+ if (envScope && isValidScope(envScope)) {
4904
+ merged.default_scope = envScope;
4905
+ }
4906
+ const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
4907
+ if (envCategory && isValidCategory(envCategory)) {
4908
+ merged.default_category = envCategory;
4909
+ }
4910
+ const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
4911
+ if (envImportance) {
4912
+ const parsed = parseInt(envImportance, 10);
4913
+ if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
4914
+ merged.default_importance = parsed;
4262
4915
  }
4263
4916
  }
4264
- results.sort((a, b) => b.score - a.score);
4265
- return results;
4917
+ return merged;
4266
4918
  }
4267
- function getGraphBoostedMemoryIds(query, d) {
4268
- const boostedIds = new Set;
4269
- try {
4270
- const matchingEntities = listEntities({ search: query, limit: 10 }, d);
4271
- const exactMatch = getEntityByName(query, undefined, undefined, d);
4272
- if (exactMatch && !matchingEntities.find((e) => e.id === exactMatch.id)) {
4273
- matchingEntities.push(exactMatch);
4274
- }
4275
- for (const entity of matchingEntities) {
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;
4919
+ function profilesDir() {
4920
+ return join2(homedir(), ".mementos", "profiles");
4283
4921
  }
4284
- function computeRecencyBoost(memory) {
4285
- if (memory.pinned)
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);
4922
+ function globalConfigPath() {
4923
+ return join2(homedir(), ".mementos", "config.json");
4292
4924
  }
4293
- function scoreResults(rows, queryLower, graphBoostedIds) {
4294
- const scored = [];
4295
- for (const row of rows) {
4296
- const memory = parseMemoryRow2(row);
4297
- const rawScore = computeScore(memory, queryLower);
4298
- if (rawScore === 0)
4299
- continue;
4300
- const weightedScore = rawScore * memory.importance / 10;
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
- });
4925
+ function readGlobalConfig() {
4926
+ const p = globalConfigPath();
4927
+ if (!existsSync2(p))
4928
+ return {};
4929
+ try {
4930
+ return JSON.parse(readFileSync(p, "utf-8"));
4931
+ } catch {
4932
+ return {};
4312
4933
  }
4313
- scored.sort((a, b) => {
4314
- if (b.score !== a.score)
4315
- return b.score - a.score;
4316
- return b.memory.importance - a.memory.importance;
4317
- });
4318
- return scored;
4319
4934
  }
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
- }
4935
+ function writeGlobalConfig(data) {
4936
+ const p = globalConfigPath();
4937
+ ensureDir2(dirname2(p));
4938
+ writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");
4939
+ }
4940
+ function getActiveProfile() {
4941
+ const envProfile = process.env["MEMENTOS_PROFILE"];
4942
+ if (envProfile)
4943
+ return envProfile.trim();
4944
+ const cfg = readGlobalConfig();
4945
+ return cfg["active_profile"] || null;
4946
+ }
4947
+ function setActiveProfile(name) {
4948
+ const cfg = readGlobalConfig();
4949
+ if (name === null) {
4950
+ delete cfg["active_profile"];
4335
4951
  } else {
4336
- scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
4337
- }
4338
- if (scored.length < 3) {
4339
- const fuzzyResults = searchWithFuzzy(d, query, filter, graphBoostedIds);
4340
- const seenIds = new Set(scored.map((r) => r.memory.id));
4341
- for (const fr of fuzzyResults) {
4342
- if (!seenIds.has(fr.memory.id)) {
4343
- scored.push(fr);
4344
- seenIds.add(fr.memory.id);
4345
- }
4346
- }
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
- });
4952
+ cfg["active_profile"] = name;
4352
4953
  }
4353
- const offset = filter?.offset ?? 0;
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;
4954
+ writeGlobalConfig(cfg);
4358
4955
  }
4359
- function logSearchQuery(query, resultCount, agentId, projectId, db) {
4360
- try {
4361
- const d = db || getDatabase();
4362
- const id = crypto.randomUUID().slice(0, 8);
4363
- d.run("INSERT INTO search_history (id, query, result_count, agent_id, project_id) VALUES (?, ?, ?, ?, ?)", [id, query, resultCount, agentId || null, projectId || null]);
4364
- } catch {}
4956
+ function listProfiles() {
4957
+ const dir = profilesDir();
4958
+ if (!existsSync2(dir))
4959
+ return [];
4960
+ return readdirSync(dir).filter((f) => f.endsWith(".db")).map((f) => basename(f, ".db")).sort();
4365
4961
  }
4366
- function getSearchHistory(limit = 20, projectId, db) {
4367
- const d = db || getDatabase();
4368
- if (projectId) {
4369
- return d.query("SELECT query, result_count, created_at FROM search_history WHERE project_id = ? ORDER BY created_at DESC LIMIT ?").all(projectId, limit);
4370
- }
4371
- return d.query("SELECT query, result_count, created_at FROM search_history ORDER BY created_at DESC LIMIT ?").all(limit);
4962
+ function deleteProfile(name) {
4963
+ const dbPath = join2(profilesDir(), `${name}.db`);
4964
+ if (!existsSync2(dbPath))
4965
+ return false;
4966
+ unlinkSync(dbPath);
4967
+ if (getActiveProfile() === name)
4968
+ setActiveProfile(null);
4969
+ return true;
4372
4970
  }
4373
- function getPopularSearches(limit = 10, projectId, db) {
4374
- const d = db || getDatabase();
4375
- if (projectId) {
4376
- 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);
4971
+ function ensureDir2(dir) {
4972
+ if (!existsSync2(dir)) {
4973
+ mkdirSync2(dir, { recursive: true });
4377
4974
  }
4378
- return d.query("SELECT query, COUNT(*) as count FROM search_history GROUP BY query ORDER BY count DESC LIMIT ?").all(limit);
4379
4975
  }
4380
4976
 
4381
- // src/cli/index.tsx
4382
- init_config();
4383
-
4384
4977
  // src/lib/retention.ts
4385
4978
  init_database();
4386
4979
  init_memories();
@@ -7449,4 +8042,110 @@ profileCmd.command("delete <name>").description("Delete a profile and its DB fil
7449
8042
  process.exit(1);
7450
8043
  }
7451
8044
  });
8045
+ var autoMemory = program2.command("auto-memory").description("LLM-based auto-memory extraction pipeline");
8046
+ 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) => {
8047
+ const { processConversationTurn: processConversationTurn2, getAutoMemoryStats: getAutoMemoryStats2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
8048
+ const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
8049
+ if (opts.sync) {
8050
+ const provider = providerRegistry2.getAvailable();
8051
+ if (!provider) {
8052
+ console.error(chalk.red("No LLM provider configured. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, CEREBRAS_API_KEY, or XAI_API_KEY."));
8053
+ process.exit(1);
8054
+ }
8055
+ console.log(chalk.dim(`Using ${provider.name} / ${provider.config.model}...`));
8056
+ const memories = await provider.extractMemories(turn, {
8057
+ agentId: opts.agent,
8058
+ projectId: opts.project
8059
+ });
8060
+ if (memories.length === 0) {
8061
+ console.log(chalk.dim("Nothing worth remembering extracted."));
8062
+ } else {
8063
+ memories.forEach((m, i) => {
8064
+ console.log(chalk.bold(`
8065
+ [${i + 1}] ${m.category} \xB7 importance ${m.importance}/10 \xB7 ${m.suggestedScope}`));
8066
+ console.log(` ${m.content}`);
8067
+ if (m.tags.length)
8068
+ console.log(chalk.dim(` tags: ${m.tags.join(", ")}`));
8069
+ });
8070
+ }
8071
+ } else {
8072
+ processConversationTurn2(turn, {
8073
+ agentId: opts.agent,
8074
+ projectId: opts.project,
8075
+ sessionId: opts.session
8076
+ });
8077
+ const stats = getAutoMemoryStats2();
8078
+ console.log(chalk.green("\u2713 Queued for extraction"));
8079
+ console.log(chalk.dim(`Queue: ${stats.pending} pending \xB7 ${stats.processed} processed \xB7 ${stats.failed} failed`));
8080
+ }
8081
+ });
8082
+ autoMemory.command("status").description("Show auto-memory queue stats and provider health").action(async () => {
8083
+ const { getAutoMemoryStats: getAutoMemoryStats2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
8084
+ const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
8085
+ const stats = getAutoMemoryStats2();
8086
+ const config = providerRegistry2.getConfig();
8087
+ const health = providerRegistry2.health();
8088
+ console.log(chalk.bold("Auto-Memory Status"));
8089
+ console.log(` Provider: ${config.enabled ? chalk.green(config.provider) : chalk.red("disabled")} / ${config.model ?? "default"}`);
8090
+ console.log(` Queue: ${stats.pending} pending \xB7 ${stats.processing} processing \xB7 ${stats.processed} processed`);
8091
+ console.log(` Errors: ${stats.failed} failed \xB7 ${stats.dropped} dropped`);
8092
+ console.log(chalk.bold(`
8093
+ Provider Health`));
8094
+ for (const [name, info] of Object.entries(health)) {
8095
+ const icon = info.available ? chalk.green("\u2713") : chalk.red("\u2717");
8096
+ console.log(` ${icon} ${name.padEnd(12)} ${info.model}`);
8097
+ }
8098
+ });
8099
+ 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) => {
8100
+ const { configureAutoMemory: configureAutoMemory2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
8101
+ const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
8102
+ if (opts.provider || opts.model || opts.minImportance) {
8103
+ configureAutoMemory2({
8104
+ ...opts.provider && { provider: opts.provider },
8105
+ ...opts.model && { model: opts.model },
8106
+ ...opts.minImportance && { minImportance: Number(opts.minImportance) }
8107
+ });
8108
+ console.log(chalk.green("\u2713 Config updated"));
8109
+ }
8110
+ const config = providerRegistry2.getConfig();
8111
+ console.log(chalk.bold("Auto-Memory Config"));
8112
+ console.log(JSON.stringify(config, null, 2));
8113
+ });
8114
+ 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) => {
8115
+ const { providerRegistry: providerRegistry2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
8116
+ const provider = opts.provider ? providerRegistry2.getProvider(opts.provider) : providerRegistry2.getAvailable();
8117
+ if (!provider) {
8118
+ console.error(chalk.red("No LLM provider configured."));
8119
+ process.exit(1);
8120
+ }
8121
+ console.log(chalk.dim(`DRY RUN \u2014 ${provider.name} / ${provider.config.model} \u2014 nothing will be saved
8122
+ `));
8123
+ const memories = await provider.extractMemories(turn, {
8124
+ agentId: opts.agent,
8125
+ projectId: opts.project
8126
+ });
8127
+ if (memories.length === 0) {
8128
+ console.log(chalk.dim("Nothing extracted."));
8129
+ } else {
8130
+ memories.forEach((m, i) => {
8131
+ console.log(chalk.bold(`[${i + 1}] ${m.category.toUpperCase()} \xB7 importance ${m.importance}/10 \xB7 ${m.suggestedScope}`));
8132
+ console.log(` ${chalk.white(m.content)}`);
8133
+ if (m.tags.length)
8134
+ console.log(chalk.dim(` tags: ${m.tags.join(", ")}`));
8135
+ if (m.reasoning)
8136
+ console.log(chalk.dim(` why: ${m.reasoning}`));
8137
+ console.log();
8138
+ });
8139
+ }
8140
+ });
8141
+ autoMemory.command("enable").description("Enable auto-memory extraction").action(async () => {
8142
+ const { configureAutoMemory: configureAutoMemory2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
8143
+ configureAutoMemory2({ enabled: true });
8144
+ console.log(chalk.green("\u2713 Auto-memory enabled"));
8145
+ });
8146
+ autoMemory.command("disable").description("Disable auto-memory extraction").action(async () => {
8147
+ const { configureAutoMemory: configureAutoMemory2 } = await Promise.resolve().then(() => (init_auto_memory(), exports_auto_memory));
8148
+ configureAutoMemory2({ enabled: false });
8149
+ console.log(chalk.yellow("\u26A0 Auto-memory disabled"));
8150
+ });
7452
8151
  program2.parse(process.argv);