@agent-native/core 0.131.5 → 0.131.7

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 (350) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +70 -0
  3. package/corpus/core/docs/content/agent-native-toolkit.mdx +13 -6
  4. package/corpus/core/docs/content/cloneable-saas.mdx +5 -5
  5. package/corpus/core/docs/content/creating-templates.mdx +9 -0
  6. package/corpus/core/docs/content/deployment.mdx +18 -12
  7. package/corpus/core/docs/content/doctor.mdx +8 -1
  8. package/corpus/core/docs/content/drop-in-agent.mdx +1 -1
  9. package/corpus/core/docs/content/extensions.mdx +18 -18
  10. package/corpus/core/docs/content/faq.mdx +4 -4
  11. package/corpus/core/docs/content/key-concepts.mdx +30 -21
  12. package/corpus/core/docs/content/security.mdx +2 -2
  13. package/corpus/core/docs/content/template-brain-developers.mdx +4 -1
  14. package/corpus/core/docs/content/template-mail-developers.mdx +4 -1
  15. package/corpus/core/docs/content/what-is-agent-native.mdx +6 -6
  16. package/corpus/core/package.json +1 -1
  17. package/corpus/core/src/a2a/client.ts +449 -66
  18. package/corpus/core/src/a2a/correlation.ts +31 -0
  19. package/corpus/core/src/a2a/invoke.ts +4 -2
  20. package/corpus/core/src/a2a/types.ts +4 -0
  21. package/corpus/core/src/action.ts +3 -0
  22. package/corpus/core/src/agent/action-continuation-guidance.ts +2 -1
  23. package/corpus/core/src/agent/engine/registry.ts +98 -6
  24. package/corpus/core/src/agent/production-agent.ts +151 -18
  25. package/corpus/core/src/agent/run-loop-with-resume.ts +190 -3
  26. package/corpus/core/src/agent/run-manager.ts +117 -41
  27. package/corpus/core/src/agent/thread-debug-history.ts +86 -0
  28. package/corpus/core/src/agent/types.ts +3 -1
  29. package/corpus/core/src/cli/create.ts +11 -1
  30. package/corpus/core/src/cli/templates-meta.ts +2 -2
  31. package/corpus/core/src/client/agent-chat-adapter.ts +91 -13
  32. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +11 -9
  33. package/corpus/core/src/client/sse-event-processor.ts +17 -3
  34. package/corpus/core/src/db/client.ts +57 -17
  35. package/corpus/core/src/guards/no-env-credentials.ts +2 -0
  36. package/corpus/core/src/integrations/adapters/slack.ts +8 -3
  37. package/corpus/core/src/localization/default-messages.ts +44 -0
  38. package/corpus/core/src/observability/traces.ts +78 -5
  39. package/corpus/core/src/scripts/call-agent.ts +294 -11
  40. package/corpus/core/src/scripts/describe-workspace-apps.ts +2 -2
  41. package/corpus/core/src/secrets/crypto.ts +156 -35
  42. package/corpus/core/src/secrets/storage.ts +64 -26
  43. package/corpus/core/src/server/agent-capabilities.ts +1 -1
  44. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +113 -7
  45. package/corpus/core/src/server/agent-chat/context-tools.ts +2 -0
  46. package/corpus/core/src/server/agent-chat/plugin-options.ts +34 -1
  47. package/corpus/core/src/server/agent-chat/prompt-resources.ts +1 -1
  48. package/corpus/core/src/server/agent-chat-plugin.ts +91 -59
  49. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
  50. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
  51. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  52. package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  53. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
  54. package/corpus/core/src/vite/client.ts +1 -0
  55. package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +5 -4
  56. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +6 -4
  57. package/corpus/templates/analytics/AGENTS.md +7 -4
  58. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -3
  59. package/corpus/templates/analytics/changelog/2026-07-30-delegated-analytics-questions-now-return-a-useful-current-me.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-30-scheduled-dashboard-emails-complete-every-panel-without-pool.md +6 -0
  61. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +28 -1
  62. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +18 -11
  63. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +4 -4
  64. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -5
  65. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +6 -2
  66. package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +11 -4
  67. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +49 -31
  68. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -8
  69. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -3
  70. package/corpus/templates/analytics/server/lib/production-serverless-runtime.ts +11 -0
  71. package/corpus/templates/analytics/server/lib/real-data-actions.ts +10 -0
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -2
  73. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +2 -1
  74. package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +2 -1
  75. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -12
  76. package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +54 -6
  77. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +20 -9
  78. package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +33 -1
  79. package/corpus/templates/brain/AGENTS.md +3 -1
  80. package/corpus/templates/brain/README.md +4 -1
  81. package/corpus/templates/brain/actions/_schemas.ts +30 -8
  82. package/corpus/templates/brain/actions/ask-brain.ts +124 -14
  83. package/corpus/templates/brain/actions/create-source.ts +18 -2
  84. package/corpus/templates/brain/actions/enqueue-captures-distillation.ts +8 -123
  85. package/corpus/templates/brain/actions/enqueue-distillation.ts +6 -127
  86. package/corpus/templates/brain/actions/import-capture.ts +17 -5
  87. package/corpus/templates/brain/actions/import-transcript.ts +17 -5
  88. package/corpus/templates/brain/actions/update-source.ts +25 -5
  89. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
  90. package/corpus/templates/brain/changelog/2026-07-30-blessed-resources-can-feed-cited-company-answers.md +6 -0
  91. package/corpus/templates/brain/changelog/2026-07-30-the-left-sidebar-no-longer-shows-a-blank-organization-placeh.md +6 -0
  92. package/corpus/templates/brain/server/lib/brain.ts +49 -19
  93. package/corpus/templates/brain/server/lib/distillation-queue.ts +147 -0
  94. package/corpus/templates/brain/server/lib/source-policy.ts +264 -0
  95. package/corpus/templates/brain/server/plugins/agent-chat.ts +3 -2
  96. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +116 -19
  97. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +51 -0
  98. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  99. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +3 -1
  100. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +6 -0
  101. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +72 -8
  102. package/corpus/templates/content/changelog/2026-07-30-the-slash-command-hint-now-stays-on-only-the-active-empty-li.md +6 -0
  103. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +506 -11
  104. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +148 -17
  105. package/corpus/templates/design/app/components/design/canvas-interactions/design-canvas-interactions.ts +357 -0
  106. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -466
  107. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +15 -311
  108. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +19 -997
  109. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +8 -1216
  110. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +31 -38
  111. package/corpus/templates/design/package.json +0 -1
  112. package/corpus/templates/dispatch/AGENTS.md +8 -0
  113. package/corpus/templates/dispatch/DEVELOPING.md +29 -0
  114. package/corpus/templates/dispatch/actions/view-screen.ts +34 -2
  115. package/corpus/templates/dispatch/app/i18n-data.ts +457 -0
  116. package/corpus/templates/dispatch/changelog/2026-07-30-delegated-dispatch-work-continues-reliably-in-the-background.md +6 -0
  117. package/corpus/templates/dispatch/changelog/2026-07-30-find-failed-runs-across-apps.md +6 -0
  118. package/corpus/templates/dispatch/netlify.toml +3 -0
  119. package/corpus/templates/plan/actions/get-visual-plan.ts +10 -6
  120. package/corpus/templates/plan/actions/patch-visual-plan-source.ts +117 -1
  121. package/corpus/templates/plan/actions/update-visual-plan.ts +56 -14
  122. package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
  123. package/corpus/templates/plan/changelog/2026-07-30-plan-agents-recover-with-targeted-writes.md +6 -0
  124. package/corpus/templates/plan/shared/plan-content.ts +35 -0
  125. package/corpus/templates/slides/.agents/skills/analytics-data-for-decks/SKILL.md +8 -12
  126. package/corpus/templates/slides/AGENTS.md +4 -4
  127. package/corpus/templates/slides/actions/create-deck.ts +1 -1
  128. package/corpus/templates/slides/actions/duplicate-deck.ts +19 -4
  129. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -10
  130. package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +12 -1
  131. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +30 -13
  132. package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +35 -0
  133. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +63 -56
  134. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -76
  135. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1110 -292
  136. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +53 -0
  137. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +235 -18
  138. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +4 -1
  139. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +11 -0
  140. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +129 -0
  141. package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +70 -0
  142. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +519 -65
  143. package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +36 -426
  144. package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +19 -458
  145. package/corpus/templates/slides/app/context/DeckContext.tsx +179 -72
  146. package/corpus/templates/slides/app/global.css +105 -0
  147. package/corpus/templates/slides/app/i18n/en-US.ts +8 -0
  148. package/corpus/templates/slides/app/lib/deck-editor-loading.ts +26 -0
  149. package/corpus/templates/slides/app/lib/mermaid-blocks.ts +27 -0
  150. package/corpus/templates/slides/app/pages/DeckEditor.tsx +71 -38
  151. package/corpus/templates/slides/app/pages/Index.tsx +98 -41
  152. package/corpus/templates/slides/changelog/2026-07-30-deck-loading-no-longer-flashes-an-unavailable-message.md +6 -0
  153. package/corpus/templates/slides/changelog/2026-07-30-duplicating-a-deck-from-the-deck-list-now-opens-the-copy-imm.md +6 -0
  154. package/corpus/templates/slides/changelog/2026-07-30-layout-overflow-warning-is-readable-and-dismissible.md +6 -0
  155. package/corpus/templates/slides/changelog/2026-07-30-new-decks-now-start-with-an-empty-slide-list-when-no-slides-.md +6 -0
  156. package/corpus/templates/slides/changelog/2026-07-30-shared-canvas-annotations.md +6 -0
  157. package/corpus/templates/slides/changelog/2026-07-30-slide-objects-can-be-moved-as-a-group-copied-and-pasted-with.md +6 -0
  158. package/corpus/templates/slides/changelog/2026-07-30-slide-text-editing-and-object-controls-now-match-google-slides.md +6 -0
  159. package/corpus/templates/slides/changelog/2026-07-30-style-panel-now-scrolls-with-speaker-notes-open-shows-the-sl.md +6 -0
  160. package/corpus/templates/slides/changelog/2026-07-30-top-toolbar-controls-now-use-consistent-sizing-with-undo-and.md +6 -0
  161. package/corpus/templates/slides/netlify.toml +3 -1
  162. package/corpus/templates/slides/package.json +0 -1
  163. package/corpus/templates/slides/server/lib/chat-attachments.ts +18 -0
  164. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
  165. package/corpus/templates/slides/vite.config.ts +0 -1
  166. package/corpus/toolkit/CHANGELOG.md +13 -0
  167. package/corpus/toolkit/README.md +19 -0
  168. package/corpus/toolkit/agent-native.eject.json +38 -0
  169. package/corpus/toolkit/package.json +29 -1
  170. package/corpus/toolkit/src/canvas-annotations/CanvasCommentPins.tsx +861 -0
  171. package/corpus/toolkit/src/canvas-annotations/DrawOverlay.tsx +1233 -0
  172. package/corpus/toolkit/src/canvas-annotations/index.ts +15 -0
  173. package/corpus/toolkit/src/canvas-annotations/types.ts +14 -0
  174. package/corpus/toolkit/src/canvas-interactions/canvas-interactions.ts +933 -0
  175. package/corpus/toolkit/src/canvas-interactions/index.ts +67 -0
  176. package/corpus/toolkit/src/design-tweaks/scrub-input-utils.ts +311 -0
  177. package/corpus/toolkit/src/design-tweaks/scrub-input.tsx +491 -0
  178. package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +26 -171
  179. package/corpus/toolkit/src/index.ts +1 -0
  180. package/dist/a2a/client.d.ts +23 -0
  181. package/dist/a2a/client.d.ts.map +1 -1
  182. package/dist/a2a/client.js +309 -44
  183. package/dist/a2a/client.js.map +1 -1
  184. package/dist/a2a/correlation.d.ts +1 -0
  185. package/dist/a2a/correlation.d.ts.map +1 -1
  186. package/dist/a2a/correlation.js +27 -0
  187. package/dist/a2a/correlation.js.map +1 -1
  188. package/dist/a2a/invoke.d.ts.map +1 -1
  189. package/dist/a2a/invoke.js +4 -2
  190. package/dist/a2a/invoke.js.map +1 -1
  191. package/dist/a2a/types.d.ts +4 -0
  192. package/dist/a2a/types.d.ts.map +1 -1
  193. package/dist/a2a/types.js.map +1 -1
  194. package/dist/action.d.ts +3 -0
  195. package/dist/action.d.ts.map +1 -1
  196. package/dist/action.js.map +1 -1
  197. package/dist/agent/action-continuation-guidance.d.ts.map +1 -1
  198. package/dist/agent/action-continuation-guidance.js +2 -1
  199. package/dist/agent/action-continuation-guidance.js.map +1 -1
  200. package/dist/agent/engine/registry.d.ts.map +1 -1
  201. package/dist/agent/engine/registry.js +82 -8
  202. package/dist/agent/engine/registry.js.map +1 -1
  203. package/dist/agent/production-agent.d.ts +40 -1
  204. package/dist/agent/production-agent.d.ts.map +1 -1
  205. package/dist/agent/production-agent.js +113 -15
  206. package/dist/agent/production-agent.js.map +1 -1
  207. package/dist/agent/run-loop-with-resume.d.ts +21 -0
  208. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  209. package/dist/agent/run-loop-with-resume.js +163 -4
  210. package/dist/agent/run-loop-with-resume.js.map +1 -1
  211. package/dist/agent/run-manager.d.ts +16 -9
  212. package/dist/agent/run-manager.d.ts.map +1 -1
  213. package/dist/agent/run-manager.js +93 -42
  214. package/dist/agent/run-manager.js.map +1 -1
  215. package/dist/agent/thread-debug-history.d.ts +10 -0
  216. package/dist/agent/thread-debug-history.d.ts.map +1 -0
  217. package/dist/agent/thread-debug-history.js +68 -0
  218. package/dist/agent/thread-debug-history.js.map +1 -0
  219. package/dist/agent/types.d.ts +3 -1
  220. package/dist/agent/types.d.ts.map +1 -1
  221. package/dist/agent/types.js.map +1 -1
  222. package/dist/cli/create.d.ts.map +1 -1
  223. package/dist/cli/create.js +6 -1
  224. package/dist/cli/create.js.map +1 -1
  225. package/dist/cli/templates-meta.js +2 -2
  226. package/dist/cli/templates-meta.js.map +1 -1
  227. package/dist/client/agent-chat-adapter.d.ts +1 -1
  228. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  229. package/dist/client/agent-chat-adapter.js +76 -13
  230. package/dist/client/agent-chat-adapter.js.map +1 -1
  231. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  232. package/dist/client/resources/McpIntegrationDialog.js +1 -1
  233. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  234. package/dist/client/sse-event-processor.d.ts.map +1 -1
  235. package/dist/client/sse-event-processor.js +12 -3
  236. package/dist/client/sse-event-processor.js.map +1 -1
  237. package/dist/collab/routes.d.ts +1 -1
  238. package/dist/db/client.d.ts.map +1 -1
  239. package/dist/db/client.js +48 -17
  240. package/dist/db/client.js.map +1 -1
  241. package/dist/guards/no-env-credentials.d.ts.map +1 -1
  242. package/dist/guards/no-env-credentials.js +2 -0
  243. package/dist/guards/no-env-credentials.js.map +1 -1
  244. package/dist/integrations/adapters/slack.js +7 -3
  245. package/dist/integrations/adapters/slack.js.map +1 -1
  246. package/dist/localization/default-messages.d.ts +43 -0
  247. package/dist/localization/default-messages.d.ts.map +1 -1
  248. package/dist/localization/default-messages.js +43 -0
  249. package/dist/localization/default-messages.js.map +1 -1
  250. package/dist/observability/traces.d.ts +3 -1
  251. package/dist/observability/traces.d.ts.map +1 -1
  252. package/dist/observability/traces.js +73 -4
  253. package/dist/observability/traces.js.map +1 -1
  254. package/dist/progress/routes.d.ts +1 -1
  255. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  256. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  257. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  258. package/dist/scripts/call-agent.d.ts.map +1 -1
  259. package/dist/scripts/call-agent.js +237 -12
  260. package/dist/scripts/call-agent.js.map +1 -1
  261. package/dist/scripts/describe-workspace-apps.js +2 -2
  262. package/dist/scripts/describe-workspace-apps.js.map +1 -1
  263. package/dist/secrets/crypto.d.ts +36 -21
  264. package/dist/secrets/crypto.d.ts.map +1 -1
  265. package/dist/secrets/crypto.js +134 -33
  266. package/dist/secrets/crypto.js.map +1 -1
  267. package/dist/secrets/storage.d.ts +6 -6
  268. package/dist/secrets/storage.d.ts.map +1 -1
  269. package/dist/secrets/storage.js +43 -26
  270. package/dist/secrets/storage.js.map +1 -1
  271. package/dist/server/agent-capabilities.js +1 -1
  272. package/dist/server/agent-capabilities.js.map +1 -1
  273. package/dist/server/agent-chat/action-filters-a2a.d.ts +17 -3
  274. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  275. package/dist/server/agent-chat/action-filters-a2a.js +94 -3
  276. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  277. package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
  278. package/dist/server/agent-chat/context-tools.js +2 -0
  279. package/dist/server/agent-chat/context-tools.js.map +1 -1
  280. package/dist/server/agent-chat/plugin-options.d.ts +23 -0
  281. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  282. package/dist/server/agent-chat/plugin-options.js +4 -1
  283. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  284. package/dist/server/agent-chat/prompt-resources.js +1 -1
  285. package/dist/server/agent-chat/prompt-resources.js.map +1 -1
  286. package/dist/server/agent-chat-plugin.d.ts +2 -1
  287. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  288. package/dist/server/agent-chat-plugin.js +79 -56
  289. package/dist/server/agent-chat-plugin.js.map +1 -1
  290. package/dist/server/realtime-token.d.ts +1 -1
  291. package/dist/templates/default/.agents/skills/secrets/SKILL.md +19 -7
  292. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
  293. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  294. package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  295. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
  296. package/dist/vite/client.d.ts.map +1 -1
  297. package/dist/vite/client.js +1 -0
  298. package/dist/vite/client.js.map +1 -1
  299. package/docs/content/agent-native-toolkit.mdx +13 -6
  300. package/docs/content/cloneable-saas.mdx +5 -5
  301. package/docs/content/creating-templates.mdx +9 -0
  302. package/docs/content/deployment.mdx +18 -12
  303. package/docs/content/doctor.mdx +8 -1
  304. package/docs/content/drop-in-agent.mdx +1 -1
  305. package/docs/content/extensions.mdx +18 -18
  306. package/docs/content/faq.mdx +4 -4
  307. package/docs/content/key-concepts.mdx +30 -21
  308. package/docs/content/security.mdx +2 -2
  309. package/docs/content/template-brain-developers.mdx +4 -1
  310. package/docs/content/template-mail-developers.mdx +4 -1
  311. package/docs/content/what-is-agent-native.mdx +6 -6
  312. package/package.json +3 -3
  313. package/src/a2a/client.ts +449 -66
  314. package/src/a2a/correlation.ts +31 -0
  315. package/src/a2a/invoke.ts +4 -2
  316. package/src/a2a/types.ts +4 -0
  317. package/src/action.ts +3 -0
  318. package/src/agent/action-continuation-guidance.ts +2 -1
  319. package/src/agent/engine/registry.ts +98 -6
  320. package/src/agent/production-agent.ts +151 -18
  321. package/src/agent/run-loop-with-resume.ts +190 -3
  322. package/src/agent/run-manager.ts +117 -41
  323. package/src/agent/thread-debug-history.ts +86 -0
  324. package/src/agent/types.ts +3 -1
  325. package/src/cli/create.ts +11 -1
  326. package/src/cli/templates-meta.ts +2 -2
  327. package/src/client/agent-chat-adapter.ts +91 -13
  328. package/src/client/resources/McpIntegrationDialog.tsx +11 -9
  329. package/src/client/sse-event-processor.ts +17 -3
  330. package/src/db/client.ts +57 -17
  331. package/src/guards/no-env-credentials.ts +2 -0
  332. package/src/integrations/adapters/slack.ts +8 -3
  333. package/src/localization/default-messages.ts +44 -0
  334. package/src/observability/traces.ts +78 -5
  335. package/src/scripts/call-agent.ts +294 -11
  336. package/src/scripts/describe-workspace-apps.ts +2 -2
  337. package/src/secrets/crypto.ts +156 -35
  338. package/src/secrets/storage.ts +64 -26
  339. package/src/server/agent-capabilities.ts +1 -1
  340. package/src/server/agent-chat/action-filters-a2a.ts +113 -7
  341. package/src/server/agent-chat/context-tools.ts +2 -0
  342. package/src/server/agent-chat/plugin-options.ts +34 -1
  343. package/src/server/agent-chat/prompt-resources.ts +1 -1
  344. package/src/server/agent-chat-plugin.ts +91 -59
  345. package/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
  346. package/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
  347. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  348. package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  349. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
  350. package/src/vite/client.ts +1 -0
