@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
@@ -31,15 +31,15 @@ __export(config_exports, {
31
31
  migrateConfig: () => migrateConfig,
32
32
  saveConfig: () => saveConfig
33
33
  });
34
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
34
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
35
35
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
36
36
  import path2 from "path";
37
- import os from "os";
37
+ import os2 from "os";
38
38
  function resolveDataDir() {
39
39
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
40
40
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
41
- const newDir = path2.join(os.homedir(), ".exe-os");
42
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
41
+ const newDir = path2.join(os2.homedir(), ".exe-os");
42
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
43
43
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
44
44
  try {
45
45
  renameSync(legacyDir, newDir);
@@ -126,7 +126,7 @@ async function loadConfig() {
126
126
  normalizeAutoUpdate(migratedCfg);
127
127
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
128
128
  if (config.dbPath.startsWith("~")) {
129
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
129
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
130
130
  }
131
131
  return config;
132
132
  } catch {
@@ -157,6 +157,9 @@ async function saveConfig(config) {
157
157
  await mkdir2(dir, { recursive: true });
158
158
  const configPath = path2.join(dir, "config.json");
159
159
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
160
+ if (config.cloud?.apiKey) {
161
+ await chmod2(configPath, 384);
162
+ }
160
163
  }
161
164
  async function loadConfigFrom(configPath) {
162
165
  const raw = await readFile2(configPath, "utf-8");
@@ -381,15 +384,20 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
381
384
  await mkdir3(path3.dirname(employeesPath), { recursive: true });
382
385
  await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
383
386
  }
387
+ function findExeBin() {
388
+ try {
389
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
390
+ } catch {
391
+ return null;
392
+ }
393
+ }
384
394
  function registerBinSymlinks(name) {
385
395
  const created = [];
386
396
  const skipped = [];
387
397
  const errors = [];
388
- let exeBinPath;
389
- try {
390
- exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
391
- } catch {
392
- errors.push("Could not find 'exe' in PATH");
398
+ const exeBinPath = findExeBin();
399
+ if (!exeBinPath) {
400
+ errors.push("Could not find 'exe-os' in PATH");
393
401
  return { created, skipped, errors };
394
402
  }
395
403
  const binDir = path3.dirname(exeBinPath);
@@ -430,6 +438,14 @@ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4
430
438
  import { randomUUID } from "crypto";
431
439
  import path4 from "path";
432
440
  import { jwtVerify, importSPKI } from "jose";
441
+ async function fetchRetry(url, init) {
442
+ try {
443
+ return await fetch(url, init);
444
+ } catch {
445
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
446
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
447
+ }
448
+ }
433
449
  function loadDeviceId() {
434
450
  const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
435
451
  try {
@@ -500,7 +516,7 @@ function cacheResponse(token) {
500
516
  async function validateLicense(apiKey, deviceId) {
501
517
  const did = deviceId ?? loadDeviceId();
502
518
  try {
503
- const res = await fetch(`${API_BASE}/auth/activate`, {
519
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
504
520
  method: "POST",
505
521
  headers: { "Content-Type": "application/json" },
506
522
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -535,14 +551,23 @@ async function validateLicense(apiKey, deviceId) {
535
551
  } catch {
536
552
  const cached = await getCachedLicense();
537
553
  if (cached) return cached;
538
- return FREE_LICENSE;
554
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
555
+ }
556
+ }
557
+ function getCacheAgeMs() {
558
+ try {
559
+ const { statSync } = __require("fs");
560
+ const s = statSync(CACHE_PATH);
561
+ return Date.now() - s.mtimeMs;
562
+ } catch {
563
+ return Infinity;
539
564
  }
540
565
  }
541
566
  async function checkLicense() {
542
567
  const key = loadLicense();
543
568
  if (!key) return FREE_LICENSE;
544
569
  const cached = await getCachedLicense();
545
- if (cached) return cached;
570
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
546
571
  const deviceId = loadDeviceId();
547
572
  return validateLicense(key, deviceId);
548
573
  }
@@ -556,7 +581,7 @@ function isFeatureAllowed(license, feature) {
556
581
  return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
557
582
  }
558
583
  }
559
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
584
+ 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;
560
585
  var init_license = __esm({
561
586
  "src/lib/license.ts"() {
562
587
  "use strict";
@@ -565,6 +590,7 @@ var init_license = __esm({
565
590
  CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
566
591
  DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
567
592
  API_BASE = "https://askexe.com/cloud";
593
+ RETRY_DELAY_MS = 500;
568
594
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
569
595
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
570
596
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -586,6 +612,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
586
612
  employeeLimit: 1,
587
613
  memoryLimit: 5e3
588
614
  };
615
+ CACHE_MAX_AGE_MS = 36e5;
589
616
  }
590
617
  });
591
618
 
@@ -621,6 +648,7 @@ var init_plan_limits = __esm({
621
648
  // src/lib/cloud-sync.ts
622
649
  var cloud_sync_exports = {};
623
650
  __export(cloud_sync_exports, {
651
+ assertSecureEndpoint: () => assertSecureEndpoint,
624
652
  buildRosterBlob: () => buildRosterBlob,
625
653
  cloudPull: () => cloudPull,
626
654
  cloudPullBehaviors: () => cloudPullBehaviors,
@@ -640,9 +668,10 @@ __export(cloud_sync_exports, {
640
668
  cloudPushTasks: () => cloudPushTasks,
641
669
  cloudSync: () => cloudSync,
642
670
  mergeConfig: () => mergeConfig,
643
- mergeRosterFromRemote: () => mergeRosterFromRemote
671
+ mergeRosterFromRemote: () => mergeRosterFromRemote,
672
+ recordRosterDeletion: () => recordRosterDeletion
644
673
  });
645
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync } from "fs";
674
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
646
675
  import path6 from "path";
647
676
  import { homedir } from "os";
648
677
  function logError(msg) {
@@ -653,16 +682,47 @@ function logError(msg) {
653
682
  } catch {
654
683
  }
655
684
  }
685
+ async function withRosterLock(fn) {
686
+ if (existsSync6(ROSTER_LOCK_PATH)) {
687
+ try {
688
+ const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
689
+ if (Date.now() - ts < LOCK_STALE_MS) {
690
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
691
+ }
692
+ } catch (err) {
693
+ if (err instanceof Error && err.message.includes("already in progress")) throw err;
694
+ }
695
+ }
696
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
697
+ try {
698
+ return await fn();
699
+ } finally {
700
+ try {
701
+ unlinkSync(ROSTER_LOCK_PATH);
702
+ } catch {
703
+ }
704
+ }
705
+ }
656
706
  async function fetchWithRetry(url, init) {
657
- const attempt = async () => {
658
- const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
659
- return fetch(url, { ...init, signal });
660
- };
661
- const resp = await attempt();
662
- if (resp.status >= 500) {
663
- return attempt();
707
+ const MAX_RETRIES = 3;
708
+ const BASE_DELAY_MS = 200;
709
+ let lastError;
710
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
711
+ try {
712
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
713
+ const resp = await fetch(url, { ...init, signal });
714
+ if (resp.status >= 500 && attempt < MAX_RETRIES) {
715
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
716
+ continue;
717
+ }
718
+ return resp;
719
+ } catch (err) {
720
+ lastError = err;
721
+ if (attempt === MAX_RETRIES) throw err;
722
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
723
+ }
664
724
  }
665
- return resp;
725
+ throw lastError;
666
726
  }
667
727
  function assertSecureEndpoint(endpoint) {
668
728
  if (endpoint.startsWith("https://")) return;
@@ -690,10 +750,15 @@ async function cloudPush(records, maxVersion, config) {
690
750
  headers: {
691
751
  Authorization: `Bearer ${config.apiKey}`,
692
752
  "Content-Type": "application/json",
693
- "X-Device-Id": loadDeviceId()
753
+ "X-Device-Id": loadDeviceId(),
754
+ "X-Expected-Version": String(maxVersion)
694
755
  },
695
756
  body: JSON.stringify({ version: maxVersion, blob })
696
757
  });
758
+ if (resp.status === 409) {
759
+ logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
760
+ return false;
761
+ }
697
762
  return resp.ok;
698
763
  } catch (err) {
699
764
  logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
@@ -780,18 +845,21 @@ async function cloudSync(config) {
780
845
  "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
781
846
  );
782
847
  const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
783
- const recordsResult = await client.execute({
784
- sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
785
- tool_name, project_name, has_error, raw_text, version,
786
- author_device_id, scope
787
- FROM memories
788
- WHERE version > ?
789
- AND (scope IS NULL OR scope != 'personal')
790
- ORDER BY version ASC`,
791
- args: [lastPushVersion]
792
- });
793
848
  let pushed = 0;
794
- if (recordsResult.rows.length > 0) {
849
+ let batchCursor = lastPushVersion;
850
+ while (true) {
851
+ const recordsResult = await client.execute({
852
+ sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
853
+ tool_name, project_name, has_error, raw_text, version,
854
+ author_device_id, scope
855
+ FROM memories
856
+ WHERE version > ?
857
+ AND (scope IS NULL OR scope != 'personal')
858
+ ORDER BY version ASC
859
+ LIMIT ?`,
860
+ args: [batchCursor, PUSH_BATCH_SIZE]
861
+ });
862
+ if (recordsResult.rows.length === 0) break;
795
863
  const records = recordsResult.rows.map((row) => ({
796
864
  id: row.id,
797
865
  agent_id: row.agent_id,
@@ -808,13 +876,14 @@ async function cloudSync(config) {
808
876
  }));
809
877
  const maxVersion = Number(records[records.length - 1].version);
810
878
  const pushOk = await cloudPush(records, maxVersion, config);
811
- if (pushOk) {
812
- await client.execute({
813
- sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
814
- args: [String(maxVersion)]
815
- });
816
- pushed = records.length;
817
- }
879
+ if (!pushOk) break;
880
+ await client.execute({
881
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
882
+ args: [String(maxVersion)]
883
+ });
884
+ pushed += records.length;
885
+ batchCursor = maxVersion;
886
+ if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
818
887
  }
819
888
  try {
820
889
  await cloudPushRoster(config);
@@ -896,6 +965,27 @@ async function cloudSync(config) {
896
965
  documents: documentsResult
897
966
  };
898
967
  }
968
+ function recordRosterDeletion(name) {
969
+ let deletions = [];
970
+ try {
971
+ if (existsSync6(ROSTER_DELETIONS_PATH)) {
972
+ deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
973
+ }
974
+ } catch {
975
+ }
976
+ if (!deletions.includes(name)) deletions.push(name);
977
+ writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
978
+ }
979
+ function consumeRosterDeletions() {
980
+ try {
981
+ if (!existsSync6(ROSTER_DELETIONS_PATH)) return [];
982
+ const deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
983
+ writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
984
+ return deletions;
985
+ } catch {
986
+ return [];
987
+ }
988
+ }
899
989
  function buildRosterBlob(paths) {
900
990
  const rosterPath = paths?.rosterPath ?? path6.join(EXE_AI_DIR, "exe-employees.json");
901
991
  const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
@@ -923,9 +1013,10 @@ function buildRosterBlob(paths) {
923
1013
  } catch {
924
1014
  }
925
1015
  }
926
- const content = JSON.stringify({ roster, identities, config });
1016
+ const deletedNames = consumeRosterDeletions();
1017
+ const content = JSON.stringify({ roster, identities, config, deletedNames });
927
1018
  const hash = Buffer.from(content).length;
928
- return { roster, identities, config, version: hash };
1019
+ return { roster, identities, config, deletedNames, version: hash };
929
1020
  }
930
1021
  async function cloudPushRoster(config) {
931
1022
  assertSecureEndpoint(config.endpoint);
@@ -1008,38 +1099,50 @@ function mergeConfig(remoteConfig, configPath) {
1008
1099
  writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
1009
1100
  }
1010
1101
  async function mergeRosterFromRemote(remote, paths) {
1011
- const rosterPath = paths?.rosterPath ?? void 0;
1012
- const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
1013
- const localEmployees = await loadEmployees(rosterPath);
1014
- const localNames = new Set(localEmployees.map((e) => e.name));
1015
- let added = 0;
1016
- for (const remoteEmp of remote.roster) {
1017
- if (localNames.has(remoteEmp.name)) continue;
1018
- localEmployees.push(remoteEmp);
1019
- localNames.add(remoteEmp.name);
1020
- added++;
1021
- if (remote.identities[`${remoteEmp.name}.md`]) {
1022
- if (!existsSync6(identityDir)) mkdirSync2(identityDir, { recursive: true });
1023
- const idPath = path6.join(identityDir, `${remoteEmp.name}.md`);
1024
- if (!existsSync6(idPath)) {
1025
- writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
1102
+ return withRosterLock(async () => {
1103
+ const rosterPath = paths?.rosterPath ?? void 0;
1104
+ const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
1105
+ const localEmployees = await loadEmployees(rosterPath);
1106
+ const localNames = new Set(localEmployees.map((e) => e.name));
1107
+ let added = 0;
1108
+ for (const remoteEmp of remote.roster) {
1109
+ if (localNames.has(remoteEmp.name)) continue;
1110
+ localEmployees.push(remoteEmp);
1111
+ localNames.add(remoteEmp.name);
1112
+ added++;
1113
+ if (remote.identities[`${remoteEmp.name}.md`]) {
1114
+ if (!existsSync6(identityDir)) mkdirSync2(identityDir, { recursive: true });
1115
+ const idPath = path6.join(identityDir, `${remoteEmp.name}.md`);
1116
+ if (!existsSync6(idPath)) {
1117
+ writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
1118
+ }
1119
+ }
1120
+ try {
1121
+ registerBinSymlinks(remoteEmp.name);
1122
+ } catch {
1026
1123
  }
1027
1124
  }
1028
- try {
1029
- registerBinSymlinks(remoteEmp.name);
1030
- } catch {
1125
+ let removed = 0;
1126
+ if (remote.deletedNames && remote.deletedNames.length > 0) {
1127
+ const toRemove = new Set(remote.deletedNames);
1128
+ const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
1129
+ removed = localEmployees.length - filtered.length;
1130
+ if (removed > 0) {
1131
+ localEmployees.length = 0;
1132
+ localEmployees.push(...filtered);
1133
+ }
1031
1134
  }
1032
- }
1033
- if (added > 0) {
1034
- await saveEmployees(localEmployees, rosterPath);
1035
- }
1036
- if (remote.config && Object.keys(remote.config).length > 0) {
1037
- try {
1038
- mergeConfig(remote.config, paths?.configPath);
1039
- } catch {
1135
+ if (added > 0 || removed > 0) {
1136
+ await saveEmployees(localEmployees, rosterPath);
1040
1137
  }
1041
- }
1042
- return { added };
1138
+ if (remote.config && Object.keys(remote.config).length > 0) {
1139
+ try {
1140
+ mergeConfig(remote.config, paths?.configPath);
1141
+ } catch {
1142
+ }
1143
+ }
1144
+ return { added };
1145
+ });
1043
1146
  }
1044
1147
  async function cloudPushBlob(route, data, metaKey, config) {
1045
1148
  if (data.length === 0) return { ok: true };
@@ -1107,7 +1210,7 @@ async function cloudPullBlob(route, config) {
1107
1210
  }
1108
1211
  async function cloudPushBehaviors(config) {
1109
1212
  const client = getClient();
1110
- const result = await client.execute("SELECT * FROM behaviors");
1213
+ const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
1111
1214
  const rows = result.rows;
1112
1215
  const { ok } = await cloudPushBlob(
1113
1216
  "/sync/push-behaviors",
@@ -1155,13 +1258,13 @@ async function cloudPullBehaviors(config) {
1155
1258
  async function cloudPushGraphRAG(config) {
1156
1259
  const client = getClient();
1157
1260
  const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
1158
- client.execute("SELECT * FROM entities"),
1159
- client.execute("SELECT * FROM relationships"),
1160
- client.execute("SELECT * FROM entity_aliases"),
1161
- client.execute("SELECT * FROM entity_memories"),
1162
- client.execute("SELECT * FROM relationship_memories"),
1163
- client.execute("SELECT * FROM hyperedges"),
1164
- client.execute("SELECT * FROM hyperedge_nodes")
1261
+ client.execute("SELECT * FROM entities LIMIT 50000"),
1262
+ client.execute("SELECT * FROM relationships LIMIT 50000"),
1263
+ client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
1264
+ client.execute("SELECT * FROM entity_memories LIMIT 50000"),
1265
+ client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
1266
+ client.execute("SELECT * FROM hyperedges LIMIT 50000"),
1267
+ client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
1165
1268
  ]);
1166
1269
  const blob = {
1167
1270
  entities: entities.rows,
@@ -1263,7 +1366,7 @@ async function cloudPullGraphRAG(config) {
1263
1366
  }
1264
1367
  async function cloudPushTasks(config) {
1265
1368
  const client = getClient();
1266
- const result = await client.execute("SELECT * FROM tasks");
1369
+ const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
1267
1370
  const rows = result.rows;
1268
1371
  const { ok } = await cloudPushBlob(
1269
1372
  "/sync/push-tasks",
@@ -1309,7 +1412,7 @@ async function cloudPullTasks(config) {
1309
1412
  }
1310
1413
  async function cloudPushConversations(config) {
1311
1414
  const client = getClient();
1312
- const result = await client.execute("SELECT * FROM conversations");
1415
+ const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
1313
1416
  const rows = result.rows;
1314
1417
  const { ok } = await cloudPushBlob(
1315
1418
  "/sync/push-conversations",
@@ -1359,8 +1462,8 @@ async function cloudPullConversations(config) {
1359
1462
  async function cloudPushDocuments(config) {
1360
1463
  const client = getClient();
1361
1464
  const [workspaces, documents] = await Promise.all([
1362
- client.execute("SELECT * FROM workspaces"),
1363
- client.execute("SELECT * FROM documents")
1465
+ client.execute("SELECT * FROM workspaces LIMIT 1000"),
1466
+ client.execute("SELECT * FROM documents LIMIT 10000")
1364
1467
  ]);
1365
1468
  const blob = {
1366
1469
  workspaces: workspaces.rows,
@@ -1413,7 +1516,7 @@ async function cloudPullDocuments(config) {
1413
1516
  }
1414
1517
  return { pulled };
1415
1518
  }
1416
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS;
1519
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
1417
1520
  var init_cloud_sync = __esm({
1418
1521
  "src/lib/cloud-sync.ts"() {
1419
1522
  "use strict";
@@ -1426,6 +1529,10 @@ var init_cloud_sync = __esm({
1426
1529
  init_employees();
1427
1530
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
1428
1531
  FETCH_TIMEOUT_MS = 3e4;
1532
+ PUSH_BATCH_SIZE = 5e3;
1533
+ ROSTER_LOCK_PATH = path6.join(EXE_AI_DIR, "roster-merge.lock");
1534
+ LOCK_STALE_MS = 3e4;
1535
+ ROSTER_DELETIONS_PATH = path6.join(EXE_AI_DIR, "roster-deletions.json");
1429
1536
  }
1430
1537
  });
1431
1538
 
@@ -1436,11 +1543,12 @@ import { createInterface } from "readline";
1436
1543
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1437
1544
  import { existsSync } from "fs";
1438
1545
  import path from "path";
1546
+ import os from "os";
1439
1547
  import crypto from "crypto";
1440
1548
  var SERVICE = "exe-mem";
1441
1549
  var ACCOUNT = "master-key";
1442
1550
  function getKeyDir() {
1443
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1551
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1444
1552
  }
1445
1553
  function getKeyPath() {
1446
1554
  return path.join(getKeyDir(), "master.key");
@@ -1580,6 +1688,7 @@ async function main() {
1580
1688
  `);
1581
1689
  console.log("Write this down and enter it on your new device with /exe-link import.");
1582
1690
  console.log("Anyone with this phrase can decrypt your memories.");
1691
+ console.log("\u26A0 Clear your terminal history after copying.");
1583
1692
  } else if (mode === "import") {
1584
1693
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1585
1694
  const mnemonic = await new Promise((resolve) => {