@askexenow/exe-os 0.8.38 → 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 (91) hide show
  1. package/README.md +17 -8
  2. package/dist/bin/backfill-conversations.js +46 -10
  3. package/dist/bin/backfill-responses.js +46 -10
  4. package/dist/bin/backfill-vectors.js +42 -8
  5. package/dist/bin/cleanup-stale-review-tasks.js +37 -8
  6. package/dist/bin/cli.js +281 -154
  7. package/dist/bin/exe-agent.js +19 -4
  8. package/dist/bin/exe-assign.js +39 -5
  9. package/dist/bin/exe-boot.js +237 -111
  10. package/dist/bin/exe-call.js +11 -6
  11. package/dist/bin/exe-cloud.js +99 -28
  12. package/dist/bin/exe-dispatch.js +1 -1
  13. package/dist/bin/exe-doctor.js +37 -8
  14. package/dist/bin/exe-export-behaviors.js +39 -10
  15. package/dist/bin/exe-forget.js +38 -9
  16. package/dist/bin/exe-gateway.js +109 -42
  17. package/dist/bin/exe-heartbeat.js +49 -20
  18. package/dist/bin/exe-kill.js +39 -10
  19. package/dist/bin/exe-launch-agent.js +58 -22
  20. package/dist/bin/exe-link.js +184 -85
  21. package/dist/bin/exe-new-employee.js +21 -7
  22. package/dist/bin/exe-pending-messages.js +46 -17
  23. package/dist/bin/exe-pending-notifications.js +37 -8
  24. package/dist/bin/exe-pending-reviews.js +47 -18
  25. package/dist/bin/exe-rename.js +21 -7
  26. package/dist/bin/exe-review.js +34 -5
  27. package/dist/bin/exe-search.js +47 -10
  28. package/dist/bin/exe-session-cleanup.js +56 -19
  29. package/dist/bin/exe-settings.js +63 -2
  30. package/dist/bin/exe-status.js +34 -5
  31. package/dist/bin/exe-team.js +34 -5
  32. package/dist/bin/git-sweep.js +38 -9
  33. package/dist/bin/graph-backfill.js +37 -8
  34. package/dist/bin/graph-export.js +37 -8
  35. package/dist/bin/install.js +1 -1
  36. package/dist/bin/scan-tasks.js +40 -11
  37. package/dist/bin/setup.js +58 -24
  38. package/dist/bin/shard-migrate.js +37 -8
  39. package/dist/bin/wiki-sync.js +39 -9
  40. package/dist/gateway/index.js +102 -37
  41. package/dist/hooks/bug-report-worker.js +62 -28
  42. package/dist/hooks/commit-complete.js +38 -9
  43. package/dist/hooks/error-recall.js +49 -8
  44. package/dist/hooks/exe-heartbeat-hook.js +3 -2
  45. package/dist/hooks/ingest-worker.js +151 -37
  46. package/dist/hooks/ingest.js +74 -28
  47. package/dist/hooks/instructions-loaded.js +39 -9
  48. package/dist/hooks/notification.js +37 -7
  49. package/dist/hooks/post-compact.js +37 -7
  50. package/dist/hooks/pre-compact.js +35 -6
  51. package/dist/hooks/pre-tool-use.js +52 -14
  52. package/dist/hooks/prompt-ingest-worker.js +56 -10
  53. package/dist/hooks/prompt-submit.js +61 -23
  54. package/dist/hooks/response-ingest-worker.js +57 -11
  55. package/dist/hooks/session-end.js +43 -10
  56. package/dist/hooks/session-start.js +46 -8
  57. package/dist/hooks/stop.js +37 -7
  58. package/dist/hooks/subagent-stop.js +37 -7
  59. package/dist/hooks/summary-worker.js +317 -99
  60. package/dist/index.js +87 -22
  61. package/dist/lib/cloud-sync.js +172 -78
  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/employees.js +11 -6
  68. package/dist/lib/exe-daemon-client.js +6 -1
  69. package/dist/lib/exe-daemon.js +71 -28
  70. package/dist/lib/hybrid-search.js +47 -10
  71. package/dist/lib/identity.js +1 -1
  72. package/dist/lib/keychain.js +2 -1
  73. package/dist/lib/license.js +13 -4
  74. package/dist/lib/messaging.js +1 -1
  75. package/dist/lib/reminders.js +2 -2
  76. package/dist/lib/schedules.js +37 -8
  77. package/dist/lib/skill-learning.js +1 -1
  78. package/dist/lib/store.js +37 -8
  79. package/dist/lib/tasks.js +1 -1
  80. package/dist/lib/tmux-routing.js +1 -1
  81. package/dist/mcp/server.js +97 -43
  82. package/dist/mcp/tools/complete-reminder.js +1 -1
  83. package/dist/mcp/tools/create-task.js +14 -6
  84. package/dist/mcp/tools/deactivate-behavior.js +2 -2
  85. package/dist/mcp/tools/list-reminders.js +1 -1
  86. package/dist/mcp/tools/list-tasks.js +1 -1
  87. package/dist/mcp/tools/send-message.js +1 -1
  88. package/dist/mcp/tools/update-task.js +1 -1
  89. package/dist/runtime/index.js +35 -6
  90. package/dist/tui/App.js +177 -95
  91. package/package.json +3 -3
