@askexenow/exe-os 0.9.85 → 0.9.87

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 (86) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +50 -14
  2. package/dist/bin/agentic-reflection-backfill.js +50 -14
  3. package/dist/bin/agentic-semantic-label.js +50 -14
  4. package/dist/bin/backfill-conversations.js +50 -14
  5. package/dist/bin/backfill-responses.js +50 -14
  6. package/dist/bin/backfill-vectors.js +50 -14
  7. package/dist/bin/bulk-sync-postgres.js +50 -14
  8. package/dist/bin/cleanup-stale-review-tasks.js +53 -17
  9. package/dist/bin/cli.js +339 -81
  10. package/dist/bin/exe-agent.js +18 -0
  11. package/dist/bin/exe-assign.js +50 -14
  12. package/dist/bin/exe-boot.js +75 -39
  13. package/dist/bin/exe-call.js +18 -0
  14. package/dist/bin/exe-cloud.js +40 -4
  15. package/dist/bin/exe-dispatch.js +61 -25
  16. package/dist/bin/exe-doctor.js +40 -4
  17. package/dist/bin/exe-export-behaviors.js +50 -14
  18. package/dist/bin/exe-forget.js +50 -14
  19. package/dist/bin/exe-gateway.js +65 -29
  20. package/dist/bin/exe-heartbeat.js +55 -19
  21. package/dist/bin/exe-kill.js +54 -18
  22. package/dist/bin/exe-launch-agent.js +58 -22
  23. package/dist/bin/exe-new-employee.js +33 -2
  24. package/dist/bin/exe-pending-messages.js +53 -17
  25. package/dist/bin/exe-pending-notifications.js +53 -17
  26. package/dist/bin/exe-pending-reviews.js +55 -19
  27. package/dist/bin/exe-rename.js +52 -16
  28. package/dist/bin/exe-review.js +50 -14
  29. package/dist/bin/exe-search.js +58 -22
  30. package/dist/bin/exe-session-cleanup.js +85 -44
  31. package/dist/bin/exe-start-codex.js +57 -21
  32. package/dist/bin/exe-start-opencode.js +55 -19
  33. package/dist/bin/exe-status.js +62 -26
  34. package/dist/bin/exe-team.js +50 -14
  35. package/dist/bin/git-sweep.js +63 -27
  36. package/dist/bin/graph-backfill.js +50 -14
  37. package/dist/bin/graph-export.js +50 -14
  38. package/dist/bin/install.js +9 -0
  39. package/dist/bin/intercom-check.js +67 -31
  40. package/dist/bin/scan-tasks.js +63 -27
  41. package/dist/bin/setup.js +53 -13
  42. package/dist/bin/shard-migrate.js +50 -14
  43. package/dist/bin/stack-update.js +59 -2
  44. package/dist/bin/update.js +1 -1
  45. package/dist/gateway/index.js +65 -29
  46. package/dist/hooks/bug-report-worker.js +65 -29
  47. package/dist/hooks/codex-stop-task-finalizer.js +59 -23
  48. package/dist/hooks/commit-complete.js +64 -28
  49. package/dist/hooks/error-recall.js +62 -26
  50. package/dist/hooks/ingest-worker.js +4 -4
  51. package/dist/hooks/ingest.js +56 -20
  52. package/dist/hooks/instructions-loaded.js +50 -14
  53. package/dist/hooks/notification.js +50 -14
  54. package/dist/hooks/post-compact.js +50 -14
  55. package/dist/hooks/post-tool-combined.js +63 -27
  56. package/dist/hooks/pre-compact.js +61 -25
  57. package/dist/hooks/pre-tool-use.js +58 -22
  58. package/dist/hooks/prompt-submit.js +78 -42
  59. package/dist/hooks/session-end.js +66 -30
  60. package/dist/hooks/session-start.js +68 -32
  61. package/dist/hooks/stop.js +53 -17
  62. package/dist/hooks/subagent-stop.js +50 -14
  63. package/dist/hooks/summary-worker.js +55 -19
  64. package/dist/index.js +61 -25
  65. package/dist/lib/cloud-sync.js +32 -14
  66. package/dist/lib/database.js +22 -4
  67. package/dist/lib/db-daemon-client.js +16 -4
  68. package/dist/lib/db.js +22 -4
  69. package/dist/lib/device-registry.js +22 -4
  70. package/dist/lib/embedder.js +16 -4
  71. package/dist/lib/employee-templates.js +18 -0
  72. package/dist/lib/exe-daemon-client.js +16 -4
  73. package/dist/lib/exe-daemon.js +874 -232
  74. package/dist/lib/hybrid-search.js +58 -22
  75. package/dist/lib/identity-templates.js +6 -2
  76. package/dist/lib/schedules.js +53 -17
  77. package/dist/lib/skill-learning.js +16 -4
  78. package/dist/lib/store.js +50 -14
  79. package/dist/lib/tasks.js +16 -4
  80. package/dist/lib/tmux-routing.js +18 -6
  81. package/dist/mcp/server.js +809 -200
  82. package/dist/mcp/tools/create-task.js +24 -8
  83. package/dist/mcp/tools/update-task.js +18 -6
  84. package/dist/runtime/index.js +61 -25
  85. package/dist/tui/App.js +91 -55
  86. package/package.json +1 -1
@@ -390,7 +390,7 @@ __export(exe_daemon_client_exports, {
390
390
  });
391
391
  import net from "net";
392
392
  import os2 from "os";
393
- import { spawn } from "child_process";
393
+ import { spawn, execSync } from "child_process";
394
394
  import { randomUUID } from "crypto";
395
395
  import { existsSync as existsSync4, unlinkSync, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
396
396
  import path3 from "path";
@@ -420,6 +420,14 @@ function handleData(chunk) {
420
420
  }
421
421
  }
422
422
  }
