@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
@@ -106,6 +106,7 @@ async function ensureSchema() {
106
106
  const client = getRawClient();
107
107
  await client.execute("PRAGMA journal_mode = WAL");
108
108
  await client.execute("PRAGMA busy_timeout = 30000");
109
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
109
110
  try {
110
111
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
111
112
  } catch {
@@ -917,9 +918,10 @@ var init_database = __esm({
917
918
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
918
919
  import { existsSync } from "fs";
919
920
  import path from "path";
921
+ import os from "os";
920
922
  import crypto from "crypto";
921
923
  function getKeyDir() {
922
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
924
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
923
925
  }
924
926
  function getKeyPath() {
925
927
  return path.join(getKeyDir(), "master.key");
@@ -978,15 +980,15 @@ __export(config_exports, {
978
980
  migrateConfig: () => migrateConfig,
979
981
  saveConfig: () => saveConfig
980
982
  });
981
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
983
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
982
984
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
983
985
  import path2 from "path";
984
- import os from "os";
986
+ import os2 from "os";
985
987
  function resolveDataDir() {
986
988
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
987
989
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
988
- const newDir = path2.join(os.homedir(), ".exe-os");
989
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
990
+ const newDir = path2.join(os2.homedir(), ".exe-os");
991
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
990
992
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
991
993
  try {
992
994
  renameSync(legacyDir, newDir);
@@ -1073,7 +1075,7 @@ async function loadConfig() {
1073
1075
  normalizeAutoUpdate(migratedCfg);
1074
1076
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
1075
1077
  if (config.dbPath.startsWith("~")) {
1076
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
1078
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1077
1079
  }
1078
1080
  return config;
1079
1081
  } catch {
@@ -1104,6 +1106,9 @@ async function saveConfig(config) {
1104
1106
  await mkdir2(dir, { recursive: true });
1105
1107
  const configPath = path2.join(dir, "config.json");
1106
1108
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
1109
+ if (config.cloud?.apiKey) {
1110
+ await chmod2(configPath, 384);
1111
+ }
1107
1112
  }
1108
1113
  async function loadConfigFrom(configPath) {
1109
1114
  const raw = await readFile2(configPath, "utf-8");
@@ -1463,6 +1468,28 @@ __export(store_exports, {
1463
1468
  vectorToBlob: () => vectorToBlob,
1464
1469
  writeMemory: () => writeMemory
1465
1470
  });
1471
+ function isBusyError2(err) {
1472
+ if (err instanceof Error) {
1473
+ const msg = err.message.toLowerCase();
1474
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1475
+ }
1476
+ return false;
1477
+ }
1478
+ async function retryOnBusy2(fn, label) {
1479
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1480
+ try {
1481
+ return await fn();
1482
+ } catch (err) {
1483
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1484
+ process.stderr.write(
1485
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1486
+ `
1487
+ );
1488
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1489
+ }
1490
+ }
1491
+ throw new Error("unreachable");
1492
+ }
1466
1493
  async function initStore(options) {
1467
1494
  if (_flushTimer !== null) {
1468
1495
  clearInterval(_flushTimer);
@@ -1491,14 +1518,17 @@ async function initStore(options) {
1491
1518
  dbPath,
1492
1519
  encryptionKey: hexKey
1493
1520
  });
1494
- await ensureSchema();
1521
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1495
1522
  try {
1496
1523
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1497
1524
  initShardManager2(hexKey);
1498
1525
  } catch {
1499
1526
  }
1500
1527
  const client = getClient();
1501
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1528
+ const vResult = await retryOnBusy2(
1529
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1530
+ "version-query"
1531
+ );
1502
1532
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1503
1533
  }
1504
1534
  function classifyTier(record) {
@@ -1878,7 +1908,7 @@ async function getMemoryCardinality(agentId) {
1878
1908
  return 0;
1879
1909
  }
1880
1910
  }
1881
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1911
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1882
1912
  var init_store = __esm({
1883
1913
  "src/lib/store.ts"() {
1884
1914
  "use strict";
@@ -1886,6 +1916,8 @@ var init_store = __esm({
1886
1916
  init_database();
1887
1917
  init_keychain();
1888
1918
  init_config();
1919
+ INIT_MAX_RETRIES = 3;
1920
+ INIT_RETRY_DELAY_MS = 1e3;
1889
1921
  _pendingRecords = [];
1890
1922
  _batchSize = 20;
1891
1923
  _flushIntervalMs = 1e4;
@@ -1993,6 +2025,10 @@ import path4 from "path";
1993
2025
  import { fileURLToPath } from "url";
1994
2026
  function handleData(chunk) {
1995
2027
  _buffer += chunk.toString();
2028
+ if (_buffer.length > MAX_BUFFER) {
2029
+ _buffer = "";
2030
+ return;
2031
+ }
1996
2032
  let newlineIdx;
1997
2033
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1998
2034
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -2300,7 +2336,7 @@ function disconnectClient() {
2300
2336
  entry.resolve({ error: "Client disconnected" });
2301
2337
  }
2302
2338
  }
2303
- 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;
2339
+ 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;
2304
2340
  var init_exe_daemon_client = __esm({
2305
2341
  "src/lib/exe-daemon-client.ts"() {
2306
2342
  "use strict";
@@ -2317,6 +2353,7 @@ var init_exe_daemon_client = __esm({
2317
2353
  _requestCount = 0;
2318
2354
  HEALTH_CHECK_INTERVAL = 100;
2319
2355
  _pending = /* @__PURE__ */ new Map();
2356
+ MAX_BUFFER = 1e7;
2320
2357
  }
2321
2358
  });
2322
2359
 
@@ -117,6 +117,7 @@ async function ensureSchema() {
117
117
  const client = getRawClient();
118
118
  await client.execute("PRAGMA journal_mode = WAL");
119
119
  await client.execute("PRAGMA busy_timeout = 30000");
120
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
120
121
  try {
121
122
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
122
123
  } catch {
@@ -932,15 +933,15 @@ __export(config_exports, {
932
933
  migrateConfig: () => migrateConfig,
933
934
  saveConfig: () => saveConfig
934
935
  });
935
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
936
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
936
937
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
937
938
  import path2 from "path";
938
- import os from "os";
939
+ import os2 from "os";
939
940
  function resolveDataDir() {
940
941
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
941
942
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
942
- const newDir = path2.join(os.homedir(), ".exe-os");
943
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
943
+ const newDir = path2.join(os2.homedir(), ".exe-os");
944
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
944
945
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
945
946
  try {
946
947
  renameSync(legacyDir, newDir);
@@ -1027,7 +1028,7 @@ async function loadConfig() {
1027
1028
  normalizeAutoUpdate(migratedCfg);
1028
1029
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
1029
1030
  if (config.dbPath.startsWith("~")) {
1030
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
1031
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1031
1032
  }
1032
1033
  return config;
1033
1034
  } catch {
@@ -1058,6 +1059,9 @@ async function saveConfig(config) {
1058
1059
  await mkdir2(dir, { recursive: true });
1059
1060
  const configPath = path2.join(dir, "config.json");
1060
1061
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
1062
+ if (config.cloud?.apiKey) {
1063
+ await chmod2(configPath, 384);
1064
+ }
1061
1065
  }
1062
1066
  async function loadConfigFrom(configPath) {
1063
1067
  const raw = await readFile2(configPath, "utf-8");
@@ -1451,6 +1455,10 @@ import path5 from "path";
1451
1455
  import { fileURLToPath } from "url";
1452
1456
  function handleData(chunk) {
1453
1457
  _buffer += chunk.toString();
1458
+ if (_buffer.length > MAX_BUFFER) {
1459
+ _buffer = "";
1460
+ return;
1461
+ }
1454
1462
  let newlineIdx;
1455
1463
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1456
1464
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -1758,7 +1766,7 @@ function disconnectClient() {
1758
1766
  entry.resolve({ error: "Client disconnected" });
1759
1767
  }
1760
1768
  }
1761
- 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;
1769
+ 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;
1762
1770
  var init_exe_daemon_client = __esm({
1763
1771
  "src/lib/exe-daemon-client.ts"() {
1764
1772
  "use strict";
@@ -1775,6 +1783,7 @@ var init_exe_daemon_client = __esm({
1775
1783
  _requestCount = 0;
1776
1784
  HEALTH_CHECK_INTERVAL = 100;
1777
1785
  _pending = /* @__PURE__ */ new Map();
1786
+ MAX_BUFFER = 1e7;
1778
1787
  }
1779
1788
  });
1780
1789
 
@@ -1877,7 +1886,7 @@ var init_employees = __esm({
1877
1886
  // src/lib/notifications.ts
1878
1887
  import crypto2 from "crypto";
1879
1888
  import path7 from "path";
1880
- import os2 from "os";
1889
+ import os3 from "os";
1881
1890
  import {
1882
1891
  readFileSync as readFileSync4,
1883
1892
  readdirSync as readdirSync2,
@@ -2129,12 +2138,12 @@ var init_tasks_crud = __esm({
2129
2138
 
2130
2139
  // src/lib/session-registry.ts
2131
2140
  import path9 from "path";
2132
- import os3 from "os";
2141
+ import os4 from "os";
2133
2142
  var REGISTRY_PATH;
2134
2143
  var init_session_registry = __esm({
2135
2144
  "src/lib/session-registry.ts"() {
2136
2145
  "use strict";
2137
- REGISTRY_PATH = path9.join(os3.homedir(), ".exe-os", "session-registry.json");
2146
+ REGISTRY_PATH = path9.join(os4.homedir(), ".exe-os", "session-registry.json");
2138
2147
  }
2139
2148
  });
2140
2149
 
@@ -2312,7 +2321,7 @@ var init_provider_table = __esm({
2312
2321
  // src/lib/intercom-queue.ts
2313
2322
  import { readFileSync as readFileSync6, writeFileSync, renameSync as renameSync2, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
2314
2323
  import path10 from "path";
2315
- import os4 from "os";
2324
+ import os5 from "os";
2316
2325
  function ensureDir() {
2317
2326
  const dir = path10.dirname(QUEUE_PATH);
2318
2327
  if (!existsSync8(dir)) mkdirSync2(dir, { recursive: true });
@@ -2352,9 +2361,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2352
2361
  var init_intercom_queue = __esm({
2353
2362
  "src/lib/intercom-queue.ts"() {
2354
2363
  "use strict";
2355
- QUEUE_PATH = path10.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2364
+ QUEUE_PATH = path10.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2356
2365
  TTL_MS = 60 * 60 * 1e3;
2357
- INTERCOM_LOG = path10.join(os4.homedir(), ".exe-os", "intercom.log");
2366
+ INTERCOM_LOG = path10.join(os5.homedir(), ".exe-os", "intercom.log");
2358
2367
  }
2359
2368
  });
2360
2369
 
@@ -2392,7 +2401,7 @@ var init_plan_limits = __esm({
2392
2401
  // src/lib/tmux-routing.ts
2393
2402
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync11, appendFileSync } from "fs";
2394
2403
  import path13 from "path";
2395
- import os5 from "os";
2404
+ import os6 from "os";
2396
2405
  import { fileURLToPath as fileURLToPath2 } from "url";
2397
2406
  function getParentExe(sessionKey) {
2398
2407
  try {
@@ -2515,10 +2524,10 @@ var init_tmux_routing = __esm({
2515
2524
  init_provider_table();
2516
2525
  init_intercom_queue();
2517
2526
  init_plan_limits();
2518
- SPAWN_LOCK_DIR = path13.join(os5.homedir(), ".exe-os", "spawn-locks");
2519
- SESSION_CACHE = path13.join(os5.homedir(), ".exe-os", "session-cache");
2527
+ SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
2528
+ SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
2520
2529
  INTERCOM_DEBOUNCE_MS = 3e4;
2521
- INTERCOM_LOG2 = path13.join(os5.homedir(), ".exe-os", "intercom.log");
2530
+ INTERCOM_LOG2 = path13.join(os6.homedir(), ".exe-os", "intercom.log");
2522
2531
  DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
2523
2532
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2524
2533
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -2993,11 +3002,12 @@ init_database();
2993
3002
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
2994
3003
  import { existsSync } from "fs";
2995
3004
  import path from "path";
3005
+ import os from "os";
2996
3006
  import crypto from "crypto";
2997
3007
  var SERVICE = "exe-mem";
2998
3008
  var ACCOUNT = "master-key";
2999
3009
  function getKeyDir() {
3000
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
3010
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
3001
3011
  }
3002
3012
  function getKeyPath() {
3003
3013
  return path.join(getKeyDir(), "master.key");
@@ -3034,6 +3044,30 @@ async function getMasterKey() {
3034
3044
 
3035
3045
  // src/lib/store.ts
3036
3046
  init_config();
3047
+ var INIT_MAX_RETRIES = 3;
3048
+ var INIT_RETRY_DELAY_MS = 1e3;
3049
+ function isBusyError2(err) {
3050
+ if (err instanceof Error) {
3051
+ const msg = err.message.toLowerCase();
3052
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
3053
+ }
3054
+ return false;
3055
+ }
3056
+ async function retryOnBusy2(fn, label) {
3057
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
3058
+ try {
3059
+ return await fn();
3060
+ } catch (err) {
3061
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
3062
+ process.stderr.write(
3063
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
3064
+ `
3065
+ );
3066
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
3067
+ }
3068
+ }
3069
+ throw new Error("unreachable");
3070
+ }
3037
3071
  var _pendingRecords = [];
3038
3072
  var _batchSize = 20;
3039
3073
  var _flushIntervalMs = 1e4;
@@ -3068,14 +3102,17 @@ async function initStore(options) {
3068
3102
  dbPath,
3069
3103
  encryptionKey: hexKey
3070
3104
  });
3071
- await ensureSchema();
3105
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
3072
3106
  try {
3073
3107
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
3074
3108
  initShardManager2(hexKey);
3075
3109
  } catch {
3076
3110
  }
3077
3111
  const client = getClient();
3078
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
3112
+ const vResult = await retryOnBusy2(
3113
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
3114
+ "version-query"
3115
+ );
3079
3116
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
3080
3117
  }
3081
3118
  function classifyTier(record) {
@@ -2,7 +2,7 @@
2
2
  import { createInterface } from "readline";
3
3
 
4
4
  // src/lib/config.ts
5
- import { readFile, writeFile, mkdir } from "fs/promises";
5
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
6
6
  import { readFileSync, existsSync, renameSync } from "fs";
7
7
  import path from "path";
8
8
  import os from "os";
@@ -188,6 +188,9 @@ async function saveConfig(config) {
188
188
  await mkdir(dir, { recursive: true });
189
189
  const configPath = path.join(dir, "config.json");
190
190
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
191
+ if (config.cloud?.apiKey) {
192
+ await chmod(configPath, 384);
193
+ }
191
194
  }
192
195
 
193
196
  // src/lib/is-main.ts
@@ -204,6 +207,62 @@ function isMainModule(importMetaUrl) {
204
207
  }
205
208
  }
206
209
 
210
+ // src/lib/cloud-sync.ts
211
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync5, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
212
+ import path5 from "path";
213
+ import { homedir } from "os";
214
+
215
+ // src/lib/database.ts
216
+ import { createClient } from "@libsql/client";
217
+
218
+ // src/lib/crypto.ts
219
+ import crypto from "crypto";
220
+
221
+ // src/lib/compress.ts
222
+ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
223
+
224
+ // src/lib/plan-limits.ts
225
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
226
+ import path4 from "path";
227
+
228
+ // src/lib/employees.ts
229
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
230
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
231
+ import { execSync } from "child_process";
232
+ import path2 from "path";
233
+ var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
234
+
235
+ // src/lib/license.ts
236
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
237
+ import { randomUUID } from "crypto";
238
+ import path3 from "path";
239
+ import { jwtVerify, importSPKI } from "jose";
240
+ var LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
241
+ var CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
242
+ var DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
243
+
244
+ // src/lib/plan-limits.ts
245
+ var CACHE_PATH2 = path4.join(EXE_AI_DIR, "license-cache.json");
246
+
247
+ // src/lib/cloud-sync.ts
248
+ var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
249
+ var ROSTER_LOCK_PATH = path5.join(EXE_AI_DIR, "roster-merge.lock");
250
+ function assertSecureEndpoint(endpoint) {
251
+ if (endpoint.startsWith("https://")) return;
252
+ if (endpoint.startsWith("http://")) {
253
+ try {
254
+ const parsed = new URL(endpoint);
255
+ if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
256
+ } catch {
257
+ return;
258
+ }
259
+ throw new Error(
260
+ `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
261
+ );
262
+ }
263
+ }
264
+ var ROSTER_DELETIONS_PATH = path5.join(EXE_AI_DIR, "roster-deletions.json");
265
+
207
266
  // src/bin/exe-settings.ts
208
267
  function label(value) {
209
268
  return value ? "ON" : "OFF";
@@ -315,9 +374,11 @@ async function main() {
315
374
  const endpoint = ep.trim() || "https://askexe.com/cloud";
316
375
  console.log(" Validating...");
317
376
  try {
377
+ assertSecureEndpoint(endpoint);
318
378
  const resp = await fetch(`${endpoint}/auth/verify`, {
319
379
  method: "POST",
320
- headers: { "Authorization": `Bearer ${key}`, "Content-Type": "application/json" }
380
+ headers: { "Authorization": `Bearer ${key}`, "Content-Type": "application/json" },
381
+ signal: AbortSignal.timeout(3e4)
321
382
  });
322
383
  if (resp.ok) {
323
384
  config.cloud = { apiKey: key, endpoint };
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir } from "fs/promises";
13
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
14
  import { readFileSync, existsSync, renameSync } from "fs";
15
15
  import path from "path";
16
16
  import os from "os";
@@ -545,6 +545,7 @@ async function ensureSchema() {
545
545
  const client = getRawClient();
546
546
  await client.execute("PRAGMA journal_mode = WAL");
547
547
  await client.execute("PRAGMA busy_timeout = 30000");
548
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
548
549
  try {
549
550
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
550
551
  } catch {
@@ -1335,14 +1336,15 @@ async function ensureSchema() {
1335
1336
  }
1336
1337
 
1337
1338
  // src/lib/keychain.ts
1338
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod } from "fs/promises";
1339
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1339
1340
  import { existsSync as existsSync3 } from "fs";
1340
1341
  import path3 from "path";
1342
+ import os2 from "os";
1341
1343
  import crypto from "crypto";
1342
1344
  var SERVICE = "exe-mem";
1343
1345
  var ACCOUNT = "master-key";
1344
1346
  function getKeyDir() {
1345
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(process.env.HOME ?? "/tmp", ".exe-os");
1347
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os2.homedir(), ".exe-os");
1346
1348
  }
1347
1349
  function getKeyPath() {
1348
1350
  return path3.join(getKeyDir(), "master.key");
@@ -1379,6 +1381,30 @@ async function getMasterKey() {
1379
1381
 
1380
1382
  // src/lib/store.ts
1381
1383
  init_config();
1384
+ var INIT_MAX_RETRIES = 3;
1385
+ var INIT_RETRY_DELAY_MS = 1e3;
1386
+ function isBusyError2(err) {
1387
+ if (err instanceof Error) {
1388
+ const msg = err.message.toLowerCase();
1389
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1390
+ }
1391
+ return false;
1392
+ }
1393
+ async function retryOnBusy2(fn, label) {
1394
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1395
+ try {
1396
+ return await fn();
1397
+ } catch (err) {
1398
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1399
+ process.stderr.write(
1400
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1401
+ `
1402
+ );
1403
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1404
+ }
1405
+ }
1406
+ throw new Error("unreachable");
1407
+ }
1382
1408
  var _pendingRecords = [];
1383
1409
  var _batchSize = 20;
1384
1410
  var _flushIntervalMs = 1e4;
@@ -1413,14 +1439,17 @@ async function initStore(options) {
1413
1439
  dbPath,
1414
1440
  encryptionKey: hexKey
1415
1441
  });
1416
- await ensureSchema();
1442
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1417
1443
  try {
1418
1444
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1419
1445
  initShardManager2(hexKey);
1420
1446
  } catch {
1421
1447
  }
1422
1448
  const client = getClient();
1423
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1449
+ const vResult = await retryOnBusy2(
1450
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1451
+ "version-query"
1452
+ );
1424
1453
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1425
1454
  }
1426
1455
 
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir } from "fs/promises";
13
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
14
  import { readFileSync, existsSync, renameSync } from "fs";
15
15
  import path from "path";
16
16
  import os from "os";
@@ -545,6 +545,7 @@ async function ensureSchema() {
545
545
  const client = getRawClient();
546
546
  await client.execute("PRAGMA journal_mode = WAL");
547
547
  await client.execute("PRAGMA busy_timeout = 30000");
548
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
548
549
  try {
549
550
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
550
551
  } catch {
@@ -1335,14 +1336,15 @@ async function ensureSchema() {
1335
1336
  }
1336
1337
 
1337
1338
  // src/lib/keychain.ts
1338
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod } from "fs/promises";
1339
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1339
1340
  import { existsSync as existsSync3 } from "fs";
1340
1341
  import path3 from "path";
1342
+ import os2 from "os";
1341
1343
  import crypto from "crypto";
1342
1344
  var SERVICE = "exe-mem";
1343
1345
  var ACCOUNT = "master-key";
1344
1346
  function getKeyDir() {
1345
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(process.env.HOME ?? "/tmp", ".exe-os");
1347
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os2.homedir(), ".exe-os");
1346
1348
  }
1347
1349
  function getKeyPath() {
1348
1350
  return path3.join(getKeyDir(), "master.key");
@@ -1379,6 +1381,30 @@ async function getMasterKey() {
1379
1381
 
1380
1382
  // src/lib/store.ts
1381
1383
  init_config();
1384
+ var INIT_MAX_RETRIES = 3;
1385
+ var INIT_RETRY_DELAY_MS = 1e3;
1386
+ function isBusyError2(err) {
1387
+ if (err instanceof Error) {
1388
+ const msg = err.message.toLowerCase();
1389
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1390
+ }
1391
+ return false;
1392
+ }
1393
+ async function retryOnBusy2(fn, label) {
1394
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1395
+ try {
1396
+ return await fn();
1397
+ } catch (err) {
1398
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1399
+ process.stderr.write(
1400
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1401
+ `
1402
+ );
1403
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1404
+ }
1405
+ }
1406
+ throw new Error("unreachable");
1407
+ }
1382
1408
  var _pendingRecords = [];
1383
1409
  var _batchSize = 20;
1384
1410
  var _flushIntervalMs = 1e4;
@@ -1413,14 +1439,17 @@ async function initStore(options) {
1413
1439
  dbPath,
1414
1440
  encryptionKey: hexKey
1415
1441
  });
1416
- await ensureSchema();
1442
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1417
1443
  try {
1418
1444
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1419
1445
  initShardManager2(hexKey);
1420
1446
  } catch {
1421
1447
  }
1422
1448
  const client = getClient();
1423
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1449
+ const vResult = await retryOnBusy2(
1450
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1451
+ "version-query"
1452
+ );
1424
1453
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1425
1454
  }
1426
1455