@askexenow/exe-os 0.8.83 → 0.8.86

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 (103) 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 +154 -21
  5. package/dist/bin/cli.js +14678 -12676
  6. package/dist/bin/exe-agent-config.js +242 -0
  7. package/dist/bin/exe-agent.js +100 -91
  8. package/dist/bin/exe-assign.js +1003 -854
  9. package/dist/bin/exe-boot.js +1420 -485
  10. package/dist/bin/exe-call.js +10 -0
  11. package/dist/bin/exe-cloud.js +29 -6
  12. package/dist/bin/exe-dispatch.js +572 -271
  13. package/dist/bin/exe-doctor.js +403 -6
  14. package/dist/bin/exe-export-behaviors.js +175 -72
  15. package/dist/bin/exe-forget.js +102 -3
  16. package/dist/bin/exe-gateway.js +796 -292
  17. package/dist/bin/exe-healthcheck.js +134 -1
  18. package/dist/bin/exe-heartbeat.js +172 -36
  19. package/dist/bin/exe-kill.js +175 -72
  20. package/dist/bin/exe-launch-agent.js +189 -76
  21. package/dist/bin/exe-link.js +927 -82
  22. package/dist/bin/exe-new-employee.js +60 -8
  23. package/dist/bin/exe-pending-messages.js +151 -19
  24. package/dist/bin/exe-pending-notifications.js +97 -2
  25. package/dist/bin/exe-pending-reviews.js +155 -22
  26. package/dist/bin/exe-rename.js +564 -23
  27. package/dist/bin/exe-review.js +231 -73
  28. package/dist/bin/exe-search.js +995 -228
  29. package/dist/bin/exe-session-cleanup.js +4930 -1664
  30. package/dist/bin/exe-settings.js +20 -5
  31. package/dist/bin/exe-start-codex.js +2598 -0
  32. package/dist/bin/exe-start.sh +15 -3
  33. package/dist/bin/exe-status.js +154 -21
  34. package/dist/bin/exe-team.js +97 -2
  35. package/dist/bin/git-sweep.js +1180 -363
  36. package/dist/bin/graph-backfill.js +175 -72
  37. package/dist/bin/graph-export.js +175 -72
  38. package/dist/bin/install.js +60 -7
  39. package/dist/bin/list-providers.js +1 -0
  40. package/dist/bin/scan-tasks.js +1185 -367
  41. package/dist/bin/setup.js +914 -270
  42. package/dist/bin/shard-migrate.js +175 -72
  43. package/dist/bin/update.js +1 -0
  44. package/dist/bin/wiki-sync.js +175 -72
  45. package/dist/gateway/index.js +792 -285
  46. package/dist/hooks/bug-report-worker.js +445 -135
  47. package/dist/hooks/commit-complete.js +1178 -361
  48. package/dist/hooks/error-recall.js +994 -228
  49. package/dist/hooks/ingest-worker.js +1799 -1234
  50. package/dist/hooks/ingest.js +3 -0
  51. package/dist/hooks/instructions-loaded.js +707 -97
  52. package/dist/hooks/notification.js +699 -89
  53. package/dist/hooks/post-compact.js +757 -109
  54. package/dist/hooks/pre-compact.js +1061 -244
  55. package/dist/hooks/pre-tool-use.js +787 -130
  56. package/dist/hooks/prompt-ingest-worker.js +242 -101
  57. package/dist/hooks/prompt-submit.js +1121 -299
  58. package/dist/hooks/response-ingest-worker.js +242 -101
  59. package/dist/hooks/session-end.js +4063 -397
  60. package/dist/hooks/session-start.js +1071 -254
  61. package/dist/hooks/stop.js +768 -120
  62. package/dist/hooks/subagent-stop.js +757 -109
  63. package/dist/hooks/summary-worker.js +1706 -1011
  64. package/dist/index.js +1821 -1098
  65. package/dist/lib/agent-config.js +167 -0
  66. package/dist/lib/cloud-sync.js +932 -88
  67. package/dist/lib/consolidation.js +2 -1
  68. package/dist/lib/database.js +642 -87
  69. package/dist/lib/db-daemon-client.js +503 -0
  70. package/dist/lib/device-registry.js +547 -7
  71. package/dist/lib/embedder.js +14 -28
  72. package/dist/lib/employee-templates.js +84 -74
  73. package/dist/lib/employees.js +9 -0
  74. package/dist/lib/exe-daemon-client.js +16 -29
  75. package/dist/lib/exe-daemon.js +2733 -1575
  76. package/dist/lib/hybrid-search.js +995 -228
  77. package/dist/lib/identity.js +87 -67
  78. package/dist/lib/keychain.js +9 -1
  79. package/dist/lib/messaging.js +103 -40
  80. package/dist/lib/reminders.js +91 -74
  81. package/dist/lib/runtime-table.js +16 -0
  82. package/dist/lib/schedules.js +96 -2
  83. package/dist/lib/session-wrappers.js +22 -0
  84. package/dist/lib/skill-learning.js +103 -85
  85. package/dist/lib/store.js +234 -73
  86. package/dist/lib/tasks.js +348 -134
  87. package/dist/lib/tmux-routing.js +422 -208
  88. package/dist/lib/token-spend.js +273 -0
  89. package/dist/lib/ws-client.js +11 -0
  90. package/dist/mcp/server.js +5742 -696
  91. package/dist/mcp/tools/complete-reminder.js +94 -77
  92. package/dist/mcp/tools/create-reminder.js +94 -77
  93. package/dist/mcp/tools/create-task.js +375 -152
  94. package/dist/mcp/tools/deactivate-behavior.js +95 -77
  95. package/dist/mcp/tools/list-reminders.js +94 -77
  96. package/dist/mcp/tools/list-tasks.js +99 -31
  97. package/dist/mcp/tools/send-message.js +108 -45
  98. package/dist/mcp/tools/update-task.js +162 -77
  99. package/dist/runtime/index.js +1075 -258
  100. package/dist/tui/App.js +1333 -506
  101. package/package.json +6 -1
  102. package/src/commands/exe/agent-config.md +27 -0
  103. package/src/commands/exe/cc-doctor.md +10 -0
@@ -391,6 +391,12 @@ function getClient() {
391
391
  if (!_resilientClient) {
392
392
  throw new Error("Database client not initialized. Call initDatabase() first.");
393
393
  }
394
+ if (process.env.EXE_IS_DAEMON === "1") {
395
+ return _resilientClient;
396
+ }
397
+ if (_daemonClient && _daemonClient._isDaemonActive()) {
398
+ return _daemonClient;
399
+ }
394
400
  return _resilientClient;
395
401
  }
396
402
  function getRawClient() {
@@ -879,6 +885,12 @@ async function ensureSchema() {
879
885
  } catch {
880
886
  }
881
887
  }
888
+ try {
889
+ await client.execute(
890
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
891
+ );
892
+ } catch {
893
+ }
882
894
  await client.executeMultiple(`
883
895
  CREATE TABLE IF NOT EXISTS entities (
884
896
  id TEXT PRIMARY KEY,
@@ -931,7 +943,30 @@ async function ensureSchema() {
931
943
  entity_id TEXT NOT NULL,
932
944
  PRIMARY KEY (hyperedge_id, entity_id)
933
945
  );
946
+
947
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
948
+ name,
949
+ content=entities,
950
+ content_rowid=rowid
951
+ );
952
+
953
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
954
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
955
+ END;
956
+
957
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
958
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
959
+ END;
960
+
961
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
962
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
963
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
964
+ END;
934
965
  `);
966
+ try {
967
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
968
+ } catch {
969
+ }
935
970
  await client.executeMultiple(`
936
971
  CREATE TABLE IF NOT EXISTS entity_aliases (
937
972
  alias TEXT NOT NULL PRIMARY KEY,
@@ -1112,6 +1147,33 @@ async function ensureSchema() {
1112
1147
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1113
1148
  ON conversations(channel_id);
1114
1149
  `);
