@muxoai/platform 1.4.1 → 1.4.3

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 (53) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/README.md +1 -1
  4. package/dist/cli.js +94 -3
  5. package/dist/composio-catalog.d.ts +177 -1
  6. package/dist/composio-catalog.js +1427 -7
  7. package/dist/config.d.ts +6 -0
  8. package/dist/config.js +11 -1
  9. package/dist/durable-workflow.d.ts +71 -4
  10. package/dist/durable-workflow.js +199 -5
  11. package/dist/engine.d.ts +82 -3
  12. package/dist/engine.js +406 -41
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.js +3 -0
  15. package/dist/mcp-schema.d.ts +162 -1
  16. package/dist/mcp-schema.js +1544 -40
  17. package/dist/mcp.d.ts +2 -2
  18. package/dist/mcp.js +474 -26
  19. package/dist/memory-graph.d.ts +26 -2
  20. package/dist/memory-graph.js +127 -7
  21. package/dist/oss-suite.d.ts +217 -0
  22. package/dist/oss-suite.js +836 -0
  23. package/dist/policy.d.ts +108 -1
  24. package/dist/policy.js +406 -1
  25. package/dist/providers/composio-instant.js +38 -3
  26. package/dist/providers/composio-sdk.d.ts +59 -2
  27. package/dist/providers/composio-sdk.js +273 -59
  28. package/dist/providers/stripe-projects.d.ts +4 -0
  29. package/dist/providers/stripe-projects.js +548 -126
  30. package/dist/sdk.d.ts +24 -1
  31. package/dist/sdk.js +53 -2
  32. package/dist/server.js +659 -64
  33. package/dist/store.js +2 -0
  34. package/dist/types.d.ts +34 -1
  35. package/dist/ui/console-html.d.ts +31 -1
  36. package/dist/ui/console-html.js +922 -368
  37. package/dist/ui/design-tokens.js +1 -1
  38. package/dist/version.d.ts +1 -1
  39. package/dist/version.js +1 -1
  40. package/package.json +3 -2
  41. package/plugins/muxo/.claude-plugin/plugin.json +2 -2
  42. package/plugins/muxo/.codex-plugin/plugin.json +15 -8
  43. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  44. package/plugins/muxo/hooks/session-start.sh +15 -3
  45. package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
  46. package/plugins/muxo/mcp.json +0 -10
  47. package/plugins/muxo/plugin.json +4 -4
  48. package/plugins/muxo/skills/muxo/SKILL.md +25 -16
  49. package/plugins/muxo/skills/muxo/references/cli.md +5 -1
  50. package/plugins/muxo/skills/muxo/references/mcp.md +11 -6
  51. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  52. package/plugins/muxo/skills/setup/SKILL.md +1 -1
  53. package/src/catalogues/stripe-projects-catalog.json +140 -0
package/dist/server.js CHANGED
@@ -5,13 +5,14 @@ import path from "node:path";
5
5
  import { getConfig, ROOT_DIR } from "./config.js";
6
6
  import { MuxoEngine } from "./engine.js";
7
7
  import { getMuxoMcpSkills, handleJsonRpcMcpRequest, MUXO_MCP_SERVER_INSTRUCTIONS, MUXO_MCP_TOOLS, } from "./mcp.js";
8
- import { APPROVED_PLAN_STATUSES, MuxoToolError, clampWaitSeconds, errorPayloadFrom, isPublicMcpRequest, workspaceAgentDid, } from "./mcp-schema.js";
8
+ import { APPROVED_PLAN_STATUSES, LATEST_MCP_PROTOCOL_VERSION, MuxoToolError, SUPPORTED_MCP_PROTOCOL_VERSIONS, clampWaitSeconds, errorPayloadFrom, isPublicMcpRequest, validateMcpHttpHeadersAndMeta, workspaceAgentDid, } from "./mcp-schema.js";
9
9
  import { MuxoStore } from "./store.js";
10
10
  import { managedProviderConnectionList, managedProviderConnectionRequest, managedProviderConnectionUnlink, managedResource, managedResourceAction, } from "./provisioning-managed.js";
11
11
  import { DEFAULT_WORKSPACE_POLICY, describePolicyInPlainEnglish, sanitizePolicyPatch, } from "./policy.js";
12
12
  import { parseNaturalLanguageMemoryInstruction } from "./providers/supermemory.js";
13
13
  import { MUXO_VERSION } from "./version.js";
14
- import { isConsolePath, renderApprovalPageHtml, renderConsoleHtml, renderLandingHtml, renderOAuthConsentHtml, } from "./ui/console-html.js";
14
+ import { fetchMuxoConnectLinkMetadata, isStripeProjectsExclusiveProvider, normalizeStripeProjectsProviderSlug, parseComposioWebhookEnvelope, submitMuxoConnectLink, verifyComposioWebhookSignature, wrapMuxoConnectUrl, } from "./composio-catalog.js";
15
+ import { isConsolePath, renderApprovalPageHtml, renderConsoleHtml, renderLandingHtml, renderOAuthCallbackPageHtml, renderOAuthConnectPageHtml, renderOAuthConsentHtml, } from "./ui/console-html.js";
15
16
  import { fetchClerkProfile, looksLikeJwt, readClerkConfig, verifyClerkSessionToken, } from "./clerk.js";
16
17
  import { DEFAULT_OAUTH_SCOPE, OAUTH_ACCESS_TOKEN_TTL_SECONDS, OAUTH_CODE_TTL_MS, oauthConnectionKeyName, oauthRedirectOrigin, oauthSecretToken, parseAuthorizeRequest, parseOAuthFormBody, verifyPkce, } from "./oauth.js";
