@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
@@ -2086,8 +2086,8 @@ function findPackageRoot() {
2086
2086
  function getAvailableMemoryGB() {
2087
2087
  if (process.platform === "darwin") {
2088
2088
  try {
2089
- const { execSync: execSync12 } = __require("child_process");
2090
- const vmstat = execSync12("vm_stat", { encoding: "utf8" });
2089
+ const { execSync: execSync13 } = __require("child_process");
2090
+ const vmstat = execSync13("vm_stat", { encoding: "utf8" });
2091
2091
  const pageSize = 16384;
2092
2092
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
2093
2093
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3715,6 +3715,7 @@ __export(keychain_exports, {
3715
3715
  });
3716
3716
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3717
3717
  import { existsSync as existsSync10 } from "fs";
3718
+ import { execSync as execSync2 } from "child_process";
3718
3719
  import path10 from "path";
3719
3720
  import os6 from "os";
3720
3721
  function getKeyDir() {
@@ -3723,6 +3724,83 @@ function getKeyDir() {
3723
3724
  function getKeyPath() {
3724
3725
  return path10.join(getKeyDir(), "master.key");
3725
3726
  }
3727
+ function macKeychainGet() {
3728
+ if (process.platform !== "darwin") return null;
3729
+ try {
3730
+ return execSync2(
3731
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3732
+ { encoding: "utf-8", timeout: 5e3 }
3733
+ ).trim();
3734
+ } catch {
3735
+ return null;
3736
+ }
3737
+ }
3738
+ function macKeychainSet(value) {
3739
+ if (process.platform !== "darwin") return false;
3740
+ try {
3741
+ try {
3742
+ execSync2(
3743
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3744
+ { timeout: 5e3 }
3745
+ );
3746
+ } catch {
3747
+ }
3748
+ execSync2(
3749
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3750
+ { timeout: 5e3 }
3751
+ );
3752
+ return true;
3753
+ } catch {
3754
+ return false;
3755
+ }
3756
+ }
3757
+ function macKeychainDelete() {
3758
+ if (process.platform !== "darwin") return false;
3759
+ try {
3760
+ execSync2(
3761
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3762
+ { timeout: 5e3 }
3763
+ );
3764
+ return true;
3765
+ } catch {
3766
+ return false;
3767
+ }
3768
+ }
3769
+ function linuxSecretGet() {
3770
+ if (process.platform !== "linux") return null;
3771
+ try {
3772
+ return execSync2(
3773
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3774
+ { encoding: "utf-8", timeout: 5e3 }
3775
+ ).trim();
3776
+ } catch {
3777
+ return null;
3778
+ }
3779
+ }
3780
+ function linuxSecretSet(value) {
3781
+ if (process.platform !== "linux") return false;
3782
+ try {
3783
+ execSync2(
3784
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3785
+ { timeout: 5e3 }
3786
+ );
3787
+ return true;
3788
+ } catch {
3789
+ return false;
3790
+ }
3791
+ }
3792
+ function linuxSecretDelete() {
3793
+ if (process.platform !== "linux") return false;
3794
+ try {
3795
+ execSync2(
3796
+ `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3797
+ { timeout: 5e3 }
3798
+ );
3799
+ return true;
3800
+ } catch {
3801
+ return false;
3802
+ }
3803
+ }
3726
3804
  async function tryKeytar() {
3727
3805
  try {
3728
3806
  return await import("keytar");
@@ -3730,13 +3808,72 @@ async function tryKeytar() {
3730
3808
  return null;
3731
3809
  }
3732
3810
  }
3811
+ function deriveMachineKey() {
3812
+ try {
3813
+ const crypto14 = __require("crypto");
3814
+ const material = [
3815
+ os6.hostname(),
3816
+ os6.userInfo().username,
3817
+ os6.arch(),
3818
+ os6.platform(),
3819
+ // Machine ID on Linux (stable across reboots)
3820
+ process.platform === "linux" ? readMachineId() : ""
3821
+ ].join("|");
3822
+ return crypto14.createHash("sha256").update(material).digest();
3823
+ } catch {
3824
+ return null;
3825
+ }
3826
+ }
3827
+ function readMachineId() {
3828
+ try {
3829
+ const { readFileSync: readFileSync22 } = __require("fs");
3830
+ return readFileSync22("/etc/machine-id", "utf-8").trim();
3831
+ } catch {
3832
+ return "";
3833
+ }
3834
+ }
3835
+ function encryptWithMachineKey(plaintext, machineKey) {
3836
+ const crypto14 = __require("crypto");
3837
+ const iv = crypto14.randomBytes(12);
3838
+ const cipher = crypto14.createCipheriv("aes-256-gcm", machineKey, iv);
3839
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3840
+ encrypted += cipher.final("base64");
3841
+ const authTag = cipher.getAuthTag().toString("base64");
3842
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3843
+ }
3844
+ function decryptWithMachineKey(encrypted, machineKey) {
3845
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3846
+ try {
3847
+ const crypto14 = __require("crypto");
3848
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3849
+ if (parts.length !== 3) return null;
3850
+ const [ivB64, tagB64, cipherB64] = parts;
3851
+ const iv = Buffer.from(ivB64, "base64");
3852
+ const authTag = Buffer.from(tagB64, "base64");
3853
+ const decipher = crypto14.createDecipheriv("aes-256-gcm", machineKey, iv);
3854
+ decipher.setAuthTag(authTag);
3855
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3856
+ decrypted += decipher.final("utf-8");
3857
+ return decrypted;
3858
+ } catch {
3859
+ return null;
3860
+ }
3861
+ }
3733
3862
  async function getMasterKey() {
3863
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3864
+ if (nativeValue) {
3865
+ return Buffer.from(nativeValue, "base64");
3866
+ }
3734
3867
  const keytar = await tryKeytar();
3735
3868
  if (keytar) {
3736
3869
  try {
3737
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3738
- if (stored) {
3739
- return Buffer.from(stored, "base64");
3870
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3871
+ if (keytarValue) {
3872
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3873
+ if (migrated) {
3874
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3875
+ }
3876
+ return Buffer.from(keytarValue, "base64");
3740
3877
  }
3741
3878
  } catch {
3742
3879
  }
@@ -3750,8 +3887,31 @@ async function getMasterKey() {
3750
3887
  return null;
3751
3888
  }
3752
3889
  try {
3753
- const content = await readFile3(keyPath, "utf-8");
3754
- return Buffer.from(content.trim(), "base64");
3890
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3891
+ let b64Value;
3892
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3893
+ const machineKey = deriveMachineKey();
3894
+ if (!machineKey) {
3895
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3896
+ return null;
3897
+ }
3898
+ const decrypted = decryptWithMachineKey(content, machineKey);
3899
+ if (!decrypted) {
3900
+ process.stderr.write(
3901
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3902
+ );
3903
+ return null;
3904
+ }
3905
+ b64Value = decrypted;
3906
+ } else {
3907
+ b64Value = content;
3908
+ }
3909
+ const key = Buffer.from(b64Value, "base64");
3910
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3911
+ if (migrated) {
3912
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3913
+ }
3914
+ return key;
3755
3915
  } catch (err) {
3756
3916
  process.stderr.write(
3757
3917
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3762,6 +3922,9 @@ async function getMasterKey() {
3762
3922
  }
3763
3923
  async function setMasterKey(key) {
3764
3924
  const b64 = key.toString("base64");
3925
+ if (macKeychainSet(b64) || linuxSecretSet(b64)) {
3926
+ return;
3927
+ }
3765
3928
  const keytar = await tryKeytar();
3766
3929
  if (keytar) {
3767
3930
  try {
@@ -3773,10 +3936,23 @@ async function setMasterKey(key) {
3773
3936
  const dir = getKeyDir();
3774
3937
  await mkdir3(dir, { recursive: true });
3775
3938
  const keyPath = getKeyPath();
3776
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3777
- await chmod2(keyPath, 384);
3939
+ const machineKey = deriveMachineKey();
3940
+ if (machineKey) {
3941
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3942
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3943
+ await chmod2(keyPath, 384);
3944
+ process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
3945
+ } else {
3946
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3947
+ await chmod2(keyPath, 384);
3948
+ process.stderr.write(
3949
+ "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
3950
+ );
3951
+ }
3778
3952
  }
3779
3953
  async function deleteMasterKey() {
3954
+ macKeychainDelete();
3955
+ linuxSecretDelete();
3780
3956
  const keytar = await tryKeytar();
3781
3957
  if (keytar) {
3782
3958
  try {
@@ -3818,12 +3994,13 @@ async function importMnemonic(mnemonic) {
3818
3994
  const entropy = mnemonicToEntropy(trimmed);
3819
3995
  return Buffer.from(entropy, "hex");
3820
3996
  }
3821
- var SERVICE, ACCOUNT;
3997
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3822
3998
  var init_keychain = __esm({
3823
3999
  "src/lib/keychain.ts"() {
3824
4000
  "use strict";
3825
4001
  SERVICE = "exe-mem";
3826
4002
  ACCOUNT = "master-key";
4003
+ ENCRYPTED_PREFIX = "enc:";
3827
4004
  }
3828
4005
  });
3829
4006
 
@@ -4827,7 +5004,7 @@ __export(session_registry_exports, {
4827
5004
  registerSession: () => registerSession
4828
5005
  });
4829
5006
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, existsSync as existsSync12 } from "fs";
4830
- import { execSync as execSync2 } from "child_process";
5007
+ import { execSync as execSync3 } from "child_process";
4831
5008
  import path12 from "path";
4832
5009
  import os7 from "os";
4833
5010
  function registerSession(entry) {
@@ -4867,7 +5044,7 @@ function pruneStaleSessions() {
4867
5044
  if (sessions.length === 0) return 0;
4868
5045
  let liveSessions = [];
4869
5046
  try {
4870
- liveSessions = execSync2("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
5047
+ liveSessions = execSync3("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
4871
5048
  encoding: "utf8"
4872
5049
  }).trim().split("\n").filter(Boolean);
4873
5050
  } catch {
@@ -4890,7 +5067,7 @@ var init_session_registry = __esm({
4890
5067
  });
4891
5068
 
4892
5069
  // src/lib/session-key.ts
4893
- import { execSync as execSync3 } from "child_process";
5070
+ import { execSync as execSync4 } from "child_process";
4894
5071
  function normalizeCommand(command) {
4895
5072
  const trimmed = command.trim().toLowerCase();
4896
5073
  const parts = trimmed.split(/[\\/]/);
@@ -4909,7 +5086,7 @@ function resolveRuntimeProcess() {
4909
5086
  let pid = process.ppid;
4910
5087
  for (let i = 0; i < 10; i++) {
4911
5088
  try {
4912
- const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
5089
+ const info = execSync4(`ps -p ${pid} -o ppid=,comm=`, {
4913
5090
  encoding: "utf8",
4914
5091
  timeout: 2e3
4915
5092
  }).trim();
@@ -5083,14 +5260,14 @@ var init_transport = __esm({
5083
5260
  });
5084
5261
 
5085
5262
  // src/lib/cc-agent-support.ts
5086
- import { execSync as execSync4 } from "child_process";
5263
+ import { execSync as execSync5 } from "child_process";
5087
5264
  function _resetCcAgentSupportCache() {
5088
5265
  _cachedSupport = null;
5089
5266
  }
5090
5267
  function claudeSupportsAgentFlag() {
5091
5268
  if (_cachedSupport !== null) return _cachedSupport;
5092
5269
  try {
5093
- const helpOutput = execSync4("claude --help 2>&1", {
5270
+ const helpOutput = execSync5("claude --help 2>&1", {
5094
5271
  encoding: "utf-8",
5095
5272
  timeout: 5e3
5096
5273
  });
@@ -5568,7 +5745,7 @@ var init_session_kill_telemetry = __esm({
5568
5745
  });
5569
5746
 
5570
5747
  // src/lib/project-name.ts
5571
- import { execSync as execSync5 } from "child_process";
5748
+ import { execSync as execSync6 } from "child_process";
5572
5749
  import path17 from "path";
5573
5750
  function getProjectName(cwd) {
5574
5751
  const dir = cwd ?? process.cwd();
@@ -5576,7 +5753,7 @@ function getProjectName(cwd) {
5576
5753
  try {
5577
5754
  let repoRoot;
5578
5755
  try {
5579
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
5756
+ const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
5580
5757
  cwd: dir,
5581
5758
  encoding: "utf8",
5582
5759
  timeout: 2e3,
@@ -5584,7 +5761,7 @@ function getProjectName(cwd) {
5584
5761
  }).trim();
5585
5762
  repoRoot = path17.dirname(gitCommonDir);
5586
5763
  } catch {
5587
- repoRoot = execSync5("git rev-parse --show-toplevel", {
5764
+ repoRoot = execSync6("git rev-parse --show-toplevel", {
5588
5765
  cwd: dir,
5589
5766
  encoding: "utf8",
5590
5767
  timeout: 2e3,
@@ -5675,7 +5852,7 @@ var init_session_scope = __esm({
5675
5852
  import crypto5 from "crypto";
5676
5853
  import path18 from "path";
5677
5854
  import os11 from "os";
5678
- import { execSync as execSync6 } from "child_process";
5855
+ import { execSync as execSync7 } from "child_process";
5679
5856
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
5680
5857
  import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
5681
5858
  async function writeCheckpoint(input) {
@@ -6020,14 +6197,14 @@ function isTmuxSessionAlive(identifier) {
6020
6197
  if (!identifier || identifier === "unknown") return true;
6021
6198
  try {
6022
6199
  if (identifier.startsWith("%")) {
6023
- const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
6200
+ const output = execSync7("tmux list-panes -a -F '#{pane_id}'", {
6024
6201
  timeout: 2e3,
6025
6202
  encoding: "utf8",
6026
6203
  stdio: ["pipe", "pipe", "pipe"]
6027
6204
  });
6028
6205
  return output.split("\n").some((l) => l.trim() === identifier);
6029
6206
  } else {
6030
- execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
6207
+ execSync7(`tmux has-session -t ${JSON.stringify(identifier)}`, {
6031
6208
  timeout: 2e3,
6032
6209
  stdio: ["pipe", "pipe", "pipe"]
6033
6210
  });
@@ -6036,7 +6213,7 @@ function isTmuxSessionAlive(identifier) {
6036
6213
  } catch {
6037
6214
  if (identifier.startsWith("%")) return true;
6038
6215
  try {
6039
- execSync6("tmux list-sessions", {
6216
+ execSync7("tmux list-sessions", {
6040
6217
  timeout: 2e3,
6041
6218
  stdio: ["pipe", "pipe", "pipe"]
6042
6219
  });
@@ -6051,12 +6228,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
6051
6228
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
6052
6229
  try {
6053
6230
  const since = new Date(taskCreatedAt).toISOString();
6054
- const branch = execSync6(
6231
+ const branch = execSync7(
6055
6232
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
6056
6233
  { encoding: "utf8", timeout: 3e3 }
6057
6234
  ).trim();
6058
6235
  const branchArg = branch && branch !== "HEAD" ? branch : "";
6059
- const commitCount = execSync6(
6236
+ const commitCount = execSync7(
6060
6237
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
6061
6238
  { encoding: "utf8", timeout: 5e3 }
6062
6239
  ).trim();
@@ -6794,10 +6971,10 @@ async function disposeEmbedder() {
6794
6971
  async function embedDirect(text) {
6795
6972
  const llamaCpp = await import("node-llama-cpp");
6796
6973
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6797
- const { existsSync: existsSync26 } = await import("fs");
6798
- const path31 = await import("path");
6799
- const modelPath = path31.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
6800
- if (!existsSync26(modelPath)) {
6974
+ const { existsSync: existsSync27 } = await import("fs");
6975
+ const path32 = await import("path");
6976
+ const modelPath = path32.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
6977
+ if (!existsSync27(modelPath)) {
6801
6978
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
6802
6979
  }
6803
6980
  const llama = await llamaCpp.getLlama();
@@ -7654,7 +7831,7 @@ __export(tmux_routing_exports, {
7654
7831
  spawnEmployee: () => spawnEmployee,
7655
7832
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
7656
7833
  });
7657
- import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
7834
+ import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
7658
7835
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync19, appendFileSync as appendFileSync2, readdirSync as readdirSync4 } from "fs";
7659
7836
  import path22 from "path";
7660
7837
  import os12 from "os";
@@ -8364,7 +8541,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8364
8541
  let booted = false;
8365
8542
  for (let i = 0; i < 30; i++) {
8366
8543
  try {
8367
- execSync7("sleep 0.5");
8544
+ execSync8("sleep 0.5");
8368
8545
  } catch {
8369
8546
  }
8370
8547
  try {
@@ -8484,7 +8661,7 @@ __export(review_polling_exports, {
8484
8661
  createRealDeps: () => createRealDeps,
8485
8662
  pollPendingReviews: () => pollPendingReviews
8486
8663
  });
8487
- import { execSync as execSync8 } from "child_process";
8664
+ import { execSync as execSync9 } from "child_process";
8488
8665
  async function pollPendingReviews(deps, _state) {
8489
8666
  let sessions;
8490
8667
  try {
@@ -8591,7 +8768,7 @@ async function pollPendingReviews(deps, _state) {
8591
8768
  function createRealDeps(getClient2) {
8592
8769
  return {
8593
8770
  listTmuxSessions: () => {
8594
- return execSync8("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
8771
+ return execSync9("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
8595
8772
  encoding: "utf8",
8596
8773
  timeout: 3e3
8597
8774
  }).trim().split("\n").filter(Boolean);
@@ -8698,13 +8875,13 @@ __export(tmux_status_exports, {
8698
8875
  parseActivity: () => parseActivity,
8699
8876
  parseContextPercentage: () => parseContextPercentage
8700
8877
  });
8701
- import { execSync as execSync9 } from "child_process";
8878
+ import { execSync as execSync10 } from "child_process";
8702
8879
  function inTmux() {
8703
8880
  if (process.env.TMUX || process.env.TMUX_PANE) return true;
8704
8881
  const term = process.env.TERM ?? "";
8705
8882
  if (term.startsWith("tmux") || term.startsWith("screen")) return true;
8706
8883
  try {
8707
- execSync9("tmux display-message -p '#{session_name}' 2>/dev/null", {
8884
+ execSync10("tmux display-message -p '#{session_name}' 2>/dev/null", {
8708
8885
  encoding: "utf8",
8709
8886
  timeout: 2e3
8710
8887
  });
@@ -8714,12 +8891,12 @@ function inTmux() {
8714
8891
  try {
8715
8892
  let pid = process.ppid;
8716
8893
  for (let depth = 0; depth < 8 && pid > 1; depth++) {
8717
- const comm = execSync9(`ps -p ${pid} -o comm= 2>/dev/null`, {
8894
+ const comm = execSync10(`ps -p ${pid} -o comm= 2>/dev/null`, {
8718
8895
  encoding: "utf8",
8719
8896
  timeout: 1e3
8720
8897
  }).trim();
8721
8898
  if (/tmux/.test(comm)) return true;
8722
- const ppid = execSync9(`ps -p ${pid} -o ppid= 2>/dev/null`, {
8899
+ const ppid = execSync10(`ps -p ${pid} -o ppid= 2>/dev/null`, {
8723
8900
  encoding: "utf8",
8724
8901
  timeout: 1e3
8725
8902
  }).trim();
@@ -8732,7 +8909,7 @@ function inTmux() {
8732
8909
  }
8733
8910
  function listTmuxSessions() {
8734
8911
  try {
8735
- const out = execSync9("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
8912
+ const out = execSync10("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
8736
8913
  encoding: "utf8",
8737
8914
  timeout: 3e3
8738
8915
  });
@@ -8743,7 +8920,7 @@ function listTmuxSessions() {
8743
8920
  }
8744
8921
  function capturePaneLines(windowName, lines = 10) {
8745
8922
  try {
8746
- const out = execSync9(
8923
+ const out = execSync10(
8747
8924
  `tmux capture-pane -t ${JSON.stringify(windowName)} -p 2>/dev/null | tail -${lines}`,
8748
8925
  { encoding: "utf8", timeout: 3e3 }
8749
8926
  );
@@ -8754,7 +8931,7 @@ function capturePaneLines(windowName, lines = 10) {
8754
8931
  }
8755
8932
  function getPaneCwd(windowName) {
8756
8933
  try {
8757
- const out = execSync9(
8934
+ const out = execSync10(
8758
8935
  `tmux display-message -t ${JSON.stringify(windowName)} -p '#{pane_current_path}' 2>/dev/null`,
8759
8936
  { encoding: "utf8", timeout: 3e3 }
8760
8937
  );
@@ -8765,7 +8942,7 @@ function getPaneCwd(windowName) {
8765
8942
  }
8766
8943
  function projectFromPath(dir) {
8767
8944
  try {
8768
- const root = execSync9("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
8945
+ const root = execSync10("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
8769
8946
  encoding: "utf8",
8770
8947
  timeout: 3e3
8771
8948
  }).trim();
@@ -8834,7 +9011,7 @@ function getEmployeeStatuses(employeeNames) {
8834
9011
  }
8835
9012
  let paneAlive = true;
8836
9013
  try {
8837
- const paneStatus = execSync9(
9014
+ const paneStatus = execSync10(
8838
9015
  `tmux list-panes -t ${JSON.stringify(sessionName)} -F '#{pane_dead}' 2>/dev/null`,
8839
9016
  { encoding: "utf8", timeout: 3e3 }
8840
9017
  ).trim();
@@ -9080,7 +9257,7 @@ __export(daemon_orchestration_exports, {
9080
9257
  shouldKillSession: () => shouldKillSession,
9081
9258
  shouldNudgeEmployee: () => shouldNudgeEmployee
9082
9259
  });
9083
- import { execSync as execSync10 } from "child_process";
9260
+ import { execSync as execSync11 } from "child_process";
9084
9261
  import { existsSync as existsSync21, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
9085
9262
  import { homedir } from "os";
9086
9263
  import { join } from "path";
@@ -9398,7 +9575,7 @@ function createSessionTTLRealDeps(getClient2) {
9398
9575
  },
9399
9576
  getSessionCreatedEpoch: (sessionName) => {
9400
9577
  try {
9401
- const out = execSync10(
9578
+ const out = execSync11(
9402
9579
  `tmux display-message -t ${JSON.stringify(sessionName)} -p '#{session_created}' 2>/dev/null`,
9403
9580
  { encoding: "utf8", timeout: 3e3 }
9404
9581
  ).trim();
@@ -9625,7 +9802,7 @@ function reapOrphanedMcpProcesses(deps) {
9625
9802
  function createOrphanReaperRealDeps() {
9626
9803
  return {
9627
9804
  listProcesses: () => {
9628
- const output = execSync10("ps -eo pid,ppid,args", {
9805
+ const output = execSync11("ps -eo pid,ppid,args", {
9629
9806
  encoding: "utf8",
9630
9807
  timeout: 5e3
9631
9808
  });
@@ -10079,6 +10256,109 @@ var init_consolidation = __esm({
10079
10256
  }
10080
10257
  });
10081
10258
 
10259
+ // src/lib/db-backup.ts
10260
+ var db_backup_exports = {};
10261
+ __export(db_backup_exports, {
10262
+ createBackup: () => createBackup,
10263
+ findActiveDb: () => findActiveDb,
10264
+ getBackupDir: () => getBackupDir,
10265
+ getLatestBackup: () => getLatestBackup,
10266
+ hasBackupToday: () => hasBackupToday,
10267
+ listBackups: () => listBackups,
10268
+ rotateBackups: () => rotateBackups
10269
+ });
10270
+ import { copyFileSync, existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync5, unlinkSync as unlinkSync8, statSync as statSync3 } from "fs";
10271
+ import path24 from "path";
10272
+ function findActiveDb() {
10273
+ for (const name of DB_NAMES) {
10274
+ const p = path24.join(EXE_AI_DIR, name);
10275
+ if (existsSync22(p)) return p;
10276
+ }
10277
+ return null;
10278
+ }
10279
+ function createBackup(reason = "manual") {
10280
+ const dbPath = findActiveDb();
10281
+ if (!dbPath) return null;
10282
+ mkdirSync8(BACKUP_DIR, { recursive: true });
10283
+ const dbName = path24.basename(dbPath, ".db");
10284
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
10285
+ const backupName = `${dbName}-${reason}-${timestamp}.db`;
10286
+ const backupPath = path24.join(BACKUP_DIR, backupName);
10287
+ copyFileSync(dbPath, backupPath);
10288
+ const walPath = dbPath + "-wal";
10289
+ if (existsSync22(walPath)) {
10290
+ try {
10291
+ copyFileSync(walPath, backupPath + "-wal");
10292
+ } catch {
10293
+ }
10294
+ }
10295
+ const shmPath = dbPath + "-shm";
10296
+ if (existsSync22(shmPath)) {
10297
+ try {
10298
+ copyFileSync(shmPath, backupPath + "-shm");
10299
+ } catch {
10300
+ }
10301
+ }
10302
+ return backupPath;
10303
+ }
10304
+ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
10305
+ if (!existsSync22(BACKUP_DIR)) return 0;
10306
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
10307
+ let deleted = 0;
10308
+ try {
10309
+ const files = readdirSync5(BACKUP_DIR);
10310
+ for (const file of files) {
10311
+ if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
10312
+ const filePath = path24.join(BACKUP_DIR, file);
10313
+ try {
10314
+ const stat = statSync3(filePath);
10315
+ if (stat.mtimeMs < cutoff) {
10316
+ unlinkSync8(filePath);
10317
+ deleted++;
10318
+ }
10319
+ } catch {
10320
+ }
10321
+ }
10322
+ } catch {
10323
+ }
10324
+ return deleted;
10325
+ }
10326
+ function listBackups() {
10327
+ if (!existsSync22(BACKUP_DIR)) return [];
10328
+ try {
10329
+ const files = readdirSync5(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
10330
+ return files.map((name) => {
10331
+ const p = path24.join(BACKUP_DIR, name);
10332
+ const stat = statSync3(p);
10333
+ return { path: p, name, size: stat.size, date: stat.mtime };
10334
+ }).sort((a, b) => b.date.getTime() - a.date.getTime());
10335
+ } catch {
10336
+ return [];
10337
+ }
10338
+ }
10339
+ function hasBackupToday(reason) {
10340
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
10341
+ const backups = listBackups();
10342
+ return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
10343
+ }
10344
+ function getLatestBackup() {
10345
+ const backups = listBackups();
10346
+ return backups.length > 0 ? backups[0].path : null;
10347
+ }
10348
+ function getBackupDir() {
10349
+ return BACKUP_DIR;
10350
+ }
10351
+ var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
10352
+ var init_db_backup = __esm({
10353
+ "src/lib/db-backup.ts"() {
10354
+ "use strict";
10355
+ init_config();
10356
+ BACKUP_DIR = path24.join(EXE_AI_DIR, "backups");
10357
+ DEFAULT_KEEP_DAYS = 3;
10358
+ DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
10359
+ }
10360
+ });
10361
+
10082
10362
  // src/lib/crypto.ts
10083
10363
  import crypto8 from "crypto";
10084
10364
  function initSyncCrypto(masterKey) {
@@ -10170,8 +10450,8 @@ __export(crdt_sync_exports, {
10170
10450
  rebuildFromDb: () => rebuildFromDb
10171
10451
  });
10172
10452
  import * as Y from "yjs";
10173
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, existsSync as existsSync22, mkdirSync as mkdirSync8, unlinkSync as unlinkSync8 } from "fs";
10174
- import path24 from "path";
10453
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, existsSync as existsSync23, mkdirSync as mkdirSync9, unlinkSync as unlinkSync9 } from "fs";
10454
+ import path25 from "path";
10175
10455
  import { homedir as homedir2 } from "os";
10176
10456
  function getStatePath() {
10177
10457
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -10183,14 +10463,14 @@ function initCrdtDoc() {
10183
10463
  if (doc) return doc;
10184
10464
  doc = new Y.Doc();
10185
10465
  const sp = getStatePath();
10186
- if (existsSync22(sp)) {
10466
+ if (existsSync23(sp)) {
10187
10467
  try {
10188
10468
  const state = readFileSync17(sp);
10189
10469
  Y.applyUpdate(doc, new Uint8Array(state));
10190
10470
  } catch {
10191
10471
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
10192
10472
  try {
10193
- unlinkSync8(sp);
10473
+ unlinkSync9(sp);
10194
10474
  } catch {
10195
10475
  }
10196
10476
  rebuildFromDb().catch((err) => {
@@ -10327,8 +10607,8 @@ function persistState() {
10327
10607
  if (!doc) return;
10328
10608
  try {
10329
10609
  const sp = getStatePath();
10330
- const dir = path24.dirname(sp);
10331
- if (!existsSync22(dir)) mkdirSync8(dir, { recursive: true });
10610
+ const dir = path25.dirname(sp);
10611
+ if (!existsSync23(dir)) mkdirSync9(dir, { recursive: true });
10332
10612
  const state = Y.encodeStateAsUpdate(doc);
10333
10613
  writeFileSync11(sp, Buffer.from(state));
10334
10614
  } catch {
@@ -10371,7 +10651,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
10371
10651
  var init_crdt_sync = __esm({
10372
10652
  "src/lib/crdt-sync.ts"() {
10373
10653
  "use strict";
10374
- DEFAULT_STATE_PATH = path24.join(homedir2(), ".exe-os", "crdt-state.bin");
10654
+ DEFAULT_STATE_PATH = path25.join(homedir2(), ".exe-os", "crdt-state.bin");
10375
10655
  _statePathOverride = null;
10376
10656
  doc = null;
10377
10657
  }
@@ -10406,16 +10686,16 @@ __export(cloud_sync_exports, {
10406
10686
  pushToPostgres: () => pushToPostgres,
10407
10687
  recordRosterDeletion: () => recordRosterDeletion
10408
10688
  });
10409
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync23, readdirSync as readdirSync5, mkdirSync as mkdirSync9, appendFileSync as appendFileSync3, unlinkSync as unlinkSync9, openSync as openSync2, closeSync as closeSync2 } from "fs";
10689
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync24, readdirSync as readdirSync6, mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
10410
10690
  import crypto9 from "crypto";
10411
- import path25 from "path";
10691
+ import path26 from "path";
10412
10692
  import { homedir as homedir3 } from "os";
10413
10693
  function sqlSafe(v) {
10414
10694
  return v === void 0 ? null : v;
10415
10695
  }
10416
10696
  function logError(msg) {
10417
10697
  try {
10418
- const logPath = path25.join(homedir3(), ".exe-os", "workers.log");
10698
+ const logPath = path26.join(homedir3(), ".exe-os", "workers.log");
10419
10699
  appendFileSync3(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
10420
10700
  `);
10421
10701
  } catch {
@@ -10424,10 +10704,10 @@ function logError(msg) {
10424
10704
  function loadPgClient() {
10425
10705
  if (_pgFailed) return null;
10426
10706
  const postgresUrl = process.env.DATABASE_URL;
10427
- const configPath = path25.join(EXE_AI_DIR, "config.json");
10707
+ const configPath = path26.join(EXE_AI_DIR, "config.json");
10428
10708
  let cloudPostgresUrl;
10429
10709
  try {
10430
- if (existsSync23(configPath)) {
10710
+ if (existsSync24(configPath)) {
10431
10711
  const cfg = JSON.parse(readFileSync18(configPath, "utf8"));
10432
10712
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
10433
10713
  if (cfg.cloud?.syncToPostgres === false) {
@@ -10446,8 +10726,8 @@ function loadPgClient() {
10446
10726
  _pgPromise = (async () => {
10447
10727
  const { createRequire: createRequire4 } = await import("module");
10448
10728
  const { pathToFileURL: pathToFileURL4 } = await import("url");
10449
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path25.join(homedir3(), "exe-db");
10450
- const req = createRequire4(path25.join(exeDbRoot, "package.json"));
10729
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path26.join(homedir3(), "exe-db");
10730
+ const req = createRequire4(path26.join(exeDbRoot, "package.json"));
10451
10731
  const entry = req.resolve("@prisma/client");
10452
10732
  const mod = await import(pathToFileURL4(entry).href);
10453
10733
  const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
@@ -10500,7 +10780,7 @@ async function withRosterLock(fn) {
10500
10780
  if (Date.now() - ts2 < LOCK_STALE_MS) {
10501
10781
  throw new Error("Roster merge already in progress \u2014 another sync is running");
10502
10782
  }
10503
- unlinkSync9(ROSTER_LOCK_PATH);
10783
+ unlinkSync10(ROSTER_LOCK_PATH);
10504
10784
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
10505
10785
  closeSync2(fd);
10506
10786
  writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
@@ -10516,7 +10796,7 @@ async function withRosterLock(fn) {
10516
10796
  return await fn();
10517
10797
  } finally {
10518
10798
  try {
10519
- unlinkSync9(ROSTER_LOCK_PATH);
10799
+ unlinkSync10(ROSTER_LOCK_PATH);
10520
10800
  } catch {
10521
10801
  }
10522
10802
  }
@@ -10887,13 +11167,42 @@ async function cloudSync(config) {
10887
11167
  try {
10888
11168
  const employees = await loadEmployees();
10889
11169
  rosterResult.employees = employees.length;
10890
- const idDir = path25.join(EXE_AI_DIR, "identity");
10891
- if (existsSync23(idDir)) {
10892
- rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
11170
+ const idDir = path26.join(EXE_AI_DIR, "identity");
11171
+ if (existsSync24(idDir)) {
11172
+ rosterResult.identities = readdirSync6(idDir).filter((f) => f.endsWith(".md")).length;
10893
11173
  }
10894
11174
  } catch {
10895
11175
  }
10896
11176
  const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
11177
+ try {
11178
+ const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
11179
+ const { statSync: statFile } = await import("fs");
11180
+ const latestBackup = getLatestBackup2();
11181
+ if (latestBackup) {
11182
+ const backupSize = statFile(latestBackup).size;
11183
+ const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
11184
+ if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
11185
+ const backupData = readFileSync18(latestBackup);
11186
+ const deviceId = loadDeviceId() ?? "unknown";
11187
+ const encrypted = encryptSyncBlob(backupData);
11188
+ const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
11189
+ method: "POST",
11190
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
11191
+ body: JSON.stringify({
11192
+ device_id: deviceId,
11193
+ filename: path26.basename(latestBackup),
11194
+ blob: encrypted,
11195
+ size: backupData.length
11196
+ })
11197
+ });
11198
+ if (backupRes && !backupRes.ok) {
11199
+ logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
11200
+ }
11201
+ }
11202
+ }
11203
+ } catch (err) {
11204
+ logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
11205
+ }
10897
11206
  return {
10898
11207
  pushed,
10899
11208
  pulled,
@@ -10909,7 +11218,7 @@ async function cloudSync(config) {
10909
11218
  function recordRosterDeletion(name) {
10910
11219
  let deletions = [];
10911
11220
  try {
10912
- if (existsSync23(ROSTER_DELETIONS_PATH)) {
11221
+ if (existsSync24(ROSTER_DELETIONS_PATH)) {
10913
11222
  deletions = JSON.parse(readFileSync18(ROSTER_DELETIONS_PATH, "utf-8"));
10914
11223
  }
10915
11224
  } catch {
@@ -10919,7 +11228,7 @@ function recordRosterDeletion(name) {
10919
11228
  }
10920
11229
  function consumeRosterDeletions() {
10921
11230
  try {
10922
- if (!existsSync23(ROSTER_DELETIONS_PATH)) return [];
11231
+ if (!existsSync24(ROSTER_DELETIONS_PATH)) return [];
10923
11232
  const deletions = JSON.parse(readFileSync18(ROSTER_DELETIONS_PATH, "utf-8"));
10924
11233
  writeFileSync12(ROSTER_DELETIONS_PATH, "[]");
10925
11234
  return deletions;
@@ -10928,35 +11237,35 @@ function consumeRosterDeletions() {
10928
11237
  }
10929
11238
  }
10930
11239
  function buildRosterBlob(paths) {
10931
- const rosterPath = paths?.rosterPath ?? path25.join(EXE_AI_DIR, "exe-employees.json");
10932
- const identityDir = paths?.identityDir ?? path25.join(EXE_AI_DIR, "identity");
10933
- const configPath = paths?.configPath ?? path25.join(EXE_AI_DIR, "config.json");
11240
+ const rosterPath = paths?.rosterPath ?? path26.join(EXE_AI_DIR, "exe-employees.json");
11241
+ const identityDir = paths?.identityDir ?? path26.join(EXE_AI_DIR, "identity");
11242
+ const configPath = paths?.configPath ?? path26.join(EXE_AI_DIR, "config.json");
10934
11243
  let roster = [];
10935
- if (existsSync23(rosterPath)) {
11244
+ if (existsSync24(rosterPath)) {
10936
11245
  try {
10937
11246
  roster = JSON.parse(readFileSync18(rosterPath, "utf-8"));
10938
11247
  } catch {
10939
11248
  }
10940
11249
  }
10941
11250
  const identities = {};
10942
- if (existsSync23(identityDir)) {
10943
- for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
11251
+ if (existsSync24(identityDir)) {
11252
+ for (const file of readdirSync6(identityDir).filter((f) => f.endsWith(".md"))) {
10944
11253
  try {
10945
- identities[file] = readFileSync18(path25.join(identityDir, file), "utf-8");
11254
+ identities[file] = readFileSync18(path26.join(identityDir, file), "utf-8");
10946
11255
  } catch {
10947
11256
  }
10948
11257
  }
10949
11258
  }
10950
11259
  let config;
10951
- if (existsSync23(configPath)) {
11260
+ if (existsSync24(configPath)) {
10952
11261
  try {
10953
11262
  config = JSON.parse(readFileSync18(configPath, "utf-8"));
10954
11263
  } catch {
10955
11264
  }
10956
11265
  }
10957
11266
  let agentConfig;
10958
- const agentConfigPath = path25.join(EXE_AI_DIR, "agent-config.json");
10959
- if (existsSync23(agentConfigPath)) {
11267
+ const agentConfigPath = path26.join(EXE_AI_DIR, "agent-config.json");
11268
+ if (existsSync24(agentConfigPath)) {
10960
11269
  try {
10961
11270
  agentConfig = JSON.parse(readFileSync18(agentConfigPath, "utf-8"));
10962
11271
  } catch {
@@ -11034,16 +11343,16 @@ async function cloudPullRoster(config) {
11034
11343
  }
11035
11344
  }
11036
11345
  function mergeConfig(remoteConfig, configPath) {
11037
- const cfgPath = configPath ?? path25.join(EXE_AI_DIR, "config.json");
11346
+ const cfgPath = configPath ?? path26.join(EXE_AI_DIR, "config.json");
11038
11347
  let local = {};
11039
- if (existsSync23(cfgPath)) {
11348
+ if (existsSync24(cfgPath)) {
11040
11349
  try {
11041
11350
  local = JSON.parse(readFileSync18(cfgPath, "utf-8"));
11042
11351
  } catch {
11043
11352
  }
11044
11353
  }
11045
11354
  const merged = { ...remoteConfig, ...local };
11046
- const dir = path25.dirname(cfgPath);
11355
+ const dir = path26.dirname(cfgPath);
11047
11356
  ensurePrivateDirSync(dir);
11048
11357
  writeFileSync12(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
11049
11358
  enforcePrivateFileSync(cfgPath);
@@ -11051,7 +11360,7 @@ function mergeConfig(remoteConfig, configPath) {
11051
11360
  async function mergeRosterFromRemote(remote, paths) {
11052
11361
  return withRosterLock(async () => {
11053
11362
  const rosterPath = paths?.rosterPath ?? void 0;
11054
- const identityDir = paths?.identityDir ?? path25.join(EXE_AI_DIR, "identity");
11363
+ const identityDir = paths?.identityDir ?? path26.join(EXE_AI_DIR, "identity");
11055
11364
  const localEmployees = await loadEmployees(rosterPath);
11056
11365
  const localNames = new Set(localEmployees.map((e) => e.name));
11057
11366
  let added = 0;
@@ -11072,11 +11381,11 @@ async function mergeRosterFromRemote(remote, paths) {
11072
11381
  ) ?? lookupKey;
11073
11382
  const remoteIdentity = remote.identities[matchedKey];
11074
11383
  if (remoteIdentity) {
11075
- if (!existsSync23(identityDir)) mkdirSync9(identityDir, { recursive: true });
11076
- const idPath = path25.join(identityDir, `${remoteEmp.name}.md`);
11384
+ if (!existsSync24(identityDir)) mkdirSync10(identityDir, { recursive: true });
11385
+ const idPath = path26.join(identityDir, `${remoteEmp.name}.md`);
11077
11386
  let localIdentity = null;
11078
11387
  try {
11079
- localIdentity = existsSync23(idPath) ? readFileSync18(idPath, "utf-8") : null;
11388
+ localIdentity = existsSync24(idPath) ? readFileSync18(idPath, "utf-8") : null;
11080
11389
  } catch {
11081
11390
  }
11082
11391
  if (localIdentity !== remoteIdentity) {
@@ -11106,16 +11415,16 @@ async function mergeRosterFromRemote(remote, paths) {
11106
11415
  }
11107
11416
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
11108
11417
  try {
11109
- const agentConfigPath = path25.join(EXE_AI_DIR, "agent-config.json");
11418
+ const agentConfigPath = path26.join(EXE_AI_DIR, "agent-config.json");
11110
11419
  let local = {};
11111
- if (existsSync23(agentConfigPath)) {
11420
+ if (existsSync24(agentConfigPath)) {
11112
11421
  try {
11113
11422
  local = JSON.parse(readFileSync18(agentConfigPath, "utf-8"));
11114
11423
  } catch {
11115
11424
  }
11116
11425
  }
11117
11426
  const merged = { ...remote.agentConfig, ...local };
11118
- ensurePrivateDirSync(path25.dirname(agentConfigPath));
11427
+ ensurePrivateDirSync(path26.dirname(agentConfigPath));
11119
11428
  writeFileSync12(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
11120
11429
  enforcePrivateFileSync(agentConfigPath);
11121
11430
  } catch {
@@ -11556,11 +11865,11 @@ var init_cloud_sync = __esm({
11556
11865
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
11557
11866
  FETCH_TIMEOUT_MS = 3e4;
11558
11867
  PUSH_BATCH_SIZE = 5e3;
11559
- ROSTER_LOCK_PATH = path25.join(EXE_AI_DIR, "roster-merge.lock");
11868
+ ROSTER_LOCK_PATH = path26.join(EXE_AI_DIR, "roster-merge.lock");
11560
11869
  LOCK_STALE_MS = 3e4;
11561
11870
  _pgPromise = null;
11562
11871
  _pgFailed = false;
11563
- ROSTER_DELETIONS_PATH = path25.join(EXE_AI_DIR, "roster-deletions.json");
11872
+ ROSTER_DELETIONS_PATH = path26.join(EXE_AI_DIR, "roster-deletions.json");
11564
11873
  }
11565
11874
  });
11566
11875
 
@@ -12259,7 +12568,7 @@ __export(token_spend_exports, {
12259
12568
  import { readdir } from "fs/promises";
12260
12569
  import { createReadStream } from "fs";
12261
12570
  import { createInterface } from "readline";
12262
- import path26 from "path";
12571
+ import path27 from "path";
12263
12572
  import os14 from "os";
12264
12573
  function getPricing(model) {
12265
12574
  if (MODEL_PRICING[model]) return MODEL_PRICING[model];
@@ -12287,18 +12596,18 @@ async function getAgentSpend(period = "7d") {
12287
12596
  for (const row of dbResult.rows) {
12288
12597
  sessionAgent.set(row.session_uuid, row.agent_id);
12289
12598
  }
12290
- const claudeDir = path26.join(os14.homedir(), ".claude", "projects");
12599
+ const claudeDir = path27.join(os14.homedir(), ".claude", "projects");
12291
12600
  let projectDirs = [];
12292
12601
  try {
12293
12602
  const entries = await readdir(claudeDir);
12294
- projectDirs = entries.map((e) => path26.join(claudeDir, e));
12603
+ projectDirs = entries.map((e) => path27.join(claudeDir, e));
12295
12604
  } catch {
12296
12605
  return [];
12297
12606
  }
12298
12607
  const agentTotals = /* @__PURE__ */ new Map();
12299
12608
  for (const [sessionUuid, agentId] of sessionAgent) {
12300
12609
  for (const dir of projectDirs) {
12301
- const jsonlPath = path26.join(dir, `${sessionUuid}.jsonl`);
12610
+ const jsonlPath = path27.join(dir, `${sessionUuid}.jsonl`);
12302
12611
  try {
12303
12612
  const usage = await extractSessionUsage(jsonlPath);
12304
12613
  if (usage.input === 0 && usage.output === 0) continue;
@@ -12431,7 +12740,7 @@ __export(task_enforcement_exports, {
12431
12740
  sendNudge: () => sendNudge
12432
12741
  });
12433
12742
  import { writeFileSync as writeFileSync13 } from "fs";
12434
- import path27 from "path";
12743
+ import path28 from "path";
12435
12744
  function writeAuditEntry(entry) {
12436
12745
  try {
12437
12746
  const line = JSON.stringify(entry) + "\n";
@@ -12606,7 +12915,7 @@ var init_task_enforcement = __esm({
12606
12915
  "What do you need?"
12607
12916
  ];
12608
12917
  MANAGER_ROLES = ["COO", "CTO"];
12609
- AUDIT_LOG_PATH = path27.join(
12918
+ AUDIT_LOG_PATH = path28.join(
12610
12919
  process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
12611
12920
  ".exe-os",
12612
12921
  "enforcement-audit.jsonl"
@@ -12622,17 +12931,17 @@ __export(update_check_exports, {
12622
12931
  getLocalVersion: () => getLocalVersion,
12623
12932
  getRemoteVersion: () => getRemoteVersion
12624
12933
  });
12625
- import { execSync as execSync11 } from "child_process";
12934
+ import { execSync as execSync12 } from "child_process";
12626
12935
  import { readFileSync as readFileSync19 } from "fs";
12627
- import path28 from "path";
12936
+ import path29 from "path";
12628
12937
  function getLocalVersion(packageRoot) {
12629
- const pkgPath = path28.join(packageRoot, "package.json");
12938
+ const pkgPath = path29.join(packageRoot, "package.json");
12630
12939
  const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
12631
12940
  return pkg.version;
12632
12941
  }
12633
12942
  function getRemoteVersion() {
12634
12943
  try {
12635
- const output = execSync11("npm view @askexenow/exe-os version", {
12944
+ const output = execSync12("npm view @askexenow/exe-os version", {
12636
12945
  encoding: "utf-8",
12637
12946
  timeout: 15e3,
12638
12947
  stdio: ["pipe", "pipe", "pipe"]
@@ -12699,10 +13008,10 @@ __export(device_registry_exports, {
12699
13008
  });
12700
13009
  import crypto12 from "crypto";
12701
13010
  import os15 from "os";
12702
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, mkdirSync as mkdirSync10, existsSync as existsSync24 } from "fs";
12703
- import path29 from "path";
13011
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, existsSync as existsSync25 } from "fs";
13012
+ import path30 from "path";
12704
13013
  function getDeviceInfo() {
12705
- if (existsSync24(DEVICE_JSON_PATH)) {
13014
+ if (existsSync25(DEVICE_JSON_PATH)) {
12706
13015
  try {
12707
13016
  const raw = readFileSync20(DEVICE_JSON_PATH, "utf8");
12708
13017
  const data = JSON.parse(raw);
@@ -12718,7 +13027,7 @@ function getDeviceInfo() {
12718
13027
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
12719
13028
  hostname
12720
13029
  };
12721
- mkdirSync10(path29.dirname(DEVICE_JSON_PATH), { recursive: true });
13030
+ mkdirSync11(path30.dirname(DEVICE_JSON_PATH), { recursive: true });
12722
13031
  writeFileSync14(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
12723
13032
  return info;
12724
13033
  }
@@ -12759,7 +13068,7 @@ var init_device_registry = __esm({
12759
13068
  "src/lib/device-registry.ts"() {
12760
13069
  "use strict";
12761
13070
  init_config();
12762
- DEVICE_JSON_PATH = path29.join(EXE_AI_DIR, "device.json");
13071
+ DEVICE_JSON_PATH = path30.join(EXE_AI_DIR, "device.json");
12763
13072
  }
12764
13073
  });
12765
13074
 
@@ -13246,8 +13555,8 @@ init_daemon_protocol();
13246
13555
  init_daemon_auth();
13247
13556
  import os16 from "os";
13248
13557
  import net2 from "net";
13249
- import { writeFileSync as writeFileSync15, unlinkSync as unlinkSync10, mkdirSync as mkdirSync11, existsSync as existsSync25, readFileSync as readFileSync21, chmodSync as chmodSync2 } from "fs";
13250
- import path30 from "path";
13558
+ import { writeFileSync as writeFileSync15, unlinkSync as unlinkSync11, mkdirSync as mkdirSync12, existsSync as existsSync26, readFileSync as readFileSync21, chmodSync as chmodSync2 } from "fs";
13559
+ import path31 from "path";
13251
13560
  import { getLlama } from "node-llama-cpp";
13252
13561
 
13253
13562
  // src/lib/orchestration-metrics.ts
@@ -13319,8 +13628,8 @@ function initMetrics() {
13319
13628
  }
13320
13629
 
13321
13630
  // src/lib/exe-daemon.ts
13322
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path30.join(EXE_AI_DIR, "exed.sock");
13323
- var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path30.join(EXE_AI_DIR, "exed.pid");
13631
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path31.join(EXE_AI_DIR, "exed.sock");
13632
+ var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path31.join(EXE_AI_DIR, "exed.pid");
13324
13633
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
13325
13634
  var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
13326
13635
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
@@ -13348,8 +13657,8 @@ function enqueue(queue, entry) {
13348
13657
  queue.push(entry);
13349
13658
  }
13350
13659
  async function loadModel() {
13351
- const modelPath = path30.join(MODELS_DIR, MODEL_FILE);
13352
- if (!existsSync25(modelPath)) {
13660
+ const modelPath = path31.join(MODELS_DIR, MODEL_FILE);
13661
+ if (!existsSync26(modelPath)) {
13353
13662
  process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
13354
13663
  `);
13355
13664
  process.exit(1);
@@ -13446,11 +13755,11 @@ async function shutdown() {
13446
13755
  }
13447
13756
  _llama = null;
13448
13757
  try {
13449
- unlinkSync10(SOCKET_PATH2);
13758
+ unlinkSync11(SOCKET_PATH2);
13450
13759
  } catch {
13451
13760
  }
13452
13761
  try {
13453
- unlinkSync10(PID_PATH2);
13762
+ unlinkSync11(PID_PATH2);
13454
13763
  } catch {
13455
13764
  }
13456
13765
  process.stderr.write("[exed] Shutdown complete.\n");
@@ -13704,14 +14013,14 @@ function startMemoryQueueDrain() {
13704
14013
  `);
13705
14014
  }
13706
14015
  function startServer() {
13707
- mkdirSync11(path30.dirname(SOCKET_PATH2), { recursive: true });
14016
+ mkdirSync12(path31.dirname(SOCKET_PATH2), { recursive: true });
13708
14017
  try {
13709
- chmodSync2(path30.dirname(SOCKET_PATH2), 448);
14018
+ chmodSync2(path31.dirname(SOCKET_PATH2), 448);
13710
14019
  } catch {
13711
14020
  }
13712
14021
  _daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
13713
14022
  for (const oldFile of ["embed.sock", "embed.pid"]) {
13714
- const oldPath = path30.join(path30.dirname(SOCKET_PATH2), oldFile);
14023
+ const oldPath = path31.join(path31.dirname(SOCKET_PATH2), oldFile);
13715
14024
  try {
13716
14025
  if (oldFile.endsWith(".pid")) {
13717
14026
  const pid = parseInt(readFileSync21(oldPath, "utf8").trim(), 10);
@@ -13720,12 +14029,12 @@ function startServer() {
13720
14029
  } catch {
13721
14030
  }
13722
14031
  }
13723
- unlinkSync10(oldPath);
14032
+ unlinkSync11(oldPath);
13724
14033
  } catch {
13725
14034
  }
13726
14035
  }
13727
14036
  try {
13728
- unlinkSync10(SOCKET_PATH2);
14037
+ unlinkSync11(SOCKET_PATH2);
13729
14038
  } catch {
13730
14039
  }
13731
14040
  const server = net2.createServer((socket) => {
@@ -13972,6 +14281,11 @@ function startConsolidation() {
13972
14281
  embedFn = embed2;
13973
14282
  } catch {
13974
14283
  }
14284
+ try {
14285
+ const { createBackup: createBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
14286
+ createBackup2("pre-consolidation");
14287
+ } catch {
14288
+ }
13975
14289
  process.stderr.write(`[exed] Starting consolidation (${count} unconsolidated memories)...
13976
14290
  `);
13977
14291
  const result = await runConsolidation2(client, {
@@ -14094,7 +14408,7 @@ function startGraphExtraction() {
14094
14408
  `);
14095
14409
  }
14096
14410
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
14097
- var AGENT_STATS_PATH = path30.join(EXE_AI_DIR, "agent-stats.json");
14411
+ var AGENT_STATS_PATH = path31.join(EXE_AI_DIR, "agent-stats.json");
14098
14412
  async function writeAgentStats() {
14099
14413
  fired("agent_stats");
14100
14414
  if (!await ensureStoreForPolling()) return;
@@ -14199,6 +14513,33 @@ function startConfidenceDecay() {
14199
14513
  process.stderr.write(`[exed] Confidence decay started (every 24h)
14200
14514
  `);
14201
14515
  }
14516
+ var DB_BACKUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
14517
+ function startDatabaseBackup() {
14518
+ const tick = () => {
14519
+ fired("db_backup");
14520
+ try {
14521
+ const { createBackup: createBackup2, rotateBackups: rotateBackups2, hasBackupToday: hasBackupToday2 } = (init_db_backup(), __toCommonJS(db_backup_exports));
14522
+ if (hasBackupToday2("daily")) return;
14523
+ const backupPath = createBackup2("daily");
14524
+ if (backupPath) {
14525
+ acted("db_backup");
14526
+ const deleted = rotateBackups2();
14527
+ process.stderr.write(
14528
+ `[exed] DB backup: ${backupPath.split("/").pop()}${deleted > 0 ? ` (rotated ${deleted} old)` : ""}
14529
+ `
14530
+ );
14531
+ }
14532
+ } catch (err) {
14533
+ process.stderr.write(`[exed] DB backup error: ${err instanceof Error ? err.message : String(err)}
14534
+ `);
14535
+ }
14536
+ };
14537
+ tick();
14538
+ const timer = setInterval(tick, DB_BACKUP_INTERVAL_MS);
14539
+ timer.unref();
14540
+ process.stderr.write(`[exed] Database backup started (daily, keep 3 days)
14541
+ `);
14542
+ }
14202
14543
  var QUEUE_DRAIN_INTERVAL_MS = 15e3;
14203
14544
  function startIntercomQueueDrain() {
14204
14545
  const tick = () => {
@@ -14209,12 +14550,12 @@ function startIntercomQueueDrain() {
14209
14550
  const hasInProgressTask = (session) => {
14210
14551
  try {
14211
14552
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
14212
- const path31 = __require("path");
14213
- const { existsSync: existsSync26 } = __require("fs");
14553
+ const path32 = __require("path");
14554
+ const { existsSync: existsSync27 } = __require("fs");
14214
14555
  const os17 = __require("os");
14215
14556
  const agent = ban(session.split("-")[0] ?? session);
14216
- const markerPath = path31.join(os17.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
14217
- return existsSync26(markerPath);
14557
+ const markerPath = path32.join(os17.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
14558
+ return existsSync27(markerPath);
14218
14559
  } catch {
14219
14560
  return false;
14220
14561
  }
@@ -14378,7 +14719,7 @@ process.on("SIGINT", () => void shutdown());
14378
14719
  process.on("SIGTERM", () => void shutdown());
14379
14720
  function checkExistingDaemon() {
14380
14721
  try {
14381
- if (!existsSync25(PID_PATH2)) return false;
14722
+ if (!existsSync26(PID_PATH2)) return false;
14382
14723
  const pid = parseInt(readFileSync21(PID_PATH2, "utf8").trim(), 10);
14383
14724
  if (!pid || isNaN(pid)) return false;
14384
14725
  process.kill(pid, 0);
@@ -14392,7 +14733,7 @@ function checkExistingDaemon() {
14392
14733
  return true;
14393
14734
  }
14394
14735
  try {
14395
- unlinkSync10(PID_PATH2);
14736
+ unlinkSync11(PID_PATH2);
14396
14737
  } catch {
14397
14738
  }
14398
14739
  return false;
@@ -14417,8 +14758,8 @@ function startAutoUpdateCheck() {
14417
14758
  );
14418
14759
  if (autoInstall) {
14419
14760
  process.stderr.write("[exed] Auto-installing update...\n");
14420
- const { execSync: execSync12 } = await import("child_process");
14421
- execSync12("npm install -g @askexenow/exe-os@latest", {
14761
+ const { execSync: execSync13 } = await import("child_process");
14762
+ execSync13("npm install -g @askexenow/exe-os@latest", {
14422
14763
  timeout: 12e4,
14423
14764
  stdio: ["pipe", "pipe", "pipe"]
14424
14765
  });
@@ -14472,6 +14813,7 @@ try {
14472
14813
  startMemoryQueueDrain();
14473
14814
  startIntercomQueueDrain();
14474
14815
  startConfidenceDecay();
14816
+ startDatabaseBackup();
14475
14817
  startAutoUpdateCheck();
14476
14818
  startRssWatchdog();
14477
14819
  startTaskEnforcementScanner();
@@ -14570,11 +14912,11 @@ try {
14570
14912
  process.stderr.write(`[exed] FATAL: ${err instanceof Error ? err.message : String(err)}
14571
14913
  `);
14572
14914
  try {
14573
- unlinkSync10(SOCKET_PATH2);
14915
+ unlinkSync11(SOCKET_PATH2);
14574
14916
  } catch {
14575
14917
  }
14576
14918
  try {
14577
- unlinkSync10(PID_PATH2);
14919
+ unlinkSync11(PID_PATH2);
14578
14920
  } catch {
14579
14921
  }
14580
14922
  process.exit(1);