@askexenow/exe-os 0.8.37 → 0.8.39

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 (93) hide show
  1. package/README.md +17 -8
  2. package/dist/bin/backfill-conversations.js +112 -70
  3. package/dist/bin/backfill-responses.js +53 -18
  4. package/dist/bin/backfill-vectors.js +43 -16
  5. package/dist/bin/cleanup-stale-review-tasks.js +38 -16
  6. package/dist/bin/cli.js +790 -468
  7. package/dist/bin/exe-agent.js +19 -4
  8. package/dist/bin/exe-assign.js +46 -13
  9. package/dist/bin/exe-boot.js +288 -129
  10. package/dist/bin/exe-call.js +20 -10
  11. package/dist/bin/exe-cloud.js +135 -30
  12. package/dist/bin/exe-dispatch.js +1 -1
  13. package/dist/bin/exe-doctor.js +38 -16
  14. package/dist/bin/exe-export-behaviors.js +43 -21
  15. package/dist/bin/exe-forget.js +39 -17
  16. package/dist/bin/exe-gateway.js +159 -50
  17. package/dist/bin/exe-heartbeat.js +53 -31
  18. package/dist/bin/exe-kill.js +40 -18
  19. package/dist/bin/exe-launch-agent.js +109 -36
  20. package/dist/bin/exe-link.js +196 -87
  21. package/dist/bin/exe-new-employee.js +56 -17
  22. package/dist/bin/exe-pending-messages.js +47 -25
  23. package/dist/bin/exe-pending-notifications.js +38 -16
  24. package/dist/bin/exe-pending-reviews.js +51 -29
  25. package/dist/bin/exe-rename.js +21 -7
  26. package/dist/bin/exe-review.js +41 -13
  27. package/dist/bin/exe-search.js +57 -21
  28. package/dist/bin/exe-session-cleanup.js +67 -31
  29. package/dist/bin/exe-settings.js +63 -2
  30. package/dist/bin/exe-status.js +35 -13
  31. package/dist/bin/exe-team.js +35 -13
  32. package/dist/bin/git-sweep.js +45 -17
  33. package/dist/bin/graph-backfill.js +38 -16
  34. package/dist/bin/graph-export.js +38 -16
  35. package/dist/bin/install.js +10 -1
  36. package/dist/bin/scan-tasks.js +47 -19
  37. package/dist/bin/setup.js +444 -259
  38. package/dist/bin/shard-migrate.js +38 -16
  39. package/dist/bin/wiki-sync.js +40 -17
  40. package/dist/gateway/index.js +113 -48
  41. package/dist/hooks/bug-report-worker.js +66 -39
  42. package/dist/hooks/commit-complete.js +45 -17
  43. package/dist/hooks/error-recall.js +60 -20
  44. package/dist/hooks/exe-heartbeat-hook.js +3 -2
  45. package/dist/hooks/ingest-worker.js +174 -45
  46. package/dist/hooks/ingest.js +74 -28
  47. package/dist/hooks/instructions-loaded.js +46 -17
  48. package/dist/hooks/notification.js +44 -15
  49. package/dist/hooks/post-compact.js +44 -15
  50. package/dist/hooks/pre-compact.js +42 -14
  51. package/dist/hooks/pre-tool-use.js +59 -22
  52. package/dist/hooks/prompt-ingest-worker.js +75 -14
  53. package/dist/hooks/prompt-submit.js +75 -32
  54. package/dist/hooks/response-ingest-worker.js +76 -15
  55. package/dist/hooks/session-end.js +54 -22
  56. package/dist/hooks/session-start.js +57 -20
  57. package/dist/hooks/stop.js +44 -15
  58. package/dist/hooks/subagent-stop.js +44 -15
  59. package/dist/hooks/summary-worker.js +339 -106
  60. package/dist/index.js +94 -23
  61. package/dist/lib/cloud-sync.js +191 -80
  62. package/dist/lib/config.js +4 -1
  63. package/dist/lib/consolidation.js +5 -4
  64. package/dist/lib/database.js +1 -0
  65. package/dist/lib/device-registry.js +2 -1
  66. package/dist/lib/embedder.js +9 -1
  67. package/dist/lib/employee-templates.js +5 -0
  68. package/dist/lib/employees.js +11 -6
  69. package/dist/lib/exe-daemon-client.js +6 -1
  70. package/dist/lib/exe-daemon.js +95 -36
  71. package/dist/lib/hybrid-search.js +57 -21
  72. package/dist/lib/identity-templates.js +16 -7
  73. package/dist/lib/identity.js +1 -1
  74. package/dist/lib/keychain.js +2 -1
  75. package/dist/lib/license.js +56 -6
  76. package/dist/lib/messaging.js +1 -1
  77. package/dist/lib/reminders.js +2 -2
  78. package/dist/lib/schedules.js +38 -16
  79. package/dist/lib/skill-learning.js +1 -1
  80. package/dist/lib/store.js +44 -16
  81. package/dist/lib/tasks.js +1 -1
  82. package/dist/lib/tmux-routing.js +1 -1
  83. package/dist/mcp/server.js +280 -155
  84. package/dist/mcp/tools/complete-reminder.js +1 -1
  85. package/dist/mcp/tools/create-task.js +14 -6
  86. package/dist/mcp/tools/deactivate-behavior.js +2 -2
  87. package/dist/mcp/tools/list-reminders.js +1 -1
  88. package/dist/mcp/tools/list-tasks.js +36 -28
  89. package/dist/mcp/tools/send-message.js +1 -1
  90. package/dist/mcp/tools/update-task.js +1 -1
  91. package/dist/runtime/index.js +42 -8
  92. package/dist/tui/App.js +220 -99
  93. package/package.json +5 -3
