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