@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
@@ -41,7 +41,7 @@ __export(config_exports, {
41
41
  migrateConfig: () => migrateConfig,
42
42
  saveConfig: () => saveConfig
43
43
  });
44
- import { readFile, writeFile, mkdir } from "fs/promises";
44
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
45
45
  import { readFileSync, existsSync, renameSync } from "fs";
46
46
  import path from "path";
47
47
  import os from "os";
@@ -167,6 +167,9 @@ async function saveConfig(config) {
167
167
  await mkdir(dir, { recursive: true });
168
168
  const configPath = path.join(dir, "config.json");
169
169
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
170
+ if (config.cloud?.apiKey) {
171
+ await chmod(configPath, 384);
172
+ }
170
173
  }
171
174
  async function loadConfigFrom(configPath) {
172
175
  const raw = await readFile(configPath, "utf-8");
@@ -379,6 +382,7 @@ async function ensureSchema() {
379
382
  const client = getRawClient();
380
383
  await client.execute("PRAGMA journal_mode = WAL");
381
384
  await client.execute("PRAGMA busy_timeout = 30000");
385
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
382
386
  try {
383
387
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
384
388
  } catch {
@@ -1195,12 +1199,13 @@ __export(keychain_exports, {
1195
1199
  importMnemonic: () => importMnemonic,
1196
1200
  setMasterKey: () => setMasterKey
1197
1201
  });
1198
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
1202
+ import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
1199
1203
  import { existsSync as existsSync2 } from "fs";
1200
1204
  import path2 from "path";
1205
+ import os2 from "os";
1201
1206
  import crypto from "crypto";
1202
1207
  function getKeyDir() {
1203
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
1208
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(os2.homedir(), ".exe-os");
1204
1209
  }
1205
1210
  function getKeyPath() {
1206
1211
  return path2.join(getKeyDir(), "master.key");
@@ -1248,7 +1253,7 @@ async function setMasterKey(key) {
1248
1253
  await mkdir2(dir, { recursive: true });
1249
1254
  const keyPath = getKeyPath();
1250
1255
  await writeFile2(keyPath, b64 + "\n", "utf-8");
1251
- await chmod(keyPath, 384);
1256
+ await chmod2(keyPath, 384);
1252
1257
  }
1253
1258
  async function deleteMasterKey() {
1254
1259
  const keytar = await tryKeytar();
@@ -1346,7 +1351,7 @@ __export(shard_manager_exports, {
1346
1351
  shardExists: () => shardExists
1347
1352
  });
1348
1353
  import path3 from "path";
1349
- import { existsSync as existsSync3, mkdirSync } from "fs";
1354
+ import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
1350
1355
  import { createClient as createClient2 } from "@libsql/client";
1351
1356
  function initShardManager(encryptionKey) {
1352
1357
  _encryptionKey = encryptionKey;
@@ -1385,8 +1390,7 @@ function shardExists(projectName) {
1385
1390
  }
1386
1391
  function listShards() {
1387
1392
  if (!existsSync3(SHARDS_DIR)) return [];
1388
- const { readdirSync: readdirSync3 } = __require("fs");
1389
- return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1393
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1390
1394
  }
1391
1395
  async function ensureShardSchema(client) {
1392
1396
  await client.execute("PRAGMA journal_mode = WAL");
@@ -1591,6 +1595,28 @@ __export(store_exports, {
1591
1595
  vectorToBlob: () => vectorToBlob,
1592
1596
  writeMemory: () => writeMemory
1593
1597
  });
1598
+ function isBusyError2(err) {
1599
+ if (err instanceof Error) {
1600
+ const msg = err.message.toLowerCase();
1601
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1602
+ }
1603
+ return false;
1604
+ }
1605
+ async function retryOnBusy2(fn, label) {
1606
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1607
+ try {
1608
+ return await fn();
1609
+ } catch (err) {
1610
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1611
+ process.stderr.write(
1612
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1613
+ `
1614
+ );
1615
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1616
+ }
1617
+ }
1618
+ throw new Error("unreachable");
1619
+ }
1594
1620
  async function initStore(options) {
1595
1621
  if (_flushTimer !== null) {
1596
1622
  clearInterval(_flushTimer);
@@ -1619,14 +1645,17 @@ async function initStore(options) {
1619
1645
  dbPath,
1620
1646
  encryptionKey: hexKey
1621
1647
  });
1622
- await ensureSchema();
1648
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1623
1649
  try {
1624
1650
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1625
1651
  initShardManager2(hexKey);
1626
1652
  } catch {
1627
1653
  }
1628
1654
  const client = getClient();
1629
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1655
+ const vResult = await retryOnBusy2(
1656
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1657
+ "version-query"
1658
+ );
1630
1659
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1631
1660
  }
1632
1661
  function classifyTier(record) {
@@ -1669,6 +1698,12 @@ async function writeMemory(record) {
1669
1698
  supersedes_id: record.supersedes_id ?? null
1670
1699
  };
1671
1700
  _pendingRecords.push(dbRow);
1701
+ const MAX_PENDING = 1e3;
1702
+ if (_pendingRecords.length > MAX_PENDING) {
1703
+ const dropped = _pendingRecords.length - MAX_PENDING;
1704
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
1705
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
1706
+ }
1672
1707
  if (_flushTimer === null) {
1673
1708
  _flushTimer = setInterval(() => {
1674
1709
  void flushBatch();
@@ -2000,7 +2035,7 @@ async function getMemoryCardinality(agentId) {
2000
2035
  return 0;
2001
2036
  }
2002
2037
  }
2003
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
2038
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
2004
2039
  var init_store = __esm({
2005
2040
  "src/lib/store.ts"() {
2006
2041
  "use strict";
@@ -2008,6 +2043,8 @@ var init_store = __esm({
2008
2043
  init_database();
2009
2044
  init_keychain();
2010
2045
  init_config();
2046
+ INIT_MAX_RETRIES = 3;
2047
+ INIT_RETRY_DELAY_MS = 1e3;
2011
2048
  _pendingRecords = [];
2012
2049
  _batchSize = 20;
2013
2050
  _flushIntervalMs = 1e4;
@@ -2027,7 +2064,7 @@ __export(session_registry_exports, {
2027
2064
  import { readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
2028
2065
  import { execSync } from "child_process";
2029
2066
  import path4 from "path";
2030
- import os2 from "os";
2067
+ import os3 from "os";
2031
2068
  function registerSession(entry) {
2032
2069
  const dir = path4.dirname(REGISTRY_PATH);
2033
2070
  if (!existsSync4(dir)) {
@@ -2073,7 +2110,7 @@ var REGISTRY_PATH;
2073
2110
  var init_session_registry = __esm({
2074
2111
  "src/lib/session-registry.ts"() {
2075
2112
  "use strict";
2076
- REGISTRY_PATH = path4.join(os2.homedir(), ".exe-os", "session-registry.json");
2113
+ REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
2077
2114
  }
2078
2115
  });
2079
2116
 
@@ -2310,7 +2347,7 @@ __export(intercom_queue_exports, {
2310
2347
  });
2311
2348
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
2312
2349
  import path5 from "path";
2313
- import os3 from "os";
2350
+ import os4 from "os";
2314
2351
  function ensureDir() {
2315
2352
  const dir = path5.dirname(QUEUE_PATH);
2316
2353
  if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
@@ -2409,10 +2446,10 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
2409
2446
  var init_intercom_queue = __esm({
2410
2447
  "src/lib/intercom-queue.ts"() {
2411
2448
  "use strict";
2412
- QUEUE_PATH = path5.join(os3.homedir(), ".exe-os", "intercom-queue.json");
2449
+ QUEUE_PATH = path5.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2413
2450
  MAX_RETRIES2 = 5;
2414
2451
  TTL_MS = 60 * 60 * 1e3;
2415
- INTERCOM_LOG = path5.join(os3.homedir(), ".exe-os", "intercom.log");
2452
+ INTERCOM_LOG = path5.join(os4.homedir(), ".exe-os", "intercom.log");
2416
2453
  }
2417
2454
  });
2418
2455
 
@@ -2563,10 +2600,10 @@ var init_plan_limits = __esm({
2563
2600
  // src/lib/notifications.ts
2564
2601
  import crypto2 from "crypto";
2565
2602
  import path9 from "path";
2566
- import os4 from "os";
2603
+ import os5 from "os";
2567
2604
  import {
2568
2605
  readFileSync as readFileSync7,
2569
- readdirSync,
2606
+ readdirSync as readdirSync2,
2570
2607
  unlinkSync,
2571
2608
  existsSync as existsSync9,
2572
2609
  rmdirSync
@@ -3128,7 +3165,7 @@ __export(tasks_review_exports, {
3128
3165
  listPendingReviews: () => listPendingReviews
3129
3166
  });
3130
3167
  import path11 from "path";
3131
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
3168
+ import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
3132
3169
  async function countPendingReviews() {
3133
3170
  const client = getClient();
3134
3171
  const result = await client.execute({
@@ -3340,7 +3377,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3340
3377
  try {
3341
3378
  const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3342
3379
  if (existsSync11(cacheDir)) {
3343
- for (const f of readdirSync2(cacheDir)) {
3380
+ for (const f of readdirSync3(cacheDir)) {
3344
3381
  if (f.startsWith("review-notified-")) {
3345
3382
  unlinkSync2(path11.join(cacheDir, f));
3346
3383
  }
@@ -4386,7 +4423,7 @@ __export(tmux_routing_exports, {
4386
4423
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4387
4424
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync } from "fs";
4388
4425
  import path15 from "path";
4389
- import os5 from "os";
4426
+ import os6 from "os";
4390
4427
  import { fileURLToPath } from "url";
4391
4428
  import { unlinkSync as unlinkSync4 } from "fs";
4392
4429
  function spawnLockPath(sessionName) {
@@ -4739,7 +4776,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4739
4776
  const transport = getTransport();
4740
4777
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4741
4778
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4742
- const logDir = path15.join(os5.homedir(), ".exe-os", "session-logs");
4779
+ const logDir = path15.join(os6.homedir(), ".exe-os", "session-logs");
4743
4780
  const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4744
4781
  if (!existsSync12(logDir)) {
4745
4782
  mkdirSync6(logDir, { recursive: true });
@@ -4755,7 +4792,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4755
4792
  } catch {
4756
4793
  }
4757
4794
  try {
4758
- const claudeJsonPath = path15.join(os5.homedir(), ".claude.json");
4795
+ const claudeJsonPath = path15.join(os6.homedir(), ".claude.json");
4759
4796
  let claudeJson = {};
4760
4797
  try {
4761
4798
  claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
@@ -4770,7 +4807,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4770
4807
  } catch {
4771
4808
  }
4772
4809
  try {
4773
- const settingsDir = path15.join(os5.homedir(), ".claude", "projects");
4810
+ const settingsDir = path15.join(os6.homedir(), ".claude", "projects");
4774
4811
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4775
4812
  const projSettingsDir = path15.join(settingsDir, normalizedKey);
4776
4813
  const settingsPath = path15.join(projSettingsDir, "settings.json");
@@ -4818,7 +4855,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4818
4855
  let legacyFallbackWarned = false;
4819
4856
  if (!useExeAgent && !useBinSymlink) {
4820
4857
  const identityPath = path15.join(
4821
- os5.homedir(),
4858
+ os6.homedir(),
4822
4859
  ".exe-os",
4823
4860
  "identity",
4824
4861
  `${employeeName}.md`
@@ -4848,7 +4885,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4848
4885
  }
4849
4886
  let sessionContextFlag = "";
4850
4887
  try {
4851
- const ctxDir = path15.join(os5.homedir(), ".exe-os", "session-cache");
4888
+ const ctxDir = path15.join(os6.homedir(), ".exe-os", "session-cache");
4852
4889
  mkdirSync6(ctxDir, { recursive: true });
4853
4890
  const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
4854
4891
  const ctxContent = [
@@ -4959,12 +4996,12 @@ var init_tmux_routing = __esm({
4959
4996
  init_provider_table();
4960
4997
  init_intercom_queue();
4961
4998
  init_plan_limits();
4962
- SPAWN_LOCK_DIR = path15.join(os5.homedir(), ".exe-os", "spawn-locks");
4963
- SESSION_CACHE = path15.join(os5.homedir(), ".exe-os", "session-cache");
4999
+ SPAWN_LOCK_DIR = path15.join(os6.homedir(), ".exe-os", "spawn-locks");
5000
+ SESSION_CACHE = path15.join(os6.homedir(), ".exe-os", "session-cache");
4964
5001
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4965
5002
  VERIFY_PANE_LINES = 200;
4966
5003
  INTERCOM_DEBOUNCE_MS = 3e4;
4967
- INTERCOM_LOG2 = path15.join(os5.homedir(), ".exe-os", "intercom.log");
5004
+ INTERCOM_LOG2 = path15.join(os6.homedir(), ".exe-os", "intercom.log");
4968
5005
  DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
4969
5006
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4970
5007
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -5453,7 +5490,7 @@ __export(agent_signals_exports, {
5453
5490
  hasUnreadInbox: () => hasUnreadInbox
5454
5491
  });
5455
5492
  import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
5456
- import os6 from "os";
5493
+ import os7 from "os";
5457
5494
  import path16 from "path";
5458
5495
  async function hasOpenTasks(client, agentId) {
5459
5496
  try {
@@ -5494,7 +5531,7 @@ async function hasUnreadInbox(client, agentId) {
5494
5531
  return CONSERVATIVE_ON_ERROR;
5495
5532
  }
5496
5533
  }
5497
- function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path16.join(os6.homedir(), ".exe-os", "intercom.log")) {
5534
+ function hadRecentIntercomAck(sessionName, windowMs, nowMs = Date.now(), intercomLog = path16.join(os7.homedir(), ".exe-os", "intercom.log")) {
5498
5535
  if (!existsSync13(intercomLog)) return false;
5499
5536
  try {
5500
5537
  const raw = readFileSync10(intercomLog, "utf8");
@@ -6185,6 +6222,10 @@ import path17 from "path";
6185
6222
  import { fileURLToPath as fileURLToPath2 } from "url";
6186
6223
  function handleData(chunk) {
6187
6224
  _buffer += chunk.toString();
6225
+ if (_buffer.length > MAX_BUFFER) {
6226
+ _buffer = "";
6227
+ return;
6228
+ }
6188
6229
  let newlineIdx;
6189
6230
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
6190
6231
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -6492,7 +6533,7 @@ function disconnectClient() {
6492
6533
  entry.resolve({ error: "Client disconnected" });
6493
6534
  }
6494
6535
  }
6495
- 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;
6536
+ 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;
6496
6537
  var init_exe_daemon_client = __esm({
6497
6538
  "src/lib/exe-daemon-client.ts"() {
6498
6539
  "use strict";
@@ -6509,6 +6550,7 @@ var init_exe_daemon_client = __esm({
6509
6550
  _requestCount = 0;
6510
6551
  HEALTH_CHECK_INTERVAL = 100;
6511
6552
  _pending = /* @__PURE__ */ new Map();
6553
+ MAX_BUFFER = 1e7;
6512
6554
  }
6513
6555
  });
6514
6556
 
@@ -7250,7 +7292,8 @@ async function wikiRequest(config, path21, method = "GET", body) {
7250
7292
  const response = await fetch(url, {
7251
7293
  method,
7252
7294
  headers,
7253
- body: body ? JSON.stringify(body) : void 0
7295
+ body: body ? JSON.stringify(body) : void 0,
7296
+ signal: AbortSignal.timeout(3e4)
7254
7297
  });
7255
7298
  if (!response.ok) {
7256
7299
  throw new Error(`Wiki API ${method} ${path21}: ${response.status} ${response.statusText}`);
@@ -7440,7 +7483,7 @@ __export(device_registry_exports, {
7440
7483
  setFriendlyName: () => setFriendlyName
7441
7484
  });
7442
7485
  import crypto9 from "crypto";
7443
- import os7 from "os";
7486
+ import os8 from "os";
7444
7487
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
7445
7488
  import path19 from "path";
7446
7489
  function getDeviceInfo() {
@@ -7454,7 +7497,7 @@ function getDeviceInfo() {
7454
7497
  } catch {
7455
7498
  }
7456
7499
  }
7457
- const hostname = os7.hostname();
7500
+ const hostname = os8.hostname();
7458
7501
  const info = {
7459
7502
  deviceId: crypto9.randomUUID(),
7460
7503
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
@@ -7952,13 +7995,24 @@ var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
7952
7995
  var _context = null;
7953
7996
  var _model = null;
7954
7997
  var _llama = null;
7998
+ var MAX_QUEUE_SIZE = 1e3;
7955
7999
  var highQueue = [];
7956
8000
  var lowQueue = [];
7957
8001
  var _processing = false;
7958
8002
  var _activeConnections = 0;
7959
8003
  var _idleTimer = null;
7960
8004
  var _requestsServed = 0;
8005
+ var _droppedRequests = 0;
7961
8006
  var _startedAt = Date.now();
8007
+ function enqueue(queue, entry) {
8008
+ if (queue.length >= MAX_QUEUE_SIZE) {
8009
+ queue.shift();
8010
+ _droppedRequests++;
8011
+ process.stderr.write(`[exed] Queue overflow \u2014 dropped oldest request (total dropped: ${_droppedRequests})
8012
+ `);
8013
+ }
8014
+ queue.push(entry);
8015
+ }
7962
8016
  async function loadModel() {
7963
8017
  const modelPath = path20.join(MODELS_DIR, MODEL_FILE);
7964
8018
  if (!existsSync16(modelPath)) {
@@ -8106,8 +8160,13 @@ function startServer() {
8106
8160
  _activeConnections++;
8107
8161
  resetIdleTimer();
8108
8162
  let buffer = "";
8163
+ const MAX_BUFFER2 = 1e7;
8109
8164
  socket.on("data", (chunk) => {
8110
8165
  buffer += chunk.toString();
8166
+ if (buffer.length > MAX_BUFFER2) {
8167
+ socket.destroy(new Error("Buffer overflow \u2014 payload too large"));
8168
+ return;
8169
+ }
8111
8170
  let newlineIdx;
8112
8171
  while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
8113
8172
  const line = buffer.slice(0, newlineIdx).trim();
@@ -8125,9 +8184,9 @@ function startServer() {
8125
8184
  }
8126
8185
  const entry = { request, socket };
8127
8186
  if (request.priority === "high") {
8128
- highQueue.push(entry);
8187
+ enqueue(highQueue, entry);
8129
8188
  } else {
8130
- lowQueue.push(entry);
8189
+ enqueue(lowQueue, entry);
8131
8190
  }
8132
8191
  void processQueue();
8133
8192
  } catch (err) {
@@ -1,11 +1,5 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
3
  var __esm = (fn, res) => function __init() {
10
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
5
  };
@@ -111,6 +105,7 @@ async function ensureSchema() {
111
105
  const client = getRawClient();
112
106
  await client.execute("PRAGMA journal_mode = WAL");
113
107
  await client.execute("PRAGMA busy_timeout = 30000");
108
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
114
109
  try {
115
110
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
116
111
  } catch {
@@ -922,9 +917,10 @@ var init_database = __esm({
922
917
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
923
918
  import { existsSync } from "fs";
924
919
  import path from "path";
920
+ import os from "os";
925
921
  import crypto from "crypto";
926
922
  function getKeyDir() {
927
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
923
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
928
924
  }
929
925
  function getKeyPath() {
930
926
  return path.join(getKeyDir(), "master.key");
@@ -983,15 +979,15 @@ __export(config_exports, {
983
979
  migrateConfig: () => migrateConfig,
984
980
  saveConfig: () => saveConfig
985
981
  });
986
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
982
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
987
983
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
988
984
  import path2 from "path";
989
- import os from "os";
985
+ import os2 from "os";
990
986
  function resolveDataDir() {
991
987
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
992
988
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
993
- const newDir = path2.join(os.homedir(), ".exe-os");
994
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
989
+ const newDir = path2.join(os2.homedir(), ".exe-os");
990
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
995
991
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
996
992
  try {
997
993
  renameSync(legacyDir, newDir);
@@ -1078,7 +1074,7 @@ async function loadConfig() {
1078
1074
  normalizeAutoUpdate(migratedCfg);
1079
1075
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
1080
1076
  if (config.dbPath.startsWith("~")) {
1081
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
1077
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1082
1078
  }
1083
1079
  return config;
1084
1080
  } catch {
@@ -1109,6 +1105,9 @@ async function saveConfig(config) {
1109
1105
  await mkdir2(dir, { recursive: true });
1110
1106
  const configPath = path2.join(dir, "config.json");
1111
1107
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
1108
+ if (config.cloud?.apiKey) {
1109
+ await chmod2(configPath, 384);
1110
+ }
1112
1111
  }
1113
1112
  async function loadConfigFrom(configPath) {
1114
1113
  const raw = await readFile2(configPath, "utf-8");
@@ -1224,7 +1223,7 @@ __export(shard_manager_exports, {
1224
1223
  shardExists: () => shardExists
1225
1224
  });
1226
1225
  import path3 from "path";
1227
- import { existsSync as existsSync3, mkdirSync } from "fs";
1226
+ import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
1228
1227
  import { createClient as createClient2 } from "@libsql/client";
1229
1228
  function initShardManager(encryptionKey) {
1230
1229
  _encryptionKey = encryptionKey;
@@ -1263,8 +1262,7 @@ function shardExists(projectName) {
1263
1262
  }
1264
1263
  function listShards() {
1265
1264
  if (!existsSync3(SHARDS_DIR)) return [];
1266
- const { readdirSync: readdirSync2 } = __require("fs");
1267
- return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1265
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1268
1266
  }
1269
1267
  async function ensureShardSchema(client) {
1270
1268
  await client.execute("PRAGMA journal_mode = WAL");
@@ -1469,6 +1467,28 @@ __export(store_exports, {
1469
1467
  vectorToBlob: () => vectorToBlob,
1470
1468
  writeMemory: () => writeMemory
1471
1469
  });
1470
+ function isBusyError2(err) {
1471
+ if (err instanceof Error) {
1472
+ const msg = err.message.toLowerCase();
1473
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1474
+ }
1475
+ return false;
1476
+ }
1477
+ async function retryOnBusy2(fn, label) {
1478
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1479
+ try {
1480
+ return await fn();
1481
+ } catch (err) {
1482
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1483
+ process.stderr.write(
1484
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1485
+ `
1486
+ );
1487
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1488
+ }
1489
+ }
1490
+ throw new Error("unreachable");
1491
+ }
1472
1492
  async function initStore(options) {
1473
1493
  if (_flushTimer !== null) {
1474
1494
  clearInterval(_flushTimer);
@@ -1497,14 +1517,17 @@ async function initStore(options) {
1497
1517
  dbPath,
1498
1518
  encryptionKey: hexKey
1499
1519
  });
1500
- await ensureSchema();
1520
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1501
1521
  try {
1502
1522
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1503
1523
  initShardManager2(hexKey);
1504
1524
  } catch {
1505
1525
  }
1506
1526
  const client = getClient();
1507
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1527
+ const vResult = await retryOnBusy2(
1528
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1529
+ "version-query"
1530
+ );
1508
1531
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1509
1532
  }
1510
1533
  function classifyTier(record) {
@@ -1547,6 +1570,12 @@ async function writeMemory(record) {
1547
1570
  supersedes_id: record.supersedes_id ?? null
1548
1571
  };
1549
1572
  _pendingRecords.push(dbRow);
1573
+ const MAX_PENDING = 1e3;
1574
+ if (_pendingRecords.length > MAX_PENDING) {
1575
+ const dropped = _pendingRecords.length - MAX_PENDING;
1576
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
1577
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
1578
+ }
1550
1579
  if (_flushTimer === null) {
1551
1580
  _flushTimer = setInterval(() => {
1552
1581
  void flushBatch();
@@ -1878,7 +1907,7 @@ async function getMemoryCardinality(agentId) {
1878
1907
  return 0;
1879
1908
  }
1880
1909
  }
1881
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1910
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1882
1911
  var init_store = __esm({
1883
1912
  "src/lib/store.ts"() {
1884
1913
  "use strict";
@@ -1886,6 +1915,8 @@ var init_store = __esm({
1886
1915
  init_database();
1887
1916
  init_keychain();
1888
1917
  init_config();
1918
+ INIT_MAX_RETRIES = 3;
1919
+ INIT_RETRY_DELAY_MS = 1e3;
1889
1920
  _pendingRecords = [];
1890
1921
  _batchSize = 20;
1891
1922
  _flushIntervalMs = 1e4;
@@ -1993,6 +2024,10 @@ import path4 from "path";
1993
2024
  import { fileURLToPath } from "url";
1994
2025
  function handleData(chunk) {
1995
2026
  _buffer += chunk.toString();
2027
+ if (_buffer.length > MAX_BUFFER) {
2028
+ _buffer = "";
2029
+ return;
2030
+ }
1996
2031
  let newlineIdx;
1997
2032
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1998
2033
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -2300,7 +2335,7 @@ function disconnectClient() {
2300
2335
  entry.resolve({ error: "Client disconnected" });
2301
2336
  }
2302
2337
  }
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;
2338
+ 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
2339
  var init_exe_daemon_client = __esm({
2305
2340
  "src/lib/exe-daemon-client.ts"() {
2306
2341
  "use strict";
@@ -2317,6 +2352,7 @@ var init_exe_daemon_client = __esm({
2317
2352
  _requestCount = 0;
2318
2353
  HEALTH_CHECK_INTERVAL = 100;
2319
2354
  _pending = /* @__PURE__ */ new Map();
2355
+ MAX_BUFFER = 1e7;
2320
2356
  }
2321
2357
  });
2322
2358
 
@@ -2445,7 +2481,7 @@ __export(file_grep_exports, {
2445
2481
  grepProjectFiles: () => grepProjectFiles
2446
2482
  });
2447
2483
  import { execSync as execSync2 } from "child_process";
2448
- import { readFileSync as readFileSync3, readdirSync, statSync as statSync2, existsSync as existsSync5 } from "fs";
2484
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync5 } from "fs";
2449
2485
  import path6 from "path";
2450
2486
  import crypto2 from "crypto";
2451
2487
  function hasRipgrep() {
@@ -2590,7 +2626,7 @@ function collectFiles(root, patterns) {
2590
2626
  const basename = path6.basename(dir);
2591
2627
  if (EXCLUDE_DIRS.includes(basename)) return;
2592
2628
  try {
2593
- const entries = readdirSync(dir, { withFileTypes: true });
2629
+ const entries = readdirSync2(dir, { withFileTypes: true });
2594
2630
  for (const entry of entries) {
2595
2631
  if (files.length >= MAX_FILES) return;
2596
2632
  const rel = path6.join(relative, entry.name);