@agent-native/core 0.126.0 → 0.127.1

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 (152) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +71 -0
  3. package/corpus/core/docs/content/external-agents-catalog.mdx +14 -11
  4. package/corpus/core/docs/content/mcp-protocol.mdx +9 -4
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/a2a/index.ts +2 -0
  7. package/corpus/core/src/agent/production-agent.ts +6 -1
  8. package/corpus/core/src/agent/run-manager.ts +37 -0
  9. package/corpus/core/src/agent/run-store.ts +18 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +75 -43
  11. package/corpus/core/src/client/agent-chat.ts +19 -1
  12. package/corpus/core/src/client/chat/tool-call-display.tsx +25 -0
  13. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +23 -0
  14. package/corpus/core/src/client/settings/AgentsSection.tsx +565 -71
  15. package/corpus/core/src/client/settings/SecretsSection.tsx +71 -36
  16. package/corpus/core/src/client/use-chat-models.ts +54 -37
  17. package/corpus/core/src/localization/default-messages.ts +11 -0
  18. package/corpus/core/src/mcp/ask-app-task-handle.ts +183 -0
  19. package/corpus/core/src/mcp/builtin-tools.ts +146 -34
  20. package/corpus/core/src/server/agent-capabilities.ts +8 -1
  21. package/corpus/core/src/server/agent-chat-plugin.ts +17 -0
  22. package/corpus/core/src/server/agent-discovery.ts +1 -1
  23. package/corpus/core/src/server/agent-peer-probe.ts +133 -0
  24. package/corpus/core/src/server/core-routes-plugin.ts +67 -0
  25. package/corpus/core/src/shared/mcp-embed-headers.ts +4 -0
  26. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
  27. package/corpus/templates/design/app/hooks/use-question-flow.ts +21 -1
  28. package/corpus/templates/design/changelog/2026-07-28-the-model-you-pick-when-starting-a-new-design-is-now-used-fo.md +6 -0
  29. package/corpus/templates/dispatch/changelog/2026-07-28-pending-apps-are-hidden-by-default-and-app-ownership-details.md +6 -0
  30. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +5 -1
  31. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +5 -0
  32. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +31 -8
  33. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +15 -0
  34. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +78 -8
  35. package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +179 -0
  36. package/corpus/templates/slides/app/components/presentation/present-channel.ts +15 -0
  37. package/corpus/templates/slides/app/i18n/en-US.ts +10 -1
  38. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +67 -0
  39. package/corpus/templates/slides/app/lib/export-pptx-client.ts +16 -3
  40. package/corpus/templates/slides/app/pages/DeckEditor.tsx +11 -0
  41. package/corpus/templates/slides/app/pages/Presentation.tsx +11 -3
  42. package/corpus/templates/slides/changelog/2026-07-28-export-to-google-slides-now-creates-the-deck-directly-in-you.md +6 -0
  43. package/corpus/templates/slides/changelog/2026-07-28-opening-a-presentation-link-directly-no-longer-bounces-back-.md +6 -0
  44. package/corpus/templates/slides/changelog/2026-07-28-presenter-view-open-a-second-window-while-presenting-to-see-.md +6 -0
  45. package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +90 -0
  46. package/corpus/toolkit/CHANGELOG.md +6 -0
  47. package/corpus/toolkit/package.json +1 -1
  48. package/corpus/toolkit/src/composer/TiptapComposer.tsx +143 -111
  49. package/corpus/toolkit/src/composer/runtime-adapters.tsx +2 -0
  50. package/dist/a2a/index.d.ts +2 -0
  51. package/dist/a2a/index.d.ts.map +1 -1
  52. package/dist/a2a/index.js +1 -0
  53. package/dist/a2a/index.js.map +1 -1
  54. package/dist/agent/production-agent.d.ts +2 -2
  55. package/dist/agent/production-agent.d.ts.map +1 -1
  56. package/dist/agent/production-agent.js +6 -3
  57. package/dist/agent/production-agent.js.map +1 -1
  58. package/dist/agent/run-manager.d.ts +11 -0
  59. package/dist/agent/run-manager.d.ts.map +1 -1
  60. package/dist/agent/run-manager.js +34 -1
  61. package/dist/agent/run-manager.js.map +1 -1
  62. package/dist/agent/run-store.d.ts +8 -0
  63. package/dist/agent/run-store.d.ts.map +1 -1
  64. package/dist/agent/run-store.js +16 -0
  65. package/dist/agent/run-store.js.map +1 -1
  66. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  67. package/dist/client/MultiTabAssistantChat.js +60 -31
  68. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  69. package/dist/client/agent-chat.d.ts +6 -0
  70. package/dist/client/agent-chat.d.ts.map +1 -1
  71. package/dist/client/agent-chat.js +13 -1
  72. package/dist/client/agent-chat.js.map +1 -1
  73. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  74. package/dist/client/chat/tool-call-display.js +14 -0
  75. package/dist/client/chat/tool-call-display.js.map +1 -1
  76. package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
  77. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  78. package/dist/client/resources/mcp-integration-catalog.js +16 -0
  79. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  80. package/dist/client/settings/AgentsSection.d.ts.map +1 -1
  81. package/dist/client/settings/AgentsSection.js +250 -30
  82. package/dist/client/settings/AgentsSection.js.map +1 -1
  83. package/dist/client/settings/SecretsSection.d.ts.map +1 -1
  84. package/dist/client/settings/SecretsSection.js +16 -2
  85. package/dist/client/settings/SecretsSection.js.map +1 -1
  86. package/dist/client/use-chat-models.d.ts.map +1 -1
  87. package/dist/client/use-chat-models.js +46 -27
  88. package/dist/client/use-chat-models.js.map +1 -1
  89. package/dist/collab/routes.d.ts +1 -1
  90. package/dist/localization/default-messages.d.ts +9 -0
  91. package/dist/localization/default-messages.d.ts.map +1 -1
  92. package/dist/localization/default-messages.js +9 -0
  93. package/dist/localization/default-messages.js.map +1 -1
  94. package/dist/mcp/ask-app-task-handle.d.ts +25 -0
  95. package/dist/mcp/ask-app-task-handle.d.ts.map +1 -0
  96. package/dist/mcp/ask-app-task-handle.js +136 -0
  97. package/dist/mcp/ask-app-task-handle.js.map +1 -0
  98. package/dist/mcp/builtin-tools.d.ts +3 -3
  99. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  100. package/dist/mcp/builtin-tools.js +115 -38
  101. package/dist/mcp/builtin-tools.js.map +1 -1
  102. package/dist/observability/routes.d.ts +1 -1
  103. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  104. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  105. package/dist/server/agent-capabilities.d.ts +7 -1
  106. package/dist/server/agent-capabilities.d.ts.map +1 -1
  107. package/dist/server/agent-capabilities.js +1 -0
  108. package/dist/server/agent-capabilities.js.map +1 -1
  109. package/dist/server/agent-chat-plugin.d.ts +7 -0
  110. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  111. package/dist/server/agent-chat-plugin.js +16 -1
  112. package/dist/server/agent-chat-plugin.js.map +1 -1
  113. package/dist/server/agent-discovery.d.ts +1 -0
  114. package/dist/server/agent-discovery.d.ts.map +1 -1
  115. package/dist/server/agent-discovery.js +1 -1
  116. package/dist/server/agent-discovery.js.map +1 -1
  117. package/dist/server/agent-peer-probe.d.ts +37 -0
  118. package/dist/server/agent-peer-probe.d.ts.map +1 -0
  119. package/dist/server/agent-peer-probe.js +90 -0
  120. package/dist/server/agent-peer-probe.js.map +1 -0
  121. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  122. package/dist/server/core-routes-plugin.js +53 -0
  123. package/dist/server/core-routes-plugin.js.map +1 -1
  124. package/dist/shared/mcp-embed-headers.d.ts +1 -0
  125. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  126. package/dist/shared/mcp-embed-headers.js +4 -0
  127. package/dist/shared/mcp-embed-headers.js.map +1 -1
  128. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
  129. package/docs/content/external-agents-catalog.mdx +14 -11
  130. package/docs/content/mcp-protocol.mdx +9 -4
  131. package/package.json +2 -2
  132. package/src/a2a/index.ts +2 -0
  133. package/src/agent/production-agent.ts +6 -1
  134. package/src/agent/run-manager.ts +37 -0
  135. package/src/agent/run-store.ts +18 -0
  136. package/src/client/MultiTabAssistantChat.tsx +75 -43
  137. package/src/client/agent-chat.ts +19 -1
  138. package/src/client/chat/tool-call-display.tsx +25 -0
  139. package/src/client/resources/mcp-integration-catalog.ts +23 -0
  140. package/src/client/settings/AgentsSection.tsx +565 -71
  141. package/src/client/settings/SecretsSection.tsx +71 -36
  142. package/src/client/use-chat-models.ts +54 -37
  143. package/src/localization/default-messages.ts +11 -0
  144. package/src/mcp/ask-app-task-handle.ts +183 -0
  145. package/src/mcp/builtin-tools.ts +146 -34
  146. package/src/server/agent-capabilities.ts +8 -1
  147. package/src/server/agent-chat-plugin.ts +17 -0
  148. package/src/server/agent-discovery.ts +1 -1
  149. package/src/server/agent-peer-probe.ts +133 -0
  150. package/src/server/core-routes-plugin.ts +67 -0
  151. package/src/shared/mcp-embed-headers.ts +4 -0
  152. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