17
18
  function renderSkillMarkdown(baseUrl) {
@@ -109,7 +110,20 @@ Users and LLM harnesses can set safety rules in natural language or structured p
109
110
  - "Only post Slack messages to #ops-alerts or #deployments" → channel allowlist guardrail
110
111
  - "Never spend more than 25 credits per run" → workspace per-run credit cap
111
112
 
112
- Set or inspect them via MCP \`get_run_context({ action: "set_guardrail", prompt: "...", parameter_rules: [...] })\`, \`POST /api/v1/do\` (\`{ "prompt": "..." }\`), \`POST /api/v1/guardrails\` (\`{ "prompt": "...", "parameterRules": [...] }\`), or list them with \`GET /api/v1/guardrails\`. Responses always include \`plainEnglishGuardrails\` so humans and agents can read the active rules.
113
+ Set or inspect them via MCP \`get_run_context({ action: "set_guardrail", prompt: "...", parameter_rules: [...] })\`, dry-run simulate a tool call before planning via \`get_run_context({ action: "check_policy", tool_slug: "...", input: { ... } })\`, reset guardrails via \`get_run_context({ action: "reset_guardrails" })\`, toggle the emergency kill switch via \`get_run_context({ action: "kill_switch", enabled: true, reason: "..." })\`, or scan untrusted inputs for OWASP ASI-06 prompt injection via \`get_run_context({ action: "scan_context", prompt: "..." })\`. Equivalent REST routes: \`POST /api/v1/do\`, \`GET/POST /api/v1/guardrails\`, \`POST /api/v1/guardrails/check\`, and \`POST /api/v1/guardrails/reset\`. Responses always include \`plainEnglishGuardrails\` and a structured \`llmPolicyBrief\` (\`riskLaneMatrix\`, \`parameterRules[].howToComply\`, \`llmSystemDirective\`, and \`agentControlCheatsheet\`).
114
+
115
+ ## MCP Resources & Prompts for AI Harnesses
116
+
117
+ - **MCP Resources (\`resources/list\` & \`resources/read\`)**:
118
+ - \`muxo://policy/brief\` — Structured \`llmPolicyBrief\` (risk-lane approval matrix, parameter compliance rules, learned defaults, and agent control cheatsheet)
119
+ - \`muxo://governance/policy\` — Active guardrails, kill-switch status, trust score, and \`llmPolicyBrief\`
120
+ - \`muxo://context\` — Live workspace context, active guardrails, provisioned resources, and \`llmSystemPrompt\`
121
+ - \`muxo://memory/graph\` — Active Supermemory defaults and bi-temporal knowledge graph facts
122
+ - **MCP Prompts (\`prompts/list\` & \`prompts/get\`)**:
123
+ - \`context\` — Inject workspace memory, active defaults, and guardrail summary
124
+ - \`policy_guardrails\` — Inject the active \`llmSystemDirective\`, risk-lane matrix, and optional dry-run policy simulation for \`tool_slug\`
125
+ - \`governed_execution\` — Step-by-step Plan → Approve → Execute → Receipt system instructions
126
+ - \`security_audit\` — OWASP Agentic Top 10 security and receipt audit workflow
113
127
 
114
128
  ## Active Supermemory (teach Muxo once, auto-fill forever)
115
129
 
@@ -134,8 +148,8 @@ function sendJson(res, status, payload, extraHeaders) {
134
148
  res.writeHead(status, {
135
149
  "Content-Type": "application/json; charset=utf-8",
136
150
  "Access-Control-Allow-Origin": "*",
137
- "Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id",
138
- "Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate",
151
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Muxo-Workspace-Id, Mcp-Session-Id, mcp-session-id, MCP-Protocol-Version, mcp-protocol-version, Mcp-Method, mcp-method, Mcp-Name, mcp-name",
152
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version, WWW-Authenticate",
139
153
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
140
154
  ...(extraHeaders || {}),
141
155
  });
@@ -320,7 +334,8 @@ export function createMuxoServer(customConfig) {
320
334
  if (req.method === "OPTIONS") {
321
335
  res.writeHead(204, {
322
336
  "Access-Control-Allow-Origin": "*",
323
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Muxo-Workspace-Id",
337
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Muxo-Workspace-Id, Mcp-Session-Id, mcp-session-id, MCP-Protocol-Version, mcp-protocol-version, Mcp-Method, mcp-method, Mcp-Name, mcp-name",
338
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version, WWW-Authenticate",
324
339
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
325
340
  });
326
341
  res.end();
@@ -746,8 +761,14 @@ export function createMuxoServer(customConfig) {
746
761
  "/api/v1/governance/kill-switch": { post: { summary: "Pause or resume workspace execution via kill switch" } },
747
762
  "/api/v1/governance/scan-context": { post: { summary: "Scan untrusted text for prompt injection before storage or use" } },
748
763
  "/api/v1/guardrails": {
749
- get: { summary: "List active plain-English guardrails, policy, and learned memory defaults" },
750
- post: { summary: "Set or reset guardrails in plain English (e.g. 'never send emails')" },
764
+ get: { summary: "List active plain-English guardrails, llmPolicyBrief, policy, and learned memory defaults" },
765
+ post: { summary: "Set or reset guardrails in plain English or structured parameterRules" },
766
+ },
767
+ "/api/v1/guardrails/check": {
768
+ post: { summary: "Dry-run evaluate a tool call and arguments against active workspace policy, conditional parameter rules, and memory defaults" },
769
+ },
770
+ "/api/v1/guardrails/reset": {
771
+ post: { summary: "Reset workspace guardrails and parameter rules to Safe Default" },
751
772
  },
752
773
  "/approve/{planId}": { get: { summary: "One-click human approval handoff page for a pending plan" } },
753
774
  "/api/v1/context": {
@@ -806,10 +827,10 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
806
827
  }
807
828
  if (pathname === "/llms.txt" && req.method === "GET") {
808
829
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
809
- res.end(`# Muxo\n> Unified agent execution across Connected Apps, Instant Tools, Cloud Infrastructure, Workspace Memory, and Safety Guardrails.\n\n- Skill Document: ${config.baseUrl}/skill.md\n- Auth Guide: ${config.baseUrl}/auth.md\n- OpenAPI 3.1 Spec: ${config.baseUrl}/api/openapi.json\n- MCP Endpoint: ${config.baseUrl}/mcp\n- OAuth Callback Handoff: ${config.baseUrl}/oauth/callback\n- Plugin Marketplace (Codex/OpenAI): ${config.baseUrl}/.agents/plugins/marketplace.json\n- Plugin Marketplace (Claude/Droid): ${config.baseUrl}/.claude-plugin/marketplace.json\n- Console (app.muxo.ai): ${config.baseUrl}/\n- Landing & Provider Canvas (muxo.ai): ${config.baseUrl}/landing\n- Documentation (docs.muxo.ai): ${config.docsUrl}\n`);
830
+ res.end(`# Muxo\n> Unified agent execution across Connected Apps, Instant Tools, Cloud Infrastructure, Workspace Memory, and Safety Guardrails.\n\n- Skill Document: ${config.baseUrl}/skill.md\n- Auth Guide: ${config.baseUrl}/auth.md\n- OpenAPI 3.1 Spec: ${config.baseUrl}/api/openapi.json\n- MCP Endpoint: ${config.baseUrl}/mcp\n- OAuth Callback Handoff: ${config.baseUrl}/oauth/callback\n- Plugin Marketplace (Codex/OpenAI): ${config.baseUrl}/.agents/plugins/marketplace.json\n- Plugin Marketplace (Claude/Droid): ${config.baseUrl}/.claude-plugin/marketplace.json\n- Console (app.muxo.ai): ${config.baseUrl}/\n- Landing & Provider Canvas (muxo.ai): ${config.baseUrl}/landing\n- Documentation (docs.muxo.ai): ${config.docsUrl}\n\n## Agent-Native Policy, Context & Governance Interface\n- MCP Control Plane (\`get_run_context\`): \`action: "recall" | "remember" | "forget" | "set_guardrail" | "reset_guardrails" | "check_policy" | "governance_status" | "kill_switch" | "scan_context"\`\n- Strict /v1 Organization Brief: \`get_organization({ company_context: { required: false } })\` returns accepted workspace policy facts + learned defaults in \`company_context.packet\`\n- Dry-Run Policy Simulator: \`POST ${config.baseUrl}/api/v1/guardrails/check\` (\`{ "tool_slug": "...", "input": { ... } }\`) or MCP \`get_run_context({ action: "check_policy", tool_slug, input })\`\n- Guardrails & Policy Brief: \`GET/POST ${config.baseUrl}/api/v1/guardrails\`, \`POST ${config.baseUrl}/api/v1/guardrails/reset\`, \`GET ${config.baseUrl}/api/v1/governance\` (includes \`llmPolicyBrief\`)\n- MCP Resources: \`muxo://policy/brief\`, \`muxo://governance/policy\`, \`muxo://context\`, \`muxo://memory/graph\`\n- MCP Prompts: \`context\`, \`policy_guardrails\`, \`governed_execution\`, \`security_audit\`\n`);
810
831
  return;
811
832
  }
812
- // Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude)
833
+ // Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude + Cursor)
813
834
  const pluginJsonRoutes = {
814
835
  "/.agents/plugins/marketplace.json": path.join(ROOT_DIR, ".agents", "plugins", "marketplace.json"),
815
836
  "/.claude-plugin/marketplace.json": path.join(ROOT_DIR, ".claude-plugin", "marketplace.json"),
@@ -817,6 +838,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
817
838
  "/plugins/muxo/mcp.json": path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"),
818
839
  "/plugins/muxo/.codex-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"),
