@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.
- package/dist/bin/backfill-conversations.js +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- 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
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
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
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
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
|
|
2381
|
-
import
|
|
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
|
|
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(
|
|
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 (
|
|
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 =
|
|
2431
|
-
const { root } =
|
|
2720
|
+
let dir = path6.dirname(fileURLToPath(import.meta.url));
|
|
2721
|
+
const { root } = path6.parse(dir);
|
|
2432
2722
|
while (dir !== root) {
|
|
2433
|
-
if (
|
|
2434
|
-
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 =
|
|
2445
|
-
if (!
|
|
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 =
|
|
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
|
-
},
|
|
2872
|
+
}, timeoutMs);
|
|
2580
2873
|
_pending.set(id, { resolve, timer });
|
|
2581
2874
|
try {
|
|
2582
|
-
_socket.write(JSON.stringify({ id,
|
|
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
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
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 (
|
|
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 ??
|
|
2706
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
2707
|
-
SPAWN_LOCK_PATH =
|
|
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
|
|
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 =
|
|
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 =
|
|
3092
|
+
_cached = path7.basename(repoRoot);
|
|
2819
3093
|
_cachedCwd = dir;
|
|
2820
3094
|
return _cached;
|
|
2821
3095
|
} catch {
|
|
2822
|
-
_cached =
|
|
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
|
|
2847
|
-
import
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3268
|
+
const rel = path8.join(relative, entry.name);
|
|
2995
3269
|
if (entry.isDirectory()) {
|
|
2996
|
-
walk(
|
|
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 =
|
|
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 =
|
|
3037
|
-
if (!
|
|
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/
|
|
3064
|
-
var
|
|
3065
|
-
__export(
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
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
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
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
|
|
3084
|
-
|
|
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
|
|
3087
|
-
|
|
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
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
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
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
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
|
-
|
|
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
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
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
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
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
|
-
|
|
3693
|
+
} catch {
|
|
3119
3694
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
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
|
-
|
|
3734
|
+
} catch {
|
|
3126
3735
|
}
|
|
3127
|
-
|
|
3736
|
+
return linked;
|
|
3128
3737
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
const
|
|
3134
|
-
|
|
3135
|
-
|
|
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
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
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
|
-
|
|
3146
|
-
|
|
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
|
|
3149
|
-
if (
|
|
3150
|
-
|
|
3151
|
-
const
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
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
|
-
|
|
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
|
|
3159
|
-
var
|
|
3160
|
-
"src/lib/
|
|
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
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3385
|
-
if (
|
|
3386
|
-
const
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
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 =
|
|
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
|
});
|