package/src/a2a/client.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+
1
3
  import * as jose from "jose";
2
4
 
3
5
  import { ssrfSafeFetch } from "../extensions/url-safety.js";
@@ -59,6 +61,10 @@ import type {
59
61
  } from "./types.js";
60
62
 
61
63
  const DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS = 15_000;
64
+ const DEFAULT_A2A_DISCOVERY_TIMEOUT_MS = 3_000;
65
+ const MAX_A2A_RPC_ATTEMPTS = 3;
66
+ const A2A_RPC_RETRY_BASE_MS = 100;
67
+ export const MAX_A2A_CALLER_RESPONSE_CHARS = 32_768;
62
68
 
63
69
  export class A2ATaskTimeoutError extends Error {
64
70
  readonly taskId: string;
@@ -79,6 +85,54 @@ export class A2ATaskTimeoutError extends Error {
79
85
  }
80
86
  }
81
87
 
88
+ export type A2ATerminalTaskErrorState =
89
+ | "failed"
90
+ | "canceled"
91
+ | "input-required"
92
+ | "completed";
93
+
94
+ /**
95
+ * Preserves a receiver's terminal protocol state across the text-oriented
96
+ * callAgent convenience boundary. Callers can distinguish a real answer from
97
+ * failure, cancellation, approval/input, and an invalid empty completion
98
+ * without parsing English prose.
99
+ */
100
+ export class A2ATaskTerminalError extends Error {
101
+ readonly taskId: string;
102
+ readonly state: A2ATerminalTaskErrorState;
103
+ readonly responseText: string;
104
+ readonly errorCode: string;
105
+ readonly task: Task;
106
+
107
+ constructor(
108
+ task: Task,
109
+ state: A2ATerminalTaskErrorState,
110
+ responseText: string,
111
+ errorCode = `a2a_task_${state.replace(/-/g, "_")}`,
112
+ ) {
113
+ const detail = responseText.trim()
114
+ ? `: ${boundA2ACallerResponseText(responseText).trim()}`
115
+ : "";
116
+ super(`A2A task ${task.id} ended ${state}${detail}`);
117
+ this.name = "A2ATaskTerminalError";
118
+ this.taskId = task.id;
119
+ this.state = state;
120
+ this.responseText = boundA2ACallerResponseText(responseText);
121
+ this.errorCode = errorCode;
122
+ this.task = task;
123
+ }
124
+ }
125
+
126
+ /** Keep both the answer lead and artifact/source tail within caller context. */
127
+ export function boundA2ACallerResponseText(value: string): string {
128
+ if (value.length <= MAX_A2A_CALLER_RESPONSE_CHARS) return value;
129
+ const marker =
130
+ "\n\n...[A2A response compacted at the caller boundary; use the receiving app or returned artifact links for full details]...\n\n";
131
+ const tailChars = 8_192;
132
+ const headChars = MAX_A2A_CALLER_RESPONSE_CHARS - tailChars - marker.length;
133
+ return value.slice(0, headChars) + marker + value.slice(-tailChars);
134
+ }
135
+
82
136
  /**
83
137
  * Sign a JWT for A2A cross-app identity verification.
84
138
  *
@@ -217,7 +271,7 @@ export class A2AClient {
217
271
  private async rpc(
218
272
  method: string,
219
273
  params: Record<string, unknown>,
220
- options?: { requestTimeoutMs?: number },
274
+ options?: { requestTimeoutMs?: number; deadlineMs?: number },
221
275
  ): Promise<JsonRpcResponse> {
222
276
  const body: JsonRpcRequest = {
223
277
  jsonrpc: "2.0",
@@ -226,51 +280,114 @@ export class A2AClient {
226
280
  params,
227
281
  };
228
282
 
229
- await this.ensureEndpointCandidates();
283
+ const discoveryTimeoutMs = resolveA2ADiscoveryTimeoutMs(
284
+ options?.requestTimeoutMs ?? this.requestTimeoutMs,
285
+ options?.deadlineMs,
286
+ );
287
+ await this.ensureEndpointCandidates(discoveryTimeoutMs);
230
288
  let lastError: Error | null = null;
231
289
 
232
290
  for (const url of this.endpointCandidates) {
233
291
  for (let i = 0; i < this.apiKeyAttempts.length; i++) {
234
- console.log(`[A2A Client] POST ${url} method=${method}`);
235
- const startTime = Date.now();
236
- const res = await this.postJson(
237
- url,
238
- body,
292
+ const maxAttempts = isRetrySafeA2ARpc(
293
+ method,
294
+ params,
239
295
  this.apiKeyAttempts[i],
240
- options?.requestTimeoutMs,
241
- );
242
- console.log(
243
- `[A2A Client] Response: ${res.status} in ${Date.now() - startTime}ms`,
244
- );
296
+ )
297
+ ? MAX_A2A_RPC_ATTEMPTS
298
+ : 1;
299
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
300
+ const requestTimeoutMs = resolveA2ARequestTimeoutMs(
301
+ options?.requestTimeoutMs ?? this.requestTimeoutMs,
302
+ options?.deadlineMs,
303
+ );
304
+ if (requestTimeoutMs !== undefined && requestTimeoutMs <= 0) {
305
+ throw new Error("A2A request deadline exceeded");
306
+ }
307
+ console.log(
308
+ `[A2A Client] POST ${url} method=${method} attempt=${attempt}/${maxAttempts}`,
309
+ );
310
+ const startTime = Date.now();
311
+ let res: Response;
312
+ try {
313
+ res = await this.postJson(
314
+ url,
315
+ body,
316
+ this.apiKeyAttempts[i],
317
+ requestTimeoutMs,
318
+ );
319
+ } catch (error) {
320
+ lastError =
321
+ error instanceof Error ? error : new Error(String(error));
322
+ if (attempt < maxAttempts) {
323
+ await waitForA2ARetry(
324
+ attempt,
325
+ null,
326
+ remainingA2ADeadlineMs(options?.deadlineMs),
327
+ );
328
+ continue;
329
+ }
330
+ break;
331
+ }
332
+ console.log(
333
+ `[A2A Client] Response: ${res.status} in ${Date.now() - startTime}ms`,
334
+ );
335
+
336
+ if (res.ok) {
337
+ const text = await res.text();
338
+ if (
339
+ i < this.apiKeyAttempts.length - 1 &&
340
+ isA2AAuthRejectionResponse(res.status, text)
341
+ ) {
342
+ lastError = new Error(
343
+ `A2A request failed (${res.status}): ${text}`,
344
+ );
345
+ break;
346
+ }
347
+ try {
348
+ const parsed = JSON.parse(text) as JsonRpcResponse;
349
+ this.endpointCandidates = [url];
350
+ this.markApiKeySucceeded(this.apiKeyAttempts[i]);
351
+ return parsed;
352
+ } catch (error) {
353
+ lastError = new Error(
354
+ `A2A response was not valid JSON: ${
355
+ error instanceof Error ? error.message : String(error)
356
+ }`,
357
+ );
358
+ if (attempt < maxAttempts) {
359
+ await waitForA2ARetry(
360
+ attempt,
361
+ null,
362
+ remainingA2ADeadlineMs(options?.deadlineMs),
363
+ );
364
+ continue;
365
+ }
366
+ break;
367
+ }
368
+ }
245
369
 
246
- if (res.ok) {
247
370
  const text = await res.text();
371
+ lastError = new Error(`A2A request failed (${res.status}): ${text}`);
248
372
  if (
249
373
  i < this.apiKeyAttempts.length - 1 &&
250
374
  isA2AAuthRejectionResponse(res.status, text)
251
375
  ) {
252
- lastError = new Error(
253
- `A2A request failed (${res.status}): ${text}`,
376
+ break;
377
+ }
378
+ if (isRetryableA2AStatus(res.status) && attempt < maxAttempts) {
379
+ await waitForA2ARetry(
380
+ attempt,
381
+ res.headers.get("retry-after"),
382
+ remainingA2ADeadlineMs(options?.deadlineMs),
254
383
  );
255
384
  continue;
256
385
  }
257
- this.endpointCandidates = [url];
258
- this.markApiKeySucceeded(this.apiKeyAttempts[i]);
259
- return JSON.parse(text) as JsonRpcResponse;
260
- }
261
-
262
- const text = await res.text();
263
- lastError = new Error(`A2A request failed (${res.status}): ${text}`);
264
- if (
265
- i < this.apiKeyAttempts.length - 1 &&
266
- isA2AAuthRejectionResponse(res.status, text)
267
- ) {
268
- continue;
269
- }
270
- if (!shouldTryNextEndpoint(res.status)) {
271
- throw lastError;
386
+ if (!shouldTryNextEndpoint(res.status)) {
387
+ throw lastError;
388
+ }
389
+ break;
272
390
  }
273
- break;
274
391
  }
275
392
  }
276
393
 
@@ -312,6 +429,10 @@ export class A2AClient {
312
429
  metadata?: Record<string, unknown>;
313
430
  idempotencyKey?: string;
314
431
  approvedActions?: A2AApprovedAction[];
432
+ /** Per-request transport cap, bounded by deadlineMs when both exist. */
433
+ requestTimeoutMs?: number;
434
+ /** Absolute end-to-end deadline shared with async polling. */
435
+ deadlineMs?: number;
315
436
  /**
316
437
  * If true, ask the server to return the task immediately in `working`
317
438
  * state and process the handler in the background. The caller should
@@ -323,16 +444,25 @@ export class A2AClient {
323
444
  async?: boolean;
324
445
  },
325
446
  ): Promise<Task> {
326
- const response = await this.rpc("message/send", {
327
- message,
328
- contextId: opts?.contextId,
329
- metadata: opts?.metadata,
330
- ...(opts?.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
331
- ...(opts?.approvedActions?.length
332
- ? { approvedActions: opts.approvedActions }
333
- : {}),
334
- ...(opts?.async ? { async: true } : {}),
335
- });
447
+ const response = await this.rpc(
448
+ "message/send",
449
+ {
450
+ message,
451
+ contextId: opts?.contextId,
452
+ metadata: opts?.metadata,
453
+ ...(opts?.idempotencyKey
454
+ ? { idempotencyKey: opts.idempotencyKey }
455
+ : {}),
456
+ ...(opts?.approvedActions?.length
457
+ ? { approvedActions: opts.approvedActions }
458
+ : {}),
459
+ ...(opts?.async ? { async: true } : {}),
460
+ },
461
+ {
462
+ requestTimeoutMs: opts?.requestTimeoutMs,
463
+ deadlineMs: opts?.deadlineMs,
464
+ },
465
+ );
336
466
 
337
467
  if (response.error) {
338
468
  throw new Error(
@@ -348,12 +478,15 @@ export class A2AClient {
348
478
  */
