@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
@@ -373,6 +373,12 @@ function getClient() {
373
373
  if (!_resilientClient) {
374
374
  throw new Error("Database client not initialized. Call initDatabase() first.");
375
375
  }
376
+ if (process.env.EXE_IS_DAEMON === "1") {
377
+ return _resilientClient;
378
+ }
379
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
380
+ return _daemonClient;
381
+ }
376
382
  return _resilientClient;
377
383
  }
378
384
  function getRawClient() {
@@ -861,6 +867,12 @@ async function ensureSchema() {
861
867
  } catch {
862
868
  }
863
869
  }
870
+ try {
871
+ await client.execute(
872
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
873
+ );
874
+ } catch {
875
+ }
864
876
  await client.executeMultiple(`
865
877
  CREATE TABLE IF NOT EXISTS entities (
866
878
  id TEXT PRIMARY KEY,
@@ -913,7 +925,30 @@ async function ensureSchema() {
913
925
  entity_id TEXT NOT NULL,
914
926
  PRIMARY KEY (hyperedge_id, entity_id)
915
927
  );
928
+
929
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
930
+ name,
931
+ content=entities,
932
+ content_rowid=rowid
933
+ );
934
+
935
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
936
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
937
+ END;
938
+
939
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
940
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
941
+ END;
942
+
943
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
944
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
945
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
946
+ END;
916
947
  `);
948
+ try {
949
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
950
+ } catch {
951
+ }
917
952
  await client.executeMultiple(`
918
953
  CREATE TABLE IF NOT EXISTS entity_aliases (
919
954
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1094,6 +1129,33 @@ async function ensureSchema() {
1094
1129
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1095
1130
  ON conversations(channel_id);
1096
1131
  `);
1132
+ await client.executeMultiple(`
1133
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1134
+ session_uuid TEXT PRIMARY KEY,
1135
+ agent_id TEXT NOT NULL,
1136
+ session_name TEXT,
1137
+ task_id TEXT,
1138
+ project_name TEXT,
1139
+ started_at TEXT NOT NULL
1140
+ );
1141
+
1142
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1143
+ ON session_agent_map(agent_id);
1144
+ `);
1145
+ try {
1146
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1147
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1148
+ await client.execute({
1149
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1150
+ SELECT session_id, agent_id, '', MIN(timestamp)
1151
+ FROM memories
1152
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1153
+ GROUP BY session_id, agent_id`,
1154
+ args: []
1155
+ });
1156
+ }
1157
+ } catch {
1158
+ }
1097
1159
  try {
1098
1160
  await client.execute({
1099
1161
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1227,15 +1289,41 @@ async function ensureSchema() {
1227
1289
  });
1228
1290
  } catch {
1229
1291
  }
1292
+ for (const col of [
1293
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1294
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1295
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1296
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1297
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1298
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1299
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1300
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1301
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1302
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1303
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1304
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1305
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1306
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1307
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1308
+ ]) {
1309
+ try {
1310
+ await client.execute(col);
1311
+ } catch {
1312
+ }
1313
+ }
1230
1314
  }
1231
1315
  async function disposeDatabase() {
1316
+ if (_daemonClient) {
1317
+ _daemonClient.close();
1318
+ _daemonClient = null;
1319
+ }
1232
1320
  if (_client) {
1233
1321
  _client.close();
1234
1322
  _client = null;
1235
1323
  _resilientClient = null;
1236
1324
  }
1237
1325
  }
1238
- var _client, _resilientClient, initTurso, disposeTurso;
1326
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1239
1327
  var init_database = __esm({
1240
1328
  "src/lib/database.ts"() {
1241
1329
  "use strict";
@@ -1243,6 +1331,7 @@ var init_database = __esm({
1243
1331
  init_employees();
1244
1332
  _client = null;
1245
1333
  _resilientClient = null;
1334
+ _daemonClient = null;
1246
1335
  initTurso = initDatabase;
1247
1336
  disposeTurso = disposeDatabase;
1248
1337
  }
@@ -1279,12 +1368,20 @@ async function getMasterKey() {
1279
1368
  }
1280
1369
  const keyPath = getKeyPath();
1281
1370
  if (!existsSync3(keyPath)) {
1371
+ process.stderr.write(
1372
+ `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1373
+ `
1374
+ );
1282
1375
  return null;
1283
1376
  }
1284
1377
  try {
1285
1378
  const content = await readFile3(keyPath, "utf-8");
1286
1379
  return Buffer.from(content.trim(), "base64");
1287
- } catch {
1380
+ } catch (err) {
1381
+ process.stderr.write(
1382
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
1383
+ `
1384
+ );
1288
1385
  return null;
1289
1386
  }
1290
1387
  }
@@ -1795,6 +1892,7 @@ __export(store_exports, {
1795
1892
  vectorToBlob: () => vectorToBlob,
1796
1893
  writeMemory: () => writeMemory
1797
1894
  });
