@askexenow/exe-os 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +178 -79
  2. package/package.json +1 -1
  3. package/dist/bin/backfill-responses.js +0 -1912
  4. package/dist/bin/backfill-vectors.js +0 -1642
  5. package/dist/bin/cleanup-stale-review-tasks.js +0 -1339
  6. package/dist/bin/cli.js +0 -18800
  7. package/dist/bin/exe-agent.js +0 -1858
  8. package/dist/bin/exe-assign.js +0 -1957
  9. package/dist/bin/exe-boot.js +0 -6460
  10. package/dist/bin/exe-call.js +0 -197
  11. package/dist/bin/exe-cloud.js +0 -850
  12. package/dist/bin/exe-dispatch.js +0 -1146
  13. package/dist/bin/exe-doctor.js +0 -1657
  14. package/dist/bin/exe-export-behaviors.js +0 -1494
  15. package/dist/bin/exe-forget.js +0 -1627
  16. package/dist/bin/exe-gateway.js +0 -7732
  17. package/dist/bin/exe-healthcheck.js +0 -207
  18. package/dist/bin/exe-heartbeat.js +0 -1647
  19. package/dist/bin/exe-kill.js +0 -1479
  20. package/dist/bin/exe-launch-agent.js +0 -1704
  21. package/dist/bin/exe-link.js +0 -192
  22. package/dist/bin/exe-new-employee.js +0 -852
  23. package/dist/bin/exe-pending-messages.js +0 -1446
  24. package/dist/bin/exe-pending-notifications.js +0 -1321
  25. package/dist/bin/exe-pending-reviews.js +0 -1468
  26. package/dist/bin/exe-repo-drift.js +0 -95
  27. package/dist/bin/exe-review.js +0 -1590
  28. package/dist/bin/exe-search.js +0 -2651
  29. package/dist/bin/exe-session-cleanup.js +0 -3173
  30. package/dist/bin/exe-settings.js +0 -354
  31. package/dist/bin/exe-status.js +0 -1532
  32. package/dist/bin/exe-team.js +0 -1324
  33. package/dist/bin/git-sweep.js +0 -2185
  34. package/dist/bin/graph-backfill.js +0 -1968
  35. package/dist/bin/graph-export.js +0 -1604
  36. package/dist/bin/install.js +0 -656
  37. package/dist/bin/list-providers.js +0 -140
  38. package/dist/bin/scan-tasks.js +0 -1820
  39. package/dist/bin/setup.js +0 -951
  40. package/dist/bin/shard-migrate.js +0 -1494
  41. package/dist/bin/update.js +0 -95
  42. package/dist/bin/wiki-sync.js +0 -1514
  43. package/dist/gateway/index.js +0 -8848
  44. package/dist/hooks/bug-report-worker.js +0 -2743
  45. package/dist/hooks/commit-complete.js +0 -2108
  46. package/dist/hooks/error-recall.js +0 -2861
  47. package/dist/hooks/exe-heartbeat-hook.js +0 -232
  48. package/dist/hooks/ingest-worker.js +0 -4793
  49. package/dist/hooks/ingest.js +0 -684
  50. package/dist/hooks/instructions-loaded.js +0 -1880
  51. package/dist/hooks/notification.js +0 -1726
  52. package/dist/hooks/post-compact.js +0 -1751
  53. package/dist/hooks/pre-compact.js +0 -1746
  54. package/dist/hooks/pre-tool-use.js +0 -2191
  55. package/dist/hooks/prompt-ingest-worker.js +0 -2126
  56. package/dist/hooks/prompt-submit.js +0 -4693
  57. package/dist/hooks/response-ingest-worker.js +0 -1936
  58. package/dist/hooks/session-end.js +0 -1752
  59. package/dist/hooks/session-start.js +0 -2795
  60. package/dist/hooks/stop.js +0 -1835
  61. package/dist/hooks/subagent-stop.js +0 -1726
  62. package/dist/hooks/summary-worker.js +0 -2661
  63. package/dist/index.js +0 -11834
  64. package/dist/lib/cloud-sync.js +0 -495
  65. package/dist/lib/config.js +0 -222
  66. package/dist/lib/consolidation.js +0 -476
  67. package/dist/lib/crypto.js +0 -51
  68. package/dist/lib/database.js +0 -730
  69. package/dist/lib/device-registry.js +0 -900
  70. package/dist/lib/embedder.js +0 -632
  71. package/dist/lib/employee-templates.js +0 -543
  72. package/dist/lib/employees.js +0 -177
  73. package/dist/lib/error-detector.js +0 -156
  74. package/dist/lib/exe-daemon-client.js +0 -451
  75. package/dist/lib/exe-daemon.js +0 -8285
  76. package/dist/lib/file-grep.js +0 -199
  77. package/dist/lib/hybrid-search.js +0 -1819
  78. package/dist/lib/identity-templates.js +0 -320
  79. package/dist/lib/identity.js +0 -223
  80. package/dist/lib/keychain.js +0 -145
  81. package/dist/lib/license.js +0 -377
  82. package/dist/lib/messaging.js +0 -1376
  83. package/dist/lib/reminders.js +0 -63
  84. package/dist/lib/schedules.js +0 -1396
  85. package/dist/lib/session-registry.js +0 -52
  86. package/dist/lib/skill-learning.js +0 -477
  87. package/dist/lib/status-brief.js +0 -235
  88. package/dist/lib/store.js +0 -1551
  89. package/dist/lib/task-router.js +0 -62
  90. package/dist/lib/tasks.js +0 -2456
  91. package/dist/lib/tmux-routing.js +0 -2836
  92. package/dist/lib/tmux-status.js +0 -261
  93. package/dist/lib/tmux-transport.js +0 -83
  94. package/dist/lib/transport.js +0 -128
  95. package/dist/lib/ws-auth.js +0 -19
  96. package/dist/lib/ws-client.js +0 -160
  97. package/dist/mcp/server.js +0 -10538
  98. package/dist/mcp/tools/complete-reminder.js +0 -67
  99. package/dist/mcp/tools/create-reminder.js +0 -52
  100. package/dist/mcp/tools/create-task.js +0 -1853
  101. package/dist/mcp/tools/deactivate-behavior.js +0 -263
  102. package/dist/mcp/tools/list-reminders.js +0 -62
  103. package/dist/mcp/tools/list-tasks.js +0 -463
  104. package/dist/mcp/tools/send-message.js +0 -1382
  105. package/dist/mcp/tools/update-task.js +0 -1692
  106. package/dist/runtime/index.js +0 -6809
  107. package/dist/tui/App.js +0 -17479
