@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
@@ -211,6 +211,7 @@ async function ensureSchema() {
211
211
  const client = getRawClient();
212
212
  await client.execute("PRAGMA journal_mode = WAL");
213
213
  await client.execute("PRAGMA busy_timeout = 30000");
214
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
214
215
  try {
215
216
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
216
217
  } catch {
@@ -1034,15 +1035,15 @@ __export(config_exports, {
1034
1035
  migrateConfig: () => migrateConfig,
1035
1036
  saveConfig: () => saveConfig
1036
1037
  });
1037
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1038
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
1038
1039
  import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
1039
1040
  import path4 from "path";
1040
- import os from "os";
1041
+ import os2 from "os";
1041
1042
  function resolveDataDir() {
1042
1043
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
1043
1044
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
1044
- const newDir = path4.join(os.homedir(), ".exe-os");
1045
- const legacyDir = path4.join(os.homedir(), ".exe-mem");
1045
+ const newDir = path4.join(os2.homedir(), ".exe-os");
1046
+ const legacyDir = path4.join(os2.homedir(), ".exe-mem");
1046
1047
  if (!existsSync3(newDir) && existsSync3(legacyDir)) {
1047
1048
  try {
1048
1049
  renameSync(legacyDir, newDir);
@@ -1129,7 +1130,7 @@ async function loadConfig() {
1129
1130
  normalizeAutoUpdate(migratedCfg);
1130
1131
  const config = { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
1131
1132
  if (config.dbPath.startsWith("~")) {
1132
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
1133
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1133
1134
  }
1134
1135
  return config;
1135
1136
  } catch {
@@ -1160,6 +1161,9 @@ async function saveConfig(config) {
1160
1161
  await mkdir2(dir, { recursive: true });
1161
1162
  const configPath = path4.join(dir, "config.json");
1162
1163
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
1164
+ if (config.cloud?.apiKey) {
1165
+ await chmod2(configPath, 384);
1166
+ }
1163
1167
  }
1164
1168
  async function loadConfigFrom(configPath) {
1165
1169
  const raw = await readFile2(configPath, "utf-8");
@@ -1275,7 +1279,7 @@ __export(shard_manager_exports, {
1275
1279
  shardExists: () => shardExists
1276
1280
  });
1277
1281
  import path5 from "path";
1278
- import { existsSync as existsSync4, mkdirSync } from "fs";
1282
+ import { existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "fs";
1279
1283
  import { createClient as createClient2 } from "@libsql/client";
1280
1284
  function initShardManager(encryptionKey) {
1281
1285
  _encryptionKey = encryptionKey;
@@ -1314,8 +1318,7 @@ function shardExists(projectName) {
1314
1318
  }
1315
1319
  function listShards() {
1316
1320
  if (!existsSync4(SHARDS_DIR)) return [];
1317
- const { readdirSync: readdirSync4 } = __require("fs");
1318
- return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1321
+ return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1319
1322
  }
1320
1323
  async function ensureShardSchema(client) {
1321
1324
  await client.execute("PRAGMA journal_mode = WAL");
@@ -1506,10 +1509,10 @@ var init_shard_manager = __esm({
1506
1509
  // src/lib/notifications.ts
1507
1510
  import crypto3 from "crypto";
1508
1511
  import path6 from "path";
1509
- import os2 from "os";
1512
+ import os3 from "os";
1510
1513
  import {
1511
1514
  readFileSync as readFileSync3,
1512
- readdirSync as readdirSync2,
1515
+ readdirSync as readdirSync3,
1513
1516
  unlinkSync,
1514
1517
  existsSync as existsSync5,
1515
1518
  rmdirSync
@@ -1592,6 +1595,14 @@ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7
1592
1595
  import { randomUUID } from "crypto";
1593
1596
  import path8 from "path";
1594
1597
  import { jwtVerify, importSPKI } from "jose";
1598
+ async function fetchRetry(url, init) {
1599
+ try {
1600
+ return await fetch(url, init);
1601
+ } catch {
1602
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1603
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
1604
+ }
1605
+ }
1595
1606
  function loadDeviceId() {
1596
1607
  const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
1597
1608
  try {
@@ -1662,7 +1673,7 @@ function cacheResponse(token) {
1662
1673
  async function validateLicense(apiKey, deviceId) {
1663
1674
  const did = deviceId ?? loadDeviceId();
1664
1675
  try {
1665
- const res = await fetch(`${API_BASE}/auth/activate`, {
1676
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1666
1677
  method: "POST",
1667
1678
  headers: { "Content-Type": "application/json" },
1668
1679
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -1697,18 +1708,27 @@ async function validateLicense(apiKey, deviceId) {
1697
1708
  } catch {
1698
1709
  const cached = await getCachedLicense();
1699
1710
  if (cached) return cached;
1700
- return FREE_LICENSE;
1711
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
1712
+ }
1713
+ }
1714
+ function getCacheAgeMs() {
1715
+ try {
1716
+ const { statSync: statSync3 } = __require("fs");
1717
+ const s = statSync3(CACHE_PATH);
1718
+ return Date.now() - s.mtimeMs;
1719
+ } catch {
1720
+ return Infinity;
1701
1721
  }
1702
1722
  }
1703
1723
  async function checkLicense() {
1704
1724
  const key = loadLicense();
1705
1725
  if (!key) return FREE_LICENSE;
1706
1726
  const cached = await getCachedLicense();
1707
- if (cached) return cached;
1727
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
1708
1728
  const deviceId = loadDeviceId();
1709
1729
  return validateLicense(key, deviceId);
1710
1730
  }
1711
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
1731
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
1712
1732
  var init_license = __esm({
1713
1733
  "src/lib/license.ts"() {
1714
1734
  "use strict";
@@ -1717,6 +1737,7 @@ var init_license = __esm({
1717
1737
  CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
1718
1738
  DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
1719
1739
  API_BASE = "https://askexe.com/cloud";
1740
+ RETRY_DELAY_MS = 500;
1720
1741
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1721
1742
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1722
1743
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -1738,6 +1759,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1738
1759
  employeeLimit: 1,
1739
1760
  memoryLimit: 5e3
1740
1761
  };
1762
+ CACHE_MAX_AGE_MS = 36e5;
1741
1763
  }
1742
1764
  });
1743
1765
 
@@ -1847,6 +1869,10 @@ import path10 from "path";
1847
1869
  import { fileURLToPath } from "url";
1848
1870
  function handleData(chunk) {
1849
1871
  _buffer += chunk.toString();
1872
+ if (_buffer.length > MAX_BUFFER) {
1873
+ _buffer = "";
1874
+ return;
1875
+ }
1850
1876
  let newlineIdx;
1851
1877
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1852
1878
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -2154,7 +2180,7 @@ function disconnectClient() {
2154
2180
  entry.resolve({ error: "Client disconnected" });
2155
2181
  }
2156
2182
  }
2157
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
2183
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
2158
2184
  var init_exe_daemon_client = __esm({
2159
2185
  "src/lib/exe-daemon-client.ts"() {
2160
2186
  "use strict";
@@ -2171,6 +2197,7 @@ var init_exe_daemon_client = __esm({
2171
2197
  _requestCount = 0;
2172
2198
  HEALTH_CHECK_INTERVAL = 100;
2173
2199
  _pending = /* @__PURE__ */ new Map();
2200
+ MAX_BUFFER = 1e7;
2174
2201
  }
2175
2202
  });
2176
2203
 
@@ -2212,8 +2239,8 @@ async function embedDirect(text) {
2212
2239
  const llamaCpp = await import("node-llama-cpp");
2213
2240
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2214
2241
  const { existsSync: existsSync15 } = await import("fs");
2215
- const path19 = await import("path");
2216
- const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2242
+ const path20 = await import("path");
2243
+ const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2217
2244
  if (!existsSync15(modelPath)) {
2218
2245
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
2219
2246
  }
@@ -2646,7 +2673,7 @@ var init_tasks_crud = __esm({
2646
2673
  // src/lib/session-registry.ts
2647
2674
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync11 } from "fs";
2648
2675
  import path12 from "path";
2649
- import os3 from "os";
2676
+ import os4 from "os";
2650
2677
  function registerSession(entry) {
2651
2678
  const dir = path12.dirname(REGISTRY_PATH);
2652
2679
  if (!existsSync11(dir)) {
@@ -2673,7 +2700,7 @@ var REGISTRY_PATH;
2673
2700
  var init_session_registry = __esm({
2674
2701
  "src/lib/session-registry.ts"() {
2675
2702
  "use strict";
2676
- REGISTRY_PATH = path12.join(os3.homedir(), ".exe-os", "session-registry.json");
2703
+ REGISTRY_PATH = path12.join(os4.homedir(), ".exe-os", "session-registry.json");
2677
2704
  }
2678
2705
  });
2679
2706
 
@@ -2872,7 +2899,7 @@ var init_provider_table = __esm({
2872
2899
  // src/lib/intercom-queue.ts
2873
2900
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
2874
2901
  import path13 from "path";
2875
- import os4 from "os";
2902
+ import os5 from "os";
2876
2903
  function ensureDir() {
2877
2904
  const dir = path13.dirname(QUEUE_PATH);
2878
2905
  if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
@@ -2912,9 +2939,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2912
2939
  var init_intercom_queue = __esm({
2913
2940
  "src/lib/intercom-queue.ts"() {
2914
2941
  "use strict";
2915
- QUEUE_PATH = path13.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2942
+ QUEUE_PATH = path13.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2916
2943
  TTL_MS = 60 * 60 * 1e3;
2917
- INTERCOM_LOG = path13.join(os4.homedir(), ".exe-os", "intercom.log");
2944
+ INTERCOM_LOG = path13.join(os5.homedir(), ".exe-os", "intercom.log");
2918
2945
  }
2919
2946
  });
2920
2947
 
@@ -2922,7 +2949,7 @@ var init_intercom_queue = __esm({
2922
2949
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
2923
2950
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync13, appendFileSync } from "fs";
2924
2951
  import path14 from "path";
2925
- import os5 from "os";
2952
+ import os6 from "os";
2926
2953
  import { fileURLToPath as fileURLToPath2 } from "url";
2927
2954
  import { unlinkSync as unlinkSync3 } from "fs";
2928
2955
  function spawnLockPath(sessionName) {
@@ -3227,7 +3254,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3227
3254
  const transport = getTransport();
3228
3255
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3229
3256
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3230
- const logDir = path14.join(os5.homedir(), ".exe-os", "session-logs");
3257
+ const logDir = path14.join(os6.homedir(), ".exe-os", "session-logs");
3231
3258
  const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3232
3259
  if (!existsSync13(logDir)) {
3233
3260
  mkdirSync5(logDir, { recursive: true });
@@ -3243,7 +3270,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3243
3270
  } catch {
3244
3271
  }
3245
3272
  try {
3246
- const claudeJsonPath = path14.join(os5.homedir(), ".claude.json");
3273
+ const claudeJsonPath = path14.join(os6.homedir(), ".claude.json");
3247
3274
  let claudeJson = {};
3248
3275
  try {
3249
3276
  claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
@@ -3258,7 +3285,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3258
3285
  } catch {
3259
3286
  }
3260
3287
  try {
3261
- const settingsDir = path14.join(os5.homedir(), ".claude", "projects");
3288
+ const settingsDir = path14.join(os6.homedir(), ".claude", "projects");
3262
3289
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3263
3290
  const projSettingsDir = path14.join(settingsDir, normalizedKey);
3264
3291
  const settingsPath = path14.join(projSettingsDir, "settings.json");
@@ -3306,7 +3333,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3306
3333
  let legacyFallbackWarned = false;
3307
3334
  if (!useExeAgent && !useBinSymlink) {
3308
3335
  const identityPath = path14.join(
3309
- os5.homedir(),
3336
+ os6.homedir(),
3310
3337
  ".exe-os",
3311
3338
  "identity",
3312
3339
  `${employeeName}.md`
@@ -3336,7 +3363,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3336
3363
  }
3337
3364
  let sessionContextFlag = "";
3338
3365
  try {
3339
- const ctxDir = path14.join(os5.homedir(), ".exe-os", "session-cache");
3366
+ const ctxDir = path14.join(os6.homedir(), ".exe-os", "session-cache");
3340
3367
  mkdirSync5(ctxDir, { recursive: true });
3341
3368
  const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
3342
3369
  const ctxContent = [
@@ -3447,11 +3474,11 @@ var init_tmux_routing = __esm({
3447
3474
  init_provider_table();
3448
3475
  init_intercom_queue();
3449
3476
  init_plan_limits();
3450
- SPAWN_LOCK_DIR = path14.join(os5.homedir(), ".exe-os", "spawn-locks");
3451
- SESSION_CACHE = path14.join(os5.homedir(), ".exe-os", "session-cache");
3477
+ SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
3478
+ SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
3452
3479
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3453
3480
  INTERCOM_DEBOUNCE_MS = 3e4;
3454
- INTERCOM_LOG2 = path14.join(os5.homedir(), ".exe-os", "intercom.log");
3481
+ INTERCOM_LOG2 = path14.join(os6.homedir(), ".exe-os", "intercom.log");
3455
3482
  DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
3456
3483
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3457
3484
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -3460,7 +3487,7 @@ var init_tmux_routing = __esm({
3460
3487
 
3461
3488
  // src/lib/tasks-review.ts
3462
3489
  import path15 from "path";
3463
- import { existsSync as existsSync14, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
3490
+ import { existsSync as existsSync14, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
3464
3491
  async function countPendingReviews() {
3465
3492
  const client = getClient();
3466
3493
  const result = await client.execute({
@@ -3582,7 +3609,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3582
3609
  try {
3583
3610
  const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3584
3611
  if (existsSync14(cacheDir)) {
3585
- for (const f of readdirSync3(cacheDir)) {
3612
+ for (const f of readdirSync4(cacheDir)) {
3586
3613
  if (f.startsWith("review-notified-")) {
3587
3614
  unlinkSync4(path15.join(cacheDir, f));
3588
3615
  }
@@ -4279,11 +4306,69 @@ var init_tasks = __esm({
4279
4306
  }
4280
4307
  });
4281
4308
 
4309
+ // src/lib/worker-gate.ts
4310
+ var worker_gate_exports = {};
4311
+ __export(worker_gate_exports, {
4312
+ MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
4313
+ cleanupWorkerPid: () => cleanupWorkerPid,
4314
+ registerWorkerPid: () => registerWorkerPid,
4315
+ tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
4316
+ });
4317
+ import { readdirSync as readdirSync5, writeFileSync as writeFileSync6, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
4318
+ import path18 from "path";
4319
+ function tryAcquireWorkerSlot() {
4320
+ try {
4321
+ mkdirSync7(WORKER_PID_DIR, { recursive: true });
4322
+ const files = readdirSync5(WORKER_PID_DIR);
4323
+ let alive = 0;
4324
+ for (const f of files) {
4325
+ if (!f.endsWith(".pid")) continue;
4326
+ const dashIdx = f.lastIndexOf("-");
4327
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
4328
+ if (isNaN(pid)) continue;
4329
+ try {
4330
+ process.kill(pid, 0);
4331
+ alive++;
4332
+ } catch {
4333
+ try {
4334
+ unlinkSync6(path18.join(WORKER_PID_DIR, f));
4335
+ } catch {
4336
+ }
4337
+ }
4338
+ }
4339
+ return alive < MAX_CONCURRENT_WORKERS;
4340
+ } catch {
4341
+ return true;
4342
+ }
4343
+ }
4344
+ function registerWorkerPid(pid) {
4345
+ try {
4346
+ mkdirSync7(WORKER_PID_DIR, { recursive: true });
4347
+ writeFileSync6(path18.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
4348
+ } catch {
4349
+ }
4350
+ }
4351
+ function cleanupWorkerPid() {
4352
+ try {
4353
+ unlinkSync6(path18.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
4354
+ } catch {
4355
+ }
4356
+ }
4357
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
4358
+ var init_worker_gate = __esm({
4359
+ "src/lib/worker-gate.ts"() {
4360
+ "use strict";
4361
+ init_config();
4362
+ WORKER_PID_DIR = path18.join(EXE_AI_DIR, "worker-pids");
4363
+ MAX_CONCURRENT_WORKERS = 3;
4364
+ }
4365
+ });
4366
+
4282
4367
  // src/adapters/claude/hooks/ingest-worker.ts
4283
4368
  import crypto7 from "crypto";
4284
4369
  import { execSync as execSync8 } from "child_process";
4285
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
4286
- import path18 from "path";
4370
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
4371
+ import path19 from "path";
4287
4372
 
4288
4373
  // src/lib/error-detector.ts
4289
4374
  init_mcp_prefix();
@@ -4397,11 +4482,12 @@ init_database();
4397
4482
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
4398
4483
  import { existsSync as existsSync2 } from "fs";
4399
4484
  import path3 from "path";
4485
+ import os from "os";
4400
4486
  import crypto2 from "crypto";
4401
4487
  var SERVICE = "exe-mem";
4402
4488
  var ACCOUNT = "master-key";
4403
4489
  function getKeyDir() {
4404
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(process.env.HOME ?? "/tmp", ".exe-os");
4490
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os.homedir(), ".exe-os");
4405
4491
  }
4406
4492
  function getKeyPath() {
4407
4493
  return path3.join(getKeyDir(), "master.key");
@@ -4438,6 +4524,30 @@ async function getMasterKey() {
4438
4524
 
4439
4525
  // src/lib/store.ts
4440
4526
  init_config();
4527
+ var INIT_MAX_RETRIES = 3;
4528
+ var INIT_RETRY_DELAY_MS = 1e3;
4529
+ function isBusyError2(err) {
4530
+ if (err instanceof Error) {
4531
+ const msg = err.message.toLowerCase();
4532
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
4533
+ }
4534
+ return false;
4535
+ }
4536
+ async function retryOnBusy2(fn, label) {
4537
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
4538
+ try {
4539
+ return await fn();
4540
+ } catch (err) {
4541
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
4542
+ process.stderr.write(
4543
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
4544
+ `
4545
+ );
4546
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
4547
+ }
4548
+ }
4549
+ throw new Error("unreachable");
4550
+ }
4441
4551
  var _pendingRecords = [];
4442
4552
  var _batchSize = 20;
4443
4553
  var _flushIntervalMs = 1e4;
@@ -4472,14 +4582,17 @@ async function initStore(options) {
4472
4582
  dbPath,
4473
4583
  encryptionKey: hexKey
4474
4584
  });
4475
- await ensureSchema();
4585
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
4476
4586
  try {
4477
4587
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4478
4588
  initShardManager2(hexKey);
4479
4589
  } catch {
4480
4590
  }
4481
4591
  const client = getClient();
4482
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4592
+ const vResult = await retryOnBusy2(
4593
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4594
+ "version-query"
4595
+ );
4483
4596
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4484
4597
  }
4485
4598
  function classifyTier(record) {
@@ -4522,6 +4635,12 @@ async function writeMemory(record) {
4522
4635
  supersedes_id: record.supersedes_id ?? null
4523
4636
  };
4524
4637
  _pendingRecords.push(dbRow);
4638
+ const MAX_PENDING = 1e3;
4639
+ if (_pendingRecords.length > MAX_PENDING) {
4640
+ const dropped = _pendingRecords.length - MAX_PENDING;
4641
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
4642
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
4643
+ }
4525
4644
  if (_flushTimer === null) {
4526
4645
  _flushTimer = setInterval(() => {
4527
4646
  void flushBatch();
@@ -4772,9 +4891,9 @@ function extractBash(input2, response) {
4772
4891
  }
4773
4892
  function extractGrep(input2, response) {
4774
4893
  const pattern = String(input2.pattern ?? "");
4775
- const path19 = input2.path ? String(input2.path) : "";
4894
+ const path20 = input2.path ? String(input2.path) : "";
4776
4895
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
4777
- return `Searched for "${pattern}"${path19 ? ` in ${path19}` : ""}
4896
+ return `Searched for "${pattern}"${path20 ? ` in ${path20}` : ""}
4778
4897
  ${output.slice(0, MAX_OUTPUT)}`;
4779
4898
  }
4780
4899
  function extractGlob(input2, response) {
@@ -4909,7 +5028,7 @@ process.stdin.on("end", async () => {
4909
5028
  try {
4910
5029
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
4911
5030
  const agentId2 = process.env.AGENT_ID;
4912
- const cachePath = path18.join(exeDir, "session-cache", `current-task-${agentId2}.json`);
5031
+ const cachePath = path19.join(exeDir, "session-cache", `current-task-${agentId2}.json`);
4913
5032
  const { readFileSync: rf } = await import("fs");
4914
5033
  const cached = JSON.parse(rf(cachePath, "utf8"));
4915
5034
  taskId = cached.taskId ?? null;
@@ -4934,8 +5053,8 @@ process.stdin.on("end", async () => {
4934
5053
  if (needsBackfill) {
4935
5054
  try {
4936
5055
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
4937
- const flagPath = path18.join(exeDir, "session-cache", "needs-backfill");
4938
- writeFileSync6(flagPath, "1");
5056
+ const flagPath = path19.join(exeDir, "session-cache", "needs-backfill");
5057
+ writeFileSync7(flagPath, "1");
4939
5058
  } catch (err) {
4940
5059
  process.stderr.write(`[ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
4941
5060
  `);
@@ -5047,8 +5166,8 @@ process.stdin.on("end", async () => {
5047
5166
  }
5048
5167
  const cwd = data.cwd ?? process.cwd();
5049
5168
  try {
5050
- mkdirSync7(path18.join(cwd, "exe/output"), { recursive: true });
5051
- mkdirSync7(path18.join(cwd, "exe/research"), { recursive: true });
5169
+ mkdirSync8(path19.join(cwd, "exe/output"), { recursive: true });
5170
+ mkdirSync8(path19.join(cwd, "exe/research"), { recursive: true });
5052
5171
  const { ensureGitignoreExe: ensureGitignoreExe2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
5053
5172
  await ensureGitignoreExe2(cwd);
5054
5173
  } catch (err) {
@@ -5058,9 +5177,19 @@ process.stdin.on("end", async () => {
5058
5177
  }
5059
5178
  }
5060
5179
  }
5180
+ try {
5181
+ const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5182
+ await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
5183
+ } catch {
5184
+ }
5061
5185
  } catch (err) {
5062
5186
  process.stderr.write(`[ingest-worker] FATAL: ${err instanceof Error ? err.message : String(err)}
5063
5187
  `);
5188
+ }
5189
+ try {
5190
+ const { cleanupWorkerPid: cleanupWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
5191
+ cleanupWorkerPid2();
5192
+ } catch {
5064
5193
  }
5065
5194
  process.exit(0);
5066
5195
  });