@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
@@ -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();
@@ -1408,7 +1418,7 @@ __export(shard_manager_exports, {
1408
1418
  shardExists: () => shardExists
1409
1419
  });
1410
1420
  import path4 from "path";
1411
- import { existsSync as existsSync4, mkdirSync } from "fs";
1421
+ import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1412
1422
  import { createClient as createClient2 } from "@libsql/client";
1413
1423
  function initShardManager(encryptionKey) {
1414
1424
  _encryptionKey = encryptionKey;
@@ -1447,8 +1457,7 @@ function shardExists(projectName) {
1447
1457
  }
1448
1458
  function listShards() {
1449
1459
  if (!existsSync4(SHARDS_DIR)) return [];
1450
- const { readdirSync: readdirSync7 } = __require("fs");
1451
- return readdirSync7(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1460
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1452
1461
  }
1453
1462
  async function ensureShardSchema(client) {
1454
1463
  await client.execute("PRAGMA journal_mode = WAL");
@@ -1637,6 +1646,28 @@ var init_shard_manager = __esm({
1637
1646
  });
1638
1647
 
1639
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
+ }
1640
1671
  async function initStore(options) {
1641
1672
  if (_flushTimer !== null) {
1642
1673
  clearInterval(_flushTimer);
@@ -1665,17 +1696,20 @@ async function initStore(options) {
1665
1696
  dbPath,
1666
1697
  encryptionKey: hexKey
1667
1698
  });
1668
- await ensureSchema();
1699
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1669
1700
  try {
1670
1701
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1671
1702
  initShardManager2(hexKey);
1672
1703
  } catch {
1673
1704
  }
1674
1705
  const client = getClient();
1675
- 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
+ );
1676
1710
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1677
1711
  }
1678
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1712
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1679
1713
  var init_store = __esm({
1680
1714
  "src/lib/store.ts"() {
1681
1715
  "use strict";
@@ -1683,6 +1717,8 @@ var init_store = __esm({
1683
1717
  init_database();
1684
1718
  init_keychain();
1685
1719
  init_config();
1720
+ INIT_MAX_RETRIES = 3;
1721
+ INIT_RETRY_DELAY_MS = 1e3;
1686
1722
  _pendingRecords = [];
1687
1723
  _batchSize = 20;
1688
1724
  _flushIntervalMs = 1e4;
@@ -1695,10 +1731,10 @@ var init_store = __esm({
1695
1731
  // src/lib/notifications.ts
1696
1732
  import crypto2 from "crypto";
1697
1733
  import path5 from "path";
1698
- import os2 from "os";
1734
+ import os3 from "os";
1699
1735
  import {
1700
1736
  readFileSync as readFileSync3,
1701
- readdirSync,
1737
+ readdirSync as readdirSync2,
1702
1738
  unlinkSync,
1703
1739
  existsSync as existsSync5,
1704
1740
  rmdirSync
@@ -1813,12 +1849,12 @@ async function markDoneTaskNotificationsAsRead() {
1813
1849
  }
1814
1850
  }
1815
1851
  async function migrateJsonNotifications() {
1816
- 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");
1817
1853
  const notifDir = path5.join(base, "notifications");
1818
1854
  if (!existsSync5(notifDir)) return 0;
1819
1855
  let migrated = 0;
1820
1856
  try {
1821
- const files = readdirSync(notifDir).filter((f) => f.endsWith(".json"));
1857
+ const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
1822
1858
  if (files.length === 0) return 0;
1823
1859
  const client = getClient();
1824
1860
  for (const file of files) {
@@ -1846,7 +1882,7 @@ async function migrateJsonNotifications() {
1846
1882
  }
1847
1883
  }
1848
1884
  try {
1849
- const remaining = readdirSync(notifDir);
1885
+ const remaining = readdirSync2(notifDir);
1850
1886
  if (remaining.length === 0) {
1851
1887
  rmdirSync(notifDir);
1852
1888
  }
@@ -1910,7 +1946,7 @@ __export(session_registry_exports, {
1910
1946
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
1911
1947
  import { execSync as execSync4 } from "child_process";
1912
1948
  import path7 from "path";
1913
- import os3 from "os";
1949
+ import os4 from "os";
1914
1950
  function registerSession(entry) {
1915
1951
  const dir = path7.dirname(REGISTRY_PATH);
1916
1952
  if (!existsSync6(dir)) {
@@ -1956,7 +1992,7 @@ var REGISTRY_PATH;
1956
1992
  var init_session_registry = __esm({
1957
1993
  "src/lib/session-registry.ts"() {
1958
1994
  "use strict";
1959
- REGISTRY_PATH = path7.join(os3.homedir(), ".exe-os", "session-registry.json");
1995
+ REGISTRY_PATH = path7.join(os4.homedir(), ".exe-os", "session-registry.json");
1960
1996
  }
1961
1997
  });
1962
1998
 
@@ -2143,7 +2179,7 @@ var init_provider_table = __esm({
2143
2179
  // src/lib/intercom-queue.ts
2144
2180
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2145
2181
  import path8 from "path";
2146
- import os4 from "os";
2182
+ import os5 from "os";
2147
2183
  function ensureDir() {
2148
2184
  const dir = path8.dirname(QUEUE_PATH);
2149
2185
  if (!existsSync7(dir)) mkdirSync4(dir, { recursive: true });
@@ -2183,9 +2219,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2183
2219
  var init_intercom_queue = __esm({
2184
2220
  "src/lib/intercom-queue.ts"() {
2185
2221
  "use strict";
2186
- QUEUE_PATH = path8.join(os4.homedir(), ".exe-os", "intercom-queue.json");
2222
+ QUEUE_PATH = path8.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2187
2223
  TTL_MS = 60 * 60 * 1e3;
2188
- INTERCOM_LOG = path8.join(os4.homedir(), ".exe-os", "intercom.log");
2224
+ INTERCOM_LOG = path8.join(os5.homedir(), ".exe-os", "intercom.log");
2189
2225
  }
2190
2226
  });
2191
2227
 
@@ -2202,12 +2238,22 @@ __export(license_exports, {
2202
2238
  loadLicense: () => loadLicense,
2203
2239
  mirrorLicenseKey: () => mirrorLicenseKey,
2204
2240
  saveLicense: () => saveLicense,
2241
+ startLicenseRevalidation: () => startLicenseRevalidation,
2242
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
2205
2243
  validateLicense: () => validateLicense
2206
2244
  });
2207
2245
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
2208
2246
  import { randomUUID } from "crypto";
2209
2247
  import path9 from "path";
2210
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
+ }
2211
2257
  function loadDeviceId() {
2212
2258
  const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
2213
2259
  try {
@@ -2239,7 +2285,7 @@ function loadLicense() {
2239
2285
  }
2240
2286
  function saveLicense(apiKey) {
2241
2287
  mkdirSync5(EXE_AI_DIR, { recursive: true });
2242
- writeFileSync4(LICENSE_PATH, apiKey.trim(), "utf8");
2288
+ writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2243
2289
  }
2244
2290
  async function verifyLicenseJwt(token) {
2245
2291
  try {
@@ -2291,7 +2337,7 @@ function cacheResponse(token) {
2291
2337
  async function validateLicense(apiKey, deviceId) {
2292
2338
  const did = deviceId ?? loadDeviceId();
2293
2339
  try {
2294
- const res = await fetch(`${API_BASE}/auth/activate`, {
2340
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2295
2341
  method: "POST",
2296
2342
  headers: { "Content-Type": "application/json" },
2297
2343
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -2326,14 +2372,23 @@ async function validateLicense(apiKey, deviceId) {
2326
2372
  } catch {
2327
2373
  const cached = await getCachedLicense();
2328
2374
  if (cached) return cached;
2329
- return FREE_LICENSE;
2375
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
2376
+ }
2377
+ }
2378
+ function getCacheAgeMs() {
2379
+ try {
2380
+ const { statSync: statSync2 } = __require("fs");
2381
+ const s = statSync2(CACHE_PATH);
2382
+ return Date.now() - s.mtimeMs;
2383
+ } catch {
2384
+ return Infinity;
2330
2385
  }
2331
2386
  }
2332
2387
  async function checkLicense() {
2333
2388
  const key = loadLicense();
2334
2389
  if (!key) return FREE_LICENSE;
2335
2390
  const cached = await getCachedLicense();
2336
- if (cached) return cached;
2391
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
2337
2392
  const deviceId = loadDeviceId();
2338
2393
  return validateLicense(key, deviceId);
2339
2394
  }
@@ -2373,7 +2428,7 @@ async function assertVpsLicense(opts) {
2373
2428
  let explicitRejection = false;
2374
2429
  let transientFailure = false;
2375
2430
  try {
2376
- const res = await fetch(`${API_BASE}/auth/activate`, {
2431
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2377
2432
  method: "POST",
2378
2433
  headers: { "Content-Type": "application/json" },
2379
2434
  body: JSON.stringify({ apiKey, deviceId }),
@@ -2454,7 +2509,28 @@ async function assertVpsLicense(opts) {
2454
2509
  `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
2455
2510
  );
2456
2511
  }
2457
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
2512
+ function startLicenseRevalidation(intervalMs = 36e5) {
2513
+ if (_revalTimer) return;
2514
+ _revalTimer = setInterval(async () => {
2515
+ try {
2516
+ const license = await checkLicense();
2517
+ if (!license.valid) {
2518
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
2519
+ }
2520
+ } catch {
2521
+ }
2522
+ }, intervalMs);
2523
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
2524
+ _revalTimer.unref();
2525
+ }
2526
+ }
2527
+ function stopLicenseRevalidation() {
2528
+ if (_revalTimer) {
2529
+ clearInterval(_revalTimer);
2530
+ _revalTimer = null;
2531
+ }
2532
+ }
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;
2458
2534
  var init_license = __esm({
2459
2535
  "src/lib/license.ts"() {
2460
2536
  "use strict";
@@ -2463,6 +2539,7 @@ var init_license = __esm({
2463
2539
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2464
2540
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2465
2541
  API_BASE = "https://askexe.com/cloud";
2542
+ RETRY_DELAY_MS = 500;
2466
2543
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
2467
2544
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2468
2545
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -2484,6 +2561,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2484
2561
  employeeLimit: 1,
2485
2562
  memoryLimit: 5e3
2486
2563
  };
2564
+ CACHE_MAX_AGE_MS = 36e5;
2565
+ _revalTimer = null;
2487
2566
  }
2488
2567
  });
2489
2568
 
@@ -2577,7 +2656,7 @@ var init_plan_limits = __esm({
2577
2656
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
2578
2657
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync10, appendFileSync } from "fs";
2579
2658
  import path11 from "path";
2580
- import os5 from "os";
2659
+ import os6 from "os";
2581
2660
  import { fileURLToPath as fileURLToPath2 } from "url";
2582
2661
  import { unlinkSync as unlinkSync3 } from "fs";
2583
2662
  function spawnLockPath(sessionName) {
@@ -2882,7 +2961,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2882
2961
  const transport = getTransport();
2883
2962
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
2884
2963
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
2885
- const logDir = path11.join(os5.homedir(), ".exe-os", "session-logs");
2964
+ const logDir = path11.join(os6.homedir(), ".exe-os", "session-logs");
2886
2965
  const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
2887
2966
  if (!existsSync10(logDir)) {
2888
2967
  mkdirSync6(logDir, { recursive: true });
@@ -2898,7 +2977,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2898
2977
  } catch {
2899
2978
  }
2900
2979
  try {
2901
- const claudeJsonPath = path11.join(os5.homedir(), ".claude.json");
2980
+ const claudeJsonPath = path11.join(os6.homedir(), ".claude.json");
2902
2981
  let claudeJson = {};
2903
2982
  try {
2904
2983
  claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
@@ -2913,7 +2992,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2913
2992
  } catch {
2914
2993
  }
2915
2994
  try {
2916
- const settingsDir = path11.join(os5.homedir(), ".claude", "projects");
2995
+ const settingsDir = path11.join(os6.homedir(), ".claude", "projects");
2917
2996
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
2918
2997
  const projSettingsDir = path11.join(settingsDir, normalizedKey);
2919
2998
  const settingsPath = path11.join(projSettingsDir, "settings.json");
@@ -2961,7 +3040,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2961
3040
  let legacyFallbackWarned = false;
2962
3041
  if (!useExeAgent && !useBinSymlink) {
2963
3042
  const identityPath = path11.join(
2964
- os5.homedir(),
3043
+ os6.homedir(),
2965
3044
  ".exe-os",
2966
3045
  "identity",
2967
3046
  `${employeeName}.md`
@@ -2991,7 +3070,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2991
3070
  }
2992
3071
  let sessionContextFlag = "";
2993
3072
  try {
2994
- const ctxDir = path11.join(os5.homedir(), ".exe-os", "session-cache");
3073
+ const ctxDir = path11.join(os6.homedir(), ".exe-os", "session-cache");
2995
3074
  mkdirSync6(ctxDir, { recursive: true });
2996
3075
  const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
2997
3076
  const ctxContent = [
@@ -3102,11 +3181,11 @@ var init_tmux_routing = __esm({
3102
3181
  init_provider_table();
3103
3182
  init_intercom_queue();
3104
3183
  init_plan_limits();
3105
- SPAWN_LOCK_DIR = path11.join(os5.homedir(), ".exe-os", "spawn-locks");
3106
- 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");
3107
3186
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3108
3187
  INTERCOM_DEBOUNCE_MS = 3e4;
3109
- INTERCOM_LOG2 = path11.join(os5.homedir(), ".exe-os", "intercom.log");
3188
+ INTERCOM_LOG2 = path11.join(os6.homedir(), ".exe-os", "intercom.log");
3110
3189
  DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
3111
3190
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3112
3191
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -3124,7 +3203,7 @@ __export(task_scanner_exports, {
3124
3203
  formatText: () => formatText,
3125
3204
  scanAgentTasks: () => scanAgentTasks
3126
3205
  });
3127
- import { readdirSync as readdirSync3, readFileSync as readFileSync10, existsSync as existsSync11, statSync } from "fs";
3206
+ import { readdirSync as readdirSync4, readFileSync as readFileSync10, existsSync as existsSync11, statSync } from "fs";
3128
3207
  import { execSync as execSync7 } from "child_process";
3129
3208
  import path12 from "path";
3130
3209
  function getProjectRoot() {
@@ -3146,7 +3225,7 @@ function scanAgentTasks(agentId) {
3146
3225
  let total = 0;
3147
3226
  if (!existsSync11(taskDir)) return { open, inProgress, done, total };
3148
3227
  try {
3149
- const files = readdirSync3(taskDir).filter((f) => f.endsWith(".md"));
3228
+ const files = readdirSync4(taskDir).filter((f) => f.endsWith(".md"));
3150
3229
  total = files.length;
3151
3230
  for (const f of files) {
3152
3231
  try {
@@ -3702,7 +3781,7 @@ var init_tasks_crud = __esm({
3702
3781
 
3703
3782
  // src/lib/tasks-review.ts
3704
3783
  import path15 from "path";
3705
- import { existsSync as existsSync13, readdirSync as readdirSync4, unlinkSync as unlinkSync4 } from "fs";
3784
+ import { existsSync as existsSync13, readdirSync as readdirSync5, unlinkSync as unlinkSync4 } from "fs";
3706
3785
  async function countPendingReviews() {
3707
3786
  const client = getClient();
3708
3787
  const result = await client.execute({
@@ -3824,7 +3903,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3824
3903
  try {
3825
3904
  const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
3826
3905
  if (existsSync13(cacheDir)) {
3827
- for (const f of readdirSync4(cacheDir)) {
3906
+ for (const f of readdirSync5(cacheDir)) {
3828
3907
  if (f.startsWith("review-notified-")) {
3829
3908
  unlinkSync4(path15.join(cacheDir, f));
3830
3909
  }
@@ -4707,6 +4786,7 @@ var init_compress = __esm({
4707
4786
  // src/lib/cloud-sync.ts
4708
4787
  var cloud_sync_exports = {};
4709
4788
  __export(cloud_sync_exports, {
4789
+ assertSecureEndpoint: () => assertSecureEndpoint,
4710
4790
  buildRosterBlob: () => buildRosterBlob,
4711
4791
  cloudPull: () => cloudPull,
4712
4792
  cloudPullBehaviors: () => cloudPullBehaviors,
@@ -4726,9 +4806,10 @@ __export(cloud_sync_exports, {
4726
4806
  cloudPushTasks: () => cloudPushTasks,
4727
4807
  cloudSync: () => cloudSync,
4728
4808
  mergeConfig: () => mergeConfig,
4729
- mergeRosterFromRemote: () => mergeRosterFromRemote
4809
+ mergeRosterFromRemote: () => mergeRosterFromRemote,
4810
+ recordRosterDeletion: () => recordRosterDeletion
4730
4811
  });
4731
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, readdirSync as readdirSync5, 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";
4732
4813
  import path18 from "path";
4733
4814
  import { homedir } from "os";
4734
4815
  function logError(msg) {
@@ -4739,16 +4820,47 @@ function logError(msg) {
4739
4820
  } catch {
4740
4821
  }
4741
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
+ }
4742
4844
  async function fetchWithRetry(url, init) {
4743
- const attempt = async () => {
4744
- const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
4745
- return fetch(url, { ...init, signal });
4746
- };
4747
- const resp = await attempt();
4748
- if (resp.status >= 500) {
4749
- 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
+ }
4750
4862
  }
4751
- return resp;
4863
+ throw lastError;
4752
4864
  }
4753
4865
  function assertSecureEndpoint(endpoint) {
4754
4866
  if (endpoint.startsWith("https://")) return;
@@ -4776,10 +4888,15 @@ async function cloudPush(records, maxVersion, config) {
4776
4888
  headers: {
4777
4889
  Authorization: `Bearer ${config.apiKey}`,
4778
4890
  "Content-Type": "application/json",
4779
- "X-Device-Id": loadDeviceId()
4891
+ "X-Device-Id": loadDeviceId(),
4892
+ "X-Expected-Version": String(maxVersion)
4780
4893
  },
4781
4894
  body: JSON.stringify({ version: maxVersion, blob })
4782
4895
  });
4896
+ if (resp.status === 409) {
4897
+ logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
4898
+ return false;
4899
+ }
4783
4900
  return resp.ok;
4784
4901
  } catch (err) {
4785
4902
  logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
@@ -4866,18 +4983,21 @@ async function cloudSync(config) {
4866
4983
  "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
4867
4984
  );
4868
4985
  const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
4869
- const recordsResult = await client.execute({
4870
- sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
4871
- tool_name, project_name, has_error, raw_text, version,
4872
- author_device_id, scope
4873
- FROM memories
4874
- WHERE version > ?
4875
- AND (scope IS NULL OR scope != 'personal')
4876
- ORDER BY version ASC`,
4877
- args: [lastPushVersion]
4878
- });
4879
4986
  let pushed = 0;
4880
- 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;
4881
5001
  const records = recordsResult.rows.map((row) => ({
4882
5002
  id: row.id,
4883
5003
  agent_id: row.agent_id,
@@ -4894,13 +5014,14 @@ async function cloudSync(config) {
4894
5014
  }));
4895
5015
  const maxVersion = Number(records[records.length - 1].version);
4896
5016
  const pushOk = await cloudPush(records, maxVersion, config);
4897
- if (pushOk) {
4898
- await client.execute({
4899
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
4900
- args: [String(maxVersion)]
4901
- });
4902
- pushed = records.length;
4903
- }
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;
4904
5025
  }
4905
5026
  try {
4906
5027
  await cloudPushRoster(config);
@@ -4982,6 +5103,27 @@ async function cloudSync(config) {
4982
5103
  documents: documentsResult
4983
5104
  };
4984
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
+ }
4985
5127
  function buildRosterBlob(paths) {
4986
5128
  const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
4987
5129
  const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
@@ -4995,7 +5137,7 @@ function buildRosterBlob(paths) {
4995
5137
  }
4996
5138
  const identities = {};
4997
5139
  if (existsSync14(identityDir)) {
4998
- for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
5140
+ for (const file of readdirSync6(identityDir).filter((f) => f.endsWith(".md"))) {
4999
5141
  try {
5000
5142
  identities[file] = readFileSync12(path18.join(identityDir, file), "utf-8");
5001
5143
  } catch {
@@ -5009,9 +5151,10 @@ function buildRosterBlob(paths) {
5009
5151
  } catch {
5010
5152
  }
5011
5153
  }
5012
- const content = JSON.stringify({ roster, identities, config });
5154
+ const deletedNames = consumeRosterDeletions();
5155
+ const content = JSON.stringify({ roster, identities, config, deletedNames });
5013
5156
  const hash = Buffer.from(content).length;
5014
- return { roster, identities, config, version: hash };
5157
+ return { roster, identities, config, deletedNames, version: hash };
5015
5158
  }
5016
5159
  async function cloudPushRoster(config) {
5017
5160
  assertSecureEndpoint(config.endpoint);
@@ -5094,38 +5237,50 @@ function mergeConfig(remoteConfig, configPath) {
5094
5237
  writeFileSync7(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5095
5238
  }
5096
5239
  async function mergeRosterFromRemote(remote, paths) {
5097
- const rosterPath = paths?.rosterPath ?? void 0;
5098
- const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
5099
- const localEmployees = await loadEmployees(rosterPath);
5100
- const localNames = new Set(localEmployees.map((e) => e.name));
5101
- let added = 0;
5102
- for (const remoteEmp of remote.roster) {
5103
- if (localNames.has(remoteEmp.name)) continue;
5104
- localEmployees.push(remoteEmp);
5105
- localNames.add(remoteEmp.name);
5106
- added++;
5107
- if (remote.identities[`${remoteEmp.name}.md`]) {
5108
- if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
5109
- const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
5110
- if (!existsSync14(idPath)) {
5111
- 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 {
5112
5261
  }
5113
5262
  }
5114
- try {
5115
- registerBinSymlinks(remoteEmp.name);
5116
- } 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
+ }
5117
5272
  }
5118
- }
5119
- if (added > 0) {
5120
- await saveEmployees(localEmployees, rosterPath);
5121
- }
5122
- if (remote.config && Object.keys(remote.config).length > 0) {
5123
- try {
5124
- mergeConfig(remote.config, paths?.configPath);
5125
- } catch {
5273
+ if (added > 0 || removed > 0) {
5274
+ await saveEmployees(localEmployees, rosterPath);
5126
5275
  }
5127
- }
5128
- 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
+ });
5129
5284
  }
5130
5285
  async function cloudPushBlob(route, data, metaKey, config) {
5131
5286
  if (data.length === 0) return { ok: true };
@@ -5193,7 +5348,7 @@ async function cloudPullBlob(route, config) {
5193
5348
  }
5194
5349
  async function cloudPushBehaviors(config) {
5195
5350
  const client = getClient();
5196
- const result = await client.execute("SELECT * FROM behaviors");
5351
+ const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
5197
5352
  const rows = result.rows;
5198
5353
  const { ok } = await cloudPushBlob(
5199
5354
  "/sync/push-behaviors",
@@ -5241,13 +5396,13 @@ async function cloudPullBehaviors(config) {
5241
5396
  async function cloudPushGraphRAG(config) {
5242
5397
  const client = getClient();
5243
5398
  const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
5244
- client.execute("SELECT * FROM entities"),
5245
- client.execute("SELECT * FROM relationships"),
5246
- client.execute("SELECT * FROM entity_aliases"),
5247
- client.execute("SELECT * FROM entity_memories"),
5248
- client.execute("SELECT * FROM relationship_memories"),
5249
- client.execute("SELECT * FROM hyperedges"),
5250
- 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")
5251
5406
  ]);
5252
5407
  const blob = {
5253
5408
  entities: entities.rows,
@@ -5349,7 +5504,7 @@ async function cloudPullGraphRAG(config) {
5349
5504
  }
5350
5505
  async function cloudPushTasks(config) {
5351
5506
  const client = getClient();
5352
- const result = await client.execute("SELECT * FROM tasks");
5507
+ const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
5353
5508
  const rows = result.rows;
5354
5509
  const { ok } = await cloudPushBlob(
5355
5510
  "/sync/push-tasks",
@@ -5395,7 +5550,7 @@ async function cloudPullTasks(config) {
5395
5550
  }
5396
5551
  async function cloudPushConversations(config) {
5397
5552
  const client = getClient();
5398
- const result = await client.execute("SELECT * FROM conversations");
5553
+ const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
5399
5554
  const rows = result.rows;
5400
5555
  const { ok } = await cloudPushBlob(
5401
5556
  "/sync/push-conversations",
@@ -5445,8 +5600,8 @@ async function cloudPullConversations(config) {
5445
5600
  async function cloudPushDocuments(config) {
5446
5601
  const client = getClient();
5447
5602
  const [workspaces, documents] = await Promise.all([
5448
- client.execute("SELECT * FROM workspaces"),
5449
- client.execute("SELECT * FROM documents")
5603
+ client.execute("SELECT * FROM workspaces LIMIT 1000"),
5604
+ client.execute("SELECT * FROM documents LIMIT 10000")
5450
5605
  ]);
5451
5606
  const blob = {
5452
5607
  workspaces: workspaces.rows,
@@ -5499,7 +5654,7 @@ async function cloudPullDocuments(config) {
5499
5654
  }
5500
5655
  return { pulled };
5501
5656
  }
5502
- 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;
5503
5658
  var init_cloud_sync = __esm({
5504
5659
  "src/lib/cloud-sync.ts"() {
5505
5660
  "use strict";
@@ -5512,6 +5667,10 @@ var init_cloud_sync = __esm({
5512
5667
  init_employees();
5513
5668
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
5514
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");
5515
5674
  }
5516
5675
  });
5517
5676
 
@@ -5695,8 +5854,8 @@ var init_schedules = __esm({
5695
5854
  init_employees();
5696
5855
  import path19 from "path";
5697
5856
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
5698
- import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync as unlinkSync6 } from "fs";
5699
- 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";
5700
5859
 
5701
5860
  // src/lib/employee-templates.ts
5702
5861
  var DEFAULT_EXE = {
@@ -6034,7 +6193,7 @@ init_notifications();
6034
6193
 
6035
6194
  // src/adapters/claude/active-agent.ts
6036
6195
  init_config();
6037
- import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync2 } from "fs";
6196
+ import { readFileSync as readFileSync4, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync as readdirSync3 } from "fs";
6038
6197
  import { execSync as execSync3 } from "child_process";
6039
6198
  import path6 from "path";
6040
6199
 
@@ -6144,18 +6303,18 @@ async function boot(options) {
6144
6303
  } catch {
6145
6304
  }
6146
6305
  try {
6147
- const { readdirSync: readdirSync7, readFileSync: readFs } = await import("fs");
6306
+ const { readdirSync: readdirSync8, readFileSync: readFs } = await import("fs");
6148
6307
  const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
6149
6308
  const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
6150
6309
  const exeDir = "exe";
6151
- const entries = readdirSync7(exeDir, { withFileTypes: true });
6310
+ const entries = readdirSync8(exeDir, { withFileTypes: true });
6152
6311
  const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
6153
6312
  for (const dir of employeeDirs) {
6154
6313
  const employee = dir.name;
6155
6314
  const taskDir = path19.join(exeDir, employee);
6156
6315
  let files;
6157
6316
  try {
6158
- files = readdirSync7(taskDir).filter((f) => f.endsWith(".md"));
6317
+ files = readdirSync8(taskDir).filter((f) => f.endsWith(".md"));
6159
6318
  } catch {
6160
6319
  continue;
6161
6320
  }
@@ -6249,10 +6408,10 @@ async function boot(options) {
6249
6408
  try {
6250
6409
  const exeExeDir = path19.join(process.cwd(), "exe", "exe");
6251
6410
  if (existsSync15(exeExeDir)) {
6252
- for (const f of readdirSync6(exeExeDir)) {
6411
+ for (const f of readdirSync7(exeExeDir)) {
6253
6412
  if (f.startsWith("review-") && f.endsWith(".md")) {
6254
6413
  try {
6255
- unlinkSync6(path19.join(exeExeDir, f));
6414
+ unlinkSync7(path19.join(exeExeDir, f));
6256
6415
  } catch {
6257
6416
  }
6258
6417
  }
@@ -6762,7 +6921,7 @@ async function boot(options) {
6762
6921
  ]);
6763
6922
  try {
6764
6923
  const configPath = path19.join(
6765
- 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"),
6766
6925
  "config.json"
6767
6926
  );
6768
6927
  if (existsSync15(configPath)) {
@@ -6854,8 +7013,8 @@ async function boot(options) {
6854
7013
  try {
6855
7014
  const flagPath = path19.join(EXE_AI_DIR, "session-cache", "needs-backfill");
6856
7015
  if (existsSync15(flagPath)) {
6857
- const { unlinkSync: unlinkSync7 } = await import("fs");
6858
- unlinkSync7(flagPath);
7016
+ const { unlinkSync: unlinkSync8 } = await import("fs");
7017
+ unlinkSync8(flagPath);
6859
7018
  }
6860
7019
  } catch {
6861
7020
  }
@@ -6956,7 +7115,7 @@ ${brief}`;
6956
7115
  console.log(brief);
6957
7116
  try {
6958
7117
  const configPath2 = path19.join(
6959
- 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"),
6960
7119
  "config.json"
6961
7120
  );
6962
7121
  if (existsSync15(configPath2)) {