@askexenow/exe-os 0.9.86 → 0.9.88

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 (103) hide show
  1. package/deploy/compose/docker-compose.yml +3 -3
  2. package/dist/bin/age-ontology-load.js +8 -2
  3. package/dist/bin/agentic-ontology-backfill.js +29 -0
  4. package/dist/bin/agentic-reflection-backfill.js +29 -0
  5. package/dist/bin/agentic-semantic-label.js +29 -0
  6. package/dist/bin/backfill-conversations.js +30 -0
  7. package/dist/bin/backfill-responses.js +30 -0
  8. package/dist/bin/backfill-vectors.js +30 -0
  9. package/dist/bin/bulk-sync-postgres.js +47 -1
  10. package/dist/bin/cc-doctor.js +3 -2
  11. package/dist/bin/cleanup-stale-review-tasks.js +30 -0
  12. package/dist/bin/cli.js +357 -19
  13. package/dist/bin/exe-agent.js +19 -0
  14. package/dist/bin/exe-assign.js +30 -0
  15. package/dist/bin/exe-boot.js +157 -4
  16. package/dist/bin/exe-call.js +20 -0
  17. package/dist/bin/exe-cloud.js +156 -3
  18. package/dist/bin/exe-dispatch.js +30 -1
  19. package/dist/bin/exe-doctor.js +30 -0
  20. package/dist/bin/exe-export-behaviors.js +29 -0
  21. package/dist/bin/exe-forget.js +30 -0
  22. package/dist/bin/exe-gateway.js +150 -35
  23. package/dist/bin/exe-healthcheck.js +2 -1
  24. package/dist/bin/exe-heartbeat.js +30 -0
  25. package/dist/bin/exe-kill.js +29 -0
  26. package/dist/bin/exe-launch-agent.js +29 -0
  27. package/dist/bin/exe-new-employee.js +37 -4
  28. package/dist/bin/exe-pending-messages.js +29 -0
  29. package/dist/bin/exe-pending-notifications.js +30 -0
  30. package/dist/bin/exe-pending-reviews.js +30 -0
  31. package/dist/bin/exe-rename.js +30 -0
  32. package/dist/bin/exe-review.js +30 -0
  33. package/dist/bin/exe-search.js +30 -0
  34. package/dist/bin/exe-session-cleanup.js +30 -1
  35. package/dist/bin/exe-settings.js +3 -0
  36. package/dist/bin/exe-start-codex.js +31 -2
  37. package/dist/bin/exe-start-opencode.js +31 -2
  38. package/dist/bin/exe-status.js +30 -0
  39. package/dist/bin/exe-team.js +30 -0
  40. package/dist/bin/git-sweep.js +30 -1
  41. package/dist/bin/graph-backfill.js +29 -0
  42. package/dist/bin/graph-export.js +29 -0
  43. package/dist/bin/graph-layer-benchmark.js +9 -1
  44. package/dist/bin/install.js +9 -0
  45. package/dist/bin/intercom-check.js +31 -1
  46. package/dist/bin/list-providers.js +1 -0
  47. package/dist/bin/postgres-agentic-reflection-backfill.js +7 -1
  48. package/dist/bin/postgres-agentic-semantic-backfill.js +7 -1
  49. package/dist/bin/registry-proxy.js +1 -0
  50. package/dist/bin/scan-tasks.js +31 -1
  51. package/dist/bin/setup.js +165 -9
  52. package/dist/bin/shard-migrate.js +29 -0
  53. package/dist/bin/stack-update.js +24 -7
  54. package/dist/bin/update.js +5 -0
  55. package/dist/gateway/index.js +30 -1
  56. package/dist/hooks/bug-report-worker.js +30 -1
  57. package/dist/hooks/codex-stop-task-finalizer.js +30 -1
  58. package/dist/hooks/commit-complete.js +30 -1
  59. package/dist/hooks/error-recall.js +29 -0
  60. package/dist/hooks/ingest.js +29 -0
  61. package/dist/hooks/instructions-loaded.js +29 -0
  62. package/dist/hooks/notification.js +29 -0
  63. package/dist/hooks/post-compact.js +29 -0
  64. package/dist/hooks/post-tool-combined.js +29 -0
  65. package/dist/hooks/pre-compact.js +30 -1
  66. package/dist/hooks/pre-tool-use.js +29 -0
  67. package/dist/hooks/prompt-submit.js +30 -1
  68. package/dist/hooks/session-end.js +30 -1
  69. package/dist/hooks/session-start.js +29 -0
  70. package/dist/hooks/stop.js +29 -0
  71. package/dist/hooks/subagent-stop.js +29 -0
  72. package/dist/hooks/summary-worker.js +155 -3
  73. package/dist/index.js +30 -1
  74. package/dist/lib/cloud-sync.js +136 -2
  75. package/dist/lib/consolidation.js +1 -0
  76. package/dist/lib/database.js +11 -0
  77. package/dist/lib/db.js +11 -0
  78. package/dist/lib/device-registry.js +11 -0
  79. package/dist/lib/employee-templates.js +19 -0
  80. package/dist/lib/exe-daemon.js +1455 -208
  81. package/dist/lib/hybrid-search.js +29 -0
  82. package/dist/lib/identity-templates.js +6 -2
  83. package/dist/lib/identity.js +1 -0
  84. package/dist/lib/messaging.js +2 -1
  85. package/dist/lib/reminders.js +1 -0
  86. package/dist/lib/schedules.js +29 -0
  87. package/dist/lib/skill-learning.js +1 -0
  88. package/dist/lib/store.js +29 -0
  89. package/dist/lib/tasks.js +2 -1
  90. package/dist/lib/tmux-routing.js +2 -1
  91. package/dist/lib/token-spend.js +1 -0
  92. package/dist/mcp/server.js +1278 -165
  93. package/dist/mcp/tools/complete-reminder.js +1 -0
  94. package/dist/mcp/tools/create-reminder.js +1 -0
  95. package/dist/mcp/tools/create-task.js +8 -3
  96. package/dist/mcp/tools/deactivate-behavior.js +1 -0
  97. package/dist/mcp/tools/list-reminders.js +1 -0
  98. package/dist/mcp/tools/list-tasks.js +1 -0
  99. package/dist/mcp/tools/send-message.js +2 -1
  100. package/dist/mcp/tools/update-task.js +2 -1
  101. package/dist/runtime/index.js +30 -1
  102. package/dist/tui/App.js +30 -1
  103. package/package.json +2 -2
@@ -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
  }
@@ -2180,6 +2180,7 @@ __export(database_exports, {
2180
2180
  isInitialized: () => isInitialized,
2181
2181
  setExternalClient: () => setExternalClient
2182
2182
  });
2183
+ import { chmodSync as chmodSync2 } from "fs";
2183
2184
  import { createClient } from "@libsql/client";
