@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
@@ -1501,7 +1501,7 @@ __export(exe_daemon_client_exports, {
1501
1501
  });
1502
1502
  import net from "net";
1503
1503
  import os4 from "os";
1504
- import { spawn } from "child_process";
1504
+ import { spawn, execSync as execSync2 } from "child_process";
1505
1505
  import { randomUUID } from "crypto";
1506
1506
  import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1507
1507
  import path7 from "path";
@@ -1531,6 +1531,14 @@ function handleData(chunk) {
1531
1531
  }
1532
1532
  }
1533
1533
  }
1534
+ function isZombie(pid) {
1535
+ try {
1536
+ const state = execSync2(`ps -p ${pid} -o state=`, { encoding: "utf8", timeout: 2e3 }).trim();
1537
+ return state.startsWith("Z");
1538
+ } catch {
1539
+ return false;
1540
+ }
1541
+ }
1534
1542
  function cleanupStaleFiles() {
1535
1543
  if (existsSync7(PID_PATH)) {
1536
1544
  try {
@@ -1538,7 +1546,11 @@ function cleanupStaleFiles() {
1538
1546
  if (pid > 0) {
1539
1547
  try {
1540
1548
  process.kill(pid, 0);
1541
- return;
1549
+ if (!isZombie(pid)) {
1550
+ return;
1551
+ }
1552
+ process.stderr.write(`[exed-client] PID ${pid} is a zombie \u2014 cleaning up stale files
1553
+ `);
1542
1554
  } catch {
1543
1555
  }
1544
1556
  }
@@ -1566,8 +1578,8 @@ function findPackageRoot() {
1566
1578
  function getAvailableMemoryGB() {
1567
1579
  if (process.platform === "darwin") {
1568
1580
  try {
1569
- const { execSync: execSync17 } = __require("child_process");
1570
- const vmstat = execSync17("vm_stat", { encoding: "utf8" });
1581
+ const { execSync: execSync18 } = __require("child_process");
1582
+ const vmstat = execSync18("vm_stat", { encoding: "utf8" });
1571
1583
  const pageSize = 16384;
1572
1584
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1573
1585
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3458,6 +3470,12 @@ async function disposeDatabase() {
3458
3470
  clearInterval(_walCheckpointTimer);
3459
3471
  _walCheckpointTimer = null;
3460
3472
  }
3473
+ if (_client) {
3474
+ try {
3475
+ await _client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3476
+ } catch {
3477
+ }
3478
+ }
3461
3479
  if (_daemonClient) {
3462
3480
  _daemonClient.close();
3463
3481
  _daemonClient = null;
@@ -5198,7 +5216,7 @@ __export(keychain_exports, {
5198
5216
  });
5199
5217
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
5200
5218
  import { existsSync as existsSync11, statSync as statSync4 } from "fs";
5201
- import { execSync as execSync2 } from "child_process";
5219
+ import { execSync as execSync3 } from "child_process";
5202
5220
  import path11 from "path";
5203
5221
  import os6 from "os";
5204
5222
  function getKeyDir() {
@@ -5215,13 +5233,13 @@ function linuxSecretAvailable() {
5215
5233
  if (process.platform !== "linux") return false;
5216
5234
  if (linuxSecretAvailability !== null) return linuxSecretAvailability;
5217
5235
  try {
5218
- execSync2("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
5236
+ execSync3("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
5219
5237
  } catch {
5220
5238
  linuxSecretAvailability = false;
5221
5239
  return false;
5222
5240
  }
5223
5241
  try {
5224
- execSync2("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
5242
+ execSync3("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
5225
5243
  linuxSecretAvailability = true;
5226
5244
  } catch {
5227
5245
  linuxSecretAvailability = false;
@@ -5245,7 +5263,7 @@ function macKeychainGet(service = SERVICE) {
5245
5263
  if (!nativeKeychainAllowed()) return null;
5246
5264
  if (process.platform !== "darwin") return null;
5247
5265
  try {
5248
- return execSync2(
5266
+ return execSync3(
5249
5267
  `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
5250
5268
  { encoding: "utf-8", timeout: 5e3 }
5251
5269
  ).trim();
@@ -5258,13 +5276,13 @@ function macKeychainSet(value, service = SERVICE) {
5258
5276
  if (process.platform !== "darwin") return false;
5259
5277
  try {
5260
5278
  try {
5261
- execSync2(
5279
+ execSync3(
5262
5280
  `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
5263
5281
  { timeout: 5e3 }
5264
5282
  );
5265
5283
  } catch {
5266
5284
  }
5267
- execSync2(
5285
+ execSync3(
5268
5286
  `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
5269
5287
  { timeout: 5e3 }
5270
5288
  );
@@ -5277,7 +5295,7 @@ function macKeychainDelete(service = SERVICE) {
5277
5295
  if (!nativeKeychainAllowed()) return false;
5278
5296
  if (process.platform !== "darwin") return false;
5279
5297
  try {
5280
- execSync2(
5298
+ execSync3(
5281
5299
  `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
5282
5300
  { timeout: 5e3 }
5283
5301
  );
@@ -5289,7 +5307,7 @@ function macKeychainDelete(service = SERVICE) {
5289
5307
  function linuxSecretGet(service = SERVICE) {
5290
5308
  if (!linuxSecretAvailable()) return null;
5291
5309
  try {
5292
- return execSync2(
5310
+ return execSync3(
5293
5311
  `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
5294
5312
  { encoding: "utf-8", timeout: 5e3 }
5295
5313
  ).trim();
@@ -5300,7 +5318,7 @@ function linuxSecretGet(service = SERVICE) {
5300
5318
  function linuxSecretSet(value, service = SERVICE) {
5301
5319
  if (!linuxSecretAvailable()) return false;
5302
5320
  try {
5303
- execSync2(
5321
+ execSync3(
5304
5322
  `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
5305
5323
  { timeout: 5e3 }
5306
5324
  );
@@ -5313,7 +5331,7 @@ function linuxSecretDelete(service = SERVICE) {
5313
5331
  if (!nativeKeychainAllowed()) return false;
5314
5332
  if (process.platform !== "linux") return false;
5315
5333
  try {
5316
- execSync2(
5334
+ execSync3(
5317
5335
  `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
5318
5336
  { timeout: 5e3 }
5319
5337
  );
@@ -5332,7 +5350,7 @@ async function tryKeytar() {
5332
5350
  }
5333
5351
  function deriveMachineKey() {
5334
5352
  try {
5335
- const crypto23 = __require("crypto");
5353
+ const crypto24 = __require("crypto");
5336
5354
  const material = [
5337
5355
  os6.hostname(),
5338
5356
  os6.userInfo().username,
@@ -5341,7 +5359,7 @@ function deriveMachineKey() {
5341
5359
  // Machine ID on Linux (stable across reboots)
5342
5360
  process.platform === "linux" ? readMachineId() : ""
5343
5361
  ].join("|");
5344
- return crypto23.createHash("sha256").update(material).digest();
5362
+ return crypto24.createHash("sha256").update(material).digest();
5345
5363
  } catch {
5346
5364
  return null;
5347
5365
  }
@@ -5355,9 +5373,9 @@ function readMachineId() {
5355
5373
  }
5356
5374
  }
5357
5375
  function encryptWithMachineKey(plaintext, machineKey) {
5358
- const crypto23 = __require("crypto");
5359
- const iv = crypto23.randomBytes(12);
5360
- const cipher = crypto23.createCipheriv("aes-256-gcm", machineKey, iv);
5376
+ const crypto24 = __require("crypto");
5377
+ const iv = crypto24.randomBytes(12);
5378
+ const cipher = crypto24.createCipheriv("aes-256-gcm", machineKey, iv);
5361
5379
  let encrypted = cipher.update(plaintext, "utf-8", "base64");
5362
5380
  encrypted += cipher.final("base64");
5363
5381
  const authTag = cipher.getAuthTag().toString("base64");
@@ -5366,13 +5384,13 @@ function encryptWithMachineKey(plaintext, machineKey) {
5366
5384
  function decryptWithMachineKey(encrypted, machineKey) {
5367
5385
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
5368
5386
  try {
5369
- const crypto23 = __require("crypto");
5387
+ const crypto24 = __require("crypto");
5370
5388
  const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
5371
5389
  if (parts.length !== 3) return null;
5372
5390
  const [ivB64, tagB64, cipherB64] = parts;
5373
5391
  const iv = Buffer.from(ivB64, "base64");
5374
5392
  const authTag = Buffer.from(tagB64, "base64");
5375
- const decipher = crypto23.createDecipheriv("aes-256-gcm", machineKey, iv);
5393
+ const decipher = crypto24.createDecipheriv("aes-256-gcm", machineKey, iv);
5376
5394
  decipher.setAuthTag(authTag);
5377
5395
  let decrypted = decipher.update(cipherB64, "base64", "utf-8");
5378
5396
  decrypted += decipher.final("utf-8");
@@ -5801,6 +5819,24 @@ var init_platform_procedures = __esm({
5801
5819
  priority: "p0",
5802
5820
  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."
5803
5821
  },
5822
+ {
5823
+ title: "Bug report status check \u2014 surface available fixes on boot",
5824
+ domain: "support",
5825
+ priority: "p1",
5826
+ 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."
5827
+ },
5828
+ {
5829
+ title: "Feature request triage \u2014 upstream feature vs local customization",
5830
+ domain: "support",
5831
+ priority: "p0",
5832
+ 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."
5833
+ },
5834
+ {
5835
+ title: "Feature request status check \u2014 surface shipped features on boot",
5836
+ domain: "support",
5837
+ priority: "p1",
5838
+ 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."
5839
+ },
5804
5840
  // --- Operations ---
5805
5841
  {
5806
5842
  title: "Managers must supervise deployed workers",
@@ -7175,8 +7211,8 @@ async function embedDirect(text3) {
7175
7211
  const llamaCpp = await import("node-llama-cpp");
7176
7212
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
7177
7213
  const { existsSync: existsSync48 } = await import("fs");
7178
- const path62 = await import("path");
7179
- const modelPath = path62.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7214
+ const path63 = await import("path");
7215
+ const modelPath = path63.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7180
7216
  if (!existsSync48(modelPath)) {
7181
7217
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
7182
7218
  }
@@ -7211,7 +7247,7 @@ __export(project_name_exports, {
7211
7247
  _resetCache: () => _resetCache,
7212
7248
  getProjectName: () => getProjectName
7213
7249
  });
7214
- import { execSync as execSync3 } from "child_process";
7250
+ import { execSync as execSync4 } from "child_process";
7215
7251
  import path14 from "path";
7216
7252
  function getProjectName(cwd) {
7217
7253
  const dir = cwd ?? process.cwd();
@@ -7219,7 +7255,7 @@ function getProjectName(cwd) {
7219
7255
  try {
7220
7256
  let repoRoot;
7221
7257
  try {
7222
- const gitCommonDir = execSync3("git rev-parse --path-format=absolute --git-common-dir", {
7258
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
7223
7259
  cwd: dir,
7224
7260
  encoding: "utf8",
7225
7261
  timeout: 2e3,
@@ -7227,7 +7263,7 @@ function getProjectName(cwd) {
7227
7263
  }).trim();
7228
7264
  repoRoot = path14.dirname(gitCommonDir);
7229
7265
  } catch {
7230
- repoRoot = execSync3("git rev-parse --show-toplevel", {
7266
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
7231
7267
  cwd: dir,
7232
7268
  encoding: "utf8",
7233
7269
  timeout: 2e3,
@@ -7261,14 +7297,14 @@ var file_grep_exports = {};
7261
7297
  __export(file_grep_exports, {
7262
7298
  grepProjectFiles: () => grepProjectFiles
7263
7299
  });
7264
- import { execSync as execSync4 } from "child_process";
7300
+ import { execSync as execSync5 } from "child_process";
7265
7301
  import { readFileSync as readFileSync9, readdirSync as readdirSync2, statSync as statSync6, existsSync as existsSync14 } from "fs";
7266
7302
  import path15 from "path";
7267
7303
  import crypto3 from "crypto";
7268
7304
  function hasRipgrep() {
7269
7305
  if (_hasRg === null) {
7270
7306
  try {
7271
- execSync4("rg --version", { stdio: "ignore", timeout: 2e3 });
7307
+ execSync5("rg --version", { stdio: "ignore", timeout: 2e3 });
7272
7308
  _hasRg = true;
7273
7309
  } catch {
7274
7310
  _hasRg = false;
@@ -7334,7 +7370,7 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
7334
7370
  const globs = (patterns ?? DEFAULT_PATTERNS).map((p) => `--glob '${p}'`).join(" ");
7335
7371
  const excludes = EXCLUDE_DIRS.map((d) => `--glob '!${d}'`).join(" ");
7336
7372
  const cmd = `rg -i -c --hidden --no-config --no-ignore '${pattern.replace(/'/g, "\\'")}' . ${globs} ${excludes} --max-filesize ${MAX_FILE_SIZE} 2>/dev/null || true`;
7337
- const output = execSync4(cmd, {
7373
+ const output = execSync5(cmd, {
7338
7374
  cwd: projectRoot,
7339
7375
  encoding: "utf8",
7340
7376
  timeout: 3e3,
@@ -7349,12 +7385,12 @@ function grepWithRipgrep(pattern, projectRoot, patterns) {
7349
7385
  const matchCount = parseInt(line.slice(colonIdx + 1));
7350
7386
  if (isNaN(matchCount) || matchCount === 0) continue;
7351
7387
  try {
7352
- const firstMatch = execSync4(
7388
+ const firstMatch = execSync5(
7353
7389
  `rg -i -n --hidden '${pattern.replace(/'/g, "\\'")}' '${filePath}' --max-count 1 2>/dev/null | head -1`,
7354
7390
  { cwd: projectRoot, encoding: "utf8", timeout: 1e3 }
7355
7391
  ).trim();
7356
7392
  const lineNum = parseInt(firstMatch.split(":")[0] ?? "1");
7357
- const totalLines = execSync4(`wc -l < '${filePath}'`, {
7393
+ const totalLines = execSync5(`wc -l < '${filePath}'`, {
7358
7394
  cwd: projectRoot,
7359
7395
  encoding: "utf8",
7360
7396
  timeout: 1e3
@@ -8268,10 +8304,10 @@ async function hybridSearch(queryText, agentId, options) {
8268
8304
  };
8269
8305
  try {
8270
8306
  const fs = await import("fs");
8271
- const path62 = await import("path");
8307
+ const path63 = await import("path");
8272
8308
  const os25 = await import("os");
8273
- const logPath = path62.join(os25.homedir(), ".exe-os", "search-quality.jsonl");
8274
- fs.mkdirSync(path62.dirname(logPath), { recursive: true });
8309
+ const logPath = path63.join(os25.homedir(), ".exe-os", "search-quality.jsonl");
8310
+ fs.mkdirSync(path63.dirname(logPath), { recursive: true });
8275
8311
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
8276
8312
  } catch {
8277
8313
  }
@@ -8705,7 +8741,7 @@ var init_hybrid_search = __esm({
8705
8741
  });
8706
8742
 
8707
8743
  // src/lib/session-key.ts
8708
- import { execSync as execSync5 } from "child_process";
8744
+ import { execSync as execSync6 } from "child_process";
8709
8745
  function normalizeCommand(command) {
8710
8746
  const trimmed = command.trim().toLowerCase();
8711
8747
  const parts = trimmed.split(/[\\/]/);
@@ -8724,7 +8760,7 @@ function resolveRuntimeProcess() {
8724
8760
  let pid = process.ppid;
8725
8761
  for (let i = 0; i < 10; i++) {
8726
8762
  try {
8727
- const info = execSync5(`ps -p ${pid} -o ppid=,comm=`, {
8763
+ const info = execSync6(`ps -p ${pid} -o ppid=,comm=`, {
8728
8764
  encoding: "utf8",
8729
8765
  timeout: 2e3
8730
8766
  }).trim();
@@ -8804,7 +8840,7 @@ __export(active_agent_exports, {
8804
8840
  writeActiveAgent: () => writeActiveAgent
8805
8841
  });
8806
8842
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
8807
- import { execSync as execSync6 } from "child_process";
8843
+ import { execSync as execSync7 } from "child_process";
8808
8844
  import path16 from "path";
8809
8845
  function isNameWithOptionalInstance(candidate, baseName) {
8810
8846
  if (candidate === baseName) return true;
@@ -8898,7 +8934,7 @@ function getActiveAgent() {
8898
8934
  } catch {
8899
8935
  }
8900
8936
  try {
8901
- const sessionName = execSync6(
8937
+ const sessionName = execSync7(
8902
8938
  "tmux display-message -p '#{session_name}' 2>/dev/null",
8903
8939
  { encoding: "utf8", timeout: 2e3 }
8904
8940
  ).trim();
@@ -9904,8 +9940,8 @@ __export(wiki_client_exports, {
9904
9940
  listDocuments: () => listDocuments,
9905
9941
  listWorkspaces: () => listWorkspaces
9906
9942
  });
9907
- async function wikiFetch(config2, path62, method = "GET", body) {
9908
- const url = `${config2.baseUrl}/api/v1${path62}`;
9943
+ async function wikiFetch(config2, path63, method = "GET", body) {
9944
+ const url = `${config2.baseUrl}/api/v1${path63}`;
9909
9945
  const headers = {
9910
9946
  Authorization: `Bearer ${config2.apiKey}`,
9911
9947
  "Content-Type": "application/json"
@@ -9938,7 +9974,7 @@ async function wikiFetch(config2, path62, method = "GET", body) {
9938
9974
  }
9939
9975
  }
9940
9976
  if (!response.ok) {
9941
- throw new Error(`Wiki API ${method} ${path62}: ${response.status} ${response.statusText}`);
9977
+ throw new Error(`Wiki API ${method} ${path63}: ${response.status} ${response.statusText}`);
9942
9978
  }
9943
9979
  return response.json();
9944
9980
  } finally {
@@ -11308,7 +11344,7 @@ __export(session_registry_exports, {
11308
11344
  registerSession: () => registerSession
11309
11345
  });
11310
11346
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync6, existsSync as existsSync17 } from "fs";
11311
- import { execSync as execSync7 } from "child_process";
11347
+ import { execSync as execSync8 } from "child_process";
11312
11348
  import path21 from "path";
11313
11349
  import os8 from "os";
11314
11350
  function registerSession(entry) {
@@ -11348,7 +11384,7 @@ function pruneStaleSessions() {
11348
11384
  if (sessions.length === 0) return 0;
11349
11385
  let liveSessions = [];
11350
11386
  try {
11351
- liveSessions = execSync7("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
11387
+ liveSessions = execSync8("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
11352
11388
  encoding: "utf8"
11353
11389
  }).trim().split("\n").filter(Boolean);
11354
11390
  } catch {
@@ -11496,14 +11532,14 @@ var init_transport = __esm({
11496
11532
  });
11497
11533
 
11498
11534
  // src/lib/cc-agent-support.ts
11499
- import { execSync as execSync8 } from "child_process";
11535
+ import { execSync as execSync9 } from "child_process";
11500
11536
  function _resetCcAgentSupportCache() {
11501
11537
  _cachedSupport = null;
11502
11538
  }
11503
11539
  function claudeSupportsAgentFlag() {
11504
11540
  if (_cachedSupport !== null) return _cachedSupport;
11505
11541
  try {
11506
- const helpOutput = execSync8("claude --help 2>&1", {
11542
+ const helpOutput = execSync9("claude --help 2>&1", {
11507
11543
  encoding: "utf-8",
11508
11544
  timeout: 5e3
11509
11545
  });
@@ -12480,7 +12516,7 @@ __export(tmux_routing_exports, {
12480
12516
  spawnEmployee: () => spawnEmployee,
12481
12517
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
12482
12518
  });
12483
- import { execFileSync as execFileSync2, execSync as execSync9 } from "child_process";
12519
+ import { execFileSync as execFileSync2, execSync as execSync10 } from "child_process";
12484
12520
  import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, mkdirSync as mkdirSync9, existsSync as existsSync21, appendFileSync as appendFileSync3, readdirSync as readdirSync5 } from "fs";
12485
12521
  import path25 from "path";
12486
12522
  import os11 from "os";
@@ -13201,7 +13237,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
13201
13237
  let booted = false;
13202
13238
  for (let i = 0; i < 30; i++) {
13203
13239
  try {
13204
- execSync9("sleep 0.5");
13240
+ execSync10("sleep 0.5");
13205
13241
  } catch {
13206
13242
  }
13207
13243
  try {
@@ -13455,7 +13491,7 @@ __export(tasks_crud_exports, {
13455
13491
  import crypto8 from "crypto";
13456
13492
  import path27 from "path";
13457
13493
  import os13 from "os";
13458
- import { execSync as execSync10 } from "child_process";
13494
+ import { execSync as execSync11 } from "child_process";
13459
13495
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
13460
13496
  import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
13461
13497
  async function writeCheckpoint(input) {
@@ -13800,14 +13836,14 @@ function isTmuxSessionAlive(identifier) {
13800
13836
  if (!identifier || identifier === "unknown") return true;
13801
13837
  try {
13802
13838
  if (identifier.startsWith("%")) {
13803
- const output = execSync10("tmux list-panes -a -F '#{pane_id}'", {
13839
+ const output = execSync11("tmux list-panes -a -F '#{pane_id}'", {
13804
13840
  timeout: 2e3,
13805
13841
  encoding: "utf8",
13806
13842
  stdio: ["pipe", "pipe", "pipe"]
13807
13843
  });
13808
13844
  return output.split("\n").some((l) => l.trim() === identifier);
13809
13845
  } else {
13810
- execSync10(`tmux has-session -t ${JSON.stringify(identifier)}`, {
13846
+ execSync11(`tmux has-session -t ${JSON.stringify(identifier)}`, {
13811
13847
  timeout: 2e3,
13812
13848
  stdio: ["pipe", "pipe", "pipe"]
13813
13849
  });
@@ -13816,7 +13852,7 @@ function isTmuxSessionAlive(identifier) {
13816
13852
  } catch {
13817
13853
  if (identifier.startsWith("%")) return true;
13818
13854
  try {
13819
- execSync10("tmux list-sessions", {
13855
+ execSync11("tmux list-sessions", {
13820
13856
  timeout: 2e3,
13821
13857
  stdio: ["pipe", "pipe", "pipe"]
13822
13858
  });
@@ -13831,12 +13867,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
13831
13867
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
13832
13868
  try {
13833
13869
  const since = new Date(taskCreatedAt).toISOString();
13834
- const branch = execSync10(
13870
+ const branch = execSync11(
13835
13871
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
13836
13872
  { encoding: "utf8", timeout: 3e3 }
13837
13873
  ).trim();
13838
13874
  const branchArg = branch && branch !== "HEAD" ? branch : "";
13839
- const commitCount = execSync10(
13875
+ const commitCount = execSync11(
13840
13876
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
13841
13877
  { encoding: "utf8", timeout: 5e3 }
13842
13878
  ).trim();
@@ -15019,12 +15055,14 @@ On EVERY new conversation, before doing anything else:
15019
15055
  1. **Memory scan**: Run recall_my_memory with broad queries \u2014 "project", "client", "pipeline", "campaign", "deal", "decision", "blocker". Summarize what you find.
15020
15056
  2. **Task scan**: Run list_tasks to see what's open, in progress, blocked, or needs review across all employees.
15021
15057
  3. **Team check**: Run ask_team_memory for recent activity from CTO/CMO/engineers.
15022
- 4. **Present the brief**: Give the founder a concise status report:
15058
+ 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.
15059
+ 5. **Present the brief**: Give the founder a concise status report:
15023
15060
  - What's active and progressing
15024
15061
  - What's blocked and needs attention
15025
15062
  - What decisions are pending
15063
+ - Available bug fixes (from step 4, if any)
15026
15064
  - What you recommend doing next
15027
- 5. Then ask: "What's the priority?"
15065
+ 6. Then ask: "What's the priority?"
15028
15066
 
15029
15067
  If this is your FIRST ever conversation (few or no prior memories):
15030
15068
  - Search more broadly: "product", "SEO", "meeting", "strategy", "revenue"
@@ -15044,6 +15082,8 @@ Never say "I have no memories" without first searching broadly. Your memory may
15044
15082
  - **get_identity** \u2014 read any agent's identity for coordination
15045
15083
  - **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.
15046
15084
  - **send_message** \u2014 direct intercom to employees
15085
+ - **create_bug_report** \u2014 file a bug when you encounter an Exe OS platform issue
15086
+ - **list_my_bug_reports** \u2014 check status of filed bugs (boot check: surface available fixes to founder)
15047
15087
  ${PLAN_MODE_COMPAT}
15048
15088
  ## Completion Workflow
15049
15089
 
@@ -19040,12 +19080,12 @@ function registerExportGraph(server) {
19040
19080
  }
19041
19081
  const html = await exportGraphHTML(client);
19042
19082
  const fs = await import("fs");
19043
- const path62 = await import("path");
19083
+ const path63 = await import("path");
19044
19084
  const os25 = await import("os");
19045
- const outDir = path62.join(os25.homedir(), ".exe-os", "exports");
19085
+ const outDir = path63.join(os25.homedir(), ".exe-os", "exports");
19046
19086
  fs.mkdirSync(outDir, { recursive: true });
19047
19087
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
19048
- const filePath = path62.join(outDir, `graph-${timestamp}.html`);
19088
+ const filePath = path63.join(outDir, `graph-${timestamp}.html`);
19049
19089
  fs.writeFileSync(filePath, html, "utf-8");
19050
19090
  return {
19051
19091
  content: [
@@ -20863,13 +20903,13 @@ __export(tmux_status_exports, {
20863
20903
  parseActivity: () => parseActivity,
20864
20904
  parseContextPercentage: () => parseContextPercentage
20865
20905
  });
20866
- import { execSync as execSync11 } from "child_process";
20906
+ import { execSync as execSync12 } from "child_process";
20867
20907
  function inTmux() {
20868
20908
  if (process.env.TMUX || process.env.TMUX_PANE) return true;
20869
20909
  const term = process.env.TERM ?? "";
20870
20910
  if (term.startsWith("tmux") || term.startsWith("screen")) return true;
20871
20911
  try {
20872
- execSync11("tmux display-message -p '#{session_name}' 2>/dev/null", {
20912
+ execSync12("tmux display-message -p '#{session_name}' 2>/dev/null", {
20873
20913
  encoding: "utf8",
20874
20914
  timeout: 2e3
20875
20915
  });
@@ -20879,12 +20919,12 @@ function inTmux() {
20879
20919
  try {
20880
20920
  let pid = process.ppid;
20881
20921
  for (let depth = 0; depth < 8 && pid > 1; depth++) {
20882
- const comm = execSync11(`ps -p ${pid} -o comm= 2>/dev/null`, {
20922
+ const comm = execSync12(`ps -p ${pid} -o comm= 2>/dev/null`, {
20883
20923
  encoding: "utf8",
20884
20924
  timeout: 1e3
20885
20925
  }).trim();
20886
20926
  if (/tmux/.test(comm)) return true;
20887
- const ppid = execSync11(`ps -p ${pid} -o ppid= 2>/dev/null`, {
20927
+ const ppid = execSync12(`ps -p ${pid} -o ppid= 2>/dev/null`, {
20888
20928
  encoding: "utf8",
20889
20929
  timeout: 1e3
20890
20930
  }).trim();
@@ -20897,7 +20937,7 @@ function inTmux() {
20897
20937
  }
20898
20938
  function listTmuxSessions() {
20899
20939
  try {
20900
- const out = execSync11("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
20940
+ const out = execSync12("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
20901
20941
  encoding: "utf8",
20902
20942
  timeout: 3e3
20903
20943
  });
@@ -20908,7 +20948,7 @@ function listTmuxSessions() {
20908
20948
  }
20909
20949
  function capturePaneLines(windowName, lines = 10) {
20910
20950
  try {
20911
- const out = execSync11(
20951
+ const out = execSync12(
20912
20952
  `tmux capture-pane -t ${JSON.stringify(windowName)} -p 2>/dev/null | tail -${lines}`,
20913
20953
  { encoding: "utf8", timeout: 3e3 }
20914
20954
  );
@@ -20919,7 +20959,7 @@ function capturePaneLines(windowName, lines = 10) {
20919
20959
  }
20920
20960
  function getPaneCwd(windowName) {
20921
20961
  try {
20922
- const out = execSync11(
20962
+ const out = execSync12(
20923
20963
  `tmux display-message -t ${JSON.stringify(windowName)} -p '#{pane_current_path}' 2>/dev/null`,
20924
20964
  { encoding: "utf8", timeout: 3e3 }
20925
20965
  );
@@ -20930,7 +20970,7 @@ function getPaneCwd(windowName) {
20930
20970
  }
20931
20971
  function projectFromPath(dir) {
20932
20972
  try {
20933
- const root = execSync11("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
20973
+ const root = execSync12("git -C " + JSON.stringify(dir) + " rev-parse --show-toplevel 2>/dev/null", {
20934
20974
  encoding: "utf8",
20935
20975
  timeout: 3e3
20936
20976
  }).trim();
@@ -20999,7 +21039,7 @@ function getEmployeeStatuses(employeeNames) {
20999
21039
  }
21000
21040
  let paneAlive = true;
21001
21041
  try {
21002
- const paneStatus = execSync11(
21042
+ const paneStatus = execSync12(
21003
21043
  `tmux list-panes -t ${JSON.stringify(sessionName)} -F '#{pane_dead}' 2>/dev/null`,
21004
21044
  { encoding: "utf8", timeout: 3e3 }
21005
21045
  ).trim();
@@ -21245,7 +21285,7 @@ __export(daemon_orchestration_exports, {
21245
21285
  shouldKillSession: () => shouldKillSession,
21246
21286
  shouldNudgeEmployee: () => shouldNudgeEmployee
21247
21287
  });
21248
- import { execSync as execSync12 } from "child_process";
21288
+ import { execSync as execSync13 } from "child_process";
21249
21289
  import { existsSync as existsSync29, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "fs";
21250
21290
  import { homedir as homedir5 } from "os";
21251
21291
  import { join as join3 } from "path";
@@ -21563,7 +21603,7 @@ function createSessionTTLRealDeps(getClient2) {
21563
21603
  },
21564
21604
  getSessionCreatedEpoch: (sessionName) => {
21565
21605
  try {
21566
- const out = execSync12(
21606
+ const out = execSync13(
21567
21607
  `tmux display-message -t ${JSON.stringify(sessionName)} -p '#{session_created}' 2>/dev/null`,
21568
21608
  { encoding: "utf8", timeout: 3e3 }
21569
21609
  ).trim();
@@ -21790,7 +21830,7 @@ function reapOrphanedMcpProcesses(deps) {
21790
21830
  function createOrphanReaperRealDeps() {
21791
21831
  return {
21792
21832
  listProcesses: () => {
21793
- const output = execSync12("ps -eo pid,ppid,args", {
21833
+ const output = execSync13("ps -eo pid,ppid,args", {
21794
21834
  encoding: "utf8",
21795
21835
  timeout: 5e3
21796
21836
  });
@@ -26130,9 +26170,9 @@ var init_hostinger_api = __esm({
26130
26170
  }
26131
26171
  this.lastRequestTime = Date.now();
26132
26172
  }
26133
- async request(method, path62, body) {
26173
+ async request(method, path63, body) {
26134
26174
  await this.rateLimit();
26135
- const url = `${this.baseUrl}${path62}`;
26175
+ const url = `${this.baseUrl}${path63}`;
26136
26176
  const headers = {
26137
26177
  Authorization: `Bearer ${this.apiKey}`,
26138
26178
  "Content-Type": "application/json",
@@ -26201,8 +26241,8 @@ async function requestCloudflare(cfApiToken, zoneId, options) {
26201
26241
  }
26202
26242
  return envelope.result;
26203
26243
  }
26204
- function buildUrl(zoneId, path62 = "/dns_records", query) {
26205
- const normalizedPath = path62.startsWith("/") ? path62 : `/${path62}`;
26244
+ function buildUrl(zoneId, path63 = "/dns_records", query) {
26245
+ const normalizedPath = path63.startsWith("/") ? path63 : `/${path63}`;
26206
26246
  const url = new URL(
26207
26247
  `${CLOUDFLARE_API_BASE_URL}/zones/${zoneId}${normalizedPath}`
26208
26248
  );
@@ -26901,7 +26941,7 @@ var init_trigger_engine = __esm({
26901
26941
 
26902
26942
  // src/lib/schedules.ts
26903
26943
  import crypto17 from "crypto";
26904
- import { execSync as execSync13 } from "child_process";
26944
+ import { execSync as execSync14 } from "child_process";
26905
26945
  function isValidCron(cron) {
26906
26946
  const fields = cron.trim().split(/\s+/);
26907
26947
  if (fields.length !== 5) return false;
@@ -27025,7 +27065,7 @@ function addToCrontab(id, cron, prompt, projectDir) {
27025
27065
  const cwd = projectDir ? `cd ${JSON.stringify(projectDir)} && ` : "";
27026
27066
  const escapedPrompt = prompt.replace(/"/g, '\\"');
27027
27067
  const entry = `${cron} ${cwd}claude -p --dangerously-skip-permissions "${escapedPrompt}" # exe-schedule:${id}`;
27028
- execSync13(
27068
+ execSync14(
27029
27069
  `(crontab -l 2>/dev/null; echo ${JSON.stringify(entry)}) | crontab -`,
27030
27070
  { timeout: 5e3, stdio: "ignore" }
27031
27071
  );
@@ -30161,9 +30201,207 @@ var init_create_bug_report = __esm({
30161
30201
  }
30162
30202
  });
30163
30203
 
30204
+ // src/mcp/tools/create-feature-request.ts
30205
+ import { z as z89 } from "zod";
30206
+ import crypto20 from "crypto";
30207
+ import { mkdir as mkdir7, writeFile as writeFile8 } from "fs/promises";
30208
+ import path54 from "path";
30209
+ function slugify3(input) {
30210
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "feature-request";
30211
+ }
30212
+ function section2(title, body) {
30213
+ return `## ${title}
30214
+
30215
+ ${body?.trim() || "Not provided"}`;
30216
+ }
30217
+ function buildMarkdown2(input) {
30218
+ return [
30219
+ `# Feature Request \u2014 ${input.title}`,
30220
+ "",
30221
+ `id: ${input.id}`,
30222
+ `category: ${input.category}`,
30223
+ `priority: ${input.priority}`,
30224
+ `filed_by: ${input.agentId} (${input.agentRole})`,
30225
+ `package_version: ${input.packageVersion}`,
30226
+ `project: ${input.projectName ?? "unknown"}`,
30227
+ `created_at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
30228
+ "",
30229
+ section2("Description", input.description),
30230
+ section2("Use Case", input.useCase),
30231
+ section2("Current Workaround", input.currentWorkaround),
30232
+ section2("Proposed Solution", input.proposedSolution),
30233
+ section2("Business Impact", input.businessImpact)
30234
+ ].join("\n");
30235
+ }
30236
+ async function maybeSendUpstream2(payload) {
30237
+ const config2 = await loadConfig();
30238
+ const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
30239
+ const endpoint2 = config2.support?.featureRequestEndpoint || process.env.EXE_FEATURE_REQUEST_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support/feature-requests` : "https://askexe.com/v1/support/feature-requests");
30240
+ const token = config2.support?.featureRequestToken || process.env.EXE_FEATURE_REQUEST_TOKEN;
30241
+ const licenseKey = loadLicense() || process.env.EXE_LICENSE_KEY || config2.cloud?.apiKey;
30242
+ const licenseToken = readCachedLicenseToken();
30243
+ if (!endpoint2) {
30244
+ return "not_configured";
30245
+ }
30246
+ try {
30247
+ const parsed = new URL(endpoint2);
30248
+ if (parsed.protocol !== "https:" && !["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
30249
+ return "failed: insecure endpoint rejected";
30250
+ }
30251
+ const response = await fetch(parsed, {
30252
+ method: "POST",
30253
+ headers: {
30254
+ "content-type": "application/json",
30255
+ ...token ? { authorization: `Bearer ${token}` } : {},
30256
+ ...licenseKey ? { "x-exe-license-key": licenseKey } : {},
30257
+ ...licenseToken ? { "x-exe-license-token": licenseToken } : {}
30258
+ },
30259
+ body: JSON.stringify(payload),
30260
+ signal: AbortSignal.timeout(1e4)
30261
+ });
30262
+ if (!response.ok) return `failed: HTTP ${response.status}`;
30263
+ return "sent";
30264
+ } catch (err) {
30265
+ return `failed: ${err instanceof Error ? err.message : String(err)}`;
30266
+ }
30267
+ }
30268
+ function registerCreateFeatureRequest(server) {
30269
+ server.registerTool(
30270
+ "create_feature_request",
30271
+ {
30272
+ title: "Create Feature Request",
30273
+ 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.",
30274
+ inputSchema: {
30275
+ title: z89.string().min(3).describe("Short descriptive title"),
30276
+ category: CATEGORY.describe(
30277
+ "upstream_feature = platform capability change; local_customization = configurable in customer layers; integration = third-party connector; unclear = needs product triage"
30278
+ ),
30279
+ priority: PRIORITY.default("p2").describe("p0 critical \u2192 p3 low"),
30280
+ description: z89.string().min(10).describe("What capability is needed and why"),
30281
+ use_case: z89.string().optional().describe("Concrete scenario where this feature would be used"),
30282
+ current_workaround: z89.string().optional().describe("How the need is currently addressed, if at all"),
30283
+ proposed_solution: z89.string().optional().describe("Suggested implementation approach"),
30284
+ business_impact: z89.string().optional().describe("Impact on business/workflow if this ships"),
30285
+ package_version: z89.string().optional().describe("Installed @askexenow/exe-os version"),
30286
+ project_name: z89.string().optional().describe("Project/customer context"),
30287
+ send_upstream: z89.boolean().default(true).describe("Attempt to POST to configured AskExe support endpoint")
30288
+ }
30289
+ },
30290
+ async ({
30291
+ title,
30292
+ category,
30293
+ priority,
30294
+ description,
30295
+ use_case,
30296
+ current_workaround,
30297
+ proposed_solution,
30298
+ business_impact,
30299
+ package_version,
30300
+ project_name,
30301
+ send_upstream
30302
+ }) => {
30303
+ const { agentId, agentRole } = getActiveAgent();
30304
+ const id = crypto20.randomUUID();
30305
+ const version = package_version ?? "unknown";
30306
+ const markdown = buildMarkdown2({
30307
+ id,
30308
+ title,
30309
+ category,
30310
+ priority,
30311
+ agentId,
30312
+ agentRole,
30313
+ packageVersion: version,
30314
+ description,
30315
+ useCase: use_case,
30316
+ currentWorkaround: current_workaround,
30317
+ proposedSolution: proposed_solution,
30318
+ businessImpact: business_impact,
30319
+ projectName: project_name
30320
+ });
30321
+ const outDir = path54.join(EXE_AI_DIR, "feature-requests");
30322
+ await mkdir7(outDir, { recursive: true });
30323
+ const reportPath = path54.join(outDir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}-${slugify3(title)}-${id.slice(0, 8)}.md`);
30324
+ await writeFile8(reportPath, markdown, "utf-8");
30325
+ let vector = null;
30326
+ try {
30327
+ vector = await embed(markdown);
30328
+ } catch {
30329
+ vector = null;
30330
+ }
30331
+ await writeMemory({
30332
+ id,
30333
+ agent_id: agentId,
30334
+ agent_role: agentRole,
30335
+ session_id: process.env.SESSION_ID ?? "manual",
30336
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
30337
+ tool_name: "create_feature_request",
30338
+ project_name: project_name ?? "support",
30339
+ has_error: false,
30340
+ raw_text: markdown,
30341
+ vector,
30342
+ source_path: reportPath,
30343
+ source_type: "feature_request",
30344
+ memory_type: "feature_request",
30345
+ tier: 1,
30346
+ importance: priority === "p0" ? 10 : priority === "p1" ? 9 : priority === "p2" ? 7 : 5,
30347
+ intent: "request",
30348
+ domain: "support",
30349
+ file_paths: null
30350
+ });
30351
+ await flushBatch();
30352
+ const upstreamStatus = send_upstream ? await maybeSendUpstream2({
30353
+ id,
30354
+ title,
30355
+ category,
30356
+ priority,
30357
+ description,
30358
+ use_case,
30359
+ current_workaround,
30360
+ proposed_solution,
30361
+ business_impact,
30362
+ package_version: version,
30363
+ project_name,
30364
+ agent_id: agentId,
30365
+ agent_role: agentRole
30366
+ }) : "skipped";
30367
+ return {
30368
+ content: [
30369
+ {
30370
+ type: "text",
30371
+ text: `Feature request created.
30372
+ ID: ${id}
30373
+ Category: ${category}
30374
+ Local report: ${reportPath}
30375
+ Memory stored: ${id}
30376
+ Upstream status: ${upstreamStatus}`
30377
+ }
30378
+ ]
30379
+ };
30380
+ }
30381
+ );
30382
+ }
30383
+ var CATEGORY, PRIORITY;
30384
+ var init_create_feature_request = __esm({
30385
+ "src/mcp/tools/create-feature-request.ts"() {
30386
+ "use strict";
30387
+ init_embedder();
30388
+ init_active_agent();
30389
+ init_config();
30390
+ init_license();
30391
+ init_store();
30392
+ CATEGORY = z89.enum([
30393
+ "upstream_feature",
30394
+ "local_customization",
30395
+ "integration",
30396
+ "unclear"
30397
+ ]);
30398
+ PRIORITY = z89.enum(["p0", "p1", "p2", "p3"]);
30399
+ }
30400
+ });
30401
+
30164
30402
  // src/bin/exe-support.ts
30165
30403
  import { mkdirSync as mkdirSync21, readFileSync as readFileSync35, unlinkSync as unlinkSync13, writeFileSync as writeFileSync24 } from "fs";
30166
- import path54 from "path";
30404
+ import path55 from "path";
30167
30405
  import { randomUUID as randomUUID9 } from "crypto";
30168
30406
  async function runHealth() {
30169
30407
  const checks = [];
@@ -30282,8 +30520,8 @@ async function resolveEndpoints() {
30282
30520
  return { bugEndpoint, healthEndpoint, adminEndpoint };
30283
30521
  }
30284
30522
  function checkLocalWrite() {
30285
- const dir = path54.join(EXE_AI_DIR, "bug-reports");
30286
- const testPath = path54.join(dir, ".support-write-test");
30523
+ const dir = path55.join(EXE_AI_DIR, "bug-reports");
30524
+ const testPath = path55.join(dir, ".support-write-test");
30287
30525
  try {
30288
30526
  mkdirSync21(dir, { recursive: true, mode: 448 });
30289
30527
  writeFileSync24(testPath, "ok\n", { mode: 384 });
@@ -30299,10 +30537,10 @@ function checkLocalWrite() {
30299
30537
  }
30300
30538
  }
30301
30539
  function writeLocalTestReport(id, project, version) {
30302
- const dir = path54.join(EXE_AI_DIR, "bug-reports");
30540
+ const dir = path55.join(EXE_AI_DIR, "bug-reports");
30303
30541
  mkdirSync21(dir, { recursive: true, mode: 448 });
30304
30542
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
30305
- const filePath = path54.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
30543
+ const filePath = path55.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
30306
30544
  writeFileSync24(filePath, `# TEST \u2014 ${project} support intake
30307
30545
 
30308
30546
  Report ID: ${id}
@@ -30344,15 +30582,15 @@ async function maybeCloseAdmin(id, adminEndpoint, version) {
30344
30582
  }
30345
30583
  }
30346
30584
  function readPackageVersion2() {
30347
- let dir = path54.dirname(new URL(import.meta.url).pathname);
30585
+ let dir = path55.dirname(new URL(import.meta.url).pathname);
30348
30586
  for (let i = 0; i < 6; i++) {
30349
- const pkg = path54.join(dir, "package.json");
30587
+ const pkg = path55.join(dir, "package.json");
30350
30588
  try {
30351
30589
  const parsed = JSON.parse(readFileSync35(pkg, "utf8"));
30352
30590
  if (parsed.version) return parsed.version;
30353
30591
  } catch {
30354
30592
  }
30355
- dir = path54.dirname(dir);
30593
+ dir = path55.dirname(dir);
30356
30594
  }
30357
30595
  return "unknown";
30358
30596
  }
@@ -30388,7 +30626,7 @@ var init_exe_support = __esm({
30388
30626
  });
30389
30627
 
30390
30628
  // src/mcp/tools/support.ts
30391
- import { z as z89 } from "zod";
30629
+ import { z as z90 } from "zod";
30392
30630
  function formatRows(rows, mode) {
30393
30631
  const lines = [`exe-os support ${mode}`, ""];
30394
30632
  for (const row of rows) {
@@ -30429,7 +30667,7 @@ function registerSupportTools(server) {
30429
30667
  title: "Support Test",
30430
30668
  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.",
30431
30669
  inputSchema: {
30432
- project: z89.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
30670
+ project: z90.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
30433
30671
  }
30434
30672
  },
30435
30673
  async ({ project }) => {
@@ -30441,11 +30679,139 @@ function registerSupportTools(server) {
30441
30679
  };
30442
30680
  }
30443
30681
  );
30682
+ server.registerTool(
30683
+ "list_my_bug_reports",
30684
+ {
30685
+ title: "My Bug Reports",
30686
+ 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.",
30687
+ inputSchema: {
30688
+ status: z90.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("all").describe("Filter by status. Default: all"),
30689
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
30690
+ }
30691
+ },
30692
+ async ({ status: status2, limit }) => {
30693
+ const licenseKey = loadLicense();
30694
+ const licenseToken = readCachedLicenseToken();
30695
+ if (!licenseKey && !licenseToken) {
30696
+ return {
30697
+ content: [{ type: "text", text: "No license key found. Run `exe-os setup` or `exe-os cloud setup` first." }],
30698
+ isError: true
30699
+ };
30700
+ }
30701
+ const endpoint2 = new URL("https://askexe.com/v1/support/my-reports");
30702
+ endpoint2.searchParams.set("status", status2);
30703
+ endpoint2.searchParams.set("limit", String(limit));
30704
+ const headers = { "content-type": "application/json" };
30705
+ if (licenseKey) headers["x-exe-license-key"] = licenseKey;
30706
+ if (licenseToken) headers["x-exe-license-token"] = licenseToken;
30707
+ try {
30708
+ const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
30709
+ if (!res.ok) {
30710
+ const body = await res.text().catch(() => "");
30711
+ return {
30712
+ content: [{ type: "text", text: `Failed to fetch bug reports: HTTP ${res.status}${body ? ` \u2014 ${body}` : ""}` }],
30713
+ isError: true
30714
+ };
30715
+ }
30716
+ const data = await res.json();
30717
+ if (data.count === 0) {
30718
+ return {
30719
+ content: [{ type: "text", text: `No bug reports found${status2 !== "all" ? ` with status '${status2}'` : ""}.` }],
30720
+ structuredContent: { items: [], count: 0 }
30721
+ };
30722
+ }
30723
+ const lines = [`Bug reports (${data.count}):`, ""];
30724
+ for (const r of data.items) {
30725
+ const sevIcon = r.severity === "p0" ? "\u{1F534}" : r.severity === "p1" ? "\u{1F534}" : r.severity === "p2" ? "\u{1F7E0}" : "\u{1F7E2}";
30726
+ const statusIcon = r.status === "fixed" ? "\u2705" : r.status === "closed" ? "\u2611\uFE0F" : r.status === "triaged" ? "\u{1F50D}" : r.status === "wontfix" ? "\u26D4" : "\u{1F535}";
30727
+ lines.push(`${sevIcon} ${r.severity.toUpperCase()} ${statusIcon} ${r.status} \u2014 ${r.title}`);
30728
+ lines.push(` ID: ${r.id} | Filed: ${r.created_at?.slice(0, 10) ?? "?"}`);
30729
+ if (r.fixed_version) lines.push(` \u{1F527} Fixed in: ${r.fixed_version} \u2014 run \`exe-os update\` to get this fix`);
30730
+ if (r.triage_notes) lines.push(` \u{1F4DD} AskExe: ${r.triage_notes}`);
30731
+ lines.push("");
30732
+ }
30733
+ return {
30734
+ content: [{ type: "text", text: lines.join("\n") }],
30735
+ structuredContent: data
30736
+ };
30737
+ } catch (err) {
30738
+ return {
30739
+ content: [{ type: "text", text: `Error fetching bug reports: ${err instanceof Error ? err.message : String(err)}` }],
30740
+ isError: true
30741
+ };
30742
+ }
30743
+ }
30744
+ );
30745
+ server.registerTool(
30746
+ "list_my_feature_requests",
30747
+ {
30748
+ title: "My Feature Requests",
30749
+ 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.",
30750
+ inputSchema: {
30751
+ status: z90.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("all").describe("Filter by status. Default: all"),
30752
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
30753
+ }
30754
+ },
30755
+ async ({ status: status2, limit }) => {
30756
+ const licenseKey = loadLicense();
30757
+ const licenseToken = readCachedLicenseToken();
30758
+ if (!licenseKey && !licenseToken) {
30759
+ return {
30760
+ content: [{ type: "text", text: "No license key found. Run `exe-os setup` or `exe-os cloud setup` first." }],
30761
+ isError: true
30762
+ };
30763
+ }
30764
+ const endpoint2 = new URL("https://askexe.com/v1/support/my-feature-requests");
30765
+ endpoint2.searchParams.set("status", status2);
30766
+ endpoint2.searchParams.set("limit", String(limit));
30767
+ const headers = { "content-type": "application/json" };
30768
+ if (licenseKey) headers["x-exe-license-key"] = licenseKey;
30769
+ if (licenseToken) headers["x-exe-license-token"] = licenseToken;
30770
+ try {
30771
+ const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
30772
+ if (!res.ok) {
30773
+ const body = await res.text().catch(() => "");
30774
+ return {
30775
+ content: [{ type: "text", text: `Failed to fetch feature requests: HTTP ${res.status}${body ? ` \u2014 ${body}` : ""}` }],
30776
+ isError: true
30777
+ };
30778
+ }
30779
+ const data = await res.json();
30780
+ if (data.count === 0) {
30781
+ return {
30782
+ content: [{ type: "text", text: `No feature requests found${status2 !== "all" ? ` with status '${status2}'` : ""}.` }],
30783
+ structuredContent: { items: [], count: 0 }
30784
+ };
30785
+ }
30786
+ const lines = [`Feature requests (${data.count}):`, ""];
30787
+ for (const r of data.items) {
30788
+ const priIcon = r.priority === "p0" ? "\u{1F534}" : r.priority === "p1" ? "\u{1F534}" : r.priority === "p2" ? "\u{1F7E0}" : "\u{1F7E2}";
30789
+ 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}";
30790
+ lines.push(`${priIcon} ${r.priority.toUpperCase()} ${statusIcon} ${r.status} \u2014 ${r.title}`);
30791
+ lines.push(` ID: ${r.id} | Filed: ${r.created_at?.slice(0, 10) ?? "?"}`);
30792
+ if (r.shipped_version) lines.push(` \u{1F680} Shipped in: ${r.shipped_version} \u2014 run \`exe-os update\` to get this feature`);
30793
+ if (r.target_version) lines.push(` \u{1F3AF} Target: ${r.target_version}`);
30794
+ if (r.response_notes) lines.push(` \u{1F4DD} AskExe: ${r.response_notes}`);
30795
+ lines.push("");
30796
+ }
30797
+ return {
30798
+ content: [{ type: "text", text: lines.join("\n") }],
30799
+ structuredContent: data
30800
+ };
30801
+ } catch (err) {
30802
+ return {
30803
+ content: [{ type: "text", text: `Error fetching feature requests: ${err instanceof Error ? err.message : String(err)}` }],
30804
+ isError: true
30805
+ };
30806
+ }
30807
+ }
30808
+ );
30444
30809
  }
30445
30810
  var init_support = __esm({
30446
30811
  "src/mcp/tools/support.ts"() {
30447
30812
  "use strict";
30448
30813
  init_exe_support();
30814
+ init_license();
30449
30815
  }
30450
30816
  });
30451
30817
 
@@ -30514,21 +30880,21 @@ var init_exe_status = __esm({
30514
30880
 
30515
30881
  // src/bin/exe-healthcheck.ts
30516
30882
  import { existsSync as existsSync43, readFileSync as readFileSync36, readdirSync as readdirSync14 } from "fs";
30517
- import path55 from "path";
30518
- import { execSync as execSync14 } from "child_process";
30883
+ import path56 from "path";
30884
+ import { execSync as execSync15 } from "child_process";
30519
30885
  import { fileURLToPath as fileURLToPath6 } from "url";
30520
30886
  function findPackageRoot2() {
30521
- let dir = path55.dirname(fileURLToPath6(import.meta.url));
30522
- const { root } = path55.parse(dir);
30887
+ let dir = path56.dirname(fileURLToPath6(import.meta.url));
30888
+ const { root } = path56.parse(dir);
30523
30889
  while (dir !== root) {
30524
- if (existsSync43(path55.join(dir, "package.json"))) return dir;
30525
- dir = path55.dirname(dir);
30890
+ if (existsSync43(path56.join(dir, "package.json"))) return dir;
30891
+ dir = path56.dirname(dir);
30526
30892
  }
30527
30893
  throw new Error("Cannot find package root");
30528
30894
  }
30529
30895
  function checkBuildIntegrity(pkgRoot) {
30530
30896
  const results = [];
30531
- const tsupConfig = path55.join(pkgRoot, "tsup.config.ts");
30897
+ const tsupConfig = path56.join(pkgRoot, "tsup.config.ts");
30532
30898
  if (!existsSync43(tsupConfig)) {
30533
30899
  return [{ name: "build/tsup-config", pass: false, detail: "tsup.config.ts not found" }];
30534
30900
  }
@@ -30538,7 +30904,7 @@ function checkBuildIntegrity(pkgRoot) {
30538
30904
  let total = 0;
30539
30905
  for (const match of entryMatches) {
30540
30906
  const outputKey = match[1];
30541
- const expectedPath = path55.join(pkgRoot, "dist", `${outputKey}.js`);
30907
+ const expectedPath = path56.join(pkgRoot, "dist", `${outputKey}.js`);
30542
30908
  total++;
30543
30909
  if (!existsSync43(expectedPath)) {
30544
30910
  missing.push(`dist/${outputKey}.js`);
@@ -30562,7 +30928,7 @@ function checkBuildIntegrity(pkgRoot) {
30562
30928
  }
30563
30929
  function checkEmbedPipeline(pkgRoot) {
30564
30930
  const results = [];
30565
- const daemonPath = path55.join(pkgRoot, "dist", "lib", "exe-daemon.js");
30931
+ const daemonPath = path56.join(pkgRoot, "dist", "lib", "exe-daemon.js");
30566
30932
  if (!existsSync43(daemonPath)) {
30567
30933
  results.push({
30568
30934
  name: "exed/daemon-exists",
@@ -30574,17 +30940,17 @@ function checkEmbedPipeline(pkgRoot) {
30574
30940
  results.push({ name: "exed/daemon-exists", pass: true, detail: "dist/lib/exe-daemon.js exists" });
30575
30941
  const entryDirs = ["dist/hooks", "dist/bin", "dist/mcp"];
30576
30942
  for (const dir of entryDirs) {
30577
- const fullDir = path55.join(pkgRoot, dir);
30943
+ const fullDir = path56.join(pkgRoot, dir);
30578
30944
  if (!existsSync43(fullDir)) continue;
30579
30945
  let walkDir = fullDir;
30580
- const { root } = path55.parse(walkDir);
30946
+ const { root } = path56.parse(walkDir);
30581
30947
  let foundRoot = null;
30582
30948
  while (walkDir !== root) {
30583
- if (existsSync43(path55.join(walkDir, "package.json"))) {
30949
+ if (existsSync43(path56.join(walkDir, "package.json"))) {
30584
30950
  foundRoot = walkDir;
30585
30951
  break;
30586
30952
  }
30587
- walkDir = path55.dirname(walkDir);
30953
+ walkDir = path56.dirname(walkDir);
30588
30954
  }
30589
30955
  if (!foundRoot) {
30590
30956
  results.push({
@@ -30594,7 +30960,7 @@ function checkEmbedPipeline(pkgRoot) {
30594
30960
  });
30595
30961
  continue;
30596
30962
  }
30597
- const resolvedDaemon = path55.join(foundRoot, "dist", "lib", "exe-daemon.js");
30963
+ const resolvedDaemon = path56.join(foundRoot, "dist", "lib", "exe-daemon.js");
30598
30964
  const reachable = existsSync43(resolvedDaemon);
30599
30965
  results.push({
30600
30966
  name: `exed/reachable-from-${dir}`,
@@ -30606,13 +30972,13 @@ function checkEmbedPipeline(pkgRoot) {
30606
30972
  }
30607
30973
  function checkTaskSystem(pkgRoot) {
30608
30974
  const results = [];
30609
- const scannerPath = path55.join(pkgRoot, "dist", "bin", "scan-tasks.js");
30975
+ const scannerPath = path56.join(pkgRoot, "dist", "bin", "scan-tasks.js");
30610
30976
  if (!existsSync43(scannerPath)) {
30611
30977
  results.push({ name: "tasks/scanner", pass: false, detail: "scan-tasks.js not found" });
30612
30978
  return results;
30613
30979
  }
30614
30980
  try {
30615
- execSync14(`node "${scannerPath}" /tmp/nonexistent-healthcheck-test --format=json 2>/dev/null`, {
30981
+ execSync15(`node "${scannerPath}" /tmp/nonexistent-healthcheck-test --format=json 2>/dev/null`, {
30616
30982
  timeout: 1e4,
30617
30983
  encoding: "utf-8"
30618
30984
  });
@@ -30629,13 +30995,13 @@ function checkTaskSystem(pkgRoot) {
30629
30995
  }
30630
30996
  function checkWorkerSpawning(pkgRoot) {
30631
30997
  const results = [];
30632
- const workerPath = path55.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
30998
+ const workerPath = path56.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
30633
30999
  if (!existsSync43(workerPath)) {
30634
31000
  results.push({ name: "workers/ingest-worker", pass: false, detail: "ingest-worker.js not found" });
30635
31001
  return results;
30636
31002
  }
30637
31003
  try {
30638
- execSync14(`node --check "${workerPath}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
31004
+ execSync15(`node --check "${workerPath}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
30639
31005
  results.push({ name: "workers/ingest-worker", pass: true, detail: "ingest-worker.js parses OK" });
30640
31006
  } catch (err) {
30641
31007
  results.push({
@@ -30644,14 +31010,14 @@ function checkWorkerSpawning(pkgRoot) {
30644
31010
  detail: `Parse error: ${err instanceof Error ? err.message.slice(0, 200) : String(err)}`
30645
31011
  });
30646
31012
  }
30647
- const hooksDir = path55.join(pkgRoot, "dist", "hooks");
31013
+ const hooksDir = path56.join(pkgRoot, "dist", "hooks");
30648
31014
  if (existsSync43(hooksDir)) {
30649
31015
  const hookFiles = readdirSync14(hooksDir).filter((f) => f.endsWith(".js") && !f.endsWith(".js.map"));
30650
31016
  let hooksPassed = 0;
30651
31017
  const hooksFailed = [];
30652
31018
  for (const hook of hookFiles) {
30653
31019
  try {
30654
- execSync14(`node --check "${path55.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
31020
+ execSync15(`node --check "${path56.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
30655
31021
  hooksPassed++;
30656
31022
  } catch {
30657
31023
  hooksFailed.push(hook);
@@ -30675,8 +31041,8 @@ function checkWorkerSpawning(pkgRoot) {
30675
31041
  }
30676
31042
  function checkMcpTransport() {
30677
31043
  const results = [];
30678
- const pidPath = path55.join(EXE_AI_DIR, "exed.pid");
30679
- const tokenPath = path55.join(EXE_AI_DIR, "exed.token");
31044
+ const pidPath = path56.join(EXE_AI_DIR, "exed.pid");
31045
+ const tokenPath = path56.join(EXE_AI_DIR, "exed.token");
30680
31046
  let daemonAlive = false;
30681
31047
  if (existsSync43(pidPath)) {
30682
31048
  try {
@@ -30693,7 +31059,7 @@ function checkMcpTransport() {
30693
31059
  if (daemonAlive && existsSync43(tokenPath)) {
30694
31060
  try {
30695
31061
  const token = readFileSync36(tokenPath, "utf8").trim();
30696
- const response = execSync14(
31062
+ const response = execSync15(
30697
31063
  `curl -sS -i -m 2 -H "Authorization: Bearer ${token}" -H 'Accept: application/json, text/event-stream' http://127.0.0.1:48739/mcp`,
30698
31064
  { encoding: "utf8", timeout: 3e3 }
30699
31065
  );
@@ -30723,7 +31089,7 @@ function checkMcpTransport() {
30723
31089
  results.push({
30724
31090
  name: "mcp/monitor-summary",
30725
31091
  pass: true,
30726
- detail: `Privacy-safe local monitor summary: ${path55.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
31092
+ detail: `Privacy-safe local monitor summary: ${path56.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
30727
31093
  });
30728
31094
  return results;
30729
31095
  }
@@ -30744,7 +31110,7 @@ function checkClaudeCodeInstall() {
30744
31110
  });
30745
31111
  }
30746
31112
  try {
30747
- const claudePath = execSync14("which claude 2>/dev/null || true", { encoding: "utf8", timeout: 5e3 }).trim();
31113
+ const claudePath = execSync15("which claude 2>/dev/null || true", { encoding: "utf8", timeout: 5e3 }).trim();
30748
31114
  if (!claudePath) {
30749
31115
  results.push({
30750
31116
  name: "cc/cli-binary",
@@ -30754,7 +31120,7 @@ function checkClaudeCodeInstall() {
30754
31120
  } else {
30755
31121
  let resolved = claudePath;
30756
31122
  try {
30757
- resolved = execSync14(`readlink -f "${claudePath}" 2>/dev/null || readlink "${claudePath}" 2>/dev/null || echo "${claudePath}"`, {
31123
+ resolved = execSync15(`readlink -f "${claudePath}" 2>/dev/null || readlink "${claudePath}" 2>/dev/null || echo "${claudePath}"`, {
30758
31124
  encoding: "utf8",
30759
31125
  timeout: 5e3
30760
31126
  }).trim();
@@ -30781,7 +31147,7 @@ function checkClaudeCodeInstall() {
30781
31147
  detail: "Failed to check claude binary path"
30782
31148
  });
30783
31149
  }
30784
- const versionsDir = path55.join(
31150
+ const versionsDir = path56.join(
30785
31151
  process.env.HOME ?? process.env.USERPROFILE ?? "",
30786
31152
  ".local",
30787
31153
  "share",
@@ -30838,7 +31204,7 @@ function checkClaudeCodeInstall() {
30838
31204
  });
30839
31205
  }
30840
31206
  try {
30841
- const ccVersion = execSync14("claude --version 2>/dev/null || echo unknown", {
31207
+ const ccVersion = execSync15("claude --version 2>/dev/null || echo unknown", {
30842
31208
  encoding: "utf8",
30843
31209
  timeout: 5e3
30844
31210
  }).trim();
@@ -30900,17 +31266,17 @@ __export(update_check_exports, {
30900
31266
  getLocalVersion: () => getLocalVersion,
30901
31267
  getRemoteVersion: () => getRemoteVersion
30902
31268
  });
30903
- import { execSync as execSync15 } from "child_process";
31269
+ import { execSync as execSync16 } from "child_process";
30904
31270
  import { readFileSync as readFileSync37 } from "fs";
30905
- import path56 from "path";
31271
+ import path57 from "path";
30906
31272
  function getLocalVersion(packageRoot) {
30907
- const pkgPath = path56.join(packageRoot, "package.json");
31273
+ const pkgPath = path57.join(packageRoot, "package.json");
30908
31274
  const pkg = JSON.parse(readFileSync37(pkgPath, "utf-8"));
30909
31275
  return pkg.version;
30910
31276
  }
30911
31277
  function getRemoteVersion() {
30912
31278
  try {
30913
- const output = execSync15("npm view @askexenow/exe-os version", {
31279
+ const output = execSync16("npm view @askexenow/exe-os version", {
30914
31280
  encoding: "utf-8",
30915
31281
  timeout: 15e3,
30916
31282
  stdio: ["pipe", "pipe", "pipe"]
@@ -30944,7 +31310,7 @@ var init_update_check = __esm({
30944
31310
  // src/mcp/tools/cli-parity.ts
30945
31311
  import { execFile as execFile2 } from "child_process";
30946
31312
  import { promisify as promisify2 } from "util";
30947
- import { z as z90 } from "zod";
31313
+ import { z as z91 } from "zod";
30948
31314
  async function runCommand(command, args, timeout = 6e4) {
30949
31315
  const printable = [command, ...args].join(" ");
30950
31316
  try {
@@ -30979,12 +31345,12 @@ function registerCliParityTools(server) {
30979
31345
  title: "Doctor",
30980
31346
  description: "Run exe-os doctor audit. Defaults to read-only diagnostics; optional dry-run/fix flags mirror CLI.",
30981
31347
  inputSchema: {
30982
- agent: z90.string().optional(),
30983
- project: z90.string().optional(),
30984
- verbose: z90.boolean().default(false),
30985
- conflicts: z90.boolean().default(false),
30986
- dry_run: z90.boolean().default(false),
30987
- fix: z90.boolean().default(false)
31348
+ agent: z91.string().optional(),
31349
+ project: z91.string().optional(),
31350
+ verbose: z91.boolean().default(false),
31351
+ conflicts: z91.boolean().default(false),
31352
+ dry_run: z91.boolean().default(false),
31353
+ fix: z91.boolean().default(false)
30988
31354
  }
30989
31355
  }, async ({ agent, project, verbose, conflicts, dry_run, fix }) => {
30990
31356
  const args = [];
@@ -31001,9 +31367,9 @@ function registerCliParityTools(server) {
31001
31367
  title: "Rename Employee",
31002
31368
  description: "Rename an employee using the same path as `exe-os rename <old> <new>`. Use for customer roster/identity renames.",
31003
31369
  inputSchema: {
31004
- old_name: z90.string().min(1),
31005
- new_name: z90.string().min(1),
31006
- dry_run: z90.boolean().default(false)
31370
+ old_name: z91.string().min(1),
31371
+ new_name: z91.string().min(1),
31372
+ dry_run: z91.boolean().default(false)
31007
31373
  }
31008
31374
  }, async ({ old_name, new_name, dry_run }) => {
31009
31375
  if (dry_run) {
@@ -31016,7 +31382,7 @@ function registerCliParityTools(server) {
31016
31382
  server.registerTool("status_brief", {
31017
31383
  title: "Status Brief",
31018
31384
  description: "Return current employee/tmux status. Mirrors `exe-status` and supports optional deep view for one employee.",
31019
- inputSchema: { employee: z90.string().optional() }
31385
+ inputSchema: { employee: z91.string().optional() }
31020
31386
  }, async ({ employee }) => {
31021
31387
  const text3 = await status(employee);
31022
31388
  return result2(text3, { ok: true, employee: employee ?? null });
@@ -31024,7 +31390,7 @@ function registerCliParityTools(server) {
31024
31390
  server.registerTool("pending_work_summary", {
31025
31391
  title: "Pending Work Summary",
31026
31392
  description: "Return pending reviews, messages, and notifications using the same summaries as the CLI tools.",
31027
- inputSchema: { agent: z90.string().optional() }
31393
+ inputSchema: { agent: z91.string().optional() }
31028
31394
  }, async ({ agent }) => {
31029
31395
  const parts = await Promise.all([
31030
31396
  runCommand("exe-pending-reviews", [], 3e4),
@@ -31045,7 +31411,7 @@ function registerCliParityTools(server) {
31045
31411
  server.registerTool("key_rotation_preflight", {
31046
31412
  title: "Key Rotation Preflight",
31047
31413
  description: "Dry-run key rotation/update preflight. No destructive changes.",
31048
- inputSchema: { mode: z90.enum(["rotate", "update"]).default("rotate") }
31414
+ inputSchema: { mode: z91.enum(["rotate", "update"]).default("rotate") }
31049
31415
  }, async ({ mode }) => {
31050
31416
  const out = await runCommand("exe-os", ["key", mode, "--dry-run"], 6e4);
31051
31417
  return result2(out.text, { ok: out.ok, command: out.command, mode }, !out.ok);
@@ -31064,11 +31430,11 @@ function registerCliParityTools(server) {
31064
31430
  title: "Stack Update Check",
31065
31431
  description: "Plan/check a customer stack update without applying Docker changes. Mirrors `exe-os stack-update --check`.",
31066
31432
  inputSchema: {
31067
- target: z90.string().optional(),
31068
- manifest: z90.string().optional(),
31069
- compose_file: z90.string().optional(),
31070
- env_file: z90.string().optional(),
31071
- deployment_persona: z90.enum(["customer", "askexe-control-plane"]).default("customer")
31433
+ target: z91.string().optional(),
31434
+ manifest: z91.string().optional(),
31435
+ compose_file: z91.string().optional(),
31436
+ env_file: z91.string().optional(),
31437
+ deployment_persona: z91.enum(["customer", "askexe-control-plane"]).default("customer")
31072
31438
  }
31073
31439
  }, async ({ target, manifest, compose_file, env_file, deployment_persona }) => {
31074
31440
  const args = ["stack-update", "--check", "--deployment-persona", deployment_persona];
@@ -31189,7 +31555,7 @@ var init_session_events = __esm({
31189
31555
  });
31190
31556
 
31191
31557
  // src/mcp/tools/get-session-events.ts
31192
- import { z as z91 } from "zod";
31558
+ import { z as z92 } from "zod";
31193
31559
  function canReadAgent(activeRole, activeAgent, requestedAgent) {
31194
31560
  return requestedAgent === activeAgent || activeRole === "COO" || activeRole === "CTO";
31195
31561
  }
@@ -31213,11 +31579,11 @@ function registerGetSessionEvents(server) {
31213
31579
  title: "Get Session Events",
31214
31580
  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.",
31215
31581
  inputSchema: {
31216
- agent_id: z91.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
31217
- session_id: z91.string().optional().describe("Optional exact runtime session id."),
31582
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
31583
+ session_id: z92.string().optional().describe("Optional exact runtime session id."),
31218
31584
  event_type: EVENT_TYPE.optional().describe("Filter to one event type."),
31219
- project_name: z91.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
31220
- limit: z91.number().int().min(1).max(100).default(20).describe("Number of events to return.")
31585
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
31586
+ limit: z92.number().int().min(1).max(100).default(20).describe("Number of events to return.")
31221
31587
  }
31222
31588
  },
31223
31589
  async ({ agent_id, session_id, event_type, project_name, limit }) => {
@@ -31253,8 +31619,8 @@ function registerGetLastAssistantResponse(server) {
31253
31619
  title: "Get Last Assistant Response",
31254
31620
  description: "Return the exact last assistant response for an agent from the session event journal. Use for 'what was the last thing you said?'",
31255
31621
  inputSchema: {
31256
- agent_id: z91.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
31257
- project_name: z91.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
31622
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
31623
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
31258
31624
  }
31259
31625
  },
31260
31626
  async ({ agent_id, project_name }) => {
@@ -31289,7 +31655,7 @@ var init_get_session_events = __esm({
31289
31655
  init_active_agent();
31290
31656
  init_fast_db_init();
31291
31657
  init_session_events();
31292
- EVENT_TYPE = z91.enum([
31658
+ EVENT_TYPE = z92.enum([
31293
31659
  "user_prompt",
31294
31660
  "assistant_response",
31295
31661
  "tool_call",
@@ -31300,25 +31666,96 @@ var init_get_session_events = __esm({
31300
31666
  });
31301
31667
 
31302
31668
  // src/lib/code-context-index.ts
31303
- import crypto20 from "crypto";
31304
- import path57 from "path";
31669
+ import crypto21 from "crypto";
31670
+ import path58 from "path";
31305
31671
  import { existsSync as existsSync44, mkdirSync as mkdirSync22, readFileSync as readFileSync38, readdirSync as readdirSync15, statSync as statSync10, writeFileSync as writeFileSync25 } from "fs";
31306
31672
  import { spawnSync } from "child_process";
31673
+ function vectorStorePath(projectRoot) {
31674
+ const rootHash = hashText(projectRoot).slice(0, 16);
31675
+ return path58.join(indexDir(), `${rootHash}.vectors.json`);
31676
+ }
31677
+ function loadVectorStore(projectRoot) {
31678
+ const file = vectorStorePath(projectRoot);
31679
+ if (!existsSync44(file)) return null;
31680
+ try {
31681
+ const parsed = JSON.parse(readFileSync38(file, "utf8"));
31682
+ if (parsed.version !== VECTOR_STORE_VERSION) return null;
31683
+ return parsed;
31684
+ } catch {
31685
+ return null;
31686
+ }
31687
+ }
31688
+ function saveVectorStore(projectRoot, store) {
31689
+ writeFileSync25(vectorStorePath(projectRoot), JSON.stringify(store));
31690
+ }
31691
+ function cosineSimilarity3(a, b) {
31692
+ let dot = 0, normA = 0, normB = 0;
31693
+ for (let i = 0; i < a.length; i++) {
31694
+ dot += a[i] * b[i];
31695
+ normA += a[i] * a[i];
31696
+ normB += b[i] * b[i];
31697
+ }
31698
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
31699
+ return denom === 0 ? 0 : dot / denom;
31700
+ }
31701
+ async function embedSymbols(index) {
31702
+ const rootHash = hashText(index.projectRoot).slice(0, 16);
31703
+ const existing = loadVectorStore(index.projectRoot);
31704
+ const store = {
31705
+ version: VECTOR_STORE_VERSION,
31706
+ projectRootHash: rootHash,
31707
+ vectors: {}
31708
+ };
31709
+ const allSymbols = [];
31710
+ for (const file of Object.values(index.files)) {
31711
+ for (const symbol of file.symbols) {
31712
+ if (existing?.vectors[symbol.id]) {
31713
+ store.vectors[symbol.id] = existing.vectors[symbol.id];
31714
+ } else {
31715
+ allSymbols.push({ id: symbol.id, text: symbol.summary || `${symbol.kind} ${symbol.name} in ${symbol.filePath}` });
31716
+ }
31717
+ }
31718
+ }
31719
+ if (allSymbols.length === 0) {
31720
+ saveVectorStore(index.projectRoot, store);
31721
+ return store;
31722
+ }
31723
+ const connected = await connectEmbedDaemon().catch(() => false);
31724
+ if (!connected) {
31725
+ saveVectorStore(index.projectRoot, store);
31726
+ return store;
31727
+ }
31728
+ for (let i = 0; i < allSymbols.length; i += EMBED_BATCH_SIZE) {
31729
+ const batch = allSymbols.slice(i, i + EMBED_BATCH_SIZE);
31730
+ const texts = batch.map((s) => s.text);
31731
+ try {
31732
+ const vectors = await embedBatchViaClient(texts, "low");
31733
+ if (vectors && vectors.length === batch.length) {
31734
+ for (let j = 0; j < batch.length; j++) {
31735
+ store.vectors[batch[j].id] = vectors[j];
31736
+ }
31737
+ }
31738
+ } catch {
31739
+ }
31740
+ }
31741
+ saveVectorStore(index.projectRoot, store);
31742
+ return store;
31743
+ }
31307
31744
  function normalizeProjectRoot(projectRoot) {
31308
- return path57.resolve(projectRoot || process.cwd());
31745
+ return path58.resolve(projectRoot || process.cwd());
31309
31746
  }
31310
31747
  function hashText(text3) {
31311
- return crypto20.createHash("sha256").update(text3).digest("hex");
31748
+ return crypto21.createHash("sha256").update(text3).digest("hex");
31312
31749
  }
31313
31750
  function indexDir() {
31314
- const dir = path57.join(EXE_AI_DIR, "code-context");
31751
+ const dir = path58.join(EXE_AI_DIR, "code-context");
31315
31752
  mkdirSync22(dir, { recursive: true });
31316
31753
  return dir;
31317
31754
  }
31318
31755
  function getCodeContextIndexPath(projectRoot) {
31319
31756
  const root = normalizeProjectRoot(projectRoot);
31320
31757
  const rootHash = hashText(root).slice(0, 16);
31321
- return path57.join(indexDir(), `${rootHash}.json`);
31758
+ return path58.join(indexDir(), `${rootHash}.json`);
31322
31759
  }
31323
31760
  function currentBranch(projectRoot) {
31324
31761
  const result3 = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
@@ -31331,8 +31768,8 @@ function shouldIgnore(relPath) {
31331
31768
  }
31332
31769
  function listRecursive(projectRoot, dir = projectRoot, out = []) {
31333
31770
  for (const entry of readdirSync15(dir, { withFileTypes: true })) {
31334
- const abs = path57.join(dir, entry.name);
31335
- const rel = path57.relative(projectRoot, abs).replaceAll(path57.sep, "/");
31771
+ const abs = path58.join(dir, entry.name);
31772
+ const rel = path58.relative(projectRoot, abs).replaceAll(path58.sep, "/");
31336
31773
  if (shouldIgnore(rel)) continue;
31337
31774
  if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
31338
31775
  else if (entry.isFile()) out.push(rel);
@@ -31348,7 +31785,7 @@ function listCodeFiles(projectRoot, maxFiles) {
31348
31785
  const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
31349
31786
  files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
31350
31787
  }
31351
- return files.map((file) => file.replaceAll(path57.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
31788
+ return files.map((file) => file.replaceAll(path58.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
31352
31789
  }
31353
31790
  function parseImportPaths2(importText) {
31354
31791
  const paths = [];
@@ -31361,13 +31798,13 @@ function parseImportPaths2(importText) {
31361
31798
  }
31362
31799
  function resolveImport(fromFile, importPath, allFiles) {
31363
31800
  if (!importPath.startsWith(".")) return null;
31364
- const base = path57.posix.normalize(path57.posix.join(path57.posix.dirname(fromFile.replaceAll(path57.sep, "/")), importPath));
31801
+ const base = path58.posix.normalize(path58.posix.join(path58.posix.dirname(fromFile.replaceAll(path58.sep, "/")), importPath));
31365
31802
  const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
31366
31803
  const candidates = [base];
31367
31804
  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"]) {
31368
31805
  candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
31369
31806
  }
31370
- for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path57.posix.join(base, indexName));
31807
+ for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path58.posix.join(base, indexName));
31371
31808
  return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
31372
31809
  }
31373
31810
  function symbolId(filePath, chunk) {
@@ -31388,7 +31825,7 @@ function saveIndex(index) {
31388
31825
  writeFileSync25(getCodeContextIndexPath(index.projectRoot), JSON.stringify(index, null, 2));
31389
31826
  }
31390
31827
  function buildFileRecord(projectRoot, relPath, allFiles, previous) {
31391
- const absPath = path57.join(projectRoot, relPath);
31828
+ const absPath = path58.join(projectRoot, relPath);
31392
31829
  let stat;
31393
31830
  try {
31394
31831
  stat = statSync10(absPath);
@@ -31426,13 +31863,13 @@ function buildCodeContextIndex(options = {}) {
31426
31863
  const branch = currentBranch(projectRoot);
31427
31864
  const previous = options.force ? null : loadIndex(projectRoot);
31428
31865
  const files = listCodeFiles(projectRoot, maxFiles);
31429
- const allFiles = new Set(files.map((file) => file.replaceAll(path57.sep, "/")));
31866
+ const allFiles = new Set(files.map((file) => file.replaceAll(path58.sep, "/")));
31430
31867
  const fileRecords = {};
31431
31868
  let rebuiltFiles = 0;
31432
31869
  let reusedFiles = 0;
31433
31870
  let skippedFiles = 0;
31434
31871
  for (const rel of files) {
31435
- const normalized = rel.replaceAll(path57.sep, "/");
31872
+ const normalized = rel.replaceAll(path58.sep, "/");
31436
31873
  const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
31437
31874
  if (record) {
31438
31875
  fileRecords[normalized] = record;
@@ -31461,11 +31898,11 @@ function loadOrBuildCodeContextIndex(options = {}) {
31461
31898
  if (loaded) {
31462
31899
  const currentFiles = listCodeFiles(projectRoot, options.maxFiles ?? DEFAULT_MAX_FILES);
31463
31900
  const unchanged = currentFiles.every((rel) => {
31464
- const normalized = rel.replaceAll(path57.sep, "/");
31901
+ const normalized = rel.replaceAll(path58.sep, "/");
31465
31902
  const existing = loaded.files[normalized];
31466
31903
  if (!existing) return false;
31467
31904
  try {
31468
- const stat = statSync10(path57.join(projectRoot, normalized));
31905
+ const stat = statSync10(path58.join(projectRoot, normalized));
31469
31906
  return stat.mtimeMs === existing.mtimeMs && stat.size === existing.size;
31470
31907
  } catch {
31471
31908
  return false;
@@ -31518,9 +31955,9 @@ function globToRegex(pattern) {
31518
31955
  }
31519
31956
  function matchesPath(filePath, patterns) {
31520
31957
  if (!patterns || patterns.length === 0) return true;
31521
- const normalized = filePath.replaceAll(path57.sep, "/");
31958
+ const normalized = filePath.replaceAll(path58.sep, "/");
31522
31959
  return patterns.some((pattern) => {
31523
- const p = pattern.replaceAll(path57.sep, "/").replace(/^\.\//, "");
31960
+ const p = pattern.replaceAll(path58.sep, "/").replace(/^\.\//, "");
31524
31961
  return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
31525
31962
  });
31526
31963
  }
@@ -31579,7 +32016,7 @@ function filteredFiles(index, options = {}) {
31579
32016
  return matchesPath(file.path, options.paths);
31580
32017
  });
31581
32018
  }
31582
- function searchCodeContext(query, options = {}) {
32019
+ function lexicalSearch(query, options = {}) {
31583
32020
  const terms = tokenize(query);
31584
32021
  if (terms.length === 0) return [];
31585
32022
  const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
@@ -31605,6 +32042,81 @@ function searchCodeContext(query, options = {}) {
31605
32042
  const limit = options.limit ?? 20;
31606
32043
  return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
31607
32044
  }
32045
+ function searchCodeContext(query, options = {}) {
32046
+ return lexicalSearch(query, options);
32047
+ }
32048
+ async function searchCodeContextSemantic(query, options = {}) {
32049
+ const terms = tokenize(query);
32050
+ if (terms.length === 0) return [];
32051
+ const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
32052
+ const projectRoot = normalizeProjectRoot(options.projectRoot);
32053
+ const vectorStore = loadVectorStore(projectRoot);
32054
+ if (!vectorStore || Object.keys(vectorStore.vectors).length === 0) {
32055
+ return lexicalSearch(query, options);
32056
+ }
32057
+ let queryVector = null;
32058
+ try {
32059
+ const connected = await connectEmbedDaemon().catch(() => false);
32060
+ if (connected) {
32061
+ const result3 = await embedBatchViaClient([query], "high");
32062
+ if (result3 && result3.length === 1) queryVector = result3[0];
32063
+ }
32064
+ } catch {
32065
+ }
32066
+ if (!queryVector) return lexicalSearch(query, options);
32067
+ const files = filteredFiles(index, options);
32068
+ const candidates = [];
32069
+ for (const file of files) {
32070
+ for (const symbol of file.symbols) {
32071
+ const lexical = scoreSymbol(symbol, terms);
32072
+ const vec = vectorStore.vectors[symbol.id];
32073
+ const vectorScore = vec ? cosineSimilarity3(queryVector, vec) : 0;
32074
+ if (lexical.score > 0 || vectorScore > 0.3) {
32075
+ candidates.push({
32076
+ symbol,
32077
+ lexicalScore: lexical.score,
32078
+ vectorScore,
32079
+ matches: lexical.matches
32080
+ });
32081
+ }
32082
+ }
32083
+ }
32084
+ const byLexical = [...candidates].sort((a, b) => b.lexicalScore - a.lexicalScore);
32085
+ const byVector = [...candidates].sort((a, b) => b.vectorScore - a.vectorScore);
32086
+ const lexicalRank = /* @__PURE__ */ new Map();
32087
+ const vectorRank = /* @__PURE__ */ new Map();
32088
+ byLexical.forEach((c, i) => lexicalRank.set(c.symbol.id, i + 1));
32089
+ byVector.forEach((c, i) => vectorRank.set(c.symbol.id, i + 1));
32090
+ const VECTOR_WEIGHT = 0.6;
32091
+ const LEXICAL_WEIGHT = 0.4;
32092
+ const fused = candidates.map((c) => {
32093
+ const lRank = lexicalRank.get(c.symbol.id) ?? candidates.length + 1;
32094
+ const vRank = vectorRank.get(c.symbol.id) ?? candidates.length + 1;
32095
+ const rrfScore = VECTOR_WEIGHT * (1 / (RRF_K2 + vRank)) + LEXICAL_WEIGHT * (1 / (RRF_K2 + lRank));
32096
+ return {
32097
+ symbol: c.symbol,
32098
+ score: rrfScore,
32099
+ matches: c.vectorScore > 0.3 ? [...c.matches, `semantic=${c.vectorScore.toFixed(3)}`] : c.matches,
32100
+ filePath: c.symbol.filePath,
32101
+ language: c.symbol.language,
32102
+ content: c.symbol.text,
32103
+ startLine: c.symbol.startLine,
32104
+ endLine: c.symbol.endLine
32105
+ };
32106
+ });
32107
+ const offset = Math.max(0, options.offset ?? 0);
32108
+ const limit = options.limit ?? 20;
32109
+ return fused.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
32110
+ }
32111
+ async function buildCodeContextIndexWithEmbeddings(options = {}) {
32112
+ const index = buildCodeContextIndex(options);
32113
+ const existingStore = loadVectorStore(index.projectRoot);
32114
+ const existingCount = existingStore ? Object.keys(existingStore.vectors).length : 0;
32115
+ const store = await embedSymbols(index);
32116
+ const vectorCount = Object.keys(store.vectors).length;
32117
+ const newEmbeddings = vectorCount - Math.min(existingCount, vectorCount);
32118
+ return { index, vectorCount, newEmbeddings };
32119
+ }
31608
32120
  function dependentsMap(index) {
31609
32121
  const map = /* @__PURE__ */ new Map();
31610
32122
  for (const file of Object.values(index.files)) {
@@ -31638,7 +32150,7 @@ function traceCodeSymbol(symbolName, options = {}) {
31638
32150
  }
31639
32151
  function resolveTargetFile(index, input) {
31640
32152
  if (input.filePath) {
31641
- const normalized = input.filePath.replaceAll(path57.sep, "/").replace(/^\.\//, "");
32153
+ const normalized = input.filePath.replaceAll(path58.sep, "/").replace(/^\.\//, "");
31642
32154
  if (index.files[normalized]) return { filePath: normalized, target: normalized };
31643
32155
  const suffix = Object.keys(index.files).find((file) => file.endsWith(normalized));
31644
32156
  if (suffix) return { filePath: suffix, target: input.filePath };
@@ -31668,7 +32180,7 @@ function analyzeBlastRadius(input) {
31668
32180
  }
31669
32181
  }
31670
32182
  }
31671
- const targetBase = path57.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
32183
+ const targetBase = path58.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
31672
32184
  const symbolLower = input.symbol?.toLowerCase();
31673
32185
  const tests = Object.keys(index.files).filter((file) => {
31674
32186
  const lower = file.toLowerCase();
@@ -31696,20 +32208,24 @@ function getCodeContextStats(options = {}) {
31696
32208
  indexPath: getCodeContextIndexPath(index.projectRoot)
31697
32209
  };
31698
32210
  }
31699
- var INDEX_VERSION, DEFAULT_MAX_FILES, IGNORE_SEGMENTS;
32211
+ var VECTOR_STORE_VERSION, EMBED_BATCH_SIZE, INDEX_VERSION, DEFAULT_MAX_FILES, IGNORE_SEGMENTS, RRF_K2;
31700
32212
  var init_code_context_index = __esm({
31701
32213
  "src/lib/code-context-index.ts"() {
31702
32214
  "use strict";
31703
32215
  init_config();
31704
32216
  init_code_chunker();
32217
+ init_exe_daemon_client();
32218
+ VECTOR_STORE_VERSION = 1;
32219
+ EMBED_BATCH_SIZE = 64;
31705
32220
  INDEX_VERSION = 2;
31706
32221
  DEFAULT_MAX_FILES = 5e3;
31707
32222
  IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
32223
+ RRF_K2 = 60;
31708
32224
  }
31709
32225
  });
31710
32226
 
31711
32227
  // src/mcp/tools/code-context.ts
31712
- import { z as z92 } from "zod";
32228
+ import { z as z93 } from "zod";
31713
32229
  function errorResult10(text3) {
31714
32230
  return { content: [{ type: "text", text: text3 }], isError: true };
31715
32231
  }
@@ -31719,23 +32235,24 @@ function jsonResult(value) {
31719
32235
  function registerCodeContext(server) {
31720
32236
  server.registerTool("code_context", {
31721
32237
  title: "Code Context",
31722
- description: "Persistent codebase context engine. One consolidated tool to avoid MCP bloat. Actions: index, search, trace, blast_radius, stats.",
32238
+ 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.",
31723
32239
  inputSchema: {
31724
- action: z92.enum(["index", "search", "trace", "blast_radius", "stats"]).describe("Code context operation"),
31725
- project_root: z92.string().optional().describe("Repository root. Defaults to current working directory."),
31726
- query: z92.string().optional().describe("Search query for action=search"),
31727
- symbol: z92.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
31728
- file_path: z92.string().optional().describe("File path for blast_radius"),
31729
- force: z92.boolean().optional().describe("Force rebuild before answering"),
31730
- limit: z92.coerce.number().int().min(1).max(100).optional().describe("Max results"),
31731
- offset: z92.coerce.number().int().min(0).optional().describe("Search pagination offset"),
31732
- refresh_index: z92.boolean().optional().describe("Refresh/rebuild index before searching"),
31733
- languages: z92.array(z92.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
31734
- paths: z92.array(z92.string()).optional().describe("Path/glob filters"),
31735
- depth: z92.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
31736
- max_files: z92.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index")
31737
- }
31738
- }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files }) => {
32240
+ action: z93.enum(["index", "index_embed", "search", "trace", "blast_radius", "stats"]).describe("Code context operation. index_embed = index + generate embeddings for semantic search."),
32241
+ project_root: z93.string().optional().describe("Repository root. Defaults to current working directory."),
32242
+ query: z93.string().optional().describe("Natural language search query (e.g. 'authentication logic', 'database migration'). Semantic search finds conceptual matches, not just keywords."),
32243
+ symbol: z93.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
32244
+ file_path: z93.string().optional().describe("File path for blast_radius"),
32245
+ force: z93.boolean().optional().describe("Force rebuild before answering"),
32246
+ limit: z93.coerce.number().int().min(1).max(100).optional().describe("Max results"),
32247
+ offset: z93.coerce.number().int().min(0).optional().describe("Search pagination offset"),
32248
+ refresh_index: z93.boolean().optional().describe("Refresh/rebuild index before searching"),
32249
+ languages: z93.array(z93.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
32250
+ paths: z93.array(z93.string()).optional().describe("Path/glob filters"),
32251
+ depth: z93.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
32252
+ max_files: z93.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index"),
32253
+ lexical_only: z93.boolean().optional().describe("Force lexical-only search (skip vector similarity). Default: false \u2014 uses semantic hybrid search.")
32254
+ }
32255
+ }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files, lexical_only }) => {
31739
32256
  const opts = { projectRoot: project_root, force, maxFiles: max_files };
31740
32257
  if (action === "index") {
31741
32258
  const index = buildCodeContextIndex(opts);
@@ -31745,7 +32262,24 @@ function registerCodeContext(server) {
31745
32262
  indexedAt: index.indexedAt,
31746
32263
  files: Object.keys(index.files).length,
31747
32264
  symbols: Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0),
31748
- imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0)
32265
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
32266
+ note: "Structural index only. Use action=index_embed to also generate vector embeddings for semantic search."
32267
+ });
32268
+ }
32269
+ if (action === "index_embed") {
32270
+ const { index, vectorCount, newEmbeddings } = await buildCodeContextIndexWithEmbeddings(opts);
32271
+ const totalSymbols = Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0);
32272
+ return jsonResult({
32273
+ projectRoot: index.projectRoot,
32274
+ branch: index.branch,
32275
+ indexedAt: index.indexedAt,
32276
+ files: Object.keys(index.files).length,
32277
+ symbols: totalSymbols,
32278
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
32279
+ vectorCount,
32280
+ newEmbeddings,
32281
+ embeddingCoverage: totalSymbols > 0 ? `${(vectorCount / totalSymbols * 100).toFixed(1)}%` : "0%",
32282
+ note: "Structural index + vector embeddings. Semantic search is now available."
31749
32283
  });
31750
32284
  }
31751
32285
  if (action === "stats") {
@@ -31753,14 +32287,28 @@ function registerCodeContext(server) {
31753
32287
  }
31754
32288
  if (action === "search") {
31755
32289
  if (!query) return errorResult10('code_context action "search" requires query');
32290
+ const searchOpts = { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths };
32291
+ if (lexical_only) {
32292
+ return jsonResult({
32293
+ query,
32294
+ mode: "lexical",
32295
+ limit: limit ?? 20,
32296
+ offset: offset ?? 0,
32297
+ languages: languages ?? [],
32298
+ paths: paths ?? [],
32299
+ results: searchCodeContext(query, searchOpts)
32300
+ });
32301
+ }
32302
+ const results = await searchCodeContextSemantic(query, searchOpts);
32303
+ const hasSemantic = results.some((r) => r.matches.some((m) => m.startsWith("semantic=")));
31756
32304
  return jsonResult({
31757
32305
  query,
32306
+ mode: hasSemantic ? "semantic+lexical" : "lexical-fallback",
31758
32307
  limit: limit ?? 20,
31759
32308
  offset: offset ?? 0,
31760
- refresh_index: refresh_index ?? false,
31761
32309
  languages: languages ?? [],
31762
32310
  paths: paths ?? [],
31763
- results: searchCodeContext(query, { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths })
32311
+ results
31764
32312
  });
31765
32313
  }
31766
32314
  if (action === "trace") {
@@ -31784,7 +32332,7 @@ var init_code_context = __esm({
31784
32332
  });
31785
32333
 
31786
32334
  // src/mcp/tools/support-inbox.ts
31787
- import { z as z93 } from "zod";
32335
+ import { z as z94 } from "zod";
31788
32336
  function adminToken() {
31789
32337
  return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN;
31790
32338
  }
@@ -31823,9 +32371,9 @@ function registerListBugReports(server) {
31823
32371
  title: "List Bug Reports",
31824
32372
  description: "AskExe-internal only: list incoming customer bug reports from the support inbox.",
31825
32373
  inputSchema: {
31826
- status: z93.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
31827
- severity: z93.enum(["p0", "p1", "p2", "p3"]).optional(),
31828
- limit: z93.number().int().min(1).max(100).default(25)
32374
+ status: z94.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
32375
+ severity: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
32376
+ limit: z94.number().int().min(1).max(100).default(25)
31829
32377
  }
31830
32378
  },
31831
32379
  async ({ status: status2, severity, limit }) => {
@@ -31844,7 +32392,7 @@ function registerGetBugReport(server) {
31844
32392
  {
31845
32393
  title: "Get Bug Report",
31846
32394
  description: "AskExe-internal only: fetch one customer bug report with full markdown payload.",
31847
- inputSchema: { id: z93.string().min(8) }
32395
+ inputSchema: { id: z94.string().min(8) }
31848
32396
  },
31849
32397
  async ({ id }) => {
31850
32398
  const data = await requestJson(`${endpoint()}/${encodeURIComponent(id)}`);
@@ -31859,12 +32407,12 @@ function registerTriageBugReport(server) {
31859
32407
  title: "Triage Bug Report",
31860
32408
  description: "AskExe-internal only: update bug report status and link task/commit/release metadata.",
31861
32409
  inputSchema: {
31862
- id: z93.string().min(8),
32410
+ id: z94.string().min(8),
31863
32411
  status: STATUS.optional(),
31864
- triage_notes: z93.string().optional(),
31865
- linked_task_id: z93.string().optional(),
31866
- linked_commit: z93.string().optional(),
31867
- fixed_version: z93.string().optional()
32412
+ triage_notes: z94.string().optional(),
32413
+ linked_task_id: z94.string().optional(),
32414
+ linked_commit: z94.string().optional(),
32415
+ fixed_version: z94.string().optional()
31868
32416
  }
31869
32417
  },
31870
32418
  async ({ id, status: status2, triage_notes, linked_task_id, linked_commit, fixed_version }) => {
@@ -31876,12 +32424,75 @@ function registerTriageBugReport(server) {
31876
32424
  }
31877
32425
  );
31878
32426
  }
31879
- var DEFAULT_ENDPOINT, STATUS;
32427
+ function featureEndpoint() {
32428
+ return endpoint().replace(/\/bug-reports\/?$/, "/feature-requests");
32429
+ }
32430
+ function registerListFeatureRequests(server) {
32431
+ server.registerTool(
32432
+ "list_feature_requests",
32433
+ {
32434
+ title: "List Feature Requests",
32435
+ description: "AskExe-internal only: list incoming customer feature requests from the support inbox.",
32436
+ inputSchema: {
32437
+ status: z94.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("open"),
32438
+ priority: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
32439
+ limit: z94.number().int().min(1).max(100).default(25)
32440
+ }
32441
+ },
32442
+ async ({ status: status2, priority, limit }) => {
32443
+ const url = new URL(featureEndpoint());
32444
+ url.searchParams.set("status", status2);
32445
+ url.searchParams.set("limit", String(limit));
32446
+ if (priority) url.searchParams.set("priority", priority);
32447
+ const data = await requestJson(url.toString());
32448
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
32449
+ }
32450
+ );
32451
+ }
32452
+ function registerGetFeatureRequest(server) {
32453
+ server.registerTool(
32454
+ "get_feature_request",
32455
+ {
32456
+ title: "Get Feature Request",
32457
+ description: "AskExe-internal only: fetch one customer feature request with full payload.",
32458
+ inputSchema: { id: z94.string().min(8) }
32459
+ },
32460
+ async ({ id }) => {
32461
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`);
32462
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
32463
+ }
32464
+ );
32465
+ }
32466
+ function registerTriageFeatureRequest(server) {
32467
+ server.registerTool(
32468
+ "triage_feature_request",
32469
+ {
32470
+ title: "Triage Feature Request",
32471
+ description: "AskExe-internal only: update feature request status, response notes, and version metadata.",
32472
+ inputSchema: {
32473
+ id: z94.string().min(8),
32474
+ status: FEATURE_STATUS.optional(),
32475
+ response_notes: z94.string().optional(),
32476
+ target_version: z94.string().optional(),
32477
+ shipped_version: z94.string().optional()
32478
+ }
32479
+ },
32480
+ async ({ id, status: status2, response_notes, target_version, shipped_version }) => {
32481
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`, {
32482
+ method: "PATCH",
32483
+ body: JSON.stringify({ status: status2, response_notes, target_version, shipped_version })
32484
+ });
32485
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
32486
+ }
32487
+ );
32488
+ }
32489
+ var DEFAULT_ENDPOINT, STATUS, FEATURE_STATUS;
31880
32490
  var init_support_inbox = __esm({
31881
32491
  "src/mcp/tools/support-inbox.ts"() {
31882
32492
  "use strict";
31883
32493
  DEFAULT_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
31884
- STATUS = z93.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
32494
+ STATUS = z94.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
32495
+ FEATURE_STATUS = z94.enum(["open", "planned", "in_progress", "shipped", "closed", "wontdo"]);
31885
32496
  }
31886
32497
  });
31887
32498
 
@@ -32217,6 +32828,7 @@ function registerAllTools(server) {
32217
32828
  gate("registerStoreDecision", registerStoreDecision);
32218
32829
  gate("registerGetDecision", registerGetDecision);
32219
32830
  gate("registerCreateBugReport", registerCreateBugReport);
32831
+ gate("registerCreateFeatureRequest", registerCreateFeatureRequest);
32220
32832
  gate("registerSupportTools", registerSupportTools);
32221
32833
  gate("registerCliParityTools", registerCliParityTools);
32222
32834
  gate("registerGetSessionEvents", registerGetSessionEvents);
@@ -32226,6 +32838,9 @@ function registerAllTools(server) {
32226
32838
  gate("registerListBugReports", registerListBugReports);
32227
32839
  gate("registerGetBugReport", registerGetBugReport);
32228
32840
  gate("registerTriageBugReport", registerTriageBugReport);
32841
+ gate("registerListFeatureRequests", registerListFeatureRequests);
32842
+ gate("registerGetFeatureRequest", registerGetFeatureRequest);
32843
+ gate("registerTriageFeatureRequest", registerTriageFeatureRequest);
32229
32844
  }
32230
32845
  if (exposeLegacyConfig) {
32231
32846
  gate("registerGetAgentSpend", registerGetAgentSpend);
@@ -32368,6 +32983,7 @@ var init_register_tools = __esm({
32368
32983
  init_query_company_brain();
32369
32984
  init_company_actions();
32370
32985
  init_create_bug_report();
32986
+ init_create_feature_request();
32371
32987
  init_support();
32372
32988
  init_cli_parity();
32373
32989
  init_get_session_events();
@@ -32383,7 +32999,7 @@ __export(review_polling_exports, {
32383
32999
  createRealDeps: () => createRealDeps,
32384
33000
  pollPendingReviews: () => pollPendingReviews
32385
33001
  });
32386
- import { execSync as execSync16 } from "child_process";
33002
+ import { execSync as execSync17 } from "child_process";
32387
33003
  async function pollPendingReviews(deps, _state) {
32388
33004
  let sessions;
32389
33005
  try {
@@ -32490,7 +33106,7 @@ async function pollPendingReviews(deps, _state) {
32490
33106
  function createRealDeps(getClient2) {
32491
33107
  return {
32492
33108
  listTmuxSessions: () => {
32493
- return execSync16("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
33109
+ return execSync17("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
32494
33110
  encoding: "utf8",
32495
33111
  timeout: 3e3
32496
33112
  }).trim().split("\n").filter(Boolean);
@@ -32598,7 +33214,7 @@ __export(task_enforcement_exports, {
32598
33214
  sendNudge: () => sendNudge
32599
33215
  });
32600
33216
  import { writeFileSync as writeFileSync26 } from "fs";
32601
- import path58 from "path";
33217
+ import path59 from "path";
32602
33218
  function writeAuditEntry(entry) {
32603
33219
  try {
32604
33220
  const line = JSON.stringify(entry) + "\n";
@@ -32773,7 +33389,7 @@ var init_task_enforcement = __esm({
32773
33389
  "What do you need?"
32774
33390
  ];
32775
33391
  MANAGER_ROLES = ["COO", "CTO"];
32776
- AUDIT_LOG_PATH = path58.join(
33392
+ AUDIT_LOG_PATH = path59.join(
32777
33393
  process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
32778
33394
  ".exe-os",
32779
33395
  "enforcement-audit.jsonl"
@@ -32798,7 +33414,7 @@ __export(background_jobs_exports, {
32798
33414
  import { existsSync as existsSync45, mkdirSync as mkdirSync23, readFileSync as readFileSync39, writeFileSync as writeFileSync27, unlinkSync as unlinkSync14 } from "fs";
32799
33415
  import { execFileSync as execFileSync3 } from "child_process";
32800
33416
  import os22 from "os";
32801
- import path59 from "path";
33417
+ import path60 from "path";
32802
33418
  function ensureDirs() {
32803
33419
  mkdirSync23(LOCK_DIR, { recursive: true });
32804
33420
  }
@@ -32844,7 +33460,7 @@ function listBackgroundJobs() {
32844
33460
  return jobs;
32845
33461
  }
32846
33462
  function lockPath(type) {
32847
- return path59.join(LOCK_DIR, `${type.replace(/[^a-zA-Z0-9_.-]/g, "_")}.lock`);
33463
+ return path60.join(LOCK_DIR, `${type.replace(/[^a-zA-Z0-9_.-]/g, "_")}.lock`);
32848
33464
  }
32849
33465
  function acquireJobLock(type, ttlMs = DEFAULT_LOCK_TTL_MS) {
32850
33466
  ensureDirs();
@@ -33071,9 +33687,9 @@ var init_background_jobs = __esm({
33071
33687
  "src/lib/background-jobs.ts"() {
33072
33688
  "use strict";
33073
33689
  init_config();
33074
- JOB_DIR = path59.join(EXE_AI_DIR, "jobs");
33075
- JOBS_FILE = path59.join(JOB_DIR, "jobs.json");
33076
- LOCK_DIR = path59.join(JOB_DIR, "locks");
33690
+ JOB_DIR = path60.join(EXE_AI_DIR, "jobs");
33691
+ JOBS_FILE = path60.join(JOB_DIR, "jobs.json");
33692
+ LOCK_DIR = path60.join(JOB_DIR, "locks");
33077
33693
  DEFAULT_LOCK_TTL_MS = 6 * 60 * 60 * 1e3;
33078
33694
  MAX_HISTORY = 200;
33079
33695
  }
@@ -33086,16 +33702,16 @@ __export(ws_auth_exports, {
33086
33702
  deriveWsAuthToken: () => deriveWsAuthToken,
33087
33703
  hashAuthToken: () => hashAuthToken
33088
33704
  });
33089
- import crypto21 from "crypto";
33705
+ import crypto22 from "crypto";
33090
33706
  function deriveWsAuthToken(masterKey) {
33091
- return Buffer.from(crypto21.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
33707
+ return Buffer.from(crypto22.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
33092
33708
  }
33093
33709
  function deriveOrgId(masterKey) {
33094
- const raw = Buffer.from(crypto21.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
33095
- return crypto21.createHash("sha256").update(raw).digest("hex").slice(0, 32);
33710
+ const raw = Buffer.from(crypto22.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
33711
+ return crypto22.createHash("sha256").update(raw).digest("hex").slice(0, 32);
33096
33712
  }
33097
33713
  function hashAuthToken(token) {
33098
- return crypto21.createHash("sha256").update(token).digest("hex");
33714
+ return crypto22.createHash("sha256").update(token).digest("hex");
33099
33715
  }
33100
33716
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
33101
33717
  var init_ws_auth = __esm({
@@ -33113,10 +33729,10 @@ __export(device_registry_exports, {
33113
33729
  resolveTargetDevice: () => resolveTargetDevice,
33114
33730
  setFriendlyName: () => setFriendlyName
33115
33731
  });
33116
- import crypto22 from "crypto";
33732
+ import crypto23 from "crypto";
33117
33733
  import os23 from "os";
33118
33734
  import { readFileSync as readFileSync40, writeFileSync as writeFileSync28, mkdirSync as mkdirSync24, existsSync as existsSync46 } from "fs";
33119
- import path60 from "path";
33735
+ import path61 from "path";
33120
33736
  function getDeviceInfo() {
33121
33737
  if (existsSync46(DEVICE_JSON_PATH)) {
33122
33738
  try {
@@ -33130,11 +33746,11 @@ function getDeviceInfo() {
33130
33746
  }
33131
33747
  const hostname = os23.hostname();
33132
33748
  const info = {
33133
- deviceId: crypto22.randomUUID(),
33749
+ deviceId: crypto23.randomUUID(),
33134
33750
  friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
33135
33751
  hostname
33136
33752
  };
33137
- mkdirSync24(path60.dirname(DEVICE_JSON_PATH), { recursive: true });
33753
+ mkdirSync24(path61.dirname(DEVICE_JSON_PATH), { recursive: true });
33138
33754
  writeFileSync28(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
33139
33755
  return info;
33140
33756
  }
@@ -33175,7 +33791,7 @@ var init_device_registry = __esm({
33175
33791
  "src/lib/device-registry.ts"() {
33176
33792
  "use strict";
33177
33793
  init_config();
33178
- DEVICE_JSON_PATH = path60.join(EXE_AI_DIR, "device.json");
33794
+ DEVICE_JSON_PATH = path61.join(EXE_AI_DIR, "device.json");
33179
33795
  }
33180
33796
  });
33181
33797
 
@@ -33392,7 +34008,7 @@ import net2 from "net";
33392
34008
  import { createServer as createHttpServer } from "http";
33393
34009
  import { randomUUID as randomUUID11 } from "crypto";
33394
34010
  import { writeFileSync as writeFileSync29, unlinkSync as unlinkSync15, mkdirSync as mkdirSync25, existsSync as existsSync47, readFileSync as readFileSync41, chmodSync as chmodSync2 } from "fs";
33395
- import path61 from "path";
34011
+ import path62 from "path";
33396
34012
 
33397
34013
  // src/lib/orchestration-metrics.ts
33398
34014
  init_config();
@@ -33465,8 +34081,8 @@ function initMetrics() {
33465
34081
  // src/lib/exe-daemon.ts
33466
34082
  init_memory_write_governor();
33467
34083
  init_mcp_transport_health();
33468
- var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path61.join(EXE_AI_DIR, "exed.sock");
33469
- var PID_PATH4 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path61.join(EXE_AI_DIR, "exed.pid");
34084
+ var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path62.join(EXE_AI_DIR, "exed.sock");
34085
+ var PID_PATH4 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path62.join(EXE_AI_DIR, "exed.pid");
33470
34086
  var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
33471
34087
  var IDLE_TIMEOUT_MS2 = parseInt(process.env.EXE_DAEMON_IDLE_TIMEOUT_MS || "0", 10);
33472
34088
  var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
@@ -33495,7 +34111,7 @@ function enqueue(queue, entry) {
33495
34111
  queue.push(entry);
33496
34112
  }
33497
34113
  async function loadModel() {
33498
- const modelPath = path61.join(MODELS_DIR, MODEL_FILE);
34114
+ const modelPath = path62.join(MODELS_DIR, MODEL_FILE);
33499
34115
  if (!existsSync47(modelPath)) {
33500
34116
  process.stderr.write(`[exed] No model at ${modelPath} \u2014 running without embeddings (VPS mode).
33501
34117
  `);
@@ -33937,14 +34553,14 @@ function startMemoryQueueDrain() {
33937
34553
  `);
33938
34554
  }
33939
34555
  function startServer() {
33940
- mkdirSync25(path61.dirname(SOCKET_PATH2), { recursive: true });
34556
+ mkdirSync25(path62.dirname(SOCKET_PATH2), { recursive: true });
33941
34557
  try {
33942
- chmodSync2(path61.dirname(SOCKET_PATH2), 448);
34558
+ chmodSync2(path62.dirname(SOCKET_PATH2), 448);
33943
34559
  } catch {
33944
34560
  }
33945
34561
  _daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
33946
34562
  for (const oldFile of ["embed.sock", "embed.pid"]) {
33947
- const oldPath = path61.join(path61.dirname(SOCKET_PATH2), oldFile);
34563
+ const oldPath = path62.join(path62.dirname(SOCKET_PATH2), oldFile);
33948
34564
  try {
33949
34565
  if (oldFile.endsWith(".pid")) {
33950
34566
  const pid = parseInt(readFileSync41(oldPath, "utf8").trim(), 10);
@@ -34069,6 +34685,7 @@ function startServer() {
34069
34685
  void startMcpHttpServer();
34070
34686
  }
34071
34687
  async function startMcpHttpServer() {
34688
+ process.stderr.write("[exed] MCP HTTP: starting setup...\n");
34072
34689
  try {
34073
34690
  let parseDurationMs2 = function(value, fallback, options = {}) {
34074
34691
  if (!value) return fallback;
@@ -34123,8 +34740,14 @@ async function startMcpHttpServer() {
34123
34740
  const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
34124
34741
  const { registerAllTools: registerAllTools2 } = await Promise.resolve().then(() => (init_register_tools(), register_tools_exports));
34125
34742
  const { runWithAgent: runWithAgent2 } = await Promise.resolve().then(() => (init_agent_context(), agent_context_exports));
34126
- const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
34127
- await initStore2();
34743
+ const dbReady = await ensureStoreForPolling();
34744
+ if (!dbReady) {
34745
+ process.stderr.write(
34746
+ "[exed] MCP HTTP: DB init failed \u2014 MCP HTTP endpoint will NOT start. Run 'exe-os setup' to fix keychain/encryption.\n"
34747
+ );
34748
+ return;
34749
+ }
34750
+ process.stderr.write("[exed] MCP HTTP: DB ready\n");
34128
34751
  const transports = /* @__PURE__ */ new Map();
34129
34752
  const MCP_HTTP_PORT = parseInt(process.env.EXE_MCP_PORT || "48739", 10);
34130
34753
  const MCP_SESSION_TTL_MS = parseDurationMs2(process.env.EXE_MCP_SESSION_TTL_MS, 4 * 60 * 60 * 1e3, { allowZero: true });
@@ -34309,8 +34932,10 @@ async function startMcpHttpServer() {
34309
34932
  }
34310
34933
  });
34311
34934
  } catch (err) {
34312
- process.stderr.write(`[exed] MCP HTTP setup failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
34935
+ const msg = err instanceof Error ? err.stack ?? err.message : String(err);
34936
+ process.stderr.write(`[exed] MCP HTTP setup FAILED: ${msg}
34313
34937
  `);
34938
+ process.stderr.write("[exed] MCP HTTP endpoint will NOT be available this session.\n");
34314
34939
  }
34315
34940
  }
34316
34941
  var _storeInitialized = false;
@@ -34578,7 +35203,7 @@ function startGraphExtraction() {
34578
35203
  `);
34579
35204
  }
34580
35205
  var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
34581
- var AGENT_STATS_PATH = path61.join(EXE_AI_DIR, "agent-stats.json");
35206
+ var AGENT_STATS_PATH = path62.join(EXE_AI_DIR, "agent-stats.json");
34582
35207
  async function writeAgentStats() {
34583
35208
  fired("agent_stats");
34584
35209
  if (!await ensureStoreForPolling()) return;
@@ -34755,11 +35380,11 @@ function startIntercomQueueDrain() {
34755
35380
  const hasInProgressTask = (session) => {
34756
35381
  try {
34757
35382
  const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
34758
- const path62 = __require("path");
35383
+ const path63 = __require("path");
34759
35384
  const { existsSync: existsSync48 } = __require("fs");
34760
35385
  const os25 = __require("os");
34761
35386
  const agent = ban(session.split("-")[0] ?? session);
34762
- const markerPath = path62.join(os25.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
35387
+ const markerPath = path63.join(os25.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
34763
35388
  return existsSync48(markerPath);
34764
35389
  } catch {
34765
35390
  return false;
@@ -34983,6 +35608,23 @@ function checkExistingDaemon() {
34983
35608
  return false;
34984
35609
  }
34985
35610
  process.kill(pid, 0);
35611
+ try {
35612
+ const state = __require("child_process").execSync(`ps -p ${pid} -o state=`, { encoding: "utf8", timeout: 2e3 }).trim();
35613
+ if (state.startsWith("Z")) {
35614
+ process.stderr.write(`[exed] PID ${pid} is a zombie \u2014 cleaning up and proceeding.
35615
+ `);
35616
+ try {
35617
+ unlinkSync15(PID_PATH4);
35618
+ } catch {
35619
+ }
35620
+ try {
35621
+ unlinkSync15(SOCKET_PATH2);
35622
+ } catch {
35623
+ }
35624
+ return false;
35625
+ }
35626
+ } catch {
35627
+ }
34986
35628
  process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
34987
35629
  `);
34988
35630
  return true;
@@ -35018,8 +35660,8 @@ function startAutoUpdateCheck() {
35018
35660
  );
35019
35661
  if (autoInstall) {
35020
35662
  process.stderr.write("[exed] Auto-installing update...\n");
35021
- const { execSync: execSync17 } = await import("child_process");
35022
- execSync17("npm install -g @askexenow/exe-os@latest", {
35663
+ const { execSync: execSync18 } = await import("child_process");
35664
+ execSync18("npm install -g @askexenow/exe-os@latest", {
35023
35665
  timeout: 12e4,
35024
35666
  stdio: ["pipe", "pipe", "pipe"]
35025
35667
  });