819
840
  "/plugins/muxo/.claude-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".claude-plugin", "plugin.json"),
841
+ "/plugins/muxo/.cursor-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".cursor-plugin", "plugin.json"),
820
842
  "/plugins/muxo/.app.json": path.join(ROOT_DIR, "plugins", "muxo", ".app.json"),
821
843
  "/plugins/muxo/.mcp.json": path.join(ROOT_DIR, "plugins", "muxo", ".mcp.json"),
822
844
  };
@@ -844,6 +866,10 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
844
866
  file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "setup", "SKILL.md"),
845
867
  contentType: "text/markdown; charset=utf-8",
846
868
  },
869
+ "/plugins/muxo/skills/muxo-activity-summary/SKILL.md": {
870
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo-activity-summary", "SKILL.md"),
871
+ contentType: "text/markdown; charset=utf-8",
872
+ },
847
873
  "/.codex/config.toml": {
848
874
  file: path.join(ROOT_DIR, ".codex", "config.toml"),
849
875
  contentType: "text/plain; charset=utf-8",
@@ -864,10 +890,15 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
864
890
  const portableManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"), "utf8"));
865
891
  const mcpConfig = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"), "utf8"));
866
892
  const codexManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"), "utf8"));
893
+ const cursorManifestPath = path.join(ROOT_DIR, "plugins", "muxo", ".cursor-plugin", "plugin.json");
894
+ const cursorManifest = fs.existsSync(cursorManifestPath)
895
+ ? JSON.parse(fs.readFileSync(cursorManifestPath, "utf8"))
896
+ : undefined;
867
897
  sendJson(res, 200, {
868
898
  ok: true,
869
899
  plugin: portableManifest,
870
900
  codexPlugin: codexManifest,
901
+ cursorPlugin: cursorManifest,
871
902
  mcp: mcpConfig,
872
903
  skills: getMuxoMcpSkills().map((s) => ({
873
904
  name: s.name,
@@ -884,11 +915,14 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
884
915
  portablePluginJson: `${config.baseUrl}/plugins/muxo/plugin.json`,
885
916
  portableMcpJson: `${config.baseUrl}/plugins/muxo/mcp.json`,
886
917
  onboardingSkill: `${config.baseUrl}/plugins/muxo/skills/setup/SKILL.md`,
918
+ activitySummarySkill: `${config.baseUrl}/plugins/muxo/skills/muxo-activity-summary/SKILL.md`,
887
919
  codexPluginJson: `${config.baseUrl}/plugins/muxo/.codex-plugin/plugin.json`,
920
+ cursorPluginJson: `${config.baseUrl}/plugins/muxo/.cursor-plugin/plugin.json`,
888
921
  codexConfigToml: `${config.baseUrl}/.codex/config.toml`,
889
922
  },
890
923
  installCommands: {
891
924
  codex: "codex plugin add muxo@muxo",
925
+ cursor: "/add-plugin muxo",
892
926
  cliSignup: "muxo login --signup --name my-agent",
893
927
  oauthHandoff: "muxo link github --wait",
894
928
  },
@@ -915,6 +949,35 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
915
949
  res.end(renderLandingHtml());
916
950
  return;
917
951
  }
952
+ // Public Stripe Projects Hosted Static Sites (GET /sites/:slug and GET /sites/:slug/*)
953
+ const siteMatch = pathname.match(/^\/sites\/([^/]+)(?:\/(.*))?$/);
954
+ if (siteMatch && req.method === "GET") {
955
+ const siteSlug = decodeURIComponent(siteMatch[1]).replace(/\/+$/, "");
956
+ const filePath = siteMatch[2] ? decodeURIComponent(siteMatch[2]) : undefined;
957
+ const htmlContent = engine.getHostedSiteHtml(siteSlug, filePath);
958
+ if (!htmlContent) {
959
+ sendJson(res, 404, {
960
+ ok: false,
961
+ code: "site_not_found",
962
+ error: `Hosted site '${siteSlug}' was not found.`,
963
+ });
964
+ return;
965
+ }
966
+ const contentType = filePath && filePath.endsWith(".css")
967
+ ? "text/css; charset=utf-8"
968
+ : filePath && filePath.endsWith(".js")
969
+ ? "application/javascript; charset=utf-8"
970
+ : filePath && filePath.endsWith(".json")
971
+ ? "application/json; charset=utf-8"
972
+ : "text/html; charset=utf-8";
973
+ res.writeHead(200, {
974
+ "Content-Type": contentType,
975
+ "Access-Control-Allow-Origin": "*",
976
+ "Cache-Control": "no-store",
977
+ });
978
+ res.end(htmlContent);
979
+ return;
980
+ }
918
981
  // Workspace claim link: read-only; accepting requires the invited person's verified sign-in.
919
982
  if (pathname.startsWith("/claim/") && req.method === "GET") {
920
983
  const claimSlug = decodeURIComponent(pathname.slice("/claim/".length));
@@ -1041,30 +1104,289 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1041
1104
  return;
1042
1105
  }
1043
1106
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1044
- res.end(`<!DOCTYPE html>
1045
- <html lang="en">
1046
- <head>
1047
- <meta charset="utf-8" />
1048
- <title>Muxo OAuth Handoff Complete</title>
1049
- <style>
1050
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #090a0f; color: #f4f4f6; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
1051
- .card { background: #12141f; border: 1px solid #26293d; border-radius: 14px; padding: 32px; max-width: 460px; text-align: center; box-shadow: 0 20px 50px rgba(0,0,0,0.45); }
1052
- .badge { display: inline-block; background: rgba(16,185,129,0.15); color: #34d399; border: 1px solid rgba(16,185,129,0.35); border-radius: 999px; padding: 4px 12px; font-size: 12px; font-weight: 600; margin-bottom: 16px; }
1053
- h1 { font-size: 22px; margin: 0 0 10px; }
1054
- p { color: #a1a7bb; font-size: 14px; line-height: 1.5; margin: 0 0 20px; }
1055
- code { background: #1c1f30; padding: 3px 7px; border-radius: 6px; color: #e2e8f0; font-size: 12px; }
1056
- .btn { display: inline-block; background: #6366f1; color: #fff; text-decoration: none; padding: 10px 18px; border-radius: 8px; font-size: 14px; font-weight: 600; }
1057
- </style>
1058
- </head>
1059
- <body>
1060
- <div class="card">
1061
- <div class="badge">OAuth Handoff Complete (${handoff.status.toUpperCase()})</div>
1062
- <h1>${handoff.providerName} Connected to Muxo</h1>
1063
- <p>Workspace <code>${handoff.workspaceId}</code> is now linked${handoff.externalAccountId ? ` (<code>${handoff.externalAccountId}</code>)` : ""}. You can close this window and return to ChatGPT, Codex, Claude Code, Droid, or your terminal.</p>
1064
- <a class="btn" href="/?connected=${encodeURIComponent(handoff.providerSlug)}">Open Muxo Console</a>
1065
- </div>
1066
- </body>
1067
- </html>`);
1107
+ res.end(renderOAuthCallbackPageHtml({
1108
+ providerSlug: handoff.providerSlug,
1109
+ providerName: handoff.providerName,
1110
+ status: handoff.status,
1111
+ workspaceId: handoff.workspaceId,
1112
+ externalAccountId: handoff.externalAccountId,
1113
+ }));
1114
+ return;
1115
+ }
1116
+ // Public bare /oauth/connect or /connect redirector & zero-OAuth status handler
1117
+ if ((pathname === "/oauth/connect" || pathname === "/connect") && req.method === "GET") {
1118
+ const queryToken = (url.searchParams.get("token") ||
1119
+ url.searchParams.get("link_token") ||
1120
+ "").trim();
1121
+ const directRedirect = (url.searchParams.get("redirect_url") ||
1122
+ url.searchParams.get("url") ||
1123
+ url.searchParams.get("link") ||
1124
+ "").trim();
1125
+ const workspaceId = url.searchParams.get("workspace_id") ||
1126
+ url.searchParams.get("user_id") ||
1127
+ undefined;
1128
+ const rawProvider = url.searchParams.get("provider") ||
1129
+ url.searchParams.get("toolkit") ||
1130
+ "";
1131
+ const cleanProvider = normalizeStripeProjectsProviderSlug(rawProvider) ||
1132
+ rawProvider.trim().toLowerCase().replace(/^(app\.|resource\.|projects\.)/, "");
1133
+ const wantsJson = url.searchParams.get("format") === "json" ||
1134
+ String(req.headers.accept || "").includes("application/json");
1135
+ if (queryToken) {
1136
+ const targetUrl = queryToken.startsWith("s_")
1137
+ ? `https://backend.composio.dev/api/v3/s/${encodeURIComponent(queryToken.slice(2))}`
1138
+ : `https://connect.composio.dev/link/${encodeURIComponent(queryToken)}`;
1139
+ if (wantsJson) {
1140
+ sendJson(res, 200, { ok: true, status: "pending", redirectUrl: targetUrl });
1141
+ return;
1142
+ }
1143
+ res.writeHead(302, { Location: targetUrl, "Cache-Control": "no-store" });
1144
+ res.end();
1145
+ return;
1146
+ }
1147
+ if (directRedirect &&
1148
+ /^https:\/\/(?:connect|backend)\.composio\.dev\//i.test(directRedirect)) {
1149
+ if (wantsJson) {
1150
+ sendJson(res, 200, { ok: true, status: "pending", redirectUrl: directRedirect });
1151
+ return;
1152
+ }
1153
+ res.writeHead(302, { Location: directRedirect, "Cache-Control": "no-store" });
1154
+ res.end();
1155
+ return;
1156
+ }
1157
+ if (cleanProvider && !isStripeProjectsExclusiveProvider(cleanProvider)) {
1158
+ const allWs = [
1159
+ workspaceId
1160
+ ? store.getWorkspace(workspaceId)
1161
+ : store.ensureDefaultWorkspace().workspace,
1162
+ ].filter(Boolean);
1163
+ for (const ws of allWs) {
1164
+ if (!ws)
1165
+ continue;
1166
+ const pending = store
1167
+ .listConnections(ws.id)
1168
+ .find((c) => c.providerSlug.toLowerCase() === cleanProvider &&
1169
+ c.redirectUrl &&
1170
+ !isStripeProjectsExclusiveProvider(c.providerSlug));
1171
+ if (pending?.redirectUrl) {
1172
+ const nativeUrl = wrapMuxoConnectUrl(pending.redirectUrl);
1173
+ if (nativeUrl) {
1174
+ if (wantsJson) {
1175
+ sendJson(res, 200, {
1176
+ ok: true,
1177
+ provider: cleanProvider,
1178
+ status: pending.status,
1179
+ redirectUrl: nativeUrl,
1180
+ });
1181
+ return;
1182
+ }
1183
+ res.writeHead(302, { Location: nativeUrl, "Cache-Control": "no-store" });
1184
+ res.end();
1185
+ return;
1186
+ }
1187
+ }
1188
+ }
1189
+ }
1190
+ else if (!cleanProvider) {
1191
+ const allWs = [
1192
+ workspaceId
1193
+ ? store.getWorkspace(workspaceId)
1194
+ : store.ensureDefaultWorkspace().workspace,
1195
+ ].filter(Boolean);
1196
+ for (const ws of allWs) {
1197
+ if (!ws)
1198
+ continue;
1199
+ const pending = store
1200
+ .listConnections(ws.id)
1201
+ .find((c) => c.status === "pending" &&
1202
+ c.redirectUrl &&
1203
+ !isStripeProjectsExclusiveProvider(c.providerSlug));
1204
+ if (pending?.redirectUrl) {
1205
+ const nativeUrl = wrapMuxoConnectUrl(pending.redirectUrl);
1206
+ if (nativeUrl &&
1207
+ (/^https:\/\/(?:connect|backend)\.composio\.dev\//i.test(nativeUrl) ||
1208
+ nativeUrl.includes("/oauth/connect/"))) {
1209
+ if (wantsJson) {
1210
+ sendJson(res, 200, {
1211
+ ok: true,
1212
+ provider: pending.providerSlug,
1213
+ status: "pending",
1214
+ redirectUrl: nativeUrl,
1215
+ });
1216
+ return;
1217
+ }
1218
+ res.writeHead(302, { Location: nativeUrl, "Cache-Control": "no-store" });
1219
+ res.end();
1220
+ return;
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+ const displayProviderSlug = cleanProvider || "supabase";
1226
+ const displayProviderName = cleanProvider
1227
+ ? cleanProvider.charAt(0).toUpperCase() + cleanProvider.slice(1)
1228
+ : "Supabase & Vercel (Stripe Projects)";
1229
+ if (wantsJson) {
1230
+ sendJson(res, 200, {
1231
+ ok: true,
1232
+ status: "active",
1233
+ lane: "stripe_projects",
1234
+ provider: displayProviderSlug,
1235
+ noOAuthRequired: true,
1236
+ redirectUrl: null,
1237
+ message: "Provisioned Stripe Projects cloud resources (Supabase, Vercel, Cloudflare, Turso, Neon, etc.) are active immediately with zero OAuth required.",
1238
+ });
1239
+ return;
1240
+ }
1241
+ res.writeHead(200, {
1242
+ "Content-Type": "text/html; charset=utf-8",
1243
+ "Cache-Control": "no-store",
1244
+ });
1245
+ res.end(renderOAuthCallbackPageHtml({
1246
+ providerSlug: displayProviderSlug,
1247
+ providerName: displayProviderName,
1248
+ status: "active",
1249
+ workspaceId: workspaceId || "default",
1250
+ externalAccountId: `sp_conn_${displayProviderSlug}`,
1251
+ }));
1252
+ return;
1253
+ }
1254
+ // Muxo OAuth Connection Handoff (GET/POST /oauth/connect/:token or /connect/:token)
1255
+ const connectPageMatch = pathname.match(/^\/(?:oauth\/)?connect\/([^/]+)$/);
1256
+ if (connectPageMatch && (req.method === "GET" || req.method === "POST")) {
1257
+ const token = decodeURIComponent(connectPageMatch[1]);
1258
+ const workspaceId = url.searchParams.get("workspace_id") ||
1259
+ url.searchParams.get("user_id") ||
1260
+ undefined;
1261
+ const providerSlug = url.searchParams.get("provider") ||
1262
+ url.searchParams.get("toolkit") ||
1263
+ undefined;
1264
+ const callbackUrl = url.searchParams.get("callback_url") || undefined;
1265
+ const connectedAccountId = url.searchParams.get("connected_account_id") || undefined;
1266
+ if (req.method === "GET") {
1267
+ const wantsJson = url.searchParams.get("format") === "json" ||
1268
+ String(req.headers.accept || "").includes("application/json");
1269
+ const isTestToken = token.startsWith("lk_test");
1270
+ const forceHtml = url.searchParams.get("render") === "html";
1271
+ // Redirect real Composio link tokens directly to Composio's native hosted OAuth page
1272
+ if (!wantsJson && !isTestToken && !forceHtml && (token.startsWith("lk_") || token.startsWith("s_"))) {
1273
+ const nativeComposioUrl = token.startsWith("s_")
1274
+ ? `https://backend.composio.dev/api/v3/s/${encodeURIComponent(token.slice(2))}`
1275
+ : `https://connect.composio.dev/link/${encodeURIComponent(token)}`;
1276
+ res.writeHead(302, {
1277
+ Location: nativeComposioUrl,
1278
+ "Cache-Control": "no-store",
1279
+ });
1280
+ res.end();
1281
+ return;
1282
+ }
1283
+ const meta = await fetchMuxoConnectLinkMetadata(token, {
1284
+ workspaceId,
1285
+ providerSlug,
1286
+ callbackUrl,
1287
+ connectedAccountId,
1288
+ });
1289
+ if (url.searchParams.get("continue") === "1" ||
1290
+ url.searchParams.get("auto") === "1") {
1291
+ const submitted = await submitMuxoConnectLink(token, {}, {
1292
+ workspaceId,
1293
+ providerSlug: meta.toolkitSlug,
1294
+ callbackUrl: meta.callbackUrl || callbackUrl,
1295
+ connectedAccountId: meta.connectedAccountId,
1296
+ });
1297
+ if (submitted.ok && submitted.redirectUrl) {
1298
+ res.writeHead(302, { Location: submitted.redirectUrl });
1299
+ res.end();
1300
+ return;
1301
+ }
1302
+ }
1303
+ if (wantsJson) {
1304
+ sendJson(res, 200, { ok: true, handoff: meta });
1305
+ return;
1306
+ }
1307
+ res.writeHead(200, {
1308
+ "Content-Type": "text/html; charset=utf-8",
1309
+ "Cache-Control": "no-store",
1310
+ "Referrer-Policy": "no-referrer",
1311
+ });
1312
+ res.end(renderOAuthConnectPageHtml({
1313
+ token: meta.token,
1314
+ toolkitSlug: meta.toolkitSlug,
1315
+ toolkitName: meta.toolkitName,
1316
+ workspaceId,
1317
+ authMode: meta.authMode,
1318
+ status: meta.status,
1319
+ callbackUrl: meta.callbackUrl || callbackUrl,
1320
+ expectedInputFields: meta.expectedInputFields,
1321
+ }));
1322
+ return;
1323
+ }
1324
+ // POST /oauth/connect/:token
1325
+ const rawBody = await readBody(req);
1326
+ let input = {};
1327
+ const contentType = String(req.headers["content-type"] || "");
1328
+ if (contentType.includes("application/x-www-form-urlencoded")) {
1329
+ const formParams = new URLSearchParams(rawBody);
1330
+ for (const [k, v] of formParams.entries()) {
1331
+ if (v !== "")
1332
+ input[k] = v;
1333
+ }
1334
+ }
1335
+ else if (rawBody) {
1336
+ try {
1337
+ const parsed = JSON.parse(rawBody);
1338
+ input =
1339
+ parsed && typeof parsed.input === "object" && parsed.input !== null
1340
+ ? parsed.input
1341
+ : parsed;
1342
+ }
1343
+ catch {
1344
+ input = {};
1345
+ }
1346
+ }
1347
+ const submitted = await submitMuxoConnectLink(token, input, {
1348
+ workspaceId,
1349
+ providerSlug,
1350
+ callbackUrl,
1351
+ connectedAccountId,
1352
+ });
1353
+ const wantsJson = url.searchParams.get("format") === "json" ||
1354
+ String(req.headers.accept || "").includes("application/json") ||
1355
+ contentType.includes("application/json");
1356
+ if (wantsJson) {
1357
+ sendJson(res, submitted.ok ? 200 : 400, {
1358
+ ok: submitted.ok,
1359
+ status: submitted.status,
1360
+ redirectUrl: submitted.redirectUrl,
1361
+ redirect_url: submitted.redirectUrl,
1362
+ connectedAccountId: submitted.connectedAccountId,
1363
+ ...(submitted.error ? { error: submitted.error } : {}),
1364
+ });
1365
+ return;
1366
+ }
1367
+ if (submitted.ok && submitted.redirectUrl) {
1368
+ res.writeHead(302, { Location: submitted.redirectUrl });
1369
+ res.end();
1370
+ return;
1371
+ }
1372
+ const meta = await fetchMuxoConnectLinkMetadata(token, {
1373
+ workspaceId,
1374
+ providerSlug,
1375
+ callbackUrl,
1376
+ connectedAccountId,
1377
+ });
1378
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
1379
+ res.end(renderOAuthConnectPageHtml({
1380
+ token: meta.token,
1381
+ toolkitSlug: meta.toolkitSlug,
1382
+ toolkitName: meta.toolkitName,
1383
+ workspaceId,
1384
+ authMode: meta.authMode,
1385
+ status: meta.status,
1386
+ callbackUrl: meta.callbackUrl || callbackUrl,
1387
+ expectedInputFields: meta.expectedInputFields,
1388
+ error: submitted.error || "Could not start authorization handoff.",
1389
+ }));
1068
1390
  return;
1069
1391
  }
1070
1392
  // One-click Plan Approval Handoff Page (GET /approve/:planId)
@@ -1151,8 +1473,9 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1151
1473
  "Cache-Control": "no-cache",
1152
1474
  Connection: "keep-alive",
1153
1475
  "Mcp-Session-Id": sessionId,
1476
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1154
1477
  "Access-Control-Allow-Origin": "*",
1155
- "Access-Control-Expose-Headers": "Mcp-Session-Id",
1478
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version",
1156
1479
  });
1157
1480
  if (req.method !== "HEAD") {
1158
1481
  res.write(`event: endpoint\ndata: ${JSON.stringify({ uri: `${config.baseUrl}/mcp`, sessionId })}\n\n`);
@@ -1163,15 +1486,20 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1163
1486
  sendJson(res, 200, {
1164
1487
  ok: true,
1165
1488
  transport: "streamable-http",
1166
- protocolVersion: "2025-06-18",
1489
+ protocolVersion: LATEST_MCP_PROTOCOL_VERSION,
1490
+ supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
1167
1491
  serverInfo: {
1168
1492
  name: "muxo-mcp-server",
1493
+ title: "Muxo Unified Agent Runtime & Governance MCP Server",
1169
1494
  version: MUXO_VERSION,
1170
1495
  },
1171
1496
  instructions: MUXO_MCP_SERVER_INSTRUCTIONS,
1172
1497
  toolsCount: MUXO_MCP_TOOLS.length,
1173
1498
  skillsCount: getMuxoMcpSkills().length,
1174
- }, { "Mcp-Session-Id": sessionId });
1499
+ }, {
1500
+ "Mcp-Session-Id": sessionId,
1501
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1502
+ });
1175
1503
  return;
1176
1504
  }
1177
1505
  if (pathname === "/mcp" && req.method === "DELETE") {
@@ -1184,15 +1512,39 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1184
1512
  if (pathname === "/mcp" && req.method === "POST") {
1185
1513
  const raw = await readBody(req);
1186
1514
  const parsed = JSON.parse(raw || "{}");
1515
+ const headerCheck = !Array.isArray(parsed)
1516
+ ? validateMcpHttpHeadersAndMeta({
1517
+ protocolVersion: String(req.headers["mcp-protocol-version"] || ""),
1518
+ method: String(req.headers["mcp-method"] || ""),
1519
+ name: String(req.headers["mcp-name"] || ""),
1520
+ }, parsed)
1521
+ : { ok: true, protocolVersion: LATEST_MCP_PROTOCOL_VERSION };
1522
+ if (!headerCheck.ok) {
1523
+ const fallbackSessionId = String(req.headers["mcp-session-id"] || "") ||
1524
+ `mcp_sess_${crypto.randomBytes(6).toString("hex")}`;
1525
+ sendJson(res, 200, {
1526
+ jsonrpc: "2.0",
1527
+ id: headerCheck.id,
1528
+ error: headerCheck.error,
1529
+ }, {
1530
+ "Mcp-Session-Id": fallbackSessionId,
1531
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1532
+ });
1533
+ return;
1534
+ }
1187
1535
  const resolved = await requireContext(req, res, pathname, !Array.isArray(parsed) && isPublicMcpRequest(parsed));
1188
1536
  if (!resolved)
1189
1537
  return;
1190
1538
  const { ctx } = resolved;
1191
1539
  const sessionId = String(req.headers["mcp-session-id"] || "") ||
1192
1540
  `mcp_sess_${ctx.workspace.id}`;
1541
+ const responseHeaders = {
1542
+ "Mcp-Session-Id": sessionId,
1543
+ "MCP-Protocol-Version": headerCheck.protocolVersion,
1544
+ };
1193
1545
  if (Array.isArray(parsed)) {
1194
1546
  const responses = (await Promise.all(parsed.map((item) => handleJsonRpcMcpRequest(engine, ctx, (item || {}))))).filter((r) => r !== null);
1195
- sendJson(res, 200, responses, { "Mcp-Session-Id": sessionId });
1547
+ sendJson(res, 200, responses, responseHeaders);
1196
1548
  return;
1197
1549
  }
1198
1550
  const rpc = parsed;
@@ -1200,12 +1552,12 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1200
1552
  if (!response) {
1201
1553
  res.writeHead(202, {
1202
1554
  "Access-Control-Allow-Origin": "*",
1203
- "Mcp-Session-Id": sessionId,
1555
+ ...responseHeaders,
1204
1556
  });
1205
1557
  res.end();
1206
1558
  return;
1207
1559
  }
1208
- sendJson(res, 200, response, { "Mcp-Session-Id": sessionId });
1560
+ sendJson(res, 200, response, responseHeaders);
1209
1561
  return;
1210
1562
  }
1211
1563
  // Auth Endpoints
@@ -1263,7 +1615,11 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1263
1615
  const isPublicRoute = ((pathname === "/api/v1/tools" || pathname === "/v1/tools/search") &&
1264
1616
  (req.method === "GET" || req.method === "POST")) ||
1265
1617
  (pathname.startsWith("/api/v1/tools/") && req.method === "GET") ||
1266
- (pathname === "/api/v1/billing/webhook" && req.method === "POST");
1618
+ (pathname === "/api/v1/billing/webhook" && req.method === "POST") ||
1619
+ ((pathname === "/webhooks" ||
1620
+ pathname === "/webhooks/composio" ||
1621
+ pathname === "/api/v1/triggers/webhook") &&
1622
+ req.method === "POST");
1267
1623
  const resolvedCtx = await requireContext(req, res, pathname, isPublicRoute);
1268
1624
  if (!resolvedCtx)
1269
1625
  return;
@@ -1277,6 +1633,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1277
1633
  profile: engine.getProfile(ctx),
1278
1634
  organization: org,
1279
1635
  governance: engine.governance.getStatus(),
1636
+ llmPolicyBrief: guardrailsSummary.llmPolicyBrief,
1280
1637
  plainEnglishGuardrails: guardrailsSummary.plainEnglish,
1281
1638
  activeMemoryDefaults: guardrailsSummary.activeDefaults,
1282
1639
  activeGuardrails: guardrailsSummary.activeGuardrails,
@@ -1451,6 +1808,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1451
1808
  auditChain: fullReport.auditChain,
1452
1809
  },
1453
1810
  workspacePolicy: ws.policy,
1811
+ llmPolicyBrief: guardrailsSummary.llmPolicyBrief,
1454
1812
  plainEnglishGuardrails: guardrailsSummary.plainEnglish,
1455
1813
  activeMemoryDefaults: guardrailsSummary.activeDefaults,
1456
1814
  activeGuardrails: guardrailsSummary.activeGuardrails,
@@ -1465,16 +1823,37 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1465
1823
  });
1466
1824
  return;
1467
1825
  }
