@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
@@ -211,6 +211,7 @@ async function ensureSchema() {
211
211
  const client = getRawClient();
212
212
  await client.execute("PRAGMA journal_mode = WAL");
213
213
  await client.execute("PRAGMA busy_timeout = 30000");
214
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
214
215
  try {
215
216
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
216
217
  } catch {
@@ -1034,15 +1035,15 @@ __export(config_exports, {
1034
1035
  migrateConfig: () => migrateConfig,
1035
1036
  saveConfig: () => saveConfig
1036
1037
  });
1037
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1038
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
1038
1039
  import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
1039
1040
  import path4 from "path";
1040
- import os from "os";
1041
+ import os2 from "os";
1041
1042
  function resolveDataDir() {
1042
1043
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
1043
1044
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
1044
- const newDir = path4.join(os.homedir(), ".exe-os");
1045
- const legacyDir = path4.join(os.homedir(), ".exe-mem");
1045
+ const newDir = path4.join(os2.homedir(), ".exe-os");
1046
+ const legacyDir = path4.join(os2.homedir(), ".exe-mem");
1046
1047
  if (!existsSync3(newDir) && existsSync3(legacyDir)) {
1047
1048
  try {
1048
1049
  renameSync(legacyDir, newDir);
@@ -1129,7 +1130,7 @@ async function loadConfig() {
1129
1130
  normalizeAutoUpdate(migratedCfg);
1130
1131
  const config = { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
1131
1132
  if (config.dbPath.startsWith("~")) {
1132
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
1133
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1133
1134
  }
1134
1135
  return config;
1135
1136
  } catch {
@@ -1160,6 +1161,9 @@ async function saveConfig(config) {
1160
1161
  await mkdir2(dir, { recursive: true });
1161
1162
  const configPath = path4.join(dir, "config.json");
1162
1163
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
1164
+ if (config.cloud?.apiKey) {
1165
+ await chmod2(configPath, 384);
1166
+ }
1163
1167
  }
1164
1168
  async function loadConfigFrom(configPath) {
1165
1169
  const raw = await readFile2(configPath, "utf-8");
@@ -1505,7 +1509,7 @@ var init_shard_manager = __esm({
1505
1509
  // src/lib/notifications.ts
1506
1510
  import crypto3 from "crypto";
1507
1511
  import path6 from "path";
1508
- import os2 from "os";
1512
+ import os3 from "os";
1509
1513
  import {
1510
1514
  readFileSync as readFileSync3,
1511
1515
  readdirSync as readdirSync3,
@@ -1591,6 +1595,14 @@ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7
1591
1595
  import { randomUUID } from "crypto";
1592
1596
  import path8 from "path";
1593
1597
  import { jwtVerify, importSPKI } from "jose";
1598
+ async function fetchRetry(url, init) {
1599
+ try {
1600
+ return await fetch(url, init);
1601
+ } catch {
1602
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1603
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
1604
+ }
1605
+ }
1594
1606
  function loadDeviceId() {
1595
1607
  const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
1596
1608
  try {
@@ -1661,7 +1673,7 @@ function cacheResponse(token) {
1661
1673
  async function validateLicense(apiKey, deviceId) {
1662
1674
  const did = deviceId ?? loadDeviceId();
1663
1675
  try {
1664
- const res = await fetch(`${API_BASE}/auth/activate`, {
1676
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1665
1677
  method: "POST",
1666
1678
  headers: { "Content-Type": "application/json" },
1667
1679
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -1716,7 +1728,7 @@ async function checkLicense() {
1716
1728
  const deviceId = loadDeviceId();
1717
1729
  return validateLicense(key, deviceId);
1718
1730
  }
1719
- 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;
1731
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS;
1720
1732
  var init_license = __esm({
1721
1733
  "src/lib/license.ts"() {
1722
1734
  "use strict";
@@ -1725,6 +1737,7 @@ var init_license = __esm({
1725
1737
  CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
1726
1738
  DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
1727
1739
  API_BASE = "https://askexe.com/cloud";
1740
+ RETRY_DELAY_MS = 500;
1728
1741
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1729
1742
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1730
1743
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -1856,6 +1869,10 @@ import path10 from "path";
1856
1869
  import { fileURLToPath } from "url";
1857
1870
  function handleData(chunk) {
1858
1871
  _buffer += chunk.toString();
1872
+ if (_buffer.length > MAX_BUFFER) {
1873
+ _buffer = "";
1874
+ return;
1875
+ }
1859
1876
  let newlineIdx;
1860
1877
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1861
1878
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -2163,7 +2180,7 @@ function disconnectClient() {
2163
2180
  entry.resolve({ error: "Client disconnected" });
2164
2181
  }
2165
2182
  }
2166
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
2183
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
2167
2184
  var init_exe_daemon_client = __esm({
2168
2185
  "src/lib/exe-daemon-client.ts"() {
2169
2186
  "use strict";
@@ -2180,6 +2197,7 @@ var init_exe_daemon_client = __esm({
2180
2197
  _requestCount = 0;
2181
2198
  HEALTH_CHECK_INTERVAL = 100;
2182
2199
  _pending = /* @__PURE__ */ new Map();
2200
+ MAX_BUFFER = 1e7;
2183
2201
  }
2184
2202
  });
2185
2203
 
@@ -2221,8 +2239,8 @@ async function embedDirect(text) {
2221
2239
  const llamaCpp = await import("node-llama-cpp");
2222
2240
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2223
2241
  const { existsSync: existsSync15 } = await import("fs");
2224
- const path19 = await import("path");
2225
- const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2242
+ const path20 = await import("path");
2243
+ const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
2226
2244
  if (!existsSync15(modelPath)) {
2227
2245
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
2228
2246
  }
@@ -2655,7 +2673,7 @@ var init_tasks_crud = __esm({
2655
2673
  // src/lib/session-registry.ts
2656
2674
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync11 } from "fs";
2657
2675
  import path12 from "path";
2658
- import os3 from "os";
2676
+ import os4 from "os";
2659
2677
  function registerSession(entry) {
2660
2678
  const dir = path12.dirname(REGISTRY_PATH);
2661
2679
  if (!existsSync11(dir)) {
@@ -2682,7 +2700,7 @@ var REGISTRY_PATH;
2682
2700
  var init_session_registry = __esm({
2683
2701
  "src/lib/session-registry.ts"() {
2684
2702
  "use strict";
2685
- REGISTRY_PATH = path12.join(os3.homedir(), ".exe-os", "session-registry.json");
2703
+ REGISTRY_PATH = path12.join(os4.homedir(), ".exe-os", "session-registry.json");
2686
2704
  }
2687
2705
  });
2688
2706
 
@@ -2881,7 +2899,7 @@ var init_provider_table = __esm({
2881
2899
  // src/lib/intercom-queue.ts
2882
2900
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
2883
2901
  import path13 from "path";
2884
- import os4 from "os";
2902
+ import os5 from "os";
2885
2903
  function ensureDir() {
2886
2904
  const dir = path13.dirname(QUEUE_PATH);
2887
2905
  if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
@@ -2921,9 +2939,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2921
2939
  var init_intercom_queue = __esm({
2922
2940
  "src/lib/intercom-queue.ts"() {
2923
2941
  "use strict";
2924
- QUEUE_PATH = path13.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2942
+ QUEUE_PATH = path13.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2925
2943
  TTL_MS = 60 * 60 * 1e3;
2926
- INTERCOM_LOG = path13.join(os4.homedir(), ".exe-os", "intercom.log");
2944
+ INTERCOM_LOG = path13.join(os5.homedir(), ".exe-os", "intercom.log");
2927
2945
  }
2928
2946
  });
2929
2947
 
@@ -2931,7 +2949,7 @@ var init_intercom_queue = __esm({
2931
2949
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
2932
2950
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync13, appendFileSync } from "fs";
2933
2951
  import path14 from "path";
2934
- import os5 from "os";
2952
+ import os6 from "os";
2935
2953
  import { fileURLToPath as fileURLToPath2 } from "url";
2936
2954
  import { unlinkSync as unlinkSync3 } from "fs";
2937
2955
  function spawnLockPath(sessionName) {
@@ -3236,7 +3254,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3236
3254
  const transport = getTransport();
3237
3255
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3238
3256
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3239
- const logDir = path14.join(os5.homedir(), ".exe-os", "session-logs");
3257
+ const logDir = path14.join(os6.homedir(), ".exe-os", "session-logs");
3240
3258
  const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3241
3259
  if (!existsSync13(logDir)) {
3242
3260
  mkdirSync5(logDir, { recursive: true });
@@ -3252,7 +3270,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3252
3270
  } catch {
3253
3271
  }
3254
3272
  try {
3255
- const claudeJsonPath = path14.join(os5.homedir(), ".claude.json");
3273
+ const claudeJsonPath = path14.join(os6.homedir(), ".claude.json");
3256
3274
  let claudeJson = {};
3257
3275
  try {
3258
3276
  claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
@@ -3267,7 +3285,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3267
3285
  } catch {
3268
3286
  }
3269
3287
  try {
3270
- const settingsDir = path14.join(os5.homedir(), ".claude", "projects");
3288
+ const settingsDir = path14.join(os6.homedir(), ".claude", "projects");
3271
3289
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3272
3290
  const projSettingsDir = path14.join(settingsDir, normalizedKey);
3273
3291
  const settingsPath = path14.join(projSettingsDir, "settings.json");
@@ -3315,7 +3333,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3315
3333
  let legacyFallbackWarned = false;
3316
3334
  if (!useExeAgent && !useBinSymlink) {
3317
3335
  const identityPath = path14.join(
3318
- os5.homedir(),
3336
+ os6.homedir(),
3319
3337
  ".exe-os",
3320
3338
  "identity",
3321
3339
  `${employeeName}.md`
@@ -3345,7 +3363,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3345
3363
  }
3346
3364
  let sessionContextFlag = "";
3347
3365
  try {
3348
- const ctxDir = path14.join(os5.homedir(), ".exe-os", "session-cache");
3366
+ const ctxDir = path14.join(os6.homedir(), ".exe-os", "session-cache");
3349
3367
  mkdirSync5(ctxDir, { recursive: true });
3350
3368
  const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
3351
3369
  const ctxContent = [
@@ -3456,11 +3474,11 @@ var init_tmux_routing = __esm({
3456
3474
  init_provider_table();
3457
3475
  init_intercom_queue();
3458
3476
  init_plan_limits();
3459
- SPAWN_LOCK_DIR = path14.join(os5.homedir(), ".exe-os", "spawn-locks");
3460
- SESSION_CACHE = path14.join(os5.homedir(), ".exe-os", "session-cache");
3477
+ SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
3478
+ SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
3461
3479
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3462
3480
  INTERCOM_DEBOUNCE_MS = 3e4;
3463
- INTERCOM_LOG2 = path14.join(os5.homedir(), ".exe-os", "intercom.log");
3481
+ INTERCOM_LOG2 = path14.join(os6.homedir(), ".exe-os", "intercom.log");
3464
3482
  DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
3465
3483
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3466
3484
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -4288,11 +4306,69 @@ var init_tasks = __esm({
4288
4306
  }
4289
4307
  });
4290
4308
 
4309
+ // src/lib/worker-gate.ts
4310
+ var worker_gate_exports = {};
4311
+ __export(worker_gate_exports, {
4312
+ MAX_CONCURRENT_WORKERS: () => MAX_CONCURRENT_WORKERS,
4313
+ cleanupWorkerPid: () => cleanupWorkerPid,
4314
+ registerWorkerPid: () => registerWorkerPid,
4315
+ tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
4316
+ });
4317
+ import { readdirSync as readdirSync5, writeFileSync as writeFileSync6, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
4318
+ import path18 from "path";
4319
+ function tryAcquireWorkerSlot() {
4320
+ try {
4321
+ mkdirSync7(WORKER_PID_DIR, { recursive: true });
4322
+ const files = readdirSync5(WORKER_PID_DIR);
4323
+ let alive = 0;
4324
+ for (const f of files) {
4325
+ if (!f.endsWith(".pid")) continue;
4326
+ const dashIdx = f.lastIndexOf("-");
4327
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
4328
+ if (isNaN(pid)) continue;
4329
+ try {
4330
+ process.kill(pid, 0);
4331
+ alive++;
4332
+ } catch {
4333
+ try {
4334
+ unlinkSync6(path18.join(WORKER_PID_DIR, f));
4335
+ } catch {
4336
+ }
4337
+ }
4338
+ }
4339
+ return alive < MAX_CONCURRENT_WORKERS;
4340
+ } catch {
4341
+ return true;
4342
+ }
4343
+ }
4344
+ function registerWorkerPid(pid) {
4345
+ try {
4346
+ mkdirSync7(WORKER_PID_DIR, { recursive: true });
4347
+ writeFileSync6(path18.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
4348
+ } catch {
4349
+ }
4350
+ }
4351
+ function cleanupWorkerPid() {
4352
+ try {
4353
+ unlinkSync6(path18.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
4354
+ } catch {
4355
+ }
4356
+ }
4357
+ var WORKER_PID_DIR, MAX_CONCURRENT_WORKERS;
4358
+ var init_worker_gate = __esm({
4359
+ "src/lib/worker-gate.ts"() {
4360
+ "use strict";
4361
+ init_config();
4362
+ WORKER_PID_DIR = path18.join(EXE_AI_DIR, "worker-pids");
4363
+ MAX_CONCURRENT_WORKERS = 3;
4364
+ }
4365
+ });
4366
+
4291
4367
  // src/adapters/claude/hooks/ingest-worker.ts
4292
4368
  import crypto7 from "crypto";
4293
4369
  import { execSync as execSync8 } from "child_process";
4294
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
4295
- import path18 from "path";
4370
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
4371
+ import path19 from "path";
4296
4372
 
4297
4373
  // src/lib/error-detector.ts
4298
4374
  init_mcp_prefix();
@@ -4406,11 +4482,12 @@ init_database();
4406
4482
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
4407
4483
  import { existsSync as existsSync2 } from "fs";
4408
4484
  import path3 from "path";
4485
+ import os from "os";
4409
4486
  import crypto2 from "crypto";
4410
4487
  var SERVICE = "exe-mem";
4411
4488
  var ACCOUNT = "master-key";
4412
4489
  function getKeyDir() {
4413
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(process.env.HOME ?? "/tmp", ".exe-os");
4490
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os.homedir(), ".exe-os");
4414
4491
  }
4415
4492
  function getKeyPath() {
4416
4493
  return path3.join(getKeyDir(), "master.key");
@@ -4447,6 +4524,30 @@ async function getMasterKey() {
4447
4524
 
4448
4525
  // src/lib/store.ts
4449
4526
  init_config();
4527
+ var INIT_MAX_RETRIES = 3;
4528
+ var INIT_RETRY_DELAY_MS = 1e3;
4529
+ function isBusyError2(err) {
4530
+ if (err instanceof Error) {
4531
+ const msg = err.message.toLowerCase();
4532
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
4533
+ }
4534
+ return false;
4535
+ }
4536
+ async function retryOnBusy2(fn, label) {
4537
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
4538
+ try {
4539
+ return await fn();
4540
+ } catch (err) {
4541
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
4542
+ process.stderr.write(
4543
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
4544
+ `
4545
+ );
4546
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
4547
+ }
4548
+ }
4549
+ throw new Error("unreachable");
4550
+ }
4450
4551
  var _pendingRecords = [];
4451
4552
  var _batchSize = 20;
4452
4553
  var _flushIntervalMs = 1e4;
@@ -4481,14 +4582,17 @@ async function initStore(options) {
4481
4582
  dbPath,
4482
4583
  encryptionKey: hexKey
4483
4584
  });
4484
- await ensureSchema();
4585
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
4485
4586
  try {
4486
4587
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4487
4588
  initShardManager2(hexKey);
4488
4589
  } catch {
4489
4590
  }
4490
4591
  const client = getClient();
4491
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
4592
+ const vResult = await retryOnBusy2(
4593
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4594
+ "version-query"
4595
+ );
4492
4596
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4493
4597
  }
4494
4598
  function classifyTier(record) {
@@ -4787,9 +4891,9 @@ function extractBash(input2, response) {
4787
4891
  }
4788
4892
  function extractGrep(input2, response) {
4789
4893
  const pattern = String(input2.pattern ?? "");
4790
- const path19 = input2.path ? String(input2.path) : "";
4894
+ const path20 = input2.path ? String(input2.path) : "";
4791
4895
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
4792
- return `Searched for "${pattern}"${path19 ? ` in ${path19}` : ""}
4896
+ return `Searched for "${pattern}"${path20 ? ` in ${path20}` : ""}
4793
4897
  ${output.slice(0, MAX_OUTPUT)}`;
4794
4898
  }
4795
4899
  function extractGlob(input2, response) {
@@ -4924,7 +5028,7 @@ process.stdin.on("end", async () => {
4924
5028
  try {
4925
5029
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
4926
5030
  const agentId2 = process.env.AGENT_ID;
4927
- const cachePath = path18.join(exeDir, "session-cache", `current-task-${agentId2}.json`);
5031
+ const cachePath = path19.join(exeDir, "session-cache", `current-task-${agentId2}.json`);
4928
5032
  const { readFileSync: rf } = await import("fs");
4929
5033
  const cached = JSON.parse(rf(cachePath, "utf8"));
4930
5034
  taskId = cached.taskId ?? null;
@@ -4949,8 +5053,8 @@ process.stdin.on("end", async () => {
4949
5053
  if (needsBackfill) {
4950
5054
  try {
4951
5055
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
4952
- const flagPath = path18.join(exeDir, "session-cache", "needs-backfill");
4953
- writeFileSync6(flagPath, "1");
5056
+ const flagPath = path19.join(exeDir, "session-cache", "needs-backfill");
5057
+ writeFileSync7(flagPath, "1");
4954
5058
  } catch (err) {
4955
5059
  process.stderr.write(`[ingest-worker] backfill flag write failed: ${err instanceof Error ? err.message : String(err)}
4956
5060
  `);
@@ -5062,8 +5166,8 @@ process.stdin.on("end", async () => {
5062
5166
  }
5063
5167
  const cwd = data.cwd ?? process.cwd();
5064
5168
  try {
5065
- mkdirSync7(path18.join(cwd, "exe/output"), { recursive: true });
5066
- mkdirSync7(path18.join(cwd, "exe/research"), { recursive: true });
5169
+ mkdirSync8(path19.join(cwd, "exe/output"), { recursive: true });
5170
+ mkdirSync8(path19.join(cwd, "exe/research"), { recursive: true });
5067
5171
  const { ensureGitignoreExe: ensureGitignoreExe2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
5068
5172
  await ensureGitignoreExe2(cwd);
5069
5173
  } catch (err) {
@@ -5073,9 +5177,19 @@ process.stdin.on("end", async () => {
5073
5177
  }
5074
5178
  }
5075
5179
  }
5180
+ try {
5181
+ const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
5182
+ await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
5183
+ } catch {
5184
+ }
5076
5185
  } catch (err) {
5077
5186
  process.stderr.write(`[ingest-worker] FATAL: ${err instanceof Error ? err.message : String(err)}
5078
5187
  `);
5188
+ }
5189
+ try {
5190
+ const { cleanupWorkerPid: cleanupWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
5191
+ cleanupWorkerPid2();
5192
+ } catch {
5079
5193
  }
5080
5194
  process.exit(0);
5081
5195
  });
@@ -1,5 +1,5 @@
1
1
  // src/lib/config.ts
2
- import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
3
3
  import { readFileSync, existsSync, renameSync } from "fs";
4
4
  import path from "path";
5
5
  import os from "os";
@@ -170,8 +170,8 @@ function loadConfigSync() {
170
170
 
171
171
  // src/adapters/claude/hooks/ingest.ts
172
172
  import { spawn } from "child_process";
173
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, openSync, closeSync } from "fs";
174
- import path3 from "path";
173
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2, openSync, closeSync } from "fs";
174
+ import path4 from "path";
175
175
  import { fileURLToPath } from "url";
176
176
 
177
177
  // src/adapters/claude/active-agent.ts
@@ -412,6 +412,44 @@ function errorFingerprint(toolName, errorText) {
412
412
  return crypto.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
413
413
  }
414
414
 
415
+ // src/lib/worker-gate.ts
416
+ import { readdirSync as readdirSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
417
+ import path3 from "path";
418
+ var WORKER_PID_DIR = path3.join(EXE_AI_DIR, "worker-pids");
419
+ var MAX_CONCURRENT_WORKERS = 3;
420
+ function tryAcquireWorkerSlot() {
421
+ try {
422
+ mkdirSync2(WORKER_PID_DIR, { recursive: true });
423
+ const files = readdirSync2(WORKER_PID_DIR);
424
+ let alive = 0;
425
+ for (const f of files) {
426
+ if (!f.endsWith(".pid")) continue;
427
+ const dashIdx = f.lastIndexOf("-");
428
+ const pid = parseInt(f.slice(dashIdx + 1).replace(".pid", ""), 10);
429
+ if (isNaN(pid)) continue;
430
+ try {
431
+ process.kill(pid, 0);
432
+ alive++;
433
+ } catch {
434
+ try {
435
+ unlinkSync2(path3.join(WORKER_PID_DIR, f));
436
+ } catch {
437
+ }
438
+ }
439
+ }
440
+ return alive < MAX_CONCURRENT_WORKERS;
441
+ } catch {
442
+ return true;
443
+ }
444
+ }
445
+ function registerWorkerPid(pid) {
446
+ try {
447
+ mkdirSync2(WORKER_PID_DIR, { recursive: true });
448
+ writeFileSync2(path3.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
449
+ } catch {
450
+ }
451
+ }
452
+
415
453
  // src/adapters/claude/hooks/ingest.ts
416
454
  if (!process.env.AGENT_ID) {
417
455
  process.env.AGENT_ID = "default";
@@ -420,7 +458,7 @@ if (!process.env.AGENT_ID) {
420
458
  if (!loadConfigSync().autoIngestion) {
421
459
  process.exit(0);
422
460
  }
423
- var WORKER_LOG_PATH = path3.join(EXE_AI_DIR, "workers.log");
461
+ var WORKER_LOG_PATH = path4.join(EXE_AI_DIR, "workers.log");
424
462
  function openWorkerLog() {
425
463
  try {
426
464
  return openSync(WORKER_LOG_PATH, "a");
@@ -432,9 +470,9 @@ var ALLOWED_TOOL_RE = /^(Bash|Edit|Write|Read|Glob|Grep|Agent|mcp__.*)$/;
432
470
  var WRITE_TOOL_RE = /^(Bash|Edit|Write)$/;
433
471
  var SUMMARY_INTERVAL = 25;
434
472
  var MIN_WRITES_FOR_SUMMARY = 3;
435
- var COUNTER_DIR = path3.join(EXE_AI_DIR, "session-cache");
473
+ var COUNTER_DIR = path4.join(EXE_AI_DIR, "session-cache");
436
474
  function getCounterPath(sessionId) {
437
- return path3.join(COUNTER_DIR, `counter-${sessionId}.json`);
475
+ return path4.join(COUNTER_DIR, `counter-${sessionId}.json`);
438
476
  }
439
477
  function loadCounter(sessionId) {
440
478
  try {
@@ -446,8 +484,8 @@ function loadCounter(sessionId) {
446
484
  }
447
485
  function saveCounter(sessionId, counter) {
448
486
  try {
449
- mkdirSync2(COUNTER_DIR, { recursive: true });
450
- writeFileSync2(getCounterPath(sessionId), JSON.stringify(counter));
487
+ mkdirSync3(COUNTER_DIR, { recursive: true });
488
+ writeFileSync3(getCounterPath(sessionId), JSON.stringify(counter));
451
489
  } catch {
452
490
  }
453
491
  }
@@ -466,7 +504,7 @@ process.stdin.on("end", () => {
466
504
  process.exit(0);
467
505
  }
468
506
  const agent = getActiveAgent();
469
- if (/^(Read|Write|Edit)$/.test(data.tool_name) && agent.agentId !== "exe" && agent.agentId !== "default") {
507
+ if (/^(Read|Write|Edit)$/.test(data.tool_name) && agent.agentRole !== "COO" && agent.agentId !== "default") {
470
508
  const filePath = data.tool_input?.file_path ?? "";
471
509
  const exeMatch = filePath.match(/exe\/([^/]+)\//);
472
510
  if (exeMatch && exeMatch[1] !== agent.agentId) {
@@ -483,7 +521,7 @@ Do NOT read, write, or modify files in another employee's folder.`
483
521
  process.stdout.write(warning);
484
522
  }
485
523
  }
486
- if (/^(Read|Glob|Grep|Bash)$/.test(data.tool_name) && agent.agentId === "exe") {
524
+ if (/^(Read|Glob|Grep|Bash)$/.test(data.tool_name) && agent.agentRole === "COO") {
487
525
  const target = data.tool_input?.file_path ?? data.tool_input?.path ?? data.tool_input?.pattern ?? String(data.tool_input?.command ?? "");
488
526
  if (/exe\/exe\//.test(target)) {
489
527
  const warning = JSON.stringify({
@@ -498,7 +536,7 @@ Open reviews are tracked in the tasks table: SELECT COUNT(*) FROM tasks WHERE as
498
536
  process.stdout.write(warning);
499
537
  }
500
538
  }
501
- if (/^(Write|Bash)$/.test(data.tool_name) && agent.agentId !== "exe" && agent.agentId !== "default") {
539
+ if (/^(Write|Bash)$/.test(data.tool_name) && agent.agentRole !== "COO" && agent.agentId !== "default") {
502
540
  const filePath = data.tool_input?.file_path ?? String(data.tool_input?.command ?? "");
503
541
  const outputMatch = filePath.match(/exe\/output\/([^/\-]+)-([^/]+)/);
504
542
  if (outputMatch) {
@@ -522,7 +560,7 @@ Your output files must start with: exe/output/${agent.agentId}-`
522
560
  const classification = classifyError(errorText);
523
561
  if (classification === "system" && data.session_id) {
524
562
  const fp = errorFingerprint(data.tool_name, errorText);
525
- const fpFilePath = path3.join(COUNTER_DIR, `bug-fingerprints-${data.session_id}.json`);
563
+ const fpFilePath = path4.join(COUNTER_DIR, `bug-fingerprints-${data.session_id}.json`);
526
564
  let fpData = {
527
565
  seen: {},
528
566
  taskCount: 0,
@@ -547,13 +585,13 @@ Your output files must start with: exe/output/${agent.agentId}-`
547
585
  fpData.seen[fp] = { count: 1, firstAt: now, lastAt: now };
548
586
  fpData.taskCount++;
549
587
  fpData.lastTaskAt = now;
550
- const bugWorkerPath = path3.resolve(
551
- path3.dirname(fileURLToPath(import.meta.url)),
588
+ const bugWorkerPath = path4.resolve(
589
+ path4.dirname(fileURLToPath(import.meta.url)),
552
590
  "bug-report-worker.js"
553
591
  );
554
- if (existsSync2(bugWorkerPath)) {
592
+ if (existsSync2(bugWorkerPath) && tryAcquireWorkerSlot()) {
555
593
  const stderrFd2 = openWorkerLog();
556
- const projectName = process.cwd().split(path3.sep).pop() ?? "unknown";
594
+ const projectName = process.cwd().split(path4.sep).pop() ?? "unknown";
557
595
  const bugToolInput = data.tool_input ?? {};
558
596
  const bugWorker = spawn(process.execPath, [bugWorkerPath], {
559
597
  detached: true,
@@ -569,6 +607,7 @@ Your output files must start with: exe/output/${agent.agentId}-`
569
607
  BUG_PROJECT_NAME: projectName
570
608
  }
571
609
  });
610
+ if (bugWorker.pid) registerWorkerPid(bugWorker.pid);
572
611
  bugWorker.unref();
573
612
  if (typeof stderrFd2 === "number") try {
574
613
  closeSync(stderrFd2);
@@ -580,13 +619,13 @@ Your output files must start with: exe/output/${agent.agentId}-`
580
619
  }
581
620
  }
582
621
  try {
583
- mkdirSync2(COUNTER_DIR, { recursive: true });
584
- writeFileSync2(fpFilePath, JSON.stringify(fpData));
622
+ mkdirSync3(COUNTER_DIR, { recursive: true });
623
+ writeFileSync3(fpFilePath, JSON.stringify(fpData));
585
624
  } catch {
586
625
  }
587
626
  }
588
627
  }
589
- if (data.session_id && agent.agentId !== "exe" && agent.agentId !== "default") {
628
+ if (data.session_id && agent.agentRole !== "COO" && agent.agentId !== "default") {
590
629
  const counter = loadCounter(data.session_id);
591
630
  counter.total++;
592
631
  if (WRITE_TOOL_RE.test(data.tool_name)) {
@@ -598,11 +637,11 @@ Your output files must start with: exe/output/${agent.agentId}-`
598
637
  }
599
638
  const callsSinceLastSummary = counter.total - counter.lastSummaryAt;
600
639
  if (callsSinceLastSummary >= SUMMARY_INTERVAL && counter.writes >= MIN_WRITES_FOR_SUMMARY) {
601
- const summaryWorkerPath = path3.resolve(
602
- path3.dirname(fileURLToPath(import.meta.url)),
640
+ const summaryWorkerPath = path4.resolve(
641
+ path4.dirname(fileURLToPath(import.meta.url)),
603
642
  "summary-worker.js"
604
643
  );
605
- if (existsSync2(summaryWorkerPath)) {
644
+ if (existsSync2(summaryWorkerPath) && tryAcquireWorkerSlot()) {
606
645
  const stderrFd2 = openWorkerLog();
607
646
  const summaryWorker = spawn(process.execPath, [summaryWorkerPath], {
608
647
  detached: true,
@@ -616,6 +655,7 @@ Your output files must start with: exe/output/${agent.agentId}-`
616
655
  EXE_SUMMARY_WRITES: String(counter.writes)
617
656
  }
618
657
  });
658
+ if (summaryWorker.pid) registerWorkerPid(summaryWorker.pid);
619
659
  summaryWorker.unref();
620
660
  if (typeof stderrFd2 === "number") try {
621
661
  closeSync(stderrFd2);
@@ -643,13 +683,13 @@ Your output files must start with: exe/output/${agent.agentId}-`
643
683
  const bashOutput = typeof data.tool_response === "string" ? data.tool_response : JSON.stringify(data.tool_response ?? "");
644
684
  const commitMatch = bashOutput.match(/\[(\S+)\s+([a-f0-9]{7,40})\]\s+(.+)/);
645
685
  if (commitMatch) {
646
- const commitWorkerPath = path3.resolve(
647
- path3.dirname(fileURLToPath(import.meta.url)),
686
+ const commitWorkerPath = path4.resolve(
687
+ path4.dirname(fileURLToPath(import.meta.url)),
648
688
  "commit-complete.js"
649
689
  );
650
- if (existsSync2(commitWorkerPath)) {
690
+ if (existsSync2(commitWorkerPath) && tryAcquireWorkerSlot()) {
651
691
  const stderrFd2 = openWorkerLog();
652
- const projectName = process.cwd().split(path3.sep).pop() ?? "unknown";
692
+ const projectName = process.cwd().split(path4.sep).pop() ?? "unknown";
653
693
  const commitWorker = spawn(process.execPath, [commitWorkerPath], {
654
694
  detached: true,
655
695
  stdio: ["ignore", "ignore", stderrFd2],
@@ -661,6 +701,7 @@ Your output files must start with: exe/output/${agent.agentId}-`
661
701
  PROJECT_NAME: projectName
662
702
  }
663
703
  });
704
+ if (commitWorker.pid) registerWorkerPid(commitWorker.pid);
664
705
  commitWorker.unref();
665
706
  if (typeof stderrFd2 === "number") try {
666
707
  closeSync(stderrFd2);
@@ -672,16 +713,21 @@ Your output files must start with: exe/output/${agent.agentId}-`
672
713
  if (!ALLOWED_TOOL_RE.test(data.tool_name)) {
673
714
  process.exit(0);
674
715
  }
675
- const workerPath = path3.resolve(
676
- path3.dirname(fileURLToPath(import.meta.url)),
716
+ const workerPath = path4.resolve(
717
+ path4.dirname(fileURLToPath(import.meta.url)),
677
718
  "ingest-worker.js"
678
719
  );
720
+ if (!tryAcquireWorkerSlot()) {
721
+ process.stderr.write("[ingest] Worker concurrency limit reached, skipping ingestion\n");
722
+ process.exit(0);
723
+ }
679
724
  const stderrFd = openWorkerLog();
680
725
  const worker = spawn(process.execPath, [workerPath], {
681
726
  detached: true,
682
727
  stdio: ["pipe", "ignore", stderrFd],
683
728
  env: { ...process.env, AGENT_ID: agent.agentId, AGENT_ROLE: agent.agentRole }
684
729
  });
730
+ if (worker.pid) registerWorkerPid(worker.pid);
685
731
  worker.stdin.write(input);
686
732
  worker.stdin.end();
687
733
  worker.unref();