@agent-native/core 0.7.50 → 0.7.52

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 (121) hide show
  1. package/dist/a2a/agent-card.d.ts.map +1 -1
  2. package/dist/a2a/agent-card.js +21 -16
  3. package/dist/a2a/agent-card.js.map +1 -1
  4. package/dist/a2a/artifact-response.d.ts.map +1 -1
  5. package/dist/a2a/artifact-response.js +109 -5
  6. package/dist/a2a/artifact-response.js.map +1 -1
  7. package/dist/a2a/auth-policy.d.ts +10 -0
  8. package/dist/a2a/auth-policy.d.ts.map +1 -0
  9. package/dist/a2a/auth-policy.js +34 -0
  10. package/dist/a2a/auth-policy.js.map +1 -0
  11. package/dist/a2a/handlers.d.ts.map +1 -1
  12. package/dist/a2a/handlers.js +5 -4
  13. package/dist/a2a/handlers.js.map +1 -1
  14. package/dist/a2a/index.d.ts +1 -0
  15. package/dist/a2a/index.d.ts.map +1 -1
  16. package/dist/a2a/index.js +1 -0
  17. package/dist/a2a/index.js.map +1 -1
  18. package/dist/a2a/server.d.ts.map +1 -1
  19. package/dist/a2a/server.js +27 -14
  20. package/dist/a2a/server.js.map +1 -1
  21. package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
  22. package/dist/client/resources/ResourceEditor.js +2 -4
  23. package/dist/client/resources/ResourceEditor.js.map +1 -1
  24. package/dist/client/settings/AgentsSection.d.ts.map +1 -1
  25. package/dist/client/settings/AgentsSection.js +4 -6
  26. package/dist/client/settings/AgentsSection.js.map +1 -1
  27. package/dist/deploy/build.d.ts.map +1 -1
  28. package/dist/deploy/build.js +8 -0
  29. package/dist/deploy/build.js.map +1 -1
  30. package/dist/deploy/route-discovery.d.ts.map +1 -1
  31. package/dist/deploy/route-discovery.js +11 -2
  32. package/dist/deploy/route-discovery.js.map +1 -1
  33. package/dist/deploy/workspace-deploy.js +32 -3
  34. package/dist/deploy/workspace-deploy.js.map +1 -1
  35. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  36. package/dist/integrations/a2a-continuation-processor.js +17 -11
  37. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  38. package/dist/integrations/a2a-continuations-store.d.ts +2 -1
  39. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  40. package/dist/integrations/a2a-continuations-store.js +33 -4
  41. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  42. package/dist/integrations/plugin.d.ts.map +1 -1
  43. package/dist/integrations/plugin.js +2 -1
  44. package/dist/integrations/plugin.js.map +1 -1
  45. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  46. package/dist/integrations/webhook-handler.js +11 -1
  47. package/dist/integrations/webhook-handler.js.map +1 -1
  48. package/dist/onboarding/plugin.d.ts.map +1 -1
  49. package/dist/onboarding/plugin.js +2 -1
  50. package/dist/onboarding/plugin.js.map +1 -1
  51. package/dist/org/plugin.d.ts.map +1 -1
  52. package/dist/org/plugin.js +2 -1
  53. package/dist/org/plugin.js.map +1 -1
  54. package/dist/resources/handlers.d.ts.map +1 -1
  55. package/dist/resources/handlers.js +2 -3
  56. package/dist/resources/handlers.js.map +1 -1
  57. package/dist/resources/metadata.d.ts +5 -0
  58. package/dist/resources/metadata.d.ts.map +1 -1
  59. package/dist/resources/metadata.js +17 -2
  60. package/dist/resources/metadata.js.map +1 -1
  61. package/dist/resources/store.d.ts.map +1 -1
  62. package/dist/resources/store.js +2 -1
  63. package/dist/resources/store.js.map +1 -1
  64. package/dist/scripts/call-agent.js +2 -2
  65. package/dist/scripts/call-agent.js.map +1 -1
  66. package/dist/server/action-routes.d.ts.map +1 -1
  67. package/dist/server/action-routes.js +5 -11
  68. package/dist/server/action-routes.js.map +1 -1
  69. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  70. package/dist/server/agent-chat-plugin.js +2 -1
  71. package/dist/server/agent-chat-plugin.js.map +1 -1
  72. package/dist/server/agent-discovery.d.ts.map +1 -1
  73. package/dist/server/agent-discovery.js +7 -4
  74. package/dist/server/agent-discovery.js.map +1 -1
  75. package/dist/server/auth-plugin.d.ts.map +1 -1
  76. package/dist/server/auth-plugin.js +2 -1
  77. package/dist/server/auth-plugin.js.map +1 -1
  78. package/dist/server/auth.d.ts.map +1 -1
  79. package/dist/server/auth.js +13 -12
  80. package/dist/server/auth.js.map +1 -1
  81. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  82. package/dist/server/core-routes-plugin.js +9 -29
  83. package/dist/server/core-routes-plugin.js.map +1 -1
  84. package/dist/server/cors-origins.d.ts +10 -0
  85. package/dist/server/cors-origins.d.ts.map +1 -0
  86. package/dist/server/cors-origins.js +34 -0
  87. package/dist/server/cors-origins.js.map +1 -0
  88. package/dist/server/create-server.d.ts.map +1 -1
  89. package/dist/server/create-server.js +10 -29
  90. package/dist/server/create-server.js.map +1 -1
  91. package/dist/server/framework-request-handler.d.ts +11 -0
  92. package/dist/server/framework-request-handler.d.ts.map +1 -1
  93. package/dist/server/framework-request-handler.js +24 -1
  94. package/dist/server/framework-request-handler.js.map +1 -1
  95. package/dist/server/resources-plugin.d.ts.map +1 -1
  96. package/dist/server/resources-plugin.js +2 -1
  97. package/dist/server/resources-plugin.js.map +1 -1
  98. package/dist/terminal/terminal-plugin.d.ts.map +1 -1
  99. package/dist/terminal/terminal-plugin.js +2 -1
  100. package/dist/terminal/terminal-plugin.js.map +1 -1
  101. package/dist/vite/index.d.ts +1 -1
  102. package/dist/vite/index.d.ts.map +1 -1
  103. package/dist/vite/index.js +1 -1
  104. package/dist/vite/index.js.map +1 -1
  105. package/docs/content/a2a-protocol.md +75 -6
  106. package/docs/content/creating-templates.md +10 -0
  107. package/docs/content/dispatch.md +94 -0
  108. package/docs/content/getting-started.md +8 -0
  109. package/docs/content/key-concepts.md +16 -0
  110. package/docs/content/messaging.md +45 -13
  111. package/docs/content/multi-app-workspace.md +10 -2
  112. package/docs/content/notifications.md +1 -1
  113. package/docs/content/observability.md +184 -0
  114. package/docs/content/onboarding.md +7 -2
  115. package/docs/content/template-dispatch.md +3 -1
  116. package/docs/content/tools.md +95 -1
  117. package/docs/content/tracking.md +1 -1
  118. package/docs/content/what-is-agent-native.md +3 -1
  119. package/docs/content/workspace-management.md +5 -5
  120. package/docs/content/workspace.md +2 -0
  121. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-plugin.js","sourceRoot":"","sources":["../../src/terminal/terminal-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { getH3App } from \"../server/framework-request-handler.js\";\nimport { isNodeRuntime } from \"../shared/runtime.js\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { createRequire } from \"node:module\";\n/**\n * Nitro Plugin — Agent Terminal\n *\n * Starts a PTY WebSocket server alongside the app so the <AgentTerminal />\n * component can connect to a real CLI. Mounts a discovery endpoint at\n * /_agent-native/agent-terminal-info for the client component.\n *\n * Skips activation when running inside a frame (FRAME_PORT is set).\n */\n\nimport { defineEventHandler } from \"h3\";\n\n// ─── module-load self-heal: chmod node-pty's spawn-helper ─────────────────\n// pnpm can extract node-pty's prebuilds tarball without running the\n// post-install that chmods spawn-helper, leaving it as `-rw-r--r--` instead\n// of `-rwxr-xr-x`. Every PTY spawn then fails with `posix_spawnp failed`.\n// Run the fix synchronously at module load (static imports, sync fs calls)\n// so by the time ANY plugin worker starts spawning PTYs, the helper is\n// already executable.\n(function fixSpawnHelperPermissions() {\n if (!isNodeRuntime()) return;\n try {\n const req = createRequire(import.meta.url);\n const ptyPkg = req.resolve(\"node-pty/package.json\");\n const ptyDir = path.dirname(ptyPkg);\n const helper = path.join(\n ptyDir,\n \"prebuilds\",\n `${process.platform}-${process.arch}`,\n \"spawn-helper\",\n );\n if (fs.existsSync(helper)) {\n const mode = fs.statSync(helper).mode;\n if (!(mode & 0o100)) {\n fs.chmodSync(helper, 0o755);\n console.log(\n `[terminal] Fixed non-executable node-pty spawn-helper at ${helper}`,\n );\n }\n }\n } catch (err) {\n // node-pty not installed → stay silent here; createTerminalPlugin emits\n // the \"install node-pty\" message when the PTY server actually fails to\n // start. Logging twice for the same root cause just adds noise.\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code === \"MODULE_NOT_FOUND\" || code === \"ERR_MODULE_NOT_FOUND\") return;\n console.warn(\n \"[terminal] Could not verify node-pty spawn-helper permissions:\",\n (err as Error).message,\n );\n }\n})();\n\nexport interface TerminalPluginOptions {\n /** CLI command to run. Defaults to AGENT_CLI_COMMAND env or 'builder' */\n command?: string;\n /** Port for the WebSocket server. Defaults to AGENT_TERMINAL_PORT env or auto-assigned */\n port?: number;\n /** Enable in production. Defaults to AGENT_TERMINAL_ENABLED env or false in prod */\n enabledInProduction?: boolean;\n /** Auth check for WebSocket connections in production */\n authCheck?: (req: any) => boolean | Promise<boolean>;\n}\n\n// Vite's dev server can initialize Nitro plugins more than once during boot.\n// Module-scope flags ensure the \"node-pty not installed\" / \"Disabled in\n// production\" / \"Frame detected\" notices each fire at most once per process.\nlet _ptyMissingLogged = false;\nlet _disabledLogged = false;\nlet _frameDetectedLogged = false;\n\nexport function createTerminalPlugin(options: TerminalPluginOptions = {}) {\n return async (nitroApp: any) => {\n // Terminal requires Node.js (PTY, child_process) — skip on edge runtimes\n if (!isNodeRuntime()) return;\n\n // Always mount /_agent-native/available-clis so the client doesn't get 404s\n getH3App(nitroApp).use(\n \"/_agent-native/available-clis\",\n defineEventHandler(async () => {\n try {\n const { CLI_REGISTRY, commandExists } =\n await import(\"./cli-registry.js\");\n const results = [];\n for (const [cmd, entry] of Object.entries(CLI_REGISTRY)) {\n results.push({\n command: cmd,\n label: entry.label,\n available: await commandExists(cmd),\n });\n }\n return results;\n } catch {\n return [];\n }\n }),\n );\n\n // Skip if running inside a frame\n if (process.env.FRAME_PORT) {\n if (!_frameDetectedLogged) {\n console.log(\"[terminal] Frame detected, skipping embedded terminal\");\n _frameDetectedLogged = true;\n }\n return;\n }\n\n const isProd = process.env.NODE_ENV === \"production\";\n const enabled =\n options.enabledInProduction ??\n (process.env.AGENT_TERMINAL_ENABLED === \"true\" || !isProd);\n\n if (!enabled) {\n if (!_disabledLogged) {\n console.log(\n \"[terminal] Disabled in production (set AGENT_TERMINAL_ENABLED=true to enable)\",\n );\n _disabledLogged = true;\n }\n // Mount a disabled info endpoint\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({ available: false })),\n );\n return;\n }\n\n // Require authCheck in production to prevent unauthenticated shell access\n if (isProd && !options.authCheck) {\n console.error(\n \"[terminal] FATAL: authCheck is required when enabling the terminal in production. \" +\n \"Pass an authCheck function to createTerminalPlugin().\",\n );\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: false,\n error: \"Terminal requires authCheck in production\",\n })),\n );\n return;\n }\n\n // Skip if a PTY server is already running (prevents leak on HMR rebuild)\n if (process.env.__AGENT_TERMINAL_RUNNING === \"true\") {\n const existingPort = process.env.AGENT_TERMINAL_PORT;\n console.log(\n `[terminal] PTY server already running on port ${existingPort}, skipping`,\n );\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: true,\n wsPort: existingPort ? parseInt(existingPort, 10) : 0,\n command:\n options.command || process.env.AGENT_CLI_COMMAND || \"builder\",\n })),\n );\n return;\n }\n\n const command =\n options.command || process.env.AGENT_CLI_COMMAND || \"builder\";\n const port =\n options.port ??\n (process.env.AGENT_TERMINAL_PORT\n ? parseInt(process.env.AGENT_TERMINAL_PORT, 10)\n : 0);\n\n // Mark as running BEFORE the async server start. The previous code only\n // set this AFTER `await createPtyWebSocketServer(...)`, which left a\n // TOCTOU window where two concurrent plugin invocations would both pass\n // the running-check, both spawn a server, and end up fighting for the\n // CLI's PTY pool — leading to `posix_spawnp failed` floods.\n process.env.__AGENT_TERMINAL_RUNNING = \"true\"; // guard:allow-env-mutation — process-wide running flag set once at boot, before any HTTP request handling, to coordinate concurrent plugin invocations\n\n try {\n const { createPtyWebSocketServer } = await import(\"./pty-server.js\");\n\n const result = await createPtyWebSocketServer({\n appDir: process.cwd(),\n command,\n port,\n authCheck: isProd ? options.authCheck : undefined,\n logPrefix: \"[terminal]\",\n });\n\n // Store port for other consumers\n process.env.AGENT_TERMINAL_PORT = String(result.port); // guard:allow-env-mutation — terminal subprocess port published once at boot, not per-request\n\n // Mount discovery endpoint\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: true,\n wsPort: result.port,\n command,\n })),\n );\n\n // Cleanup on shutdown (use once to avoid listener leak on hot-reload)\n const cleanup = () => result.close();\n process.once(\"SIGTERM\", cleanup);\n process.once(\"SIGINT\", cleanup);\n process.once(\"exit\", cleanup);\n\n if (process.env.DEBUG)\n console.log(\n `[terminal] Agent terminal ready (command: ${command}, port: ${result.port})`,\n );\n } catch (err) {\n // Clear the running flag so a retry can spawn a fresh server\n delete process.env.__AGENT_TERMINAL_RUNNING; // guard:allow-env-mutation — terminal subprocess boot failed, clearing boot-time sentinel so a later plugin retry can start cleanly\n\n // Distinguish \"node-pty not installed\" (expected when the user opts\n // out of the terminal feature) from real failures (port conflict,\n // native binding mismatch). Native deps are optional — log as info\n // so the dev console isn't filled with red noise.\n const code = (err as NodeJS.ErrnoException)?.code;\n const missingPty =\n code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n if (missingPty) {\n if (!_ptyMissingLogged) {\n console.log(\n \"[terminal] node-pty not installed — embedded terminal disabled. \" +\n \"Install with `pnpm add node-pty` to enable.\",\n );\n _ptyMissingLogged = true;\n }\n } else {\n console.error(\"[terminal] Failed to start PTY server:\", err);\n console.error(\n \"[terminal] If node-pty is installed but PTY fails to spawn, \" +\n \"try `pnpm rebuild node-pty` (common after switching Node \" +\n \"versions via fnm/nvm).\",\n );\n }\n\n // Mount a fallback info endpoint\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: false,\n error: missingPty ? \"node-pty not installed\" : \"PTY server failed\",\n })),\n );\n }\n };\n}\n\n/** Pre-configured terminal plugin with defaults */\nexport const defaultTerminalPlugin = createTerminalPlugin();\n"]}