package/README.md CHANGED
@@ -88,10 +88,19 @@ You talk to your COO. Your COO delegates. Each employee has:
88
88
  ### 1. Install
89
89
 
90
90
  ```bash
91
+ # macOS
92
+ xcode-select --install && brew install tmux
93
+ npm install -g @askexenow/exe-os
94
+
95
+ # Linux / WSL2
96
+ sudo apt install -y tmux git cmake g++ libsecret-1-dev curl
91
97
  npm install -g @askexenow/exe-os
98
+
99
+ # Windows — must use WSL2 (native Windows not supported)
100
+ # Run `wsl --install` in PowerShell first, then follow Linux steps inside WSL2
92
101
  ```
93
102
 
94
- Requires Node.js 20+ and tmux (`brew install tmux` on macOS).
103
+ Requires Node.js 22+ and tmux. See **[docs/install.md](docs/install.md)** for full platform-specific instructions and troubleshooting.
95
104
 
96
105
  ### 2. Run Setup
97
106
 
@@ -219,17 +228,17 @@ exe-os/
219
228
 
220
229
  ## Requirements
221
230
 
222
- - **Node.js 20+**
231
+ - **Node.js 22+**
223
232
  - **tmux** — for multi-agent session management
224
233
  - **Claude Code** subscription (Mode 1) or API key (Mode 2)
225
234
 
226
- ```bash
227
- # macOS
228
- brew install tmux node
235
+ | Platform | Quick Install |
236
+ |----------|-------------|
237
+ | macOS | `brew install tmux node` + Xcode CLI Tools |
238
+ | Linux | `apt install tmux git cmake g++ libsecret-1-dev` + Node.js 22+ |
239
+ | Windows | WSL2 required (`wsl --install`), then follow Linux steps |
229
240
 
230
- # Linux
231
- apt install tmux nodejs
232
- ```
241
+ Full guide: **[docs/install.md](docs/install.md)**
233
242
 
234
243
  ---
235
244
 