@@ -11,6 +11,7 @@ export declare function shouldAllowMcpEmbedCredentials(origin: string | null | u
11
11
  export declare const MCP_EMBED_STATIC_ASSET_HEADERS: {
12
12
  readonly "Access-Control-Allow-Origin": "*";
13
13
  readonly "Cross-Origin-Resource-Policy": "cross-origin";
14
+ readonly "X-Content-Type-Options": "nosniff";
14
15
  };
15
16
  export declare function mcpEmbedStaticAssetRouteRules(basePath?: string): Record<string, {
16
17
  headers: typeof MCP_EMBED_STATIC_ASSET_HEADERS;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-embed-headers.d.ts","sourceRoot":"","sources":["../../src/shared/mcp-embed-headers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,qPAC2M,CAAC;AACrP,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAYzE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAWT;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAUT;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAWT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAOT;AAED,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAgBT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAiBT;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAUT;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAQT;AAED,eAAO,MAAM,8BAA8B;aACzC,6BAA6B,EAAE,GAAG;aAClC,8BAA8B,EAAE,cAAc;CACtC,CAAC;AAoBX,wBAAgB,6BAA6B,CAC3C,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,8BAA8B,CAAA;CAAE,CAAC,CAapE"}
1
+ {"version":3,"file":"mcp-embed-headers.d.ts","sourceRoot":"","sources":["../../src/shared/mcp-embed-headers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,qPAC2M,CAAC;AACrP,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAYzE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAWT;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAUT;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAWT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAOT;AAED,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAgBT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAiBT;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAUT;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAQT;AAKD,eAAO,MAAM,8BAA8B;aACzC,6BAA6B,EAAE,GAAG;aAClC,8BAA8B,EAAE,cAAc;aAC9C,wBAAwB,EAAE,SAAS;CAC3B,CAAC;AAoBX,wBAAgB,6BAA6B,CAC3C,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,EAAE,OAAO,8BAA8B,CAAA;CAAE,CAAC,CAapE"}
@@ -104,9 +104,13 @@ export function shouldAllowMcpEmbedCredentials(origin) {
104
104
  !isMcpProductHostOrigin(origin) &&
105
105
  !isAgentNativeFirstPartyAppOrigin(origin));
106
106
  }
107
+ // These paths are served straight off the CDN, so the security-headers h3
108
+ // middleware never runs for them — anything it sets that must also hold for
109
+ // static assets has to be repeated here.
107
110
  export const MCP_EMBED_STATIC_ASSET_HEADERS = {
108
111
  "Access-Control-Allow-Origin": "*",
109
112
  "Cross-Origin-Resource-Policy": "cross-origin",
113
+ "X-Content-Type-Options": "nosniff",
110
114
  };
111
115
  const STATIC_ASSET_PATTERNS = [
112
116
  "/assets/**",
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-embed-headers.js","sourceRoot":"","sources":["../../src/shared/mcp-embed-headers.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GACvC,kPAAkP,CAAC;AACrP,MAAM,CAAC,MAAM,uBAAuB,GAAG,iCAAiC,CAAC;AAEzE,MAAM,0BAA0B,GAAG,wCAAwC,CAAC;AAC5E,MAAM,2BAA2B,GAC/B,iDAAiD,CAAC;AACpD,MAAM,wCAAwC,GAAG,mBAAmB,CAAC;AACrE,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC;IACvC,yBAAyB;IACzB,qBAAqB;IACrB,mBAAmB;CACpB,CAAC,CAAC;AAEH,MAAM,UAAU,qBAAqB,CACnC,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,OAAO;YACxB,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAClE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAC3E,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,QAAQ;YACzB,2BAA2B,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAC/C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,wBAAwB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gCAAgC,CAC9C,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC5C,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,QAAQ;YACzB,CAAC,GAAG,CAAC,QAAQ;YACb,CAAC,GAAG,CAAC,QAAQ;YACb,CAAC,GAAG,CAAC,IAAI;YACT,QAAQ,CAAC,QAAQ,CAAC,wCAAwC,CAAC;YAC3D,QAAQ,CAAC,MAAM,GAAG,wCAAwC,CAAC,MAAM,CAClE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC5C,OAAO,CACL,GAAG,CAAC,QAAQ,KAAK,QAAQ;YACzB,CAAC,GAAG,CAAC,QAAQ;YACb,CAAC,GAAG,CAAC,QAAQ;YACb,CAAC,QAAQ,KAAK,YAAY;gBACxB,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAChC,QAAQ,KAAK,YAAY;gBACzB,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CACpC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAiC;IAEjC,OAAO,CACL,MAAM,KAAK,MAAM;QACjB,qBAAqB,CAAC,MAAM,CAAC;QAC7B,wBAAwB,CAAC,MAAM,CAAC;QAChC,yBAAyB,CAAC,MAAM,CAAC;QACjC,sBAAsB,CAAC,MAAM,CAAC;QAC9B,gCAAgC,CAAC,MAAM,CAAC;QACxC,sBAAsB,CAAC,MAAM,CAAC,CAC/B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,MAAiC;IAEjC,OAAO,CACL,MAAM,KAAK,MAAM;QACjB,CAAC,wBAAwB,CAAC,MAAM,CAAC;QACjC,CAAC,yBAAyB,CAAC,MAAM,CAAC;QAClC,CAAC,sBAAsB,CAAC,MAAM,CAAC;QAC/B,CAAC,gCAAgC,CAAC,MAAM,CAAC,CAC1C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,6BAA6B,EAAE,GAAG;IAClC,8BAA8B,EAAE,cAAc;CACtC,CAAC;AAEX,MAAM,qBAAqB,GAAG;IAC5B,YAAY;IACZ,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,aAAa;IACb,qBAAqB;IACrB,qBAAqB;CACtB,CAAC;AAEF,SAAS,iBAAiB,CAAC,QAA4B;IACrD,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QACzB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC3B,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,QAAiB;IAEjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,KAAK,GAGP,EAAE,CAAC;IACP,KAAK,MAAM,OAAO,IAAI,qBAAqB,EAAE,CAAC;QAC5C,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC;QAC7D,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,CAAC,GAAG,IAAI,GAAG,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC;QAC3E,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["export const MCP_EMBED_CORS_ALLOW_HEADERS =\n \"Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-User-Timezone,X-Agent-Native-Embed-Target,X-Agent-Native-Embed-Transplant\";\nexport const EMBED_TRANSPLANT_HEADER = \"x-agent-native-embed-transplant\";\n\nconst CLAUDE_MCP_CONTENT_HOST_RE = /^[a-f0-9]{32}\\.claudemcpcontent\\.com$/i;\nconst CHATGPT_MCP_SANDBOX_HOST_RE =\n /^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i;\nconst AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX = \".agent-native.com\";\nconst MCP_PRODUCT_HOST_ORIGINS = new Set([\n \"https://chat.openai.com\",\n \"https://chatgpt.com\",\n \"https://claude.ai\",\n]);\n\nexport function isLocalMcpEmbedOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n return (\n url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"].includes(url.hostname)\n );\n } catch {\n return false;\n }\n}\n\nexport function isClaudeMcpContentOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n return (\n url.protocol === \"https:\" && CLAUDE_MCP_CONTENT_HOST_RE.test(url.hostname)\n );\n } catch {\n return false;\n }\n}\n\nexport function isChatGptMcpSandboxOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n return (\n url.protocol === \"https:\" &&\n CHATGPT_MCP_SANDBOX_HOST_RE.test(url.hostname)\n );\n } catch {\n return false;\n }\n}\n\nexport function isMcpProductHostOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n return MCP_PRODUCT_HOST_ORIGINS.has(new URL(origin).origin);\n } catch {\n return false;\n }\n}\n\nexport function isAgentNativeFirstPartyAppOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n const hostname = url.hostname.toLowerCase();\n return (\n url.protocol === \"https:\" &&\n !url.username &&\n !url.password &&\n !url.port &&\n hostname.endsWith(AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX) &&\n hostname.length > AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX.length\n );\n } catch {\n return false;\n }\n}\n\nexport function isBuilderIoEmbedOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n const hostname = url.hostname.toLowerCase();\n return (\n url.protocol === \"https:\" &&\n !url.username &&\n !url.password &&\n (hostname === \"builder.io\" ||\n hostname.endsWith(\".builder.io\") ||\n hostname === \"builder.my\" ||\n hostname.endsWith(\".builder.my\"))\n );\n } catch {\n return false;\n }\n}\n\nexport function isMcpEmbedCorsOrigin(\n origin: string | null | undefined,\n): boolean {\n return (\n origin === \"null\" ||\n isLocalMcpEmbedOrigin(origin) ||\n isClaudeMcpContentOrigin(origin) ||\n isChatGptMcpSandboxOrigin(origin) ||\n isMcpProductHostOrigin(origin) ||\n isAgentNativeFirstPartyAppOrigin(origin) ||\n isBuilderIoEmbedOrigin(origin)\n );\n}\n\nexport function shouldAllowMcpEmbedCredentials(\n origin: string | null | undefined,\n): boolean {\n return (\n origin !== \"null\" &&\n !isClaudeMcpContentOrigin(origin) &&\n !isChatGptMcpSandboxOrigin(origin) &&\n !isMcpProductHostOrigin(origin) &&\n !isAgentNativeFirstPartyAppOrigin(origin)\n );\n}\n\nexport const MCP_EMBED_STATIC_ASSET_HEADERS = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n} as const;\n\nconst STATIC_ASSET_PATTERNS = [\n \"/assets/**\",\n \"/favicon.ico\",\n \"/favicon.svg\",\n \"/manifest.json\",\n \"/icon-*.svg\",\n \"/agent-native-*.svg\",\n \"/library-presets/**\",\n];\n\nfunction normalizeBasePath(basePath: string | undefined): string {\n const base = (basePath ?? \"\").trim();\n if (!base || base === \"/\") return \"\";\n return base.startsWith(\"/\")\n ? base.replace(/\\/+$/g, \"\")\n : `/${base.replace(/\\/+$/g, \"\")}`;\n}\n\nexport function mcpEmbedStaticAssetRouteRules(\n basePath?: string,\n): Record<string, { headers: typeof MCP_EMBED_STATIC_ASSET_HEADERS }> {\n const base = normalizeBasePath(basePath);\n const rules: Record<\n string,\n { headers: typeof MCP_EMBED_STATIC_ASSET_HEADERS }\n > = {};\n for (const pattern of STATIC_ASSET_PATTERNS) {\n rules[pattern] = { headers: MCP_EMBED_STATIC_ASSET_HEADERS };\n if (base) {\n rules[`${base}${pattern}`] = { headers: MCP_EMBED_STATIC_ASSET_HEADERS };\n }\n }\n return rules;\n}\n"]}
1
+ {"version":3,"file":"mcp-embed-headers.js","sourceRoot":"","sources":["../../src/shared/mcp-embed-headers.ts"],"names":[],"mappings":"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","sourcesContent":["export const MCP_EMBED_CORS_ALLOW_HEADERS =\n \"Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-User-Timezone,X-Agent-Native-Embed-Target,X-Agent-Native-Embed-Transplant\";\nexport const EMBED_TRANSPLANT_HEADER = \"x-agent-native-embed-transplant\";\n\nconst CLAUDE_MCP_CONTENT_HOST_RE = /^[a-f0-9]{32}\\.claudemcpcontent\\.com$/i;\nconst CHATGPT_MCP_SANDBOX_HOST_RE =\n /^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i;\nconst AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX = \".agent-native.com\";\nconst MCP_PRODUCT_HOST_ORIGINS = new Set([\n \"https://chat.openai.com\",\n \"https://chatgpt.com\",\n \"https://claude.ai\",\n]);\n\nexport function isLocalMcpEmbedOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n return (\n url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"].includes(url.hostname)\n );\n } catch {\n return false;\n }\n}\n\nexport function isClaudeMcpContentOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n return (\n url.protocol === \"https:\" && CLAUDE_MCP_CONTENT_HOST_RE.test(url.hostname)\n );\n } catch {\n return false;\n }\n}\n\nexport function isChatGptMcpSandboxOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n return (\n url.protocol === \"https:\" &&\n CHATGPT_MCP_SANDBOX_HOST_RE.test(url.hostname)\n );\n } catch {\n return false;\n }\n}\n\nexport function isMcpProductHostOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n return MCP_PRODUCT_HOST_ORIGINS.has(new URL(origin).origin);\n } catch {\n return false;\n }\n}\n\nexport function isAgentNativeFirstPartyAppOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n const hostname = url.hostname.toLowerCase();\n return (\n url.protocol === \"https:\" &&\n !url.username &&\n !url.password &&\n !url.port &&\n hostname.endsWith(AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX) &&\n hostname.length > AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX.length\n );\n } catch {\n return false;\n }\n}\n\nexport function isBuilderIoEmbedOrigin(\n origin: string | null | undefined,\n): boolean {\n if (!origin) return false;\n try {\n const url = new URL(origin);\n const hostname = url.hostname.toLowerCase();\n return (\n url.protocol === \"https:\" &&\n !url.username &&\n !url.password &&\n (hostname === \"builder.io\" ||\n hostname.endsWith(\".builder.io\") ||\n hostname === \"builder.my\" ||\n hostname.endsWith(\".builder.my\"))\n );\n } catch {\n return false;\n }\n}\n\nexport function isMcpEmbedCorsOrigin(\n origin: string | null | undefined,\n): boolean {\n return (\n origin === \"null\" ||\n isLocalMcpEmbedOrigin(origin) ||\n isClaudeMcpContentOrigin(origin) ||\n isChatGptMcpSandboxOrigin(origin) ||\n isMcpProductHostOrigin(origin) ||\n isAgentNativeFirstPartyAppOrigin(origin) ||\n isBuilderIoEmbedOrigin(origin)\n );\n}\n\nexport function shouldAllowMcpEmbedCredentials(\n origin: string | null | undefined,\n): boolean {\n return (\n origin !== \"null\" &&\n !isClaudeMcpContentOrigin(origin) &&\n !isChatGptMcpSandboxOrigin(origin) &&\n !isMcpProductHostOrigin(origin) &&\n !isAgentNativeFirstPartyAppOrigin(origin)\n );\n}\n\n// These paths are served straight off the CDN, so the security-headers h3\n// middleware never runs for them — anything it sets that must also hold for\n// static assets has to be repeated here.\nexport const MCP_EMBED_STATIC_ASSET_HEADERS = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n \"X-Content-Type-Options\": \"nosniff\",\n} as const;\n\nconst STATIC_ASSET_PATTERNS = [\n \"/assets/**\",\n \"/favicon.ico\",\n \"/favicon.svg\",\n \"/manifest.json\",\n \"/icon-*.svg\",\n \"/agent-native-*.svg\",\n \"/library-presets/**\",\n];\n\nfunction normalizeBasePath(basePath: string | undefined): string {\n const base = (basePath ?? \"\").trim();\n if (!base || base === \"/\") return \"\";\n return base.startsWith(\"/\")\n ? base.replace(/\\/+$/g, \"\")\n : `/${base.replace(/\\/+$/g, \"\")}`;\n}\n\nexport function mcpEmbedStaticAssetRouteRules(\n basePath?: string,\n): Record<string, { headers: typeof MCP_EMBED_STATIC_ASSET_HEADERS }> {\n const base = normalizeBasePath(basePath);\n const rules: Record<\n string,\n { headers: typeof MCP_EMBED_STATIC_ASSET_HEADERS }\n > = {};\n for (const pattern of STATIC_ASSET_PATTERNS) {\n rules[pattern] = { headers: MCP_EMBED_STATIC_ASSET_HEADERS };\n if (base) {\n rules[`${base}${pattern}`] = { headers: MCP_EMBED_STATIC_ASSET_HEADERS };\n }\n }\n return rules;\n}\n"]}
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: a2a-protocol
3
3
  description: >-