1
+ {"version":3,"file":"terminal-plugin.js","sourceRoot":"","sources":["../../src/terminal/terminal-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getH3App,\n markDefaultPluginProvided,\n} from \"../server/framework-request-handler.js\";\nimport { isNodeRuntime } from \"../shared/runtime.js\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { createRequire } from \"node:module\";\n/**\n * Nitro Plugin — Agent Terminal\n *\n * Starts a PTY WebSocket server alongside the app so the <AgentTerminal />\n * component can connect to a real CLI. Mounts a discovery endpoint at\n * /_agent-native/agent-terminal-info for the client component.\n *\n * Skips activation when running inside a frame (FRAME_PORT is set).\n */\n\nimport { defineEventHandler } from \"h3\";\n\n// ─── module-load self-heal: chmod node-pty's spawn-helper ─────────────────\n// pnpm can extract node-pty's prebuilds tarball without running the\n// post-install that chmods spawn-helper, leaving it as `-rw-r--r--` instead\n// of `-rwxr-xr-x`. Every PTY spawn then fails with `posix_spawnp failed`.\n// Run the fix synchronously at module load (static imports, sync fs calls)\n// so by the time ANY plugin worker starts spawning PTYs, the helper is\n// already executable.\n(function fixSpawnHelperPermissions() {\n if (!isNodeRuntime()) return;\n try {\n const req = createRequire(import.meta.url);\n const ptyPkg = req.resolve(\"node-pty/package.json\");\n const ptyDir = path.dirname(ptyPkg);\n const helper = path.join(\n ptyDir,\n \"prebuilds\",\n `${process.platform}-${process.arch}`,\n \"spawn-helper\",\n );\n if (fs.existsSync(helper)) {\n const mode = fs.statSync(helper).mode;\n if (!(mode & 0o100)) {\n fs.chmodSync(helper, 0o755);\n console.log(\n `[terminal] Fixed non-executable node-pty spawn-helper at ${helper}`,\n );\n }\n }\n } catch (err) {\n // node-pty not installed → stay silent here; createTerminalPlugin emits\n // the \"install node-pty\" message when the PTY server actually fails to\n // start. Logging twice for the same root cause just adds noise.\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code === \"MODULE_NOT_FOUND\" || code === \"ERR_MODULE_NOT_FOUND\") return;\n console.warn(\n \"[terminal] Could not verify node-pty spawn-helper permissions:\",\n (err as Error).message,\n );\n }\n})();\n\nexport interface TerminalPluginOptions {\n /** CLI command to run. Defaults to AGENT_CLI_COMMAND env or 'builder' */\n command?: string;\n /** Port for the WebSocket server. Defaults to AGENT_TERMINAL_PORT env or auto-assigned */\n port?: number;\n /** Enable in production. Defaults to AGENT_TERMINAL_ENABLED env or false in prod */\n enabledInProduction?: boolean;\n /** Auth check for WebSocket connections in production */\n authCheck?: (req: any) => boolean | Promise<boolean>;\n}\n\n// Vite's dev server can initialize Nitro plugins more than once during boot.\n// Module-scope flags ensure the \"node-pty not installed\" / \"Disabled in\n// production\" / \"Frame detected\" notices each fire at most once per process.\nlet _ptyMissingLogged = false;\nlet _disabledLogged = false;\nlet _frameDetectedLogged = false;\n\nexport function createTerminalPlugin(options: TerminalPluginOptions = {}) {\n return async (nitroApp: any) => {\n markDefaultPluginProvided(nitroApp, \"terminal\");\n // Terminal requires Node.js (PTY, child_process) — skip on edge runtimes\n if (!isNodeRuntime()) return;\n\n // Always mount /_agent-native/available-clis so the client doesn't get 404s\n getH3App(nitroApp).use(\n \"/_agent-native/available-clis\",\n defineEventHandler(async () => {\n try {\n const { CLI_REGISTRY, commandExists } =\n await import(\"./cli-registry.js\");\n const results = [];\n for (const [cmd, entry] of Object.entries(CLI_REGISTRY)) {\n results.push({\n command: cmd,\n label: entry.label,\n available: await commandExists(cmd),\n });\n }\n return results;\n } catch {\n return [];\n }\n }),\n );\n\n // Skip if running inside a frame\n if (process.env.FRAME_PORT) {\n if (!_frameDetectedLogged) {\n console.log(\"[terminal] Frame detected, skipping embedded terminal\");\n _frameDetectedLogged = true;\n }\n return;\n }\n\n const isProd = process.env.NODE_ENV === \"production\";\n const enabled =\n options.enabledInProduction ??\n (process.env.AGENT_TERMINAL_ENABLED === \"true\" || !isProd);\n\n if (!enabled) {\n if (!_disabledLogged) {\n console.log(\n \"[terminal] Disabled in production (set AGENT_TERMINAL_ENABLED=true to enable)\",\n );\n _disabledLogged = true;\n }\n // Mount a disabled info endpoint\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({ available: false })),\n );\n return;\n }\n\n // Require authCheck in production to prevent unauthenticated shell access\n if (isProd && !options.authCheck) {\n console.error(\n \"[terminal] FATAL: authCheck is required when enabling the terminal in production. \" +\n \"Pass an authCheck function to createTerminalPlugin().\",\n );\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: false,\n error: \"Terminal requires authCheck in production\",\n })),\n );\n return;\n }\n\n // Skip if a PTY server is already running (prevents leak on HMR rebuild)\n if (process.env.__AGENT_TERMINAL_RUNNING === \"true\") {\n const existingPort = process.env.AGENT_TERMINAL_PORT;\n console.log(\n `[terminal] PTY server already running on port ${existingPort}, skipping`,\n );\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: true,\n wsPort: existingPort ? parseInt(existingPort, 10) : 0,\n command:\n options.command || process.env.AGENT_CLI_COMMAND || \"builder\",\n })),\n );\n return;\n }\n\n const command =\n options.command || process.env.AGENT_CLI_COMMAND || \"builder\";\n const port =\n options.port ??\n (process.env.AGENT_TERMINAL_PORT\n ? parseInt(process.env.AGENT_TERMINAL_PORT, 10)\n : 0);\n\n // Mark as running BEFORE the async server start. The previous code only\n // set this AFTER `await createPtyWebSocketServer(...)`, which left a\n // TOCTOU window where two concurrent plugin invocations would both pass\n // the running-check, both spawn a server, and end up fighting for the\n // CLI's PTY pool — leading to `posix_spawnp failed` floods.\n process.env.__AGENT_TERMINAL_RUNNING = \"true\"; // guard:allow-env-mutation — process-wide running flag set once at boot, before any HTTP request handling, to coordinate concurrent plugin invocations\n\n try {\n const { createPtyWebSocketServer } = await import(\"./pty-server.js\");\n\n const result = await createPtyWebSocketServer({\n appDir: process.cwd(),\n command,\n port,\n authCheck: isProd ? options.authCheck : undefined,\n logPrefix: \"[terminal]\",\n });\n\n // Store port for other consumers\n process.env.AGENT_TERMINAL_PORT = String(result.port); // guard:allow-env-mutation — terminal subprocess port published once at boot, not per-request\n\n // Mount discovery endpoint\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: true,\n wsPort: result.port,\n command,\n })),\n );\n\n // Cleanup on shutdown (use once to avoid listener leak on hot-reload)\n const cleanup = () => result.close();\n process.once(\"SIGTERM\", cleanup);\n process.once(\"SIGINT\", cleanup);\n process.once(\"exit\", cleanup);\n\n if (process.env.DEBUG)\n console.log(\n `[terminal] Agent terminal ready (command: ${command}, port: ${result.port})`,\n );\n } catch (err) {\n // Clear the running flag so a retry can spawn a fresh server\n delete process.env.__AGENT_TERMINAL_RUNNING; // guard:allow-env-mutation — terminal subprocess boot failed, clearing boot-time sentinel so a later plugin retry can start cleanly\n\n // Distinguish \"node-pty not installed\" (expected when the user opts\n // out of the terminal feature) from real failures (port conflict,\n // native binding mismatch). Native deps are optional — log as info\n // so the dev console isn't filled with red noise.\n const code = (err as NodeJS.ErrnoException)?.code;\n const missingPty =\n code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\";\n if (missingPty) {\n if (!_ptyMissingLogged) {\n console.log(\n \"[terminal] node-pty not installed — embedded terminal disabled. \" +\n \"Install with `pnpm add node-pty` to enable.\",\n );\n _ptyMissingLogged = true;\n }\n } else {\n console.error(\"[terminal] Failed to start PTY server:\", err);\n console.error(\n \"[terminal] If node-pty is installed but PTY fails to spawn, \" +\n \"try `pnpm rebuild node-pty` (common after switching Node \" +\n \"versions via fnm/nvm).\",\n );\n }\n\n // Mount a fallback info endpoint\n getH3App(nitroApp).use(\n \"/_agent-native/agent-terminal-info\",\n defineEventHandler(() => ({\n available: false,\n error: missingPty ? \"node-pty not installed\" : \"PTY server failed\",\n })),\n );\n }\n };\n}\n\n/** Pre-configured terminal plugin with defaults */\nexport const defaultTerminalPlugin = createTerminalPlugin();\n"]}
@@ -1,4 +1,4 @@
1
1
  export { defineConfig, type ClientConfigOptions, type NitroOptions, } from "./client.js";