1826
+ if ((pathname === "/api/v1/guardrails/reset" || pathname === "/api/v1/policy/reset") &&
1827
+ req.method === "POST") {
1828
+ const resetResult = await engine.resetWorkspaceGuardrails(ctx);
1829
+ sendJson(res, 200, {
1830
+ ...resetResult,
1831
+ humanMessage: "Reset workspace guardrails to Safe Default.",
1832
+ });
1833
+ return;
1834
+ }
1835
+ if ((pathname === "/api/v1/guardrails/check" ||
1836
+ pathname === "/api/v1/guardrails/simulate" ||
1837
+ pathname === "/api/v1/policy/check") &&
1838
+ req.method === "POST") {
1839
+ const body = JSON.parse((await readBody(req)) || "{}");
1840
+ const toolSlug = String(body.tool_slug || body.toolSlug || body.slug || body.tool_id || "instant.web.search").trim();
1841
+ const input = (body.input || body.arguments) || {};
1842
+ const result = await engine.checkToolPolicy(ctx, { toolSlug, input });
1843
+ const summary = engine.getGuardrailsSummary(ctx);
1844
+ sendJson(res, 200, {
1845
+ ...result,
1846
+ simulation: result,
1847
+ llmPolicyBrief: summary.llmPolicyBrief,
1848
+ });
1849
+ return;
1850
+ }
1468
1851
  if (pathname === "/api/v1/guardrails" && req.method === "POST") {
1469
1852
  const body = JSON.parse((await readBody(req)) || "{}");
1470
1853
  if (body.reset === true) {
1471
- const updated = store.updateWorkspacePolicy(ctx.workspace.id, DEFAULT_WORKSPACE_POLICY);
1472
- const plainEnglish = describePolicyInPlainEnglish(updated.policy);
1854
+ const resetResult = await engine.resetWorkspaceGuardrails(ctx);
1473
1855
  sendJson(res, 200, {
1474
- ok: true,
1475
- workspace: updated,
1476
- policy: updated.policy,
1477
- plainEnglish,
1856
+ ...resetResult,
1478
1857
  humanMessage: "Reset workspace guardrails to Safe Default.",
1479
1858
  });
1480
1859
  return;
@@ -1569,11 +1948,35 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
1569
1948
  });