4
- How agents call other agents via the A2A (agent-to-agent) JSON-RPC protocol.
5
- Use when enabling inter-agent communication, exposing agent skills to other
6
- agents, or calling external agents from scripts.
4
+ How agent-native apps discover, authenticate with, and call each other over
5
+ A2A. Use when connecting one app to another, debugging a remote agent that
6
+ won't authenticate, exposing skills to peers, or calling agents from scripts.
7
7
  scope: dev
8
8
  metadata:
9
9
  internal: true
@@ -13,110 +13,155 @@ metadata:
13
13
 
14
14
  ## Rule
15
15
 
16
- Agents can call other agents using the A2A protocol. This is a JSON-RPC-based protocol for agent discovery and communication. Use it when work should go to a different agent entirely — not the local agent chat.
16
+ Agents call other agents over A2A, a JSON-RPC protocol for discovery and
17
+ delegation. Use it when work belongs to a different agent entirely — not the
18
+ local agent chat.
17
19
 
18
- ## Why
20
+ Connecting app A to app B is two independent things, and both must be true:
19
21
 
20
- Agent-native apps don't exist in isolation. A mail agent might need analytics data. A calendar agent might need to search issues. A2A lets agents discover each other, send messages, and receive structured results — all over HTTP with bearer token auth.
22
+ 1. **B is registered on A** as a `remote-agents/<id>.json` resource.
23
+ 2. **A and B share a secret**, so A's signed JWT verifies on B.
21
24
 
