@askexenow/exe-os 0.9.86 → 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 (72) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +18 -0
  2. package/dist/bin/agentic-reflection-backfill.js +18 -0
  3. package/dist/bin/agentic-semantic-label.js +18 -0
  4. package/dist/bin/backfill-conversations.js +18 -0
  5. package/dist/bin/backfill-responses.js +18 -0
  6. package/dist/bin/backfill-vectors.js +18 -0
  7. package/dist/bin/bulk-sync-postgres.js +18 -0
  8. package/dist/bin/cleanup-stale-review-tasks.js +18 -0
  9. package/dist/bin/cli.js +187 -4
  10. package/dist/bin/exe-agent.js +18 -0
  11. package/dist/bin/exe-assign.js +18 -0
  12. package/dist/bin/exe-boot.js +18 -0
  13. package/dist/bin/exe-call.js +18 -0
  14. package/dist/bin/exe-cloud.js +18 -0
  15. package/dist/bin/exe-dispatch.js +18 -0
  16. package/dist/bin/exe-doctor.js +18 -0
  17. package/dist/bin/exe-export-behaviors.js +18 -0
  18. package/dist/bin/exe-forget.js +18 -0
  19. package/dist/bin/exe-gateway.js +18 -0
  20. package/dist/bin/exe-heartbeat.js +18 -0
  21. package/dist/bin/exe-kill.js +18 -0
  22. package/dist/bin/exe-launch-agent.js +18 -0
  23. package/dist/bin/exe-new-employee.js +33 -2
  24. package/dist/bin/exe-pending-messages.js +18 -0
  25. package/dist/bin/exe-pending-notifications.js +18 -0
  26. package/dist/bin/exe-pending-reviews.js +18 -0
  27. package/dist/bin/exe-rename.js +18 -0
  28. package/dist/bin/exe-review.js +18 -0
  29. package/dist/bin/exe-search.js +18 -0
  30. package/dist/bin/exe-session-cleanup.js +18 -0
  31. package/dist/bin/exe-start-codex.js +18 -0
  32. package/dist/bin/exe-start-opencode.js +18 -0
  33. package/dist/bin/exe-status.js +18 -0
  34. package/dist/bin/exe-team.js +18 -0
  35. package/dist/bin/git-sweep.js +18 -0
  36. package/dist/bin/graph-backfill.js +18 -0
  37. package/dist/bin/graph-export.js +18 -0
  38. package/dist/bin/install.js +9 -0
  39. package/dist/bin/intercom-check.js +18 -0
  40. package/dist/bin/scan-tasks.js +18 -0
  41. package/dist/bin/setup.js +24 -2
  42. package/dist/bin/shard-migrate.js +18 -0
  43. package/dist/gateway/index.js +18 -0
  44. package/dist/hooks/bug-report-worker.js +18 -0
  45. package/dist/hooks/codex-stop-task-finalizer.js +18 -0
  46. package/dist/hooks/commit-complete.js +18 -0
  47. package/dist/hooks/error-recall.js +18 -0
  48. package/dist/hooks/ingest.js +18 -0
  49. package/dist/hooks/instructions-loaded.js +18 -0
  50. package/dist/hooks/notification.js +18 -0
  51. package/dist/hooks/post-compact.js +18 -0
  52. package/dist/hooks/post-tool-combined.js +18 -0
  53. package/dist/hooks/pre-compact.js +18 -0
  54. package/dist/hooks/pre-tool-use.js +18 -0
  55. package/dist/hooks/prompt-submit.js +18 -0
  56. package/dist/hooks/session-end.js +18 -0
  57. package/dist/hooks/session-start.js +18 -0
  58. package/dist/hooks/stop.js +18 -0
  59. package/dist/hooks/subagent-stop.js +18 -0
  60. package/dist/hooks/summary-worker.js +18 -0
  61. package/dist/index.js +18 -0
  62. package/dist/lib/employee-templates.js +18 -0
  63. package/dist/lib/exe-daemon.js +712 -169
  64. package/dist/lib/hybrid-search.js +18 -0
  65. package/dist/lib/identity-templates.js +6 -2
  66. package/dist/lib/schedules.js +18 -0
  67. package/dist/lib/store.js +18 -0
  68. package/dist/mcp/server.js +670 -143
  69. package/dist/mcp/tools/create-task.js +6 -2
  70. package/dist/runtime/index.js +18 -0
  71. package/dist/tui/App.js +18 -0
  72. package/package.json +1 -1
