@muxoai/sdk 1.2.3 → 1.4.2

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 (208) hide show
  1. package/.agents/plugins/marketplace.json +22 -0
  2. package/.claude-plugin/marketplace.json +32 -0
  3. package/.codex/config.toml +12 -0
  4. package/ARCHITECTURE.md +121 -0
  5. package/README.md +85 -31
  6. package/dist/clerk.d.ts +35 -0
  7. package/dist/clerk.js +201 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +2380 -0
  10. package/dist/composio-catalog.d.ts +234 -0
  11. package/dist/composio-catalog.js +1894 -0
  12. package/dist/config.d.ts +36 -0
  13. package/dist/config.js +115 -0
  14. package/dist/durable-workflow.d.ts +45 -0
  15. package/dist/durable-workflow.js +90 -0
  16. package/dist/engine.d.ts +465 -0
  17. package/dist/engine.js +2560 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.js +7 -0
  20. package/dist/mcp-schema.d.ts +286 -0
  21. package/dist/mcp-schema.js +2559 -0
  22. package/dist/mcp.d.ts +22 -0
  23. package/dist/mcp.js +1356 -0
  24. package/dist/memory-graph.d.ts +21 -0
  25. package/dist/memory-graph.js +173 -0
  26. package/dist/nl.d.ts +34 -0
  27. package/dist/nl.js +756 -0
  28. package/dist/oauth.d.ts +32 -0
  29. package/dist/oauth.js +134 -0
  30. package/dist/policy.d.ts +190 -0
  31. package/dist/policy.js +1597 -0
  32. package/dist/providers/composio-instant.d.ts +18 -0
  33. package/dist/providers/composio-instant.js +1169 -0
  34. package/dist/providers/composio-sdk.d.ts +162 -0
  35. package/dist/providers/composio-sdk.js +1395 -0
  36. package/dist/providers/microsoft-governance.d.ts +152 -0
  37. package/dist/providers/microsoft-governance.js +1062 -0
  38. package/dist/providers/stripe-billing.d.ts +83 -0
  39. package/dist/providers/stripe-billing.js +611 -0
  40. package/dist/providers/stripe-projects.d.ts +508 -0
  41. package/dist/providers/stripe-projects.js +3188 -0
  42. package/dist/providers/supermemory.d.ts +160 -0
  43. package/dist/providers/supermemory.js +778 -0
  44. package/dist/provisioning-managed.d.ts +50 -0
  45. package/dist/provisioning-managed.js +155 -0
  46. package/dist/sdk.d.ts +951 -0
  47. package/dist/sdk.js +748 -0
  48. package/dist/server.d.ts +10 -0
  49. package/dist/server.js +3575 -0
  50. package/dist/store.d.ts +348 -0
  51. package/dist/store.js +2323 -0
  52. package/dist/types.d.ts +593 -0
  53. package/dist/types.js +1 -0
  54. package/dist/ui/console-html.d.ts +64 -0
  55. package/dist/ui/console-html.js +3857 -0
  56. package/dist/ui/design-tokens.d.ts +177 -0
  57. package/dist/ui/design-tokens.js +210 -0
  58. package/dist/version.d.ts +1 -0
  59. package/dist/version.js +1 -0
  60. package/package.json +68 -7
  61. package/plugins/muxo/.app.json +8 -0
  62. package/plugins/muxo/.claude-plugin/plugin.json +16 -0
  63. package/plugins/muxo/.codex-plugin/plugin.json +64 -0
  64. package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
  65. package/plugins/muxo/.mcp.json +8 -0
  66. package/plugins/muxo/assets/logo.png +0 -0
  67. package/plugins/muxo/assets/logo.svg +6 -0
  68. package/plugins/muxo/hooks/hooks.json +27 -0
  69. package/plugins/muxo/hooks/session-start.sh +97 -0
  70. package/plugins/muxo/hooks/user-prompt-submit.sh +58 -0
  71. package/plugins/muxo/mcp.json +28 -0
  72. package/plugins/muxo/plugin.json +116 -0
  73. package/plugins/muxo/skills/muxo/SKILL.md +153 -0
  74. package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
  75. package/plugins/muxo/skills/muxo/references/cli.md +63 -0
  76. package/plugins/muxo/skills/muxo/references/mcp.md +37 -0
  77. package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
  78. package/plugins/muxo/skills/setup/SKILL.md +42 -0
  79. package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
  80. package/src/catalogues/stripe-projects-catalog.json +9972 -0
  81. package/vendor/microsoft-governance/README.md +363 -0
  82. package/vendor/microsoft-governance/dist/audit.d.ts +26 -0
  83. package/vendor/microsoft-governance/dist/audit.d.ts.map +1 -0
  84. package/vendor/microsoft-governance/dist/audit.js +107 -0
  85. package/vendor/microsoft-governance/dist/audit.js.map +1 -0
  86. package/vendor/microsoft-governance/dist/cascade-containment.d.ts +84 -0
  87. package/vendor/microsoft-governance/dist/cascade-containment.d.ts.map +1 -0
  88. package/vendor/microsoft-governance/dist/cascade-containment.js +387 -0
  89. package/vendor/microsoft-governance/dist/cascade-containment.js.map +1 -0
  90. package/vendor/microsoft-governance/dist/client.d.ts +34 -0
  91. package/vendor/microsoft-governance/dist/client.d.ts.map +1 -0
  92. package/vendor/microsoft-governance/dist/client.js +112 -0
  93. package/vendor/microsoft-governance/dist/client.js.map +1 -0
  94. package/vendor/microsoft-governance/dist/context-poisoning.d.ts +58 -0
  95. package/vendor/microsoft-governance/dist/context-poisoning.d.ts.map +1 -0
  96. package/vendor/microsoft-governance/dist/context-poisoning.js +410 -0
  97. package/vendor/microsoft-governance/dist/context-poisoning.js.map +1 -0
  98. package/vendor/microsoft-governance/dist/credential-vault.d.ts +213 -0
  99. package/vendor/microsoft-governance/dist/credential-vault.d.ts.map +1 -0
  100. package/vendor/microsoft-governance/dist/credential-vault.js +567 -0
  101. package/vendor/microsoft-governance/dist/credential-vault.js.map +1 -0
  102. package/vendor/microsoft-governance/dist/discovery.d.ts +80 -0
  103. package/vendor/microsoft-governance/dist/discovery.d.ts.map +1 -0
  104. package/vendor/microsoft-governance/dist/discovery.js +404 -0
  105. package/vendor/microsoft-governance/dist/discovery.js.map +1 -0
  106. package/vendor/microsoft-governance/dist/encryption/channel.d.ts +29 -0
  107. package/vendor/microsoft-governance/dist/encryption/channel.d.ts.map +1 -0
  108. package/vendor/microsoft-governance/dist/encryption/channel.js +78 -0
  109. package/vendor/microsoft-governance/dist/encryption/channel.js.map +1 -0
  110. package/vendor/microsoft-governance/dist/encryption/index.d.ts +9 -0
  111. package/vendor/microsoft-governance/dist/encryption/index.d.ts.map +1 -0
  112. package/vendor/microsoft-governance/dist/encryption/index.js +23 -0
  113. package/vendor/microsoft-governance/dist/encryption/index.js.map +1 -0
  114. package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts +267 -0
  115. package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts.map +1 -0
  116. package/vendor/microsoft-governance/dist/encryption/mesh-client.js +1001 -0
  117. package/vendor/microsoft-governance/dist/encryption/mesh-client.js.map +1 -0
  118. package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts +38 -0
  119. package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts.map +1 -0
  120. package/vendor/microsoft-governance/dist/encryption/ratchet.js +223 -0
  121. package/vendor/microsoft-governance/dist/encryption/ratchet.js.map +1 -0
  122. package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts +136 -0
  123. package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts.map +1 -0
  124. package/vendor/microsoft-governance/dist/encryption/registry-client.js +369 -0
  125. package/vendor/microsoft-governance/dist/encryption/registry-client.js.map +1 -0
  126. package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts +80 -0
  127. package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts.map +1 -0
  128. package/vendor/microsoft-governance/dist/encryption/x3dh.js +238 -0
  129. package/vendor/microsoft-governance/dist/encryption/x3dh.js.map +1 -0
  130. package/vendor/microsoft-governance/dist/framework-adapter.d.ts +63 -0
  131. package/vendor/microsoft-governance/dist/framework-adapter.d.ts.map +1 -0
  132. package/vendor/microsoft-governance/dist/framework-adapter.js +253 -0
  133. package/vendor/microsoft-governance/dist/framework-adapter.js.map +1 -0
  134. package/vendor/microsoft-governance/dist/identity.d.ts +124 -0
  135. package/vendor/microsoft-governance/dist/identity.d.ts.map +1 -0
  136. package/vendor/microsoft-governance/dist/identity.js +446 -0
  137. package/vendor/microsoft-governance/dist/identity.js.map +1 -0
  138. package/vendor/microsoft-governance/dist/index.d.ts +39 -0
  139. package/vendor/microsoft-governance/dist/index.d.ts.map +1 -0
  140. package/vendor/microsoft-governance/dist/index.js +95 -0
  141. package/vendor/microsoft-governance/dist/index.js.map +1 -0
  142. package/vendor/microsoft-governance/dist/kill-switch.d.ts +22 -0
  143. package/vendor/microsoft-governance/dist/kill-switch.d.ts.map +1 -0
  144. package/vendor/microsoft-governance/dist/kill-switch.js +60 -0
  145. package/vendor/microsoft-governance/dist/kill-switch.js.map +1 -0
  146. package/vendor/microsoft-governance/dist/lifecycle.d.ts +50 -0
  147. package/vendor/microsoft-governance/dist/lifecycle.d.ts.map +1 -0
  148. package/vendor/microsoft-governance/dist/lifecycle.js +113 -0
  149. package/vendor/microsoft-governance/dist/lifecycle.js.map +1 -0
  150. package/vendor/microsoft-governance/dist/mcp.d.ts +41 -0
  151. package/vendor/microsoft-governance/dist/mcp.d.ts.map +1 -0
  152. package/vendor/microsoft-governance/dist/mcp.js +233 -0
  153. package/vendor/microsoft-governance/dist/mcp.js.map +1 -0
  154. package/vendor/microsoft-governance/dist/metrics.d.ts +123 -0
  155. package/vendor/microsoft-governance/dist/metrics.d.ts.map +1 -0
  156. package/vendor/microsoft-governance/dist/metrics.js +289 -0
  157. package/vendor/microsoft-governance/dist/metrics.js.map +1 -0
  158. package/vendor/microsoft-governance/dist/oci-manifest.d.ts +49 -0
  159. package/vendor/microsoft-governance/dist/oci-manifest.d.ts.map +1 -0
  160. package/vendor/microsoft-governance/dist/oci-manifest.js +182 -0
  161. package/vendor/microsoft-governance/dist/oci-manifest.js.map +1 -0
  162. package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts +15 -0
  163. package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts.map +1 -0
  164. package/vendor/microsoft-governance/dist/policy-backends/cedar.js +88 -0
  165. package/vendor/microsoft-governance/dist/policy-backends/cedar.js.map +1 -0
  166. package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts +17 -0
  167. package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts.map +1 -0
  168. package/vendor/microsoft-governance/dist/policy-backends/opa.js +74 -0
  169. package/vendor/microsoft-governance/dist/policy-backends/opa.js.map +1 -0
  170. package/vendor/microsoft-governance/dist/policy.d.ts +77 -0
  171. package/vendor/microsoft-governance/dist/policy.d.ts.map +1 -0
  172. package/vendor/microsoft-governance/dist/policy.js +760 -0
  173. package/vendor/microsoft-governance/dist/policy.js.map +1 -0
  174. package/vendor/microsoft-governance/dist/prompt-defense.d.ts +37 -0
  175. package/vendor/microsoft-governance/dist/prompt-defense.d.ts.map +1 -0
  176. package/vendor/microsoft-governance/dist/prompt-defense.js +229 -0
  177. package/vendor/microsoft-governance/dist/prompt-defense.js.map +1 -0
  178. package/vendor/microsoft-governance/dist/protocol-facets.d.ts +87 -0
  179. package/vendor/microsoft-governance/dist/protocol-facets.d.ts.map +1 -0
  180. package/vendor/microsoft-governance/dist/protocol-facets.js +728 -0
  181. package/vendor/microsoft-governance/dist/protocol-facets.js.map +1 -0
  182. package/vendor/microsoft-governance/dist/rings.d.ts +17 -0
  183. package/vendor/microsoft-governance/dist/rings.d.ts.map +1 -0
  184. package/vendor/microsoft-governance/dist/rings.js +61 -0
  185. package/vendor/microsoft-governance/dist/rings.js.map +1 -0
  186. package/vendor/microsoft-governance/dist/sandbox.d.ts +61 -0
  187. package/vendor/microsoft-governance/dist/sandbox.d.ts.map +1 -0
  188. package/vendor/microsoft-governance/dist/sandbox.js +199 -0
  189. package/vendor/microsoft-governance/dist/sandbox.js.map +1 -0
  190. package/vendor/microsoft-governance/dist/surface-parity.d.ts +45 -0
  191. package/vendor/microsoft-governance/dist/surface-parity.d.ts.map +1 -0
  192. package/vendor/microsoft-governance/dist/surface-parity.js +216 -0
  193. package/vendor/microsoft-governance/dist/surface-parity.js.map +1 -0
  194. package/vendor/microsoft-governance/dist/trust.d.ts +28 -0
  195. package/vendor/microsoft-governance/dist/trust.d.ts.map +1 -0
  196. package/vendor/microsoft-governance/dist/trust.js +209 -0
  197. package/vendor/microsoft-governance/dist/trust.js.map +1 -0
  198. package/vendor/microsoft-governance/dist/types.d.ts +422 -0
  199. package/vendor/microsoft-governance/dist/types.d.ts.map +1 -0
  200. package/vendor/microsoft-governance/dist/types.js +29 -0
  201. package/vendor/microsoft-governance/dist/types.js.map +1 -0
  202. package/vendor/microsoft-governance/dist/verify.d.ts +135 -0
  203. package/vendor/microsoft-governance/dist/verify.d.ts.map +1 -0
  204. package/vendor/microsoft-governance/dist/verify.js +356 -0
  205. package/vendor/microsoft-governance/dist/verify.js.map +1 -0
  206. package/vendor/microsoft-governance/package.json +64 -0
  207. package/index.d.ts +0 -177
  208. package/index.js +0 -1