22
- ## How to Enable A2A (Server Side)
25
+ Neither is a code change, and neither is symmetric. Registering B on A does not
26
+ let B call A.
23
27
 
24
- Add `mountA2A()` to a server plugin:
28
+ ## A2A is already mounted
25
29
 
26
- ```ts
27
- // server/plugins/a2a.ts
28
- import { mountA2A } from "@agent-native/core/a2a";
29
-
30
- export default defineNitroPlugin((nitro) => {
31
- const app = nitro.h3App;
32
-
33
- mountA2A(app, {
34
- name: "Analytics Agent",
35
- description: "Queries analytics data across providers",
36
- version: "1.0.0",
37
- skills: [
38
- {
39
- id: "query-data",
40
- name: "Query Data",
41
- description: "Run analytics queries across connected data sources",
42
- tags: ["analytics", "data"],
43
- examples: ["What were last week's signups?", "Show conversion rates"],
44
- },
45
- ],
46
- apiKeyEnv: "A2A_API_KEY", // env var holding the bearer token
47
- streaming: true, // enable message/stream method
48
- });
49
- });
50
- ```
51
-
52
- This mounts the agent-native A2A endpoints:
53
-
54
- - `GET /.well-known/agent-card.json` — public agent discovery (no auth required)
55
- - `POST /_agent-native/a2a` — primary JSON-RPC endpoint (bearer token auth required)
30
+ `createAgentChatPlugin` calls `mountA2A` for every app, so a generated app
31
+ already serves:
56
32
 
