@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
@@ -42,7 +42,7 @@ __export(config_exports, {
42
42
  migrateConfig: () => migrateConfig,
43
43
  saveConfig: () => saveConfig
44
44
  });
45
- import { readFile, writeFile, mkdir } from "fs/promises";
45
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
46
46
  import { readFileSync, existsSync, renameSync } from "fs";
47
47
  import path from "path";
48
48
  import os from "os";
@@ -168,6 +168,9 @@ async function saveConfig(config) {
168
168
  await mkdir(dir, { recursive: true });
169
169
  const configPath = path.join(dir, "config.json");
170
170
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
171
+ if (config.cloud?.apiKey) {
172
+ await chmod(configPath, 384);
173
+ }
171
174
  }
172
175
  async function loadConfigFrom(configPath) {
173
176
  const raw = await readFile(configPath, "utf-8");
@@ -306,15 +309,20 @@ function isMultiInstance(agentName, employees) {
306
309
  if (!emp) return false;
307
310
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
308
311
  }
312
+ function findExeBin() {
313
+ try {
314
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
315
+ } catch {
316
+ return null;
317
+ }
318
+ }
309
319
  function registerBinSymlinks(name) {
310
320
  const created = [];
311
321
  const skipped = [];
312
322
  const errors = [];
313
- let exeBinPath;
314
- try {
315
- exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
316
- } catch {
317
- errors.push("Could not find 'exe' in PATH");
323
+ const exeBinPath = findExeBin();
324
+ if (!exeBinPath) {
325
+ errors.push("Could not find 'exe-os' in PATH");
318
326
  return { created, skipped, errors };
319
327
  }
320
328
  const binDir = path2.dirname(exeBinPath);
@@ -449,6 +457,7 @@ async function ensureSchema() {
449
457
  const client = getRawClient();
450
458
  await client.execute("PRAGMA journal_mode = WAL");
451
459
  await client.execute("PRAGMA busy_timeout = 30000");
460
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
452
461
  try {
453
462
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
454
463
  } catch {
@@ -1257,12 +1266,13 @@ __export(keychain_exports, {
1257
1266
  importMnemonic: () => importMnemonic,
1258
1267
  setMasterKey: () => setMasterKey
1259
1268
  });
1260
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod } from "fs/promises";
1269
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1261
1270
  import { existsSync as existsSync3 } from "fs";
1262
1271
  import path3 from "path";
1272
+ import os2 from "os";
1263
1273
  import crypto from "crypto";
1264
1274
  function getKeyDir() {
1265
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(process.env.HOME ?? "/tmp", ".exe-os");
1275
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os2.homedir(), ".exe-os");
1266
1276
  }
1267
1277
  function getKeyPath() {
1268
1278
  return path3.join(getKeyDir(), "master.key");
@@ -1310,7 +1320,7 @@ async function setMasterKey(key) {
1310
1320
  await mkdir3(dir, { recursive: true });
1311
1321
  const keyPath = getKeyPath();
1312
1322
  await writeFile3(keyPath, b64 + "\n", "utf-8");
1313
- await chmod(keyPath, 384);
1323
+ await chmod2(keyPath, 384);
1314
1324
  }
1315
1325
  async function deleteMasterKey() {
1316
1326
  const keytar = await tryKeytar();
@@ -1636,6 +1646,28 @@ var init_shard_manager = __esm({
1636
1646
  });
1637
1647
 
1638
1648
  // src/lib/store.ts
1649
+ function isBusyError2(err) {
1650
+ if (err instanceof Error) {
1651
+ const msg = err.message.toLowerCase();
1652
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1653
+ }
1654
+ return false;
1655
+ }
1656
+ async function retryOnBusy2(fn, label) {
1657
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1658
+ try {
1659
+ return await fn();
1660
+ } catch (err) {
1661
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1662
+ process.stderr.write(
1663
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1664
+ `
1665
+ );
1666
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1667
+ }
1668
+ }
1669
+ throw new Error("unreachable");
1670
+ }
1639
1671
  async function initStore(options) {
1640
1672
  if (_flushTimer !== null) {
1641
1673
  clearInterval(_flushTimer);
@@ -1664,17 +1696,20 @@ async function initStore(options) {
1664
1696
  dbPath,
1665
1697
  encryptionKey: hexKey
1666
1698
  });
1667
- await ensureSchema();
1699
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1668
1700
  try {
1669
1701
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1670
1702
  initShardManager2(hexKey);
1671
1703
  } catch {
1672
1704
  }
1673
1705
  const client = getClient();
1674
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1706
+ const vResult = await retryOnBusy2(
1707
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1708
+ "version-query"
1709
+ );
1675
1710
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1676
1711
  }
1677
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1712
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1678
1713
  var init_store = __esm({
1679
1714
  "src/lib/store.ts"() {
1680
1715
  "use strict";
@@ -1682,6 +1717,8 @@ var init_store = __esm({
1682
1717
  init_database();
1683
1718
  init_keychain();
1684
1719
  init_config();
1720
+ INIT_MAX_RETRIES = 3;
1721
+ INIT_RETRY_DELAY_MS = 1e3;
1685
1722
  _pendingRecords = [];
1686
1723
  _batchSize = 20;
1687
1724
  _flushIntervalMs = 1e4;
@@ -1694,7 +1731,7 @@ var init_store = __esm({
1694
1731
  // src/lib/notifications.ts
1695
1732
  import crypto2 from "crypto";
1696
1733
  import path5 from "path";
1697
- import os2 from "os";
1734
+ import os3 from "os";
1698
1735
  import {
1699
1736
  readFileSync as readFileSync3,
1700
1737
  readdirSync as readdirSync2,
@@ -1812,7 +1849,7 @@ async function markDoneTaskNotificationsAsRead() {
1812
1849
  }
1813
1850
  }
1814
1851
  async function migrateJsonNotifications() {
1815
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path5.join(os2.homedir(), ".exe-os");
1852
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path5.join(os3.homedir(), ".exe-os");
1816
1853
  const notifDir = path5.join(base, "notifications");
1817
1854
  if (!existsSync5(notifDir)) return 0;
1818
1855
  let migrated = 0;
@@ -1909,7 +1946,7 @@ __export(session_registry_exports, {
1909
1946
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
1910
1947
  import { execSync as execSync4 } from "child_process";
1911
1948
  import path7 from "path";
1912
- import os3 from "os";
1949
+ import os4 from "os";
1913
1950
  function registerSession(entry) {
1914
1951
  const dir = path7.dirname(REGISTRY_PATH);
1915
1952
  if (!existsSync6(dir)) {
@@ -1955,7 +1992,7 @@ var REGISTRY_PATH;
1955
1992
  var init_session_registry = __esm({
1956
1993
  "src/lib/session-registry.ts"() {
1957
1994
  "use strict";
1958
- REGISTRY_PATH = path7.join(os3.homedir(), ".exe-os", "session-registry.json");
1995
+ REGISTRY_PATH = path7.join(os4.homedir(), ".exe-os", "session-registry.json");
1959
1996
  }
1960
1997
  });
1961
1998
 
@@ -2142,7 +2179,7 @@ var init_provider_table = __esm({
2142
2179
  // src/lib/intercom-queue.ts
2143
2180
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2144
2181
  import path8 from "path";
2145
- import os4 from "os";
2182
+ import os5 from "os";
2146
2183
  function ensureDir() {
2147
2184
  const dir = path8.dirname(QUEUE_PATH);
2148
2185
  if (!existsSync7(dir)) mkdirSync4(dir, { recursive: true });
@@ -2182,9 +2219,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2182
2219
  var init_intercom_queue = __esm({
2183
2220
  "src/lib/intercom-queue.ts"() {
2184
2221
  "use strict";
2185
- QUEUE_PATH = path8.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2222
+ QUEUE_PATH = path8.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2186
2223
  TTL_MS = 60 * 60 * 1e3;
2187
- INTERCOM_LOG = path8.join(os4.homedir(), ".exe-os", "intercom.log");
2224
+ INTERCOM_LOG = path8.join(os5.homedir(), ".exe-os", "intercom.log");
2188
2225
  }
2189
2226
  });
2190
2227
 
@@ -2209,6 +2246,14 @@ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsS
2209
2246
  import { randomUUID } from "crypto";
2210
2247
  import path9 from "path";
2211
2248
  import { jwtVerify, importSPKI } from "jose";
2249
+ async function fetchRetry(url, init) {
2250
+ try {
2251
+ return await fetch(url, init);
2252
+ } catch {
2253
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
2254
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
2255
+ }
2256
+ }
2212
2257
  function loadDeviceId() {
2213
2258
  const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
2214
2259
  try {
@@ -2240,7 +2285,7 @@ function loadLicense() {
2240
2285
  }
2241
2286
  function saveLicense(apiKey) {
2242
2287
  mkdirSync5(EXE_AI_DIR, { recursive: true });
2243
- writeFileSync4(LICENSE_PATH, apiKey.trim(), "utf8");
2288
+ writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2244
2289
  }
2245
2290
  async function verifyLicenseJwt(token) {
2246
2291
  try {
@@ -2292,7 +2337,7 @@ function cacheResponse(token) {
2292
2337
  async function validateLicense(apiKey, deviceId) {
2293
2338
  const did = deviceId ?? loadDeviceId();
2294
2339
  try {
2295
- const res = await fetch(`${API_BASE}/auth/activate`, {
2340
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2296
2341
  method: "POST",
2297
2342
  headers: { "Content-Type": "application/json" },
2298
2343
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -2383,7 +2428,7 @@ async function assertVpsLicense(opts) {
2383
2428
  let explicitRejection = false;
2384
2429
  let transientFailure = false;
2385
2430
  try {
2386
- const res = await fetch(`${API_BASE}/auth/activate`, {
2431
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2387
2432
  method: "POST",
2388
2433
  headers: { "Content-Type": "application/json" },
2389
2434
  body: JSON.stringify({ apiKey, deviceId }),
@@ -2485,7 +2530,7 @@ function stopLicenseRevalidation() {
2485
2530
  _revalTimer = null;
2486
2531
  }
2487
2532
  }
2488
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
2533
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
2489
2534
  var init_license = __esm({
2490
2535
  "src/lib/license.ts"() {
2491
2536
  "use strict";
@@ -2494,6 +2539,7 @@ var init_license = __esm({
2494
2539
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2495
2540
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2496
2541
  API_BASE = "https://askexe.com/cloud";
2542
+ RETRY_DELAY_MS = 500;
2497
2543
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
2498
2544
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2499
2545
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -2610,7 +2656,7 @@ var init_plan_limits = __esm({
2610
2656
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2611
2657
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
2612
2658
  import path11 from "path";
2613
- import os5 from "os";
2659
+ import os6 from "os";
2614
2660
  import { fileURLToPath as fileURLToPath2 } from "url";
2615
2661
  import { unlinkSync as unlinkSync3 } from "fs";
2616
2662
  function spawnLockPath(sessionName) {
@@ -2915,7 +2961,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2915
2961
  const transport = getTransport();
2916
2962
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
2917
2963
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
2918
- const logDir = path11.join(os5.homedir(), ".exe-os", "session-logs");
2964
+ const logDir = path11.join(os6.homedir(), ".exe-os", "session-logs");
2919
2965
  const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
2920
2966
  if (!existsSync10(logDir)) {
2921
2967
  mkdirSync6(logDir, { recursive: true });
@@ -2931,7 +2977,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2931
2977
  } catch {
2932
2978
  }
2933
2979
  try {
2934
- const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
2980
+ const claudeJsonPath = path11.join(os6.homedir(), ".claude.json");
2935
2981
  let claudeJson = {};
2936
2982
  try {
2937
2983
  claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
@@ -2946,7 +2992,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2946
2992
  } catch {
2947
2993
  }
2948
2994
  try {
2949
- const settingsDir = path11.join(os5.homedir(), ".claude", "projects");
2995
+ const settingsDir = path11.join(os6.homedir(), ".claude", "projects");
2950
2996
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
2951
2997
  const projSettingsDir = path11.join(settingsDir, normalizedKey);
2952
2998
  const settingsPath = path11.join(projSettingsDir, "settings.json");
@@ -2994,7 +3040,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2994
3040
  let legacyFallbackWarned = false;
2995
3041
  if (!useExeAgent && !useBinSymlink) {
2996
3042
  const identityPath = path11.join(
2997
- os5.homedir(),
3043
+ os6.homedir(),
2998
3044
  ".exe-os",
2999
3045
  "identity",
3000
3046
  `${employeeName}.md`
@@ -3024,7 +3070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3024
3070
  }
3025
3071
  let sessionContextFlag = "";
3026
3072
  try {
3027
- const ctxDir = path11.join(os5.homedir(), ".exe-os", "session-cache");
3073
+ const ctxDir = path11.join(os6.homedir(), ".exe-os", "session-cache");
3028
3074
  mkdirSync6(ctxDir, { recursive: true });
3029
3075
  const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
3030
3076
  const ctxContent = [
@@ -3135,11 +3181,11 @@ var init_tmux_routing = __esm({
3135
3181
  init_provider_table();
3136
3182
  init_intercom_queue();
3137
3183
  init_plan_limits();
3138
- SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
3139
- SESSION_CACHE = path11.join(os5.homedir(), ".exe-os", "session-cache");
3184
+ SPAWN_LOCK_DIR = path11.join(os6.homedir(), ".exe-os", "spawn-locks");
3185
+ SESSION_CACHE = path11.join(os6.homedir(), ".exe-os", "session-cache");
3140
3186
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3141
3187
  INTERCOM_DEBOUNCE_MS = 3e4;
3142
- INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
3188
+ INTERCOM_LOG2 = path11.join(os6.homedir(), ".exe-os", "intercom.log");
3143
3189
  DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
3144
3190
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3145
3191
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -4740,6 +4786,7 @@ var init_compress = __esm({
4740
4786
  // src/lib/cloud-sync.ts
4741
4787
  var cloud_sync_exports = {};
4742
4788
  __export(cloud_sync_exports, {
4789
+ assertSecureEndpoint: () => assertSecureEndpoint,
4743
4790
  buildRosterBlob: () => buildRosterBlob,
4744
4791
  cloudPull: () => cloudPull,
4745
4792
  cloudPullBehaviors: () => cloudPullBehaviors,
@@ -4759,9 +4806,10 @@ __export(cloud_sync_exports, {
4759
4806
  cloudPushTasks: () => cloudPushTasks,
4760
4807
  cloudSync: () => cloudSync,
4761
4808
  mergeConfig: () => mergeConfig,
4762
- mergeRosterFromRemote: () => mergeRosterFromRemote
4809
+ mergeRosterFromRemote: () => mergeRosterFromRemote,
4810
+ recordRosterDeletion: () => recordRosterDeletion
4763
4811
  });
4764
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync6, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
4812
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync6, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6 } from "fs";
4765
4813
  import path18 from "path";
4766
4814
  import { homedir } from "os";
4767
4815
  function logError(msg) {
@@ -4772,16 +4820,47 @@ function logError(msg) {
4772
4820
  } catch {
4773
4821
  }
4774
4822
  }
4823
+ async function withRosterLock(fn) {
4824
+ if (existsSync14(ROSTER_LOCK_PATH)) {
4825
+ try {
4826
+ const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
4827
+ if (Date.now() - ts < LOCK_STALE_MS) {
4828
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
4829
+ }
4830
+ } catch (err) {
4831
+ if (err instanceof Error && err.message.includes("already in progress")) throw err;
4832
+ }
4833
+ }
4834
+ writeFileSync7(ROSTER_LOCK_PATH, String(Date.now()));
4835
+ try {
4836
+ return await fn();
4837
+ } finally {
4838
+ try {
4839
+ unlinkSync6(ROSTER_LOCK_PATH);
4840
+ } catch {
4841
+ }
4842
+ }
4843
+ }
4775
4844
  async function fetchWithRetry(url, init) {
4776
- const attempt = async () => {
4777
- const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
4778
- return fetch(url, { ...init, signal });
4779
- };
4780
- const resp = await attempt();
4781
- if (resp.status >= 500) {
4782
- return attempt();
4845
+ const MAX_RETRIES2 = 3;
4846
+ const BASE_DELAY_MS2 = 200;
4847
+ let lastError;
4848
+ for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
4849
+ try {
4850
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
4851
+ const resp = await fetch(url, { ...init, signal });
4852
+ if (resp.status >= 500 && attempt < MAX_RETRIES2) {
4853
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
4854
+ continue;
4855
+ }
4856
+ return resp;
4857
+ } catch (err) {
4858
+ lastError = err;
4859
+ if (attempt === MAX_RETRIES2) throw err;
4860
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
4861
+ }
4783
4862
  }
4784
- return resp;
4863
+ throw lastError;
4785
4864
  }
4786
4865
  function assertSecureEndpoint(endpoint) {
4787
4866
  if (endpoint.startsWith("https://")) return;
@@ -4809,10 +4888,15 @@ async function cloudPush(records, maxVersion, config) {
4809
4888
  headers: {
4810
4889
  Authorization: `Bearer ${config.apiKey}`,
4811
4890
  "Content-Type": "application/json",
4812
- "X-Device-Id": loadDeviceId()
4891
+ "X-Device-Id": loadDeviceId(),
4892
+ "X-Expected-Version": String(maxVersion)
4813
4893
  },
4814
4894
  body: JSON.stringify({ version: maxVersion, blob })
4815
4895
  });
4896
+ if (resp.status === 409) {
4897
+ logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
4898
+ return false;
4899
+ }
4816
4900
  return resp.ok;
4817
4901
  } catch (err) {
4818
4902
  logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
@@ -4899,18 +4983,21 @@ async function cloudSync(config) {
4899
4983
  "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
4900
4984
  );
4901
4985
  const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
4902
- const recordsResult = await client.execute({
4903
- sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
4904
- tool_name, project_name, has_error, raw_text, version,
4905
- author_device_id, scope
4906
- FROM memories
4907
- WHERE version > ?
4908
- AND (scope IS NULL OR scope != 'personal')
4909
- ORDER BY version ASC`,
4910
- args: [lastPushVersion]
4911
- });
4912
4986
  let pushed = 0;
4913
- if (recordsResult.rows.length > 0) {
4987
+ let batchCursor = lastPushVersion;
4988
+ while (true) {
4989
+ const recordsResult = await client.execute({
4990
+ sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
4991
+ tool_name, project_name, has_error, raw_text, version,
4992
+ author_device_id, scope
4993
+ FROM memories
4994
+ WHERE version > ?
4995
+ AND (scope IS NULL OR scope != 'personal')
4996
+ ORDER BY version ASC
4997
+ LIMIT ?`,
4998
+ args: [batchCursor, PUSH_BATCH_SIZE]
4999
+ });
5000
+ if (recordsResult.rows.length === 0) break;
4914
5001
  const records = recordsResult.rows.map((row) => ({
4915
5002
  id: row.id,
4916
5003
  agent_id: row.agent_id,
@@ -4927,13 +5014,14 @@ async function cloudSync(config) {
4927
5014
  }));
4928
5015
  const maxVersion = Number(records[records.length - 1].version);
4929
5016
  const pushOk = await cloudPush(records, maxVersion, config);
4930
- if (pushOk) {
4931
- await client.execute({
4932
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
4933
- args: [String(maxVersion)]
4934
- });
4935
- pushed = records.length;
4936
- }
5017
+ if (!pushOk) break;
5018
+ await client.execute({
5019
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
5020
+ args: [String(maxVersion)]
5021
+ });
5022
+ pushed += records.length;
5023
+ batchCursor = maxVersion;
5024
+ if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
4937
5025
  }
4938
5026
  try {
4939
5027
  await cloudPushRoster(config);
@@ -5015,6 +5103,27 @@ async function cloudSync(config) {
5015
5103
  documents: documentsResult
5016
5104
  };
5017
5105
  }
5106
+ function recordRosterDeletion(name) {
5107
+ let deletions = [];
5108
+ try {
5109
+ if (existsSync14(ROSTER_DELETIONS_PATH)) {
5110
+ deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
5111
+ }
5112
+ } catch {
5113
+ }
5114
+ if (!deletions.includes(name)) deletions.push(name);
5115
+ writeFileSync7(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
5116
+ }
5117
+ function consumeRosterDeletions() {
5118
+ try {
5119
+ if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
5120
+ const deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
5121
+ writeFileSync7(ROSTER_DELETIONS_PATH, "[]");
5122
+ return deletions;
5123
+ } catch {
5124
+ return [];
5125
+ }
5126
+ }
5018
5127
  function buildRosterBlob(paths) {
5019
5128
  const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
5020
5129
  const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
@@ -5042,9 +5151,10 @@ function buildRosterBlob(paths) {
5042
5151
  } catch {
5043
5152
  }
5044
5153
  }
5045
- const content = JSON.stringify({ roster, identities, config });
5154
+ const deletedNames = consumeRosterDeletions();
5155
+ const content = JSON.stringify({ roster, identities, config, deletedNames });
5046
5156
  const hash = Buffer.from(content).length;
5047
- return { roster, identities, config, version: hash };
5157
+ return { roster, identities, config, deletedNames, version: hash };
5048
5158
  }
5049
5159
  async function cloudPushRoster(config) {
5050
5160
  assertSecureEndpoint(config.endpoint);
@@ -5127,38 +5237,50 @@ function mergeConfig(remoteConfig, configPath) {
5127
5237
  writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5128
5238
  }
5129
5239
  async function mergeRosterFromRemote(remote, paths) {
5130
- const rosterPath = paths?.rosterPath ?? void 0;
5131
- const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
5132
- const localEmployees = await loadEmployees(rosterPath);
5133
- const localNames = new Set(localEmployees.map((e) => e.name));
5134
- let added = 0;
5135
- for (const remoteEmp of remote.roster) {
5136
- if (localNames.has(remoteEmp.name)) continue;
5137
- localEmployees.push(remoteEmp);
5138
- localNames.add(remoteEmp.name);
5139
- added++;
5140
- if (remote.identities[`${remoteEmp.name}.md`]) {
5141
- if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
5142
- const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
5143
- if (!existsSync14(idPath)) {
5144
- writeFileSync7(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
5240
+ return withRosterLock(async () => {
5241
+ const rosterPath = paths?.rosterPath ?? void 0;
5242
+ const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
5243
+ const localEmployees = await loadEmployees(rosterPath);
5244
+ const localNames = new Set(localEmployees.map((e) => e.name));
5245
+ let added = 0;
5246
+ for (const remoteEmp of remote.roster) {
5247
+ if (localNames.has(remoteEmp.name)) continue;
5248
+ localEmployees.push(remoteEmp);
5249
+ localNames.add(remoteEmp.name);
5250
+ added++;
5251
+ if (remote.identities[`${remoteEmp.name}.md`]) {
5252
+ if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
5253
+ const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
5254
+ if (!existsSync14(idPath)) {
5255
+ writeFileSync7(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
5256
+ }
5257
+ }
5258
+ try {
5259
+ registerBinSymlinks(remoteEmp.name);
5260
+ } catch {
5145
5261
  }
5146
5262
  }
5147
- try {
5148
- registerBinSymlinks(remoteEmp.name);
5149
- } catch {
5263
+ let removed = 0;
5264
+ if (remote.deletedNames && remote.deletedNames.length > 0) {
5265
+ const toRemove = new Set(remote.deletedNames);
5266
+ const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
5267
+ removed = localEmployees.length - filtered.length;
5268
+ if (removed > 0) {
5269
+ localEmployees.length = 0;
5270
+ localEmployees.push(...filtered);
5271
+ }
5150
5272
  }
5151
- }
5152
- if (added > 0) {
5153
- await saveEmployees(localEmployees, rosterPath);
5154
- }
5155
- if (remote.config && Object.keys(remote.config).length > 0) {
5156
- try {
5157
- mergeConfig(remote.config, paths?.configPath);
5158
- } catch {
5273
+ if (added > 0 || removed > 0) {
5274
+ await saveEmployees(localEmployees, rosterPath);
5159
5275
  }
5160
- }
5161
- return { added };
5276
+ if (remote.config && Object.keys(remote.config).length > 0) {
5277
+ try {
5278
+ mergeConfig(remote.config, paths?.configPath);
5279
+ } catch {
5280
+ }
5281
+ }
5282
+ return { added };
5283
+ });
5162
5284
  }
5163
5285
  async function cloudPushBlob(route, data, metaKey, config) {
5164
5286
  if (data.length === 0) return { ok: true };
@@ -5226,7 +5348,7 @@ async function cloudPullBlob(route, config) {
5226
5348
  }
5227
5349
  async function cloudPushBehaviors(config) {
5228
5350
  const client = getClient();
5229
- const result = await client.execute("SELECT * FROM behaviors");
5351
+ const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
5230
5352
  const rows = result.rows;
5231
5353
  const { ok } = await cloudPushBlob(
5232
5354
  "/sync/push-behaviors",
@@ -5274,13 +5396,13 @@ async function cloudPullBehaviors(config) {
5274
5396
  async function cloudPushGraphRAG(config) {
5275
5397
  const client = getClient();
5276
5398
  const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
5277
- client.execute("SELECT * FROM entities"),
5278
- client.execute("SELECT * FROM relationships"),
5279
- client.execute("SELECT * FROM entity_aliases"),
5280
- client.execute("SELECT * FROM entity_memories"),
5281
- client.execute("SELECT * FROM relationship_memories"),
5282
- client.execute("SELECT * FROM hyperedges"),
5283
- client.execute("SELECT * FROM hyperedge_nodes")
5399
+ client.execute("SELECT * FROM entities LIMIT 50000"),
5400
+ client.execute("SELECT * FROM relationships LIMIT 50000"),
5401
+ client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
5402
+ client.execute("SELECT * FROM entity_memories LIMIT 50000"),
5403
+ client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
5404
+ client.execute("SELECT * FROM hyperedges LIMIT 50000"),
5405
+ client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
5284
5406
  ]);
5285
5407
  const blob = {
5286
5408
  entities: entities.rows,
@@ -5382,7 +5504,7 @@ async function cloudPullGraphRAG(config) {
5382
5504
  }
5383
5505
  async function cloudPushTasks(config) {
5384
5506
  const client = getClient();
5385
- const result = await client.execute("SELECT * FROM tasks");
5507
+ const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
5386
5508
  const rows = result.rows;
5387
5509
  const { ok } = await cloudPushBlob(
5388
5510
  "/sync/push-tasks",
@@ -5428,7 +5550,7 @@ async function cloudPullTasks(config) {
5428
5550
  }
5429
5551
  async function cloudPushConversations(config) {
5430
5552
  const client = getClient();
5431
- const result = await client.execute("SELECT * FROM conversations");
5553
+ const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
5432
5554
  const rows = result.rows;
5433
5555
  const { ok } = await cloudPushBlob(
5434
5556
  "/sync/push-conversations",
@@ -5478,8 +5600,8 @@ async function cloudPullConversations(config) {
5478
5600
  async function cloudPushDocuments(config) {
5479
5601
  const client = getClient();
5480
5602
  const [workspaces, documents] = await Promise.all([
5481
- client.execute("SELECT * FROM workspaces"),
5482
- client.execute("SELECT * FROM documents")
5603
+ client.execute("SELECT * FROM workspaces LIMIT 1000"),
5604
+ client.execute("SELECT * FROM documents LIMIT 10000")
5483
5605
  ]);
5484
5606
  const blob = {
5485
5607
  workspaces: workspaces.rows,
@@ -5532,7 +5654,7 @@ async function cloudPullDocuments(config) {
5532
5654
  }
5533
5655
  return { pulled };
5534
5656
  }
5535
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
5657
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
5536
5658
  var init_cloud_sync = __esm({
5537
5659
  "src/lib/cloud-sync.ts"() {
5538
5660
  "use strict";
@@ -5545,6 +5667,10 @@ var init_cloud_sync = __esm({
5545
5667
  init_employees();
5546
5668
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
5547
5669
  FETCH_TIMEOUT_MS = 3e4;
5670
+ PUSH_BATCH_SIZE = 5e3;
5671
+ ROSTER_LOCK_PATH = path18.join(EXE_AI_DIR, "roster-merge.lock");
5672
+ LOCK_STALE_MS = 3e4;
5673
+ ROSTER_DELETIONS_PATH = path18.join(EXE_AI_DIR, "roster-deletions.json");
5548
5674
  }
5549
5675
  });
5550
5676
 
@@ -5728,8 +5854,8 @@ var init_schedules = __esm({
5728
5854
  init_employees();
5729
5855
  import path19 from "path";
5730
5856
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
5731
- import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync7, unlinkSync as unlinkSync6 } from "fs";
5732
- import os6 from "os";
5857
+ import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync7, unlinkSync as unlinkSync7 } from "fs";
5858
+ import os7 from "os";
5733
5859
 
5734
5860
  // src/lib/employee-templates.ts
5735
5861
  var DEFAULT_EXE = {
@@ -6285,7 +6411,7 @@ async function boot(options) {
6285
6411
  for (const f of readdirSync7(exeExeDir)) {
6286
6412
  if (f.startsWith("review-") && f.endsWith(".md")) {
6287
6413
  try {
6288
- unlinkSync6(path19.join(exeExeDir, f));
6414
+ unlinkSync7(path19.join(exeExeDir, f));
6289
6415
  } catch {
6290
6416
  }
6291
6417
  }
@@ -6795,7 +6921,7 @@ async function boot(options) {
6795
6921
  ]);
6796
6922
  try {
6797
6923
  const configPath = path19.join(
6798
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
6924
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os7.homedir(), ".exe-os"),
6799
6925
  "config.json"
6800
6926
  );
6801
6927
  if (existsSync15(configPath)) {
@@ -6887,8 +7013,8 @@ async function boot(options) {
6887
7013
  try {
6888
7014
  const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
6889
7015
  if (existsSync15(flagPath)) {
6890
- const { unlinkSync: unlinkSync7 } = await import("fs");
6891
- unlinkSync7(flagPath);
7016
+ const { unlinkSync: unlinkSync8 } = await import("fs");
7017
+ unlinkSync8(flagPath);
6892
7018
  }
6893
7019
  } catch {
6894
7020
  }
@@ -6989,7 +7115,7 @@ ${brief}`;
6989
7115
  console.log(brief);
6990
7116
  try {
6991
7117
  const configPath2 = path19.join(
6992
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os6.homedir(), ".exe-os"),
7118
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os7.homedir(), ".exe-os"),
6993
7119
  "config.json"
6994
7120
  );
6995
7121
  if (existsSync15(configPath2)) {