2184
2185
  async function initDatabase(config2) {
2185
2186
  if (_walCheckpointTimer) {
@@ -2221,6 +2222,16 @@ async function initDatabase(config2) {
2221
2222
  if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
2222
2223
  _adapterClient = await createPrismaDbAdapter(_resilientClient);
2223
2224
  }
2225
+ try {
2226
+ chmodSync2(config2.dbPath, 384);
2227
+ for (const suffix of ["-wal", "-shm"]) {
2228
+ try {
2229
+ chmodSync2(config2.dbPath + suffix, 384);
2230
+ } catch {
2231
+ }
2232
+ }
2233
+ } catch {
2234
+ }
2224
2235
  }
2225
2236
  function isInitialized() {
2226
2237
  return _adapterClient !== null || _client !== null;
@@ -3723,7 +3734,7 @@ async function tryKeytar() {
3723
3734
  }
3724
3735
  function deriveMachineKey() {
3725
3736
  try {
3726
- const crypto20 = __require("crypto");
3737
+ const crypto21 = __require("crypto");
3727
3738
  const material = [
3728
3739
  os5.hostname(),
3729
3740
  os5.userInfo().username,
@@ -3732,7 +3743,7 @@ function deriveMachineKey() {
3732
3743
  // Machine ID on Linux (stable across reboots)
3733
3744
  process.platform === "linux" ? readMachineId() : ""
3734
3745
  ].join("|");
3735
- return crypto20.createHash("sha256").update(material).digest();
3746
+ return crypto21.createHash("sha256").update(material).digest();
3736
3747
  } catch {
3737
3748
  return null;
3738
3749
  }
@@ -3746,9 +3757,9 @@ function readMachineId() {
3746
3757
  }
3747
3758
  }
3748
3759
  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);
3760
+ const crypto21 = __require("crypto");
3761
+ const iv = crypto21.randomBytes(12);
3762
+ const cipher = crypto21.createCipheriv("aes-256-gcm", machineKey, iv);
3752
3763
  let encrypted = cipher.update(plaintext, "utf-8", "base64");
3753
3764
  encrypted += cipher.final("base64");
3754
3765
  const authTag = cipher.getAuthTag().toString("base64");
@@ -3757,13 +3768,13 @@ function encryptWithMachineKey(plaintext, machineKey) {
3757
3768
  function decryptWithMachineKey(encrypted, machineKey) {
3758
3769
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3759
3770
  try {
3760
- const crypto20 = __require("crypto");
3771
+ const crypto21 = __require("crypto");
3761
3772
  const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3762
3773
  if (parts.length !== 3) return null;
3763
3774
  const [ivB64, tagB64, cipherB64] = parts;
3764
3775
  const iv = Buffer.from(ivB64, "base64");
3765
3776
  const authTag = Buffer.from(tagB64, "base64");
3766
- const decipher = crypto20.createDecipheriv("aes-256-gcm", machineKey, iv);
3777
+ const decipher = crypto21.createDecipheriv("aes-256-gcm", machineKey, iv);
3767
3778
  decipher.setAuthTag(authTag);
3768
3779
  let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3769
3780
  decrypted += decipher.final("utf-8");
@@ -4801,6 +4812,24 @@ var init_platform_procedures = __esm({
4801
4812
  priority: "p0",
4802
4813
  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
4814
  },
4815
+ {
4816
+ title: "Bug report status check \u2014 surface available fixes on boot",
4817
+ domain: "support",
4818
+ priority: "p1",
4819
+ 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."
4820
+ },
4821
+ {
4822
+ title: "Feature request triage \u2014 upstream feature vs local customization",
4823
+ domain: "support",
4824
+ priority: "p0",
4825
+ 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."
4826
+ },
4827
+ {
4828
+ title: "Feature request status check \u2014 surface shipped features on boot",
4829
+ domain: "support",
4830
+ priority: "p1",
4831
+ 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."
4832
+ },
4804
4833
  // --- Operations ---
4805
4834
  {
4806
4835
  title: "Managers must supervise deployed workers",
@@ -7321,10 +7350,10 @@ async function hybridSearch(queryText, agentId, options) {
7321
7350
  };
7322
7351
  try {
7323
7352
  const fs = await import("fs");
7324
- const path55 = await import("path");
7353
+ const path56 = await import("path");
7325
7354
  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 });
7355
+ const logPath = path56.join(os21.homedir(), ".exe-os", "search-quality.jsonl");
7356
+ fs.mkdirSync(path56.dirname(logPath), { recursive: true });
7328
7357
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
7329
7358
  } catch {
7330
7359
  }
@@ -8617,8 +8646,8 @@ __export(wiki_client_exports, {
8617
8646
  listDocuments: () => listDocuments,
8618
8647
  listWorkspaces: () => listWorkspaces
8619
8648
  });
8620
- async function wikiFetch(config2, path55, method = "GET", body) {
8621
- const url = `${config2.baseUrl}/api/v1${path55}`;
8649
+ async function wikiFetch(config2, path56, method = "GET", body) {
8650
+ const url = `${config2.baseUrl}/api/v1${path56}`;
8622
8651
  const headers = {
8623
8652
  Authorization: `Bearer ${config2.apiKey}`,
8624
8653
  "Content-Type": "application/json"
@@ -8651,7 +8680,7 @@ async function wikiFetch(config2, path55, method = "GET", body) {
8651
8680
  }
8652
8681
  }
8653
8682
  if (!response.ok) {
8654
- throw new Error(`Wiki API ${method} ${path55}: ${response.status} ${response.statusText}`);
8683
+ throw new Error(`Wiki API ${method} ${path56}: ${response.status} ${response.statusText}`);
8655
8684
  }
8656
8685
  return response.json();
8657
8686
  } finally {
@@ -9408,7 +9437,7 @@ function readQueue() {
9408
9437
  function writeQueue(queue) {
9409
9438
  ensureDir();
9410
9439
  const tmp = `${QUEUE_PATH}.tmp`;
9411
- writeFileSync9(tmp, JSON.stringify(queue, null, 2));
9440
+ writeFileSync9(tmp, JSON.stringify(queue, null, 2), { mode: 384 });
9412
9441
  renameSync4(tmp, QUEUE_PATH);
9413
9442
  }
9414
9443
  function queueIntercom(targetSession, reason) {
@@ -12786,12 +12815,14 @@ On EVERY new conversation, before doing anything else:
12786
12815
  1. **Memory scan**: Run recall_my_memory with broad queries \u2014 "project", "client", "pipeline", "campaign", "deal", "decision", "blocker". Summarize what you find.
12787
12816
  2. **Task scan**: Run list_tasks to see what's open, in progress, blocked, or needs review across all employees.
12788
12817
  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:
12818
+ 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.
12819
+ 5. **Present the brief**: Give the founder a concise status report:
12790
12820
  - What's active and progressing
12791
12821
  - What's blocked and needs attention
12792
12822
  - What decisions are pending
12823
+ - Available bug fixes (from step 4, if any)
12793
12824
  - What you recommend doing next
12794
- 5. Then ask: "What's the priority?"
12825
+ 6. Then ask: "What's the priority?"
12795
12826
 
12796
12827
  If this is your FIRST ever conversation (few or no prior memories):
12797
12828
  - Search more broadly: "product", "SEO", "meeting", "strategy", "revenue"
@@ -12811,6 +12842,8 @@ Never say "I have no memories" without first searching broadly. Your memory may
12811
12842
  - **get_identity** \u2014 read any agent's identity for coordination
12812
12843
  - **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
12844
  - **send_message** \u2014 direct intercom to employees
12845
+ - **create_bug_report** \u2014 file a bug when you encounter an Exe OS platform issue
12846
+ - **list_my_bug_reports** \u2014 check status of filed bugs (boot check: surface available fixes to founder)
12814
12847
  ${PLAN_MODE_COMPAT}
12815
12848
  ## Completion Workflow
12816
12849
 
@@ -13974,6 +14007,21 @@ var init_crdt_sync = __esm({
13974
14007
  }
13975
14008
  });
13976
14009
 
14010
+ // src/lib/pg-ssl.ts
14011
+ var pg_ssl_exports = {};
14012
+ __export(pg_ssl_exports, {
14013
+ pgSslConfig: () => pgSslConfig
14014
+ });
14015
+ function pgSslConfig() {
14016
+ if (process.env.EXE_DB_SSL_DISABLED === "true") return {};
14017
+ return { ssl: { rejectUnauthorized: process.env.EXE_DB_SSL_ALLOW_SELFSIGNED !== "true" } };
14018
+ }
14019
+ var init_pg_ssl = __esm({
14020
+ "src/lib/pg-ssl.ts"() {
14021
+ "use strict";
14022
+ }
14023
+ });
14024
+
13977
14025
  // src/lib/tmux-status.ts
13978
14026
  import { execSync as execSync13 } from "child_process";
13979
14027
  function inTmux() {
@@ -14208,7 +14256,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14208
14256
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14209
14257
  import { spawn as spawn4 } from "child_process";
14210
14258
  import { existsSync as existsSync41, openSync as openSync4, mkdirSync as mkdirSync23, closeSync as closeSync4, readFileSync as readFileSync36 } from "fs";
14211
- import path54 from "path";
14259
+ import path55 from "path";
14212
14260
  import os20 from "os";
14213
14261
  import { fileURLToPath as fileURLToPath7 } from "url";
14214
14262
 
@@ -18596,12 +18644,12 @@ function registerExportGraph(server2) {
18596
18644
  }
18597
18645
  const html = await exportGraphHTML(client);
18598
18646
  const fs = await import("fs");
18599
- const path55 = await import("path");
18647
+ const path56 = await import("path");
18600
18648
  const os21 = await import("os");
18601
- const outDir = path55.join(os21.homedir(), ".exe-os", "exports");
18649
+ const outDir = path56.join(os21.homedir(), ".exe-os", "exports");
18602
18650
  fs.mkdirSync(outDir, { recursive: true });
18603
18651
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18604
- const filePath = path55.join(outDir, `graph-${timestamp}.html`);
18652
+ const filePath = path56.join(outDir, `graph-${timestamp}.html`);
18605
18653
  fs.writeFileSync(filePath, html, "utf-8");
18606
18654
  return {
18607
18655
  content: [
@@ -19918,6 +19966,130 @@ import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
19918
19966
  import path30 from "path";
19919
19967
  import { homedir as homedir4 } from "os";
19920
19968
  import { z as z54 } from "zod";
19969
+
19970
+ // src/mcp/license-gate.ts
19971
+ init_license();
19972
+ var _cachedLicense = null;
19973
+ var _hasLicenseKey = false;
19974
+ async function initLicenseGate() {
19975
+ const key = loadLicense();
19976
+ _hasLicenseKey = key !== null && key.length > 0;
19977
+ if (_hasLicenseKey) {
19978
+ try {
19979
+ _cachedLicense = await checkLicense();
19980
+ } catch {
19981
+ _cachedLicense = null;
19982
+ }
19983
+ return { license: _cachedLicense, hasKey: true };
19984
+ }
19985
+ return { license: null, hasKey: false };
19986
+ }
19987
+ function getCachedLicenseGate() {
19988
+ return {
19989
+ license: _cachedLicense,
19990
+ hasKey: _hasLicenseKey
19991
+ };
19992
+ }
19993
+
19994
+ // src/mcp/tool-telemetry.ts
19995
+ var _toolCalls = /* @__PURE__ */ new Map();
19996
+ var _sessionStartedAt = Date.now();
19997
+ var _flushTimer2 = null;
19998
+ function recordCall(toolName, action, isError) {
19999
+ let record = _toolCalls.get(toolName);
20000
+ if (!record) {
20001
+ record = { count: 0, actions: /* @__PURE__ */ new Map(), lastCalledAt: 0, errors: 0 };
20002
+ _toolCalls.set(toolName, record);
20003
+ }
20004
+ record.count++;
20005
+ record.lastCalledAt = Date.now();
20006
+ if (isError) record.errors++;
20007
+ if (action) {
20008
+ record.actions.set(action, (record.actions.get(action) ?? 0) + 1);
20009
+ }
20010
+ }
20011
+ function wrapServerWithTelemetry(server2) {
20012
+ const originalRegisterTool = server2.registerTool.bind(server2);
20013
+ server2.registerTool = (name, config2, handler) => {
20014
+ const wrappedHandler = async (input, extra) => {
20015
+ const action = input.action;
20016
+ try {
20017
+ const result3 = await handler(input, extra);
20018
+ const isError = result3?.isError === true;
20019
+ recordCall(name, action, isError);
20020
+ return result3;
20021
+ } catch (err) {
20022
+ recordCall(name, action, true);
20023
+ throw err;
20024
+ }
20025
+ };
20026
+ return originalRegisterTool(name, config2, wrappedHandler);
20027
+ };
20028
+ return server2;
20029
+ }
20030
+ function getToolUsageStats() {
20031
+ let totalCalls = 0;
20032
+ let totalErrors = 0;
20033
+ const tools = {};
20034
+ for (const [name, record] of _toolCalls) {
20035
+ totalCalls += record.count;
20036
+ totalErrors += record.errors;
20037
+ const entry = {
20038
+ calls: record.count,
20039
+ errors: record.errors,
20040
+ lastCalledAt: new Date(record.lastCalledAt).toISOString()
20041
+ };
20042
+ if (record.actions.size > 0) {
20043
+ entry.actions = Object.fromEntries(record.actions);
20044
+ }
20045
+ tools[name] = entry;
20046
+ }
20047
+ return {
20048
+ sessionStartedAt: new Date(_sessionStartedAt).toISOString(),
20049
+ uptimeMs: Date.now() - _sessionStartedAt,
20050
+ totalCalls,
20051
+ totalErrors,
20052
+ tools
20053
+ };
20054
+ }
20055
+ var FLUSH_INTERVAL_MS = 5 * 60 * 1e3;
20056
+ var _lastFlushedAt = 0;
20057
+ async function flushToMemory() {
20058
+ const stats = getToolUsageStats();
20059
+ if (stats.totalCalls === 0) return;
20060
+ if (stats.totalCalls === _lastFlushedAt) return;
20061
+ try {
20062
+ const { getClient: getClient2, isInitialized: isInitialized2 } = await Promise.resolve().then(() => (init_database(), database_exports));
20063
+ if (!isInitialized2()) return;
20064
+ const client = getClient2();
20065
+ const toolSummary = Object.entries(stats.tools).sort((a, b) => b[1].calls - a[1].calls).map(([name, t]) => {
20066
+ const actionStr = t.actions ? ` (${Object.entries(t.actions).sort((a, b) => b[1] - a[1]).map(([a, c]) => `${a}:${c}`).join(", ")})` : "";
20067
+ return `${name}: ${t.calls} calls${t.errors > 0 ? ` (${t.errors} errors)` : ""}${actionStr}`;
20068
+ }).join("\n");
20069
+ const raw_text = `Tool usage since ${stats.sessionStartedAt} (${Math.round(stats.uptimeMs / 6e4)}min):
20070
+ Total: ${stats.totalCalls} calls, ${stats.totalErrors} errors
20071
+
20072
+ ${toolSummary}`;
20073
+ await client.execute({
20074
+ sql: `INSERT INTO memories (id, agent_id, raw_text, memory_type, project_name, importance, created_at, updated_at)
20075
+ VALUES (?, 'system', ?, 'telemetry', 'exe-os', 2, datetime('now'), datetime('now'))`,
20076
+ args: [
20077
+ `telemetry-tools-${Date.now()}`,
20078
+ raw_text
20079
+ ]
20080
+ });
20081
+ _lastFlushedAt = stats.totalCalls;
20082
+ } catch {
20083
+ }
20084
+ }
20085
+ function startToolTelemetryFlush() {
20086
+ if (_flushTimer2) return;
20087
+ _sessionStartedAt = Date.now();
20088
+ _flushTimer2 = setInterval(() => void flushToMemory(), FLUSH_INTERVAL_MS);
20089
+ _flushTimer2.unref();
20090
+ }
20091
+
20092
+ // src/mcp/tools/mcp-ping.ts
19921
20093
  var PID_PATH3 = path30.join(homedir4(), ".exe-os", "exed.pid");
19922
20094
  function isDaemonAlive2() {
19923
20095
  try {
@@ -19944,6 +20116,7 @@ function registerMcpPing(server2) {
19944
20116
  const daemon = isDaemonAlive2();
19945
20117
  const summary = summarizeMcpTransport();
19946
20118
  writeMcpTransportSummary();
20119
+ const gate = getCachedLicenseGate();
19947
20120
  const status2 = daemon.alive ? "ok" : "degraded";
19948
20121
  return {
19949
20122
  content: [
@@ -19952,8 +20125,14 @@ function registerMcpPing(server2) {
19952
20125
  text: JSON.stringify({
19953
20126
  status: status2,
19954
20127
  daemon,
20128
+ licenseGate: {
20129
+ hasKey: gate.hasKey,
20130
+ plan: gate.license?.plan ?? "none",
20131
+ valid: gate.license?.valid ?? false
20132
+ },
20133
+ toolUsage: getToolUsageStats(),
19955
20134
  mcpTransport: summary,
19956
- remediation: daemon.alive ? "MCP transport probe reached the server. If tools still fail, reconnect the MCP client." : "Daemon is offline. Restart exe-os/exed; do not bypass MCP or access the DB directly."
20135
+ remediation: daemon.alive ? gate.hasKey ? "MCP transport probe reached the server. If tools still fail, reconnect the MCP client." : "No license key found. Run: echo 'exe_sk_YOUR_KEY' > ~/.exe-os/license.key then reconnect MCP." : "Daemon is offline. Restart exe-os/exed; do not bypass MCP or access the DB directly."
19957
20136
  }, null, 2)
19958
20137
  }
19959
20138
  ]
@@ -20159,6 +20338,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
20159
20338
  function isMainModule(importMetaUrl) {
20160
20339
  if (process.argv[1] == null) return false;
20161
20340
  if (process.argv[1].includes("mcp/server")) return false;
20341
+ if (process.argv[1].includes("exe-daemon")) return false;
20162
20342
  try {
20163
20343
  const scriptPath = realpathSync(process.argv[1]);
20164
20344
  const modulePath = realpathSync(fileURLToPath4(importMetaUrl));
@@ -21575,7 +21755,8 @@ function loadPgClient() {
21575
21755
  return new Ctor();
21576
21756
  }
21577
21757
  const { Pool } = await import("pg");
21578
- const pool = new Pool({ connectionString: process.env.DATABASE_URL });
21758
+ const { pgSslConfig: pgSslConfig2 } = await Promise.resolve().then(() => (init_pg_ssl(), pg_ssl_exports));
21759
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL, ...pgSslConfig2() });
21579
21760
  return {
21580
21761
  async $queryRawUnsafe(query, ...values) {
21581
21762
  const result3 = await pool.query(query, values);
@@ -22104,6 +22285,17 @@ async function cloudSync(config2) {
22104
22285
  } catch (err) {
22105
22286
  logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
22106
22287
  }
22288
+ let codeContextResult = { pushed: 0, pulled: 0 };
22289
+ try {
22290
+ codeContextResult.pushed = await cloudPushCodeContext(config2);
22291
+ } catch (err) {
22292
+ logError(`[cloud-sync] Code context push: ${err instanceof Error ? err.message : String(err)}`);
22293
+ }
22294
+ try {
22295
+ codeContextResult.pulled = await cloudPullCodeContext(config2);
22296
+ } catch (err) {
22297
+ logError(`[cloud-sync] Code context pull: ${err instanceof Error ? err.message : String(err)}`);
22298
+ }
22107
22299
  return {
22108
22300
  pushed,
22109
22301
  pulled,
@@ -22113,7 +22305,8 @@ async function cloudSync(config2) {
22113
22305
  tasks: tasksResult,
22114
22306
  conversations: conversationsResult,
22115
22307
  documents: documentsResult,
22116
- roster: rosterResult
22308
+ roster: rosterResult,
22309
+ codeContext: codeContextResult
22117
22310
  };
22118
22311
  }
22119
22312
  var ROSTER_DELETIONS_PATH = path37.join(EXE_AI_DIR, "roster-deletions.json");
@@ -22741,6 +22934,99 @@ async function cloudPullDocuments(config2) {
22741
22934
  }
22742
22935
  return { pulled };
22743
22936
  }
22937
+ var CODE_CONTEXT_DIR = path37.join(EXE_AI_DIR, "code-context");
22938
+ async function cloudPushCodeContext(config2) {
22939
+ assertSecureEndpoint(config2.endpoint);
22940
+ if (!existsSync32(CODE_CONTEXT_DIR)) return 0;
22941
+ const files = readdirSync11(CODE_CONTEXT_DIR).filter(
22942
+ (f) => f.endsWith(".json") && !f.endsWith(".vectors.json") && !f.startsWith(".")
22943
+ );
22944
+ if (files.length === 0) return 0;
22945
+ const metaPath = path37.join(CODE_CONTEXT_DIR, ".sync-meta.json");
22946
+ let syncMeta = {};
22947
+ if (existsSync32(metaPath)) {
22948
+ try {
22949
+ syncMeta = JSON.parse(readFileSync25(metaPath, "utf-8"));
22950
+ } catch {
22951
+ }
22952
+ }
22953
+ let pushed = 0;
22954
+ for (const file of files) {
22955
+ const filePath = path37.join(CODE_CONTEXT_DIR, file);
22956
+ try {
22957
+ const stat = statSync7(filePath);
22958
+ const lastPushed = syncMeta[file] ?? 0;
22959
+ if (stat.mtimeMs <= lastPushed) continue;
22960
+ const content = readFileSync25(filePath, "utf-8");
22961
+ const header = content.substring(0, 300);
22962
+ if (header.includes("/tmp") || header.includes("/var/folders") || header.includes(".worktrees/")) continue;
22963
+ const compressed = compress(Buffer.from(content, "utf8"));
22964
+ const encrypted = encryptSyncBlob(compressed);
22965
+ const resp = await fetchWithRetry(`${config2.endpoint}/sync/push-code-context`, {
22966
+ method: "POST",
22967
+ headers: {
22968
+ Authorization: `Bearer ${config2.apiKey}`,
22969
+ "Content-Type": "application/json",
22970
+ "X-Device-Id": loadDeviceId()
22971
+ },
22972
+ body: JSON.stringify({ key: file, blob: encrypted })
22973
+ });
22974
+ if (resp.ok) {
22975
+ syncMeta[file] = stat.mtimeMs;
22976
+ pushed++;
22977
+ }
22978
+ } catch {
22979
+ }
22980
+ }
22981
+ if (pushed > 0) {
22982
+ try {
22983
+ writeFileSync18(metaPath, JSON.stringify(syncMeta));
22984
+ } catch {
22985
+ }
22986
+ }
22987
+ return pushed;
22988
+ }
22989
+ async function cloudPullCodeContext(config2) {
22990
+ assertSecureEndpoint(config2.endpoint);
22991
+ try {
22992
+ const resp = await fetchWithRetry(`${config2.endpoint}/sync/pull-code-context`, {
22993
+ method: "GET",
22994
+ headers: {
22995
+ Authorization: `Bearer ${config2.apiKey}`,
22996
+ "X-Device-Id": loadDeviceId()
22997
+ }
22998
+ });
22999
+ if (!resp.ok) return 0;
23000
+ const data = await resp.json();
23001
+ if (!data.indexes || data.indexes.length === 0) return 0;
23002
+ mkdirSync16(CODE_CONTEXT_DIR, { recursive: true });
23003
+ let pulled = 0;
23004
+ for (const { key, blob } of data.indexes) {
23005
+ try {
23006
+ if (key.endsWith(".vectors.json")) continue;
23007
+ const localPath = path37.join(CODE_CONTEXT_DIR, key);
23008
+ const compressed = decryptSyncBlob(blob);
23009
+ const content = decompress(compressed).toString("utf8");
23010
+ if (!existsSync32(localPath)) {
23011
+ writeFileSync18(localPath, content, "utf-8");
23012
+ pulled++;
23013
+ } else {
23014
+ const localContent = readFileSync25(localPath, "utf-8");
23015
+ if (localContent.length !== content.length) {
23016
+ writeFileSync18(localPath, content, "utf-8");
23017
+ pulled++;
23018
+ }
23019
+ }
23020
+ } catch {
23021
+ }
23022
+ }
23023
+ return pulled;
23024
+ } catch (err) {
23025
+ process.stderr.write(`[cloud-sync] Code context pull failed: ${err instanceof Error ? err.message : String(err)}
23026
+ `);
23027
+ return 0;
23028
+ }
23029
+ }
22744
23030
 
22745
23031
  // src/mcp/tools/cloud-sync.ts
22746
23032
  init_config();
@@ -23580,9 +23866,9 @@ var HostingerApiClient = class {
23580
23866
  }
23581
23867
  this.lastRequestTime = Date.now();
23582
23868
  }
23583
- async request(method, path55, body) {
23869
+ async request(method, path56, body) {
23584
23870
  await this.rateLimit();
23585
- const url = `${this.baseUrl}${path55}`;
23871
+ const url = `${this.baseUrl}${path56}`;
23586
23872
  const headers = {
23587
23873
  Authorization: `Bearer ${this.apiKey}`,
23588
23874
  "Content-Type": "application/json",
@@ -23655,8 +23941,8 @@ async function requestCloudflare(cfApiToken, zoneId, options) {
23655
23941
  }
23656
23942
  return envelope.result;
23657
23943
  }
23658
- function buildUrl(zoneId, path55 = "/dns_records", query) {
23659
- const normalizedPath = path55.startsWith("/") ? path55 : `/${path55}`;
23944
+ function buildUrl(zoneId, path56 = "/dns_records", query) {
23945
+ const normalizedPath = path56.startsWith("/") ? path56 : `/${path56}`;
23660
23946
  const url = new URL(
23661
23947
  `${CLOUDFLARE_API_BASE_URL}/zones/${zoneId}${normalizedPath}`
23662
23948
  );
@@ -26899,6 +27185,22 @@ async function getExeDbReadClient() {
26899
27185
  }
26900
27186
  if (!prismaPromise3) {
26901
27187
  prismaPromise3 = (async () => {
27188
+ const readonlyUrl = process.env.MCP_READONLY_DATABASE_URL;
27189
+ if (readonlyUrl) {
27190
+ const { Pool } = await import("pg");
27191
+ const { pgSslConfig: pgSslConfig2 } = await Promise.resolve().then(() => (init_pg_ssl(), pg_ssl_exports));
27192
+ const pool = new Pool({ connectionString: readonlyUrl, ...pgSslConfig2() });
27193
+ return {
27194
+ async $queryRawUnsafe(query, ...values) {
27195
+ const result3 = await pool.query(query, values);
27196
+ return result3.rows;
27197
+ },
27198
+ async $disconnect() {
27199
+ await pool.end();
27200
+ }
27201
+ };
27202
+ }
27203
+ console.warn("[exe-db-read] WARNING: MCP_READONLY_DATABASE_URL not set \u2014 falling back to admin role");
26902
27204
  const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
26903
27205
  if (explicitPath) {
26904
27206
  const mod2 = await import(pathToFileURL5(explicitPath).href);
@@ -27367,14 +27669,206 @@ Upstream status: ${upstreamStatus}`
27367
27669
  );
27368
27670
  }
27369
27671
 
27370
- // src/mcp/tools/support.ts
27672
+ // src/mcp/tools/create-feature-request.ts
27673
+ init_embedder();
27674
+ init_active_agent();
27675
+ init_config();
27676
+ init_license();
27677
+ init_store();
27371
27678
  import { z as z89 } from "zod";
27679
+ import crypto19 from "crypto";
27680
+ import { mkdir as mkdir7, writeFile as writeFile8 } from "fs/promises";
27681
+ import path50 from "path";
27682
+ var CATEGORY = z89.enum([
27683
+ "upstream_feature",
27684
+ "local_customization",
27685
+ "integration",
27686
+ "unclear"
27687
+ ]);
27688
+ var PRIORITY = z89.enum(["p0", "p1", "p2", "p3"]);
27689
+ function slugify3(input) {
27690
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "feature-request";
27691
+ }
27692
+ function section2(title, body) {
27693
+ return `## ${title}
27694
+
27695
+ ${body?.trim() || "Not provided"}`;
27696
+ }
27697
+ function buildMarkdown2(input) {
27698
+ return [
27699
+ `# Feature Request \u2014 ${input.title}`,
27700
+ "",
27701
+ `id: ${input.id}`,
27702
+ `category: ${input.category}`,
27703
+ `priority: ${input.priority}`,
27704
+ `filed_by: ${input.agentId} (${input.agentRole})`,
27705
+ `package_version: ${input.packageVersion}`,
27706
+ `project: ${input.projectName ?? "unknown"}`,
27707
+ `created_at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
27708
+ "",
27709
+ section2("Description", input.description),
27710
+ section2("Use Case", input.useCase),
27711
+ section2("Current Workaround", input.currentWorkaround),
27712
+ section2("Proposed Solution", input.proposedSolution),
27713
+ section2("Business Impact", input.businessImpact)
27714
+ ].join("\n");
27715
+ }
27716
+ async function maybeSendUpstream2(payload) {
27717
+ const config2 = await loadConfig();
27718
+ const routerUrl = process.env.API_ROUTER_URL?.replace(/\/+$/, "");
27719
+ const endpoint2 = config2.support?.featureRequestEndpoint || process.env.EXE_FEATURE_REQUEST_ENDPOINT || (routerUrl ? `${routerUrl}/v1/support/feature-requests` : "https://askexe.com/v1/support/feature-requests");
27720
+ const token = config2.support?.featureRequestToken || process.env.EXE_FEATURE_REQUEST_TOKEN;
27721
+ const licenseKey = loadLicense() || process.env.EXE_LICENSE_KEY || config2.cloud?.apiKey;
27722
+ const licenseToken = readCachedLicenseToken();
27723
+ if (!endpoint2) {
27724
+ return "not_configured";
27725
+ }
27726
+ try {
27727
+ const parsed = new URL(endpoint2);
27728
+ if (parsed.protocol !== "https:" && !["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)) {
27729
+ return "failed: insecure endpoint rejected";
27730
+ }
27731
+ const response = await fetch(parsed, {
27732
+ method: "POST",
27733
+ headers: {
27734
+ "content-type": "application/json",
27735
+ ...token ? { authorization: `Bearer ${token}` } : {},
27736
+ ...licenseKey ? { "x-exe-license-key": licenseKey } : {},
27737
+ ...licenseToken ? { "x-exe-license-token": licenseToken } : {}
27738
+ },
27739
+ body: JSON.stringify(payload),
27740
+ signal: AbortSignal.timeout(1e4)
27741
+ });
27742
+ if (!response.ok) return `failed: HTTP ${response.status}`;
27743
+ return "sent";
27744
+ } catch (err) {
27745
+ return `failed: ${err instanceof Error ? err.message : String(err)}`;
27746
+ }
27747
+ }
27748
+ function registerCreateFeatureRequest(server2) {
27749
+ server2.registerTool(
27750
+ "create_feature_request",
27751
+ {
27752
+ title: "Create Feature Request",
27753
+ 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.",
27754
+ inputSchema: {
27755
+ title: z89.string().min(3).describe("Short descriptive title"),
27756
+ category: CATEGORY.describe(
27757
+ "upstream_feature = platform capability change; local_customization = configurable in customer layers; integration = third-party connector; unclear = needs product triage"
27758
+ ),
27759
+ priority: PRIORITY.default("p2").describe("p0 critical \u2192 p3 low"),
27760
+ description: z89.string().min(10).describe("What capability is needed and why"),
27761
+ use_case: z89.string().optional().describe("Concrete scenario where this feature would be used"),
27762
+ current_workaround: z89.string().optional().describe("How the need is currently addressed, if at all"),
27763
+ proposed_solution: z89.string().optional().describe("Suggested implementation approach"),
27764
+ business_impact: z89.string().optional().describe("Impact on business/workflow if this ships"),
27765
+ package_version: z89.string().optional().describe("Installed @askexenow/exe-os version"),
27766
+ project_name: z89.string().optional().describe("Project/customer context"),
27767
+ send_upstream: z89.boolean().default(true).describe("Attempt to POST to configured AskExe support endpoint")
27768
+ }
27769
+ },
27770
+ async ({
27771
+ title,
27772
+ category,
27773
+ priority,
27774
+ description,
27775
+ use_case,
27776
+ current_workaround,
27777
+ proposed_solution,
27778
+ business_impact,
27779
+ package_version,
27780
+ project_name,
27781
+ send_upstream
27782
+ }) => {
27783
+ const { agentId, agentRole } = getActiveAgent();
27784
+ const id = crypto19.randomUUID();
27785
+ const version = package_version ?? "unknown";
27786
+ const markdown = buildMarkdown2({
27787
+ id,
27788
+ title,
27789
+ category,
27790
+ priority,
27791
+ agentId,
27792
+ agentRole,
27793
+ packageVersion: version,
27794
+ description,
27795
+ useCase: use_case,
27796
+ currentWorkaround: current_workaround,
27797
+ proposedSolution: proposed_solution,
27798
+ businessImpact: business_impact,
27799
+ projectName: project_name
27800
+ });
27801
+ const outDir = path50.join(EXE_AI_DIR, "feature-requests");
27802
+ await mkdir7(outDir, { recursive: true });
27803
+ const reportPath = path50.join(outDir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}-${slugify3(title)}-${id.slice(0, 8)}.md`);
27804
+ await writeFile8(reportPath, markdown, "utf-8");
27805
+ let vector = null;
27806
+ try {
27807
+ vector = await embed(markdown);
27808
+ } catch {
27809
+ vector = null;
27810
+ }
27811
+ await writeMemory({
27812
+ id,
27813
+ agent_id: agentId,
27814
+ agent_role: agentRole,
27815
+ session_id: process.env.SESSION_ID ?? "manual",
27816
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
27817
+ tool_name: "create_feature_request",
27818
+ project_name: project_name ?? "support",
27819
+ has_error: false,
27820
+ raw_text: markdown,
27821
+ vector,
27822
+ source_path: reportPath,
27823
+ source_type: "feature_request",
27824
+ memory_type: "feature_request",
27825
+ tier: 1,
27826
+ importance: priority === "p0" ? 10 : priority === "p1" ? 9 : priority === "p2" ? 7 : 5,
27827
+ intent: "request",
27828
+ domain: "support",
27829
+ file_paths: null
27830
+ });
27831
+ await flushBatch();
27832
+ const upstreamStatus = send_upstream ? await maybeSendUpstream2({
27833
+ id,
27834
+ title,
27835
+ category,
27836
+ priority,
27837
+ description,
27838
+ use_case,
27839
+ current_workaround,
27840
+ proposed_solution,
27841
+ business_impact,
27842
+ package_version: version,
27843
+ project_name,
27844
+ agent_id: agentId,
27845
+ agent_role: agentRole
27846
+ }) : "skipped";
27847
+ return {
27848
+ content: [
27849
+ {
27850
+ type: "text",
27851
+ text: `Feature request created.
27852
+ ID: ${id}
27853
+ Category: ${category}
27854
+ Local report: ${reportPath}
27855
+ Memory stored: ${id}
27856
+ Upstream status: ${upstreamStatus}`
27857
+ }
27858
+ ]
27859
+ };
27860
+ }
27861
+ );
27862
+ }
27863
+
27864
+ // src/mcp/tools/support.ts
27865
+ import { z as z90 } from "zod";
27372
27866
 
27373
27867
  // src/bin/exe-support.ts
27374
27868
  init_config();
27375
27869
  init_license();
27376
27870
  import { mkdirSync as mkdirSync21, readFileSync as readFileSync32, unlinkSync as unlinkSync12, writeFileSync as writeFileSync23 } from "fs";
27377
- import path50 from "path";
27871
+ import path51 from "path";
27378
27872
  import { randomUUID as randomUUID9 } from "crypto";
27379
27873
  var DEFAULT_BUG_ENDPOINT = "https://askexe.com/v1/support/bug-reports";
27380
27874
  var DEFAULT_ADMIN_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
@@ -27495,8 +27989,8 @@ async function resolveEndpoints() {
27495
27989
  return { bugEndpoint, healthEndpoint, adminEndpoint };
27496
27990
  }
27497
27991
  function checkLocalWrite() {
27498
- const dir = path50.join(EXE_AI_DIR, "bug-reports");
27499
- const testPath = path50.join(dir, ".support-write-test");
27992
+ const dir = path51.join(EXE_AI_DIR, "bug-reports");
27993
+ const testPath = path51.join(dir, ".support-write-test");
27500
27994
  try {
27501
27995
  mkdirSync21(dir, { recursive: true, mode: 448 });
27502
27996
  writeFileSync23(testPath, "ok\n", { mode: 384 });
@@ -27512,10 +28006,10 @@ function checkLocalWrite() {
27512
28006
  }
27513
28007
  }
27514
28008
  function writeLocalTestReport(id, project, version) {
27515
- const dir = path50.join(EXE_AI_DIR, "bug-reports");
28009
+ const dir = path51.join(EXE_AI_DIR, "bug-reports");
27516
28010
  mkdirSync21(dir, { recursive: true, mode: 448 });
27517
28011
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
27518
- const filePath = path50.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
28012
+ const filePath = path51.join(dir, `${date}-support-intake-test-${id.slice(0, 8)}.md`);
27519
28013
  writeFileSync23(filePath, `# TEST \u2014 ${project} support intake
27520
28014
 
27521
28015
  Report ID: ${id}
@@ -27557,15 +28051,15 @@ async function maybeCloseAdmin(id, adminEndpoint, version) {
27557
28051
  }
27558
28052
  }
27559
28053
  function readPackageVersion2() {
27560
- let dir = path50.dirname(new URL(import.meta.url).pathname);
28054
+ let dir = path51.dirname(new URL(import.meta.url).pathname);
27561
28055
  for (let i = 0; i < 6; i++) {
27562
- const pkg = path50.join(dir, "package.json");
28056
+ const pkg = path51.join(dir, "package.json");
27563
28057
  try {
27564
28058
  const parsed = JSON.parse(readFileSync32(pkg, "utf8"));
27565
28059
  if (parsed.version) return parsed.version;
27566
28060
  } catch {
27567
28061
  }
27568
- dir = path50.dirname(dir);
28062
+ dir = path51.dirname(dir);
27569
28063
  }
27570
28064
  return "unknown";
27571
28065
  }
@@ -27632,7 +28126,7 @@ function registerSupportTools(server2) {
27632
28126
  title: "Support Test",
27633
28127
  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
28128
  inputSchema: {
27635
- project: z89.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
28129
+ project: z90.string().default("support-smoke").describe("Customer/project name, e.g. hygo")
27636
28130
  }
27637
28131
  },
27638
28132
  async ({ project }) => {
@@ -27650,8 +28144,8 @@ function registerSupportTools(server2) {
27650
28144
  title: "My Bug Reports",
27651
28145
  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
28146
  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")
28147
+ status: z90.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("all").describe("Filter by status. Default: all"),
28148
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
27655
28149
  }
27656
28150
  },
27657
28151
  async ({ status: status2, limit }) => {
@@ -27667,7 +28161,10 @@ function registerSupportTools(server2) {
27667
28161
  endpoint2.searchParams.set("status", status2);
27668
28162
  endpoint2.searchParams.set("limit", String(limit));
27669
28163
  const headers = { "content-type": "application/json" };
27670
- if (licenseKey) headers["x-exe-license-key"] = licenseKey;
28164
+ if (licenseKey) {
28165
+ headers["authorization"] = `Bearer ${licenseKey}`;
28166
+ headers["x-exe-license-key"] = licenseKey;
28167
+ }
27671
28168
  if (licenseToken) headers["x-exe-license-token"] = licenseToken;
27672
28169
  try {
27673
28170
  const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
@@ -27707,12 +28204,79 @@ function registerSupportTools(server2) {
27707
28204
  }
27708
28205
  }
27709
28206
  );
28207
+ server2.registerTool(
28208
+ "list_my_feature_requests",
28209
+ {
28210
+ title: "My Feature Requests",
28211
+ 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.",
28212
+ inputSchema: {
28213
+ status: z90.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("all").describe("Filter by status. Default: all"),
28214
+ limit: z90.number().min(1).max(50).default(25).describe("Max results")
28215
+ }
28216
+ },
28217
+ async ({ status: status2, limit }) => {
28218
+ const licenseKey = loadLicense();
28219
+ const licenseToken = readCachedLicenseToken();
28220
+ if (!licenseKey && !licenseToken) {
28221
+ return {
28222
+ content: [{ type: "text", text: "No license key found. Run `exe-os setup` or `exe-os cloud setup` first." }],
28223
+ isError: true
28224
+ };
28225
+ }
28226
+ const endpoint2 = new URL("https://askexe.com/v1/support/my-feature-requests");
28227
+ endpoint2.searchParams.set("status", status2);
28228
+ endpoint2.searchParams.set("limit", String(limit));
28229
+ const headers = { "content-type": "application/json" };
28230
+ if (licenseKey) {
28231
+ headers["authorization"] = `Bearer ${licenseKey}`;
28232
+ headers["x-exe-license-key"] = licenseKey;
28233
+ }
28234
+ if (licenseToken) headers["x-exe-license-token"] = licenseToken;
28235
+ try {
28236
+ const res = await fetch(endpoint2.toString(), { method: "GET", headers, signal: AbortSignal.timeout(15e3) });
28237
+ if (!res.ok) {
28238
+ const body = await res.text().catch(() => "");
28239
+ return {
28240
+ content: [{ type: "text", text: `Failed to fetch feature requests: HTTP ${res.status}${body ? ` \u2014 ${body}` : ""}` }],
28241
+ isError: true
28242
+ };
28243
+ }
28244
+ const data = await res.json();
28245
+ if (data.count === 0) {
28246
+ return {
28247
+ content: [{ type: "text", text: `No feature requests found${status2 !== "all" ? ` with status '${status2}'` : ""}.` }],
28248
+ structuredContent: { items: [], count: 0 }
28249
+ };
28250
+ }
28251
+ const lines = [`Feature requests (${data.count}):`, ""];
28252
+ for (const r of data.items) {
28253
+ const priIcon = r.priority === "p0" ? "\u{1F534}" : r.priority === "p1" ? "\u{1F534}" : r.priority === "p2" ? "\u{1F7E0}" : "\u{1F7E2}";
28254
+ 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}";
28255
+ lines.push(`${priIcon} ${r.priority.toUpperCase()} ${statusIcon} ${r.status} \u2014 ${r.title}`);
28256
+ lines.push(` ID: ${r.id} | Filed: ${r.created_at?.slice(0, 10) ?? "?"}`);
28257
+ if (r.shipped_version) lines.push(` \u{1F680} Shipped in: ${r.shipped_version} \u2014 run \`exe-os update\` to get this feature`);
28258
+ if (r.target_version) lines.push(` \u{1F3AF} Target: ${r.target_version}`);
28259
+ if (r.response_notes) lines.push(` \u{1F4DD} AskExe: ${r.response_notes}`);
28260
+ lines.push("");
28261
+ }
28262
+ return {
28263
+ content: [{ type: "text", text: lines.join("\n") }],
28264
+ structuredContent: data
28265
+ };
28266
+ } catch (err) {
28267
+ return {
28268
+ content: [{ type: "text", text: `Error fetching feature requests: ${err instanceof Error ? err.message : String(err)}` }],
28269
+ isError: true
28270
+ };
28271
+ }
28272
+ }
28273
+ );
27710
28274
  }
27711
28275
 
27712
28276
  // src/mcp/tools/cli-parity.ts
27713
28277
  import { execFile as execFile2 } from "child_process";
27714
28278
  import { promisify as promisify2 } from "util";
27715
- import { z as z90 } from "zod";
28279
+ import { z as z91 } from "zod";
27716
28280
 
27717
28281
  // src/bin/exe-status.ts
27718
28282
  init_employees();
@@ -27773,22 +28337,22 @@ if (isMainModule(import.meta.url)) {
27773
28337
 
27774
28338
  // src/bin/exe-healthcheck.ts
27775
28339
  import { existsSync as existsSync39, readFileSync as readFileSync33, readdirSync as readdirSync14 } from "fs";
27776
- import path51 from "path";
28340
+ import path52 from "path";
27777
28341
  import { execSync as execSync14 } from "child_process";
27778
28342
  import { fileURLToPath as fileURLToPath6 } from "url";
27779
28343
  init_config();
27780
28344
  function findPackageRoot2() {
27781
- let dir = path51.dirname(fileURLToPath6(import.meta.url));
27782
- const { root } = path51.parse(dir);
28345
+ let dir = path52.dirname(fileURLToPath6(import.meta.url));
28346
+ const { root } = path52.parse(dir);
27783
28347
  while (dir !== root) {
27784
- if (existsSync39(path51.join(dir, "package.json"))) return dir;
27785
- dir = path51.dirname(dir);
28348
+ if (existsSync39(path52.join(dir, "package.json"))) return dir;
28349
+ dir = path52.dirname(dir);
27786
28350
  }
27787
28351
  throw new Error("Cannot find package root");
27788
28352
  }
27789
28353
  function checkBuildIntegrity(pkgRoot) {
27790
28354
  const results = [];
27791
- const tsupConfig = path51.join(pkgRoot, "tsup.config.ts");
28355
+ const tsupConfig = path52.join(pkgRoot, "tsup.config.ts");
27792
28356
  if (!existsSync39(tsupConfig)) {
27793
28357
  return [{ name: "build/tsup-config", pass: false, detail: "tsup.config.ts not found" }];
27794
28358
  }
@@ -27798,7 +28362,7 @@ function checkBuildIntegrity(pkgRoot) {
27798
28362
  let total = 0;
27799
28363
  for (const match of entryMatches) {
27800
28364
  const outputKey = match[1];
27801
- const expectedPath = path51.join(pkgRoot, "dist", `${outputKey}.js`);
28365
+ const expectedPath = path52.join(pkgRoot, "dist", `${outputKey}.js`);
27802
28366
  total++;
27803
28367
  if (!existsSync39(expectedPath)) {
27804
28368
  missing.push(`dist/${outputKey}.js`);
@@ -27822,7 +28386,7 @@ function checkBuildIntegrity(pkgRoot) {
27822
28386
  }
27823
28387
  function checkEmbedPipeline(pkgRoot) {
27824
28388
  const results = [];
27825
- const daemonPath = path51.join(pkgRoot, "dist", "lib", "exe-daemon.js");
28389
+ const daemonPath = path52.join(pkgRoot, "dist", "lib", "exe-daemon.js");
27826
28390
  if (!existsSync39(daemonPath)) {
27827
28391
  results.push({
27828
28392
  name: "exed/daemon-exists",
@@ -27834,17 +28398,17 @@ function checkEmbedPipeline(pkgRoot) {
27834
28398
  results.push({ name: "exed/daemon-exists", pass: true, detail: "dist/lib/exe-daemon.js exists" });
27835
28399
  const entryDirs = ["dist/hooks", "dist/bin", "dist/mcp"];
27836
28400
  for (const dir of entryDirs) {
27837
- const fullDir = path51.join(pkgRoot, dir);
28401
+ const fullDir = path52.join(pkgRoot, dir);
27838
28402
  if (!existsSync39(fullDir)) continue;
27839
28403
  let walkDir = fullDir;
27840
- const { root } = path51.parse(walkDir);
28404
+ const { root } = path52.parse(walkDir);
27841
28405
  let foundRoot = null;
27842
28406
  while (walkDir !== root) {
27843
- if (existsSync39(path51.join(walkDir, "package.json"))) {
28407
+ if (existsSync39(path52.join(walkDir, "package.json"))) {
27844
28408
  foundRoot = walkDir;
27845
28409
  break;
27846
28410
  }
27847
- walkDir = path51.dirname(walkDir);
28411
+ walkDir = path52.dirname(walkDir);
27848
28412
  }
27849
28413
  if (!foundRoot) {
27850
28414
  results.push({
@@ -27854,7 +28418,7 @@ function checkEmbedPipeline(pkgRoot) {
27854
28418
  });
27855
28419
  continue;
27856
28420
  }
27857
- const resolvedDaemon = path51.join(foundRoot, "dist", "lib", "exe-daemon.js");
28421
+ const resolvedDaemon = path52.join(foundRoot, "dist", "lib", "exe-daemon.js");
27858
28422
  const reachable = existsSync39(resolvedDaemon);
27859
28423
  results.push({
27860
28424
  name: `exed/reachable-from-${dir}`,
@@ -27866,7 +28430,7 @@ function checkEmbedPipeline(pkgRoot) {
27866
28430
  }
27867
28431
  function checkTaskSystem(pkgRoot) {
27868
28432
  const results = [];
27869
- const scannerPath = path51.join(pkgRoot, "dist", "bin", "scan-tasks.js");
28433
+ const scannerPath = path52.join(pkgRoot, "dist", "bin", "scan-tasks.js");
27870
28434
  if (!existsSync39(scannerPath)) {
27871
28435
  results.push({ name: "tasks/scanner", pass: false, detail: "scan-tasks.js not found" });
27872
28436
  return results;
@@ -27889,7 +28453,7 @@ function checkTaskSystem(pkgRoot) {
27889
28453
  }
27890
28454
  function checkWorkerSpawning(pkgRoot) {
27891
28455
  const results = [];
27892
- const workerPath = path51.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
28456
+ const workerPath = path52.join(pkgRoot, "dist", "hooks", "ingest-worker.js");
27893
28457
  if (!existsSync39(workerPath)) {
27894
28458
  results.push({ name: "workers/ingest-worker", pass: false, detail: "ingest-worker.js not found" });
27895
28459
  return results;
@@ -27904,14 +28468,14 @@ function checkWorkerSpawning(pkgRoot) {
27904
28468
  detail: `Parse error: ${err instanceof Error ? err.message.slice(0, 200) : String(err)}`
27905
28469
  });
27906
28470
  }
27907
- const hooksDir = path51.join(pkgRoot, "dist", "hooks");
28471
+ const hooksDir = path52.join(pkgRoot, "dist", "hooks");
27908
28472
  if (existsSync39(hooksDir)) {
27909
28473
  const hookFiles = readdirSync14(hooksDir).filter((f) => f.endsWith(".js") && !f.endsWith(".js.map"));
27910
28474
  let hooksPassed = 0;
27911
28475
  const hooksFailed = [];
27912
28476
  for (const hook of hookFiles) {
27913
28477
  try {
27914
- execSync14(`node --check "${path51.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
28478
+ execSync14(`node --check "${path52.join(hooksDir, hook)}" 2>&1`, { timeout: 1e4, encoding: "utf-8" });
27915
28479
  hooksPassed++;
27916
28480
  } catch {
27917
28481
  hooksFailed.push(hook);
@@ -27935,8 +28499,8 @@ function checkWorkerSpawning(pkgRoot) {
27935
28499
  }
27936
28500
  function checkMcpTransport() {
27937
28501
  const results = [];
27938
- const pidPath = path51.join(EXE_AI_DIR, "exed.pid");
27939
- const tokenPath = path51.join(EXE_AI_DIR, "exed.token");
28502
+ const pidPath = path52.join(EXE_AI_DIR, "exed.pid");
28503
+ const tokenPath = path52.join(EXE_AI_DIR, "exed.token");
27940
28504
  let daemonAlive = false;
27941
28505
  if (existsSync39(pidPath)) {
27942
28506
  try {
@@ -27983,7 +28547,7 @@ function checkMcpTransport() {
27983
28547
  results.push({
27984
28548
  name: "mcp/monitor-summary",
27985
28549
  pass: true,
27986
- detail: `Privacy-safe local monitor summary: ${path51.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
28550
+ detail: `Privacy-safe local monitor summary: ${path52.join(EXE_AI_DIR, "monitor", "mcp-transport-summary.json")}`
27987
28551
  });
27988
28552
  return results;
27989
28553
  }
@@ -28041,7 +28605,7 @@ function checkClaudeCodeInstall() {
28041
28605
  detail: "Failed to check claude binary path"
28042
28606
  });
28043
28607
  }
28044
- const versionsDir = path51.join(
28608
+ const versionsDir = path52.join(
28045
28609
  process.env.HOME ?? process.env.USERPROFILE ?? "",
28046
28610
  ".local",
28047
28611
  "share",
@@ -28131,7 +28695,7 @@ function runHealthCheck() {
28131
28695
  const failed = results.filter((r) => !r.pass).length;
28132
28696
  return { results, passed, failed };
28133
28697
  }
28134
- if (isMainModule(import.meta.url)) {
28698
+ if (isMainModule(import.meta.url) && (process.argv[1] ?? "").includes("exe-healthcheck")) {
28135
28699
  const { results, passed, failed } = runHealthCheck();
28136
28700
  console.log("\n exe-os Health Check\n");
28137
28701
  for (const r of results) {
@@ -28148,9 +28712,9 @@ if (isMainModule(import.meta.url)) {
28148
28712
  // src/lib/update-check.ts
28149
28713
  import { execSync as execSync15 } from "child_process";
28150
28714
  import { readFileSync as readFileSync34 } from "fs";
28151
- import path52 from "path";
28715
+ import path53 from "path";
28152
28716
  function getLocalVersion(packageRoot) {
28153
- const pkgPath = path52.join(packageRoot, "package.json");
28717
+ const pkgPath = path53.join(packageRoot, "package.json");
28154
28718
  const pkg = JSON.parse(readFileSync34(pkgPath, "utf-8"));
28155
28719
  return pkg.version;
28156
28720
  }
@@ -28218,12 +28782,12 @@ function registerCliParityTools(server2) {
28218
28782
  title: "Doctor",
28219
28783
  description: "Run exe-os doctor audit. Defaults to read-only diagnostics; optional dry-run/fix flags mirror CLI.",
28220
28784
  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)
28785
+ agent: z91.string().optional(),
28786
+ project: z91.string().optional(),
28787
+ verbose: z91.boolean().default(false),
28788
+ conflicts: z91.boolean().default(false),
28789
+ dry_run: z91.boolean().default(false),
28790
+ fix: z91.boolean().default(false)
28227
28791
  }
28228
28792
  }, async ({ agent, project, verbose, conflicts, dry_run, fix }) => {
28229
28793
  const args = [];
@@ -28240,9 +28804,9 @@ function registerCliParityTools(server2) {
28240
28804
  title: "Rename Employee",
28241
28805
  description: "Rename an employee using the same path as `exe-os rename <old> <new>`. Use for customer roster/identity renames.",
28242
28806
  inputSchema: {
28243
- old_name: z90.string().min(1),
28244
- new_name: z90.string().min(1),
28245
- dry_run: z90.boolean().default(false)
28807
+ old_name: z91.string().min(1),
28808
+ new_name: z91.string().min(1),
28809
+ dry_run: z91.boolean().default(false)
28246
28810
  }
28247
28811
  }, async ({ old_name, new_name, dry_run }) => {
28248
28812
  if (dry_run) {
@@ -28255,7 +28819,7 @@ function registerCliParityTools(server2) {
28255
28819
  server2.registerTool("status_brief", {
28256
28820
  title: "Status Brief",
28257
28821
  description: "Return current employee/tmux status. Mirrors `exe-status` and supports optional deep view for one employee.",
28258
- inputSchema: { employee: z90.string().optional() }
28822
+ inputSchema: { employee: z91.string().optional() }
28259
28823
  }, async ({ employee }) => {
28260
28824
  const text3 = await status(employee);
28261
28825
  return result2(text3, { ok: true, employee: employee ?? null });
@@ -28263,7 +28827,7 @@ function registerCliParityTools(server2) {
28263
28827
  server2.registerTool("pending_work_summary", {
28264
28828
  title: "Pending Work Summary",
28265
28829
  description: "Return pending reviews, messages, and notifications using the same summaries as the CLI tools.",
28266
- inputSchema: { agent: z90.string().optional() }
28830
+ inputSchema: { agent: z91.string().optional() }
28267
28831
  }, async ({ agent }) => {
28268
28832
  const parts = await Promise.all([
28269
28833
  runCommand("exe-pending-reviews", [], 3e4),
@@ -28284,7 +28848,7 @@ function registerCliParityTools(server2) {
28284
28848
  server2.registerTool("key_rotation_preflight", {
28285
28849
  title: "Key Rotation Preflight",
28286
28850
  description: "Dry-run key rotation/update preflight. No destructive changes.",
28287
- inputSchema: { mode: z90.enum(["rotate", "update"]).default("rotate") }
28851
+ inputSchema: { mode: z91.enum(["rotate", "update"]).default("rotate") }
28288
28852
  }, async ({ mode }) => {
28289
28853
  const out = await runCommand("exe-os", ["key", mode, "--dry-run"], 6e4);
28290
28854
  return result2(out.text, { ok: out.ok, command: out.command, mode }, !out.ok);
@@ -28303,11 +28867,11 @@ function registerCliParityTools(server2) {
28303
28867
  title: "Stack Update Check",
28304
28868
  description: "Plan/check a customer stack update without applying Docker changes. Mirrors `exe-os stack-update --check`.",
28305
28869
  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")
28870
+ target: z91.string().optional(),
28871
+ manifest: z91.string().optional(),
28872
+ compose_file: z91.string().optional(),
28873
+ env_file: z91.string().optional(),
28874
+ deployment_persona: z91.enum(["customer", "askexe-control-plane"]).default("customer")
28311
28875
  }
28312
28876
  }, async ({ target, manifest, compose_file, env_file, deployment_persona }) => {
28313
28877
  const args = ["stack-update", "--check", "--deployment-persona", deployment_persona];
@@ -28331,7 +28895,7 @@ function registerCliParityTools(server2) {
28331
28895
  // src/mcp/tools/get-session-events.ts
28332
28896
  init_active_agent();
28333
28897
  init_fast_db_init();
28334
- import { z as z91 } from "zod";
28898
+ import { z as z92 } from "zod";
28335
28899
 
28336
28900
  // src/lib/session-events.ts
28337
28901
  init_task_scope();
@@ -28418,7 +28982,7 @@ async function listRecentSessionEvents(client, options) {
28418
28982
  }
28419
28983
 
28420
28984
  // src/mcp/tools/get-session-events.ts
28421
- var EVENT_TYPE = z91.enum([
28985
+ var EVENT_TYPE = z92.enum([
28422
28986
  "user_prompt",
28423
28987
  "assistant_response",
28424
28988
  "tool_call",
@@ -28448,11 +29012,11 @@ function registerGetSessionEvents(server2) {
28448
29012
  title: "Get Session Events",
28449
29013
  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
29014
  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."),
29015
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
29016
+ session_id: z92.string().optional().describe("Optional exact runtime session id."),
28453
29017
  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.")
29018
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects."),
29019
+ limit: z92.number().int().min(1).max(100).default(20).describe("Number of events to return.")
28456
29020
  }
28457
29021
  },
28458
29022
  async ({ agent_id, session_id, event_type, project_name, limit }) => {
@@ -28488,8 +29052,8 @@ function registerGetLastAssistantResponse(server2) {
28488
29052
  title: "Get Last Assistant Response",
28489
29053
  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
29054
  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.")
29055
+ agent_id: z92.string().optional().describe("Agent to inspect. Defaults to active agent. COO/CTO may inspect others."),
29056
+ project_name: z92.string().optional().describe("Optional project filter. Pass 'all' for all projects.")
28493
29057
  }
28494
29058
  },
28495
29059
  async ({ agent_id, project_name }) => {
@@ -28519,32 +29083,106 @@ function registerGetLastAssistantResponse(server2) {
28519
29083
  }
28520
29084
 
28521
29085
  // src/mcp/tools/code-context.ts
28522
- import { z as z92 } from "zod";
29086
+ import { z as z93 } from "zod";
28523
29087
 
28524
29088
  // src/lib/code-context-index.ts
28525
29089
  init_config();
28526
- import crypto19 from "crypto";
28527
- import path53 from "path";
29090
+ import crypto20 from "crypto";
29091
+ import path54 from "path";
28528
29092
  import { existsSync as existsSync40, mkdirSync as mkdirSync22, readFileSync as readFileSync35, readdirSync as readdirSync15, statSync as statSync9, writeFileSync as writeFileSync24 } from "fs";
28529
29093
  import { spawnSync } from "child_process";
29094
+ init_exe_daemon_client();
29095
+ var VECTOR_STORE_VERSION = 1;
29096
+ var EMBED_BATCH_SIZE = 64;
29097
+ function vectorStorePath(projectRoot) {
29098
+ const rootHash = hashText(projectRoot).slice(0, 16);
29099
+ return path54.join(indexDir(), `${rootHash}.vectors.json`);
29100
+ }
29101
+ function loadVectorStore(projectRoot) {
29102
+ const file = vectorStorePath(projectRoot);
29103
+ if (!existsSync40(file)) return null;
29104
+ try {
29105
+ const parsed = JSON.parse(readFileSync35(file, "utf8"));
29106
+ if (parsed.version !== VECTOR_STORE_VERSION) return null;
29107
+ return parsed;
29108
+ } catch {
29109
+ return null;
29110
+ }
29111
+ }
29112
+ function saveVectorStore(projectRoot, store) {
29113
+ writeFileSync24(vectorStorePath(projectRoot), JSON.stringify(store));
29114
+ }
29115
+ function cosineSimilarity3(a, b) {
29116
+ let dot = 0, normA = 0, normB = 0;
29117
+ for (let i = 0; i < a.length; i++) {
29118
+ dot += a[i] * b[i];
29119
+ normA += a[i] * a[i];
29120
+ normB += b[i] * b[i];
29121
+ }
29122
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
29123
+ return denom === 0 ? 0 : dot / denom;
29124
+ }
29125
+ async function embedSymbols(index) {
29126
+ const rootHash = hashText(index.projectRoot).slice(0, 16);
29127
+ const existing = loadVectorStore(index.projectRoot);
29128
+ const store = {
29129
+ version: VECTOR_STORE_VERSION,
29130
+ projectRootHash: rootHash,
29131
+ vectors: {}
29132
+ };
29133
+ const allSymbols = [];
29134
+ for (const file of Object.values(index.files)) {
29135
+ for (const symbol of file.symbols) {
29136
+ if (existing?.vectors[symbol.id]) {
29137
+ store.vectors[symbol.id] = existing.vectors[symbol.id];
29138
+ } else {
29139
+ allSymbols.push({ id: symbol.id, text: symbol.summary || `${symbol.kind} ${symbol.name} in ${symbol.filePath}` });
29140
+ }
29141
+ }
29142
+ }
29143
+ if (allSymbols.length === 0) {
29144
+ saveVectorStore(index.projectRoot, store);
29145
+ return store;
29146
+ }
29147
+ const connected = await connectEmbedDaemon().catch(() => false);
29148
+ if (!connected) {
29149
+ saveVectorStore(index.projectRoot, store);
29150
+ return store;
29151
+ }
29152
+ for (let i = 0; i < allSymbols.length; i += EMBED_BATCH_SIZE) {
29153
+ const batch = allSymbols.slice(i, i + EMBED_BATCH_SIZE);
29154
+ const texts = batch.map((s) => s.text);
29155
+ try {
29156
+ const vectors = await embedBatchViaClient(texts, "low");
29157
+ if (vectors && vectors.length === batch.length) {
29158
+ for (let j = 0; j < batch.length; j++) {
29159
+ store.vectors[batch[j].id] = vectors[j];
29160
+ }
29161
+ }
29162
+ } catch {
29163
+ }
29164
+ }
29165
+ saveVectorStore(index.projectRoot, store);
29166
+ return store;
29167
+ }
28530
29168
  var INDEX_VERSION = 2;
28531
29169
  var DEFAULT_MAX_FILES = 5e3;
28532
29170
  var IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
28533
29171
  function normalizeProjectRoot(projectRoot) {
28534
- return path53.resolve(projectRoot || process.cwd());
29172
+ return path54.resolve(projectRoot || process.cwd());
28535
29173
  }
28536
29174
  function hashText(text3) {
28537
- return crypto19.createHash("sha256").update(text3).digest("hex");
29175
+ return crypto20.createHash("sha256").update(text3).digest("hex");
28538
29176
  }
28539
29177
  function indexDir() {
28540
- const dir = path53.join(EXE_AI_DIR, "code-context");
29178
+ const dir = path54.join(EXE_AI_DIR, "code-context");
28541
29179
  mkdirSync22(dir, { recursive: true });
28542
29180
  return dir;
28543
29181
  }
28544
29182
  function getCodeContextIndexPath(projectRoot) {
28545
29183
  const root = normalizeProjectRoot(projectRoot);
28546
29184
  const rootHash = hashText(root).slice(0, 16);
28547
- return path53.join(indexDir(), `${rootHash}.json`);
29185
+ return path54.join(indexDir(), `${rootHash}.json`);
28548
29186
  }
28549
29187
  function currentBranch(projectRoot) {
28550
29188
  const result3 = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
@@ -28557,8 +29195,8 @@ function shouldIgnore(relPath) {
28557
29195
  }
28558
29196
  function listRecursive(projectRoot, dir = projectRoot, out = []) {
28559
29197
  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, "/");
29198
+ const abs = path54.join(dir, entry.name);
29199
+ const rel = path54.relative(projectRoot, abs).replaceAll(path54.sep, "/");
28562
29200
  if (shouldIgnore(rel)) continue;
28563
29201
  if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
28564
29202
  else if (entry.isFile()) out.push(rel);
@@ -28574,7 +29212,7 @@ function listCodeFiles(projectRoot, maxFiles) {
28574
29212
  const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
28575
29213
  files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
28576
29214
  }
28577
- return files.map((file) => file.replaceAll(path53.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
29215
+ return files.map((file) => file.replaceAll(path54.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
28578
29216
  }
28579
29217
  function parseImportPaths(importText) {
28580
29218
  const paths = [];
@@ -28587,13 +29225,13 @@ function parseImportPaths(importText) {
28587
29225
  }
28588
29226
  function resolveImport(fromFile, importPath, allFiles) {
28589
29227
  if (!importPath.startsWith(".")) return null;
28590
- const base = path53.posix.normalize(path53.posix.join(path53.posix.dirname(fromFile.replaceAll(path53.sep, "/")), importPath));
29228
+ const base = path54.posix.normalize(path54.posix.join(path54.posix.dirname(fromFile.replaceAll(path54.sep, "/")), importPath));
28591
29229
  const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
28592
29230
  const candidates = [base];
28593
29231
  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
29232
  candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
28595
29233
  }
28596
- for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path53.posix.join(base, indexName));
29234
+ for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path54.posix.join(base, indexName));
28597
29235
  return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
28598
29236
  }
28599
29237
  function symbolId(filePath, chunk) {
@@ -28614,7 +29252,7 @@ function saveIndex(index) {
28614
29252
  writeFileSync24(getCodeContextIndexPath(index.projectRoot), JSON.stringify(index, null, 2));
28615
29253
  }
28616
29254
  function buildFileRecord(projectRoot, relPath, allFiles, previous) {
28617
- const absPath = path53.join(projectRoot, relPath);
29255
+ const absPath = path54.join(projectRoot, relPath);
28618
29256
  let stat;
28619
29257
  try {
28620
29258
  stat = statSync9(absPath);
@@ -28652,13 +29290,13 @@ function buildCodeContextIndex(options = {}) {
28652
29290
  const branch = currentBranch(projectRoot);
28653
29291
  const previous = options.force ? null : loadIndex(projectRoot);
28654
29292
  const files = listCodeFiles(projectRoot, maxFiles);
28655
- const allFiles = new Set(files.map((file) => file.replaceAll(path53.sep, "/")));
29293
+ const allFiles = new Set(files.map((file) => file.replaceAll(path54.sep, "/")));
28656
29294
  const fileRecords = {};
28657
29295
  let rebuiltFiles = 0;
28658
29296
  let reusedFiles = 0;
28659
29297
  let skippedFiles = 0;
28660
29298
  for (const rel of files) {
28661
- const normalized = rel.replaceAll(path53.sep, "/");
29299
+ const normalized = rel.replaceAll(path54.sep, "/");
28662
29300
  const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
28663
29301
  if (record) {
28664
29302
  fileRecords[normalized] = record;
@@ -28687,11 +29325,11 @@ function loadOrBuildCodeContextIndex(options = {}) {
28687
29325
  if (loaded) {
28688
29326
  const currentFiles = listCodeFiles(projectRoot, options.maxFiles ?? DEFAULT_MAX_FILES);
28689
29327
  const unchanged = currentFiles.every((rel) => {
28690
- const normalized = rel.replaceAll(path53.sep, "/");
29328
+ const normalized = rel.replaceAll(path54.sep, "/");
28691
29329
  const existing = loaded.files[normalized];
28692
29330
  if (!existing) return false;
28693
29331
  try {
28694
- const stat = statSync9(path53.join(projectRoot, normalized));
29332
+ const stat = statSync9(path54.join(projectRoot, normalized));
28695
29333
  return stat.mtimeMs === existing.mtimeMs && stat.size === existing.size;
28696
29334
  } catch {
28697
29335
  return false;
@@ -28744,9 +29382,9 @@ function globToRegex(pattern) {
28744
29382
  }
28745
29383
  function matchesPath(filePath, patterns) {
28746
29384
  if (!patterns || patterns.length === 0) return true;
28747
- const normalized = filePath.replaceAll(path53.sep, "/");
29385
+ const normalized = filePath.replaceAll(path54.sep, "/");
28748
29386
  return patterns.some((pattern) => {
28749
- const p = pattern.replaceAll(path53.sep, "/").replace(/^\.\//, "");
29387
+ const p = pattern.replaceAll(path54.sep, "/").replace(/^\.\//, "");
28750
29388
  return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
28751
29389
  });
28752
29390
  }
@@ -28805,7 +29443,7 @@ function filteredFiles(index, options = {}) {
28805
29443
  return matchesPath(file.path, options.paths);
28806
29444
  });
28807
29445
  }
28808
- function searchCodeContext(query, options = {}) {
29446
+ function lexicalSearch(query, options = {}) {
28809
29447
  const terms = tokenize(query);
28810
29448
  if (terms.length === 0) return [];
28811
29449
  const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
@@ -28831,6 +29469,82 @@ function searchCodeContext(query, options = {}) {
28831
29469
  const limit = options.limit ?? 20;
28832
29470
  return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
28833
29471
  }
29472
+ function searchCodeContext(query, options = {}) {
29473
+ return lexicalSearch(query, options);
29474
+ }
29475
+ var RRF_K2 = 60;
29476
+ async function searchCodeContextSemantic(query, options = {}) {
29477
+ const terms = tokenize(query);
29478
+ if (terms.length === 0) return [];
29479
+ const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
29480
+ const projectRoot = normalizeProjectRoot(options.projectRoot);
29481
+ const vectorStore = loadVectorStore(projectRoot);
29482
+ if (!vectorStore || Object.keys(vectorStore.vectors).length === 0) {
29483
+ return lexicalSearch(query, options);
29484
+ }
29485
+ let queryVector = null;
29486
+ try {
29487
+ const connected = await connectEmbedDaemon().catch(() => false);
29488
+ if (connected) {
29489
+ const result3 = await embedBatchViaClient([query], "high");
29490
+ if (result3 && result3.length === 1) queryVector = result3[0];
29491
+ }
29492
+ } catch {
29493
+ }
29494
+ if (!queryVector) return lexicalSearch(query, options);
29495
+ const files = filteredFiles(index, options);
29496
+ const candidates = [];
29497
+ for (const file of files) {
29498
+ for (const symbol of file.symbols) {
29499
+ const lexical = scoreSymbol(symbol, terms);
29500
+ const vec = vectorStore.vectors[symbol.id];
29501
+ const vectorScore = vec ? cosineSimilarity3(queryVector, vec) : 0;
29502
+ if (lexical.score > 0 || vectorScore > 0.3) {
29503
+ candidates.push({
29504
+ symbol,
29505
+ lexicalScore: lexical.score,
29506
+ vectorScore,
29507
+ matches: lexical.matches
29508
+ });
29509
+ }
29510
+ }
29511
+ }
29512
+ const byLexical = [...candidates].sort((a, b) => b.lexicalScore - a.lexicalScore);
29513
+ const byVector = [...candidates].sort((a, b) => b.vectorScore - a.vectorScore);
29514
+ const lexicalRank = /* @__PURE__ */ new Map();
29515
+ const vectorRank = /* @__PURE__ */ new Map();
29516
+ byLexical.forEach((c, i) => lexicalRank.set(c.symbol.id, i + 1));
29517
+ byVector.forEach((c, i) => vectorRank.set(c.symbol.id, i + 1));
29518
+ const VECTOR_WEIGHT = 0.6;
29519
+ const LEXICAL_WEIGHT = 0.4;
29520
+ const fused = candidates.map((c) => {
29521
+ const lRank = lexicalRank.get(c.symbol.id) ?? candidates.length + 1;
29522
+ const vRank = vectorRank.get(c.symbol.id) ?? candidates.length + 1;
29523
+ const rrfScore = VECTOR_WEIGHT * (1 / (RRF_K2 + vRank)) + LEXICAL_WEIGHT * (1 / (RRF_K2 + lRank));
29524
+ return {
29525
+ symbol: c.symbol,
29526
+ score: rrfScore,
29527
+ matches: c.vectorScore > 0.3 ? [...c.matches, `semantic=${c.vectorScore.toFixed(3)}`] : c.matches,
29528
+ filePath: c.symbol.filePath,
29529
+ language: c.symbol.language,
29530
+ content: c.symbol.text,
29531
+ startLine: c.symbol.startLine,
29532
+ endLine: c.symbol.endLine
29533
+ };
29534
+ });
29535
+ const offset = Math.max(0, options.offset ?? 0);
29536
+ const limit = options.limit ?? 20;
29537
+ return fused.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
29538
+ }
29539
+ async function buildCodeContextIndexWithEmbeddings(options = {}) {
29540
+ const index = buildCodeContextIndex(options);
29541
+ const existingStore = loadVectorStore(index.projectRoot);
29542
+ const existingCount = existingStore ? Object.keys(existingStore.vectors).length : 0;
29543
+ const store = await embedSymbols(index);
29544
+ const vectorCount = Object.keys(store.vectors).length;
29545
+ const newEmbeddings = vectorCount - Math.min(existingCount, vectorCount);
29546
+ return { index, vectorCount, newEmbeddings };
29547
+ }
28834
29548
  function dependentsMap(index) {
28835
29549
  const map = /* @__PURE__ */ new Map();
28836
29550
  for (const file of Object.values(index.files)) {
@@ -28864,7 +29578,7 @@ function traceCodeSymbol(symbolName, options = {}) {
28864
29578
  }
28865
29579
  function resolveTargetFile(index, input) {
28866
29580
  if (input.filePath) {
28867
- const normalized = input.filePath.replaceAll(path53.sep, "/").replace(/^\.\//, "");
29581
+ const normalized = input.filePath.replaceAll(path54.sep, "/").replace(/^\.\//, "");
28868
29582
  if (index.files[normalized]) return { filePath: normalized, target: normalized };
28869
29583
  const suffix = Object.keys(index.files).find((file) => file.endsWith(normalized));
28870
29584
  if (suffix) return { filePath: suffix, target: input.filePath };
@@ -28894,7 +29608,7 @@ function analyzeBlastRadius(input) {
28894
29608
  }
28895
29609
  }
28896
29610
  }
28897
- const targetBase = path53.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
29611
+ const targetBase = path54.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
28898
29612
  const symbolLower = input.symbol?.toLowerCase();
28899
29613
  const tests = Object.keys(index.files).filter((file) => {
28900
29614
  const lower = file.toLowerCase();
@@ -28933,23 +29647,24 @@ function jsonResult(value) {
28933
29647
  function registerCodeContext(server2) {
28934
29648
  server2.registerTool("code_context", {
28935
29649
  title: "Code Context",
28936
- description: "Persistent codebase context engine. One consolidated tool to avoid MCP bloat. Actions: index, search, trace, blast_radius, stats.",
29650
+ 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
29651
  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 }) => {
29652
+ action: z93.enum(["index", "index_embed", "search", "trace", "blast_radius", "stats"]).describe("Code context operation. index_embed = index + generate embeddings for semantic search."),
29653
+ project_root: z93.string().optional().describe("Repository root. Defaults to current working directory."),
29654
+ query: z93.string().optional().describe("Natural language search query (e.g. 'authentication logic', 'database migration'). Semantic search finds conceptual matches, not just keywords."),
29655
+ symbol: z93.string().optional().describe("Symbol/function/class/type name for trace or blast_radius"),
29656
+ file_path: z93.string().optional().describe("File path for blast_radius"),
29657
+ force: z93.boolean().optional().describe("Force rebuild before answering"),
29658
+ limit: z93.coerce.number().int().min(1).max(100).optional().describe("Max results"),
29659
+ offset: z93.coerce.number().int().min(0).optional().describe("Search pagination offset"),
29660
+ refresh_index: z93.boolean().optional().describe("Refresh/rebuild index before searching"),
29661
+ languages: z93.array(z93.string()).optional().describe('Language filters, e.g. ["python", "typescript"]'),
29662
+ paths: z93.array(z93.string()).optional().describe("Path/glob filters"),
29663
+ depth: z93.coerce.number().int().min(1).max(5).optional().describe("Dependent traversal depth for blast_radius"),
29664
+ max_files: z93.coerce.number().int().min(1).max(1e4).optional().describe("Max code files to index"),
29665
+ lexical_only: z93.boolean().optional().describe("Force lexical-only search (skip vector similarity). Default: false \u2014 uses semantic hybrid search.")
29666
+ }
29667
+ }, async ({ action, project_root, query, symbol, file_path, force, limit, offset, refresh_index, languages, paths, depth, max_files, lexical_only }) => {
28953
29668
  const opts = { projectRoot: project_root, force, maxFiles: max_files };
28954
29669
  if (action === "index") {
28955
29670
  const index = buildCodeContextIndex(opts);
@@ -28959,7 +29674,24 @@ function registerCodeContext(server2) {
28959
29674
  indexedAt: index.indexedAt,
28960
29675
  files: Object.keys(index.files).length,
28961
29676
  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)
29677
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
29678
+ note: "Structural index only. Use action=index_embed to also generate vector embeddings for semantic search."
29679
+ });
29680
+ }
29681
+ if (action === "index_embed") {
29682
+ const { index, vectorCount, newEmbeddings } = await buildCodeContextIndexWithEmbeddings(opts);
29683
+ const totalSymbols = Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0);
29684
+ return jsonResult({
29685
+ projectRoot: index.projectRoot,
29686
+ branch: index.branch,
29687
+ indexedAt: index.indexedAt,
29688
+ files: Object.keys(index.files).length,
29689
+ symbols: totalSymbols,
29690
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
29691
+ vectorCount,
29692
+ newEmbeddings,
29693
+ embeddingCoverage: totalSymbols > 0 ? `${(vectorCount / totalSymbols * 100).toFixed(1)}%` : "0%",
29694
+ note: "Structural index + vector embeddings. Semantic search is now available."
28963
29695
  });
28964
29696
  }
28965
29697
  if (action === "stats") {
@@ -28967,14 +29699,28 @@ function registerCodeContext(server2) {
28967
29699
  }
28968
29700
  if (action === "search") {
28969
29701
  if (!query) return errorResult10('code_context action "search" requires query');
29702
+ const searchOpts = { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths };
29703
+ if (lexical_only) {
29704
+ return jsonResult({
29705
+ query,
29706
+ mode: "lexical",
29707
+ limit: limit ?? 20,
29708
+ offset: offset ?? 0,
29709
+ languages: languages ?? [],
29710
+ paths: paths ?? [],
29711
+ results: searchCodeContext(query, searchOpts)
29712
+ });
29713
+ }
29714
+ const results = await searchCodeContextSemantic(query, searchOpts);
29715
+ const hasSemantic = results.some((r) => r.matches.some((m) => m.startsWith("semantic=")));
28970
29716
  return jsonResult({
28971
29717
  query,
29718
+ mode: hasSemantic ? "semantic+lexical" : "lexical-fallback",
28972
29719
  limit: limit ?? 20,
28973
29720
  offset: offset ?? 0,
28974
- refresh_index: refresh_index ?? false,
28975
29721
  languages: languages ?? [],
28976
29722
  paths: paths ?? [],
28977
- results: searchCodeContext(query, { ...opts, limit, offset, refreshIndex: refresh_index, languages, paths })
29723
+ results
28978
29724
  });
28979
29725
  }
28980
29726
  if (action === "trace") {
@@ -28992,9 +29738,9 @@ function registerCodeContext(server2) {
28992
29738
  }
28993
29739
 
28994
29740
  // src/mcp/tools/support-inbox.ts
28995
- import { z as z93 } from "zod";
29741
+ import { z as z94 } from "zod";
28996
29742
  var DEFAULT_ENDPOINT = "https://askexe.com/admin/support/bug-reports";
28997
- var STATUS = z93.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
29743
+ var STATUS = z94.enum(["open", "triaged", "fixed", "closed", "wontfix"]);
28998
29744
  function adminToken() {
28999
29745
  return process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN;
29000
29746
  }
@@ -29033,9 +29779,9 @@ function registerListBugReports(server2) {
29033
29779
  title: "List Bug Reports",
29034
29780
  description: "AskExe-internal only: list incoming customer bug reports from the support inbox.",
29035
29781
  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)
29782
+ status: z94.enum(["all", "open", "triaged", "fixed", "closed", "wontfix"]).default("open"),
29783
+ severity: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
29784
+ limit: z94.number().int().min(1).max(100).default(25)
29039
29785
  }
29040
29786
  },
29041
29787
  async ({ status: status2, severity, limit }) => {
@@ -29054,7 +29800,7 @@ function registerGetBugReport(server2) {
29054
29800
  {
29055
29801
  title: "Get Bug Report",
29056
29802
  description: "AskExe-internal only: fetch one customer bug report with full markdown payload.",
29057
- inputSchema: { id: z93.string().min(8) }
29803
+ inputSchema: { id: z94.string().min(8) }
29058
29804
  },
29059
29805
  async ({ id }) => {
29060
29806
  const data = await requestJson(`${endpoint()}/${encodeURIComponent(id)}`);
@@ -29069,12 +29815,12 @@ function registerTriageBugReport(server2) {
29069
29815
  title: "Triage Bug Report",
29070
29816
  description: "AskExe-internal only: update bug report status and link task/commit/release metadata.",
29071
29817
  inputSchema: {
29072
- id: z93.string().min(8),
29818
+ id: z94.string().min(8),
29073
29819
  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()
29820
+ triage_notes: z94.string().optional(),
29821
+ linked_task_id: z94.string().optional(),
29822
+ linked_commit: z94.string().optional(),
29823
+ fixed_version: z94.string().optional()
29078
29824
  }
29079
29825
  },
29080
29826
  async ({ id, status: status2, triage_notes, linked_task_id, linked_commit, fixed_version }) => {
@@ -29086,6 +29832,294 @@ function registerTriageBugReport(server2) {
29086
29832
  }
29087
29833
  );
29088
29834
  }
29835
+ var FEATURE_STATUS = z94.enum(["open", "planned", "in_progress", "shipped", "closed", "wontdo"]);
29836
+ function featureEndpoint() {
29837
+ return endpoint().replace(/\/bug-reports\/?$/, "/feature-requests");
29838
+ }
29839
+ function registerListFeatureRequests(server2) {
29840
+ server2.registerTool(
29841
+ "list_feature_requests",
29842
+ {
29843
+ title: "List Feature Requests",
29844
+ description: "AskExe-internal only: list incoming customer feature requests from the support inbox.",
29845
+ inputSchema: {
29846
+ status: z94.enum(["all", "open", "planned", "in_progress", "shipped", "closed", "wontdo"]).default("open"),
29847
+ priority: z94.enum(["p0", "p1", "p2", "p3"]).optional(),
29848
+ limit: z94.number().int().min(1).max(100).default(25)
29849
+ }
29850
+ },
29851
+ async ({ status: status2, priority, limit }) => {
29852
+ const url = new URL(featureEndpoint());
29853
+ url.searchParams.set("status", status2);
29854
+ url.searchParams.set("limit", String(limit));
29855
+ if (priority) url.searchParams.set("priority", priority);
29856
+ const data = await requestJson(url.toString());
29857
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29858
+ }
29859
+ );
29860
+ }
29861
+ function registerGetFeatureRequest(server2) {
29862
+ server2.registerTool(
29863
+ "get_feature_request",
29864
+ {
29865
+ title: "Get Feature Request",
29866
+ description: "AskExe-internal only: fetch one customer feature request with full payload.",
29867
+ inputSchema: { id: z94.string().min(8) }
29868
+ },
29869
+ async ({ id }) => {
29870
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`);
29871
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29872
+ }
29873
+ );
29874
+ }
29875
+ function registerTriageFeatureRequest(server2) {
29876
+ server2.registerTool(
29877
+ "triage_feature_request",
29878
+ {
29879
+ title: "Triage Feature Request",
29880
+ description: "AskExe-internal only: update feature request status, response notes, and version metadata.",
29881
+ inputSchema: {
29882
+ id: z94.string().min(8),
29883
+ status: FEATURE_STATUS.optional(),
29884
+ response_notes: z94.string().optional(),
29885
+ target_version: z94.string().optional(),
29886
+ shipped_version: z94.string().optional()
29887
+ }
29888
+ },
29889
+ async ({ id, status: status2, response_notes, target_version, shipped_version }) => {
29890
+ const data = await requestJson(`${featureEndpoint()}/${encodeURIComponent(id)}`, {
29891
+ method: "PATCH",
29892
+ body: JSON.stringify({ status: status2, response_notes, target_version, shipped_version })
29893
+ });
29894
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
29895
+ }
29896
+ );
29897
+ }
29898
+
29899
+ // src/mcp/tools/decision.ts
29900
+ import { z as z95 } from "zod";
29901
+ function buildHandlers6() {
29902
+ const tools = /* @__PURE__ */ new Map();
29903
+ const fake = {
29904
+ registerTool(name, _config, handler) {
29905
+ tools.set(name, handler);
29906
+ }
29907
+ };
29908
+ registerStoreDecision(fake);
29909
+ registerGetDecision(fake);
29910
+ return tools;
29911
+ }
29912
+ var ACTION_TO_TOOL3 = {
29913
+ store: "store_decision",
29914
+ get: "get_decision"
29915
+ };
29916
+ function registerDecision(server2) {
29917
+ const handlers = buildHandlers6();
29918
+ server2.registerTool(
29919
+ "decision",
29920
+ {
29921
+ title: "Decision",
29922
+ description: "Store or retrieve authoritative decisions. Actions: store (persist a decision by domain), get (look up a decision by domain/query).",
29923
+ inputSchema: {
29924
+ action: z95.enum(["store", "get"]).describe("Decision operation"),
29925
+ domain: z95.string().optional().describe("Decision domain (e.g. 'architecture', 'hiring')"),
29926
+ decision: z95.string().optional().describe("Decision text for action=store"),
29927
+ rationale: z95.string().optional().describe("Why this decision was made (action=store)"),
29928
+ query: z95.string().optional().describe("Search query for action=get"),
29929
+ limit: z95.coerce.number().optional().describe("Max results for action=get")
29930
+ }
29931
+ },
29932
+ async (input, extra) => {
29933
+ const action = input.action;
29934
+ const toolName = ACTION_TO_TOOL3[action];
29935
+ if (!toolName) return { content: [{ type: "text", text: `Unknown decision action: ${action}` }], isError: true };
29936
+ const handler = handlers.get(toolName);
29937
+ if (!handler) return { content: [{ type: "text", text: `Handler not found: ${toolName}` }], isError: true };
29938
+ const { action: _, ...args } = input;
29939
+ return handler(args, extra);
29940
+ }
29941
+ );
29942
+ }
29943
+
29944
+ // src/mcp/tools/session.ts
29945
+ import { z as z96 } from "zod";
29946
+ function buildHandlers7() {
29947
+ const tools = /* @__PURE__ */ new Map();
29948
+ const fake = {
29949
+ registerTool(name, _config, handler) {
29950
+ tools.set(name, handler);
29951
+ }
29952
+ };
29953
+ registerGetSessionEvents(fake);
29954
+ registerGetLastAssistantResponse(fake);
29955
+ return tools;
29956
+ }
29957
+ var ACTION_TO_TOOL4 = {
29958
+ events: "get_session_events",
29959
+ last_response: "get_last_assistant_response"
29960
+ };
29961
+ function registerSession2(server2) {
29962
+ const handlers = buildHandlers7();
29963
+ server2.registerTool(
29964
+ "session",
29965
+ {
29966
+ title: "Session",
29967
+ description: "Session inspection tools. Actions: events (get session event log), last_response (get the most recent assistant response text).",
29968
+ inputSchema: {
29969
+ action: z96.enum(["events", "last_response"]).describe("Session operation"),
29970
+ session_id: z96.string().optional().describe("Session ID for action=events"),
29971
+ limit: z96.coerce.number().optional().describe("Max events for action=events")
29972
+ }
29973
+ },
29974
+ async (input, extra) => {
29975
+ const action = input.action;
29976
+ const toolName = ACTION_TO_TOOL4[action];
29977
+ if (!toolName) return { content: [{ type: "text", text: `Unknown session action: ${action}` }], isError: true };
29978
+ const handler = handlers.get(toolName);
29979
+ if (!handler) return { content: [{ type: "text", text: `Handler not found: ${toolName}` }], isError: true };
29980
+ const { action: _, ...args } = input;
29981
+ return handler(args, extra);
29982
+ }
29983
+ );
29984
+ }
29985
+
29986
+ // src/mcp/tools/support-consolidated.ts
29987
+ import { z as z97 } from "zod";
29988
+ function buildHandlers8() {
29989
+ const tools = /* @__PURE__ */ new Map();
29990
+ const fake = {
29991
+ registerTool(name, _config, handler) {
29992
+ tools.set(name, handler);
29993
+ }
29994
+ };
29995
+ registerCreateBugReport(fake);
29996
+ registerCreateFeatureRequest(fake);
29997
+ registerSupportTools(fake);
29998
+ registerListBugReports(fake);
29999
+ registerGetBugReport(fake);
30000
+ registerTriageBugReport(fake);
30001
+ registerListFeatureRequests(fake);
30002
+ registerGetFeatureRequest(fake);
30003
+ registerTriageFeatureRequest(fake);
30004
+ return tools;
30005
+ }
30006
+ var ACTION_TO_TOOL5 = {
30007
+ create_bug: "create_bug_report",
30008
+ create_feature: "create_feature_request",
30009
+ list_my_bugs: "list_my_bug_reports",
30010
+ list_my_features: "list_my_feature_requests",
30011
+ list_bugs: "list_bug_reports",
30012
+ get_bug: "get_bug_report",
30013
+ triage_bug: "triage_bug_report",
30014
+ list_features: "list_feature_requests",
30015
+ get_feature: "get_feature_request",
30016
+ triage_feature: "triage_feature_request",
30017
+ health: "support_health",
30018
+ test: "support_test"
30019
+ };
30020
+ function registerSupportConsolidated(server2) {
30021
+ const handlers = buildHandlers8();
30022
+ server2.registerTool(
30023
+ "support",
30024
+ {
30025
+ title: "Support",
30026
+ description: "Bug reports, feature requests, and support system. Actions: create_bug, create_feature, list_my_bugs, list_my_features, list_bugs, get_bug, triage_bug, list_features, get_feature, triage_feature, health, test.",
30027
+ inputSchema: {
30028
+ action: z97.enum([
30029
+ "create_bug",
30030
+ "create_feature",
30031
+ "list_my_bugs",
30032
+ "list_my_features",
30033
+ "list_bugs",
30034
+ "get_bug",
30035
+ "triage_bug",
30036
+ "list_features",
30037
+ "get_feature",
30038
+ "triage_feature",
30039
+ "health",
30040
+ "test"
30041
+ ]).describe("Support operation"),
30042
+ title: z97.string().optional().describe("Bug/feature title"),
30043
+ description: z97.string().optional().describe("Bug/feature description"),
30044
+ steps_to_reproduce: z97.string().optional().describe("Steps to reproduce (bugs)"),
30045
+ expected_behavior: z97.string().optional().describe("Expected behavior (bugs)"),
30046
+ actual_behavior: z97.string().optional().describe("Actual behavior (bugs)"),
30047
+ severity: z97.string().optional().describe("Severity: p0/p1/p2/p3"),
30048
+ use_case: z97.string().optional().describe("Use case (features)"),
30049
+ proposed_solution: z97.string().optional().describe("Proposed solution (features)"),
30050
+ priority: z97.string().optional().describe("Priority (features)"),
30051
+ id: z97.string().optional().describe("Bug/feature ID for get/triage"),
30052
+ status: z97.string().optional().describe("Filter by status"),
30053
+ classification: z97.string().optional().describe("Triage classification"),
30054
+ resolution: z97.string().optional().describe("Triage resolution"),
30055
+ notes: z97.string().optional().describe("Triage notes (mapped to triage_notes for bug/feature triage)"),
30056
+ triage_notes: z97.string().optional().describe("Triage notes (legacy field name)"),
30057
+ linked_task_id: z97.string().optional().describe("Linked task ID for triage"),
30058
+ linked_commit: z97.string().optional().describe("Linked commit hash for triage"),
30059
+ fixed_version: z97.string().optional().describe("Version that fixes this bug"),
30060
+ limit: z97.coerce.number().optional().describe("Max results")
30061
+ }
30062
+ },
30063
+ async (input, extra) => {
30064
+ const action = input.action;
30065
+ const toolName = ACTION_TO_TOOL5[action];
30066
+ if (!toolName) return { content: [{ type: "text", text: `Unknown support action: ${action}` }], isError: true };
30067
+ const handler = handlers.get(toolName);
30068
+ if (!handler) return { content: [{ type: "text", text: `Handler not found: ${toolName}` }], isError: true };
30069
+ const { action: _, ...args } = input;
30070
+ if ((action === "triage_bug" || action === "triage_feature") && args.notes && !args.triage_notes) {
30071
+ args.triage_notes = args.notes;
30072
+ delete args.notes;
30073
+ }
30074
+ return handler(args, extra);
30075
+ }
30076
+ );
30077
+ }
30078
+
30079
+ // src/mcp/tools/diagnostics.ts
30080
+ import { z as z98 } from "zod";
30081
+ function buildHandlers9() {
30082
+ const tools = /* @__PURE__ */ new Map();
30083
+ const fake = {
30084
+ registerTool(name, _config, handler) {
30085
+ tools.set(name, handler);
30086
+ }
30087
+ };
30088
+ registerCliParityTools(fake);
30089
+ return tools;
30090
+ }
30091
+ function registerDiagnostics(server2) {
30092
+ const handlers = buildHandlers9();
30093
+ const toolNames = Array.from(handlers.keys());
30094
+ void (toolNames.length > 0 ? toolNames : ["healthcheck"]);
30095
+ server2.registerTool(
30096
+ "diagnostics",
30097
+ {
30098
+ title: "Diagnostics",
30099
+ description: `System diagnostics and admin operations. Actions: ${toolNames.join(", ")}. Covers health checks, update status, cloud status, key management, and employee rename.`,
30100
+ inputSchema: {
30101
+ action: z98.string().describe(`Diagnostic operation: ${toolNames.join(", ")}`),
30102
+ // Pass-through params used by various sub-tools
30103
+ name: z98.string().optional().describe("Employee name (rename_employee)"),
30104
+ new_name: z98.string().optional().describe("New employee name (rename_employee)"),
30105
+ query: z98.string().optional().describe("Search/filter query"),
30106
+ format: z98.string().optional().describe("Output format")
30107
+ }
30108
+ },
30109
+ async (input, extra) => {
30110
+ const action = input.action;
30111
+ const handler = handlers.get(action);
30112
+ if (!handler) {
30113
+ return {
30114
+ content: [{ type: "text", text: `Unknown diagnostics action: ${action}. Available: ${toolNames.join(", ")}` }],
30115
+ isError: true
30116
+ };
30117
+ }
30118
+ const { action: _, ...args } = input;
30119
+ return handler(args, extra);
30120
+ }
30121
+ );
30122
+ }
29089
30123
 
29090
30124
  // src/mcp/tool-gates.ts
29091
30125
  var TOOL_GATES = {
@@ -29142,6 +30176,11 @@ var TOOL_CATEGORIES = {
29142
30176
  registerGetSessionEvents: "core",
29143
30177
  registerGetLastAssistantResponse: "core",
29144
30178
  registerCodeContext: "graph-read",
30179
+ // consolidated tools
30180
+ registerDecision: "core",
30181
+ registerSession: "core",
30182
+ registerSupportConsolidated: "core",
30183
+ registerDiagnostics: "admin",
29145
30184
  registerListBugReports: "admin",
29146
30185
  registerGetBugReport: "admin",
29147
30186
  registerTriageBugReport: "admin",
@@ -29235,6 +30274,36 @@ var TOOL_CATEGORIES = {
29235
30274
  registerListGlobalProcedures: "orchestration",
29236
30275
  registerDeactivateGlobalProcedure: "orchestration"
29237
30276
  };
30277
+ var PLAN_TIERS = {
30278
+ free: 0,
30279
+ pro: 1,
30280
+ team: 2,
30281
+ agency: 3,
30282
+ enterprise: 4
30283
+ };
30284
+ var PLAN_GATES = {
30285
+ core: "free",
30286
+ tasks: "free",
30287
+ comms: "free",
30288
+ reminders: "free",
30289
+ "graph-read": "free",
30290
+ licensing: "free",
30291
+ // always allow checking/managing own license
30292
+ "graph-write": "pro",
30293
+ wiki: "pro",
30294
+ crm: "pro",
30295
+ "raw-data": "pro",
30296
+ documents: "pro",
30297
+ gateway: "pro",
30298
+ admin: "pro",
30299
+ orchestration: "pro"
30300
+ };
30301
+ function isToolAllowedForPlan(registerFnName, plan) {
30302
+ const category = TOOL_CATEGORIES[registerFnName];
30303
+ if (!category) return true;
30304
+ const requiredPlan = PLAN_GATES[category] ?? "free";
30305
+ return PLAN_TIERS[plan] >= PLAN_TIERS[requiredPlan];
30306
+ }
29238
30307
  var CHIEF_OF_STAFF_READ_TOOLS = /* @__PURE__ */ new Set([
29239
30308
  "registerRecallMyMemory",
29240
30309
  "registerAskTeamMemory",
@@ -29270,9 +30339,17 @@ function isToolAllowed(registerFnName) {
29270
30339
  }
29271
30340
 
29272
30341
  // src/mcp/register-tools.ts
29273
- function registerAllTools(server2) {
30342
+ var ALWAYS_ALLOWED = /* @__PURE__ */ new Set([
30343
+ "registerMcpPing",
30344
+ "registerGetLicenseStatus",
30345
+ "registerActivateLicense"
30346
+ ]);
30347
+ function registerAllTools(server2, license, hasKey) {
29274
30348
  const gate = (name, fn) => {
29275
- if (isToolAllowed(name)) fn(server2);
30349
+ if (!isToolAllowed(name)) return;
30350
+ if (hasKey === false && !ALWAYS_ALLOWED.has(name)) return;
30351
+ if (license && !isToolAllowedForPlan(name, license.plan)) return;
30352
+ fn(server2);
29276
30353
  };
29277
30354
  const mcpToolSurface = process.env.EXE_MCP_TOOL_SURFACE ?? "legacy";
29278
30355
  const exposeConsolidatedTasks = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
@@ -29293,6 +30370,14 @@ function registerAllTools(server2) {
29293
30370
  const exposeLegacyGraph = mcpToolSurface !== "consolidated";
29294
30371
  const exposeConsolidatedConfig = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
29295
30372
  const exposeLegacyConfig = mcpToolSurface !== "consolidated";
30373
+ const exposeConsolidatedDecisions = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
30374
+ const exposeLegacyDecisions = mcpToolSurface !== "consolidated";
30375
+ const exposeConsolidatedSession = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
30376
+ const exposeLegacySession = mcpToolSurface !== "consolidated";
30377
+ const exposeConsolidatedSupport = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
30378
+ const exposeLegacySupport = mcpToolSurface !== "consolidated";
30379
+ const exposeConsolidatedDiagnostics = mcpToolSurface === "both" || mcpToolSurface === "consolidated";
30380
+ const exposeLegacyDiagnostics = mcpToolSurface !== "consolidated";
29296
30381
  if (exposeConsolidatedMemory) {
29297
30382
  gate("registerMemory", registerMemory);
29298
30383
  }
@@ -29406,19 +30491,43 @@ function registerAllTools(server2) {
29406
30491
  if (exposeLegacyMemory) {
29407
30492
  gate("registerSearchEverything", registerSearchEverything);
29408
30493
  }
29409
- gate("registerStoreDecision", registerStoreDecision);
29410
- gate("registerGetDecision", registerGetDecision);
29411
- gate("registerCreateBugReport", registerCreateBugReport);
29412
- gate("registerSupportTools", registerSupportTools);
29413
- gate("registerCliParityTools", registerCliParityTools);
29414
- gate("registerGetSessionEvents", registerGetSessionEvents);
29415
- gate("registerGetLastAssistantResponse", registerGetLastAssistantResponse);
29416
- gate("registerCodeContext", registerCodeContext);
29417
- if (process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN) {
29418
- gate("registerListBugReports", registerListBugReports);
29419
- gate("registerGetBugReport", registerGetBugReport);
29420
- gate("registerTriageBugReport", registerTriageBugReport);
30494
+ if (exposeConsolidatedDecisions) {
30495
+ gate("registerDecision", registerDecision);
30496
+ }
30497
+ if (exposeLegacyDecisions) {
30498
+ gate("registerStoreDecision", registerStoreDecision);
30499
+ gate("registerGetDecision", registerGetDecision);
30500
+ }
30501
+ if (exposeConsolidatedSupport) {
30502
+ gate("registerSupportConsolidated", registerSupportConsolidated);
29421
30503
  }
30504
+ if (exposeLegacySupport) {
30505
+ gate("registerCreateBugReport", registerCreateBugReport);
30506
+ gate("registerCreateFeatureRequest", registerCreateFeatureRequest);
30507
+ gate("registerSupportTools", registerSupportTools);
30508
+ if (process.env.ASKEXE_SUPPORT_ADMIN_TOKEN || process.env.EXE_SUPPORT_ADMIN_TOKEN) {
30509
+ gate("registerListBugReports", registerListBugReports);
30510
+ gate("registerGetBugReport", registerGetBugReport);
30511
+ gate("registerTriageBugReport", registerTriageBugReport);
30512
+ gate("registerListFeatureRequests", registerListFeatureRequests);
30513
+ gate("registerGetFeatureRequest", registerGetFeatureRequest);
30514
+ gate("registerTriageFeatureRequest", registerTriageFeatureRequest);
30515
+ }
30516
+ }
30517
+ if (exposeConsolidatedDiagnostics) {
30518
+ gate("registerDiagnostics", registerDiagnostics);
30519
+ }
30520
+ if (exposeLegacyDiagnostics) {
30521
+ gate("registerCliParityTools", registerCliParityTools);
30522
+ }
30523
+ if (exposeConsolidatedSession) {
30524
+ gate("registerSession", registerSession2);
30525
+ }
30526
+ if (exposeLegacySession) {
30527
+ gate("registerGetSessionEvents", registerGetSessionEvents);
30528
+ gate("registerGetLastAssistantResponse", registerGetLastAssistantResponse);
30529
+ }
30530
+ gate("registerCodeContext", registerCodeContext);
29422
30531
  if (exposeLegacyConfig) {
29423
30532
  gate("registerGetAgentSpend", registerGetAgentSpend);
29424
30533
  }
@@ -29533,9 +30642,13 @@ var _backfillTimer = null;
29533
30642
  var _ppidWatchdog = null;
29534
30643
  var _shuttingDown = false;
29535
30644
  instrumentServer(server);
29536
- registerAllTools(server);
30645
+ var wrappedServer = wrapServerWithTelemetry(server);
30646
+ var _licenseGate = await initLicenseGate();
30647
+ registerAllTools(wrappedServer, _licenseGate.license, _licenseGate.hasKey);
30648
+ startToolTelemetryFlush();
29537
30649
  var _gatedRole = process.env.AGENT_ROLE ?? "standalone";
29538
- process.stderr.write(`[exe-os] Tool gating: role=${_gatedRole}
30650
+ var _gatedPlan = _licenseGate.license?.plan ?? (_licenseGate.hasKey ? "validating" : "no-key");
30651
+ process.stderr.write(`[exe-os] Tool gating: role=${_gatedRole} plan=${_gatedPlan}
29539
30652
  `);
29540
30653
  try {
29541
30654
  await initStore();
@@ -29585,7 +30698,7 @@ try {
29585
30698
  }
29586
30699
  }, 3e4);
29587
30700
  _ppidWatchdog.unref();
29588
- const MCP_VERSION_PATH = path54.join(os20.homedir(), ".exe-os", "mcp-version");
30701
+ const MCP_VERSION_PATH = path55.join(os20.homedir(), ".exe-os", "mcp-version");
29589
30702
  let _currentMcpVersion = null;
29590
30703
  try {
29591
30704
  _currentMcpVersion = existsSync41(MCP_VERSION_PATH) ? readFileSync36(MCP_VERSION_PATH, "utf8").trim() : null;
@@ -29627,14 +30740,14 @@ try {
29627
30740
  `
29628
30741
  );
29629
30742
  const thisFile = fileURLToPath7(import.meta.url);
29630
- const backfillPath = path54.resolve(
29631
- path54.dirname(thisFile),
30743
+ const backfillPath = path55.resolve(
30744
+ path55.dirname(thisFile),
29632
30745
  "../bin/backfill-vectors.js"
29633
30746
  );
29634
30747
  if (existsSync41(backfillPath)) {
29635
30748
  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 });
30749
+ const logPath = path55.join(exeDir, "workers.log");
30750
+ mkdirSync23(path55.dirname(logPath), { recursive: true });
29638
30751
  let logFd = "ignore";
29639
30752
  try {
29640
30753
  logFd = openSync4(logPath, "a");