2
- export { actionTypesPlugin } from "./action-types-plugin.js";
2
+ export { actionTypesPlugin, generateActionRegistryForProject, } from "./action-types-plugin.js";
3
3
  export { agentsBundlePlugin } from "./agents-bundle-plugin.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,gCAAgC,GACjC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -1,4 +1,4 @@
1
1
  export { defineConfig, } from "./client.js";
2
- export { actionTypesPlugin } from "./action-types-plugin.js";
2
+ export { actionTypesPlugin, generateActionRegistryForProject, } from "./action-types-plugin.js";
3
3
  export { agentsBundlePlugin } from "./agents-bundle-plugin.js";
4
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n defineConfig,\n type ClientConfigOptions,\n type NitroOptions,\n} from \"./client.js\";\nexport { actionTypesPlugin } from \"./action-types-plugin.js\";\nexport { agentsBundlePlugin } from \"./agents-bundle-plugin.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,gCAAgC,GACjC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n defineConfig,\n type ClientConfigOptions,\n type NitroOptions,\n} from \"./client.js\";\nexport {\n actionTypesPlugin,\n generateActionRegistryForProject,\n} from \"./action-types-plugin.js\";\nexport { agentsBundlePlugin } from \"./agents-bundle-plugin.js\";\n"]}
@@ -11,6 +11,8 @@ Agent-to-agent communication over HTTP. Agents discover each other, send message
11
11
 