57
- The client may fall back to `POST /a2a` for external or legacy peers that only
58
- expose that simple path. New agent-native apps should document and call the
59
- `/_agent-native/a2a` endpoint.
33
+ - `GET /.well-known/agent-card.json` public discovery, never authenticated
34
+ - `POST /_agent-native/a2a` JSON-RPC, authenticated
60
35
 
61
- ## The Config Object
62
-
63
- ```ts
64
- interface A2AConfig {
65
- name: string; // agent display name
66
- description: string; // what this agent does
67
- version?: string; // semver version (default: "1.0.0")
68
- skills: AgentSkill[]; // capabilities this agent exposes
69
- handler?: A2AHandler; // custom message handler
70
- apiKeyEnv?: string; // env var name for bearer token auth
71
- streaming?: boolean; // enable streaming responses
72
- }
36
+ Do not add a `mountA2A` server plugin to enable A2A; it is on. Hand-mounting is
37
+ only for a bespoke card or a custom handler (see **Custom mount** below).
73
38
 
74
- interface AgentSkill {
75
- id: string; // unique skill identifier
76
- name: string; // human-readable name
77
- description: string; // what this skill does
78
- tags?: string[]; // categorization tags
79
- examples?: string[]; // example prompts
80
- }
81
- ```
39
+ The client falls back to `POST /a2a` for external peers that only expose that
40
+ path. New agent-native apps should call `/_agent-native/a2a`.
82
41
 
83
- ## Agent Card
42
+ ## Registering a remote agent
84
43
 
85
- The agent card is auto-generated at `GET /.well-known/agent-card.json`. Other agents fetch this to discover what skills are available:
44
+ A remote agent is **a row in the resources table, not a file on disk**. Path
45
+ `remote-agents/<id>.json`, owner `SHARED_OWNER`, content:
86
46
 
87
47
  ```json
88
48
  {
89
- "name": "Analytics Agent",
49
+ "id": "analytics",
50
+ "name": "Analytics",
90
51
  "description": "Queries analytics data across providers",
91
52
  "url": "https://analytics.example.com",
92
- "version": "1.0.0",
93
- "protocolVersion": "0.3",
94
- "capabilities": { "streaming": true },
95
- "skills": [
96
- {
97
- "id": "query-data",
98
- "name": "Query Data",
99
- "description": "Run analytics queries across connected data sources"
100
- }
101
- ]
53
+ "color": "#6B7280"
102
54
  }
103
55
  ```
104
56
 