@@ -1,12 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
- }) : x)(function(x) {
7
- if (typeof require !== "undefined") return require.apply(this, arguments);
8
- throw Error('Dynamic require of "' + x + '" is not supported');
9
- });
10
4
  var __esm = (fn, res) => function __init() {
11
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
6
  };
@@ -16,15 +10,15 @@ var __export = (target, all) => {
16
10
  };
17
11
 
18
12
  // src/lib/config.ts
19
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
20
14
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
15
  import path2 from "path";
22
- import os from "os";
16
+ import os2 from "os";
23
17
  function resolveDataDir() {
24
18
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
25
19
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
- const newDir = path2.join(os.homedir(), ".exe-os");
27
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
20
+ const newDir = path2.join(os2.homedir(), ".exe-os");
21
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
28
22
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
29
23
  try {
30
24
  renameSync(legacyDir, newDir);
@@ -111,7 +105,7 @@ async function loadConfig() {
111
105
  normalizeAutoUpdate(migratedCfg);
112
106
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
113
107
  if (config.dbPath.startsWith("~")) {
114
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
108
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
115
109
  }
116
110
  return config;
117
111
  } catch {
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
218
212
  shardExists: () => shardExists
219
213
  });
220
214
  import path3 from "path";
221
- import { existsSync as existsSync3, mkdirSync } from "fs";
215
+ import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
222
216
  import { createClient as createClient2 } from "@libsql/client";
223
217
  function initShardManager(encryptionKey) {
224
218
  _encryptionKey = encryptionKey;
@@ -257,7 +251,6 @@ function shardExists(projectName) {
257
251
  }
258
252
  function listShards() {
259
253
  if (!existsSync3(SHARDS_DIR)) return [];
260
- const { readdirSync } = __require("fs");
261
254
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
262
255
  }
263
256
  async function ensureShardSchema(client) {
@@ -545,6 +538,7 @@ async function ensureSchema() {
545
538
  const client = getRawClient();
546
539
  await client.execute("PRAGMA journal_mode = WAL");
547
540
  await client.execute("PRAGMA busy_timeout = 30000");
541
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
548
542
  try {
549
543
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
550
544
  } catch {
@@ -1338,11 +1332,12 @@ async function ensureSchema() {
1338
1332
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1339
1333
  import { existsSync } from "fs";
1340
1334
  import path from "path";
1335
+ import os from "os";
1341
1336
  import crypto from "crypto";
1342
1337
  var SERVICE = "exe-mem";
1343
1338
  var ACCOUNT = "master-key";
1344
1339
  function getKeyDir() {
1345
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1340
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1346
1341
  }
1347
1342
  function getKeyPath() {
1348
1343
  return path.join(getKeyDir(), "master.key");
@@ -1379,6 +1374,30 @@ async function getMasterKey() {
1379
1374
 
1380
1375
  // src/lib/store.ts
1381
1376
  init_config();
1377
+ var INIT_MAX_RETRIES = 3;
1378
+ var INIT_RETRY_DELAY_MS = 1e3;
1379
+ function isBusyError2(err) {
1380
+ if (err instanceof Error) {
1381
+ const msg = err.message.toLowerCase();
1382
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1383
+ }
1384
+ return false;
1385
+ }
1386
+ async function retryOnBusy2(fn, label) {
1387
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1388
+ try {
1389
+ return await fn();
1390
+ } catch (err) {
1391
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1392
+ process.stderr.write(
1393
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1394
+ `
1395
+ );
1396
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1397
+ }
1398
+ }
1399
+ throw new Error("unreachable");
1400
+ }
1382
1401
  var _pendingRecords = [];
1383
1402
  var _batchSize = 20;
1384
1403
  var _flushIntervalMs = 1e4;
@@ -1413,14 +1432,17 @@ async function initStore(options) {
1413
1432
  dbPath,
1414
1433
  encryptionKey: hexKey
1415
1434
  });
1416
- await ensureSchema();
1435
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1417
1436
  try {
1418
1437
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1419
1438
  initShardManager2(hexKey);
1420
1439
  } catch {
1421
1440
  }
1422
1441
  const client = getClient();
1423
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1442
+ const vResult = await retryOnBusy2(
1443
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1444
+ "version-query"
1445
+ );
1424
1446
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1425
1447
  }
1426
1448
  function classifyTier(record) {
@@ -1463,6 +1485,12 @@ async function writeMemory(record) {
1463
1485
  supersedes_id: record.supersedes_id ?? null
1464
1486
  };
1465
1487
  _pendingRecords.push(dbRow);
1488
+ const MAX_PENDING = 1e3;
1489
+ if (_pendingRecords.length > MAX_PENDING) {
1490
+ const dropped = _pendingRecords.length - MAX_PENDING;
1491
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
1492
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
1493
+ }
1466
1494
  if (_flushTimer === null) {
1467
1495
  _flushTimer = setInterval(() => {
1468
1496
  void flushBatch();
@@ -1631,8 +1659,13 @@ var _buffer = "";
1631
1659
  var _requestCount = 0;
1632
1660
  var HEALTH_CHECK_INTERVAL = 100;
1633
1661
  var _pending = /* @__PURE__ */ new Map();
1662
+ var MAX_BUFFER = 1e7;
1634
1663
  function handleData(chunk) {
1635
1664
  _buffer += chunk.toString();
1665
+ if (_buffer.length > MAX_BUFFER) {
1666
+ _buffer = "";
1667
+ return;
1668
+ }
1636
1669
  let newlineIdx;
1637
1670
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1638
1671
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -1946,10 +1979,12 @@ function isMainModule(importMetaUrl) {
1946
1979
  var TOOL_NAME = "backfill-conversation";
1947
1980
  var MIN_MESSAGES = 3;
1948
1981
  var MAX_SUMMARY_LENGTH = 4e3;
1982
+ var MAX_WALK_DEPTH = 10;
1949
1983
  async function findJsonlFiles(sinceDate, projectFilter) {
1950
1984
  const projectsDir = path5.join(homedir(), ".claude", "projects");
1951
1985
  const files = [];
1952
- async function walk(dir) {
1986
+ async function walk(dir, depth = 0) {
1987
+ if (depth > MAX_WALK_DEPTH) return;
1953
1988
  let entries;
1954
1989
  try {
1955
1990
  entries = await readdir(dir, { withFileTypes: true });
@@ -1960,7 +1995,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
1960
1995
  const full = path5.join(dir, entry.name);
1961
1996
  if (entry.isDirectory()) {
1962
1997
  if (entry.name === "subagents" || entry.name === "tool-results") continue;
1963
- await walk(full);
1998
+ await walk(full, depth + 1);
1964
1999
  } else if (entry.name.endsWith(".jsonl")) {
1965
2000
  try {
1966
2001
  const s = await stat(full);
@@ -2201,65 +2236,72 @@ async function backfillConversations(options) {
2201
2236
  process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
2202
2237
  `);
2203
2238
  process.env.EXE_EMBED_PRIORITY = "low";
2204
- for (const file of files) {
2205
- stats.filesScanned++;
2206
- if (existingPaths.has(file)) {
2207
- stats.skippedDedup++;
2208
- continue;
2209
- }
2210
- const conv = await parseConversation(file);
2211
- if (conv.totalMessages < MIN_MESSAGES) {
2212
- stats.skippedTooShort++;
2213
- continue;
2214
- }
2215
- const summary = buildSummary(conv);
2216
- if (options.dryRun) {
2217
- process.stdout.write(`
2239
+ const BATCH_SIZE = 50;
2240
+ const MAX_DEDUP_SIZE = 5e4;
2241
+ for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
2242
+ const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
2243
+ for (const file of batch) {
2244
+ stats.filesScanned++;
2245
+ if (existingPaths.size < MAX_DEDUP_SIZE && existingPaths.has(file)) {
2246
+ stats.skippedDedup++;
2247
+ continue;
2248
+ }
2249
+ const conv = await parseConversation(file);
2250
+ if (conv.totalMessages < MIN_MESSAGES) {
2251
+ stats.skippedTooShort++;
2252
+ continue;
2253
+ }
2254
+ const summary = buildSummary(conv);
2255
+ if (options.dryRun) {
2256
+ process.stdout.write(`
2218
2257
  \u2500\u2500\u2500 ${file} \u2500\u2500\u2500
2219
2258
  `);
2220
- process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
2221
- process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
2222
- process.stdout.write(` | Files: ${conv.filesTouched.size}
2259
+ process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
2260
+ process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
2261
+ process.stdout.write(` | Files: ${conv.filesTouched.size}
2223
2262
  `);
2224
- const firstPrompt = conv.userMessages[0];
2225
- if (firstPrompt) {
2226
- process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
2263
+ const firstPrompt = conv.userMessages[0];
2264
+ if (firstPrompt) {
2265
+ process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
2227
2266
  `);
2267
+ }
2268
+ stats.conversationsStored++;
2269
+ continue;
2228
2270
  }
2229
- stats.conversationsStored++;
2230
- continue;
2231
- }
2232
- let vector = null;
2233
- if (daemonConnected) {
2234
- try {
2235
- vector = await embedViaClient(summary, "low");
2236
- if (!vector) stats.embedFailed++;
2237
- } catch {
2238
- stats.embedFailed++;
2271
+ let vector = null;
2272
+ if (daemonConnected) {
2273
+ try {
2274
+ vector = await embedViaClient(summary, "low");
2275
+ if (!vector) stats.embedFailed++;
2276
+ } catch {
2277
+ stats.embedFailed++;
2278
+ }
2239
2279
  }
2240
- }
2241
- await writeMemory({
2242
- id: crypto2.randomUUID(),
2243
- agent_id: conv.agentId,
2244
- agent_role: conv.agentId === "exe" ? "COO" : "specialist",
2245
- session_id: conv.sessionId,
2246
- timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
2247
- tool_name: TOOL_NAME,
2248
- project_name: conv.projectName,
2249
- has_error: conv.errorCount > 0,
2250
- raw_text: summary,
2251
- vector,
2252
- source_path: file,
2253
- source_type: "conversation"
2254
- });
2255
- existingPaths.add(file);
2256
- stats.conversationsStored++;
2257
- if (stats.filesScanned % 50 === 0) {
2258
- process.stderr.write(
2259
- `[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
2280
+ await writeMemory({
2281
+ id: crypto2.randomUUID(),
2282
+ agent_id: conv.agentId,
2283
+ agent_role: conv.agentId === "exe" ? "COO" : "specialist",
2284
+ session_id: conv.sessionId,
2285
+ timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
2286
+ tool_name: TOOL_NAME,
2287
+ project_name: conv.projectName,
2288
+ has_error: conv.errorCount > 0,
2289
+ raw_text: summary,
2290
+ vector,
2291
+ source_path: file,
2292
+ source_type: "conversation"
2293
+ });
2294
+ if (existingPaths.size < MAX_DEDUP_SIZE) {
2295
+ existingPaths.add(file);
2296
+ }
2297
+ stats.conversationsStored++;
2298
+ if (stats.filesScanned % 50 === 0) {
2299
+ process.stderr.write(
2300
+ `[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
2260
2301
  `
2261
- );
2262
- await flushBatch();
2302
+ );
2303
+ await flushBatch();
2304
+ }
2263
2305
  }
2264
2306
  }
2265
2307
  if (!options.dryRun) {
@@ -1,12 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
- }) : x)(function(x) {
7
- if (typeof require !== "undefined") return require.apply(this, arguments);
8
- throw Error('Dynamic require of "' + x + '" is not supported');
9
- });
10
4
  var __esm = (fn, res) => function __init() {
11
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
6
  };
@@ -16,15 +10,15 @@ var __export = (target, all) => {
16
10
  };
17
11
 
18
12
  // src/lib/config.ts
19
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
20
14
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
15
  import path2 from "path";
22
- import os from "os";
16
+ import os2 from "os";
23
17
  function resolveDataDir() {
24
18
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
25
19
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
- const newDir = path2.join(os.homedir(), ".exe-os");
27
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
20
+ const newDir = path2.join(os2.homedir(), ".exe-os");
21
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
28
22
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
29
23
  try {
30
24
  renameSync(legacyDir, newDir);
@@ -111,7 +105,7 @@ async function loadConfig() {
111
105
  normalizeAutoUpdate(migratedCfg);
112
106
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
113
107
  if (config.dbPath.startsWith("~")) {
114
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
108
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
115
109
  }
116
110
  return config;
117
111
  } catch {
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
218
212
  shardExists: () => shardExists
219
213
  });
220
214
  import path3 from "path";
221
- import { existsSync as existsSync3, mkdirSync } from "fs";
215
+ import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
222
216
  import { createClient as createClient2 } from "@libsql/client";
223
217
  function initShardManager(encryptionKey) {
224
218
  _encryptionKey = encryptionKey;
@@ -257,7 +251,6 @@ function shardExists(projectName) {
257
251
  }
258
252
  function listShards() {
259
253
  if (!existsSync3(SHARDS_DIR)) return [];
260
- const { readdirSync } = __require("fs");
261
254
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
262
255
  }
263
256
  async function ensureShardSchema(client) {
@@ -544,6 +537,7 @@ async function ensureSchema() {
544
537
  const client = getRawClient();
545
538
  await client.execute("PRAGMA journal_mode = WAL");
546
539
  await client.execute("PRAGMA busy_timeout = 30000");
540
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
547
541
  try {
548
542
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
549
543
  } catch {
@@ -1337,11 +1331,12 @@ async function ensureSchema() {
1337
1331
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1338
1332
  import { existsSync } from "fs";
1339
1333
  import path from "path";
1334
+ import os from "os";
1340
1335
  import crypto from "crypto";
1341
1336
  var SERVICE = "exe-mem";
1342
1337
  var ACCOUNT = "master-key";
1343
1338
  function getKeyDir() {
1344
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1339
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1345
1340
  }
1346
1341
  function getKeyPath() {
1347
1342
  return path.join(getKeyDir(), "master.key");
@@ -1378,6 +1373,30 @@ async function getMasterKey() {
1378
1373
 
1379
1374
  // src/lib/store.ts
1380
1375
  init_config();
1376
+ var INIT_MAX_RETRIES = 3;
1377
+ var INIT_RETRY_DELAY_MS = 1e3;
1378
+ function isBusyError2(err) {
1379
+ if (err instanceof Error) {
1380
+ const msg = err.message.toLowerCase();
1381
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1382
+ }
1383
+ return false;
1384
+ }
1385
+ async function retryOnBusy2(fn, label) {
1386
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1387
+ try {
1388
+ return await fn();
1389
+ } catch (err) {
1390
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1391
+ process.stderr.write(
1392
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1393
+ `
1394
+ );
1395
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1396
+ }
1397
+ }
1398
+ throw new Error("unreachable");
1399
+ }
1381
1400
  var _pendingRecords = [];
1382
1401
  var _batchSize = 20;
1383
1402
  var _flushIntervalMs = 1e4;
@@ -1412,14 +1431,17 @@ async function initStore(options) {
1412
1431
  dbPath,
1413
1432
  encryptionKey: hexKey
1414
1433
  });
1415
- await ensureSchema();
1434
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1416
1435
  try {
1417
1436
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1418
1437
  initShardManager2(hexKey);
1419
1438
  } catch {
1420
1439
  }
1421
1440
  const client = getClient();
1422
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1441
+ const vResult = await retryOnBusy2(
1442
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1443
+ "version-query"
1444
+ );
1423
1445
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1424
1446
  }
1425
1447
  function classifyTier(record) {
@@ -1462,6 +1484,12 @@ async function writeMemory(record) {
1462
1484
  supersedes_id: record.supersedes_id ?? null
1463
1485
  };
1464
1486
  _pendingRecords.push(dbRow);
1487
+ const MAX_PENDING = 1e3;
1488
+ if (_pendingRecords.length > MAX_PENDING) {
1489
+ const dropped = _pendingRecords.length - MAX_PENDING;
1490
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
1491
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
1492
+ }
1465
1493
  if (_flushTimer === null) {
1466
1494
  _flushTimer = setInterval(() => {
1467
1495
  void flushBatch();
@@ -1630,8 +1658,13 @@ var _buffer = "";
1630
1658
  var _requestCount = 0;
1631
1659
  var HEALTH_CHECK_INTERVAL = 100;
1632
1660
  var _pending = /* @__PURE__ */ new Map();
1661
+ var MAX_BUFFER = 1e7;
1633
1662
  function handleData(chunk) {
1634
1663
  _buffer += chunk.toString();
1664
+ if (_buffer.length > MAX_BUFFER) {
1665
+ _buffer = "";
1666
+ return;
1667
+ }
1635
1668
  let newlineIdx;
1636
1669
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1637
1670
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -1946,11 +1979,13 @@ var MIN_LENGTH = 100;
1946
1979
  var MAX_LENGTH = 5e3;
1947
1980
  var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
1948
1981
  process.env.EXE_EMBED_PRIORITY = "low";
1982
+ var MAX_WALK_DEPTH = 10;
1949
1983
  async function findRecentJsonlFiles() {
1950
1984
  const projectsDir = path5.join(homedir(), ".claude", "projects");
1951
1985
  const cutoff = Date.now() - SEVEN_DAYS_MS;
1952
1986
  const files = [];
1953
- async function walk(dir) {
1987
+ async function walk(dir, depth = 0) {
1988
+ if (depth > MAX_WALK_DEPTH) return;
1954
1989
  let entries;
1955
1990
  try {
1956
1991
  entries = await readdir(dir, { withFileTypes: true });
@@ -1960,7 +1995,7 @@ async function findRecentJsonlFiles() {
1960
1995
  for (const entry of entries) {
1961
1996
  const full = path5.join(dir, entry.name);
1962
1997
  if (entry.isDirectory()) {
1963
- await walk(full);
1998
+ await walk(full, depth + 1);
1964
1999
  } else if (entry.name.endsWith(".jsonl")) {
1965
2000
  try {
1966
2001
  const s = await stat(full);
@@ -1,12 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
- }) : x)(function(x) {
7
- if (typeof require !== "undefined") return require.apply(this, arguments);
8
- throw Error('Dynamic require of "' + x + '" is not supported');
9
- });
10
4
  var __esm = (fn, res) => function __init() {
11
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
6
  };
@@ -16,15 +10,15 @@ var __export = (target, all) => {
16
10
  };
17
11
 
18
12
  // src/lib/config.ts
19
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
20
14
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
15
  import path2 from "path";
22
- import os from "os";
16
+ import os2 from "os";
23
17
  function resolveDataDir() {
24
18
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
25
19
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
- const newDir = path2.join(os.homedir(), ".exe-os");
27
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
20
+ const newDir = path2.join(os2.homedir(), ".exe-os");
21
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
28
22
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
29
23
  try {
30
24
  renameSync(legacyDir, newDir);
@@ -111,7 +105,7 @@ async function loadConfig() {
111
105
  normalizeAutoUpdate(migratedCfg);
112
106
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
113
107
  if (config.dbPath.startsWith("~")) {
114
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
108
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
115
109
  }
116
110
  return config;
117
111
  } catch {
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
218
212
  shardExists: () => shardExists
219
213
  });
220
214
  import path3 from "path";
221
- import { existsSync as existsSync3, mkdirSync } from "fs";
215
+ import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
222
216
  import { createClient as createClient2 } from "@libsql/client";
223
217
  function initShardManager(encryptionKey) {
224
218
  _encryptionKey = encryptionKey;
@@ -257,7 +251,6 @@ function shardExists(projectName) {
257
251
  }
258
252
  function listShards() {
259
253
  if (!existsSync3(SHARDS_DIR)) return [];
260
- const { readdirSync } = __require("fs");
261
254
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
262
255
  }
263
256
  async function ensureShardSchema(client) {
@@ -533,6 +526,7 @@ async function ensureSchema() {
533
526
  const client = getRawClient();
534
527
  await client.execute("PRAGMA journal_mode = WAL");
535
528
  await client.execute("PRAGMA busy_timeout = 30000");
529
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
536
530
  try {
537
531
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
538
532
  } catch {
@@ -1326,11 +1320,12 @@ async function ensureSchema() {
1326
1320
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1327
1321
  import { existsSync } from "fs";
1328
1322
  import path from "path";
1323
+ import os from "os";
1329
1324
  import crypto from "crypto";
1330
1325
  var SERVICE = "exe-mem";
1331
1326
  var ACCOUNT = "master-key";
1332
1327
  function getKeyDir() {
1333
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1328
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1334
1329
  }
1335
1330
  function getKeyPath() {
1336
1331
  return path.join(getKeyDir(), "master.key");
@@ -1367,6 +1362,30 @@ async function getMasterKey() {
1367
1362
 
1368
1363
  // src/lib/store.ts
1369
1364
  init_config();
1365
+ var INIT_MAX_RETRIES = 3;
1366
+ var INIT_RETRY_DELAY_MS = 1e3;
1367
+ function isBusyError2(err) {
1368
+ if (err instanceof Error) {
1369
+ const msg = err.message.toLowerCase();
1370
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1371
+ }
1372
+ return false;
1373
+ }
1374
+ async function retryOnBusy2(fn, label) {
1375
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1376
+ try {
1377
+ return await fn();
1378
+ } catch (err) {
1379
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1380
+ process.stderr.write(
1381
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1382
+ `
1383
+ );
1384
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1385
+ }
1386
+ }
1387
+ throw new Error("unreachable");
1388
+ }
1370
1389
  var _pendingRecords = [];
1371
1390
  var _batchSize = 20;
1372
1391
  var _flushIntervalMs = 1e4;
@@ -1401,14 +1420,17 @@ async function initStore(options) {
1401
1420
  dbPath,
1402
1421
  encryptionKey: hexKey
1403
1422
  });
1404
- await ensureSchema();
1423
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1405
1424
  try {
1406
1425
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1407
1426
  initShardManager2(hexKey);
1408
1427
  } catch {
1409
1428
  }
1410
1429
  const client = getClient();
1411
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1430
+ const vResult = await retryOnBusy2(
1431
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1432
+ "version-query"
1433
+ );
1412
1434
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1413
1435
  }
1414
1436
  function vectorToBlob(vector) {
@@ -1436,8 +1458,13 @@ var _buffer = "";
1436
1458
  var _requestCount = 0;
1437
1459
  var HEALTH_CHECK_INTERVAL = 100;
1438
1460
  var _pending = /* @__PURE__ */ new Map();
1461
+ var MAX_BUFFER = 1e7;
1439
1462
  function handleData(chunk) {
1440
1463
  _buffer += chunk.toString();
1464
+ if (_buffer.length > MAX_BUFFER) {
1465
+ _buffer = "";
1466
+ return;
1467
+ }
1441
1468
  let newlineIdx;
1442
1469
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1443
1470
  const line = _buffer.slice(0, newlineIdx).trim();