12
12
  A2A (agent-to-agent) is a JSON-RPC protocol for inter-agent communication. A mail agent can ask an analytics agent to run a query. A calendar agent can search issues in a project management agent. Each agent exposes its capabilities via an agent card and accepts work via a standard JSON-RPC endpoint.
13
13
 
14
+ A2A is the substrate for cross-app delegation in this framework — most prominently for [Dispatch](/docs/dispatch), which routes a single inbound message (Slack, email, etc.) to whichever app in the workspace is best suited to handle it.
15
+
14
16
  Key concepts:
15
17
 
16
18
  - **Agent card** — public metadata at `/.well-known/agent-card.json` describing skills and capabilities
@@ -51,6 +53,10 @@ This mounts two endpoints: `GET /.well-known/agent-card.json` (public, no auth)
51
53
 
52
54
  The agent card is auto-generated from your config and served at `/.well-known/agent-card.json`. Other agents fetch it to discover your agent's skills.
53
55
 
56
+ ### Per-tenant skill filtering {#agent-card-filtering}
57
+
58
+ The card endpoint is public, so the framework redacts skills whose IDs reveal per-user or per-org integrations before serving it. Any skill whose id starts with `mcp__user_<emailhash>_…` or `mcp__org_<orgid>_…` is dropped from the published card. Operator-controlled stdio MCP tools (loaded from `mcp.config.json`) and template-defined skills stay visible. This prevents an unauthenticated caller from fingerprinting which tenants exist or which integrations they have connected. See `packages/core/src/a2a/server.ts`.
59
+
54
60
  ```json
55
61
  {
56
62
  "name": "Analytics Agent",
@@ -83,12 +89,14 @@ The agent card is auto-generated from your config and served at `/.well-known/ag
83
89
 
84
90
  All methods are called via `POST /a2a` with JSON-RPC 2.0 format:
85
91
 
86
- | Method | Description | Key params |
87
- | ---------------- | ----------------------------------------- | --------------------- |
88
- | `message/send` | Send a message, get a completed task back | `message, contextId?` |
89
- | `message/stream` | Send a message, receive SSE task updates | `message, contextId?` |
90
- | `tasks/get` | Fetch a task by ID | `id` |
91
- | `tasks/cancel` | Cancel a running task | `id` |
92
+ | Method | Description | Key params |
93
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
94
+ | `message/send` | Send a message and wait for the completed task. Pass `async: true` to return immediately in `working` state and poll. | `message, contextId?, async?` |
95
+ | `message/stream` | Send a message, receive SSE task updates | `message, contextId?` |
96
+ | `tasks/get` | Fetch a task by ID — used to poll an async task to completion | `id` |
97
+ | `tasks/cancel` | Cancel a running task | `id` |
98
+
99
+ When `message/send` is called with `async: true`, the JSON-RPC handler enqueues the task and self-fires a POST to an internal `/_agent-native/a2a/_process-task` route so the handler runs in a fresh function execution with its own full timeout. This route is authenticated with an HMAC token bound to the task ID (5-minute lifetime, signed with `A2A_SECRET`). It is mounted before the `/_agent-native/a2a` JSON-RPC route so h3's prefix matching does not swallow it.
92
100
 
93
101
  Messages contain typed parts:
94
102
 
@@ -186,6 +194,67 @@ const client = new A2AClient(url, process.env.A2A_API_KEY);
186
194
 
187
195
  The agent card endpoint is always public (no auth) so other agents can discover capabilities. The `/a2a` JSON-RPC endpoint requires a valid bearer token when `apiKeyEnv` is set. In dev mode (no env var configured), auth is skipped.
188
196
 