1570
1949
  return;
1571
1950
  }
1572
- if (pathname === "/api/v1/memory/graph" && req.method === "GET") {
1951
+ if (pathname === "/api/v1/memory/graph" &&
1952
+ (req.method === "GET" || req.method === "POST")) {
1953
+ const body = req.method === "POST"
1954
+ ? JSON.parse((await readBody(req)) || "{}")
1955
+ : {};
1573
1956
  const query = url.searchParams.get("query") ||
1574
1957
  url.searchParams.get("q") ||
1575
- undefined;
1576
- const graph = await engine.getMemoryGraph(ctx, query);
1958
+ (body.query ? String(body.query) : undefined);
1959
+ const asOf = url.searchParams.get("as_of") ||
1960
+ url.searchParams.get("asOf") ||
1961
+ (body.as_of || body.asOf ? String(body.as_of || body.asOf) : null);
1962
+ const focalEntity = url.searchParams.get("focal_entity") ||
1963
+ url.searchParams.get("focalEntity") ||
1964
+ (body.focal_entity || body.focalEntity
1965
+ ? String(body.focal_entity || body.focalEntity)
1966
+ : null);
1967
+ const rawMaxHops = url.searchParams.get("max_hops") ??
1968
+ url.searchParams.get("maxHops") ??
1969
+ body.max_hops ??
1970
+ body.maxHops;
1971
+ const maxHops = rawMaxHops !== undefined && rawMaxHops !== null && rawMaxHops !== ""
1972
+ ? Number(rawMaxHops)
1973
+ : undefined;
1974
+ const graph = await engine.getMemoryGraph(ctx, {
1975
+ query,
1976
+ asOf,
1977
+ focalEntity,
1978
+ maxHops,
1979
+ });
1577
1980
  sendJson(res, 200, { ok: true, ...graph });
1578
1981
  return;
1579
1982
  }
@@ -2968,52 +3371,143 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
2968
3371
  }