423
+ function isZombie(pid) {
424
+ try {
425
+ const state = execSync(`ps -p ${pid} -o state=`, { encoding: "utf8", timeout: 2e3 }).trim();
426
+ return state.startsWith("Z");
427
+ } catch {
428
+ return false;
429
+ }
430
+ }
423
431
  function cleanupStaleFiles() {
424
432
  if (existsSync4(PID_PATH)) {
425
433
  try {
@@ -427,7 +435,11 @@ function cleanupStaleFiles() {
427
435
  if (pid > 0) {
428
436
  try {
429
437
  process.kill(pid, 0);
430
- return;
438
+ if (!isZombie(pid)) {
439
+ return;
440
+ }
441
+ process.stderr.write(`[exed-client] PID ${pid} is a zombie \u2014 cleaning up stale files
442
+ `);
431
443
  } catch {
432
444
  }
433
445
  }
@@ -455,8 +467,8 @@ function findPackageRoot() {
455
467
  function getAvailableMemoryGB() {
456
468
  if (process.platform === "darwin") {
457
469
  try {
458
- const { execSync: execSync15 } = __require("child_process");
459
- const vmstat = execSync15("vm_stat", { encoding: "utf8" });
470
+ const { execSync: execSync16 } = __require("child_process");
471
+ const vmstat = execSync16("vm_stat", { encoding: "utf8" });
460
472
  const pageSize = 16384;
461
473
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
462
474
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -877,8 +889,8 @@ async function embedDirect(text3) {
877
889
  const llamaCpp = await import("node-llama-cpp");
878
890
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
879
891
  const { existsSync: existsSync42 } = await import("fs");
880
- const path55 = await import("path");
881
- const modelPath = path55.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
892
+ const path56 = await import("path");
893
+ const modelPath = path56.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
882
894
  if (!existsSync42(modelPath)) {
883
895
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
884
896
  }
@@ -1107,7 +1119,7 @@ __export(employees_exports, {
1107
1119
  });
1108
1120
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1109
1121
  import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
1110
- import { execSync } from "child_process";
1122
+ import { execSync as execSync2 } from "child_process";
1111
1123
  import path5 from "path";
1112
1124
  import os3 from "os";
1113
1125
  function normalizeRole(role) {
@@ -1283,7 +1295,7 @@ async function normalizeRosterCase(rosterPath) {
1283
1295
  }
1284
1296
  function findExeBin() {
1285
1297
  try {
1286
- return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
1298
+ return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
1287
1299
  } catch {
1288
1300
  return null;
1289
1301
  }
@@ -3526,6 +3538,12 @@ async function disposeDatabase() {
3526
3538
  clearInterval(_walCheckpointTimer);
3527
3539
  _walCheckpointTimer = null;
3528
3540
  }
3541
+ if (_client) {
3542
+ try {
3543
+ await _client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3544
+ } catch {
3545
+ }
3546
+ }
3529
3547
  if (_daemonClient) {
3530
3548
  _daemonClient.close();
3531
3549
  _daemonClient = null;
@@ -3571,7 +3589,7 @@ __export(keychain_exports, {
3571
3589
  });
3572
3590
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3573
3591
  import { existsSync as existsSync7, statSync as statSync2 } from "fs";
3574
- import { execSync as execSync2 } from "child_process";
3592
+ import { execSync as execSync3 } from "child_process";
3575
3593
  import path7 from "path";
3576
3594
  import os5 from "os";
3577
3595
  function getKeyDir() {
@@ -3588,13 +3606,13 @@ function linuxSecretAvailable() {
3588
3606
  if (process.platform !== "linux") return false;
3589
3607
  if (linuxSecretAvailability !== null) return linuxSecretAvailability;
3590
3608
  try {
3591
- execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3609
+ execSync3("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3592
3610
  } catch {
3593
3611
  linuxSecretAvailability = false;
3594
3612
  return false;
3595
3613
  }
3596
3614
  try {
3597
- execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
3615
+ execSync3("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
3598
3616
  linuxSecretAvailability = true;
3599
3617
  } catch {
3600
3618
  linuxSecretAvailability = false;
@@ -3618,7 +3636,7 @@ function macKeychainGet(service = SERVICE) {
3618
3636
  if (!nativeKeychainAllowed()) return null;
3619
3637
  if (process.platform !== "darwin") return null;
3620
3638
  try {
3621
- return execSync2(
3639
+ return execSync3(
3622
3640
  `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
3623
3641
  { encoding: "utf-8", timeout: 5e3 }
3624
3642
  ).trim();
@@ -3631,13 +3649,13 @@ function macKeychainSet(value, service = SERVICE) {
3631
3649
  if (process.platform !== "darwin") return false;
3632
3650
  try {
3633
3651
  try {
3634
- execSync2(
3652
+ execSync3(
3635
3653
  `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3636
3654
  { timeout: 5e3 }
3637
3655
  );
3638
3656
  } catch {
3639
3657
  }
3640
- execSync2(
3658
+ execSync3(
3641
3659
  `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
3642
3660
  { timeout: 5e3 }
3643
3661
  );
@@ -3650,7 +3668,7 @@ function macKeychainDelete(service = SERVICE) {
3650
3668
  if (!nativeKeychainAllowed()) return false;
3651
3669
  if (process.platform !== "darwin") return false;
3652
3670
  try {
3653
- execSync2(
3671
+ execSync3(
3654
3672
  `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
3655
3673
  { timeout: 5e3 }
3656
3674
  );
@@ -3662,7 +3680,7 @@ function macKeychainDelete(service = SERVICE) {
3662
3680
  function linuxSecretGet(service = SERVICE) {
3663
3681
  if (!linuxSecretAvailable()) return null;
3664
3682
  try {
3665
- return execSync2(
3683
+ return execSync3(
3666
3684
  `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3667
3685
  { encoding: "utf-8", timeout: 5e3 }
3668
3686
  ).trim();
@@ -3673,7 +3691,7 @@ function linuxSecretGet(service = SERVICE) {
3673
3691
  function linuxSecretSet(value, service = SERVICE) {
3674
3692
  if (!linuxSecretAvailable()) return false;
3675
3693
  try {
3676
- execSync2(
3694
+ execSync3(
3677
3695
  `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3678
3696
  { timeout: 5e3 }
3679
3697
  );
@@ -3686,7 +3704,7 @@ function linuxSecretDelete(service = SERVICE) {
3686
3704
  if (!nativeKeychainAllowed()) return false;
3687
3705
  if (process.platform !== "linux") return false;
3688
3706
  try {
3689
- execSync2(
3707
+ execSync3(
3690
3708
  `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
3691
3709
  { timeout: 5e3 }
3692
3710
  );
@@ -3705,7 +3723,7 @@ async function tryKeytar() {
3705
3723
  }
3706
3724
  function deriveMachineKey() {
3707
3725
  try {
3708
- const crypto20 = __require("crypto");
3726
+ const crypto21 = __require("crypto");
3709
3727
  const material = [
3710
3728
  os5.hostname(),
3711
3729
  os5.userInfo().username,
@@ -3714,7 +3732,7 @@ function deriveMachineKey() {
3714
3732
  // Machine ID on Linux (stable across reboots)
3715
3733
  process.platform === "linux" ? readMachineId() : ""
3716
3734
  ].join("|");
3717
- return crypto20.createHash("sha256").update(material).digest();
3735
+ return crypto21.createHash("sha256").update(material).digest();
3718
3736
  } catch {
3719
3737
  return null;
3720
3738
  }
@@ -3728,9 +3746,9 @@ function readMachineId() {
3728
3746
  }
3729
3747
  }
3730
3748
  function encryptWithMachineKey(plaintext, machineKey) {
3731
- const crypto20 = __require("crypto");
3732
- const iv = crypto20.randomBytes(12);
3733
- const cipher = crypto20.createCipheriv("aes-256-gcm", machineKey, iv);
3749
+ const crypto21 = __require("crypto");
3750
+ const iv = crypto21.randomBytes(12);
3751
+ const cipher = crypto21.createCipheriv("aes-256-gcm", machineKey, iv);
3734
3752
  let encrypted = cipher.update(plaintext, "utf-8", "base64");
3735
3753
  encrypted += cipher.final("base64");
3736
3754
  const authTag = cipher.getAuthTag().toString("base64");
@@ -3739,13 +3757,13 @@ function encryptWithMachineKey(plaintext, machineKey) {
3739
3757
  function decryptWithMachineKey(encrypted, machineKey) {
3740
3758
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3741
3759
  try {
3742
- const crypto20 = __require("crypto");
3760
+ const crypto21 = __require("crypto");
3743
3761
  const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3744
3762
  if (parts.length !== 3) return null;
3745
3763
  const [ivB64, tagB64, cipherB64] = parts;
3746
3764
  const iv = Buffer.from(ivB64, "base64");
3747
3765
  const authTag = Buffer.from(tagB64, "base64");
3748
- const decipher = crypto20.createDecipheriv("aes-256-gcm", machineKey, iv);
3766
+ const decipher = crypto21.createDecipheriv("aes-256-gcm", machineKey, iv);
3749
3767
  decipher.setAuthTag(authTag);
3750
3768
  let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3751
3769
  decrypted += decipher.final("utf-8");
@@ -4783,6 +4801,24 @@ var init_platform_procedures = __esm({
4783
4801
  priority: "p0",
4784
4802
  content: "When an agent encounters a suspected Exe OS bug, update breakage, MCP/tool failure, installer issue, memory/orchestration defect, or customer-local patch need, it MUST use create_bug_report. Do this before or alongside any local workaround so the report reaches AskExe support directly via the customer's license. Do NOT ask the founder for permission to file a required bug report. If create_bug_report is deferred/lazy-loaded, load it and call it. If it is unavailable in the live MCP surface, report 'create_bug_report unavailable in this session' and save a local report in exe/output \u2014 never claim the tool does not exist unless the live MCP surface was checked. If upstream delivery fails, call support_test (MCP) and include its result in the local report so AskExe can distinguish customer setup, license provisioning, and server intake issues; only ask the founder to run `exe-os support test` if MCP is disconnected/unavailable. Classify first: upstream_bug = reproducible exe-os/platform defect; customer_customization = identity, behavior, procedure, config, branding, workflow preference that belongs in customer-owned layers; emergency_hotfix = temporary local patch. For upstream bugs/emergency hotfixes include version, repro steps, expected/actual, files changed, workaround, and local diff summary. Avoid permanent platform-code patches unless founder approves; if a hotfix is unavoidable, document it in the bug report and re-check after npm update."
4785
4803
  },
4804
+ {
4805
+ title: "Bug report status check \u2014 surface available fixes on boot",
4806
+ domain: "support",
4807
+ priority: "p1",
4808
+ content: "Once per session (COO boot only, never repeat), call list_my_bug_reports to check if any previously filed bug reports have been fixed by AskExe. If any report has status 'fixed' with a fixed_version, surface it to the founder immediately: '\u{1F527} N bug fix(es) available \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no reports exist or none are fixed, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4809
+ },
4810
+ {
4811
+ title: "Feature request triage \u2014 upstream feature vs local customization",
4812
+ domain: "support",
4813
+ priority: "p0",
4814
+ content: "When an agent or founder identifies a desired capability that exe-os does not yet provide, the COO (or equivalent coordinator) must decide: is this a local customization (identity, behavior, procedure, config, branding, workflow preference that can be configured in customer-owned layers) or an upstream feature request (a platform capability that requires changes to exe-os code, shipped via npm update)? Local customizations: implement immediately using store_behavior, update_identity, company_procedure, or config changes. Upstream features: use create_feature_request to submit to AskExe. Include use case, business impact, and current workaround. Do NOT ask the founder for permission to file a feature request \u2014 file it proactively when the need is clear."
4815
+ },
4816
+ {
4817
+ title: "Feature request status check \u2014 surface shipped features on boot",
4818
+ domain: "support",
4819
+ priority: "p1",
4820
+ content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4821
+ },
4786
4822
  // --- Operations ---
4787
4823
  {
4788
4824
  title: "Managers must supervise deployed workers",
@@ -6246,7 +6282,7 @@ __export(project_name_exports, {
6246
6282
  _resetCache: () => _resetCache,
6247
6283
  getProjectName: () => getProjectName
6248
6284
  });
6249
- import { execSync as execSync3 } from "child_process";
6285
+ import { execSync as execSync4 } from "child_process";
6250
6286
  import path10 from "path";
6251
6287
  function getProjectName(cwd) {
6252
6288
  const dir = cwd ?? process.cwd();
@@ -6254,7 +6290,7 @@ function getProjectName(cwd) {
6254
6290
  try {
6255
6291
  let repoRoot;
6256
6292
  try {
6257
- const gitCommonDir = execSync3("git rev-parse --path-format=absolute --git-common-dir", {
6293
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
6258
6294
  cwd: dir,
6259
6295
  encoding: "utf8",
6260
6296
  timeout: 2e3,
@@ -6262,7 +6298,7 @@ function getProjectName(cwd) {
6262
6298
  }).trim();
6263
6299
  repoRoot = path10.dirname(gitCommonDir);
6264
6300
  } catch {
6265
- repoRoot = execSync3("git rev-parse --show-toplevel", {
6301
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
6266
6302
  cwd: dir,
6267
6303
  encoding: "utf8",
6268
6304
  timeout: 2e3,
@@ -6296,14 +6332,14 @@ var file_grep_exports = {};
6296
6332
  __export(file_grep_exports, {
6297
6333
  grepProjectFiles: () => grepProjectFiles
6298
6334
  });
6299
- import { execSync as execSync4 } from "child_process";
6335
+ import { execSync as execSync5 } from "child_process";
6300
6336
  import { readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync4, existsSync as existsSync10 } from "fs";
6301
6337
  import path11 from "path";
6302
6338
  import crypto2 from "crypto";
6303
6339
  function hasRipgrep() {
6304
6340
  if (_hasRg === null) {
6305
6341
  try {
6306
- execSync4("rg --version", { stdio: "ignore", timeout: 2e3 });
6342
+ execSync5("rg --version", { stdio: "ignore", timeout: 2e3 });
6307
6343
  _hasRg = true;
6308
6344
  } catch {
6309
6345
  _hasRg = false;
@@ -6369,7 +6405,7 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
6369
6405
  const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
6370
6406
  const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
6371
6407
  const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
6372
- const output = execSync4(cmd, {
6408
+ const output = execSync5(cmd, {
6373
6409
  cwd: projectRoot,
6374
6410
  encoding: "utf8",
6375
6411
  timeout: 3e3,
@@ -6384,12 +6420,12 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
6384
6420
  const matchCount = parseInt(line.slice(colonIdx + 1));
6385
6421
  if (isNaN(matchCount) || matchCount === 0) continue;
6386
6422
  try {
6387
- const firstMatch = execSync4(
6423
+ const firstMatch = execSync5(
6388
6424
  `rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
6389
6425
  { cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
6390
6426
  ).trim();
6391
6427
  const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
6392
- const totalLines = execSync4(`wc -l < '${filePath}'`, {
6428
+ const totalLines = execSync5(`wc -l < '${filePath}'`, {
6393
6429
  cwd: projectRoot,
6394
6430
  encoding: "utf8",
6395
6431
  timeout: 1e3
@@ -7303,10 +7339,10 @@ async function hybridSearch(queryText, agentId, options) {
7303
7339
  };
7304
7340
  try {
7305
7341
  const fs = await import("fs");
7306
- const path55 = await import("path");
7342
+ const path56 = await import("path");
7307
7343
  const os21 = await import("os");
7308
- const logPath = path55.join(os21.homedir(), ".exe-os", "search-quality.jsonl");
7309
- fs.mkdirSync(path55.dirname(logPath), { recursive: true });
7344
+ const logPath = path56.join(os21.homedir(), ".exe-os", "search-quality.jsonl");
7345
+ fs.mkdirSync(path56.dirname(logPath), { recursive: true });
7310
7346
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
7311
7347
  } catch {
7312
7348
  }
@@ -7740,7 +7776,7 @@ var init_hybrid_search = __esm({
7740
7776
  });
7741
7777
 
7742
7778
  // src/lib/session-key.ts
7743
- import { execSync as execSync5 } from "child_process";
7779
+ import { execSync as execSync6 } from "child_process";
7744
7780
  function normalizeCommand(command) {
7745
7781
  const trimmed = command.trim().toLowerCase();
7746
7782
  const parts = trimmed.split(/[\\/]/);
@@ -7759,7 +7795,7 @@ function resolveRuntimeProcess() {
7759
7795
  let pid = process.ppid;
7760
7796
  for (let i = 0; i < 10; i++) {
7761
7797
  try {
7762
- const info = execSync5(`ps -p ${pid} -o ppid=,comm=`, {
7798
+ const info = execSync6(`ps -p ${pid} -o ppid=,comm=`, {
7763
7799
  encoding: "utf8",
7764
7800
  timeout: 2e3
7765
7801
  }).trim();
@@ -7831,7 +7867,7 @@ __export(active_agent_exports, {
7831
7867
  writeActiveAgent: () => writeActiveAgent
7832
7868
  });
7833
7869
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
7834
- import { execSync as execSync6 } from "child_process";
7870
+ import { execSync as execSync7 } from "child_process";
7835
7871
  import path12 from "path";
7836
7872
  function isNameWithOptionalInstance(candidate, baseName) {
7837
7873
  if (candidate === baseName) return true;
@@ -7925,7 +7961,7 @@ function getActiveAgent() {
7925
7961
  } catch {
7926
7962
  }
7927
7963
  try {
7928
- const sessionName = execSync6(
7964
+ const sessionName = execSync7(
7929
7965
  "tmux display-message -p '#{session_name}' 2>/dev/null",
7930
7966
  { encoding: "utf8", timeout: 2e3 }
7931
7967
  ).trim();
@@ -8599,8 +8635,8 @@ __export(wiki_client_exports, {
8599
8635
  listDocuments: () => listDocuments,
8600
8636
  listWorkspaces: () => listWorkspaces
8601
8637
  });
8602
- async function wikiFetch(config2, path55, method = "GET", body) {
8603
- const url = `${config2.baseUrl}/api/v1${path55}`;
8638
+ async function wikiFetch(config2, path56, method = "GET", body) {
8639
+ const url = `${config2.baseUrl}/api/v1${path56}`;
8604
8640
  const headers = {
8605
8641
  Authorization: `Bearer ${config2.apiKey}`,
8606
8642
  "Content-Type": "application/json"
@@ -8633,7 +8669,7 @@ async function wikiFetch(config2, path55, method = "GET", body) {
8633
8669
  }
8634
8670
  }
8635
8671
  if (!response.ok) {
8636
- throw new Error(`Wiki API ${method} ${path55}: ${response.status} ${response.statusText}`);
8672
+ throw new Error(`Wiki API ${method} ${path56}: ${response.status} ${response.statusText}`);
8637
8673
  }
8638
8674
  return response.json();
8639
8675
  } finally {
@@ -9291,14 +9327,14 @@ var init_transport = __esm({
9291
9327
  });
9292
9328
 
9293
9329
  // src/lib/cc-agent-support.ts
9294
- import { execSync as execSync7 } from "child_process";
9330
+ import { execSync as execSync8 } from "child_process";
9295
9331
  function _resetCcAgentSupportCache() {
9296
9332
  _cachedSupport = null;
9297
9333
  }
9298
9334
  function claudeSupportsAgentFlag() {
9299
9335
  if (_cachedSupport !== null) return _cachedSupport;
9300
9336
  try {
9301
- const helpOutput = execSync7("claude --help 2>&1", {
9337
+ const helpOutput = execSync8("claude --help 2>&1", {
9302
9338
  encoding: "utf-8",
9303
9339
  timeout: 5e3
9304
9340
  });
@@ -10235,7 +10271,7 @@ __export(tmux_routing_exports, {
10235
10271
  spawnEmployee: () => spawnEmployee,
10236
10272
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
10237
10273
  });
10238
- import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
10274
+ import { execFileSync as execFileSync2, execSync as execSync9 } from "child_process";
10239
10275
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync17, appendFileSync, readdirSync as readdirSync5 } from "fs";
10240
10276
  import path21 from "path";
10241
10277
  import os10 from "os";
@@ -10956,7 +10992,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
10956
10992
  let booted = false;
10957
10993
  for (let i = 0; i < 30; i++) {
10958
10994
  try {
10959
- execSync8("sleep 0.5");
10995
+ execSync9("sleep 0.5");
10960
10996
  } catch {
10961
10997
  }
10962
10998
  try {
@@ -11204,7 +11240,7 @@ __export(tasks_crud_exports, {
11204
11240
  import crypto7 from "crypto";
11205
11241
  import path23 from "path";
11206
11242
  import os12 from "os";
11207
- import { execSync as execSync9 } from "child_process";
11243
+ import { execSync as execSync10 } from "child_process";
11208
11244
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
11209
11245
  import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
11210
11246
  async function writeCheckpoint(input) {
@@ -11549,14 +11585,14 @@ function isTmuxSessionAlive(identifier) {
11549
11585
  if (!identifier || identifier === "unknown") return true;
11550
11586
  try {
11551
11587
  if (identifier.startsWith("%")) {
11552
- const output = execSync9("tmux list-panes -a -F '#{pane_id}'", {
11588
+ const output = execSync10("tmux list-panes -a -F '#{pane_id}'", {
11553
11589
  timeout: 2e3,
11554
11590
  encoding: "utf8",
11555
11591
  stdio: ["pipe", "pipe", "pipe"]
11556
11592
  });
11557
11593
  return output.split("\n").some((l) => l.trim() === identifier);
11558
11594
  } else {
11559
- execSync9(`tmux has-session -t ${JSON.stringify(identifier)}`, {
11595
+ execSync10(`tmux has-session -t ${JSON.stringify(identifier)}`, {
11560
11596
  timeout: 2e3,
11561
11597
  stdio: ["pipe", "pipe", "pipe"]
11562
11598
  });
@@ -11565,7 +11601,7 @@ function isTmuxSessionAlive(identifier) {
11565
11601
  } catch {
11566
11602
  if (identifier.startsWith("%")) return true;
11567
11603
  try {
11568
- execSync9("tmux list-sessions", {
11604
+ execSync10("tmux list-sessions", {
11569
11605
  timeout: 2e3,
11570
11606
  stdio: ["pipe", "pipe", "pipe"]
11571
11607
  });
@@ -11580,12 +11616,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
11580
11616
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
11581
11617
  try {
11582
11618
  const since = new Date(taskCreatedAt).toISOString();
11583
- const branch = execSync9(
11619
+ const branch = execSync10(
11584
11620
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
11585
11621
  { encoding: "utf8", timeout: 3e3 }
11586
11622
  ).trim();
11587
11623
  const branchArg = branch && branch !== "HEAD" ? branch : "";
11588
- const commitCount = execSync9(
11624
+ const commitCount = execSync10(
11589
11625
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
11590
11626
  { encoding: "utf8", timeout: 5e3 }
11591
11627
  ).trim();
@@ -12768,12 +12804,14 @@ On EVERY new conversation, before doing anything else:
12768
12804
  1. **Memory scan**: Run recall_my_memory with broad queries \u2014 "project", "client", "pipeline", "campaign", "deal", "decision", "blocker". Summarize what you find.
12769
12805
  2. **Task scan**: Run list_tasks to see what's open, in progress, blocked, or needs review across all employees.
12770
12806
  3. **Team check**: Run ask_team_memory for recent activity from CTO/CMO/engineers.
12771
- 4. **Present the brief**: Give the founder a concise status report:
12807
+ 4. **Bug fix check** (one-time, never repeat): Call list_my_bug_reports to see if AskExe has fixed any previously filed bugs. If any have status "fixed" with a fixed_version, tell the founder: "\u{1F527} N bug fix(es) available \u2014 run \`exe-os update\` to get version X.Y.Z." Skip silently if none or if the call fails.
12808
+ 5. **Present the brief**: Give the founder a concise status report:
12772
12809
  - What's active and progressing
12773
12810
  - What's blocked and needs attention
12774
12811
  - What decisions are pending
12812
+ - Available bug fixes (from step 4, if any)
12775
12813
  - What you recommend doing next
12776
- 5. Then ask: "What's the priority?"
12814
+ 6. Then ask: "What's the priority?"
12777
12815
 
12778
12816
  If this is your FIRST ever conversation (few or no prior memories):
12779
12817
  - Search more broadly: "product", "SEO", "meeting", "strategy", "revenue"
@@ -12793,6 +12831,8 @@ Never say "I have no memories" without first searching broadly. Your memory may
12793
12831
  - **get_identity** \u2014 read any agent's identity for coordination
12794
12832
  - **set_agent_config** \u2014 view or change which tool (Claude Code, Codex, OpenCode) and model each agent uses. Call with no args to show all agents' current settings. Call with agent_id + runtime + model to change.
12795
12833
  - **send_message** \u2014 direct intercom to employees
12834
+ - **create_bug_report** \u2014 file a bug when you encounter an Exe OS platform issue
12835
+ - **list_my_bug_reports** \u2014 check status of filed bugs (boot check: surface available fixes to founder)
12796
12836
  ${PLAN_MODE_COMPAT}
12797
12837
  ## Completion Workflow
12798
12838
 
@@ -13957,13 +13997,13 @@ var init_crdt_sync = __esm({
13957
13997
  });
13958
13998
 
13959
13999
  // src/lib/tmux-status.ts
13960
- import { execSync as execSync12 } from "child_process";
14000
+ import { execSync as execSync13 } from "child_process";
13961
14001
  function inTmux() {
13962
14002
  if (process.env.TMUX || process.env.TMUX_PANE) return true;
13963
14003
  const term = process.env.TERM ?? "";
13964
14004
  if (term.startsWith("tmux") || term.startsWith("screen")) return true;
13965
14005
  try {
13966
- execSync12("tmux display-message -p '#{session_name}' 2>/dev/null", {
14006
+ execSync13("tmux display-message -p '#{session_name}' 2>/dev/null", {
13967
14007
  encoding: "utf8",
13968
14008
  timeout: 2e3
13969
14009
  });
@@ -13973,12 +14013,12 @@ function inTmux() {
13973
14013
  try {
13974
14014
  let pid = process.ppid;
13975
14015
  for (let depth = 0; depth < 8 && pid > 1; depth++) {
13976
- const comm = execSync12(`ps -p ${pid} -o comm= 2>/dev/null`, {
14016
+ const comm = execSync13(`ps -p ${pid} -o comm= 2>/dev/null`, {
13977
14017
  encoding: "utf8",
13978
14018
  timeout: 1e3
13979
14019
  }).trim();
13980
14020
  if (/tmux/.test(comm)) return true;
13981
- const ppid = execSync12(`ps -p ${pid} -o ppid= 2>/dev/null`, {
14021
+ const ppid = execSync13(`ps -p ${pid} -o ppid= 2>/dev/null`, {
13982
14022
  encoding: "utf8",
13983
14023
  timeout: 1e3
13984
14024
  }).trim();
@@ -13991,7 +14031,7 @@ function inTmux() {
13991
14031
  }
13992
14032
  function listTmuxSessions() {
13993
14033
  try {
13994
- const out = execSync12("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
14034
+ const out = execSync13("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
13995
14035
  encoding: "utf8",
13996
14036
  timeout: 3e3
13997
14037
  });
@@ -14002,7 +14042,7 @@ function listTmuxSessions() {
14002
14042
  }
14003
14043
  function capturePaneLines(windowName, lines = 10) {
14004
14044
  try {
14005
- const out = execSync12(
14045
+ const out = execSync13(
14006
14046
  `tmux capture-pane -t ${JSON.stringify(windowName)} -p 2>/dev/null | tail -${lines}`,
14007
14047
  { encoding: "utf8", timeout: 3e3 }
14008
14048
  );
@@ -14013,7 +14053,7 @@ function capturePaneLines(windowName, lines = 10) {
14013
14053
  }
14014
14054
  function getPaneCwd(windowName) {
14015
14055
  try {
14016
- const out = execSync12(
14056
+ const out = execSync13(
14017
14057
  `tmux display-message -t ${JSON.stringify(windowName)} -p '#{pane_current_path}' 2>/dev/null`,
14018
14058
  { encoding: "utf8", timeout: 3e3 }
14019
14059
  );
@@ -14024,7 +14064,7 @@ function getPaneCwd(windowName) {
14024
14064
  }
14025
14065
  function projectFromPath(dir) {
14026
14066
  try {
14027
- const root = execSync12("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
14067
+ const root = execSync13("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
14028
14068
  encoding: "utf8",
14029
14069
  timeout: 3e3
14030
14070
  }).trim();
@@ -14083,7 +14123,7 @@ function getEmployeeStatuses(employeeNames) {
14083
14123
  }
14084
14124
  let paneAlive = true;
14085
14125
  try {
14086
- const paneStatus = execSync12(
14126
+ const paneStatus = execSync13(
14087
14127
  `tmux list-panes -t ${JSON.stringify(sessionName)} -F '#{pane_dead}' 2>/dev/null`,
14088
14128
  { encoding: "utf8", timeout: 3e3 }
14089
14129
  ).trim();
@@ -14190,7 +14230,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14190
14230
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14191
14231
  import { spawn as spawn4 } from "child_process";
14192
14232
  import { existsSync as existsSync41, openSync as openSync4, mkdirSync as mkdirSync23, closeSync as closeSync4, readFileSync as readFileSync36 } from "fs";
14193
- import path54 from "path";
14233
+ import path55 from "path";
14194
14234
  import os20 from "os";
14195
14235
  import { fileURLToPath as fileURLToPath7 } from "url";
14196
14236
 
@@ -18578,12 +18618,12 @@ function registerExportGraph(server2) {
18578
18618
  }
18579
18619
  const html = await exportGraphHTML(client);
18580
18620
  const fs = await import("fs");
18581
- const path55 = await import("path");
18621
+ const path56 = await import("path");
18582
18622
  const os21 = await import("os");
18583
- const outDir = path55.join(os21.homedir(), ".exe-os", "exports");
18623
+ const outDir = path56.join(os21.homedir(), ".exe-os", "exports");
18584
18624
  fs.mkdirSync(outDir, { recursive: true });
18585
18625
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18586
- const filePath = path55.join(outDir, `graph-${timestamp}.html`);
18626
+ const filePath = path56.join(outDir, `graph-${timestamp}.html`);
18587
18627
  fs.writeFileSync(filePath, html, "utf-8");
18588
18628
  return {
18589
18629
  content: [
@@ -19953,7 +19993,7 @@ import { z as z55 } from "zod";
19953
19993
  init_tmux_routing();
19954
19994
  init_task_scope();
19955
19995
  init_employees();
19956
- import { execSync as execSync10 } from "child_process";
19996
+ import { execSync as execSync11 } from "child_process";
19957
19997
  import { existsSync as existsSync25, readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "fs";
19958
19998
  import { homedir as homedir5 } from "os";
19959
19999
  import { join as join3 } from "path";
@@ -23562,9 +23602,9 @@ var HostingerApiClient = class {
23562
23602
  }
23563
23603
  this.lastRequestTime = Date.now();
23564
23604
  }
23565
- async request(method, path55, body) {
23605
+ async request(method, path56, body) {
23566
23606
  await this.rateLimit();
23567
- const url = `${this.baseUrl}${path55}`;
23607
+ const url = `${this.baseUrl}${path56}`;
23568
23608
  const headers = {
23569
23609
  Authorization: `Bearer ${this.apiKey}`,
23570
23610
  "Content-Type": "application/json",
@@ -23637,8 +23677,8 @@ async function requestCloudflare(cfApiToken, zoneId, options) {
23637
23677
  }
23638
23678
  return envelope.result;
23639
23679
  }
23640
- function buildUrl(zoneId, path55 = "/dns_records", query) {
23641
- const normalizedPath = path55.startsWith("/") ? path55 : `/${path55}`;
23680
+ function buildUrl(zoneId, path56 = "/dns_records", query) {
23681
+ const normalizedPath = path56.startsWith("/") ? path56 : `/${path56}`;
23642
23682
  const url = new URL(
23643
23683
  `${CLOUDFLARE_API_BASE_URL}/zones/${zoneId}${normalizedPath}`
23644
23684
  );
@@ -24288,7 +24328,7 @@ function isScheduledTrigger(trigger) {
24288
24328
  init_database();
24289
24329
  init_store();
24290
24330
  import crypto16 from "crypto";
24291
- import { execSync as execSync11 } from "child_process";
24331
+ import { execSync as execSync12 } from "child_process";
24292
24332
  var CRON_FIELD = /^[\d*/,\-]+$/;
24293
24333
  function isValidCron(cron) {
24294
24334
  const fields = cron.trim().split(/\s+/);
@@ -24414,7 +24454,7 @@ function addToCrontab(id, cron, prompt, projectDir) {
24414
24454
  const cwd = projectDir ? `cd ${JSON.stringify(projectDir)} && ` : "";
24415
24455
  const escapedPrompt = prompt.replace(/"/g, '\\"');
24416
24456
  const entry = `${cron} ${cwd}claude -p --dangerously-skip-permissions "${escapedPrompt}" # exe-schedule:${id}`;
24417
- execSync11(
24457
+ execSync12(
24418
24458
  `(crontab -l 2>/dev/null; echo ${JSON.stringify(entry)}) | crontab -`,
24419
24459
  { timeout: 5e3, stdio: "ignore" }
24420
24460
  );
@@ -27349,14 +27389,206 @@ Upstream status: ${upstreamStatus}`
27349
27389
  );
27350
27390
  }
27351
27391
 
27352
- // src/mcp/tools/support.ts
27392
+ // src/mcp/tools/create-feature-request.ts
27393
+ init_embedder();
27394
+ init_active_agent();
27395
+ init_config();
27396
+ init_license();
27397
+ init_store();
27353
27398
  import { z as z89 } from "zod";
27399
+ import crypto19 from "crypto";
27400
+ import { mkdir as mkdir7, writeFile as writeFile8 } from "fs/promises";
27401
+ import path50 from "path";
27402
+ var CATEGORY = z89.enum([
27403
+ "upstream_feature",
27404
+ "local_customization",
27405
+ "integration",
27406
+ "unclear"
27407
+ ]);
27408
+ var PRIORITY = z89.enum(["p0", "p1", "p2", "p3"]);
27409
+ function slugify3(input) {
27410
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "feature-request";
27411
+ }
27412
+ function section2(title, body) {
27413
+ return `## ${title}
27414
+
27415
+ ${body?.trim() || "Not provided"}`;
27416
+ }
27417
+ function buildMarkdown2(input) {
27418
+ return [
27419
+ `# Feature Request \u2014 ${input.title}`,
27420
+ "",
27421
+ `id: ${input.id}`,
27422
+ `category: ${input.category}`,
27423
+ `priority: ${input.priority}`,
27424
+ `filed_by: ${input.agentId} (${input.agentRole})`,
27425
+ `package_version: ${input.packageVersion}`,
27426
+ `project: ${input.projectName ?? "unknown"}`,
27427
+ `created_at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
27428
+ "",
27429
+ section2("Description", input.description),
27430
+ section2("Use Case", input.useCase),
27431
+ section2("Current Workaround", input.currentWorkaround),
27432
+ section2("Proposed Solution", input.proposedSolution),
27433
+ section2("Business Impact", input.businessImpact)
27434
+ ].join("\n");
27435
+ }
27436
+ async function maybeSendUpstream2(payload) {
27437
+ const config2 = await loadConfig();
27438
+ const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
27439
+ const endpoint2 = config2.support?.featureRequestEndpoint || process.env.EXE_FEATURE_REQUEST_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support/feature-requests` : "https://askexe.com/v1/support/feature-requests");
27440
+ const token = config2.support?.featureRequestToken || process.env.EXE_FEATURE_REQUEST_TOKEN;
27441
+ const licenseKey = loadLicense() || process.env.EXE_LICENSE_KEY || config2.cloud?.apiKey;
27442
+ const licenseToken = readCachedLicenseToken();
27443
+ if (!endpoint2) {
27444
+ return "not_configured";
27445
+ }
27446
+ try {
27447
+ const parsed = new URL(endpoint2);
27448
+ if (parsed.protocol !== "https:" && !["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
27449
+ return "failed: insecure endpoint rejected";
27450
+ }
27451
+ const response = await fetch(parsed, {
27452
+ method: "POST",
27453
+ headers: {
27454
+ "content-type": "application/json",
27455
+ ...token ? { authorization: `Bearer ${token}` } : {},
27456
+ ...licenseKey ? { "x-exe-license-key": licenseKey } : {},
27457
+ ...licenseToken ? { "x-exe-license-token": licenseToken } : {}
27458
+ },
27459
+ body: JSON.stringify(payload),
27460
+ signal: AbortSignal.timeout(1e4)
27461
+ });
27462
+ if (!response.ok) return `failed: HTTP ${response.status}`;
27463
+ return "sent";
27464
+ } catch (err) {
27465
+ return `failed: ${err instanceof Error ? err.message : String(err)}`;
27466
+ }
27467
+ }
27468
+ function registerCreateFeatureRequest(server2) {
27469
+ server2.registerTool(
27470
+ "create_feature_request",
27471
+ {
27472
+ title: "Create Feature Request",
27473
+ description: "File a feature request for exe-os: upstream_feature (requires platform changes), local_customization (configurable in customer layers), integration (third-party), or unclear. Writes a local report, stores memory, and optionally sends to AskExe.",
27474
+ inputSchema: {
27475
+ title: z89.string().min(3).describe("Short descriptive title"),
27476
+ category: CATEGORY.describe(
27477
+ "upstream_feature = platform capability change; local_customization = configurable in customer layers; integration = third-party connector; unclear = needs product triage"
27478
+ ),
27479
+ priority: PRIORITY.default("p2").describe("p0 critical \u2192 p3 low"),
27480
+ description: z89.string().min(10).describe("What capability is needed and why"),
27481
+ use_case: z89.string().optional().describe("Concrete scenario where this feature would be used"),
27482
+ current_workaround: z89.string().optional().describe("How the need is currently addressed, if at all"),
27483
+ proposed_solution: z89.string().optional().describe("Suggested implementation approach"),
27484
+ business_impact: z89.string().optional().describe("Impact on business/workflow if this ships"),
27485
+ package_version: z89.string().optional().describe("Installed @askexenow/exe-os version"),
27486
+ project_name: z89.string().optional().describe("Project/customer context"),
27487
+ send_upstream: z89.boolean().default(true).describe("Attempt to POST to configured AskExe support endpoint")
27488
+ }
27489
+ },
27490
+ async ({
27491
+ title,
27492
+ category,
27493
+ priority,
27494
+ description,
27495
+ use_case,
27496
+ current_workaround,
27497
+ proposed_solution,
27498
+ business_impact,
27499
+ package_version,
27500
+ project_name,
27501
+ send_upstream
27502
+ }) => {
27503
+ const { agentId, agentRole } = getActiveAgent();
27504
+ const id = crypto19.randomUUID();
27505
+ const version = package_version ?? "unknown";
27506
+ const markdown = buildMarkdown2({
27507
+ id,
27508
+ title,
27509
+ category,
27510
+ priority,
27511
+ agentId,
27512
+ agentRole,
27513
+ packageVersion: version,
27514
+ description,
27515
+ useCase: use_case,
27516
+ currentWorkaround: current_workaround,
27517
+ proposedSolution: proposed_solution,
27518
+ businessImpact: business_impact,
27519
+ projectName: project_name
27520
+ });
27521
+ const outDir = path50.join(EXE_AI_DIR, "feature-requests");
27522
+ await mkdir7(outDir, { recursive: true });
27523
+ const reportPath = path50.join(outDir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}-${slugify3(title)}-${id.slice(0, 8)}.md`);
27524
+ await writeFile8(reportPath, markdown, "utf-8");
27525
+ let vector = null;
27526
+ try {
27527
+ vector = await embed(markdown);
27528
+ } catch {
27529
+ vector = null;
27530
+ }
27531
+ await writeMemory({
27532
+ id,
27533
+ agent_id: agentId,
27534
+ agent_role: agentRole,
27535
+ session_id: process.env.SESSION_ID ?? "manual",
27536
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
27537
+ tool_name: "create_feature_request",
27538
+ project_name: project_name ?? "support",
27539
+ has_error: false,
27540
+ raw_text: markdown,
27541
+ vector,
27542
+ source_path: reportPath,
27543
+ source_type: "feature_request",
27544
+ memory_type: "feature_request",
27545
+ tier: 1,
27546
+ importance: priority === "p0" ? 10 : priority === "p1" ? 9 : priority === "p2" ? 7 : 5,
27547
+ intent: "request",
27548
+ domain: "support",
27549
+ file_paths: null
27550
+ });
27551
+ await flushBatch();
27552
+ const upstreamStatus = send_upstream ? await maybeSendUpstream2({
27553
+ id,
27554
+ title,
27555
+ category,
27556
+ priority,
27557
+ description,
27558
+ use_case,
27559
+ current_workaround,
27560
+ proposed_solution,
27561
+ business_impact,
27562
+ package_version: version,
27563
+ project_name,
27564
+ agent_id: agentId,
27565
+ agent_role: agentRole
27566
+ }) : "skipped";
27567
+ return {
27568
+ content: [
27569
+ {
27570
+ type: "text",
27571
+ text: `Feature request created.
27572
+ ID: ${id}
27573
+ Category: ${category}
27574
+ Local report: ${reportPath}
27575
+ Memory stored: ${id}
27576
+ Upstream status: ${upstreamStatus}`
27577
+ }
27578
+ ]
27579
+ };
27580
+ }
27581
+ );
27582
+ }
27583
+
27584
+ // src/mcp/tools/support.ts
27585
+ import { z as z90 } from "zod";
27354
27586
 
27355
27587
  // src/bin/exe-support.ts
27356
27588
  init_config();
27357
27589
  init_license();
27358
27590
  import { mkdirSync as mkdirSync21, readFileSync as readFileSync32, unlinkSync as unlinkSync12, writeFileSync as writeFileSync23 } from "fs";
27359
- import path50 from "path";
27591
+ import path51 from "path";
27360
27592
  import { randomUUID as randomUUID9 } from "crypto";
27361
27593
  var DEFAULT_BUG_ENDPOINT = "https://askexe.com/v1/support/bug-reports";
27362
27594
  var DEFAULT_ADMIN_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
@@ -27477,8 +27709,8 @@ async function resolveEndpoints() {
27477
27709
  return { bugEndpoint, healthEndpoint, adminEndpoint };
27478
27710
  }
27479
27711
  function checkLocalWrite() {
27480
- const dir = path50.join(EXE_AI_DIR, "bug-reports");
27481
- const testPath = path50.join(dir, ".support-write-test");
27712
+ const dir = path51.join(EXE_AI_DIR, "bug-reports");
27713
+ const testPath = path51.join(dir, ".support-write-test");
27482
27714
  try {
27483
27715
  mkdirSync21(dir, { recursive: true, mode: 448 });
27484
27716
  writeFileSync23(testPath, "ok\n", { mode: 384 });
@@ -27494,10 +27726,10 @@ function checkLocalWrite() {
27494
27726
  }
27495
27727
  }
27496
27728
  function writeLocalTestReport(id, project, version) {
27497
- const dir = path50.join(EXE_AI_DIR, "bug-reports");
27729
+ const dir = path51.join(EXE_AI_DIR, "bug-reports");
27498
27730
  mkdirSync21(dir, { recursive: true, mode: 448 });
27499
27731
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
27500
- const filePath = path50.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
27732
+ const filePath = path51.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
27501
27733
  writeFileSync23(filePath, `# TEST \u2014 ${project} support intake
27502
27734
 
27503
27735
  Report ID: ${id}
@@ -27539,15 +27771,15 @@ async function maybeCloseAdmin(id, adminEndpoint, version) {
27539
27771
  }
27540
27772
  }
27541
27773
  function readPackageVersion2() {
27542
- let dir = path50.dirname(new URL(import.meta.url).pathname);
27774
+ let dir = path51.dirname(new URL(import.meta.url).pathname);
27543
27775
  for (let i = 0; i < 6; i++) {
27544
- const pkg = path50.join(dir, "package.json");
27776
+ const pkg = path51.join(dir, "package.json");
27545
27777
  try {
27546
27778
  const parsed = JSON.parse(readFileSync32(pkg, "utf8"));
27547
27779
  if (parsed.version) return parsed.version;
27548
27780
  } catch {
27549
27781
  }
27550
- dir = path50.dirname(dir);
27782
+ dir = path51.dirname(dir);
27551
27783
  }
27552
27784
  return "unknown";
27553
27785
  }
@@ -27573,6 +27805,7 @@ function nextForPostFailure(status2) {
27573
27805
  }
27574
27806
 
27575
27807
  // src/mcp/tools/support.ts
27808
+ init_license();
27576
27809
  function formatRows(rows, mode) {
27577
27810
  const lines = [`exe-os support ${mode}`, ""];
27578
27811
  for (const row of rows) {
@@ -27613,7 +27846,7 @@ function registerSupportTools(server2) {
27613
27846
  title: "Support Test",
27614
27847
  description: "End-to-end support intake smoke test. Files a clearly marked test report upstream and auto-closes it on AskExe machines with admin credentials.",
27615
27848
  inputSchema: {
27616
- project: z89.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
27849
+ project: z90.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
27617
27850
  }
27618
27851
  },
27619
27852
  async ({ project }) => {
@@ -27625,12 +27858,139 @@ function registerSupportTools(server2) {
27625
27858
  };
27626
27859
  }
27627
27860
  );
27861
+ server2.registerTool(
27862
+ "list_my_bug_reports",
27863
+ {
27864
+ title: "My Bug Reports",
27865
+ description: "List bug reports you've filed, scoped to your license. Shows status (open/triaged/fixed/closed), severity, and fixed_version so you know when to update.",
27866
+ inputSchema: {
27867
+ status: z90.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("all").describe("Filter by status. Default: all"),
27868
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
27869
+ }
27870
+ },
27871
+ async ({ status: status2, limit }) => {
27872
+ const licenseKey = loadLicense();
27873
+ const licenseToken = readCachedLicenseToken();
27874
+ if (!licenseKey && !licenseToken) {
27875
+ return {
27876
+ content: [{ type: "text", text: "No license key found. Run `exe-os setup` or `exe-os cloud setup` first." }],
27877
+ isError: true
27878
+ };
27879
+ }
27880
+ const endpoint2 = new URL("https://askexe.com/v1/support/my-reports");
27881
+ endpoint2.searchParams.set("status", status2);
27882
+ endpoint2.searchParams.set("limit", String(limit));
27883
+ const headers = { "content-type": "application/json" };
27884
+ if (licenseKey) headers["x-exe-license-key"] = licenseKey;
27885
+ if (licenseToken) headers["x-exe-license-token"] = licenseToken;
27886
+ try {
27887
+ const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
27888
+ if (!res.ok) {
27889
+ const body = await res.text().catch(() => "");
27890
+ return {
27891
+ content: [{ type: "text", text: `Failed to fetch bug reports: HTTP ${res.status}${body ? ` \u2014 ${body}` : ""}` }],
27892
+ isError: true
27893
+ };
27894
+ }
27895
+ const data = await res.json();
27896
+ if (data.count === 0) {
27897
+ return {
27898
+ content: [{ type: "text", text: `No bug reports found${status2 !== "all" ? ` with status '${status2}'` : ""}.` }],
27899
+ structuredContent: { items: [], count: 0 }
27900
+ };
27901
+ }
27902
+ const lines = [`Bug reports (${data.count}):`, ""];
27903
+ for (const r of data.items) {
27904
+ const sevIcon = r.severity === "p0" ? "\u{1F534}" : r.severity === "p1" ? "\u{1F534}" : r.severity === "p2" ? "\u{1F7E0}" : "\u{1F7E2}";
27905
+ const statusIcon = r.status === "fixed" ? "\u2705" : r.status === "closed" ? "\u2611\uFE0F" : r.status === "triaged" ? "\u{1F50D}" : r.status === "wontfix" ? "\u26D4" : "\u{1F535}";
27906
+ lines.push(`${sevIcon} ${r.severity.toUpperCase()} ${statusIcon} ${r.status} \u2014 ${r.title}`);
27907
+ lines.push(` ID: ${r.id} | Filed: ${r.created_at?.slice(0, 10) ?? "?"}`);
27908
+ if (r.fixed_version) lines.push(` \u{1F527} Fixed in: ${r.fixed_version} \u2014 run \`exe-os update\` to get this fix`);
27909
+ if (r.triage_notes) lines.push(` \u{1F4DD} AskExe: ${r.triage_notes}`);
27910
+ lines.push("");
27911
+ }
27912
+ return {
27913
+ content: [{ type: "text", text: lines.join("\n") }],
27914
+ structuredContent: data
27915
+ };
27916
+ } catch (err) {
27917
+ return {
27918
+ content: [{ type: "text", text: `Error fetching bug reports: ${err instanceof Error ? err.message : String(err)}` }],
27919
+ isError: true
27920
+ };
27921
+ }
27922
+ }
27923
+ );
27924
+ server2.registerTool(
27925
+ "list_my_feature_requests",
27926
+ {
27927
+ title: "My Feature Requests",
27928
+ description: "List feature requests you've filed, scoped to your license. Shows status (open/planned/in_progress/shipped/closed), priority, and shipped_version so you know when to update.",
27929
+ inputSchema: {
27930
+ status: z90.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("all").describe("Filter by status. Default: all"),
27931
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
27932
+ }
27933
+ },
27934
+ async ({ status: status2, limit }) => {
27935
+ const licenseKey = loadLicense();
27936
+ const licenseToken = readCachedLicenseToken();
27937
+ if (!licenseKey && !licenseToken) {
27938
+ return {
27939
+ content: [{ type: "text", text: "No license key found. Run `exe-os setup` or `exe-os cloud setup` first." }],
27940
+ isError: true
27941
+ };
27942
+ }
27943
+ const endpoint2 = new URL("https://askexe.com/v1/support/my-feature-requests");
27944
+ endpoint2.searchParams.set("status", status2);
27945
+ endpoint2.searchParams.set("limit", String(limit));
27946
+ const headers = { "content-type": "application/json" };
27947
+ if (licenseKey) headers["x-exe-license-key"] = licenseKey;
27948
+ if (licenseToken) headers["x-exe-license-token"] = licenseToken;
27949
+ try {
27950
+ const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
27951
+ if (!res.ok) {
27952
+ const body = await res.text().catch(() => "");
27953
+ return {
27954
+ content: [{ type: "text", text: `Failed to fetch feature requests: HTTP ${res.status}${body ? ` \u2014 ${body}` : ""}` }],
27955
+ isError: true
27956
+ };
27957
+ }
27958
+ const data = await res.json();
27959
+ if (data.count === 0) {
27960
+ return {
27961
+ content: [{ type: "text", text: `No feature requests found${status2 !== "all" ? ` with status '${status2}'` : ""}.` }],
27962
+ structuredContent: { items: [], count: 0 }
27963
+ };
27964
+ }
27965
+ const lines = [`Feature requests (${data.count}):`, ""];
27966
+ for (const r of data.items) {
27967
+ const priIcon = r.priority === "p0" ? "\u{1F534}" : r.priority === "p1" ? "\u{1F534}" : r.priority === "p2" ? "\u{1F7E0}" : "\u{1F7E2}";
27968
+ const statusIcon = r.status === "shipped" ? "\u2705" : r.status === "closed" ? "\u2611\uFE0F" : r.status === "planned" ? "\u{1F4CB}" : r.status === "in_progress" ? "\u{1F528}" : r.status === "wontdo" ? "\u26D4" : "\u{1F535}";
27969
+ lines.push(`${priIcon} ${r.priority.toUpperCase()} ${statusIcon} ${r.status} \u2014 ${r.title}`);
27970
+ lines.push(` ID: ${r.id} | Filed: ${r.created_at?.slice(0, 10) ?? "?"}`);
27971
+ if (r.shipped_version) lines.push(` \u{1F680} Shipped in: ${r.shipped_version} \u2014 run \`exe-os update\` to get this feature`);
27972
+ if (r.target_version) lines.push(` \u{1F3AF} Target: ${r.target_version}`);
27973
+ if (r.response_notes) lines.push(` \u{1F4DD} AskExe: ${r.response_notes}`);
27974
+ lines.push("");
27975
+ }
27976
+ return {
27977
+ content: [{ type: "text", text: lines.join("\n") }],
27978
+ structuredContent: data
27979
+ };
27980
+ } catch (err) {
27981
+ return {
27982
+ content: [{ type: "text", text: `Error fetching feature requests: ${err instanceof Error ? err.message : String(err)}` }],
27983
+ isError: true
27984
+ };
27985
+ }
27986
+ }
27987
+ );
27628
27988
  }
27629
27989
 
27630
27990
  // src/mcp/tools/cli-parity.ts
27631
27991
  import { execFile as execFile2 } from "child_process";
27632
27992
  import { promisify as promisify2 } from "util";
27633
- import { z as z90 } from "zod";
27993
+ import { z as z91 } from "zod";
27634
27994
 
27635
27995
  // src/bin/exe-status.ts
27636
27996
  init_employees();
@@ -27691,22 +28051,22 @@ if (isMainModule(import.meta.url)) {
27691
28051
 
27692
28052
  // src/bin/exe-healthcheck.ts
27693
28053
  import { existsSync as existsSync39, readFileSync as readFileSync33, readdirSync as readdirSync14 } from "fs";
27694
- import path51 from "path";
27695
- import { execSync as execSync13 } from "child_process";
28054
+ import path52 from "path";
28055
+ import { execSync as execSync14 } from "child_process";
27696
28056
  import { fileURLToPath as fileURLToPath6 } from "url";
27697
28057
  init_config();
27698
28058
  function findPackageRoot2() {
27699
- let dir = path51.dirname(fileURLToPath6(import.meta.url));
27700
- const { root } = path51.parse(dir);
28059
+ let dir = path52.dirname(fileURLToPath6(import.meta.url));
28060
+ const { root } = path52.parse(dir);
27701
28061
  while (dir !== root) {
27702
- if (existsSync39(path51.join(dir, "package.json"))) return dir;
27703
- dir = path51.dirname(dir);
28062
+ if (existsSync39(path52.join(dir, "package.json"))) return dir;
28063
+ dir = path52.dirname(dir);
27704
28064
  }
27705
28065
  throw new Error("Cannot find package root");
27706
28066
  }
27707
28067
  function checkBuildIntegrity(pkgRoot) {
27708
28068
  const results = [];
27709
- const tsupConfig = path51.join(pkgRoot, "tsup.config.ts");
28069
+ const tsupConfig = path52.join(pkgRoot, "tsup.config.ts");
27710
28070
  if (!existsSync39(tsupConfig)) {
27711
28071
  return [{ name: "build/tsup-config", pass: false, detail: "tsup.config.ts not found" }];
27712
28072
  }
@@ -27716,7 +28076,7 @@ function checkBuildIntegrity(pkgRoot) {
27716
28076
  let total = 0;
27717
28077
  for (const match of entryMatches) {
27718
28078
  const outputKey = match[1];
27719
- const expectedPath = path51.join(pkgRoot, "dist", `${outputKey}.js`);
28079
+ const expectedPath = path52.join(pkgRoot, "dist", `${outputKey}.js`);
27720
28080
  total++;
27721
28081
  if (!existsSync39(expectedPath)) {
27722
28082
  missing.push(`dist/${outputKey}.js`);
@@ -27740,7 +28100,7 @@ function checkBuildIntegrity(pkgRoot) {
27740
28100
  }
27741
28101
  function checkEmbedPipeline(pkgRoot) {
27742
28102
  const results = [];
27743
- const daemonPath = path51.join(pkgRoot, "dist", "lib", "exe-daemon.js");
28103
+ const daemonPath = path52.join(pkgRoot, "dist", "lib", "exe-daemon.js");
27744
28104
  if (!existsSync39(daemonPath)) {
27745
28105
  results.push({
27746
28106
  name: "exed/daemon-exists",
@@ -27752,17 +28112,17 @@ function checkEmbedPipeline(pkgRoot) {
27752
28112
  results.push({ name: "exed/daemon-exists", pass: true, detail: "dist/lib/exe-daemon.js exists" });
27753
28113
  const entryDirs = ["dist/hooks", "dist/bin", "dist/mcp"];
27754
28114
  for (const dir of entryDirs) {
27755
- const fullDir = path51.join(pkgRoot, dir);
28115
+ const fullDir = path52.join(pkgRoot, dir);
27756
28116
  if (!existsSync39(fullDir)) continue;
27757
28117
  let walkDir = fullDir;
27758
- const { root } = path51.parse(walkDir);
28118
+ const { root } = path52.parse(walkDir);
27759
28119
  let foundRoot = null;
27760
28120
  while (walkDir !== root) {
27761
- if (existsSync39(path51.join(walkDir, "package.json"))) {
28121
+ if (existsSync39(path52.join(walkDir, "package.json"))) {
27762
28122
  foundRoot = walkDir;
27763
28123
  break;
27764
28124
  }
27765
- walkDir = path51.dirname(walkDir);
28125
+ walkDir = path52.dirname(walkDir);
27766
28126
  }
27767
28127
  if (!foundRoot) {
27768
28128
  results.push({
@@ -27772,7 +28132,7 @@ function checkEmbedPipeline(pkgRoot) {
27772
28132
  });
27773
28133
  continue;
27774
28134
  }
27775
- const resolvedDaemon = path51.join(foundRoot, "dist", "lib", "exe-daemon.js");
28135
+ const resolvedDaemon = path52.join(foundRoot, "dist", "lib", "exe-daemon.js");
27776
28136
  const reachable = existsSync39(resolvedDaemon);
27777
28137
  results.push({
27778
28138
  name: `exed/reachable-from-${dir}`,
@@ -27784,13 +28144,13 @@ function checkEmbedPipeline(pkgRoot) {
27784
28144
  }
27785
28145
  function checkTaskSystem(pkgRoot) {
27786
28146
  const results = [];
27787
- const scannerPath = path51.join(pkgRoot, "dist", "bin", "scan-tasks.js");
28147
+ const scannerPath = path52.join(pkgRoot, "dist", "bin", "scan-tasks.js");
27788
28148
  if (!existsSync39(scannerPath)) {
27789
28149
  results.push({ name: "tasks/scanner", pass: false, detail: "scan-tasks.js not found" });
27790
28150
  return results;
27791
28151
  }
27792
28152
  try {
27793
- execSync13(`node "${scannerPath}" /tmp/nonexistent-healthcheck-test --format=json 2>/dev/null`, {
28153
+ execSync14(`node "${scannerPath}" /tmp/nonexistent-healthcheck-test --format=json 2>/dev/null`, {
27794
28154
  timeout: 1e4,
27795
28155
  encoding: "utf-8"
27796
28156
  });
@@ -27807,13 +28167,13 @@ function checkTaskSystem(pkgRoot) {
27807
28167
  }
27808
28168
  function checkWorkerSpawning(pkgRoot) {
27809
28169
  const results = [];
27810
- const workerPath = path51.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
28170
+ const workerPath = path52.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
27811
28171
  if (!existsSync39(workerPath)) {
27812
28172
  results.push({ name: "workers/ingest-worker", pass: false, detail: "ingest-worker.js not found" });
27813
28173
  return results;
27814
28174
  }
27815
28175
  try {
27816
- execSync13(`node --check "${workerPath}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
28176
+ execSync14(`node --check "${workerPath}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
27817
28177
  results.push({ name: "workers/ingest-worker", pass: true, detail: "ingest-worker.js parses OK" });
27818
28178
  } catch (err) {
27819
28179
  results.push({
@@ -27822,14 +28182,14 @@ function checkWorkerSpawning(pkgRoot) {
27822
28182
  detail: `Parse error: ${err instanceof Error ? err.message.slice(0, 200) : String(err)}`
27823
28183
  });
27824
28184
  }
27825
- const hooksDir = path51.join(pkgRoot, "dist", "hooks");
28185
+ const hooksDir = path52.join(pkgRoot, "dist", "hooks");
27826
28186
  if (existsSync39(hooksDir)) {
27827
28187
  const hookFiles = readdirSync14(hooksDir).filter((f) => f.endsWith(".js") && !f.endsWith(".js.map"));
27828
28188
  let hooksPassed = 0;
27829
28189
  const hooksFailed = [];
27830
28190
  for (const hook of hookFiles) {
27831
28191
  try {
27832
- execSync13(`node --check "${path51.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
28192
+ execSync14(`node --check "${path52.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
27833
28193
  hooksPassed++;
27834
28194
  } catch {
27835
28195
  hooksFailed.push(hook);
@@ -27853,8 +28213,8 @@ function checkWorkerSpawning(pkgRoot) {
27853
28213
  }
27854
28214
  function checkMcpTransport() {
27855
28215
  const results = [];
27856
- const pidPath = path51.join(EXE_AI_DIR, "exed.pid");
27857
- const tokenPath = path51.join(EXE_AI_DIR, "exed.token");
28216
+ const pidPath = path52.join(EXE_AI_DIR, "exed.pid");
28217
+ const tokenPath = path52.join(EXE_AI_DIR, "exed.token");
27858
28218
  let daemonAlive = false;
27859
28219
  if (existsSync39(pidPath)) {
27860
28220
  try {
@@ -27871,7 +28231,7 @@ function checkMcpTransport() {
27871
28231
  if (daemonAlive && existsSync39(tokenPath)) {
27872
28232
  try {
27873
28233
  const token = readFileSync33(tokenPath, "utf8").trim();
27874
- const response = execSync13(
28234
+ const response = execSync14(
27875
28235
  `curl -sS -i -m 2 -H "Authorization: Bearer ${token}" -H 'Accept: application/json, text/event-stream' http://127.0.0.1:48739/mcp`,
27876
28236
  { encoding: "utf8", timeout: 3e3 }
27877
28237
  );
@@ -27901,7 +28261,7 @@ function checkMcpTransport() {
27901
28261
  results.push({
27902
28262
  name: "mcp/monitor-summary",
27903
28263
  pass: true,
27904
- detail: `Privacy-safe local monitor summary: ${path51.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
28264
+ detail: `Privacy-safe local monitor summary: ${path52.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
27905
28265
  });
27906
28266
  return results;
27907
28267
  }
@@ -27922,7 +28282,7 @@ function checkClaudeCodeInstall() {
27922
28282
  });
27923
28283
  }
27924
28284
  try {
27925
- const claudePath = execSync13("which claude 2>/dev/null || true", { encoding: "utf8", timeout: 5e3 }).trim();
28285
+ const claudePath = execSync14("which claude 2>/dev/null || true", { encoding: "utf8", timeout: 5e3 }).trim();
27926
28286
  if (!claudePath) {
27927
28287
  results.push({
27928
28288
  name: "cc/cli-binary",
@@ -27932,7 +28292,7 @@ function checkClaudeCodeInstall() {
27932
28292
  } else {
27933
28293
  let resolved = claudePath;
27934
28294
  try {
27935
- resolved = execSync13(`readlink -f "${claudePath}" 2>/dev/null || readlink "${claudePath}" 2>/dev/null || echo "${claudePath}"`, {
28295
+ resolved = execSync14(`readlink -f "${claudePath}" 2>/dev/null || readlink "${claudePath}" 2>/dev/null || echo "${claudePath}"`, {
27936
28296
  encoding: "utf8",
27937
28297
  timeout: 5e3
27938
28298
  }).trim();
@@ -27959,7 +28319,7 @@ function checkClaudeCodeInstall() {
27959
28319
  detail: "Failed to check claude binary path"
27960
28320
  });
27961
28321
  }
27962
- const versionsDir = path51.join(
28322
+ const versionsDir = path52.join(
27963
28323
  process.env.HOME ?? process.env.USERPROFILE ?? "",
27964
28324
  ".local",
27965
28325
  "share",
@@ -28016,7 +28376,7 @@ function checkClaudeCodeInstall() {
28016
28376
  });
28017
28377
  }
28018
28378
  try {
28019
- const ccVersion = execSync13("claude --version 2>/dev/null || echo unknown", {
28379
+ const ccVersion = execSync14("claude --version 2>/dev/null || echo unknown", {
28020
28380
  encoding: "utf8",
28021
28381
  timeout: 5e3
28022
28382
  }).trim();
@@ -28064,17 +28424,17 @@ if (isMainModule(import.meta.url)) {
28064
28424
  }
28065
28425
 
28066
28426
  // src/lib/update-check.ts
28067
- import { execSync as execSync14 } from "child_process";
28427
+ import { execSync as execSync15 } from "child_process";
28068
28428
  import { readFileSync as readFileSync34 } from "fs";
28069
- import path52 from "path";
28429
+ import path53 from "path";
28070
28430
  function getLocalVersion(packageRoot) {
28071
- const pkgPath = path52.join(packageRoot, "package.json");
28431
+ const pkgPath = path53.join(packageRoot, "package.json");
28072
28432
  const pkg = JSON.parse(readFileSync34(pkgPath, "utf-8"));
28073
28433
  return pkg.version;
28074
28434
  }
28075
28435
  function getRemoteVersion() {
28076
28436
  try {
28077
- const output = execSync14("npm view @askexenow/exe-os version", {
28437
+ const output = execSync15("npm view @askexenow/exe-os version", {
28078
28438
  encoding: "utf-8",
28079
28439
  timeout: 15e3,
28080
28440
  stdio: ["pipe", "pipe", "pipe"]
@@ -28136,12 +28496,12 @@ function registerCliParityTools(server2) {
28136
28496
  title: "Doctor",
28137
28497
  description: "Run exe-os doctor audit. Defaults to read-only diagnostics; optional dry-run/fix flags mirror CLI.",
28138
28498
  inputSchema: {
28139
- agent: z90.string().optional(),
28140
- project: z90.string().optional(),
28141
- verbose: z90.boolean().default(false),
28142
- conflicts: z90.boolean().default(false),
28143
- dry_run: z90.boolean().default(false),
28144
- fix: z90.boolean().default(false)
28499
+ agent: z91.string().optional(),
28500
+ project: z91.string().optional(),
28501
+ verbose: z91.boolean().default(false),
28502
+ conflicts: z91.boolean().default(false),
28503
+ dry_run: z91.boolean().default(false),
28504
+ fix: z91.boolean().default(false)
28145
28505
  }
28146
28506
  }, async ({ agent, project, verbose, conflicts, dry_run, fix }) => {
28147
28507
  const args = [];
@@ -28158,9 +28518,9 @@ function registerCliParityTools(server2) {
28158
28518
  title: "Rename Employee",
28159
28519
  description: "Rename an employee using the same path as `exe-os rename <old> <new>`. Use for customer roster/identity renames.",
28160
28520
  inputSchema: {
28161
- old_name: z90.string().min(1),
28162
- new_name: z90.string().min(1),
28163
- dry_run: z90.boolean().default(false)
28521
+ old_name: z91.string().min(1),
28522
+ new_name: z91.string().min(1),
28523
+ dry_run: z91.boolean().default(false)
28164
28524
  }
28165
28525
  }, async ({ old_name, new_name, dry_run }) => {
28166
28526
  if (dry_run) {
@@ -28173,7 +28533,7 @@ function registerCliParityTools(server2) {
28173
28533
  server2.registerTool("status_brief", {
28174
28534
  title: "Status Brief",
28175
28535
  description: "Return current employee/tmux status. Mirrors `exe-status` and supports optional deep view for one employee.",
28176
- inputSchema: { employee: z90.string().optional() }
28536
+ inputSchema: { employee: z91.string().optional() }
28177
28537
  }, async ({ employee }) => {
28178
28538
  const text3 = await status(employee);
28179
28539
  return result2(text3, { ok: true, employee: employee ?? null });
@@ -28181,7 +28541,7 @@ function registerCliParityTools(server2) {
28181
28541
  server2.registerTool("pending_work_summary", {
28182
28542
  title: "Pending Work Summary",
28183
28543
  description: "Return pending reviews, messages, and notifications using the same summaries as the CLI tools.",
28184
- inputSchema: { agent: z90.string().optional() }
28544
+ inputSchema: { agent: z91.string().optional() }
28185
28545
  }, async ({ agent }) => {
28186
28546
  const parts = await Promise.all([
28187
28547
  runCommand("exe-pending-reviews", [], 3e4),
@@ -28202,7 +28562,7 @@ function registerCliParityTools(server2) {
28202
28562
  server2.registerTool("key_rotation_preflight", {
28203
28563
  title: "Key Rotation Preflight",
28204
28564
  description: "Dry-run key rotation/update preflight. No destructive changes.",
28205
- inputSchema: { mode: z90.enum(["rotate", "update"]).default("rotate") }
28565
+ inputSchema: { mode: z91.enum(["rotate", "update"]).default("rotate") }
28206
28566
  }, async ({ mode }) => {
28207
28567
  const out = await runCommand("exe-os", ["key", mode, "--dry-run"], 6e4);
28208
28568
  return result2(out.text, { ok: out.ok, command: out.command, mode }, !out.ok);
@@ -28221,11 +28581,11 @@ function registerCliParityTools(server2) {
28221
28581
  title: "Stack Update Check",
28222
28582
  description: "Plan/check a customer stack update without applying Docker changes. Mirrors `exe-os stack-update --check`.",
28223
28583
  inputSchema: {
28224
- target: z90.string().optional(),
28225
- manifest: z90.string().optional(),
28226
- compose_file: z90.string().optional(),
28227
- env_file: z90.string().optional(),
28228
- deployment_persona: z90.enum(["customer", "askexe-control-plane"]).default("customer")
28584
+ target: z91.string().optional(),
28585
+ manifest: z91.string().optional(),
28586
+ compose_file: z91.string().optional(),
28587
+ env_file: z91.string().optional(),
28588
+ deployment_persona: z91.enum(["customer", "askexe-control-plane"]).default("customer")
28229
28589
  }
28230
28590
  }, async ({ target, manifest, compose_file, env_file, deployment_persona }) => {
28231
28591
  const args = ["stack-update", "--check", "--deployment-persona", deployment_persona];
@@ -28249,7 +28609,7 @@ function registerCliParityTools(server2) {
28249
28609
  // src/mcp/tools/get-session-events.ts
28250
28610
  init_active_agent();
28251
28611
  init_fast_db_init();
28252
- import { z as z91 } from "zod";
28612
+ import { z as z92 } from "zod";
28253
28613
 
28254
28614
  // src/lib/session-events.ts
28255
28615
  init_task_scope();
@@ -28336,7 +28696,7 @@ async function listRecentSessionEvents(client, options) {
28336
28696
  }
28337
28697
 
28338
28698
  // src/mcp/tools/get-session-events.ts
28339
- var EVENT_TYPE = z91.enum([
28699
+ var EVENT_TYPE = z92.enum([
28340
28700
  "user_prompt",
28341
28701
  "assistant_response",
28342
28702
  "tool_call",
@@ -28366,11 +28726,11 @@ function registerGetSessionEvents(server2) {
28366
28726
  title: "Get Session Events",
28367
28727
  description: "Return exact recent chronological user prompts, assistant responses, and tool calls from the append-only session journal. Use this when asked what happened last, not semantic memory search.",
28368
28728
  inputSchema: {
28369
- agent_id: z91.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28370
- session_id: z91.string().optional().describe("Optional exact runtime session id."),
28729
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28730
+ session_id: z92.string().optional().describe("Optional exact runtime session id."),
28371
28731
  event_type: EVENT_TYPE.optional().describe("Filter to one event type."),
28372
- project_name: z91.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
28373
- limit: z91.number().int().min(1).max(100).default(20).describe("Number of events to return.")
28732
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
28733
+ limit: z92.number().int().min(1).max(100).default(20).describe("Number of events to return.")
28374
28734
  }
28375
28735
  },
28376
28736
  async ({ agent_id, session_id, event_type, project_name, limit }) => {
@@ -28406,8 +28766,8 @@ function registerGetLastAssistantResponse(server2) {
28406
28766
  title: "Get Last Assistant Response",
28407
28767
  description: "Return the exact last assistant response for an agent from the session event journal. Use for 'what was the last thing you said?'",
28408
28768
  inputSchema: {
28409
- agent_id: z91.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28410
- project_name: z91.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
28769
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28770
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
28411
28771
  }
28412
28772
  },
28413
28773
  async ({ agent_id, project_name }) => {
@@ -28437,32 +28797,106 @@ function registerGetLastAssistantResponse(server2) {
28437
28797
  }
28438
28798
 
28439
28799
  // src/mcp/tools/code-context.ts
28440
- import { z as z92 } from "zod";
28800
+ import { z as z93 } from "zod";
28441
28801
 
28442
28802
  // src/lib/code-context-index.ts
28443
28803
  init_config();
28444
- import crypto19 from "crypto";
28445
- import path53 from "path";
28804
+ import crypto20 from "crypto";
28805
+ import path54 from "path";
28446
28806
  import { existsSync as existsSync40, mkdirSync as mkdirSync22, readFileSync as readFileSync35, readdirSync as readdirSync15, statSync as statSync9, writeFileSync as writeFileSync24 } from "fs";
28447
28807
  import { spawnSync } from "child_process";
28808
+ init_exe_daemon_client();
28809
+ var VECTOR_STORE_VERSION = 1;
28810
+ var EMBED_BATCH_SIZE = 64;
28811
+ function vectorStorePath(projectRoot) {
28812
+ const rootHash = hashText(projectRoot).slice(0, 16);
28813
+ return path54.join(indexDir(), `${rootHash}.vectors.json`);
28814
+ }
28815
+ function loadVectorStore(projectRoot) {
28816
+ const file = vectorStorePath(projectRoot);
28817
+ if (!existsSync40(file)) return null;
28818
+ try {
28819
+ const parsed = JSON.parse(readFileSync35(file, "utf8"));
28820
+ if (parsed.version !== VECTOR_STORE_VERSION) return null;
28821
+ return parsed;
28822
+ } catch {
28823
+ return null;
28824
+ }
28825
+ }
28826
+ function saveVectorStore(projectRoot, store) {
28827
+ writeFileSync24(vectorStorePath(projectRoot), JSON.stringify(store));
28828
+ }
28829
+ function cosineSimilarity3(a, b) {
28830
+ let dot = 0, normA = 0, normB = 0;
28831
+ for (let i = 0; i < a.length; i++) {
28832
+ dot += a[i] * b[i];
28833
+ normA += a[i] * a[i];
28834
+ normB += b[i] * b[i];
28835
+ }
28836
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
28837
+ return denom === 0 ? 0 : dot / denom;
28838
+ }
28839
+ async function embedSymbols(index) {
28840
+ const rootHash = hashText(index.projectRoot).slice(0, 16);
28841
+ const existing = loadVectorStore(index.projectRoot);
28842
+ const store = {
28843
+ version: VECTOR_STORE_VERSION,
28844
+ projectRootHash: rootHash,
28845
+ vectors: {}
28846
+ };
28847
+ const allSymbols = [];
28848
+ for (const file of Object.values(index.files)) {
28849
+ for (const symbol of file.symbols) {
28850
+ if (existing?.vectors[symbol.id]) {
28851
+ store.vectors[symbol.id] = existing.vectors[symbol.id];
28852
+ } else {
28853
+ allSymbols.push({ id: symbol.id, text: symbol.summary || `${symbol.kind} ${symbol.name} in ${symbol.filePath}` });
28854
+ }
28855
+ }
28856
+ }
28857
+ if (allSymbols.length === 0) {
28858
+ saveVectorStore(index.projectRoot, store);
28859
+ return store;
28860
+ }
28861
+ const connected = await connectEmbedDaemon().catch(() => false);
28862
+ if (!connected) {
28863
+ saveVectorStore(index.projectRoot, store);
28864
+ return store;
28865
+ }
28866
+ for (let i = 0; i < allSymbols.length; i += EMBED_BATCH_SIZE) {
28867
+ const batch = allSymbols.slice(i, i + EMBED_BATCH_SIZE);
28868
+ const texts = batch.map((s) => s.text);
28869
+ try {
28870
+ const vectors = await embedBatchViaClient(texts, "low");
28871
+ if (vectors && vectors.length === batch.length) {
28872
+ for (let j = 0; j < batch.length; j++) {
28873
+ store.vectors[batch[j].id] = vectors[j];
28874
+ }
28875
+ }
28876
+ } catch {
28877
+ }
28878
+ }
28879
+ saveVectorStore(index.projectRoot, store);
28880
+ return store;
28881
+ }
28448
28882
  var INDEX_VERSION = 2;
28449
28883
  var DEFAULT_MAX_FILES = 5e3;
28450
28884
  var IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
28451
28885
  function normalizeProjectRoot(projectRoot) {
28452
- return path53.resolve(projectRoot || process.cwd());
28886
+ return path54.resolve(projectRoot || process.cwd());
28453
28887
  }
28454
28888
  function hashText(text3) {
28455
- return crypto19.createHash("sha256").update(text3).digest("hex");
28889
+ return crypto20.createHash("sha256").update(text3).digest("hex");
28456
28890
  }
28457
28891
  function indexDir() {
28458
- const dir = path53.join(EXE_AI_DIR, "code-context");
28892
+ const dir = path54.join(EXE_AI_DIR, "code-context");
28459
28893
  mkdirSync22(dir, { recursive: true });
28460
28894
  return dir;
28461
28895
  }
28462
28896
  function getCodeContextIndexPath(projectRoot) {
28463
28897
  const root = normalizeProjectRoot(projectRoot);
28464
28898
  const rootHash = hashText(root).slice(0, 16);
28465
- return path53.join(indexDir(), `${rootHash}.json`);
28899
+ return path54.join(indexDir(), `${rootHash}.json`);
28466
28900
  }
28467
28901
  function currentBranch(projectRoot) {
28468
28902
  const result3 = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
@@ -28475,8 +28909,8 @@ function shouldIgnore(relPath) {
28475
28909
  }
28476
28910
  function listRecursive(projectRoot, dir = projectRoot, out = []) {
28477
28911
  for (const entry of readdirSync15(dir, { withFileTypes: true })) {
28478
- const abs = path53.join(dir, entry.name);
28479
- const rel = path53.relative(projectRoot, abs).replaceAll(path53.sep, "/");
28912
+ const abs = path54.join(dir, entry.name);
28913
+ const rel = path54.relative(projectRoot, abs).replaceAll(path54.sep, "/");
28480
28914
  if (shouldIgnore(rel)) continue;
28481
28915
  if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
28482
28916
  else if (entry.isFile()) out.push(rel);
@@ -28492,7 +28926,7 @@ function listCodeFiles(projectRoot, maxFiles) {
28492
28926
  const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
28493
28927
  files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
28494
28928
  }
28495
- return files.map((file) => file.replaceAll(path53.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
28929
+ return files.map((file) => file.replaceAll(path54.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
28496
28930
  }
28497
28931
  function parseImportPaths(importText) {
28498
28932
  const paths = [];
@@ -28505,13 +28939,13 @@ function parseImportPaths(importText) {
28505
28939
  }
28506
28940
  function resolveImport(fromFile, importPath, allFiles) {
28507
28941
  if (!importPath.startsWith(".")) return null;
28508
- const base = path53.posix.normalize(path53.posix.join(path53.posix.dirname(fromFile.replaceAll(path53.sep, "/")), importPath));
28942
+ const base = path54.posix.normalize(path54.posix.join(path54.posix.dirname(fromFile.replaceAll(path54.sep, "/")), importPath));
28509
28943
  const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
28510
28944
  const candidates = [base];
28511
28945
  for (const ext of ["ts", "tsx", "js", "jsx", "py", "rs", "go", "java", "cs", "cpp", "c", "rb", "php", "swift", "kt", "scala", "sql", "md", "json", "yaml", "yml"]) {
28512
28946
  candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
28513
28947
  }
28514
- for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path53.posix.join(base, indexName));
28948
+ for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path54.posix.join(base, indexName));
28515
28949
  return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
28516
28950
  }
28517
28951
  function symbolId(filePath, chunk) {
@@ -28532,7 +28966,7 @@ function saveIndex(index) {
28532
28966
  writeFileSync24(getCodeContextIndexPath(index.projectRoot), JSON.stringify(index, null, 2));
28533
28967
  }
28534
28968
  function buildFileRecord(projectRoot, relPath, allFiles, previous) {
28535
- const absPath = path53.join(projectRoot, relPath);
28969
+ const absPath = path54.join(projectRoot, relPath);
28536
28970
  let stat;
28537
28971
  try {
28538
28972
  stat = statSync9(absPath);
@@ -28570,13 +29004,13 @@ function buildCodeContextIndex(options = {}) {
28570
29004
  const branch = currentBranch(projectRoot);
28571
29005
  const previous = options.force ? null : loadIndex(projectRoot);
28572
29006
  const files = listCodeFiles(projectRoot, maxFiles);
28573
- const allFiles = new Set(files.map((file) => file.replaceAll(path53.sep, "/")));
29007
+ const allFiles = new Set(files.map((file) => file.replaceAll(path54.sep, "/")));
28574
29008
  const fileRecords = {};
28575
29009
  let rebuiltFiles = 0;
28576
29010
  let reusedFiles = 0;
28577
29011
  let skippedFiles = 0;
28578
29012
  for (const rel of files) {
28579
- const normalized = rel.replaceAll(path53.sep, "/");
29013
+ const normalized = rel.replaceAll(path54.sep, "/");
28580
29014
  const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
28581
29015
  if (record) {
28582
29016
  fileRecords[normalized] = record;
@@ -28605,11 +29039,11 @@ function loadOrBuildCodeContextIndex(options = {}) {
28605
29039
  if (loaded) {
28606
29040
  const currentFiles = listCodeFiles(projectRoot, options.maxFiles ?? DEFAULT_MAX_FILES);
28607
29041
  const unchanged = currentFiles.every((rel) => {
28608
- const normalized = rel.replaceAll(path53.sep, "/");
29042
+ const normalized = rel.replaceAll(path54.sep, "/");
28609
29043
  const existing = loaded.files[normalized];
28610
29044
  if (!existing) return false;
28611
29045
  try {
28612
- const stat = statSync9(path53.join(projectRoot, normalized));
29046
+ const stat = statSync9(path54.join(projectRoot, normalized));
28613
29047
  return stat.mtimeMs === existing.mtimeMs && stat.size === existing.size;
28614
29048
  } catch {
28615
29049
  return false;
@@ -28662,9 +29096,9 @@ function globToRegex(pattern) {
28662
29096
  }
28663
29097
  function matchesPath(filePath, patterns) {
28664
29098
  if (!patterns || patterns.length === 0) return true;
28665
- const normalized = filePath.replaceAll(path53.sep, "/");
29099
+ const normalized = filePath.replaceAll(path54.sep, "/");
28666
29100
  return patterns.some((pattern) => {
28667
- const p = pattern.replaceAll(path53.sep, "/").replace(/^\.\//, "");
29101
+ const p = pattern.replaceAll(path54.sep, "/").replace(/^\.\//, "");
28668
29102
  return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
28669
29103
  });
28670
29104
  }
@@ -28723,7 +29157,7 @@ function filteredFiles(index, options = {}) {
28723
29157
  return matchesPath(file.path, options.paths);
28724
29158
  });
28725
29159
  }
28726
- function searchCodeContext(query, options = {}) {
29160
+ function lexicalSearch(query, options = {}) {
28727
29161
  const terms = tokenize(query);
28728
29162
  if (terms.length === 0) return [];
28729
29163
  const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
@@ -28749,6 +29183,82 @@ function searchCodeContext(query, options = {}) {
28749
29183
  const limit = options.limit ?? 20;
28750
29184
  return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
28751
29185
  }
29186
+ function searchCodeContext(query, options = {}) {
29187
+ return lexicalSearch(query, options);
29188
+ }
29189
+ var RRF_K2 = 60;
29190
+ async function searchCodeContextSemantic(query, options = {}) {
29191
+ const terms = tokenize(query);
29192
+ if (terms.length === 0) return [];
29193
+ const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
29194
+ const projectRoot = normalizeProjectRoot(options.projectRoot);
29195
+ const vectorStore = loadVectorStore(projectRoot);
29196
+ if (!vectorStore || Object.keys(vectorStore.vectors).length === 0) {
29197
+ return lexicalSearch(query, options);
29198
+ }
29199
+ let queryVector = null;
29200
+ try {
29201
+ const connected = await connectEmbedDaemon().catch(() => false);
29202
+ if (connected) {
29203
+ const result3 = await embedBatchViaClient([query], "high");
29204
+ if (result3 && result3.length === 1) queryVector = result3[0];
29205
+ }
29206
+ } catch {
29207
+ }
29208
+ if (!queryVector) return lexicalSearch(query, options);
29209
+ const files = filteredFiles(index, options);
29210
+ const candidates = [];
29211
+ for (const file of files) {
29212
+ for (const symbol of file.symbols) {
29213
+ const lexical = scoreSymbol(symbol, terms);
29214
+ const vec = vectorStore.vectors[symbol.id];
29215
+ const vectorScore = vec ? cosineSimilarity3(queryVector, vec) : 0;
29216
+ if (lexical.score > 0 || vectorScore > 0.3) {
29217
+ candidates.push({
29218
+ symbol,
29219
+ lexicalScore: lexical.score,
29220
+ vectorScore,
29221
+ matches: lexical.matches
29222
+ });
29223
+ }
29224
+ }
29225
+ }
29226
+ const byLexical = [...candidates].sort((a, b) => b.lexicalScore - a.lexicalScore);
29227
+ const byVector = [...candidates].sort((a, b) => b.vectorScore - a.vectorScore);
29228
+ const lexicalRank = /* @__PURE__ */ new Map();
29229
+ const vectorRank = /* @__PURE__ */ new Map();
29230
+ byLexical.forEach((c, i) => lexicalRank.set(c.symbol.id, i + 1));
29231
+ byVector.forEach((c, i) => vectorRank.set(c.symbol.id, i + 1));
29232
+ const VECTOR_WEIGHT = 0.6;
29233
+ const LEXICAL_WEIGHT = 0.4;
29234
+ const fused = candidates.map((c) => {
29235
+ const lRank = lexicalRank.get(c.symbol.id) ?? candidates.length + 1;
29236
+ const vRank = vectorRank.get(c.symbol.id) ?? candidates.length + 1;
29237
+ const rrfScore = VECTOR_WEIGHT * (1 / (RRF_K2 + vRank)) + LEXICAL_WEIGHT * (1 / (RRF_K2 + lRank));
29238
+ return {
29239
+ symbol: c.symbol,
29240
+ score: rrfScore,
29241
+ matches: c.vectorScore > 0.3 ? [...c.matches, `semantic=${c.vectorScore.toFixed(3)}`] : c.matches,
29242
+ filePath: c.symbol.filePath,
29243
+ language: c.symbol.language,
29244
+ content: c.symbol.text,
29245
+ startLine: c.symbol.startLine,
29246
+ endLine: c.symbol.endLine
29247
+ };
29248
+ });
29249
+ const offset = Math.max(0, options.offset ?? 0);
29250
+ const limit = options.limit ?? 20;
29251
+ return fused.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
29252
+ }
29253
+ async function buildCodeContextIndexWithEmbeddings(options = {}) {
29254
+ const index = buildCodeContextIndex(options);
29255
+ const existingStore = loadVectorStore(index.projectRoot);
29256
+ const existingCount = existingStore ? Object.keys(existingStore.vectors).length : 0;
29257
+ const store = await embedSymbols(index);
29258
+ const vectorCount = Object.keys(store.vectors).length;
29259
+ const newEmbeddings = vectorCount - Math.min(existingCount, vectorCount);
29260
+ return { index, vectorCount, newEmbeddings };
29261
+ }
28752
29262
  function dependentsMap(index) {
28753
29263
  const map = /* @__PURE__ */ new Map();
28754
29264
  for (const file of Object.values(index.files)) {
@@ -28782,7 +29292,7 @@ function traceCodeSymbol(symbolName, options = {}) {
28782
29292
  }
28783
29293
  function resolveTargetFile(index, input) {
28784
29294
  if (input.filePath) {
28785
- const normalized = input.filePath.replaceAll(path53.sep, "/").replace(/^\.\//, "");
29295
+ const normalized = input.filePath.replaceAll(path54.sep, "/").replace(/^\.\//, "");
28786
29296
  if (index.files[normalized]) return { filePath: normalized, target: normalized };
28787
29297
  const suffix = Object.keys(index.files).find((file) => file.endsWith(normalized));
28788
29298
  if (suffix) return { filePath: suffix, target: input.filePath };
@@ -28812,7 +29322,7 @@ function analyzeBlastRadius(input) {
28812
29322
  }
28813
29323
  }
28814
29324
  }
28815
- const targetBase = path53.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
29325
+ const targetBase = path54.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
28816
29326
  const symbolLower = input.symbol?.toLowerCase();
28817
29327
  const tests = Object.keys(index.files).filter((file) => {
28818
29328
  const lower = file.toLowerCase();
@@ -28851,23 +29361,24 @@ function jsonResult(value) {
28851
29361
  function registerCodeContext(server2) {
28852
29362
  server2.registerTool("code_context", {
28853
29363
  title: "Code Context",
28854
- description: "Persistent codebase context engine. One consolidated tool to avoid MCP bloat. Actions: index, search, trace, blast_radius, stats.",
29364
+ description: "Persistent codebase context engine with semantic vector search. One consolidated tool to avoid MCP bloat. Actions: index (structural only), index_embed (structural + vector embeddings), search (semantic+lexical hybrid), trace, blast_radius, stats. Search uses RRF fusion of Jina v5 embeddings (cosine similarity) + lexical scoring. Falls back to lexical if daemon unavailable.",
28855
29365
  inputSchema: {
28856
- action: z92.enum(["index", "search", "trace", "blast_radius", "stats"]).describe("Code context operation"),
28857
- project_root: z92.string().optional().describe("Repository root. Defaults to current working directory."),
28858
- query: z92.string().optional().describe("Search query for action=search"),
28859
- symbol: z92.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
28860
- file_path: z92.string().optional().describe("File path for blast_radius"),
28861
- force: z92.boolean().optional().describe("Force rebuild before answering"),
28862
- limit: z92.coerce.number().int().min(1).max(100).optional().describe("Max results"),
28863
- offset: z92.coerce.number().int().min(0).optional().describe("Search pagination offset"),
28864
- refresh_index: z92.boolean().optional().describe("Refresh/rebuild index before searching"),
28865
- languages: z92.array(z92.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
28866
- paths: z92.array(z92.string()).optional().describe("Path/glob filters"),
28867
- depth: z92.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
28868
- max_files: z92.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index")
28869
- }
28870
- }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files }) => {
29366
+ action: z93.enum(["index", "index_embed", "search", "trace", "blast_radius", "stats"]).describe("Code context operation. index_embed = index + generate embeddings for semantic search."),
29367
+ project_root: z93.string().optional().describe("Repository root. Defaults to current working directory."),
29368
+ query: z93.string().optional().describe("Natural language search query (e.g. 'authentication logic', 'database migration'). Semantic search finds conceptual matches, not just keywords."),
29369
+ symbol: z93.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
29370
+ file_path: z93.string().optional().describe("File path for blast_radius"),
29371
+ force: z93.boolean().optional().describe("Force rebuild before answering"),
29372
+ limit: z93.coerce.number().int().min(1).max(100).optional().describe("Max results"),
29373
+ offset: z93.coerce.number().int().min(0).optional().describe("Search pagination offset"),
29374
+ refresh_index: z93.boolean().optional().describe("Refresh/rebuild index before searching"),
29375
+ languages: z93.array(z93.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
29376
+ paths: z93.array(z93.string()).optional().describe("Path/glob filters"),
29377
+ depth: z93.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
29378
+ max_files: z93.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index"),
29379
+ lexical_only: z93.boolean().optional().describe("Force lexical-only search (skip vector similarity). Default: false \u2014 uses semantic hybrid search.")
29380
+ }
29381
+ }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files, lexical_only }) => {
28871
29382
  const opts = { projectRoot: project_root, force, maxFiles: max_files };
28872
29383
  if (action === "index") {
28873
29384
  const index = buildCodeContextIndex(opts);
@@ -28877,7 +29388,24 @@ function registerCodeContext(server2) {
28877
29388
  indexedAt: index.indexedAt,
28878
29389
  files: Object.keys(index.files).length,
28879
29390
  symbols: Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0),
28880
- imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0)
29391
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
29392
+ note: "Structural index only. Use action=index_embed to also generate vector embeddings for semantic search."
29393
+ });
29394
+ }
29395
+ if (action === "index_embed") {
29396
+ const { index, vectorCount, newEmbeddings } = await buildCodeContextIndexWithEmbeddings(opts);
29397
+ const totalSymbols = Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0);
29398
+ return jsonResult({
29399
+ projectRoot: index.projectRoot,
29400
+ branch: index.branch,
29401
+ indexedAt: index.indexedAt,
29402
+ files: Object.keys(index.files).length,
29403
+ symbols: totalSymbols,
29404
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
29405
+ vectorCount,
29406
+ newEmbeddings,
29407
+ embeddingCoverage: totalSymbols > 0 ? `${(vectorCount / totalSymbols * 100).toFixed(1)}%` : "0%",
29408
+ note: "Structural index + vector embeddings. Semantic search is now available."
28881
29409
  });
28882
29410
  }
28883
29411
  if (action === "stats") {
@@ -28885,14 +29413,28 @@ function registerCodeContext(server2) {
28885
29413
  }
28886
29414
  if (action === "search") {
28887
29415
  if (!query) return errorResult10('code_context action "search" requires query');
29416
+ const searchOpts = { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths };
29417
+ if (lexical_only) {
29418
+ return jsonResult({
29419
+ query,
29420
+ mode: "lexical",
29421
+ limit: limit ?? 20,
29422
+ offset: offset ?? 0,
29423
+ languages: languages ?? [],
29424
+ paths: paths ?? [],
29425
+ results: searchCodeContext(query, searchOpts)
29426
+ });
29427
+ }
29428
+ const results = await searchCodeContextSemantic(query, searchOpts);
29429
+ const hasSemantic = results.some((r) => r.matches.some((m) => m.startsWith("semantic=")));
28888
29430
  return jsonResult({
28889
29431
  query,
29432
+ mode: hasSemantic ? "semantic+lexical" : "lexical-fallback",
28890
29433
  limit: limit ?? 20,
28891
29434
  offset: offset ?? 0,
28892
- refresh_index: refresh_index ?? false,
28893
29435
  languages: languages ?? [],
28894
29436
  paths: paths ?? [],
28895
- results: searchCodeContext(query, { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths })
29437
+ results
28896
29438
  });
28897
29439
  }
28898
29440
  if (action === "trace") {
@@ -28910,9 +29452,9 @@ function registerCodeContext(server2) {
28910
29452
  }
28911
29453
 
28912
29454
  // src/mcp/tools/support-inbox.ts
28913
- import { z as z93 } from "zod";
29455
+ import { z as z94 } from "zod";
28914
29456
  var DEFAULT_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
28915
- var STATUS = z93.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
29457
+ var STATUS = z94.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
28916
29458
  function adminToken() {
28917
29459
  return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN;
28918
29460
  }
@@ -28951,9 +29493,9 @@ function registerListBugReports(server2) {
28951
29493
  title: "List Bug Reports",
28952
29494
  description: "AskExe-internal only: list incoming customer bug reports from the support inbox.",
28953
29495
  inputSchema: {
28954
- status: z93.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
28955
- severity: z93.enum(["p0", "p1", "p2", "p3"]).optional(),
28956
- limit: z93.number().int().min(1).max(100).default(25)
29496
+ status: z94.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
29497
+ severity: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
29498
+ limit: z94.number().int().min(1).max(100).default(25)
28957
29499
  }
28958
29500
  },
28959
29501
  async ({ status: status2, severity, limit }) => {
@@ -28972,7 +29514,7 @@ function registerGetBugReport(server2) {
28972
29514
  {
28973
29515
  title: "Get Bug Report",
28974
29516
  description: "AskExe-internal only: fetch one customer bug report with full markdown payload.",
28975
- inputSchema: { id: z93.string().min(8) }
29517
+ inputSchema: { id: z94.string().min(8) }
28976
29518
  },
28977
29519
  async ({ id }) => {
28978
29520
  const data = await requestJson(`${endpoint()}/${encodeURIComponent(id)}`);
@@ -28987,12 +29529,12 @@ function registerTriageBugReport(server2) {
28987
29529
  title: "Triage Bug Report",
28988
29530
  description: "AskExe-internal only: update bug report status and link task/commit/release metadata.",
28989
29531
  inputSchema: {
28990
- id: z93.string().min(8),
29532
+ id: z94.string().min(8),
28991
29533
  status: STATUS.optional(),
28992
- triage_notes: z93.string().optional(),
28993
- linked_task_id: z93.string().optional(),
28994
- linked_commit: z93.string().optional(),
28995
- fixed_version: z93.string().optional()
29534
+ triage_notes: z94.string().optional(),
29535
+ linked_task_id: z94.string().optional(),
29536
+ linked_commit: z94.string().optional(),
29537
+ fixed_version: z94.string().optional()
28996
29538
  }
28997
29539
  },
28998
29540
  async ({ id, status: status2, triage_notes, linked_task_id, linked_commit, fixed_version }) => {
@@ -29004,6 +29546,69 @@ function registerTriageBugReport(server2) {
29004
29546
  }
29005
29547
  );
29006
29548
  }
29549
+ var FEATURE_STATUS = z94.enum(["open", "planned", "in_progress", "shipped", "closed", "wontdo"]);
29550
+ function featureEndpoint() {
29551
+ return endpoint().replace(/\/bug-reports\/?$/, "/feature-requests");
29552
+ }
29553
+ function registerListFeatureRequests(server2) {
29554
+ server2.registerTool(
29555
+ "list_feature_requests",
29556
+ {
29557
+ title: "List Feature Requests",
29558
+ description: "AskExe-internal only: list incoming customer feature requests from the support inbox.",
29559
+ inputSchema: {
29560
+ status: z94.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("open"),
29561
+ priority: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
29562
+ limit: z94.number().int().min(1).max(100).default(25)
29563
+ }
29564
+ },
29565
+ async ({ status: status2, priority, limit }) => {
29566
+ const url = new URL(featureEndpoint());
29567
+ url.searchParams.set("status", status2);
29568
+ url.searchParams.set("limit", String(limit));
29569
+ if (priority) url.searchParams.set("priority", priority);
29570
+ const data = await requestJson(url.toString());
29571
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29572
+ }
29573
+ );
29574
+ }
29575
+ function registerGetFeatureRequest(server2) {
29576
+ server2.registerTool(
29577
+ "get_feature_request",
29578
+ {
29579
+ title: "Get Feature Request",
29580
+ description: "AskExe-internal only: fetch one customer feature request with full payload.",
29581
+ inputSchema: { id: z94.string().min(8) }
29582
+ },
29583
+ async ({ id }) => {
29584
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`);
29585
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29586
+ }
29587
+ );
29588
+ }
29589
+ function registerTriageFeatureRequest(server2) {
29590
+ server2.registerTool(
29591
+ "triage_feature_request",
29592
+ {
29593
+ title: "Triage Feature Request",
29594
+ description: "AskExe-internal only: update feature request status, response notes, and version metadata.",
29595
+ inputSchema: {
29596
+ id: z94.string().min(8),
29597
+ status: FEATURE_STATUS.optional(),
29598
+ response_notes: z94.string().optional(),
29599
+ target_version: z94.string().optional(),
29600
+ shipped_version: z94.string().optional()
29601
+ }
29602
+ },
29603
+ async ({ id, status: status2, response_notes, target_version, shipped_version }) => {
29604
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`, {
29605
+ method: "PATCH",
29606
+ body: JSON.stringify({ status: status2, response_notes, target_version, shipped_version })
29607
+ });
29608
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29609
+ }
29610
+ );
29611
+ }
29007
29612
 
29008
29613
  // src/mcp/tool-gates.ts
29009
29614
  var TOOL_GATES = {
@@ -29327,6 +29932,7 @@ function registerAllTools(server2) {
29327
29932
  gate("registerStoreDecision", registerStoreDecision);
29328
29933
  gate("registerGetDecision", registerGetDecision);
29329
29934
  gate("registerCreateBugReport", registerCreateBugReport);
29935
+ gate("registerCreateFeatureRequest", registerCreateFeatureRequest);
29330
29936
  gate("registerSupportTools", registerSupportTools);
29331
29937
  gate("registerCliParityTools", registerCliParityTools);
29332
29938
  gate("registerGetSessionEvents", registerGetSessionEvents);
@@ -29336,6 +29942,9 @@ function registerAllTools(server2) {
29336
29942
  gate("registerListBugReports", registerListBugReports);
29337
29943
  gate("registerGetBugReport", registerGetBugReport);
29338
29944
  gate("registerTriageBugReport", registerTriageBugReport);
29945
+ gate("registerListFeatureRequests", registerListFeatureRequests);
29946
+ gate("registerGetFeatureRequest", registerGetFeatureRequest);
29947
+ gate("registerTriageFeatureRequest", registerTriageFeatureRequest);
29339
29948
  }
29340
29949
  if (exposeLegacyConfig) {
29341
29950
  gate("registerGetAgentSpend", registerGetAgentSpend);
@@ -29503,7 +30112,7 @@ try {
29503
30112
  }
29504
30113
  }, 3e4);
29505
30114
  _ppidWatchdog.unref();
29506
- const MCP_VERSION_PATH = path54.join(os20.homedir(), ".exe-os", "mcp-version");
30115
+ const MCP_VERSION_PATH = path55.join(os20.homedir(), ".exe-os", "mcp-version");
29507
30116
  let _currentMcpVersion = null;
29508
30117
  try {
29509
30118
  _currentMcpVersion = existsSync41(MCP_VERSION_PATH) ? readFileSync36(MCP_VERSION_PATH, "utf8").trim() : null;
@@ -29545,14 +30154,14 @@ try {
29545
30154
  `
29546
30155
  );
29547
30156
  const thisFile = fileURLToPath7(import.meta.url);
29548
- const backfillPath = path54.resolve(
29549
- path54.dirname(thisFile),
30157
+ const backfillPath = path55.resolve(
30158
+ path55.dirname(thisFile),
29550
30159
  "../bin/backfill-vectors.js"
29551
30160
  );
29552
30161
  if (existsSync41(backfillPath)) {
29553
30162
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
29554
- const logPath = path54.join(exeDir, "workers.log");
29555
- mkdirSync23(path54.dirname(logPath), { recursive: true });
30163
+ const logPath = path55.join(exeDir, "workers.log");
30164
+ mkdirSync23(path55.dirname(logPath), { recursive: true });
29556
30165
  let logFd = "ignore";
29557
30166
  try {
29558
30167
  logFd = openSync4(logPath, "a");