105
- ## Calling Another Agent
57
+ `url` is the only required field. `parseRemoteAgentManifest` accepts **only**
58
+ these five keys — there is no `apiKey`, `env`, `skills`, or `token` field, and
59
+ anything else is silently dropped.
60
+
61
+ Four ways to create it, all writing the same row:
62
+
63
+ | Surface | Where |
64
+ | --- | --- |
65
+ | Settings → Manage agent → Connected Agents (A2A) | any app with the settings panel |
66
+ | Agent page → Connections | apps mounting `AgentTabsPage` |
67
+ | Dispatch → Agents → Add external agent | workspace dispatch |
68
+ | The agent itself | `resources` tool, `action: "write"`, `--scope shared` |
69
+
70
+ The last one matters for template apps with no Code tab: "connect me to
71
+ https://analytics.example.com" is a complete instruction. `remote-agents/` is
72
+ whitelisted as a durable control path, so the write is workspace-scoped, and it
73
+ takes effect on the next `discoverAgents()` call with no restart.
74
+
75
+ First-party templates are seeded automatically and overlaid on top of manifests,
76
+ so `Mail`, `Calendar`, and friends appear without registration. In dev they
77
+ resolve to `http://localhost:<devPort>`; a manifest pointing at localhost is
78
+ ignored in production.
79
+
80
+ `call-agent` also accepts a raw URL, which skips registration entirely — useful
81
+ for one-off calls, useless for @-mentions.
82
+
83
+ ### Checking a peer
84
+
85
+ `GET /_agent-native/agents/probe?url=<base>` reads the peer's card and makes one
86
+ authenticated no-op call, returning `reachable` and `authorized` as independent
87
+ fields. Omit `url` to probe every registered peer. Reachable-but-unauthorized is
88
+ the failure worth looking for: local dev runs the receiver unauthenticated, so a
89
+ mismatched secret only surfaces after deploy. The Connected Agents settings
90
+ section calls this on add and on open.
91
+
92
+ ## Authentication
93
+
94
+ A2A authenticates with a **short-lived JWT the caller signs**, not a stored
95
+ bearer key. `Authorization: Bearer <jwt>`; claims carry the caller's email and
96
+ org domain; HS256; 15-minute default TTL. There is no per-peer API key
97
+ anywhere in the system, which is why the manifest has no field for one.
98
+
99
+ Two secrets can sign it:
100
+
101
+ | Secret | Scope | How it is set |
102
+ | --- | --- | --- |
103
+ | `A2A_SECRET` | whole deployment | env var, **never auto-generated** |
104
+ | org `a2a_secret` | one organization | auto-generated on org creation; Team page UI |
105
+
106
+ The org secret is the managed path: **Team page → A2A secret** (owner only)
107
+ reveals, copies, regenerates, and pushes it to every discovered peer. Rotation
108
+ is tolerated — the caller tries both secrets in order and sticks to whichever
109
+ worked.
110
+
111
+ `A2A_SECRET` is the deploy-level path. `workspace-deploy` refuses a production
112
+ workspace deploy without it and prints the generator command. Peers that must
113
+ trust each other need the *same* value on both sides.
114
+
115
+ With no secret configured at all:
116
+
117
+ | Runtime | Result |
118
+ | --- | --- |
119
+ | local dev | open, one-time console warning |
120
+ | production | `503` — "A2A authentication not configured" |
121
+ | production, secret set, bad/absent token | `401` |
122
+
123
+ "Production" is detected broadly (NODE_ENV, Netlify, Lambda, Vercel, Render,
124
+ Fly, Cloud Run, …) and unrecognized deployed hosts fail closed unless the
125
+ request is genuine loopback or `A2A_ALLOW_UNSIGNED_INTERNAL=1`.
126
+
127
+ `A2AConfig.apiKeyEnv` still exists for static bearer auth against non-agent-native
128
+ peers, but the framework's own mount never sets it. Do not reach for it when
129
+ debugging a connection between two agent-native apps — the answer there is
130
+ always the shared secret.
131
+
132
+ Never hardcode either secret in source, docs, prompts, app state, action
133
+ descriptions, client bundles, or examples. Read them from runtime config; never
134
+ log or return them.
135
+
136
+ ## Advertising what this agent can do
137
+
138
+ Card `skills` are derived from actions marked `publicAgent`. An app that marks
139
+ none publishes `"skills": []` and peers cannot see what it offers, even though
140
+ delegation still works. Mark the actions a peer should be able to see and
141
+ invoke directly; leave the rest internal.
142
+
143
+ ## Calling another agent
106
144
 
107
145
  ### Simple: `callAgent()` (text in, text out)
108
146
 
109
147
  ```ts
110
- import { callAgent } from "@agent-native/core/a2a";
148
+ import { callAgent, resolveA2ACallerAuth } from "@agent-native/core/a2a";
111
149
 
150
+ const auth = await resolveA2ACallerAuth();
112
151
  const answer = await callAgent(
113
152
  "https://analytics.example.com",
114
153
  "What were last week's signups?",
115
- { apiKey: process.env.ANALYTICS_A2A_KEY },
154
+ auth,
116
155
  );
117
156
  // answer is a plain string
118
157
  ```
119
158
 
159
+ `callAgent` signs nothing on its own — with no `userEmail`/`orgSecret` in opts
160
+ it calls **unauthenticated**, which silently works in local dev and 401s in
161
+ production. `resolveA2ACallerAuth()` pulls the authenticated email, org domain,
162
+ and org secret out of request context; pass them explicitly only from CLI or
163
+ cron, where there is no request.
164
+
120
165
  ### Fast bounded read: `invokeAgentAction()`
121
166
 
122
167
  When the caller knows the exact receiver-owned read action and arguments, skip
@@ -168,12 +213,13 @@ action arguments or results.
168
213
  ### Advanced: `A2AClient` (full control)
169
214
 
170
215
  ```ts
171
- import { A2AClient } from "@agent-native/core/a2a";
216
+ import { A2AClient, resolveA2ACallerAuth } from "@agent-native/core/a2a";
172
217
 
173
- const client = new A2AClient(
174
- "https://analytics.example.com",
175
- process.env.ANALYTICS_A2A_KEY,
176
- );
218
+ // The second argument is the bearer token itself, not a signing secret.
219
+ const { apiKey, apiKeyFallbacks } = await resolveA2ACallerAuth();
220
+ const client = new A2AClient("https://analytics.example.com", apiKey, {
221
+ fallbackApiKeys: apiKeyFallbacks,
222
+ });
177
223
 
178
224
  // Discover agent capabilities
179
225
  const card = await client.getAgentCard();
@@ -264,19 +310,6 @@ submitted → working → completed
264
310
  - **canceled** — task was canceled via `tasks/cancel`
265
311
  - **input-required** — agent needs more information from the caller
266
312
 
267
- ## Security
268
-
269
- A2A uses bearer token auth. The server reads the token from the environment variable specified by `apiKeyEnv`:
270
-
271
- - Set `A2A_API_KEY=<A2A_API_KEY_VALUE>` in the server's deployment environment
272
- - Callers pass it as `Authorization: Bearer <A2A_API_KEY_VALUE>`
273
- - The agent card endpoint (`/.well-known/agent-card.json`) is public — no auth needed for discovery
274
-
275
- Never hardcode the bearer token in source, docs, prompts, app state, action
276
- descriptions, client bundles, or examples. A2A tokens are deploy-level secrets
277
- unless a specific app designs a scoped credential flow; read them from secure
278
- runtime configuration and never log or return them.
279
-
280
313
  ## Message Parts
281
314
 
282
315
  Messages contain typed parts:
@@ -287,34 +320,38 @@ Messages contain typed parts:
287
320
  | `file` | `{ type: "file", file: { ... } }` | Files (bytes or URI) |
288
321
  | `data` | `{ type: "data", data: { ... } }` | Structured JSON data |
289
322
 
290
- ## Example: Cross-Agent Workflow
323
+ ## Custom mount
291
324
 
292
- A mail agent calls an analytics agent to include data in an email draft:
325
+ Only when the default card is wrong for the app a curated skill list, a
326
+ non-agent handler, or static bearer auth against an external peer:
293
327
 
294
328
  ```ts
