@hasna/mementos 0.4.39 → 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.
- package/dist/cli/index.js +2301 -1545
- package/dist/db/agents.d.ts +1 -1
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts +52 -0
- package/dist/db/locks.d.ts.map +1 -0
- package/dist/db/memories.d.ts +1 -0
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1905 -1003
- package/dist/lib/auto-memory-queue.d.ts +46 -0
- package/dist/lib/auto-memory-queue.d.ts.map +1 -0
- package/dist/lib/auto-memory.d.ts +18 -0
- package/dist/lib/auto-memory.d.ts.map +1 -0
- package/dist/lib/dedup.d.ts +33 -0
- package/dist/lib/dedup.d.ts.map +1 -0
- package/dist/lib/focus.d.ts +58 -0
- package/dist/lib/focus.d.ts.map +1 -0
- package/dist/lib/memory-lock.d.ts +58 -0
- package/dist/lib/memory-lock.d.ts.map +1 -0
- package/dist/lib/providers/anthropic.d.ts +21 -0
- package/dist/lib/providers/anthropic.d.ts.map +1 -0
- package/dist/lib/providers/base.d.ts +96 -0
- package/dist/lib/providers/base.d.ts.map +1 -0
- package/dist/lib/providers/cerebras.d.ts +20 -0
- package/dist/lib/providers/cerebras.d.ts.map +1 -0
- package/dist/lib/providers/grok.d.ts +19 -0
- package/dist/lib/providers/grok.d.ts.map +1 -0
- package/dist/lib/providers/index.d.ts +7 -0
- package/dist/lib/providers/index.d.ts.map +1 -0
- package/dist/lib/providers/openai-compat.d.ts +18 -0
- package/dist/lib/providers/openai-compat.d.ts.map +1 -0
- package/dist/lib/providers/openai.d.ts +20 -0
- package/dist/lib/providers/openai.d.ts.map +1 -0
- package/dist/lib/providers/registry.d.ts +38 -0
- package/dist/lib/providers/registry.d.ts.map +1 -0
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +1917 -996
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1602 -894
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -2405,13 +2405,58 @@ var init_database = __esm(() => {
|
|
|
2405
2405
|
ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
2406
2406
|
CREATE INDEX IF NOT EXISTS idx_agents_active_project ON agents(active_project_id);
|
|
2407
2407
|
INSERT OR IGNORE INTO _migrations (id) VALUES (6);
|
|
2408
|
+
`,
|
|
2409
|
+
`
|
|
2410
|
+
ALTER TABLE agents ADD COLUMN session_id TEXT;
|
|
2411
|
+
CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);
|
|
2412
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (7);
|
|
2413
|
+
`,
|
|
2414
|
+
`
|
|
2415
|
+
CREATE TABLE IF NOT EXISTS resource_locks (
|
|
2416
|
+
id TEXT PRIMARY KEY,
|
|
2417
|
+
resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector')),
|
|
2418
|
+
resource_id TEXT NOT NULL,
|
|
2419
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
2420
|
+
lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
|
|
2421
|
+
locked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2422
|
+
expires_at TEXT NOT NULL
|
|
2423
|
+
);
|
|
2424
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
|
|
2425
|
+
ON resource_locks(resource_type, resource_id)
|
|
2426
|
+
WHERE lock_type = 'exclusive';
|
|
2427
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
|
|
2428
|
+
CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
|
|
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);
|
|
2408
2435
|
`
|
|
2409
2436
|
];
|
|
2410
2437
|
});
|
|
2411
2438
|
|
|
2412
2439
|
// src/types/index.ts
|
|
2413
|
-
var EntityNotFoundError, MemoryNotFoundError, VersionConflictError;
|
|
2440
|
+
var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, VersionConflictError;
|
|
2414
2441
|
var init_types = __esm(() => {
|
|
2442
|
+
AgentConflictError = class AgentConflictError extends Error {
|
|
2443
|
+
conflict = true;
|
|
2444
|
+
existing_id;
|
|
2445
|
+
existing_name;
|
|
2446
|
+
last_seen_at;
|
|
2447
|
+
session_hint;
|
|
2448
|
+
working_dir;
|
|
2449
|
+
constructor(opts) {
|
|
2450
|
+
const msg = `Agent "${opts.existing_name}" is already active (session hint: ${opts.session_hint ?? "unknown"}, last seen ${opts.last_seen_at}). Wait 30 minutes or use a different name.`;
|
|
2451
|
+
super(msg);
|
|
2452
|
+
this.name = "AgentConflictError";
|
|
2453
|
+
this.existing_id = opts.existing_id;
|
|
2454
|
+
this.existing_name = opts.existing_name;
|
|
2455
|
+
this.last_seen_at = opts.last_seen_at;
|
|
2456
|
+
this.session_hint = opts.session_hint;
|
|
2457
|
+
this.working_dir = opts.working_dir ?? null;
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2415
2460
|
EntityNotFoundError = class EntityNotFoundError extends Error {
|
|
2416
2461
|
constructor(id) {
|
|
2417
2462
|
super(`Entity not found: ${id}`);
|
|
@@ -2466,464 +2511,462 @@ var init_redact = __esm(() => {
|
|
|
2466
2511
|
];
|
|
2467
2512
|
});
|
|
2468
2513
|
|
|
2469
|
-
// src/db/
|
|
2470
|
-
function
|
|
2514
|
+
// src/db/entity-memories.ts
|
|
2515
|
+
function parseEntityMemoryRow(row) {
|
|
2471
2516
|
return {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2477
|
-
active_project_id: row["active_project_id"] || null,
|
|
2478
|
-
created_at: row["created_at"],
|
|
2479
|
-
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"]
|
|
2480
2521
|
};
|
|
2481
2522
|
}
|
|
2482
|
-
function
|
|
2523
|
+
function linkEntityToMemory(entityId, memoryId, role = "context", db) {
|
|
2483
2524
|
const d = db || getDatabase();
|
|
2484
2525
|
const timestamp = now();
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [
|
|
2490
|
-
timestamp,
|
|
2491
|
-
existingId
|
|
2492
|
-
]);
|
|
2493
|
-
if (description) {
|
|
2494
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
2495
|
-
description,
|
|
2496
|
-
existingId
|
|
2497
|
-
]);
|
|
2498
|
-
}
|
|
2499
|
-
if (role) {
|
|
2500
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
2501
|
-
role,
|
|
2502
|
-
existingId
|
|
2503
|
-
]);
|
|
2504
|
-
}
|
|
2505
|
-
return getAgent(existingId, d);
|
|
2506
|
-
}
|
|
2507
|
-
const id = shortUuid();
|
|
2508
|
-
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, normalizedName, description || null, role || "agent", timestamp, timestamp]);
|
|
2509
|
-
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);
|
|
2510
2530
|
}
|
|
2511
|
-
function
|
|
2531
|
+
function unlinkEntityFromMemory(entityId, memoryId, db) {
|
|
2512
2532
|
const d = db || getDatabase();
|
|
2513
|
-
|
|
2514
|
-
if (row)
|
|
2515
|
-
return parseAgentRow(row);
|
|
2516
|
-
row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
|
|
2517
|
-
if (row)
|
|
2518
|
-
return parseAgentRow(row);
|
|
2519
|
-
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
2520
|
-
if (rows.length === 1)
|
|
2521
|
-
return parseAgentRow(rows[0]);
|
|
2522
|
-
return null;
|
|
2533
|
+
d.run("DELETE FROM entity_memories WHERE entity_id = ? AND memory_id = ?", [entityId, memoryId]);
|
|
2523
2534
|
}
|
|
2524
|
-
function
|
|
2535
|
+
function getMemoriesForEntity(entityId, db) {
|
|
2525
2536
|
const d = db || getDatabase();
|
|
2526
|
-
const rows = d.query(
|
|
2527
|
-
|
|
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);
|
|
2528
2542
|
}
|
|
2529
|
-
function
|
|
2543
|
+
function getEntityMemoryLinks(entityId, memoryId, db) {
|
|
2530
2544
|
const d = db || getDatabase();
|
|
2531
|
-
const
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
const normalizedNewName = updates.name.trim().toLowerCase();
|
|
2537
|
-
if (normalizedNewName !== agent.name) {
|
|
2538
|
-
const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
|
|
2539
|
-
if (existing) {
|
|
2540
|
-
throw new Error(`Agent name already taken: ${normalizedNewName}`);
|
|
2541
|
-
}
|
|
2542
|
-
d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
if (updates.description !== undefined) {
|
|
2546
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
|
|
2547
|
-
}
|
|
2548
|
-
if (updates.role !== undefined) {
|
|
2549
|
-
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);
|
|
2550
2550
|
}
|
|
2551
|
-
if (
|
|
2552
|
-
|
|
2551
|
+
if (memoryId) {
|
|
2552
|
+
conditions.push("memory_id = ?");
|
|
2553
|
+
params.push(memoryId);
|
|
2553
2554
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2555
|
+
let sql = "SELECT * FROM entity_memories";
|
|
2556
|
+
if (conditions.length > 0) {
|
|
2557
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
2556
2558
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
+
sql += " ORDER BY created_at DESC";
|
|
2560
|
+
const rows = d.query(sql).all(...params);
|
|
2561
|
+
return rows.map(parseEntityMemoryRow);
|
|
2559
2562
|
}
|
|
2560
|
-
var
|
|
2563
|
+
var init_entity_memories = __esm(() => {
|
|
2561
2564
|
init_database();
|
|
2565
|
+
init_memories();
|
|
2562
2566
|
});
|
|
2563
2567
|
|
|
2564
|
-
// src/db/
|
|
2565
|
-
function
|
|
2568
|
+
// src/db/memories.ts
|
|
2569
|
+
function runEntityExtraction(_memory, _projectId, _d) {}
|
|
2570
|
+
function parseMemoryRow(row) {
|
|
2566
2571
|
return {
|
|
2567
2572
|
id: row["id"],
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
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,
|
|
2572
2590
|
created_at: row["created_at"],
|
|
2573
|
-
updated_at: row["updated_at"]
|
|
2591
|
+
updated_at: row["updated_at"],
|
|
2592
|
+
accessed_at: row["accessed_at"] || null
|
|
2574
2593
|
};
|
|
2575
2594
|
}
|
|
2576
|
-
function
|
|
2595
|
+
function createMemory(input, dedupeMode = "merge", db) {
|
|
2577
2596
|
const d = db || getDatabase();
|
|
2578
2597
|
const timestamp = now();
|
|
2579
|
-
|
|
2580
|
-
if (
|
|
2581
|
-
|
|
2582
|
-
d.run("UPDATE projects SET updated_at = ? WHERE id = ?", [
|
|
2583
|
-
timestamp,
|
|
2584
|
-
existingId
|
|
2585
|
-
]);
|
|
2586
|
-
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();
|
|
2587
2601
|
}
|
|
2588
2602
|
const id = uuid();
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
const
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
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);
|
|
2635
|
+
}
|
|
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;
|
|
2631
2645
|
}
|
|
2632
2646
|
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
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, ?, ?, ?)`, [
|
|
2649
|
+
id,
|
|
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,
|
|
2659
|
+
input.project_id || null,
|
|
2660
|
+
input.session_id || null,
|
|
2661
|
+
metadataJson,
|
|
2662
|
+
expiresAt,
|
|
2663
|
+
timestamp,
|
|
2664
|
+
timestamp
|
|
2665
|
+
]);
|
|
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);
|
|
2641
2669
|
}
|
|
2670
|
+
const memory = getMemory(id, d);
|
|
2642
2671
|
try {
|
|
2643
|
-
|
|
2644
|
-
const agents = listAgents(d);
|
|
2645
|
-
const textLower2 = text.toLowerCase();
|
|
2646
|
-
for (const agent of agents) {
|
|
2647
|
-
const nameLower = agent.name.toLowerCase();
|
|
2648
|
-
if (nameLower.length >= 3 && textLower2.includes(nameLower)) {
|
|
2649
|
-
add(agent.name, "person", 0.95);
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2672
|
+
runEntityExtraction(memory, input.project_id, d);
|
|
2652
2673
|
} catch {}
|
|
2653
|
-
|
|
2654
|
-
const d = db || getDatabase();
|
|
2655
|
-
const projects = listProjects(d);
|
|
2656
|
-
const textLower2 = text.toLowerCase();
|
|
2657
|
-
for (const project of projects) {
|
|
2658
|
-
const nameLower = project.name.toLowerCase();
|
|
2659
|
-
if (nameLower.length >= 3 && textLower2.includes(nameLower)) {
|
|
2660
|
-
add(project.name, "project", 0.95);
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
} catch {}
|
|
2664
|
-
const textLower = text.toLowerCase();
|
|
2665
|
-
for (const keyword of TECH_KEYWORDS) {
|
|
2666
|
-
const re = new RegExp(`\\b${keyword}\\b`, "i");
|
|
2667
|
-
if (re.test(textLower)) {
|
|
2668
|
-
add(keyword, "tool", 0.7);
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
for (const match of text.matchAll(PASCAL_CASE_RE)) {
|
|
2672
|
-
add(match[1], "concept", 0.5);
|
|
2673
|
-
}
|
|
2674
|
-
return Array.from(entityMap.values()).sort((a, b) => b.confidence - a.confidence);
|
|
2675
|
-
}
|
|
2676
|
-
var TECH_KEYWORDS, FILE_PATH_RE, URL_RE, NPM_PACKAGE_RE, PASCAL_CASE_RE;
|
|
2677
|
-
var init_extractor = __esm(() => {
|
|
2678
|
-
init_database();
|
|
2679
|
-
init_agents();
|
|
2680
|
-
init_projects();
|
|
2681
|
-
TECH_KEYWORDS = new Set([
|
|
2682
|
-
"typescript",
|
|
2683
|
-
"javascript",
|
|
2684
|
-
"python",
|
|
2685
|
-
"rust",
|
|
2686
|
-
"go",
|
|
2687
|
-
"java",
|
|
2688
|
-
"ruby",
|
|
2689
|
-
"swift",
|
|
2690
|
-
"kotlin",
|
|
2691
|
-
"react",
|
|
2692
|
-
"vue",
|
|
2693
|
-
"angular",
|
|
2694
|
-
"svelte",
|
|
2695
|
-
"nextjs",
|
|
2696
|
-
"bun",
|
|
2697
|
-
"node",
|
|
2698
|
-
"deno",
|
|
2699
|
-
"sqlite",
|
|
2700
|
-
"postgres",
|
|
2701
|
-
"mysql",
|
|
2702
|
-
"redis",
|
|
2703
|
-
"docker",
|
|
2704
|
-
"kubernetes",
|
|
2705
|
-
"git",
|
|
2706
|
-
"npm",
|
|
2707
|
-
"yarn",
|
|
2708
|
-
"pnpm",
|
|
2709
|
-
"webpack",
|
|
2710
|
-
"vite",
|
|
2711
|
-
"tailwind",
|
|
2712
|
-
"prisma",
|
|
2713
|
-
"drizzle",
|
|
2714
|
-
"zod",
|
|
2715
|
-
"commander",
|
|
2716
|
-
"express",
|
|
2717
|
-
"fastify",
|
|
2718
|
-
"hono"
|
|
2719
|
-
]);
|
|
2720
|
-
FILE_PATH_RE = /(?:^|\s)((?:\/|\.\/|~\/)?(?:[\w.-]+\/)+[\w.-]+\.\w+)/g;
|
|
2721
|
-
URL_RE = /https?:\/\/[^\s)]+/g;
|
|
2722
|
-
NPM_PACKAGE_RE = /@[\w-]+\/[\w.-]+/g;
|
|
2723
|
-
PASCAL_CASE_RE = /\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g;
|
|
2724
|
-
});
|
|
2725
|
-
|
|
2726
|
-
// src/db/entities.ts
|
|
2727
|
-
function parseEntityRow(row) {
|
|
2728
|
-
return {
|
|
2729
|
-
id: row["id"],
|
|
2730
|
-
name: row["name"],
|
|
2731
|
-
type: row["type"],
|
|
2732
|
-
description: row["description"] || null,
|
|
2733
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2734
|
-
project_id: row["project_id"] || null,
|
|
2735
|
-
created_at: row["created_at"],
|
|
2736
|
-
updated_at: row["updated_at"]
|
|
2737
|
-
};
|
|
2738
|
-
}
|
|
2739
|
-
function createEntity(input, db) {
|
|
2740
|
-
const d = db || getDatabase();
|
|
2741
|
-
const timestamp = now();
|
|
2742
|
-
const metadataJson = JSON.stringify(input.metadata || {});
|
|
2743
|
-
const existing = d.query(`SELECT * FROM entities
|
|
2744
|
-
WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
|
|
2745
|
-
if (existing) {
|
|
2746
|
-
const sets = ["updated_at = ?"];
|
|
2747
|
-
const params = [timestamp];
|
|
2748
|
-
if (input.description !== undefined) {
|
|
2749
|
-
sets.push("description = ?");
|
|
2750
|
-
params.push(input.description);
|
|
2751
|
-
}
|
|
2752
|
-
if (input.metadata !== undefined) {
|
|
2753
|
-
sets.push("metadata = ?");
|
|
2754
|
-
params.push(metadataJson);
|
|
2755
|
-
}
|
|
2756
|
-
const existingId = existing["id"];
|
|
2757
|
-
params.push(existingId);
|
|
2758
|
-
d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2759
|
-
return getEntity(existingId, d);
|
|
2760
|
-
}
|
|
2761
|
-
const id = shortUuid();
|
|
2762
|
-
d.run(`INSERT INTO entities (id, name, type, description, metadata, project_id, created_at, updated_at)
|
|
2763
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
2764
|
-
id,
|
|
2765
|
-
input.name,
|
|
2766
|
-
input.type,
|
|
2767
|
-
input.description || null,
|
|
2768
|
-
metadataJson,
|
|
2769
|
-
input.project_id || null,
|
|
2770
|
-
timestamp,
|
|
2771
|
-
timestamp
|
|
2772
|
-
]);
|
|
2773
|
-
return getEntity(id, d);
|
|
2674
|
+
return memory;
|
|
2774
2675
|
}
|
|
2775
|
-
function
|
|
2676
|
+
function getMemory(id, db) {
|
|
2776
2677
|
const d = db || getDatabase();
|
|
2777
|
-
const row = d.query("SELECT * FROM
|
|
2678
|
+
const row = d.query("SELECT * FROM memories WHERE id = ?").get(id);
|
|
2778
2679
|
if (!row)
|
|
2779
|
-
|
|
2780
|
-
return
|
|
2680
|
+
return null;
|
|
2681
|
+
return parseMemoryRow(row);
|
|
2781
2682
|
}
|
|
2782
|
-
function
|
|
2683
|
+
function getMemoryByKey(key, scope, agentId, projectId, sessionId, db) {
|
|
2783
2684
|
const d = db || getDatabase();
|
|
2784
|
-
let sql = "SELECT * FROM
|
|
2785
|
-
const params = [
|
|
2786
|
-
if (
|
|
2787
|
-
sql += " AND
|
|
2788
|
-
params.push(
|
|
2685
|
+
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
2686
|
+
const params = [key];
|
|
2687
|
+
if (scope) {
|
|
2688
|
+
sql += " AND scope = ?";
|
|
2689
|
+
params.push(scope);
|
|
2789
2690
|
}
|
|
2790
|
-
if (
|
|
2691
|
+
if (agentId) {
|
|
2692
|
+
sql += " AND agent_id = ?";
|
|
2693
|
+
params.push(agentId);
|
|
2694
|
+
}
|
|
2695
|
+
if (projectId) {
|
|
2791
2696
|
sql += " AND project_id = ?";
|
|
2792
2697
|
params.push(projectId);
|
|
2793
2698
|
}
|
|
2794
|
-
|
|
2699
|
+
if (sessionId) {
|
|
2700
|
+
sql += " AND session_id = ?";
|
|
2701
|
+
params.push(sessionId);
|
|
2702
|
+
}
|
|
2703
|
+
sql += " AND status = 'active' ORDER BY importance DESC LIMIT 1";
|
|
2795
2704
|
const row = d.query(sql).get(...params);
|
|
2796
2705
|
if (!row)
|
|
2797
2706
|
return null;
|
|
2798
|
-
return
|
|
2707
|
+
return parseMemoryRow(row);
|
|
2799
2708
|
}
|
|
2800
|
-
function
|
|
2709
|
+
function getMemoriesByKey(key, scope, agentId, projectId, db) {
|
|
2801
2710
|
const d = db || getDatabase();
|
|
2802
|
-
|
|
2803
|
-
const params = [];
|
|
2804
|
-
if (
|
|
2805
|
-
|
|
2806
|
-
params.push(
|
|
2711
|
+
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
2712
|
+
const params = [key];
|
|
2713
|
+
if (scope) {
|
|
2714
|
+
sql += " AND scope = ?";
|
|
2715
|
+
params.push(scope);
|
|
2807
2716
|
}
|
|
2808
|
-
if (
|
|
2809
|
-
|
|
2810
|
-
params.push(
|
|
2717
|
+
if (agentId) {
|
|
2718
|
+
sql += " AND agent_id = ?";
|
|
2719
|
+
params.push(agentId);
|
|
2811
2720
|
}
|
|
2812
|
-
if (
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
params.push(term, term);
|
|
2721
|
+
if (projectId) {
|
|
2722
|
+
sql += " AND project_id = ?";
|
|
2723
|
+
params.push(projectId);
|
|
2816
2724
|
}
|
|
2817
|
-
|
|
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";
|
|
2818
2807
|
if (conditions.length > 0) {
|
|
2819
2808
|
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
2820
2809
|
}
|
|
2821
|
-
sql += " ORDER BY
|
|
2822
|
-
if (filter
|
|
2810
|
+
sql += " ORDER BY importance DESC, created_at DESC";
|
|
2811
|
+
if (filter?.limit) {
|
|
2823
2812
|
sql += " LIMIT ?";
|
|
2824
2813
|
params.push(filter.limit);
|
|
2825
2814
|
}
|
|
2826
|
-
if (filter
|
|
2815
|
+
if (filter?.offset) {
|
|
2827
2816
|
sql += " OFFSET ?";
|
|
2828
2817
|
params.push(filter.offset);
|
|
2829
2818
|
}
|
|
2830
2819
|
const rows = d.query(sql).all(...params);
|
|
2831
|
-
return rows.map(
|
|
2832
|
-
}
|
|
2833
|
-
function deleteEntity(id, db) {
|
|
2834
|
-
const d = db || getDatabase();
|
|
2835
|
-
const result = d.run("DELETE FROM entities WHERE id = ?", [id]);
|
|
2836
|
-
if (result.changes === 0)
|
|
2837
|
-
throw new EntityNotFoundError(id);
|
|
2838
|
-
}
|
|
2839
|
-
function mergeEntities(sourceId, targetId, db) {
|
|
2840
|
-
const d = db || getDatabase();
|
|
2841
|
-
getEntity(sourceId, d);
|
|
2842
|
-
getEntity(targetId, d);
|
|
2843
|
-
d.run(`UPDATE OR IGNORE relations SET source_entity_id = ? WHERE source_entity_id = ?`, [targetId, sourceId]);
|
|
2844
|
-
d.run(`UPDATE OR IGNORE relations SET target_entity_id = ? WHERE target_entity_id = ?`, [targetId, sourceId]);
|
|
2845
|
-
d.run("DELETE FROM relations WHERE source_entity_id = ? OR target_entity_id = ?", [
|
|
2846
|
-
sourceId,
|
|
2847
|
-
sourceId
|
|
2848
|
-
]);
|
|
2849
|
-
d.run(`UPDATE OR IGNORE entity_memories SET entity_id = ? WHERE entity_id = ?`, [targetId, sourceId]);
|
|
2850
|
-
d.run("DELETE FROM entity_memories WHERE entity_id = ?", [sourceId]);
|
|
2851
|
-
d.run("DELETE FROM entities WHERE id = ?", [sourceId]);
|
|
2852
|
-
d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), targetId]);
|
|
2853
|
-
return getEntity(targetId, d);
|
|
2854
|
-
}
|
|
2855
|
-
var init_entities = __esm(() => {
|
|
2856
|
-
init_database();
|
|
2857
|
-
init_types();
|
|
2858
|
-
});
|
|
2859
|
-
|
|
2860
|
-
// src/db/entity-memories.ts
|
|
2861
|
-
function parseEntityMemoryRow(row) {
|
|
2862
|
-
return {
|
|
2863
|
-
entity_id: row["entity_id"],
|
|
2864
|
-
memory_id: row["memory_id"],
|
|
2865
|
-
role: row["role"],
|
|
2866
|
-
created_at: row["created_at"]
|
|
2867
|
-
};
|
|
2868
|
-
}
|
|
2869
|
-
function linkEntityToMemory(entityId, memoryId, role = "context", db) {
|
|
2870
|
-
const d = db || getDatabase();
|
|
2871
|
-
const timestamp = now();
|
|
2872
|
-
d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
|
|
2873
|
-
VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
|
|
2874
|
-
const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
|
|
2875
|
-
return parseEntityMemoryRow(row);
|
|
2820
|
+
return rows.map(parseMemoryRow);
|
|
2876
2821
|
}
|
|
2877
|
-
function
|
|
2822
|
+
function updateMemory(id, input, db) {
|
|
2878
2823
|
const d = db || getDatabase();
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
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;
|
|
2907
|
+
}
|
|
2908
|
+
function deleteMemory(id, db) {
|
|
2882
2909
|
const d = db || getDatabase();
|
|
2883
|
-
const
|
|
2884
|
-
|
|
2885
|
-
WHERE em.entity_id = ?
|
|
2886
|
-
ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
|
|
2887
|
-
return rows.map(parseMemoryRow);
|
|
2910
|
+
const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
2911
|
+
return result.changes > 0;
|
|
2888
2912
|
}
|
|
2889
|
-
function
|
|
2913
|
+
function bulkDeleteMemories(ids, db) {
|
|
2890
2914
|
const d = db || getDatabase();
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
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);
|
|
2896
2922
|
}
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2923
|
+
return count;
|
|
2924
|
+
}
|
|
2925
|
+
function touchMemory(id, db) {
|
|
2926
|
+
const d = db || getDatabase();
|
|
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]);
|
|
2900
2936
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
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 [];
|
|
2904
2959
|
}
|
|
2905
|
-
sql += " ORDER BY created_at DESC";
|
|
2906
|
-
const rows = d.query(sql).all(...params);
|
|
2907
|
-
return rows.map(parseEntityMemoryRow);
|
|
2908
2960
|
}
|
|
2909
|
-
var
|
|
2961
|
+
var init_memories = __esm(() => {
|
|
2962
|
+
init_types();
|
|
2910
2963
|
init_database();
|
|
2911
|
-
|
|
2964
|
+
init_redact();
|
|
2965
|
+
init_entity_memories();
|
|
2912
2966
|
});
|
|
2913
2967
|
|
|
2914
|
-
// src/db/
|
|
2915
|
-
function
|
|
2916
|
-
return {
|
|
2917
|
-
id: row["id"],
|
|
2918
|
-
source_entity_id: row["source_entity_id"],
|
|
2919
|
-
target_entity_id: row["target_entity_id"],
|
|
2920
|
-
relation_type: row["relation_type"],
|
|
2921
|
-
weight: row["weight"],
|
|
2922
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2923
|
-
created_at: row["created_at"]
|
|
2924
|
-
};
|
|
2925
|
-
}
|
|
2926
|
-
function parseEntityRow2(row) {
|
|
2968
|
+
// src/db/entities.ts
|
|
2969
|
+
function parseEntityRow(row) {
|
|
2927
2970
|
return {
|
|
2928
2971
|
id: row["id"],
|
|
2929
2972
|
name: row["name"],
|
|
@@ -2935,299 +2978,129 @@ function parseEntityRow2(row) {
|
|
|
2935
2978
|
updated_at: row["updated_at"]
|
|
2936
2979
|
};
|
|
2937
2980
|
}
|
|
2938
|
-
function
|
|
2981
|
+
function createEntity(input, db) {
|
|
2939
2982
|
const d = db || getDatabase();
|
|
2940
|
-
const id = shortUuid();
|
|
2941
2983
|
const timestamp = now();
|
|
2942
|
-
const
|
|
2943
|
-
const
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
}
|
|
2952
|
-
function listRelations(filter, db) {
|
|
2953
|
-
const d = db || getDatabase();
|
|
2954
|
-
const conditions = [];
|
|
2955
|
-
const params = [];
|
|
2956
|
-
if (filter.entity_id) {
|
|
2957
|
-
const dir = filter.direction || "both";
|
|
2958
|
-
if (dir === "outgoing") {
|
|
2959
|
-
conditions.push("source_entity_id = ?");
|
|
2960
|
-
params.push(filter.entity_id);
|
|
2961
|
-
} else if (dir === "incoming") {
|
|
2962
|
-
conditions.push("target_entity_id = ?");
|
|
2963
|
-
params.push(filter.entity_id);
|
|
2964
|
-
} else {
|
|
2965
|
-
conditions.push("(source_entity_id = ? OR target_entity_id = ?)");
|
|
2966
|
-
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);
|
|
2967
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);
|
|
2968
3002
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
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);
|
|
2976
3016
|
}
|
|
2977
|
-
function
|
|
3017
|
+
function getEntity(id, db) {
|
|
2978
3018
|
const d = db || getDatabase();
|
|
2979
|
-
const
|
|
2980
|
-
if (
|
|
2981
|
-
throw new
|
|
3019
|
+
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
3020
|
+
if (!row)
|
|
3021
|
+
throw new EntityNotFoundError(id);
|
|
3022
|
+
return parseEntityRow(row);
|
|
2982
3023
|
}
|
|
2983
|
-
function
|
|
3024
|
+
function getEntityByName(name, type, projectId, db) {
|
|
2984
3025
|
const d = db || getDatabase();
|
|
2985
|
-
let sql;
|
|
2986
|
-
const params = [];
|
|
2987
|
-
if (
|
|
2988
|
-
sql =
|
|
2989
|
-
|
|
2990
|
-
JOIN relations r ON (
|
|
2991
|
-
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
2992
|
-
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
2993
|
-
)
|
|
2994
|
-
WHERE r.relation_type = ?
|
|
2995
|
-
`;
|
|
2996
|
-
params.push(entityId, entityId, relationType);
|
|
2997
|
-
} else {
|
|
2998
|
-
sql = `
|
|
2999
|
-
SELECT DISTINCT e.* FROM entities e
|
|
3000
|
-
JOIN relations r ON (
|
|
3001
|
-
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
3002
|
-
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
3003
|
-
)
|
|
3004
|
-
`;
|
|
3005
|
-
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);
|
|
3006
3031
|
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
function getEntityGraph(entityId, depth = 2, db) {
|
|
3011
|
-
const d = db || getDatabase();
|
|
3012
|
-
const entityRows = d.query(`WITH RECURSIVE graph(id, depth) AS (
|
|
3013
|
-
VALUES(?, 0)
|
|
3014
|
-
UNION
|
|
3015
|
-
SELECT CASE WHEN r.source_entity_id = g.id THEN r.target_entity_id ELSE r.source_entity_id END, g.depth + 1
|
|
3016
|
-
FROM relations r JOIN graph g ON (r.source_entity_id = g.id OR r.target_entity_id = g.id)
|
|
3017
|
-
WHERE g.depth < ?
|
|
3018
|
-
)
|
|
3019
|
-
SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
|
|
3020
|
-
const entities = entityRows.map(parseEntityRow2);
|
|
3021
|
-
const entityIds = new Set(entities.map((e) => e.id));
|
|
3022
|
-
if (entityIds.size === 0) {
|
|
3023
|
-
return { entities: [], relations: [] };
|
|
3032
|
+
if (projectId !== undefined) {
|
|
3033
|
+
sql += " AND project_id = ?";
|
|
3034
|
+
params.push(projectId);
|
|
3024
3035
|
}
|
|
3025
|
-
|
|
3026
|
-
const
|
|
3027
|
-
|
|
3028
|
-
AND target_entity_id IN (${placeholders})`).all(...Array.from(entityIds), ...Array.from(entityIds));
|
|
3029
|
-
const relations = relationRows.map(parseRelationRow);
|
|
3030
|
-
return { entities, relations };
|
|
3031
|
-
}
|
|
3032
|
-
function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
|
|
3033
|
-
const d = db || getDatabase();
|
|
3034
|
-
const rows = d.query(`WITH RECURSIVE path(id, trail, depth) AS (
|
|
3035
|
-
SELECT ?, ?, 0
|
|
3036
|
-
UNION
|
|
3037
|
-
SELECT
|
|
3038
|
-
CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
3039
|
-
p.trail || ',' || CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
3040
|
-
p.depth + 1
|
|
3041
|
-
FROM relations r JOIN path p ON (r.source_entity_id = p.id OR r.target_entity_id = p.id)
|
|
3042
|
-
WHERE p.depth < ?
|
|
3043
|
-
AND INSTR(p.trail, CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END) = 0
|
|
3044
|
-
)
|
|
3045
|
-
SELECT trail FROM path WHERE id = ? ORDER BY depth ASC LIMIT 1`).get(fromEntityId, fromEntityId, maxDepth, toEntityId);
|
|
3046
|
-
if (!rows)
|
|
3036
|
+
sql += " LIMIT 1";
|
|
3037
|
+
const row = d.query(sql).get(...params);
|
|
3038
|
+
if (!row)
|
|
3047
3039
|
return null;
|
|
3048
|
-
|
|
3049
|
-
const entities = [];
|
|
3050
|
-
for (const id of ids) {
|
|
3051
|
-
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
3052
|
-
if (row)
|
|
3053
|
-
entities.push(parseEntityRow2(row));
|
|
3054
|
-
}
|
|
3055
|
-
return entities.length > 0 ? entities : null;
|
|
3056
|
-
}
|
|
3057
|
-
var init_relations = __esm(() => {
|
|
3058
|
-
init_database();
|
|
3059
|
-
});
|
|
3060
|
-
|
|
3061
|
-
// src/lib/config.ts
|
|
3062
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync, unlinkSync } from "fs";
|
|
3063
|
-
import { homedir } from "os";
|
|
3064
|
-
import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
3065
|
-
function deepMerge(target, source) {
|
|
3066
|
-
const result = { ...target };
|
|
3067
|
-
for (const key of Object.keys(source)) {
|
|
3068
|
-
const sourceVal = source[key];
|
|
3069
|
-
const targetVal = result[key];
|
|
3070
|
-
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
3071
|
-
result[key] = deepMerge(targetVal, sourceVal);
|
|
3072
|
-
} else {
|
|
3073
|
-
result[key] = sourceVal;
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
|
-
return result;
|
|
3077
|
-
}
|
|
3078
|
-
function isValidScope(value) {
|
|
3079
|
-
return VALID_SCOPES.includes(value);
|
|
3080
|
-
}
|
|
3081
|
-
function isValidCategory(value) {
|
|
3082
|
-
return VALID_CATEGORIES.includes(value);
|
|
3040
|
+
return parseEntityRow(row);
|
|
3083
3041
|
}
|
|
3084
|
-
function
|
|
3085
|
-
const
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
} 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);
|
|
3092
3049
|
}
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
merged.default_scope = envScope;
|
|
3050
|
+
if (filter.project_id) {
|
|
3051
|
+
conditions.push("project_id = ?");
|
|
3052
|
+
params.push(filter.project_id);
|
|
3097
3053
|
}
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3054
|
+
if (filter.search) {
|
|
3055
|
+
conditions.push("(name LIKE ? OR description LIKE ?)");
|
|
3056
|
+
const term = `%${filter.search}%`;
|
|
3057
|
+
params.push(term, term);
|
|
3101
3058
|
}
|
|
3102
|
-
|
|
3103
|
-
if (
|
|
3104
|
-
|
|
3105
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
3106
|
-
merged.default_importance = parsed;
|
|
3107
|
-
}
|
|
3059
|
+
let sql = "SELECT * FROM entities";
|
|
3060
|
+
if (conditions.length > 0) {
|
|
3061
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
3108
3062
|
}
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
}
|
|
3114
|
-
function globalConfigPath() {
|
|
3115
|
-
return join2(homedir(), ".mementos", "config.json");
|
|
3116
|
-
}
|
|
3117
|
-
function readGlobalConfig() {
|
|
3118
|
-
const p = globalConfigPath();
|
|
3119
|
-
if (!existsSync2(p))
|
|
3120
|
-
return {};
|
|
3121
|
-
try {
|
|
3122
|
-
return JSON.parse(readFileSync(p, "utf-8"));
|
|
3123
|
-
} catch {
|
|
3124
|
-
return {};
|
|
3063
|
+
sql += " ORDER BY updated_at DESC";
|
|
3064
|
+
if (filter.limit) {
|
|
3065
|
+
sql += " LIMIT ?";
|
|
3066
|
+
params.push(filter.limit);
|
|
3125
3067
|
}
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
ensureDir2(dirname2(p));
|
|
3130
|
-
writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");
|
|
3131
|
-
}
|
|
3132
|
-
function getActiveProfile() {
|
|
3133
|
-
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
3134
|
-
if (envProfile)
|
|
3135
|
-
return envProfile.trim();
|
|
3136
|
-
const cfg = readGlobalConfig();
|
|
3137
|
-
return cfg["active_profile"] || null;
|
|
3138
|
-
}
|
|
3139
|
-
function setActiveProfile(name) {
|
|
3140
|
-
const cfg = readGlobalConfig();
|
|
3141
|
-
if (name === null) {
|
|
3142
|
-
delete cfg["active_profile"];
|
|
3143
|
-
} else {
|
|
3144
|
-
cfg["active_profile"] = name;
|
|
3068
|
+
if (filter.offset) {
|
|
3069
|
+
sql += " OFFSET ?";
|
|
3070
|
+
params.push(filter.offset);
|
|
3145
3071
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
function listProfiles() {
|
|
3149
|
-
const dir = profilesDir();
|
|
3150
|
-
if (!existsSync2(dir))
|
|
3151
|
-
return [];
|
|
3152
|
-
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);
|
|
3153
3074
|
}
|
|
3154
|
-
function
|
|
3155
|
-
const
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
if (getActiveProfile() === name)
|
|
3160
|
-
setActiveProfile(null);
|
|
3161
|
-
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);
|
|
3162
3080
|
}
|
|
3163
|
-
function
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
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);
|
|
3167
3096
|
}
|
|
3168
|
-
var
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
default_scope: "private",
|
|
3172
|
-
default_category: "knowledge",
|
|
3173
|
-
default_importance: 5,
|
|
3174
|
-
max_entries: 1000,
|
|
3175
|
-
max_entries_per_scope: {
|
|
3176
|
-
global: 500,
|
|
3177
|
-
shared: 300,
|
|
3178
|
-
private: 200
|
|
3179
|
-
},
|
|
3180
|
-
injection: {
|
|
3181
|
-
max_tokens: 500,
|
|
3182
|
-
min_importance: 5,
|
|
3183
|
-
categories: ["preference", "fact"],
|
|
3184
|
-
refresh_interval: 5
|
|
3185
|
-
},
|
|
3186
|
-
extraction: {
|
|
3187
|
-
enabled: true,
|
|
3188
|
-
min_confidence: 0.5
|
|
3189
|
-
},
|
|
3190
|
-
sync_agents: ["claude", "codex", "gemini"],
|
|
3191
|
-
auto_cleanup: {
|
|
3192
|
-
enabled: true,
|
|
3193
|
-
expired_check_interval: 3600,
|
|
3194
|
-
unused_archive_days: 7,
|
|
3195
|
-
stale_deprioritize_days: 14
|
|
3196
|
-
}
|
|
3197
|
-
};
|
|
3198
|
-
VALID_SCOPES = ["global", "shared", "private"];
|
|
3199
|
-
VALID_CATEGORIES = [
|
|
3200
|
-
"preference",
|
|
3201
|
-
"fact",
|
|
3202
|
-
"knowledge",
|
|
3203
|
-
"history"
|
|
3204
|
-
];
|
|
3097
|
+
var init_entities = __esm(() => {
|
|
3098
|
+
init_database();
|
|
3099
|
+
init_types();
|
|
3205
3100
|
});
|
|
3206
3101
|
|
|
3207
|
-
// src/
|
|
3208
|
-
function
|
|
3209
|
-
const config = loadConfig();
|
|
3210
|
-
if (config.extraction?.enabled === false)
|
|
3211
|
-
return;
|
|
3212
|
-
const extracted = extractEntities(memory, d);
|
|
3213
|
-
const minConfidence = config.extraction?.min_confidence ?? 0.5;
|
|
3214
|
-
const entityIds = [];
|
|
3215
|
-
for (const ext of extracted) {
|
|
3216
|
-
if (ext.confidence >= minConfidence) {
|
|
3217
|
-
const entity = createEntity({ name: ext.name, type: ext.type, project_id: projectId }, d);
|
|
3218
|
-
linkEntityToMemory(entity.id, memory.id, "context", d);
|
|
3219
|
-
entityIds.push(entity.id);
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
for (let i = 0;i < entityIds.length; i++) {
|
|
3223
|
-
for (let j = i + 1;j < entityIds.length; j++) {
|
|
3224
|
-
try {
|
|
3225
|
-
createRelation({ source_entity_id: entityIds[i], target_entity_id: entityIds[j], relation_type: "related_to" }, d);
|
|
3226
|
-
} catch {}
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3230
|
-
function parseMemoryRow(row) {
|
|
3102
|
+
// src/lib/search.ts
|
|
3103
|
+
function parseMemoryRow2(row) {
|
|
3231
3104
|
return {
|
|
3232
3105
|
id: row["id"],
|
|
3233
3106
|
key: row["key"],
|
|
@@ -3252,381 +3125,686 @@ function parseMemoryRow(row) {
|
|
|
3252
3125
|
accessed_at: row["accessed_at"] || null
|
|
3253
3126
|
};
|
|
3254
3127
|
}
|
|
3255
|
-
function
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
const
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
3292
|
-
const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3293
|
-
for (const tag of tags) {
|
|
3294
|
-
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;
|
|
3295
3164
|
}
|
|
3296
|
-
const merged = getMemory(existing.id, d);
|
|
3297
|
-
try {
|
|
3298
|
-
const oldLinks = getEntityMemoryLinks(undefined, merged.id, d);
|
|
3299
|
-
for (const link of oldLinks) {
|
|
3300
|
-
unlinkEntityFromMemory(link.entity_id, merged.id, d);
|
|
3301
|
-
}
|
|
3302
|
-
runEntityExtraction(merged, input.project_id, d);
|
|
3303
|
-
} catch {}
|
|
3304
|
-
return merged;
|
|
3305
3165
|
}
|
|
3306
3166
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
input.value,
|
|
3312
|
-
input.category || "knowledge",
|
|
3313
|
-
input.scope || "private",
|
|
3314
|
-
input.summary || null,
|
|
3315
|
-
tagsJson,
|
|
3316
|
-
input.importance ?? 5,
|
|
3317
|
-
input.source || "agent",
|
|
3318
|
-
input.agent_id || null,
|
|
3319
|
-
input.project_id || null,
|
|
3320
|
-
input.session_id || null,
|
|
3321
|
-
metadataJson,
|
|
3322
|
-
expiresAt,
|
|
3323
|
-
timestamp,
|
|
3324
|
-
timestamp
|
|
3325
|
-
]);
|
|
3326
|
-
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3327
|
-
for (const tag of tags) {
|
|
3328
|
-
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
|
+
}
|
|
3329
3171
|
}
|
|
3330
|
-
|
|
3331
|
-
try {
|
|
3332
|
-
runEntityExtraction(memory, input.project_id, d);
|
|
3333
|
-
} catch {}
|
|
3334
|
-
return memory;
|
|
3172
|
+
return highlights;
|
|
3335
3173
|
}
|
|
3336
|
-
function
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
if (
|
|
3340
|
-
return
|
|
3341
|
-
|
|
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";
|
|
3342
3182
|
}
|
|
3343
|
-
function
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
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);
|
|
3350
3190
|
}
|
|
3351
|
-
if (
|
|
3352
|
-
|
|
3353
|
-
|
|
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);
|
|
3354
3195
|
}
|
|
3355
|
-
if (
|
|
3356
|
-
|
|
3357
|
-
params.push(projectId);
|
|
3196
|
+
if (memory.summary && memory.summary.toLowerCase().includes(queryLower)) {
|
|
3197
|
+
fieldScores.push(4);
|
|
3358
3198
|
}
|
|
3359
|
-
if (
|
|
3360
|
-
|
|
3361
|
-
params.push(sessionId);
|
|
3199
|
+
if (memory.value.toLowerCase().includes(queryLower)) {
|
|
3200
|
+
fieldScores.push(3);
|
|
3362
3201
|
}
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
return null;
|
|
3367
|
-
return parseMemoryRow(row);
|
|
3368
|
-
}
|
|
3369
|
-
function getMemoriesByKey(key, scope, agentId, projectId, db) {
|
|
3370
|
-
const d = db || getDatabase();
|
|
3371
|
-
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
3372
|
-
const params = [key];
|
|
3373
|
-
if (scope) {
|
|
3374
|
-
sql += " AND scope = ?";
|
|
3375
|
-
params.push(scope);
|
|
3202
|
+
const metadataStr = JSON.stringify(memory.metadata).toLowerCase();
|
|
3203
|
+
if (metadataStr !== "{}" && metadataStr.includes(queryLower)) {
|
|
3204
|
+
fieldScores.push(2);
|
|
3376
3205
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
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);
|
|
3380
3211
|
}
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
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;
|
|
3384
3220
|
}
|
|
3385
|
-
|
|
3386
|
-
const
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
if (filter.scope) {
|
|
3395
|
-
if (Array.isArray(filter.scope)) {
|
|
3396
|
-
conditions.push(`scope IN (${filter.scope.map(() => "?").join(",")})`);
|
|
3397
|
-
params.push(...filter.scope);
|
|
3398
|
-
} else {
|
|
3399
|
-
conditions.push("scope = ?");
|
|
3400
|
-
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;
|
|
3401
3230
|
}
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
if (
|
|
3405
|
-
|
|
3406
|
-
params.push(...filter.category);
|
|
3407
|
-
} else {
|
|
3408
|
-
conditions.push("category = ?");
|
|
3409
|
-
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;
|
|
3410
3235
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
if (Array.isArray(filter.source)) {
|
|
3414
|
-
conditions.push(`source IN (${filter.source.map(() => "?").join(",")})`);
|
|
3415
|
-
params.push(...filter.source);
|
|
3416
|
-
} else {
|
|
3417
|
-
conditions.push("source = ?");
|
|
3418
|
-
params.push(filter.source);
|
|
3236
|
+
if (memory.summary && memory.summary.toLowerCase().includes(token)) {
|
|
3237
|
+
tokenScore += 4 / tokens.length;
|
|
3419
3238
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
} else {
|
|
3426
|
-
conditions.push("status = ?");
|
|
3427
|
-
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;
|
|
3428
3244
|
}
|
|
3429
|
-
} else {
|
|
3430
|
-
conditions.push("status = 'active'");
|
|
3431
|
-
}
|
|
3432
|
-
if (filter.project_id) {
|
|
3433
|
-
conditions.push("project_id = ?");
|
|
3434
|
-
params.push(filter.project_id);
|
|
3435
|
-
}
|
|
3436
|
-
if (filter.agent_id) {
|
|
3437
|
-
conditions.push("agent_id = ?");
|
|
3438
|
-
params.push(filter.agent_id);
|
|
3439
3245
|
}
|
|
3440
|
-
if (
|
|
3441
|
-
|
|
3442
|
-
|
|
3246
|
+
if (score > 0) {
|
|
3247
|
+
score += tokenScore * 0.3;
|
|
3248
|
+
} else {
|
|
3249
|
+
score += tokenScore;
|
|
3443
3250
|
}
|
|
3444
|
-
if (filter.min_importance) {
|
|
3445
|
-
conditions.push("importance >= ?");
|
|
3446
|
-
params.push(filter.min_importance);
|
|
3447
|
-
}
|
|
3448
|
-
if (filter.pinned !== undefined) {
|
|
3449
|
-
conditions.push("pinned = ?");
|
|
3450
|
-
params.push(filter.pinned ? 1 : 0);
|
|
3451
|
-
}
|
|
3452
|
-
if (filter.tags && filter.tags.length > 0) {
|
|
3453
|
-
for (const tag of filter.tags) {
|
|
3454
|
-
conditions.push("id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
|
|
3455
|
-
params.push(tag);
|
|
3456
|
-
}
|
|
3457
|
-
}
|
|
3458
|
-
if (filter.search) {
|
|
3459
|
-
conditions.push("(key LIKE ? OR value LIKE ? OR summary LIKE ?)");
|
|
3460
|
-
const term = `%${filter.search}%`;
|
|
3461
|
-
params.push(term, term, term);
|
|
3462
|
-
}
|
|
3463
|
-
} else {
|
|
3464
|
-
conditions.push("status = 'active'");
|
|
3465
|
-
}
|
|
3466
|
-
let sql = "SELECT * FROM memories";
|
|
3467
|
-
if (conditions.length > 0) {
|
|
3468
|
-
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
3469
3251
|
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
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, '""')}"`);
|
|
3474
3267
|
}
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3268
|
+
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
3269
|
+
for (const t of tokens) {
|
|
3270
|
+
parts.push(`"${t.replace(/"/g, '""')}"`);
|
|
3478
3271
|
}
|
|
3479
|
-
|
|
3480
|
-
return rows.map(parseMemoryRow);
|
|
3272
|
+
return parts.join(" ");
|
|
3481
3273
|
}
|
|
3482
|
-
function
|
|
3483
|
-
const d = db || getDatabase();
|
|
3484
|
-
const existing = getMemory(id, d);
|
|
3485
|
-
if (!existing)
|
|
3486
|
-
throw new MemoryNotFoundError(id);
|
|
3487
|
-
if (existing.version !== input.version) {
|
|
3488
|
-
throw new VersionConflictError(id, input.version, existing.version);
|
|
3489
|
-
}
|
|
3274
|
+
function hasFts5Table(d) {
|
|
3490
3275
|
try {
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
existing.version,
|
|
3496
|
-
existing.value,
|
|
3497
|
-
existing.importance,
|
|
3498
|
-
existing.scope,
|
|
3499
|
-
existing.category,
|
|
3500
|
-
JSON.stringify(existing.tags),
|
|
3501
|
-
existing.summary,
|
|
3502
|
-
existing.pinned ? 1 : 0,
|
|
3503
|
-
existing.status,
|
|
3504
|
-
existing.updated_at
|
|
3505
|
-
]);
|
|
3506
|
-
} catch {}
|
|
3507
|
-
const sets = ["version = version + 1", "updated_at = ?"];
|
|
3508
|
-
const params = [now()];
|
|
3509
|
-
if (input.value !== undefined) {
|
|
3510
|
-
sets.push("value = ?");
|
|
3511
|
-
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;
|
|
3512
3280
|
}
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
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
|
+
}
|
|
3516
3297
|
}
|
|
3517
|
-
if (
|
|
3518
|
-
|
|
3519
|
-
|
|
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
|
+
}
|
|
3520
3306
|
}
|
|
3521
|
-
if (
|
|
3522
|
-
|
|
3523
|
-
|
|
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
|
+
}
|
|
3524
3315
|
}
|
|
3525
|
-
if (
|
|
3526
|
-
|
|
3527
|
-
|
|
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
|
+
}
|
|
3528
3325
|
}
|
|
3529
|
-
if (
|
|
3530
|
-
|
|
3531
|
-
params.push(
|
|
3326
|
+
if (filter.project_id) {
|
|
3327
|
+
conditions.push("m.project_id = ?");
|
|
3328
|
+
params.push(filter.project_id);
|
|
3532
3329
|
}
|
|
3533
|
-
if (
|
|
3534
|
-
|
|
3535
|
-
params.push(
|
|
3330
|
+
if (filter.agent_id) {
|
|
3331
|
+
conditions.push("m.agent_id = ?");
|
|
3332
|
+
params.push(filter.agent_id);
|
|
3536
3333
|
}
|
|
3537
|
-
if (
|
|
3538
|
-
|
|
3539
|
-
params.push(
|
|
3334
|
+
if (filter.session_id) {
|
|
3335
|
+
conditions.push("m.session_id = ?");
|
|
3336
|
+
params.push(filter.session_id);
|
|
3540
3337
|
}
|
|
3541
|
-
if (
|
|
3542
|
-
|
|
3543
|
-
params.push(
|
|
3338
|
+
if (filter.min_importance) {
|
|
3339
|
+
conditions.push("m.importance >= ?");
|
|
3340
|
+
params.push(filter.min_importance);
|
|
3544
3341
|
}
|
|
3545
|
-
if (
|
|
3546
|
-
|
|
3547
|
-
params.push(
|
|
3548
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [id]);
|
|
3549
|
-
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
3550
|
-
for (const tag of input.tags) {
|
|
3551
|
-
insertTag.run(id, tag);
|
|
3552
|
-
}
|
|
3342
|
+
if (filter.pinned !== undefined) {
|
|
3343
|
+
conditions.push("m.pinned = ?");
|
|
3344
|
+
params.push(filter.pinned ? 1 : 0);
|
|
3553
3345
|
}
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
if (input.value !== undefined) {
|
|
3559
|
-
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
3560
|
-
for (const link of oldLinks) {
|
|
3561
|
-
unlinkEntityFromMemory(link.entity_id, updated.id, d);
|
|
3562
|
-
}
|
|
3563
|
-
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);
|
|
3564
3350
|
}
|
|
3565
|
-
} catch {}
|
|
3566
|
-
return updated;
|
|
3567
|
-
}
|
|
3568
|
-
function deleteMemory(id, db) {
|
|
3569
|
-
const d = db || getDatabase();
|
|
3570
|
-
const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
3571
|
-
return result.changes > 0;
|
|
3572
|
-
}
|
|
3573
|
-
function bulkDeleteMemories(ids, db) {
|
|
3574
|
-
const d = db || getDatabase();
|
|
3575
|
-
if (ids.length === 0)
|
|
3576
|
-
return 0;
|
|
3577
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
3578
|
-
const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...ids);
|
|
3579
|
-
const count = countRow.c;
|
|
3580
|
-
if (count > 0) {
|
|
3581
|
-
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, ids);
|
|
3582
|
-
}
|
|
3583
|
-
return count;
|
|
3584
|
-
}
|
|
3585
|
-
function touchMemory(id, db) {
|
|
3586
|
-
const d = db || getDatabase();
|
|
3587
|
-
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
3588
|
-
}
|
|
3589
|
-
function cleanExpiredMemories(db) {
|
|
3590
|
-
const d = db || getDatabase();
|
|
3591
|
-
const timestamp = now();
|
|
3592
|
-
const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
|
|
3593
|
-
const count = countRow.c;
|
|
3594
|
-
if (count > 0) {
|
|
3595
|
-
d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
|
|
3596
3351
|
}
|
|
3597
|
-
return
|
|
3352
|
+
return { conditions, params };
|
|
3598
3353
|
}
|
|
3599
|
-
function
|
|
3600
|
-
const
|
|
3354
|
+
function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
|
|
3355
|
+
const ftsQuery = escapeFts5Query(query);
|
|
3356
|
+
if (!ftsQuery)
|
|
3357
|
+
return null;
|
|
3601
3358
|
try {
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
category: row["category"],
|
|
3611
|
-
tags: JSON.parse(row["tags"] || "[]"),
|
|
3612
|
-
summary: row["summary"] || null,
|
|
3613
|
-
pinned: !!row["pinned"],
|
|
3614
|
-
status: row["status"],
|
|
3615
|
-
created_at: row["created_at"]
|
|
3616
|
-
}));
|
|
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);
|
|
3617
3367
|
} catch {
|
|
3618
|
-
return
|
|
3368
|
+
return null;
|
|
3619
3369
|
}
|
|
3620
3370
|
}
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
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(() => {
|
|
3567
|
+
init_database();
|
|
3568
|
+
init_entities();
|
|
3627
3569
|
init_entity_memories();
|
|
3628
|
-
|
|
3629
|
-
|
|
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();
|
|
3630
3808
|
});
|
|
3631
3809
|
|
|
3632
3810
|
// src/lib/poll.ts
|
|
@@ -3698,48 +3876,774 @@ function startPolling(opts) {
|
|
|
3698
3876
|
}
|
|
3699
3877
|
}
|
|
3700
3878
|
} catch (err) {
|
|
3701
|
-
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);
|
|
3702
4463
|
} finally {
|
|
3703
|
-
|
|
4464
|
+
this.activeCount--;
|
|
4465
|
+
this.stats.processing = this.activeCount;
|
|
3704
4466
|
}
|
|
3705
|
-
}
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
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;
|
|
3712
4503
|
}
|
|
3713
|
-
|
|
4504
|
+
return false;
|
|
4505
|
+
} catch {
|
|
4506
|
+
return false;
|
|
4507
|
+
}
|
|
3714
4508
|
}
|
|
3715
|
-
function
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
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
|
|
3738
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
|
+
}
|
|
3739
4620
|
}
|
|
3740
|
-
|
|
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(() => {
|
|
3741
4639
|
init_memories();
|
|
3742
|
-
|
|
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);
|
|
3743
4647
|
});
|
|
3744
4648
|
|
|
3745
4649
|
// node_modules/commander/esm.mjs
|
|
@@ -3761,569 +4665,315 @@ var {
|
|
|
3761
4665
|
// src/cli/index.tsx
|
|
3762
4666
|
init_database();
|
|
3763
4667
|
init_memories();
|
|
3764
|
-
init_agents();
|
|
3765
|
-
init_projects();
|
|
3766
4668
|
import chalk from "chalk";
|
|
3767
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";
|
|
3768
4670
|
import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
|
|
3769
4671
|
import { homedir as homedir2 } from "os";
|
|
3770
4672
|
import { fileURLToPath } from "url";
|
|
3771
4673
|
|
|
3772
|
-
// src/
|
|
4674
|
+
// src/db/agents.ts
|
|
4675
|
+
init_types();
|
|
3773
4676
|
init_database();
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
function parseMemoryRow2(row) {
|
|
4677
|
+
var CONFLICT_WINDOW_MS = 30 * 60 * 1000;
|
|
4678
|
+
function parseAgentRow(row) {
|
|
3777
4679
|
return {
|
|
3778
4680
|
id: row["id"],
|
|
3779
|
-
|
|
3780
|
-
value: row["value"],
|
|
3781
|
-
category: row["category"],
|
|
3782
|
-
scope: row["scope"],
|
|
3783
|
-
summary: row["summary"] || null,
|
|
3784
|
-
tags: JSON.parse(row["tags"] || "[]"),
|
|
3785
|
-
importance: row["importance"],
|
|
3786
|
-
source: row["source"],
|
|
3787
|
-
status: row["status"],
|
|
3788
|
-
pinned: !!row["pinned"],
|
|
3789
|
-
agent_id: row["agent_id"] || null,
|
|
3790
|
-
project_id: row["project_id"] || null,
|
|
4681
|
+
name: row["name"],
|
|
3791
4682
|
session_id: row["session_id"] || null,
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
created_at: row["created_at"],
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
"being",
|
|
3821
|
-
"have",
|
|
3822
|
-
"has",
|
|
3823
|
-
"had",
|
|
3824
|
-
"do",
|
|
3825
|
-
"does",
|
|
3826
|
-
"did",
|
|
3827
|
-
"will",
|
|
3828
|
-
"would",
|
|
3829
|
-
"could",
|
|
3830
|
-
"should",
|
|
3831
|
-
"may",
|
|
3832
|
-
"might",
|
|
3833
|
-
"shall",
|
|
3834
|
-
"can",
|
|
3835
|
-
"need",
|
|
3836
|
-
"dare",
|
|
3837
|
-
"ought",
|
|
3838
|
-
"used",
|
|
3839
|
-
"to",
|
|
3840
|
-
"of",
|
|
3841
|
-
"in",
|
|
3842
|
-
"for",
|
|
3843
|
-
"on",
|
|
3844
|
-
"with",
|
|
3845
|
-
"at",
|
|
3846
|
-
"by",
|
|
3847
|
-
"from",
|
|
3848
|
-
"as",
|
|
3849
|
-
"into",
|
|
3850
|
-
"through",
|
|
3851
|
-
"during",
|
|
3852
|
-
"before",
|
|
3853
|
-
"after",
|
|
3854
|
-
"above",
|
|
3855
|
-
"below",
|
|
3856
|
-
"between",
|
|
3857
|
-
"out",
|
|
3858
|
-
"off",
|
|
3859
|
-
"over",
|
|
3860
|
-
"under",
|
|
3861
|
-
"again",
|
|
3862
|
-
"further",
|
|
3863
|
-
"then",
|
|
3864
|
-
"once",
|
|
3865
|
-
"here",
|
|
3866
|
-
"there",
|
|
3867
|
-
"when",
|
|
3868
|
-
"where",
|
|
3869
|
-
"why",
|
|
3870
|
-
"how",
|
|
3871
|
-
"all",
|
|
3872
|
-
"each",
|
|
3873
|
-
"every",
|
|
3874
|
-
"both",
|
|
3875
|
-
"few",
|
|
3876
|
-
"more",
|
|
3877
|
-
"most",
|
|
3878
|
-
"other",
|
|
3879
|
-
"some",
|
|
3880
|
-
"such",
|
|
3881
|
-
"no",
|
|
3882
|
-
"not",
|
|
3883
|
-
"only",
|
|
3884
|
-
"own",
|
|
3885
|
-
"same",
|
|
3886
|
-
"so",
|
|
3887
|
-
"than",
|
|
3888
|
-
"too",
|
|
3889
|
-
"very",
|
|
3890
|
-
"just",
|
|
3891
|
-
"because",
|
|
3892
|
-
"but",
|
|
3893
|
-
"and",
|
|
3894
|
-
"or",
|
|
3895
|
-
"if",
|
|
3896
|
-
"while",
|
|
3897
|
-
"that",
|
|
3898
|
-
"this",
|
|
3899
|
-
"it"
|
|
3900
|
-
]);
|
|
3901
|
-
function removeStopWords(tokens) {
|
|
3902
|
-
if (tokens.length <= 1)
|
|
3903
|
-
return tokens;
|
|
3904
|
-
const filtered = tokens.filter((t) => !STOP_WORDS.has(t.toLowerCase()));
|
|
3905
|
-
return filtered.length > 0 ? filtered : tokens;
|
|
3906
|
-
}
|
|
3907
|
-
function extractHighlights(memory, queryLower) {
|
|
3908
|
-
const highlights = [];
|
|
3909
|
-
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
3910
|
-
for (const field of ["key", "value", "summary"]) {
|
|
3911
|
-
const text = field === "summary" ? memory.summary : memory[field];
|
|
3912
|
-
if (!text)
|
|
3913
|
-
continue;
|
|
3914
|
-
const textLower = text.toLowerCase();
|
|
3915
|
-
const searchTerms = [queryLower, ...tokens].filter(Boolean);
|
|
3916
|
-
for (const term of searchTerms) {
|
|
3917
|
-
const idx = textLower.indexOf(term);
|
|
3918
|
-
if (idx !== -1) {
|
|
3919
|
-
const start = Math.max(0, idx - 30);
|
|
3920
|
-
const end = Math.min(text.length, idx + term.length + 30);
|
|
3921
|
-
const prefix = start > 0 ? "..." : "";
|
|
3922
|
-
const suffix = end < text.length ? "..." : "";
|
|
3923
|
-
highlights.push({
|
|
3924
|
-
field,
|
|
3925
|
-
snippet: prefix + text.slice(start, end) + suffix
|
|
3926
|
-
});
|
|
3927
|
-
break;
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
}
|
|
3931
|
-
for (const tag of memory.tags) {
|
|
3932
|
-
if (tag.toLowerCase().includes(queryLower) || tokens.some((t) => tag.toLowerCase().includes(t))) {
|
|
3933
|
-
highlights.push({ field: "tag", snippet: tag });
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
return highlights;
|
|
3937
|
-
}
|
|
3938
|
-
function determineMatchType(memory, queryLower) {
|
|
3939
|
-
if (memory.key.toLowerCase() === queryLower)
|
|
3940
|
-
return "exact";
|
|
3941
|
-
if (memory.tags.some((t) => t.toLowerCase() === queryLower))
|
|
3942
|
-
return "tag";
|
|
3943
|
-
if (memory.tags.some((t) => t.toLowerCase().includes(queryLower)))
|
|
3944
|
-
return "tag";
|
|
3945
|
-
return "fuzzy";
|
|
3946
|
-
}
|
|
3947
|
-
function computeScore(memory, queryLower) {
|
|
3948
|
-
const fieldScores = [];
|
|
3949
|
-
const keyLower = memory.key.toLowerCase();
|
|
3950
|
-
if (keyLower === queryLower) {
|
|
3951
|
-
fieldScores.push(10);
|
|
3952
|
-
} else if (keyLower.includes(queryLower)) {
|
|
3953
|
-
fieldScores.push(7);
|
|
3954
|
-
}
|
|
3955
|
-
if (memory.tags.some((t) => t.toLowerCase() === queryLower)) {
|
|
3956
|
-
fieldScores.push(6);
|
|
3957
|
-
} else if (memory.tags.some((t) => t.toLowerCase().includes(queryLower))) {
|
|
3958
|
-
fieldScores.push(3);
|
|
3959
|
-
}
|
|
3960
|
-
if (memory.summary && memory.summary.toLowerCase().includes(queryLower)) {
|
|
3961
|
-
fieldScores.push(4);
|
|
3962
|
-
}
|
|
3963
|
-
if (memory.value.toLowerCase().includes(queryLower)) {
|
|
3964
|
-
fieldScores.push(3);
|
|
3965
|
-
}
|
|
3966
|
-
const metadataStr = JSON.stringify(memory.metadata).toLowerCase();
|
|
3967
|
-
if (metadataStr !== "{}" && metadataStr.includes(queryLower)) {
|
|
3968
|
-
fieldScores.push(2);
|
|
3969
|
-
}
|
|
3970
|
-
fieldScores.sort((a, b) => b - a);
|
|
3971
|
-
const diminishingMultipliers = [1, 0.5, 0.25, 0.15, 0.15];
|
|
3972
|
-
let score = 0;
|
|
3973
|
-
for (let i = 0;i < fieldScores.length; i++) {
|
|
3974
|
-
score += fieldScores[i] * (diminishingMultipliers[i] ?? 0.15);
|
|
3975
|
-
}
|
|
3976
|
-
const { phrases } = extractQuotedPhrases(queryLower);
|
|
3977
|
-
for (const phrase of phrases) {
|
|
3978
|
-
if (keyLower.includes(phrase))
|
|
3979
|
-
score += 8;
|
|
3980
|
-
if (memory.value.toLowerCase().includes(phrase))
|
|
3981
|
-
score += 5;
|
|
3982
|
-
if (memory.summary && memory.summary.toLowerCase().includes(phrase))
|
|
3983
|
-
score += 4;
|
|
3984
|
-
}
|
|
3985
|
-
const { remainder } = extractQuotedPhrases(queryLower);
|
|
3986
|
-
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
3987
|
-
if (tokens.length > 1) {
|
|
3988
|
-
let tokenScore = 0;
|
|
3989
|
-
for (const token of tokens) {
|
|
3990
|
-
if (keyLower === token) {
|
|
3991
|
-
tokenScore += 10 / tokens.length;
|
|
3992
|
-
} else if (keyLower.includes(token)) {
|
|
3993
|
-
tokenScore += 7 / tokens.length;
|
|
3994
|
-
}
|
|
3995
|
-
if (memory.tags.some((t) => t.toLowerCase() === token)) {
|
|
3996
|
-
tokenScore += 6 / tokens.length;
|
|
3997
|
-
} else if (memory.tags.some((t) => t.toLowerCase().includes(token))) {
|
|
3998
|
-
tokenScore += 3 / tokens.length;
|
|
3999
|
-
}
|
|
4000
|
-
if (memory.summary && memory.summary.toLowerCase().includes(token)) {
|
|
4001
|
-
tokenScore += 4 / tokens.length;
|
|
4002
|
-
}
|
|
4003
|
-
if (memory.value.toLowerCase().includes(token)) {
|
|
4004
|
-
tokenScore += 3 / tokens.length;
|
|
4005
|
-
}
|
|
4006
|
-
if (metadataStr !== "{}" && metadataStr.includes(token)) {
|
|
4007
|
-
tokenScore += 2 / tokens.length;
|
|
4683
|
+
description: row["description"] || null,
|
|
4684
|
+
role: row["role"] || null,
|
|
4685
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
4686
|
+
active_project_id: row["active_project_id"] || null,
|
|
4687
|
+
created_at: row["created_at"],
|
|
4688
|
+
last_seen_at: row["last_seen_at"]
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
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
|
+
});
|
|
4008
4711
|
}
|
|
4009
4712
|
}
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
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]);
|
|
4723
|
+
}
|
|
4724
|
+
if (projectId !== undefined) {
|
|
4725
|
+
d.run("UPDATE agents SET active_project_id = ? WHERE id = ?", [projectId, existingId]);
|
|
4014
4726
|
}
|
|
4727
|
+
return getAgent(existingId, d);
|
|
4015
4728
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
const phrases = [];
|
|
4020
|
-
const remainder = query.replace(/"([^"]+)"/g, (_match, phrase) => {
|
|
4021
|
-
phrases.push(phrase);
|
|
4022
|
-
return "";
|
|
4023
|
-
});
|
|
4024
|
-
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);
|
|
4025
4732
|
}
|
|
4026
|
-
function
|
|
4027
|
-
const
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
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;
|
|
4037
4745
|
}
|
|
4038
|
-
function
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
} catch {
|
|
4043
|
-
return false;
|
|
4044
|
-
}
|
|
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);
|
|
4045
4750
|
}
|
|
4046
|
-
function
|
|
4047
|
-
const
|
|
4048
|
-
const
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
if (
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
}
|
|
4061
|
-
}
|
|
4062
|
-
if (filter.category) {
|
|
4063
|
-
if (Array.isArray(filter.category)) {
|
|
4064
|
-
conditions.push(`m.category IN (${filter.category.map(() => "?").join(",")})`);
|
|
4065
|
-
params.push(...filter.category);
|
|
4066
|
-
} else {
|
|
4067
|
-
conditions.push("m.category = ?");
|
|
4068
|
-
params.push(filter.category);
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
if (filter.source) {
|
|
4072
|
-
if (Array.isArray(filter.source)) {
|
|
4073
|
-
conditions.push(`m.source IN (${filter.source.map(() => "?").join(",")})`);
|
|
4074
|
-
params.push(...filter.source);
|
|
4075
|
-
} else {
|
|
4076
|
-
conditions.push("m.source = ?");
|
|
4077
|
-
params.push(filter.source);
|
|
4078
|
-
}
|
|
4079
|
-
}
|
|
4080
|
-
if (filter.status) {
|
|
4081
|
-
conditions.shift();
|
|
4082
|
-
if (Array.isArray(filter.status)) {
|
|
4083
|
-
conditions.push(`m.status IN (${filter.status.map(() => "?").join(",")})`);
|
|
4084
|
-
params.push(...filter.status);
|
|
4085
|
-
} else {
|
|
4086
|
-
conditions.push("m.status = ?");
|
|
4087
|
-
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]);
|
|
4088
4765
|
}
|
|
4089
4766
|
}
|
|
4090
|
-
if (
|
|
4091
|
-
|
|
4092
|
-
params.push(filter.project_id);
|
|
4093
|
-
}
|
|
4094
|
-
if (filter.agent_id) {
|
|
4095
|
-
conditions.push("m.agent_id = ?");
|
|
4096
|
-
params.push(filter.agent_id);
|
|
4097
|
-
}
|
|
4098
|
-
if (filter.session_id) {
|
|
4099
|
-
conditions.push("m.session_id = ?");
|
|
4100
|
-
params.push(filter.session_id);
|
|
4767
|
+
if (updates.description !== undefined) {
|
|
4768
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
|
|
4101
4769
|
}
|
|
4102
|
-
if (
|
|
4103
|
-
|
|
4104
|
-
params.push(filter.min_importance);
|
|
4770
|
+
if (updates.role !== undefined) {
|
|
4771
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
|
|
4105
4772
|
}
|
|
4106
|
-
if (
|
|
4107
|
-
|
|
4108
|
-
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]);
|
|
4109
4775
|
}
|
|
4110
|
-
if (
|
|
4111
|
-
|
|
4112
|
-
conditions.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
|
|
4113
|
-
params.push(tag);
|
|
4114
|
-
}
|
|
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]);
|
|
4115
4778
|
}
|
|
4116
|
-
|
|
4779
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
|
|
4780
|
+
return getAgent(agent.id, d);
|
|
4117
4781
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
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);
|
|
4133
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);
|
|
4134
4811
|
}
|
|
4135
|
-
function
|
|
4136
|
-
const
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
if (
|
|
4142
|
-
|
|
4143
|
-
|
|
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
|
|
4144
4864
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
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
|
+
}
|
|
4157
4876
|
}
|
|
4158
|
-
|
|
4159
|
-
const sql = `SELECT DISTINCT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
|
|
4160
|
-
const rows = d.query(sql).all(...params);
|
|
4161
|
-
return scoreResults(rows, queryLower, graphBoostedIds);
|
|
4877
|
+
return result;
|
|
4162
4878
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
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);
|
|
4170
4888
|
}
|
|
4171
|
-
function
|
|
4172
|
-
|
|
4173
|
-
const triB = generateTrigrams(b);
|
|
4174
|
-
if (triA.size === 0 || triB.size === 0)
|
|
4175
|
-
return 0;
|
|
4176
|
-
let intersection = 0;
|
|
4177
|
-
for (const t of triA) {
|
|
4178
|
-
if (triB.has(t))
|
|
4179
|
-
intersection++;
|
|
4180
|
-
}
|
|
4181
|
-
const union = triA.size + triB.size - intersection;
|
|
4182
|
-
return union === 0 ? 0 : intersection / union;
|
|
4889
|
+
function isValidCategory(value) {
|
|
4890
|
+
return VALID_CATEGORIES.includes(value);
|
|
4183
4891
|
}
|
|
4184
|
-
function
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
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;
|
|
4205
4915
|
}
|
|
4206
4916
|
}
|
|
4207
|
-
|
|
4208
|
-
return results;
|
|
4917
|
+
return merged;
|
|
4209
4918
|
}
|
|
4210
|
-
function
|
|
4211
|
-
|
|
4212
|
-
try {
|
|
4213
|
-
const matchingEntities = listEntities({ search: query, limit: 10 }, d);
|
|
4214
|
-
const exactMatch = getEntityByName(query, undefined, undefined, d);
|
|
4215
|
-
if (exactMatch && !matchingEntities.find((e) => e.id === exactMatch.id)) {
|
|
4216
|
-
matchingEntities.push(exactMatch);
|
|
4217
|
-
}
|
|
4218
|
-
for (const entity of matchingEntities) {
|
|
4219
|
-
const memories = getMemoriesForEntity(entity.id, d);
|
|
4220
|
-
for (const mem of memories) {
|
|
4221
|
-
boostedIds.add(mem.id);
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4224
|
-
} catch {}
|
|
4225
|
-
return boostedIds;
|
|
4919
|
+
function profilesDir() {
|
|
4920
|
+
return join2(homedir(), ".mementos", "profiles");
|
|
4226
4921
|
}
|
|
4227
|
-
function
|
|
4228
|
-
|
|
4229
|
-
return 1;
|
|
4230
|
-
const mostRecent = memory.accessed_at || memory.updated_at;
|
|
4231
|
-
if (!mostRecent)
|
|
4232
|
-
return 0;
|
|
4233
|
-
const daysSinceAccess = (Date.now() - Date.parse(mostRecent)) / (1000 * 60 * 60 * 24);
|
|
4234
|
-
return Math.max(0, 1 - daysSinceAccess / 30);
|
|
4922
|
+
function globalConfigPath() {
|
|
4923
|
+
return join2(homedir(), ".mementos", "config.json");
|
|
4235
4924
|
}
|
|
4236
|
-
function
|
|
4237
|
-
const
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
const recencyBoost = computeRecencyBoost(memory);
|
|
4245
|
-
const accessBoost = Math.min(memory.access_count / 20, 0.2);
|
|
4246
|
-
const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
|
|
4247
|
-
const finalScore = (weightedScore + graphBoost) * (1 + recencyBoost * 0.3) * (1 + accessBoost);
|
|
4248
|
-
const matchType = determineMatchType(memory, queryLower);
|
|
4249
|
-
scored.push({
|
|
4250
|
-
memory,
|
|
4251
|
-
score: finalScore,
|
|
4252
|
-
match_type: matchType,
|
|
4253
|
-
highlights: extractHighlights(memory, queryLower)
|
|
4254
|
-
});
|
|
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 {};
|
|
4255
4933
|
}
|
|
4256
|
-
scored.sort((a, b) => {
|
|
4257
|
-
if (b.score !== a.score)
|
|
4258
|
-
return b.score - a.score;
|
|
4259
|
-
return b.memory.importance - a.memory.importance;
|
|
4260
|
-
});
|
|
4261
|
-
return scored;
|
|
4262
4934
|
}
|
|
4263
|
-
function
|
|
4264
|
-
const
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
const
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
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"];
|
|
4278
4951
|
} else {
|
|
4279
|
-
|
|
4280
|
-
}
|
|
4281
|
-
if (scored.length < 3) {
|
|
4282
|
-
const fuzzyResults = searchWithFuzzy(d, query, filter, graphBoostedIds);
|
|
4283
|
-
const seenIds = new Set(scored.map((r) => r.memory.id));
|
|
4284
|
-
for (const fr of fuzzyResults) {
|
|
4285
|
-
if (!seenIds.has(fr.memory.id)) {
|
|
4286
|
-
scored.push(fr);
|
|
4287
|
-
seenIds.add(fr.memory.id);
|
|
4288
|
-
}
|
|
4289
|
-
}
|
|
4290
|
-
scored.sort((a, b) => {
|
|
4291
|
-
if (b.score !== a.score)
|
|
4292
|
-
return b.score - a.score;
|
|
4293
|
-
return b.memory.importance - a.memory.importance;
|
|
4294
|
-
});
|
|
4952
|
+
cfg["active_profile"] = name;
|
|
4295
4953
|
}
|
|
4296
|
-
|
|
4297
|
-
const limit = filter?.limit ?? scored.length;
|
|
4298
|
-
const finalResults = scored.slice(offset, offset + limit);
|
|
4299
|
-
logSearchQuery(query, scored.length, filter?.agent_id, filter?.project_id, d);
|
|
4300
|
-
return finalResults;
|
|
4954
|
+
writeGlobalConfig(cfg);
|
|
4301
4955
|
}
|
|
4302
|
-
function
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
} 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();
|
|
4308
4961
|
}
|
|
4309
|
-
function
|
|
4310
|
-
const
|
|
4311
|
-
if (
|
|
4312
|
-
return
|
|
4313
|
-
|
|
4314
|
-
|
|
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;
|
|
4315
4970
|
}
|
|
4316
|
-
function
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
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 });
|
|
4320
4974
|
}
|
|
4321
|
-
return d.query("SELECT query, COUNT(*) as count FROM search_history GROUP BY query ORDER BY count DESC LIMIT ?").all(limit);
|
|
4322
4975
|
}
|
|
4323
4976
|
|
|
4324
|
-
// src/cli/index.tsx
|
|
4325
|
-
init_config();
|
|
4326
|
-
|
|
4327
4977
|
// src/lib/retention.ts
|
|
4328
4978
|
init_database();
|
|
4329
4979
|
init_memories();
|
|
@@ -5347,10 +5997,10 @@ program2.command("clean").description("Remove expired memories and enforce quota
|
|
|
5347
5997
|
handleError(e);
|
|
5348
5998
|
}
|
|
5349
5999
|
});
|
|
5350
|
-
program2.command("init <name>").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").action((name, opts) => {
|
|
6000
|
+
program2.command("init <name>").description("Register an agent (returns ID)").option("-d, --description <text>", "Agent description").option("-r, --role <role>", "Agent role").option("-p, --project <id>", "Lock agent to a project (sets active_project_id)").action((name, opts) => {
|
|
5351
6001
|
try {
|
|
5352
6002
|
const globalOpts = program2.opts();
|
|
5353
|
-
const agent = registerAgent(name, opts.description, opts.role);
|
|
6003
|
+
const agent = registerAgent(name, undefined, opts.description, opts.role, opts.project);
|
|
5354
6004
|
if (globalOpts.json) {
|
|
5355
6005
|
outputJson(agent);
|
|
5356
6006
|
} else {
|
|
@@ -7392,4 +8042,110 @@ profileCmd.command("delete <name>").description("Delete a profile and its DB fil
|
|
|
7392
8042
|
process.exit(1);
|
|
7393
8043
|
}
|
|
7394
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
|
+
});
|
|
7395
8151
|
program2.parse(process.argv);
|