197
+ ### Auth policy boundary {#auth-policy}
198
+
199
+ Bearer validation runs at the request boundary — in the JSON-RPC handler — before the agent loop ever sees the message. The shared helpers in `packages/core/src/a2a/auth-policy.ts` decide what the deployment requires:
200
+
201
+ - `isA2AProductionRuntime()` returns `true` on Netlify, AWS Lambda, Cloudflare Pages/Workers, Vercel, Render, Fly, and Cloud Run — even when `NODE_ENV` isn't `"production"`. Some serverless providers don't set `NODE_ENV` consistently, so the policy reads provider-specific flags too.
202
+ - `hasConfiguredA2ASecret()` returns `true` when `A2A_SECRET` is set.
203
+ - `shouldAdvertiseJwtA2AAuth()` is what the agent card uses to decide whether to publish a `jwtBearer` security scheme.
204
+
205
+ The production policy is strict: in any production runtime, the async `_process-task` route refuses to dispatch unless `A2A_SECRET` is configured (returns 503), and the JSON-RPC endpoint refuses unauthenticated calls. The dev fallback (warn once, allow) only fires when no production flag is set.
206
+
207
+ This boundary matters because the agent loop accepts free-form input from a remote caller. Putting the bearer check inside the loop, or relying on a tool to enforce it, would let prompt-injection or a buggy handler bypass auth. Keeping it at the HTTP boundary means a token failure short-circuits before any LLM call.
208
+
209
+ JWT verification (`verifyA2AToken` in `server.ts`) accepts tokens signed with either the global `A2A_SECRET` or an org-scoped secret looked up from SQL via the token's `org_domain` claim, and enforces the token's own `aud`/`iss` claims when present.
210
+
211
+ ## Continuations {#continuations}
212
+
213
+ When an agent calls a remote A2A peer that doesn't return immediately, the framework polls `tasks/get` until the task settles. This is wired through `A2AClient.sendAndWait`, which is the default mode used by the `callAgent()` helper.
214
+
215
+ ```ts
216
+ // Default: async + poll (safe on serverless hosts)
217
+ const reply = await callAgent(url, "Generate the quarterly report", {
218
+ userEmail: session.user.email,
219
+ });
220
+
221
+ // Single-shot blocking POST (avoid on Netlify/Vercel for slow handlers)
222
+ const reply2 = await callAgent(url, "Quick lookup", { async: false });
223
+ ```
224
+
225
+ For inbound continuations triggered by a messaging integration (Slack, email), the framework persists the continuation in SQL and processes it out-of-band:
226
+
227
+ - A row is written to the `a2a_continuations` table when the integration handler hands off to a remote agent.
228
+ - A self-fired `POST /_agent-native/integrations/process-a2a-continuation` claims the row, calls `tasks/get` on the remote agent, and either delivers the reply to the integration adapter or reschedules.
229
+ - If the remote task is still working, the row is rescheduled and re-dispatched. The poll budget is **bounded by ~10 minutes of remote work** (`MAX_REMOTE_WORK_MS`) and **6 dispatch attempts** (`MAX_ATTEMPTS`); after either limit, the continuation is failed with a clear error and the user gets a "the agent didn't respond in time" reply.
230
+ - A recurring sweeper (`claimDueA2AContinuations`) re-claims any continuation rows that were left in flight when the previous function execution died. Even if the calling app crashes mid-poll, the next sweep tick resumes the work.
231
+
232
+ Defined in `packages/core/src/integrations/a2a-continuation-processor.ts`. The same retry job pattern is used for integration webhook tasks (`pending-tasks-retry-job.ts`, capped at 3 attempts).
233
+
234
+ ## Workspace A2A {#workspace-a2a}
235
+
236
+ In a multi-app workspace deployed to a single Netlify site (see [multi-app workspace](/docs/multi-app-workspace)), every app under `apps/<id>/` is auto-registered as an A2A peer:
237
+
238
+ - A shared `A2A_SECRET` is mounted into every app's environment at build time.
239
+ - Cross-app calls are same-origin — `https://workspace.example.com/apps/analytics` calls `https://workspace.example.com/apps/mail` — so there is no DNS, CORS, or per-pair JWT setup.
240
+ - Outbound calls signed with the shared secret carry the caller's email as `sub` and (when present) the org domain. The receiver's JWT verifier accepts either the shared secret or the org-scoped secret from SQL, in that order.
241
+ - Agent discovery walks the workspace registry rather than relying on the operator to wire each peer by hand. See `discoverAgents` in `packages/core/src/server/agent-discovery.ts` and the org refresh path in `packages/core/src/org/handlers.ts`.
242
+
243
+ External A2A — calls to agents outside your workspace — still uses the bearer-token model (`apiKeyEnv` + `A2AClient(url, apiKey)`). Workspace A2A is layered on top; nothing about external peers changes.
244
+
245
+ ## Serverless gotchas {#serverless}
246
+
247
+ **Never rely on a fire-and-forget `Promise` outliving the response.** Serverless functions (Netlify, Vercel, AWS Lambda, Cloud Run) freeze the moment the response body is flushed — sometimes before the TCP handshake of an unawaited `fetch(...)` even completes. Patterns that work locally on Node will silently drop work in production.
248
+
249
+ The framework's pattern, used by both A2A async dispatch and the [integration webhook queue](/docs/messaging), is:
250
+
251
+ 1. Accept the request, persist what needs to happen to SQL, return 200 immediately.
252
+ 2. Self-fire a `POST` to a separate framework route (`/_agent-native/a2a/_process-task` or `/_agent-native/integrations/process-task`) so the actual work runs in a **fresh function execution** with its own full timeout.
253
+ 3. Authenticate the self-fire with an HMAC token bound to the row id, signed with `A2A_SECRET`.
254
+ 4. A recurring retry job sweeps any rows that were claimed but not finished, so a crashed function doesn't strand the work.
255
+
256
+ When you write your own A2A handler or integration adapter, follow the same shape. Don't attach work to a detached promise after `return`. The `integration-webhooks` skill is the canonical reference.
257
+
189
258
  ## Agent mentions {#agent-mentions}
190
259
 
191
260
  You can `@`-mention agents directly in the chat composer. Connected agents use A2A: when you mention a connected agent, the server makes an A2A call to that agent and weaves the response into your conversation context.
@@ -293,6 +293,16 @@ OPENAI_API_KEY=sk-...
293
293
 
294
294
  When users fork your template, they copy `.env.example` to `.env` and fill in their own values. Keep the number of required keys minimal — the template should work with example data before any keys are configured.
295
295
 