1150
+ await client.executeMultiple(`
1151
+ CREATE TABLE IF NOT EXISTS session_agent_map (
1152
+ session_uuid TEXT PRIMARY KEY,
1153
+ agent_id TEXT NOT NULL,
1154
+ session_name TEXT,
1155
+ task_id TEXT,
1156
+ project_name TEXT,
1157
+ started_at TEXT NOT NULL
1158
+ );
1159
+
1160
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
1161
+ ON session_agent_map(agent_id);
1162
+ `);
1163
+ try {
1164
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
1165
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
1166
+ await client.execute({
1167
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
1168
+ SELECT session_id, agent_id, '', MIN(timestamp)
1169
+ FROM memories
1170
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
1171
+ GROUP BY session_id, agent_id`,
1172
+ args: []
1173
+ });
1174
+ }
1175
+ } catch {
1176
+ }
1115
1177
  try {
1116
1178
  await client.execute({
1117
1179
  sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
@@ -1245,15 +1307,41 @@ async function ensureSchema() {
1245
1307
  });
1246
1308
  } catch {
1247
1309
  }
1310
+ for (const col of [
1311
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
1312
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
1313
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
1314
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
1315
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
1316
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
1317
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
1318
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
1319
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
1320
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
1321
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
1322
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
1323
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
1324
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
1325
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1326
+ ]) {
1327
+ try {
1328
+ await client.execute(col);
1329
+ } catch {
1330
+ }
1331
+ }
1248
1332
  }
1249
1333
  async function disposeDatabase() {
1334
+ if (_daemonClient) {
1335
+ _daemonClient.close();
1336
+ _daemonClient = null;
1337
+ }
1250
1338
  if (_client) {
1251
1339
  _client.close();
1252
1340
  _client = null;
1253
1341
  _resilientClient = null;
1254
1342
  }
1255
1343
  }
1256
- var _client, _resilientClient, initTurso, disposeTurso;
1344
+ var _client, _resilientClient, _daemonClient, initTurso, disposeTurso;
1257
1345
  var init_database = __esm({
1258
1346
  "src/lib/database.ts"() {
1259
1347
  "use strict";
@@ -1261,6 +1349,7 @@ var init_database = __esm({
1261
1349
  init_employees();
1262
1350
  _client = null;
1263
1351
  _resilientClient = null;
1352
+ _daemonClient = null;
1264
1353
  initTurso = initDatabase;
1265
1354
  disposeTurso = disposeDatabase;
1266
1355
  }
@@ -1297,12 +1386,20 @@ async function getMasterKey() {
1297
1386
  }
1298
1387
  const keyPath = getKeyPath();
1299
1388
  if (!existsSync3(keyPath)) {
1389
+ process.stderr.write(
1390
+ `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1391
+ `
1392
+ );
1300
1393
  return null;
1301
1394
  }
1302
1395
  try {
1303
1396
  const content = await readFile3(keyPath, "utf-8");
1304
1397
  return Buffer.from(content.trim(), "base64");
1305
- } catch {
1398
+ } catch (err) {
1399
+ process.stderr.write(
1400
+ `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
1401
+ `
1402
+ );
1306
1403
  return null;
1307
1404
  }
1308
1405
  }
@@ -1813,6 +1910,7 @@ __export(store_exports, {
1813
1910
  vectorToBlob: () => vectorToBlob,
1814
1911
  writeMemory: () => writeMemory
1815
1912
  });
1913
+ import { createHash } from "crypto";
1816
1914
  function isBusyError2(err) {
1817
1915
  if (err instanceof Error) {
1818
1916
  const msg = err.message.toLowerCase();
@@ -1886,12 +1984,52 @@ function classifyTier(record) {
1886
1984
  if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1887
1985
  return 3;
1888
1986
  }
1987
+ function inferFilePaths(record) {
1988
+ if (!["Read", "Write", "Edit"].includes(record.tool_name)) return null;
1989
+ const firstLine = record.raw_text.split("\n")[0] ?? "";
1990
+ const match = firstLine.match(/(\/[\w./-]+\.\w+)/);
1991
+ return match ? JSON.stringify([match[1]]) : null;
1992
+ }
1993
+ function inferCommitHash(record) {
1994
+ if (record.tool_name !== "Bash") return null;
1995
+ const match = record.raw_text.match(/\b([a-f0-9]{7,40})\b/);
1996
+ return match ? match[1] : null;
1997
+ }
1998
+ function inferLanguageType(record) {
1999
+ const text = record.raw_text;
2000
+ if (!text || text.length < 10) return null;
2001
+ const trimmed = text.trimStart();
2002
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
2003
+ if (/\b(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE|ALTER TABLE)\b/i.test(text)) return "sql";
2004
+ if (/\b(function |const |import |export |class |def |async |=>)\b/.test(text)) return "code";
2005
+ if (trimmed.startsWith("#") || trimmed.startsWith("*")) return "prose";
2006
+ return "mixed";
2007
+ }
2008
+ function inferDomain(record) {
2009
+ const proj = (record.project_name ?? "").toLowerCase();
2010
+ if (proj.includes("marketing") || proj.includes("content")) return "marketing";
2011
+ if (proj.includes("crm") || proj.includes("customer")) return "customer";
2012
+ return null;
2013
+ }
1889
2014
  async function writeMemory(record) {
1890
2015
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1891
2016
  throw new Error(
1892
2017
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
1893
2018
  );
1894
2019
  }
2020
+ const contentHash = createHash("md5").update(record.raw_text).digest("hex");
2021
+ if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
2022
+ return;
2023
+ }
2024
+ try {
2025
+ const client = getClient();
2026
+ const existing = await client.execute({
2027
+ sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
2028
+ args: [contentHash, record.agent_id]
2029
+ });
2030
+ if (existing.rows.length > 0) return;
2031
+ } catch {
2032
+ }
1895
2033
  const dbRow = {
1896
2034
  id: record.id,
1897
2035
  agent_id: record.agent_id,
@@ -1921,7 +2059,23 @@ async function writeMemory(record) {
1921
2059
  supersedes_id: record.supersedes_id ?? null,
1922
2060
  draft: record.draft ? 1 : 0,
1923
2061
  memory_type: record.memory_type ?? "raw",
1924
- trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
2062
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
2063
+ content_hash: contentHash,
2064
+ intent: record.intent ?? null,
2065
+ outcome: record.outcome ?? null,
2066
+ domain: record.domain ?? inferDomain(record),
2067
+ referenced_entities: record.referenced_entities ?? null,
2068
+ retrieval_count: record.retrieval_count ?? 0,
2069
+ chain_position: record.chain_position ?? null,
2070
+ review_status: record.review_status ?? null,
2071
+ context_window_pct: record.context_window_pct ?? null,
2072
+ file_paths: record.file_paths ?? inferFilePaths(record),
2073
+ commit_hash: record.commit_hash ?? inferCommitHash(record),
2074
+ duration_ms: record.duration_ms ?? null,
2075
+ token_cost: record.token_cost ?? null,
2076
+ audience: record.audience ?? null,
2077
+ language_type: record.language_type ?? inferLanguageType(record),
2078
+ parent_memory_id: record.parent_memory_id ?? null
1925
2079
  };
1926
2080
  _pendingRecords.push(dbRow);
1927
2081
  orgBus.emit({
@@ -1979,80 +2133,85 @@ async function flushBatch() {
1979
2133
  const draft = row.draft ? 1 : 0;
1980
2134
  const memoryType = row.memory_type ?? "raw";
1981
2135
  const trajectory = row.trajectory ?? null;
1982
- return {
1983
- sql: hasVector ? `INSERT OR IGNORE INTO memories
1984
- (id, agent_id, agent_role, session_id, timestamp,
1985
- tool_name, project_name,
1986
- has_error, raw_text, vector, version, task_id, importance, status,
1987
- confidence, last_accessed,
1988
- workspace_id, document_id, user_id, char_offset, page_number,
1989
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1990
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1991
- (id, agent_id, agent_role, session_id, timestamp,
2136
+ const contentHash = row.content_hash ?? null;
2137
+ const intent = row.intent ?? null;
2138
+ const outcome = row.outcome ?? null;
2139
+ const domain = row.domain ?? null;
2140
+ const referencedEntities = row.referenced_entities ?? null;
2141
+ const retrievalCount = row.retrieval_count ?? 0;
2142
+ const chainPosition = row.chain_position ?? null;
2143
+ const reviewStatus = row.review_status ?? null;
2144
+ const contextWindowPct = row.context_window_pct ?? null;
2145
+ const filePaths = row.file_paths ?? null;
2146
+ const commitHash = row.commit_hash ?? null;
2147
+ const durationMs = row.duration_ms ?? null;
2148
+ const tokenCost = row.token_cost ?? null;
2149
+ const audience = row.audience ?? null;
2150
+ const languageType = row.language_type ?? null;
2151
+ const parentMemoryId = row.parent_memory_id ?? null;
2152
+ const cols = `id, agent_id, agent_role, session_id, timestamp,
1992
2153
  tool_name, project_name,
1993
2154
  has_error, raw_text, vector, version, task_id, importance, status,
1994
2155
  confidence, last_accessed,
1995
2156
  workspace_id, document_id, user_id, char_offset, page_number,
1996
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
1997
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1998
- args: hasVector ? [
1999
- row.id,
2000
- row.agent_id,
2001
- row.agent_role,
2002
- row.session_id,
2003
- row.timestamp,
2004
- row.tool_name,
2005
- row.project_name,
2006
- row.has_error,
2007
- row.raw_text,
2008
- vectorToBlob(row.vector),
2009
- row.version,
2010
- taskId,
2011
- importance,
2012
- status,
2013
- confidence,
2014
- lastAccessed,
2015
- workspaceId,
2016
- documentId,
2017
- userId,
2018
- charOffset,
2019
- pageNumber,
2020
- sourcePath,
2021
- sourceType,
2022
- tier,
2023
- supersedesId,
2024
- draft,
2025
- memoryType,
2026
- trajectory
2027
- ] : [
2028
- row.id,
2029
- row.agent_id,
2030
- row.agent_role,
2031
- row.session_id,
2032
- row.timestamp,
2033
- row.tool_name,
2034
- row.project_name,
2035
- row.has_error,
2036
- row.raw_text,
2037
- row.version,
2038
- taskId,
2039
- importance,
2040
- status,
2041
- confidence,
2042
- lastAccessed,
2043
- workspaceId,
2044
- documentId,
2045
- userId,
2046
- charOffset,
2047
- pageNumber,
2048
- sourcePath,
2049
- sourceType,
2050
- tier,
2051
- supersedesId,
2052
- draft,
2053
- memoryType,
2054
- trajectory
2055
- ]
2157
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
2158
+ intent, outcome, domain, referenced_entities, retrieval_count,
2159
+ chain_position, review_status, context_window_pct, file_paths, commit_hash,
2160
+ duration_ms, token_cost, audience, language_type, parent_memory_id`;
2161
+ const metaArgs = [
2162
+ intent,
2163
+ outcome,
2164
+ domain,
2165
+ referencedEntities,
2166
+ retrievalCount,
2167
+ chainPosition,
2168
+ reviewStatus,
2169
+ contextWindowPct,
2170
+ filePaths,
2171
+ commitHash,
2172
+ durationMs,
2173
+ tokenCost,
2174
+ audience,
2175
+ languageType,
2176
+ parentMemoryId
2177
+ ];
2178
+ const baseArgs = [
2179
+ row.id,
2180
+ row.agent_id,
2181
+ row.agent_role,
2182
+ row.session_id,
2183
+ row.timestamp,
2184
+ row.tool_name,
2185
+ row.project_name,
2186
+ row.has_error,
2187
+ row.raw_text
2188
+ ];
2189
+ const sharedArgs = [
2190
+ row.version,
2191
+ taskId,
2192
+ importance,
2193
+ status,
2194
+ confidence,
2195
+ lastAccessed,
2196
+ workspaceId,
2197
+ documentId,
2198
+ userId,
2199
+ charOffset,
2200
+ pageNumber,
2201
+ sourcePath,
2202
+ sourceType,
2203
+ tier,
2204
+ supersedesId,
2205
+ draft,
2206
+ memoryType,
2207
+ trajectory,
2208
+ contentHash
2209
+ ];
2210
+ return {
2211
+ sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
2212
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
2213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2214
+ args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
2056
2215
  };
2057
2216
  };
2058
2217
  const globalClient = getClient();
@@ -2391,12 +2550,141 @@ var init_self_query_router = __esm({
2391
2550
  }
2392
2551
  });
2393
2552
 
2553
+ // src/lib/reranker.ts
2554
+ var reranker_exports = {};
2555
+ __export(reranker_exports, {
2556
+ disposeReranker: () => disposeReranker,
2557
+ getRerankerModelPath: () => getRerankerModelPath,
2558
+ isRerankerAvailable: () => isRerankerAvailable,
2559
+ rerank: () => rerank,
2560
+ rerankWithContext: () => rerankWithContext,
2561
+ rerankWithScores: () => rerankWithScores
2562
+ });
2563
+ import path5 from "path";
2564
+ import { existsSync as existsSync5 } from "fs";
2565
+ function resetIdleTimer() {
2566
+ if (_idleTimer) clearTimeout(_idleTimer);
2567
+ _idleTimer = setTimeout(() => {
2568
+ void disposeReranker();
2569
+ }, IDLE_TIMEOUT_MS);
2570
+ if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
2571
+ _idleTimer.unref();
2572
+ }
2573
+ }
2574
+ function isRerankerAvailable() {
2575
+ return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
2576
+ }
2577
+ function getRerankerModelPath() {
2578
+ return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2579
+ }
2580
+ async function ensureLoaded() {
2581
+ if (_rerankerContext) {
2582
+ resetIdleTimer();
2583
+ return;
2584
+ }
2585
+ const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2586
+ if (!existsSync5(modelPath)) {
2587
+ throw new Error(
2588
+ `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
2589
+ );
2590
+ }
2591
+ process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
2592
+ const { getLlama } = await import("node-llama-cpp");
2593
+ const llama = await getLlama();
2594
+ _rerankerModel = await llama.loadModel({ modelPath });
2595
+ _rerankerContext = await _rerankerModel.createEmbeddingContext();
2596
+ process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
2597
+ resetIdleTimer();
2598
+ }
2599
+ async function disposeReranker() {
2600
+ if (_idleTimer) {
2601
+ clearTimeout(_idleTimer);
2602
+ _idleTimer = null;
2603
+ }
2604
+ if (_rerankerContext) {
2605
+ try {
2606
+ await _rerankerContext.dispose();
2607
+ } catch {
2608
+ }
2609
+ _rerankerContext = null;
2610
+ }
2611
+ if (_rerankerModel) {
2612
+ try {
2613
+ await _rerankerModel.dispose();
2614
+ } catch {
2615
+ }
2616
+ _rerankerModel = null;
2617
+ }
2618
+ process.stderr.write("[reranker] Unloaded (idle timeout).\n");
2619
+ }
2620
+ async function rerankWithScores(query, texts, topK) {
2621
+ if (texts.length === 0) return [];
2622
+ await ensureLoaded();
2623
+ const ctx = _rerankerContext;
2624
+ const scored = [];
2625
+ for (let i = 0; i < texts.length; i++) {
2626
+ const text = texts[i] ?? "";
2627
+ try {
2628
+ const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
2629
+ const embedding = await ctx.getEmbeddingFor(input2);
2630
+ const score = embedding.vector[0] ?? 0;
2631
+ scored.push({ text, score, index: i });
2632
+ } catch {
2633
+ scored.push({ text, score: -1, index: i });
2634
+ }
2635
+ }
2636
+ scored.sort((a, b) => b.score - a.score);
2637
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
2638
+ }
2639
+ async function rerank(query, candidates, topK = 5) {
2640
+ if (candidates.length === 0) return [];
2641
+ if (candidates.length <= topK) return candidates;
2642
+ const scored = await rerankWithScores(
2643
+ query,
2644
+ candidates.map((c) => c.raw_text),
2645
+ topK
2646
+ );
2647
+ return scored.map((s) => candidates[s.index]);
2648
+ }
2649
+ async function rerankWithContext(query, candidates, topK) {
2650
+ if (candidates.length === 0) return [];
2651
+ await ensureLoaded();
2652
+ const ctx = _rerankerContext;
2653
+ const scored = [];
2654
+ for (let i = 0; i < candidates.length; i++) {
2655
+ const candidate = candidates[i];
2656
+ try {
2657
+ const docText = candidate.context ? `[${candidate.context}] ${candidate.text.slice(0, 460)}` : candidate.text.slice(0, 512);
2658
+ const input2 = `query: ${query} document: ${docText}`;
2659
+ const embedding = await ctx.getEmbeddingFor(input2);
2660
+ const score = embedding.vector[0] ?? 0;
2661
+ scored.push({ text: candidate.text, score, index: i });
2662
+ } catch {
2663
+ scored.push({ text: candidate.text, score: -1, index: i });
2664
+ }
2665
+ }
2666
+ scored.sort((a, b) => b.score - a.score);
2667
+ return typeof topK === "number" ? scored.slice(0, topK) : scored;
2668
+ }
2669
+ var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
2670
+ var init_reranker = __esm({
2671
+ "src/lib/reranker.ts"() {
2672
+ "use strict";
2673
+ init_config();
2674
+ RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
2675
+ IDLE_TIMEOUT_MS = 6e4;
2676
+ _rerankerContext = null;
2677
+ _rerankerModel = null;
2678
+ _idleTimer = null;
2679
+ }
2680
+ });
2681
+
2394
2682
  // src/lib/exe-daemon-client.ts
2395
2683
  import net from "net";
2396
2684
  import { spawn } from "child_process";
2397
2685
  import { randomUUID as randomUUID2 } from "crypto";
2398
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2399
- import path5 from "path";
2686
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2687
+ import path6 from "path";
2400
2688
  import { fileURLToPath } from "url";
2401
2689
  function handleData(chunk) {
2402
2690
  _buffer += chunk.toString();
@@ -2411,10 +2699,12 @@ function handleData(chunk) {
2411
2699
  if (!line) continue;
2412
2700
  try {
2413
2701
  const response = JSON.parse(line);
2414
- const entry = _pending.get(response.id);
2702
+ const id = response.id;
2703
+ if (!id) continue;
2704
+ const entry = _pending.get(id);
2415
2705
  if (entry) {
2416
2706
  clearTimeout(entry.timer);
2417
- _pending.delete(response.id);
2707
+ _pending.delete(id);
2418
2708
  entry.resolve(response);
2419
2709
  }
2420
2710
  } catch {
@@ -2422,7 +2712,7 @@ function handleData(chunk) {
2422
2712
  }
2423
2713
  }
2424
2714
  function cleanupStaleFiles() {
2425
- if (existsSync5(PID_PATH)) {
2715
+ if (existsSync6(PID_PATH)) {
2426
2716
  try {
2427
2717
  const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2428
2718
  if (pid > 0) {
@@ -2445,11 +2735,11 @@ function cleanupStaleFiles() {
2445
2735
  }
2446
2736
  }
2447
2737
  function findPackageRoot() {
2448
- let dir = path5.dirname(fileURLToPath(import.meta.url));
2449
- const { root } = path5.parse(dir);
2738
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
2739
+ const { root } = path6.parse(dir);
2450
2740
  while (dir !== root) {
2451
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
2452
- dir = path5.dirname(dir);
2741
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
2742
+ dir = path6.dirname(dir);
2453
2743
  }
2454
2744
  return null;
2455
2745
  }
@@ -2459,8 +2749,8 @@ function spawnDaemon() {
2459
2749
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2460
2750
  return;
2461
2751
  }
2462
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2463
- if (!existsSync5(daemonPath)) {
2752
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2753
+ if (!existsSync6(daemonPath)) {
2464
2754
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2465
2755
  `);
2466
2756
  return;
@@ -2468,7 +2758,7 @@ function spawnDaemon() {
2468
2758
  const resolvedPath = daemonPath;
2469
2759
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2470
2760
  `);
2471
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
2761
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
2472
2762
  let stderrFd = "ignore";
2473
2763
  try {
2474
2764
  stderrFd = openSync(logPath, "a");
@@ -2585,6 +2875,9 @@ async function connectEmbedDaemon() {
2585
2875
  return false;
2586
2876
  }
2587
2877
  function sendRequest(texts, priority) {
2878
+ return sendDaemonRequest({ texts, priority });
2879
+ }
2880
+ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2588
2881
  return new Promise((resolve) => {
2589
2882
  if (!_socket || !_connected) {
2590
2883
  resolve({ error: "Not connected" });
@@ -2594,10 +2887,10 @@ function sendRequest(texts, priority) {
2594
2887
  const timer = setTimeout(() => {
2595
2888
  _pending.delete(id);
2596
2889
  resolve({ error: "Request timeout" });
2597
- }, REQUEST_TIMEOUT_MS);
2890
+ }, timeoutMs);
2598
2891
  _pending.set(id, { resolve, timer });
2599
2892
  try {
2600
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
2893
+ _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2601
2894
  } catch {
2602
2895
  clearTimeout(timer);
2603
2896
  _pending.delete(id);
@@ -2607,34 +2900,15 @@ function sendRequest(texts, priority) {
2607
2900
  }
2608
2901
  async function pingDaemon() {
2609
2902
  if (!_socket || !_connected) return null;
2610
- return new Promise((resolve) => {
2611
- const id = randomUUID2();
2612
- const timer = setTimeout(() => {
2613
- _pending.delete(id);
2614
- resolve(null);
2615
- }, 5e3);
2616
- _pending.set(id, {
2617
- resolve: (data) => {
2618
- if (data.health) {
2619
- resolve(data.health);
2620
- } else {
2621
- resolve(null);
2622
- }
2623
- },
2624
- timer
2625
- });
2626
- try {
2627
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
2628
- } catch {
2629
- clearTimeout(timer);
2630
- _pending.delete(id);
2631
- resolve(null);
2632
- }
2633
- });
2903
+ const response = await sendDaemonRequest({ type: "health" }, 5e3);
2904
+ if (response.health) {
2905
+ return response.health;
2906
+ }
2907
+ return null;
2634
2908
  }
2635
2909
  function killAndRespawnDaemon() {
2636
2910
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
2637
- if (existsSync5(PID_PATH)) {
2911
+ if (existsSync6(PID_PATH)) {
2638
2912
  try {
2639
2913
  const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2640
2914
  if (pid > 0) {
@@ -2720,9 +2994,9 @@ var init_exe_daemon_client = __esm({
2720
2994
  "src/lib/exe-daemon-client.ts"() {
2721
2995
  "use strict";
2722
2996
  init_config();
2723
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
2724
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
2725
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
2997
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
2998
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
2999
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2726
3000
  SPAWN_LOCK_STALE_MS = 3e4;
2727
3001
  CONNECT_TIMEOUT_MS = 15e3;
2728
3002
  REQUEST_TIMEOUT_MS = 3e4;
@@ -2773,10 +3047,10 @@ async function disposeEmbedder() {
2773
3047
  async function embedDirect(text) {
2774
3048
  const llamaCpp = await import("node-llama-cpp");
2775
3049
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2776
- const { existsSync: existsSync12 } = await import("fs");
2777
- const path16 = await import("path");
2778
- const modelPath = path16.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2779
- if (!existsSync12(modelPath)) {
3050
+ const { existsSync: existsSync13 } = await import("fs");
3051
+ const path17 = await import("path");
3052
+ const modelPath = path17.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3053
+ if (!existsSync13(modelPath)) {
2780
3054
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
2781
3055
  }
2782
3056
  const llama = await llamaCpp.getLlama();
@@ -2811,7 +3085,7 @@ __export(project_name_exports, {
2811
3085
  getProjectName: () => getProjectName
2812
3086
  });
2813
3087
  import { execSync as execSync2 } from "child_process";
2814
- import path6 from "path";
3088
+ import path7 from "path";
2815
3089
  function getProjectName(cwd) {
2816
3090
  const dir = cwd ?? process.cwd();
2817
3091
  if (_cached && _cachedCwd === dir) return _cached;
@@ -2824,7 +3098,7 @@ function getProjectName(cwd) {
2824
3098
  timeout: 2e3,
2825
3099
  stdio: ["pipe", "pipe", "pipe"]
2826
3100
  }).trim();
2827
- repoRoot = path6.dirname(gitCommonDir);
3101
+ repoRoot = path7.dirname(gitCommonDir);
2828
3102
  } catch {
2829
3103
  repoRoot = execSync2("git rev-parse --show-toplevel", {
2830
3104
  cwd: dir,
@@ -2833,11 +3107,11 @@ function getProjectName(cwd) {
2833
3107
  stdio: ["pipe", "pipe", "pipe"]
2834
3108
  }).trim();
2835
3109
  }
2836
- _cached = path6.basename(repoRoot);
3110
+ _cached = path7.basename(repoRoot);
2837
3111
  _cachedCwd = dir;
2838
3112
  return _cached;
2839
3113
  } catch {
2840
- _cached = path6.basename(dir);
3114
+ _cached = path7.basename(dir);
2841
3115
  _cachedCwd = dir;
2842
3116
  return _cached;
2843
3117
  }
@@ -2861,8 +3135,8 @@ __export(file_grep_exports, {
2861
3135
  grepProjectFiles: () => grepProjectFiles
2862
3136
  });
2863
3137
  import { execSync as execSync3 } from "child_process";
2864
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync6 } from "fs";
2865
- import path7 from "path";
3138
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3139
+ import path8 from "path";
2866
3140
  import crypto from "crypto";
2867
3141
  function hasRipgrep() {
2868
3142
  if (_hasRg === null) {
@@ -2902,7 +3176,7 @@ async function grepProjectFiles(query, projectRoot, options) {
2902
3176
  session_id: "file-grep",
2903
3177
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2904
3178
  tool_name: "file_grep",
2905
- project_name: path7.basename(projectRoot),
3179
+ project_name: path8.basename(projectRoot),
2906
3180
  has_error: false,
2907
3181
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
2908
3182
  vector: null,
@@ -2976,7 +3250,7 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
2976
3250
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
2977
3251
  const hits = [];
2978
3252
  for (const filePath of files.slice(0, MAX_FILES)) {
2979
- const absPath = path7.join(projectRoot, filePath);
3253
+ const absPath = path8.join(projectRoot, filePath);
2980
3254
  try {
2981
3255
  const stat = statSync2(absPath);
2982
3256
  if (stat.size > MAX_FILE_SIZE) continue;
@@ -3003,15 +3277,15 @@ function collectFiles(root, patterns) {
3003
3277
  const files = [];
3004
3278
  function walk(dir, relative) {
3005
3279
  if (files.length >= MAX_FILES) return;
3006
- const basename = path7.basename(dir);
3280
+ const basename = path8.basename(dir);
3007
3281
  if (EXCLUDE_DIRS.includes(basename)) return;
3008
3282
  try {
3009
3283
  const entries = readdirSync2(dir, { withFileTypes: true });
3010
3284
  for (const entry of entries) {
3011
3285
  if (files.length >= MAX_FILES) return;
3012
- const rel = path7.join(relative, entry.name);
3286
+ const rel = path8.join(relative, entry.name);
3013
3287
  if (entry.isDirectory()) {
3014
- walk(path7.join(dir, entry.name), rel);
3288
+ walk(path8.join(dir, entry.name), rel);
3015
3289
  } else if (entry.isFile()) {
3016
3290
  for (const pat of patterns) {
3017
3291
  if (matchGlob(rel, pat)) {
@@ -3043,7 +3317,7 @@ function matchGlob(filePath, pattern) {
3043
3317
  if (slashIdx !== -1) {
3044
3318
  const dir = pattern.slice(0, slashIdx);
3045
3319
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3046
- const fileDir = path7.dirname(filePath);
3320
+ const fileDir = path8.dirname(filePath);
3047
3321
  return fileDir === dir && filePath.endsWith(ext2);
3048
3322
  }
3049
3323
  const ext = pattern.replace("*", "");
@@ -3051,8 +3325,8 @@ function matchGlob(filePath, pattern) {
3051
3325
  }
3052
3326
  function buildSnippet(hit, projectRoot) {
3053
3327
  try {
3054
- const absPath = path7.join(projectRoot, hit.filePath);
3055
- if (!existsSync6(absPath)) return hit.matchLine;
3328
+ const absPath = path8.join(projectRoot, hit.filePath);
3329
+ if (!existsSync7(absPath)) return hit.matchLine;
3056
3330
  const lines = readFileSync4(absPath, "utf8").split("\n");
3057
3331
  const start = Math.max(0, hit.lineNumber - 3);
3058
3332
  const end = Math.min(lines.length, hit.lineNumber + 2);
@@ -3078,111 +3352,574 @@ var init_file_grep = __esm({
3078
3352
  }
3079
3353
  });
3080
3354
 
3081
- // src/lib/reranker.ts
3082
- var reranker_exports = {};
3083
- __export(reranker_exports, {
3084
- disposeReranker: () => disposeReranker,
3085
- getRerankerModelPath: () => getRerankerModelPath,
3086
- isRerankerAvailable: () => isRerankerAvailable,
3087
- rerank: () => rerank,
3088
- rerankWithScores: () => rerankWithScores
3355
+ // src/lib/graph-query.ts
3356
+ var graph_query_exports = {};
3357
+ __export(graph_query_exports, {
3358
+ getConversationPartners: () => getConversationPartners,
3359
+ getEntityByName: () => getEntityByName,
3360
+ getEntityNeighbors: () => getEntityNeighbors,
3361
+ getEntityTimeline: () => getEntityTimeline,
3362
+ getGraphStats: () => getGraphStats,
3363
+ getHotEntities: () => getHotEntities,
3364
+ getRelationshipFrequency: () => getRelationshipFrequency,
3365
+ getRelationships: () => getRelationships,
3366
+ searchEntities: () => searchEntities,
3367
+ traverseChain: () => traverseChain
3089
3368
  });
3090
- import path8 from "path";
3091
- import { existsSync as existsSync7 } from "fs";
3092
- function resetIdleTimer() {
3093
- if (_idleTimer) clearTimeout(_idleTimer);
3094
- _idleTimer = setTimeout(() => {
3095
- void disposeReranker();
3096
- }, IDLE_TIMEOUT_MS);
3097
- if (_idleTimer && typeof _idleTimer === "object" && "unref" in _idleTimer) {
3098
- _idleTimer.unref();
3369
+ async function getEntityByName(client, name, type) {
3370
+ const sql = type ? `SELECT * FROM entities WHERE LOWER(name) = LOWER(?) AND type = ? LIMIT 1` : `SELECT * FROM entities WHERE LOWER(name) = LOWER(?) LIMIT 1`;
3371
+ const args = type ? [name, type] : [name];
3372
+ const result = await client.execute({ sql, args });
3373
+ if (result.rows.length === 0) return null;
3374
+ const row = result.rows[0];
3375
+ return {
3376
+ id: String(row.id),
3377
+ name: String(row.name),
3378
+ type: String(row.type),
3379
+ firstSeen: String(row.first_seen),
3380
+ lastSeen: String(row.last_seen),
3381
+ properties: JSON.parse(String(row.properties ?? "{}"))
3382
+ };
3383
+ }
3384
+ async function searchEntities(client, query, limit = 10) {
3385
+ const result = await client.execute({
3386
+ sql: `SELECT * FROM entities WHERE LOWER(name) LIKE ? ORDER BY last_seen DESC LIMIT ?`,
3387
+ args: [`%${query.toLowerCase()}%`, limit]
3388
+ });
3389
+ return result.rows.map((row) => ({
3390
+ id: String(row.id),
3391
+ name: String(row.name),
3392
+ type: String(row.type),
3393
+ firstSeen: String(row.first_seen),
3394
+ lastSeen: String(row.last_seen),
3395
+ properties: JSON.parse(String(row.properties ?? "{}"))
3396
+ }));
3397
+ }
3398
+ async function getRelationships(client, entityId, options) {
3399
+ const direction = options?.direction ?? "both";
3400
+ let sql;
3401
+ const args = [];
3402
+ if (direction === "outgoing") {
3403
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3404
+ FROM relationships r
3405
+ JOIN entities s ON r.source_entity_id = s.id
3406
+ JOIN entities t ON r.target_entity_id = t.id
3407
+ WHERE r.source_entity_id = ?`;
3408
+ args.push(entityId);
3409
+ } else if (direction === "incoming") {
3410
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3411
+ FROM relationships r
3412
+ JOIN entities s ON r.source_entity_id = s.id
3413
+ JOIN entities t ON r.target_entity_id = t.id
3414
+ WHERE r.target_entity_id = ?`;
3415
+ args.push(entityId);
3416
+ } else {
3417
+ sql = `SELECT r.*, s.name as source_name, t.name as target_name
3418
+ FROM relationships r
3419
+ JOIN entities s ON r.source_entity_id = s.id
3420
+ JOIN entities t ON r.target_entity_id = t.id
3421
+ WHERE r.source_entity_id = ? OR r.target_entity_id = ?`;
3422
+ args.push(entityId, entityId);
3423
+ }
3424
+ if (options?.type) {
3425
+ sql += ` AND r.type = ?`;
3426
+ args.push(options.type);
3427
+ }
3428
+ sql += ` ORDER BY r.weight DESC, r.timestamp DESC`;
3429
+ const result = await client.execute({ sql, args });
3430
+ return result.rows.map((row) => ({
3431
+ id: String(row.id),
3432
+ sourceEntityId: String(row.source_entity_id),
3433
+ targetEntityId: String(row.target_entity_id),
3434
+ type: String(row.type),
3435
+ weight: Number(row.weight),
3436
+ timestamp: String(row.timestamp),
3437
+ properties: JSON.parse(String(row.properties ?? "{}")),
3438
+ confidence: Number(row.confidence ?? 1),
3439
+ confidenceLabel: String(row.confidence_label ?? "extracted"),
3440
+ sourceName: String(row.source_name),
3441
+ targetName: String(row.target_name)
3442
+ }));
3443
+ }
3444
+ async function traverseChain(client, startEntityId, relationshipType, maxDepth = 3) {
3445
+ const result = await client.execute({
3446
+ sql: `WITH RECURSIVE chain(entity_id, depth, rel_type) AS (
3447
+ SELECT ?, 0, ''
3448
+ UNION ALL
3449
+ SELECT r.target_entity_id, c.depth + 1, r.type
3450
+ FROM chain c
3451
+ JOIN relationships r ON r.source_entity_id = c.entity_id
3452
+ WHERE r.type = ? AND c.depth < ?
3453
+ )
3454
+ SELECT DISTINCT e.*, chain.depth, chain.rel_type
3455
+ FROM chain
3456
+ JOIN entities e ON e.id = chain.entity_id
3457
+ ORDER BY chain.depth ASC`,
3458
+ args: [startEntityId, relationshipType, maxDepth]
3459
+ });
3460
+ return result.rows.map((row) => ({
3461
+ entity: {
3462
+ id: String(row.id),
3463
+ name: String(row.name),
3464
+ type: String(row.type),
3465
+ firstSeen: String(row.first_seen),
3466
+ lastSeen: String(row.last_seen),
3467
+ properties: JSON.parse(String(row.properties ?? "{}"))
3468
+ },
3469
+ depth: Number(row.depth),
3470
+ relationship: String(row.rel_type)
3471
+ }));
3472
+ }
3473
+ async function getEntityNeighbors(client, entityId, maxHops = 2) {
3474
+ const result = await client.execute({
3475
+ sql: `WITH RECURSIVE neighborhood(entity_id, depth, rel_type, rel_confidence) AS (
3476
+ SELECT ?, 0, '', 1.0
3477
+ UNION ALL
3478
+ SELECT CASE
3479
+ WHEN r.source_entity_id = n.entity_id THEN r.target_entity_id
3480
+ ELSE r.source_entity_id
3481
+ END, n.depth + 1, r.type, COALESCE(r.confidence, 1.0)
3482
+ FROM neighborhood n
3483
+ JOIN relationships r ON r.source_entity_id = n.entity_id
3484
+ OR r.target_entity_id = n.entity_id
3485
+ WHERE n.depth < ?
3486
+ )
3487
+ SELECT DISTINCT e.*, MIN(neighborhood.depth) as depth,
3488
+ neighborhood.rel_type, neighborhood.rel_confidence
3489
+ FROM neighborhood
3490
+ JOIN entities e ON e.id = neighborhood.entity_id
3491
+ GROUP BY e.id
3492
+ ORDER BY depth ASC, e.last_seen DESC
3493
+ LIMIT 50`,
3494
+ args: [entityId, maxHops]
3495
+ });
3496
+ return result.rows.map((row) => ({
3497
+ entity: {
3498
+ id: String(row.id),
3499
+ name: String(row.name),
3500
+ type: String(row.type),
3501
+ firstSeen: String(row.first_seen),
3502
+ lastSeen: String(row.last_seen),
3503
+ properties: JSON.parse(String(row.properties ?? "{}"))
3504
+ },
3505
+ depth: Number(row.depth),
3506
+ relType: String(row.rel_type ?? ""),
3507
+ relConfidence: Number(row.rel_confidence ?? 1)
3508
+ }));
3509
+ }
3510
+ async function getGraphStats(client) {
3511
+ const entityCount = await client.execute("SELECT COUNT(*) as cnt FROM entities");
3512
+ const relCount = await client.execute("SELECT COUNT(*) as cnt FROM relationships");
3513
+ const typeResult = await client.execute(
3514
+ "SELECT type, COUNT(*) as cnt FROM entities GROUP BY type ORDER BY cnt DESC"
3515
+ );
3516
+ const types = {};
3517
+ for (const row of typeResult.rows) {
3518
+ types[String(row.type)] = Number(row.cnt);
3099
3519
  }
3520
+ return {
3521
+ entities: Number(entityCount.rows[0]?.cnt ?? 0),
3522
+ relationships: Number(relCount.rows[0]?.cnt ?? 0),
3523
+ types
3524
+ };
3100
3525
  }
3101
- function isRerankerAvailable() {
3102
- return existsSync7(path8.join(MODELS_DIR, RERANKER_MODEL_FILE));
3526
+ async function getEntityTimeline(client, entityId, since) {
3527
+ const args = [entityId, entityId];
3528
+ let sinceClause = "";
3529
+ if (since) {
3530
+ sinceClause = " AND r.timestamp >= ?";
3531
+ args.push(since.toISOString());
3532
+ }
3533
+ const result = await client.execute({
3534
+ sql: `SELECT r.*, s.name as source_name, t.name as target_name
3535
+ FROM relationships r
3536
+ JOIN entities s ON r.source_entity_id = s.id
3537
+ JOIN entities t ON r.target_entity_id = t.id
3538
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${sinceClause}
3539
+ ORDER BY r.timestamp DESC`,
3540
+ args
3541
+ });
3542
+ return result.rows.map((row) => ({
3543
+ relationship: {
3544
+ id: String(row.id),
3545
+ sourceEntityId: String(row.source_entity_id),
3546
+ targetEntityId: String(row.target_entity_id),
3547
+ type: String(row.type),
3548
+ weight: Number(row.weight),
3549
+ timestamp: String(row.timestamp),
3550
+ properties: JSON.parse(String(row.properties ?? "{}")),
3551
+ sourceName: String(row.source_name),
3552
+ targetName: String(row.target_name)
3553
+ },
3554
+ timestamp: String(row.timestamp)
3555
+ }));
3103
3556
  }
3104
- function getRerankerModelPath() {
3105
- return path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3557
+ async function getRelationshipFrequency(client, entityId, options) {
3558
+ const granularity = options?.granularity ?? "day";
3559
+ const args = [entityId, entityId];
3560
+ let typeClause = "";
3561
+ if (options?.type) {
3562
+ typeClause = " AND r.type = ?";
3563
+ args.push(options.type);
3564
+ }
3565
+ let bucketExpr;
3566
+ switch (granularity) {
3567
+ case "week":
3568
+ bucketExpr = `strftime('%Y', r.timestamp) || '-W' || strftime('%W', r.timestamp)`;
3569
+ break;
3570
+ case "month":
3571
+ bucketExpr = `strftime('%Y-%m', r.timestamp)`;
3572
+ break;
3573
+ default:
3574
+ bucketExpr = `strftime('%Y-%m-%d', r.timestamp)`;
3575
+ }
3576
+ const result = await client.execute({
3577
+ sql: `SELECT ${bucketExpr} as bucket, COUNT(*) as cnt, r.type as rel_type
3578
+ FROM relationships r
3579
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${typeClause}
3580
+ GROUP BY bucket${options?.type ? "" : ", r.type"}
3581
+ ORDER BY bucket DESC`,
3582
+ args
3583
+ });
3584
+ return result.rows.map((row) => ({
3585
+ bucket: String(row.bucket),
3586
+ count: Number(row.cnt),
3587
+ relationshipType: options?.type ? options.type : String(row.rel_type)
3588
+ }));
3106
3589
  }
3107
- async function ensureLoaded() {
3108
- if (_rerankerContext) {
3109
- resetIdleTimer();
3110
- return;
3590
+ async function getConversationPartners(client, contactEntityId, since) {
3591
+ const args = [contactEntityId, contactEntityId];
3592
+ let sinceClause = "";
3593
+ if (since) {
3594
+ sinceClause = " AND r.timestamp >= ?";
3595
+ args.push(since.toISOString());
3111
3596
  }
3112
- const modelPath = path8.join(MODELS_DIR, RERANKER_MODEL_FILE);
3113
- if (!existsSync7(modelPath)) {
3114
- throw new Error(
3115
- `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3116
- );
3597
+ const result = await client.execute({
3598
+ sql: `SELECT
3599
+ CASE
3600
+ WHEN r.source_entity_id = ? THEN r.target_entity_id
3601
+ ELSE r.source_entity_id
3602
+ END as partner_id,
3603
+ COUNT(*) as interaction_count,
3604
+ MAX(r.timestamp) as last_interaction
3605
+ FROM relationships r
3606
+ WHERE (r.source_entity_id = ? OR r.target_entity_id = ?)${sinceClause}
3607
+ GROUP BY partner_id
3608
+ ORDER BY interaction_count DESC`,
3609
+ args: [contactEntityId, ...args]
3610
+ });
3611
+ const partners = [];
3612
+ for (const row of result.rows) {
3613
+ const partnerId = String(row.partner_id);
3614
+ const entityResult = await client.execute({
3615
+ sql: "SELECT * FROM entities WHERE id = ?",
3616
+ args: [partnerId]
3617
+ });
3618
+ if (entityResult.rows.length === 0) continue;
3619
+ const e = entityResult.rows[0];
3620
+ partners.push({
3621
+ agentEntity: {
3622
+ id: String(e.id),
3623
+ name: String(e.name),
3624
+ type: String(e.type),
3625
+ firstSeen: String(e.first_seen),
3626
+ lastSeen: String(e.last_seen),
3627
+ properties: JSON.parse(String(e.properties ?? "{}"))
3628
+ },
3629
+ interactionCount: Number(row.interaction_count),
3630
+ lastInteraction: String(row.last_interaction)
3631
+ });
3117
3632
  }
3118
- process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
3119
- const { getLlama } = await import("node-llama-cpp");
3120
- const llama = await getLlama();
3121
- _rerankerModel = await llama.loadModel({ modelPath });
3122
- _rerankerContext = await _rerankerModel.createEmbeddingContext();
3123
- process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
3124
- resetIdleTimer();
3633
+ return partners;
3125
3634
  }
3126
- async function disposeReranker() {
3127
- if (_idleTimer) {
3128
- clearTimeout(_idleTimer);
3129
- _idleTimer = null;
3635
+ async function getHotEntities(client, since, limit = 10) {
3636
+ const sinceISO = since.toISOString();
3637
+ const result = await client.execute({
3638
+ sql: `SELECT entity_id, COUNT(*) as rel_count
3639
+ FROM (
3640
+ SELECT source_entity_id as entity_id FROM relationships WHERE timestamp >= ?
3641
+ UNION ALL
3642
+ SELECT target_entity_id as entity_id FROM relationships WHERE timestamp >= ?
3643
+ )
3644
+ GROUP BY entity_id
3645
+ ORDER BY rel_count DESC
3646
+ LIMIT ?`,
3647
+ args: [sinceISO, sinceISO, limit]
3648
+ });
3649
+ const hotEntities = [];
3650
+ for (const row of result.rows) {
3651
+ const eid = String(row.entity_id);
3652
+ const entityResult = await client.execute({
3653
+ sql: "SELECT * FROM entities WHERE id = ?",
3654
+ args: [eid]
3655
+ });
3656
+ if (entityResult.rows.length === 0) continue;
3657
+ const e = entityResult.rows[0];
3658
+ hotEntities.push({
3659
+ entity: {
3660
+ id: String(e.id),
3661
+ name: String(e.name),
3662
+ type: String(e.type),
3663
+ firstSeen: String(e.first_seen),
3664
+ lastSeen: String(e.last_seen),
3665
+ properties: JSON.parse(String(e.properties ?? "{}"))
3666
+ },
3667
+ newRelationships: Number(row.rel_count)
3668
+ });
3130
3669
  }
3131
- if (_rerankerContext) {
3132
- try {
3133
- await _rerankerContext.dispose();
3134
- } catch {
3670
+ return hotEntities;
3671
+ }
3672
+ var init_graph_query = __esm({
3673
+ "src/lib/graph-query.ts"() {
3674
+ "use strict";
3675
+ }
3676
+ });
3677
+
3678
+ // src/lib/entity-boost.ts
3679
+ var entity_boost_exports = {};
3680
+ __export(entity_boost_exports, {
3681
+ applyEntityBoost: () => applyEntityBoost
3682
+ });
3683
+ function getRelTypeWeights() {
3684
+ const override = process.env.RELATIONSHIP_TYPE_WEIGHTS;
3685
+ if (!override) return DEFAULT_RELATIONSHIP_WEIGHTS;
3686
+ try {
3687
+ return { ...DEFAULT_RELATIONSHIP_WEIGHTS, ...JSON.parse(override) };
3688
+ } catch {
3689
+ return DEFAULT_RELATIONSHIP_WEIGHTS;
3690
+ }
3691
+ }
3692
+ async function matchEntities(query, client) {
3693
+ 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);
3694
+ if (words.length === 0) return [];
3695
+ try {
3696
+ const matchExpr = words.map((w) => `${w}*`).join(" OR ");
3697
+ const result = await client.execute({
3698
+ sql: `SELECT e.id, e.name FROM entities e
3699
+ JOIN entities_fts fts ON e.rowid = fts.rowid
3700
+ WHERE entities_fts MATCH ?
3701
+ ORDER BY rank
3702
+ LIMIT ?`,
3703
+ args: [matchExpr, MAX_ENTITY_MATCHES]
3704
+ });
3705
+ if (result.rows.length > 0) {
3706
+ return result.rows.map((row) => ({
3707
+ entityId: String(row.id),
3708
+ name: String(row.name)
3709
+ }));
3135
3710
  }
3136
- _rerankerContext = null;
3711
+ } catch {
3137
3712
  }
3138
- if (_rerankerModel) {
3139
- try {
3140
- await _rerankerModel.dispose();
3141
- } catch {
3713
+ const conditions = words.map(() => `LOWER(name) LIKE ?`);
3714
+ const args = words.map((w) => `%${w}%`);
3715
+ try {
3716
+ const result = await client.execute({
3717
+ sql: `SELECT id, name FROM entities
3718
+ WHERE ${conditions.join(" OR ")}
3719
+ LIMIT ?`,
3720
+ args: [...args, MAX_ENTITY_MATCHES]
3721
+ });
3722
+ return result.rows.map((row) => ({
3723
+ entityId: String(row.id),
3724
+ name: String(row.name)
3725
+ }));
3726
+ } catch {
3727
+ return [];
3728
+ }
3729
+ }
3730
+ async function getLinkedMemories(entityIds, client) {
3731
+ const linked = /* @__PURE__ */ new Map();
3732
+ if (entityIds.length === 0) return linked;
3733
+ const placeholders = entityIds.map(() => "?").join(",");
3734
+ try {
3735
+ const result = await client.execute({
3736
+ sql: `SELECT entity_id, memory_id FROM entity_memories
3737
+ WHERE entity_id IN (${placeholders})`,
3738
+ args: entityIds
3739
+ });
3740
+ for (const row of result.rows) {
3741
+ const entityId = String(row.entity_id);
3742
+ const memoryId = String(row.memory_id);
3743
+ const entry = linked.get(entityId) ?? {
3744
+ entityId,
3745
+ memoryIds: /* @__PURE__ */ new Set(),
3746
+ count: 0
3747
+ };
3748
+ entry.memoryIds.add(memoryId);
3749
+ entry.count = entry.memoryIds.size;
3750
+ linked.set(entityId, entry);
3142
3751
  }
3143
- _rerankerModel = null;
3752
+ } catch {
3144
3753
  }
3145
- process.stderr.write("[reranker] Unloaded (idle timeout).\n");
3754
+ return linked;
3146
3755
  }
3147
- async function rerankWithScores(query, texts, topK) {
3148
- if (texts.length === 0) return [];
3149
- await ensureLoaded();
3150
- const ctx = _rerankerContext;
3151
- const scored = [];
3152
- for (let i = 0; i < texts.length; i++) {
3153
- const text = texts[i] ?? "";
3756
+ function spreadAttenuation(numLinked) {
3757
+ return 1 / (1 + 1e-3 * (numLinked - 1) ** 2);
3758
+ }
3759
+ async function traverseAndScore(entities, client, boostMap, resultIds, graphContextMap) {
3760
+ const topEntities = entities.slice(0, TOP_ENTITIES_TO_TRAVERSE);
3761
+ if (topEntities.length === 0) return;
3762
+ const { getEntityNeighbors: getEntityNeighbors2 } = await Promise.resolve().then(() => (init_graph_query(), graph_query_exports));
3763
+ const relWeights = getRelTypeWeights();
3764
+ const neighborEntityIds = [];
3765
+ const neighborMeta = /* @__PURE__ */ new Map();
3766
+ for (const entity of topEntities) {
3154
3767
  try {
3155
- const input2 = `query: ${query} document: ${text.slice(0, 512)}`;
3156
- const embedding = await ctx.getEmbeddingFor(input2);
3157
- const score = embedding.vector[0] ?? 0;
3158
- scored.push({ text, score, index: i });
3768
+ const neighbors = await getEntityNeighbors2(
3769
+ client,
3770
+ entity.entityId,
3771
+ GRAPH_TRAVERSAL_MAX_HOPS
3772
+ );
3773
+ for (const n of neighbors) {
3774
+ if (n.depth === 0) continue;
3775
+ if (neighborMeta.has(n.entity.id)) continue;
3776
+ neighborEntityIds.push(n.entity.id);
3777
+ neighborMeta.set(n.entity.id, {
3778
+ depth: n.depth,
3779
+ relType: n.relType,
3780
+ relConfidence: n.relConfidence,
3781
+ sourceName: entity.name
3782
+ });
3783
+ }
3159
3784
  } catch {
3160
- scored.push({ text, score: -1, index: i });
3161
3785
  }
3162
3786
  }
3163
- scored.sort((a, b) => b.score - a.score);
3164
- return typeof topK === "number" ? scored.slice(0, topK) : scored;
3787
+ if (neighborEntityIds.length === 0) return;
3788
+ const neighborLinked = await getLinkedMemories(neighborEntityIds, client);
3789
+ for (const [entityId, entry] of neighborLinked) {
3790
+ const meta = neighborMeta.get(entityId);
3791
+ if (!meta) continue;
3792
+ const relWeight = relWeights[meta.relType] ?? DEFAULT_REL_WEIGHT;
3793
+ const depthDecay = 1 / (1 + meta.depth);
3794
+ const neighborBoost = ENTITY_BOOST_WEIGHT * meta.relConfidence * depthDecay * relWeight;
3795
+ const attenuation = spreadAttenuation(entry.count);
3796
+ const finalBoost = neighborBoost * attenuation;
3797
+ for (const memoryId of entry.memoryIds) {
3798
+ if (resultIds.has(memoryId)) {
3799
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + finalBoost);
3800
+ const contextPath = `${meta.sourceName} ${meta.relType} ${entityId}`;
3801
+ if (!graphContextMap.has(memoryId)) {
3802
+ graphContextMap.set(memoryId, contextPath);
3803
+ }
3804
+ }
3805
+ }
3806
+ }
3165
3807
  }
3166
- async function rerank(query, candidates, topK = 5) {
3167
- if (candidates.length === 0) return [];
3168
- if (candidates.length <= topK) return candidates;
3169
- const scored = await rerankWithScores(
3170
- query,
3171
- candidates.map((c) => c.raw_text),
3172
- topK
3808
+ async function applyHyperedgeBoost(entities, client, boostMap, resultIds) {
3809
+ if (entities.length < 2) return;
3810
+ const entityIds = entities.map((e) => e.entityId);
3811
+ const placeholders = entityIds.map(() => "?").join(",");
3812
+ try {
3813
+ const result = await client.execute({
3814
+ sql: `SELECT hyperedge_id, entity_id FROM hyperedge_nodes
3815
+ WHERE entity_id IN (${placeholders})`,
3816
+ args: entityIds
3817
+ });
3818
+ const hyperedgeEntities = /* @__PURE__ */ new Map();
3819
+ for (const row of result.rows) {
3820
+ const hid = String(row.hyperedge_id);
3821
+ const eid = String(row.entity_id);
3822
+ const set = hyperedgeEntities.get(hid) ?? /* @__PURE__ */ new Set();
3823
+ set.add(eid);
3824
+ hyperedgeEntities.set(hid, set);
3825
+ }
3826
+ const sharedHyperedgeIds = [];
3827
+ for (const [hid, eids] of hyperedgeEntities) {
3828
+ if (eids.size >= 2) {
3829
+ sharedHyperedgeIds.push(hid);
3830
+ }
3831
+ }
3832
+ if (sharedHyperedgeIds.length === 0) return;
3833
+ const hPlaceholders = sharedHyperedgeIds.map(() => "?").join(",");
3834
+ const allNodesResult = await client.execute({
3835
+ sql: `SELECT DISTINCT entity_id FROM hyperedge_nodes
3836
+ WHERE hyperedge_id IN (${hPlaceholders})`,
3837
+ args: sharedHyperedgeIds
3838
+ });
3839
+ const allEntityIds = allNodesResult.rows.map((r) => String(r.entity_id));
3840
+ if (allEntityIds.length === 0) return;
3841
+ const linked = await getLinkedMemories(allEntityIds, client);
3842
+ for (const [, entry] of linked) {
3843
+ const attenuation = spreadAttenuation(entry.count);
3844
+ const boost = ENTITY_BOOST_WEIGHT * attenuation;
3845
+ for (const memoryId of entry.memoryIds) {
3846
+ if (resultIds.has(memoryId)) {
3847
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + boost);
3848
+ }
3849
+ }
3850
+ }
3851
+ } catch {
3852
+ }
3853
+ }
3854
+ async function applyEntityBoost(results, query, client) {
3855
+ const emptyResult = { results, graphContext: /* @__PURE__ */ new Map() };
3856
+ if (ENTITY_BOOST_WEIGHT === 0 || results.length === 0) {
3857
+ return emptyResult;
3858
+ }
3859
+ console.time("entity-boost");
3860
+ const entities = await matchEntities(query, client);
3861
+ if (entities.length === 0) {
3862
+ console.timeEnd("entity-boost");
3863
+ return emptyResult;
3864
+ }
3865
+ const boostMap = /* @__PURE__ */ new Map();
3866
+ const resultIds = new Set(results.map((r) => r.id));
3867
+ const graphContextMap = /* @__PURE__ */ new Map();
3868
+ const directLinked = await getLinkedMemories(
3869
+ entities.map((e) => e.entityId),
3870
+ client
3173
3871
  );
3174
- return scored.map((s) => candidates[s.index]);
3872
+ for (const [, entry] of directLinked) {
3873
+ const attenuation = spreadAttenuation(entry.count);
3874
+ const boost = ENTITY_BOOST_WEIGHT * attenuation;
3875
+ for (const memoryId of entry.memoryIds) {
3876
+ if (resultIds.has(memoryId)) {
3877
+ boostMap.set(memoryId, (boostMap.get(memoryId) ?? 0) + boost);
3878
+ }
3879
+ }
3880
+ }
3881
+ await traverseAndScore(entities, client, boostMap, resultIds, graphContextMap);
3882
+ await applyHyperedgeBoost(entities, client, boostMap, resultIds);
3883
+ if (boostMap.size === 0) {
3884
+ console.timeEnd("entity-boost");
3885
+ return emptyResult;
3886
+ }
3887
+ const scored = results.map((r, i) => ({
3888
+ record: r,
3889
+ baseScore: 1 / (1 + i * 0.1),
3890
+ entityBoost: boostMap.get(r.id) ?? 0
3891
+ }));
3892
+ scored.sort(
3893
+ (a, b) => b.baseScore + b.entityBoost - (a.baseScore + a.entityBoost)
3894
+ );
3895
+ console.timeEnd("entity-boost");
3896
+ return {
3897
+ results: scored.map((s) => s.record),
3898
+ graphContext: graphContextMap
3899
+ };
3175
3900
  }
3176
- var RERANKER_MODEL_FILE, IDLE_TIMEOUT_MS, _rerankerContext, _rerankerModel, _idleTimer;
3177
- var init_reranker = __esm({
3178
- "src/lib/reranker.ts"() {
3901
+ var ENTITY_BOOST_WEIGHT, MIN_WORD_LENGTH, MAX_ENTITY_MATCHES, GRAPH_TRAVERSAL_MAX_HOPS, TOP_ENTITIES_TO_TRAVERSE, DEFAULT_RELATIONSHIP_WEIGHTS, DEFAULT_REL_WEIGHT;
3902
+ var init_entity_boost = __esm({
3903
+ "src/lib/entity-boost.ts"() {
3179
3904
  "use strict";
3180
- init_config();
3181
- RERANKER_MODEL_FILE = "jina-reranker-v3-q4_k_m.gguf";
3182
- IDLE_TIMEOUT_MS = 6e4;
3183
- _rerankerContext = null;
3184
- _rerankerModel = null;
3185
- _idleTimer = null;
3905
+ ENTITY_BOOST_WEIGHT = parseFloat(
3906
+ process.env.ENTITY_BOOST_WEIGHT ?? "0.5"
3907
+ );
3908
+ MIN_WORD_LENGTH = 3;
3909
+ MAX_ENTITY_MATCHES = 20;
3910
+ GRAPH_TRAVERSAL_MAX_HOPS = Math.min(
3911
+ parseInt(process.env.GRAPH_TRAVERSAL_MAX_HOPS ?? "2", 10),
3912
+ 3
3913
+ );
3914
+ TOP_ENTITIES_TO_TRAVERSE = 5;
3915
+ DEFAULT_RELATIONSHIP_WEIGHTS = {
3916
+ decided: 1,
3917
+ depends_on: 0.8,
3918
+ part_of: 0.7,
3919
+ related_to: 0.5,
3920
+ mentioned_in: 0.3
3921
+ };
3922
+ DEFAULT_REL_WEIGHT = 0.4;
3186
3923
  }
3187
3924
  });
3188
3925
 
@@ -3368,39 +4105,75 @@ var init_provider_table = __esm({
3368
4105
  }
3369
4106
  });
3370
4107
 
3371
- // src/lib/intercom-queue.ts
3372
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
4108
+ // src/lib/runtime-table.ts
4109
+ var RUNTIME_TABLE;
4110
+ var init_runtime_table = __esm({
4111
+ "src/lib/runtime-table.ts"() {
4112
+ "use strict";
4113
+ RUNTIME_TABLE = {
4114
+ codex: {
4115
+ binary: "codex",
4116
+ launchMode: "exec",
4117
+ autoApproveFlag: "--full-auto",
4118
+ inlineFlag: "--no-alt-screen",
4119
+ apiKeyEnv: "OPENAI_API_KEY",
4120
+ defaultModel: "gpt-5.4"
4121
+ }
4122
+ };
4123
+ }
4124
+ });
4125
+
4126
+ // src/lib/agent-config.ts
4127
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3373
4128
  import path11 from "path";
4129
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
4130
+ var init_agent_config = __esm({
4131
+ "src/lib/agent-config.ts"() {
4132
+ "use strict";
4133
+ init_config();
4134
+ init_runtime_table();
4135
+ AGENT_CONFIG_PATH = path11.join(EXE_AI_DIR, "agent-config.json");
4136
+ DEFAULT_MODELS = {
4137
+ claude: "claude-opus-4",
4138
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
4139
+ opencode: "minimax-m2.7"
4140
+ };
4141
+ }
4142
+ });
4143
+
4144
+ // src/lib/intercom-queue.ts
4145
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
4146
+ import path12 from "path";
3374
4147
  import os5 from "os";
3375
4148
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
3376
4149
  var init_intercom_queue = __esm({
3377
4150
  "src/lib/intercom-queue.ts"() {
3378
4151
  "use strict";
3379
- QUEUE_PATH = path11.join(os5.homedir(), ".exe-os", "intercom-queue.json");
4152
+ QUEUE_PATH = path12.join(os5.homedir(), ".exe-os", "intercom-queue.json");
3380
4153
  TTL_MS = 60 * 60 * 1e3;
3381
- INTERCOM_LOG = path11.join(os5.homedir(), ".exe-os", "intercom.log");
4154
+ INTERCOM_LOG = path12.join(os5.homedir(), ".exe-os", "intercom.log");
3382
4155
  }
3383
4156
  });
3384
4157
 
3385
4158
  // src/lib/license.ts
3386
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
4159
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
3387
4160
  import { randomUUID as randomUUID3 } from "crypto";
3388
- import path12 from "path";
4161
+ import path13 from "path";
3389
4162
  import { jwtVerify, importSPKI } from "jose";
3390
4163
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
3391
4164
  var init_license = __esm({
3392
4165
  "src/lib/license.ts"() {
3393
4166
  "use strict";
3394
4167
  init_config();
3395
- LICENSE_PATH = path12.join(EXE_AI_DIR, "license.key");
3396
- CACHE_PATH = path12.join(EXE_AI_DIR, "license-cache.json");
3397
- DEVICE_ID_PATH = path12.join(EXE_AI_DIR, "device-id");
4168
+ LICENSE_PATH = path13.join(EXE_AI_DIR, "license.key");
4169
+ CACHE_PATH = path13.join(EXE_AI_DIR, "license-cache.json");
4170
+ DEVICE_ID_PATH = path13.join(EXE_AI_DIR, "device-id");
3398
4171
  }
3399
4172
  });
3400
4173
 
3401
4174
  // src/lib/plan-limits.ts
3402
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3403
- import path13 from "path";
4175
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
4176
+ import path14 from "path";
3404
4177
  var CACHE_PATH2;
3405
4178
  var init_plan_limits = __esm({
3406
4179
  "src/lib/plan-limits.ts"() {
@@ -3409,13 +4182,13 @@ var init_plan_limits = __esm({
3409
4182
  init_employees();
3410
4183
  init_license();
3411
4184
  init_config();
3412
- CACHE_PATH2 = path13.join(EXE_AI_DIR, "license-cache.json");
4185
+ CACHE_PATH2 = path14.join(EXE_AI_DIR, "license-cache.json");
3413
4186
  }
3414
4187
  });
3415
4188
 
3416
4189
  // src/lib/tmux-routing.ts
3417
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync } from "fs";
3418
- import path14 from "path";
4190
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4191
+ import path15 from "path";
3419
4192
  import os6 from "os";
3420
4193
  import { fileURLToPath as fileURLToPath2 } from "url";
3421
4194
  function getMySession() {
@@ -3429,7 +4202,7 @@ function extractRootExe(name) {
3429
4202
  }
3430
4203
  function getParentExe(sessionKey) {
3431
4204
  try {
3432
- const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4205
+ const data = JSON.parse(readFileSync10(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3433
4206
  return data.parentExe || null;
3434
4207
  } catch {
3435
4208
  return null;
@@ -3458,13 +4231,15 @@ var init_tmux_routing = __esm({
3458
4231
  init_cc_agent_support();
3459
4232
  init_mcp_prefix();
3460
4233
  init_provider_table();
4234
+ init_agent_config();
4235
+ init_runtime_table();
3461
4236
  init_intercom_queue();
3462
4237
  init_plan_limits();
3463
4238
  init_employees();
3464
- SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
3465
- SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
3466
- INTERCOM_LOG2 = path14.join(os6.homedir(), ".exe-os", "intercom.log");
3467
- DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4239
+ SPAWN_LOCK_DIR = path15.join(os6.homedir(), ".exe-os", "spawn-locks");
4240
+ SESSION_CACHE = path15.join(os6.homedir(), ".exe-os", "session-cache");
4241
+ INTERCOM_LOG2 = path15.join(os6.homedir(), ".exe-os", "intercom.log");
4242
+ DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
3468
4243
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3469
4244
  }
3470
4245
  });
@@ -3498,7 +4273,7 @@ init_config();
3498
4273
  init_config();
3499
4274
  init_store();
3500
4275
  init_database();
3501
- import path15 from "path";
4276
+ import path16 from "path";
3502
4277
  import { unlinkSync as unlinkSync4 } from "fs";
3503
4278
 
3504
4279
  // src/lib/hybrid-search.ts
@@ -3545,8 +4320,14 @@ async function hybridSearch(queryText, agentId, options) {
3545
4320
  `
3546
4321
  );
3547
4322
  }
4323
+ let rerankerAvailable = false;
4324
+ try {
4325
+ const { isRerankerAvailable: isRerankerAvailable2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4326
+ rerankerAvailable = isRerankerAvailable2();
4327
+ } catch {
4328
+ }
3548
4329
  const broadFetchTopK = config.scalingRoadmap?.rerankerAutoTrigger?.fetchTopK ?? 150;
3549
- const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : Math.max(limit * 3, 30);
4330
+ const fetchLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : rerankerAvailable ? Math.max(limit * 4, 60) : Math.max(limit * 3, 30);
3550
4331
  const fetchOptions = { ...effectiveOptions, limit: fetchLimit, includeSource: false };
3551
4332
  let queryVector = null;
3552
4333
  try {
@@ -3556,7 +4337,7 @@ async function hybridSearch(queryText, agentId, options) {
3556
4337
  process.stderr.write("[hybrid-search] Embed daemon unavailable \u2014 FTS-only mode\n");
3557
4338
  }
3558
4339
  let grepPromise = Promise.resolve([]);
3559
- if (config.fileGrepEnabled !== false) {
4340
+ if (config.fileGrepEnabled === true) {
3560
4341
  try {
3561
4342
  const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
3562
4343
  const projectRoot = process.cwd();
@@ -3592,7 +4373,17 @@ async function hybridSearch(queryText, agentId, options) {
3592
4373
  if (lists.length === 0) return [];
3593
4374
  if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
3594
4375
  const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
3595
- const merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4376
+ let merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
4377
+ let graphContextMap = /* @__PURE__ */ new Map();
4378
+ if (merged.length > 0) {
4379
+ try {
4380
+ const { applyEntityBoost: applyEntityBoost2 } = await Promise.resolve().then(() => (init_entity_boost(), entity_boost_exports));
4381
+ const boosted = await applyEntityBoost2(merged, effectiveQuery, getClient());
4382
+ merged = boosted.results;
4383
+ graphContextMap = boosted.graphContext;
4384
+ } catch {
4385
+ }
4386
+ }
3596
4387
  const auto = config.scalingRoadmap?.rerankerAutoTrigger ?? {
3597
4388
  enabled: config.rerankerEnabled ?? true,
3598
4389
  broadQueryMinCardinality: 5e4,
@@ -3600,20 +4391,29 @@ async function hybridSearch(queryText, agentId, options) {
3600
4391
  returnTopK: 5
3601
4392
  };
3602
4393
  let rerankedAndBlended = null;
3603
- if (effectiveIsBroad && auto.enabled) {
4394
+ if (effectiveIsBroad && auto.enabled && rerankerAvailable) {
3604
4395
  const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
3605
4396
  if (cardinality2 > auto.broadQueryMinCardinality) {
3606
4397
  try {
3607
- const { isRerankerAvailable: isRerankerAvailable2, rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
3608
- if (isRerankerAvailable2()) {
3609
- const reranked = await rerank2(effectiveQuery, merged, auto.returnTopK);
3610
- if (reranked.length > 0) {
3611
- rerankedAndBlended = rrfMergeMulti(
3612
- [reranked],
3613
- auto.returnTopK,
3614
- RRF_K
3615
- );
3616
- }
4398
+ let rerankedRecords;
4399
+ if (graphContextMap.size > 0) {
4400
+ const { rerankWithContext: rerankWithContext2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4401
+ const candidates = merged.map((m) => ({
4402
+ text: m.raw_text,
4403
+ context: graphContextMap.get(m.id)
4404
+ }));
4405
+ const scored = await rerankWithContext2(effectiveQuery, candidates, auto.returnTopK);
4406
+ rerankedRecords = scored.map((s) => merged[s.index]);
4407
+ } else {
4408
+ const { rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
4409
+ rerankedRecords = await rerank2(effectiveQuery, merged, auto.returnTopK);
4410
+ }
4411
+ if (rerankedRecords.length > 0) {
4412
+ rerankedAndBlended = rrfMergeMulti(
4413
+ [rerankedRecords],
4414
+ auto.returnTopK,
4415
+ RRF_K
4416
+ );
3617
4417
  }
3618
4418
  } catch {
3619
4419
  }
@@ -3633,7 +4433,7 @@ async function hybridSearch(queryText, agentId, options) {
3633
4433
  try {
3634
4434
  const client = getClient();
3635
4435
  void client.execute({
3636
- sql: `UPDATE memories SET last_accessed = ? WHERE id IN (${placeholders})`,
4436
+ sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1 WHERE id IN (${placeholders})`,
3637
4437
  args: [now, ...ids]
3638
4438
  }).catch(() => {
3639
4439
  });
@@ -3803,7 +4603,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
3803
4603
  source_type: row.source_type ?? null
3804
4604
  }));
3805
4605
  }
3806
- async function recentRecords(agentId, options, limit) {
4606
+ async function recentRecords(agentId, options, limit, textFilter) {
3807
4607
  const client = getClient();
3808
4608
  const statusFilter = options?.includeArchived ? "" : `
3809
4609
  AND COALESCE(status, 'active') = 'active'`;
@@ -3843,6 +4643,10 @@ async function recentRecords(agentId, options, limit) {
3843
4643
  sql += ` AND memory_type = ?`;
3844
4644
  args.push(options.memoryType);
3845
4645
  }
4646
+ if (textFilter) {
4647
+ sql += ` AND raw_text LIKE '%' || ? || '%'`;
4648
+ args.push(textFilter);
4649
+ }
3846
4650
  sql += ` ORDER BY timestamp DESC LIMIT ?`;
3847
4651
  args.push(limit);
3848
4652
  const result = await client.execute({ sql, args });
@@ -4083,8 +4887,8 @@ process.stdin.on("end", async () => {
4083
4887
  const source = data.source ?? "startup";
4084
4888
  if (source === "startup") {
4085
4889
  try {
4086
- const undefinedPath = path15.join(
4087
- process.env.EXE_OS_DIR ?? path15.join(process.env.HOME ?? "", ".exe-os"),
4890
+ const undefinedPath = path16.join(
4891
+ process.env.EXE_OS_DIR ?? path16.join(process.env.HOME ?? "", ".exe-os"),
4088
4892
  "session-cache",
4089
4893
  "active-agent-undefined.json"
4090
4894
  );
@@ -4097,6 +4901,19 @@ process.stdin.on("end", async () => {
4097
4901
  const search = config.hookSearchMode === "hybrid" ? hybridSearch : lightweightSearch;
4098
4902
  const agent = getActiveAgent();
4099
4903
  const agentId = agent.agentId;
4904
+ try {
4905
+ const sessionId = data.session_id;
4906
+ if (sessionId && agentId !== "default") {
4907
+ const client = getClient();
4908
+ const sessionName = process.env.EXE_SESSION_NAME ?? "";
4909
+ await client.execute({
4910
+ sql: `INSERT OR REPLACE INTO session_agent_map (session_uuid, agent_id, session_name, project_name, started_at)
4911
+ VALUES (?, ?, ?, ?, ?)`,
4912
+ args: [sessionId, agentId, sessionName, projectName, (/* @__PURE__ */ new Date()).toISOString()]
4913
+ });
4914
+ }
4915
+ } catch {
4916
+ }
4100
4917
  let query;
4101
4918
  let header;
4102
4919
  if (source === "resume") {