349
479
  async getTask(
350
480
  taskId: string,
351
- opts?: { requestTimeoutMs?: number },
481
+ opts?: { requestTimeoutMs?: number; deadlineMs?: number },
352
482
  ): Promise<Task> {
353
483
  const response = await this.rpc(
354
484
  "tasks/get",
355
485
  { id: taskId },
356
- { requestTimeoutMs: opts?.requestTimeoutMs },
486
+ {
487
+ requestTimeoutMs: opts?.requestTimeoutMs,
488
+ deadlineMs: opts?.deadlineMs,
489
+ },
357
490
  );
358
491
  if (response.error) {
359
492
  throw new Error(
@@ -411,6 +544,8 @@ export class A2AClient {
411
544
  onUpdate?: (task: Task) => void;
412
545
  },
413
546
  ): Promise<Task> {
547
+ const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
548
+ const deadlineMs = Date.now() + timeoutMs;
414
549
  const submitted = await this.send(message, {
415
550
  contextId: opts?.contextId,
416
551
  metadata: opts?.metadata,
@@ -419,9 +554,14 @@ export class A2AClient {
419
554
  ? { approvedActions: opts.approvedActions }
420
555
  : {}),
421
556
  async: true,
557
+ requestTimeoutMs: Math.min(
558
+ this.requestTimeoutMs ?? DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS,
559
+ Math.max(1, deadlineMs - Date.now()),
560
+ ),
561
+ deadlineMs,
422
562
  });
423
563
 
424
- return this.pollTask(submitted, opts);
564
+ return this.pollTask(submitted, { ...opts, timeoutMs, deadlineMs });
425
565
  }
426
566
 
427
567
  /**
@@ -440,9 +580,17 @@ export class A2AClient {
440
580
  onUpdate?: (task: Task) => void;
441
581
  },
442
582
  ): Promise<Task> {
443
- const current = await this.getTask(taskId);
444
- opts?.onUpdate?.(current);
445
- return this.pollTask(current, opts);
583
+ const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
584
+ const deadlineMs = Date.now() + timeoutMs;
585
+ const current = await this.getTask(taskId, {
586
+ requestTimeoutMs: Math.min(
587
+ this.requestTimeoutMs ?? DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS,
588
+ Math.max(1, deadlineMs - Date.now()),
589
+ ),
590
+ deadlineMs,
591
+ });
592
+ safelyNotifyA2AUpdate(opts?.onUpdate, current);
593
+ return this.pollTask(current, { ...opts, timeoutMs, deadlineMs });
446
594
  }
447
595
 
448
596
  private async pollTask(
@@ -451,6 +599,7 @@ export class A2AClient {
451
599
  timeoutMs?: number;
452
600
  pollIntervalMs?: number;
453
601
  onUpdate?: (task: Task) => void;
602
+ deadlineMs?: number;
454
603
  },
455
604
  ): Promise<Task> {
456
605
  const terminalStates = new Set([
@@ -463,7 +612,7 @@ export class A2AClient {
463
612
 
464
613
  const timeoutMs = opts?.timeoutMs ?? 5 * 60_000;
465
614
  const pollMs = opts?.pollIntervalMs ?? 2_000;
466
- const deadline = Date.now() + timeoutMs;
615
+ const deadline = opts?.deadlineMs ?? Date.now() + timeoutMs;
467
616
 
468
617
  let current = submitted;
469
618
  while (Date.now() < deadline) {
@@ -477,11 +626,15 @@ export class A2AClient {
477
626
  this.requestTimeoutMs ?? DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS,
478
627
  remainingMs,
479
628
  ),
629
+ deadlineMs: deadline,
480
630
  });
481
- opts?.onUpdate?.(current);
482
- } catch {
483
- // Transient fetch failure keep polling until the deadline.
484
- continue;
631
+ safelyNotifyA2AUpdate(opts?.onUpdate, current);
632
+ } catch (error) {
633
+ // Retry only transport/gateway interruptions. Authentication,
634
+ // task-not-found, invalid params, and other protocol failures are
635
+ // permanent for this poll and must surface immediately.
636
+ if (isRetryableA2APollError(error)) continue;
637
+ throw error;
485
638
  }
486
639
  if (terminalStates.has(current.status.state)) return current;
487
640
  }
@@ -563,7 +716,7 @@ export class A2AClient {
563
716
  }
564
717
  }
565
718
 
566
- private async ensureEndpointCandidates(): Promise<void> {
719
+ private async ensureEndpointCandidates(timeoutMs?: number): Promise<void> {
567
720
  if (this.endpointResolved) return;
568
721
  this.endpointResolved = true;
569
722
 
@@ -571,7 +724,7 @@ export class A2AClient {
571
724
  addDefaultEndpointCandidates(candidates, this.baseUrl);
572
725
 
573
726
  try {
574
- const card = await this.getAgentCard();
727
+ const card = await this.getAgentCard({ timeoutMs });
575
728
  const cardUrl = normalizeUrl(card.url, this.baseUrl);
576
729
  if (cardUrl) {
577
730
  const explicitEndpoint = splitExplicitA2AEndpoint(cardUrl);
@@ -669,6 +822,137 @@ function shouldTryNextEndpoint(status: number): boolean {
669
822
  return status === 404 || status === 405;
670
823
  }
671
824
 
825
+ function isRetrySafeA2ARpc(
826
+ method: string,
827
+ params: Record<string, unknown>,
828
+ apiKey: string | undefined,
829
+ ): boolean {
830
+ if (method === "message/send") {
831
+ return (
832
+ params.async === true &&
833
+ typeof params.idempotencyKey === "string" &&
834
+ params.idempotencyKey.trim().length > 0 &&
835
+ hasJwtSubject(apiKey)
836
+ );
837
+ }
838
+ return method === "tasks/get" || method === "actions/invoke";
839
+ }
840
+
841
+ function isRetryableA2AStatus(status: number): boolean {
842
+ return (
843
+ status === 408 ||
844
+ status === 425 ||
845
+ status === 429 ||
846
+ status === 500 ||
847
+ status === 502 ||
848
+ status === 503 ||
849
+ status === 504
850
+ );
851
+ }
852
+
853
+ async function waitForA2ARetry(
854
+ attempt: number,
855
+ retryAfter: string | null = null,
856
+ maxWaitMs?: number,
857
+ ): Promise<void> {
858
+ const retryAfterSeconds = retryAfter ? Number(retryAfter) : NaN;
859
+ const retryAfterMs = Number.isFinite(retryAfterSeconds)
860
+ ? Math.max(0, Math.min(2_000, retryAfterSeconds * 1_000))
861
+ : 0;
862
+ const backoffMs = Math.max(
863
+ retryAfterMs,
864
+ Math.min(1_000, A2A_RPC_RETRY_BASE_MS * 2 ** (attempt - 1)),
865
+ );
866
+ const boundedWaitMs =
867
+ maxWaitMs === undefined ? backoffMs : Math.min(backoffMs, maxWaitMs);
868
+ if (boundedWaitMs <= 0) return;
869
+ await new Promise((resolve) => setTimeout(resolve, boundedWaitMs));
870
+ }
871
+
872
+ function remainingA2ADeadlineMs(
873
+ deadlineMs: number | undefined,
874
+ ): number | undefined {
875
+ return deadlineMs === undefined
876
+ ? undefined
877
+ : Math.max(0, deadlineMs - Date.now());
878
+ }
879
+
880
+ function resolveA2ARequestTimeoutMs(
881
+ configuredMs: number | undefined,
882
+ deadlineMs: number | undefined,
883
+ ): number | undefined {
884
+ const remainingMs = remainingA2ADeadlineMs(deadlineMs);
885
+ if (remainingMs === undefined) return configuredMs;
886
+ return configuredMs === undefined
887
+ ? remainingMs
888
+ : Math.min(configuredMs, remainingMs);
889
+ }
890
+
891
+ /**
892
+ * Agent-card discovery is only a hint because first-party endpoints have
893
+ * conventional paths. Preserve most of a bounded call's deadline for the
894
+ * actual message instead of letting a cold or unavailable card consume it.
895
+ */
896
+ function resolveA2ADiscoveryTimeoutMs(
897
+ configuredMs: number | undefined,
898
+ deadlineMs: number | undefined,
899
+ ): number {
900
+ const transportBudgetMs = resolveA2ARequestTimeoutMs(
901
+ configuredMs,
902
+ deadlineMs,
903
+ );
904
+ const remainingMs = remainingA2ADeadlineMs(deadlineMs);
905
+ const deadlineShareMs =
906
+ remainingMs === undefined
907
+ ? DEFAULT_A2A_DISCOVERY_TIMEOUT_MS
908
+ : Math.max(1, Math.floor(remainingMs / 4));
909
+ return Math.min(
910
+ DEFAULT_A2A_DISCOVERY_TIMEOUT_MS,
911
+ transportBudgetMs ?? DEFAULT_A2A_DISCOVERY_TIMEOUT_MS,
912
+ deadlineShareMs,
913
+ );
914
+ }
915
+
916
+ function hasJwtSubject(apiKey: string | undefined): boolean {
917
+ if (!apiKey) return false;
918
+ const segments = apiKey.split(".");
919
+ if (segments.length !== 3) return false;
920
+ try {
921
+ const payload = JSON.parse(
922
+ Buffer.from(segments[1], "base64url").toString("utf8"),
923
+ ) as Record<string, unknown>;
924
+ return typeof payload.sub === "string" && payload.sub.trim().length > 0;
925
+ } catch {
926
+ return false;
927
+ }
928
+ }
929
+
930
+ function isRetryableA2APollError(error: unknown): boolean {
931
+ const message = error instanceof Error ? error.message : String(error ?? "");
932
+ if (isA2AAuthRejection(error)) return false;
933
+ if (
934
+ /A2A error \(-32\d+\)|A2A request failed \((?:400|403|404|405|409|422)\)/i.test(
935
+ message,
936
+ )
937
+ ) {
938
+ return false;
939
+ }
940
+ return /(?:A2A request failed \((?:408|425|429|5\d\d)\)|fetch failed|network|socket|ECONN|ETIMEDOUT|timeout|deadline|aborted|invalid JSON)/i.test(
941
+ message,
942
+ );
943
+ }
944
+
945
+ function safelyNotifyA2AUpdate(
946
+ callback: ((task: Task) => void) | undefined,
947
+ task: Task,
948
+ ): void {
949
+ try {
950
+ callback?.(task);
951
+ } catch {
952
+ // Presentation/progress callbacks cannot alter task polling.
953
+ }
954
+ }
955
+
672
956
  function unique(values: string[]): string[] {
673
957
  return Array.from(new Set(values));
674
958
  }
@@ -771,6 +1055,11 @@ export async function callAgent(
771
1055
  // detached promises that get killed on Netlify/Vercel. Callers that
772
1056
  // explicitly want a single-shot blocking POST can pass `async: false`.
773
1057
  const useAsync = opts?.async ?? true;
1058
+ // A stable per-call key makes retrying a submission safe even when this
1059
+ // invocation has no durable parent turn id. If the receiver committed the
1060
+ // task but the response was lost, the retry reuses the task.
1061
+ const effectiveIdempotencyKey =
1062
+ opts?.idempotencyKey ?? (opts?.taskId ? undefined : `auto:${randomUUID()}`);
774
1063
  const message: Message = {
775
1064
  role: "user",
776
1065
  parts: [{ type: "text", text }],
@@ -798,7 +1087,7 @@ export async function callAgent(
798
1087
  : await client.sendAndWait(message, {
799
1088
  contextId: opts?.contextId,
800
1089
  metadata,
801
- idempotencyKey: opts?.idempotencyKey,
1090
+ idempotencyKey: effectiveIdempotencyKey,
802
1091
  ...(opts?.approvedActions?.length
803
1092
  ? { approvedActions: opts.approvedActions }
804
1093
  : {}),
@@ -813,30 +1102,67 @@ export async function callAgent(
813
1102
  task = await client.send(message, {
814
1103
  contextId: opts?.contextId,
815
1104
  metadata,
816
- idempotencyKey: opts?.idempotencyKey,
1105
+ idempotencyKey: effectiveIdempotencyKey,
817
1106
  ...(opts?.approvedActions?.length
818
1107
  ? { approvedActions: opts.approvedActions }
819
1108
  : {}),
820
1109
  });
821
1110
  }
822
1111
 
823
- // Extract text from the response
1112
+ // Preserve the receiver's typed terminal state. Failed, canceled, and
1113
+ // input-required tasks are not successful text answers, even when the
1114
+ // receiver attached a friendly explanatory message.
824
1115
  const responseMessage = task.status.message;
825
- if (responseMessage) {
826
- const textParts = responseMessage.parts
827
- .filter((p): p is { type: "text"; text: string } => p.type === "text")
828
- .map((p) => p.text);
829
- return textParts.join("\n");
1116
+ const responseText = responseMessage
1117
+ ? extractMessageText(responseMessage)
1118
+ : "";
1119
+ const state = task.status.state;
1120
+ if (
1121
+ state === "failed" ||
1122
+ state === "canceled" ||
1123
+ state === "input-required"
1124
+ ) {
1125
+ throw new A2ATaskTerminalError(task, state, responseText);
830
1126
  }
831
-
832
- return "";
1127
+ if (state !== "completed") {
1128
+ throw new A2ATaskTerminalError(
1129
+ task,
1130
+ "failed",
1131
+ responseText || `Unexpected terminal state: ${state}`,
1132
+ "a2a_invalid_terminal_state",
1133
+ );
1134
+ }
1135
+ if (responseText.trim()) {
1136
+ if (responseText.length > MAX_A2A_CALLER_RESPONSE_CHARS) {
1137
+ throw new A2ATaskTerminalError(
1138
+ task,
1139
+ "completed",
1140
+ responseText,
1141
+ "a2a_response_too_large",
1142
+ );
1143
+ }
1144
+ return responseText;
1145
+ }
1146
+ const artifactSummary = verifiedArtifactSummary(task);
1147
+ if (artifactSummary) return artifactSummary;
1148
+ throw new A2ATaskTerminalError(
1149
+ task,
1150
+ "completed",
1151
+ "Agent completed without a response or verified artifact.",
1152
+ "empty_agent_response",
1153
+ );
833
1154
  } catch (err) {
834
1155
  if (
835
1156
  opts?.returnRecoverableArtifactsOnTimeout !== false &&
836
1157
  err instanceof A2ATaskTimeoutError
837
1158
  ) {
838
1159
  const recoverableText = extractRecoverableArtifactText(err.lastTask);
839
- if (recoverableText) return recoverableText;
1160
+ if (
1161
+ recoverableText &&
1162
+ recoverableText.length <= MAX_A2A_CALLER_RESPONSE_CHARS
1163
+ ) {
1164
+ return recoverableText;
1165
+ }
840
1166
  }
841
1167
  if (i < apiKeyAttempts.length - 1 && isA2AAuthRejection(err)) {
842
1168
  lastAuthError = err;
@@ -970,8 +1296,65 @@ function extractRecoverableArtifactText(task: Task): string {
970
1296
  }
971
1297
 
972
1298
  function extractMessageText(message: Message): string {
973
- return message.parts
1299
+ return (Array.isArray(message.parts) ? message.parts : [])
974
1300
  .filter((p): p is { type: "text"; text: string } => p.type === "text")
975
1301
  .map((p) => p.text)
976
1302
  .join("\n");
977
1303
  }
1304
+
1305
+ function verifiedArtifactSummary(task: Task): string {
1306
+ const artifacts = Array.isArray(task.artifacts) ? task.artifacts : [];
1307
+ if (artifacts.length === 0) return "";
1308
+ const summaries = artifacts
1309
+ .map((artifact, artifactIndex) => {
1310
+ const parts = Array.isArray(artifact?.parts) ? artifact.parts : [];
1311
+ const usableParts = parts
1312
+ .map((part) => summarizeVerifiedArtifactPart(part))
1313
+ .filter((value): value is string => !!value)
1314
+ .slice(0, 5);
1315
+ if (usableParts.length === 0) return null;
1316
+ const fallbackFileName = parts.find(
1317
+ (part) => part.type === "file" && part.file.name?.trim(),
1318
+ );
1319
+ const label =
1320
+ artifact?.name?.trim() ||
1321
+ (fallbackFileName?.type === "file"
1322
+ ? fallbackFileName.file.name?.trim()
1323
+ : "") ||
1324
+ `Artifact ${artifactIndex + 1}`;
1325
+ return `- ${label}\n${usableParts.map((part) => ` - ${part}`).join("\n")}`;
1326
+ })
1327
+ .filter((value): value is string => !!value)
1328
+ .slice(0, 20);
1329
+ if (summaries.length === 0) return "";
1330
+ return boundA2ACallerResponseText(
1331
+ `The agent completed with ${summaries.length} verified artifact(s):\n` +
1332
+ summaries.join("\n"),
1333
+ );
1334
+ }
1335
+
1336
+ function summarizeVerifiedArtifactPart(part: unknown): string | null {
1337
+ if (!part || typeof part !== "object") return null;
1338
+ const candidate = part as Record<string, unknown>;
1339
+ if (candidate.type === "text" && typeof candidate.text === "string") {
1340
+ const text = candidate.text.trim();
1341
+ return text ? boundA2ACallerResponseText(text).slice(0, 2_000) : null;
1342
+ }
1343
+ if (candidate.type === "file") {
1344
+ const file = candidate.file as Record<string, unknown> | undefined;
1345
+ if (!file) return null;
1346
+ const uri = typeof file.uri === "string" ? file.uri.trim() : "";
1347
+ const name = typeof file.name === "string" ? file.name.trim() : "";
1348
+ const bytes = typeof file.bytes === "string" ? file.bytes : "";
1349
+ if (uri) return name ? `${name}: ${uri}` : uri;
1350
+ if (bytes) return name ? `${name} (inline file)` : "Inline file artifact";
1351
+ return null;
1352
+ }
1353
+ if (candidate.type === "data") {
1354
+ const data = candidate.data;
1355
+ if (!data || typeof data !== "object" || Array.isArray(data)) return null;
1356
+ const serialized = JSON.stringify(data);
1357
+ return serialized !== "{}" ? serialized.slice(0, 2_000) : null;
1358
+ }
1359
+ return null;
1360
+ }