1895
+ import { createHash } from "crypto";
1798
1896
  function isBusyError2(err) {
1799
1897
  if (err instanceof Error) {
1800
1898
  const msg = err.message.toLowerCase();
@@ -1868,12 +1966,52 @@ function classifyTier(record) {
1868
1966
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1869
1967
  return 3;
1870
1968
  }
1969
+ function inferFilePaths(record) {
1970
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
1971
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
1972
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
1973
+ return match ? JSON.stringify([match[1]]) : null;
1974
+ }
1975
+ function inferCommitHash(record) {
1976
+ if (record.tool_name !== "Bash") return null;
1977
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
1978
+ return match ? match[1] : null;
1979
+ }
1980
+ function inferLanguageType(record) {
1981
+ const text = record.raw_text;
1982
+ if (!text || text.length < 10) return null;
1983
+ const trimmed = text.trimStart();
1984
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
1985
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
1986
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
1987
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
1988
+ return "mixed";
1989
+ }
1990
+ function inferDomain(record) {
1991
+ const proj = (record.project_name ?? "").toLowerCase();
1992
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
1993
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
1994
+ return null;
1995
+ }
1871
1996
  async function writeMemory(record) {
1872
1997
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1873
1998
  throw new Error(
1874
1999
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
1875
2000
  );
1876
2001
  }
2002
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
2003
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
2004
+ return;
2005
+ }
2006
+ try {
2007
+ const client = getClient();
2008
+ const existing = await client.execute({
2009
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
2010
+ args: [contentHash, record.agent_id]
2011
+ });
2012
+ if (existing.rows.length > 0) return;
2013
+ } catch {
2014
+ }
1877
2015
  const dbRow = {
1878
2016
  id: record.id,
1879
2017
  agent_id: record.agent_id,
@@ -1903,7 +2041,23 @@ async function writeMemory(record) {
1903
2041
  supersedes_id: record.supersedes_id ?? null,
1904
2042
  draft: record.draft ? 1 : 0,
1905
2043
  memory_type: record.memory_type ?? "raw",
1906
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
2044
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
2045
+ content_hash: contentHash,
2046
+ intent: record.intent ?? null,
2047
+ outcome: record.outcome ?? null,
2048
+ domain: record.domain ?? inferDomain(record),
2049
+ referenced_entities: record.referenced_entities ?? null,
2050
+ retrieval_count: record.retrieval_count ?? 0,
2051
+ chain_position: record.chain_position ?? null,
2052
+ review_status: record.review_status ?? null,
2053
+ context_window_pct: record.context_window_pct ?? null,
2054
+ file_paths: record.file_paths ?? inferFilePaths(record),
2055
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
2056
+ duration_ms: record.duration_ms ?? null,
2057
+ token_cost: record.token_cost ?? null,
2058
+ audience: record.audience ?? null,
2059
+ language_type: record.language_type ?? inferLanguageType(record),
2060
+ parent_memory_id: record.parent_memory_id ?? null
1907
2061
  };
1908
2062
  _pendingRecords.push(dbRow);
1909
2063
  orgBus.emit({
@@ -1961,80 +2115,85 @@ async function flushBatch() {
1961
2115
  const draft = row.draft ? 1 : 0;
1962
2116
  const memoryType = row.memory_type ?? "raw";
1963
2117
  const trajectory = row.trajectory ?? null;
1964
- return {
1965
- sql: hasVector ? `INSERT OR IGNORE INTO memories
1966
- (id, agent_id, agent_role, session_id, timestamp,
1967
- tool_name, project_name,
1968
- has_error, raw_text, vector, version, task_id, importance, status,
1969
- confidence, last_accessed,
1970
- workspace_id, document_id, user_id, char_offset, page_number,
1971
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1972
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1973
- (id, agent_id, agent_role, session_id, timestamp,
2118
+ const contentHash = row.content_hash ?? null;
2119
+ const intent = row.intent ?? null;
2120
+ const outcome = row.outcome ?? null;
2121
+ const domain = row.domain ?? null;
2122
+ const referencedEntities = row.referenced_entities ?? null;
2123
+ const retrievalCount = row.retrieval_count ?? 0;
2124
+ const chainPosition = row.chain_position ?? null;
2125
+ const reviewStatus = row.review_status ?? null;
2126
+ const contextWindowPct = row.context_window_pct ?? null;
2127
+ const filePaths = row.file_paths ?? null;
2128
+ const commitHash = row.commit_hash ?? null;
2129
+ const durationMs = row.duration_ms ?? null;
2130
+ const tokenCost = row.token_cost ?? null;
2131
+ const audience = row.audience ?? null;
2132
+ const languageType = row.language_type ?? null;
2133
+ const parentMemoryId = row.parent_memory_id ?? null;
2134
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
1974
2135
  tool_name, project_name,
1975
2136
  has_error, raw_text, vector, version, task_id, importance, status,
1976
2137
  confidence, last_accessed,
1977
2138
  workspace_id, document_id, user_id, char_offset, page_number,
1978
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1979
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1980
- args: hasVector ? [
1981
- row.id,
1982
- row.agent_id,
1983
- row.agent_role,
1984
- row.session_id,
1985
- row.timestamp,
1986
- row.tool_name,
1987
- row.project_name,
1988
- row.has_error,
1989
- row.raw_text,
1990
- vectorToBlob(row.vector),
1991
- row.version,
1992
- taskId,
1993
- importance,
1994
- status,
1995
- confidence,
1996
- lastAccessed,
1997
- workspaceId,
1998
- documentId,
1999
- userId,
2000
- charOffset,
2001
- pageNumber,
2002
- sourcePath,
2003
- sourceType,
2004
- tier,
2005
- supersedesId,
2006
- draft,
2007
- memoryType,
2008
- trajectory
2009
- ] : [
2010
- row.id,
2011
- row.agent_id,
2012
- row.agent_role,
2013
- row.session_id,
2014
- row.timestamp,
2015
- row.tool_name,
2016
- row.project_name,
2017
- row.has_error,
2018
- row.raw_text,
2019
- row.version,
2020
- taskId,
2021
- importance,
2022
- status,
2023
- confidence,
2024
- lastAccessed,
2025
- workspaceId,
2026
- documentId,
2027
- userId,
2028
- charOffset,
2029
- pageNumber,
2030
- sourcePath,
2031
- sourceType,
2032
- tier,
2033
- supersedesId,
2034
- draft,
2035
- memoryType,
2036
- trajectory
2037
- ]
2139
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
2140
+ intent, outcome, domain, referenced_entities, retrieval_count,
2141
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
2142
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
2143
+ const metaArgs = [
2144
+ intent,
2145
+ outcome,
2146
+ domain,
2147
+ referencedEntities,
2148
+ retrievalCount,
2149
+ chainPosition,
2150
+ reviewStatus,
2151
+ contextWindowPct,
2152
+ filePaths,
2153
+ commitHash,
2154
+ durationMs,
2155
+ tokenCost,
2156
+ audience,
2157
+ languageType,
2158
+ parentMemoryId
2159
+ ];
2160
+ const baseArgs = [
2161
+ row.id,
2162
+ row.agent_id,
2163
+ row.agent_role,
2164
+ row.session_id,
2165
+ row.timestamp,
2166
+ row.tool_name,
2167
+ row.project_name,
2168
+ row.has_error,
2169
+ row.raw_text
2170
+ ];
2171
+ const sharedArgs = [
2172
+ row.version,
2173
+ taskId,
2174
+ importance,
2175
+ status,
2176
+ confidence,
2177
+ lastAccessed,
2178
+ workspaceId,
2179
+ documentId,
2180
+ userId,
2181
+ charOffset,
2182
+ pageNumber,
2183
+ sourcePath,
2184
+ sourceType,
2185
+ tier,
2186
+ supersedesId,
2187
+ draft,
2188
+ memoryType,
2189
+ trajectory,
2190
+ contentHash
2191
+ ];
2192
+ return {
2193
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
2194
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
2195
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2196
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
2038
2197
  };
2039
2198
  };
2040
2199
  const globalClient = getClient();
@@ -2373,12 +2532,141 @@ var init_self_query_router = __esm({
2373
2532
  }
2374
2533
  });
2375
2534
 
2535
+ // src/lib/reranker.ts
2536
+ var reranker_exports = {};
2537
+ __export(reranker_exports, {
2538
+ disposeReranker: () => disposeReranker,
2539
+ getRerankerModelPath: () => getRerankerModelPath,
2540
+ isRerankerAvailable: () => isRerankerAvailable,
2541
+ rerank: () => rerank,
2542
+ rerankWithContext: () => rerankWithContext,
2543
+ rerankWithScores: () => rerankWithScores
2544
+ });
2545
+ import path5 from "path";
2546
+ import { existsSync as existsSync5 } from "fs";
2547
+ function resetIdleTimer() {
2548
+ if (_idleTimer) clearTimeout(_idleTimer);
2549
+ _idleTimer = setTimeout(() => {
2550
+ void disposeReranker();
2551
+ }, IDLE_TIMEOUT_MS);
2552
+ if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
2553
+ _idleTimer.unref();
2554
+ }
2555
+ }
2556
+ function isRerankerAvailable() {
2557
+ return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
2558
+ }
2559
+ function getRerankerModelPath() {
2560
+ return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2561
+ }
2562
+ async function ensureLoaded() {
2563
+ if (_rerankerContext) {
2564
+ resetIdleTimer();
2565
+ return;
2566
+ }
2567
+ const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2568
+ if (!existsSync5(modelPath)) {
2569
+ throw new Error(
2570
+ `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
2571
+ );
2572
+ }
2573
+ process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
2574
+ const { getLlama } = await import("node-llama-cpp");
2575
+ const llama = await getLlama();
2576
+ _rerankerModel = await llama.loadModel({ modelPath });
2577
+ _rerankerContext = await _rerankerModel.createEmbeddingContext();
2578
+ process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
2579
+ resetIdleTimer();
2580
+ }
2581
+ async function disposeReranker() {
2582
+ if (_idleTimer) {
2583
+ clearTimeout(_idleTimer);
2584
+ _idleTimer = null;
2585
+ }
2586
+ if (_rerankerContext) {
2587
+ try {
2588
+ await _rerankerContext.dispose();
2589
+ } catch {
2590
+ }
2591
+ _rerankerContext = null;
2592
+ }
2593
+ if (_rerankerModel) {
2594
+ try {
2595
+ await _rerankerModel.dispose();
2596
+ } catch {
2597
+ }
2598
+ _rerankerModel = null;
2599
+ }
2600
+ process.stderr.write("[reranker] Unloaded (idle timeout).\n");
2601
+ }
2602
+ async function rerankWithScores(query, texts, topK) {
2603
+ if (texts.length === 0) return [];
2604
+ await ensureLoaded();
2605
+ const ctx = _rerankerContext;
2606
+ const scored = [];
2607
+ for (let i = 0; i < texts.length; i++) {
2608
+ const text = texts[i] ?? "";
2609
+ try {
2610
+ const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
2611
+ const embedding = await ctx.getEmbeddingFor(input2);
2612
+ const score = embedding.vector[0] ?? 0;
2613
+ scored.push({ text, score, index: i });
2614
+ } catch {
2615
+ scored.push({ text, score: -1, index: i });
2616
+ }
2617
+ }
2618
+ scored.sort((a, b) => b.score - a.score);
2619
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
2620
+ }
2621
+ async function rerank(query, candidates, topK = 5) {
2622
+ if (candidates.length === 0) return [];
2623
+ if (candidates.length <= topK) return candidates;
2624
+ const scored = await rerankWithScores(
2625
+ query,
2626
+ candidates.map((c) => c.raw_text),
2627
+ topK
2628
+ );
2629
+ return scored.map((s) => candidates[s.index]);
2630
+ }
2631
+ async function rerankWithContext(query, candidates, topK) {
2632
+ if (candidates.length === 0) return [];
2633
+ await ensureLoaded();
2634
+ const ctx = _rerankerContext;
2635
+ const scored = [];
2636
+ for (let i = 0; i < candidates.length; i++) {
2637
+ const candidate = candidates[i];
2638
+ try {
2639
+ const docText = candidate.context ? `[${candidate.context}] ${candidate.text.slice(0, 460)}` : candidate.text.slice(0, 512);
2640
+ const input2 = `query: ${query} document: ${docText}`;
2641
+ const embedding = await ctx.getEmbeddingFor(input2);
2642
+ const score = embedding.vector[0] ?? 0;
2643
+ scored.push({ text: candidate.text, score, index: i });
2644
+ } catch {
2645
+ scored.push({ text: candidate.text, score: -1, index: i });
2646
+ }
2647
+ }
2648
+ scored.sort((a, b) => b.score - a.score);
2649
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
2650
+ }
2651
+ var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
2652
+ var init_reranker = __esm({
2653
+ "src/lib/reranker.ts"() {
2654
+ "use strict";
2655
+ init_config();
2656
+ RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
2657
+ IDLE_TIMEOUT_MS = 6e4;
2658
+ _rerankerContext = null;
2659
+ _rerankerModel = null;
2660
+ _idleTimer = null;
2661
+ }
2662
+ });
2663
+
2376
2664
  // src/lib/exe-daemon-client.ts
2377
2665
  import net from "net";
2378
2666
  import { spawn } from "child_process";
2379
2667
  import { randomUUID as randomUUID2 } from "crypto";
2380
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2381
- import path5 from "path";
2668
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2669
+ import path6 from "path";
2382
2670
  import { fileURLToPath } from "url";
2383
2671
  function handleData(chunk) {
2384
2672
  _buffer += chunk.toString();
@@ -2393,10 +2681,12 @@ function handleData(chunk) {
2393
2681
  if (!line) continue;
2394
2682
  try {
2395
2683
  const response = JSON.parse(line);
2396
- const entry = _pending.get(response.id);
2684
+ const id = response.id;
2685
+ if (!id) continue;
2686
+ const entry = _pending.get(id);
2397
2687
  if (entry) {
2398
2688
  clearTimeout(entry.timer);
2399
- _pending.delete(response.id);
2689
+ _pending.delete(id);
2400
2690
  entry.resolve(response);
2401
2691
  }
2402
2692
  } catch {
@@ -2404,7 +2694,7 @@ function handleData(chunk) {
2404
2694
  }
2405
2695
  }
2406
2696
  function cleanupStaleFiles() {
2407
- if (existsSync5(PID_PATH)) {
2697
+ if (existsSync6(PID_PATH)) {
2408
2698
  try {
2409
2699
  const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2410
2700
  if (pid > 0) {
@@ -2427,11 +2717,11 @@ function cleanupStaleFiles() {
2427
2717
  }
2428
2718
  }
2429
2719
  function findPackageRoot() {
2430
- let dir = path5.dirname(fileURLToPath(import.meta.url));
2431
- const { root } = path5.parse(dir);
2720
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2721
+ const { root } = path6.parse(dir);
2432
2722
  while (dir !== root) {
2433
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
2434
- dir = path5.dirname(dir);
2723
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2724
+ dir = path6.dirname(dir);
2435
2725
  }
2436
2726
  return null;
2437
2727
  }
@@ -2441,8 +2731,8 @@ function spawnDaemon() {
2441
2731
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2442
2732
  return;
2443
2733
  }
2444
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2445
- if (!existsSync5(daemonPath)) {
2734
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2735
+ if (!existsSync6(daemonPath)) {
2446
2736
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2447
2737
  `);
2448
2738
  return;
@@ -2450,7 +2740,7 @@ function spawnDaemon() {
2450
2740
  const resolvedPath = daemonPath;
2451
2741
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2452
2742
  `);
2453
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2743
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
2454
2744
  let stderrFd = "ignore";
2455
2745
  try {
2456
2746
  stderrFd = openSync(logPath, "a");
@@ -2567,6 +2857,9 @@ async function connectEmbedDaemon() {
2567
2857
  return false;
2568
2858
  }
2569
2859
  function sendRequest(texts, priority) {
2860
+ return sendDaemonRequest({ texts, priority });
2861
+ }
2862
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2570
2863
  return new Promise((resolve) => {
2571
2864
  if (!_socket || !_connected) {
2572
2865
  resolve({ error: "Not connected" });
@@ -2576,10 +2869,10 @@ function sendRequest(texts, priority) {
2576
2869
  const timer = setTimeout(() => {
2577
2870
  _pending.delete(id);
2578
2871
  resolve({ error: "Request timeout" });
2579
- }, REQUEST_TIMEOUT_MS);
2872
+ }, timeoutMs);
2580
2873
  _pending.set(id, { resolve, timer });
2581
2874
  try {
2582
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
2875
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2583
2876
  } catch {
2584
2877
  clearTimeout(timer);
2585
2878
  _pending.delete(id);
@@ -2589,34 +2882,15 @@ function sendRequest(texts, priority) {
2589
2882
  }
2590
2883
  async function pingDaemon() {
2591
2884
  if (!_socket || !_connected) return null;
2592
- return new Promise((resolve) => {
2593
- const id = randomUUID2();
2594
- const timer = setTimeout(() => {
2595
- _pending.delete(id);
2596
- resolve(null);
2597
- }, 5e3);
2598
- _pending.set(id, {
2599
- resolve: (data) => {
2600
- if (data.health) {
2601
- resolve(data.health);
2602
- } else {
2603
- resolve(null);
2604
- }
2605
- },
2606
- timer
2607
- });
2608
- try {
2609
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
2610
- } catch {
2611
- clearTimeout(timer);
2612
- _pending.delete(id);
2613
- resolve(null);
2614
- }
2615
- });
2885
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
2886
+ if (response.health) {
2887
+ return response.health;
2888
+ }
2889
+ return null;
2616
2890
  }
2617
2891
  function killAndRespawnDaemon() {
2618
2892
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2619
- if (existsSync5(PID_PATH)) {
2893
+ if (existsSync6(PID_PATH)) {
2620
2894
  try {
2621
2895
  const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2622
2896
  if (pid > 0) {
@@ -2702,9 +2976,9 @@ var init_exe_daemon_client = __esm({
2702
2976
  "src/lib/exe-daemon-client.ts"() {
2703
2977
  "use strict";
2704
2978
  init_config();
2705
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
2706
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
2707
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2979
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
2980
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
2981
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2708
2982
  SPAWN_LOCK_STALE_MS = 3e4;
2709
2983
  CONNECT_TIMEOUT_MS = 15e3;
2710
2984
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2793,7 +3067,7 @@ __export(project_name_exports, {
2793
3067
  getProjectName: () => getProjectName
2794
3068
  });
2795
3069
  import { execSync as execSync2 } from "child_process";
2796
- import path6 from "path";
3070
+ import path7 from "path";
2797
3071
  function getProjectName(cwd) {
2798
3072
  const dir = cwd ?? process.cwd();
2799
3073
  if (_cached && _cachedCwd === dir) return _cached;
@@ -2806,7 +3080,7 @@ function getProjectName(cwd) {
2806
3080
  timeout: 2e3,
2807
3081
  stdio: ["pipe", "pipe", "pipe"]
2808
3082
  }).trim();
2809
- repoRoot = path6.dirname(gitCommonDir);
3083
+ repoRoot = path7.dirname(gitCommonDir);
2810
3084
  } catch {
2811
3085
  repoRoot = execSync2("git rev-parse --show-toplevel", {
2812
3086
  cwd: dir,
@@ -2815,11 +3089,11 @@ function getProjectName(cwd) {
2815
3089
  stdio: ["pipe", "pipe", "pipe"]
2816
3090
  }).trim();
2817
3091
  }
2818
- _cached = path6.basename(repoRoot);
3092
+ _cached = path7.basename(repoRoot);
2819
3093
  _cachedCwd = dir;
2820
3094
  return _cached;
2821
3095
  } catch {
2822
- _cached = path6.basename(dir);
3096
+ _cached = path7.basename(dir);
2823
3097
  _cachedCwd = dir;
2824
3098
  return _cached;
2825
3099
  }
@@ -2843,8 +3117,8 @@ __export(file_grep_exports, {
2843
3117
  grepProjectFiles: () => grepProjectFiles
2844
3118
  });
2845
3119
  import { execSync as execSync3 } from "child_process";
2846
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync6 } from "fs";
2847
- import path7 from "path";
3120
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3121
+ import path8 from "path";
2848
3122
  import crypto2 from "crypto";
2849
3123
  function hasRipgrep() {
2850
3124
  if (_hasRg === null) {
@@ -2884,7 +3158,7 @@ async function grepProjectFiles(query, projectRoot, options) {
2884
3158
  session_id: "file-grep",
2885
3159
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2886
3160
  tool_name: "file_grep",
2887
- project_name: path7.basename(projectRoot),
3161
+ project_name: path8.basename(projectRoot),
2888
3162
  has_error: false,
2889
3163
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
2890
3164
  vector: null,
@@ -2958,7 +3232,7 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
2958
3232
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
2959
3233
  const hits = [];
2960
3234
  for (const filePath of files.slice(0, MAX_FILES)) {
2961
- const absPath = path7.join(projectRoot, filePath);
3235
+ const absPath = path8.join(projectRoot, filePath);
2962
3236
  try {
2963
3237
  const stat = statSync2(absPath);
2964
3238
  if (stat.size > MAX_FILE_SIZE) continue;
@@ -2985,15 +3259,15 @@ function collectFiles(root, patterns) {
2985
3259
  const files = [];
2986
3260
  function walk(dir, relative) {
2987
3261
  if (files.length >= MAX_FILES) return;
2988
- const basename = path7.basename(dir);
3262
+ const basename = path8.basename(dir);
2989
3263
  if (EXCLUDE_DIRS.includes(basename)) return;
2990
3264
  try {
2991
3265
  const entries = readdirSync2(dir, { withFileTypes: true });
2992
3266
  for (const entry of entries) {
2993
3267
  if (files.length >= MAX_FILES) return;
2994
- const rel = path7.join(relative, entry.name);
3268
+ const rel = path8.join(relative, entry.name);
2995
3269
  if (entry.isDirectory()) {
2996
- walk(path7.join(dir, entry.name), rel);
3270
+ walk(path8.join(dir, entry.name), rel);
2997
3271
  } else if (entry.isFile()) {
2998
3272
  for (const pat of patterns) {
2999
3273
  if (matchGlob(rel, pat)) {
@@ -3025,7 +3299,7 @@ function matchGlob(filePath, pattern) {
3025
3299
  if (slashIdx !== -1) {
3026
3300
  const dir = pattern.slice(0, slashIdx);
3027
3301
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3028
- const fileDir = path7.dirname(filePath);
3302
+ const fileDir = path8.dirname(filePath);
3029
3303
  return fileDir === dir && filePath.endsWith(ext2);
3030
3304
  }
3031
3305
  const ext = pattern.replace("*", "");
@@ -3033,8 +3307,8 @@ function matchGlob(filePath, pattern) {
3033
3307
  }
3034
3308
  function buildSnippet(hit, projectRoot) {
3035
3309
  try {
3036
- const absPath = path7.join(projectRoot, hit.filePath);
3037
- if (!existsSync6(absPath)) return hit.matchLine;
3310
+ const absPath = path8.join(projectRoot, hit.filePath);
3311
+ if (!existsSync7(absPath)) return hit.matchLine;
3038
3312
  const lines = readFileSync4(absPath, "utf8").split("\n");
3039
3313
  const start = Math.max(0, hit.lineNumber - 3);
3040
3314
  const end = Math.min(lines.length, hit.lineNumber + 2);
@@ -3060,111 +3334,574 @@ var init_file_grep = __esm({
3060
3334
  }
3061
3335
  });
3062
3336
 
3063
- // src/lib/reranker.ts
3064
- var reranker_exports = {};
3065
- __export(reranker_exports, {
3066
- disposeReranker: () => disposeReranker,
3067
- getRerankerModelPath: () => getRerankerModelPath,
3068
- isRerankerAvailable: () => isRerankerAvailable,
3069
- rerank: () => rerank,
3070
- rerankWithScores: () => rerankWithScores
3337
+ // src/lib/graph-query.ts
3338
+ var graph_query_exports = {};
3339
+ __export(graph_query_exports, {
3340
+ getConversationPartners: () => getConversationPartners,
3341
+ getEntityByName: () => getEntityByName,
3342
+ getEntityNeighbors: () => getEntityNeighbors,
3343
+ getEntityTimeline: () => getEntityTimeline,
3344
+ getGraphStats: () => getGraphStats,
3345
+ getHotEntities: () => getHotEntities,
3346
+ getRelationshipFrequency: () => getRelationshipFrequency,
3347
+ getRelationships: () => getRelationships,
3348
+ searchEntities: () => searchEntities,
3349
+ traverseChain: () => traverseChain
3071
3350
  });
3072
- import path8 from "path";
3073
- import { existsSync as existsSync7 } from "fs";
3074
- function resetIdleTimer() {
3075
- if (_idleTimer) clearTimeout(_idleTimer);
3076
- _idleTimer = setTimeout(() => {
3077
- void disposeReranker();
3078
- }, IDLE_TIMEOUT_MS);
3079
- if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
3080
- _idleTimer.unref();
3351
+ async function getEntityByName(client, name, type) {
3352
+ const sql = type ? `SELECT * FROM entities WHERE LOWER(name) = LOWER(?) AND type = ? LIMIT 1` : `SELECT * FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1`;
3353
+ const args = type ? [name, type] : [name];
3354
+ const result = await client.execute({ sql, args });
3355
+ if (result.rows.length === 0) return null;
3356
+ const row = result.rows[0];
3357
+ return {
3358
+ id: String(row.id),
3359
+ name: String(row.name),
3360
+ type: String(row.type),
3361
+ firstSeen: String(row.first_seen),
3362
+ lastSeen: String(row.last_seen),
3363
+ properties: JSON.parse(String(row.properties ?? "{}"))
3364
+ };
3365
+ }
3366
+ async function searchEntities(client, query, limit = 10) {
3367
+ const result = await client.execute({
3368
+ sql: `SELECT * FROM entities WHERE LOWER(name) LIKE ? ORDER BY last_seen DESC LIMIT ?`,
3369
+ args: [`%${query.toLowerCase()}%`, limit]
3370
+ });
3371
+ return result.rows.map((row) => ({
3372
+ id: String(row.id),
3373
+ name: String(row.name),
3374
+ type: String(row.type),
3375
+ firstSeen: String(row.first_seen),
3376
+ lastSeen: String(row.last_seen),
3377
+ properties: JSON.parse(String(row.properties ?? "{}"))
3378
+ }));
3379
+ }
3380
+ async function getRelationships(client, entityId, options) {
3381
+ const direction = options?.direction ?? "both";
3382
+ let sql;
3383
+ const args = [];
3384
+ if (direction === "outgoing") {
3385
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3386
+ FROM relationships r
3387
+ JOIN entities s ON r.source_entity_id = s.id
3388
+ JOIN entities t ON r.target_entity_id = t.id
3389
+ WHERE r.source_entity_id = ?`;
3390
+ args.push(entityId);
3391
+ } else if (direction === "incoming") {
3392
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3393
+ FROM relationships r
3394
+ JOIN entities s ON r.source_entity_id = s.id
3395
+ JOIN entities t ON r.target_entity_id = t.id
3396
+ WHERE r.target_entity_id = ?`;
3397
+ args.push(entityId);
3398
+ } else {
3399
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3400
+ FROM relationships r
3401
+ JOIN entities s ON r.source_entity_id = s.id
3402
+ JOIN entities t ON r.target_entity_id = t.id
3403
+ WHERE r.source_entity_id = ? OR r.target_entity_id = ?`;
3404
+ args.push(entityId, entityId);
3405
+ }
3406
+ if (options?.type) {
3407
+ sql += ` AND r.type = ?`;
3408
+ args.push(options.type);
3409
+ }
3410
+ sql += ` ORDER BY r.weight DESC, r.timestamp DESC`;
3411
+ const result = await client.execute({ sql, args });
3412
+ return result.rows.map((row) => ({
3413
+ id: String(row.id),
3414
+ sourceEntityId: String(row.source_entity_id),
3415
+ targetEntityId: String(row.target_entity_id),
3416
+ type: String(row.type),
3417
+ weight: Number(row.weight),
3418
+ timestamp: String(row.timestamp),
3419
+ properties: JSON.parse(String(row.properties ?? "{}")),
3420
+ confidence: Number(row.confidence ?? 1),
3421
+ confidenceLabel: String(row.confidence_label ?? "extracted"),
3422
+ sourceName: String(row.source_name),
3423
+ targetName: String(row.target_name)
3424
+ }));
3425
+ }
3426
+ async function traverseChain(client, startEntityId, relationshipType, maxDepth = 3) {
3427
+ const result = await client.execute({
3428
+ sql: `WITH RECURSIVE chain(entity_id, depth, rel_type) AS (
3429
+ SELECT ?, 0, ''
3430
+ UNION ALL
3431
+ SELECT r.target_entity_id, c.depth + 1, r.type
3432
+ FROM chain c
3433
+ JOIN relationships r ON r.source_entity_id = c.entity_id
3434
+ WHERE r.type = ? AND c.depth < ?
3435
+ )
3436
+ SELECT DISTINCT e.*, chain.depth, chain.rel_type
3437
+ FROM chain
3438
+ JOIN entities e ON e.id = chain.entity_id
3439
+ ORDER BY chain.depth ASC`,
3440
+ args: [startEntityId, relationshipType, maxDepth]
3441
+ });
3442
+ return result.rows.map((row) => ({
3443
+ entity: {
3444
+ id: String(row.id),
3445
+ name: String(row.name),
3446
+ type: String(row.type),
3447
+ firstSeen: String(row.first_seen),
3448
+ lastSeen: String(row.last_seen),
3449
+ properties: JSON.parse(String(row.properties ?? "{}"))
3450
+ },
3451
+ depth: Number(row.depth),
3452
+ relationship: String(row.rel_type)
3453
+ }));
3454
+ }
3455
+ async function getEntityNeighbors(client, entityId, maxHops = 2) {
3456
+ const result = await client.execute({
3457
+ sql: `WITH RECURSIVE neighborhood(entity_id, depth, rel_type, rel_confidence) AS (
3458
+ SELECT ?, 0, '', 1.0
3459
+ UNION ALL
3460
+ SELECT CASE
3461
+ WHEN r.source_entity_id = n.entity_id THEN r.target_entity_id
3462
+ ELSE r.source_entity_id
3463
+ END, n.depth + 1, r.type, COALESCE(r.confidence, 1.0)
3464
+ FROM neighborhood n
3465
+ JOIN relationships r ON r.source_entity_id = n.entity_id
3466
+ OR r.target_entity_id = n.entity_id
3467
+ WHERE n.depth < ?
3468
+ )
3469
+ SELECT DISTINCT e.*, MIN(neighborhood.depth) as depth,
3470
+ neighborhood.rel_type, neighborhood.rel_confidence
3471
+ FROM neighborhood
3472
+ JOIN entities e ON e.id = neighborhood.entity_id
3473
+ GROUP BY e.id
3474
+ ORDER BY depth ASC, e.last_seen DESC
3475
+ LIMIT 50`,
3476
+ args: [entityId, maxHops]
3477
+ });
3478
+ return result.rows.map((row) => ({
3479
+ entity: {
3480
+ id: String(row.id),
3481
+ name: String(row.name),
3482
+ type: String(row.type),
3483
+ firstSeen: String(row.first_seen),
3484
+ lastSeen: String(row.last_seen),
3485
+ properties: JSON.parse(String(row.properties ?? "{}"))
3486
+ },
3487
+ depth: Number(row.depth),
3488
+ relType: String(row.rel_type ?? ""),
3489
+ relConfidence: Number(row.rel_confidence ?? 1)
3490
+ }));
3491
+ }
3492
+ async function getGraphStats(client) {
3493
+ const entityCount = await client.execute("SELECT COUNT(*) as cnt FROM entities");
3494
+ const relCount = await client.execute("SELECT COUNT(*) as cnt FROM relationships");
3495
+ const typeResult = await client.execute(
3496
+ "SELECT type, COUNT(*) as cnt FROM entities GROUP BY type ORDER BY cnt DESC"
3497
+ );
3498
+ const types = {};
3499
+ for (const row of typeResult.rows) {
3500
+ types[String(row.type)] = Number(row.cnt);
3081
3501
  }
3502
+ return {
3503
+ entities: Number(entityCount.rows[0]?.cnt ?? 0),
3504
+ relationships: Number(relCount.rows[0]?.cnt ?? 0),
3505
+ types
3506
+ };
3082
3507
  }
3083
- function isRerankerAvailable() {
3084
- return existsSync7(path8.join(MODELS_DIR, RERANKER_MODEL_FILE));
3508
+ async function getEntityTimeline(client, entityId, since) {
3509
+ const args = [entityId, entityId];
3510
+ let sinceClause = "";
3511
+ if (since) {
3512
+ sinceClause = " AND r.timestamp >= ?";
3513
+ args.push(since.toISOString());
3514
+ }
3515
+ const result = await client.execute({
3516
+ sql: `SELECT r.*, s.name as source_name, t.name as target_name
3517
+ FROM relationships r
3518
+ JOIN entities s ON r.source_entity_id = s.id
3519
+ JOIN entities t ON r.target_entity_id = t.id
3520
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${sinceClause}
3521
+ ORDER BY r.timestamp DESC`,
3522
+ args
3523
+ });
3524
+ return result.rows.map((row) => ({
3525
+ relationship: {
3526
+ id: String(row.id),
3527
+ sourceEntityId: String(row.source_entity_id),
3528
+ targetEntityId: String(row.target_entity_id),
3529
+ type: String(row.type),
3530
+ weight: Number(row.weight),
3531
+ timestamp: String(row.timestamp),
3532
+ properties: JSON.parse(String(row.properties ?? "{}")),
3533
+ sourceName: String(row.source_name),
3534
+ targetName: String(row.target_name)
3535
+ },
3536
+ timestamp: String(row.timestamp)
3537
+ }));
3085
3538
  }
3086
- function getRerankerModelPath() {
3087
- return path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3539
+ async function getRelationshipFrequency(client, entityId, options) {
3540
+ const granularity = options?.granularity ?? "day";
3541
+ const args = [entityId, entityId];
3542
+ let typeClause = "";
3543
+ if (options?.type) {
3544
+ typeClause = " AND r.type = ?";
3545
+ args.push(options.type);
3546
+ }
3547
+ let bucketExpr;
3548
+ switch (granularity) {
3549
+ case "week":
3550
+ bucketExpr = `strftime('%Y', r.timestamp) || '-W' || strftime('%W', r.timestamp)`;
3551
+ break;
3552
+ case "month":
3553
+ bucketExpr = `strftime('%Y-%m', r.timestamp)`;
3554
+ break;
3555
+ default:
3556
+ bucketExpr = `strftime('%Y-%m-%d', r.timestamp)`;
3557
+ }
3558
+ const result = await client.execute({
3559
+ sql: `SELECT ${bucketExpr} as bucket, COUNT(*) as cnt, r.type as rel_type
3560
+ FROM relationships r
3561
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${typeClause}
3562
+ GROUP BY bucket${options?.type ? "" : ", r.type"}
3563
+ ORDER BY bucket DESC`,
3564
+ args
3565
+ });
3566
+ return result.rows.map((row) => ({
3567
+ bucket: String(row.bucket),
3568
+ count: Number(row.cnt),
3569
+ relationshipType: options?.type ? options.type : String(row.rel_type)
3570
+ }));
3088
3571
  }
3089
- async function ensureLoaded() {
3090
- if (_rerankerContext) {
3091
- resetIdleTimer();
3092
- return;
3572
+ async function getConversationPartners(client, contactEntityId, since) {
3573
+ const args = [contactEntityId, contactEntityId];
3574
+ let sinceClause = "";
3575
+ if (since) {
3576
+ sinceClause = " AND r.timestamp >= ?";
3577
+ args.push(since.toISOString());
3093
3578
  }
3094
- const modelPath = path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3095
- if (!existsSync7(modelPath)) {
3096
- throw new Error(
3097
- `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3098
- );
3579
+ const result = await client.execute({
3580
+ sql: `SELECT
3581
+ CASE
3582
+ WHEN r.source_entity_id = ? THEN r.target_entity_id
3583
+ ELSE r.source_entity_id
3584
+ END as partner_id,
3585
+ COUNT(*) as interaction_count,
3586
+ MAX(r.timestamp) as last_interaction
3587
+ FROM relationships r
3588
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${sinceClause}
3589
+ GROUP BY partner_id
3590
+ ORDER BY interaction_count DESC`,
3591
+ args: [contactEntityId, ...args]
3592
+ });
3593
+ const partners = [];
3594
+ for (const row of result.rows) {
3595
+ const partnerId = String(row.partner_id);
3596
+ const entityResult = await client.execute({
3597
+ sql: "SELECT * FROM entities WHERE id = ?",
3598
+ args: [partnerId]
3599
+ });
3600
+ if (entityResult.rows.length === 0) continue;
3601
+ const e = entityResult.rows[0];
3602
+ partners.push({
3603
+ agentEntity: {
3604
+ id: String(e.id),
3605
+ name: String(e.name),
3606
+ type: String(e.type),
3607
+ firstSeen: String(e.first_seen),
3608
+ lastSeen: String(e.last_seen),
3609
+ properties: JSON.parse(String(e.properties ?? "{}"))
3610
+ },
3611
+ interactionCount: Number(row.interaction_count),
3612
+ lastInteraction: String(row.last_interaction)
3613
+ });
3099
3614
  }
3100
- process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
3101
- const { getLlama } = await import("node-llama-cpp");
3102
- const llama = await getLlama();
3103
- _rerankerModel = await llama.loadModel({ modelPath });
3104
- _rerankerContext = await _rerankerModel.createEmbeddingContext();
3105
- process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
3106
- resetIdleTimer();
3615
+ return partners;
3107
3616
  }
3108
- async function disposeReranker() {
3109
- if (_idleTimer) {
3110
- clearTimeout(_idleTimer);
3111
- _idleTimer = null;
3617
+ async function getHotEntities(client, since, limit = 10) {
3618
+ const sinceISO = since.toISOString();
3619
+ const result = await client.execute({
3620
+ sql: `SELECT entity_id, COUNT(*) as rel_count
3621
+ FROM (
3622
+ SELECT source_entity_id as entity_id FROM relationships WHERE timestamp >= ?
3623
+ UNION ALL
3624
+ SELECT target_entity_id as entity_id FROM relationships WHERE timestamp >= ?
3625
+ )
3626
+ GROUP BY entity_id
3627
+ ORDER BY rel_count DESC
3628
+ LIMIT ?`,
3629
+ args: [sinceISO, sinceISO, limit]
3630
+ });
3631
+ const hotEntities = [];
3632
+ for (const row of result.rows) {
3633
+ const eid = String(row.entity_id);
3634
+ const entityResult = await client.execute({
3635
+ sql: "SELECT * FROM entities WHERE id = ?",
3636
+ args: [eid]
3637
+ });
3638
+ if (entityResult.rows.length === 0) continue;
3639
+ const e = entityResult.rows[0];
3640
+ hotEntities.push({
3641
+ entity: {
3642
+ id: String(e.id),
3643
+ name: String(e.name),
3644
+ type: String(e.type),
3645
+ firstSeen: String(e.first_seen),
3646
+ lastSeen: String(e.last_seen),
3647
+ properties: JSON.parse(String(e.properties ?? "{}"))
3648
+ },
3649
+ newRelationships: Number(row.rel_count)
3650
+ });
3112
3651
  }
3113
- if (_rerankerContext) {
3114
- try {
3115
- await _rerankerContext.dispose();
3116
- } catch {
3652
+ return hotEntities;
3653
+ }
3654
+ var init_graph_query = __esm({
3655
+ "src/lib/graph-query.ts"() {
3656
+ "use strict";
3657
+ }
3658
+ });
3659
+
3660
+ // src/lib/entity-boost.ts
3661
+ var entity_boost_exports = {};
3662
+ __export(entity_boost_exports, {
3663
+ applyEntityBoost: () => applyEntityBoost
3664
+ });
3665
+ function getRelTypeWeights() {
3666
+ const override = process.env.RELATIONSHIP_TYPE_WEIGHTS;
3667
+ if (!override) return DEFAULT_RELATIONSHIP_WEIGHTS;
3668
+ try {
3669
+ return { ...DEFAULT_RELATIONSHIP_WEIGHTS, ...JSON.parse(override) };
3670
+ } catch {
3671
+ return DEFAULT_RELATIONSHIP_WEIGHTS;
3672
+ }
3673
+ }
3674
+ async function matchEntities(query, client) {
3675
+ 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);
3676
+ if (words.length === 0) return [];
3677
+ try {
3678
+ const matchExpr = words.map((w) => `${w}*`).join(" OR ");
3679
+ const result = await client.execute({
3680
+ sql: `SELECT e.id, e.name FROM entities e
3681
+ JOIN entities_fts fts ON e.rowid = fts.rowid
3682
+ WHERE entities_fts MATCH ?
3683
+ ORDER BY rank
3684
+ LIMIT ?`,
3685
+ args: [matchExpr, MAX_ENTITY_MATCHES]
3686
+ });
3687
+ if (result.rows.length > 0) {
3688
+ return result.rows.map((row) => ({
3689
+ entityId: String(row.id),
3690
+ name: String(row.name)
3691
+ }));
3117
3692
  }
3118
- _rerankerContext = null;
3693
+ } catch {
3119
3694
  }
3120
- if (_rerankerModel) {
3121
- try {
3122
- await _rerankerModel.dispose();
3123
- } catch {
3695
+ const conditions = words.map(() => `LOWER(name) LIKE ?`);
3696
+ const args = words.map((w) => `%${w}%`);
3697
+ try {
3698
+ const result = await client.execute({
3699
+ sql: `SELECT id, name FROM entities
3700
+ WHERE ${conditions.join(" OR ")}
3701
+ LIMIT ?`,
3702
+ args: [...args, MAX_ENTITY_MATCHES]
3703
+ });
3704
+ return result.rows.map((row) => ({
3705
+ entityId: String(row.id),
3706
+ name: String(row.name)
3707
+ }));
3708
+ } catch {
3709
+ return [];
3710
+ }
3711
+ }
3712
+ async function getLinkedMemories(entityIds, client) {
3713
+ const linked = /* @__PURE__ */ new Map();
3714
+ if (entityIds.length === 0) return linked;
3715
+ const placeholders = entityIds.map(() => "?").join(",");
3716
+ try {
3717
+ const result = await client.execute({
3718
+ sql: `SELECT entity_id, memory_id FROM entity_memories
3719
+ WHERE entity_id IN (${placeholders})`,
3720
+ args: entityIds
3721
+ });
3722
+ for (const row of result.rows) {
3723
+ const entityId = String(row.entity_id);
3724
+ const memoryId = String(row.memory_id);
3725
+ const entry = linked.get(entityId) ?? {
3726
+ entityId,
3727
+ memoryIds: /* @__PURE__ */ new Set(),
3728
+ count: 0
3729
+ };
3730
+ entry.memoryIds.add(memoryId);
3731
+ entry.count = entry.memoryIds.size;
3732
+ linked.set(entityId, entry);
3124
3733
  }
3125
- _rerankerModel = null;
3734
+ } catch {
3126
3735
  }
3127
- process.stderr.write("[reranker] Unloaded (idle timeout).\n");
3736
+ return linked;
3128
3737
  }
3129
- async function rerankWithScores(query, texts, topK) {
3130
- if (texts.length === 0) return [];
3131
- await ensureLoaded();
3132
- const ctx = _rerankerContext;
3133
- const scored = [];
3134
- for (let i = 0; i < texts.length; i++) {
3135
- const text = texts[i] ?? "";
3738
+ function spreadAttenuation(numLinked) {
3739
+ return 1 / (1 + 1e-3 * (numLinked - 1) ** 2);
3740
+ }
3741
+ async function traverseAndScore(entities, client, boostMap, resultIds, graphContextMap) {
3742
+ const topEntities = entities.slice(0, TOP_ENTITIES_TO_TRAVERSE);
3743
+ if (topEntities.length === 0) return;
3744
+ const { getEntityNeighbors: getEntityNeighbors2 } = await Promise.resolve().then(() => (init_graph_query(), graph_query_exports));
3745
+ const relWeights = getRelTypeWeights();
3746
+ const neighborEntityIds = [];
3747
+ const neighborMeta = /* @__PURE__ */ new Map();
3748
+ for (const entity of topEntities) {
3136
3749
  try {
3137
- const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
3138
- const embedding = await ctx.getEmbeddingFor(input2);
3139
- const score = embedding.vector[0] ?? 0;
3140
- scored.push({ text, score, index: i });
3750
+ const neighbors = await getEntityNeighbors2(
3751
+ client,
3752
+ entity.entityId,
3753
+ GRAPH_TRAVERSAL_MAX_HOPS
3754
+ );
3755
+ for (const n of neighbors) {
3756
+ if (n.depth === 0) continue;
3757
+ if (neighborMeta.has(n.entity.id)) continue;
3758
+ neighborEntityIds.push(n.entity.id);
3759
+ neighborMeta.set(n.entity.id, {
3760
+ depth: n.depth,
3761
+ relType: n.relType,
3762
+ relConfidence: n.relConfidence,
3763
+ sourceName: entity.name
3764
+ });
3765
+ }
3141
3766
  } catch {
3142
- scored.push({ text, score: -1, index: i });
3143
3767
  }
3144
3768
  }
3145
- scored.sort((a, b) => b.score - a.score);
3146
- return typeof topK === "number" ? scored.slice(0, topK) : scored;
3769
+ if (neighborEntityIds.length === 0) return;
3770
+ const neighborLinked = await getLinkedMemories(neighborEntityIds, client);
3771
+ for (const [entityId, entry] of neighborLinked) {
3772
+ const meta = neighborMeta.get(entityId);
3773
+ if (!meta) continue;
3774
+ const relWeight = relWeights[meta.relType] ?? DEFAULT_REL_WEIGHT;
3775
+ const depthDecay = 1 / (1 + meta.depth);
3776
+ const neighborBoost = ENTITY_BOOST_WEIGHT * meta.relConfidence * depthDecay * relWeight;
3777
+ const attenuation = spreadAttenuation(entry.count);
3778
+ const finalBoost = neighborBoost * attenuation;
3779
+ for (const memoryId of entry.memoryIds) {
3780
+ if (resultIds.has(memoryId)) {
3781
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + finalBoost);
3782
+ const contextPath = `${meta.sourceName} ${meta.relType} ${entityId}`;
3783
+ if (!graphContextMap.has(memoryId)) {
3784
+ graphContextMap.set(memoryId, contextPath);
3785
+ }
3786
+ }
3787
+ }
3788
+ }
3147
3789
  }
3148
- async function rerank(query, candidates, topK = 5) {
3149
- if (candidates.length === 0) return [];
3150
- if (candidates.length <= topK) return candidates;
3151
- const scored = await rerankWithScores(
3152
- query,
3153
- candidates.map((c) => c.raw_text),
3154
- topK
3790
+ async function applyHyperedgeBoost(entities, client, boostMap, resultIds) {
3791
+ if (entities.length < 2) return;
3792
+ const entityIds = entities.map((e) => e.entityId);
3793
+ const placeholders = entityIds.map(() => "?").join(",");
3794
+ try {
3795
+ const result = await client.execute({
3796
+ sql: `SELECT hyperedge_id, entity_id FROM hyperedge_nodes
3797
+ WHERE entity_id IN (${placeholders})`,
3798
+ args: entityIds
3799
+ });
3800
+ const hyperedgeEntities = /* @__PURE__ */ new Map();
3801
+ for (const row of result.rows) {
3802
+ const hid = String(row.hyperedge_id);
3803
+ const eid = String(row.entity_id);
3804
+ const set = hyperedgeEntities.get(hid) ?? /* @__PURE__ */ new Set();
3805
+ set.add(eid);
3806
+ hyperedgeEntities.set(hid, set);
3807
+ }
3808
+ const sharedHyperedgeIds = [];
3809
+ for (const [hid, eids] of hyperedgeEntities) {
3810
+ if (eids.size >= 2) {
3811
+ sharedHyperedgeIds.push(hid);
3812
+ }
3813
+ }
3814
+ if (sharedHyperedgeIds.length === 0) return;
3815
+ const hPlaceholders = sharedHyperedgeIds.map(() => "?").join(",");
3816
+ const allNodesResult = await client.execute({
3817
+ sql: `SELECT DISTINCT entity_id FROM hyperedge_nodes
3818
+ WHERE hyperedge_id IN (${hPlaceholders})`,
3819
+ args: sharedHyperedgeIds
3820
+ });
3821
+ const allEntityIds = allNodesResult.rows.map((r) => String(r.entity_id));
3822
+ if (allEntityIds.length === 0) return;
3823
+ const linked = await getLinkedMemories(allEntityIds, client);
3824
+ for (const [, entry] of linked) {
3825
+ const attenuation = spreadAttenuation(entry.count);
3826
+ const boost = ENTITY_BOOST_WEIGHT * attenuation;
3827
+ for (const memoryId of entry.memoryIds) {
3828
+ if (resultIds.has(memoryId)) {
3829
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + boost);
3830
+ }
3831
+ }
3832
+ }
3833
+ } catch {
3834
+ }
3835
+ }
3836
+ async function applyEntityBoost(results, query, client) {
3837
+ const emptyResult = { results, graphContext: /* @__PURE__ */ new Map() };
3838
+ if (ENTITY_BOOST_WEIGHT === 0 || results.length === 0) {
3839
+ return emptyResult;
3840
+ }
3841
+ console.time("entity-boost");
3842
+ const entities = await matchEntities(query, client);
3843
+ if (entities.length === 0) {
3844
+ console.timeEnd("entity-boost");
3845
+ return emptyResult;
3846
+ }
3847
+ const boostMap = /* @__PURE__ */ new Map();
3848
+ const resultIds = new Set(results.map((r) => r.id));
3849
+ const graphContextMap = /* @__PURE__ */ new Map();
3850
+ const directLinked = await getLinkedMemories(
3851
+ entities.map((e) => e.entityId),
3852
+ client
3155
3853
  );
3156
- return scored.map((s) => candidates[s.index]);
3854
+ for (const [, entry] of directLinked) {
3855
+ const attenuation = spreadAttenuation(entry.count);
3856
+ const boost = ENTITY_BOOST_WEIGHT * attenuation;
3857
+ for (const memoryId of entry.memoryIds) {
3858
+ if (resultIds.has(memoryId)) {
3859
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + boost);
3860
+ }
3861
+ }
3862
+ }
3863
+ await traverseAndScore(entities, client, boostMap, resultIds, graphContextMap);
3864
+ await applyHyperedgeBoost(entities, client, boostMap, resultIds);
3865
+ if (boostMap.size === 0) {
3866
+ console.timeEnd("entity-boost");
3867
+ return emptyResult;
3868
+ }
3869
+ const scored = results.map((r, i) => ({
3870
+ record: r,
3871
+ baseScore: 1 / (1 + i * 0.1),
3872
+ entityBoost: boostMap.get(r.id) ?? 0
3873
+ }));
3874
+ scored.sort(
3875
+ (a, b) => b.baseScore + b.entityBoost - (a.baseScore + a.entityBoost)
3876
+ );
3877
+ console.timeEnd("entity-boost");
3878
+ return {
3879
+ results: scored.map((s) => s.record),
3880
+ graphContext: graphContextMap
3881
+ };
3157
3882
  }
3158
- var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
3159
- var init_reranker = __esm({
3160
- "src/lib/reranker.ts"() {
3883
+ var ENTITY_BOOST_WEIGHT, MIN_WORD_LENGTH, MAX_ENTITY_MATCHES, GRAPH_TRAVERSAL_MAX_HOPS, TOP_ENTITIES_TO_TRAVERSE, DEFAULT_RELATIONSHIP_WEIGHTS, DEFAULT_REL_WEIGHT;
3884
+ var init_entity_boost = __esm({
3885
+ "src/lib/entity-boost.ts"() {
3161
3886
  "use strict";
3162
- init_config();
3163
- RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
3164
- IDLE_TIMEOUT_MS = 6e4;
3165
- _rerankerContext = null;
3166
- _rerankerModel = null;
3167
- _idleTimer = null;
3887
+ ENTITY_BOOST_WEIGHT = parseFloat(
3888
+ process.env.ENTITY_BOOST_WEIGHT ?? "0.5"
3889
+ );
3890
+ MIN_WORD_LENGTH = 3;
3891
+ MAX_ENTITY_MATCHES = 20;
3892
+ GRAPH_TRAVERSAL_MAX_HOPS = Math.min(
3893
+ parseInt(process.env.GRAPH_TRAVERSAL_MAX_HOPS ?? "2", 10),
3894
+ 3
3895
+ );
3896
+ TOP_ENTITIES_TO_TRAVERSE = 5;
3897
+ DEFAULT_RELATIONSHIP_WEIGHTS = {
3898
+ decided: 1,
3899
+ depends_on: 0.8,
3900
+ part_of: 0.7,
3901
+ related_to: 0.5,
3902
+ mentioned_in: 0.3
3903
+ };
3904
+ DEFAULT_REL_WEIGHT = 0.4;
3168
3905
  }
3169
3906
  });
3170
3907
 
@@ -3322,8 +4059,14 @@ async function hybridSearch(queryText, agentId, options) {
3322
4059
  `
3323
4060
  );
3324
4061
  }
4062
+ let rerankerAvailable = false;
4063
+ try {
4064
+ const { isRerankerAvailable: isRerankerAvailable2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4065
+ rerankerAvailable = isRerankerAvailable2();
4066
+ } catch {
4067
+ }
3325
4068
  const broadFetchTopK = config.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
3326
- const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
4069
+ const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : rerankerAvailable ? Math.max(limit * 4, 60) : Math.max(limit * 3, 30);
3327
4070
  const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
3328
4071
  let queryVector = null;
3329
4072
  try {
@@ -3369,7 +4112,17 @@ async function hybridSearch(queryText, agentId, options) {
3369
4112
  if (lists.length === 0) return [];
3370
4113
  if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
3371
4114
  const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
3372
- const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4115
+ let merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4116
+ let graphContextMap = /* @__PURE__ */ new Map();
4117
+ if (merged.length > 0) {
4118
+ try {
4119
+ const { applyEntityBoost: applyEntityBoost2 } = await Promise.resolve().then(() => (init_entity_boost(), entity_boost_exports));
4120
+ const boosted = await applyEntityBoost2(merged, effectiveQuery, getClient());
4121
+ merged = boosted.results;
4122
+ graphContextMap = boosted.graphContext;
4123
+ } catch {
4124
+ }
4125
+ }
3373
4126
  const auto = config.scalingRoadmap?.rerankerAutoTrigger ?? {
3374
4127
  enabled: config.rerankerEnabled ?? true,
3375
4128
  broadQueryMinCardinality: 5e4,
@@ -3377,20 +4130,29 @@ async function hybridSearch(queryText, agentId, options) {
3377
4130
  returnTopK: 5
3378
4131
  };
3379
4132
  let rerankedAndBlended = null;
3380
- if (effectiveIsBroad && auto.enabled) {
4133
+ if (effectiveIsBroad && auto.enabled && rerankerAvailable) {
3381
4134
  const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
3382
4135
  if (cardinality2 > auto.broadQueryMinCardinality) {
3383
4136
  try {
3384
- const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
3385
- if (isRerankerAvailable2()) {
3386
- const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
3387
- if (reranked.length > 0) {
3388
- rerankedAndBlended = rrfMergeMulti(
3389
- [reranked],
3390
- auto.returnTopK,
3391
- RRF_K
3392
- );
3393
- }
4137
+ let rerankedRecords;
4138
+ if (graphContextMap.size > 0) {
4139
+ const { rerankWithContext: rerankWithContext2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4140
+ const candidates = merged.map((m) => ({
4141
+ text: m.raw_text,
4142
+ context: graphContextMap.get(m.id)
4143
+ }));
4144
+ const scored = await rerankWithContext2(effectiveQuery, candidates, auto.returnTopK);
4145
+ rerankedRecords = scored.map((s) => merged[s.index]);
4146
+ } else {
4147
+ const { rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4148
+ rerankedRecords = await rerank2(effectiveQuery, merged, auto.returnTopK);
4149
+ }
4150
+ if (rerankedRecords.length > 0) {
4151
+ rerankedAndBlended = rrfMergeMulti(
4152
+ [rerankedRecords],
4153
+ auto.returnTopK,
4154
+ RRF_K
4155
+ );
3394
4156
  }
3395
4157
  } catch {
3396
4158
  }
@@ -3410,7 +4172,7 @@ async function hybridSearch(queryText, agentId, options) {
3410
4172
  try {
3411
4173
  const client = getClient();
3412
4174
  void client.execute({
3413
- sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
4175
+ sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1 WHERE id IN (${placeholders})`,
3414
4176
  args: [now, ...ids]
3415
4177
  }).catch(() => {
3416
4178
  });