2969
3372
  if (pathname === "/api/v1/triggers" && req.method === "GET") {
2970
3373
  const provider = url.searchParams.get("provider") || undefined;
3374
+ const triggerSlug = url.searchParams.get("triggerSlug") || url.searchParams.get("trigger_slug") || undefined;
3375
+ const composioByok = store.getByokKey(ctx.workspace.id, "composio");
2971
3376
  const available = engine.composioSdk.listTriggerTypes(provider);
2972
3377
  const subscriptions = engine.composioSdk.listActiveTriggers(ctx.workspace.id);
2973
3378
  const events = engine.composioSdk.listTriggerEvents(ctx.workspace.id, 25);
3379
+ const triggerType = triggerSlug
3380
+ ? await engine.composioSdk.getTriggerType(triggerSlug, composioByok)
3381
+ : undefined;
2974
3382
  sendJson(res, 200, {
2975
3383
  ok: true,
2976
3384
  availableTriggers: available,
2977
3385
  activeSubscriptions: subscriptions,
2978
3386
  recentEvents: events,
3387
+ webhookConfig: engine.composioSdk.getWebhookConfigInfo(),
3388
+ ...(triggerType ? { triggerType } : {}),
2979
3389
  });
2980
3390
  return;
2981
3391
  }
2982
3392
  if (pathname === "/api/v1/triggers" && req.method === "POST") {
2983
3393
  const composioByok = store.getByokKey(ctx.workspace.id, "composio");
2984
3394
  const body = JSON.parse((await readBody(req)) || "{}");
3395
+ const rawConfig = body.triggerConfig && typeof body.triggerConfig === "object"
3396
+ ? body.triggerConfig
3397
+ : body.config && typeof body.config === "object"
3398
+ ? body.config
3399
+ : {};
2985
3400
  const subscription = await engine.composioSdk.enableTrigger({
2986
3401
  workspaceId: ctx.workspace.id,
3402
+ userId: body.userId ? String(body.userId) : undefined,
2987
3403
  triggerSlug: String(body.triggerSlug || body.trigger_slug || "GITHUB_COMMIT_EVENT"),
2988
3404
  providerSlug: String(body.providerSlug || body.provider || "github"),
2989
3405
  connectedAccountId: body.connectedAccountId
2990
3406
  ? String(body.connectedAccountId)
2991
3407
  : undefined,
2992
3408
  webhookUrl: body.webhookUrl ? String(body.webhookUrl) : undefined,
2993
- config: body.config && typeof body.config === "object" ? body.config : {},
3409
+ config: rawConfig,
3410
+ subscribe: body.subscribe === true,
2994
3411
  composioApiKey: composioByok,
2995
3412
  });
2996
3413
  sendJson(res, 201, { ok: true, subscription });
2997
3414
  return;
2998
3415
  }