295
- // actions/draft-with-analytics.ts
296
- import { callAgent } from "@agent-native/core/a2a";
297
- import { writeAppState } from "@agent-native/core/application-state";
298
-
299
- export default async function (args: string[]) {
300
- // Ask the analytics agent for data
301
- const stats = await callAgent(
302
- process.env.ANALYTICS_AGENT_URL!,
303
- "Summarize last week's key metrics in 3 bullet points",
304
- { apiKey: process.env.ANALYTICS_A2A_KEY },
305
- );
306
-
307
- // Create a draft with the analytics data
308
- await writeAppState("compose-analytics-report", {
309
- id: "analytics-report",
310
- to: "team@example.com",
311
- subject: "Weekly Analytics Summary",
312
- body: `Hi team,\n\nHere are last week's numbers:\n\n${stats}\n\nBest`,
313
- mode: "compose",
329
+ // server/plugins/a2a.ts
330
+ import { mountA2A } from "@agent-native/core/a2a";
331
+
332
+ export default defineNitroPlugin((nitro) => {
333
+ mountA2A(nitro, {
334
+ appId: "analytics",
335
+ name: "Analytics Agent",
336
+ description: "Queries analytics data across providers",
337
+ skills: [
338
+ {
339
+ id: "query-data",
340
+ name: "Query Data",
341
+ description: "Run analytics queries across connected data sources",
342
+ tags: ["analytics", "data"],
343
+ examples: ["What were last week's signups?", "Show conversion rates"],
344
+ },
345
+ ],
346
+ streaming: true,
314
347
  });
315
- }
348
+ });
316
349
  ```
317
350
 
351
+ Config also carries `handler`, `publicSkillsOnly`, `durableBackgroundRuns`,
352
+ `executeReadOnlyAction`, `executeApproval`, and `apiKeyEnv`. See `A2AConfig` in
353
+ `@agent-native/core/a2a` for the full shape.
354
+
318
355
  ## All Types
319
356
 
320
357
  All types are exported from `@agent-native/core/a2a`:
@@ -345,5 +382,8 @@ import type {
345
382
  ## Related Skills
346
383
 
347
384
  - **delegate-to-agent** — For work the local agent handles. Use A2A when the work goes to a different agent.
348
- - **actions** — A2A calls typically happen inside actions
349
- - **storing-data** — Results from A2A calls are stored in SQL like any other data
385
+ - **authentication** — Org creation, membership, and where the org secret lives.
386
+ - **actions** — A2A calls typically happen inside actions; `publicAgent` marks what peers can see.
387
+ - **storing-data** — Results from A2A calls are stored in SQL like any other data.
388
+ </content>
389
+ </invoke>
@@ -225,13 +225,13 @@ In practice, every agent-native app should be authored with both: MCP Apps for i
225
225
 
226
226
  On top of the per-action tools the MCP server exposes a stable verb set, so an external agent has a predictable surface without guessing per-app action names:
227
227
 
228
- | Tool | Side effects | Returns |
229
- | -------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
230
- | `list_apps` | none | workspace apps + their URLs / running state |
231
- | `open_app({ app, view?, path?, params?, embed? })` | none | a deep link or same-origin route; `embed: true` renders the full app inline where supported |
232
- | `ask_app({ app, message, async?, maxWaitMs? })` | agent loop | routes a natural-language task to that app's in-app agent; long work returns a durable `taskId` for `ask_app_status` polling |
233
- | `create_workspace_app({ name, template })` | scaffolds | a new app booted via the workspace path, plus its running URL + deep link |
234
- | `list_templates` | none | the allow-listed templates only |
228
+ | Tool | Side effects | Returns |
229
+ | -------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
230
+ | `list_apps` | none | workspace apps + their URLs / running state |
231
+ | `open_app({ app, view?, path?, params?, embed? })` | none | a deep link or same-origin route; `embed: true` renders the full app inline where supported |
232
+ | `ask_app({ app, message, async?, maxWaitMs? })` | agent loop | routes a natural-language task to that app's in-app agent; long work returns a durable `taskHandle` plus a legacy `taskId` for `ask_app_status` polling |
233
+ | `create_workspace_app({ name, template })` | scaffolds | a new app booted via the workspace path, plus its running URL + deep link |
234
+ | `list_templates` | none | the allow-listed templates only |
235
235
 
236
236
  `create_workspace_app` rejects any non-allow-listed template — the public template allow-list in `packages/shared-app-config/templates.ts` is authoritative and CI-guarded; an external agent cannot widen it. A same-named template action overrides a builtin (template-over-core precedence). Disable the whole set with `MCPConfig.builtinCrossAppTools: false`.
237
237
 
@@ -244,9 +244,10 @@ For fast ChatGPT/Claude handoffs, the ideal path is direct: call the action that
244
244
  MCP hosts and gateways do not share one long tool-call timeout; some enforce a
245
245
  five-minute ceiling and others are configurable. Do not design an external
246
246
  agent integration around a ten-minute blocking `tools/call`. `ask-agent` and
247
- `ask_app` acknowledge hosted work quickly, then return a durable `taskId` when
248
- the inline wait expires. Poll with `ask_app_status` using the returned `app`
249
- and `taskId`; the result includes `pollAfterMs` as a host-friendly interval.
247
+ `ask_app` acknowledge hosted work quickly, then return a durable `taskHandle`
248
+ plus a legacy `taskId` when the inline wait expires. Poll with `ask_app_status`
249
+ by replaying the returned `poll.arguments` unchanged, especially the
250
+ `taskHandle`; the result includes `pollAfterMs` as a host-friendly interval.
250
251
  The task must be created before the first response can time out, so a polling
251
252
  URL returned only after a long call is not a reliable recovery contract.
252
253
 
@@ -254,7 +255,9 @@ If a bounded transient status read is unavailable, `ask_app_status` can return
254
255
  `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, the
255
256
  original `app` and `taskId`, and `pollAfterMs` / `poll`. This means the status
256
257
  read is unavailable—not that the durable task failed. Retry `ask_app_status`
257
- with that exact `app` and `taskId`; never resubmit `ask_app` to recover, because
258
+ with the exact returned `poll.arguments`, including `taskHandle` when present;
259
+ legacy results without a handle must reuse the same `app` and `taskId`. Never
260
+ resubmit `ask_app` to recover, because
258
261
  submission can duplicate work. Permanent authentication, not-found, and
259
262
  protocol failures remain errors.
260
263
 
@@ -235,7 +235,11 @@ The agent runs the same loop as the interactive chat — it can call multiple to
235
235
  {
236
236
  "poll": {
237
237
  "tool": "ask_app_status",
238
- "arguments": { "app": "mail", "taskId": "task_123" }
238
+ "arguments": {
239
+ "app": "mail",
240
+ "taskId": "task_123",
241
+ "taskHandle": "opaque_handle"
242
+ }
239
243
  }
240
244
  }
241
245
  ```
@@ -248,9 +252,10 @@ If a bounded transient status read is unavailable, `ask_app_status` can return
248
252
  `status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, the
249
253
  original `app` and `taskId`, and `pollAfterMs` / `poll`. This means the status
250
254
  read is unavailable—not that the durable task failed. Retry `ask_app_status`
251
- with that exact `app` and `taskId`; never resubmit `ask_app` to recover, because
252
- submission can duplicate work. Permanent authentication, not-found, and
253
- protocol failures remain errors.
255
+ with the returned `poll.arguments` unchanged, including `taskHandle` when
256
+ present; legacy results without a handle must reuse the same `app` and `taskId`.
257
+ Never resubmit `ask_app` to recover, because submission can duplicate work.
258
+ Permanent authentication, not-found, and protocol failures remain errors.
254
259
 
255
260
  ## Authentication {#authentication}
256
261
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.126.0",
3
+ "version": "0.127.1",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -389,7 +389,7 @@
389
389
  "yjs": "^13.6.31",
390
390
  "zod": "^4.3.6",
391
391
  "@agent-native/recap-cli": "0.5.1",
392
- "@agent-native/toolkit": "^0.10.8"
392
+ "@agent-native/toolkit": "^0.10.9"
393
393
  },
394
394
  "devDependencies": {
395
395
  "@ai-sdk/anthropic": "^3.0.71",
package/src/a2a/index.ts CHANGED
@@ -21,6 +21,8 @@ export {
21
21
 
22
22
  // Client
23
23
  export { A2AClient, callAction, callAgent, signA2AToken } from "./client.js";
24
+ export { resolveA2ACallerAuth } from "./caller-auth.js";
25
+ export type { A2ACallerAuth } from "./caller-auth.js";
24
26
  export {
25
27
  AgentInvocationError,
26
28
  buildAgentInvocationPrompt,
@@ -141,6 +141,7 @@ import {
141
141
  getRun,
142
142
  abortRun,
143
143
  abortRunDurably,
144
+ abortTurnDurably,
144
145
  tryClaimRunSlot,
145
146
  isHostedRuntime,
146
147
  resolveRunSoftTimeoutMs,
@@ -7409,8 +7410,11 @@ export function createProductionAgentHandler(
7409
7410
  // sent from the model picker; `engine.name` is what resolveEngine picked.
7410
7411
  // Divergence between them is the usual cause of "status says builder but
7411
7412
  // no [builder-engine] log lines appear" confusion.
7413
+ // `requestModel` differing from `model` means normalizeModelForEngine
7414
+ // substituted the engine default — otherwise indistinguishable from the
7415
+ // client never asking for one.
7412
7416
  console.log(
7413
- `[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource}`,
7417
+ `[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestModel=${requestModel ?? "(none)"} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource} turnId=${requestTurnId ?? "(none)"}`,
7414
7418
  );
7415
7419
 
7416
7420
  if (
@@ -9020,5 +9024,6 @@ export {
9020
9024
  getRun,
9021
9025
  abortRun,
9022
9026
  abortRunDurably,
9027
+ abortTurnDurably,
9023
9028
  subscribeToRun,
9024
9029
  };
@@ -15,6 +15,8 @@ import {
15
15
  getRunEventsSince,
16
16
  getRunById,
17
17
  getRunByThread,
18
+ getRunTurnRef,
19
+ markTurnAborted,
18
20
  cleanupOldRuns,
19
21
  updateRunHeartbeat,
20
22
  bumpRunProgress,
@@ -2095,5 +2097,40 @@ export async function abortRunDurably(
2095
2097
  return abortedInMemory;
2096
2098
  }
2097
2099
 
2100
+ /**
2101
+ * Stop the whole turn `runId` belongs to, not just that run.
2102
+ *
2103
+ * A turn is executed as a chain of runs: every `loop_limit` / `auto_continue`
2104
+ * boundary and every background handoff starts a successor run with a NEW run
2105
+ * id under the same turn id. Aborting one run therefore only ends the current
2106
+ * chunk — the successor claims itself and the turn keeps going, which is what
2107
+ * users see as "Stop didn't stop it". The durable turn-abort marker is the only
2108
+ * thing the successor-claim path (`isTurnAborted`) consults.
2109
+ */
2110
+ export async function abortTurnDurably(
2111
+ runId: string,
2112
+ reason: string = "user",
2113
+ ): Promise<void> {
2114
+ const memRun = activeRuns.get(runId);
2115
+ // In-memory first: a foreground run's SQL insert is async, so the row may not
2116
+ // exist yet when Stop lands moments after send.
2117
+ const ref = memRun
2118
+ ? { threadId: memRun.threadId, turnId: memRun.turnId }
2119
+ : await getRunTurnRef(runId).catch(() => null);
2120
+ if (!ref) return;
2121
+ try {
2122
+ await markTurnAborted(ref.threadId, ref.turnId, reason);
2123
+ } catch (error) {
2124
+ // The current run is already stopped; a failed marker write must not turn
2125
+ // Stop into a 500. Successors will keep running — capture it so that is
2126
+ // visible rather than silent.
2127
+ captureError(error, {
2128
+ route: "/_agent-native/agent-chat/runs/:id/abort",
2129
+ tags: { source: "agent-run-manager", phase: "abort-turn" },
2130
+ extra: { runId, reason, ...ref },
2131
+ });
2132
+ }
2133
+ }
2134
+
2098
2135
  // Re-export so callers can avoid importing from run-store directly.
2099
2136
  export { tryClaimRunSlot } from "./run-store.js";
@@ -2053,6 +2053,24 @@ export async function markTurnAborted(
2053
2053
  );
2054
2054
  }
2055
2055
 
2056
+ /**
2057
+ * Turn identity for a run, or null when the row is unknown. `turn_id` is null
2058
+ * for rows written before the column existed; those runs are their own turn.
2059
+ */
2060
+ export async function getRunTurnRef(
2061
+ runId: string,
2062
+ ): Promise<{ threadId: string; turnId: string } | null> {
2063
+ await ensureRunTables();
2064
+ const { rows } = await getDbExec().execute({
2065
+ sql: `SELECT thread_id, turn_id FROM agent_runs WHERE id = ?`,
2066
+ args: [runId],
2067
+ });
2068
+ const row = rows[0] as { thread_id?: unknown; turn_id?: unknown } | undefined;
2069
+ const threadId = row?.thread_id ? String(row.thread_id) : "";
2070
+ if (!threadId) return null;
2071
+ return { threadId, turnId: row?.turn_id ? String(row.turn_id) : runId };
2072
+ }
2073
+
2056
2074
  export async function isTurnAborted(
2057
2075
  threadId: string,
2058
2076
  turnId: string,