@askexenow/exe-os 0.8.83 → 0.8.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/bin/backfill-conversations.js +746 -595
  2. package/dist/bin/backfill-responses.js +745 -594
  3. package/dist/bin/backfill-vectors.js +312 -226
  4. package/dist/bin/cleanup-stale-review-tasks.js +97 -2
  5. package/dist/bin/cli.js +14350 -12518
  6. package/dist/bin/exe-agent.js +97 -88
  7. package/dist/bin/exe-assign.js +1003 -854
  8. package/dist/bin/exe-boot.js +1257 -320
  9. package/dist/bin/exe-call.js +10 -0
  10. package/dist/bin/exe-cloud.js +29 -6
  11. package/dist/bin/exe-dispatch.js +210 -34
  12. package/dist/bin/exe-doctor.js +403 -6
  13. package/dist/bin/exe-export-behaviors.js +175 -72
  14. package/dist/bin/exe-forget.js +97 -2
  15. package/dist/bin/exe-gateway.js +550 -171
  16. package/dist/bin/exe-healthcheck.js +1 -0
  17. package/dist/bin/exe-heartbeat.js +100 -5
  18. package/dist/bin/exe-kill.js +175 -72
  19. package/dist/bin/exe-launch-agent.js +189 -76
  20. package/dist/bin/exe-link.js +902 -80
  21. package/dist/bin/exe-new-employee.js +38 -8
  22. package/dist/bin/exe-pending-messages.js +96 -2
  23. package/dist/bin/exe-pending-notifications.js +97 -2
  24. package/dist/bin/exe-pending-reviews.js +98 -3
  25. package/dist/bin/exe-rename.js +564 -23
  26. package/dist/bin/exe-review.js +231 -73
  27. package/dist/bin/exe-search.js +989 -226
  28. package/dist/bin/exe-session-cleanup.js +4806 -1665
  29. package/dist/bin/exe-settings.js +20 -5
  30. package/dist/bin/exe-status.js +97 -2
  31. package/dist/bin/exe-team.js +97 -2
  32. package/dist/bin/git-sweep.js +899 -207
  33. package/dist/bin/graph-backfill.js +175 -72
  34. package/dist/bin/graph-export.js +175 -72
  35. package/dist/bin/install.js +38 -7
  36. package/dist/bin/list-providers.js +1 -0
  37. package/dist/bin/scan-tasks.js +904 -211
  38. package/dist/bin/setup.js +867 -268
  39. package/dist/bin/shard-migrate.js +175 -72
  40. package/dist/bin/update.js +1 -0
  41. package/dist/bin/wiki-sync.js +175 -72
  42. package/dist/gateway/index.js +548 -166
  43. package/dist/hooks/bug-report-worker.js +208 -23
  44. package/dist/hooks/commit-complete.js +897 -205
  45. package/dist/hooks/error-recall.js +988 -226
  46. package/dist/hooks/ingest-worker.js +1638 -1194
  47. package/dist/hooks/ingest.js +3 -0
  48. package/dist/hooks/instructions-loaded.js +707 -97
  49. package/dist/hooks/notification.js +699 -89
  50. package/dist/hooks/post-compact.js +714 -104
  51. package/dist/hooks/pre-compact.js +897 -205
  52. package/dist/hooks/pre-tool-use.js +742 -123
  53. package/dist/hooks/prompt-ingest-worker.js +242 -101
  54. package/dist/hooks/prompt-submit.js +995 -233
  55. package/dist/hooks/response-ingest-worker.js +242 -101
  56. package/dist/hooks/session-end.js +3941 -400
  57. package/dist/hooks/session-start.js +1001 -226
  58. package/dist/hooks/stop.js +725 -115
  59. package/dist/hooks/subagent-stop.js +714 -104
  60. package/dist/hooks/summary-worker.js +1964 -1330
  61. package/dist/index.js +1651 -1053
  62. package/dist/lib/cloud-sync.js +907 -86
  63. package/dist/lib/consolidation.js +2 -1
  64. package/dist/lib/database.js +642 -87
  65. package/dist/lib/db-daemon-client.js +503 -0
  66. package/dist/lib/device-registry.js +547 -7
  67. package/dist/lib/embedder.js +14 -28
  68. package/dist/lib/employee-templates.js +84 -74
  69. package/dist/lib/employees.js +9 -0
  70. package/dist/lib/exe-daemon-client.js +16 -29
  71. package/dist/lib/exe-daemon.js +1955 -922
  72. package/dist/lib/hybrid-search.js +988 -226
  73. package/dist/lib/identity.js +87 -67
  74. package/dist/lib/keychain.js +9 -1
  75. package/dist/lib/messaging.js +8 -1
  76. package/dist/lib/reminders.js +91 -74
  77. package/dist/lib/schedules.js +96 -2
  78. package/dist/lib/skill-learning.js +103 -85
  79. package/dist/lib/store.js +234 -73
  80. package/dist/lib/tasks.js +111 -22
  81. package/dist/lib/tmux-routing.js +120 -31
  82. package/dist/lib/token-spend.js +273 -0
  83. package/dist/lib/ws-client.js +11 -0
  84. package/dist/mcp/server.js +5222 -475
  85. package/dist/mcp/tools/complete-reminder.js +94 -77
  86. package/dist/mcp/tools/create-reminder.js +94 -77
  87. package/dist/mcp/tools/create-task.js +120 -22
  88. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  89. package/dist/mcp/tools/list-reminders.js +94 -77
  90. package/dist/mcp/tools/list-tasks.js +31 -1
  91. package/dist/mcp/tools/send-message.js +8 -1
  92. package/dist/mcp/tools/update-task.js +39 -10
  93. package/dist/runtime/index.js +911 -219
  94. package/dist/tui/App.js +997 -295
  95. package/package.json +6 -1
@@ -370,6 +370,12 @@ function getClient() {
370
370
  if (!_resilientClient) {
371
371
  throw new Error("Database client not initialized. Call initDatabase() first.");
372
372
  }
373
+ if (process.env.EXE_IS_DAEMON === "1") {
374
+ return _resilientClient;
375
+ }
376
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
377
+ return _daemonClient;
378
+ }
373
379
  return _resilientClient;
374
380
  }
375
381
  function getRawClient() {
@@ -858,6 +864,12 @@ async function ensureSchema() {
858
864
  } catch {
859
865
  }
860
866
  }
867
+ try {
868
+ await client.execute(
869
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
870
+ );
871
+ } catch {
872
+ }
861
873
  await client.executeMultiple(`
862
874
  CREATE TABLE IF NOT EXISTS entities (
863
875
  id TEXT PRIMARY KEY,
@@ -910,7 +922,30 @@ async function ensureSchema() {
910
922
  entity_id TEXT NOT NULL,
911
923
  PRIMARY KEY (hyperedge_id, entity_id)
912
924
  );
925
+
926
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
927
+ name,
928
+ content=entities,
929
+ content_rowid=rowid
930
+ );
931
+
932
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
933
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
934
+ END;
935
+
936
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
937
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
938
+ END;
939
+
940
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
941
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
942
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
943
+ END;
913
944
  `);
945
+ try {
946
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
947
+ } catch {
948
+ }
914
949
  await client.executeMultiple(`
915
950
  CREATE TABLE IF NOT EXISTS entity_aliases (
916
951
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1091,6 +1126,33 @@ async function ensureSchema() {
1091
1126
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1092
1127
  ON conversations(channel_id);
1093
1128
  `);
