@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
@@ -428,8 +428,8 @@ function findPackageRoot() {
428
428
  function getAvailableMemoryGB() {
429
429
  if (process.platform === "darwin") {
430
430
  try {
431
- const { execSync: execSync11 } = __require("child_process");
432
- const vmstat = execSync11("vm_stat", { encoding: "utf8" });
431
+ const { execSync: execSync12 } = __require("child_process");
432
+ const vmstat = execSync12("vm_stat", { encoding: "utf8" });
433
433
  const pageSize = 16384;
434
434
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
435
435
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -849,10 +849,10 @@ async function disposeEmbedder() {
849
849
  async function embedDirect(text) {
850
850
  const llamaCpp = await import("node-llama-cpp");
851
851
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
852
- const { existsSync: existsSync34 } = await import("fs");
853
- const path44 = await import("path");
854
- const modelPath = path44.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
855
- if (!existsSync34(modelPath)) {
852
+ const { existsSync: existsSync35 } = await import("fs");
853
+ const path45 = await import("path");
854
+ const modelPath = path45.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
855
+ if (!existsSync35(modelPath)) {
856
856
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
857
857
  }
858
858
  const llama = await llamaCpp.getLlama();
@@ -3220,6 +3220,7 @@ __export(keychain_exports, {
3220
3220
  });
3221
3221
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3222
3222
  import { existsSync as existsSync7 } from "fs";
3223
+ import { execSync as execSync2 } from "child_process";
3223
3224
  import path7 from "path";
3224
3225
  import os5 from "os";
3225
3226
  function getKeyDir() {
@@ -3228,6 +3229,83 @@ function getKeyDir() {
3228
3229
  function getKeyPath() {
3229
3230
  return path7.join(getKeyDir(), "master.key");
3230
3231
  }
3232
+ function macKeychainGet() {
3233
+ if (process.platform !== "darwin") return null;
3234
+ try {
3235
+ return execSync2(
3236
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3237
+ { encoding: "utf-8", timeout: 5e3 }
3238
+ ).trim();
3239
+ } catch {
3240
+ return null;
3241
+ }
3242
+ }
3243
+ function macKeychainSet(value) {
3244
+ if (process.platform !== "darwin") return false;
3245
+ try {
3246
+ try {
3247
+ execSync2(
3248
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3249
+ { timeout: 5e3 }
3250
+ );
3251
+ } catch {
3252
+ }
3253
+ execSync2(
3254
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3255
+ { timeout: 5e3 }
3256
+ );
3257
+ return true;
3258
+ } catch {
3259
+ return false;
3260
+ }
3261
+ }
3262
+ function macKeychainDelete() {
3263
+ if (process.platform !== "darwin") return false;
3264
+ try {
3265
+ execSync2(
3266
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3267
+ { timeout: 5e3 }
3268
+ );
3269
+ return true;
3270
+ } catch {
3271
+ return false;
3272
+ }
3273
+ }
3274
+ function linuxSecretGet() {
3275
+ if (process.platform !== "linux") return null;
3276
+ try {
3277
+ return execSync2(
3278
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3279
+ { encoding: "utf-8", timeout: 5e3 }
3280
+ ).trim();
3281
+ } catch {
3282
+ return null;
3283
+ }
3284
+ }
3285
+ function linuxSecretSet(value) {
3286
+ if (process.platform !== "linux") return false;
3287
+ try {
3288
+ execSync2(
3289
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3290
+ { timeout: 5e3 }
3291
+ );
3292
+ return true;
3293
+ } catch {
3294
+ return false;
3295
+ }
3296
+ }
3297
+ function linuxSecretDelete() {
3298
+ if (process.platform !== "linux") return false;
3299
+ try {
3300
+ execSync2(
3301
+ `secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3302
+ { timeout: 5e3 }
3303
+ );
3304
+ return true;
3305
+ } catch {
3306
+ return false;
3307
+ }
3308
+ }
3231
3309
  async function tryKeytar() {
3232
3310
  try {
3233
3311
  return await import("keytar");
@@ -3235,13 +3313,72 @@ async function tryKeytar() {
3235
3313
  return null;
3236
3314
  }
3237
3315
  }
3316
+ function deriveMachineKey() {
3317
+ try {
3318
+ const crypto18 = __require("crypto");
3319
+ const material = [
3320
+ os5.hostname(),
3321
+ os5.userInfo().username,
3322
+ os5.arch(),
3323
+ os5.platform(),
3324
+ // Machine ID on Linux (stable across reboots)
3325
+ process.platform === "linux" ? readMachineId() : ""
3326
+ ].join("|");
3327
+ return crypto18.createHash("sha256").update(material).digest();
3328
+ } catch {
3329
+ return null;
3330
+ }
3331
+ }
3332
+ function readMachineId() {
3333
+ try {
3334
+ const { readFileSync: readFileSync29 } = __require("fs");
3335
+ return readFileSync29("/etc/machine-id", "utf-8").trim();
3336
+ } catch {
3337
+ return "";
3338
+ }
3339
+ }
3340
+ function encryptWithMachineKey(plaintext, machineKey) {
3341
+ const crypto18 = __require("crypto");
3342
+ const iv = crypto18.randomBytes(12);
3343
+ const cipher = crypto18.createCipheriv("aes-256-gcm", machineKey, iv);
3344
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3345
+ encrypted += cipher.final("base64");
3346
+ const authTag = cipher.getAuthTag().toString("base64");
3347
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3348
+ }
3349
+ function decryptWithMachineKey(encrypted, machineKey) {
3350
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3351
+ try {
3352
+ const crypto18 = __require("crypto");
3353
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3354
+ if (parts.length !== 3) return null;
3355
+ const [ivB64, tagB64, cipherB64] = parts;
3356
+ const iv = Buffer.from(ivB64, "base64");
3357
+ const authTag = Buffer.from(tagB64, "base64");
3358
+ const decipher = crypto18.createDecipheriv("aes-256-gcm", machineKey, iv);
3359
+ decipher.setAuthTag(authTag);
3360
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3361
+ decrypted += decipher.final("utf-8");
3362
+ return decrypted;
3363
+ } catch {
3364
+ return null;
3365
+ }
3366
+ }
3238
3367
  async function getMasterKey() {
3368
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3369
+ if (nativeValue) {
3370
+ return Buffer.from(nativeValue, "base64");
3371
+ }
3239
3372
  const keytar = await tryKeytar();
3240
3373
  if (keytar) {
3241
3374
  try {
3242
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3243
- if (stored) {
3244
- return Buffer.from(stored, "base64");
3375
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3376
+ if (keytarValue) {
3377
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3378
+ if (migrated) {
3379
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3380
+ }
3381
+ return Buffer.from(keytarValue, "base64");
3245
3382
  }
3246
3383
  } catch {
3247
3384
  }
@@ -3255,8 +3392,31 @@ async function getMasterKey() {
3255
3392
  return null;
3256
3393
  }
3257
3394
  try {
3258
- const content = await readFile3(keyPath, "utf-8");
3259
- return Buffer.from(content.trim(), "base64");
3395
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3396
+ let b64Value;
3397
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3398
+ const machineKey = deriveMachineKey();
3399
+ if (!machineKey) {
3400
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3401
+ return null;
3402
+ }
3403
+ const decrypted = decryptWithMachineKey(content, machineKey);
3404
+ if (!decrypted) {
3405
+ process.stderr.write(
3406
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3407
+ );
3408
+ return null;
3409
+ }
3410
+ b64Value = decrypted;
3411
+ } else {
3412
+ b64Value = content;
3413
+ }
3414
+ const key = Buffer.from(b64Value, "base64");
3415
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3416
+ if (migrated) {
3417
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3418
+ }
3419
+ return key;
3260
3420
  } catch (err) {
3261
3421
  process.stderr.write(
3262
3422
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3267,6 +3427,9 @@ async function getMasterKey() {
3267
3427
  }
3268
3428
  async function setMasterKey(key) {
3269
3429
  const b64 = key.toString("base64");
3430
+ if (macKeychainSet(b64) || linuxSecretSet(b64)) {
3431
+ return;
3432
+ }
3270
3433
  const keytar = await tryKeytar();
3271
3434
  if (keytar) {
3272
3435
  try {
@@ -3278,10 +3441,23 @@ async function setMasterKey(key) {
3278
3441
  const dir = getKeyDir();
3279
3442
  await mkdir3(dir, { recursive: true });
3280
3443
  const keyPath = getKeyPath();
3281
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3282
- await chmod2(keyPath, 384);
3444
+ const machineKey = deriveMachineKey();
3445
+ if (machineKey) {
3446
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3447
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3448
+ await chmod2(keyPath, 384);
3449
+ process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
3450
+ } else {
3451
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3452
+ await chmod2(keyPath, 384);
3453
+ process.stderr.write(
3454
+ "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
3455
+ );
3456
+ }
3283
3457
  }
3284
3458
  async function deleteMasterKey() {
3459
+ macKeychainDelete();
3460
+ linuxSecretDelete();
3285
3461
  const keytar = await tryKeytar();
3286
3462
  if (keytar) {
3287
3463
  try {
@@ -3323,12 +3499,13 @@ async function importMnemonic(mnemonic) {
3323
3499
  const entropy = mnemonicToEntropy(trimmed);
3324
3500
  return Buffer.from(entropy, "hex");
3325
3501
  }
3326
- var SERVICE, ACCOUNT;
3502
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3327
3503
  var init_keychain = __esm({
3328
3504
  "src/lib/keychain.ts"() {
3329
3505
  "use strict";
3330
3506
  SERVICE = "exe-mem";
3331
3507
  ACCOUNT = "master-key";
3508
+ ENCRYPTED_PREFIX = "enc:";
3332
3509
  }
3333
3510
  });
3334
3511
 
@@ -4783,7 +4960,7 @@ __export(project_name_exports, {
4783
4960
  _resetCache: () => _resetCache,
4784
4961
  getProjectName: () => getProjectName
4785
4962
  });
4786
- import { execSync as execSync2 } from "child_process";
4963
+ import { execSync as execSync3 } from "child_process";
4787
4964
  import path10 from "path";
4788
4965
  function getProjectName(cwd) {
4789
4966
  const dir = cwd ?? process.cwd();
@@ -4791,7 +4968,7 @@ function getProjectName(cwd) {
4791
4968
  try {
4792
4969
  let repoRoot;
4793
4970
  try {
4794
- const gitCommonDir = execSync2("git rev-parse --path-format=absolute --git-common-dir", {
4971
+ const gitCommonDir = execSync3("git rev-parse --path-format=absolute --git-common-dir", {
4795
4972
  cwd: dir,
4796
4973
  encoding: "utf8",
4797
4974
  timeout: 2e3,
@@ -4799,7 +4976,7 @@ function getProjectName(cwd) {
4799
4976
  }).trim();
4800
4977
  repoRoot = path10.dirname(gitCommonDir);
4801
4978
  } catch {
4802
- repoRoot = execSync2("git rev-parse --show-toplevel", {
4979
+ repoRoot = execSync3("git rev-parse --show-toplevel", {
4803
4980
  cwd: dir,
4804
4981
  encoding: "utf8",
4805
4982
  timeout: 2e3,
@@ -4833,14 +5010,14 @@ var file_grep_exports = {};
4833
5010
  __export(file_grep_exports, {
4834
5011
  grepProjectFiles: () => grepProjectFiles
4835
5012
  });
4836
- import { execSync as execSync3 } from "child_process";
5013
+ import { execSync as execSync4 } from "child_process";
4837
5014
  import { readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync10 } from "fs";
4838
5015
  import path11 from "path";
4839
5016
  import crypto2 from "crypto";
4840
5017
  function hasRipgrep() {
4841
5018
  if (_hasRg === null) {
4842
5019
  try {
4843
- execSync3("rg --version", { stdio: "ignore", timeout: 2e3 });
5020
+ execSync4("rg --version", { stdio: "ignore", timeout: 2e3 });
4844
5021
  _hasRg = true;
4845
5022
  } catch {
4846
5023
  _hasRg = false;
@@ -4906,7 +5083,7 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
4906
5083
  const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
4907
5084
  const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
4908
5085
  const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
4909
- const output = execSync3(cmd, {
5086
+ const output = execSync4(cmd, {
4910
5087
  cwd: projectRoot,
4911
5088
  encoding: "utf8",
4912
5089
  timeout: 3e3,
@@ -4921,12 +5098,12 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
4921
5098
  const matchCount = parseInt(line.slice(colonIdx + 1));
4922
5099
  if (isNaN(matchCount) || matchCount === 0) continue;
4923
5100
  try {
4924
- const firstMatch = execSync3(
5101
+ const firstMatch = execSync4(
4925
5102
  `rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
4926
5103
  { cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
4927
5104
  ).trim();
4928
5105
  const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
4929
- const totalLines = execSync3(`wc -l < '${filePath}'`, {
5106
+ const totalLines = execSync4(`wc -l < '${filePath}'`, {
4930
5107
  cwd: projectRoot,
4931
5108
  encoding: "utf8",
4932
5109
  timeout: 1e3
@@ -5555,10 +5732,17 @@ async function applyEntityBoost(results, query, client) {
5555
5732
  if (ENTITY_BOOST_WEIGHT === 0 || results.length === 0) {
5556
5733
  return emptyResult;
5557
5734
  }
5558
- console.time("entity-boost");
5735
+ const debugStart = process.env.EXE_DEBUG_HOOKS ? performance.now() : 0;
5736
+ const debugEnd = () => {
5737
+ if (!process.env.EXE_DEBUG_HOOKS) return;
5738
+ process.stderr.write(
5739
+ `[entity-boost] ${(performance.now() - debugStart).toFixed(3)}ms
5740
+ `
5741
+ );
5742
+ };
5559
5743
  const entities = await matchEntities(query, client);
5560
5744
  if (entities.length === 0) {
5561
- console.timeEnd("entity-boost");
5745
+ debugEnd();
5562
5746
  return emptyResult;
5563
5747
  }
5564
5748
  const boostMap = /* @__PURE__ */ new Map();
@@ -5580,7 +5764,7 @@ async function applyEntityBoost(results, query, client) {
5580
5764
  await traverseAndScore(entities, client, boostMap, resultIds, graphContextMap);
5581
5765
  await applyHyperedgeBoost(entities, client, boostMap, resultIds);
5582
5766
  if (boostMap.size === 0) {
5583
- console.timeEnd("entity-boost");
5767
+ debugEnd();
5584
5768
  return emptyResult;
5585
5769
  }
5586
5770
  const scored = results.map((r, i) => ({
@@ -5591,7 +5775,7 @@ async function applyEntityBoost(results, query, client) {
5591
5775
  scored.sort(
5592
5776
  (a, b) => b.baseScore + b.entityBoost - (a.baseScore + a.entityBoost)
5593
5777
  );
5594
- console.timeEnd("entity-boost");
5778
+ debugEnd();
5595
5779
  return {
5596
5780
  results: scored.map((s) => s.record),
5597
5781
  graphContext: graphContextMap
@@ -5810,10 +5994,10 @@ async function hybridSearch(queryText, agentId, options) {
5810
5994
  };
5811
5995
  try {
5812
5996
  const fs = await import("fs");
5813
- const path44 = await import("path");
5997
+ const path45 = await import("path");
5814
5998
  const os19 = await import("os");
5815
- const logPath = path44.join(os19.homedir(), ".exe-os", "search-quality.jsonl");
5816
- fs.mkdirSync(path44.dirname(logPath), { recursive: true });
5999
+ const logPath = path45.join(os19.homedir(), ".exe-os", "search-quality.jsonl");
6000
+ fs.mkdirSync(path45.dirname(logPath), { recursive: true });
5817
6001
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
5818
6002
  } catch {
5819
6003
  }
@@ -6236,7 +6420,7 @@ var init_hybrid_search = __esm({
6236
6420
  });
6237
6421
 
6238
6422
  // src/lib/session-key.ts
6239
- import { execSync as execSync4 } from "child_process";
6423
+ import { execSync as execSync5 } from "child_process";
6240
6424
  function normalizeCommand(command) {
6241
6425
  const trimmed = command.trim().toLowerCase();
6242
6426
  const parts = trimmed.split(/[\\/]/);
@@ -6255,7 +6439,7 @@ function resolveRuntimeProcess() {
6255
6439
  let pid = process.ppid;
6256
6440
  for (let i = 0; i < 10; i++) {
6257
6441
  try {
6258
- const info = execSync4(`ps -p ${pid} -o ppid=,comm=`, {
6442
+ const info = execSync5(`ps -p ${pid} -o ppid=,comm=`, {
6259
6443
  encoding: "utf8",
6260
6444
  timeout: 2e3
6261
6445
  }).trim();
@@ -6314,7 +6498,7 @@ __export(active_agent_exports, {
6314
6498
  writeActiveAgent: () => writeActiveAgent
6315
6499
  });
6316
6500
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
6317
- import { execSync as execSync5 } from "child_process";
6501
+ import { execSync as execSync6 } from "child_process";
6318
6502
  import path12 from "path";
6319
6503
  function isNameWithOptionalInstance(candidate, baseName) {
6320
6504
  if (candidate === baseName) return true;
@@ -6406,7 +6590,7 @@ function getActiveAgent() {
6406
6590
  } catch {
6407
6591
  }
6408
6592
  try {
6409
- const sessionName = execSync5(
6593
+ const sessionName = execSync6(
6410
6594
  "tmux display-message -p '#{session_name}' 2>/dev/null",
6411
6595
  { encoding: "utf8", timeout: 2e3 }
6412
6596
  ).trim();
@@ -6739,8 +6923,8 @@ async function validateLicense(apiKey, deviceId) {
6739
6923
  }
6740
6924
  function getCacheAgeMs() {
6741
6925
  try {
6742
- const { statSync: statSync4 } = __require("fs");
6743
- const s = statSync4(CACHE_PATH);
6926
+ const { statSync: statSync5 } = __require("fs");
6927
+ const s = statSync5(CACHE_PATH);
6744
6928
  return Date.now() - s.mtimeMs;
6745
6929
  } catch {
6746
6930
  return Infinity;
@@ -7221,14 +7405,14 @@ var init_transport = __esm({
7221
7405
  });
7222
7406
 
7223
7407
  // src/lib/cc-agent-support.ts
7224
- import { execSync as execSync6 } from "child_process";
7408
+ import { execSync as execSync7 } from "child_process";
7225
7409
  function _resetCcAgentSupportCache() {
7226
7410
  _cachedSupport = null;
7227
7411
  }
7228
7412
  function claudeSupportsAgentFlag() {
7229
7413
  if (_cachedSupport !== null) return _cachedSupport;
7230
7414
  try {
7231
- const helpOutput = execSync6("claude --help 2>&1", {
7415
+ const helpOutput = execSync7("claude --help 2>&1", {
7232
7416
  encoding: "utf-8",
7233
7417
  timeout: 5e3
7234
7418
  });
@@ -8101,7 +8285,7 @@ __export(tmux_routing_exports, {
8101
8285
  spawnEmployee: () => spawnEmployee,
8102
8286
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
8103
8287
  });
8104
- import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
8288
+ import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
8105
8289
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync5 } from "fs";
8106
8290
  import path20 from "path";
8107
8291
  import os9 from "os";
@@ -8811,7 +8995,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8811
8995
  let booted = false;
8812
8996
  for (let i = 0; i < 30; i++) {
8813
8997
  try {
8814
- execSync7("sleep 0.5");
8998
+ execSync8("sleep 0.5");
8815
8999
  } catch {
8816
9000
  }
8817
9001
  try {
@@ -9058,7 +9242,7 @@ __export(tasks_crud_exports, {
9058
9242
  import crypto7 from "crypto";
9059
9243
  import path22 from "path";
9060
9244
  import os11 from "os";
9061
- import { execSync as execSync8 } from "child_process";
9245
+ import { execSync as execSync9 } from "child_process";
9062
9246
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
9063
9247
  import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
9064
9248
  async function writeCheckpoint(input) {
@@ -9403,14 +9587,14 @@ function isTmuxSessionAlive(identifier) {
9403
9587
  if (!identifier || identifier === "unknown") return true;
9404
9588
  try {
9405
9589
  if (identifier.startsWith("%")) {
9406
- const output = execSync8("tmux list-panes -a -F '#{pane_id}'", {
9590
+ const output = execSync9("tmux list-panes -a -F '#{pane_id}'", {
9407
9591
  timeout: 2e3,
9408
9592
  encoding: "utf8",
9409
9593
  stdio: ["pipe", "pipe", "pipe"]
9410
9594
  });
9411
9595
  return output.split("\n").some((l) => l.trim() === identifier);
9412
9596
  } else {
9413
- execSync8(`tmux has-session -t ${JSON.stringify(identifier)}`, {
9597
+ execSync9(`tmux has-session -t ${JSON.stringify(identifier)}`, {
9414
9598
  timeout: 2e3,
9415
9599
  stdio: ["pipe", "pipe", "pipe"]
9416
9600
  });
@@ -9419,7 +9603,7 @@ function isTmuxSessionAlive(identifier) {
9419
9603
  } catch {
9420
9604
  if (identifier.startsWith("%")) return true;
9421
9605
  try {
9422
- execSync8("tmux list-sessions", {
9606
+ execSync9("tmux list-sessions", {
9423
9607
  timeout: 2e3,
9424
9608
  stdio: ["pipe", "pipe", "pipe"]
9425
9609
  });
@@ -9434,12 +9618,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
9434
9618
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
9435
9619
  try {
9436
9620
  const since = new Date(taskCreatedAt).toISOString();
9437
- const branch = execSync8(
9621
+ const branch = execSync9(
9438
9622
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
9439
9623
  { encoding: "utf8", timeout: 3e3 }
9440
9624
  ).trim();
9441
9625
  const branchArg = branch && branch !== "HEAD" ? branch : "";
9442
- const commitCount = execSync8(
9626
+ const commitCount = execSync9(
9443
9627
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
9444
9628
  { encoding: "utf8", timeout: 5e3 }
9445
9629
  ).trim();
@@ -11668,8 +11852,8 @@ __export(wiki_client_exports, {
11668
11852
  listDocuments: () => listDocuments,
11669
11853
  listWorkspaces: () => listWorkspaces
11670
11854
  });
11671
- async function wikiFetch(config2, path44, method = "GET", body) {
11672
- const url = `${config2.baseUrl}/api/v1${path44}`;
11855
+ async function wikiFetch(config2, path45, method = "GET", body) {
11856
+ const url = `${config2.baseUrl}/api/v1${path45}`;
11673
11857
  const headers = {
11674
11858
  Authorization: `Bearer ${config2.apiKey}`,
11675
11859
  "Content-Type": "application/json"
@@ -11702,7 +11886,7 @@ async function wikiFetch(config2, path44, method = "GET", body) {
11702
11886
  }
11703
11887
  }
11704
11888
  if (!response.ok) {
11705
- throw new Error(`Wiki API ${method} ${path44}: ${response.status} ${response.statusText}`);
11889
+ throw new Error(`Wiki API ${method} ${path45}: ${response.status} ${response.statusText}`);
11706
11890
  }
11707
11891
  return response.json();
11708
11892
  } finally {
@@ -11911,6 +12095,109 @@ var init_worker_gate = __esm({
11911
12095
  }
11912
12096
  });
11913
12097
 
12098
+ // src/lib/db-backup.ts
12099
+ var db_backup_exports = {};
12100
+ __export(db_backup_exports, {
12101
+ createBackup: () => createBackup,
12102
+ findActiveDb: () => findActiveDb,
12103
+ getBackupDir: () => getBackupDir,
12104
+ getLatestBackup: () => getLatestBackup,
12105
+ hasBackupToday: () => hasBackupToday,
12106
+ listBackups: () => listBackups,
12107
+ rotateBackups: () => rotateBackups
12108
+ });
12109
+ import { copyFileSync as copyFileSync2, existsSync as existsSync29, mkdirSync as mkdirSync15, readdirSync as readdirSync12, unlinkSync as unlinkSync9, statSync as statSync4 } from "fs";
12110
+ import path37 from "path";
12111
+ function findActiveDb() {
12112
+ for (const name of DB_NAMES) {
12113
+ const p = path37.join(EXE_AI_DIR, name);
12114
+ if (existsSync29(p)) return p;
12115
+ }
12116
+ return null;
12117
+ }
12118
+ function createBackup(reason = "manual") {
12119
+ const dbPath = findActiveDb();
12120
+ if (!dbPath) return null;
12121
+ mkdirSync15(BACKUP_DIR, { recursive: true });
12122
+ const dbName = path37.basename(dbPath, ".db");
12123
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
12124
+ const backupName = `${dbName}-${reason}-${timestamp}.db`;
12125
+ const backupPath = path37.join(BACKUP_DIR, backupName);
12126
+ copyFileSync2(dbPath, backupPath);
12127
+ const walPath = dbPath + "-wal";
12128
+ if (existsSync29(walPath)) {
12129
+ try {
12130
+ copyFileSync2(walPath, backupPath + "-wal");
12131
+ } catch {
12132
+ }
12133
+ }
12134
+ const shmPath = dbPath + "-shm";
12135
+ if (existsSync29(shmPath)) {
12136
+ try {
12137
+ copyFileSync2(shmPath, backupPath + "-shm");
12138
+ } catch {
12139
+ }
12140
+ }
12141
+ return backupPath;
12142
+ }
12143
+ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
12144
+ if (!existsSync29(BACKUP_DIR)) return 0;
12145
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
12146
+ let deleted = 0;
12147
+ try {
12148
+ const files = readdirSync12(BACKUP_DIR);
12149
+ for (const file of files) {
12150
+ if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
12151
+ const filePath = path37.join(BACKUP_DIR, file);
12152
+ try {
12153
+ const stat = statSync4(filePath);
12154
+ if (stat.mtimeMs < cutoff) {
12155
+ unlinkSync9(filePath);
12156
+ deleted++;
12157
+ }
12158
+ } catch {
12159
+ }
12160
+ }
12161
+ } catch {
12162
+ }
12163
+ return deleted;
12164
+ }
12165
+ function listBackups() {
12166
+ if (!existsSync29(BACKUP_DIR)) return [];
12167
+ try {
12168
+ const files = readdirSync12(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
12169
+ return files.map((name) => {
12170
+ const p = path37.join(BACKUP_DIR, name);
12171
+ const stat = statSync4(p);
12172
+ return { path: p, name, size: stat.size, date: stat.mtime };
12173
+ }).sort((a, b) => b.date.getTime() - a.date.getTime());
12174
+ } catch {
12175
+ return [];
12176
+ }
12177
+ }
12178
+ function hasBackupToday(reason) {
12179
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
12180
+ const backups = listBackups();
12181
+ return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
12182
+ }
12183
+ function getLatestBackup() {
12184
+ const backups = listBackups();
12185
+ return backups.length > 0 ? backups[0].path : null;
12186
+ }
12187
+ function getBackupDir() {
12188
+ return BACKUP_DIR;
12189
+ }
12190
+ var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
12191
+ var init_db_backup = __esm({
12192
+ "src/lib/db-backup.ts"() {
12193
+ "use strict";
12194
+ init_config();
12195
+ BACKUP_DIR = path37.join(EXE_AI_DIR, "backups");
12196
+ DEFAULT_KEEP_DAYS = 3;
12197
+ DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
12198
+ }
12199
+ });
12200
+
11914
12201
  // src/lib/crdt-sync.ts
11915
12202
  var crdt_sync_exports = {};
11916
12203
  __export(crdt_sync_exports, {
@@ -11930,8 +12217,8 @@ __export(crdt_sync_exports, {
11930
12217
  rebuildFromDb: () => rebuildFromDb
11931
12218
  });
11932
12219
  import * as Y from "yjs";
11933
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync19, existsSync as existsSync30, mkdirSync as mkdirSync15, unlinkSync as unlinkSync9 } from "fs";
11934
- import path38 from "path";
12220
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync19, existsSync as existsSync31, mkdirSync as mkdirSync16, unlinkSync as unlinkSync10 } from "fs";
12221
+ import path39 from "path";
11935
12222
  import { homedir as homedir5 } from "os";
11936
12223
  function getStatePath() {
11937
12224
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -11943,14 +12230,14 @@ function initCrdtDoc() {
11943
12230
  if (doc) return doc;
11944
12231
  doc = new Y.Doc();
11945
12232
  const sp = getStatePath();
11946
- if (existsSync30(sp)) {
12233
+ if (existsSync31(sp)) {
11947
12234
  try {
11948
12235
  const state = readFileSync25(sp);
11949
12236
  Y.applyUpdate(doc, new Uint8Array(state));
11950
12237
  } catch {
11951
12238
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
11952
12239
  try {
11953
- unlinkSync9(sp);
12240
+ unlinkSync10(sp);
11954
12241
  } catch {
11955
12242
  }
11956
12243
  rebuildFromDb().catch((err) => {
@@ -12087,8 +12374,8 @@ function persistState() {
12087
12374
  if (!doc) return;
12088
12375
  try {
12089
12376
  const sp = getStatePath();
12090
- const dir = path38.dirname(sp);
12091
- if (!existsSync30(dir)) mkdirSync15(dir, { recursive: true });
12377
+ const dir = path39.dirname(sp);
12378
+ if (!existsSync31(dir)) mkdirSync16(dir, { recursive: true });
12092
12379
  const state = Y.encodeStateAsUpdate(doc);
12093
12380
  writeFileSync19(sp, Buffer.from(state));
12094
12381
  } catch {
@@ -12131,7 +12418,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
12131
12418
  var init_crdt_sync = __esm({
12132
12419
  "src/lib/crdt-sync.ts"() {
12133
12420
  "use strict";
12134
- DEFAULT_STATE_PATH = path38.join(homedir5(), ".exe-os", "crdt-state.bin");
12421
+ DEFAULT_STATE_PATH = path39.join(homedir5(), ".exe-os", "crdt-state.bin");
12135
12422
  _statePathOverride = null;
12136
12423
  doc = null;
12137
12424
  }
@@ -12144,8 +12431,8 @@ init_database();
12144
12431
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12145
12432
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12146
12433
  import { spawn as spawn4 } from "child_process";
12147
- import { existsSync as existsSync33, openSync as openSync3, mkdirSync as mkdirSync17, closeSync as closeSync3, readFileSync as readFileSync28 } from "fs";
12148
- import path43 from "path";
12434
+ import { existsSync as existsSync34, openSync as openSync3, mkdirSync as mkdirSync18, closeSync as closeSync3, readFileSync as readFileSync28 } from "fs";
12435
+ import path44 from "path";
12149
12436
  import os18 from "os";
12150
12437
  import { fileURLToPath as fileURLToPath5 } from "url";
12151
12438
 
@@ -12832,10 +13119,10 @@ function registerCreateTask(server2) {
12832
13119
  skipDispatch: true
12833
13120
  });
12834
13121
  try {
12835
- const { existsSync: existsSync34, mkdirSync: mkdirSync18, writeFileSync: writeFileSync21 } = await import("fs");
13122
+ const { existsSync: existsSync35, mkdirSync: mkdirSync19, writeFileSync: writeFileSync21 } = await import("fs");
12836
13123
  const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
12837
13124
  const idPath = identityPath2(assigned_to);
12838
- if (!existsSync34(idPath)) {
13125
+ if (!existsSync35(idPath)) {
12839
13126
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
12840
13127
  const employees = await loadEmployees2();
12841
13128
  const emp = employees.find((e) => e.name === assigned_to);
@@ -12844,7 +13131,7 @@ function registerCreateTask(server2) {
12844
13131
  const template = getTemplateForTitle2(emp.role);
12845
13132
  if (template) {
12846
13133
  const dir = (await import("path")).dirname(idPath);
12847
- if (!existsSync34(dir)) mkdirSync18(dir, { recursive: true });
13134
+ if (!existsSync35(dir)) mkdirSync19(dir, { recursive: true });
12848
13135
  writeFileSync21(idPath, template.replace(/^agent_id: \w+/m, `agent_id: ${assigned_to}`), "utf-8");
12849
13136
  }
12850
13137
  }
@@ -14747,7 +15034,7 @@ function isScheduledTrigger(trigger) {
14747
15034
  init_database();
14748
15035
  init_store();
14749
15036
  import crypto13 from "crypto";
14750
- import { execSync as execSync9 } from "child_process";
15037
+ import { execSync as execSync10 } from "child_process";
14751
15038
  var CRON_FIELD = /^[\d*/,\-]+$/;
14752
15039
  function isValidCron(cron) {
14753
15040
  const fields = cron.trim().split(/\s+/);
@@ -14873,7 +15160,7 @@ function addToCrontab(id, cron, prompt, projectDir) {
14873
15160
  const cwd = projectDir ? `cd ${JSON.stringify(projectDir)} && ` : "";
14874
15161
  const escapedPrompt = prompt.replace(/"/g, '\\"');
14875
15162
  const entry = `${cron} ${cwd}claude -p --dangerously-skip-permissions "${escapedPrompt}" # exe-schedule:${id}`;
14876
- execSync9(
15163
+ execSync10(
14877
15164
  `(crontab -l 2>/dev/null; echo ${JSON.stringify(entry)}) | crontab -`,
14878
15165
  { timeout: 5e3, stdio: "ignore" }
14879
15166
  );
@@ -16238,9 +16525,9 @@ var HostingerApiClient = class {
16238
16525
  }
16239
16526
  this.lastRequestTime = Date.now();
16240
16527
  }
16241
- async request(method, path44, body) {
16528
+ async request(method, path45, body) {
16242
16529
  await this.rateLimit();
16243
- const url = `${this.baseUrl}${path44}`;
16530
+ const url = `${this.baseUrl}${path45}`;
16244
16531
  const headers = {
16245
16532
  Authorization: `Bearer ${this.apiKey}`,
16246
16533
  "Content-Type": "application/json",
@@ -16313,8 +16600,8 @@ async function requestCloudflare(cfApiToken, zoneId, options) {
16313
16600
  }
16314
16601
  return envelope.result;
16315
16602
  }
16316
- function buildUrl(zoneId, path44 = "/dns_records", query) {
16317
- const normalizedPath = path44.startsWith("/") ? path44 : `/${path44}`;
16603
+ function buildUrl(zoneId, path45 = "/dns_records", query) {
16604
+ const normalizedPath = path45.startsWith("/") ? path45 : `/${path45}`;
16318
16605
  const url = new URL(
16319
16606
  `${CLOUDFLARE_API_BASE_URL}/zones/${zoneId}${normalizedPath}`
16320
16607
  );
@@ -18934,12 +19221,12 @@ function registerExportGraph(server2) {
18934
19221
  }
18935
19222
  const html = await exportGraphHTML(client);
18936
19223
  const fs = await import("fs");
18937
- const path44 = await import("path");
19224
+ const path45 = await import("path");
18938
19225
  const os19 = await import("os");
18939
- const outDir = path44.join(os19.homedir(), ".exe-os", "exports");
19226
+ const outDir = path45.join(os19.homedir(), ".exe-os", "exports");
18940
19227
  fs.mkdirSync(outDir, { recursive: true });
18941
19228
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18942
- const filePath = path44.join(outDir, `graph-${timestamp}.html`);
19229
+ const filePath = path45.join(outDir, `graph-${timestamp}.html`);
18943
19230
  fs.writeFileSync(filePath, html, "utf-8");
18944
19231
  return {
18945
19232
  content: [
@@ -19248,7 +19535,7 @@ import { z as z60 } from "zod";
19248
19535
  init_tmux_routing();
19249
19536
  init_task_scope();
19250
19537
  init_employees();
19251
- import { execSync as execSync10 } from "child_process";
19538
+ import { execSync as execSync11 } from "child_process";
19252
19539
  import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
19253
19540
  import { homedir as homedir4 } from "os";
19254
19541
  import { join as join2 } from "path";
@@ -19448,9 +19735,9 @@ function isMainModule(importMetaUrl) {
19448
19735
  }
19449
19736
 
19450
19737
  // src/bin/exe-doctor.ts
19451
- import { existsSync as existsSync29, readFileSync as readFileSync24 } from "fs";
19738
+ import { existsSync as existsSync30, readFileSync as readFileSync24 } from "fs";
19452
19739
  import { spawn as spawn2 } from "child_process";
19453
- import path37 from "path";
19740
+ import path38 from "path";
19454
19741
  import { randomUUID as randomUUID7 } from "crypto";
19455
19742
 
19456
19743
  // src/lib/conflict-detector.ts
@@ -19854,7 +20141,7 @@ async function auditOrphanedProjects(client) {
19854
20141
  for (const row of result.rows) {
19855
20142
  const name = row.project_name;
19856
20143
  const count = Number(row.cnt);
19857
- const exists = existsSync29(path37.join(home, name)) || existsSync29(path37.join(home, "..", name)) || existsSync29(path37.join(process.cwd(), "..", name));
20144
+ const exists = existsSync30(path38.join(home, name)) || existsSync30(path38.join(home, "..", name)) || existsSync30(path38.join(process.cwd(), "..", name));
19858
20145
  if (!exists) {
19859
20146
  orphans.push({ project_name: name, count });
19860
20147
  }
@@ -19862,13 +20149,13 @@ async function auditOrphanedProjects(client) {
19862
20149
  return orphans;
19863
20150
  }
19864
20151
  function auditHookHealth() {
19865
- const logPath = path37.join(
20152
+ const logPath = path38.join(
19866
20153
  process.env.HOME ?? process.env.USERPROFILE ?? "",
19867
20154
  ".exe-os",
19868
20155
  "logs",
19869
20156
  "hooks.log"
19870
20157
  );
19871
- if (!existsSync29(logPath)) {
20158
+ if (!existsSync30(logPath)) {
19872
20159
  return { logExists: false, totalLines: 0, errorsLastHour: 0, topPatterns: [] };
19873
20160
  }
19874
20161
  let content;
@@ -20086,7 +20373,7 @@ async function fixNullVectors() {
20086
20373
  }
20087
20374
  }
20088
20375
  const npmRoot = (await import("child_process")).execSync("npm root -g", { encoding: "utf8" }).trim();
20089
- const backfillPath = path37.join(npmRoot, "exe-os", "dist", "bin", "backfill-vectors.js");
20376
+ const backfillPath = path38.join(npmRoot, "exe-os", "dist", "bin", "backfill-vectors.js");
20090
20377
  return new Promise((resolve, reject) => {
20091
20378
  const child = spawn2("node", [backfillPath], { stdio: "inherit" });
20092
20379
  if (child.pid) registerWorkerPid2(child.pid);
@@ -20198,6 +20485,17 @@ async function main(argv = process.argv.slice(2)) {
20198
20485
  console.log(`
20199
20486
  ${mode} Applying repairs...
20200
20487
  `);
20488
+ if (!flags.dryRun) {
20489
+ try {
20490
+ const { createBackup: createBackup3 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
20491
+ const backupPath = createBackup3("pre-fix");
20492
+ if (backupPath) {
20493
+ console.log(` Backup created: ${backupPath.split("/").pop()}`);
20494
+ }
20495
+ } catch {
20496
+ console.log(" Warning: backup failed \u2014 proceeding with fix anyway");
20497
+ }
20498
+ }
20201
20499
  if (report.nullVectors > 0) {
20202
20500
  console.log(`${mode} Backfilling ${fmtNum(report.nullVectors)} null vectors...`);
20203
20501
  if (!flags.dryRun) {
@@ -20286,9 +20584,9 @@ import { z as z63 } from "zod";
20286
20584
 
20287
20585
  // src/lib/cloud-sync.ts
20288
20586
  init_database();
20289
- import { readFileSync as readFileSync26, writeFileSync as writeFileSync20, existsSync as existsSync31, readdirSync as readdirSync12, mkdirSync as mkdirSync16, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
20587
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync20, existsSync as existsSync32, readdirSync as readdirSync13, mkdirSync as mkdirSync17, appendFileSync as appendFileSync2, unlinkSync as unlinkSync11, openSync as openSync2, closeSync as closeSync2 } from "fs";
20290
20588
  import crypto17 from "crypto";
20291
- import path39 from "path";
20589
+ import path40 from "path";
20292
20590
  import { homedir as homedir6 } from "os";
20293
20591
 
20294
20592
  // src/lib/crypto.ts
@@ -20363,7 +20661,7 @@ function sqlSafe(v) {
20363
20661
  }
20364
20662
  function logError(msg) {
20365
20663
  try {
20366
- const logPath = path39.join(homedir6(), ".exe-os", "workers.log");
20664
+ const logPath = path40.join(homedir6(), ".exe-os", "workers.log");
20367
20665
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
20368
20666
  `);
20369
20667
  } catch {
@@ -20372,17 +20670,17 @@ function logError(msg) {
20372
20670
  var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
20373
20671
  var FETCH_TIMEOUT_MS4 = 3e4;
20374
20672
  var PUSH_BATCH_SIZE = 5e3;
20375
- var ROSTER_LOCK_PATH = path39.join(EXE_AI_DIR, "roster-merge.lock");
20673
+ var ROSTER_LOCK_PATH = path40.join(EXE_AI_DIR, "roster-merge.lock");
20376
20674
  var LOCK_STALE_MS = 3e4;
20377
20675
  var _pgPromise = null;
20378
20676
  var _pgFailed = false;
20379
20677
  function loadPgClient() {
20380
20678
  if (_pgFailed) return null;
20381
20679
  const postgresUrl = process.env.DATABASE_URL;
20382
- const configPath = path39.join(EXE_AI_DIR, "config.json");
20680
+ const configPath = path40.join(EXE_AI_DIR, "config.json");
20383
20681
  let cloudPostgresUrl;
20384
20682
  try {
20385
- if (existsSync31(configPath)) {
20683
+ if (existsSync32(configPath)) {
20386
20684
  const cfg = JSON.parse(readFileSync26(configPath, "utf8"));
20387
20685
  cloudPostgresUrl = cfg.cloud?.postgresUrl;
20388
20686
  if (cfg.cloud?.syncToPostgres === false) {
@@ -20401,8 +20699,8 @@ function loadPgClient() {
20401
20699
  _pgPromise = (async () => {
20402
20700
  const { createRequire: createRequire5 } = await import("module");
20403
20701
  const { pathToFileURL: pathToFileURL5 } = await import("url");
20404
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path39.join(homedir6(), "exe-db");
20405
- const req = createRequire5(path39.join(exeDbRoot, "package.json"));
20702
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path40.join(homedir6(), "exe-db");
20703
+ const req = createRequire5(path40.join(exeDbRoot, "package.json"));
20406
20704
  const entry = req.resolve("@prisma/client");
20407
20705
  const mod = await import(pathToFileURL5(entry).href);
20408
20706
  const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
@@ -20455,7 +20753,7 @@ async function withRosterLock(fn) {
20455
20753
  if (Date.now() - ts2 < LOCK_STALE_MS) {
20456
20754
  throw new Error("Roster merge already in progress \u2014 another sync is running");
20457
20755
  }
20458
- unlinkSync10(ROSTER_LOCK_PATH);
20756
+ unlinkSync11(ROSTER_LOCK_PATH);
20459
20757
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
20460
20758
  closeSync2(fd);
20461
20759
  writeFileSync20(ROSTER_LOCK_PATH, String(Date.now()));
@@ -20471,7 +20769,7 @@ async function withRosterLock(fn) {
20471
20769
  return await fn();
20472
20770
  } finally {
20473
20771
  try {
20474
- unlinkSync10(ROSTER_LOCK_PATH);
20772
+ unlinkSync11(ROSTER_LOCK_PATH);
20475
20773
  } catch {
20476
20774
  }
20477
20775
  }
@@ -20842,13 +21140,42 @@ async function cloudSync(config2) {
20842
21140
  try {
20843
21141
  const employees = await loadEmployees();
20844
21142
  rosterResult.employees = employees.length;
20845
- const idDir = path39.join(EXE_AI_DIR, "identity");
20846
- if (existsSync31(idDir)) {
20847
- rosterResult.identities = readdirSync12(idDir).filter((f) => f.endsWith(".md")).length;
21143
+ const idDir = path40.join(EXE_AI_DIR, "identity");
21144
+ if (existsSync32(idDir)) {
21145
+ rosterResult.identities = readdirSync13(idDir).filter((f) => f.endsWith(".md")).length;
20848
21146
  }
20849
21147
  } catch {
20850
21148
  }
20851
21149
  const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
21150
+ try {
21151
+ const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
21152
+ const { statSync: statFile } = await import("fs");
21153
+ const latestBackup = getLatestBackup2();
21154
+ if (latestBackup) {
21155
+ const backupSize = statFile(latestBackup).size;
21156
+ const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
21157
+ if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
21158
+ const backupData = readFileSync26(latestBackup);
21159
+ const deviceId = loadDeviceId() ?? "unknown";
21160
+ const encrypted = encryptSyncBlob(backupData);
21161
+ const backupRes = await fetchWithRetry(`${config2.endpoint}/sync/push-db-backup`, {
21162
+ method: "POST",
21163
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config2.apiKey}` },
21164
+ body: JSON.stringify({
21165
+ device_id: deviceId,
21166
+ filename: path40.basename(latestBackup),
21167
+ blob: encrypted,
21168
+ size: backupData.length
21169
+ })
21170
+ });
21171
+ if (backupRes && !backupRes.ok) {
21172
+ logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
21173
+ }
21174
+ }
21175
+ }
21176
+ } catch (err) {
21177
+ logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
21178
+ }
20852
21179
  return {
20853
21180
  pushed,
20854
21181
  pulled,
@@ -20861,10 +21188,10 @@ async function cloudSync(config2) {
20861
21188
  roster: rosterResult
20862
21189
  };
20863
21190
  }
20864
- var ROSTER_DELETIONS_PATH = path39.join(EXE_AI_DIR, "roster-deletions.json");
21191
+ var ROSTER_DELETIONS_PATH = path40.join(EXE_AI_DIR, "roster-deletions.json");
20865
21192
  function consumeRosterDeletions() {
20866
21193
  try {
20867
- if (!existsSync31(ROSTER_DELETIONS_PATH)) return [];
21194
+ if (!existsSync32(ROSTER_DELETIONS_PATH)) return [];
20868
21195
  const deletions = JSON.parse(readFileSync26(ROSTER_DELETIONS_PATH, "utf-8"));
20869
21196
  writeFileSync20(ROSTER_DELETIONS_PATH, "[]");
20870
21197
  return deletions;
@@ -20873,35 +21200,35 @@ function consumeRosterDeletions() {
20873
21200
  }
20874
21201
  }
20875
21202
  function buildRosterBlob(paths) {
20876
- const rosterPath = paths?.rosterPath ?? path39.join(EXE_AI_DIR, "exe-employees.json");
20877
- const identityDir = paths?.identityDir ?? path39.join(EXE_AI_DIR, "identity");
20878
- const configPath = paths?.configPath ?? path39.join(EXE_AI_DIR, "config.json");
21203
+ const rosterPath = paths?.rosterPath ?? path40.join(EXE_AI_DIR, "exe-employees.json");
21204
+ const identityDir = paths?.identityDir ?? path40.join(EXE_AI_DIR, "identity");
21205
+ const configPath = paths?.configPath ?? path40.join(EXE_AI_DIR, "config.json");
20879
21206
  let roster = [];
20880
- if (existsSync31(rosterPath)) {
21207
+ if (existsSync32(rosterPath)) {
20881
21208
  try {
20882
21209
  roster = JSON.parse(readFileSync26(rosterPath, "utf-8"));
20883
21210
  } catch {
20884
21211
  }
20885
21212
  }
20886
21213
  const identities = {};
20887
- if (existsSync31(identityDir)) {
20888
- for (const file of readdirSync12(identityDir).filter((f) => f.endsWith(".md"))) {
21214
+ if (existsSync32(identityDir)) {
21215
+ for (const file of readdirSync13(identityDir).filter((f) => f.endsWith(".md"))) {
20889
21216
  try {
20890
- identities[file] = readFileSync26(path39.join(identityDir, file), "utf-8");
21217
+ identities[file] = readFileSync26(path40.join(identityDir, file), "utf-8");
20891
21218
  } catch {
20892
21219
  }
20893
21220
  }
20894
21221
  }
20895
21222
  let config2;
20896
- if (existsSync31(configPath)) {
21223
+ if (existsSync32(configPath)) {
20897
21224
  try {
20898
21225
  config2 = JSON.parse(readFileSync26(configPath, "utf-8"));
20899
21226
  } catch {
20900
21227
  }
20901
21228
  }
20902
21229
  let agentConfig;
20903
- const agentConfigPath = path39.join(EXE_AI_DIR, "agent-config.json");
20904
- if (existsSync31(agentConfigPath)) {
21230
+ const agentConfigPath = path40.join(EXE_AI_DIR, "agent-config.json");
21231
+ if (existsSync32(agentConfigPath)) {
20905
21232
  try {
20906
21233
  agentConfig = JSON.parse(readFileSync26(agentConfigPath, "utf-8"));
20907
21234
  } catch {
@@ -20979,16 +21306,16 @@ async function cloudPullRoster(config2) {
20979
21306
  }
20980
21307
  }
20981
21308
  function mergeConfig(remoteConfig, configPath) {
20982
- const cfgPath = configPath ?? path39.join(EXE_AI_DIR, "config.json");
21309
+ const cfgPath = configPath ?? path40.join(EXE_AI_DIR, "config.json");
20983
21310
  let local = {};
20984
- if (existsSync31(cfgPath)) {
21311
+ if (existsSync32(cfgPath)) {
20985
21312
  try {
20986
21313
  local = JSON.parse(readFileSync26(cfgPath, "utf-8"));
20987
21314
  } catch {
20988
21315
  }
20989
21316
  }
20990
21317
  const merged = { ...remoteConfig, ...local };
20991
- const dir = path39.dirname(cfgPath);
21318
+ const dir = path40.dirname(cfgPath);
20992
21319
  ensurePrivateDirSync(dir);
20993
21320
  writeFileSync20(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
20994
21321
  enforcePrivateFileSync(cfgPath);
@@ -20996,7 +21323,7 @@ function mergeConfig(remoteConfig, configPath) {
20996
21323
  async function mergeRosterFromRemote(remote, paths) {
20997
21324
  return withRosterLock(async () => {
20998
21325
  const rosterPath = paths?.rosterPath ?? void 0;
20999
- const identityDir = paths?.identityDir ?? path39.join(EXE_AI_DIR, "identity");
21326
+ const identityDir = paths?.identityDir ?? path40.join(EXE_AI_DIR, "identity");
21000
21327
  const localEmployees = await loadEmployees(rosterPath);
21001
21328
  const localNames = new Set(localEmployees.map((e) => e.name));
21002
21329
  let added = 0;
@@ -21017,11 +21344,11 @@ async function mergeRosterFromRemote(remote, paths) {
21017
21344
  ) ?? lookupKey;
21018
21345
  const remoteIdentity = remote.identities[matchedKey];
21019
21346
  if (remoteIdentity) {
21020
- if (!existsSync31(identityDir)) mkdirSync16(identityDir, { recursive: true });
21021
- const idPath = path39.join(identityDir, `${remoteEmp.name}.md`);
21347
+ if (!existsSync32(identityDir)) mkdirSync17(identityDir, { recursive: true });
21348
+ const idPath = path40.join(identityDir, `${remoteEmp.name}.md`);
21022
21349
  let localIdentity = null;
21023
21350
  try {
21024
- localIdentity = existsSync31(idPath) ? readFileSync26(idPath, "utf-8") : null;
21351
+ localIdentity = existsSync32(idPath) ? readFileSync26(idPath, "utf-8") : null;
21025
21352
  } catch {
21026
21353
  }
21027
21354
  if (localIdentity !== remoteIdentity) {
@@ -21051,16 +21378,16 @@ async function mergeRosterFromRemote(remote, paths) {
21051
21378
  }
21052
21379
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
21053
21380
  try {
21054
- const agentConfigPath = path39.join(EXE_AI_DIR, "agent-config.json");
21381
+ const agentConfigPath = path40.join(EXE_AI_DIR, "agent-config.json");
21055
21382
  let local = {};
21056
- if (existsSync31(agentConfigPath)) {
21383
+ if (existsSync32(agentConfigPath)) {
21057
21384
  try {
21058
21385
  local = JSON.parse(readFileSync26(agentConfigPath, "utf-8"));
21059
21386
  } catch {
21060
21387
  }
21061
21388
  }
21062
21389
  const merged = { ...remote.agentConfig, ...local };
21063
- ensurePrivateDirSync(path39.dirname(agentConfigPath));
21390
+ ensurePrivateDirSync(path40.dirname(agentConfigPath));
21064
21391
  writeFileSync20(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
21065
21392
  enforcePrivateFileSync(agentConfigPath);
21066
21393
  } catch {
@@ -22024,7 +22351,7 @@ function normalizeS3Body(body) {
22024
22351
  }
22025
22352
  throw new Error("Unsupported S3 object body type");
22026
22353
  }
22027
- async function createBackup(opts) {
22354
+ async function createBackup2(opts) {
22028
22355
  validateEncryptionKey(opts.encryptionKey);
22029
22356
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
22030
22357
  const key = makeBackupKey(opts.databaseUrl, new Date(timestamp));
@@ -22075,7 +22402,7 @@ async function restoreBackup(opts) {
22075
22402
  const decompressed = decompress2(decryptBlob(encrypted, opts.encryptionKey));
22076
22403
  await runProcess("pg_restore", ["--clean", "--if-exists", "-d", opts.databaseUrl], decompressed);
22077
22404
  }
22078
- async function listBackups(opts) {
22405
+ async function listBackups2(opts) {
22079
22406
  const s3 = makeS3Client(opts);
22080
22407
  const objects = await listBackupObjects(s3, opts.r2Bucket, BACKUP_PREFIX);
22081
22408
  return objects.filter((obj) => obj.key.endsWith(BACKUP_EXT)).map((obj) => ({
@@ -22085,7 +22412,7 @@ async function listBackups(opts) {
22085
22412
  })).sort((a, b) => b.lastModified.localeCompare(a.lastModified));
22086
22413
  }
22087
22414
  async function backupHealth(opts) {
22088
- const backups = await listBackups(opts);
22415
+ const backups = await listBackups2(opts);
22089
22416
  if (backups.length === 0) {
22090
22417
  return {
22091
22418
  lastBackup: null,
@@ -22162,7 +22489,7 @@ function registerBackupVps(server2) {
22162
22489
  r2AccessKeyId: input.r2AccessKeyId,
22163
22490
  r2SecretAccessKey: input.r2SecretAccessKey
22164
22491
  };
22165
- const result = await createBackup(options);
22492
+ const result = await createBackup2(options);
22166
22493
  return {
22167
22494
  content: [
22168
22495
  {
@@ -22202,7 +22529,7 @@ ${result.timestamp}`
22202
22529
  r2AccessKeyId: input.r2AccessKeyId,
22203
22530
  r2SecretAccessKey: input.r2SecretAccessKey
22204
22531
  };
22205
- const result = await listBackups(options);
22532
+ const result = await listBackups2(options);
22206
22533
  return {
22207
22534
  content: [
22208
22535
  {
@@ -22288,11 +22615,11 @@ import { z as z68 } from "zod";
22288
22615
  // src/lib/people.ts
22289
22616
  init_config();
22290
22617
  import { readFile as readFile5, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
22291
- import { existsSync as existsSync32, readFileSync as readFileSync27 } from "fs";
22292
- import path40 from "path";
22293
- var PEOPLE_PATH = path40.join(EXE_AI_DIR, "people.json");
22618
+ import { existsSync as existsSync33, readFileSync as readFileSync27 } from "fs";
22619
+ import path41 from "path";
22620
+ var PEOPLE_PATH = path41.join(EXE_AI_DIR, "people.json");
22294
22621
  async function loadPeople() {
22295
- if (!existsSync32(PEOPLE_PATH)) return [];
22622
+ if (!existsSync33(PEOPLE_PATH)) return [];
22296
22623
  try {
22297
22624
  const raw = await readFile5(PEOPLE_PATH, "utf-8");
22298
22625
  return JSON.parse(raw);
@@ -22301,7 +22628,7 @@ async function loadPeople() {
22301
22628
  }
22302
22629
  }
22303
22630
  async function savePeople(people) {
22304
- await mkdir5(path40.dirname(PEOPLE_PATH), { recursive: true });
22631
+ await mkdir5(path41.dirname(PEOPLE_PATH), { recursive: true });
22305
22632
  await writeFile6(PEOPLE_PATH, JSON.stringify(people, null, 2) + "\n", "utf-8");
22306
22633
  }
22307
22634
  async function addPerson(person) {
@@ -22594,7 +22921,7 @@ function registerListEmployees(server2) {
22594
22921
  // src/mcp/tools/create-license.ts
22595
22922
  init_license();
22596
22923
  import os16 from "os";
22597
- import path41 from "path";
22924
+ import path42 from "path";
22598
22925
  import { randomBytes as randomBytes2, randomUUID as randomUUID8 } from "crypto";
22599
22926
  import { createRequire as createRequire3 } from "module";
22600
22927
  import { pathToFileURL as pathToFileURL3 } from "url";
@@ -22610,8 +22937,8 @@ function loadPrisma() {
22610
22937
  if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
22611
22938
  return new Ctor2();
22612
22939
  }
22613
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path41.join(os16.homedir(), "exe-db");
22614
- const req = createRequire3(path41.join(exeDbRoot, "package.json"));
22940
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path42.join(os16.homedir(), "exe-db");
22941
+ const req = createRequire3(path42.join(exeDbRoot, "package.json"));
22615
22942
  const entry = req.resolve("@prisma/client");
22616
22943
  const mod = await import(pathToFileURL3(entry).href);
22617
22944
  const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
@@ -22686,7 +23013,7 @@ Give this key to the customer. They paste it during \`exe-os setup\`.`);
22686
23013
 
22687
23014
  // src/mcp/tools/list-licenses.ts
22688
23015
  import os17 from "os";
22689
- import path42 from "path";
23016
+ import path43 from "path";
22690
23017
  import { createRequire as createRequire4 } from "module";
22691
23018
  import { pathToFileURL as pathToFileURL4 } from "url";
22692
23019
  import { z as z72 } from "zod";
@@ -22701,8 +23028,8 @@ function loadPrisma2() {
22701
23028
  if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
22702
23029
  return new Ctor2();
22703
23030
  }
22704
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path42.join(os17.homedir(), "exe-db");
22705
- const req = createRequire4(path42.join(exeDbRoot, "package.json"));
23031
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path43.join(os17.homedir(), "exe-db");
23032
+ const req = createRequire4(path43.join(exeDbRoot, "package.json"));
22706
23033
  const entry = req.resolve("@prisma/client");
22707
23034
  const mod = await import(pathToFileURL4(entry).href);
22708
23035
  const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
@@ -22828,6 +23155,21 @@ init_config();
22828
23155
  import { z as z74 } from "zod";
22829
23156
  var FETCH_TIMEOUT_MS5 = 1e4;
22830
23157
  var QUERY_SCOPE = z74.enum(["raw", "wiki", "memory", "gateway", "all"]);
23158
+ var LOCAL_GATEWAY_HTTP_HOSTS = /^(localhost|127\.0\.0\.1|::1|exe-gateway|gateway)$/i;
23159
+ function assertSecureGatewayUrlForToken(gatewayUrl, authToken) {
23160
+ if (!authToken) return;
23161
+ let parsed;
23162
+ try {
23163
+ parsed = new URL(gatewayUrl);
23164
+ } catch {
23165
+ return;
23166
+ }
23167
+ if (parsed.protocol === "https:") return;
23168
+ if (parsed.protocol === "http:" && LOCAL_GATEWAY_HTTP_HOSTS.test(parsed.hostname)) return;
23169
+ throw new Error(
23170
+ `Insecure Company Brain gateway URL rejected: "${gatewayUrl}". Bearer tokens require https:// for remote hosts; plain http:// is only allowed for localhost or Docker-internal gateway services.`
23171
+ );
23172
+ }
22831
23173
  function formatSuccess(query, scope, payload) {
22832
23174
  const pretty = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
22833
23175
  return [
@@ -22866,6 +23208,19 @@ function registerQueryCompanyBrain(server2) {
22866
23208
  ]
22867
23209
  };
22868
23210
  }
23211
+ try {
23212
+ assertSecureGatewayUrlForToken(gatewayUrl, authToken);
23213
+ } catch (err) {
23214
+ return {
23215
+ content: [
23216
+ {
23217
+ type: "text",
23218
+ text: err instanceof Error ? err.message : String(err)
23219
+ }
23220
+ ],
23221
+ isError: true
23222
+ };
23223
+ }
22869
23224
  let url;
22870
23225
  try {
22871
23226
  url = new URL("/query", gatewayUrl);
@@ -22982,6 +23337,138 @@ function instrumentServer(server2) {
22982
23337
  };
22983
23338
  }
22984
23339
 
23340
+ // src/mcp/tool-gates.ts
23341
+ var TOOL_GATES = {
23342
+ core: [],
23343
+ // all agents — recall, store, ask_team, commit, search, session, consolidate, cardinality, behavior, identity, decisions
23344
+ tasks: [],
23345
+ // all agents — create, list, get, update, close, checkpoint, resume
23346
+ comms: [],
23347
+ // all agents — send_message, acknowledge
23348
+ reminders: [],
23349
+ // all agents — create, list, complete, reminder
23350
+ "graph-read": [],
23351
+ // all agents — query_relationships, get_entity_neighbors, get_hot_entities, get_graph_stats, find_similar_trajectories
23352
+ "graph-write": ["COO", "CTO"],
23353
+ // merge_entities, export_graph
23354
+ wiki: ["COO", "Wiki Agent"],
23355
+ // create/list/get/update wiki pages
23356
+ crm: ["COO", "CRM Agent"],
23357
+ // add/list/get person
23358
+ documents: ["COO", "CTO", "Wiki Agent"],
23359
+ // ingest, list, purge, set_importance, rerank
23360
+ gateway: ["COO", "Gateway Agent"],
23361
+ // send_whatsapp, query_conversations, query_company_brain
23362
+ admin: ["COO"],
23363
+ // deploy, backup, daemon health, auto-wake, worker gate, memory audit, consolidation, cloud sync, agent config, list employees, agent spend, sessions, session kills
23364
+ licensing: ["COO"],
23365
+ // get/create/list/activate license
23366
+ orchestration: ["COO"]
23367
+ // triggers, load_skill, starter_pack, export/import orchestration, global procedures
23368
+ };
23369
+ var TOOL_CATEGORIES = {
23370
+ // core
23371
+ registerRecallMyMemory: "core",
23372
+ registerAskTeamMemory: "core",
23373
+ registerGetSessionContext: "core",
23374
+ registerStoreMemory: "core",
23375
+ registerCommitMemory: "core",
23376
+ registerSearchEverything: "core",
23377
+ registerConsolidateMemories: "core",
23378
+ registerGetMemoryCardinality: "core",
23379
+ registerStoreBehavior: "core",
23380
+ registerListBehaviors: "core",
23381
+ registerDeactivateBehavior: "core",
23382
+ registerBehavior: "core",
23383
+ registerStoreDecision: "core",
23384
+ registerGetDecision: "core",
23385
+ registerGetIdentity: "core",
23386
+ registerUpdateIdentity: "core",
23387
+ // tasks
23388
+ registerCreateTask: "tasks",
23389
+ registerListTasks: "tasks",
23390
+ registerUpdateTask: "tasks",
23391
+ registerCloseTask: "tasks",
23392
+ registerGetTask: "tasks",
23393
+ registerCheckpointTask: "tasks",
23394
+ registerResumeEmployee: "tasks",
23395
+ // comms
23396
+ registerSendMessage: "comms",
23397
+ registerAcknowledgeMessages: "comms",
23398
+ // reminders
23399
+ registerCreateReminder: "reminders",
23400
+ registerListReminders: "reminders",
23401
+ registerCompleteReminder: "reminders",
23402
+ registerReminder: "reminders",
23403
+ // graph-read
23404
+ registerQueryRelationships: "graph-read",
23405
+ registerGetEntityNeighbors: "graph-read",
23406
+ registerGetHotEntities: "graph-read",
23407
+ registerGetGraphStats: "graph-read",
23408
+ registerFindSimilarTrajectories: "graph-read",
23409
+ // graph-write
23410
+ registerMergeEntities: "graph-write",
23411
+ registerExportGraph: "graph-write",
23412
+ // wiki
23413
+ registerCreateWikiPage: "wiki",
23414
+ registerListWikiPages: "wiki",
23415
+ registerGetWikiPage: "wiki",
23416
+ registerUpdateWikiPage: "wiki",
23417
+ // crm
23418
+ registerAddPerson: "crm",
23419
+ registerListPeople: "crm",
23420
+ registerGetPerson: "crm",
23421
+ // documents
23422
+ registerIngestDocument: "documents",
23423
+ registerListDocuments: "documents",
23424
+ registerPurgeDocument: "documents",
23425
+ registerSetDocumentImportance: "documents",
23426
+ registerRerankDocuments: "documents",
23427
+ // gateway
23428
+ registerSendWhatsapp: "gateway",
23429
+ registerQueryConversations: "gateway",
23430
+ registerQueryCompanyBrain: "gateway",
23431
+ // admin
23432
+ registerDeployClient: "admin",
23433
+ registerBackupVps: "admin",
23434
+ registerGetDaemonHealth: "admin",
23435
+ registerGetAutoWakeStatus: "admin",
23436
+ registerGetWorkerGate: "admin",
23437
+ registerRunMemoryAudit: "admin",
23438
+ registerRunConsolidation: "admin",
23439
+ registerCloudSync: "admin",
23440
+ registerSetAgentConfig: "admin",
23441
+ registerListEmployees: "admin",
23442
+ registerGetAgentSpend: "admin",
23443
+ registerListAgentSessions: "admin",
23444
+ registerGetSessionKills: "admin",
23445
+ // licensing
23446
+ registerGetLicenseStatus: "licensing",
23447
+ registerCreateLicense: "licensing",
23448
+ registerListLicenses: "licensing",
23449
+ registerActivateLicense: "licensing",
23450
+ // orchestration
23451
+ registerCreateTrigger: "orchestration",
23452
+ registerListTriggers: "orchestration",
23453
+ registerApplyStarterPack: "orchestration",
23454
+ registerLoadSkill: "orchestration",
23455
+ registerExportOrchestration: "orchestration",
23456
+ registerImportOrchestration: "orchestration",
23457
+ registerGlobalProcedure: "orchestration",
23458
+ registerStoreGlobalProcedure: "orchestration",
23459
+ registerListGlobalProcedures: "orchestration",
23460
+ registerDeactivateGlobalProcedure: "orchestration"
23461
+ };
23462
+ function isToolAllowed(registerFnName) {
23463
+ const role = process.env.AGENT_ROLE;
23464
+ if (!role) return true;
23465
+ const category = TOOL_CATEGORIES[registerFnName];
23466
+ if (!category) return true;
23467
+ const allowedRoles = TOOL_GATES[category];
23468
+ if (!allowedRoles || allowedRoles.length === 0) return true;
23469
+ return allowedRoles.includes(role);
23470
+ }
23471
+
22985
23472
  // src/mcp/server.ts
22986
23473
  var server = new McpServer({
22987
23474
  name: "exe-os",
@@ -22991,84 +23478,90 @@ var _backfillTimer = null;
22991
23478
  var _ppidWatchdog = null;
22992
23479
  var _shuttingDown = false;
22993
23480
  instrumentServer(server);
22994
- registerRecallMyMemory(server);
22995
- registerAskTeamMemory(server);
22996
- registerGetSessionContext(server);
22997
- registerStoreMemory(server);
22998
- registerCommitMemory(server);
22999
- registerQueryRelationships(server);
23000
- registerCreateTask(server);
23001
- registerListTasks(server);
23002
- registerUpdateTask(server);
23003
- registerCloseTask(server);
23004
- registerGetTask(server);
23005
- registerCheckpointTask(server);
23006
- registerResumeEmployee(server);
23007
- registerBehavior(server);
23008
- registerSendMessage(server);
23009
- registerReminder(server);
23010
- registerGetIdentity(server);
23011
- registerUpdateIdentity(server);
23012
- registerIngestDocument(server);
23013
- registerListDocuments(server);
23014
- registerPurgeDocument(server);
23015
- registerSetDocumentImportance(server);
23016
- registerRerankDocuments(server);
23017
- registerAcknowledgeMessages(server);
23018
- registerSendWhatsapp(server);
23019
- registerCreateTrigger(server);
23020
- registerListTriggers(server);
23021
- registerApplyStarterPack(server);
23022
- registerMergeEntities(server);
23023
- registerCreateWikiPage(server);
23024
- registerListWikiPages(server);
23025
- registerGetWikiPage(server);
23026
- registerUpdateWikiPage(server);
23027
- registerDeployClient(server);
23028
- registerExportOrchestration(server);
23029
- registerImportOrchestration(server);
23030
- registerQueryConversations(server);
23031
- registerLoadSkill(server);
23032
- registerConsolidateMemories(server);
23033
- registerGlobalProcedure(server);
23034
- registerStoreGlobalProcedure(server);
23035
- registerListGlobalProcedures(server);
23036
- registerDeactivateGlobalProcedure(server);
23037
- registerStoreBehavior(server);
23038
- registerListBehaviors(server);
23039
- registerDeactivateBehavior(server);
23040
- registerCreateReminder(server);
23041
- registerListReminders(server);
23042
- registerCompleteReminder(server);
23043
- registerSearchEverything(server);
23044
- registerStoreDecision(server);
23045
- registerGetDecision(server);
23046
- registerGetAgentSpend(server);
23047
- registerGetGraphStats(server);
23048
- registerGetEntityNeighbors(server);
23049
- registerGetHotEntities(server);
23050
- registerExportGraph(server);
23051
- registerFindSimilarTrajectories(server);
23052
- registerGetSessionKills(server);
23053
- registerListAgentSessions(server);
23054
- registerGetDaemonHealth(server);
23055
- registerGetAutoWakeStatus(server);
23056
- registerGetWorkerGate(server);
23057
- registerRunMemoryAudit(server);
23058
- registerCloudSync(server);
23059
- registerBackupVps(server);
23060
- registerGetMemoryCardinality(server);
23061
- registerRunConsolidation(server);
23062
- registerGetLicenseStatus(server);
23063
- registerAddPerson(server);
23064
- registerListPeople(server);
23065
- registerGetPerson(server);
23066
- registerSetAgentConfig(server);
23067
- registerListEmployees(server);
23068
- registerCreateLicense(server);
23069
- registerListLicenses(server);
23070
- registerActivateLicense(server);
23071
- registerQueryCompanyBrain(server);
23481
+ var gate = (name, fn) => {
23482
+ if (isToolAllowed(name)) fn(server);
23483
+ };
23484
+ gate("registerRecallMyMemory", registerRecallMyMemory);
23485
+ gate("registerAskTeamMemory", registerAskTeamMemory);
23486
+ gate("registerGetSessionContext", registerGetSessionContext);
23487
+ gate("registerStoreMemory", registerStoreMemory);
23488
+ gate("registerCommitMemory", registerCommitMemory);
23489
+ gate("registerQueryRelationships", registerQueryRelationships);
23490
+ gate("registerCreateTask", registerCreateTask);
23491
+ gate("registerListTasks", registerListTasks);
23492
+ gate("registerUpdateTask", registerUpdateTask);
23493
+ gate("registerCloseTask", registerCloseTask);
23494
+ gate("registerGetTask", registerGetTask);
23495
+ gate("registerCheckpointTask", registerCheckpointTask);
23496
+ gate("registerResumeEmployee", registerResumeEmployee);
23497
+ gate("registerBehavior", registerBehavior);
23498
+ gate("registerSendMessage", registerSendMessage);
23499
+ gate("registerReminder", registerReminder);
23500
+ gate("registerGetIdentity", registerGetIdentity);
23501
+ gate("registerUpdateIdentity", registerUpdateIdentity);
23502
+ gate("registerIngestDocument", registerIngestDocument);
23503
+ gate("registerListDocuments", registerListDocuments);
23504
+ gate("registerPurgeDocument", registerPurgeDocument);
23505
+ gate("registerSetDocumentImportance", registerSetDocumentImportance);
23506
+ gate("registerRerankDocuments", registerRerankDocuments);
23507
+ gate("registerAcknowledgeMessages", registerAcknowledgeMessages);
23508
+ gate("registerSendWhatsapp", registerSendWhatsapp);
23509
+ gate("registerCreateTrigger", registerCreateTrigger);
23510
+ gate("registerListTriggers", registerListTriggers);
23511
+ gate("registerApplyStarterPack", registerApplyStarterPack);
23512
+ gate("registerMergeEntities", registerMergeEntities);
23513
+ gate("registerCreateWikiPage", registerCreateWikiPage);
23514
+ gate("registerListWikiPages", registerListWikiPages);
23515
+ gate("registerGetWikiPage", registerGetWikiPage);
23516
+ gate("registerUpdateWikiPage", registerUpdateWikiPage);
23517
+ gate("registerDeployClient", registerDeployClient);
23518
+ gate("registerExportOrchestration", registerExportOrchestration);
23519
+ gate("registerImportOrchestration", registerImportOrchestration);
23520
+ gate("registerQueryConversations", registerQueryConversations);
23521
+ gate("registerLoadSkill", registerLoadSkill);
23522
+ gate("registerConsolidateMemories", registerConsolidateMemories);
23523
+ gate("registerGlobalProcedure", registerGlobalProcedure);
23524
+ gate("registerStoreGlobalProcedure", registerStoreGlobalProcedure);
23525
+ gate("registerListGlobalProcedures", registerListGlobalProcedures);
23526
+ gate("registerDeactivateGlobalProcedure", registerDeactivateGlobalProcedure);
23527
+ gate("registerStoreBehavior", registerStoreBehavior);
23528
+ gate("registerListBehaviors", registerListBehaviors);
23529
+ gate("registerDeactivateBehavior", registerDeactivateBehavior);
23530
+ gate("registerCreateReminder", registerCreateReminder);
23531
+ gate("registerListReminders", registerListReminders);
23532
+ gate("registerCompleteReminder", registerCompleteReminder);
23533
+ gate("registerSearchEverything", registerSearchEverything);
23534
+ gate("registerStoreDecision", registerStoreDecision);
23535
+ gate("registerGetDecision", registerGetDecision);
23536
+ gate("registerGetAgentSpend", registerGetAgentSpend);
23537
+ gate("registerGetGraphStats", registerGetGraphStats);
23538
+ gate("registerGetEntityNeighbors", registerGetEntityNeighbors);
23539
+ gate("registerGetHotEntities", registerGetHotEntities);
23540
+ gate("registerExportGraph", registerExportGraph);
23541
+ gate("registerFindSimilarTrajectories", registerFindSimilarTrajectories);
23542
+ gate("registerGetSessionKills", registerGetSessionKills);
23543
+ gate("registerListAgentSessions", registerListAgentSessions);
23544
+ gate("registerGetDaemonHealth", registerGetDaemonHealth);
23545
+ gate("registerGetAutoWakeStatus", registerGetAutoWakeStatus);
23546
+ gate("registerGetWorkerGate", registerGetWorkerGate);
23547
+ gate("registerRunMemoryAudit", registerRunMemoryAudit);
23548
+ gate("registerCloudSync", registerCloudSync);
23549
+ gate("registerBackupVps", registerBackupVps);
23550
+ gate("registerGetMemoryCardinality", registerGetMemoryCardinality);
23551
+ gate("registerRunConsolidation", registerRunConsolidation);
23552
+ gate("registerGetLicenseStatus", registerGetLicenseStatus);
23553
+ gate("registerAddPerson", registerAddPerson);
23554
+ gate("registerListPeople", registerListPeople);
23555
+ gate("registerGetPerson", registerGetPerson);
23556
+ gate("registerSetAgentConfig", registerSetAgentConfig);
23557
+ gate("registerListEmployees", registerListEmployees);
23558
+ gate("registerCreateLicense", registerCreateLicense);
23559
+ gate("registerListLicenses", registerListLicenses);
23560
+ gate("registerActivateLicense", registerActivateLicense);
23561
+ gate("registerQueryCompanyBrain", registerQueryCompanyBrain);
23562
+ var _gatedRole = process.env.AGENT_ROLE ?? "standalone";
23563
+ process.stderr.write(`[exe-os] Tool gating: role=${_gatedRole}
23564
+ `);
23072
23565
  try {
23073
23566
  await initStore();
23074
23567
  process.stderr.write("[exe-os] MCP server starting...\n");
@@ -23117,15 +23610,15 @@ try {
23117
23610
  }
23118
23611
  }, 3e4);
23119
23612
  _ppidWatchdog.unref();
23120
- const MCP_VERSION_PATH = path43.join(os18.homedir(), ".exe-os", "mcp-version");
23613
+ const MCP_VERSION_PATH = path44.join(os18.homedir(), ".exe-os", "mcp-version");
23121
23614
  let _currentMcpVersion = null;
23122
23615
  try {
23123
- _currentMcpVersion = existsSync33(MCP_VERSION_PATH) ? readFileSync28(MCP_VERSION_PATH, "utf8").trim() : null;
23616
+ _currentMcpVersion = existsSync34(MCP_VERSION_PATH) ? readFileSync28(MCP_VERSION_PATH, "utf8").trim() : null;
23124
23617
  } catch {
23125
23618
  }
23126
23619
  const _versionWatchdog = setInterval(() => {
23127
23620
  try {
23128
- if (!existsSync33(MCP_VERSION_PATH)) return;
23621
+ if (!existsSync34(MCP_VERSION_PATH)) return;
23129
23622
  const diskVersion = readFileSync28(MCP_VERSION_PATH, "utf8").trim();
23130
23623
  if (_currentMcpVersion && diskVersion !== _currentMcpVersion) {
23131
23624
  process.stderr.write(
@@ -23159,14 +23652,14 @@ try {
23159
23652
  `
23160
23653
  );
23161
23654
  const thisFile = fileURLToPath5(import.meta.url);
23162
- const backfillPath = path43.resolve(
23163
- path43.dirname(thisFile),
23655
+ const backfillPath = path44.resolve(
23656
+ path44.dirname(thisFile),
23164
23657
  "../bin/backfill-vectors.js"
23165
23658
  );
23166
- if (existsSync33(backfillPath)) {
23659
+ if (existsSync34(backfillPath)) {
23167
23660
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
23168
- const logPath = path43.join(exeDir, "workers.log");
23169
- mkdirSync17(path43.dirname(logPath), { recursive: true });
23661
+ const logPath = path44.join(exeDir, "workers.log");
23662
+ mkdirSync18(path44.dirname(logPath), { recursive: true });
23170
23663
  let logFd = "ignore";
23171
23664
  try {
23172
23665
  logFd = openSync3(logPath, "a");