@askexenow/exe-os 0.9.30 → 0.9.32

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 (64) hide show
  1. package/dist/bin/backfill-conversations.js +135 -7
  2. package/dist/bin/backfill-responses.js +135 -7
  3. package/dist/bin/backfill-vectors.js +135 -7
  4. package/dist/bin/cleanup-stale-review-tasks.js +139 -11
  5. package/dist/bin/cli.js +812 -486
  6. package/dist/bin/exe-assign.js +135 -7
  7. package/dist/bin/exe-boot.js +422 -113
  8. package/dist/bin/exe-cloud.js +160 -9
  9. package/dist/bin/exe-dispatch.js +136 -8
  10. package/dist/bin/exe-doctor.js +255 -13
  11. package/dist/bin/exe-export-behaviors.js +136 -8
  12. package/dist/bin/exe-forget.js +136 -8
  13. package/dist/bin/exe-gateway.js +171 -24
  14. package/dist/bin/exe-heartbeat.js +141 -13
  15. package/dist/bin/exe-kill.js +140 -12
  16. package/dist/bin/exe-launch-agent.js +143 -15
  17. package/dist/bin/exe-link.js +357 -48
  18. package/dist/bin/exe-pending-messages.js +136 -8
  19. package/dist/bin/exe-pending-notifications.js +136 -8
  20. package/dist/bin/exe-pending-reviews.js +138 -10
  21. package/dist/bin/exe-review.js +136 -8
  22. package/dist/bin/exe-search.js +155 -20
  23. package/dist/bin/exe-session-cleanup.js +166 -38
  24. package/dist/bin/exe-start-codex.js +142 -14
  25. package/dist/bin/exe-start-opencode.js +140 -12
  26. package/dist/bin/exe-status.js +148 -20
  27. package/dist/bin/exe-team.js +136 -8
  28. package/dist/bin/git-sweep.js +138 -10
  29. package/dist/bin/graph-backfill.js +135 -7
  30. package/dist/bin/graph-export.js +136 -8
  31. package/dist/bin/intercom-check.js +153 -25
  32. package/dist/bin/scan-tasks.js +138 -10
  33. package/dist/bin/setup.js +447 -121
  34. package/dist/bin/shard-migrate.js +135 -7
  35. package/dist/gateway/index.js +151 -23
  36. package/dist/hooks/bug-report-worker.js +151 -23
  37. package/dist/hooks/codex-stop-task-finalizer.js +145 -17
  38. package/dist/hooks/commit-complete.js +138 -10
  39. package/dist/hooks/error-recall.js +159 -24
  40. package/dist/hooks/ingest.js +142 -14
  41. package/dist/hooks/instructions-loaded.js +136 -8
  42. package/dist/hooks/notification.js +136 -8
  43. package/dist/hooks/post-compact.js +136 -8
  44. package/dist/hooks/post-tool-combined.js +159 -24
  45. package/dist/hooks/pre-compact.js +136 -8
  46. package/dist/hooks/pre-tool-use.js +144 -16
  47. package/dist/hooks/prompt-submit.js +195 -55
  48. package/dist/hooks/session-end.js +141 -13
  49. package/dist/hooks/session-start.js +165 -30
  50. package/dist/hooks/stop.js +136 -8
  51. package/dist/hooks/subagent-stop.js +136 -8
  52. package/dist/hooks/summary-worker.js +374 -65
  53. package/dist/index.js +136 -8
  54. package/dist/lib/cloud-sync.js +355 -46
  55. package/dist/lib/consolidation.js +1 -0
  56. package/dist/lib/exe-daemon.js +469 -127
  57. package/dist/lib/hybrid-search.js +155 -20
  58. package/dist/lib/keychain.js +191 -7
  59. package/dist/lib/schedules.js +138 -10
  60. package/dist/lib/store.js +135 -7
  61. package/dist/mcp/server.js +706 -213
  62. package/dist/runtime/index.js +136 -8
  63. package/dist/tui/App.js +208 -31
  64. package/package.json +1 -1