package/dist/server.js ADDED
@@ -0,0 +1,3575 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import http from "node:http";
4
+ import path from "node:path";
5
+ import { getConfig, ROOT_DIR } from "./config.js";
6
+ import { MuxoEngine } from "./engine.js";
7
+ import { getMuxoMcpSkills, handleJsonRpcMcpRequest, MUXO_MCP_SERVER_INSTRUCTIONS, MUXO_MCP_TOOLS, } from "./mcp.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
+ import { MuxoStore } from "./store.js";
10
+ import { managedProviderConnectionList, managedProviderConnectionRequest, managedProviderConnectionUnlink, managedResource, managedResourceAction, } from "./provisioning-managed.js";
11
+ import { DEFAULT_WORKSPACE_POLICY, describePolicyInPlainEnglish, sanitizePolicyPatch, } from "./policy.js";
12
+ import { parseNaturalLanguageMemoryInstruction } from "./providers/supermemory.js";
13
+ import { MUXO_VERSION } from "./version.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";
16
+ import { fetchClerkProfile, looksLikeJwt, readClerkConfig, verifyClerkSessionToken, } from "./clerk.js";
17
+ import { DEFAULT_OAUTH_SCOPE, OAUTH_ACCESS_TOKEN_TTL_SECONDS, OAUTH_CODE_TTL_MS, oauthConnectionKeyName, oauthRedirectOrigin, oauthSecretToken, parseAuthorizeRequest, parseOAuthFormBody, verifyPkce, } from "./oauth.js";
18
+ function renderSkillMarkdown(baseUrl) {
19
+ return `---
20
+ name: muxo
21
+ description: Discover, plan, and execute governed actions across Connected Apps (app.*), Instant Tools (instant.*), Cloud Infrastructure (resource.*), Workspace Memory, and Safety Guardrails through Muxo.
22
+ ---
23
+
24
+ # Muxo — Unified Agent Execution, Infrastructure, Memory & Safety Platform
25
+
26
+ Muxo provides one unified interface across 4 capabilities so agents and users only ever interact with Muxo:
27
+
28
+ 1. **Instant Tools (\`instant.*\`)**: Zero-OAuth tools ready immediately (\`instant.web.search\`, \`instant.web.scrape\`, \`instant.web.crawl\`, \`instant.seo.audit\`, \`instant.hackernews.top_stories\`, \`instant.github.repo_intel\`, \`instant.dns.lookup\`).
29
+ 2. **Connected Apps (\`app.*\`)**: Managed OAuth SaaS integrations (\`app.github.*\`, \`app.gmail.*\`, \`app.slack.*\`, \`app.notion.*\`, \`app.linear.*\`, etc.).
30
+ 3. **Cloud Infrastructure (\`resource.*\`)**: Database, cache, storage, and hosting provisioning (\`resource.neon.*\`, \`resource.supabase.*\`, \`resource.turso.*\`, \`resource.cloudflare.*\`, \`resource.vercel.*\`, etc.) with AES-256-GCM secret custody in Muxo Vault.
31
+ 4. **Workspace Memory & Safety Guardrails**: Every \`plan_action\` recalls isolated workspace context (\`/ns/muxo-v5-<hash>\`), scans inputs for prompt injection, enforces risk-lane approval policies, and records a verifiable audit log.
32
+
33
+ ## Installable Muxo Plugin (Claude Code, OpenAI Codex, Claude Desktop & ChatGPT)
34
+
35
+ > **Requirement:** \`@muxoai/cli\` requires **Node.js 22+** (\`node >= 22.0.0\`).
36
+
37
+ \`\`\`bash
38
+ # Install Muxo CLI & sign in against the hosted workspace (Node.js 22+)
39
+ npm install -g @muxoai/cli@latest
40
+ muxo login --signup --name "my-agent"
41
+
42
+ # Claude Code marketplace (muxo@muxo):
43
+ claude plugin marketplace add ${baseUrl}/.claude-plugin/marketplace.json
44
+ claude plugin install muxo@muxo
45
+
46
+ # OpenAI Codex: add a Git repo or local marketplace directory containing .agents/plugins/marketplace.json,
47
+ # or configure [mcp_servers.muxo] in ~/.codex/config.toml (url = "${baseUrl}/mcp")
48
+
49
+ # Claude Desktop:
50
+ # - Hosted MCP: Settings -> Connectors -> Add custom connector -> ${baseUrl}/mcp
51
+ # - Local stdio server: configure npx -y @muxoai/cli@latest mcp in claude_desktop_config.json
52
+
53
+ # ChatGPT:
54
+ # - Plugins -> + -> Add custom MCP server -> ${baseUrl}/mcp (OAuth supports CIMD or DCR)
55
+
56
+ # Link OAuth apps with live browser handoff + polling recovery
57
+ muxo link github --wait
58
+
59
+ # Plugin manifests served by this instance:
60
+ # - ${baseUrl}/.agents/plugins/marketplace.json (OpenAI Codex local/Git marketplace manifest)
61
+ # - ${baseUrl}/.claude-plugin/marketplace.json (Claude Code marketplace manifest)
62
+ # - ${baseUrl}/api/v1/plugin
63
+ \`\`\`
64
+
65
+ ## CLI-First Autonomous Agent 2-Call Signup
66
+
67
+ \`\`\`bash
68
+ # 1. Create an autonomous workspace + mk_live_* API key
69
+ curl -sX POST ${baseUrl}/api/v1/agents/signup \\
70
+ -H "Content-Type: application/json" \\
71
+ -d '{"agentName":"MyAgent","harness":"claude-code"}'
72
+
73
+ # 2. (Optional) Generate a human owner claim link
74
+ curl -sX POST ${baseUrl}/api/v1/agents/claim \\
75
+ -H "Authorization: Bearer <mk_live_key>" \\
76
+ -H "Content-Type: application/json" \\
77
+ -d '{"email":"founder@company.com"}'
78
+ \`\`\`
79
+
80
+ ## Endpoint & Authentication
81
+
82
+ - **MCP Endpoint (Streamable HTTP)**: \`${baseUrl}/mcp\`
83
+ - **OAuth Callback Handoff**: \`${baseUrl}/oauth/callback\`
84
+ - **REST API Base**: \`${baseUrl}/api/v1\`
85
+ - **Marketing & Provider Canvas (muxo.ai)**: \`${baseUrl}/landing\`
86
+ - **Documentation (docs.muxo.ai)**: \`https://docs.muxo.ai\`
87
+ - **Authorization Header**: \`Authorization: Bearer mk_live_<your_key>\`
88
+
89
+ ## 19 Canonical MCP Operations
90
+
91
+ ${MUXO_MCP_TOOLS.map((t, i) => `${i + 1}. \`${t.name}\` — ${t.description}`).join("\n")}
92
+
93
+ ## Standard Execution & OAuth Handoff Workflow
94
+
95
+ 1. **Discover**: Call \`search_tools\` with a keyword or lane filter.
96
+ 2. **Inspect**: Call \`inspect_tool\` to read the tool's \`inputSchema\`, \`riskLevel\`, and \`creditCost\`.
97
+ 3. **Plan**: Call \`plan_action({ tool_slug, input, reason })\` to receive a \`plan_*\` ID.
98
+ - If the tool is an unconnected \`app.<provider>.*\` action, \`plan_action\` automatically attaches \`authHandoff.redirectUrl\` and a structured \`humanHandoff\` block. Show \`humanHandoff.url\` to the user, then call \`wait_for_connection({ provider })\` until \`status === "active"\`.
99
+ - If the plan's \`approvalStatus\` is \`pending_approval\`, \`plan_action\` attaches a \`humanHandoff\` of kind \`approval\` whose \`url\` (\`${baseUrl}/approve/<plan_id>\`) opens a one-click **Review & approve** page for the workspace owner. Send that link to the user and stop until they confirm.
100
+ - Always follow \`llmGuidance.nextToolCalls\` for the exact next tool call sequence.
101
+ 4. **Execute**: Call the matching risk lane tool (\`execute_read\`, \`execute_write\`, \`execute_admin\`, \`execute_financial\`, \`execute_provisioning\`, or \`execute_destructive\`) with \`{ plan_id }\`.
102
+ 5. **Verify Receipt**: Every run issues an HMAC-SHA256 chained cryptographic receipt (\`rcpt_*\`) verifiable via \`get_receipt\`.
103
+
104
+ ## Plain-English & Conditional Guardrails (LLM & MCP Harness Native)
105
+
106
+ Users and LLM harnesses can set safety rules in natural language or structured parameter rules; Muxo compiles and enforces them across all tool calls:
107
+
108
+ - "Only allow sending emails to @muxo.ai" → conditional recipient domain guardrail (\`must_end_with: @muxo.ai\`)
109
+ - "Require approval for Stripe refunds over $50, auto-approve under $50" → conditional threshold guardrail (\`max_number: 50\`, \`unit: "usd"\`, \`action: "require_approval"\`, \`elseAction: "auto_approve"\`)
110
+ - "Only post Slack messages to #ops-alerts or #deployments" → channel allowlist guardrail
111
+ - "Never spend more than 25 credits per run" → workspace per-run credit cap
112
+
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
127
+
128
+ ## Active Supermemory (teach Muxo once, auto-fill forever)
129
+
130
+ Teach preferences in plain English and Muxo auto-fills matching tool inputs on every future plan:
131
+
132
+ - "Hey Muxo, remember our default GitHub repo is acme/api and default region is us-east-1"
133
+ - "Hey Muxo, forget our staging redis"
134
+ - "What do you remember?"
135
+
136
+ \`plan_action\` recalls workspace memory before governance checks and reports \`memoryContext.autoFilledInput\` plus \`activeDefaults\` so callers can see exactly which parameters were filled from memory.
137
+ `;
138
+ }
139
+ async function readBody(req) {
140
+ const chunks = [];
141
+ for await (const chunk of req) {
142
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
143
+ }
144
+ return Buffer.concat(chunks).toString("utf8");
145
+ }
146
+ function sendJson(res, status, payload, extraHeaders) {
147
+ const body = JSON.stringify(payload, null, 2);
148
+ res.writeHead(status, {
149
+ "Content-Type": "application/json; charset=utf-8",
150
+ "Access-Control-Allow-Origin": "*",
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",
153
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
154
+ ...(extraHeaders || {}),
155
+ });
156
+ res.end(body);
157
+ }
158
+ export function createMuxoServer(customConfig) {
159
+ const config = { ...getConfig(), ...customConfig };
160
+ const store = new MuxoStore(config.dbPath, config.vaultMasterKey);
161
+ const engine = new MuxoEngine(config, store);
162
+ const bootstrap = store.ensureDefaultWorkspace();
163
+ // OAuth 2.1 DCR + PKCE Authorization Code store for OpenAI Plugin / MCP clients
164
+ const oauthClients = new Map();
165
+ oauthClients.set("muxo-openai-plugin", {
166
+ client_id: "muxo-openai-plugin",
167
+ client_name: "Muxo Official OpenAI Plugin",
168
+ redirect_uris: [
169
+ "https://chatgpt.com/connector_platform_oauth_redirect",
170
+ "https://platform.openai.com/apps-manage/oauth",
171
+ "http://127.0.0.1:3118/oauth/callback",
172
+ "http://localhost:3118/oauth/callback",
173
+ ],
174
+ grant_types: ["authorization_code", "refresh_token"],
175
+ response_types: ["code"],
176
+ token_endpoint_auth_method: "none",
177
+ client_id_issued_at: Math.floor(Date.now() / 1000),
178
+ });
179
+ const oauthCodes = new Map();
180
+ const clerkEnv = config.clerkEnv ?? {};
181
+ const clerkConfig = () => readClerkConfig(clerkEnv, config.baseUrl);
182
+ const ownerEmails = () => String(clerkEnv.MUXO_OWNER_EMAILS || "")
183
+ .split(",")
184
+ .map((s) => s.trim().toLowerCase())
185
+ .filter(Boolean);
186
+ const bearerTokenOf = (req) => {
187
+ const header = String(req.headers.authorization || "").trim();
188
+ return /^bearer\s+/i.test(header) ? header.replace(/^bearer\s+/i, "").trim() : null;
189
+ };
190
+ /**
191
+ * Resolves the caller from a workspace API key, a console session token, or a Clerk session
192
+ * JWT. A bearer token that resolves to nothing is "invalid" and must never fall back to a
193
+ * default workspace.
194
+ */
195
+ const authenticate = async (req) => {
196
+ const token = bearerTokenOf(req);
197
+ if (token === null)
198
+ return { kind: "anonymous" };
199
+ if (!token)
200
+ return { kind: "invalid" };
201
+ const wsOverride = String(req.headers["x-muxo-workspace-id"] || "").trim() || null;
202
+ const resolved = store.resolveAuthToken(token, wsOverride);
203
+ if (resolved)
204
+ return { kind: "ok", ctx: resolved, via: resolved.authType };
205
+ const clerk = looksLikeJwt(token) ? clerkConfig() : null;
206
+ if (!clerk)
207
+ return { kind: "invalid" };
208
+ const session = await verifyClerkSessionToken(token, clerk);
209
+ if (!session)
210
+ return { kind: "invalid" };
211
+ let user = store.getClerkLinkedUser(session.userId);
212
+ if (!user) {
213
+ const profile = clerk.secretKey ? await fetchClerkProfile(session.userId, clerk.secretKey) : null;
214
+ const identity = {
215
+ email: profile?.email || session.email || null,
216
+ emailVerified: profile ? profile.emailVerified : Boolean(session.emailVerified),
217
+ name: profile?.name || null,
218
+ };
219
+ const joinsPrimary = Boolean(identity.email && identity.emailVerified && ownerEmails().includes(identity.email.toLowerCase()));
220
+ user = store.provisionClerkUser(session.userId, identity, joinsPrimary ? bootstrap.workspace.id : null);
221
+ }
222
+ const targetWsId = wsOverride && store.userCanAccessWorkspace(user.id, wsOverride) ? wsOverride : user.workspaceId;
223
+ const workspace = store.getWorkspace(targetWsId) || store.getWorkspace(user.workspaceId);
224
+ if (!workspace)
225
+ return { kind: "invalid" };
226
+ return {
227
+ kind: "ok",
228
+ ctx: { user: { ...user, workspaceId: workspace.id }, workspace, actorId: user.id, authType: "session" },
229
+ via: "clerk",
230
+ clerkUserId: session.userId,
231
+ clerkSessionEmail: session.email && session.emailVerified ? session.email : null,
232
+ };
233
+ };
234
+ /** Without Clerk, a local server serves anonymous callers from its primary workspace. */
235
+ const localFallbackContext = (req) => {
236
+ const wsOverride = String(req.headers["x-muxo-workspace-id"] || "").trim() || null;
237
+ const fallbackWsId = wsOverride && store.userCanAccessWorkspace(bootstrap.user.id, wsOverride)
238
+ ? wsOverride
239
+ : bootstrap.user.workspaceId || bootstrap.workspace.id;
240
+ const ws = store.getWorkspace(fallbackWsId) || store.getWorkspace(bootstrap.workspace.id);
241
+ const user = store.getUser(ws.ownerUserId) || bootstrap.user;
242
+ return {
243
+ user,
244
+ workspace: ws,
245
+ actorId: user.id,
246
+ authType: "session",
247
+ };
248
+ };
249
+ const anonymousContext = () => {
250
+ const epoch = new Date(0).toISOString();
251
+ return {
252
+ user: { id: "usr_anonymous", email: "", name: "Anonymous", workspaceId: "ws_public", role: "member", createdAt: epoch },
253
+ workspace: {
254
+ id: "ws_public",
255
+ name: "Public catalog",
256
+ slug: "public",
257
+ ownerUserId: "usr_anonymous",
258
+ tier: "free",
259
+ monthlyActionsLimit: 0,
260
+ monthlyActionsUsed: 0,
261
+ creditsBalance: 0,
262
+ stripeCustomerId: null,
263
+ stripeSubscriptionId: null,
264
+ projectsEnvName: "muxo-public",
265
+ policy: { ...DEFAULT_WORKSPACE_POLICY },
266
+ createdAt: epoch,
267
+ updatedAt: epoch,
268
+ },
269
+ actorId: "usr_anonymous",
270
+ authType: "session",
271
+ };
272
+ };
273
+ const requestOriginOf = (req) => {
274
+ const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
275
+ return req.headers.host ? `${proto}://${req.headers.host}` : config.baseUrl;
276
+ };
277
+ const sendAuthRequired = (req, res, pathname, message) => {
278
+ const origin = requestOriginOf(req);
279
+ const resourceMetadata = pathname === "/mcp"
280
+ ? `${origin}/.well-known/oauth-protected-resource/mcp`
281
+ : `${origin}/.well-known/oauth-protected-resource`;
282
+ sendJson(res, 401, {
283
+ ok: false,
284
+ code: "auth_required",
285
+ error: message ||
286
+ `Sign in required. Send a Muxo API key (Authorization: Bearer mk_live_...) or sign in at ${origin}/login.`,
287
+ }, { "WWW-Authenticate": `Bearer resource_metadata="${resourceMetadata}"` });
288
+ };
289
+ /** Emails the signed-in person has verified; agent API keys never count as a person. */
290
+ const verifiedEmailsOf = async (auth) => {
291
+ if (auth.kind !== "ok")
292
+ return [];
293
+ if (auth.via === "session")
294
+ return auth.ctx.user.email ? [auth.ctx.user.email.toLowerCase()] : [];
295
+ if (auth.via !== "clerk")
296
+ return [];
297
+ const secretKey = clerkConfig()?.secretKey;
298
+ if (secretKey && auth.clerkUserId) {
299
+ const profile = await fetchClerkProfile(auth.clerkUserId, secretKey);
300
+ if (profile)
301
+ return profile.verifiedEmails;
302
+ }
303
+ return auth.clerkSessionEmail ? [auth.clerkSessionEmail.toLowerCase()] : [];
304
+ };
305
+ const signedInPerson = (auth) => auth.kind === "ok" && auth.via !== "api_key";
306
+ /**
307
+ * Resolves the caller of a protected route, or answers 401 and returns null. Public routes
308
+ * accept anonymous callers with an empty catalog context once Clerk sign-in is configured.
309
+ */
310
+ const requireContext = async (req, res, pathname, publicRoute = false) => {
311
+ const auth = await authenticate(req);
312
+ if (auth.kind === "ok")
313
+ return { ctx: auth.ctx, auth };
314
+ if (auth.kind === "invalid") {
315
+ sendAuthRequired(req, res, pathname);
316
+ return null;
317
+ }
318
+ if (clerkConfig()) {
319
+ if (publicRoute)
320
+ return { ctx: anonymousContext(), auth };
321
+ sendAuthRequired(req, res, pathname);
322
+ return null;
323
+ }
324
+ return { ctx: localFallbackContext(req), auth };
325
+ };
326
+ const consentPageHeaders = {
327
+ "Content-Type": "text/html; charset=utf-8",
328
+ "Cache-Control": "no-store",
329
+ "Content-Security-Policy": "frame-ancestors 'none'",
330
+ "X-Frame-Options": "DENY",
331
+ "Referrer-Policy": "no-referrer",
332
+ };
333
+ const server = http.createServer(async (req, res) => {
334
+ if (req.method === "OPTIONS") {
335
+ res.writeHead(204, {
336
+ "Access-Control-Allow-Origin": "*",
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",
339
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
340
+ });
341
+ res.end();
342
+ return;
343
+ }
344
+ const url = new URL(req.url || "/", config.baseUrl);
345
+ const pathname = url.pathname;
346
+ try {
347
+ // Public Discovery & Health Endpoints
348
+ if (pathname === "/health" && req.method === "GET") {
349
+ sendJson(res, 200, {
350
+ ok: true,
351
+ status: "ready",
352
+ service: "muxo-platform",
353
+ version: MUXO_VERSION,
354
+ environment: "production",
355
+ live_execution: true,
356
+ customer_billing: true,
357
+ supplier_spending: true,
358
+ fundingMode: "muxo_funded",
359
+ lanes: {
360
+ composio_sdk: Boolean(config.composioApiKey),
361
+ composio_instant: true,
362
+ stripe_projects: true,
363
+ stripe_billing: Boolean(config.stripeSecretKey),
364
+ supermemory_v5: engine.supermemory.isEnabled(),
365
+ microsoft_governance: true,
366
+ },
367
+ billing: {
368
+ subscriptions: "configured",
369
+ prepaid: "configured",
370
+ resources: "configured",
371
+ merchantOfRecord: "muxo_pays_suppliers",
372
+ },
373
+ surfaces: {
374
+ consoleUrl: `${config.baseUrl}/`,
375
+ landingUrl: `${config.baseUrl}/landing`,
376
+ publicWebUrl: config.publicWebUrl,
377
+ docsUrl: config.docsUrl,
378
+ },
379
+ mcpToolsCount: MUXO_MCP_TOOLS.length,
380
+ });
381
+ return;
382
+ }
383
+ if (pathname === "/.well-known/jwks.json" && req.method === "GET") {
384
+ sendJson(res, 200, {
385
+ keys: [
386
+ {
387
+ kty: "OKP",
388
+ crv: "Ed25519",
389
+ use: "sig",
390
+ alg: "EdDSA",
391
+ kid: "muxo-governance-ed25519-v1",
392
+ },
393
+ ],
394
+ });
395
+ return;
396
+ }
397
+ if ((pathname === "/.well-known/oauth-protected-resource" ||
398
+ pathname === "/.well-known/oauth-protected-resource/mcp") &&
399
+ req.method === "GET") {
400
+ const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
401
+ const requestOrigin = req.headers.host
402
+ ? `${proto}://${req.headers.host}`
403
+ : config.baseUrl;
404
+ const resource = pathname === "/.well-known/oauth-protected-resource/mcp"
405
+ ? `${requestOrigin}/mcp`
406
+ : requestOrigin;
407
+ sendJson(res, 200, {
408
+ resource,
409
+ authorization_servers: [requestOrigin],
410
+ bearer_methods_supported: ["header"],
411
+ scopes_supported: ["mcp:tools", "muxo:mcp", "muxo:read", "muxo:execute", "muxo:provision"],
412
+ resource_documentation: `${config.docsUrl}/quickstart`,
413
+ mcp_endpoint: `${requestOrigin}/mcp`,
414
+ });
415
+ return;
416
+ }
417
+ if ((pathname === "/.well-known/oauth-authorization-server" ||
418
+ pathname === "/.well-known/openid-configuration" ||
419
+ pathname === "/.well-known/oauth-authorization-server/mcp") &&
420
+ req.method === "GET") {
421
+ const proto = String(req.headers["x-forwarded-proto"] || "http").split(",")[0].trim();
422
+ const requestOrigin = req.headers.host
423
+ ? `${proto}://${req.headers.host}`
424
+ : config.baseUrl;
425
+ sendJson(res, 200, {
426
+ issuer: requestOrigin,
427
+ authorization_endpoint: `${requestOrigin}/oauth/authorize`,
428
+ token_endpoint: `${requestOrigin}/oauth/token`,
429
+ registration_endpoint: `${requestOrigin}/oauth/register`,
430
+ userinfo_endpoint: `${requestOrigin}/oauth/userinfo`,
431
+ jwks_uri: `${requestOrigin}/.well-known/jwks.json`,
432
+ scopes_supported: [
433
+ "mcp:tools",
434
+ "muxo:mcp",
435
+ "muxo:read",
436
+ "muxo:execute",
437
+ "muxo:provision",
438
+ "openid",
439
+ "profile",
440
+ "email",
441
+ ],
442
+ response_types_supported: ["code"],
443
+ grant_types_supported: ["authorization_code", "refresh_token"],
444
+ token_endpoint_auth_methods_supported: [
445
+ "none",
446
+ "client_secret_post",
447
+ "client_secret_basic",
448
+ ],
449
+ code_challenge_methods_supported: ["S256", "plain"],
450
+ client_id_metadata_document_supported: true,
451
+ authorization_response_iss_parameter_supported: true,
452
+ service_documentation: `${config.docsUrl}/quickstart`,
453
+ });
454
+ return;
455
+ }
456
+ if (pathname === "/.well-known/openai-apps-challenge" && req.method === "GET") {
457
+ const challenge = process.env.OPENAI_APPS_CHALLENGE ||
458
+ "muxo-openai-apps-domain-verified";
459
+ res.writeHead(200, {
460
+ "Content-Type": "text/plain; charset=utf-8",
461
+ "Access-Control-Allow-Origin": "*",
462
+ });
463
+ res.end(challenge);
464
+ return;
465
+ }
466
+ if (pathname === "/oauth/register" && req.method === "POST") {
467
+ const body = JSON.parse((await readBody(req)) || "{}");
468
+ const clientId = typeof body.client_id === "string" && body.client_id.trim()
469
+ ? body.client_id.trim()
470
+ : `muxo_client_${crypto.randomBytes(8).toString("hex")}`;
471
+ const clientRecord = {
472
+ client_id: clientId,
473
+ client_name: String(body.client_name || "OpenAI Plugin MCP Client"),
474
+ redirect_uris: Array.isArray(body.redirect_uris)
475
+ ? body.redirect_uris.map(String)
476
+ : [
477
+ "https://chatgpt.com/connector_platform_oauth_redirect",
478
+ "http://127.0.0.1:3118/oauth/callback",
479
+ ],
480
+ grant_types: Array.isArray(body.grant_types)
481
+ ? body.grant_types.map(String)
482
+ : ["authorization_code", "refresh_token"],
483
+ response_types: Array.isArray(body.response_types)
484
+ ? body.response_types.map(String)
485
+ : ["code"],
486
+ token_endpoint_auth_method: String(body.token_endpoint_auth_method || "none"),
487
+ client_id_issued_at: Math.floor(Date.now() / 1000),
488
+ };
489
+ oauthClients.set(clientId, clientRecord);
490
+ sendJson(res, 201, clientRecord);
491
+ return;
492
+ }
493
+ if (pathname === "/oauth/authorize" && req.method === "GET") {
494
+ const parsed = parseAuthorizeRequest(url.searchParams);
495
+ const wantsJson = url.searchParams.get("format") === "json" ||
496
+ String(req.headers.accept || "").includes("application/json");
497
+ if ("error" in parsed) {
498
+ if (wantsJson) {
499
+ sendJson(res, 400, { error: "invalid_request", error_description: parsed.error }, { "Cache-Control": "no-store" });
500
+ }
501
+ else {
502
+ res.writeHead(400, consentPageHeaders);
503
+ res.end(renderOAuthConsentHtml({ error: parsed.error }));
504
+ }
505
+ return;
506
+ }
507
+ if (wantsJson) {
508
+ sendJson(res, 401, {
509
+ ok: false,
510
+ code: "interactive_sign_in_required",
511
+ error: "Open this authorization URL in a browser to sign in and approve access.",
512
+ authorizeUrl: url.toString(),
513
+ });
514
+ return;
515
+ }
516
+ const clerk = clerkConfig();
517
+ res.writeHead(200, consentPageHeaders);
518
+ res.end(renderOAuthConsentHtml({
519
+ clientName: parsed.clientName,
520
+ redirectOrigin: oauthRedirectOrigin(parsed.redirectUri),
521
+ clerk: clerk ? { publishableKey: clerk.publishableKey, frontendApi: clerk.frontendApi } : null,
522
+ request: {
523
+ client_id: parsed.clientId,
524
+ redirect_uri: parsed.redirectUri,
525
+ state: parsed.state,
526
+ code_challenge: parsed.codeChallenge || "",
527
+ code_challenge_method: parsed.codeChallengeMethod || "",
528
+ scope: parsed.scope,
529
+ },
530
+ }));
531
+ return;
532
+ }
533
+ if (pathname === "/oauth/authorize" && req.method === "POST") {
534
+ const auth = await authenticate(req);
535
+ // A person must approve; agent API keys cannot grant themselves new access.
536
+ if (!signedInPerson(auth)) {
537
+ sendJson(res, 401, { ok: false, code: "auth_required", error: "Sign in to approve this connection." });
538
+ return;
539
+ }
540
+ const raw = await readBody(req);
541
+ const contentType = String(req.headers["content-type"] || "");
542
+ const params = contentType.includes("application/json")
543
+ ? parseOAuthFormBody(raw, "application/json", "")
544
+ : new URLSearchParams(raw);
545
+ const parsed = parseAuthorizeRequest(params);
546
+ if ("error" in parsed) {
547
+ sendJson(res, 400, { ok: false, error: parsed.error });
548
+ return;
549
+ }
550
+ const redirectTarget = new URL(parsed.redirectUri);
551
+ if (parsed.state)
552
+ redirectTarget.searchParams.set("state", parsed.state);
553
+ redirectTarget.searchParams.set("iss", requestOriginOf(req));
554
+ if (params.get("decision") === "deny") {
555
+ redirectTarget.searchParams.set("error", "access_denied");
556
+ sendJson(res, 200, { ok: true, redirect: redirectTarget.toString() });
557
+ return;
558
+ }
559
+ const now = Date.now();
560
+ for (const [c, rec] of oauthCodes)
561
+ if (rec.expiresAt < now)
562
+ oauthCodes.delete(c);
563
+ const code = oauthSecretToken("muxo_code");
564
+ oauthCodes.set(code, {
565
+ userId: auth.ctx.user.id,
566
+ workspaceId: auth.ctx.workspace.id,
567
+ clientId: parsed.clientId,
568
+ clientName: parsed.clientName,
569
+ redirectUri: parsed.redirectUri,
570
+ scope: parsed.scope,
571
+ codeChallenge: parsed.codeChallenge,
572
+ codeChallengeMethod: parsed.codeChallengeMethod,
573
+ expiresAt: now + OAUTH_CODE_TTL_MS,
574
+ });
575
+ redirectTarget.searchParams.set("code", code);
576
+ sendJson(res, 200, { ok: true, redirect: redirectTarget.toString(), workspaceId: auth.ctx.workspace.id });
577
+ return;
578
+ }
579
+ if (pathname === "/oauth/token" && req.method === "POST") {
580
+ const form = parseOAuthFormBody(await readBody(req), String(req.headers["content-type"] || ""), String(req.headers.authorization || ""));
581
+ const grantType = form.get("grant_type") || "";
582
+ const tokenHeaders = { "Cache-Control": "no-store", Pragma: "no-cache" };
583
+ const oauthError = (error, description) => sendJson(res, 400, { error, error_description: description }, tokenHeaders);
584
+ if (grantType === "authorization_code") {
585
+ const code = form.get("code") || "";
586
+ const rec = oauthCodes.get(code);
587
+ if (!rec)
588
+ return oauthError("invalid_grant", "Authorization code is invalid or was already used.");
589
+ oauthCodes.delete(code);
590
+ if (rec.expiresAt < Date.now())
591
+ return oauthError("invalid_grant", "Authorization code expired.");
592
+ const clientId = form.get("client_id");
593
+ if (clientId && clientId !== rec.clientId)
594
+ return oauthError("invalid_grant", "client_id does not match.");
595
+ const redirectUri = form.get("redirect_uri");
596
+ if (redirectUri && redirectUri !== rec.redirectUri) {
597
+ return oauthError("invalid_grant", "redirect_uri does not match.");
598
+ }
599
+ if (rec.codeChallenge) {
600
+ const check = await verifyPkce(form.get("code_verifier") || "", rec.codeChallenge, rec.codeChallengeMethod);
601
+ if (check === "missing")
602
+ return oauthError("invalid_grant", "code_verifier is required.");
603
+ if (check === "mismatch")
604
+ return oauthError("invalid_grant", "code_verifier does not match.");
605
+ }
606
+ const ws = store.getWorkspace(rec.workspaceId);
607
+ if (!ws || !store.userCanAccessWorkspace(rec.userId, ws.id)) {
608
+ return oauthError("invalid_grant", "Workspace is no longer available.");
609
+ }
610
+ const minted = store.createApiKey({
611
+ workspaceId: ws.id,
612
+ name: oauthConnectionKeyName(rec.clientName),
613
+ environment: "live",
614
+ });
615
+ const refreshToken = oauthSecretToken("muxo_rt");
616
+ store.saveOAuthRefreshToken({
617
+ refreshToken,
618
+ accessToken: minted.secretKey,
619
+ keyId: minted.record.id,
620
+ workspaceId: ws.id,
621
+ clientId: rec.clientId,
622
+ scope: rec.scope,
623
+ });
624
+ sendJson(res, 200, {
625
+ access_token: minted.secretKey,
626
+ token_type: "Bearer",
627
+ expires_in: OAUTH_ACCESS_TOKEN_TTL_SECONDS,
628
+ refresh_token: refreshToken,
629
+ scope: rec.scope,
630
+ }, tokenHeaders);
631
+ return;
632
+ }
633
+ if (grantType === "refresh_token") {
634
+ const refreshToken = form.get("refresh_token") || "";
635
+ const rec = refreshToken ? store.getOAuthRefreshToken(refreshToken) : null;
636
+ const active = rec ? store.findActiveApiKey(rec.accessToken) : null;
637
+ if (!rec || !active || active.id !== rec.keyId) {
638
+ if (rec)
639
+ store.deleteOAuthRefreshToken(refreshToken);
640
+ return oauthError("invalid_grant", "Refresh token is invalid or its access was revoked.");
641
+ }
642
+ const clientId = form.get("client_id");
643
+ if (clientId && clientId !== rec.clientId)
644
+ return oauthError("invalid_grant", "client_id does not match.");
645
+ // Refresh tokens rotate on every use; the replaced token stops working immediately.
646
+ store.deleteOAuthRefreshToken(refreshToken);
647
+ const rotated = oauthSecretToken("muxo_rt");
648
+ store.saveOAuthRefreshToken({
649
+ refreshToken: rotated,
650
+ accessToken: rec.accessToken,
651
+ keyId: rec.keyId,
652
+ workspaceId: rec.workspaceId,
653
+ clientId: rec.clientId,
654
+ scope: rec.scope,
655
+ });
656
+ sendJson(res, 200, {
657
+ access_token: rec.accessToken,
658
+ token_type: "Bearer",
659
+ expires_in: OAUTH_ACCESS_TOKEN_TTL_SECONDS,
660
+ refresh_token: rotated,
661
+ scope: rec.scope || DEFAULT_OAUTH_SCOPE,
662
+ }, tokenHeaders);
663
+ return;
664
+ }
665
+ return oauthError("unsupported_grant_type", "Use grant_type=authorization_code (with PKCE) or refresh_token.");
666
+ }
667
+ if (pathname === "/oauth/userinfo" && req.method === "GET") {
668
+ const resolved = await requireContext(req, res, pathname);
669
+ if (!resolved)
670
+ return;
671
+ const { ctx } = resolved;
672
+ const nickname = (ctx.user.email || "").split("@")[0] ||
673
+ ctx.user.name.toLowerCase().replace(/\s+/g, ".") ||
674
+ ctx.user.id;
675
+ sendJson(res, 200, {
676
+ sub: ctx.user.id,
677
+ id: ctx.user.id,
678
+ name: ctx.user.name,
679
+ email: ctx.user.email,
680
+ email_verified: Boolean(ctx.user.email),
681
+ nickname,
682
+ workspace_id: ctx.workspace.id,
683
+ workspace_name: ctx.workspace.name,
684
+ tier: ctx.workspace.tier,
685
+ });
686
+ return;
687
+ }
688
+ if ((pathname === "/api/openapi.json" || pathname === "/openapi.json") &&
689
+ req.method === "GET") {
690
+ sendJson(res, 200, {
691
+ openapi: "3.1.0",
692
+ info: {
693
+ title: "Muxo Unified Agent Execution, Infrastructure, Memory & Governance API",
694
+ version: MUXO_VERSION,
695
+ description: "One governed boundary over Connected Apps, Instant Tools, Cloud Infrastructure, Workspace Memory, and Safety & Approvals.",
696
+ },
697
+ servers: [{ url: config.baseUrl }],
698
+ paths: {
699
+ "/health": { get: { summary: "Platform health & 4-pillar readiness" } },
700
+ "/mcp": { post: { summary: "Streamable HTTP MCP JSON-RPC 2.0 endpoint (19 tools)" } },
701
+ "/skill.md": { get: { summary: "Canonical agent skill & onboarding markdown" } },
702
+ "/api/v1/agents/signup": { post: { summary: "Autonomous 2-call agent workspace & mk_live_* key signup" } },
703
+ "/api/v1/agents/claim": { post: { summary: "Generate human owner claim link for an agent workspace" } },
704
+ "/api/v1/do": { post: { summary: "Natural-language task routing, planning, and optional execution" } },
705
+ "/api/v1/byok": {
706
+ get: { summary: "List configured BYOK provider keys in the AES-256-GCM vault" },
707
+ post: { summary: "Store or rotate a BYOK provider key (0 credit cost when active)" },
708
+ },
709
+ "/api/v1/byok/{provider}": { delete: { summary: "Delete a BYOK provider key from the vault" } },
710
+ "/api/v1/tools": { get: { summary: "Search unified catalogue across instant.*, app.*, and resource.*" } },
711
+ "/api/v1/tools/{slug}": { get: { summary: "Inspect tool schema, risk level, credit cost, and LLM context URL" } },
712
+ "/api/v1/connections": {
713
+ get: { summary: "List connected OAuth accounts and cloud providers" },
714
+ post: { summary: "Start managed OAuth connection handoff" },
715
+ },
716
+ "/api/v1/connections/wait": { post: { summary: "Wait/poll for browser OAuth handoff completion" } },
717
+ "/api/v1/plans": {
718
+ get: { summary: "List governed action plans" },
719
+ post: { summary: "Create governed action plan with Workspace Memory recall & Ed25519 signature" },
720
+ },
721
+ "/api/v1/plans/{planId}/approve": { post: { summary: "Approve a pending high-risk action plan" } },
722
+ "/api/v1/plans/{planId}/execute": { post: { summary: "Execute an approved plan and issue HMAC-SHA256 receipt" } },
723
+ "/api/v1/resources": { get: { summary: "List provisioned cloud infrastructure resources" } },
724
+ "/api/v1/resources/env": { get: { summary: "Pull decrypted AES-256-GCM workspace environment secrets" } },
725
+ "/api/v1/provisioning/connected_accounts": {
726
+ get: { summary: "Inspect workspace Stripe v2 Connected Account & requirements" },
727
+ post: { summary: "Create a Stripe v2 Connected Account with developer.projects capability" },
728
+ },
729
+ "/api/v1/provisioning/connected_account_links": {
730
+ post: { summary: "Create a Stripe v2 Hosted Account Link (account_onboarding / account_update)" },
731
+ },
732
+ "/api/v1/provisioning/connected_account_sessions": {
733
+ post: { summary: "Create a Stripe Connect Account Session for Accounts v2 Embedded Onboarding" },
734
+ },
735
+ "/api/v1/provisioning/eligibility": { get: { summary: "Check Stripe v2 Provisioning eligibility" } },
736
+ "/api/v1/provisioning/providers": { get: { summary: "List Stripe v2 Provisioning cloud providers" } },
737
+ "/api/v1/provisioning/services": { get: { summary: "List Stripe v2 Provisioning service catalog" } },
738
+ "/api/v1/provisioning/payment_profile": { get: { summary: "Inspect Stripe v2 Provisioning payment profile" } },
739
+ "/api/v1/provisioning/projects": {
740
+ get: { summary: "List Stripe v2 Provisioning projects" },
741
+ post: { summary: "Create a Stripe v2 Provisioning project" },
742
+ },
743
+ "/api/v1/provisioning/resources": {
744
+ get: { summary: "List Stripe v2 Provisioning resources" },
745
+ post: { summary: "Create a Stripe v2 Provisioning resource" },
746
+ },
747
+ "/api/v1/provisioning/resources/{id}/reveal_access_configuration": {
748
+ post: { summary: "Reveal decrypted credentials for a provisioned resource" },
749
+ },
750
+ "/api/v1/memory": {
751
+ get: { summary: "Recall isolated Workspace Memory facts and hybrid context" },
752
+ post: { summary: "Store a workspace memory fact or decision" },
753
+ },
754
+ "/api/v1/memory/facts": { get: { summary: "Query temporal Workspace Memory facts with lineage" } },
755
+ "/api/v1/memory/forget": { post: { summary: "Delete matching memories and retire matching facts" } },
756
+ "/api/v1/policy": {
757
+ get: { summary: "Inspect workspace safety & approval policy" },
758
+ post: { summary: "Update workspace safety & approval policy" },
759
+ },
760
+ "/api/v1/governance": { get: { summary: "Inspect workspace Safety & Approvals identity, policy, and audit chain" } },
761
+ "/api/v1/governance/kill-switch": { post: { summary: "Pause or resume workspace execution via kill switch" } },
762
+ "/api/v1/governance/scan-context": { post: { summary: "Scan untrusted text for prompt injection before storage or use" } },
763
+ "/api/v1/guardrails": {
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" },
772
+ },
773
+ "/approve/{planId}": { get: { summary: "One-click human approval handoff page for a pending plan" } },
774
+ "/api/v1/context": {
775
+ get: { summary: "Inspect full workspace run context with a plain-English explanation" },
776
+ post: { summary: "Inspect workspace run context with an optional query" },
777
+ },
778
+ "/api/v1/receipts": { get: { summary: "List recent tamper-proof execution receipts with plain-English explanations" } },
779
+ "/api/v1/billing/topup": { post: { summary: "Purchase Muxo credits" } },
780
+ "/api/v1/billing/settlements": { get: { summary: "Inspect workspace credit activity ledger" } },
781
+ },
782
+ });
783
+ return;
784
+ }
785
+ if (pathname === "/auth.md" && req.method === "GET") {
786
+ res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8" });
787
+ res.end(`# Muxo Authentication & OAuth Handoff Guide
788
+
789
+ ## 1. CLI & Autonomous Agent Signup
790
+ \`\`\`bash
791
+ muxo login --signup --name "my-agent"
792
+ \`\`\`
793
+ Or via HTTP:
794
+ \`\`\`bash
795
+ curl -sX POST ${config.baseUrl}/api/v1/agents/signup -H "Content-Type: application/json" -d '{"agentName":"my-agent"}'
796
+ \`\`\`
797
+
798
+ ## 2. OAuth Connection Handoff
799
+ When \`plan_action\` targets an unconnected \`app.<provider>.*\` tool, Muxo returns \`authHandoff.redirectUrl\`.
800
+ Open the URL in a browser, complete OAuth (which redirects to \`${config.baseUrl}/oauth/callback\`), and call \`wait_for_connection({ provider })\` or \`muxo link <provider> --wait\`.
801
+
802
+ ## 3. Credit Billing & BYOK Support
803
+ Users purchase Muxo Credits (\`POST /api/v1/billing/topup\` or \`muxo billing topup 1000\`), and Muxo settles upstream execution costs from Muxo's master accounts (\`fundingMode: "muxo_funded"\`), or users can configure their own BYOK provider keys for 0-credit execution.
804
+ `);
805
+ return;
806
+ }
807
+ if (pathname === "/api/setup" && req.method === "GET") {
808
+ sendJson(res, 200, {
809
+ ok: true,
810
+ status: "ready",
811
+ mcpToolsCount: MUXO_MCP_TOOLS.length,
812
+ mcpEndpoint: `${config.baseUrl}/mcp`,
813
+ skillUrl: `${config.baseUrl}/skill.md`,
814
+ openApiUrl: `${config.baseUrl}/api/openapi.json`,
815
+ oauthCallbackUrl: `${config.baseUrl}/oauth/callback`,
816
+ plugins: {
817
+ openaiCodex: `${config.baseUrl}/.agents/plugins/marketplace.json`,
818
+ claudeDroid: `${config.baseUrl}/.claude-plugin/marketplace.json`,
819
+ },
820
+ });
821
+ return;
822
+ }
823
+ if ((pathname === "/skill.md" || pathname === "/skills.md") && req.method === "GET") {
824
+ res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8" });
825
+ res.end(renderSkillMarkdown(config.baseUrl));
826
+ return;
827
+ }
828
+ if (pathname === "/llms.txt" && req.method === "GET") {
829
+ res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
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`);
831
+ return;
832
+ }
833
+ // Serve Installable Muxo Plugin Manifests & Metadata (Portable OpenAI Agent Plugins + Codex + Claude + Cursor)
834
+ const pluginJsonRoutes = {
835
+ "/.agents/plugins/marketplace.json": path.join(ROOT_DIR, ".agents", "plugins", "marketplace.json"),
836
+ "/.claude-plugin/marketplace.json": path.join(ROOT_DIR, ".claude-plugin", "marketplace.json"),
837
+ "/plugins/muxo/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"),
838
+ "/plugins/muxo/mcp.json": path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"),
839
+ "/plugins/muxo/.codex-plugin/plugin.json": path.join(ROOT_DIR, "plugins", "muxo", ".codex-plugin", "plugin.json"),
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"),
842
+ "/plugins/muxo/.app.json": path.join(ROOT_DIR, "plugins", "muxo", ".app.json"),
843
+ "/plugins/muxo/.mcp.json": path.join(ROOT_DIR, "plugins", "muxo", ".mcp.json"),
844
+ };
845
+ if (pluginJsonRoutes[pathname] && req.method === "GET") {
846
+ const targetFile = pluginJsonRoutes[pathname];
847
+ if (fs.existsSync(targetFile)) {
848
+ sendJson(res, 200, JSON.parse(fs.readFileSync(targetFile, "utf8")));
849
+ return;
850
+ }
851
+ }
852
+ const pluginTextRoutes = {
853
+ "/plugins/muxo/skills/muxo/SKILL.md": {
854
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "SKILL.md"),
855
+ contentType: "text/markdown; charset=utf-8",
856
+ },
857
+ "/plugins/muxo/skills/muxo/references/cli.md": {
858
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "references", "cli.md"),
859
+ contentType: "text/markdown; charset=utf-8",
860
+ },
861
+ "/plugins/muxo/skills/muxo/references/mcp.md": {
862
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "muxo", "references", "mcp.md"),
863
+ contentType: "text/markdown; charset=utf-8",
864
+ },
865
+ "/plugins/muxo/skills/setup/SKILL.md": {
866
+ file: path.join(ROOT_DIR, "plugins", "muxo", "skills", "setup", "SKILL.md"),
867
+ contentType: "text/markdown; charset=utf-8",
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
+ },
873
+ "/.codex/config.toml": {
874
+ file: path.join(ROOT_DIR, ".codex", "config.toml"),
875
+ contentType: "text/plain; charset=utf-8",
876
+ },
877
+ };
878
+ if (pluginTextRoutes[pathname] && req.method === "GET") {
879
+ const entry = pluginTextRoutes[pathname];
880
+ if (fs.existsSync(entry.file)) {
881
+ res.writeHead(200, {
882
+ "Content-Type": entry.contentType,
883
+ "Access-Control-Allow-Origin": "*",
884
+ });
885
+ res.end(fs.readFileSync(entry.file, "utf8"));
886
+ return;
887
+ }
888
+ }
889
+ if (pathname === "/api/v1/plugin" && req.method === "GET") {
890
+ const portableManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "plugin.json"), "utf8"));
891
+ const mcpConfig = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, "plugins", "muxo", "mcp.json"), "utf8"));
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;
897
+ sendJson(res, 200, {
898
+ ok: true,
899
+ plugin: portableManifest,
900
+ codexPlugin: codexManifest,
901
+ cursorPlugin: cursorManifest,
902
+ mcp: mcpConfig,
903
+ skills: getMuxoMcpSkills().map((s) => ({
904
+ name: s.name,
905
+ description: s.description,
906
+ uri: s.uri,
907
+ version: s.version,
908
+ files: s.files.length,
909
+ })),
910
+ marketplaces: {
911
+ openaiCodex: `${config.baseUrl}/.agents/plugins/marketplace.json`,
912
+ claudeDroid: `${config.baseUrl}/.claude-plugin/marketplace.json`,
913
+ },
914
+ manifests: {
915
+ portablePluginJson: `${config.baseUrl}/plugins/muxo/plugin.json`,
916
+ portableMcpJson: `${config.baseUrl}/plugins/muxo/mcp.json`,
917
+ onboardingSkill: `${config.baseUrl}/plugins/muxo/skills/setup/SKILL.md`,
918
+ activitySummarySkill: `${config.baseUrl}/plugins/muxo/skills/muxo-activity-summary/SKILL.md`,
919
+ codexPluginJson: `${config.baseUrl}/plugins/muxo/.codex-plugin/plugin.json`,
920
+ cursorPluginJson: `${config.baseUrl}/plugins/muxo/.cursor-plugin/plugin.json`,
921
+ codexConfigToml: `${config.baseUrl}/.codex/config.toml`,
922
+ },
923
+ installCommands: {
924
+ codex: "codex plugin add muxo@muxo",
925
+ cursor: "/add-plugin muxo",
926
+ cliSignup: "muxo login --signup --name my-agent",
927
+ oauthHandoff: "muxo link github --wait",
928
+ },
929
+ });
930
+ return;
931
+ }
932
+ // Web Console UI (app.muxo.ai) & SPA deep-links
933
+ if (isConsolePath(pathname) && req.method === "GET") {
934
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
935
+ res.end(renderConsoleHtml());
936
+ return;
937
+ }
938
+ if (pathname === "/downloads/muxo-cli.zip" && req.method === "GET") {
939
+ res.writeHead(302, {
940
+ Location: "https://muxo.ai/plugins/muxo-chatgpt-plugin.zip",
941
+ "Cache-Control": "no-store",
942
+ });
943
+ res.end();
944
+ return;
945
+ }
946
+ // Built-in Interactive Landing & Provider Canvas (muxo.ai)
947
+ if (pathname === "/landing" && req.method === "GET") {
948
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
949
+ res.end(renderLandingHtml());
950
+ return;
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
+ }
981
+ // Workspace claim link: read-only; accepting requires the invited person's verified sign-in.
982
+ if (pathname.startsWith("/claim/") && req.method === "GET") {
983
+ const claimSlug = decodeURIComponent(pathname.slice("/claim/".length));
984
+ const claim = store.getOpenAgentClaim(claimSlug);
985
+ const wantsHtml = url.searchParams.get("format") !== "json" &&
986
+ String(req.headers.accept || "").includes("text/html");
987
+ if (!claim) {
988
+ const error = "This claim link is invalid, expired, or was already used.";
989
+ if (wantsHtml) {
990
+ res.writeHead(410, consentPageHeaders);
991
+ res.end(renderOAuthConsentHtml({
992
+ error,
993
+ claim: { workspaceName: "", targetEmail: "", action: "", cancelUrl: "/" },
994
+ }));
995
+ }
996
+ else {
997
+ sendJson(res, 410, { ok: false, code: "claim_invalid", error });
998
+ }
999
+ return;
1000
+ }
1001
+ if (!wantsHtml) {
1002
+ sendJson(res, 200, {
1003
+ ok: true,
1004
+ code: "sign_in_required",
1005
+ message: `Open this link in a browser and sign in as ${claim.targetEmail} to claim the workspace.`,
1006
+ claim: {
1007
+ workspaceName: claim.workspace.name,
1008
+ targetEmail: claim.targetEmail,
1009
+ status: claim.status,
1010
+ expiresAt: claim.expiresAt,
1011
+ },
1012
+ acceptEndpoint: `${requestOriginOf(req)}/api/v1/claims/${encodeURIComponent(claimSlug)}/accept`,
1013
+ });
1014
+ return;
1015
+ }
1016
+ const clerk = clerkConfig();
1017
+ res.writeHead(200, consentPageHeaders);
1018
+ res.end(renderOAuthConsentHtml({
1019
+ clerk: clerk ? { publishableKey: clerk.publishableKey, frontendApi: clerk.frontendApi } : null,
1020
+ claim: {
1021
+ workspaceName: claim.workspace.name,
1022
+ targetEmail: claim.targetEmail,
1023
+ action: `/api/v1/claims/${encodeURIComponent(claimSlug)}/accept`,
1024
+ cancelUrl: "/",
1025
+ },
1026
+ }));
1027
+ return;
1028
+ }
1029
+ const claimAcceptMatch = pathname.match(/^\/api\/v1\/claims\/([^/]+)\/accept$/);
1030
+ if (claimAcceptMatch && req.method === "POST") {
1031
+ const auth = await authenticate(req);
1032
+ // Agents mint claim links with their own key, so a key must never be able to accept one.
1033
+ if (!signedInPerson(auth)) {
1034
+ sendJson(res, 401, {
1035
+ ok: false,
1036
+ code: "auth_required",
1037
+ error: "Sign in as the invited person to claim this workspace.",
1038
+ });
1039
+ return;
1040
+ }
1041
+ const claimSlug = decodeURIComponent(claimAcceptMatch[1]);
1042
+ const claim = store.getOpenAgentClaim(claimSlug);
1043
+ if (!claim) {
1044
+ sendJson(res, 410, {
1045
+ ok: false,
1046
+ code: "claim_invalid",
1047
+ error: "This claim link is invalid, expired, or was already used.",
1048
+ });
1049
+ return;
1050
+ }
1051
+ const emails = await verifiedEmailsOf(auth);
1052
+ if (!emails.includes(claim.targetEmail.toLowerCase())) {
1053
+ sendJson(res, 403, {
1054
+ ok: false,
1055
+ code: "claim_email_mismatch",
1056
+ error: `This workspace was offered to ${claim.targetEmail}. Sign in with that verified email to claim it.`,
1057
+ });
1058
+ return;
1059
+ }
1060
+ const workspace = store.acceptAgentClaim(claimSlug, auth.ctx.user.id);
1061
+ if (!workspace) {
1062
+ sendJson(res, 410, {
1063
+ ok: false,
1064
+ code: "claim_invalid",
1065
+ error: "This claim link is invalid, expired, or was already used.",
1066
+ });
1067
+ return;
1068
+ }
1069
+ sendJson(res, 200, {
1070
+ ok: true,
1071
+ redirect: `${requestOriginOf(req)}/`,
1072
+ workspace: { id: workspace.id, name: workspace.name },
1073
+ });
1074
+ return;
1075
+ }
1076
+ // Browser OAuth Handoff Callback Route (GET /oauth/callback)
1077
+ if (pathname === "/oauth/callback" && req.method === "GET") {
1078
+ const workspaceId = url.searchParams.get("workspace_id") ||
1079
+ url.searchParams.get("user_id") ||
1080
+ bootstrap.workspace.id;
1081
+ const provider = url.searchParams.get("provider") ||
1082
+ url.searchParams.get("toolkit") ||
1083
+ url.searchParams.get("appName") ||
1084
+ "unknown";
1085
+ const connectedAccountId = url.searchParams.get("connected_account_id") ||
1086
+ url.searchParams.get("connectedAccountId") ||
1087
+ undefined;
1088
+ const oauthError = url.searchParams.get("error") || url.searchParams.get("error_description");
1089
+ const callbackStatus = oauthError ? "failed" : url.searchParams.get("status") || "success";
1090
+ const handoff = await engine.handleOAuthCallback({
1091
+ workspaceId,
1092
+ provider,
1093
+ connectedAccountId,
1094
+ status: callbackStatus,
1095
+ });
1096
+ const wantsJson = url.searchParams.get("format") === "json" ||
1097
+ String(req.headers.accept || "").includes("application/json");
1098
+ if (wantsJson) {
1099
+ sendJson(res, 200, {
1100
+ ok: true,
1101
+ handoff,
1102
+ message: `${handoff.providerName} OAuth handoff completed (${handoff.status}). Return to your agent or terminal.`,
1103
+ });
1104
+ return;
1105
+ }
1106
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
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
+ }));
1390
+ return;
1391
+ }
1392
+ // One-click Plan Approval Handoff Page (GET /approve/:planId)
1393
+ const approvePageMatch = pathname.match(/^\/approve\/([^/]+)$/);
1394
+ if (approvePageMatch && req.method === "GET") {
1395
+ const planId = decodeURIComponent(approvePageMatch[1]);
1396
+ const clerk = clerkConfig();
1397
+ res.writeHead(200, {
1398
+ "Content-Type": "text/html; charset=utf-8",
1399
+ "Cache-Control": "no-store",
1400
+ "Content-Security-Policy": "frame-ancestors 'none'",
1401
+ "X-Frame-Options": "DENY",
1402
+ "Referrer-Policy": "no-referrer",
1403
+ });
1404
+ res.end(renderApprovalPageHtml({
1405
+ planId,
1406
+ clerk: clerk
1407
+ ? {
1408
+ publishableKey: clerk.publishableKey,
1409
+ frontendApi: clerk.frontendApi,
1410
+ }
1411
+ : null,
1412
+ }));
1413
+ return;
1414
+ }
1415
+ // Autonomous Agent 2-Call Signup — Public
1416
+ if ((pathname === "/api/v1/agents/signup" ||
1417
+ pathname === "/api/v1/auth/signup" ||
1418
+ pathname === "/api/signup") &&
1419
+ req.method === "POST") {
1420
+ const body = JSON.parse((await readBody(req)) || "{}");
1421
+ const ownerEmail = String(body.ownerEmail || body.email || "").trim().toLowerCase();
1422
+ if (ownerEmail && !/^[^\s@]+@[^\s@]+$/.test(ownerEmail)) {
1423
+ sendJson(res, 400, { ok: false, error: "ownerEmail must be a valid email address" });
1424
+ return;
1425
+ }
1426
+ const created = store.createAutonomousAgentSignup({
1427
+ agentName: body.agentName ? String(body.agentName) : undefined,
1428
+ harness: body.harness ? String(body.harness) : undefined,
1429
+ });
1430
+ const claim = store.createAgentClaim({
1431
+ workspaceId: created.workspace.id,
1432
+ email: ownerEmail || null,
1433
+ baseUrl: config.baseUrl,
1434
+ });
1435
+ sendJson(res, 201, {
1436
+ ok: true,
1437
+ agentId: created.user.id,
1438
+ agentDid: workspaceAgentDid(created.workspace.id),
1439
+ claim_slug: claim.claimSlug,
1440
+ claimSlug: claim.claimSlug,
1441
+ claimUrl: claim.claimUrl,
1442
+ claim: { ...claim, targetEmail: claim.targetEmail || null },
1443
+ workspace: {
1444
+ id: created.workspace.id,
1445
+ name: created.workspace.name,
1446
+ slug: created.workspace.slug,
1447
+ tier: created.workspace.tier,
1448
+ creditsBalance: created.workspace.creditsBalance,
1449
+ },
1450
+ apiKey: created.apiKey,
1451
+ mcpEndpoint: `${config.baseUrl}/mcp`,
1452
+ skillUrl: `${config.baseUrl}/skill.md`,
1453
+ claimEndpoint: `${config.baseUrl}/api/v1/agents/claim`,
1454
+ governanceDid: engine.governance.getStatus(created.workspace).agentDid,
1455
+ supermemoryNamespace: engine.supermemory.getWorkspaceNamespace(created.workspace.id),
1456
+ });
1457
+ return;
1458
+ }
1459
+ // Streamable HTTP MCP Endpoint (POST /mcp, GET /mcp, HEAD /mcp, DELETE /mcp)
1460
+ if (pathname === "/mcp" && (req.method === "GET" || req.method === "HEAD" || req.method === "DELETE")) {
1461
+ if ((await authenticate(req)).kind === "invalid") {
1462
+ sendAuthRequired(req, res, pathname);
1463
+ return;
1464
+ }
1465
+ }
1466
+ if (pathname === "/mcp" && (req.method === "GET" || req.method === "HEAD")) {
1467
+ const accept = String(req.headers.accept || "");
1468
+ const sessionId = String(req.headers["mcp-session-id"] || "") ||
1469
+ `mcp_sess_${crypto.randomBytes(8).toString("hex")}`;
1470
+ if (accept.includes("text/event-stream")) {
1471
+ res.writeHead(200, {
1472
+ "Content-Type": "text/event-stream; charset=utf-8",
1473
+ "Cache-Control": "no-cache",
1474
+ Connection: "keep-alive",
1475
+ "Mcp-Session-Id": sessionId,
1476
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1477
+ "Access-Control-Allow-Origin": "*",
1478
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, MCP-Protocol-Version",
1479
+ });
1480
+ if (req.method !== "HEAD") {
1481
+ res.write(`event: endpoint\ndata: ${JSON.stringify({ uri: `${config.baseUrl}/mcp`, sessionId })}\n\n`);
1482
+ }
1483
+ res.end();
1484
+ return;
1485
+ }
1486
+ sendJson(res, 200, {
1487
+ ok: true,
1488
+ transport: "streamable-http",
1489
+ protocolVersion: LATEST_MCP_PROTOCOL_VERSION,
1490
+ supportedVersions: [...SUPPORTED_MCP_PROTOCOL_VERSIONS],
1491
+ serverInfo: {
1492
+ name: "muxo-mcp-server",
1493
+ title: "Muxo Unified Agent Runtime & Governance MCP Server",
1494
+ version: MUXO_VERSION,
1495
+ },
1496
+ instructions: MUXO_MCP_SERVER_INSTRUCTIONS,
1497
+ toolsCount: MUXO_MCP_TOOLS.length,
1498
+ skillsCount: getMuxoMcpSkills().length,
1499
+ }, {
1500
+ "Mcp-Session-Id": sessionId,
1501
+ "MCP-Protocol-Version": LATEST_MCP_PROTOCOL_VERSION,
1502
+ });
1503
+ return;
1504
+ }
1505
+ if (pathname === "/mcp" && req.method === "DELETE") {
1506
+ res.writeHead(204, {
1507
+ "Access-Control-Allow-Origin": "*",
1508
+ });
1509
+ res.end();
1510
+ return;
1511
+ }
1512
+ if (pathname === "/mcp" && req.method === "POST") {
1513
+ const raw = await readBody(req);
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
+ }
1535
+ const resolved = await requireContext(req, res, pathname, !Array.isArray(parsed) && isPublicMcpRequest(parsed));
1536
+ if (!resolved)
1537
+ return;
1538
+ const { ctx } = resolved;
1539
+ const sessionId = String(req.headers["mcp-session-id"] || "") ||
1540
+ `mcp_sess_${ctx.workspace.id}`;
1541
+ const responseHeaders = {
1542
+ "Mcp-Session-Id": sessionId,
1543
+ "MCP-Protocol-Version": headerCheck.protocolVersion,
1544
+ };
1545
+ if (Array.isArray(parsed)) {
1546
+ const responses = (await Promise.all(parsed.map((item) => handleJsonRpcMcpRequest(engine, ctx, (item || {}))))).filter((r) => r !== null);
1547
+ sendJson(res, 200, responses, responseHeaders);
1548
+ return;
1549
+ }
1550
+ const rpc = parsed;
1551
+ const response = await handleJsonRpcMcpRequest(engine, ctx, rpc);
1552
+ if (!response) {
1553
+ res.writeHead(202, {
1554
+ "Access-Control-Allow-Origin": "*",
1555
+ ...responseHeaders,
1556
+ });
1557
+ res.end();
1558
+ return;
1559
+ }
1560
+ sendJson(res, 200, response, responseHeaders);
1561
+ return;
1562
+ }
1563
+ // Auth Endpoints
1564
+ if (pathname === "/api/v1/auth/config" && req.method === "GET") {
1565
+ const clerk = clerkConfig();
1566
+ sendJson(res, 200, {
1567
+ ok: true,
1568
+ provider: clerk ? "clerk" : "password",
1569
+ clerk: clerk ? { publishableKey: clerk.publishableKey, frontendApi: clerk.frontendApi } : null,
1570
+ signInUrl: `${requestOriginOf(req)}/login`,
1571
+ });
1572
+ return;
1573
+ }
1574
+ if ((pathname === "/api/v1/auth/register" || pathname === "/api/v1/auth/login") &&
1575
+ req.method === "POST" &&
1576
+ clerkConfig()) {
1577
+ sendJson(res, 410, {
1578
+ ok: false,
1579
+ code: "use_clerk",
1580
+ error: `Muxo accounts use secure sign-in. Open ${requestOriginOf(req)}/login to sign in or create an account.`,
1581
+ });
1582
+ return;
1583
+ }
1584
+ if (pathname === "/api/v1/auth/register" && req.method === "POST") {
1585
+ const body = JSON.parse((await readBody(req)) || "{}");
1586
+ const created = store.registerUser({
1587
+ email: String(body.email || ""),
1588
+ name: String(body.name || "User"),
1589
+ password: String(body.password || ""),
1590
+ workspaceName: body.workspaceName ? String(body.workspaceName) : undefined,
1591
+ });
1592
+ const initialKey = store.createApiKey({
1593
+ workspaceId: created.workspace.id,
1594
+ name: "Default Live Key",
1595
+ environment: "live",
1596
+ });
1597
+ sendJson(res, 201, {
1598
+ ok: true,
1599
+ ...created,
1600
+ token: created.sessionToken,
1601
+ apiKey: initialKey.secretKey,
1602
+ });
1603
+ return;
1604
+ }
1605
+ if (pathname === "/api/v1/auth/login" && req.method === "POST") {
1606
+ const body = JSON.parse((await readBody(req)) || "{}");
1607
+ const loggedIn = store.loginUser(String(body.email || ""), String(body.password || ""));
1608
+ if (!loggedIn) {
1609
+ sendJson(res, 401, { ok: false, error: "Invalid email or password" });
1610
+ return;
1611
+ }
1612
+ sendJson(res, 200, { ok: true, ...loggedIn, token: loggedIn.sessionToken });
1613
+ return;
1614
+ }
1615
+ const isPublicRoute = ((pathname === "/api/v1/tools" || pathname === "/v1/tools/search") &&
1616
+ (req.method === "GET" || req.method === "POST")) ||
1617
+ (pathname.startsWith("/api/v1/tools/") && req.method === "GET") ||
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");
1623
+ const resolvedCtx = await requireContext(req, res, pathname, isPublicRoute);
1624
+ if (!resolvedCtx)
1625
+ return;
1626
+ const { ctx, auth } = resolvedCtx;
1627
+ // Console Bootstrap
1628
+ if (pathname === "/api/v1/bootstrap" && req.method === "GET") {
1629
+ const org = engine.getOrganization(ctx).organization;
1630
+ const guardrailsSummary = engine.getGuardrailsSummary(ctx);
1631
+ sendJson(res, 200, {
1632
+ ok: true,
1633
+ profile: engine.getProfile(ctx),
1634
+ organization: org,
1635
+ governance: engine.governance.getStatus(),
1636
+ llmPolicyBrief: guardrailsSummary.llmPolicyBrief,
1637
+ plainEnglishGuardrails: guardrailsSummary.plainEnglish,
1638
+ activeMemoryDefaults: guardrailsSummary.activeDefaults,
1639
+ activeGuardrails: guardrailsSummary.activeGuardrails,
1640
+ supermemory: {
1641
+ enabled: engine.supermemory.isEnabled(),
1642
+ namespace: engine.supermemory.getWorkspaceNamespace(ctx.workspace.id),
1643
+ activeDefaults: guardrailsSummary.activeDefaults,
1644
+ activeGuardrails: guardrailsSummary.activeGuardrails,
1645
+ },
1646
+ surfaces: {
1647
+ publicWebUrl: config.publicWebUrl,
1648
+ docsUrl: config.docsUrl,
1649
+ landingUrl: `${config.baseUrl}/landing`,
1650
+ },
1651
+ apiKeys: store.listApiKeys(ctx.workspace.id),
1652
+ byokKeys: store.listByokKeys(ctx.workspace.id),
1653
+ defaultApiKey: ctx.workspace.id === bootstrap.workspace.id ? bootstrap.defaultApiKey || null : null,
1654
+ workspaces: store.listUserWorkspaces(ctx.user.id, ctx.workspace.id),
1655
+ connections: store.listConnections(ctx.workspace.id),
1656
+ resources: store.listResources(ctx.workspace.id),
1657
+ plans: store.listPlans(ctx.workspace.id, 25),
1658
+ runs: store.listRuns(ctx.workspace.id, 25),
1659
+ });
1660
+ return;
1661
+ }
1662
+ // Multi-Workspace & Team API Key Onboarding Endpoints
1663
+ if (pathname === "/api/v1/workspaces" && req.method === "GET") {
1664
+ sendJson(res, 200, {
1665
+ ok: true,
1666
+ currentWorkspaceId: ctx.workspace.id,
1667
+ workspaces: store.listUserWorkspaces(ctx.user.id, ctx.workspace.id),
1668
+ });
1669
+ return;
1670
+ }
1671
+ if (pathname === "/api/v1/workspaces" && req.method === "POST") {
1672
+ const body = JSON.parse((await readBody(req)) || "{}");
1673
+ const name = String(body.name || body.workspaceName || "Team Workspace").trim();
1674
+ const teamName = body.teamName ? String(body.teamName).trim() : null;
1675
+ const switchActive = body.switchActive !== false;
1676
+ const created = store.createWorkspaceForUser({
1677
+ userId: ctx.user.id,
1678
+ name,
1679
+ teamName,
1680
+ switchActive,
1681
+ });
1682
+ sendJson(res, 201, {
1683
+ ok: true,
1684
+ ...created,
1685
+ workspaces: store.listUserWorkspaces(ctx.user.id, created.workspace.id),
1686
+ });
1687
+ return;
1688
+ }
1689
+ if (pathname === "/api/v1/workspaces/switch" && req.method === "POST") {
1690
+ const body = JSON.parse((await readBody(req)) || "{}");
1691
+ const workspaceId = String(body.workspaceId || body.workspace_id || "").trim();
1692
+ const switched = store.switchUserWorkspace(ctx.user.id, workspaceId);
1693
+ if (!switched) {
1694
+ sendJson(res, 404, {
1695
+ ok: false,
1696
+ error: "Workspace not found or not linked to your account",
1697
+ });
1698
+ return;
1699
+ }
1700
+ sendJson(res, 200, {
1701
+ ok: true,
1702
+ ...switched,
1703
+ workspaces: store.listUserWorkspaces(ctx.user.id, switched.workspace.id),
1704
+ });
1705
+ return;
1706
+ }
1707
+ if ((pathname === "/api/v1/workspaces/join" || pathname === "/api/v1/auth/key") &&
1708
+ req.method === "POST") {
1709
+ const body = JSON.parse((await readBody(req)) || "{}");
1710
+ const rawKey = String(body.apiKey || body.api_key || body.key || "").trim();
1711
+ const hasExplicitSession = Boolean(String(req.headers.authorization || "").trim()) &&
1712
+ ctx.authType === "session";
1713
+ const joined = store.joinWorkspaceWithApiKey({
1714
+ apiKey: rawKey,
1715
+ userId: hasExplicitSession ? ctx.user.id : null,
1716
+ switchActive: body.switchActive !== false,
1717
+ });
1718
+ if (!joined) {
1719
+ sendJson(res, 401, {
1720
+ ok: false,
1721
+ error: "Invalid or revoked workspace API key (expected mk_live_... or mk_test_...)",
1722
+ });
1723
+ return;
1724
+ }
1725
+ const sessionToken = joined.user && !hasExplicitSession
1726
+ ? store.createSession(joined.user.id, joined.workspace.id)
1727
+ : undefined;
1728
+ sendJson(res, 200, {
1729
+ ok: true,
1730
+ workspace: joined.workspace,
1731
+ keyRecord: joined.keyRecord,
1732
+ user: joined.user,
1733
+ membership: joined.membership,
1734
+ ...(sessionToken ? { sessionToken, token: sessionToken } : {}),
1735
+ workspaces: joined.user
1736
+ ? store.listUserWorkspaces(joined.user.id, joined.workspace.id)
1737
+ : [],
1738
+ });
1739
+ return;
1740
+ }
1741
+ if ((pathname === "/api/v1/workspaces/onboard-team" ||
1742
+ pathname === "/api/v1/teams/onboard") &&
1743
+ req.method === "POST") {
1744
+ const body = JSON.parse((await readBody(req)) || "{}");
1745
+ const teamName = String(body.teamName || body.team_name || body.name || "Engineering Team").trim();
1746
+ const role = (body.role ? String(body.role) : "operator");
1747
+ const mode = body.mode === "dedicated_workspace" ? "dedicated_workspace" : "shared_workspace";
1748
+ const inviteEmail = body.inviteEmail || body.email ? String(body.inviteEmail || body.email).trim() : null;
1749
+ const kit = store.onboardTeam({
1750
+ currentWorkspaceId: ctx.workspace.id,
1751
+ userId: ctx.user.id,
1752
+ userEmail: ctx.user.email,
1753
+ teamName,
1754
+ role,
1755
+ mode,
1756
+ scopes: Array.isArray(body.scopes) ? body.scopes : undefined,
1757
+ inviteEmail,
1758
+ baseUrl: config.baseUrl,
1759
+ });
1760
+ sendJson(res, 201, {
1761
+ ok: true,
1762
+ kit,
1763
+ onboardingKit: kit,
1764
+ ...kit,
1765
+ workspaces: store.listUserWorkspaces(ctx.user.id, ctx.workspace.id),
1766
+ });
1767
+ return;
1768
+ }
1769
+ // Autonomous Agent Claim Link Creation (Authenticated)
1770
+ if (pathname === "/api/v1/agents/claim" && req.method === "POST") {
1771
+ if (auth.kind !== "ok") {
1772
+ sendAuthRequired(req, res, pathname);
1773
+ return;
1774
+ }
1775
+ const body = JSON.parse((await readBody(req)) || "{}");
1776
+ const email = String(body.email || body.ownerEmail || "").trim().toLowerCase();
1777
+ if (!email || !/^[^\s@]+@[^\s@]+$/.test(email)) {
1778
+ sendJson(res, 400, { ok: false, error: "Valid target email is required" });
1779
+ return;
1780
+ }
1781
+ const claim = store.createAgentClaim({
1782
+ workspaceId: ctx.workspace.id,
1783
+ email,
1784
+ baseUrl: config.baseUrl,
1785
+ });
1786
+ sendJson(res, 201, {
1787
+ ok: true,
1788
+ claim: { ...claim, ownerEmail: email },
1789
+ ...claim,
1790
+ claim_slug: claim.claimSlug,
1791
+ ownerEmail: email,
1792
+ });
1793
+ return;
1794
+ }
1795
+ // Microsoft Agent Governance Status & Audit Verification
1796
+ if (pathname === "/api/v1/governance" && req.method === "GET") {
1797
+ const ws = store.getWorkspace(ctx.workspace.id) || ctx.workspace;
1798
+ const fullReport = engine.governance.getGovernanceStatus(ws);
1799
+ const guardrailsSummary = engine.getGuardrailsSummary(ctx);
1800
+ sendJson(res, 200, {
1801
+ ok: true,
1802
+ governance: {
1803
+ ...engine.governance.getStatus(ws),
1804
+ modules: fullReport.modules,
1805
+ trustScore: fullReport.trustScore,
1806
+ killSwitch: fullReport.killSwitch,
1807
+ contextPoisoning: fullReport.contextPoisoning,
1808
+ auditChain: fullReport.auditChain,
1809
+ },
1810
+ workspacePolicy: ws.policy,
1811
+ llmPolicyBrief: guardrailsSummary.llmPolicyBrief,
1812
+ plainEnglishGuardrails: guardrailsSummary.plainEnglish,
1813
+ activeMemoryDefaults: guardrailsSummary.activeDefaults,
1814
+ activeGuardrails: guardrailsSummary.activeGuardrails,
1815
+ });
1816
+ return;
1817
+ }
1818
+ if (pathname === "/api/v1/guardrails" && req.method === "GET") {
1819
+ const summary = engine.getGuardrailsSummary(ctx);
1820
+ sendJson(res, 200, {
1821
+ ok: true,
1822
+ ...summary,
1823
+ });
1824
+ return;
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
+ }
1851
+ if (pathname === "/api/v1/guardrails" && req.method === "POST") {
1852
+ const body = JSON.parse((await readBody(req)) || "{}");
1853
+ if (body.reset === true) {
1854
+ const resetResult = await engine.resetWorkspaceGuardrails(ctx);
1855
+ sendJson(res, 200, {
1856
+ ...resetResult,
1857
+ humanMessage: "Reset workspace guardrails to Safe Default.",
1858
+ });
1859
+ return;
1860
+ }
1861
+ const rawParamRules = Array.isArray(body.parameterRules)
1862
+ ? body.parameterRules
1863
+ : Array.isArray(body.parameter_rules)
1864
+ ? body.parameter_rules
1865
+ : undefined;
1866
+ const prompt = String(body.prompt ||
1867
+ body.rule ||
1868
+ body.instruction ||
1869
+ body.naturalLanguageRule ||
1870
+ (rawParamRules && rawParamRules.length > 0
1871
+ ? String(rawParamRules[0]?.description ||
1872
+ "Structured conditional parameter guardrail")
1873
+ : "")).trim();
1874
+ if (!prompt && (!rawParamRules || rawParamRules.length === 0)) {
1875
+ sendJson(res, 400, {
1876
+ ok: false,
1877
+ error: "Provide a plain-English guardrail in 'prompt'/'rule' or structured 'parameterRules' (e.g. \"Only allow sending emails to @muxo.ai\" or \"Require approval for Stripe refunds over $50, auto-approve under $50\").",
1878
+ });
1879
+ return;
1880
+ }
1881
+ const applied = await engine.applyNaturalLanguageGuardrail(ctx, prompt, {
1882
+ parameterRules: rawParamRules,
1883
+ policyPatch: body.policyPatch && typeof body.policyPatch === "object"
1884
+ ? body.policyPatch
1885
+ : undefined,
1886
+ source: rawParamRules ? "mcp_harness" : "deterministic",
1887
+ });
1888
+ sendJson(res, 200, {
1889
+ ...applied,
1890
+ policy: applied.workspace.policy,
1891
+ humanMessage: `Guardrail active: ${applied.guardrail.summary}`,
1892
+ });
1893
+ return;
1894
+ }
1895
+ if (pathname === "/api/v1/governance/kill-switch" && req.method === "POST") {
1896
+ const body = JSON.parse((await readBody(req)) || "{}");
1897
+ const action = String(body.action || "quarantine").toLowerCase();
1898
+ if (action === "resume" || body.active === false) {
1899
+ const out = engine.resumeWorkspaceKillSwitch(ctx);
1900
+ sendJson(res, 200, { ok: true, ...out });
1901
+ return;
1902
+ }
1903
+ const out = await engine.activateWorkspaceKillSwitch(ctx, {
1904
+ reason: String(body.reason || "Emergency kill-switch activated by workspace operator"),
1905
+ action: action === "terminate" || action === "suspend"
1906
+ ? action
1907
+ : "quarantine",
1908
+ });
1909
+ sendJson(res, 200, { ok: true, ...out });
1910
+ return;
1911
+ }
1912
+ if (pathname === "/api/v1/governance/scan-context" && req.method === "POST") {
1913
+ const body = JSON.parse((await readBody(req)) || "{}");
1914
+ const scan = engine.governance.scanMemoryContent({
1915
+ workspaceId: ctx.workspace.id,
1916
+ sessionId: String(body.sessionId || "default"),
1917
+ role: body.role || "user",
1918
+ content: String(body.content || ""),
1919
+ recordInDetector: Boolean(body.recordInDetector),
1920
+ });
1921
+ sendJson(res, 200, { ok: true, scan });
1922
+ return;
1923
+ }
1924
+ // Workspace Context Inspector (natural-language friendly)
1925
+ if ((pathname === "/api/v1/context" || pathname === "/v1/context") &&
1926
+ (req.method === "GET" || req.method === "POST")) {
1927
+ const body = req.method === "POST"
1928
+ ? JSON.parse((await readBody(req)) || "{}")
1929
+ : {};
1930
+ const query = url.searchParams.get("query") ||
1931
+ url.searchParams.get("q") ||
1932
+ (body.query ? String(body.query) : undefined);
1933
+ const runContext = await engine.getRunContext(ctx, query);
1934
+ sendJson(res, 200, { ok: true, ...runContext });
1935
+ return;
1936
+ }
1937
+ // Supermemory v5 Workspace Brain Endpoints
1938
+ if (pathname === "/api/v1/memory" && req.method === "GET") {
1939
+ const query = url.searchParams.get("query") ||
1940
+ url.searchParams.get("q") ||
1941
+ undefined;
1942
+ const overview = await engine.getMemoryOverview(ctx, query);
1943
+ sendJson(res, 200, {
1944
+ ok: true,
1945
+ ...overview,
1946
+ activeDefaults: overview.context.activeDefaults || {},
1947
+ activeGuardrails: overview.context.activeGuardrails || [],
1948
+ });
1949
+ return;
1950
+ }
1951
+ if (pathname === "/api/v1/memory/graph" && req.method === "GET") {
1952
+ const query = url.searchParams.get("query") ||
1953
+ url.searchParams.get("q") ||
1954
+ undefined;
1955
+ const graph = await engine.getMemoryGraph(ctx, query);
1956
+ sendJson(res, 200, { ok: true, ...graph });
1957
+ return;
1958
+ }
1959
+ if (pathname === "/api/v1/memory/facts" && req.method === "GET") {
1960
+ const query = url.searchParams.get("query") ||
1961
+ url.searchParams.get("q") ||
1962
+ undefined;
1963
+ const includeHistorical = url.searchParams.get("historical") !== "false";
1964
+ const facts = store.listTemporalFacts(ctx.workspace.id, {
1965
+ includeHistorical,
1966
+ subject: url.searchParams.get("subject") || undefined,
1967
+ predicate: url.searchParams.get("predicate") || undefined,
1968
+ query,
1969
+ limit: Number(url.searchParams.get("limit") || 50),
1970
+ });
1971
+ sendJson(res, 200, {
1972
+ ok: true,
1973
+ activeFacts: facts.filter((f) => f.validTo === null),
1974
+ historicalFacts: facts.filter((f) => f.validTo !== null),
1975
+ facts,
1976
+ });
1977
+ return;
1978
+ }
1979
+ if (pathname === "/api/v1/memory/forget" && req.method === "POST") {
1980
+ const body = JSON.parse((await readBody(req)) || "{}");
1981
+ const query = String(body.query || body.q || "").trim();
1982
+ if (!query) {
1983
+ sendJson(res, 400, { ok: false, error: "query is required to forget matching memories" });
1984
+ return;
1985
+ }
1986
+ const pruned = await engine.forgetWorkspaceMemoryMatching(ctx, query);
1987
+ sendJson(res, 200, { ok: true, ...pruned });
1988
+ return;
1989
+ }
1990
+ if (pathname === "/api/v1/memory" && req.method === "POST") {
1991
+ const body = JSON.parse((await readBody(req)) || "{}");
1992
+ const nlPrompt = String(body.prompt || body.remember || "").trim();
1993
+ if (nlPrompt && !body.content) {
1994
+ const parsed = parseNaturalLanguageMemoryInstruction(nlPrompt);
1995
+ if (parsed.isMemoryInstruction) {
1996
+ const primaryFact = parsed.temporalFacts[0];
1997
+ const record = await engine.addWorkspaceMemory(ctx, {
1998
+ title: parsed.title,
1999
+ content: parsed.content,
2000
+ kind: parsed.kind,
2001
+ subject: primaryFact?.subject,
2002
+ predicate: primaryFact?.predicate,
2003
+ object: primaryFact?.object,
2004
+ metadata: {
2005
+ extractedDefaults: parsed.extractedDefaults,
2006
+ source: "natural_language_api",
2007
+ },
2008
+ });
2009
+ for (const tf of parsed.temporalFacts) {
2010
+ store.upsertTemporalFact({
2011
+ workspaceId: ctx.workspace.id,
2012
+ subject: tf.subject,
2013
+ predicate: tf.predicate,
2014
+ object: tf.object,
2015
+ summary: tf.summary,
2016
+ confidence: 0.98,
2017
+ sourceKind: "preference",
2018
+ sourceReferenceId: record.id,
2019
+ metadata: { source: "natural_language_api" },
2020
+ });
2021
+ }
2022
+ const overview = await engine.getMemoryOverview(ctx);
2023
+ sendJson(res, 201, {
2024
+ ok: true,
2025
+ memory: record,
2026
+ activeDefaults: overview.activeDefaults,
2027
+ explanation: overview.explanation,
2028
+ humanMessage: parsed.humanSummary,
2029
+ });
2030
+ return;
2031
+ }
2032
+ body.content = nlPrompt;
2033
+ }
2034
+ const record = await engine.addWorkspaceMemory(ctx, {
2035
+ title: String(body.title || "Workspace Fact"),
2036
+ content: String(body.content || ""),
2037
+ kind: body.kind || "fact",
2038
+ metadata: body.metadata || {},
2039
+ subject: body.subject ? String(body.subject) : undefined,
2040
+ predicate: body.predicate ? String(body.predicate) : undefined,
2041
+ object: body.object ? String(body.object) : undefined,
2042
+ });
2043
+ sendJson(res, 201, { ok: true, memory: record });
2044
+ return;
2045
+ }
2046
+ if (pathname.startsWith("/api/v1/memory/") && req.method === "DELETE") {
2047
+ const id = decodeURIComponent(pathname.slice("/api/v1/memory/".length));
2048
+ const deleted = await engine.deleteWorkspaceMemory(ctx, id);
2049
+ sendJson(res, 200, { ok: true, ...deleted });
2050
+ return;
2051
+ }
2052
+ // API Keys
2053
+ if ((pathname === "/api/v1/api-keys" || pathname === "/api/v1/keys") &&
2054
+ req.method === "GET") {
2055
+ sendJson(res, 200, {
2056
+ ok: true,
2057
+ apiKeys: store.listApiKeys(ctx.workspace.id),
2058
+ keys: store.listApiKeys(ctx.workspace.id),
2059
+ });
2060
+ return;
2061
+ }
2062
+ if ((pathname === "/api/v1/api-keys" || pathname === "/api/v1/keys") &&
2063
+ req.method === "POST") {
2064
+ const body = JSON.parse((await readBody(req)) || "{}");
2065
+ const created = store.createApiKey({
2066
+ workspaceId: ctx.workspace.id,
2067
+ name: String(body.name || (body.teamName ? `${String(body.teamName)} Team Key` : "API Key")),
2068
+ environment: body.environment === "test" ? "test" : "live",
2069
+ scopes: Array.isArray(body.scopes) ? body.scopes : undefined,
2070
+ teamName: body.teamName ? String(body.teamName) : null,
2071
+ role: (body.role ? String(body.role) : "operator"),
2072
+ createdByEmail: ctx.user.email,
2073
+ });
2074
+ sendJson(res, 201, { ok: true, apiKey: created.secretKey, ...created });
2075
+ return;
2076
+ }
2077
+ if ((pathname.startsWith("/api/v1/api-keys/") ||
2078
+ pathname.startsWith("/api/v1/keys/")) &&
2079
+ req.method === "DELETE") {
2080
+ const prefix = pathname.startsWith("/api/v1/api-keys/")
2081
+ ? "/api/v1/api-keys/"
2082
+ : "/api/v1/keys/";
2083
+ const keyId = decodeURIComponent(pathname.slice(prefix.length));
2084
+ if (!store.revokeApiKey(ctx.workspace.id, keyId)) {
2085
+ sendJson(res, 404, { ok: false, error: "API key not found" });
2086
+ return;
2087
+ }
2088
+ sendJson(res, 200, { ok: true, revoked: keyId });
2089
+ return;
2090
+ }
2091
+ // Profile & Organization (v1 and api/v1)
2092
+ if ((pathname === "/api/v1/profile" || pathname === "/v1/profile") && req.method === "GET") {
2093
+ sendJson(res, 200, { ok: true, ...engine.getProfile(ctx) });
2094
+ return;
2095
+ }
2096
+ if ((pathname === "/api/v1/organization" || pathname === "/v1/organization") && req.method === "GET") {
2097
+ sendJson(res, 200, { ok: true, ...engine.getOrganization(ctx) });
2098
+ return;
2099
+ }
2100
+ // Catalog Search & Inspect
2101
+ if ((pathname === "/api/v1/tools" || pathname === "/v1/tools/search") &&
2102
+ (req.method === "GET" || req.method === "POST")) {
2103
+ const body = req.method === "POST" ? JSON.parse((await readBody(req)) || "{}") : {};
2104
+ const query = url.searchParams.get("query") || body.query || undefined;
2105
+ const lane = (url.searchParams.get("lane") || body.lane) ||
2106
+ "all";
2107
+ const riskLevel = (url.searchParams.get("risk_level") || body.risk_level) ||
2108
+ undefined;
2109
+ const limit = Number(url.searchParams.get("limit") || body.limit || 30);
2110
+ const results = await engine.searchTools({ query, lane, riskLevel, limit });
2111
+ sendJson(res, 200, { ok: true, ...results });
2112
+ return;
2113
+ }
2114
+ if (pathname.startsWith("/api/v1/tools/") && req.method === "GET") {
2115
+ const slug = decodeURIComponent(pathname.slice("/api/v1/tools/".length));
2116
+ const tool = await engine.inspectTool(slug);
2117
+ sendJson(res, 200, tool);
2118
+ return;
2119
+ }
2120
+ // Connections
2121
+ if (pathname === "/api/v1/connections" && req.method === "GET") {
2122
+ const syncLive = url.searchParams.get("sync") === "true";
2123
+ const provider = url.searchParams.get("provider") || undefined;
2124
+ const status = await engine.connectionStatus(ctx, { provider, syncLive });
2125
+ sendJson(res, 200, { ok: true, ...status });
2126
+ return;
2127
+ }
2128
+ if (pathname === "/api/v1/connections" && req.method === "POST") {
2129
+ const body = JSON.parse((await readBody(req)) || "{}");
2130
+ const connection = await engine.startConnection(ctx, {
2131
+ provider: String(body.provider || ""),
2132
+ lane: body.lane,
2133
+ callbackUrl: body.callbackUrl ? String(body.callbackUrl) : undefined,
2134
+ });
2135
+ sendJson(res, 201, { ok: true, connection });
2136
+ return;
2137
+ }
2138
+ if (pathname === "/api/v1/connections/wait" && req.method === "POST") {
2139
+ const body = JSON.parse((await readBody(req)) || "{}");
2140
+ const provider = String(body.provider || "");
2141
+ if (body.simulateCallback === true || body.simulate_callback === true) {
2142
+ await engine.handleOAuthCallback({
2143
+ workspaceId: ctx.workspace.id,
2144
+ provider,
2145
+ status: "success",
2146
+ });
2147
+ }
2148
+ const result = await engine.waitForConnection(ctx, {
2149
+ provider,
2150
+ timeoutSeconds: clampWaitSeconds(body.timeoutSeconds ?? body.timeout_seconds),
2151
+ });
2152
+ sendJson(res, 200, result);
2153
+ return;
2154
+ }
2155
+ const connectionWaitMatch = pathname.match(/^\/api\/v1\/connections\/([^/]+)\/wait$/);
2156
+ if (connectionWaitMatch && req.method === "GET") {
2157
+ const result = await engine.waitForConnection(ctx, {
2158
+ provider: decodeURIComponent(connectionWaitMatch[1]),
2159
+ timeoutSeconds: clampWaitSeconds(url.searchParams.get("timeout_seconds") ?? url.searchParams.get("timeoutSeconds")),
2160
+ });
2161
+ sendJson(res, 200, result);
2162
+ return;
2163
+ }
2164
+ // Provisioned Resources & Decrypted Vault Env Pull
2165
+ if (pathname === "/api/v1/resources" && req.method === "GET") {
2166
+ sendJson(res, 200, {
2167
+ ok: true,
2168
+ resources: store.listResources(ctx.workspace.id),
2169
+ });
2170
+ return;
2171
+ }
2172
+ if (pathname === "/api/v1/resources/env" && req.method === "GET") {
2173
+ // Also pull any fresh .env from Stripe Projects workdir if present
2174
+ const workdirSecrets = await engine.stripeProjects
2175
+ .pullEnvVarsFromWorkdir()
2176
+ .catch(() => ({}));
2177
+ const vaultSecrets = store.getDecryptedWorkspaceEnv(ctx.workspace.id);
2178
+ const merged = { ...workdirSecrets, ...vaultSecrets };
2179
+ const dotenvLines = [
2180
+ `# Muxo Encrypted Vault Export — Workspace ${ctx.workspace.slug} (${ctx.workspace.id})`,
2181
+ `# Generated at ${new Date().toISOString()}`,
2182
+ ...Object.entries(merged).map(([k, v]) => `${k}=${JSON.stringify(v)}`),
2183
+ ];
2184
+ sendJson(res, 200, {
2185
+ ok: true,
2186
+ workspaceId: ctx.workspace.id,
2187
+ variables: merged,
2188
+ dotenv: dotenvLines.join("\n") + "\n",
2189
+ });
2190
+ return;
2191
+ }
2192
+ const resourceSecretsMatch = pathname.match(/^\/api\/v1\/resources\/([^/]+)\/secrets$/);
2193
+ if (resourceSecretsMatch && req.method === "GET") {
2194
+ const resourceId = decodeURIComponent(resourceSecretsMatch[1]);
2195
+ const anyRes = store.findResourceAnyWorkspace(resourceId);
2196
+ if (anyRes && anyRes.workspaceId !== ctx.workspace.id) {
2197
+ sendJson(res, 403, {
2198
+ ok: false,
2199
+ code: "resource_workspace_mismatch",
2200
+ error: "Resource belongs to a different workspace.",
2201
+ });
2202
+ return;
2203
+ }
2204
+ const ownedRes = store.getResource(ctx.workspace.id, resourceId);
2205
+ if (!ownedRes) {
2206
+ sendJson(res, 404, {
2207
+ ok: false,
2208
+ code: "resource_not_found",
2209
+ error: `Resource '${resourceId}' not found in workspace.`,
2210
+ });
2211
+ return;
2212
+ }
2213
+ const secrets = store.getDecryptedResourceSecrets(ctx.workspace.id, resourceId);
2214
+ sendJson(res, 200, {
2215
+ ok: true,
2216
+ resourceId: ownedRes.id,
2217
+ resourceName: ownedRes.resourceName,
2218
+ workspaceId: ctx.workspace.id,
2219
+ envVarKeys: ownedRes.envVarKeys,
2220
+ secrets,
2221
+ });
2222
+ return;
2223
+ }
2224
+ // Stripe v2 Provisioning API (`Stripe-Version: 2026-09-30.preview`) Routes
2225
+ if (pathname === "/api/v1/provisioning/audit_log" && req.method === "GET") {
2226
+ const limit = Number(url.searchParams.get("limit") || 50);
2227
+ sendJson(res, 200, {
2228
+ ok: true,
2229
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2230
+ entries: engine.stripeProjects.getProvisioningAuditLog(limit),
2231
+ });
2232
+ return;
2233
+ }
2234
+ if (pathname === "/api/v1/provisioning/connected_accounts" && req.method === "GET") {
2235
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2236
+ const verifiedAccount = store.getByokKey(ctx.workspace.id, "stripe_connected_account") ||
2237
+ config.stripeConnectedAccountId ||
2238
+ "";
2239
+ if (!verifiedAccount) {
2240
+ sendJson(res, 200, {
2241
+ ok: true,
2242
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2243
+ connected: false,
2244
+ account: null,
2245
+ publishable_key: config.stripePublishableKey || null,
2246
+ });
2247
+ return;
2248
+ }
2249
+ const retrieved = await engine.stripeProjects.retrieveConnectedAccount(verifiedAccount, stripeByok);
2250
+ sendJson(res, 200, {
2251
+ ok: true,
2252
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2253
+ connected: true,
2254
+ account: retrieved,
2255
+ publishable_key: config.stripePublishableKey || null,
2256
+ });
2257
+ return;
2258
+ }
2259
+ if (pathname === "/api/v1/provisioning/connected_accounts" && req.method === "POST") {
2260
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2261
+ const body = JSON.parse((await readBody(req)) || "{}");
2262
+ const created = await engine.stripeProjects.createConnectedAccount({
2263
+ contact_email: String(body.contact_email || ctx.user?.email || "owner@muxo.ai"),
2264
+ display_name: String(body.display_name || ctx.workspace.name || "Muxo Tenant"),
2265
+ country: body.country ? String(body.country) : "US",
2266
+ entity_type: body.entity_type === "individual" ? "individual" : "company",
2267
+ apiKeyOverride: stripeByok,
2268
+ });
2269
+ if (created.id) {
2270
+ store.setByokKey(ctx.workspace.id, "stripe_connected_account", created.id);
2271
+ }
2272
+ sendJson(res, 201, {
2273
+ ok: true,
2274
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2275
+ ...created,
2276
+ });
2277
+ return;
2278
+ }
2279
+ if (pathname === "/api/v1/provisioning/connected_account_links" && req.method === "POST") {
2280
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2281
+ let verifiedAccount = store.getByokKey(ctx.workspace.id, "stripe_connected_account") ||
2282
+ config.stripeConnectedAccountId ||
2283
+ "";
2284
+ const body = JSON.parse((await readBody(req)) || "{}");
2285
+ if (!verifiedAccount && !body.account && body.auto_create === true) {
2286
+ const created = await engine.stripeProjects.createConnectedAccount({
2287
+ contact_email: String(body.contact_email || ctx.user?.email || "owner@muxo.ai"),
2288
+ display_name: String(body.display_name || ctx.workspace.name || "Muxo Tenant"),
2289
+ country: body.country ? String(body.country) : "US",
2290
+ entity_type: body.entity_type === "individual" ? "individual" : "company",
2291
+ apiKeyOverride: stripeByok,
2292
+ });
2293
+ if (created.id) {
2294
+ store.setByokKey(ctx.workspace.id, "stripe_connected_account", created.id);
2295
+ verifiedAccount = created.id;
2296
+ }
2297
+ }
2298
+ const accountId = String(verifiedAccount || body.account || "").trim();
2299
+ if (!accountId) {
2300
+ sendJson(res, 400, {
2301
+ ok: false,
2302
+ error: "No verified connected_account_id is mapped to this tenant workspace.",
2303
+ });
2304
+ return;
2305
+ }
2306
+ const link = await engine.stripeProjects.createConnectedAccountLink({
2307
+ account: accountId,
2308
+ type: body.type === "account_update" ? "account_update" : "account_onboarding",
2309
+ fields: body.fields === "eventually_due" ? "eventually_due" : "currently_due",
2310
+ future_requirements: body.future_requirements === "omit"
2311
+ ? "omit"
2312
+ : body.future_requirements === "include"
2313
+ ? "include"
2314
+ : undefined,
2315
+ refresh_url: String(body.refresh_url || `${config.publicWebUrl}/?onboarding=refresh`),
2316
+ return_url: String(body.return_url || `${config.publicWebUrl}/?onboarding=return`),
2317
+ apiKeyOverride: stripeByok,
2318
+ });
2319
+ sendJson(res, 201, {
2320
+ ok: true,
2321
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2322
+ ...link,
2323
+ });
2324
+ return;
2325
+ }
2326
+ if (pathname === "/api/v1/provisioning/connected_account_sessions" && req.method === "POST") {
2327
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2328
+ let verifiedAccount = store.getByokKey(ctx.workspace.id, "stripe_connected_account") ||
2329
+ config.stripeConnectedAccountId ||
2330
+ "";
2331
+ const body = JSON.parse((await readBody(req)) || "{}");
2332
+ if (!verifiedAccount && !body.account && body.auto_create !== false) {
2333
+ const created = await engine.stripeProjects.createConnectedAccount({
2334
+ contact_email: String(body.contact_email || ctx.user?.email || "owner@muxo.ai"),
2335
+ display_name: String(body.display_name || ctx.workspace.name || "Muxo Tenant"),
2336
+ country: body.country ? String(body.country) : "US",
2337
+ entity_type: body.entity_type === "individual" ? "individual" : "company",
2338
+ apiKeyOverride: stripeByok,
2339
+ });
2340
+ if (created.id) {
2341
+ store.setByokKey(ctx.workspace.id, "stripe_connected_account", created.id);
2342
+ verifiedAccount = created.id;
2343
+ }
2344
+ }
2345
+ const accountId = String(verifiedAccount || body.account || "").trim();
2346
+ if (!accountId) {
2347
+ sendJson(res, 400, {
2348
+ ok: false,
2349
+ error: "No verified connected_account_id is mapped to this tenant workspace.",
2350
+ });
2351
+ return;
2352
+ }
2353
+ const session = await engine.stripeProjects.createConnectedAccountSession({
2354
+ account: accountId,
2355
+ enableAccountManagement: Boolean(body.enable_account_management ?? body.enableAccountManagement),
2356
+ enableNotificationBanner: body.enable_notification_banner !== undefined
2357
+ ? Boolean(body.enable_notification_banner)
2358
+ : body.enableNotificationBanner !== undefined
2359
+ ? Boolean(body.enableNotificationBanner)
2360
+ : undefined,
2361
+ disableStripeUserAuthentication: body.disable_stripe_user_authentication !== undefined
2362
+ ? Boolean(body.disable_stripe_user_authentication)
2363
+ : body.disableStripeUserAuthentication !== undefined
2364
+ ? Boolean(body.disableStripeUserAuthentication)
2365
+ : undefined,
2366
+ externalAccountCollection: body.external_account_collection !== undefined
2367
+ ? Boolean(body.external_account_collection)
2368
+ : body.externalAccountCollection !== undefined
2369
+ ? Boolean(body.externalAccountCollection)
2370
+ : undefined,
2371
+ apiKeyOverride: stripeByok,
2372
+ });
2373
+ sendJson(res, 201, {
2374
+ ok: true,
2375
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2376
+ publishable_key: config.stripePublishableKey || null,
2377
+ embedded_component: "account-onboarding",
2378
+ collection_options: {
2379
+ fields: body.fields === "eventually_due" ? "eventually_due" : "currently_due",
2380
+ future_requirements: body.future_requirements === "omit" ? "omit" : "include",
2381
+ },
2382
+ ...session,
2383
+ });
2384
+ return;
2385
+ }
2386
+ const connAccountGetMatch = pathname.match(/^\/api\/v1\/provisioning\/connected_accounts\/([^/]+)$/);
2387
+ if (connAccountGetMatch && req.method === "GET") {
2388
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2389
+ const accountId = decodeURIComponent(connAccountGetMatch[1]);
2390
+ const retrieved = await engine.stripeProjects.retrieveConnectedAccount(accountId, stripeByok);
2391
+ sendJson(res, 200, {
2392
+ ok: true,
2393
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2394
+ ...retrieved,
2395
+ });
2396
+ return;
2397
+ }
2398
+ if (pathname === "/api/v1/provisioning/eligibility" && req.method === "GET") {
2399
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2400
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
2401
+ const eligibility = await engine.stripeProjects.checkEligibility(stripeByok, verifiedContext);
2402
+ sendJson(res, 200, {
2403
+ ok: true,
2404
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2405
+ ...eligibility,
2406
+ });
2407
+ return;
2408
+ }
2409
+ if ((pathname === "/api/v1/provisioning/catalog/providers" ||
2410
+ pathname === "/api/v1/provisioning/providers") &&
2411
+ req.method === "GET") {
2412
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2413
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
2414
+ const catalog = url.searchParams.get("catalog") || "prod";
2415
+ const limit = Number(url.searchParams.get("limit") || 100);
2416
+ const page = url.searchParams.get("page") || undefined;
2417
+ const page_url = url.searchParams.get("page_url") || undefined;
2418
+ const development = url.searchParams.get("development") !== null
2419
+ ? url.searchParams.get("development") === "true"
2420
+ : undefined;
2421
+ const providers = await engine.stripeProjects.listCatalogProviders({
2422
+ catalog,
2423
+ development,
2424
+ limit,
2425
+ page,
2426
+ page_url,
2427
+ apiKeyOverride: stripeByok,
2428
+ stripeContext: verifiedContext,
2429
+ });
2430
+ sendJson(res, 200, {
2431
+ ok: true,
2432
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2433
+ ...providers,
2434
+ });
2435
+ return;
2436
+ }
2437
+ if ((pathname === "/api/v1/provisioning/catalog/services" ||
2438
+ pathname === "/api/v1/provisioning/services") &&
2439
+ req.method === "GET") {
2440
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2441
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
2442
+ const catalog = url.searchParams.get("catalog") || "prod";
2443
+ const limit = Number(url.searchParams.get("limit") || 100);
2444
+ const page = url.searchParams.get("page") || undefined;
2445
+ const page_url = url.searchParams.get("page_url") || undefined;
2446
+ const development = url.searchParams.get("development") !== null
2447
+ ? url.searchParams.get("development") === "true"
2448
+ : undefined;
2449
+ const provider_id = url.searchParams.get("provider_id") || undefined;
2450
+ const provider_name = url.searchParams.get("provider_name") || undefined;
2451
+ const services = await engine.stripeProjects.listCatalogServices({
2452
+ catalog,
2453
+ development,
2454
+ limit,
2455
+ page,
2456
+ page_url,
2457
+ provider_id,
2458
+ provider_name,
2459
+ apiKeyOverride: stripeByok,
2460
+ stripeContext: verifiedContext,
2461
+ });
2462
+ sendJson(res, 200, {
2463
+ ok: true,
2464
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2465
+ ...services,
2466
+ });
2467
+ return;
2468
+ }
2469
+ if (pathname === "/api/v1/provisioning/payment_profile" && req.method === "GET") {
2470
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2471
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
2472
+ const provider = url.searchParams.get("provider") || undefined;
2473
+ const livemodeParam = url.searchParams.get("livemode");
2474
+ const livemode = livemodeParam !== null ? livemodeParam === "true" : undefined;
2475
+ const profile = await engine.stripeProjects.getPaymentProfile({
2476
+ provider,
2477
+ livemode,
2478
+ apiKeyOverride: stripeByok,
2479
+ stripeContext: verifiedContext,
2480
+ });
2481
+ sendJson(res, 200, {
2482
+ ok: true,
2483
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2484
+ ...profile,
2485
+ });
2486
+ return;
2487
+ }
2488
+ if (pathname === "/api/v1/provisioning/payment_profile/update_limit" &&
2489
+ req.method === "POST") {
2490
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2491
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
2492
+ const body = JSON.parse((await readBody(req)) || "{}");
2493
+ const usageLimits = body.usage_limits || {
2494
+ max_amount: String(body.max_amount ?? "5000"),
2495
+ currency: String(body.currency || "usd"),
2496
+ recurring_interval: String(body.recurring_interval || "month"),
2497
+ };
2498
+ const updated = await engine.stripeProjects.updatePaymentProfileLimit({
2499
+ usage_limits: usageLimits,
2500
+ provider: body.provider ? String(body.provider) : undefined,
2501
+ livemode: body.livemode !== undefined ? Boolean(body.livemode) : undefined,
2502
+ apiKeyOverride: stripeByok,
2503
+ stripeContext: verifiedContext,
2504
+ });
2505
+ sendJson(res, 200, {
2506
+ ok: true,
2507
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2508
+ ...updated,
2509
+ });
2510
+ return;
2511
+ }
2512
+ if (pathname === "/api/v1/provisioning/payment_method_requests" &&
2513
+ req.method === "POST") {
2514
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2515
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
2516
+ const body = JSON.parse((await readBody(req)) || "{}");
2517
+ const created = await engine.stripeProjects.createPaymentMethodRequest({
2518
+ payment_method_owner: body.payment_method_owner === "platform" ||
2519
+ body.payment_method_owner === "provider"
2520
+ ? body.payment_method_owner
2521
+ : undefined,
2522
+ provider: body.provider ? String(body.provider) : undefined,
2523
+ source_account: body.source_account ? String(body.source_account) : undefined,
2524
+ source_customer: body.source_customer ? String(body.source_customer) : undefined,
2525
+ source_payment_method: body.source_payment_method
2526
+ ? String(body.source_payment_method)
2527
+ : undefined,
2528
+ livemode: body.livemode !== undefined ? Boolean(body.livemode) : undefined,
2529
+ usage_limits: body.usage_limits,
2530
+ apiKeyOverride: stripeByok,
2531
+ stripeContext: verifiedContext,
2532
+ });
2533
+ sendJson(res, 201, {
2534
+ ok: true,
2535
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2536
+ ...created,
2537
+ });
2538
+ return;
2539
+ }
2540
+ if (pathname === "/api/v1/provisioning/projects" && req.method === "GET") {
2541
+ sendJson(res, 200, {
2542
+ ok: true,
2543
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2544
+ object: "v2.list",
2545
+ data: [
2546
+ {
2547
+ id: config.stripeProjectId || `proj_muxo_${ctx.workspace.id}`,
2548
+ object: "v2.provisioning.project",
2549
+ name: `${ctx.workspace.name} Cloud Project`,
2550
+ catalog: "prod",
2551
+ created: ctx.workspace.createdAt,
2552
+ livemode: false,
2553
+ },
2554
+ ],
2555
+ next_page_url: null,
2556
+ previous_page_url: null,
2557
+ });
2558
+ return;
2559
+ }
2560
+ if (pathname === "/api/v1/provisioning/projects" && req.method === "POST") {
2561
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2562
+ const body = JSON.parse((await readBody(req)) || "{}");
2563
+ const created = await engine.stripeProjects.createProject({
2564
+ name: String(body.name || "Muxo Cloud Project"),
2565
+ catalog: body.catalog || "prod",
2566
+ project_profile: body.project_profile
2567
+ ? String(body.project_profile)
2568
+ : undefined,
2569
+ apiKeyOverride: stripeByok,
2570
+ });
2571
+ sendJson(res, 201, {
2572
+ ok: true,
2573
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2574
+ ...created,
2575
+ });
2576
+ return;
2577
+ }
2578
+ if (pathname === "/api/v1/provisioning/provider_connections" &&
2579
+ req.method === "GET") {
2580
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2581
+ const limit = Number(url.searchParams.get("limit") || 25);
2582
+ const page = url.searchParams.get("page") || undefined;
2583
+ const provider = url.searchParams.get("provider") || undefined;
2584
+ const connections = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2585
+ ? await engine.stripeProjects.listProviderConnections({
2586
+ limit,
2587
+ page,
2588
+ provider,
2589
+ apiKeyOverride: stripeByok,
2590
+ })
2591
+ : managedProviderConnectionList();
2592
+ sendJson(res, 200, {
2593
+ ok: true,
2594
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2595
+ ...connections,
2596
+ });
2597
+ return;
2598
+ }
2599
+ const provConnUnlinkMatch = pathname.match(/^\/api\/v1\/provisioning\/provider_connections\/([^/]+)\/unlink$/);
2600
+ if (provConnUnlinkMatch && req.method === "POST") {
2601
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2602
+ const id = decodeURIComponent(provConnUnlinkMatch[1]);
2603
+ const unlinked = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2604
+ ? await engine.stripeProjects.unlinkProviderConnection(id, stripeByok)
2605
+ : managedProviderConnectionUnlink(id);
2606
+ sendJson(res, 200, {
2607
+ ok: true,
2608
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2609
+ ...unlinked,
2610
+ });
2611
+ return;
2612
+ }
2613
+ if (pathname === "/api/v1/provisioning/provider_connection_requests" &&
2614
+ req.method === "POST") {
2615
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2616
+ const body = JSON.parse((await readBody(req)) || "{}");
2617
+ const reqRes = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2618
+ ? await engine.stripeProjects.createProviderConnectionRequest({
2619
+ provider: String(body.provider || ""),
2620
+ configuration: body.configuration && typeof body.configuration === "object"
2621
+ ? body.configuration
2622
+ : {},
2623
+ project: body.project ? String(body.project) : undefined,
2624
+ apiKeyOverride: stripeByok,
2625
+ })
2626
+ : managedProviderConnectionRequest({ provider: String(body.provider || "") });
2627
+ sendJson(res, 201, {
2628
+ ok: true,
2629
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2630
+ ...reqRes,
2631
+ });
2632
+ return;
2633
+ }
2634
+ const provConnReqSubmitMatch = pathname.match(/^\/api\/v1\/provisioning\/provider_connection_requests\/([^/]+)\/submit_information$/);
2635
+ if (provConnReqSubmitMatch && req.method === "POST") {
2636
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2637
+ const id = decodeURIComponent(provConnReqSubmitMatch[1]);
2638
+ const body = JSON.parse((await readBody(req)) || "{}");
2639
+ const submitted = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2640
+ ? await engine.stripeProjects.submitProviderConnectionRequestInformation(id, body.submitted_information || body, stripeByok)
2641
+ : managedProviderConnectionRequest({ id, requestStatus: "complete" });
2642
+ sendJson(res, 200, {
2643
+ ok: true,
2644
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2645
+ ...submitted,
2646
+ });
2647
+ return;
2648
+ }
2649
+ const provConnReqGetMatch = pathname.match(/^\/api\/v1\/provisioning\/provider_connection_requests\/([^/]+)$/);
2650
+ if (provConnReqGetMatch && req.method === "GET") {
2651
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2652
+ const id = decodeURIComponent(provConnReqGetMatch[1]);
2653
+ const retrieved = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2654
+ ? await engine.stripeProjects.retrieveProviderConnectionRequest(id, stripeByok)
2655
+ : managedProviderConnectionRequest({ id });
2656
+ sendJson(res, 200, {
2657
+ ok: true,
2658
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2659
+ ...retrieved,
2660
+ });
2661
+ return;
2662
+ }
2663
+ const revealAccessMatch = pathname.match(/^\/api\/v1\/provisioning\/resources\/([^/]+)\/reveal_access_configuration$/);
2664
+ if (revealAccessMatch && req.method === "POST") {
2665
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2666
+ const resourceId = decodeURIComponent(revealAccessMatch[1]);
2667
+ const anyRes = store.findResourceAnyWorkspace(resourceId);
2668
+ if (anyRes && anyRes.workspaceId !== ctx.workspace.id) {
2669
+ sendJson(res, 403, {
2670
+ ok: false,
2671
+ code: "resource_workspace_mismatch",
2672
+ error: "Resource belongs to a different workspace.",
2673
+ });
2674
+ return;
2675
+ }
2676
+ if (engine.stripeProjects.hasStripeV2ApiKey(stripeByok)) {
2677
+ try {
2678
+ const revealed = await engine.stripeProjects.revealAccessConfiguration(resourceId, stripeByok);
2679
+ sendJson(res, 200, {
2680
+ ok: true,
2681
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2682
+ source: "stripe_v2_api",
2683
+ ...revealed,
2684
+ });
2685
+ return;
2686
+ }
2687
+ catch {
2688
+ // Fall through to workspace vault lookup if Stripe v2 returns error
2689
+ }
2690
+ }
2691
+ const resourceSpecific = store.getDecryptedResourceSecrets(ctx.workspace.id, resourceId);
2692
+ const vaultSecrets = Object.keys(resourceSpecific).length > 0
2693
+ ? resourceSpecific
2694
+ : store.getDecryptedWorkspaceEnv(ctx.workspace.id);
2695
+ sendJson(res, 200, {
2696
+ ok: true,
2697
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2698
+ object: "v2.provisioning.resource.access_configuration",
2699
+ resource: resourceId,
2700
+ configuration: vaultSecrets,
2701
+ created: new Date().toISOString(),
2702
+ expires_at: null,
2703
+ livemode: false,
2704
+ source: "muxo_managed",
2705
+ });
2706
+ return;
2707
+ }
2708
+ const resourceActionMatch = pathname.match(/^\/api\/v1\/provisioning\/resources\/([^/]+)\/(remove|unlink|rotate_credentials|submit_information)$/);
2709
+ if (resourceActionMatch && req.method === "POST") {
2710
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2711
+ const resourceId = decodeURIComponent(resourceActionMatch[1]);
2712
+ const action = resourceActionMatch[2];
2713
+ const body = JSON.parse((await readBody(req)) || "{}");
2714
+ let actionResult;
2715
+ if (!engine.stripeProjects.hasStripeV2ApiKey(stripeByok)) {
2716
+ actionResult = managedResourceAction(resourceId, action);
2717
+ }
2718
+ else if (action === "remove") {
2719
+ actionResult = await engine.stripeProjects.removeResource(resourceId, stripeByok);
2720
+ }
2721
+ else if (action === "unlink") {
2722
+ actionResult = await engine.stripeProjects.unlinkResource(resourceId, stripeByok);
2723
+ }
2724
+ else if (action === "rotate_credentials") {
2725
+ actionResult = await engine.stripeProjects.rotateResourceCredentials(resourceId, stripeByok);
2726
+ }
2727
+ else {
2728
+ actionResult = await engine.stripeProjects.submitResourceInformation(resourceId, body.submitted_information || body, stripeByok);
2729
+ }
2730
+ sendJson(res, 200, {
2731
+ ok: true,
2732
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2733
+ ...actionResult,
2734
+ });
2735
+ return;
2736
+ }
2737
+ if (pathname === "/api/v1/provisioning/resources/link" && req.method === "POST") {
2738
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2739
+ const body = JSON.parse((await readBody(req)) || "{}");
2740
+ const linked = !engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2741
+ ? managedResource({
2742
+ provider: String(body.provider || ""),
2743
+ serviceRef: String(body.service_ref || ""),
2744
+ environment: body.environment ? String(body.environment) : "prod",
2745
+ catalog: body.catalog ? String(body.catalog) : "prod",
2746
+ status: "active",
2747
+ })
2748
+ : await engine.stripeProjects.linkResource({
2749
+ provider: String(body.provider || ""),
2750
+ service_ref: String(body.service_ref || ""),
2751
+ provider_resource_id: body.provider_resource_id
2752
+ ? String(body.provider_resource_id)
2753
+ : undefined,
2754
+ environment: body.environment ? String(body.environment) : "prod",
2755
+ catalog: body.catalog || "prod",
2756
+ project: body.project ? String(body.project) : undefined,
2757
+ apiKeyOverride: stripeByok,
2758
+ });
2759
+ sendJson(res, 201, {
2760
+ ok: true,
2761
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2762
+ ...linked,
2763
+ });
2764
+ return;
2765
+ }
2766
+ if (pathname === "/api/v1/provisioning/resources" && req.method === "GET") {
2767
+ const resources = store.listResources(ctx.workspace.id);
2768
+ sendJson(res, 200, {
2769
+ ok: true,
2770
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2771
+ object: "v2.list",
2772
+ data: resources,
2773
+ next_page_url: null,
2774
+ previous_page_url: null,
2775
+ });
2776
+ return;
2777
+ }
2778
+ if (pathname === "/api/v1/provisioning/resources" && req.method === "POST") {
2779
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2780
+ const body = JSON.parse((await readBody(req)) || "{}");
2781
+ const providerSlug = String(body.provider || "turso").toLowerCase();
2782
+ const serviceRef = String(body.service_ref || "database").toLowerCase();
2783
+ const resourceName = body.name ? String(body.name) : `${providerSlug}-${serviceRef}`;
2784
+ const created = !engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2785
+ ? managedResource({
2786
+ provider: providerSlug,
2787
+ serviceRef,
2788
+ name: resourceName,
2789
+ environment: body.environment ? String(body.environment) : "prod",
2790
+ catalog: body.catalog ? String(body.catalog) : "prod",
2791
+ })
2792
+ : await engine.stripeProjects.createResource({
2793
+ provider: providerSlug,
2794
+ service_ref: serviceRef,
2795
+ configuration: body.configuration && typeof body.configuration === "object"
2796
+ ? body.configuration
2797
+ : {},
2798
+ name: resourceName,
2799
+ environment: body.environment ? String(body.environment) : "prod",
2800
+ catalog: body.catalog || "prod",
2801
+ project: body.project ? String(body.project) : undefined,
2802
+ apiKeyOverride: stripeByok,
2803
+ });
2804
+ const externalId = String(created.id || `prv_res_${Date.now().toString(36)}`);
2805
+ const envKey = `${providerSlug.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_URL`;
2806
+ store.upsertResource({
2807
+ workspaceId: ctx.workspace.id,
2808
+ providerSlug,
2809
+ providerName: providerSlug.toUpperCase(),
2810
+ serviceSlug: serviceRef,
2811
+ resourceName,
2812
+ status: "active",
2813
+ externalResourceId: externalId,
2814
+ secrets: { [envKey]: `https://${resourceName}.${providerSlug}.muxo.internal` },
2815
+ metadata: { source: created.source || "muxo_managed" },
2816
+ });
2817
+ sendJson(res, 201, {
2818
+ ok: true,
2819
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2820
+ ...created,
2821
+ });
2822
+ return;
2823
+ }
2824
+ const resourceIdMatch = pathname.match(/^\/api\/v1\/provisioning\/resources\/([^/]+)$/);
2825
+ if (resourceIdMatch && (req.method === "GET" || req.method === "POST")) {
2826
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
2827
+ const resourceId = decodeURIComponent(resourceIdMatch[1]);
2828
+ if (req.method === "GET") {
2829
+ const retrieved = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2830
+ ? await engine.stripeProjects.retrieveResource(resourceId, stripeByok)
2831
+ : managedResource({ id: resourceId });
2832
+ sendJson(res, 200, {
2833
+ ok: true,
2834
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2835
+ ...retrieved,
2836
+ });
2837
+ return;
2838
+ }
2839
+ const body = JSON.parse((await readBody(req)) || "{}");
2840
+ const updated = engine.stripeProjects.hasStripeV2ApiKey(stripeByok)
2841
+ ? await engine.stripeProjects.updateResource(resourceId, body.configuration || body, stripeByok)
2842
+ : managedResource({ id: resourceId, status: "active" });
2843
+ sendJson(res, 200, {
2844
+ ok: true,
2845
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
2846
+ ...updated,
2847
+ });
2848
+ return;
2849
+ }
2850
+ // Governance Policy
2851
+ if (pathname === "/api/v1/policy" && req.method === "GET") {
2852
+ const summary = engine.getGuardrailsSummary(ctx);
2853
+ sendJson(res, 200, {
2854
+ ok: true,
2855
+ policy: summary.policy,
2856
+ plainEnglish: summary.plainEnglish,
2857
+ explanation: summary.plainEnglish.explanation,
2858
+ activeDefaults: summary.activeDefaults,
2859
+ activeGuardrails: summary.activeGuardrails,
2860
+ });
2861
+ return;
2862
+ }
2863
+ if (pathname === "/api/v1/policy" && req.method === "POST") {
2864
+ const body = JSON.parse((await readBody(req)) || "{}");
2865
+ const updated = store.updateWorkspacePolicy(ctx.workspace.id, sanitizePolicyPatch(body));
2866
+ const plainEnglish = describePolicyInPlainEnglish(updated.policy);
2867
+ sendJson(res, 200, {
2868
+ ok: true,
2869
+ workspace: updated,
2870
+ plainEnglishGuardrails: plainEnglish,
2871
+ explanation: plainEnglish.explanation,
2872
+ });
2873
+ return;
2874
+ }
2875
+ // Plans & Approvals & Execution
2876
+ if (pathname === "/api/v1/plans" && req.method === "GET") {
2877
+ sendJson(res, 200, {
2878
+ ok: true,
2879
+ plans: store.listPlans(ctx.workspace.id, 50),
2880
+ });
2881
+ return;
2882
+ }
2883
+ if (pathname === "/api/v1/plans" && req.method === "POST") {
2884
+ const body = JSON.parse((await readBody(req)) || "{}");
2885
+ const rawFunding = body.fundingMode || body.funding_mode;
2886
+ const fundingMode = rawFunding === "byok" || rawFunding === "customer_funded"
2887
+ ? "byok"
2888
+ : rawFunding === "muxo_funded"
2889
+ ? "muxo_funded"
2890
+ : undefined;
2891
+ const planned = await engine.planAction(ctx, {
2892
+ toolSlug: String(body.toolSlug || body.tool_slug || ""),
2893
+ input: body.input || {},
2894
+ reason: body.reason ? String(body.reason) : undefined,
2895
+ fundingMode,
2896
+ });
2897
+ sendJson(res, 201, { ok: true, ...planned });
2898
+ return;
2899
+ }
2900
+ const resolvePlanCtx = (planId) => {
2901
+ const found = store.getPlanById(planId);
2902
+ if (found &&
2903
+ found.workspaceId !== ctx.workspace.id &&
2904
+ ctx.authType === "session" &&
2905
+ store.userCanAccessWorkspace(ctx.user.id, found.workspaceId)) {
2906
+ const targetWs = store.getWorkspace(found.workspaceId);
2907
+ if (targetWs) {
2908
+ return { ...ctx, workspace: targetWs };
2909
+ }
2910
+ }
2911
+ return ctx;
2912
+ };
2913
+ const planApproveMatch = pathname.match(/^\/api\/v1\/plans\/([^/]+)\/approve$/);
2914
+ if (planApproveMatch && req.method === "POST") {
2915
+ const planId = decodeURIComponent(planApproveMatch[1]);
2916
+ const planCtx = resolvePlanCtx(planId);
2917
+ const body = JSON.parse((await readBody(req)) || "{}");
2918
+ const decision = body.decision === "rejected" || body.decision === "reject"
2919
+ ? "rejected"
2920
+ : "approved";
2921
+ const plan = engine.approvePlan(planCtx, planId, decision);
2922
+ sendJson(res, 200, { ok: true, plan });
2923
+ return;
2924
+ }
2925
+ const planWaitMatch = pathname.match(/^\/api\/v1\/plans\/([^/]+)\/wait$/);
2926
+ if (planWaitMatch && (req.method === "POST" || req.method === "GET")) {
2927
+ const planId = decodeURIComponent(planWaitMatch[1]);
2928
+ const planCtx = resolvePlanCtx(planId);
2929
+ const body = req.method === "POST" ? JSON.parse((await readBody(req)) || "{}") : {};
2930
+ const rawTimeout = body.timeoutSeconds ??
2931
+ body.timeout_seconds ??
2932
+ url.searchParams.get("timeout_seconds") ??
2933
+ url.searchParams.get("timeoutSeconds");
2934
+ const waited = await engine.waitForPlanApproval(planCtx, {
2935
+ planId,
2936
+ timeoutSeconds: clampWaitSeconds(rawTimeout, 15, 60),
2937
+ });
2938
+ sendJson(res, 200, waited);
2939
+ return;
2940
+ }
2941
+ const planExecuteMatch = pathname.match(/^\/api\/v1\/plans\/([^/]+)\/execute$/);
2942
+ if (planExecuteMatch && req.method === "POST") {
2943
+ const planId = decodeURIComponent(planExecuteMatch[1]);
2944
+ const planCtx = resolvePlanCtx(planId);
2945
+ const plan = engine.getPlan(planCtx, planId);
2946
+ const result = await engine.executePlanForRisk(planCtx, planId, plan.riskLevel);
2947
+ sendJson(res, 200, {
2948
+ ok: true,
2949
+ ...result,
2950
+ settlement: result.receipt?.supplierSettlement || null,
2951
+ });
2952
+ return;
2953
+ }
2954
+ const planGetMatch = pathname.match(/^\/api\/v1\/plans\/([^/]+)$/);
2955
+ if (planGetMatch && req.method === "GET") {
2956
+ const planId = decodeURIComponent(planGetMatch[1]);
2957
+ const planCtx = resolvePlanCtx(planId);
2958
+ const wait = url.searchParams.get("wait") === "true" ||
2959
+ url.searchParams.get("wait_for_approval") === "true";
2960
+ if (wait) {
2961
+ const waited = await engine.waitForPlanApproval(planCtx, {
2962
+ planId,
2963
+ timeoutSeconds: clampWaitSeconds(url.searchParams.get("timeout_seconds"), 15, 60),
2964
+ });
2965
+ sendJson(res, 200, waited);
2966
+ return;
2967
+ }
2968
+ const plan = engine.getPlan(planCtx, planId);
2969
+ sendJson(res, 200, {
2970
+ ok: true,
2971
+ approved: APPROVED_PLAN_STATUSES.has(plan.approvalStatus),
2972
+ timedOut: false,
2973
+ plan,
2974
+ });
2975
+ return;
2976
+ }
2977
+ // Runs & Receipts
2978
+ if (pathname === "/api/v1/runs" && req.method === "GET") {
2979
+ sendJson(res, 200, {
2980
+ ok: true,
2981
+ runs: store.listRuns(ctx.workspace.id, 50),
2982
+ });
2983
+ return;
2984
+ }
2985
+ const runCancelMatch = pathname.match(/^\/api\/v1\/runs\/([^/]+)\/cancel$/);
2986
+ if (runCancelMatch && req.method === "POST") {
2987
+ const runId = decodeURIComponent(runCancelMatch[1]);
2988
+ const body = JSON.parse((await readBody(req)) || "{}");
2989
+ const cancelled = engine.cancelRun(ctx, runId, body.reason ? String(body.reason) : undefined);
2990
+ sendJson(res, 200, cancelled);
2991
+ return;
2992
+ }
2993
+ const runGetMatch = pathname.match(/^\/api\/v1\/runs\/([^/]+)$/);
2994
+ if (runGetMatch && req.method === "GET") {
2995
+ const runId = decodeURIComponent(runGetMatch[1]);
2996
+ const run = engine.getRun(ctx, runId);
2997
+ sendJson(res, 200, { ok: true, run });
2998
+ return;
2999
+ }
3000
+ if (pathname === "/api/v1/receipts" && req.method === "GET") {
3001
+ const limit = Math.min(100, Math.max(1, Number(url.searchParams.get("limit") || 25)));
3002
+ const list = engine.listReceipts(ctx, limit);
3003
+ sendJson(res, 200, { ok: true, ...list });
3004
+ return;
3005
+ }
3006
+ if ((pathname === "/api/v1/receipts/verify-chain" ||
3007
+ pathname === "/api/v1/receipts/chain") &&
3008
+ req.method === "GET") {
3009
+ const limit = Math.min(200, Math.max(1, Number(url.searchParams.get("limit") || 100)));
3010
+ const chain = engine.verifyReceiptChain(ctx, limit);
3011
+ sendJson(res, 200, chain);
3012
+ return;
3013
+ }
3014
+ if (pathname.startsWith("/api/v1/receipts/") && req.method === "GET") {
3015
+ const receiptId = decodeURIComponent(pathname.slice("/api/v1/receipts/".length));
3016
+ const verified = engine.getReceipt(ctx, receiptId);
3017
+ sendJson(res, 200, { ok: true, ...verified });
3018
+ return;
3019
+ }
3020
+ // BYOK (Bring Your Own Key) Encrypted Vault Management
3021
+ if (pathname === "/api/v1/byok" && req.method === "GET") {
3022
+ sendJson(res, 200, {
3023
+ ok: true,
3024
+ workspaceId: ctx.workspace.id,
3025
+ keys: store.listByokKeys(ctx.workspace.id),
3026
+ });
3027
+ return;
3028
+ }
3029
+ if (pathname === "/api/v1/byok" && req.method === "POST") {
3030
+ const body = JSON.parse((await readBody(req)) || "{}");
3031
+ const provider = String(body.provider || "").trim();
3032
+ const secretKey = String(body.secretKey || body.apiKey || body.key || "").trim();
3033
+ if (!provider || !secretKey) {
3034
+ sendJson(res, 400, {
3035
+ ok: false,
3036
+ error: "Both 'provider' and 'secretKey' are required",
3037
+ });
3038
+ return;
3039
+ }
3040
+ const record = store.setByokKey(ctx.workspace.id, provider, secretKey);
3041
+ sendJson(res, 201, {
3042
+ ok: true,
3043
+ key: record,
3044
+ keys: store.listByokKeys(ctx.workspace.id),
3045
+ });
3046
+ return;
3047
+ }
3048
+ if (pathname.startsWith("/api/v1/byok/") && req.method === "DELETE") {
3049
+ const provider = decodeURIComponent(pathname.slice("/api/v1/byok/".length));
3050
+ const deleted = store.deleteByokKey(ctx.workspace.id, provider);
3051
+ sendJson(res, 200, {
3052
+ ok: true,
3053
+ deleted,
3054
+ provider,
3055
+ keys: store.listByokKeys(ctx.workspace.id),
3056
+ });
3057
+ return;
3058
+ }
3059
+ // Natural Language Intent & Governed Execution Endpoint
3060
+ if ((pathname === "/api/v1/do" || pathname === "/v1/do") &&
3061
+ req.method === "POST") {
3062
+ const body = JSON.parse((await readBody(req)) || "{}");
3063
+ const prompt = String(body.prompt ||
3064
+ body.instruction ||
3065
+ body.query ||
3066
+ body.command ||
3067
+ body.toolSlug ||
3068
+ body.tool ||
3069
+ "").trim();
3070
+ if (!prompt) {
3071
+ sendJson(res, 400, {
3072
+ ok: false,
3073
+ error: "Missing 'prompt' or 'instruction' in request body",
3074
+ });
3075
+ return;
3076
+ }
3077
+ const dryRun = body.dryRun === true ||
3078
+ body.dry_run === true ||
3079
+ body.planOnly === true ||
3080
+ body.autoExecute === false ||
3081
+ body.auto_execute === false;
3082
+ const autoApprove = body.autoApprove !== false &&
3083
+ body.auto_approve !== false &&
3084
+ body.approveRisky !== false &&
3085
+ body.approve_risky !== false;
3086
+ const rawFunding = body.fundingMode || body.funding_mode;
3087
+ const fundingMode = rawFunding === "byok" || rawFunding === "customer_funded"
3088
+ ? "byok"
3089
+ : rawFunding === "muxo_funded"
3090
+ ? "muxo_funded"
3091
+ : undefined;
3092
+ const result = await engine.doNaturalLanguage(ctx, {
3093
+ prompt,
3094
+ input: body.input && typeof body.input === "object"
3095
+ ? body.input
3096
+ : undefined,
3097
+ autoApprove,
3098
+ dryRun,
3099
+ planOnly: dryRun,
3100
+ fundingMode,
3101
+ });
3102
+ sendJson(res, 200, result);
3103
+ return;
3104
+ }
3105
+ // Live Stripe Billing, Credit Top-Up & Supplier Settlement Ledger
3106
+ if ((pathname === "/api/v1/billing/status" || pathname === "/api/v1/billing") &&
3107
+ req.method === "GET") {
3108
+ const ws = store.getWorkspace(ctx.workspace.id);
3109
+ const settlementSummary = store.getSupplierSettlementSummary(ctx.workspace.id);
3110
+ const byokKeys = store.listByokKeys(ctx.workspace.id);
3111
+ sendJson(res, 200, {
3112
+ ok: true,
3113
+ workspaceId: ws.id,
3114
+ workspace: ws,
3115
+ tier: ws.tier,
3116
+ creditsBalance: ws.creditsBalance,
3117
+ creditBalance: ws.creditsBalance,
3118
+ monthlyActionsUsed: ws.monthlyActionsUsed,
3119
+ monthlyActionsLimit: ws.monthlyActionsLimit,
3120
+ stripeConfigured: engine.stripeBilling.isConfigured,
3121
+ stripeCustomerId: ws.stripeCustomerId,
3122
+ stripeSubscriptionId: ws.stripeSubscriptionId,
3123
+ settlementSummary,
3124
+ byokKeys,
3125
+ entries: store.listLedgerEntries(ctx.workspace.id, 25),
3126
+ });
3127
+ return;
3128
+ }
3129
+ if (pathname === "/api/v1/billing/ledger" && req.method === "GET") {
3130
+ sendJson(res, 200, {
3131
+ ok: true,
3132
+ entries: store.listLedgerEntries(ctx.workspace.id, 50),
3133
+ settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
3134
+ });
3135
+ return;
3136
+ }
3137
+ if (pathname === "/api/v1/billing/settlements" && req.method === "GET") {
3138
+ sendJson(res, 200, {
3139
+ ok: true,
3140
+ settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
3141
+ entries: store.listLedgerEntries(ctx.workspace.id, 100),
3142
+ });
3143
+ return;
3144
+ }
3145
+ if (pathname === "/api/v1/billing/topup" && req.method === "POST") {
3146
+ const body = JSON.parse((await readBody(req)) || "{}");
3147
+ const credits = Math.max(1, Math.min(1000000, Number(body.credits || 1000)));
3148
+ const referenceId = String(body.referenceId || "").trim() ||
3149
+ `topup_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
3150
+ const usdPaid = (credits * 0.01).toFixed(2);
3151
+ const updatedWorkspace = store.addWorkspaceCredits({
3152
+ workspaceId: ctx.workspace.id,
3153
+ credits,
3154
+ referenceId,
3155
+ description: String(body.description || "").trim() ||
3156
+ `User purchased +${credits.toLocaleString()} Muxo Credits ($${usdPaid} USD -> Muxo Master Wallet)`,
3157
+ });
3158
+ sendJson(res, 200, {
3159
+ ok: true,
3160
+ creditsAdded: credits,
3161
+ creditBalance: updatedWorkspace.creditsBalance,
3162
+ creditsBalance: updatedWorkspace.creditsBalance,
3163
+ usdPaid: Number(usdPaid),
3164
+ workspace: updatedWorkspace,
3165
+ settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
3166
+ });
3167
+ return;
3168
+ }
3169
+ if (pathname === "/api/v1/billing/upgrade" && req.method === "POST") {
3170
+ const body = JSON.parse((await readBody(req)) || "{}");
3171
+ const referenceId = String(body.referenceId || "").trim() ||
3172
+ `upgrade_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
3173
+ const bonusCredits = body.bonusCredits !== undefined ? Number(body.bonusCredits) : 1000;
3174
+ const updatedWorkspace = store.upgradeWorkspaceToStarter({
3175
+ workspaceId: ctx.workspace.id,
3176
+ stripeCustomerId: ctx.workspace.stripeCustomerId ||
3177
+ `cus_muxo_${ctx.workspace.id.slice(3, 11)}`,
3178
+ stripeSubscriptionId: ctx.workspace.stripeSubscriptionId ||
3179
+ `sub_muxo_${referenceId.slice(-10)}`,
3180
+ referenceId,
3181
+ bonusCredits,
3182
+ });
3183
+ sendJson(res, 200, {
3184
+ ok: true,
3185
+ tier: updatedWorkspace.tier,
3186
+ creditsBalance: updatedWorkspace.creditsBalance,
3187
+ creditBalance: updatedWorkspace.creditsBalance,
3188
+ workspace: updatedWorkspace,
3189
+ settlementSummary: store.getSupplierSettlementSummary(ctx.workspace.id),
3190
+ });
3191
+ return;
3192
+ }
3193
+ if (pathname === "/api/v1/billing/checkout" && req.method === "POST") {
3194
+ const body = JSON.parse((await readBody(req)) || "{}");
3195
+ const kind = body.kind === "credit_topup" ? "credit_topup" : "starter_subscription";
3196
+ const credits = body.credits ? Number(body.credits) : 1000;
3197
+ const session = await engine.stripeBilling.createCheckoutSession({
3198
+ workspace: ctx.workspace,
3199
+ customerEmail: ctx.user.email,
3200
+ kind,
3201
+ credits,
3202
+ returnBaseUrl: body.returnBaseUrl ? String(body.returnBaseUrl) : config.baseUrl,
3203
+ });
3204
+ sendJson(res, 200, { ok: true, ...session });
3205
+ return;
3206
+ }
3207
+ if (pathname === "/api/v1/billing/verify-session" && req.method === "POST") {
3208
+ const body = JSON.parse((await readBody(req)) || "{}");
3209
+ const verified = await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(body.sessionId || ""), ctx.workspace.id, body.kind === "credit_topup"
3210
+ ? "credit_topup"
3211
+ : body.kind === "starter_subscription"
3212
+ ? "starter_subscription"
3213
+ : undefined);
3214
+ sendJson(res, 200, { ok: true, ...verified });
3215
+ return;
3216
+ }
3217
+ if (pathname === "/api/v1/billing/webhook" && req.method === "POST") {
3218
+ const rawBody = await readBody(req);
3219
+ const sig = String(req.headers["stripe-signature"] || "");
3220
+ if (!engine.stripeBilling.verifyWebhookSignature(rawBody, sig)) {
3221
+ sendJson(res, 400, { ok: false, error: "Invalid Stripe webhook signature" });
3222
+ return;
3223
+ }
3224
+ const event = JSON.parse(rawBody || "{}");
3225
+ if (event.type === "checkout.session.completed") {
3226
+ const sessionObj = event.data?.object;
3227
+ if (sessionObj?.id) {
3228
+ await engine.stripeBilling.verifyAndFulfillCheckoutSession(String(sessionObj.id));
3229
+ }
3230
+ }
3231
+ sendJson(res, 200, { ok: true, received: true });
3232
+ return;
3233
+ }
3234
+ // Stripe v2 Billing Meter Event Stream (`POST /v2/billing/meter_event_session` + `POST /v2/billing/meter_event_stream`)
3235
+ if (pathname === "/api/v1/billing/meter_events" && req.method === "GET") {
3236
+ const limit = Number(url.searchParams.get("limit") || 50);
3237
+ const summary = engine.stripeBilling.listMeterEvents(ctx.workspace.id, limit);
3238
+ sendJson(res, 200, {
3239
+ ok: true,
3240
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
3241
+ ...summary,
3242
+ });
3243
+ return;
3244
+ }
3245
+ if (pathname === "/api/v1/billing/meter_events" && req.method === "POST") {
3246
+ const body = JSON.parse((await readBody(req)) || "{}");
3247
+ const result = await engine.stripeBilling.recordMeterEvent({
3248
+ workspace: ctx.workspace,
3249
+ event_name: body.event_name ? String(body.event_name) : undefined,
3250
+ value: Number(body.value ?? 1),
3251
+ lane: body.lane ? String(body.lane) : undefined,
3252
+ tool_slug: body.tool_slug ? String(body.tool_slug) : undefined,
3253
+ identifier: body.identifier ? String(body.identifier) : undefined,
3254
+ timestamp: body.timestamp ? String(body.timestamp) : undefined,
3255
+ });
3256
+ sendJson(res, 201, {
3257
+ ok: true,
3258
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
3259
+ ...result,
3260
+ });
3261
+ return;
3262
+ }
3263
+ // Stripe v2 Event Destinations & Provisioning Thin Event Webhook Handler
3264
+ if (pathname === "/api/v1/provisioning/event_destinations" &&
3265
+ req.method === "POST") {
3266
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
3267
+ const body = JSON.parse((await readBody(req)) || "{}");
3268
+ const destination = await engine.stripeProjects.createV2EventDestination({
3269
+ name: body.name ? String(body.name) : undefined,
3270
+ url: String(body.url || `${config.baseUrl}/api/v1/provisioning/events`),
3271
+ events: Array.isArray(body.events) ? body.events.map(String) : undefined,
3272
+ apiKeyOverride: stripeByok,
3273
+ });
3274
+ sendJson(res, 201, {
3275
+ ok: true,
3276
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
3277
+ ...destination,
3278
+ });
3279
+ return;
3280
+ }
3281
+ if (pathname === "/api/v1/provisioning/events" && req.method === "POST") {
3282
+ const stripeByok = store.getByokKey(ctx.workspace.id, "stripe");
3283
+ const verifiedContext = store.getByokKey(ctx.workspace.id, "stripe_connected_account") || undefined;
3284
+ const body = JSON.parse((await readBody(req)) || "{}");
3285
+ const handled = await engine.stripeProjects.handleV2ProvisioningEvent({
3286
+ event: body,
3287
+ apiKeyOverride: stripeByok,
3288
+ stripeContext: verifiedContext,
3289
+ });
3290
+ if (handled.revealedConfiguration &&
3291
+ Object.keys(handled.revealedConfiguration).length > 0 &&
3292
+ handled.relatedObjectId) {
3293
+ store.upsertResource({
3294
+ workspaceId: ctx.workspace.id,
3295
+ resourceName: String(handled.resource?.name || handled.relatedObjectId),
3296
+ providerSlug: String(handled.resource?.provider || "neon"),
3297
+ providerName: String(handled.resource?.provider || "Neon"),
3298
+ serviceSlug: String(handled.resource?.service_ref || "postgres"),
3299
+ planSlug: "launch",
3300
+ status: "active",
3301
+ secrets: handled.revealedConfiguration,
3302
+ externalResourceId: handled.relatedObjectId,
3303
+ metadata: {
3304
+ region: "aws-us-east-1",
3305
+ },
3306
+ });
3307
+ }
3308
+ sendJson(res, 200, {
3309
+ ok: true,
3310
+ apiVersion: engine.stripeProjects.provisioningApiVersion,
3311
+ ...handled,
3312
+ });
3313
+ return;
3314
+ }
3315
+ // Composio Dynamic Enterprise Toolkits, Scoped Auth Configs, Triggers & Multi-Tool Batch Execution
3316
+ if (pathname === "/api/v1/toolkits" && req.method === "GET") {
3317
+ const composioByok = store.getByokKey(ctx.workspace.id, "composio");
3318
+ const category = url.searchParams.get("category") || undefined;
3319
+ const search = url.searchParams.get("search") || url.searchParams.get("query") || undefined;
3320
+ const catalog = await engine.composioSdk.listToolkits({
3321
+ category,
3322
+ search,
3323
+ composioApiKey: composioByok,
3324
+ });
3325
+ sendJson(res, 200, { ok: true, ...catalog });
3326
+ return;
3327
+ }
3328
+ if (pathname === "/api/v1/connections/auth-configs" && req.method === "POST") {
3329
+ const composioByok = store.getByokKey(ctx.workspace.id, "composio");
3330
+ const body = JSON.parse((await readBody(req)) || "{}");
3331
+ const created = await engine.composioSdk.createCustomAuthConfig({
3332
+ workspaceId: ctx.workspace.id,
3333
+ toolkitSlug: String(body.toolkitSlug || body.provider || "github"),
3334
+ name: body.name ? String(body.name) : undefined,
3335
+ authScheme: body.authScheme === "API_KEY" ? "API_KEY" : "OAUTH2",
3336
+ scopeMode: body.scopeMode === "full_access" ? "full_access" : "read_only",
3337
+ customScopes: Array.isArray(body.customScopes)
3338
+ ? body.customScopes.map(String)
3339
+ : undefined,
3340
+ credentials: body.credentials && typeof body.credentials === "object"
3341
+ ? body.credentials
3342
+ : undefined,
3343
+ composioApiKey: composioByok,
3344
+ });
3345
+ sendJson(res, 201, { ok: true, ...created });
3346
+ return;
3347
+ }
3348
+ if (pathname === "/api/v1/triggers" && req.method === "GET") {
3349
+ const provider = url.searchParams.get("provider") || undefined;
3350
+ const triggerSlug = url.searchParams.get("triggerSlug") || url.searchParams.get("trigger_slug") || undefined;
3351
+ const composioByok = store.getByokKey(ctx.workspace.id, "composio");
3352
+ const available = engine.composioSdk.listTriggerTypes(provider);
3353
+ const subscriptions = engine.composioSdk.listActiveTriggers(ctx.workspace.id);
3354
+ const events = engine.composioSdk.listTriggerEvents(ctx.workspace.id, 25);
3355
+ const triggerType = triggerSlug
3356
+ ? await engine.composioSdk.getTriggerType(triggerSlug, composioByok)
3357
+ : undefined;
3358
+ sendJson(res, 200, {
3359
+ ok: true,
3360
+ availableTriggers: available,
3361
+ activeSubscriptions: subscriptions,
3362
+ recentEvents: events,
3363
+ webhookConfig: engine.composioSdk.getWebhookConfigInfo(),
3364
+ ...(triggerType ? { triggerType } : {}),
3365
+ });
3366
+ return;
3367
+ }
3368
+ if (pathname === "/api/v1/triggers" && req.method === "POST") {
3369
+ const composioByok = store.getByokKey(ctx.workspace.id, "composio");
3370
+ const body = JSON.parse((await readBody(req)) || "{}");
3371
+ const rawConfig = body.triggerConfig && typeof body.triggerConfig === "object"
3372
+ ? body.triggerConfig
3373
+ : body.config && typeof body.config === "object"
3374
+ ? body.config
3375
+ : {};
3376
+ const subscription = await engine.composioSdk.enableTrigger({
3377
+ workspaceId: ctx.workspace.id,
3378
+ userId: body.userId ? String(body.userId) : undefined,
3379
+ triggerSlug: String(body.triggerSlug || body.trigger_slug || "GITHUB_COMMIT_EVENT"),
3380
+ providerSlug: String(body.providerSlug || body.provider || "github"),
3381
+ connectedAccountId: body.connectedAccountId
3382
+ ? String(body.connectedAccountId)
3383
+ : undefined,
3384
+ webhookUrl: body.webhookUrl ? String(body.webhookUrl) : undefined,
3385
+ config: rawConfig,
3386
+ subscribe: body.subscribe === true,
3387
+ composioApiKey: composioByok,
3388
+ });
3389
+ sendJson(res, 201, { ok: true, subscription });
3390
+ return;
3391
+ }
3392
+ if ((pathname === "/api/v1/triggers/webhook" ||
3393
+ pathname === "/webhooks" ||
3394
+ pathname === "/webhooks/composio") &&
3395
+ req.method === "POST") {
3396
+ const rawBody = await readBody(req);
3397
+ const sigHeader = String(req.headers["webhook-signature"] ||
3398
+ req.headers["x-composio-webhook-signature"] ||
3399
+ "").trim();
3400
+ const webhookIdHeader = String(req.headers["webhook-id"] || req.headers["x-composio-webhook-id"] || "").trim();
3401
+ const webhookTimestampHeader = String(req.headers["webhook-timestamp"] ||
3402
+ req.headers["x-composio-webhook-timestamp"] ||
3403
+ "").trim();
3404
+ const webhookVersionHeader = String(req.headers["x-composio-webhook-version"] || "").trim();
3405
+ let signatureVerified = false;
3406
+ if (sigHeader) {
3407
+ const check = await verifyComposioWebhookSignature({
3408
+ rawPayload: rawBody,
3409
+ signatureHeader: sigHeader,
3410
+ webhookIdHeader,
3411
+ webhookTimestampHeader,
3412
+ secret: config.composioWebhookSecret,
3413
+ });
3414
+ if (config.composioWebhookSecret && !check.verified) {
3415
+ sendJson(res, 401, {
3416
+ ok: false,
3417
+ error: `Invalid Composio webhook signature (${check.reason || "verification_failed"})`,
3418
+ });
3419
+ return;
3420
+ }
3421
+ signatureVerified = check.verified;
3422
+ }
3423
+ const body = JSON.parse(rawBody || "{}");
3424
+ const envelope = parseComposioWebhookEnvelope(body, {
3425
+ webhookId: webhookIdHeader || undefined,
3426
+ webhookTimestamp: webhookTimestampHeader || undefined,
3427
+ webhookVersion: webhookVersionHeader || undefined,
3428
+ });
3429
+ let targetWorkspaceId = ctx.workspace.id;
3430
+ if (envelope.userId && store.getWorkspace(envelope.userId)) {
3431
+ targetWorkspaceId = envelope.userId;
3432
+ }
3433
+ if (envelope.eventType === "composio.connected_account.activated" ||
3434
+ envelope.eventType === "composio.connected_account.expired") {
3435
+ const nextStatus = envelope.eventType === "composio.connected_account.activated"
3436
+ ? "active"
3437
+ : "expired";
3438
+ store.upsertConnection({
3439
+ workspaceId: targetWorkspaceId,
3440
+ lane: "composio_sdk",
3441
+ providerSlug: envelope.providerSlug,
3442
+ providerName: envelope.providerSlug.charAt(0).toUpperCase() +
3443
+ envelope.providerSlug.slice(1),
3444
+ status: nextStatus,
3445
+ externalAccountId: envelope.connectedAccountId || null,
3446
+ metadata: {
3447
+ webhookEvent: envelope.eventType,
3448
+ webhookId: envelope.webhookId,
3449
+ updatedViaWebhookAt: new Date().toISOString(),
3450
+ },
3451
+ });
3452
+ if (nextStatus === "expired") {
3453
+ engine.composioSdk.disableTriggersMatching({
3454
+ workspaceId: targetWorkspaceId,
3455
+ connectedAccountId: envelope.connectedAccountId,
3456
+ });
3457
+ }
3458
+ }
3459
+ else if (envelope.eventType === "composio.trigger.disabled") {
3460
+ engine.composioSdk.disableTriggersMatching({
3461
+ workspaceId: targetWorkspaceId,
3462
+ triggerId: envelope.triggerId,
3463
+ triggerSlug: envelope.triggerSlug,
3464
+ connectedAccountId: envelope.connectedAccountId,
3465
+ });
3466
+ }
3467
+ const rawPayloadText = JSON.stringify(envelope.payloadObj);
3468
+ const injectionScan = engine.governance.scanMemoryContent({
3469
+ workspaceId: targetWorkspaceId,
3470
+ content: rawPayloadText,
3471
+ });
3472
+ const disposition = injectionScan.safe ? "dispatched" : "quarantined";
3473
+ const eventRecord = {
3474
+ id: `trgevt_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
3475
+ workspaceId: targetWorkspaceId,
3476
+ eventType: envelope.eventType,
3477
+ webhookVersion: envelope.webhookVersion,
3478
+ signatureVerified,
3479
+ webhookId: envelope.webhookId,
3480
+ triggerId: envelope.triggerId,
3481
+ triggerSlug: envelope.triggerSlug,
3482
+ providerSlug: envelope.providerSlug,
3483
+ connectedAccountId: envelope.connectedAccountId,
3484
+ userId: envelope.userId,
3485
+ projectId: envelope.projectId,
3486
+ orgId: envelope.orgId,
3487
+ receivedAt: new Date().toISOString(),
3488
+ promptInjectionScan: {
3489
+ safe: injectionScan.safe,
3490
+ riskScore: injectionScan.findingsCount * 0.35,
3491
+ findings: injectionScan.findings.map((f) => `${f.patternId}: ${f.description}`),
3492
+ },
3493
+ disposition: disposition,
3494
+ payloadPreview: envelope.payloadObj,
3495
+ };
3496
+ engine.composioSdk.recordTriggerEvent(eventRecord);
3497
+ sendJson(res, injectionScan.safe ? 200 : 202, {
3498
+ ok: true,
3499
+ ...eventRecord,
3500
+ });
3501
+ return;
3502
+ }
3503
+ if (pathname === "/api/v1/plans/batch" && req.method === "POST") {
3504
+ const body = JSON.parse((await readBody(req)) || "{}");
3505
+ const batchResult = await engine.executeBatchPlans(ctx, {
3506
+ items: Array.isArray(body.items) ? body.items : [],
3507
+ // Only a signed-in person may pre-approve pending plans; agent keys never can.
3508
+ autoApprove: body.autoApprove === true && signedInPerson(auth),
3509
+ });
3510
+ sendJson(res, 200, { ok: true, ...batchResult });
3511
+ return;
3512
+ }
3513
+ // Cloudflare Vectorize Hybrid Semantic + Bi-Temporal Recall, Durable Workflows, and R2 WORM Audit Archive
3514
+ if (pathname === "/api/v1/memory/hybrid" && (req.method === "GET" || req.method === "POST")) {
3515
+ const body = req.method === "POST" ? JSON.parse((await readBody(req)) || "{}") : {};
3516
+ const query = String(body.query ?? body.q ?? url.searchParams.get("query") ?? url.searchParams.get("q") ?? "");
3517
+ const limit = Number(body.limit ?? url.searchParams.get("limit") ?? 10);
3518
+ const hybrid = engine.searchMemoryHybrid(ctx.workspace.id, query, limit);
3519
+ sendJson(res, 200, { ok: true, ...hybrid });
3520
+ return;
3521
+ }
3522
+ if (pathname === "/api/v1/workflows" && req.method === "GET") {
3523
+ const workflows = engine.listDurableWorkflows(ctx.workspace.id);
3524
+ sendJson(res, 200, { ok: true, workflows });
3525
+ return;
3526
+ }
3527
+ if (pathname === "/api/v1/workflows" && req.method === "POST") {
3528
+ const body = JSON.parse((await readBody(req)) || "{}");
3529
+ const workflow = await engine.startDurableWorkflow(ctx, {
3530
+ provider: body.provider ? String(body.provider) : undefined,
3531
+ serviceRef: body.serviceRef || body.service_ref ? String(body.serviceRef || body.service_ref) : undefined,
3532
+ name: body.name ? String(body.name) : undefined,
3533
+ region: body.region ? String(body.region) : undefined,
3534
+ });
3535
+ sendJson(res, 201, { ok: true, workflow });
3536
+ return;
3537
+ }
3538
+ const wfAdvanceMatch = pathname.match(/^\/api\/v1\/workflows\/([^/]+)\/advance$/);
3539
+ if (wfAdvanceMatch && req.method === "POST") {
3540
+ const wfId = decodeURIComponent(wfAdvanceMatch[1]);
3541
+ const body = JSON.parse((await readBody(req)) || "{}");
3542
+ const workflow = await engine.advanceDurableWorkflow(ctx, wfId, body);
3543
+ sendJson(res, 200, { ok: true, workflow });
3544
+ return;
3545
+ }
3546
+ if (pathname === "/api/v1/governance/archive" && req.method === "GET") {
3547
+ const archive = engine.exportTamperEvidentArchive(ctx.workspace.id);
3548
+ sendJson(res, 200, { ok: true, ...archive });
3549
+ return;
3550
+ }
3551
+ sendJson(res, 404, { ok: false, error: `Route not found: ${req.method} ${pathname}` });
3552
+ }
3553
+ catch (err) {
3554
+ sendJson(res, err instanceof MuxoToolError ? err.statusCode : 400, errorPayloadFrom(err, "Internal error"));
3555
+ }
3556
+ });
3557
+ return { server, engine, store, defaultApiKey: bootstrap.defaultApiKey };
3558
+ }
3559
+ // Start server when invoked directly
3560
+ const isMain = process.argv[1] &&
3561
+ (process.argv[1].endsWith("server.ts") || process.argv[1].endsWith("server.js"));
3562
+ if (isMain) {
3563
+ const config = getConfig();
3564
+ const { server, defaultApiKey } = createMuxoServer(config);
3565
+ server.listen(config.port, () => {
3566
+ console.log(`\n⚡ Muxo Platform running at ${config.baseUrl}`);
3567
+ console.log(` ├─ Web Console: ${config.baseUrl}/`);
3568
+ console.log(` ├─ MCP Server: ${config.baseUrl}/mcp`);
3569
+ console.log(` ├─ Agent Skill: ${config.baseUrl}/skill.md`);
3570
+ console.log(` └─ Health Check: ${config.baseUrl}/health`);
3571
+ if (defaultApiKey) {
3572
+ console.log(`\n🔑 Initial Workspace API Key created (save this): ${defaultApiKey}\n`);
3573
+ }
3574
+ });
3575
+ }