1129
+ await client.executeMultiple(`
1130
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1131
+ session_uuid TEXT PRIMARY KEY,
1132
+ agent_id TEXT NOT NULL,
1133
+ session_name TEXT,
1134
+ task_id TEXT,
1135
+ project_name TEXT,
1136
+ started_at TEXT NOT NULL
1137
+ );
1138
+
1139
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1140
+ ON session_agent_map(agent_id);
1141
+ `);
1142
+ try {
1143
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1144
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1145
+ await client.execute({
1146
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1147
+ SELECT session_id, agent_id, '', MIN(timestamp)
1148
+ FROM memories
1149
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1150
+ GROUP BY session_id, agent_id`,
1151
+ args: []
1152
+ });
1153
+ }
1154
+ } catch {
1155
+ }
1094
1156
  try {
1095
1157
  await client.execute({
1096
1158
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1224,15 +1286,41 @@ async function ensureSchema() {
1224
1286
  });
1225
1287
  } catch {
1226
1288
  }
1289
+ for (const col of [
1290
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1291
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1292
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1293
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1294
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1295
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1296
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1297
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1298
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1299
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1300
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1301
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1302
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1303
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1304
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1305
+ ]) {
1306
+ try {
1307
+ await client.execute(col);
1308
+ } catch {
1309
+ }
1310
+ }
1227
1311
  }
1228
1312
  async function disposeDatabase() {
1313
+ if (_daemonClient) {
1314
+ _daemonClient.close();
1315
+ _daemonClient = null;
1316
+ }
1229
1317
  if (_client) {
1230
1318
  _client.close();
1231
1319
  _client = null;
1232
1320
  _resilientClient = null;
1233
1321
  }
1234
1322
  }
1235
- var _client, _resilientClient, initTurso, disposeTurso;
1323
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1236
1324
  var init_database = __esm({
1237
1325
  "src/lib/database.ts"() {
1238
1326
  "use strict";
@@ -1240,6 +1328,7 @@ var init_database = __esm({
1240
1328
  init_employees();
1241
1329
  _client = null;
1242
1330
  _resilientClient = null;
1331
+ _daemonClient = null;
1243
1332
  initTurso = initDatabase;
1244
1333
  disposeTurso = disposeDatabase;
1245
1334
  }
@@ -1276,12 +1365,20 @@ async function getMasterKey() {
1276
1365
  }
1277
1366
  const keyPath = getKeyPath();
1278
1367
  if (!existsSync3(keyPath)) {
1368
+ process.stderr.write(
1369
+ `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1370
+ `
1371
+ );
1279
1372
  return null;
1280
1373
  }
1281
1374
  try {
1282
1375
  const content = await readFile3(keyPath, "utf-8");
1283
1376
  return Buffer.from(content.trim(), "base64");
1284
- } catch {
1377
+ } catch (err) {
1378
+ process.stderr.write(
1379
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
1380
+ `
1381
+ );
1285
1382
  return null;
1286
1383
  }
1287
1384
  }
@@ -1792,6 +1889,7 @@ __export(store_exports, {
1792
1889
  vectorToBlob: () => vectorToBlob,
1793
1890
  writeMemory: () => writeMemory
1794
1891
  });