@@ -1156,8 +1156,8 @@ function findPackageRoot() {
1156
1156
  function getAvailableMemoryGB() {
1157
1157
  if (process.platform === "darwin") {
1158
1158
  try {
1159
- const { execSync: execSync5 } = __require("child_process");
1160
- const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1159
+ const { execSync: execSync6 } = __require("child_process");
1160
+ const vmstat = execSync6("vm_stat", { encoding: "utf8" });
1161
1161
  const pageSize = 16384;
1162
1162
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1163
1163
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2849,6 +2849,7 @@ __export(keychain_exports, {
2849
2849
  });
2850
2850
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2851
2851
  import { existsSync as existsSync6 } from "fs";
2852
+ import { execSync as execSync2 } from "child_process";
2852
2853
  import path6 from "path";
2853
2854
  import os5 from "os";
2854
2855
  function getKeyDir() {
@@ -2857,6 +2858,83 @@ function getKeyDir() {
2857
2858
  function getKeyPath() {
2858
2859
  return path6.join(getKeyDir(), "master.key");
2859
2860
  }
2861
+ function macKeychainGet() {
2862
+ if (process.platform !== "darwin") return null;
2863
+ try {
2864
+ return execSync2(
2865
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2866
+ { encoding: "utf-8", timeout: 5e3 }
2867
+ ).trim();
2868
+ } catch {
2869
+ return null;
2870
+ }
2871
+ }
2872
+ function macKeychainSet(value) {
2873
+ if (process.platform !== "darwin") return false;
2874
+ try {
2875
+ try {
2876
+ execSync2(
2877
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2878
+ { timeout: 5e3 }
2879
+ );
2880
+ } catch {
2881
+ }
2882
+ execSync2(
2883
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2884
+ { timeout: 5e3 }
2885
+ );
2886
+ return true;
2887
+ } catch {
2888
+ return false;
2889
+ }
2890
+ }
2891
+ function macKeychainDelete() {
2892
+ if (process.platform !== "darwin") return false;
2893
+ try {
2894
+ execSync2(
2895
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2896
+ { timeout: 5e3 }
2897
+ );
2898
+ return true;
2899
+ } catch {
2900
+ return false;
2901
+ }
2902
+ }
2903
+ function linuxSecretGet() {
2904
+ if (process.platform !== "linux") return null;
2905
+ try {
2906
+ return execSync2(
2907
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2908
+ { encoding: "utf-8", timeout: 5e3 }
2909
+ ).trim();
2910
+ } catch {
2911
+ return null;
2912
+ }
2913
+ }
2914
+ function linuxSecretSet(value) {
2915
+ if (process.platform !== "linux") return false;
2916
+ try {
2917
+ execSync2(
2918
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
2919
+ { timeout: 5e3 }
2920
+ );
2921
+ return true;
2922
+ } catch {
2923
+ return false;
2924
+ }
2925
+ }
2926
+ function linuxSecretDelete() {
2927
+ if (process.platform !== "linux") return false;
2928
+ try {
2929
+ execSync2(
2930
+ `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2931
+ { timeout: 5e3 }
2932
+ );
2933
+ return true;
2934
+ } catch {
2935
+ return false;
2936
+ }
2937
+ }
2860
2938
  async function tryKeytar() {
2861
2939
  try {
2862
2940
  return await import("keytar");
@@ -2864,13 +2942,72 @@ async function tryKeytar() {
2864
2942
  return null;
2865
2943
  }
2866
2944
  }
2945
+ function deriveMachineKey() {
2946
+ try {
2947
+ const crypto5 = __require("crypto");
2948
+ const material = [
2949
+ os5.hostname(),
2950
+ os5.userInfo().username,
2951
+ os5.arch(),
2952
+ os5.platform(),
2953
+ // Machine ID on Linux (stable across reboots)
2954
+ process.platform === "linux" ? readMachineId() : ""
2955
+ ].join("|");
2956
+ return crypto5.createHash("sha256").update(material).digest();
2957
+ } catch {
2958
+ return null;
2959
+ }
2960
+ }
2961
+ function readMachineId() {
2962
+ try {
2963
+ const { readFileSync: readFileSync14 } = __require("fs");
2964
+ return readFileSync14("/etc/machine-id", "utf-8").trim();
2965
+ } catch {
2966
+ return "";
2967
+ }
2968
+ }
2969
+ function encryptWithMachineKey(plaintext, machineKey) {
2970
+ const crypto5 = __require("crypto");
2971
+ const iv = crypto5.randomBytes(12);
2972
+ const cipher = crypto5.createCipheriv("aes-256-gcm", machineKey, iv);
2973
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2974
+ encrypted += cipher.final("base64");
2975
+ const authTag = cipher.getAuthTag().toString("base64");
2976
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2977
+ }
2978
+ function decryptWithMachineKey(encrypted, machineKey) {
2979
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2980
+ try {
2981
+ const crypto5 = __require("crypto");
2982
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2983
+ if (parts.length !== 3) return null;
2984
+ const [ivB64, tagB64, cipherB64] = parts;
2985
+ const iv = Buffer.from(ivB64, "base64");
2986
+ const authTag = Buffer.from(tagB64, "base64");
2987
+ const decipher = crypto5.createDecipheriv("aes-256-gcm", machineKey, iv);
2988
+ decipher.setAuthTag(authTag);
2989
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2990
+ decrypted += decipher.final("utf-8");
2991
+ return decrypted;
2992
+ } catch {
2993
+ return null;
2994
+ }
2995
+ }
2867
2996
  async function getMasterKey() {
2997
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
2998
+ if (nativeValue) {
2999
+ return Buffer.from(nativeValue, "base64");
3000
+ }
2868
3001
  const keytar = await tryKeytar();
2869
3002
  if (keytar) {
2870
3003
  try {
2871
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2872
- if (stored) {
2873
- return Buffer.from(stored, "base64");
3004
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3005
+ if (keytarValue) {
3006
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3007
+ if (migrated) {
3008
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3009
+ }
3010
+ return Buffer.from(keytarValue, "base64");
2874
3011
  }
2875
3012
  } catch {
2876
3013
  }
@@ -2884,8 +3021,31 @@ async function getMasterKey() {
2884
3021
  return null;
2885
3022
  }
2886
3023
  try {
2887
- const content = await readFile3(keyPath, "utf-8");
2888
- return Buffer.from(content.trim(), "base64");
3024
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3025
+ let b64Value;
3026
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3027
+ const machineKey = deriveMachineKey();
3028
+ if (!machineKey) {
3029
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3030
+ return null;
3031
+ }
3032
+ const decrypted = decryptWithMachineKey(content, machineKey);
3033
+ if (!decrypted) {
3034
+ process.stderr.write(
3035
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3036
+ );
3037
+ return null;
3038
+ }
3039
+ b64Value = decrypted;
3040
+ } else {
3041
+ b64Value = content;
3042
+ }
3043
+ const key = Buffer.from(b64Value, "base64");
3044
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3045
+ if (migrated) {
3046
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3047
+ }
3048
+ return key;
2889
3049
  } catch (err) {
2890
3050
  process.stderr.write(
2891
3051
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2896,6 +3056,9 @@ async function getMasterKey() {
2896
3056
  }
2897
3057
  async function setMasterKey(key) {
2898
3058
  const b64 = key.toString("base64");
3059
+ if (macKeychainSet(b64) || linuxSecretSet(b64)) {
3060
+ return;
3061
+ }
2899
3062
  const keytar = await tryKeytar();
2900
3063
  if (keytar) {
2901
3064
  try {
@@ -2907,10 +3070,23 @@ async function setMasterKey(key) {
2907
3070
  const dir = getKeyDir();
2908
3071
  await mkdir3(dir, { recursive: true });
2909
3072
  const keyPath = getKeyPath();
2910
- await writeFile3(keyPath, b64 + "\n", "utf-8");
2911
- await chmod2(keyPath, 384);
3073
+ const machineKey = deriveMachineKey();
3074
+ if (machineKey) {
3075
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3076
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3077
+ await chmod2(keyPath, 384);
3078
+ process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
3079
+ } else {
3080
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3081
+ await chmod2(keyPath, 384);
3082
+ process.stderr.write(
3083
+ "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
3084
+ );
3085
+ }
2912
3086
  }
2913
3087
  async function deleteMasterKey() {
3088
+ macKeychainDelete();
3089
+ linuxSecretDelete();
2914
3090
  const keytar = await tryKeytar();
2915
3091
  if (keytar) {
2916
3092
  try {
@@ -2952,12 +3128,13 @@ async function importMnemonic(mnemonic) {
2952
3128
  const entropy = mnemonicToEntropy(trimmed);
2953
3129
  return Buffer.from(entropy, "hex");
2954
3130
  }
2955
- var SERVICE, ACCOUNT;
3131
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2956
3132
  var init_keychain = __esm({
2957
3133
  "src/lib/keychain.ts"() {
2958
3134
  "use strict";
2959
3135
  SERVICE = "exe-mem";
2960
3136
  ACCOUNT = "master-key";
3137
+ ENCRYPTED_PREFIX = "enc:";
2961
3138
  }
2962
3139
  });
2963
3140
 
@@ -3599,7 +3776,7 @@ var init_session_registry = __esm({
3599
3776
  });
3600
3777
 
3601
3778
  // src/lib/session-key.ts
3602
- import { execSync as execSync2 } from "child_process";
3779
+ import { execSync as execSync3 } from "child_process";
3603
3780
  function normalizeCommand(command) {
3604
3781
  const trimmed = command.trim().toLowerCase();
3605
3782
  const parts = trimmed.split(/[\\/]/);
@@ -3618,7 +3795,7 @@ function resolveRuntimeProcess() {
3618
3795
  let pid = process.ppid;
3619
3796
  for (let i = 0; i < 10; i++) {
3620
3797
  try {
3621
- const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
3798
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
3622
3799
  encoding: "utf8",
3623
3800
  timeout: 2e3
3624
3801
  }).trim();
@@ -3784,7 +3961,7 @@ var init_transport = __esm({
3784
3961
  });
3785
3962
 
3786
3963
  // src/lib/cc-agent-support.ts
3787
- import { execSync as execSync3 } from "child_process";
3964
+ import { execSync as execSync4 } from "child_process";
3788
3965
  var init_cc_agent_support = __esm({
3789
3966
  "src/lib/cc-agent-support.ts"() {
3790
3967
  "use strict";
@@ -4132,8 +4309,8 @@ async function validateLicense(apiKey, deviceId) {
4132
4309
  }
4133
4310
  function getCacheAgeMs() {
4134
4311
  try {
4135
- const { statSync: statSync3 } = __require("fs");
4136
- const s = statSync3(CACHE_PATH);
4312
+ const { statSync: statSync4 } = __require("fs");
4313
+ const s = statSync4(CACHE_PATH);
4137
4314
  return Date.now() - s.mtimeMs;
4138
4315
  } catch {
4139
4316
  return Infinity;
@@ -4635,10 +4812,10 @@ async function disposeEmbedder() {
4635
4812
  async function embedDirect(text) {
4636
4813
  const llamaCpp = await import("node-llama-cpp");
4637
4814
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4638
- const { existsSync: existsSync19 } = await import("fs");
4639
- const path20 = await import("path");
4640
- const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4641
- if (!existsSync19(modelPath)) {
4815
+ const { existsSync: existsSync20 } = await import("fs");
4816
+ const path21 = await import("path");
4817
+ const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4818
+ if (!existsSync20(modelPath)) {
4642
4819
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
4643
4820
  }
4644
4821
  const llama = await llamaCpp.getLlama();
@@ -5081,6 +5258,109 @@ var init_crdt_sync = __esm({
5081
5258
  }
5082
5259
  });
5083
5260
 
5261
+ // src/lib/db-backup.ts
5262
+ var db_backup_exports = {};
5263
+ __export(db_backup_exports, {
5264
+ createBackup: () => createBackup,
5265
+ findActiveDb: () => findActiveDb,
5266
+ getBackupDir: () => getBackupDir,
5267
+ getLatestBackup: () => getLatestBackup,
5268
+ hasBackupToday: () => hasBackupToday,
5269
+ listBackups: () => listBackups,
5270
+ rotateBackups: () => rotateBackups
5271
+ });
5272
+ import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync5, unlinkSync as unlinkSync7, statSync as statSync3 } from "fs";
5273
+ import path18 from "path";
5274
+ function findActiveDb() {
5275
+ for (const name of DB_NAMES) {
5276
+ const p = path18.join(EXE_AI_DIR, name);
5277
+ if (existsSync17(p)) return p;
5278
+ }
5279
+ return null;
5280
+ }
5281
+ function createBackup(reason = "manual") {
5282
+ const dbPath = findActiveDb();
5283
+ if (!dbPath) return null;
5284
+ mkdirSync8(BACKUP_DIR, { recursive: true });
5285
+ const dbName = path18.basename(dbPath, ".db");
5286
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
5287
+ const backupName = `${dbName}-${reason}-${timestamp}.db`;
5288
+ const backupPath = path18.join(BACKUP_DIR, backupName);
5289
+ copyFileSync(dbPath, backupPath);
5290
+ const walPath = dbPath + "-wal";
5291
+ if (existsSync17(walPath)) {
5292
+ try {
5293
+ copyFileSync(walPath, backupPath + "-wal");
5294
+ } catch {
5295
+ }
5296
+ }
5297
+ const shmPath = dbPath + "-shm";
5298
+ if (existsSync17(shmPath)) {
5299
+ try {
5300
+ copyFileSync(shmPath, backupPath + "-shm");
5301
+ } catch {
5302
+ }
5303
+ }
5304
+ return backupPath;
5305
+ }
5306
+ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
5307
+ if (!existsSync17(BACKUP_DIR)) return 0;
5308
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
5309
+ let deleted = 0;
5310
+ try {
5311
+ const files = readdirSync5(BACKUP_DIR);
5312
+ for (const file of files) {
5313
+ if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
5314
+ const filePath = path18.join(BACKUP_DIR, file);
5315
+ try {
5316
+ const stat = statSync3(filePath);
5317
+ if (stat.mtimeMs < cutoff) {
5318
+ unlinkSync7(filePath);
5319
+ deleted++;
5320
+ }
5321
+ } catch {
5322
+ }
5323
+ }
5324
+ } catch {
5325
+ }
5326
+ return deleted;
5327
+ }
5328
+ function listBackups() {
5329
+ if (!existsSync17(BACKUP_DIR)) return [];
5330
+ try {
5331
+ const files = readdirSync5(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
5332
+ return files.map((name) => {
5333
+ const p = path18.join(BACKUP_DIR, name);
5334
+ const stat = statSync3(p);
5335
+ return { path: p, name, size: stat.size, date: stat.mtime };
5336
+ }).sort((a, b) => b.date.getTime() - a.date.getTime());
5337
+ } catch {
5338
+ return [];
5339
+ }
5340
+ }
5341
+ function hasBackupToday(reason) {
5342
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5343
+ const backups = listBackups();
5344
+ return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
5345
+ }
5346
+ function getLatestBackup() {
5347
+ const backups = listBackups();
5348
+ return backups.length > 0 ? backups[0].path : null;
5349
+ }
5350
+ function getBackupDir() {
5351
+ return BACKUP_DIR;
5352
+ }
5353
+ var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
5354
+ var init_db_backup = __esm({
5355
+ "src/lib/db-backup.ts"() {
5356
+ "use strict";
5357
+ init_config();
5358
+ BACKUP_DIR = path18.join(EXE_AI_DIR, "backups");
5359
+ DEFAULT_KEEP_DAYS = 3;
5360
+ DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
5361
+ }
5362
+ });
5363
+
5084
5364
  // src/lib/cloud-sync.ts
5085
5365
  var cloud_sync_exports = {};
5086
5366
  __export(cloud_sync_exports, {
@@ -5110,16 +5390,16 @@ __export(cloud_sync_exports, {
5110
5390
  pushToPostgres: () => pushToPostgres,
5111
5391
  recordRosterDeletion: () => recordRosterDeletion
5112
5392
  });
5113
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync17, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync3, unlinkSync as unlinkSync7, openSync as openSync2, closeSync as closeSync2 } from "fs";
5393
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync18, readdirSync as readdirSync6, mkdirSync as mkdirSync9, appendFileSync as appendFileSync3, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2 } from "fs";
5114
5394
  import crypto4 from "crypto";
5115
- import path18 from "path";
5395
+ import path19 from "path";
5116
5396
  import { homedir as homedir2 } from "os";
5117
5397
  function sqlSafe(v) {
5118
5398
  return v === void 0 ? null : v;
5119
5399
  }
5120
5400
  function logError(msg) {
5121
5401
  try {
5122
- const logPath = path18.join(homedir2(), ".exe-os", "workers.log");
5402
+ const logPath = path19.join(homedir2(), ".exe-os", "workers.log");
5123
5403
  appendFileSync3(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
5124
5404
  `);
5125
5405
  } catch {
@@ -5128,10 +5408,10 @@ function logError(msg) {
5128
5408
  function loadPgClient() {
5129
5409
  if (_pgFailed) return null;
5130
5410
  const postgresUrl = process.env.DATABASE_URL;
5131
- const configPath = path18.join(EXE_AI_DIR, "config.json");
5411
+ const configPath = path19.join(EXE_AI_DIR, "config.json");
5132
5412
  let cloudPostgresUrl;
5133
5413
  try {
5134
- if (existsSync17(configPath)) {
5414
+ if (existsSync18(configPath)) {
5135
5415
  const cfg = JSON.parse(readFileSync13(configPath, "utf8"));
5136
5416
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
5137
5417
  if (cfg.cloud?.syncToPostgres === false) {
@@ -5150,8 +5430,8 @@ function loadPgClient() {
5150
5430
  _pgPromise = (async () => {
5151
5431
  const { createRequire: createRequire3 } = await import("module");
5152
5432
  const { pathToFileURL: pathToFileURL3 } = await import("url");
5153
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path18.join(homedir2(), "exe-db");
5154
- const req = createRequire3(path18.join(exeDbRoot, "package.json"));
5433
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path19.join(homedir2(), "exe-db");
5434
+ const req = createRequire3(path19.join(exeDbRoot, "package.json"));
5155
5435
  const entry = req.resolve("@prisma/client");
5156
5436
  const mod = await import(pathToFileURL3(entry).href);
5157
5437
  const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
@@ -5204,7 +5484,7 @@ async function withRosterLock(fn) {
5204
5484
  if (Date.now() - ts < LOCK_STALE_MS) {
5205
5485
  throw new Error("Roster merge already in progress \u2014 another sync is running");
5206
5486
  }
5207
- unlinkSync7(ROSTER_LOCK_PATH);
5487
+ unlinkSync8(ROSTER_LOCK_PATH);
5208
5488
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
5209
5489
  closeSync2(fd);
5210
5490
  writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
@@ -5220,7 +5500,7 @@ async function withRosterLock(fn) {
5220
5500
  return await fn();
5221
5501
  } finally {
5222
5502
  try {
5223
- unlinkSync7(ROSTER_LOCK_PATH);
5503
+ unlinkSync8(ROSTER_LOCK_PATH);
5224
5504
  } catch {
5225
5505
  }
5226
5506
  }
@@ -5591,13 +5871,42 @@ async function cloudSync(config) {
5591
5871
  try {
5592
5872
  const employees = await loadEmployees();
5593
5873
  rosterResult.employees = employees.length;
5594
- const idDir = path18.join(EXE_AI_DIR, "identity");
5595
- if (existsSync17(idDir)) {
5596
- rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
5874
+ const idDir = path19.join(EXE_AI_DIR, "identity");
5875
+ if (existsSync18(idDir)) {
5876
+ rosterResult.identities = readdirSync6(idDir).filter((f) => f.endsWith(".md")).length;
5597
5877
  }
5598
5878
  } catch {
5599
5879
  }
5600
5880
  const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
5881
+ try {
5882
+ const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
5883
+ const { statSync: statFile } = await import("fs");
5884
+ const latestBackup = getLatestBackup2();
5885
+ if (latestBackup) {
5886
+ const backupSize = statFile(latestBackup).size;
5887
+ const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
5888
+ if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
5889
+ const backupData = readFileSync13(latestBackup);
5890
+ const deviceId = loadDeviceId() ?? "unknown";
5891
+ const encrypted = encryptSyncBlob(backupData);
5892
+ const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
5893
+ method: "POST",
5894
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
5895
+ body: JSON.stringify({
5896
+ device_id: deviceId,
5897
+ filename: path19.basename(latestBackup),
5898
+ blob: encrypted,
5899
+ size: backupData.length
5900
+ })
5901
+ });
5902
+ if (backupRes && !backupRes.ok) {
5903
+ logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
5904
+ }
5905
+ }
5906
+ }
5907
+ } catch (err) {
5908
+ logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
5909
+ }
5601
5910
  return {
5602
5911
  pushed,
5603
5912
  pulled,
@@ -5613,7 +5922,7 @@ async function cloudSync(config) {
5613
5922
  function recordRosterDeletion(name) {
5614
5923
  let deletions = [];
5615
5924
  try {
5616
- if (existsSync17(ROSTER_DELETIONS_PATH)) {
5925
+ if (existsSync18(ROSTER_DELETIONS_PATH)) {
5617
5926
  deletions = JSON.parse(readFileSync13(ROSTER_DELETIONS_PATH, "utf-8"));
5618
5927
  }
5619
5928
  } catch {
@@ -5623,7 +5932,7 @@ function recordRosterDeletion(name) {
5623
5932
  }
5624
5933
  function consumeRosterDeletions() {
5625
5934
  try {
5626
- if (!existsSync17(ROSTER_DELETIONS_PATH)) return [];
5935
+ if (!existsSync18(ROSTER_DELETIONS_PATH)) return [];
5627
5936
  const deletions = JSON.parse(readFileSync13(ROSTER_DELETIONS_PATH, "utf-8"));
5628
5937
  writeFileSync9(ROSTER_DELETIONS_PATH, "[]");
5629
5938
  return deletions;
@@ -5632,35 +5941,35 @@ function consumeRosterDeletions() {
5632
5941
  }
5633
5942
  }
5634
5943
  function buildRosterBlob(paths) {
5635
- const rosterPath = paths?.rosterPath ?? path18.join(EXE_AI_DIR, "exe-employees.json");
5636
- const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
5637
- const configPath = paths?.configPath ?? path18.join(EXE_AI_DIR, "config.json");
5944
+ const rosterPath = paths?.rosterPath ?? path19.join(EXE_AI_DIR, "exe-employees.json");
5945
+ const identityDir = paths?.identityDir ?? path19.join(EXE_AI_DIR, "identity");
5946
+ const configPath = paths?.configPath ?? path19.join(EXE_AI_DIR, "config.json");
5638
5947
  let roster = [];
5639
- if (existsSync17(rosterPath)) {
5948
+ if (existsSync18(rosterPath)) {
5640
5949
  try {
5641
5950
  roster = JSON.parse(readFileSync13(rosterPath, "utf-8"));
5642
5951
  } catch {
5643
5952
  }
5644
5953
  }
5645
5954
  const identities = {};
5646
- if (existsSync17(identityDir)) {
5647
- for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
5955
+ if (existsSync18(identityDir)) {
5956
+ for (const file of readdirSync6(identityDir).filter((f) => f.endsWith(".md"))) {
5648
5957
  try {
5649
- identities[file] = readFileSync13(path18.join(identityDir, file), "utf-8");
5958
+ identities[file] = readFileSync13(path19.join(identityDir, file), "utf-8");
5650
5959
  } catch {
5651
5960
  }
5652
5961
  }
5653
5962
  }
5654
5963
  let config;
5655
- if (existsSync17(configPath)) {
5964
+ if (existsSync18(configPath)) {
5656
5965
  try {
5657
5966
  config = JSON.parse(readFileSync13(configPath, "utf-8"));
5658
5967
  } catch {
5659
5968
  }
5660
5969
  }
5661
5970
  let agentConfig;
5662
- const agentConfigPath = path18.join(EXE_AI_DIR, "agent-config.json");
5663
- if (existsSync17(agentConfigPath)) {
5971
+ const agentConfigPath = path19.join(EXE_AI_DIR, "agent-config.json");
5972
+ if (existsSync18(agentConfigPath)) {
5664
5973
  try {
5665
5974
  agentConfig = JSON.parse(readFileSync13(agentConfigPath, "utf-8"));
5666
5975
  } catch {
@@ -5738,16 +6047,16 @@ async function cloudPullRoster(config) {
5738
6047
  }
5739
6048
  }
5740
6049
  function mergeConfig(remoteConfig, configPath) {
5741
- const cfgPath = configPath ?? path18.join(EXE_AI_DIR, "config.json");
6050
+ const cfgPath = configPath ?? path19.join(EXE_AI_DIR, "config.json");
5742
6051
  let local = {};
5743
- if (existsSync17(cfgPath)) {
6052
+ if (existsSync18(cfgPath)) {
5744
6053
  try {
5745
6054
  local = JSON.parse(readFileSync13(cfgPath, "utf-8"));
5746
6055
  } catch {
5747
6056
  }
5748
6057
  }
5749
6058
  const merged = { ...remoteConfig, ...local };
5750
- const dir = path18.dirname(cfgPath);
6059
+ const dir = path19.dirname(cfgPath);
5751
6060
  ensurePrivateDirSync(dir);
5752
6061
  writeFileSync9(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5753
6062
  enforcePrivateFileSync(cfgPath);
@@ -5755,7 +6064,7 @@ function mergeConfig(remoteConfig, configPath) {
5755
6064
  async function mergeRosterFromRemote(remote, paths) {
5756
6065
  return withRosterLock(async () => {
5757
6066
  const rosterPath = paths?.rosterPath ?? void 0;
5758
- const identityDir = paths?.identityDir ?? path18.join(EXE_AI_DIR, "identity");
6067
+ const identityDir = paths?.identityDir ?? path19.join(EXE_AI_DIR, "identity");
5759
6068
  const localEmployees = await loadEmployees(rosterPath);
5760
6069
  const localNames = new Set(localEmployees.map((e) => e.name));
5761
6070
  let added = 0;
@@ -5776,11 +6085,11 @@ async function mergeRosterFromRemote(remote, paths) {
5776
6085
  ) ?? lookupKey;
5777
6086
  const remoteIdentity = remote.identities[matchedKey];
5778
6087
  if (remoteIdentity) {
5779
- if (!existsSync17(identityDir)) mkdirSync8(identityDir, { recursive: true });
5780
- const idPath = path18.join(identityDir, `${remoteEmp.name}.md`);
6088
+ if (!existsSync18(identityDir)) mkdirSync9(identityDir, { recursive: true });
6089
+ const idPath = path19.join(identityDir, `${remoteEmp.name}.md`);
5781
6090
  let localIdentity = null;
5782
6091
  try {
5783
- localIdentity = existsSync17(idPath) ? readFileSync13(idPath, "utf-8") : null;
6092
+ localIdentity = existsSync18(idPath) ? readFileSync13(idPath, "utf-8") : null;
5784
6093
  } catch {
5785
6094
  }
5786
6095
  if (localIdentity !== remoteIdentity) {
@@ -5810,16 +6119,16 @@ async function mergeRosterFromRemote(remote, paths) {
5810
6119
  }
5811
6120
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
5812
6121
  try {
5813
- const agentConfigPath = path18.join(EXE_AI_DIR, "agent-config.json");
6122
+ const agentConfigPath = path19.join(EXE_AI_DIR, "agent-config.json");
5814
6123
  let local = {};
5815
- if (existsSync17(agentConfigPath)) {
6124
+ if (existsSync18(agentConfigPath)) {
5816
6125
  try {
5817
6126
  local = JSON.parse(readFileSync13(agentConfigPath, "utf-8"));
5818
6127
  } catch {
5819
6128
  }
5820
6129
  }
5821
6130
  const merged = { ...remote.agentConfig, ...local };
5822
- ensurePrivateDirSync(path18.dirname(agentConfigPath));
6131
+ ensurePrivateDirSync(path19.dirname(agentConfigPath));
5823
6132
  writeFileSync9(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
5824
6133
  enforcePrivateFileSync(agentConfigPath);
5825
6134
  } catch {
@@ -6260,11 +6569,11 @@ var init_cloud_sync = __esm({
6260
6569
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
6261
6570
  FETCH_TIMEOUT_MS = 3e4;
6262
6571
  PUSH_BATCH_SIZE = 5e3;
6263
- ROSTER_LOCK_PATH = path18.join(EXE_AI_DIR, "roster-merge.lock");
6572
+ ROSTER_LOCK_PATH = path19.join(EXE_AI_DIR, "roster-merge.lock");
6264
6573
  LOCK_STALE_MS = 3e4;
6265
6574
  _pgPromise = null;
6266
6575
  _pgFailed = false;
6267
- ROSTER_DELETIONS_PATH = path18.join(EXE_AI_DIR, "roster-deletions.json");
6576
+ ROSTER_DELETIONS_PATH = path19.join(EXE_AI_DIR, "roster-deletions.json");
6268
6577
  }
6269
6578
  });
6270
6579
 
@@ -6408,9 +6717,9 @@ async function writeMemoryViaDaemon(entry) {
6408
6717
  // src/adapters/claude/hooks/summary-worker.ts
6409
6718
  init_task_scope();
6410
6719
  init_employees();
6411
- import { execSync as execSync4 } from "child_process";
6412
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
6413
- import path19 from "path";
6720
+ import { execSync as execSync5 } from "child_process";
6721
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10, openSync as openSync3, closeSync as closeSync3 } from "fs";
6722
+ import path20 from "path";
6414
6723
  async function main() {
6415
6724
  const agentId = process.env.AGENT_ID ?? "default";
6416
6725
  const agentRole = process.env.AGENT_ROLE ?? "employee";
@@ -6541,8 +6850,8 @@ async function main() {
6541
6850
  }
6542
6851
  try {
6543
6852
  const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6544
- const flagPath = path19.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
6545
- if (existsSync18(flagPath)) {
6853
+ const flagPath = path20.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
6854
+ if (existsSync19(flagPath)) {
6546
6855
  const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
6547
6856
  if (!tryAcquireWorkerSlot2()) {
6548
6857
  process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
@@ -6550,11 +6859,11 @@ async function main() {
6550
6859
  const { spawn: spawn2 } = await import("child_process");
6551
6860
  const { fileURLToPath: fileURLToPath3 } = await import("url");
6552
6861
  const thisFile = fileURLToPath3(import.meta.url);
6553
- const backfillPath = path19.resolve(path19.dirname(thisFile), "backfill-vectors.js");
6554
- if (existsSync18(backfillPath)) {
6862
+ const backfillPath = path20.resolve(path20.dirname(thisFile), "backfill-vectors.js");
6863
+ if (existsSync19(backfillPath)) {
6555
6864
  const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6556
- const bLogPath = path19.join(exeDir2, "workers.log");
6557
- mkdirSync9(path19.dirname(bLogPath), { recursive: true });
6865
+ const bLogPath = path20.join(exeDir2, "workers.log");
6866
+ mkdirSync10(path20.dirname(bLogPath), { recursive: true });
6558
6867
  const bLogFd = openSync3(bLogPath, "a");
6559
6868
  const child = spawn2(process.execPath, [backfillPath], {
6560
6869
  detached: true,
@@ -6615,7 +6924,7 @@ async function main() {
6615
6924
  const taskTitle = String(taskRow.title);
6616
6925
  let lastCommit = "";
6617
6926
  try {
6618
- lastCommit = execSync4("git log --oneline -1 --since='30 minutes ago'", {
6927
+ lastCommit = execSync5("git log --oneline -1 --since='30 minutes ago'", {
6619
6928
  encoding: "utf8",
6620
6929
  timeout: 5e3
6621
6930
  }).trim();