@askexenow/exe-os 0.8.82 → 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 +14360 -12525
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1260 -323
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +32 -9
- package/dist/bin/exe-dispatch.js +212 -36
- 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 +553 -174
- 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 +41 -11
- 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 +577 -33
- 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 +901 -209
- 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 +906 -213
- package/dist/bin/setup.js +870 -271
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +4 -3
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +550 -168
- package/dist/hooks/bug-report-worker.js +210 -25
- package/dist/hooks/commit-complete.js +899 -207
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1639 -1195
- 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 +899 -207
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +245 -104
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +245 -104
- 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 +1970 -1336
- package/dist/index.js +1653 -1055
- 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 +1957 -924
- 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/license.js +3 -3
- 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 +113 -24
- package/dist/lib/tmux-routing.js +122 -33
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +10874 -5546
- 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 +810 -27
- 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 +913 -221
- package/dist/tui/App.js +1000 -298
- package/package.json +6 -1
- package/src/commands/exe/build-adv.md +2 -2
package/dist/bin/exe-doctor.js
CHANGED
|
@@ -311,6 +311,12 @@ function getClient() {
|
|
|
311
311
|
if (!_resilientClient) {
|
|
312
312
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
313
313
|
}
|
|
314
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
315
|
+
return _resilientClient;
|
|
316
|
+
}
|
|
317
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
318
|
+
return _daemonClient;
|
|
319
|
+
}
|
|
314
320
|
return _resilientClient;
|
|
315
321
|
}
|
|
316
322
|
function getRawClient() {
|
|
@@ -799,6 +805,12 @@ async function ensureSchema() {
|
|
|
799
805
|
} catch {
|
|
800
806
|
}
|
|
801
807
|
}
|
|
808
|
+
try {
|
|
809
|
+
await client.execute(
|
|
810
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
811
|
+
);
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
802
814
|
await client.executeMultiple(`
|
|
803
815
|
CREATE TABLE IF NOT EXISTS entities (
|
|
804
816
|
id TEXT PRIMARY KEY,
|
|
@@ -851,7 +863,30 @@ async function ensureSchema() {
|
|
|
851
863
|
entity_id TEXT NOT NULL,
|
|
852
864
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
853
865
|
);
|
|
866
|
+
|
|
867
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
868
|
+
name,
|
|
869
|
+
content=entities,
|
|
870
|
+
content_rowid=rowid
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
874
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
875
|
+
END;
|
|
876
|
+
|
|
877
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
878
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
879
|
+
END;
|
|
880
|
+
|
|
881
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
882
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
883
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
884
|
+
END;
|
|
854
885
|
`);
|
|
886
|
+
try {
|
|
887
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
888
|
+
} catch {
|
|
889
|
+
}
|
|
855
890
|
await client.executeMultiple(`
|
|
856
891
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
857
892
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1032,6 +1067,33 @@ async function ensureSchema() {
|
|
|
1032
1067
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1033
1068
|
ON conversations(channel_id);
|
|
1034
1069
|
`);
|
|
1070
|
+
await client.executeMultiple(`
|
|
1071
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1072
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1073
|
+
agent_id TEXT NOT NULL,
|
|
1074
|
+
session_name TEXT,
|
|
1075
|
+
task_id TEXT,
|
|
1076
|
+
project_name TEXT,
|
|
1077
|
+
started_at TEXT NOT NULL
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1081
|
+
ON session_agent_map(agent_id);
|
|
1082
|
+
`);
|
|
1083
|
+
try {
|
|
1084
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1085
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1086
|
+
await client.execute({
|
|
1087
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1088
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1089
|
+
FROM memories
|
|
1090
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1091
|
+
GROUP BY session_id, agent_id`,
|
|
1092
|
+
args: []
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
} catch {
|
|
1096
|
+
}
|
|
1035
1097
|
try {
|
|
1036
1098
|
await client.execute({
|
|
1037
1099
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1165,8 +1227,30 @@ async function ensureSchema() {
|
|
|
1165
1227
|
});
|
|
1166
1228
|
} catch {
|
|
1167
1229
|
}
|
|
1230
|
+
for (const col of [
|
|
1231
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1232
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1233
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1234
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1235
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1236
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1237
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1238
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1239
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1240
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1241
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1242
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1243
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1244
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1245
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1246
|
+
]) {
|
|
1247
|
+
try {
|
|
1248
|
+
await client.execute(col);
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1168
1252
|
}
|
|
1169
|
-
var _client, _resilientClient, initTurso;
|
|
1253
|
+
var _client, _resilientClient, _daemonClient, initTurso;
|
|
1170
1254
|
var init_database = __esm({
|
|
1171
1255
|
"src/lib/database.ts"() {
|
|
1172
1256
|
"use strict";
|
|
@@ -1174,6 +1258,7 @@ var init_database = __esm({
|
|
|
1174
1258
|
init_employees();
|
|
1175
1259
|
_client = null;
|
|
1176
1260
|
_resilientClient = null;
|
|
1261
|
+
_daemonClient = null;
|
|
1177
1262
|
initTurso = initDatabase;
|
|
1178
1263
|
}
|
|
1179
1264
|
});
|
|
@@ -1715,6 +1800,7 @@ var init_worker_gate = __esm({
|
|
|
1715
1800
|
});
|
|
1716
1801
|
|
|
1717
1802
|
// src/lib/store.ts
|
|
1803
|
+
import { createHash } from "crypto";
|
|
1718
1804
|
init_database();
|
|
1719
1805
|
|
|
1720
1806
|
// src/lib/keychain.ts
|
|
@@ -1750,12 +1836,20 @@ async function getMasterKey() {
|
|
|
1750
1836
|
}
|
|
1751
1837
|
const keyPath = getKeyPath();
|
|
1752
1838
|
if (!existsSync3(keyPath)) {
|
|
1839
|
+
process.stderr.write(
|
|
1840
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
1841
|
+
`
|
|
1842
|
+
);
|
|
1753
1843
|
return null;
|
|
1754
1844
|
}
|
|
1755
1845
|
try {
|
|
1756
1846
|
const content = await readFile3(keyPath, "utf-8");
|
|
1757
1847
|
return Buffer.from(content.trim(), "base64");
|
|
1758
|
-
} catch {
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
process.stderr.write(
|
|
1850
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
1851
|
+
`
|
|
1852
|
+
);
|
|
1759
1853
|
return null;
|
|
1760
1854
|
}
|
|
1761
1855
|
}
|
|
@@ -1898,6 +1992,7 @@ import { realpathSync } from "fs";
|
|
|
1898
1992
|
import { fileURLToPath } from "url";
|
|
1899
1993
|
function isMainModule(importMetaUrl) {
|
|
1900
1994
|
if (process.argv[1] == null) return false;
|
|
1995
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
1901
1996
|
try {
|
|
1902
1997
|
const scriptPath = realpathSync(process.argv[1]);
|
|
1903
1998
|
const modulePath = realpathSync(fileURLToPath(importMetaUrl));
|
|
@@ -1912,6 +2007,268 @@ import { existsSync as existsSync6 } from "fs";
|
|
|
1912
2007
|
import { spawn } from "child_process";
|
|
1913
2008
|
import path6 from "path";
|
|
1914
2009
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2010
|
+
|
|
2011
|
+
// src/lib/conflict-detector.ts
|
|
2012
|
+
var DEFAULT_MAX_PAIRS = 100;
|
|
2013
|
+
var SIM_LOW = 0.85;
|
|
2014
|
+
var SIM_HIGH = 0.95;
|
|
2015
|
+
var LLM_BATCH_SIZE = 15;
|
|
2016
|
+
function cosineSimilarity(a, b) {
|
|
2017
|
+
let dot = 0, normA = 0, normB = 0;
|
|
2018
|
+
for (let i = 0; i < a.length; i++) {
|
|
2019
|
+
dot += a[i] * b[i];
|
|
2020
|
+
normA += a[i] * a[i];
|
|
2021
|
+
normB += b[i] * b[i];
|
|
2022
|
+
}
|
|
2023
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
2024
|
+
return denom === 0 ? 0 : dot / denom;
|
|
2025
|
+
}
|
|
2026
|
+
async function fetchMemoriesWithVectors(client, projectFilter, agentFilter2) {
|
|
2027
|
+
let hasStatusCol = false;
|
|
2028
|
+
try {
|
|
2029
|
+
const info = await client.execute("PRAGMA table_info(memories)");
|
|
2030
|
+
hasStatusCol = info.rows.some((r) => r.name === "status");
|
|
2031
|
+
} catch {
|
|
2032
|
+
}
|
|
2033
|
+
const clauses = ["vector IS NOT NULL"];
|
|
2034
|
+
if (hasStatusCol) clauses.push("status = 'active'");
|
|
2035
|
+
const args = [];
|
|
2036
|
+
if (projectFilter) {
|
|
2037
|
+
clauses.push("project_name = ?");
|
|
2038
|
+
args.push(projectFilter);
|
|
2039
|
+
}
|
|
2040
|
+
if (agentFilter2) {
|
|
2041
|
+
clauses.push("agent_id = ?");
|
|
2042
|
+
args.push(agentFilter2);
|
|
2043
|
+
}
|
|
2044
|
+
const where = " WHERE " + clauses.join(" AND ");
|
|
2045
|
+
const result = await client.execute({
|
|
2046
|
+
sql: `SELECT id, agent_id, raw_text, timestamp, project_name, vector
|
|
2047
|
+
FROM memories${where}
|
|
2048
|
+
ORDER BY project_name, timestamp DESC`,
|
|
2049
|
+
args
|
|
2050
|
+
});
|
|
2051
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
2052
|
+
for (const row of result.rows) {
|
|
2053
|
+
const project = row.project_name;
|
|
2054
|
+
const vec = row.vector == null ? null : Array.isArray(row.vector) ? row.vector : Array.from(row.vector);
|
|
2055
|
+
if (!vec || vec.length === 0) continue;
|
|
2056
|
+
const mem = {
|
|
2057
|
+
id: row.id,
|
|
2058
|
+
agent_id: row.agent_id,
|
|
2059
|
+
raw_text: row.raw_text,
|
|
2060
|
+
timestamp: row.timestamp,
|
|
2061
|
+
vector: vec
|
|
2062
|
+
};
|
|
2063
|
+
const list = byProject.get(project);
|
|
2064
|
+
if (list) list.push(mem);
|
|
2065
|
+
else byProject.set(project, [mem]);
|
|
2066
|
+
}
|
|
2067
|
+
return byProject;
|
|
2068
|
+
}
|
|
2069
|
+
function findCandidatePairs(memories, maxPairs) {
|
|
2070
|
+
const pairs = [];
|
|
2071
|
+
for (let i = 0; i < memories.length && pairs.length < maxPairs; i++) {
|
|
2072
|
+
for (let j = i + 1; j < memories.length && pairs.length < maxPairs; j++) {
|
|
2073
|
+
const sim = cosineSimilarity(memories[i].vector, memories[j].vector);
|
|
2074
|
+
if (sim >= SIM_LOW && sim <= SIM_HIGH) {
|
|
2075
|
+
pairs.push({ a: memories[i], b: memories[j], similarity: sim });
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
pairs.sort((x, y) => y.similarity - x.similarity);
|
|
2080
|
+
return pairs;
|
|
2081
|
+
}
|
|
2082
|
+
async function classifyPairsBatch(pairs) {
|
|
2083
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
2084
|
+
const client = new Anthropic();
|
|
2085
|
+
const pairDescriptions = pairs.map(
|
|
2086
|
+
(p, i) => `Pair ${i + 1}:
|
|
2087
|
+
Memory A (${p.a.agent_id}, ${p.a.timestamp}): "${p.a.raw_text.slice(0, 300)}"
|
|
2088
|
+
Memory B (${p.b.agent_id}, ${p.b.timestamp}): "${p.b.raw_text.slice(0, 300)}"`
|
|
2089
|
+
).join("\n\n");
|
|
2090
|
+
const prompt = `You are classifying pairs of memory records for contradictions.
|
|
2091
|
+
|
|
2092
|
+
For each pair, classify as:
|
|
2093
|
+
- AGREE: Both memories say the same thing (no action needed).
|
|
2094
|
+
- CONFLICT: The memories contradict each other (one must be wrong).
|
|
2095
|
+
- SUPERSEDED: One memory replaces/updates the other (the older one is outdated).
|
|
2096
|
+
|
|
2097
|
+
Respond with a JSON array. Each element: {"pair": <1-indexed>, "classification": "AGREE"|"CONFLICT"|"SUPERSEDED", "explanation": "<brief reason>"}
|
|
2098
|
+
|
|
2099
|
+
${pairDescriptions}
|
|
2100
|
+
|
|
2101
|
+
Respond ONLY with the JSON array, no markdown fences.`;
|
|
2102
|
+
const response = await client.messages.create({
|
|
2103
|
+
model: "claude-haiku-4-5-20251001",
|
|
2104
|
+
max_tokens: 1024,
|
|
2105
|
+
messages: [{ role: "user", content: prompt }]
|
|
2106
|
+
});
|
|
2107
|
+
const text = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
2108
|
+
try {
|
|
2109
|
+
const parsed = JSON.parse(text);
|
|
2110
|
+
return parsed.map((r) => ({
|
|
2111
|
+
classification: ["AGREE", "CONFLICT", "SUPERSEDED"].includes(
|
|
2112
|
+
r.classification
|
|
2113
|
+
) ? r.classification : "AGREE",
|
|
2114
|
+
explanation: r.explanation ?? ""
|
|
2115
|
+
}));
|
|
2116
|
+
} catch {
|
|
2117
|
+
return pairs.map(() => ({
|
|
2118
|
+
classification: "AGREE",
|
|
2119
|
+
explanation: "LLM response parse error"
|
|
2120
|
+
}));
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
function recommend(classification) {
|
|
2124
|
+
switch (classification) {
|
|
2125
|
+
case "CONFLICT":
|
|
2126
|
+
return "manual_review";
|
|
2127
|
+
case "SUPERSEDED":
|
|
2128
|
+
return "deactivate_older";
|
|
2129
|
+
case "AGREE":
|
|
2130
|
+
return "merge";
|
|
2131
|
+
case "UNCLASSIFIED":
|
|
2132
|
+
return "none";
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
async function detectConflicts(client, projectFilter, agentFilter2) {
|
|
2136
|
+
const maxPairs = parseInt(
|
|
2137
|
+
process.env.CONFLICT_MAX_PAIRS ?? String(DEFAULT_MAX_PAIRS),
|
|
2138
|
+
10
|
|
2139
|
+
);
|
|
2140
|
+
const byProject = await fetchMemoriesWithVectors(
|
|
2141
|
+
client,
|
|
2142
|
+
projectFilter,
|
|
2143
|
+
agentFilter2
|
|
2144
|
+
);
|
|
2145
|
+
const allCandidates = [];
|
|
2146
|
+
for (const [, memories] of byProject) {
|
|
2147
|
+
if (memories.length < 2) continue;
|
|
2148
|
+
const candidates2 = findCandidatePairs(
|
|
2149
|
+
memories,
|
|
2150
|
+
maxPairs - allCandidates.length
|
|
2151
|
+
);
|
|
2152
|
+
allCandidates.push(...candidates2);
|
|
2153
|
+
if (allCandidates.length >= maxPairs) break;
|
|
2154
|
+
}
|
|
2155
|
+
if (allCandidates.length === 0) {
|
|
2156
|
+
return {
|
|
2157
|
+
candidateCount: 0,
|
|
2158
|
+
classified: 0,
|
|
2159
|
+
conflicts: 0,
|
|
2160
|
+
superseded: 0,
|
|
2161
|
+
agree: 0,
|
|
2162
|
+
unclassified: 0,
|
|
2163
|
+
pairs: [],
|
|
2164
|
+
skipped: false
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
const truncated = allCandidates.length > maxPairs;
|
|
2168
|
+
const candidates = allCandidates.slice(0, maxPairs);
|
|
2169
|
+
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
2170
|
+
const pairs = [];
|
|
2171
|
+
if (hasApiKey) {
|
|
2172
|
+
for (let i = 0; i < candidates.length; i += LLM_BATCH_SIZE) {
|
|
2173
|
+
const batch = candidates.slice(i, i + LLM_BATCH_SIZE);
|
|
2174
|
+
try {
|
|
2175
|
+
const results = await classifyPairsBatch(batch);
|
|
2176
|
+
for (let j = 0; j < batch.length; j++) {
|
|
2177
|
+
const c = batch[j];
|
|
2178
|
+
const r = results[j] ?? {
|
|
2179
|
+
classification: "AGREE",
|
|
2180
|
+
explanation: "No result"
|
|
2181
|
+
};
|
|
2182
|
+
pairs.push({
|
|
2183
|
+
memoryA: {
|
|
2184
|
+
id: c.a.id,
|
|
2185
|
+
agentId: c.a.agent_id,
|
|
2186
|
+
text: c.a.raw_text,
|
|
2187
|
+
timestamp: c.a.timestamp
|
|
2188
|
+
},
|
|
2189
|
+
memoryB: {
|
|
2190
|
+
id: c.b.id,
|
|
2191
|
+
agentId: c.b.agent_id,
|
|
2192
|
+
text: c.b.raw_text,
|
|
2193
|
+
timestamp: c.b.timestamp
|
|
2194
|
+
},
|
|
2195
|
+
similarity: c.similarity,
|
|
2196
|
+
classification: r.classification,
|
|
2197
|
+
explanation: r.explanation,
|
|
2198
|
+
recommendation: recommend(r.classification)
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
} catch (err) {
|
|
2202
|
+
for (const c of batch) {
|
|
2203
|
+
pairs.push({
|
|
2204
|
+
memoryA: {
|
|
2205
|
+
id: c.a.id,
|
|
2206
|
+
agentId: c.a.agent_id,
|
|
2207
|
+
text: c.a.raw_text,
|
|
2208
|
+
timestamp: c.a.timestamp
|
|
2209
|
+
},
|
|
2210
|
+
memoryB: {
|
|
2211
|
+
id: c.b.id,
|
|
2212
|
+
agentId: c.b.agent_id,
|
|
2213
|
+
text: c.b.raw_text,
|
|
2214
|
+
timestamp: c.b.timestamp
|
|
2215
|
+
},
|
|
2216
|
+
similarity: c.similarity,
|
|
2217
|
+
classification: "UNCLASSIFIED",
|
|
2218
|
+
explanation: `LLM error: ${err instanceof Error ? err.message : String(err)}`,
|
|
2219
|
+
recommendation: "none"
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
} else {
|
|
2225
|
+
for (const c of candidates) {
|
|
2226
|
+
pairs.push({
|
|
2227
|
+
memoryA: {
|
|
2228
|
+
id: c.a.id,
|
|
2229
|
+
agentId: c.a.agent_id,
|
|
2230
|
+
text: c.a.raw_text,
|
|
2231
|
+
timestamp: c.a.timestamp
|
|
2232
|
+
},
|
|
2233
|
+
memoryB: {
|
|
2234
|
+
id: c.b.id,
|
|
2235
|
+
agentId: c.b.agent_id,
|
|
2236
|
+
text: c.b.raw_text,
|
|
2237
|
+
timestamp: c.b.timestamp
|
|
2238
|
+
},
|
|
2239
|
+
similarity: c.similarity,
|
|
2240
|
+
classification: "UNCLASSIFIED",
|
|
2241
|
+
explanation: "ANTHROPIC_API_KEY not set \u2014 skipped LLM classification",
|
|
2242
|
+
recommendation: "none"
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
const severityOrder = { CONFLICT: 0, SUPERSEDED: 1, UNCLASSIFIED: 2, AGREE: 3 };
|
|
2247
|
+
pairs.sort(
|
|
2248
|
+
(a, b) => severityOrder[a.classification] - severityOrder[b.classification]
|
|
2249
|
+
);
|
|
2250
|
+
const conflicts = pairs.filter((p) => p.classification === "CONFLICT").length;
|
|
2251
|
+
const superseded = pairs.filter(
|
|
2252
|
+
(p) => p.classification === "SUPERSEDED"
|
|
2253
|
+
).length;
|
|
2254
|
+
const agree = pairs.filter((p) => p.classification === "AGREE").length;
|
|
2255
|
+
const unclassified = pairs.filter(
|
|
2256
|
+
(p) => p.classification === "UNCLASSIFIED"
|
|
2257
|
+
).length;
|
|
2258
|
+
return {
|
|
2259
|
+
candidateCount: candidates.length,
|
|
2260
|
+
classified: conflicts + superseded + agree,
|
|
2261
|
+
conflicts,
|
|
2262
|
+
superseded,
|
|
2263
|
+
agree,
|
|
2264
|
+
unclassified,
|
|
2265
|
+
pairs,
|
|
2266
|
+
skipped: truncated,
|
|
2267
|
+
skipReason: truncated ? `Exceeded max ${maxPairs} pairs \u2014 increase CONFLICT_MAX_PAIRS to analyze more` : void 0
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/bin/exe-doctor.ts
|
|
1915
2272
|
function parseFlags(argv) {
|
|
1916
2273
|
const flags = { fix: false, dryRun: false, verbose: false };
|
|
1917
2274
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2059,16 +2416,17 @@ async function auditOrphanedProjects(client) {
|
|
|
2059
2416
|
return orphans;
|
|
2060
2417
|
}
|
|
2061
2418
|
async function runAudit(client, flags) {
|
|
2062
|
-
const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects] = await Promise.all([
|
|
2419
|
+
const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, conflicts] = await Promise.all([
|
|
2063
2420
|
auditStats(client, flags),
|
|
2064
2421
|
auditNullVectors(client, flags),
|
|
2065
2422
|
auditDuplicates(client, flags),
|
|
2066
2423
|
auditBloated(client, flags),
|
|
2067
2424
|
auditFts(client),
|
|
2068
|
-
auditOrphanedProjects(client)
|
|
2425
|
+
auditOrphanedProjects(client),
|
|
2426
|
+
detectConflicts(client, flags.project, flags.agent)
|
|
2069
2427
|
]);
|
|
2070
2428
|
const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
|
|
2071
|
-
return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects };
|
|
2429
|
+
return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts };
|
|
2072
2430
|
}
|
|
2073
2431
|
function indicator(value, warn) {
|
|
2074
2432
|
if (value === 0) return "\u{1F7E2}";
|
|
@@ -2114,6 +2472,22 @@ function formatReport(report, flags) {
|
|
|
2114
2472
|
const orphanList = report.orphanedProjects.map((o) => `${o.project_name} \u2014 ${o.count} memories`).join(", ");
|
|
2115
2473
|
lines.push(`\u2139\uFE0F Orphaned projects: ${report.orphanedProjects.length} (${orphanList})`);
|
|
2116
2474
|
}
|
|
2475
|
+
const cr = report.conflicts;
|
|
2476
|
+
if (cr.candidateCount > 0) {
|
|
2477
|
+
const conflictIndicator = cr.conflicts > 0 ? "\u{1F534}" : cr.superseded > 0 ? "\u{1F7E0}" : "\u{1F7E2}";
|
|
2478
|
+
lines.push(`${conflictIndicator} Conflicts: ${fmtNum(cr.candidateCount)} candidate pairs`);
|
|
2479
|
+
if (cr.classified > 0) {
|
|
2480
|
+
lines.push(` CONFLICT: ${cr.conflicts}, SUPERSEDED: ${cr.superseded}, AGREE: ${cr.agree}`);
|
|
2481
|
+
}
|
|
2482
|
+
if (cr.unclassified > 0) {
|
|
2483
|
+
lines.push(` Unclassified: ${cr.unclassified}`);
|
|
2484
|
+
}
|
|
2485
|
+
if (cr.skipped) {
|
|
2486
|
+
lines.push(` \u26A0\uFE0F ${cr.skipReason}`);
|
|
2487
|
+
}
|
|
2488
|
+
} else {
|
|
2489
|
+
lines.push("\u{1F7E2} Conflicts: none detected");
|
|
2490
|
+
}
|
|
2117
2491
|
lines.push("");
|
|
2118
2492
|
if (flags.verbose) {
|
|
2119
2493
|
if (report.duplicates.length > 0) {
|
|
@@ -2130,6 +2504,23 @@ function formatReport(report, flags) {
|
|
|
2130
2504
|
}
|
|
2131
2505
|
lines.push("");
|
|
2132
2506
|
}
|
|
2507
|
+
const actionable = report.conflicts.pairs.filter(
|
|
2508
|
+
(p) => p.classification === "CONFLICT" || p.classification === "SUPERSEDED"
|
|
2509
|
+
);
|
|
2510
|
+
if (actionable.length > 0) {
|
|
2511
|
+
lines.push("Conflict Detection");
|
|
2512
|
+
lines.push("==================");
|
|
2513
|
+
for (const p of actionable.slice(0, 10)) {
|
|
2514
|
+
const aShort = p.memoryA.text.length > 60 ? p.memoryA.text.slice(0, 60) + "..." : p.memoryA.text;
|
|
2515
|
+
const bShort = p.memoryB.text.length > 60 ? p.memoryB.text.slice(0, 60) + "..." : p.memoryB.text;
|
|
2516
|
+
lines.push(
|
|
2517
|
+
` [${p.classification}] ${p.memoryA.id.slice(0, 8)} (${p.memoryA.agentId}, ${p.memoryA.timestamp.slice(0, 10)}) vs ${p.memoryB.id.slice(0, 8)} (${p.memoryB.agentId}, ${p.memoryB.timestamp.slice(0, 10)})`
|
|
2518
|
+
);
|
|
2519
|
+
lines.push(` "${aShort}" vs "${bShort}"`);
|
|
2520
|
+
lines.push(` ${p.explanation} \u2192 Recommendation: ${p.recommendation}`);
|
|
2521
|
+
}
|
|
2522
|
+
lines.push("");
|
|
2523
|
+
}
|
|
2133
2524
|
}
|
|
2134
2525
|
const recs = [];
|
|
2135
2526
|
if (report.nullVectors > 0) {
|
|
@@ -2148,6 +2539,12 @@ function formatReport(report, flags) {
|
|
|
2148
2539
|
const names = report.orphanedProjects.map((o) => `"${o.project_name}"`).join(", ");
|
|
2149
2540
|
recs.push(`Consider /exe-forget for orphaned project${report.orphanedProjects.length > 1 ? "s" : ""} ${names}`);
|
|
2150
2541
|
}
|
|
2542
|
+
if (report.conflicts.conflicts > 0) {
|
|
2543
|
+
recs.push(`${fmtNum(report.conflicts.conflicts)} conflicting memory pairs need manual review`);
|
|
2544
|
+
}
|
|
2545
|
+
if (report.conflicts.superseded > 0) {
|
|
2546
|
+
recs.push(`${fmtNum(report.conflicts.superseded)} superseded memories can be deactivated`);
|
|
2547
|
+
}
|
|
2151
2548
|
if (recs.length > 0) {
|
|
2152
2549
|
lines.push("Recommendations:");
|
|
2153
2550
|
for (const r of recs) {
|
|
@@ -2314,7 +2711,7 @@ ${mode} Complete.`);
|
|
|
2314
2711
|
}
|
|
2315
2712
|
return report;
|
|
2316
2713
|
}
|
|
2317
|
-
if (isMainModule(import.meta.url)) {
|
|
2714
|
+
if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("exe-doctor")) {
|
|
2318
2715
|
main().catch((err) => {
|
|
2319
2716
|
console.error(err instanceof Error ? err.message : String(err));
|
|
2320
2717
|
process.exit(1);
|