@@ -1,476 +0,0 @@
1
- var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __esm = (fn, res) => function __init() {
3
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
- };
5
-
6
- // src/lib/config.ts
7
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
8
- import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
9
- import path2 from "path";
10
- import os from "os";
11
- function resolveDataDir() {
12
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
13
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
14
- const newDir = path2.join(os.homedir(), ".exe-os");
15
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
16
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
17
- try {
18
- renameSync(legacyDir, newDir);
19
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
20
- `);
21
- } catch {
22
- return legacyDir;
23
- }
24
- }
25
- return newDir;
26
- }
27
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG;
28
- var init_config = __esm({
29
- "src/lib/config.ts"() {
30
- "use strict";
31
- EXE_AI_DIR = resolveDataDir();
32
- DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
33
- MODELS_DIR = path2.join(EXE_AI_DIR, "models");
34
- CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
35
- LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
36
- CURRENT_CONFIG_VERSION = 1;
37
- DEFAULT_CONFIG = {
38
- config_version: CURRENT_CONFIG_VERSION,
39
- dbPath: DB_PATH,
40
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
41
- embeddingDim: 1024,
42
- batchSize: 20,
43
- flushIntervalMs: 1e4,
44
- autoIngestion: true,
45
- autoRetrieval: true,
46
- searchMode: "hybrid",
47
- hookSearchMode: "hybrid",
48
- fileGrepEnabled: true,
49
- splashEffect: true,
50
- consolidationEnabled: true,
51
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
52
- consolidationModel: "claude-haiku-4-5-20251001",
53
- consolidationMaxCallsPerRun: 20,
54
- selfQueryRouter: true,
55
- selfQueryModel: "claude-haiku-4-5-20251001",
56
- rerankerEnabled: true,
57
- scalingRoadmap: {
58
- rerankerAutoTrigger: {
59
- enabled: true,
60
- broadQueryMinCardinality: 5e4,
61
- fetchTopK: 150,
62
- returnTopK: 5
63
- }
64
- },
65
- graphRagEnabled: true,
66
- wikiEnabled: false,
67
- wikiUrl: "",
68
- wikiApiKey: "",
69
- wikiSyncIntervalMs: 30 * 60 * 1e3,
70
- wikiWorkspaceMapping: {
71
- exe: "Executive",
72
- yoshi: "Engineering",
73
- mari: "Marketing",
74
- tom: "Engineering",
75
- sasha: "Production"
76
- },
77
- wikiAutoUpdate: true,
78
- wikiAutoUpdateThreshold: 0.5,
79
- wikiAutoUpdateCreateNew: true,
80
- skillLearning: true,
81
- skillThreshold: 3,
82
- skillModel: "claude-haiku-4-5-20251001",
83
- exeHeartbeat: {
84
- enabled: true,
85
- intervalSeconds: 60,
86
- staleInProgressThresholdHours: 2
87
- },
88
- sessionLifecycle: {
89
- idleKillEnabled: true,
90
- idleKillTicksRequired: 3,
91
- idleKillIntercomAckWindowMs: 1e4,
92
- maxAutoInstances: 10
93
- }
94
- };
95
- }
96
- });
97
-
98
- // src/lib/consolidation.ts
99
- import { randomUUID } from "crypto";
100
-
101
- // src/lib/database.ts
102
- import { createClient } from "@libsql/client";
103
-
104
- // src/lib/keychain.ts
105
- import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
106
- import { existsSync } from "fs";
107
- import path from "path";
108
- import crypto from "crypto";
109
-
110
- // src/lib/store.ts
111
- init_config();
112
- function vectorToBlob(vector) {
113
- const f32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
114
- return JSON.stringify(Array.from(f32));
115
- }
116
-
117
- // src/lib/consolidation.ts
118
- async function selectUnconsolidated(client, limit = 200) {
119
- const result = await client.execute({
120
- sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
121
- FROM memories
122
- WHERE consolidated = 0
123
- AND timestamp >= datetime('now', '-7 days')
124
- ORDER BY timestamp DESC
125
- LIMIT ?`,
126
- args: [limit]
127
- });
128
- return result.rows.map((row) => ({
129
- id: row.id,
130
- agent_id: row.agent_id,
131
- project_name: row.project_name,
132
- tool_name: row.tool_name,
133
- raw_text: row.raw_text,
134
- timestamp: row.timestamp
135
- }));
136
- }
137
- function groupMemories(memories) {
138
- const byProject = /* @__PURE__ */ new Map();
139
- for (const mem of memories) {
140
- const key = `${mem.agent_id}::${mem.project_name}`;
141
- const list = byProject.get(key) ?? [];
142
- list.push(mem);
143
- byProject.set(key, list);
144
- }
145
- const clusters = [];
146
- for (const [key, projectMemories] of byProject) {
147
- const [agentId, projectName] = key.split("::");
148
- const byDay = /* @__PURE__ */ new Map();
149
- for (const mem of projectMemories) {
150
- const day = mem.timestamp.slice(0, 10);
151
- const list = byDay.get(day) ?? [];
152
- list.push(mem);
153
- byDay.set(day, list);
154
- }
155
- for (const [day, dayMemories] of byDay) {
156
- if (dayMemories.length <= 15) {
157
- clusters.push({
158
- agentId,
159
- projectName,
160
- dateRange: day,
161
- memories: dayMemories
162
- });
163
- } else {
164
- for (let i = 0; i < dayMemories.length; i += 12) {
165
- const chunk = dayMemories.slice(i, i + 12);
166
- clusters.push({
167
- agentId,
168
- projectName,
169
- dateRange: day,
170
- memories: chunk
171
- });
172
- }
173
- }
174
- }
175
- }
176
- return clusters;
177
- }
178
- function buildConsolidationPrompt(cluster) {
179
- const snippets = cluster.memories.map((m, i) => {
180
- const text = m.raw_text.length > 200 ? m.raw_text.slice(0, 200) + "..." : m.raw_text;
181
- return `${i + 1}. [${m.tool_name}] ${text}`;
182
- }).join("\n");
183
- return `You are reviewing a set of work memories from an AI coding agent.
184
- These are raw tool call records from ${cluster.dateRange}.
185
-
186
- Agent: ${cluster.agentId} | Project: ${cluster.projectName} | Date: ${cluster.dateRange}
187
-
188
- MEMORIES:
189
- ${snippets}
190
-
191
- Extract EXACTLY THREE types of insights:
192
-
193
- 1. KEY DECISIONS \u2014 choices that were made and why (at most 3)
194
- 2. RECURRING PATTERNS \u2014 actions or approaches that repeated (at most 3)
195
- 3. OPEN QUESTIONS \u2014 things that seem unresolved or risky (at most 2)
196
-
197
- Format each as a single sentence. Be specific \u2014 include file names,
198
- function names, and concrete details. Skip if no insight for a category.`;
199
- }
200
- async function consolidateCluster(cluster, model) {
201
- const Anthropic = (await import("@anthropic-ai/sdk")).default;
202
- const client = new Anthropic();
203
- const prompt = buildConsolidationPrompt(cluster);
204
- const response = await client.messages.create({
205
- model,
206
- max_tokens: 300,
207
- messages: [{ role: "user", content: prompt }]
208
- });
209
- const textBlock = response.content.find((b) => b.type === "text");
210
- return textBlock?.text ?? "";
211
- }
212
- async function storeConsolidation(client, cluster, synthesisText, embedFn) {
213
- const consolidatedId = randomUUID();
214
- const now = (/* @__PURE__ */ new Date()).toISOString();
215
- const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
216
-
217
- ${synthesisText}`;
218
- let vector = null;
219
- if (embedFn) {
220
- try {
221
- vector = await embedFn(rawText);
222
- } catch {
223
- }
224
- }
225
- const insertSql = vector ? `INSERT INTO memories
226
- (id, agent_id, agent_role, session_id, timestamp,
227
- tool_name, project_name, has_error, raw_text, vector, version, consolidated)
228
- VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1)` : `INSERT INTO memories
229
- (id, agent_id, agent_role, session_id, timestamp,
230
- tool_name, project_name, has_error, raw_text, vector, version, consolidated)
231
- VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1)`;
232
- const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
233
- await client.execute({ sql: insertSql, args: insertArgs });
234
- const sourceIds = cluster.memories.map((m) => m.id);
235
- const linkStmts = sourceIds.map((sourceId) => ({
236
- sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
237
- VALUES (?, ?, ?, ?)`,
238
- args: [randomUUID(), consolidatedId, sourceId, now]
239
- }));
240
- const placeholders = sourceIds.map(() => "?").join(",");
241
- const markStmt = {
242
- sql: `UPDATE memories SET consolidated = 1 WHERE id IN (${placeholders})`,
243
- args: sourceIds
244
- };
245
- await client.batch([...linkStmts, markStmt], "write");
246
- return { consolidatedMemoryId: consolidatedId, sourceIds, rawText };
247
- }
248
- var WIKI_FETCH_TIMEOUT_MS = 1e4;
249
- async function pushToWiki(consolidation, config) {
250
- if (!config.wikiEnabled || !config.wikiAutoUpdate) {
251
- return { updated: false };
252
- }
253
- const apiUrl = config.wikiUrl || process.env.EXE_WIKI_API_URL;
254
- const apiKey = config.wikiApiKey || process.env.EXE_WIKI_API_KEY;
255
- if (!apiUrl || !apiKey) {
256
- return { updated: false };
257
- }
258
- const workspace = config.wikiWorkspaceMapping[consolidation.projectName] ?? consolidation.projectName;
259
- try {
260
- const listRes = await fetch(
261
- `${apiUrl}/v1/workspace/${encodeURIComponent(workspace)}/documents`,
262
- {
263
- headers: { Authorization: `Bearer ${apiKey}` },
264
- signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
265
- }
266
- );
267
- if (!listRes.ok) {
268
- return { updated: false, error: `Workspace "${workspace}" not accessible (${listRes.status})` };
269
- }
270
- const listJson = await listRes.json();
271
- const docs = listJson.documents ?? listJson.localFiles?.items ?? [];
272
- const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
273
- const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
274
- let bestMatch = null;
275
- for (const doc of docs) {
276
- if (!doc.id || !doc.title) continue;
277
- const titleWords = doc.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
278
- if (titleWords.length === 0) continue;
279
- const matchCount = titleWords.filter(
280
- (tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
281
- ).length;
282
- const score = matchCount / titleWords.length;
283
- if (score > (bestMatch?.score ?? 0)) {
284
- bestMatch = { id: doc.id, title: doc.title, score };
285
- }
286
- }
287
- if (bestMatch && bestMatch.score >= config.wikiAutoUpdateThreshold) {
288
- const updateRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
289
- method: "POST",
290
- headers: {
291
- "Content-Type": "application/json",
292
- Authorization: `Bearer ${apiKey}`
293
- },
294
- body: JSON.stringify({
295
- textContent: consolidation.rawText,
296
- metadata: { title: bestMatch.title, appendTo: bestMatch.id },
297
- workspaceSlugs: [workspace]
298
- }),
299
- signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
300
- });
301
- if (updateRes.ok) {
302
- process.stderr.write(
303
- `[consolidation] Wiki auto-updated: ${workspace}/${bestMatch.title}
304
- `
305
- );
306
- return { updated: true, action: "updated", page: bestMatch.title };
307
- }
308
- return { updated: false, error: `Update failed (${updateRes.status})` };
309
- }
310
- if (config.wikiAutoUpdateCreateNew) {
311
- const title = `Consolidated Insights \u2014 ${consolidation.projectName} (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`;
312
- const createRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
313
- method: "POST",
314
- headers: {
315
- "Content-Type": "application/json",
316
- Authorization: `Bearer ${apiKey}`
317
- },
318
- body: JSON.stringify({
319
- textContent: consolidation.rawText,
320
- metadata: { title },
321
- workspaceSlugs: [workspace]
322
- }),
323
- signal: AbortSignal.timeout(WIKI_FETCH_TIMEOUT_MS)
324
- });
325
- if (createRes.ok) {
326
- process.stderr.write(
327
- `[consolidation] Wiki page created: ${workspace}/${title}
328
- `
329
- );
330
- return { updated: true, action: "created", page: title };
331
- }
332
- return { updated: false, error: `Create failed (${createRes.status})` };
333
- }
334
- return { updated: false };
335
- } catch (err) {
336
- const msg = err instanceof Error ? err.message : String(err);
337
- process.stderr.write(`[consolidation] Wiki push failed: ${msg}
338
- `);
339
- return { updated: false, error: msg };
340
- }
341
- }
342
- async function runConsolidation(client, options) {
343
- const memories = await selectUnconsolidated(client);
344
- if (memories.length < 20) {
345
- return { clustersProcessed: 0, memoriesConsolidated: 0 };
346
- }
347
- const clusters = groupMemories(memories);
348
- let clustersProcessed = 0;
349
- let memoriesConsolidated = 0;
350
- for (const cluster of clusters) {
351
- if (clustersProcessed >= options.maxCalls) break;
352
- if (cluster.memories.length < 3) continue;
353
- try {
354
- const isExe = cluster.agentId === "exe";
355
- if (isExe) {
356
- const synthesis = await consolidateCluster(cluster, options.model);
357
- if (!synthesis.trim()) continue;
358
- const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
359
- if (options.wikiConfig) {
360
- await pushToWiki(
361
- { ...result, projectName: cluster.projectName },
362
- options.wikiConfig
363
- ).catch((err) => {
364
- process.stderr.write(
365
- `[consolidation] Wiki push error (non-fatal): ${err instanceof Error ? err.message : String(err)}
366
- `
367
- );
368
- });
369
- }
370
- const sourceIds = result.sourceIds;
371
- if (sourceIds.length > 0) {
372
- const placeholders = sourceIds.map(() => "?").join(",");
373
- await client.execute({
374
- sql: `UPDATE memories SET status = 'archived' WHERE id IN (${placeholders})`,
375
- args: sourceIds
376
- });
377
- }
378
- } else {
379
- const dedupCount = await dedupCluster(client, cluster, options.embedFn);
380
- memoriesConsolidated += dedupCount;
381
- if (dedupCount === 0) continue;
382
- }
383
- clustersProcessed++;
384
- memoriesConsolidated += isExe ? cluster.memories.length : 0;
385
- } catch (err) {
386
- process.stderr.write(
387
- `[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
388
- `
389
- );
390
- }
391
- }
392
- return { clustersProcessed, memoriesConsolidated };
393
- }
394
- async function dedupCluster(client, cluster, embedFn) {
395
- if (!embedFn || cluster.memories.length < 2) return 0;
396
- const vectors = [];
397
- for (const mem of cluster.memories) {
398
- try {
399
- const v = await embedFn(mem.raw_text.slice(0, 500));
400
- vectors.push({ id: mem.id, vector: v });
401
- } catch {
402
- }
403
- }
404
- if (vectors.length < 2) return 0;
405
- const toArchive = /* @__PURE__ */ new Set();
406
- for (let i = 0; i < vectors.length; i++) {
407
- if (toArchive.has(vectors[i].id)) continue;
408
- for (let j = i + 1; j < vectors.length; j++) {
409
- if (toArchive.has(vectors[j].id)) continue;
410
- const sim = cosineSimilarity(vectors[i].vector, vectors[j].vector);
411
- if (sim > 0.95) {
412
- toArchive.add(vectors[j].id);
413
- }
414
- }
415
- }
416
- if (toArchive.size === 0) return 0;
417
- const ids = [...toArchive];
418
- const placeholders = ids.map(() => "?").join(",");
419
- await client.execute({
420
- sql: `UPDATE memories SET status = 'archived', consolidated = 1 WHERE id IN (${placeholders})`,
421
- args: ids
422
- });
423
- const survivors = vectors.filter((v) => !toArchive.has(v.id)).map((v) => v.id);
424
- if (survivors.length > 0) {
425
- const survivorPlaceholders = survivors.map(() => "?").join(",");
426
- await client.execute({
427
- sql: `UPDATE memories SET confidence = MIN(1.0, COALESCE(confidence, 0.7) + 0.1) WHERE id IN (${survivorPlaceholders})`,
428
- args: survivors
429
- });
430
- }
431
- return ids.length;
432
- }
433
- function cosineSimilarity(a, b) {
434
- let dot = 0, normA = 0, normB = 0;
435
- for (let i = 0; i < a.length; i++) {
436
- dot += a[i] * b[i];
437
- normA += a[i] * a[i];
438
- normB += b[i] * b[i];
439
- }
440
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
441
- return denom === 0 ? 0 : dot / denom;
442
- }
443
- async function isUserIdle(client, idleMinutes = 30) {
444
- const result = await client.execute({
445
- sql: `SELECT MAX(timestamp) as last_activity
446
- FROM memories
447
- WHERE tool_name != 'consolidation'
448
- AND timestamp >= datetime('now', '-1 day')`,
449
- args: []
450
- });
451
- const lastActivity = result.rows[0]?.last_activity;
452
- if (!lastActivity) return true;
453
- const lastMs = new Date(lastActivity).getTime();
454
- const now = Date.now();
455
- return now - lastMs >= idleMinutes * 60 * 1e3;
456
- }
457
- async function countUnconsolidated(client) {
458
- const result = await client.execute({
459
- sql: `SELECT COUNT(*) as cnt FROM memories
460
- WHERE consolidated = 0
461
- AND timestamp >= datetime('now', '-7 days')`,
462
- args: []
463
- });
464
- return Number(result.rows[0]?.cnt ?? 0);
465
- }
466
- export {
467
- buildConsolidationPrompt,
468
- consolidateCluster,
469
- countUnconsolidated,
470
- groupMemories,
471
- isUserIdle,
472
- pushToWiki,
473
- runConsolidation,
474
- selectUnconsolidated,
475
- storeConsolidation
476
- };
@@ -1,51 +0,0 @@
1
- // src/lib/crypto.ts
2
- import crypto from "crypto";
3
- var ALGORITHM = "aes-256-gcm";
4
- var IV_LENGTH = 12;
5
- var TAG_LENGTH = 16;
6
- var SYNC_HKDF_INFO = "exe-mem-sync-v2";
7
- var _syncKey = null;
8
- function initSyncCrypto(masterKey) {
9
- if (masterKey.length !== 32) {
10
- throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
11
- }
12
- _syncKey = Buffer.from(
13
- crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
14
- );
15
- }
16
- function isSyncCryptoInitialized() {
17
- return _syncKey !== null;
18
- }
19
- function requireSyncKey() {
20
- if (!_syncKey) {
21
- throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
22
- }
23
- return _syncKey;
24
- }
25
- function encryptSyncBlob(data) {
26
- const key = requireSyncKey();
27
- const iv = crypto.randomBytes(IV_LENGTH);
28
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
29
- const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
30
- const tag = cipher.getAuthTag();
31
- return Buffer.concat([iv, encrypted, tag]).toString("base64");
32
- }
33
- function decryptSyncBlob(ciphertext) {
34
- const key = requireSyncKey();
35
- const combined = Buffer.from(ciphertext, "base64");
36
- if (combined.length < IV_LENGTH + TAG_LENGTH) {
37
- throw new Error("Sync blob too short to contain IV + tag");
38
- }
39
- const iv = combined.subarray(0, IV_LENGTH);
40
- const tag = combined.subarray(combined.length - TAG_LENGTH);
41
- const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
42
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
43
- decipher.setAuthTag(tag);
44
- return Buffer.concat([decipher.update(encrypted), decipher.final()]);
45
- }
46
- export {
47
- decryptSyncBlob,
48
- encryptSyncBlob,
49
- initSyncCrypto,
50
- isSyncCryptoInitialized
51
- };