296
+ ## Make your template multi-app-workspace ready {#workspace-ready}
297
+
298
+ Templates rarely live in isolation — most users will scaffold yours alongside other apps in a [multi-app workspace](/docs/multi-app-workspace), often coordinated by [Dispatch](/docs/dispatch). Three checkpoints make your template a good citizen:
299
+
300
+ 1. **Mount A2A so other apps can call yours.** Add `mountA2A()` in a server plugin so your actions are reachable over the agent-to-agent protocol. Other apps in the workspace (and Dispatch's orchestrator) discover and invoke them automatically. See [A2A Protocol](/docs/a2a-protocol).
301
+ 2. **Publish an agent card with skill metadata.** Your A2A peer exposes a manifest describing what your template does and which actions it offers. Dispatch reads these cards to decide which specialist to route a request to — clear, specific skill descriptions make routing accurate.
302
+ 3. **Register secrets through onboarding.** Any third-party API keys your template needs (OpenAI, Stripe, SendGrid, etc.) should be registered via the [onboarding](/docs/onboarding) system. They show up in the workspace setup checklist and the Dispatch secrets vault, instead of forcing every user to hand-edit `.env`.
303
+
304
+ Hit those three and your template drops into any workspace cleanly.
305
+
296
306
  ## Publishing {#publishing}
297
307
 
298
308
  To share your template:
@@ -0,0 +1,94 @@
1
+ ---
2
+ title: "Dispatch"
3
+ description: "The workspace control plane: secrets vault, integration hub, cross-app delegate, and central inbox for Slack, email, Telegram, WhatsApp."
4
+ ---
5
+
6
+ # Dispatch
7
+
8
+ Dispatch is the central app that sits in front of every other app in your workspace and handles secrets, integrations, messaging, and cross-app delegation. It is the **workspace control plane** — the single agent your team talks to, the single place credentials live, and the single router that decides which specialist app should handle a given request.
9
+
10
+ Without Dispatch, every app in a multi-app workspace ends up re-implementing the same plumbing: its own Slack bot, its own secret store, its own scheduled jobs, its own copy of the workspace's instructions. Rotating one API key turns into ten redeployments. Adding a new policy turns into ten copy-pastes. Dispatch centralizes all of that in one app so the others stay focused on their domain.
11
+
12
+ > Dispatch is shipped as a first-party template. This page covers the **concept** — what it is, why you'd want it, and how it fits into a workspace. For the scaffolded app itself (routes, screens, agent guide), see the [Dispatch template](/templates/dispatch).
13
+
14
+ ## When you want Dispatch {#when}
15
+
16
+ Reach for Dispatch when any of these are true:
17
+
18
+ - You're running a [multi-app workspace](/docs/multi-app-workspace) — mail, calendar, analytics, content, recruiting — and you don't want one Slack bot per app.
19
+ - You want **one inbox for "the agent"** so users DM a single bot and the right specialist app picks up the work behind the scenes.
20
+ - You have **workspace-wide secrets** (Stripe key, OpenAI key, third-party API tokens) that several apps need but you'd rather grant per-app than copy into every `.env`.
21
+ - You want a **runtime approval flow** in front of sensitive changes (saved destinations, policy edits) so non-admins can request and admins can sign off without a code deploy.
22
+ - You want **shared skills, instructions, and agent profiles** that every app in the workspace inherits — change once, reach all.
23
+
24
+ If you're running a single template standalone, you don't need Dispatch — each template can wire its own messaging integrations directly. See [Messaging](/docs/messaging) for the standalone setup.
25
+
26
+ ## What Dispatch does {#what-it-does}
27
+
28
+ Five capabilities, all sitting on top of the same workspace database the other apps use.
29
+
30
+ ### Central inbox
31
+
32
+ Slack, email, Telegram, and WhatsApp all flow into Dispatch's agent loop. Connect each platform once in **Settings → Messaging** and every channel reaches the same agent with the same memory and tools. A Slack DM and an email to `agent@yourcompany.com` end up as two surfaces on one conversation history, not two disconnected bots.
33
+
34
+ See [Messaging](/docs/messaging) for the credentials and webhook URLs for each platform.
35
+
36
+ ### Secret vault
37
+
38
+ Store credentials once in Dispatch's vault and grant them to the apps that need them. Non-admins can **request** a secret for an app; admins **approve**, which creates the secret + grant in one step. Every read, grant, sync, and rotation is captured in an audit log. `sync-vault-to-app` pushes granted secrets into the target app's env so you don't have to redeploy or re-paste anything.
39
+
40
+ This is what makes "rotate the OpenAI key" a one-click operation across ten apps instead of ten PRs.
41
+
42
+ ### Cross-app delegation
43
+
44
+ Dispatch auto-discovers the other apps in your workspace as A2A peers — no manual registration, no per-app config. When a user asks "summarize last week's signups" in Slack, Dispatch recognizes that as an analytics request and calls the analytics app over [A2A](/docs/a2a-protocol). When they ask "draft a reply to Alice", it routes to the mail app. Dispatch posts the final answer back in the originating thread.
45
+
46
+ The behavioral rule lives in the dispatch agent's instructions: domain work belongs to the domain app. Dispatch is the orchestrator, not the specialist.
47
+
48
+ ### Workspace resources
49
+
50
+ Skills, agent profiles, and instructions can be authored once in Dispatch and granted out to the rest of the workspace. `sync-workspace-resources-to-all` pushes them to every app's `.agents/` directory so every agent in every app picks them up. This is how a team-wide change ("always use British English in customer-facing replies") propagates without editing ten repos.
51
+
52
+ ### Approval flow
53
+
54
+ Dispatch can gate sensitive runtime changes behind admin review. Today this covers **saved destinations** (the Slack channels and email addresses the agent can proactively send to) and **dispatch approval policy** itself. When the policy is enabled, the change is queued and the agent surfaces an inline approval preview directly in chat — admins approve or reject without leaving the conversation. Resource-wide approval interception is planned but not yet shipped.
55
+
56
+ ## How a Slack message flows through Dispatch {#flow}
57
+
58
+ Walk through one example end-to-end. A user DMs the bot: _"summarize last week's signups."_
59
+
60
+ 1. **Slack → webhook.** Slack `POST`s to `/_agent-native/integrations/slack/webhook` on the Dispatch app. The handler verifies the signature and **inserts a row into `integration_pending_tasks`**, then fires a self-targeted `POST` to its own processor and returns `200` immediately so Slack doesn't retry.
61
+ 2. **Fresh processor execution.** The processor endpoint runs in a brand-new function execution with its own full timeout. It atomically claims the task and starts the agent loop.
62
+ 3. **Dispatch agent decides.** The agent reads the message, recognizes "signups" as an analytics intent, and invokes `call-agent` against the analytics app's [A2A endpoint](/docs/a2a-protocol). The actual SQL work runs over there.
63
+ 4. **Reply posted in thread.** The analytics agent returns a result. Dispatch formats it and posts back into the same Slack thread the user wrote in, using the linked identity if there is one (so the agent acts with the requester's permissions, not the workspace owner's).
64
+ 5. **Recovery if anything dies.** If the processor crashes mid-flight — A2A timeout, downstream agent error, function freeze — a retry job sweeps stuck tasks every 60 seconds and re-fires the processor. Up to three attempts before the task is marked `failed`. The user still gets a reply on the next sweep instead of the message disappearing into the void.
65
+
66
+ The same flow applies for email, Telegram, and WhatsApp — only the adapter changes.
67
+
68
+ ## Reliability story {#reliability}
69
+
70
+ The whole pipeline is built to survive on every serverless host (Netlify, Vercel, Cloudflare Workers) without leaning on platform-specific background-execution APIs.
71
+
72
+ - **Webhook → SQL queue → fresh-execution processor.** The agent loop never runs inside the webhook handler. The handler's only job is to verify, enqueue, and return 200. A separate fresh execution drains the queue, so a slow agent run can never tie up the inbound webhook or cause the platform to retry.
73
+ - **A2A continuation polling.** When Dispatch delegates to another app, it polls the downstream task with a bounded timeout. If the downstream agent takes too long or crashes, Dispatch records the continuation and the retry job picks it up — the user's Slack reply still arrives.
74
+ - **Auto-signed cross-app A2A.** Hosted multi-app workspaces auto-generate per-app A2A credentials at deploy time, so apps in the same workspace can call each other without you ever pasting a JWT secret. Dispatch's agent-discovery layer reads those creds from the workspace database so newly added apps appear as callable peers automatically.
75
+
76
+ This is the recently-hardened story — see PRs #439, #441, and #443 for the underlying changes. Conceptually: every step that crosses a network or a process boundary is recoverable.
77
+
78
+ ## Setup {#setup}
79
+
80
+ Three short steps:
81
+
82
+ 1. **Scaffold a workspace that includes Dispatch.** Run `pnpm dlx @agent-native/core create my-company-platform` and pick `dispatch` alongside whatever domain templates you want. Dispatch lives at `apps/dispatch` and the rest of the apps sit beside it. See [Multi-App Workspace](/docs/multi-app-workspace).
83
+ 2. **Connect messaging.** Open **Settings → Messaging** in Dispatch and click connect for Slack, Email, Telegram, or WhatsApp. The form fields match the env vars in the [Messaging](/docs/messaging) doc — refer there for what each platform needs.
84
+ 3. **Add other apps.** Run `agent-native add-app` from the workspace root for each domain app. They auto-appear as A2A peers in Dispatch's `list-workspace-apps` — no manual registration, no agent-card editing. Dispatch will start delegating to them as soon as their agent cards are reachable.
85
+
86
+ Then add credentials to the vault, grant them to the apps that need them, and (optionally) author workspace skills under **Resources** and sync them out.
87
+
88
+ ## See also {#see-also}
89
+
90
+ - [Dispatch template](/templates/dispatch) — the actual scaffolded app, with its full action catalog and agent guide
91
+ - [Messaging](/docs/messaging) — connecting Slack, email, Telegram, WhatsApp
92
+ - [A2A Protocol](/docs/a2a-protocol) — how cross-app delegation works under the hood
93
+ - [Multi-App Workspace](/docs/multi-app-workspace) — the deployment shape Dispatch is built for
94
+ - [Workspace Management](/docs/workspace-management) — git/GitHub governance that pairs with Dispatch's runtime governance
@@ -51,6 +51,14 @@ Common next steps:
51
51
  - **Build a brand-new template from scratch** — see [Creating Templates](/docs/creating-templates) for the full Vite, Tailwind, and TypeScript setup.
52
52
  - **Understand the architecture** — see [Key Concepts](/docs/key-concepts) for how SQL, actions, polling sync, and context awareness fit together.
53
53
 
54
+ ## What's next {#next-steps}
55
+
56
+ Once your app is running, the most common next steps are:
57
+
58
+ - **Connect Slack or email** so you can message your agent from anywhere — see [Messaging](/docs/messaging).
59
+ - **Set up Dispatch as your central inbox** to triage messages and orchestrate across multiple apps — see [Dispatch](/docs/dispatch).
60
+ - **Customize via Workspace** — edit instructions, skills, memory, and connect MCP servers per user — see [Workspace](/docs/workspace).
61
+
54
62
  ## Templates {#templates}
55
63
 
56
64
  Each template is a complete app with UI, agent actions, database schema, and AI instructions ready to go:
@@ -262,6 +262,22 @@ Never use Node-specific APIs (`fs`, `child_process`, `path`) in server routes or
262
262
 
263
263
  Never assume a persistent server process. Serverless and edge environments are stateless — no in-memory caches, no long-lived connections. Use the SQL database for all state.
264
264
 
265
+ ## Workspace {#workspace}
266
+
267
+ Every user gets a personal **workspace** — instructions, skills, memory, custom sub-agents, scheduled jobs, and connected MCP servers — all stored in SQL rather than files. That makes Claude-Code-level customization viable inside multi-tenant SaaS without spinning up a container per user. See [Workspace](/docs/workspace).
268
+
269
+ ## Dispatch {#dispatch}
270
+
271
+ **Dispatch** is the workspace control plane: a central inbox for Slack/email/Telegram, a shared secrets vault, scheduled jobs, and an orchestrator agent that delegates domain work to specialist apps over A2A. Run it alongside your domain apps when you have more than one. See [Dispatch](/docs/dispatch).
272
+
273
+ ## Tools {#tools}
274
+
275
+ **Tools** are sandboxed mini-apps the agent can create at runtime — Alpine.js HTML rendered inside an iframe, with built-in helpers for persistent storage (`toolData`), calling app actions (`appAction`), and proxied external APIs (`toolFetch`). No source-code changes, no schema migrations. See [Tools](/docs/tools).
276
+
277
+ ## A2A {#a2a}
278
+
279
+ Agent-to-agent (**A2A**) is how apps in the same workspace discover and call each other. Each app publishes an agent card with skill metadata; other agents can invoke its actions over JSON-RPC. Same-origin deploys skip JWT; cross-origin uses a shared secret. See [A2A Protocol](/docs/a2a-protocol).
280
+
265
281
  ## Deep dives {#deep-dives}
266
282
 
267
283
  For detailed guidance on specific patterns:
@@ -7,7 +7,7 @@ description: "Talk to your agent from Slack, email, Telegram, or WhatsApp — sa
7
7
 
8
8
  Connect your agent to Slack, email, Telegram, or WhatsApp so you can chat with it from the apps you already use. It's the same agent — same memory, same tools, same threads — just reachable from more places.
9
9
 
10
- > **Using the Dispatch template?** All of this is wired up for you in **Settings → Messaging**. Click to connect each platform — you don't need to read the rest of this page unless you're customizing or building your own template. See [Dispatch](/docs/template-dispatch).
10
+ > **Using the Dispatch template?** All of this is wired up for you in **Settings → Messaging**. Click to connect each platform — you don't need to read the rest of this page unless you're customizing or building your own template. See [Dispatch](/docs/dispatch) or the [Dispatch template reference](/docs/template-dispatch).
11
11
 
12
12
  ## What you can do {#what-you-can-do}
13
13
 
@@ -168,7 +168,7 @@ Email is the most powerful integration — your agent gets its own address, repl
168
168
 
169
169
  ## Use Dispatch as your agent's central inbox {#dispatch}
170
170
 
171
- If you're running multiple agent-native apps (mail, calendar, analytics, etc.), the recommended pattern is to set up messaging on the **[Dispatch template](/docs/template-dispatch)** and let it route work to your domain apps over [A2A](/docs/a2a-protocol).
171
+ If you're running multiple agent-native apps (mail, calendar, analytics, etc.), the recommended pattern is to set up messaging on **[Dispatch](/docs/dispatch)** (see also the [template reference](/docs/template-dispatch)) and let it route work to your domain apps over [A2A](/docs/a2a-protocol).
172
172
 
173
173
  Why this is nice:
174
174
 
@@ -186,19 +186,37 @@ Everything below is the technical reference. If you've finished the setup steps
186
186
 
187
187
  ### How it works {#how-it-works}
188
188
 
189
- Each platform talks to your app via a standard HTTP webhook:
189
+ Inbound platform webhooks use a cross-platform SQL-queue pattern so they work on every serverless host (Netlify, Vercel, Cloudflare Workers, Fly, Render, Node) without relying on platform-specific background-execution APIs.
190
190
 
191
- 1. A user sends a message on Slack/Telegram/WhatsApp/email.
192
- 2. The platform `POST`s to `/_agent-native/integrations/<platform>/webhook`.
193
- 3. The integrations plugin verifies the signature, parses the message, and maps it to an internal conversation thread.
194
- 4. The agent runs the same pipeline as the web chat same system prompt, same actions, same tools.
195
- 5. The response is posted back to the platform in the same thread.
191
+ 1. The platform `POST`s to `/_agent-native/integrations/<platform>/webhook`. The handler verifies the signature, parses the payload into an `IncomingMessage`, and **inserts a row into `integration_pending_tasks`** with `status='pending'`.
192
+ 2. The handler fires a fire-and-forget `POST /_agent-native/integrations/process-task` and returns `200` immediately, well inside Slack's 3-second SLA.
193
+ 3. The processor endpoint runs in a **fresh function execution** with its own full timeout budget. It atomically claims the task (`pending` → `processing` via `claimPendingTask`), runs the agent loop, posts the reply through the adapter, and marks the task `completed`.
194
+ 4. A recurring retry job (`startPendingTasksRetryJob`, every 60s) sweeps tasks stuck in `pending` >90s or `processing` >5min and re-fires the processor. Capped at 3 attempts, then marked `failed`.
196
195
 
197
196
  ```text
198
- UserPlatform webhook → Verify ParseAgent runs Response posted back
197
+ Platform/webhook → verify + parseINSERT pending task ──► return 200
198
+
199
+ └─ fetch /process-task (fire-and-forget)
200
+
201
+ fresh exec ──► claim → agent loop → adapter.sendResponse → completed
202
+
203
+ (every 60s) retry job: sweep stuck tasks → re-fire /process-task (≤3 attempts)
199
204
  ```
200
205
 
201
- No polling, no long-lived connections. Inbound and outbound conversations live in the same SQL thread, so you can continue a Slack DM from the web UI or vice versa.
206
+ Inbound and outbound conversations live in the same SQL thread, so you can continue a Slack DM from the web UI or vice versa.
207
+
208
+ #### Why this pattern (and not the platform-native shortcuts) {#why-this-pattern}
209
+
210
+ Serverless functions freeze the moment the response is sent. Anything still running — including a fire-and-forget Promise, a deferred LLM call, or an in-flight tool — gets killed mid-execution. The only way to keep an agent loop alive is to start a **new** function execution for it, which is what the self-fired `/process-task` POST does.
211
+
212
+ Do NOT use any of these alternatives:
213
+
214
+ - **Netlify Background Functions** — Netlify-only, requires a `-background.ts` filename suffix, breaks on every other host.
215
+ - **Cloudflare `event.waitUntil()`** — CF Workers only, not portable.
216
+ - **Vercel `after()` / Fluid** — Vercel-only, gated behind specific runtimes.
217
+ - **Naked fire-and-forget Promises after `return`** — silently killed when the function freezes; no error in the logs, the user just never gets a reply.
218
+
219
+ The SQL-queue + self-webhook + retry-job combination is the only thing that works identically on every supported host. The retry job is the safety net — never assume the initial dispatch flushed before the function froze.
202
220
 
203
221
  ### The integrations plugin {#plugin}
204
222
 
@@ -259,7 +277,7 @@ External threads appear in the web UI alongside web-originated threads, tagged w
259
277
 
260
278
  Every incoming webhook is signature-verified before processing:
261
279
 
262
- - **Slack** — HMAC-SHA256 of the body using `SLACK_SIGNING_SECRET`, checked against the `X-Slack-Signature` header.
280
+ - **Slack** — HMAC-SHA256 of the body using `SLACK_SIGNING_SECRET`, checked against the `X-Slack-Signature` header. The first time you save a Request URL in Slack's Event Subscriptions panel, Slack POSTs a `url_verification` challenge to it; the framework's adapter detects this and replies with the `challenge` value automatically, so the URL flips green in Slack without any extra work on your end.
263
281
  - **Telegram** — secret token set when registering the webhook.
264
282
  - **WhatsApp** — Meta's verification challenge (using `WHATSAPP_VERIFY_TOKEN`) plus payload signature.
265
283
  - **Email** — Svix-style signature verification when `EMAIL_INBOUND_WEBHOOK_SECRET` is set (Resend and SendGrid both use this format). If the secret is unset, the webhook is accepted but a warning is logged.
@@ -317,8 +335,22 @@ export default createIntegrationsPlugin({
317
335
 
318
336
  Reference implementations live in `packages/core/src/integrations/adapters/` (`slack.ts`, `telegram.ts`, `whatsapp.ts`, `email.ts`) — the email adapter is the most complete example, including signature verification, threading, rate limiting, and HTML rendering.
319
337
 
338
+ ### Reliability via Dispatch + A2A continuations {#reliability}
339
+
340
+ When [Dispatch](/docs/dispatch) delegates a request to another app over [A2A](/docs/a2a-protocol#continuations), the continuation-recovery flow guarantees the user gets a Slack/email reply even if the downstream agent crashes mid-execution. The original webhook task stays in `processing` until the continuation either resolves or the retry sweep marks it stuck; either way, the platform thread gets a final reply rather than going silent.
341
+
342
+ This means a multi-app workspace fronted by Dispatch is more resilient than a single template wired to messaging directly — failures in any one downstream app degrade to a graceful error message instead of a dropped reply. See [A2A continuations](/docs/a2a-protocol#continuations) for the full delivery-guarantee story.
343
+
344
+ ### Common pitfalls {#pitfalls}
345
+
346
+ - **Don't double-read the request body.** h3 v2's body stream is consume-once: if you call `readBody(event)` after the framework has already parsed `event.node.req.body` (or vice versa), the second read hangs the request indefinitely. This shows up most often with Resend and SendGrid — both stream the inbound payload and the dangling read never resolves, the platform times out, and the webhook gets retried until it dedups. If you wrap the framework's webhook handler in your own middleware, pass the already-parsed `IncomingMessage` via the `incoming` option rather than letting the handler re-parse.
347
+ - **Don't run agent loops inside the webhook handler.** The handler must enqueue and return — the agent loop runs in the processor's fresh execution. Putting it inline guarantees serverless freeze kills the run.
348
+ - **Don't rely on dedup memory across cold starts.** The dedup key lives in the SQL `(platform, external_event_key)` unique index, not an in-process Map. If you replace the queue, keep the SQL-level dedup or duplicate Slack retries will trigger duplicate agent runs.
349
+ - **Keep the self-webhook URL reachable.** The processor URL is built from `APP_URL` / `URL` / `DEPLOY_URL` / `BETTER_AUTH_URL`, falling back to the inbound request headers. On preview deploys with rewritten hostnames, set one of these explicitly or the dispatch will hit a 404.
350
+
320
351
  ### See also {#see-also}
321
352
 
322
- - [Dispatch template](/docs/template-dispatch) — recommended central inbox for multi-app workspaces
323
- - [A2A Protocol](/docs/a2a-protocol) — how Dispatch delegates work to other agents
353
+ - [Dispatch](/docs/dispatch) — concept overview for using a central inbox across apps
354
+ - [Dispatch template reference](/docs/template-dispatch) — recommended central inbox for multi-app workspaces
355
+ - [A2A Protocol](/docs/a2a-protocol) — how Dispatch delegates work to other agents, including continuation recovery
324
356
  - [Agent Mentions](/docs/agent-mentions) — `@`-mentioning agents inside the web chat
@@ -1,9 +1,11 @@
1
1
  ---
2
- title: "Multi-App Workspace"
2
+ title: "Multi-App Workspaces"
3
3
  description: "Host many agent-native apps in one monorepo with shared auth, RBAC, instructions, skills, components, and credentials."
4
4
  ---
5
5
 
6
- # Multi-App Workspace
6
+ # Multi-App Workspaces
7
+
8
+ > For what a workspace _is_ — the customization layer, AGENTS.md, learnings.md, skills, custom agents — see [Workspace](/docs/workspace). This page is about the **deployment shape**: hosting many agent-native apps in one monorepo with shared auth, components, and a unified deploy.
7
9
 
8
10
  When vibe-coding an internal tool takes an afternoon, you don't stop at one. A team ends up with a CRM, a support inbox, a dashboard, a recruiting tracker, an ops console — ten small apps, each scaffolded independently. That's great until you need to change something in all of them.
9
11
 
@@ -239,3 +241,9 @@ The workspace pattern is intentionally narrow. A few things it deliberately does
239
241
  - **Encrypted credential vault.** Shared credentials live in the `settings` table as plain text today. Rotate responsibly.
240
242
  - **Publishing shared code to private npm.** The shared package is `workspace:*` only; multi-repo sharing via a private registry is doable but not scaffolded.
241
243
  - **Opinionated component library.** `packages/shared` is where _you_ put shared components. The framework doesn't force shadcn/ui or any other system into that slot.
244
+
245
+ ## See also {#see-also}
246
+
247
+ - [Workspace](/docs/workspace) — the customization layer (AGENTS.md, learnings.md, skills, custom agents) every app in the workspace shares.
248
+ - [Workspace Governance](/docs/workspace-management) — branching, CODEOWNERS, PR review across many apps in one repo.
249
+ - [Dispatch](/docs/dispatch) — the runtime control plane that typically lives inside a multi-app workspace as the secrets vault, integration catalog, and approvals hub.
@@ -195,5 +195,5 @@ Automations can chain off this — e.g. _"if a critical notification fires, also
195
195
  ## What's next
196
196
 
197
197
  - [**Automations**](/docs/automations) — the most common caller of `notify()`
198
- - [**Secrets**](/docs/security) — the `${keys.NAME}` substitution that powers the webhook channel
198
+ - [**Security**](/docs/security) — the `${keys.NAME}` substitution that powers the webhook channel
199
199
  - [**Server plugins**](/docs/server) — where custom channels are registered at startup