@@ -62,7 +62,8 @@ async function gqlRequest(query, variables) {
62
62
  "Content-Type": "application/json",
63
63
  Authorization: `Bearer ${config.apiToken}`
64
64
  },
65
- body: JSON.stringify({ query, variables })
65
+ body: JSON.stringify({ query, variables }),
66
+ signal: AbortSignal.timeout(3e4)
66
67
  });
67
68
  if (!res.ok) {
68
69
  throw new Error(`CRM GraphQL request failed: ${res.status} ${res.statusText}`);
@@ -369,6 +370,7 @@ async function ensureSchema() {
369
370
  const client = getRawClient();
370
371
  await client.execute("PRAGMA journal_mode = WAL");
371
372
  await client.execute("PRAGMA busy_timeout = 30000");
373
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
372
374
  try {
373
375
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
374
376
  } catch {
@@ -1201,7 +1203,7 @@ __export(config_exports, {
1201
1203
  migrateConfig: () => migrateConfig,
1202
1204
  saveConfig: () => saveConfig
1203
1205
  });
1204
- import { readFile, writeFile, mkdir } from "fs/promises";
1206
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
1205
1207
  import { readFileSync, existsSync, renameSync } from "fs";
1206
1208
  import path from "path";
1207
1209
  import os from "os";
@@ -1327,6 +1329,9 @@ async function saveConfig(config2) {
1327
1329
  await mkdir(dir, { recursive: true });
1328
1330
  const configPath = path.join(dir, "config.json");
1329
1331
  await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n");
1332
+ if (config2.cloud?.apiKey) {
1333
+ await chmod(configPath, 384);
1334
+ }
1330
1335
  }
1331
1336
  async function loadConfigFrom(configPath) {
1332
1337
  const raw = await readFile(configPath, "utf-8");
@@ -1437,6 +1442,10 @@ import path2 from "path";
1437
1442
  import { fileURLToPath } from "url";
1438
1443
  function handleData(chunk) {
1439
1444
  _buffer += chunk.toString();
1445
+ if (_buffer.length > MAX_BUFFER) {
1446
+ _buffer = "";
1447
+ return;
1448
+ }
1440
1449
  let newlineIdx;
1441
1450
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1442
1451
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -1744,7 +1753,7 @@ function disconnectClient() {
1744
1753
  entry.resolve({ error: "Client disconnected" });
1745
1754
  }
1746
1755
  }
1747
- 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;
1756
+ 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;
1748
1757
  var init_exe_daemon_client = __esm({
1749
1758
  "src/lib/exe-daemon-client.ts"() {
1750
1759
  "use strict";
@@ -1761,6 +1770,7 @@ var init_exe_daemon_client = __esm({
1761
1770
  _requestCount = 0;
1762
1771
  HEALTH_CHECK_INTERVAL = 100;
1763
1772
  _pending = /* @__PURE__ */ new Map();
1773
+ MAX_BUFFER = 1e7;
1764
1774
  }
1765
1775
  });
1766
1776
 
@@ -1833,12 +1843,13 @@ var init_embedder = __esm({
1833
1843
  });
1834
1844
 
1835
1845
  // src/lib/keychain.ts
1836
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
1846
+ import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
1837
1847
  import { existsSync as existsSync3 } from "fs";
1838
1848
  import path3 from "path";
1849
+ import os2 from "os";
1839
1850
  import crypto from "crypto";
1840
1851
  function getKeyDir() {
1841
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(process.env.HOME ?? "/tmp", ".exe-os");
1852
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os2.homedir(), ".exe-os");
1842
1853
  }
1843
1854
  function getKeyPath() {
1844
1855
  return path3.join(getKeyDir(), "master.key");
@@ -2139,6 +2150,28 @@ __export(store_exports, {
2139
2150
  vectorToBlob: () => vectorToBlob,
2140
2151
  writeMemory: () => writeMemory
2141
2152
  });
2153
+ function isBusyError2(err) {
2154
+ if (err instanceof Error) {
2155
+ const msg = err.message.toLowerCase();
2156
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
2157
+ }
2158
+ return false;
2159
+ }
2160
+ async function retryOnBusy2(fn, label) {
2161
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
2162
+ try {
2163
+ return await fn();
2164
+ } catch (err) {
2165
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
2166
+ process.stderr.write(
2167
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
2168
+ `
2169
+ );
2170
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
2171
+ }
2172
+ }
2173
+ throw new Error("unreachable");
2174
+ }
2142
2175
  async function initStore(options) {
2143
2176
  if (_flushTimer !== null) {
2144
2177
  clearInterval(_flushTimer);
@@ -2167,14 +2200,17 @@ async function initStore(options) {
2167
2200
  dbPath,
2168
2201
  encryptionKey: hexKey
2169
2202
  });
2170
- await ensureSchema();
2203
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
2171
2204
  try {
2172
2205
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
2173
2206
  initShardManager2(hexKey);
2174
2207
  } catch {
2175
2208
  }
2176
2209
  const client = getClient();
2177
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2210
+ const vResult = await retryOnBusy2(
2211
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
2212
+ "version-query"
2213
+ );
2178
2214
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2179
2215
  }
2180
2216
  function classifyTier(record) {
@@ -2554,7 +2590,7 @@ async function getMemoryCardinality(agentId) {
2554
2590
  return 0;
2555
2591
  }
2556
2592
  }
2557
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
2593
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
2558
2594
  var init_store = __esm({
2559
2595
  "src/lib/store.ts"() {
2560
2596
  "use strict";
@@ -2562,6 +2598,8 @@ var init_store = __esm({
2562
2598
  init_database();
2563
2599
  init_keychain();
2564
2600
  init_config();
2601
+ INIT_MAX_RETRIES = 3;
2602
+ INIT_RETRY_DELAY_MS = 1e3;
2565
2603
  _pendingRecords = [];
2566
2604
  _batchSize = 20;
2567
2605
  _flushIntervalMs = 1e4;
@@ -2589,12 +2627,30 @@ async function wikiFetch(config2, path20, method = "GET", body) {
2589
2627
  const controller = new AbortController();
2590
2628
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
2591
2629
  try {
2592
- const response = await fetch(url, {
2593
- method,
2594
- headers,
2595
- body: body ? JSON.stringify(body) : void 0,
2596
- signal: controller.signal
2597
- });
2630
+ let response;
2631
+ try {
2632
+ response = await fetch(url, {
2633
+ method,
2634
+ headers,
2635
+ body: body ? JSON.stringify(body) : void 0,
2636
+ signal: controller.signal
2637
+ });
2638
+ } catch {
2639
+ clearTimeout(timeout);
2640
+ const retryController = new AbortController();
2641
+ const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
2642
+ try {
2643
+ await new Promise((r) => setTimeout(r, 500));
2644
+ response = await fetch(url, {
2645
+ method,
2646
+ headers,
2647
+ body: body ? JSON.stringify(body) : void 0,
2648
+ signal: retryController.signal
2649
+ });
2650
+ } finally {
2651
+ clearTimeout(retryTimeout);
2652
+ }
2653
+ }
2598
2654
  if (!response.ok) {
2599
2655
  throw new Error(`Wiki API ${method} ${path20}: ${response.status} ${response.statusText}`);
2600
2656
  }
@@ -2716,6 +2772,14 @@ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync5
2716
2772
  import { randomUUID as randomUUID2 } from "crypto";
2717
2773
  import path5 from "path";
2718
2774
  import { jwtVerify, importSPKI } from "jose";
2775
+ async function fetchRetry(url, init) {
2776
+ try {
2777
+ return await fetch(url, init);
2778
+ } catch {
2779
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
2780
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
2781
+ }
2782
+ }
2719
2783
  function loadDeviceId() {
2720
2784
  const deviceJsonPath = path5.join(EXE_AI_DIR, "device.json");
2721
2785
  try {
@@ -2747,7 +2811,7 @@ function loadLicense() {
2747
2811
  }
2748
2812
  function saveLicense(apiKey) {
2749
2813
  mkdirSync2(EXE_AI_DIR, { recursive: true });
2750
- writeFileSync(LICENSE_PATH, apiKey.trim(), "utf8");
2814
+ writeFileSync(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2751
2815
  }
2752
2816
  async function verifyLicenseJwt(token) {
2753
2817
  try {
@@ -2799,7 +2863,7 @@ function cacheResponse(token) {
2799
2863
  async function validateLicense(apiKey, deviceId) {
2800
2864
  const did = deviceId ?? loadDeviceId();
2801
2865
  try {
2802
- const res = await fetch(`${API_BASE}/auth/activate`, {
2866
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2803
2867
  method: "POST",
2804
2868
  headers: { "Content-Type": "application/json" },
2805
2869
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -2890,7 +2954,7 @@ async function assertVpsLicense(opts) {
2890
2954
  let explicitRejection = false;
2891
2955
  let transientFailure = false;
2892
2956
  try {
2893
- const res = await fetch(`${API_BASE}/auth/activate`, {
2957
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2894
2958
  method: "POST",
2895
2959
  headers: { "Content-Type": "application/json" },
2896
2960
  body: JSON.stringify({ apiKey, deviceId }),
@@ -2992,7 +3056,7 @@ function stopLicenseRevalidation() {
2992
3056
  _revalTimer = null;
2993
3057
  }
2994
3058
  }
2995
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
3059
+ 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, _revalTimer;
2996
3060
  var init_license = __esm({
2997
3061
  "src/lib/license.ts"() {
2998
3062
  "use strict";
@@ -3001,6 +3065,7 @@ var init_license = __esm({
3001
3065
  CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
3002
3066
  DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
3003
3067
  API_BASE = "https://askexe.com/cloud";
3068
+ RETRY_DELAY_MS = 500;
3004
3069
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
3005
3070
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3006
3071
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -3852,7 +3917,7 @@ __export(imessage_exports, {
3852
3917
  });
3853
3918
  import { execFile } from "child_process";
3854
3919
  import { promisify } from "util";
3855
- import os2 from "os";
3920
+ import os3 from "os";
3856
3921
  import path6 from "path";
3857
3922
  var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
3858
3923
  var init_imessage = __esm({
@@ -3861,7 +3926,7 @@ var init_imessage = __esm({
3861
3926
  execFileAsync = promisify(execFile);
3862
3927
  POLL_INTERVAL_MS = 5e3;
3863
3928
  MESSAGES_DB_PATH = path6.join(
3864
- process.env.HOME ?? os2.homedir(),
3929
+ process.env.HOME ?? os3.homedir(),
3865
3930
  "Library/Messages/chat.db"
3866
3931
  );
3867
3932
  IMessageAdapter = class {
@@ -4327,7 +4392,7 @@ var init_whatsapp_accounts = __esm({
4327
4392
  // src/lib/session-registry.ts
4328
4393
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
4329
4394
  import path7 from "path";
4330
- import os3 from "os";
4395
+ import os4 from "os";
4331
4396
  function registerSession(entry) {
4332
4397
  const dir = path7.dirname(REGISTRY_PATH);
4333
4398
  if (!existsSync6(dir)) {
@@ -4354,7 +4419,7 @@ var REGISTRY_PATH;
4354
4419
  var init_session_registry = __esm({
4355
4420
  "src/lib/session-registry.ts"() {
4356
4421
  "use strict";
4357
- REGISTRY_PATH = path7.join(os3.homedir(), ".exe-os", "session-registry.json");
4422
+ REGISTRY_PATH = path7.join(os4.homedir(), ".exe-os", "session-registry.json");
4358
4423
  }
4359
4424
  });
4360
4425
 
@@ -4576,7 +4641,7 @@ var init_provider_table = __esm({
4576
4641
  // src/lib/intercom-queue.ts
4577
4642
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
4578
4643
  import path8 from "path";
4579
- import os4 from "os";
4644
+ import os5 from "os";
4580
4645
  function ensureDir() {
4581
4646
  const dir = path8.dirname(QUEUE_PATH);
4582
4647
  if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
@@ -4616,9 +4681,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
4616
4681
  var init_intercom_queue = __esm({
4617
4682
  "src/lib/intercom-queue.ts"() {
4618
4683
  "use strict";
4619
- QUEUE_PATH = path8.join(os4.homedir(), ".exe-os", "intercom-queue.json");
4684
+ QUEUE_PATH = path8.join(os5.homedir(), ".exe-os", "intercom-queue.json");
4620
4685
  TTL_MS = 60 * 60 * 1e3;
4621
- INTERCOM_LOG = path8.join(os4.homedir(), ".exe-os", "intercom.log");
4686
+ INTERCOM_LOG = path8.join(os5.homedir(), ".exe-os", "intercom.log");
4622
4687
  }
4623
4688
  });
4624
4689
 
@@ -4736,7 +4801,7 @@ var init_plan_limits = __esm({
4736
4801
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
4737
4802
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
4738
4803
  import path11 from "path";
4739
- import os5 from "os";
4804
+ import os6 from "os";
4740
4805
  import { fileURLToPath as fileURLToPath2 } from "url";
4741
4806
  import { unlinkSync as unlinkSync2 } from "fs";
4742
4807
  function spawnLockPath(sessionName) {
@@ -5041,7 +5106,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5041
5106
  const transport = getTransport();
5042
5107
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5043
5108
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5044
- const logDir = path11.join(os5.homedir(), ".exe-os", "session-logs");
5109
+ const logDir = path11.join(os6.homedir(), ".exe-os", "session-logs");
5045
5110
  const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5046
5111
  if (!existsSync10(logDir)) {
5047
5112
  mkdirSync6(logDir, { recursive: true });
@@ -5057,7 +5122,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5057
5122
  } catch {
5058
5123
  }
5059
5124
  try {
5060
- const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
5125
+ const claudeJsonPath = path11.join(os6.homedir(), ".claude.json");
5061
5126
  let claudeJson = {};
5062
5127
  try {
5063
5128
  claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
@@ -5072,7 +5137,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5072
5137
  } catch {
5073
5138
  }
5074
5139
  try {
5075
- const settingsDir = path11.join(os5.homedir(), ".claude", "projects");
5140
+ const settingsDir = path11.join(os6.homedir(), ".claude", "projects");
5076
5141
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5077
5142
  const projSettingsDir = path11.join(settingsDir, normalizedKey);
5078
5143
  const settingsPath = path11.join(projSettingsDir, "settings.json");
@@ -5120,7 +5185,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5120
5185
  let legacyFallbackWarned = false;
5121
5186
  if (!useExeAgent && !useBinSymlink) {
5122
5187
  const identityPath = path11.join(
5123
- os5.homedir(),
5188
+ os6.homedir(),
5124
5189
  ".exe-os",
5125
5190
  "identity",
5126
5191
  `${employeeName}.md`
@@ -5150,7 +5215,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5150
5215
  }
5151
5216
  let sessionContextFlag = "";
5152
5217
  try {
5153
- const ctxDir = path11.join(os5.homedir(), ".exe-os", "session-cache");
5218
+ const ctxDir = path11.join(os6.homedir(), ".exe-os", "session-cache");
5154
5219
  mkdirSync6(ctxDir, { recursive: true });
5155
5220
  const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
5156
5221
  const ctxContent = [
@@ -5261,11 +5326,11 @@ var init_tmux_routing = __esm({
5261
5326
  init_provider_table();
5262
5327
  init_intercom_queue();
5263
5328
  init_plan_limits();
5264
- SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
5265
- SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
5329
+ SPAWN_LOCK_DIR = path11.join(os6.homedir(), ".exe-os", "spawn-locks");
5330
+ SESSION_CACHE = path11.join(os6.homedir(), ".exe-os", "session-cache");
5266
5331
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5267
5332
  INTERCOM_DEBOUNCE_MS = 3e4;
5268
- INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
5333
+ INTERCOM_LOG2 = path11.join(os6.homedir(), ".exe-os", "intercom.log");
5269
5334
  DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
5270
5335
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5271
5336
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -5525,7 +5590,7 @@ var init_messaging = __esm({
5525
5590
  // src/lib/notifications.ts
5526
5591
  import crypto4 from "crypto";
5527
5592
  import path12 from "path";
5528
- import os6 from "os";
5593
+ import os7 from "os";
5529
5594
  import {
5530
5595
  readFileSync as readFileSync10,
5531
5596
  readdirSync as readdirSync2,
@@ -6842,7 +6907,7 @@ var init_tasks = __esm({
6842
6907
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
6843
6908
  import { randomUUID as randomUUID7 } from "crypto";
6844
6909
  import path18 from "path";
6845
- import os7 from "os";
6910
+ import os8 from "os";
6846
6911
  function substituteTemplate(template, record) {
6847
6912
  return template.replace(
6848
6913
  /\{\{(\w+(?:\.\w+)*)\}\}/g,
@@ -6920,7 +6985,7 @@ async function executeSendWhatsapp(params) {
6920
6985
  const message = params.message ?? params.text;
6921
6986
  if (!to || !message)
6922
6987
  throw new Error("send_whatsapp requires 'to' and 'message' params");
6923
- const url = `https://graph.facebook.com/v21.0/${account.phoneNumberId}/messages`;
6988
+ const url = `https://graph.facebook.com/${GRAPH_API_VERSION}/${account.phoneNumberId}/messages`;
6924
6989
  const res = await fetch(url, {
6925
6990
  method: "POST",
6926
6991
  headers: {
@@ -6932,7 +6997,8 @@ async function executeSendWhatsapp(params) {
6932
6997
  to,
6933
6998
  type: "text",
6934
6999
  text: { body: message }
6935
- })
7000
+ }),
7001
+ signal: AbortSignal.timeout(3e4)
6936
7002
  });
6937
7003
  if (!res.ok) {
6938
7004
  const errBody = await res.text();
@@ -7127,11 +7193,12 @@ async function processCRMEvent(event, executor, triggersOverride) {
7127
7193
  }
7128
7194
  return logs;
7129
7195
  }
7130
- var TRIGGERS_PATH;
7196
+ var TRIGGERS_PATH, GRAPH_API_VERSION;
7131
7197
  var init_trigger_engine = __esm({
7132
7198
  "src/automation/trigger-engine.ts"() {
7133
7199
  "use strict";
7134
- TRIGGERS_PATH = path18.join(os7.homedir(), ".exe-os", "triggers.json");
7200
+ TRIGGERS_PATH = path18.join(os8.homedir(), ".exe-os", "triggers.json");
7201
+ GRAPH_API_VERSION = "v21.0";
7135
7202
  }
7136
7203
  });
7137
7204
 
@@ -7195,7 +7262,7 @@ var init_crm_webhook = __esm({
7195
7262
  // src/bin/exe-gateway.ts
7196
7263
  import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
7197
7264
  import path19 from "path";
7198
- import os8 from "os";
7265
+ import os9 from "os";
7199
7266
 
7200
7267
  // src/gateway/webhook-server.ts
7201
7268
  import {
@@ -8019,7 +8086,7 @@ var BotRegistry = class {
8019
8086
  };
8020
8087
 
8021
8088
  // src/bin/exe-gateway.ts
8022
- var CONFIG_DIR = path19.join(os8.homedir(), ".exe-os");
8089
+ var CONFIG_DIR = path19.join(os9.homedir(), ".exe-os");
8023
8090
  var CONFIG_PATH3 = path19.join(CONFIG_DIR, "gateway.json");
8024
8091
  var DEFAULT_PORT = 3100;
8025
8092
  function loadConfig2() {
@@ -97,6 +97,7 @@ async function ensureSchema() {
97
97
  const client = getRawClient();
98
98
  await client.execute("PRAGMA journal_mode = WAL");
99
99
  await client.execute("PRAGMA busy_timeout = 30000");
100
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
100
101
  try {
101
102
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
102
103
  } catch {
@@ -897,15 +898,15 @@ var init_database = __esm({
897
898
  });
898
899
 
899
900
  // src/lib/config.ts
900
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
901
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
901
902
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
902
903
  import path2 from "path";
903
- import os from "os";
904
+ import os2 from "os";
904
905
  function resolveDataDir() {
905
906
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
906
907
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
907
- const newDir = path2.join(os.homedir(), ".exe-os");
908
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
908
+ const newDir = path2.join(os2.homedir(), ".exe-os");
909
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
909
910
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
910
911
  try {
911
912
  renameSync(legacyDir, newDir);
@@ -992,7 +993,7 @@ async function loadConfig() {
992
993
  normalizeAutoUpdate(migratedCfg);
993
994
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
994
995
  if (config.dbPath.startsWith("~")) {
995
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
996
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
996
997
  }
997
998
  return config;
998
999
  } catch {
@@ -1362,7 +1363,7 @@ var init_employees = __esm({
1362
1363
  // src/lib/notifications.ts
1363
1364
  import crypto2 from "crypto";
1364
1365
  import path5 from "path";
1365
- import os2 from "os";
1366
+ import os3 from "os";
1366
1367
  import {
1367
1368
  readFileSync as readFileSync3,
1368
1369
  readdirSync as readdirSync2,
@@ -1392,12 +1393,12 @@ var init_tasks_crud = __esm({
1392
1393
 
1393
1394
  // src/lib/session-registry.ts
1394
1395
  import path7 from "path";
1395
- import os3 from "os";
1396
+ import os4 from "os";
1396
1397
  var REGISTRY_PATH;
1397
1398
  var init_session_registry = __esm({
1398
1399
  "src/lib/session-registry.ts"() {
1399
1400
  "use strict";
1400
- REGISTRY_PATH = path7.join(os3.homedir(), ".exe-os", "session-registry.json");
1401
+ REGISTRY_PATH = path7.join(os4.homedir(), ".exe-os", "session-registry.json");
1401
1402
  }
1402
1403
  });
1403
1404
 
@@ -1448,14 +1449,14 @@ var init_provider_table = __esm({
1448
1449
  // src/lib/intercom-queue.ts
1449
1450
  import { readFileSync as readFileSync5, writeFileSync, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
1450
1451
  import path8 from "path";
1451
- import os4 from "os";
1452
+ import os5 from "os";
1452
1453
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
1453
1454
  var init_intercom_queue = __esm({
1454
1455
  "src/lib/intercom-queue.ts"() {
1455
1456
  "use strict";
1456
- QUEUE_PATH = path8.join(os4.homedir(), ".exe-os", "intercom-queue.json");
1457
+ QUEUE_PATH = path8.join(os5.homedir(), ".exe-os", "intercom-queue.json");
1457
1458
  TTL_MS = 60 * 60 * 1e3;
1458
- INTERCOM_LOG = path8.join(os4.homedir(), ".exe-os", "intercom.log");
1459
+ INTERCOM_LOG = path8.join(os5.homedir(), ".exe-os", "intercom.log");
1459
1460
  }
1460
1461
  });
1461
1462
 
@@ -1492,7 +1493,7 @@ var init_plan_limits = __esm({
1492
1493
 
1493
1494
  // src/lib/tmux-routing.ts
1494
1495
  import path11 from "path";
1495
- import os5 from "os";
1496
+ import os6 from "os";
1496
1497
  import { fileURLToPath as fileURLToPath2 } from "url";
1497
1498
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1498
1499
  var init_tmux_routing = __esm({
@@ -1506,9 +1507,9 @@ var init_tmux_routing = __esm({
1506
1507
  init_provider_table();
1507
1508
  init_intercom_queue();
1508
1509
  init_plan_limits();
1509
- SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
1510
- SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
1511
- INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
1510
+ SPAWN_LOCK_DIR = path11.join(os6.homedir(), ".exe-os", "spawn-locks");
1511
+ SESSION_CACHE = path11.join(os6.homedir(), ".exe-os", "session-cache");
1512
+ INTERCOM_LOG2 = path11.join(os6.homedir(), ".exe-os", "intercom.log");
1512
1513
  DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
1513
1514
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
1514
1515
  }
@@ -1543,7 +1544,7 @@ var init_tasks_review = __esm({
1543
1544
  // src/bin/exe-heartbeat.ts
1544
1545
  import { createHash } from "crypto";
1545
1546
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
1546
- import os6 from "os";
1547
+ import os7 from "os";
1547
1548
  import path13 from "path";
1548
1549
 
1549
1550
  // src/lib/store.ts
@@ -1553,11 +1554,12 @@ init_database();
1553
1554
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1554
1555
  import { existsSync } from "fs";
1555
1556
  import path from "path";
1557
+ import os from "os";
1556
1558
  import crypto from "crypto";
1557
1559
  var SERVICE = "exe-mem";
1558
1560
  var ACCOUNT = "master-key";
1559
1561
  function getKeyDir() {
1560
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1562
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1561
1563
  }
1562
1564
  function getKeyPath() {
1563
1565
  return path.join(getKeyDir(), "master.key");
@@ -1594,6 +1596,30 @@ async function getMasterKey() {
1594
1596
 
1595
1597
  // src/lib/store.ts
1596
1598
  init_config();
1599
+ var INIT_MAX_RETRIES = 3;
1600
+ var INIT_RETRY_DELAY_MS = 1e3;
1601
+ function isBusyError2(err) {
1602
+ if (err instanceof Error) {
1603
+ const msg = err.message.toLowerCase();
1604
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1605
+ }
1606
+ return false;
1607
+ }
1608
+ async function retryOnBusy2(fn, label) {
1609
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1610
+ try {
1611
+ return await fn();
1612
+ } catch (err) {
1613
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1614
+ process.stderr.write(
1615
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1616
+ `
1617
+ );
1618
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1619
+ }
1620
+ }
1621
+ throw new Error("unreachable");
1622
+ }
1597
1623
  var _pendingRecords = [];
1598
1624
  var _batchSize = 20;
1599
1625
  var _flushIntervalMs = 1e4;
@@ -1628,14 +1654,17 @@ async function initStore(options) {
1628
1654
  dbPath,
1629
1655
  encryptionKey: hexKey
1630
1656
  });
1631
- await ensureSchema();
1657
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1632
1658
  try {
1633
1659
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1634
1660
  initShardManager2(hexKey);
1635
1661
  } catch {
1636
1662
  }
1637
1663
  const client = getClient();
1638
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1664
+ const vResult = await retryOnBusy2(
1665
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1666
+ "version-query"
1667
+ );
1639
1668
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1640
1669
  }
1641
1670
 
@@ -1670,7 +1699,7 @@ var MESSAGE_PREVIEW_CHARS = 80;
1670
1699
  var MARKER_FILENAME = "exe-heartbeat-marker.json";
1671
1700
  var SESSION_CACHE_SUBDIR = "session-cache";
1672
1701
  function resolveExeOsDir() {
1673
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path13.join(os6.homedir(), ".exe-os");
1702
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path13.join(os7.homedir(), ".exe-os");
1674
1703
  }
1675
1704
  function getMarkerDir() {
1676
1705
  return path13.join(resolveExeOsDir(), SESSION_CACHE_SUBDIR);