2999
- if (pathname === "/api/v1/triggers/webhook" && req.method === "POST") {
3000
- const body = JSON.parse((await readBody(req)) || "{}");
3001
- const triggerSlug = String(body.triggerSlug || body.type || "GITHUB_COMMIT_EVENT").toUpperCase();
3002
- const providerSlug = String(body.providerSlug || triggerSlug.split("_")[0] || "github").toLowerCase();
3003
- const payloadObj = body.payload && typeof body.payload === "object"
3004
- ? body.payload
3005
- : body;
3006
- const rawPayloadText = JSON.stringify(payloadObj);
3416
+ if ((pathname === "/api/v1/triggers/webhook" ||
3417
+ pathname === "/webhooks" ||
3418
+ pathname === "/webhooks/composio") &&
3419
+ req.method === "POST") {
3420
+ const rawBody = await readBody(req);
3421
+ const sigHeader = String(req.headers["webhook-signature"] ||
3422
+ req.headers["x-composio-webhook-signature"] ||
3423
+ "").trim();
3424
+ const webhookIdHeader = String(req.headers["webhook-id"] || req.headers["x-composio-webhook-id"] || "").trim();
3425
+ const webhookTimestampHeader = String(req.headers["webhook-timestamp"] ||
3426
+ req.headers["x-composio-webhook-timestamp"] ||
3427
+ "").trim();
3428
+ const webhookVersionHeader = String(req.headers["x-composio-webhook-version"] || "").trim();
3429
+ let signatureVerified = false;
3430
+ if (sigHeader) {
3431
+ const check = await verifyComposioWebhookSignature({
3432
+ rawPayload: rawBody,
3433
+ signatureHeader: sigHeader,
3434
+ webhookIdHeader,
3435
+ webhookTimestampHeader,
3436
+ secret: config.composioWebhookSecret,
3437
+ });
3438
+ if (config.composioWebhookSecret && !check.verified) {
3439
+ sendJson(res, 401, {
3440
+ ok: false,
3441
+ error: `Invalid Composio webhook signature (${check.reason || "verification_failed"})`,
3442
+ });
3443
+ return;
3444
+ }
3445
+ signatureVerified = check.verified;
3446
+ }
3447
+ const body = JSON.parse(rawBody || "{}");
3448
+ const envelope = parseComposioWebhookEnvelope(body, {
3449
+ webhookId: webhookIdHeader || undefined,
3450
+ webhookTimestamp: webhookTimestampHeader || undefined,
3451
+ webhookVersion: webhookVersionHeader || undefined,
3452
+ });
3453
+ let targetWorkspaceId = ctx.workspace.id;
3454
+ if (envelope.userId && store.getWorkspace(envelope.userId)) {
3455
+ targetWorkspaceId = envelope.userId;
3456
+ }
3457
+ if (envelope.eventType === "composio.connected_account.activated" ||
3458
+ envelope.eventType === "composio.connected_account.expired") {
3459
+ const nextStatus = envelope.eventType === "composio.connected_account.activated"
3460
+ ? "active"
3461
+ : "expired";
3462
+ store.upsertConnection({
3463
+ workspaceId: targetWorkspaceId,
3464
+ lane: "composio_sdk",
3465
+ providerSlug: envelope.providerSlug,
3466
+ providerName: envelope.providerSlug.charAt(0).toUpperCase() +
3467
+ envelope.providerSlug.slice(1),
3468
+ status: nextStatus,
3469
+ externalAccountId: envelope.connectedAccountId || null,
3470
+ metadata: {
3471
+ webhookEvent: envelope.eventType,
3472
+ webhookId: envelope.webhookId,
3473
+ updatedViaWebhookAt: new Date().toISOString(),
3474
+ },
3475
+ });
3476
+ if (nextStatus === "expired") {
3477
+ engine.composioSdk.disableTriggersMatching({
3478
+ workspaceId: targetWorkspaceId,
3479
+ connectedAccountId: envelope.connectedAccountId,
3480
+ });
3481
+ }
3482
+ }
3483
+ else if (envelope.eventType === "composio.trigger.disabled") {
3484
+ engine.composioSdk.disableTriggersMatching({
3485
+ workspaceId: targetWorkspaceId,
3486
+ triggerId: envelope.triggerId,
3487
+ triggerSlug: envelope.triggerSlug,
3488
+ connectedAccountId: envelope.connectedAccountId,
3489
+ });
3490
+ }
3491
+ const rawPayloadText = JSON.stringify(envelope.payloadObj);
3007
3492
  const injectionScan = engine.governance.scanMemoryContent({
3008
- workspaceId: ctx.workspace.id,
3493
+ workspaceId: targetWorkspaceId,
3009
3494
  content: rawPayloadText,
3010
3495
  });
3011
3496
  const disposition = injectionScan.safe ? "dispatched" : "quarantined";
3012
3497
  const eventRecord = {
3013
3498
  id: `trgevt_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
3014
- workspaceId: ctx.workspace.id,
3015
- triggerSlug,
3016
- providerSlug,
3499
+ workspaceId: targetWorkspaceId,
3500
+ eventType: envelope.eventType,
3501
+ webhookVersion: envelope.webhookVersion,
3502
+ signatureVerified,
3503
+ webhookId: envelope.webhookId,
3504
+ triggerId: envelope.triggerId,
3505
+ triggerSlug: envelope.triggerSlug,
3506
+ providerSlug: envelope.providerSlug,
3507
+ connectedAccountId: envelope.connectedAccountId,
3508
+ userId: envelope.userId,
3509
+ projectId: envelope.projectId,
3510
+ orgId: envelope.orgId,
3017
3511
  receivedAt: new Date().toISOString(),
3018
3512
  promptInjectionScan: {
3019
3513
  safe: injectionScan.safe,
@@ -3021,7 +3515,7 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
3021
3515
  findings: injectionScan.findings.map((f) => `${f.patternId}: ${f.description}`),
3022
3516
  },
3023
3517
  disposition: disposition,
3024
- payloadPreview: payloadObj,
3518
+ payloadPreview: envelope.payloadObj,
3025
3519
  };
3026
3520
  engine.composioSdk.recordTriggerEvent(eventRecord);
3027
3521
  sendJson(res, injectionScan.safe ? 200 : 202, {
@@ -3073,6 +3567,107 @@ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing to
3073
3567
  sendJson(res, 200, { ok: true, workflow });
3074
3568
  return;
3075
3569
  }
3570
+ const wfHistoryMatch = pathname.match(/^\/api\/v1\/workflows\/([^/]+)\/history$/);
3571
+ if (wfHistoryMatch && req.method === "GET") {
3572
+ const wfId = decodeURIComponent(wfHistoryMatch[1]);
3573
+ const history = engine.getDurableWorkflowHistory(ctx, wfId);
3574
+ sendJson(res, 200, { ok: true, ...history });
3575
+ return;
3576
+ }
3577
+ const wfSignalMatch = pathname.match(/^\/api\/v1\/workflows\/([^/]+)\/signal$/);
3578
+ if (wfSignalMatch && req.method === "POST") {
3579
+ const wfId = decodeURIComponent(wfSignalMatch[1]);
3580
+ const body = JSON.parse((await readBody(req)) || "{}");
3581
+ const signalName = String(body.signalName || body.signal_name || body.name || "workflow.signal");
3582
+ const workflow = engine.signalDurableWorkflow(ctx, wfId, {
3583
+ signalName,
3584
+ payload: body.payload && typeof body.payload === "object"
3585
+ ? body.payload
3586
+ : body,
3587
+ });
3588
+ sendJson(res, 200, { ok: true, workflow });
3589
+ return;
3590
+ }
3591
+ const wfRollbackMatch = pathname.match(/^\/api\/v1\/workflows\/([^/]+)\/rollback$/);
3592
+ if (wfRollbackMatch && req.method === "POST") {
3593
+ const wfId = decodeURIComponent(wfRollbackMatch[1]);
3594
+ const body = JSON.parse((await readBody(req)) || "{}");
3595
+ const workflow = engine.rollbackDurableWorkflow(ctx, wfId, {
3596
+ reason: body.reason ? String(body.reason) : undefined,
3597
+ failedStep: body.failedStep || body.failed_step
3598
+ ? String(body.failedStep || body.failed_step)
3599
+ : undefined,
3600
+ });
3601
+ sendJson(res, 200, { ok: true, workflow });
3602
+ return;
3603
+ }
3604
+ if ((pathname === "/api/v1/governance/cedar" ||
3605
+ pathname === "/api/v1/policy/cedar") &&
3606
+ req.method === "GET") {
3607
+ const bundle = engine.getPolicyAsCodeBundle(ctx);
3608
+ sendJson(res, 200, { ok: true, ...bundle });
3609
+ return;
3610
+ }
3611
+ if ((pathname === "/api/v1/governance/cedar/evaluate" ||
3612
+ pathname === "/api/v1/policy/cedar/evaluate") &&
3613
+ req.method === "POST") {
3614
+ const body = JSON.parse((await readBody(req)) || "{}");
3615
+ const evaluated = await engine.evaluatePolicyAsCode(ctx, {
3616
+ toolSlug: String(body.toolSlug || body.tool_slug || body.action || "instant.web.search"),
3617
+ input: body.input && typeof body.input === "object"
3618
+ ? body.input
3619
+ : {},
3620
+ riskLevel: (body.riskLevel || body.risk_level),
3621
+ creditCost: typeof body.creditCost === "number"
3622
+ ? body.creditCost
3623
+ : typeof body.credit_cost === "number"
3624
+ ? body.credit_cost
3625
+ : undefined,
3626
+ lane: body.lane ? String(body.lane) : undefined,
3627
+ });
3628
+ sendJson(res, 200, { ok: true, ...evaluated });
3629
+ return;
3630
+ }
3631
+ if ((pathname === "/api/v1/telemetry/otlp" ||
3632
+ pathname === "/api/v1/governance/telemetry") &&
3633
+ req.method === "GET") {
3634
+ const limit = Number(url.searchParams.get("limit") || 25);
3635
+ const otel = engine.exportOtelTraces(ctx, limit);
3636
+ sendJson(res, 200, { ok: true, ...otel });
3637
+ return;
3638
+ }
3639
+ if (pathname === "/api/v1/playbooks" && req.method === "GET") {
3640
+ const playbooks = engine.listCommercialPlaybooks();
3641
+ sendJson(res, 200, {
3642
+ ok: true,
3643
+ count: playbooks.length,
3644
+ playbooks,
3645
+ });
3646
+ return;
3647
+ }
3648
+ const playbookRunMatch = pathname.match(/^\/api\/v1\/playbooks\/([^/]+)\/run$/);
3649
+ if ((playbookRunMatch || pathname === "/api/v1/playbooks/run") &&
3650
+ req.method === "POST") {
3651
+ const body = JSON.parse((await readBody(req)) || "{}");
3652
+ const slug = playbookRunMatch
3653
+ ? decodeURIComponent(playbookRunMatch[1])
3654
+ : String(body.slug || body.playbook || "");
3655
+ const result = engine.runCommercialPlaybook(ctx, {
3656
+ slug,
3657
+ input: body.input && typeof body.input === "object"
3658
+ ? body.input
3659
+ : body,
3660
+ });
3661
+ sendJson(res, 201, result);
3662
+ return;
3663
+ }
3664
+ if ((pathname === "/api/v1/analytics/roi" ||
3665
+ pathname === "/api/v1/billing/roi") &&
3666
+ req.method === "GET") {
3667
+ const roi = engine.getCommercialRoiReport(ctx);
3668
+ sendJson(res, 200, { ok: true, ...roi });
3669
+ return;
3670
+ }
3076
3671
  if (pathname === "/api/v1/governance/archive" && req.method === "GET") {
3077
3672
  const archive = engine.exportTamperEvidentArchive(ctx.workspace.id);
3078
3673
  sendJson(res, 200, { ok: true, ...archive });