@@ -889,8 +889,8 @@ async function embedDirect(text3) {
889
889
  const llamaCpp = await import("node-llama-cpp");
890
890
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
891
891
  const { existsSync: existsSync42 } = await import("fs");
892
- const path55 = await import("path");
893
- const modelPath = path55.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
892
+ const path56 = await import("path");
893
+ const modelPath = path56.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
894
894
  if (!existsSync42(modelPath)) {
895
895
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
896
896
  }
@@ -3723,7 +3723,7 @@ async function tryKeytar() {
3723
3723
  }
3724
3724
  function deriveMachineKey() {
3725
3725
  try {
3726
- const crypto20 = __require("crypto");
3726
+ const crypto21 = __require("crypto");
3727
3727
  const material = [
3728
3728
  os5.hostname(),
3729
3729
  os5.userInfo().username,
@@ -3732,7 +3732,7 @@ function deriveMachineKey() {
3732
3732
  // Machine ID on Linux (stable across reboots)
3733
3733
  process.platform === "linux" ? readMachineId() : ""
3734
3734
  ].join("|");
3735
- return crypto20.createHash("sha256").update(material).digest();
3735
+ return crypto21.createHash("sha256").update(material).digest();
3736
3736
  } catch {
3737
3737
  return null;
3738
3738
  }
@@ -3746,9 +3746,9 @@ function readMachineId() {
3746
3746
  }
3747
3747
  }
3748
3748
  function encryptWithMachineKey(plaintext, machineKey) {
3749
- const crypto20 = __require("crypto");
3750
- const iv = crypto20.randomBytes(12);
3751
- const cipher = crypto20.createCipheriv("aes-256-gcm", machineKey, iv);
3749
+ const crypto21 = __require("crypto");
3750
+ const iv = crypto21.randomBytes(12);
3751
+ const cipher = crypto21.createCipheriv("aes-256-gcm", machineKey, iv);
3752
3752
  let encrypted = cipher.update(plaintext, "utf-8", "base64");
3753
3753
  encrypted += cipher.final("base64");
3754
3754
  const authTag = cipher.getAuthTag().toString("base64");
@@ -3757,13 +3757,13 @@ function encryptWithMachineKey(plaintext, machineKey) {
3757
3757
  function decryptWithMachineKey(encrypted, machineKey) {
3758
3758
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3759
3759
  try {
3760
- const crypto20 = __require("crypto");
3760
+ const crypto21 = __require("crypto");
3761
3761
  const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3762
3762
  if (parts.length !== 3) return null;
3763
3763
  const [ivB64, tagB64, cipherB64] = parts;
3764
3764
  const iv = Buffer.from(ivB64, "base64");
3765
3765
  const authTag = Buffer.from(tagB64, "base64");
3766
- const decipher = crypto20.createDecipheriv("aes-256-gcm", machineKey, iv);
3766
+ const decipher = crypto21.createDecipheriv("aes-256-gcm", machineKey, iv);
3767
3767
  decipher.setAuthTag(authTag);
3768
3768
  let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3769
3769
  decrypted += decipher.final("utf-8");
@@ -4801,6 +4801,24 @@ var init_platform_procedures = __esm({
4801
4801
  priority: "p0",
4802
4802
  content: "When an agent encounters a suspected Exe OS bug, update breakage, MCP/tool failure, installer issue, memory/orchestration defect, or customer-local patch need, it MUST use create_bug_report. Do this before or alongside any local workaround so the report reaches AskExe support directly via the customer's license. Do NOT ask the founder for permission to file a required bug report. If create_bug_report is deferred/lazy-loaded, load it and call it. If it is unavailable in the live MCP surface, report 'create_bug_report unavailable in this session' and save a local report in exe/output \u2014 never claim the tool does not exist unless the live MCP surface was checked. If upstream delivery fails, call support_test (MCP) and include its result in the local report so AskExe can distinguish customer setup, license provisioning, and server intake issues; only ask the founder to run `exe-os support test` if MCP is disconnected/unavailable. Classify first: upstream_bug = reproducible exe-os/platform defect; customer_customization = identity, behavior, procedure, config, branding, workflow preference that belongs in customer-owned layers; emergency_hotfix = temporary local patch. For upstream bugs/emergency hotfixes include version, repro steps, expected/actual, files changed, workaround, and local diff summary. Avoid permanent platform-code patches unless founder approves; if a hotfix is unavoidable, document it in the bug report and re-check after npm update."
4803
4803
  },
4804
+ {
4805
+ title: "Bug report status check \u2014 surface available fixes on boot",
4806
+ domain: "support",
4807
+ priority: "p1",
4808
+ content: "Once per session (COO boot only, never repeat), call list_my_bug_reports to check if any previously filed bug reports have been fixed by AskExe. If any report has status 'fixed' with a fixed_version, surface it to the founder immediately: '\u{1F527} N bug fix(es) available \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no reports exist or none are fixed, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4809
+ },
4810
+ {
4811
+ title: "Feature request triage \u2014 upstream feature vs local customization",
4812
+ domain: "support",
4813
+ priority: "p0",
4814
+ content: "When an agent or founder identifies a desired capability that exe-os does not yet provide, the COO (or equivalent coordinator) must decide: is this a local customization (identity, behavior, procedure, config, branding, workflow preference that can be configured in customer-owned layers) or an upstream feature request (a platform capability that requires changes to exe-os code, shipped via npm update)? Local customizations: implement immediately using store_behavior, update_identity, company_procedure, or config changes. Upstream features: use create_feature_request to submit to AskExe. Include use case, business impact, and current workaround. Do NOT ask the founder for permission to file a feature request \u2014 file it proactively when the need is clear."
4815
+ },
4816
+ {
4817
+ title: "Feature request status check \u2014 surface shipped features on boot",
4818
+ domain: "support",
4819
+ priority: "p1",
4820
+ content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4821
+ },
4804
4822
  // --- Operations ---
4805
4823
  {
4806
4824
  title: "Managers must supervise deployed workers",
@@ -7321,10 +7339,10 @@ async function hybridSearch(queryText, agentId, options) {
7321
7339
  };
7322
7340
  try {
7323
7341
  const fs = await import("fs");
7324
- const path55 = await import("path");
7342
+ const path56 = await import("path");
7325
7343
  const os21 = await import("os");
7326
- const logPath = path55.join(os21.homedir(), ".exe-os", "search-quality.jsonl");
7327
- fs.mkdirSync(path55.dirname(logPath), { recursive: true });
7344
+ const logPath = path56.join(os21.homedir(), ".exe-os", "search-quality.jsonl");
7345
+ fs.mkdirSync(path56.dirname(logPath), { recursive: true });
7328
7346
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
7329
7347
  } catch {
7330
7348
  }
@@ -8617,8 +8635,8 @@ __export(wiki_client_exports, {
8617
8635
  listDocuments: () => listDocuments,
8618
8636
  listWorkspaces: () => listWorkspaces
8619
8637
  });
8620
- async function wikiFetch(config2, path55, method = "GET", body) {
8621
- const url = `${config2.baseUrl}/api/v1${path55}`;
8638
+ async function wikiFetch(config2, path56, method = "GET", body) {
8639
+ const url = `${config2.baseUrl}/api/v1${path56}`;
8622
8640
  const headers = {
8623
8641
  Authorization: `Bearer ${config2.apiKey}`,
8624
8642
  "Content-Type": "application/json"
@@ -8651,7 +8669,7 @@ async function wikiFetch(config2, path55, method = "GET", body) {
8651
8669
  }
8652
8670
  }
8653
8671
  if (!response.ok) {
8654
- throw new Error(`Wiki API ${method} ${path55}: ${response.status} ${response.statusText}`);
8672
+ throw new Error(`Wiki API ${method} ${path56}: ${response.status} ${response.statusText}`);
8655
8673
  }
8656
8674
  return response.json();
8657
8675
  } finally {
@@ -12786,12 +12804,14 @@ On EVERY new conversation, before doing anything else:
12786
12804
  1. **Memory scan**: Run recall_my_memory with broad queries \u2014 "project", "client", "pipeline", "campaign", "deal", "decision", "blocker". Summarize what you find.
12787
12805
  2. **Task scan**: Run list_tasks to see what's open, in progress, blocked, or needs review across all employees.
12788
12806
  3. **Team check**: Run ask_team_memory for recent activity from CTO/CMO/engineers.
12789
- 4. **Present the brief**: Give the founder a concise status report:
12807
+ 4. **Bug fix check** (one-time, never repeat): Call list_my_bug_reports to see if AskExe has fixed any previously filed bugs. If any have status "fixed" with a fixed_version, tell the founder: "\u{1F527} N bug fix(es) available \u2014 run \`exe-os update\` to get version X.Y.Z." Skip silently if none or if the call fails.
12808
+ 5. **Present the brief**: Give the founder a concise status report:
12790
12809
  - What's active and progressing
12791
12810
  - What's blocked and needs attention
12792
12811
  - What decisions are pending
12812
+ - Available bug fixes (from step 4, if any)
12793
12813
  - What you recommend doing next
12794
- 5. Then ask: "What's the priority?"
12814
+ 6. Then ask: "What's the priority?"
12795
12815
 
12796
12816
  If this is your FIRST ever conversation (few or no prior memories):
12797
12817
  - Search more broadly: "product", "SEO", "meeting", "strategy", "revenue"
@@ -12811,6 +12831,8 @@ Never say "I have no memories" without first searching broadly. Your memory may
12811
12831
  - **get_identity** \u2014 read any agent's identity for coordination
12812
12832
  - **set_agent_config** \u2014 view or change which tool (Claude Code, Codex, OpenCode) and model each agent uses. Call with no args to show all agents' current settings. Call with agent_id + runtime + model to change.
12813
12833
  - **send_message** \u2014 direct intercom to employees
12834
+ - **create_bug_report** \u2014 file a bug when you encounter an Exe OS platform issue
12835
+ - **list_my_bug_reports** \u2014 check status of filed bugs (boot check: surface available fixes to founder)
12814
12836
  ${PLAN_MODE_COMPAT}
12815
12837
  ## Completion Workflow
12816
12838
 
@@ -14208,7 +14230,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14208
14230
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14209
14231
  import { spawn as spawn4 } from "child_process";
14210
14232
  import { existsSync as existsSync41, openSync as openSync4, mkdirSync as mkdirSync23, closeSync as closeSync4, readFileSync as readFileSync36 } from "fs";
14211
- import path54 from "path";
14233
+ import path55 from "path";
14212
14234
  import os20 from "os";
14213
14235
  import { fileURLToPath as fileURLToPath7 } from "url";
14214
14236
 
@@ -18596,12 +18618,12 @@ function registerExportGraph(server2) {
18596
18618
  }
18597
18619
  const html = await exportGraphHTML(client);
18598
18620
  const fs = await import("fs");
18599
- const path55 = await import("path");
18621
+ const path56 = await import("path");
18600
18622
  const os21 = await import("os");
18601
- const outDir = path55.join(os21.homedir(), ".exe-os", "exports");
18623
+ const outDir = path56.join(os21.homedir(), ".exe-os", "exports");
18602
18624
  fs.mkdirSync(outDir, { recursive: true });
18603
18625
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18604
- const filePath = path55.join(outDir, `graph-${timestamp}.html`);
18626
+ const filePath = path56.join(outDir, `graph-${timestamp}.html`);
18605
18627
  fs.writeFileSync(filePath, html, "utf-8");
18606
18628
  return {
18607
18629
  content: [
@@ -23580,9 +23602,9 @@ var HostingerApiClient = class {
23580
23602
  }
23581
23603
  this.lastRequestTime = Date.now();
23582
23604
  }
23583
- async request(method, path55, body) {
23605
+ async request(method, path56, body) {
23584
23606
  await this.rateLimit();
23585
- const url = `${this.baseUrl}${path55}`;
23607
+ const url = `${this.baseUrl}${path56}`;
23586
23608
  const headers = {
23587
23609
  Authorization: `Bearer ${this.apiKey}`,
23588
23610
  "Content-Type": "application/json",
@@ -23655,8 +23677,8 @@ async function requestCloudflare(cfApiToken, zoneId, options) {
23655
23677
  }
23656
23678
  return envelope.result;
23657
23679
  }
23658
- function buildUrl(zoneId, path55 = "/dns_records", query) {
23659
- const normalizedPath = path55.startsWith("/") ? path55 : `/${path55}`;
23680
+ function buildUrl(zoneId, path56 = "/dns_records", query) {
23681
+ const normalizedPath = path56.startsWith("/") ? path56 : `/${path56}`;
23660
23682
  const url = new URL(
23661
23683
  `${CLOUDFLARE_API_BASE_URL}/zones/${zoneId}${normalizedPath}`
23662
23684
  );
@@ -27367,14 +27389,206 @@ Upstream status: ${upstreamStatus}`
27367
27389
  );
27368
27390
  }
27369
27391
 
27370
- // src/mcp/tools/support.ts
27392
+ // src/mcp/tools/create-feature-request.ts
27393
+ init_embedder();
27394
+ init_active_agent();
27395
+ init_config();
27396
+ init_license();
27397
+ init_store();
27371
27398
  import { z as z89 } from "zod";
27399
+ import crypto19 from "crypto";
27400
+ import { mkdir as mkdir7, writeFile as writeFile8 } from "fs/promises";
27401
+ import path50 from "path";
27402
+ var CATEGORY = z89.enum([
27403
+ "upstream_feature",
27404
+ "local_customization",
27405
+ "integration",
27406
+ "unclear"
27407
+ ]);
27408
+ var PRIORITY = z89.enum(["p0", "p1", "p2", "p3"]);
27409
+ function slugify3(input) {
27410
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "feature-request";
27411
+ }
27412
+ function section2(title, body) {
27413
+ return `## ${title}
27414
+
27415
+ ${body?.trim() || "Not provided"}`;
27416
+ }
27417
+ function buildMarkdown2(input) {
27418
+ return [
27419
+ `# Feature Request \u2014 ${input.title}`,
27420
+ "",
27421
+ `id: ${input.id}`,
27422
+ `category: ${input.category}`,
27423
+ `priority: ${input.priority}`,
27424
+ `filed_by: ${input.agentId} (${input.agentRole})`,
27425
+ `package_version: ${input.packageVersion}`,
27426
+ `project: ${input.projectName ?? "unknown"}`,
27427
+ `created_at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
27428
+ "",
27429
+ section2("Description", input.description),
27430
+ section2("Use Case", input.useCase),
27431
+ section2("Current Workaround", input.currentWorkaround),
27432
+ section2("Proposed Solution", input.proposedSolution),
27433
+ section2("Business Impact", input.businessImpact)
27434
+ ].join("\n");
27435
+ }
27436
+ async function maybeSendUpstream2(payload) {
27437
+ const config2 = await loadConfig();
27438
+ const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
27439
+ const endpoint2 = config2.support?.featureRequestEndpoint || process.env.EXE_FEATURE_REQUEST_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support/feature-requests` : "https://askexe.com/v1/support/feature-requests");
27440
+ const token = config2.support?.featureRequestToken || process.env.EXE_FEATURE_REQUEST_TOKEN;
27441
+ const licenseKey = loadLicense() || process.env.EXE_LICENSE_KEY || config2.cloud?.apiKey;
27442
+ const licenseToken = readCachedLicenseToken();
27443
+ if (!endpoint2) {
27444
+ return "not_configured";
27445
+ }
27446
+ try {
27447
+ const parsed = new URL(endpoint2);
27448
+ if (parsed.protocol !== "https:" && !["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
27449
+ return "failed: insecure endpoint rejected";
27450
+ }
27451
+ const response = await fetch(parsed, {
27452
+ method: "POST",
27453
+ headers: {
27454
+ "content-type": "application/json",
27455
+ ...token ? { authorization: `Bearer ${token}` } : {},
27456
+ ...licenseKey ? { "x-exe-license-key": licenseKey } : {},
27457
+ ...licenseToken ? { "x-exe-license-token": licenseToken } : {}
27458
+ },
27459
+ body: JSON.stringify(payload),
27460
+ signal: AbortSignal.timeout(1e4)
27461
+ });
27462
+ if (!response.ok) return `failed: HTTP ${response.status}`;
27463
+ return "sent";
27464
+ } catch (err) {
27465
+ return `failed: ${err instanceof Error ? err.message : String(err)}`;
27466
+ }
27467
+ }
27468
+ function registerCreateFeatureRequest(server2) {
27469
+ server2.registerTool(
27470
+ "create_feature_request",
27471
+ {
27472
+ title: "Create Feature Request",
27473
+ description: "File a feature request for exe-os: upstream_feature (requires platform changes), local_customization (configurable in customer layers), integration (third-party), or unclear. Writes a local report, stores memory, and optionally sends to AskExe.",
27474
+ inputSchema: {
27475
+ title: z89.string().min(3).describe("Short descriptive title"),
27476
+ category: CATEGORY.describe(
27477
+ "upstream_feature = platform capability change; local_customization = configurable in customer layers; integration = third-party connector; unclear = needs product triage"
27478
+ ),
27479
+ priority: PRIORITY.default("p2").describe("p0 critical \u2192 p3 low"),
27480
+ description: z89.string().min(10).describe("What capability is needed and why"),
27481
+ use_case: z89.string().optional().describe("Concrete scenario where this feature would be used"),
27482
+ current_workaround: z89.string().optional().describe("How the need is currently addressed, if at all"),
27483
+ proposed_solution: z89.string().optional().describe("Suggested implementation approach"),
27484
+ business_impact: z89.string().optional().describe("Impact on business/workflow if this ships"),
27485
+ package_version: z89.string().optional().describe("Installed @askexenow/exe-os version"),
27486
+ project_name: z89.string().optional().describe("Project/customer context"),
27487
+ send_upstream: z89.boolean().default(true).describe("Attempt to POST to configured AskExe support endpoint")
27488
+ }
27489
+ },
27490
+ async ({
27491
+ title,
27492
+ category,
27493
+ priority,
27494
+ description,
27495
+ use_case,
27496
+ current_workaround,
27497
+ proposed_solution,
27498
+ business_impact,
27499
+ package_version,
27500
+ project_name,
27501
+ send_upstream
27502
+ }) => {
27503
+ const { agentId, agentRole } = getActiveAgent();
27504
+ const id = crypto19.randomUUID();
27505
+ const version = package_version ?? "unknown";
27506
+ const markdown = buildMarkdown2({
27507
+ id,
27508
+ title,
27509
+ category,
27510
+ priority,
27511
+ agentId,
27512
+ agentRole,
27513
+ packageVersion: version,
27514
+ description,
27515
+ useCase: use_case,
27516
+ currentWorkaround: current_workaround,
27517
+ proposedSolution: proposed_solution,
27518
+ businessImpact: business_impact,
27519
+ projectName: project_name
27520
+ });
27521
+ const outDir = path50.join(EXE_AI_DIR, "feature-requests");
27522
+ await mkdir7(outDir, { recursive: true });
27523
+ const reportPath = path50.join(outDir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}-${slugify3(title)}-${id.slice(0, 8)}.md`);
27524
+ await writeFile8(reportPath, markdown, "utf-8");
27525
+ let vector = null;
27526
+ try {
27527
+ vector = await embed(markdown);
27528
+ } catch {
27529
+ vector = null;
27530
+ }
27531
+ await writeMemory({
27532
+ id,
27533
+ agent_id: agentId,
27534
+ agent_role: agentRole,
27535
+ session_id: process.env.SESSION_ID ?? "manual",
27536
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
27537
+ tool_name: "create_feature_request",
27538
+ project_name: project_name ?? "support",
27539
+ has_error: false,
27540
+ raw_text: markdown,
27541
+ vector,
27542
+ source_path: reportPath,
27543
+ source_type: "feature_request",
27544
+ memory_type: "feature_request",
27545
+ tier: 1,
27546
+ importance: priority === "p0" ? 10 : priority === "p1" ? 9 : priority === "p2" ? 7 : 5,
27547
+ intent: "request",
27548
+ domain: "support",
27549
+ file_paths: null
27550
+ });
27551
+ await flushBatch();
27552
+ const upstreamStatus = send_upstream ? await maybeSendUpstream2({
27553
+ id,
27554
+ title,
27555
+ category,
27556
+ priority,
27557
+ description,
27558
+ use_case,
27559
+ current_workaround,
27560
+ proposed_solution,
27561
+ business_impact,
27562
+ package_version: version,
27563
+ project_name,
27564
+ agent_id: agentId,
27565
+ agent_role: agentRole
27566
+ }) : "skipped";
27567
+ return {
27568
+ content: [
27569
+ {
27570
+ type: "text",
27571
+ text: `Feature request created.
27572
+ ID: ${id}
27573
+ Category: ${category}
27574
+ Local report: ${reportPath}
27575
+ Memory stored: ${id}
27576
+ Upstream status: ${upstreamStatus}`
27577
+ }
27578
+ ]
27579
+ };
27580
+ }
27581
+ );
27582
+ }
27583
+
27584
+ // src/mcp/tools/support.ts
27585
+ import { z as z90 } from "zod";
27372
27586
 
27373
27587
  // src/bin/exe-support.ts
27374
27588
  init_config();
27375
27589
  init_license();
27376
27590
  import { mkdirSync as mkdirSync21, readFileSync as readFileSync32, unlinkSync as unlinkSync12, writeFileSync as writeFileSync23 } from "fs";
27377
- import path50 from "path";
27591
+ import path51 from "path";
27378
27592
  import { randomUUID as randomUUID9 } from "crypto";
27379
27593
  var DEFAULT_BUG_ENDPOINT = "https://askexe.com/v1/support/bug-reports";
27380
27594
  var DEFAULT_ADMIN_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
@@ -27495,8 +27709,8 @@ async function resolveEndpoints() {
27495
27709
  return { bugEndpoint, healthEndpoint, adminEndpoint };
27496
27710
  }
27497
27711
  function checkLocalWrite() {
27498
- const dir = path50.join(EXE_AI_DIR, "bug-reports");
27499
- const testPath = path50.join(dir, ".support-write-test");
27712
+ const dir = path51.join(EXE_AI_DIR, "bug-reports");
27713
+ const testPath = path51.join(dir, ".support-write-test");
27500
27714
  try {
27501
27715
  mkdirSync21(dir, { recursive: true, mode: 448 });
27502
27716
  writeFileSync23(testPath, "ok\n", { mode: 384 });
@@ -27512,10 +27726,10 @@ function checkLocalWrite() {
27512
27726
  }
27513
27727
  }
27514
27728
  function writeLocalTestReport(id, project, version) {
27515
- const dir = path50.join(EXE_AI_DIR, "bug-reports");
27729
+ const dir = path51.join(EXE_AI_DIR, "bug-reports");
27516
27730
  mkdirSync21(dir, { recursive: true, mode: 448 });
27517
27731
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
27518
- const filePath = path50.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
27732
+ const filePath = path51.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
27519
27733
  writeFileSync23(filePath, `# TEST \u2014 ${project} support intake
27520
27734
 
27521
27735
  Report ID: ${id}
@@ -27557,15 +27771,15 @@ async function maybeCloseAdmin(id, adminEndpoint, version) {
27557
27771
  }
27558
27772
  }
27559
27773
  function readPackageVersion2() {
27560
- let dir = path50.dirname(new URL(import.meta.url).pathname);
27774
+ let dir = path51.dirname(new URL(import.meta.url).pathname);
27561
27775
  for (let i = 0; i < 6; i++) {
27562
- const pkg = path50.join(dir, "package.json");
27776
+ const pkg = path51.join(dir, "package.json");
27563
27777
  try {
27564
27778
  const parsed = JSON.parse(readFileSync32(pkg, "utf8"));
27565
27779
  if (parsed.version) return parsed.version;
27566
27780
  } catch {
27567
27781
  }
27568
- dir = path50.dirname(dir);
27782
+ dir = path51.dirname(dir);
27569
27783
  }
27570
27784
  return "unknown";
27571
27785
  }
@@ -27632,7 +27846,7 @@ function registerSupportTools(server2) {
27632
27846
  title: "Support Test",
27633
27847
  description: "End-to-end support intake smoke test. Files a clearly marked test report upstream and auto-closes it on AskExe machines with admin credentials.",
27634
27848
  inputSchema: {
27635
- project: z89.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
27849
+ project: z90.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
27636
27850
  }
27637
27851
  },
27638
27852
  async ({ project }) => {
@@ -27650,8 +27864,8 @@ function registerSupportTools(server2) {
27650
27864
  title: "My Bug Reports",
27651
27865
  description: "List bug reports you've filed, scoped to your license. Shows status (open/triaged/fixed/closed), severity, and fixed_version so you know when to update.",
27652
27866
  inputSchema: {
27653
- status: z89.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("all").describe("Filter by status. Default: all"),
27654
- limit: z89.number().min(1).max(50).default(25).describe("Max results")
27867
+ status: z90.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("all").describe("Filter by status. Default: all"),
27868
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
27655
27869
  }
27656
27870
  },
27657
27871
  async ({ status: status2, limit }) => {
@@ -27707,12 +27921,76 @@ function registerSupportTools(server2) {
27707
27921
  }
27708
27922
  }
27709
27923
  );
27924
+ server2.registerTool(
27925
+ "list_my_feature_requests",
27926
+ {
27927
+ title: "My Feature Requests",
27928
+ description: "List feature requests you've filed, scoped to your license. Shows status (open/planned/in_progress/shipped/closed), priority, and shipped_version so you know when to update.",
27929
+ inputSchema: {
27930
+ status: z90.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("all").describe("Filter by status. Default: all"),
27931
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
27932
+ }
27933
+ },
27934
+ async ({ status: status2, limit }) => {
27935
+ const licenseKey = loadLicense();
27936
+ const licenseToken = readCachedLicenseToken();
27937
+ if (!licenseKey && !licenseToken) {
27938
+ return {
27939
+ content: [{ type: "text", text: "No license key found. Run `exe-os setup` or `exe-os cloud setup` first." }],
27940
+ isError: true
27941
+ };
27942
+ }
27943
+ const endpoint2 = new URL("https://askexe.com/v1/support/my-feature-requests");
27944
+ endpoint2.searchParams.set("status", status2);
27945
+ endpoint2.searchParams.set("limit", String(limit));
27946
+ const headers = { "content-type": "application/json" };
27947
+ if (licenseKey) headers["x-exe-license-key"] = licenseKey;
27948
+ if (licenseToken) headers["x-exe-license-token"] = licenseToken;
27949
+ try {
27950
+ const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
27951
+ if (!res.ok) {
27952
+ const body = await res.text().catch(() => "");
27953
+ return {
27954
+ content: [{ type: "text", text: `Failed to fetch feature requests: HTTP ${res.status}${body ? ` \u2014 ${body}` : ""}` }],
27955
+ isError: true
27956
+ };
27957
+ }
27958
+ const data = await res.json();
27959
+ if (data.count === 0) {
27960
+ return {
27961
+ content: [{ type: "text", text: `No feature requests found${status2 !== "all" ? ` with status '${status2}'` : ""}.` }],
27962
+ structuredContent: { items: [], count: 0 }
27963
+ };
27964
+ }
27965
+ const lines = [`Feature requests (${data.count}):`, ""];
27966
+ for (const r of data.items) {
27967
+ const priIcon = r.priority === "p0" ? "\u{1F534}" : r.priority === "p1" ? "\u{1F534}" : r.priority === "p2" ? "\u{1F7E0}" : "\u{1F7E2}";
27968
+ const statusIcon = r.status === "shipped" ? "\u2705" : r.status === "closed" ? "\u2611\uFE0F" : r.status === "planned" ? "\u{1F4CB}" : r.status === "in_progress" ? "\u{1F528}" : r.status === "wontdo" ? "\u26D4" : "\u{1F535}";
27969
+ lines.push(`${priIcon} ${r.priority.toUpperCase()} ${statusIcon} ${r.status} \u2014 ${r.title}`);
27970
+ lines.push(` ID: ${r.id} | Filed: ${r.created_at?.slice(0, 10) ?? "?"}`);
27971
+ if (r.shipped_version) lines.push(` \u{1F680} Shipped in: ${r.shipped_version} \u2014 run \`exe-os update\` to get this feature`);
27972
+ if (r.target_version) lines.push(` \u{1F3AF} Target: ${r.target_version}`);
27973
+ if (r.response_notes) lines.push(` \u{1F4DD} AskExe: ${r.response_notes}`);
27974
+ lines.push("");
27975
+ }
27976
+ return {
27977
+ content: [{ type: "text", text: lines.join("\n") }],
27978
+ structuredContent: data
27979
+ };
27980
+ } catch (err) {
27981
+ return {
27982
+ content: [{ type: "text", text: `Error fetching feature requests: ${err instanceof Error ? err.message : String(err)}` }],
27983
+ isError: true
27984
+ };
27985
+ }
27986
+ }
27987
+ );
27710
27988
  }
27711
27989
 
27712
27990
  // src/mcp/tools/cli-parity.ts
27713
27991
  import { execFile as execFile2 } from "child_process";
27714
27992
  import { promisify as promisify2 } from "util";
27715
- import { z as z90 } from "zod";
27993
+ import { z as z91 } from "zod";
27716
27994
 
27717
27995
  // src/bin/exe-status.ts
27718
27996
  init_employees();
@@ -27773,22 +28051,22 @@ if (isMainModule(import.meta.url)) {
27773
28051
 
27774
28052
  // src/bin/exe-healthcheck.ts
27775
28053
  import { existsSync as existsSync39, readFileSync as readFileSync33, readdirSync as readdirSync14 } from "fs";
27776
- import path51 from "path";
28054
+ import path52 from "path";
27777
28055
  import { execSync as execSync14 } from "child_process";
27778
28056
  import { fileURLToPath as fileURLToPath6 } from "url";
27779
28057
  init_config();
27780
28058
  function findPackageRoot2() {
27781
- let dir = path51.dirname(fileURLToPath6(import.meta.url));
27782
- const { root } = path51.parse(dir);
28059
+ let dir = path52.dirname(fileURLToPath6(import.meta.url));
28060
+ const { root } = path52.parse(dir);
27783
28061
  while (dir !== root) {
27784
- if (existsSync39(path51.join(dir, "package.json"))) return dir;
27785
- dir = path51.dirname(dir);
28062
+ if (existsSync39(path52.join(dir, "package.json"))) return dir;
28063
+ dir = path52.dirname(dir);
27786
28064
  }
27787
28065
  throw new Error("Cannot find package root");
27788
28066
  }
27789
28067
  function checkBuildIntegrity(pkgRoot) {
27790
28068
  const results = [];
27791
- const tsupConfig = path51.join(pkgRoot, "tsup.config.ts");
28069
+ const tsupConfig = path52.join(pkgRoot, "tsup.config.ts");
27792
28070
  if (!existsSync39(tsupConfig)) {
27793
28071
  return [{ name: "build/tsup-config", pass: false, detail: "tsup.config.ts not found" }];
27794
28072
  }
@@ -27798,7 +28076,7 @@ function checkBuildIntegrity(pkgRoot) {
27798
28076
  let total = 0;
27799
28077
  for (const match of entryMatches) {
27800
28078
  const outputKey = match[1];
27801
- const expectedPath = path51.join(pkgRoot, "dist", `${outputKey}.js`);
28079
+ const expectedPath = path52.join(pkgRoot, "dist", `${outputKey}.js`);
27802
28080
  total++;
27803
28081
  if (!existsSync39(expectedPath)) {
27804
28082
  missing.push(`dist/${outputKey}.js`);
@@ -27822,7 +28100,7 @@ function checkBuildIntegrity(pkgRoot) {
27822
28100
  }
27823
28101
  function checkEmbedPipeline(pkgRoot) {
27824
28102
  const results = [];
27825
- const daemonPath = path51.join(pkgRoot, "dist", "lib", "exe-daemon.js");
28103
+ const daemonPath = path52.join(pkgRoot, "dist", "lib", "exe-daemon.js");
27826
28104
  if (!existsSync39(daemonPath)) {
27827
28105
  results.push({
27828
28106
  name: "exed/daemon-exists",
@@ -27834,17 +28112,17 @@ function checkEmbedPipeline(pkgRoot) {
27834
28112
  results.push({ name: "exed/daemon-exists", pass: true, detail: "dist/lib/exe-daemon.js exists" });
27835
28113
  const entryDirs = ["dist/hooks", "dist/bin", "dist/mcp"];
27836
28114
  for (const dir of entryDirs) {
27837
- const fullDir = path51.join(pkgRoot, dir);
28115
+ const fullDir = path52.join(pkgRoot, dir);
27838
28116
  if (!existsSync39(fullDir)) continue;
27839
28117
  let walkDir = fullDir;
27840
- const { root } = path51.parse(walkDir);
28118
+ const { root } = path52.parse(walkDir);
27841
28119
  let foundRoot = null;
27842
28120
  while (walkDir !== root) {
27843
- if (existsSync39(path51.join(walkDir, "package.json"))) {
28121
+ if (existsSync39(path52.join(walkDir, "package.json"))) {
27844
28122
  foundRoot = walkDir;
27845
28123
  break;
27846
28124
  }
27847
- walkDir = path51.dirname(walkDir);
28125
+ walkDir = path52.dirname(walkDir);
27848
28126
  }
27849
28127
  if (!foundRoot) {
27850
28128
  results.push({
@@ -27854,7 +28132,7 @@ function checkEmbedPipeline(pkgRoot) {
27854
28132
  });
27855
28133
  continue;
27856
28134
  }
27857
- const resolvedDaemon = path51.join(foundRoot, "dist", "lib", "exe-daemon.js");
28135
+ const resolvedDaemon = path52.join(foundRoot, "dist", "lib", "exe-daemon.js");
27858
28136
  const reachable = existsSync39(resolvedDaemon);
27859
28137
  results.push({
27860
28138
  name: `exed/reachable-from-${dir}`,
@@ -27866,7 +28144,7 @@ function checkEmbedPipeline(pkgRoot) {
27866
28144
  }
27867
28145
  function checkTaskSystem(pkgRoot) {
27868
28146
  const results = [];
27869
- const scannerPath = path51.join(pkgRoot, "dist", "bin", "scan-tasks.js");
28147
+ const scannerPath = path52.join(pkgRoot, "dist", "bin", "scan-tasks.js");
27870
28148
  if (!existsSync39(scannerPath)) {
27871
28149
  results.push({ name: "tasks/scanner", pass: false, detail: "scan-tasks.js not found" });
27872
28150
  return results;
@@ -27889,7 +28167,7 @@ function checkTaskSystem(pkgRoot) {
27889
28167
  }
27890
28168
  function checkWorkerSpawning(pkgRoot) {
27891
28169
  const results = [];
27892
- const workerPath = path51.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
28170
+ const workerPath = path52.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
27893
28171
  if (!existsSync39(workerPath)) {
27894
28172
  results.push({ name: "workers/ingest-worker", pass: false, detail: "ingest-worker.js not found" });
27895
28173
  return results;
@@ -27904,14 +28182,14 @@ function checkWorkerSpawning(pkgRoot) {
27904
28182
  detail: `Parse error: ${err instanceof Error ? err.message.slice(0, 200) : String(err)}`
27905
28183
  });
27906
28184
  }
27907
- const hooksDir = path51.join(pkgRoot, "dist", "hooks");
28185
+ const hooksDir = path52.join(pkgRoot, "dist", "hooks");
27908
28186
  if (existsSync39(hooksDir)) {
27909
28187
  const hookFiles = readdirSync14(hooksDir).filter((f) => f.endsWith(".js") && !f.endsWith(".js.map"));
27910
28188
  let hooksPassed = 0;
27911
28189
  const hooksFailed = [];
27912
28190
  for (const hook of hookFiles) {
27913
28191
  try {
27914
- execSync14(`node --check "${path51.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
28192
+ execSync14(`node --check "${path52.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
27915
28193
  hooksPassed++;
27916
28194
  } catch {
27917
28195
  hooksFailed.push(hook);
@@ -27935,8 +28213,8 @@ function checkWorkerSpawning(pkgRoot) {
27935
28213
  }
27936
28214
  function checkMcpTransport() {
27937
28215
  const results = [];
27938
- const pidPath = path51.join(EXE_AI_DIR, "exed.pid");
27939
- const tokenPath = path51.join(EXE_AI_DIR, "exed.token");
28216
+ const pidPath = path52.join(EXE_AI_DIR, "exed.pid");
28217
+ const tokenPath = path52.join(EXE_AI_DIR, "exed.token");
27940
28218
  let daemonAlive = false;
27941
28219
  if (existsSync39(pidPath)) {
27942
28220
  try {
@@ -27983,7 +28261,7 @@ function checkMcpTransport() {
27983
28261
  results.push({
27984
28262
  name: "mcp/monitor-summary",
27985
28263
  pass: true,
27986
- detail: `Privacy-safe local monitor summary: ${path51.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
28264
+ detail: `Privacy-safe local monitor summary: ${path52.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
27987
28265
  });
27988
28266
  return results;
27989
28267
  }
@@ -28041,7 +28319,7 @@ function checkClaudeCodeInstall() {
28041
28319
  detail: "Failed to check claude binary path"
28042
28320
  });
28043
28321
  }
28044
- const versionsDir = path51.join(
28322
+ const versionsDir = path52.join(
28045
28323
  process.env.HOME ?? process.env.USERPROFILE ?? "",
28046
28324
  ".local",
28047
28325
  "share",
@@ -28148,9 +28426,9 @@ if (isMainModule(import.meta.url)) {
28148
28426
  // src/lib/update-check.ts
28149
28427
  import { execSync as execSync15 } from "child_process";
28150
28428
  import { readFileSync as readFileSync34 } from "fs";
28151
- import path52 from "path";
28429
+ import path53 from "path";
28152
28430
  function getLocalVersion(packageRoot) {
28153
- const pkgPath = path52.join(packageRoot, "package.json");
28431
+ const pkgPath = path53.join(packageRoot, "package.json");
28154
28432
  const pkg = JSON.parse(readFileSync34(pkgPath, "utf-8"));
28155
28433
  return pkg.version;
28156
28434
  }
@@ -28218,12 +28496,12 @@ function registerCliParityTools(server2) {
28218
28496
  title: "Doctor",
28219
28497
  description: "Run exe-os doctor audit. Defaults to read-only diagnostics; optional dry-run/fix flags mirror CLI.",
28220
28498
  inputSchema: {
28221
- agent: z90.string().optional(),
28222
- project: z90.string().optional(),
28223
- verbose: z90.boolean().default(false),
28224
- conflicts: z90.boolean().default(false),
28225
- dry_run: z90.boolean().default(false),
28226
- fix: z90.boolean().default(false)
28499
+ agent: z91.string().optional(),
28500
+ project: z91.string().optional(),
28501
+ verbose: z91.boolean().default(false),
28502
+ conflicts: z91.boolean().default(false),
28503
+ dry_run: z91.boolean().default(false),
28504
+ fix: z91.boolean().default(false)
28227
28505
  }
28228
28506
  }, async ({ agent, project, verbose, conflicts, dry_run, fix }) => {
28229
28507
  const args = [];
@@ -28240,9 +28518,9 @@ function registerCliParityTools(server2) {
28240
28518
  title: "Rename Employee",
28241
28519
  description: "Rename an employee using the same path as `exe-os rename <old> <new>`. Use for customer roster/identity renames.",
28242
28520
  inputSchema: {
28243
- old_name: z90.string().min(1),
28244
- new_name: z90.string().min(1),
28245
- dry_run: z90.boolean().default(false)
28521
+ old_name: z91.string().min(1),
28522
+ new_name: z91.string().min(1),
28523
+ dry_run: z91.boolean().default(false)
28246
28524
  }
28247
28525
  }, async ({ old_name, new_name, dry_run }) => {
28248
28526
  if (dry_run) {
@@ -28255,7 +28533,7 @@ function registerCliParityTools(server2) {
28255
28533
  server2.registerTool("status_brief", {
28256
28534
  title: "Status Brief",
28257
28535
  description: "Return current employee/tmux status. Mirrors `exe-status` and supports optional deep view for one employee.",
28258
- inputSchema: { employee: z90.string().optional() }
28536
+ inputSchema: { employee: z91.string().optional() }
28259
28537
  }, async ({ employee }) => {
28260
28538
  const text3 = await status(employee);
28261
28539
  return result2(text3, { ok: true, employee: employee ?? null });
@@ -28263,7 +28541,7 @@ function registerCliParityTools(server2) {
28263
28541
  server2.registerTool("pending_work_summary", {
28264
28542
  title: "Pending Work Summary",
28265
28543
  description: "Return pending reviews, messages, and notifications using the same summaries as the CLI tools.",
28266
- inputSchema: { agent: z90.string().optional() }
28544
+ inputSchema: { agent: z91.string().optional() }
28267
28545
  }, async ({ agent }) => {
28268
28546
  const parts = await Promise.all([
28269
28547
  runCommand("exe-pending-reviews", [], 3e4),
@@ -28284,7 +28562,7 @@ function registerCliParityTools(server2) {
28284
28562
  server2.registerTool("key_rotation_preflight", {
28285
28563
  title: "Key Rotation Preflight",
28286
28564
  description: "Dry-run key rotation/update preflight. No destructive changes.",
28287
- inputSchema: { mode: z90.enum(["rotate", "update"]).default("rotate") }
28565
+ inputSchema: { mode: z91.enum(["rotate", "update"]).default("rotate") }
28288
28566
  }, async ({ mode }) => {
28289
28567
  const out = await runCommand("exe-os", ["key", mode, "--dry-run"], 6e4);
28290
28568
  return result2(out.text, { ok: out.ok, command: out.command, mode }, !out.ok);
@@ -28303,11 +28581,11 @@ function registerCliParityTools(server2) {
28303
28581
  title: "Stack Update Check",
28304
28582
  description: "Plan/check a customer stack update without applying Docker changes. Mirrors `exe-os stack-update --check`.",
28305
28583
  inputSchema: {
28306
- target: z90.string().optional(),
28307
- manifest: z90.string().optional(),
28308
- compose_file: z90.string().optional(),
28309
- env_file: z90.string().optional(),
28310
- deployment_persona: z90.enum(["customer", "askexe-control-plane"]).default("customer")
28584
+ target: z91.string().optional(),
28585
+ manifest: z91.string().optional(),
28586
+ compose_file: z91.string().optional(),
28587
+ env_file: z91.string().optional(),
28588
+ deployment_persona: z91.enum(["customer", "askexe-control-plane"]).default("customer")
28311
28589
  }
28312
28590
  }, async ({ target, manifest, compose_file, env_file, deployment_persona }) => {
28313
28591
  const args = ["stack-update", "--check", "--deployment-persona", deployment_persona];
@@ -28331,7 +28609,7 @@ function registerCliParityTools(server2) {
28331
28609
  // src/mcp/tools/get-session-events.ts
28332
28610
  init_active_agent();
28333
28611
  init_fast_db_init();
28334
- import { z as z91 } from "zod";
28612
+ import { z as z92 } from "zod";
28335
28613
 
28336
28614
  // src/lib/session-events.ts
28337
28615
  init_task_scope();
@@ -28418,7 +28696,7 @@ async function listRecentSessionEvents(client, options) {
28418
28696
  }
28419
28697
 
28420
28698
  // src/mcp/tools/get-session-events.ts
28421
- var EVENT_TYPE = z91.enum([
28699
+ var EVENT_TYPE = z92.enum([
28422
28700
  "user_prompt",
28423
28701
  "assistant_response",
28424
28702
  "tool_call",
@@ -28448,11 +28726,11 @@ function registerGetSessionEvents(server2) {
28448
28726
  title: "Get Session Events",
28449
28727
  description: "Return exact recent chronological user prompts, assistant responses, and tool calls from the append-only session journal. Use this when asked what happened last, not semantic memory search.",
28450
28728
  inputSchema: {
28451
- agent_id: z91.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28452
- session_id: z91.string().optional().describe("Optional exact runtime session id."),
28729
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28730
+ session_id: z92.string().optional().describe("Optional exact runtime session id."),
28453
28731
  event_type: EVENT_TYPE.optional().describe("Filter to one event type."),
28454
- project_name: z91.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
28455
- limit: z91.number().int().min(1).max(100).default(20).describe("Number of events to return.")
28732
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
28733
+ limit: z92.number().int().min(1).max(100).default(20).describe("Number of events to return.")
28456
28734
  }
28457
28735
  },
28458
28736
  async ({ agent_id, session_id, event_type, project_name, limit }) => {
@@ -28488,8 +28766,8 @@ function registerGetLastAssistantResponse(server2) {
28488
28766
  title: "Get Last Assistant Response",
28489
28767
  description: "Return the exact last assistant response for an agent from the session event journal. Use for 'what was the last thing you said?'",
28490
28768
  inputSchema: {
28491
- agent_id: z91.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28492
- project_name: z91.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
28769
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
28770
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
28493
28771
  }
28494
28772
  },
28495
28773
  async ({ agent_id, project_name }) => {
@@ -28519,32 +28797,106 @@ function registerGetLastAssistantResponse(server2) {
28519
28797
  }
28520
28798
 
28521
28799
  // src/mcp/tools/code-context.ts
28522
- import { z as z92 } from "zod";
28800
+ import { z as z93 } from "zod";
28523
28801
 
28524
28802
  // src/lib/code-context-index.ts
28525
28803
  init_config();
28526
- import crypto19 from "crypto";
28527
- import path53 from "path";
28804
+ import crypto20 from "crypto";
28805
+ import path54 from "path";
28528
28806
  import { existsSync as existsSync40, mkdirSync as mkdirSync22, readFileSync as readFileSync35, readdirSync as readdirSync15, statSync as statSync9, writeFileSync as writeFileSync24 } from "fs";
28529
28807
  import { spawnSync } from "child_process";
28808
+ init_exe_daemon_client();
28809
+ var VECTOR_STORE_VERSION = 1;
28810
+ var EMBED_BATCH_SIZE = 64;
28811
+ function vectorStorePath(projectRoot) {
28812
+ const rootHash = hashText(projectRoot).slice(0, 16);
28813
+ return path54.join(indexDir(), `${rootHash}.vectors.json`);
28814
+ }
28815
+ function loadVectorStore(projectRoot) {
28816
+ const file = vectorStorePath(projectRoot);
28817
+ if (!existsSync40(file)) return null;
28818
+ try {
28819
+ const parsed = JSON.parse(readFileSync35(file, "utf8"));
28820
+ if (parsed.version !== VECTOR_STORE_VERSION) return null;
28821
+ return parsed;
28822
+ } catch {
28823
+ return null;
28824
+ }
28825
+ }
28826
+ function saveVectorStore(projectRoot, store) {
28827
+ writeFileSync24(vectorStorePath(projectRoot), JSON.stringify(store));
28828
+ }
28829
+ function cosineSimilarity3(a, b) {
28830
+ let dot = 0, normA = 0, normB = 0;
28831
+ for (let i = 0; i < a.length; i++) {
28832
+ dot += a[i] * b[i];
28833
+ normA += a[i] * a[i];
28834
+ normB += b[i] * b[i];
28835
+ }
28836
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
28837
+ return denom === 0 ? 0 : dot / denom;
28838
+ }
28839
+ async function embedSymbols(index) {
28840
+ const rootHash = hashText(index.projectRoot).slice(0, 16);
28841
+ const existing = loadVectorStore(index.projectRoot);
28842
+ const store = {
28843
+ version: VECTOR_STORE_VERSION,
28844
+ projectRootHash: rootHash,
28845
+ vectors: {}
28846
+ };
28847
+ const allSymbols = [];
28848
+ for (const file of Object.values(index.files)) {
28849
+ for (const symbol of file.symbols) {
28850
+ if (existing?.vectors[symbol.id]) {
28851
+ store.vectors[symbol.id] = existing.vectors[symbol.id];
28852
+ } else {
28853
+ allSymbols.push({ id: symbol.id, text: symbol.summary || `${symbol.kind} ${symbol.name} in ${symbol.filePath}` });
28854
+ }
28855
+ }
28856
+ }
28857
+ if (allSymbols.length === 0) {
28858
+ saveVectorStore(index.projectRoot, store);
28859
+ return store;
28860
+ }
28861
+ const connected = await connectEmbedDaemon().catch(() => false);
28862
+ if (!connected) {
28863
+ saveVectorStore(index.projectRoot, store);
28864
+ return store;
28865
+ }
28866
+ for (let i = 0; i < allSymbols.length; i += EMBED_BATCH_SIZE) {
28867
+ const batch = allSymbols.slice(i, i + EMBED_BATCH_SIZE);
28868
+ const texts = batch.map((s) => s.text);
28869
+ try {
28870
+ const vectors = await embedBatchViaClient(texts, "low");
28871
+ if (vectors && vectors.length === batch.length) {
28872
+ for (let j = 0; j < batch.length; j++) {
28873
+ store.vectors[batch[j].id] = vectors[j];
28874
+ }
28875
+ }
28876
+ } catch {
28877
+ }
28878
+ }
28879
+ saveVectorStore(index.projectRoot, store);
28880
+ return store;
28881
+ }
28530
28882
  var INDEX_VERSION = 2;
28531
28883
  var DEFAULT_MAX_FILES = 5e3;
28532
28884
  var IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
28533
28885
  function normalizeProjectRoot(projectRoot) {
28534
- return path53.resolve(projectRoot || process.cwd());
28886
+ return path54.resolve(projectRoot || process.cwd());
28535
28887
  }
28536
28888
  function hashText(text3) {
28537
- return crypto19.createHash("sha256").update(text3).digest("hex");
28889
+ return crypto20.createHash("sha256").update(text3).digest("hex");
28538
28890
  }
28539
28891
  function indexDir() {
28540
- const dir = path53.join(EXE_AI_DIR, "code-context");
28892
+ const dir = path54.join(EXE_AI_DIR, "code-context");
28541
28893
  mkdirSync22(dir, { recursive: true });
28542
28894
  return dir;
28543
28895
  }
28544
28896
  function getCodeContextIndexPath(projectRoot) {
28545
28897
  const root = normalizeProjectRoot(projectRoot);
28546
28898
  const rootHash = hashText(root).slice(0, 16);
28547
- return path53.join(indexDir(), `${rootHash}.json`);
28899
+ return path54.join(indexDir(), `${rootHash}.json`);
28548
28900
  }
28549
28901
  function currentBranch(projectRoot) {
28550
28902
  const result3 = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
@@ -28557,8 +28909,8 @@ function shouldIgnore(relPath) {
28557
28909
  }
28558
28910
  function listRecursive(projectRoot, dir = projectRoot, out = []) {
28559
28911
  for (const entry of readdirSync15(dir, { withFileTypes: true })) {
28560
- const abs = path53.join(dir, entry.name);
28561
- const rel = path53.relative(projectRoot, abs).replaceAll(path53.sep, "/");
28912
+ const abs = path54.join(dir, entry.name);
28913
+ const rel = path54.relative(projectRoot, abs).replaceAll(path54.sep, "/");
28562
28914
  if (shouldIgnore(rel)) continue;
28563
28915
  if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
28564
28916
  else if (entry.isFile()) out.push(rel);
@@ -28574,7 +28926,7 @@ function listCodeFiles(projectRoot, maxFiles) {
28574
28926
  const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
28575
28927
  files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
28576
28928
  }
28577
- return files.map((file) => file.replaceAll(path53.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
28929
+ return files.map((file) => file.replaceAll(path54.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
28578
28930
  }
28579
28931
  function parseImportPaths(importText) {
28580
28932
  const paths = [];
@@ -28587,13 +28939,13 @@ function parseImportPaths(importText) {
28587
28939
  }
28588
28940
  function resolveImport(fromFile, importPath, allFiles) {
28589
28941
  if (!importPath.startsWith(".")) return null;
28590
- const base = path53.posix.normalize(path53.posix.join(path53.posix.dirname(fromFile.replaceAll(path53.sep, "/")), importPath));
28942
+ const base = path54.posix.normalize(path54.posix.join(path54.posix.dirname(fromFile.replaceAll(path54.sep, "/")), importPath));
28591
28943
  const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
28592
28944
  const candidates = [base];
28593
28945
  for (const ext of ["ts", "tsx", "js", "jsx", "py", "rs", "go", "java", "cs", "cpp", "c", "rb", "php", "swift", "kt", "scala", "sql", "md", "json", "yaml", "yml"]) {
28594
28946
  candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
28595
28947
  }
28596
- for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path53.posix.join(base, indexName));
28948
+ for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path54.posix.join(base, indexName));
28597
28949
  return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
28598
28950
  }
28599
28951
  function symbolId(filePath, chunk) {
@@ -28614,7 +28966,7 @@ function saveIndex(index) {
28614
28966
  writeFileSync24(getCodeContextIndexPath(index.projectRoot), JSON.stringify(index, null, 2));
28615
28967
  }
28616
28968
  function buildFileRecord(projectRoot, relPath, allFiles, previous) {
28617
- const absPath = path53.join(projectRoot, relPath);
28969
+ const absPath = path54.join(projectRoot, relPath);
28618
28970
  let stat;
28619
28971
  try {
28620
28972
  stat = statSync9(absPath);
@@ -28652,13 +29004,13 @@ function buildCodeContextIndex(options = {}) {
28652
29004
  const branch = currentBranch(projectRoot);
28653
29005
  const previous = options.force ? null : loadIndex(projectRoot);
28654
29006
  const files = listCodeFiles(projectRoot, maxFiles);
28655
- const allFiles = new Set(files.map((file) => file.replaceAll(path53.sep, "/")));
29007
+ const allFiles = new Set(files.map((file) => file.replaceAll(path54.sep, "/")));
28656
29008
  const fileRecords = {};
28657
29009
  let rebuiltFiles = 0;
28658
29010
  let reusedFiles = 0;
28659
29011
  let skippedFiles = 0;
28660
29012
  for (const rel of files) {
28661
- const normalized = rel.replaceAll(path53.sep, "/");
29013
+ const normalized = rel.replaceAll(path54.sep, "/");
28662
29014
  const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
28663
29015
  if (record) {
28664
29016
  fileRecords[normalized] = record;
@@ -28687,11 +29039,11 @@ function loadOrBuildCodeContextIndex(options = {}) {
28687
29039
  if (loaded) {
28688
29040
  const currentFiles = listCodeFiles(projectRoot, options.maxFiles ?? DEFAULT_MAX_FILES);
28689
29041
  const unchanged = currentFiles.every((rel) => {
28690
- const normalized = rel.replaceAll(path53.sep, "/");
29042
+ const normalized = rel.replaceAll(path54.sep, "/");
28691
29043
  const existing = loaded.files[normalized];
28692
29044
  if (!existing) return false;
28693
29045
  try {
28694
- const stat = statSync9(path53.join(projectRoot, normalized));
29046
+ const stat = statSync9(path54.join(projectRoot, normalized));
28695
29047
  return stat.mtimeMs === existing.mtimeMs && stat.size === existing.size;
28696
29048
  } catch {
28697
29049
  return false;
@@ -28744,9 +29096,9 @@ function globToRegex(pattern) {
28744
29096
  }
28745
29097
  function matchesPath(filePath, patterns) {
28746
29098
  if (!patterns || patterns.length === 0) return true;
28747
- const normalized = filePath.replaceAll(path53.sep, "/");
29099
+ const normalized = filePath.replaceAll(path54.sep, "/");
28748
29100
  return patterns.some((pattern) => {
28749
- const p = pattern.replaceAll(path53.sep, "/").replace(/^\.\//, "");
29101
+ const p = pattern.replaceAll(path54.sep, "/").replace(/^\.\//, "");
28750
29102
  return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
28751
29103
  });
28752
29104
  }
@@ -28805,7 +29157,7 @@ function filteredFiles(index, options = {}) {
28805
29157
  return matchesPath(file.path, options.paths);
28806
29158
  });
28807
29159
  }
28808
- function searchCodeContext(query, options = {}) {
29160
+ function lexicalSearch(query, options = {}) {
28809
29161
  const terms = tokenize(query);
28810
29162
  if (terms.length === 0) return [];
28811
29163
  const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
@@ -28831,6 +29183,82 @@ function searchCodeContext(query, options = {}) {
28831
29183
  const limit = options.limit ?? 20;
28832
29184
  return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
28833
29185
  }
29186
+ function searchCodeContext(query, options = {}) {
29187
+ return lexicalSearch(query, options);
29188
+ }
29189
+ var RRF_K2 = 60;
29190
+ async function searchCodeContextSemantic(query, options = {}) {
29191
+ const terms = tokenize(query);
29192
+ if (terms.length === 0) return [];
29193
+ const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
29194
+ const projectRoot = normalizeProjectRoot(options.projectRoot);
29195
+ const vectorStore = loadVectorStore(projectRoot);
29196
+ if (!vectorStore || Object.keys(vectorStore.vectors).length === 0) {
29197
+ return lexicalSearch(query, options);
29198
+ }
29199
+ let queryVector = null;
29200
+ try {
29201
+ const connected = await connectEmbedDaemon().catch(() => false);
29202
+ if (connected) {
29203
+ const result3 = await embedBatchViaClient([query], "high");
29204
+ if (result3 && result3.length === 1) queryVector = result3[0];
29205
+ }
29206
+ } catch {
29207
+ }
29208
+ if (!queryVector) return lexicalSearch(query, options);
29209
+ const files = filteredFiles(index, options);
29210
+ const candidates = [];
29211
+ for (const file of files) {
29212
+ for (const symbol of file.symbols) {
29213
+ const lexical = scoreSymbol(symbol, terms);
29214
+ const vec = vectorStore.vectors[symbol.id];
29215
+ const vectorScore = vec ? cosineSimilarity3(queryVector, vec) : 0;
29216
+ if (lexical.score > 0 || vectorScore > 0.3) {
29217
+ candidates.push({
29218
+ symbol,
29219
+ lexicalScore: lexical.score,
29220
+ vectorScore,
29221
+ matches: lexical.matches
29222
+ });
29223
+ }
29224
+ }
29225
+ }
29226
+ const byLexical = [...candidates].sort((a, b) => b.lexicalScore - a.lexicalScore);
29227
+ const byVector = [...candidates].sort((a, b) => b.vectorScore - a.vectorScore);
29228
+ const lexicalRank = /* @__PURE__ */ new Map();
29229
+ const vectorRank = /* @__PURE__ */ new Map();
29230
+ byLexical.forEach((c, i) => lexicalRank.set(c.symbol.id, i + 1));
29231
+ byVector.forEach((c, i) => vectorRank.set(c.symbol.id, i + 1));
29232
+ const VECTOR_WEIGHT = 0.6;
29233
+ const LEXICAL_WEIGHT = 0.4;
29234
+ const fused = candidates.map((c) => {
29235
+ const lRank = lexicalRank.get(c.symbol.id) ?? candidates.length + 1;
29236
+ const vRank = vectorRank.get(c.symbol.id) ?? candidates.length + 1;
29237
+ const rrfScore = VECTOR_WEIGHT * (1 / (RRF_K2 + vRank)) + LEXICAL_WEIGHT * (1 / (RRF_K2 + lRank));
29238
+ return {
29239
+ symbol: c.symbol,
29240
+ score: rrfScore,
29241
+ matches: c.vectorScore > 0.3 ? [...c.matches, `semantic=${c.vectorScore.toFixed(3)}`] : c.matches,
29242
+ filePath: c.symbol.filePath,
29243
+ language: c.symbol.language,
29244
+ content: c.symbol.text,
29245
+ startLine: c.symbol.startLine,
29246
+ endLine: c.symbol.endLine
29247
+ };
29248
+ });
29249
+ const offset = Math.max(0, options.offset ?? 0);
29250
+ const limit = options.limit ?? 20;
29251
+ return fused.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
29252
+ }
29253
+ async function buildCodeContextIndexWithEmbeddings(options = {}) {
29254
+ const index = buildCodeContextIndex(options);
29255
+ const existingStore = loadVectorStore(index.projectRoot);
29256
+ const existingCount = existingStore ? Object.keys(existingStore.vectors).length : 0;
29257
+ const store = await embedSymbols(index);
29258
+ const vectorCount = Object.keys(store.vectors).length;
29259
+ const newEmbeddings = vectorCount - Math.min(existingCount, vectorCount);
29260
+ return { index, vectorCount, newEmbeddings };
29261
+ }
28834
29262
  function dependentsMap(index) {
28835
29263
  const map = /* @__PURE__ */ new Map();
28836
29264
  for (const file of Object.values(index.files)) {
@@ -28864,7 +29292,7 @@ function traceCodeSymbol(symbolName, options = {}) {
28864
29292
  }
28865
29293
  function resolveTargetFile(index, input) {
28866
29294
  if (input.filePath) {
28867
- const normalized = input.filePath.replaceAll(path53.sep, "/").replace(/^\.\//, "");
29295
+ const normalized = input.filePath.replaceAll(path54.sep, "/").replace(/^\.\//, "");
28868
29296
  if (index.files[normalized]) return { filePath: normalized, target: normalized };
28869
29297
  const suffix = Object.keys(index.files).find((file) => file.endsWith(normalized));
28870
29298
  if (suffix) return { filePath: suffix, target: input.filePath };
@@ -28894,7 +29322,7 @@ function analyzeBlastRadius(input) {
28894
29322
  }
28895
29323
  }
28896
29324
  }
28897
- const targetBase = path53.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
29325
+ const targetBase = path54.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
28898
29326
  const symbolLower = input.symbol?.toLowerCase();
28899
29327
  const tests = Object.keys(index.files).filter((file) => {
28900
29328
  const lower = file.toLowerCase();
@@ -28933,23 +29361,24 @@ function jsonResult(value) {
28933
29361
  function registerCodeContext(server2) {
28934
29362
  server2.registerTool("code_context", {
28935
29363
  title: "Code Context",
28936
- description: "Persistent codebase context engine. One consolidated tool to avoid MCP bloat. Actions: index, search, trace, blast_radius, stats.",
29364
+ description: "Persistent codebase context engine with semantic vector search. One consolidated tool to avoid MCP bloat. Actions: index (structural only), index_embed (structural + vector embeddings), search (semantic+lexical hybrid), trace, blast_radius, stats. Search uses RRF fusion of Jina v5 embeddings (cosine similarity) + lexical scoring. Falls back to lexical if daemon unavailable.",
28937
29365
  inputSchema: {
28938
- action: z92.enum(["index", "search", "trace", "blast_radius", "stats"]).describe("Code context operation"),
28939
- project_root: z92.string().optional().describe("Repository root. Defaults to current working directory."),
28940
- query: z92.string().optional().describe("Search query for action=search"),
28941
- symbol: z92.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
28942
- file_path: z92.string().optional().describe("File path for blast_radius"),
28943
- force: z92.boolean().optional().describe("Force rebuild before answering"),
28944
- limit: z92.coerce.number().int().min(1).max(100).optional().describe("Max results"),
28945
- offset: z92.coerce.number().int().min(0).optional().describe("Search pagination offset"),
28946
- refresh_index: z92.boolean().optional().describe("Refresh/rebuild index before searching"),
28947
- languages: z92.array(z92.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
28948
- paths: z92.array(z92.string()).optional().describe("Path/glob filters"),
28949
- depth: z92.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
28950
- max_files: z92.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index")
28951
- }
28952
- }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files }) => {
29366
+ action: z93.enum(["index", "index_embed", "search", "trace", "blast_radius", "stats"]).describe("Code context operation. index_embed = index + generate embeddings for semantic search."),
29367
+ project_root: z93.string().optional().describe("Repository root. Defaults to current working directory."),
29368
+ query: z93.string().optional().describe("Natural language search query (e.g. 'authentication logic', 'database migration'). Semantic search finds conceptual matches, not just keywords."),
29369
+ symbol: z93.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
29370
+ file_path: z93.string().optional().describe("File path for blast_radius"),
29371
+ force: z93.boolean().optional().describe("Force rebuild before answering"),
29372
+ limit: z93.coerce.number().int().min(1).max(100).optional().describe("Max results"),
29373
+ offset: z93.coerce.number().int().min(0).optional().describe("Search pagination offset"),
29374
+ refresh_index: z93.boolean().optional().describe("Refresh/rebuild index before searching"),
29375
+ languages: z93.array(z93.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
29376
+ paths: z93.array(z93.string()).optional().describe("Path/glob filters"),
29377
+ depth: z93.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
29378
+ max_files: z93.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index"),
29379
+ lexical_only: z93.boolean().optional().describe("Force lexical-only search (skip vector similarity). Default: false \u2014 uses semantic hybrid search.")
29380
+ }
29381
+ }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files, lexical_only }) => {
28953
29382
  const opts = { projectRoot: project_root, force, maxFiles: max_files };
28954
29383
  if (action === "index") {
28955
29384
  const index = buildCodeContextIndex(opts);
@@ -28959,7 +29388,24 @@ function registerCodeContext(server2) {
28959
29388
  indexedAt: index.indexedAt,
28960
29389
  files: Object.keys(index.files).length,
28961
29390
  symbols: Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0),
28962
- imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0)
29391
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
29392
+ note: "Structural index only. Use action=index_embed to also generate vector embeddings for semantic search."
29393
+ });
29394
+ }
29395
+ if (action === "index_embed") {
29396
+ const { index, vectorCount, newEmbeddings } = await buildCodeContextIndexWithEmbeddings(opts);
29397
+ const totalSymbols = Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0);
29398
+ return jsonResult({
29399
+ projectRoot: index.projectRoot,
29400
+ branch: index.branch,
29401
+ indexedAt: index.indexedAt,
29402
+ files: Object.keys(index.files).length,
29403
+ symbols: totalSymbols,
29404
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
29405
+ vectorCount,
29406
+ newEmbeddings,
29407
+ embeddingCoverage: totalSymbols > 0 ? `${(vectorCount / totalSymbols * 100).toFixed(1)}%` : "0%",
29408
+ note: "Structural index + vector embeddings. Semantic search is now available."
28963
29409
  });
28964
29410
  }
28965
29411
  if (action === "stats") {
@@ -28967,14 +29413,28 @@ function registerCodeContext(server2) {
28967
29413
  }
28968
29414
  if (action === "search") {
28969
29415
  if (!query) return errorResult10('code_context action "search" requires query');
29416
+ const searchOpts = { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths };
29417
+ if (lexical_only) {
29418
+ return jsonResult({
29419
+ query,
29420
+ mode: "lexical",
29421
+ limit: limit ?? 20,
29422
+ offset: offset ?? 0,
29423
+ languages: languages ?? [],
29424
+ paths: paths ?? [],
29425
+ results: searchCodeContext(query, searchOpts)
29426
+ });
29427
+ }
29428
+ const results = await searchCodeContextSemantic(query, searchOpts);
29429
+ const hasSemantic = results.some((r) => r.matches.some((m) => m.startsWith("semantic=")));
28970
29430
  return jsonResult({
28971
29431
  query,
29432
+ mode: hasSemantic ? "semantic+lexical" : "lexical-fallback",
28972
29433
  limit: limit ?? 20,
28973
29434
  offset: offset ?? 0,
28974
- refresh_index: refresh_index ?? false,
28975
29435
  languages: languages ?? [],
28976
29436
  paths: paths ?? [],
28977
- results: searchCodeContext(query, { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths })
29437
+ results
28978
29438
  });
28979
29439
  }
28980
29440
  if (action === "trace") {
@@ -28992,9 +29452,9 @@ function registerCodeContext(server2) {
28992
29452
  }
28993
29453
 
28994
29454
  // src/mcp/tools/support-inbox.ts
28995
- import { z as z93 } from "zod";
29455
+ import { z as z94 } from "zod";
28996
29456
  var DEFAULT_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
28997
- var STATUS = z93.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
29457
+ var STATUS = z94.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
28998
29458
  function adminToken() {
28999
29459
  return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN;
29000
29460
  }
@@ -29033,9 +29493,9 @@ function registerListBugReports(server2) {
29033
29493
  title: "List Bug Reports",
29034
29494
  description: "AskExe-internal only: list incoming customer bug reports from the support inbox.",
29035
29495
  inputSchema: {
29036
- status: z93.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
29037
- severity: z93.enum(["p0", "p1", "p2", "p3"]).optional(),
29038
- limit: z93.number().int().min(1).max(100).default(25)
29496
+ status: z94.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
29497
+ severity: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
29498
+ limit: z94.number().int().min(1).max(100).default(25)
29039
29499
  }
29040
29500
  },
29041
29501
  async ({ status: status2, severity, limit }) => {
@@ -29054,7 +29514,7 @@ function registerGetBugReport(server2) {
29054
29514
  {
29055
29515
  title: "Get Bug Report",
29056
29516
  description: "AskExe-internal only: fetch one customer bug report with full markdown payload.",
29057
- inputSchema: { id: z93.string().min(8) }
29517
+ inputSchema: { id: z94.string().min(8) }
29058
29518
  },
29059
29519
  async ({ id }) => {
29060
29520
  const data = await requestJson(`${endpoint()}/${encodeURIComponent(id)}`);
@@ -29069,12 +29529,12 @@ function registerTriageBugReport(server2) {
29069
29529
  title: "Triage Bug Report",
29070
29530
  description: "AskExe-internal only: update bug report status and link task/commit/release metadata.",
29071
29531
  inputSchema: {
29072
- id: z93.string().min(8),
29532
+ id: z94.string().min(8),
29073
29533
  status: STATUS.optional(),
29074
- triage_notes: z93.string().optional(),
29075
- linked_task_id: z93.string().optional(),
29076
- linked_commit: z93.string().optional(),
29077
- fixed_version: z93.string().optional()
29534
+ triage_notes: z94.string().optional(),
29535
+ linked_task_id: z94.string().optional(),
29536
+ linked_commit: z94.string().optional(),
29537
+ fixed_version: z94.string().optional()
29078
29538
  }
29079
29539
  },
29080
29540
  async ({ id, status: status2, triage_notes, linked_task_id, linked_commit, fixed_version }) => {
@@ -29086,6 +29546,69 @@ function registerTriageBugReport(server2) {
29086
29546
  }
29087
29547
  );
29088
29548
  }
29549
+ var FEATURE_STATUS = z94.enum(["open", "planned", "in_progress", "shipped", "closed", "wontdo"]);
29550
+ function featureEndpoint() {
29551
+ return endpoint().replace(/\/bug-reports\/?$/, "/feature-requests");
29552
+ }
29553
+ function registerListFeatureRequests(server2) {
29554
+ server2.registerTool(
29555
+ "list_feature_requests",
29556
+ {
29557
+ title: "List Feature Requests",
29558
+ description: "AskExe-internal only: list incoming customer feature requests from the support inbox.",
29559
+ inputSchema: {
29560
+ status: z94.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("open"),
29561
+ priority: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
29562
+ limit: z94.number().int().min(1).max(100).default(25)
29563
+ }
29564
+ },
29565
+ async ({ status: status2, priority, limit }) => {
29566
+ const url = new URL(featureEndpoint());
29567
+ url.searchParams.set("status", status2);
29568
+ url.searchParams.set("limit", String(limit));
29569
+ if (priority) url.searchParams.set("priority", priority);
29570
+ const data = await requestJson(url.toString());
29571
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29572
+ }
29573
+ );
29574
+ }
29575
+ function registerGetFeatureRequest(server2) {
29576
+ server2.registerTool(
29577
+ "get_feature_request",
29578
+ {
29579
+ title: "Get Feature Request",
29580
+ description: "AskExe-internal only: fetch one customer feature request with full payload.",
29581
+ inputSchema: { id: z94.string().min(8) }
29582
+ },
29583
+ async ({ id }) => {
29584
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`);
29585
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29586
+ }
29587
+ );
29588
+ }
29589
+ function registerTriageFeatureRequest(server2) {
29590
+ server2.registerTool(
29591
+ "triage_feature_request",
29592
+ {
29593
+ title: "Triage Feature Request",
29594
+ description: "AskExe-internal only: update feature request status, response notes, and version metadata.",
29595
+ inputSchema: {
29596
+ id: z94.string().min(8),
29597
+ status: FEATURE_STATUS.optional(),
29598
+ response_notes: z94.string().optional(),
29599
+ target_version: z94.string().optional(),
29600
+ shipped_version: z94.string().optional()
29601
+ }
29602
+ },
29603
+ async ({ id, status: status2, response_notes, target_version, shipped_version }) => {
29604
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`, {
29605
+ method: "PATCH",
29606
+ body: JSON.stringify({ status: status2, response_notes, target_version, shipped_version })
29607
+ });
29608
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29609
+ }
29610
+ );
29611
+ }
29089
29612
 
29090
29613
  // src/mcp/tool-gates.ts
29091
29614
  var TOOL_GATES = {
@@ -29409,6 +29932,7 @@ function registerAllTools(server2) {
29409
29932
  gate("registerStoreDecision", registerStoreDecision);
29410
29933
  gate("registerGetDecision", registerGetDecision);
29411
29934
  gate("registerCreateBugReport", registerCreateBugReport);
29935
+ gate("registerCreateFeatureRequest", registerCreateFeatureRequest);
29412
29936
  gate("registerSupportTools", registerSupportTools);
29413
29937
  gate("registerCliParityTools", registerCliParityTools);
29414
29938
  gate("registerGetSessionEvents", registerGetSessionEvents);
@@ -29418,6 +29942,9 @@ function registerAllTools(server2) {
29418
29942
  gate("registerListBugReports", registerListBugReports);
29419
29943
  gate("registerGetBugReport", registerGetBugReport);
29420
29944
  gate("registerTriageBugReport", registerTriageBugReport);
29945
+ gate("registerListFeatureRequests", registerListFeatureRequests);
29946
+ gate("registerGetFeatureRequest", registerGetFeatureRequest);
29947
+ gate("registerTriageFeatureRequest", registerTriageFeatureRequest);
29421
29948
  }
29422
29949
  if (exposeLegacyConfig) {
29423
29950
  gate("registerGetAgentSpend", registerGetAgentSpend);
@@ -29585,7 +30112,7 @@ try {
29585
30112
  }
29586
30113
  }, 3e4);
29587
30114
  _ppidWatchdog.unref();
29588
- const MCP_VERSION_PATH = path54.join(os20.homedir(), ".exe-os", "mcp-version");
30115
+ const MCP_VERSION_PATH = path55.join(os20.homedir(), ".exe-os", "mcp-version");
29589
30116
  let _currentMcpVersion = null;
29590
30117
  try {
29591
30118
  _currentMcpVersion = existsSync41(MCP_VERSION_PATH) ? readFileSync36(MCP_VERSION_PATH, "utf8").trim() : null;
@@ -29627,14 +30154,14 @@ try {
29627
30154
  `
29628
30155
  );
29629
30156
  const thisFile = fileURLToPath7(import.meta.url);
29630
- const backfillPath = path54.resolve(
29631
- path54.dirname(thisFile),
30157
+ const backfillPath = path55.resolve(
30158
+ path55.dirname(thisFile),
29632
30159
  "../bin/backfill-vectors.js"
29633
30160
  );
29634
30161
  if (existsSync41(backfillPath)) {
29635
30162
  const { EXE_AI_DIR: exeDir } = await Promise.resolve().then(() => (init_config(), config_exports));
29636
- const logPath = path54.join(exeDir, "workers.log");
29637
- mkdirSync23(path54.dirname(logPath), { recursive: true });
30163
+ const logPath = path55.join(exeDir, "workers.log");
30164
+ mkdirSync23(path55.dirname(logPath), { recursive: true });
29638
30165
  let logFd = "ignore";
29639
30166
  try {
29640
30167
  logFd = openSync4(logPath, "a");