1892
+ import { createHash } from "crypto";
1795
1893
  function isBusyError2(err) {
1796
1894
  if (err instanceof Error) {
1797
1895
  const msg = err.message.toLowerCase();
@@ -1865,12 +1963,52 @@ function classifyTier(record) {
1865
1963
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1866
1964
  return 3;
1867
1965
  }
1966
+ function inferFilePaths(record) {
1967
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
1968
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
1969
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
1970
+ return match ? JSON.stringify([match[1]]) : null;
1971
+ }
1972
+ function inferCommitHash(record) {
1973
+ if (record.tool_name !== "Bash") return null;
1974
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
1975
+ return match ? match[1] : null;
1976
+ }
1977
+ function inferLanguageType(record) {
1978
+ const text = record.raw_text;
1979
+ if (!text || text.length < 10) return null;
1980
+ const trimmed = text.trimStart();
1981
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
1982
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
1983
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
1984
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
1985
+ return "mixed";
1986
+ }
1987
+ function inferDomain(record) {
1988
+ const proj = (record.project_name ?? "").toLowerCase();
1989
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
1990
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
1991
+ return null;
1992
+ }
1868
1993
  async function writeMemory(record) {
1869
1994
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1870
1995
  throw new Error(
1871
1996
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
1872
1997
  );
1873
1998
  }
1999
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
2000
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
2001
+ return;
2002
+ }
2003
+ try {
2004
+ const client = getClient();
2005
+ const existing = await client.execute({
2006
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
2007
+ args: [contentHash, record.agent_id]
2008
+ });
2009
+ if (existing.rows.length > 0) return;
2010
+ } catch {
2011
+ }
1874
2012
  const dbRow = {
1875
2013
  id: record.id,
1876
2014
  agent_id: record.agent_id,
@@ -1900,7 +2038,23 @@ async function writeMemory(record) {
1900
2038
  supersedes_id: record.supersedes_id ?? null,
1901
2039
  draft: record.draft ? 1 : 0,
1902
2040
  memory_type: record.memory_type ?? "raw",
1903
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
2041
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
2042
+ content_hash: contentHash,
2043
+ intent: record.intent ?? null,
2044
+ outcome: record.outcome ?? null,
2045
+ domain: record.domain ?? inferDomain(record),
2046
+ referenced_entities: record.referenced_entities ?? null,
2047
+ retrieval_count: record.retrieval_count ?? 0,
2048
+ chain_position: record.chain_position ?? null,
2049
+ review_status: record.review_status ?? null,
2050
+ context_window_pct: record.context_window_pct ?? null,
2051
+ file_paths: record.file_paths ?? inferFilePaths(record),
2052
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
2053
+ duration_ms: record.duration_ms ?? null,
2054
+ token_cost: record.token_cost ?? null,
2055
+ audience: record.audience ?? null,
2056
+ language_type: record.language_type ?? inferLanguageType(record),
2057
+ parent_memory_id: record.parent_memory_id ?? null
1904
2058
  };
1905
2059
  _pendingRecords.push(dbRow);
1906
2060
  orgBus.emit({
@@ -1958,80 +2112,85 @@ async function flushBatch() {
1958
2112
  const draft = row.draft ? 1 : 0;
1959
2113
  const memoryType = row.memory_type ?? "raw";
1960
2114
  const trajectory = row.trajectory ?? null;
1961
- return {
1962
- sql: hasVector ? `INSERT OR IGNORE INTO memories
1963
- (id, agent_id, agent_role, session_id, timestamp,
1964
- tool_name, project_name,
1965
- has_error, raw_text, vector, version, task_id, importance, status,
1966
- confidence, last_accessed,
1967
- workspace_id, document_id, user_id, char_offset, page_number,
1968
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1969
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1970
- (id, agent_id, agent_role, session_id, timestamp,
2115
+ const contentHash = row.content_hash ?? null;
2116
+ const intent = row.intent ?? null;
2117
+ const outcome = row.outcome ?? null;
2118
+ const domain = row.domain ?? null;
2119
+ const referencedEntities = row.referenced_entities ?? null;
2120
+ const retrievalCount = row.retrieval_count ?? 0;
2121
+ const chainPosition = row.chain_position ?? null;
2122
+ const reviewStatus = row.review_status ?? null;
2123
+ const contextWindowPct = row.context_window_pct ?? null;
2124
+ const filePaths = row.file_paths ?? null;
2125
+ const commitHash = row.commit_hash ?? null;
2126
+ const durationMs = row.duration_ms ?? null;
2127
+ const tokenCost = row.token_cost ?? null;
2128
+ const audience = row.audience ?? null;
2129
+ const languageType = row.language_type ?? null;
2130
+ const parentMemoryId = row.parent_memory_id ?? null;
2131
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
1971
2132
  tool_name, project_name,
1972
2133
  has_error, raw_text, vector, version, task_id, importance, status,
1973
2134
  confidence, last_accessed,
1974
2135
  workspace_id, document_id, user_id, char_offset, page_number,
1975
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1976
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1977
- args: hasVector ? [
1978
- row.id,
1979
- row.agent_id,
1980
- row.agent_role,
1981
- row.session_id,
1982
- row.timestamp,
1983
- row.tool_name,
1984
- row.project_name,
1985
- row.has_error,
1986
- row.raw_text,
1987
- vectorToBlob(row.vector),
1988
- row.version,
1989
- taskId,
1990
- importance,
1991
- status,
1992
- confidence,
1993
- lastAccessed,
1994
- workspaceId,
1995
- documentId,
1996
- userId,
1997
- charOffset,
1998
- pageNumber,
1999
- sourcePath,
2000
- sourceType,
2001
- tier,
2002
- supersedesId,
2003
- draft,
2004
- memoryType,
2005
- trajectory
2006
- ] : [
2007
- row.id,
2008
- row.agent_id,
2009
- row.agent_role,
2010
- row.session_id,
2011
- row.timestamp,
2012
- row.tool_name,
2013
- row.project_name,
2014
- row.has_error,
2015
- row.raw_text,
2016
- row.version,
2017
- taskId,
2018
- importance,
2019
- status,
2020
- confidence,
2021
- lastAccessed,
2022
- workspaceId,
2023
- documentId,
2024
- userId,
2025
- charOffset,
2026
- pageNumber,
2027
- sourcePath,
2028
- sourceType,
2029
- tier,
2030
- supersedesId,
2031
- draft,
2032
- memoryType,
2033
- trajectory
2034
- ]
2136
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
2137
+ intent, outcome, domain, referenced_entities, retrieval_count,
2138
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
2139
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
2140
+ const metaArgs = [
2141
+ intent,
2142
+ outcome,
2143
+ domain,
2144
+ referencedEntities,
2145
+ retrievalCount,
2146
+ chainPosition,
2147
+ reviewStatus,
2148
+ contextWindowPct,
2149
+ filePaths,
2150
+ commitHash,
2151
+ durationMs,
2152
+ tokenCost,
2153
+ audience,
2154
+ languageType,
2155
+ parentMemoryId
2156
+ ];
2157
+ const baseArgs = [
2158
+ row.id,
2159
+ row.agent_id,
2160
+ row.agent_role,
2161
+ row.session_id,
2162
+ row.timestamp,
2163
+ row.tool_name,
2164
+ row.project_name,
2165
+ row.has_error,
2166
+ row.raw_text
2167
+ ];
2168
+ const sharedArgs = [
2169
+ row.version,
2170
+ taskId,
2171
+ importance,
2172
+ status,
2173
+ confidence,
2174
+ lastAccessed,
2175
+ workspaceId,
2176
+ documentId,
2177
+ userId,
2178
+ charOffset,
2179
+ pageNumber,
2180
+ sourcePath,
2181
+ sourceType,
2182
+ tier,
2183
+ supersedesId,
2184
+ draft,
2185
+ memoryType,
2186
+ trajectory,
2187
+ contentHash
2188
+ ];
2189
+ return {
2190
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
2191
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
2192
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2193
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
2035
2194
  };
2036
2195
  };
2037
2196
  const globalClient = getClient();
@@ -2370,12 +2529,141 @@ var init_self_query_router = __esm({
2370
2529
  }
2371
2530
  });
2372
2531
 
2532
+ // src/lib/reranker.ts
2533
+ var reranker_exports = {};
2534
+ __export(reranker_exports, {
2535
+ disposeReranker: () => disposeReranker,
2536
+ getRerankerModelPath: () => getRerankerModelPath,
2537
+ isRerankerAvailable: () => isRerankerAvailable,
2538
+ rerank: () => rerank,
2539
+ rerankWithContext: () => rerankWithContext,
2540
+ rerankWithScores: () => rerankWithScores
2541
+ });
2542
+ import path5 from "path";
2543
+ import { existsSync as existsSync5 } from "fs";
2544
+ function resetIdleTimer() {
2545
+ if (_idleTimer) clearTimeout(_idleTimer);
2546
+ _idleTimer = setTimeout(() => {
2547
+ void disposeReranker();
2548
+ }, IDLE_TIMEOUT_MS);
2549
+ if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
2550
+ _idleTimer.unref();
2551
+ }
2552
+ }
2553
+ function isRerankerAvailable() {
2554
+ return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
2555
+ }
2556
+ function getRerankerModelPath() {
2557
+ return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2558
+ }
2559
+ async function ensureLoaded() {
2560
+ if (_rerankerContext) {
2561
+ resetIdleTimer();
2562
+ return;
2563
+ }
2564
+ const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2565
+ if (!existsSync5(modelPath)) {
2566
+ throw new Error(
2567
+ `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
2568
+ );
2569
+ }
2570
+ process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
2571
+ const { getLlama } = await import("node-llama-cpp");
2572
+ const llama = await getLlama();
2573
+ _rerankerModel = await llama.loadModel({ modelPath });
2574
+ _rerankerContext = await _rerankerModel.createEmbeddingContext();
2575
+ process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
2576
+ resetIdleTimer();
2577
+ }
2578
+ async function disposeReranker() {
2579
+ if (_idleTimer) {
2580
+ clearTimeout(_idleTimer);
2581
+ _idleTimer = null;
2582
+ }
2583
+ if (_rerankerContext) {
2584
+ try {
2585
+ await _rerankerContext.dispose();
2586
+ } catch {
2587
+ }
2588
+ _rerankerContext = null;
2589
+ }
2590
+ if (_rerankerModel) {
2591
+ try {
2592
+ await _rerankerModel.dispose();
2593
+ } catch {
2594
+ }
2595
+ _rerankerModel = null;
2596
+ }
2597
+ process.stderr.write("[reranker] Unloaded (idle timeout).\n");
2598
+ }
2599
+ async function rerankWithScores(query, texts, topK) {
2600
+ if (texts.length === 0) return [];
2601
+ await ensureLoaded();
2602
+ const ctx = _rerankerContext;
2603
+ const scored = [];
2604
+ for (let i = 0; i < texts.length; i++) {
2605
+ const text = texts[i] ?? "";
2606
+ try {
2607
+ const input = `query: ${query} document: ${text.slice(0, 512)}`;
2608
+ const embedding = await ctx.getEmbeddingFor(input);
2609
+ const score = embedding.vector[0] ?? 0;
2610
+ scored.push({ text, score, index: i });
2611
+ } catch {
2612
+ scored.push({ text, score: -1, index: i });
2613
+ }
2614
+ }
2615
+ scored.sort((a, b) => b.score - a.score);
2616
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
2617
+ }
2618
+ async function rerank(query, candidates, topK = 5) {
2619
+ if (candidates.length === 0) return [];
2620
+ if (candidates.length <= topK) return candidates;
2621
+ const scored = await rerankWithScores(
2622
+ query,
2623
+ candidates.map((c) => c.raw_text),
2624
+ topK
2625
+ );
2626
+ return scored.map((s) => candidates[s.index]);
2627
+ }
2628
+ async function rerankWithContext(query, candidates, topK) {
2629
+ if (candidates.length === 0) return [];
2630
+ await ensureLoaded();
2631
+ const ctx = _rerankerContext;
2632
+ const scored = [];
2633
+ for (let i = 0; i < candidates.length; i++) {
2634
+ const candidate = candidates[i];
2635
+ try {
2636
+ const docText = candidate.context ? `[${candidate.context}] ${candidate.text.slice(0, 460)}` : candidate.text.slice(0, 512);
2637
+ const input = `query: ${query} document: ${docText}`;
2638
+ const embedding = await ctx.getEmbeddingFor(input);
2639
+ const score = embedding.vector[0] ?? 0;
2640
+ scored.push({ text: candidate.text, score, index: i });
2641
+ } catch {
2642
+ scored.push({ text: candidate.text, score: -1, index: i });
2643
+ }
2644
+ }
2645
+ scored.sort((a, b) => b.score - a.score);
2646
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
2647
+ }
2648
+ var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
2649
+ var init_reranker = __esm({
2650
+ "src/lib/reranker.ts"() {
2651
+ "use strict";
2652
+ init_config();
2653
+ RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
2654
+ IDLE_TIMEOUT_MS = 6e4;
2655
+ _rerankerContext = null;
2656
+ _rerankerModel = null;
2657
+ _idleTimer = null;
2658
+ }
2659
+ });
2660
+
2373
2661
  // src/lib/exe-daemon-client.ts
2374
2662
  import net from "net";
2375
2663
  import { spawn } from "child_process";
2376
2664
  import { randomUUID as randomUUID2 } from "crypto";
2377
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2378
- import path5 from "path";
2665
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2666
+ import path6 from "path";
2379
2667
  import { fileURLToPath } from "url";
2380
2668
  function handleData(chunk) {
2381
2669
  _buffer += chunk.toString();
@@ -2390,10 +2678,12 @@ function handleData(chunk) {
2390
2678
  if (!line) continue;
2391
2679
  try {
2392
2680
  const response = JSON.parse(line);
2393
- const entry = _pending.get(response.id);
2681
+ const id = response.id;
2682
+ if (!id) continue;
2683
+ const entry = _pending.get(id);
2394
2684
  if (entry) {
2395
2685
  clearTimeout(entry.timer);
2396
- _pending.delete(response.id);
2686
+ _pending.delete(id);
2397
2687
  entry.resolve(response);
2398
2688
  }
2399
2689
  } catch {
@@ -2401,7 +2691,7 @@ function handleData(chunk) {
2401
2691
  }
2402
2692
  }
2403
2693
  function cleanupStaleFiles() {
2404
- if (existsSync5(PID_PATH)) {
2694
+ if (existsSync6(PID_PATH)) {
2405
2695
  try {
2406
2696
  const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2407
2697
  if (pid > 0) {
@@ -2424,11 +2714,11 @@ function cleanupStaleFiles() {
2424
2714
  }
2425
2715
  }
2426
2716
  function findPackageRoot() {
2427
- let dir = path5.dirname(fileURLToPath(import.meta.url));
2428
- const { root } = path5.parse(dir);
2717
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2718
+ const { root } = path6.parse(dir);
2429
2719
  while (dir !== root) {
2430
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
2431
- dir = path5.dirname(dir);
2720
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2721
+ dir = path6.dirname(dir);
2432
2722
  }
2433
2723
  return null;
2434
2724
  }
@@ -2438,8 +2728,8 @@ function spawnDaemon() {
2438
2728
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2439
2729
  return;
2440
2730
  }
2441
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2442
- if (!existsSync5(daemonPath)) {
2731
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2732
+ if (!existsSync6(daemonPath)) {
2443
2733
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2444
2734
  `);
2445
2735
  return;
@@ -2447,7 +2737,7 @@ function spawnDaemon() {
2447
2737
  const resolvedPath = daemonPath;
2448
2738
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2449
2739
  `);
2450
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2740
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
2451
2741
  let stderrFd = "ignore";
2452
2742
  try {
2453
2743
  stderrFd = openSync(logPath, "a");
@@ -2564,6 +2854,9 @@ async function connectEmbedDaemon() {
2564
2854
  return false;
2565
2855
  }
2566
2856
  function sendRequest(texts, priority) {
2857
+ return sendDaemonRequest({ texts, priority });
2858
+ }
2859
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2567
2860
  return new Promise((resolve) => {
2568
2861
  if (!_socket || !_connected) {
2569
2862
  resolve({ error: "Not connected" });
@@ -2573,10 +2866,10 @@ function sendRequest(texts, priority) {
2573
2866
  const timer = setTimeout(() => {
2574
2867
  _pending.delete(id);
2575
2868
  resolve({ error: "Request timeout" });
2576
- }, REQUEST_TIMEOUT_MS);
2869
+ }, timeoutMs);
2577
2870
  _pending.set(id, { resolve, timer });
2578
2871
  try {
2579
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
2872
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2580
2873
  } catch {
2581
2874
  clearTimeout(timer);
2582
2875
  _pending.delete(id);
@@ -2586,34 +2879,15 @@ function sendRequest(texts, priority) {
2586
2879
  }
2587
2880
  async function pingDaemon() {
2588
2881
  if (!_socket || !_connected) return null;
2589
- return new Promise((resolve) => {
2590
- const id = randomUUID2();
2591
- const timer = setTimeout(() => {
2592
- _pending.delete(id);
2593
- resolve(null);
2594
- }, 5e3);
2595
- _pending.set(id, {
2596
- resolve: (data) => {
2597
- if (data.health) {
2598
- resolve(data.health);
2599
- } else {
2600
- resolve(null);
2601
- }
2602
- },
2603
- timer
2604
- });
2605
- try {
2606
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
2607
- } catch {
2608
- clearTimeout(timer);
2609
- _pending.delete(id);
2610
- resolve(null);
2611
- }
2612
- });
2882
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
2883
+ if (response.health) {
2884
+ return response.health;
2885
+ }
2886
+ return null;
2613
2887
  }
2614
2888
  function killAndRespawnDaemon() {
2615
2889
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2616
- if (existsSync5(PID_PATH)) {
2890
+ if (existsSync6(PID_PATH)) {
2617
2891
  try {
2618
2892
  const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2619
2893
  if (pid > 0) {
@@ -2699,9 +2973,9 @@ var init_exe_daemon_client = __esm({
2699
2973
  "src/lib/exe-daemon-client.ts"() {
2700
2974
  "use strict";
2701
2975
  init_config();
2702
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
2703
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
2704
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2976
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
2977
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
2978
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2705
2979
  SPAWN_LOCK_STALE_MS = 3e4;
2706
2980
  CONNECT_TIMEOUT_MS = 15e3;
2707
2981
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2790,7 +3064,7 @@ __export(project_name_exports, {
2790
3064
  getProjectName: () => getProjectName
2791
3065
  });
2792
3066
  import { execSync as execSync2 } from "child_process";
2793
- import path6 from "path";
3067
+ import path7 from "path";
2794
3068
  function getProjectName(cwd) {
2795
3069
  const dir = cwd ?? process.cwd();
2796
3070
  if (_cached && _cachedCwd === dir) return _cached;
@@ -2803,7 +3077,7 @@ function getProjectName(cwd) {
2803
3077
  timeout: 2e3,
2804
3078
  stdio: ["pipe", "pipe", "pipe"]
2805
3079
  }).trim();
2806
- repoRoot = path6.dirname(gitCommonDir);
3080
+ repoRoot = path7.dirname(gitCommonDir);
2807
3081
  } catch {
2808
3082
  repoRoot = execSync2("git rev-parse --show-toplevel", {
2809
3083
  cwd: dir,
@@ -2812,11 +3086,11 @@ function getProjectName(cwd) {
2812
3086
  stdio: ["pipe", "pipe", "pipe"]
2813
3087
  }).trim();
2814
3088
  }
2815
- _cached = path6.basename(repoRoot);
3089
+ _cached = path7.basename(repoRoot);
2816
3090
  _cachedCwd = dir;
2817
3091
  return _cached;
2818
3092
  } catch {
2819
- _cached = path6.basename(dir);
3093
+ _cached = path7.basename(dir);
2820
3094
  _cachedCwd = dir;
2821
3095
  return _cached;
2822
3096
  }
@@ -2840,8 +3114,8 @@ __export(file_grep_exports, {
2840
3114
  grepProjectFiles: () => grepProjectFiles
2841
3115
  });
2842
3116
  import { execSync as execSync3 } from "child_process";
2843
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync6 } from "fs";
2844
- import path7 from "path";
3117
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3118
+ import path8 from "path";
2845
3119
  import crypto from "crypto";
2846
3120
  function hasRipgrep() {
2847
3121
  if (_hasRg === null) {
@@ -2881,7 +3155,7 @@ async function grepProjectFiles(query, projectRoot, options) {
2881
3155
  session_id: "file-grep",
2882
3156
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2883
3157
  tool_name: "file_grep",
2884
- project_name: path7.basename(projectRoot),
3158
+ project_name: path8.basename(projectRoot),
2885
3159
  has_error: false,
2886
3160
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
2887
3161
  vector: null,
@@ -2955,7 +3229,7 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
2955
3229
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
2956
3230
  const hits = [];
2957
3231
  for (const filePath of files.slice(0, MAX_FILES)) {
2958
- const absPath = path7.join(projectRoot, filePath);
3232
+ const absPath = path8.join(projectRoot, filePath);
2959
3233
  try {
2960
3234
  const stat = statSync2(absPath);
2961
3235
  if (stat.size > MAX_FILE_SIZE) continue;
@@ -2982,15 +3256,15 @@ function collectFiles(root, patterns) {
2982
3256
  const files = [];
2983
3257
  function walk(dir, relative) {
2984
3258
  if (files.length >= MAX_FILES) return;
2985
- const basename = path7.basename(dir);
3259
+ const basename = path8.basename(dir);
2986
3260
  if (EXCLUDE_DIRS.includes(basename)) return;
2987
3261
  try {
2988
3262
  const entries = readdirSync2(dir, { withFileTypes: true });
2989
3263
  for (const entry of entries) {
2990
3264
  if (files.length >= MAX_FILES) return;
2991
- const rel = path7.join(relative, entry.name);
3265
+ const rel = path8.join(relative, entry.name);
2992
3266
  if (entry.isDirectory()) {
2993
- walk(path7.join(dir, entry.name), rel);
3267
+ walk(path8.join(dir, entry.name), rel);
2994
3268
  } else if (entry.isFile()) {
2995
3269
  for (const pat of patterns) {
2996
3270
  if (matchGlob(rel, pat)) {
@@ -3022,7 +3296,7 @@ function matchGlob(filePath, pattern) {
3022
3296
  if (slashIdx !== -1) {
3023
3297
  const dir = pattern.slice(0, slashIdx);
3024
3298
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3025
- const fileDir = path7.dirname(filePath);
3299
+ const fileDir = path8.dirname(filePath);
3026
3300
  return fileDir === dir && filePath.endsWith(ext2);
3027
3301
  }
3028
3302
  const ext = pattern.replace("*", "");
@@ -3030,8 +3304,8 @@ function matchGlob(filePath, pattern) {
3030
3304
  }
3031
3305
  function buildSnippet(hit, projectRoot) {
3032
3306
  try {
3033
- const absPath = path7.join(projectRoot, hit.filePath);
3034
- if (!existsSync6(absPath)) return hit.matchLine;
3307
+ const absPath = path8.join(projectRoot, hit.filePath);
3308
+ if (!existsSync7(absPath)) return hit.matchLine;
3035
3309
  const lines = readFileSync4(absPath, "utf8").split("\n");
3036
3310
  const start = Math.max(0, hit.lineNumber - 3);
3037
3311
  const end = Math.min(lines.length, hit.lineNumber + 2);
@@ -3057,111 +3331,574 @@ var init_file_grep = __esm({
3057
3331
  }
3058
3332
  });
3059
3333
 
3060
- // src/lib/reranker.ts
3061
- var reranker_exports = {};
3062
- __export(reranker_exports, {
3063
- disposeReranker: () => disposeReranker,
3064
- getRerankerModelPath: () => getRerankerModelPath,
3065
- isRerankerAvailable: () => isRerankerAvailable,
3066
- rerank: () => rerank,
3067
- rerankWithScores: () => rerankWithScores
3334
+ // src/lib/graph-query.ts
3335
+ var graph_query_exports = {};
3336
+ __export(graph_query_exports, {
3337
+ getConversationPartners: () => getConversationPartners,
3338
+ getEntityByName: () => getEntityByName,
3339
+ getEntityNeighbors: () => getEntityNeighbors,
3340
+ getEntityTimeline: () => getEntityTimeline,
3341
+ getGraphStats: () => getGraphStats,
3342
+ getHotEntities: () => getHotEntities,
3343
+ getRelationshipFrequency: () => getRelationshipFrequency,
3344
+ getRelationships: () => getRelationships,
3345
+ searchEntities: () => searchEntities,
3346
+ traverseChain: () => traverseChain
3068
3347
  });
3069
- import path8 from "path";
3070
- import { existsSync as existsSync7 } from "fs";
3071
- function resetIdleTimer() {
3072
- if (_idleTimer) clearTimeout(_idleTimer);
3073
- _idleTimer = setTimeout(() => {
3074
- void disposeReranker();
3075
- }, IDLE_TIMEOUT_MS);
3076
- if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
3077
- _idleTimer.unref();
3348
+ async function getEntityByName(client, name, type) {
3349
+ const sql = type ? `SELECT * FROM entities WHERE LOWER(name) = LOWER(?) AND type = ? LIMIT 1` : `SELECT * FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1`;
3350
+ const args = type ? [name, type] : [name];
3351
+ const result = await client.execute({ sql, args });
3352
+ if (result.rows.length === 0) return null;
3353
+ const row = result.rows[0];
3354
+ return {
3355
+ id: String(row.id),
3356
+ name: String(row.name),
3357
+ type: String(row.type),
3358
+ firstSeen: String(row.first_seen),
3359
+ lastSeen: String(row.last_seen),
3360
+ properties: JSON.parse(String(row.properties ?? "{}"))
3361
+ };
3362
+ }
3363
+ async function searchEntities(client, query, limit = 10) {
3364
+ const result = await client.execute({
3365
+ sql: `SELECT * FROM entities WHERE LOWER(name) LIKE ? ORDER BY last_seen DESC LIMIT ?`,
3366
+ args: [`%${query.toLowerCase()}%`, limit]
3367
+ });
3368
+ return result.rows.map((row) => ({
3369
+ id: String(row.id),
3370
+ name: String(row.name),
3371
+ type: String(row.type),
3372
+ firstSeen: String(row.first_seen),
3373
+ lastSeen: String(row.last_seen),
3374
+ properties: JSON.parse(String(row.properties ?? "{}"))
3375
+ }));
3376
+ }
3377
+ async function getRelationships(client, entityId, options) {
3378
+ const direction = options?.direction ?? "both";
3379
+ let sql;
3380
+ const args = [];
3381
+ if (direction === "outgoing") {
3382
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3383
+ FROM relationships r
3384
+ JOIN entities s ON r.source_entity_id = s.id
3385
+ JOIN entities t ON r.target_entity_id = t.id
3386
+ WHERE r.source_entity_id = ?`;
3387
+ args.push(entityId);
3388
+ } else if (direction === "incoming") {
3389
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3390
+ FROM relationships r
3391
+ JOIN entities s ON r.source_entity_id = s.id
3392
+ JOIN entities t ON r.target_entity_id = t.id
3393
+ WHERE r.target_entity_id = ?`;
3394
+ args.push(entityId);
3395
+ } else {
3396
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3397
+ FROM relationships r
3398
+ JOIN entities s ON r.source_entity_id = s.id
3399
+ JOIN entities t ON r.target_entity_id = t.id
3400
+ WHERE r.source_entity_id = ? OR r.target_entity_id = ?`;
3401
+ args.push(entityId, entityId);
3402
+ }
3403
+ if (options?.type) {
3404
+ sql += ` AND r.type = ?`;
3405
+ args.push(options.type);
3406
+ }
3407
+ sql += ` ORDER BY r.weight DESC, r.timestamp DESC`;
3408
+ const result = await client.execute({ sql, args });
3409
+ return result.rows.map((row) => ({
3410
+ id: String(row.id),
3411
+ sourceEntityId: String(row.source_entity_id),
3412
+ targetEntityId: String(row.target_entity_id),
3413
+ type: String(row.type),
3414
+ weight: Number(row.weight),
3415
+ timestamp: String(row.timestamp),
3416
+ properties: JSON.parse(String(row.properties ?? "{}")),
3417
+ confidence: Number(row.confidence ?? 1),
3418
+ confidenceLabel: String(row.confidence_label ?? "extracted"),
3419
+ sourceName: String(row.source_name),
3420
+ targetName: String(row.target_name)
3421
+ }));
3422
+ }
3423
+ async function traverseChain(client, startEntityId, relationshipType, maxDepth = 3) {
3424
+ const result = await client.execute({
3425
+ sql: `WITH RECURSIVE chain(entity_id, depth, rel_type) AS (
3426
+ SELECT ?, 0, ''
3427
+ UNION ALL
3428
+ SELECT r.target_entity_id, c.depth + 1, r.type
3429
+ FROM chain c
3430
+ JOIN relationships r ON r.source_entity_id = c.entity_id
3431
+ WHERE r.type = ? AND c.depth < ?
3432
+ )
3433
+ SELECT DISTINCT e.*, chain.depth, chain.rel_type
3434
+ FROM chain
3435
+ JOIN entities e ON e.id = chain.entity_id
3436
+ ORDER BY chain.depth ASC`,
3437
+ args: [startEntityId, relationshipType, maxDepth]
3438
+ });
3439
+ return result.rows.map((row) => ({
3440
+ entity: {
3441
+ id: String(row.id),
3442
+ name: String(row.name),
3443
+ type: String(row.type),
3444
+ firstSeen: String(row.first_seen),
3445
+ lastSeen: String(row.last_seen),
3446
+ properties: JSON.parse(String(row.properties ?? "{}"))
3447
+ },
3448
+ depth: Number(row.depth),
3449
+ relationship: String(row.rel_type)
3450
+ }));
3451
+ }
3452
+ async function getEntityNeighbors(client, entityId, maxHops = 2) {
3453
+ const result = await client.execute({
3454
+ sql: `WITH RECURSIVE neighborhood(entity_id, depth, rel_type, rel_confidence) AS (
3455
+ SELECT ?, 0, '', 1.0
3456
+ UNION ALL
3457
+ SELECT CASE
3458
+ WHEN r.source_entity_id = n.entity_id THEN r.target_entity_id
3459
+ ELSE r.source_entity_id
3460
+ END, n.depth + 1, r.type, COALESCE(r.confidence, 1.0)
3461
+ FROM neighborhood n
3462
+ JOIN relationships r ON r.source_entity_id = n.entity_id
3463
+ OR r.target_entity_id = n.entity_id
3464
+ WHERE n.depth < ?
3465
+ )
3466
+ SELECT DISTINCT e.*, MIN(neighborhood.depth) as depth,
3467
+ neighborhood.rel_type, neighborhood.rel_confidence
3468
+ FROM neighborhood
3469
+ JOIN entities e ON e.id = neighborhood.entity_id
3470
+ GROUP BY e.id
3471
+ ORDER BY depth ASC, e.last_seen DESC
3472
+ LIMIT 50`,
3473
+ args: [entityId, maxHops]
3474
+ });
3475
+ return result.rows.map((row) => ({
3476
+ entity: {
3477
+ id: String(row.id),
3478
+ name: String(row.name),
3479
+ type: String(row.type),
3480
+ firstSeen: String(row.first_seen),
3481
+ lastSeen: String(row.last_seen),
3482
+ properties: JSON.parse(String(row.properties ?? "{}"))
3483
+ },
3484
+ depth: Number(row.depth),
3485
+ relType: String(row.rel_type ?? ""),
3486
+ relConfidence: Number(row.rel_confidence ?? 1)
3487
+ }));
3488
+ }
3489
+ async function getGraphStats(client) {
3490
+ const entityCount = await client.execute("SELECT COUNT(*) as cnt FROM entities");
3491
+ const relCount = await client.execute("SELECT COUNT(*) as cnt FROM relationships");
3492
+ const typeResult = await client.execute(
3493
+ "SELECT type, COUNT(*) as cnt FROM entities GROUP BY type ORDER BY cnt DESC"
3494
+ );
3495
+ const types = {};
3496
+ for (const row of typeResult.rows) {
3497
+ types[String(row.type)] = Number(row.cnt);
3078
3498
  }
3499
+ return {
3500
+ entities: Number(entityCount.rows[0]?.cnt ?? 0),
3501
+ relationships: Number(relCount.rows[0]?.cnt ?? 0),
3502
+ types
3503
+ };
3079
3504
  }
3080
- function isRerankerAvailable() {
3081
- return existsSync7(path8.join(MODELS_DIR, RERANKER_MODEL_FILE));
3505
+ async function getEntityTimeline(client, entityId, since) {
3506
+ const args = [entityId, entityId];
3507
+ let sinceClause = "";
3508
+ if (since) {
3509
+ sinceClause = " AND r.timestamp >= ?";
3510
+ args.push(since.toISOString());
3511
+ }
3512
+ const result = await client.execute({
3513
+ sql: `SELECT r.*, s.name as source_name, t.name as target_name
3514
+ FROM relationships r
3515
+ JOIN entities s ON r.source_entity_id = s.id
3516
+ JOIN entities t ON r.target_entity_id = t.id
3517
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${sinceClause}
3518
+ ORDER BY r.timestamp DESC`,
3519
+ args
3520
+ });
3521
+ return result.rows.map((row) => ({
3522
+ relationship: {
3523
+ id: String(row.id),
3524
+ sourceEntityId: String(row.source_entity_id),
3525
+ targetEntityId: String(row.target_entity_id),
3526
+ type: String(row.type),
3527
+ weight: Number(row.weight),
3528
+ timestamp: String(row.timestamp),
3529
+ properties: JSON.parse(String(row.properties ?? "{}")),
3530
+ sourceName: String(row.source_name),
3531
+ targetName: String(row.target_name)
3532
+ },
3533
+ timestamp: String(row.timestamp)
3534
+ }));
3082
3535
  }
3083
- function getRerankerModelPath() {
3084
- return path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3536
+ async function getRelationshipFrequency(client, entityId, options) {
3537
+ const granularity = options?.granularity ?? "day";
3538
+ const args = [entityId, entityId];
3539
+ let typeClause = "";
3540
+ if (options?.type) {
3541
+ typeClause = " AND r.type = ?";
3542
+ args.push(options.type);
3543
+ }
3544
+ let bucketExpr;
3545
+ switch (granularity) {
3546
+ case "week":
3547
+ bucketExpr = `strftime('%Y', r.timestamp) || '-W' || strftime('%W', r.timestamp)`;
3548
+ break;
3549
+ case "month":
3550
+ bucketExpr = `strftime('%Y-%m', r.timestamp)`;
3551
+ break;
3552
+ default:
3553
+ bucketExpr = `strftime('%Y-%m-%d', r.timestamp)`;
3554
+ }
3555
+ const result = await client.execute({
3556
+ sql: `SELECT ${bucketExpr} as bucket, COUNT(*) as cnt, r.type as rel_type
3557
+ FROM relationships r
3558
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${typeClause}
3559
+ GROUP BY bucket${options?.type ? "" : ", r.type"}
3560
+ ORDER BY bucket DESC`,
3561
+ args
3562
+ });
3563
+ return result.rows.map((row) => ({
3564
+ bucket: String(row.bucket),
3565
+ count: Number(row.cnt),
3566
+ relationshipType: options?.type ? options.type : String(row.rel_type)
3567
+ }));
3085
3568
  }
3086
- async function ensureLoaded() {
3087
- if (_rerankerContext) {
3088
- resetIdleTimer();
3089
- return;
3569
+ async function getConversationPartners(client, contactEntityId, since) {
3570
+ const args = [contactEntityId, contactEntityId];
3571
+ let sinceClause = "";
3572
+ if (since) {
3573
+ sinceClause = " AND r.timestamp >= ?";
3574
+ args.push(since.toISOString());
3090
3575
  }
3091
- const modelPath = path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3092
- if (!existsSync7(modelPath)) {
3093
- throw new Error(
3094
- `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3095
- );
3576
+ const result = await client.execute({
3577
+ sql: `SELECT
3578
+ CASE
3579
+ WHEN r.source_entity_id = ? THEN r.target_entity_id
3580
+ ELSE r.source_entity_id
3581
+ END as partner_id,
3582
+ COUNT(*) as interaction_count,
3583
+ MAX(r.timestamp) as last_interaction
3584
+ FROM relationships r
3585
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${sinceClause}
3586
+ GROUP BY partner_id
3587
+ ORDER BY interaction_count DESC`,
3588
+ args: [contactEntityId, ...args]
3589
+ });
3590
+ const partners = [];
3591
+ for (const row of result.rows) {
3592
+ const partnerId = String(row.partner_id);
3593
+ const entityResult = await client.execute({
3594
+ sql: "SELECT * FROM entities WHERE id = ?",
3595
+ args: [partnerId]
3596
+ });
3597
+ if (entityResult.rows.length === 0) continue;
3598
+ const e = entityResult.rows[0];
3599
+ partners.push({
3600
+ agentEntity: {
3601
+ id: String(e.id),
3602
+ name: String(e.name),
3603
+ type: String(e.type),
3604
+ firstSeen: String(e.first_seen),
3605
+ lastSeen: String(e.last_seen),
3606
+ properties: JSON.parse(String(e.properties ?? "{}"))
3607
+ },
3608
+ interactionCount: Number(row.interaction_count),
3609
+ lastInteraction: String(row.last_interaction)
3610
+ });
3096
3611
  }
3097
- process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
3098
- const { getLlama } = await import("node-llama-cpp");
3099
- const llama = await getLlama();
3100
- _rerankerModel = await llama.loadModel({ modelPath });
3101
- _rerankerContext = await _rerankerModel.createEmbeddingContext();
3102
- process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
3103
- resetIdleTimer();
3612
+ return partners;
3104
3613
  }
3105
- async function disposeReranker() {
3106
- if (_idleTimer) {
3107
- clearTimeout(_idleTimer);
3108
- _idleTimer = null;
3614
+ async function getHotEntities(client, since, limit = 10) {
3615
+ const sinceISO = since.toISOString();
3616
+ const result = await client.execute({
3617
+ sql: `SELECT entity_id, COUNT(*) as rel_count
3618
+ FROM (
3619
+ SELECT source_entity_id as entity_id FROM relationships WHERE timestamp >= ?
3620
+ UNION ALL
3621
+ SELECT target_entity_id as entity_id FROM relationships WHERE timestamp >= ?
3622
+ )
3623
+ GROUP BY entity_id
3624
+ ORDER BY rel_count DESC
3625
+ LIMIT ?`,
3626
+ args: [sinceISO, sinceISO, limit]
3627
+ });
3628
+ const hotEntities = [];
3629
+ for (const row of result.rows) {
3630
+ const eid = String(row.entity_id);
3631
+ const entityResult = await client.execute({
3632
+ sql: "SELECT * FROM entities WHERE id = ?",
3633
+ args: [eid]
3634
+ });
3635
+ if (entityResult.rows.length === 0) continue;
3636
+ const e = entityResult.rows[0];
3637
+ hotEntities.push({
3638
+ entity: {
3639
+ id: String(e.id),
3640
+ name: String(e.name),
3641
+ type: String(e.type),
3642
+ firstSeen: String(e.first_seen),
3643
+ lastSeen: String(e.last_seen),
3644
+ properties: JSON.parse(String(e.properties ?? "{}"))
3645
+ },
3646
+ newRelationships: Number(row.rel_count)
3647
+ });
3109
3648
  }
3110
- if (_rerankerContext) {
3111
- try {
3112
- await _rerankerContext.dispose();
3113
- } catch {
3649
+ return hotEntities;
3650
+ }
3651
+ var init_graph_query = __esm({
3652
+ "src/lib/graph-query.ts"() {
3653
+ "use strict";
3654
+ }
3655
+ });
3656
+
3657
+ // src/lib/entity-boost.ts
3658
+ var entity_boost_exports = {};
3659
+ __export(entity_boost_exports, {
3660
+ applyEntityBoost: () => applyEntityBoost
3661
+ });
3662
+ function getRelTypeWeights() {
3663
+ const override = process.env.RELATIONSHIP_TYPE_WEIGHTS;
3664
+ if (!override) return DEFAULT_RELATIONSHIP_WEIGHTS;
3665
+ try {
3666
+ return { ...DEFAULT_RELATIONSHIP_WEIGHTS, ...JSON.parse(override) };
3667
+ } catch {
3668
+ return DEFAULT_RELATIONSHIP_WEIGHTS;
3669
+ }
3670
+ }
3671
+ async function matchEntities(query, client) {
3672
+ const words = query.toLowerCase().split(/\s+/).filter((w) => w.length >= MIN_WORD_LENGTH).map((w) => w.replace(/[^a-z0-9_-]/g, "")).filter((w) => w.length >= MIN_WORD_LENGTH);
3673
+ if (words.length === 0) return [];
3674
+ try {
3675
+ const matchExpr = words.map((w) => `${w}*`).join(" OR ");
3676
+ const result = await client.execute({
3677
+ sql: `SELECT e.id, e.name FROM entities e
3678
+ JOIN entities_fts fts ON e.rowid = fts.rowid
3679
+ WHERE entities_fts MATCH ?
3680
+ ORDER BY rank
3681
+ LIMIT ?`,
3682
+ args: [matchExpr, MAX_ENTITY_MATCHES]
3683
+ });
3684
+ if (result.rows.length > 0) {
3685
+ return result.rows.map((row) => ({
3686
+ entityId: String(row.id),
3687
+ name: String(row.name)
3688
+ }));
3114
3689
  }
3115
- _rerankerContext = null;
3690
+ } catch {
3116
3691
  }
3117
- if (_rerankerModel) {
3118
- try {
3119
- await _rerankerModel.dispose();
3120
- } catch {
3692
+ const conditions = words.map(() => `LOWER(name) LIKE ?`);
3693
+ const args = words.map((w) => `%${w}%`);
3694
+ try {
3695
+ const result = await client.execute({
3696
+ sql: `SELECT id, name FROM entities
3697
+ WHERE ${conditions.join(" OR ")}
3698
+ LIMIT ?`,
3699
+ args: [...args, MAX_ENTITY_MATCHES]
3700
+ });
3701
+ return result.rows.map((row) => ({
3702
+ entityId: String(row.id),
3703
+ name: String(row.name)
3704
+ }));
3705
+ } catch {
3706
+ return [];
3707
+ }
3708
+ }
3709
+ async function getLinkedMemories(entityIds, client) {
3710
+ const linked = /* @__PURE__ */ new Map();
3711
+ if (entityIds.length === 0) return linked;
3712
+ const placeholders = entityIds.map(() => "?").join(",");
3713
+ try {
3714
+ const result = await client.execute({
3715
+ sql: `SELECT entity_id, memory_id FROM entity_memories
3716
+ WHERE entity_id IN (${placeholders})`,
3717
+ args: entityIds
3718
+ });
3719
+ for (const row of result.rows) {
3720
+ const entityId = String(row.entity_id);
3721
+ const memoryId = String(row.memory_id);
3722
+ const entry = linked.get(entityId) ?? {
3723
+ entityId,
3724
+ memoryIds: /* @__PURE__ */ new Set(),
3725
+ count: 0
3726
+ };
3727
+ entry.memoryIds.add(memoryId);
3728
+ entry.count = entry.memoryIds.size;
3729
+ linked.set(entityId, entry);
3121
3730
  }
3122
- _rerankerModel = null;
3731
+ } catch {
3123
3732
  }
3124
- process.stderr.write("[reranker] Unloaded (idle timeout).\n");
3733
+ return linked;
3125
3734
  }
3126
- async function rerankWithScores(query, texts, topK) {
3127
- if (texts.length === 0) return [];
3128
- await ensureLoaded();
3129
- const ctx = _rerankerContext;
3130
- const scored = [];
3131
- for (let i = 0; i < texts.length; i++) {
3132
- const text = texts[i] ?? "";
3735
+ function spreadAttenuation(numLinked) {
3736
+ return 1 / (1 + 1e-3 * (numLinked - 1) ** 2);
3737
+ }
3738
+ async function traverseAndScore(entities, client, boostMap, resultIds, graphContextMap) {
3739
+ const topEntities = entities.slice(0, TOP_ENTITIES_TO_TRAVERSE);
3740
+ if (topEntities.length === 0) return;
3741
+ const { getEntityNeighbors: getEntityNeighbors2 } = await Promise.resolve().then(() => (init_graph_query(), graph_query_exports));
3742
+ const relWeights = getRelTypeWeights();
3743
+ const neighborEntityIds = [];
3744
+ const neighborMeta = /* @__PURE__ */ new Map();
3745
+ for (const entity of topEntities) {
3133
3746
  try {
3134
- const input = `query: ${query} document: ${text.slice(0, 512)}`;
3135
- const embedding = await ctx.getEmbeddingFor(input);
3136
- const score = embedding.vector[0] ?? 0;
3137
- scored.push({ text, score, index: i });
3747
+ const neighbors = await getEntityNeighbors2(
3748
+ client,
3749
+ entity.entityId,
3750
+ GRAPH_TRAVERSAL_MAX_HOPS
3751
+ );
3752
+ for (const n of neighbors) {
3753
+ if (n.depth === 0) continue;
3754
+ if (neighborMeta.has(n.entity.id)) continue;
3755
+ neighborEntityIds.push(n.entity.id);
3756
+ neighborMeta.set(n.entity.id, {
3757
+ depth: n.depth,
3758
+ relType: n.relType,
3759
+ relConfidence: n.relConfidence,
3760
+ sourceName: entity.name
3761
+ });
3762
+ }
3138
3763
  } catch {
3139
- scored.push({ text, score: -1, index: i });
3140
3764
  }
3141
3765
  }
3142
- scored.sort((a, b) => b.score - a.score);
3143
- return typeof topK === "number" ? scored.slice(0, topK) : scored;
3766
+ if (neighborEntityIds.length === 0) return;
3767
+ const neighborLinked = await getLinkedMemories(neighborEntityIds, client);
3768
+ for (const [entityId, entry] of neighborLinked) {
3769
+ const meta = neighborMeta.get(entityId);
3770
+ if (!meta) continue;
3771
+ const relWeight = relWeights[meta.relType] ?? DEFAULT_REL_WEIGHT;
3772
+ const depthDecay = 1 / (1 + meta.depth);
3773
+ const neighborBoost = ENTITY_BOOST_WEIGHT * meta.relConfidence * depthDecay * relWeight;
3774
+ const attenuation = spreadAttenuation(entry.count);
3775
+ const finalBoost = neighborBoost * attenuation;
3776
+ for (const memoryId of entry.memoryIds) {
3777
+ if (resultIds.has(memoryId)) {
3778
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + finalBoost);
3779
+ const contextPath = `${meta.sourceName} ${meta.relType} ${entityId}`;
3780
+ if (!graphContextMap.has(memoryId)) {
3781
+ graphContextMap.set(memoryId, contextPath);
3782
+ }
3783
+ }
3784
+ }
3785
+ }
3144
3786
  }
3145
- async function rerank(query, candidates, topK = 5) {
3146
- if (candidates.length === 0) return [];
3147
- if (candidates.length <= topK) return candidates;
3148
- const scored = await rerankWithScores(
3149
- query,
3150
- candidates.map((c) => c.raw_text),
3151
- topK
3787
+ async function applyHyperedgeBoost(entities, client, boostMap, resultIds) {
3788
+ if (entities.length < 2) return;
3789
+ const entityIds = entities.map((e) => e.entityId);
3790
+ const placeholders = entityIds.map(() => "?").join(",");
3791
+ try {
3792
+ const result = await client.execute({
3793
+ sql: `SELECT hyperedge_id, entity_id FROM hyperedge_nodes
3794
+ WHERE entity_id IN (${placeholders})`,
3795
+ args: entityIds
3796
+ });
3797
+ const hyperedgeEntities = /* @__PURE__ */ new Map();
3798
+ for (const row of result.rows) {
3799
+ const hid = String(row.hyperedge_id);
3800
+ const eid = String(row.entity_id);
3801
+ const set = hyperedgeEntities.get(hid) ?? /* @__PURE__ */ new Set();
3802
+ set.add(eid);
3803
+ hyperedgeEntities.set(hid, set);
3804
+ }
3805
+ const sharedHyperedgeIds = [];
3806
+ for (const [hid, eids] of hyperedgeEntities) {
3807
+ if (eids.size >= 2) {
3808
+ sharedHyperedgeIds.push(hid);
3809
+ }
3810
+ }
3811
+ if (sharedHyperedgeIds.length === 0) return;
3812
+ const hPlaceholders = sharedHyperedgeIds.map(() => "?").join(",");
3813
+ const allNodesResult = await client.execute({
3814
+ sql: `SELECT DISTINCT entity_id FROM hyperedge_nodes
3815
+ WHERE hyperedge_id IN (${hPlaceholders})`,
3816
+ args: sharedHyperedgeIds
3817
+ });
3818
+ const allEntityIds = allNodesResult.rows.map((r) => String(r.entity_id));
3819
+ if (allEntityIds.length === 0) return;
3820
+ const linked = await getLinkedMemories(allEntityIds, client);
3821
+ for (const [, entry] of linked) {
3822
+ const attenuation = spreadAttenuation(entry.count);
3823
+ const boost = ENTITY_BOOST_WEIGHT * attenuation;
3824
+ for (const memoryId of entry.memoryIds) {
3825
+ if (resultIds.has(memoryId)) {
3826
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + boost);
3827
+ }
3828
+ }
3829
+ }
3830
+ } catch {
3831
+ }
3832
+ }
3833
+ async function applyEntityBoost(results, query, client) {
3834
+ const emptyResult = { results, graphContext: /* @__PURE__ */ new Map() };
3835
+ if (ENTITY_BOOST_WEIGHT === 0 || results.length === 0) {
3836
+ return emptyResult;
3837
+ }
3838
+ console.time("entity-boost");
3839
+ const entities = await matchEntities(query, client);
3840
+ if (entities.length === 0) {
3841
+ console.timeEnd("entity-boost");
3842
+ return emptyResult;
3843
+ }
3844
+ const boostMap = /* @__PURE__ */ new Map();
3845
+ const resultIds = new Set(results.map((r) => r.id));
3846
+ const graphContextMap = /* @__PURE__ */ new Map();
3847
+ const directLinked = await getLinkedMemories(
3848
+ entities.map((e) => e.entityId),
3849
+ client
3152
3850
  );
3153
- return scored.map((s) => candidates[s.index]);
3851
+ for (const [, entry] of directLinked) {
3852
+ const attenuation = spreadAttenuation(entry.count);
3853
+ const boost = ENTITY_BOOST_WEIGHT * attenuation;
3854
+ for (const memoryId of entry.memoryIds) {
3855
+ if (resultIds.has(memoryId)) {
3856
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + boost);
3857
+ }
3858
+ }
3859
+ }
3860
+ await traverseAndScore(entities, client, boostMap, resultIds, graphContextMap);
3861
+ await applyHyperedgeBoost(entities, client, boostMap, resultIds);
3862
+ if (boostMap.size === 0) {
3863
+ console.timeEnd("entity-boost");
3864
+ return emptyResult;
3865
+ }
3866
+ const scored = results.map((r, i) => ({
3867
+ record: r,
3868
+ baseScore: 1 / (1 + i * 0.1),
3869
+ entityBoost: boostMap.get(r.id) ?? 0
3870
+ }));
3871
+ scored.sort(
3872
+ (a, b) => b.baseScore + b.entityBoost - (a.baseScore + a.entityBoost)
3873
+ );
3874
+ console.timeEnd("entity-boost");
3875
+ return {
3876
+ results: scored.map((s) => s.record),
3877
+ graphContext: graphContextMap
3878
+ };
3154
3879
  }
3155
- var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
3156
- var init_reranker = __esm({
3157
- "src/lib/reranker.ts"() {
3880
+ var ENTITY_BOOST_WEIGHT, MIN_WORD_LENGTH, MAX_ENTITY_MATCHES, GRAPH_TRAVERSAL_MAX_HOPS, TOP_ENTITIES_TO_TRAVERSE, DEFAULT_RELATIONSHIP_WEIGHTS, DEFAULT_REL_WEIGHT;
3881
+ var init_entity_boost = __esm({
3882
+ "src/lib/entity-boost.ts"() {
3158
3883
  "use strict";
3159
- init_config();
3160
- RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
3161
- IDLE_TIMEOUT_MS = 6e4;
3162
- _rerankerContext = null;
3163
- _rerankerModel = null;
3164
- _idleTimer = null;
3884
+ ENTITY_BOOST_WEIGHT = parseFloat(
3885
+ process.env.ENTITY_BOOST_WEIGHT ?? "0.5"
3886
+ );
3887
+ MIN_WORD_LENGTH = 3;
3888
+ MAX_ENTITY_MATCHES = 20;
3889
+ GRAPH_TRAVERSAL_MAX_HOPS = Math.min(
3890
+ parseInt(process.env.GRAPH_TRAVERSAL_MAX_HOPS ?? "2", 10),
3891
+ 3
3892
+ );
3893
+ TOP_ENTITIES_TO_TRAVERSE = 5;
3894
+ DEFAULT_RELATIONSHIP_WEIGHTS = {
3895
+ decided: 1,
3896
+ depends_on: 0.8,
3897
+ part_of: 0.7,
3898
+ related_to: 0.5,
3899
+ mentioned_in: 0.3
3900
+ };
3901
+ DEFAULT_REL_WEIGHT = 0.4;
3165
3902
  }
3166
3903
  });
3167
3904
 
@@ -3209,8 +3946,14 @@ async function hybridSearch(queryText, agentId, options) {
3209
3946
  `
3210
3947
  );
3211
3948
  }
3949
+ let rerankerAvailable = false;
3950
+ try {
3951
+ const { isRerankerAvailable: isRerankerAvailable2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
3952
+ rerankerAvailable = isRerankerAvailable2();
3953
+ } catch {
3954
+ }
3212
3955
  const broadFetchTopK = config.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
3213
- const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
3956
+ const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : rerankerAvailable ? Math.max(limit * 4, 60) : Math.max(limit * 3, 30);
3214
3957
  const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
3215
3958
  let queryVector = null;
3216
3959
  try {
@@ -3256,7 +3999,17 @@ async function hybridSearch(queryText, agentId, options) {
3256
3999
  if (lists.length === 0) return [];
3257
4000
  if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
3258
4001
  const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
3259
- const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4002
+ let merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4003
+ let graphContextMap = /* @__PURE__ */ new Map();
4004
+ if (merged.length > 0) {
4005
+ try {
4006
+ const { applyEntityBoost: applyEntityBoost2 } = await Promise.resolve().then(() => (init_entity_boost(), entity_boost_exports));
4007
+ const boosted = await applyEntityBoost2(merged, effectiveQuery, getClient());
4008
+ merged = boosted.results;
4009
+ graphContextMap = boosted.graphContext;
4010
+ } catch {
4011
+ }
4012
+ }
3260
4013
  const auto = config.scalingRoadmap?.rerankerAutoTrigger ?? {
3261
4014
  enabled: config.rerankerEnabled ?? true,
3262
4015
  broadQueryMinCardinality: 5e4,
@@ -3264,20 +4017,29 @@ async function hybridSearch(queryText, agentId, options) {
3264
4017
  returnTopK: 5
3265
4018
  };
3266
4019
  let rerankedAndBlended = null;
3267
- if (effectiveIsBroad && auto.enabled) {
4020
+ if (effectiveIsBroad && auto.enabled && rerankerAvailable) {
3268
4021
  const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
3269
4022
  if (cardinality2 > auto.broadQueryMinCardinality) {
3270
4023
  try {
3271
- const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
3272
- if (isRerankerAvailable2()) {
3273
- const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
3274
- if (reranked.length > 0) {
3275
- rerankedAndBlended = rrfMergeMulti(
3276
- [reranked],
3277
- auto.returnTopK,
3278
- RRF_K
3279
- );
3280
- }
4024
+ let rerankedRecords;
4025
+ if (graphContextMap.size > 0) {
4026
+ const { rerankWithContext: rerankWithContext2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4027
+ const candidates = merged.map((m) => ({
4028
+ text: m.raw_text,
4029
+ context: graphContextMap.get(m.id)
4030
+ }));
4031
+ const scored = await rerankWithContext2(effectiveQuery, candidates, auto.returnTopK);
4032
+ rerankedRecords = scored.map((s) => merged[s.index]);
4033
+ } else {
4034
+ const { rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4035
+ rerankedRecords = await rerank2(effectiveQuery, merged, auto.returnTopK);
4036
+ }
4037
+ if (rerankedRecords.length > 0) {
4038
+ rerankedAndBlended = rrfMergeMulti(
4039
+ [rerankedRecords],
4040
+ auto.returnTopK,
4041
+ RRF_K
4042
+ );
3281
4043
  }
3282
4044
  } catch {
3283
4045
  }
@@ -3297,7 +4059,7 @@ async function hybridSearch(queryText, agentId, options) {
3297
4059
  try {
3298
4060
  const client = getClient();
3299
4061
  void client.execute({
3300
- sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
4062
+ sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1 WHERE id IN (${placeholders})`,
3301
4063
  args: [now, ...ids]
3302
4064
  }).catch(() => {
3303
4065
  });