@agent-native/core 0.131.4 → 0.131.6

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 (330) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +69 -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 +14 -10
  7. package/corpus/core/docs/content/docs-components.mdx +491 -0
  8. package/corpus/core/docs/content/doctor.mdx +8 -1
  9. package/corpus/core/docs/content/drop-in-agent.mdx +1 -1
  10. package/corpus/core/docs/content/extensions.mdx +18 -18
  11. package/corpus/core/docs/content/faq.mdx +4 -4
  12. package/corpus/core/docs/content/key-concepts.mdx +30 -21
  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/types.ts +4 -0
  20. package/corpus/core/src/action.ts +3 -0
  21. package/corpus/core/src/agent/engine/registry.ts +45 -5
  22. package/corpus/core/src/agent/production-agent.ts +151 -18
  23. package/corpus/core/src/agent/run-loop-with-resume.ts +190 -3
  24. package/corpus/core/src/agent/run-manager.ts +117 -41
  25. package/corpus/core/src/agent/thread-debug-history.ts +86 -0
  26. package/corpus/core/src/agent/types.ts +3 -1
  27. package/corpus/core/src/cli/create.ts +11 -1
  28. package/corpus/core/src/cli/templates-meta.ts +2 -2
  29. package/corpus/core/src/client/agent-chat-adapter.ts +91 -13
  30. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +11 -9
  31. package/corpus/core/src/client/sse-event-processor.ts +17 -3
  32. package/corpus/core/src/db/client.ts +57 -17
  33. package/corpus/core/src/integrations/adapters/slack.ts +8 -3
  34. package/corpus/core/src/localization/default-messages.ts +44 -0
  35. package/corpus/core/src/observability/traces.ts +78 -5
  36. package/corpus/core/src/scripts/call-agent.ts +290 -9
  37. package/corpus/core/src/scripts/describe-workspace-apps.ts +2 -2
  38. package/corpus/core/src/secrets/crypto.ts +126 -34
  39. package/corpus/core/src/secrets/storage.ts +61 -25
  40. package/corpus/core/src/server/agent-capabilities.ts +1 -1
  41. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +111 -7
  42. package/corpus/core/src/server/agent-chat/context-tools.ts +2 -0
  43. package/corpus/core/src/server/agent-chat/plugin-options.ts +34 -1
  44. package/corpus/core/src/server/agent-chat/prompt-resources.ts +1 -1
  45. package/corpus/core/src/server/agent-chat-plugin.ts +91 -59
  46. package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +14 -7
  47. package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -7
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  49. package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  50. package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -7
  51. package/corpus/core/src/vite/client.ts +1 -0
  52. package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +5 -4
  53. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +6 -4
  54. package/corpus/templates/analytics/AGENTS.md +6 -4
  55. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -3
  56. package/corpus/templates/analytics/changelog/2026-07-30-scheduled-dashboard-emails-complete-every-panel-without-pool.md +6 -0
  57. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +28 -1
  58. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +18 -11
  59. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +4 -4
  60. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -5
  61. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +6 -2
  62. package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +11 -4
  63. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +49 -31
  64. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -8
  65. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -3
  66. package/corpus/templates/analytics/server/lib/production-serverless-runtime.ts +11 -0
  67. package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -2
  68. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +2 -1
  69. package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +2 -1
  70. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -12
  71. package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +54 -6
  72. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +20 -9
  73. package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +33 -1
  74. package/corpus/templates/brain/AGENTS.md +3 -1
  75. package/corpus/templates/brain/README.md +4 -1
  76. package/corpus/templates/brain/actions/_schemas.ts +30 -8
  77. package/corpus/templates/brain/actions/ask-brain.ts +124 -14
  78. package/corpus/templates/brain/actions/create-source.ts +18 -2
  79. package/corpus/templates/brain/actions/enqueue-captures-distillation.ts +8 -123
  80. package/corpus/templates/brain/actions/enqueue-distillation.ts +6 -127
  81. package/corpus/templates/brain/actions/import-capture.ts +17 -5
  82. package/corpus/templates/brain/actions/import-transcript.ts +17 -5
  83. package/corpus/templates/brain/actions/update-source.ts +25 -5
  84. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
  85. package/corpus/templates/brain/changelog/2026-07-30-blessed-resources-can-feed-cited-company-answers.md +6 -0
  86. package/corpus/templates/brain/changelog/2026-07-30-the-left-sidebar-no-longer-shows-a-blank-organization-placeh.md +6 -0
  87. package/corpus/templates/brain/server/lib/brain.ts +49 -19
  88. package/corpus/templates/brain/server/lib/distillation-queue.ts +147 -0
  89. package/corpus/templates/brain/server/lib/source-policy.ts +264 -0
  90. package/corpus/templates/brain/server/plugins/agent-chat.ts +3 -2
  91. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +116 -19
  92. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +51 -0
  93. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  94. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +3 -1
  95. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +6 -0
  96. package/corpus/templates/content/.agents/skills/content-product-development/SKILL.md +47 -0
  97. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +72 -8
  98. package/corpus/templates/content/changelog/2026-07-30-the-slash-command-hint-now-stays-on-only-the-active-empty-li.md +6 -0
  99. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +506 -11
  100. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +148 -17
  101. package/corpus/templates/design/app/components/design/canvas-interactions/design-canvas-interactions.ts +357 -0
  102. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -466
  103. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +15 -311
  104. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +19 -997
  105. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +8 -1216
  106. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +31 -38
  107. package/corpus/templates/design/package.json +0 -1
  108. package/corpus/templates/dispatch/AGENTS.md +8 -0
  109. package/corpus/templates/dispatch/DEVELOPING.md +29 -0
  110. package/corpus/templates/dispatch/actions/view-screen.ts +34 -2
  111. package/corpus/templates/dispatch/app/i18n-data.ts +457 -0
  112. package/corpus/templates/dispatch/changelog/2026-07-30-delegated-dispatch-work-continues-reliably-in-the-background.md +6 -0
  113. package/corpus/templates/dispatch/changelog/2026-07-30-find-failed-runs-across-apps.md +6 -0
  114. package/corpus/templates/dispatch/netlify.toml +3 -0
  115. package/corpus/templates/slides/.agents/skills/analytics-data-for-decks/SKILL.md +8 -12
  116. package/corpus/templates/slides/AGENTS.md +4 -4
  117. package/corpus/templates/slides/actions/duplicate-deck.ts +19 -4
  118. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -10
  119. package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +12 -1
  120. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +30 -13
  121. package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +35 -0
  122. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +63 -56
  123. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -76
  124. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1110 -292
  125. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +53 -0
  126. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +235 -18
  127. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +4 -1
  128. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +11 -0
  129. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +129 -0
  130. package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +70 -0
  131. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +519 -65
  132. package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +36 -426
  133. package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +19 -458
  134. package/corpus/templates/slides/app/context/DeckContext.tsx +179 -72
  135. package/corpus/templates/slides/app/global.css +105 -0
  136. package/corpus/templates/slides/app/i18n/en-US.ts +8 -0
  137. package/corpus/templates/slides/app/lib/deck-editor-loading.ts +26 -0
  138. package/corpus/templates/slides/app/lib/mermaid-blocks.ts +27 -0
  139. package/corpus/templates/slides/app/pages/DeckEditor.tsx +71 -38
  140. package/corpus/templates/slides/app/pages/Index.tsx +98 -41
  141. package/corpus/templates/slides/changelog/2026-07-30-deck-loading-no-longer-flashes-an-unavailable-message.md +6 -0
  142. package/corpus/templates/slides/changelog/2026-07-30-duplicating-a-deck-from-the-deck-list-now-opens-the-copy-imm.md +6 -0
  143. package/corpus/templates/slides/changelog/2026-07-30-layout-overflow-warning-is-readable-and-dismissible.md +6 -0
  144. package/corpus/templates/slides/changelog/2026-07-30-shared-canvas-annotations.md +6 -0
  145. package/corpus/templates/slides/changelog/2026-07-30-slide-objects-can-be-moved-as-a-group-copied-and-pasted-with.md +6 -0
  146. package/corpus/templates/slides/changelog/2026-07-30-slide-text-editing-and-object-controls-now-match-google-slides.md +6 -0
  147. package/corpus/templates/slides/changelog/2026-07-30-style-panel-now-scrolls-with-speaker-notes-open-shows-the-sl.md +6 -0
  148. package/corpus/templates/slides/changelog/2026-07-30-top-toolbar-controls-now-use-consistent-sizing-with-undo-and.md +6 -0
  149. package/corpus/templates/slides/netlify.toml +3 -1
  150. package/corpus/templates/slides/package.json +0 -1
  151. package/corpus/templates/slides/server/lib/chat-attachments.ts +18 -0
  152. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
  153. package/corpus/templates/slides/vite.config.ts +0 -1
  154. package/corpus/toolkit/CHANGELOG.md +13 -0
  155. package/corpus/toolkit/README.md +19 -0
  156. package/corpus/toolkit/agent-native.eject.json +38 -0
  157. package/corpus/toolkit/package.json +29 -1
  158. package/corpus/toolkit/src/canvas-annotations/CanvasCommentPins.tsx +861 -0
  159. package/corpus/toolkit/src/canvas-annotations/DrawOverlay.tsx +1233 -0
  160. package/corpus/toolkit/src/canvas-annotations/index.ts +15 -0
  161. package/corpus/toolkit/src/canvas-annotations/types.ts +14 -0
  162. package/corpus/toolkit/src/canvas-interactions/canvas-interactions.ts +933 -0
  163. package/corpus/toolkit/src/canvas-interactions/index.ts +67 -0
  164. package/corpus/toolkit/src/design-tweaks/scrub-input-utils.ts +311 -0
  165. package/corpus/toolkit/src/design-tweaks/scrub-input.tsx +491 -0
  166. package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +26 -171
  167. package/corpus/toolkit/src/index.ts +1 -0
  168. package/dist/a2a/client.d.ts +23 -0
  169. package/dist/a2a/client.d.ts.map +1 -1
  170. package/dist/a2a/client.js +309 -44
  171. package/dist/a2a/client.js.map +1 -1
  172. package/dist/a2a/correlation.d.ts +1 -0
  173. package/dist/a2a/correlation.d.ts.map +1 -1
  174. package/dist/a2a/correlation.js +27 -0
  175. package/dist/a2a/correlation.js.map +1 -1
  176. package/dist/a2a/types.d.ts +4 -0
  177. package/dist/a2a/types.d.ts.map +1 -1
  178. package/dist/a2a/types.js.map +1 -1
  179. package/dist/action.d.ts +3 -0
  180. package/dist/action.d.ts.map +1 -1
  181. package/dist/action.js.map +1 -1
  182. package/dist/agent/engine/registry.d.ts.map +1 -1
  183. package/dist/agent/engine/registry.js +34 -7
  184. package/dist/agent/engine/registry.js.map +1 -1
  185. package/dist/agent/production-agent.d.ts +40 -1
  186. package/dist/agent/production-agent.d.ts.map +1 -1
  187. package/dist/agent/production-agent.js +113 -15
  188. package/dist/agent/production-agent.js.map +1 -1
  189. package/dist/agent/run-loop-with-resume.d.ts +21 -0
  190. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  191. package/dist/agent/run-loop-with-resume.js +163 -4
  192. package/dist/agent/run-loop-with-resume.js.map +1 -1
  193. package/dist/agent/run-manager.d.ts +16 -9
  194. package/dist/agent/run-manager.d.ts.map +1 -1
  195. package/dist/agent/run-manager.js +93 -42
  196. package/dist/agent/run-manager.js.map +1 -1
  197. package/dist/agent/thread-debug-history.d.ts +10 -0
  198. package/dist/agent/thread-debug-history.d.ts.map +1 -0
  199. package/dist/agent/thread-debug-history.js +68 -0
  200. package/dist/agent/thread-debug-history.js.map +1 -0
  201. package/dist/agent/types.d.ts +3 -1
  202. package/dist/agent/types.d.ts.map +1 -1
  203. package/dist/agent/types.js.map +1 -1
  204. package/dist/cli/create.d.ts.map +1 -1
  205. package/dist/cli/create.js +6 -1
  206. package/dist/cli/create.js.map +1 -1
  207. package/dist/cli/templates-meta.js +2 -2
  208. package/dist/cli/templates-meta.js.map +1 -1
  209. package/dist/client/agent-chat-adapter.d.ts +1 -1
  210. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  211. package/dist/client/agent-chat-adapter.js +76 -13
  212. package/dist/client/agent-chat-adapter.js.map +1 -1
  213. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  214. package/dist/client/resources/McpIntegrationDialog.js +1 -1
  215. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  216. package/dist/client/sse-event-processor.d.ts.map +1 -1
  217. package/dist/client/sse-event-processor.js +12 -3
  218. package/dist/client/sse-event-processor.js.map +1 -1
  219. package/dist/collab/awareness.d.ts +2 -2
  220. package/dist/collab/awareness.d.ts.map +1 -1
  221. package/dist/db/client.d.ts.map +1 -1
  222. package/dist/db/client.js +48 -17
  223. package/dist/db/client.js.map +1 -1
  224. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  225. package/dist/integrations/adapters/slack.js +7 -3
  226. package/dist/integrations/adapters/slack.js.map +1 -1
  227. package/dist/localization/default-messages.d.ts +43 -0
  228. package/dist/localization/default-messages.d.ts.map +1 -1
  229. package/dist/localization/default-messages.js +43 -0
  230. package/dist/localization/default-messages.js.map +1 -1
  231. package/dist/notifications/routes.d.ts +2 -2
  232. package/dist/observability/traces.d.ts +3 -1
  233. package/dist/observability/traces.d.ts.map +1 -1
  234. package/dist/observability/traces.js +73 -4
  235. package/dist/observability/traces.js.map +1 -1
  236. package/dist/progress/routes.d.ts +1 -1
  237. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  238. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  239. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  240. package/dist/resources/handlers.d.ts +1 -1
  241. package/dist/scripts/call-agent.d.ts.map +1 -1
  242. package/dist/scripts/call-agent.js +233 -10
  243. package/dist/scripts/call-agent.js.map +1 -1
  244. package/dist/scripts/describe-workspace-apps.js +2 -2
  245. package/dist/scripts/describe-workspace-apps.js.map +1 -1
  246. package/dist/secrets/crypto.d.ts +31 -21
  247. package/dist/secrets/crypto.d.ts.map +1 -1
  248. package/dist/secrets/crypto.js +108 -33
  249. package/dist/secrets/crypto.js.map +1 -1
  250. package/dist/secrets/storage.d.ts +3 -5
  251. package/dist/secrets/storage.d.ts.map +1 -1
  252. package/dist/secrets/storage.js +40 -25
  253. package/dist/secrets/storage.js.map +1 -1
  254. package/dist/server/agent-capabilities.js +1 -1
  255. package/dist/server/agent-capabilities.js.map +1 -1
  256. package/dist/server/agent-chat/action-filters-a2a.d.ts +17 -3
  257. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  258. package/dist/server/agent-chat/action-filters-a2a.js +92 -3
  259. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  260. package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
  261. package/dist/server/agent-chat/context-tools.js +2 -0
  262. package/dist/server/agent-chat/context-tools.js.map +1 -1
  263. package/dist/server/agent-chat/plugin-options.d.ts +23 -0
  264. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  265. package/dist/server/agent-chat/plugin-options.js +4 -1
  266. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  267. package/dist/server/agent-chat/prompt-resources.js +1 -1
  268. package/dist/server/agent-chat/prompt-resources.js.map +1 -1
  269. package/dist/server/agent-chat-plugin.d.ts +2 -1
  270. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  271. package/dist/server/agent-chat-plugin.js +79 -56
  272. package/dist/server/agent-chat-plugin.js.map +1 -1
  273. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  274. package/dist/templates/default/.agents/skills/secrets/SKILL.md +14 -7
  275. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +14 -7
  276. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  277. package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  278. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -7
  279. package/dist/vite/client.d.ts.map +1 -1
  280. package/dist/vite/client.js +1 -0
  281. package/dist/vite/client.js.map +1 -1
  282. package/docs/content/agent-native-toolkit.mdx +13 -6
  283. package/docs/content/cloneable-saas.mdx +5 -5
  284. package/docs/content/creating-templates.mdx +9 -0
  285. package/docs/content/deployment.mdx +14 -10
  286. package/docs/content/docs-components.mdx +491 -0
  287. package/docs/content/doctor.mdx +8 -1
  288. package/docs/content/drop-in-agent.mdx +1 -1
  289. package/docs/content/extensions.mdx +18 -18
  290. package/docs/content/faq.mdx +4 -4
  291. package/docs/content/key-concepts.mdx +30 -21
  292. package/docs/content/template-brain-developers.mdx +4 -1
  293. package/docs/content/template-mail-developers.mdx +4 -1
  294. package/docs/content/what-is-agent-native.mdx +6 -6
  295. package/package.json +2 -2
  296. package/src/a2a/client.ts +449 -66
  297. package/src/a2a/correlation.ts +31 -0
  298. package/src/a2a/types.ts +4 -0
  299. package/src/action.ts +3 -0
  300. package/src/agent/engine/registry.ts +45 -5
  301. package/src/agent/production-agent.ts +151 -18
  302. package/src/agent/run-loop-with-resume.ts +190 -3
  303. package/src/agent/run-manager.ts +117 -41
  304. package/src/agent/thread-debug-history.ts +86 -0
  305. package/src/agent/types.ts +3 -1
  306. package/src/cli/create.ts +11 -1
  307. package/src/cli/templates-meta.ts +2 -2
  308. package/src/client/agent-chat-adapter.ts +91 -13
  309. package/src/client/resources/McpIntegrationDialog.tsx +11 -9
  310. package/src/client/sse-event-processor.ts +17 -3
  311. package/src/db/client.ts +57 -17
  312. package/src/integrations/adapters/slack.ts +8 -3
  313. package/src/localization/default-messages.ts +44 -0
  314. package/src/observability/traces.ts +78 -5
  315. package/src/scripts/call-agent.ts +290 -9
  316. package/src/scripts/describe-workspace-apps.ts +2 -2
  317. package/src/secrets/crypto.ts +126 -34
  318. package/src/secrets/storage.ts +61 -25
  319. package/src/server/agent-capabilities.ts +1 -1
  320. package/src/server/agent-chat/action-filters-a2a.ts +111 -7
  321. package/src/server/agent-chat/context-tools.ts +2 -0
  322. package/src/server/agent-chat/plugin-options.ts +34 -1
  323. package/src/server/agent-chat/prompt-resources.ts +1 -1
  324. package/src/server/agent-chat-plugin.ts +91 -59
  325. package/src/templates/default/.agents/skills/secrets/SKILL.md +14 -7
  326. package/src/templates/headless/.agents/skills/secrets/SKILL.md +14 -7
  327. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
  328. package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
  329. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +14 -7
  330. package/src/vite/client.ts +1 -0
@@ -10,25 +10,26 @@
10
10
  * existing single-app deployments can keep reading and writing secrets while
11
11
  * they migrate to the shared key.
12
12
  *
13
- * The default encryption key is derived from
14
- * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,
15
- * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app
16
- * workspace read one app's encrypted production data without replacing the
17
- * shared local auth secret. Hosted workspace deploys (`AGENT_NATIVE_WORKSPACE`)
18
- * typically set none of those literal env vars per app; in that case the
19
- * shared-key variant falls back further to material derived from the
20
- * workspace-wide `A2A_SECRET` (see `getWorkspaceA2ADerivedSecret`), so sibling
21
- * apps in the same workspace still land on the same shared key without any
22
- * extra configuration. In production we refuse to start without configured
23
- * key material a CWD-derived fallback would be effectively static (e.g.
24
- * `/var/task` on Lambda), so anyone with read access to the DB could decrypt
25
- * every secret.
13
+ * The generic encryption key is derived from
14
+ * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then
15
+ * `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. Workspace-shared vault
16
+ * rows use a different precedence: `SECRETS_ENCRYPTION_KEY`, then material
17
+ * derived from the workspace-wide `A2A_SECRET`, then the app-local auth/key
18
+ * fallbacks retained for backward-compatible reads. This keeps sibling apps
19
+ * on one key even when each app correctly has a different auth secret.
20
+ *
21
+ * In production we refuse to start without configured key material a
22
+ * CWD-derived fallback would be effectively static (e.g. `/var/task` on
23
+ * Lambda), so anyone with read access to the DB could decrypt every secret.
26
24
  *
27
25
  * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
28
26
  * lets readers distinguish ciphertext from legacy plaintext during migration.
29
27
  */
30
28
 
31
- import { getWorkspaceA2ADerivedSecret } from "../server/derived-secret.js";
29
+ import {
30
+ deriveServerSecret,
31
+ getWorkspaceA2ADerivedSecret,
32
+ } from "../server/derived-secret.js";
32
33
 
33
34
  type NodeCryptoModule = typeof import("node:crypto");
34
35
 
@@ -80,7 +81,12 @@ function appScopedEncryptionKey(): string | undefined {
80
81
  : undefined;
81
82
  }
82
83
 
83
- function sharedEncryptionKeyMaterial(): string | undefined {
84
+ /**
85
+ * Preserve the generic/app-local key precedence. Generic encrypted values are
86
+ * not a cross-app boundary, and changing their preferred key would strand
87
+ * existing OAuth tokens and credentials.
88
+ */
89
+ function genericEncryptionKeyMaterial(): string | undefined {
84
90
  if (typeof process === "undefined") return undefined;
85
91
  return (
86
92
  process.env.SECRETS_ENCRYPTION_KEY ||
@@ -89,13 +95,55 @@ function sharedEncryptionKeyMaterial(): string | undefined {
89
95
  );
90
96
  }
91
97
 
98
+ /**
99
+ * Vault sharing follows configured A2A trust even on independently deployed
100
+ * sibling apps that are not launched by the workspace wrapper. The wrapper's
101
+ * `AGENT_NATIVE_WORKSPACE` flag remains required for auth/OAuth-derived
102
+ * secrets, but it must not be a hidden prerequisite for decrypting vault rows
103
+ * when every app already has the same explicit `A2A_SECRET`.
104
+ */
105
+ function a2aSharedEncryptionKeyMaterial(): string | undefined {
106
+ const workspaceDerived = getWorkspaceA2ADerivedSecret("secrets-encryption");
107
+ if (workspaceDerived) return workspaceDerived;
108
+ if (typeof process === "undefined") return undefined;
109
+ const rootSecret = process.env.A2A_SECRET?.trim();
110
+ return rootSecret
111
+ ? deriveServerSecret(rootSecret, "secrets-encryption")
112
+ : undefined;
113
+ }
114
+
115
+ /**
116
+ * Stable materials that may be used for workspace-shared vault rows, ordered
117
+ * from current preference to legacy compatibility fallbacks.
118
+ *
119
+ * `BETTER_AUTH_SECRET` deliberately comes after the A2A-derived material:
120
+ * sibling apps normally have different auth secrets but share A2A trust.
121
+ */
122
+ function sharedEncryptionKeyMaterials(): string[] {
123
+ if (typeof process === "undefined") return [];
124
+ const candidates = [
125
+ process.env.SECRETS_ENCRYPTION_KEY,
126
+ a2aSharedEncryptionKeyMaterial(),
127
+ process.env.BETTER_AUTH_SECRET,
128
+ appScopedEncryptionKey(),
129
+ ].filter((value): value is string => Boolean(value));
130
+ return [...new Set(candidates)];
131
+ }
132
+
133
+ function preferredSharedEncryptionKeyMaterial(): string | undefined {
134
+ return sharedEncryptionKeyMaterials()[0];
135
+ }
136
+
137
+ function hashSecretEncryptionKey(material: string): Buffer {
138
+ const { createHash } = requireNodeCrypto();
139
+ return createHash("sha256").update(material).digest();
140
+ }
141
+
92
142
  function deriveSecretEncryptionKey(
93
143
  explicit: string | undefined,
94
144
  errorMessage: string,
95
145
  warningMessage: string,
96
146
  ): Buffer {
97
- const { createHash } = requireNodeCrypto();
98
-
99
147
  if (!explicit) {
100
148
  if (processNodeEnv() === "production") {
101
149
  throw new Error(errorMessage);
@@ -108,7 +156,7 @@ function deriveSecretEncryptionKey(
108
156
  }
109
157
 
110
158
  const material = explicit || `agent-native-secrets:${processCwd()}`;
111
- return createHash("sha256").update(material).digest();
159
+ return hashSecretEncryptionKey(material);
112
160
  }
113
161
 
114
162
  /**
@@ -125,7 +173,7 @@ export function getSecretEncryptionKey(): Buffer {
125
173
  .replace(/[^A-Z0-9]+/g, "_")
126
174
  .replace(/^_+|_+$/g, "");
127
175
  return deriveSecretEncryptionKey(
128
- appScopedEncryptionKey() || sharedEncryptionKeyMaterial(),
176
+ appScopedEncryptionKey() || genericEncryptionKeyMaterial(),
129
177
  "[agent-native/secrets] Refusing to start in production without an encryption key. " +
130
178
  `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
131
179
  "The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
@@ -138,30 +186,32 @@ export function getSecretEncryptionKey(): Buffer {
138
186
 
139
187
  /**
140
188
  * Derive the preferred workspace-shared key used by `app_secrets` rows.
141
- * Unlike generic column-level encryption, workspace vault data should decrypt
142
- * in sibling apps, so `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` take
143
- * precedence. Hosted workspace deploys (`AGENT_NATIVE_WORKSPACE`) that set
144
- * neither literal var still get stable shared material derived from the
145
- * workspace-wide `A2A_SECRET` before falling further back. The app-scoped key
146
- * remains a compatibility fallback for deployments that have not configured
147
- * shared material yet; once the shared key is configured, writes use it and
148
- * reads still fall back to old rows.
189
+ * Unlike generic column-level encryption, workspace vault data must decrypt
190
+ * in sibling apps. An explicit `SECRETS_ENCRYPTION_KEY` wins; hosted
191
+ * workspaces otherwise derive stable material from their shared `A2A_SECRET`
192
+ * before considering app-local `BETTER_AUTH_SECRET` or app-scoped key
193
+ * fallbacks. Reads try every configured legacy candidate and report when the
194
+ * ciphertext should be refreshed under the preferred key.
149
195
  */
150
196
  export function getSharedSecretEncryptionKey(): Buffer {
151
197
  return deriveSecretEncryptionKey(
152
- sharedEncryptionKeyMaterial() || appScopedEncryptionKey(),
198
+ preferredSharedEncryptionKeyMaterial(),
153
199
  "[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. " +
154
- "Set SECRETS_ENCRYPTION_KEY, BETTER_AUTH_SECRET, or the app-scoped *_SECRETS_ENCRYPTION_KEY in the deploy environment " +
155
- " or, on a hosted workspace deploy, ensure A2A_SECRET is set so shared material can be derived from it.",
200
+ "Set SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET, or set BETTER_AUTH_SECRET / an app-scoped " +
201
+ "*_SECRETS_ENCRYPTION_KEY compatibility fallback in the deploy environment.",
156
202
  "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using app-scoped or machine-local fallback for workspace secrets. " +
157
- "Set SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET in every workspace app so sibling apps share vault rows, " +
158
- "or rely on A2A_SECRET-derived material on hosted workspace deploys.",
203
+ "Set SECRETS_ENCRYPTION_KEY or rely on A2A_SECRET-derived material on hosted workspace deploys so sibling apps share vault rows.",
159
204
  );
160
205
  }
161
206
 
162
207
  /** Whether this deployment has stable workspace-shared key material. */
163
208
  export function hasSharedSecretEncryptionKeyMaterial(): boolean {
164
- return Boolean(sharedEncryptionKeyMaterial());
209
+ if (typeof process === "undefined") return false;
210
+ return Boolean(
211
+ process.env.SECRETS_ENCRYPTION_KEY ||
212
+ a2aSharedEncryptionKeyMaterial() ||
213
+ process.env.BETTER_AUTH_SECRET,
214
+ );
165
215
  }
166
216
 
167
217
  function encryptWithKey(plaintext: string, key: Buffer): string {
@@ -212,7 +262,49 @@ export function encryptSharedSecretValue(plaintext: string): string {
212
262
 
213
263
  /** Decrypt a workspace-shared `app_secrets` value. */
214
264
  export function decryptSharedSecretValue(encrypted: string): string {
215
- return decryptWithKey(encrypted, getSharedSecretEncryptionKey());
265
+ return decryptSharedSecretValueDetailed(encrypted).value;
266
+ }
267
+
268
+ export interface DecryptedSharedSecretValue {
269
+ value: string;
270
+ /**
271
+ * True when a legacy app-local candidate decrypted the value. Callers that
272
+ * own the row should opportunistically re-encrypt it with the preferred
273
+ * workspace key.
274
+ */
275
+ needsReencrypt: boolean;
276
+ }
277
+
278
+ /**
279
+ * Decrypt workspace ciphertext with the preferred key first, then configured
280
+ * legacy candidates. The result never exposes which secret material matched.
281
+ */
282
+ export function decryptSharedSecretValueDetailed(
283
+ encrypted: string,
284
+ ): DecryptedSharedSecretValue {
285
+ const preferredKey = getSharedSecretEncryptionKey();
286
+ const candidateKeys = [preferredKey];
287
+ for (const material of sharedEncryptionKeyMaterials()) {
288
+ const candidate = hashSecretEncryptionKey(material);
289
+ if (!candidateKeys.some((existing) => existing.equals(candidate))) {
290
+ candidateKeys.push(candidate);
291
+ }
292
+ }
293
+
294
+ let lastError: unknown;
295
+ for (const candidate of candidateKeys) {
296
+ try {
297
+ return {
298
+ value: decryptWithKey(encrypted, candidate),
299
+ needsReencrypt: !candidate.equals(preferredKey),
300
+ };
301
+ } catch (error) {
302
+ lastError = error;
303
+ }
304
+ }
305
+ throw lastError instanceof Error
306
+ ? lastError
307
+ : new Error("Unable to decrypt workspace secret");
216
308
  }
217
309
 
218
310
  /**
@@ -3,11 +3,9 @@
3
3
  *
4
4
  * Values are encrypted at rest with AES-256-GCM. The workspace-shared
5
5
  * encryption key is derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the
6
- * existing `BETTER_AUTH_SECRET` env var (fallback so templates don't need a
7
- * second secret during development). Deployments that only have the legacy
8
- * app-scoped key continue to work until shared key material is configured;
9
- * once configured, new rows use the shared key and reads retain a legacy-key
10
- * fallback for existing rows.
6
+ * workspace-wide `A2A_SECRET`. `BETTER_AUTH_SECRET` and app-scoped keys remain
7
+ * read fallbacks for rows written before sibling apps converged on the shared
8
+ * key. Successful fallback reads race-safely refresh the shared ciphertext.
11
9
  *
12
10
  * Secret values are NEVER logged and NEVER returned from any route handler.
13
11
  */
@@ -19,7 +17,7 @@ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
19
17
  import {
20
18
  encryptSecretValue as encryptLegacyValue,
21
19
  encryptSharedSecretValue as encryptValue,
22
- decryptSharedSecretValue as decryptValue,
20
+ decryptSharedSecretValueDetailed as decryptValue,
23
21
  decryptSecretValue as decryptLegacyValue,
24
22
  hasSharedSecretEncryptionKeyMaterial,
25
23
  } from "./crypto.js";
@@ -193,8 +191,9 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
193
191
  // pointing at data that's no longer current. Siblings then get an honest
194
192
  // cache miss (falling back to the legacy column or reporting missing) until
195
193
  // the owning app's next read repopulates shared_encrypted_value via
196
- // `populateSharedAppSecret`, which only ever fills a NULL column. A
197
- // temporary miss is safer than serving rotated-away plaintext.
194
+ // `populateSharedAppSecret`, which fills a NULL column or compare-and-swap
195
+ // replaces the exact legacy ciphertext it just decrypted. A temporary miss
196
+ // is safer than serving rotated-away plaintext.
198
197
  const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)
199
198
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
200
199
  ON CONFLICT (scope, scope_id, key) DO UPDATE SET
@@ -253,8 +252,13 @@ interface DecryptedAppSecretValue {
253
252
  value: string;
254
253
  /** True when the app-scoped fallback decrypted encrypted_value. */
255
254
  usedLegacyKey: boolean;
256
- /** True when shared_encrypted_value was not the source of the plaintext. */
255
+ /** True when shared_encrypted_value needs to be created or refreshed. */
257
256
  needsSharedCiphertext: boolean;
257
+ /**
258
+ * Existing ciphertext that must still match before a refresh. Null means the
259
+ * migration may only fill an empty shared column.
260
+ */
261
+ sharedCiphertextToReplace?: string | null;
258
262
  }
259
263
 
260
264
  function decryptAppSecretValue(
@@ -263,10 +267,14 @@ function decryptAppSecretValue(
263
267
  ): DecryptedAppSecretValue {
264
268
  if (sharedEncrypted) {
265
269
  try {
270
+ const decrypted = decryptValue(sharedEncrypted);
266
271
  return {
267
- value: decryptValue(sharedEncrypted),
272
+ value: decrypted.value,
268
273
  usedLegacyKey: false,
269
- needsSharedCiphertext: false,
274
+ needsSharedCiphertext: decrypted.needsReencrypt,
275
+ sharedCiphertextToReplace: decrypted.needsReencrypt
276
+ ? sharedEncrypted
277
+ : undefined,
270
278
  };
271
279
  } catch {
272
280
  // Fall through to the legacy column. A partially migrated row may have
@@ -274,30 +282,34 @@ function decryptAppSecretValue(
274
282
  }
275
283
  }
276
284
  try {
285
+ const decrypted = decryptValue(encrypted);
277
286
  return {
278
- value: decryptValue(encrypted),
287
+ value: decrypted.value,
279
288
  usedLegacyKey: false,
280
289
  needsSharedCiphertext: true,
290
+ sharedCiphertextToReplace: sharedEncrypted ?? null,
281
291
  };
282
292
  } catch {
283
293
  return {
284
294
  value: decryptLegacyValue(encrypted),
285
295
  usedLegacyKey: true,
286
296
  needsSharedCiphertext: true,
297
+ sharedCiphertextToReplace: sharedEncrypted ?? null,
287
298
  };
288
299
  }
289
300
  }
290
301
 
291
302
  /**
292
- * Populate the shared column without touching encrypted_value. This is
293
- * intentionally best-effort: a read must still succeed if a deployment's DB
294
- * role cannot update the row. The NULL predicate prevents a concurrent writer
295
- * from overwriting newly-populated shared ciphertext, and leaving updated_at
296
- * untouched preserves the row's user-visible ordering/metadata.
303
+ * Create or refresh the shared column without touching encrypted_value. This
304
+ * is intentionally best-effort: a read must still succeed if a deployment's
305
+ * DB role cannot update the row. The compare-and-swap predicate prevents a
306
+ * concurrent writer from being overwritten, and leaving updated_at untouched
307
+ * preserves the row's user-visible ordering/metadata.
297
308
  */
298
309
  async function populateSharedAppSecret(
299
310
  id: unknown,
300
311
  value: string,
312
+ sharedCiphertextToReplace: string | null,
301
313
  ): Promise<void> {
302
314
  if (
303
315
  id === undefined ||
@@ -307,10 +319,18 @@ async function populateSharedAppSecret(
307
319
  return;
308
320
  }
309
321
  try {
310
- await getDbExec().execute({
311
- sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,
312
- args: [encryptValue(value), id],
313
- });
322
+ const encrypted = encryptValue(value);
323
+ if (sharedCiphertextToReplace === null) {
324
+ await getDbExec().execute({
325
+ sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,
326
+ args: [encrypted, id],
327
+ });
328
+ } else {
329
+ await getDbExec().execute({
330
+ sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value = ?`,
331
+ args: [encrypted, id, sharedCiphertextToReplace],
332
+ });
333
+ }
314
334
  } catch {
315
335
  // Migration is opportunistic. Preserve the successful read if the update
316
336
  // is unavailable due to a read-only role, transient DB failure, or race.
@@ -371,7 +391,11 @@ export async function readAppSecret(
371
391
  rows[0].shared_encrypted_value as string | null,
372
392
  );
373
393
  if (decrypted.needsSharedCiphertext) {
374
- await populateSharedAppSecret(rows[0].id, decrypted.value);
394
+ await populateSharedAppSecret(
395
+ rows[0].id,
396
+ decrypted.value,
397
+ decrypted.sharedCiphertextToReplace ?? null,
398
+ );
375
399
  }
376
400
  return {
377
401
  value: decrypted.value,
@@ -410,7 +434,11 @@ export async function readAppSecrets(args: {
410
434
  row.shared_encrypted_value as string | null,
411
435
  );
412
436
  if (decrypted.needsSharedCiphertext) {
413
- await populateSharedAppSecret(row.id, decrypted.value);
437
+ await populateSharedAppSecret(
438
+ row.id,
439
+ decrypted.value,
440
+ decrypted.sharedCiphertextToReplace ?? null,
441
+ );
414
442
  }
415
443
  results.set(key, {
416
444
  value: decrypted.value,
@@ -473,7 +501,11 @@ export async function readAppSecretMeta(
473
501
  row.shared_encrypted_value as string | null,
474
502
  );
475
503
  if (decrypted.needsSharedCiphertext) {
476
- await populateSharedAppSecret(row.id, decrypted.value);
504
+ await populateSharedAppSecret(
505
+ row.id,
506
+ decrypted.value,
507
+ decrypted.sharedCiphertextToReplace ?? null,
508
+ );
477
509
  }
478
510
  last4Value = last4(decrypted.value);
479
511
  } catch {
@@ -516,7 +548,11 @@ export async function listAppSecretsForScope(
516
548
  row.shared_encrypted_value as string | null,
517
549
  );
518
550
  if (decrypted.needsSharedCiphertext) {
519
- await populateSharedAppSecret(row.id, decrypted.value);
551
+ await populateSharedAppSecret(
552
+ row.id,
553
+ decrypted.value,
554
+ decrypted.sharedCiphertextToReplace ?? null,
555
+ );
520
556
  }
521
557
  last4Value = last4(decrypted.value);
522
558
  } catch {
@@ -130,7 +130,7 @@ export function formatCapabilitySummary(
130
130
  "",
131
131
  lines.join("\n\n"),
132
132
  "",
133
- "Delegate with call-agent: pass action + input for a listed action, or message for open-ended work. Prefer the app that owns the data over rebuilding its capability here.",
133
+ "Delegate with call-agent using a natural-language message by default. The receiving app owns interpretation and its local tools. Use action + input only for an exact bounded read with a fully known schema, never as a workaround for failed delegation. Prefer the app that owns the data over rebuilding its capability here.",
134
134
  ]
135
135
  .filter(Boolean)
136
136
  .join("\n");
@@ -12,6 +12,7 @@ import {
12
12
  filterInitialEngineTools,
13
13
  resolveAgentRequestReasoningEffort,
14
14
  type ActionEntry,
15
+ type AgentLoopOutcome,
15
16
  } from "../../agent/production-agent.js";
16
17
  import { runAgentLoopDirectWithSoftTimeout } from "../../agent/run-loop-with-resume.js";
17
18
  import type { AgentChatEvent } from "../../agent/types.js";
@@ -194,6 +195,10 @@ export function buildAuthenticatedAgentA2ASkills(
194
195
  name,
195
196
  description: entry.tool.description,
196
197
  publicAgent: entry.publicAgent,
198
+ // Every action in this set is read-only by construction. Omitting the
199
+ // flag made discovery label all of them "(mutating)", which pushes a
200
+ // caller away from invoking them and back into open-ended delegation.
201
+ readOnly: true,
197
202
  // Naming an action without its parameters is what makes a caller invoke
198
203
  // it with `{}` and fail on a required field.
199
204
  ...(entry.tool.parameters
@@ -230,9 +235,14 @@ export function resolveArtifactBaseUrl(
230
235
  export function assembleA2AFinalResponse(
231
236
  events: readonly AgentChatEvent[],
232
237
  toolResults: readonly A2AToolResultSummary[],
233
- options: A2AArtifactResponseOptions & { event?: any } = {},
238
+ options: A2AArtifactResponseOptions & {
239
+ event?: any;
240
+ outcome?: AgentLoopOutcome;
241
+ } = {},
234
242
  ): { responseText: string; finalText: string } {
235
- const terminalError = getA2ATerminalErrorEvent(events);
243
+ const terminalError = options.outcome
244
+ ? terminalErrorFromOutcome(options.outcome)
245
+ : getA2ATerminalErrorEvent(events);
236
246
  const responseText = collectFinalResponseTextFromAgentEvents(events, {
237
247
  fallbackToPreToolText: !terminalError,
238
248
  });
@@ -241,12 +251,48 @@ export function assembleA2AFinalResponse(
241
251
  includeReferencedArtifacts: true,
242
252
  includePersistedArtifactMarker: true,
243
253
  });
244
- if (terminalError && !finalText.trim()) {
245
- throw new Error(formatA2ATerminalError(terminalError));
254
+ if (terminalError) {
255
+ const partialResult = finalText.trim()
256
+ ? `\n\nPartial verified results before the failure:\n${finalText.trim()}`
257
+ : "";
258
+ throw new Error(formatA2ATerminalError(terminalError) + partialResult);
259
+ }
260
+ if (!finalText.trim()) {
261
+ throw new Error(
262
+ "Agent completed without a response or verified artifact.\ncode: empty_agent_response",
263
+ );
246
264
  }
247
265
  return { responseText, finalText };
248
266
  }
249
267
 
268
+ function terminalErrorFromOutcome(
269
+ outcome: AgentLoopOutcome,
270
+ ): Extract<AgentChatEvent, { type: "error" }> | null {
271
+ if (outcome.state === "completed") return null;
272
+ if (outcome.state === "failed") {
273
+ return {
274
+ type: "error",
275
+ error: outcome.message,
276
+ errorCode: outcome.code,
277
+ recoverable: outcome.retryable,
278
+ };
279
+ }
280
+ if (outcome.state === "input_required") {
281
+ return {
282
+ type: "error",
283
+ error: outcome.message,
284
+ errorCode: outcome.code,
285
+ recoverable: true,
286
+ };
287
+ }
288
+ return {
289
+ type: "error",
290
+ error: outcome.message ?? "Agent run was canceled.",
291
+ errorCode: "canceled",
292
+ recoverable: false,
293
+ };
294
+ }
295
+
250
296
  function getA2ATerminalErrorEvent(
251
297
  events: readonly AgentChatEvent[],
252
298
  ): Extract<AgentChatEvent, { type: "error" }> | null {
@@ -255,6 +301,23 @@ function getA2ATerminalErrorEvent(
255
301
  if (event.type === "clear") continue;
256
302
  if (event.type === "done") return null;
257
303
  if (event.type === "error") return event;
304
+ if (event.type === "tripwire") {
305
+ return {
306
+ type: "error",
307
+ error: event.reason || "Agent stopped at a delegated-run guardrail.",
308
+ errorCode: event.processor ? `tripwire:${event.processor}` : "tripwire",
309
+ recoverable: true,
310
+ };
311
+ }
312
+ if (event.type === "loop_limit") {
313
+ return {
314
+ type: "error",
315
+ error:
316
+ "Agent stopped before finishing at the delegated-run step limit.",
317
+ errorCode: "loop_limit",
318
+ recoverable: true,
319
+ };
320
+ }
258
321
  if (event.type === "auto_continue") {
259
322
  return {
260
323
  type: "error",
@@ -280,6 +343,27 @@ function formatA2ATerminalError(
280
343
 
281
344
  type A2AAgentLoopRunner = typeof runAgentLoopDirectWithSoftTimeout;
282
345
 
346
+ /**
347
+ * Delegated turns should do specialist work with the receiver's own tools,
348
+ * but must return a bounded caller-ready result. Interactive defaults allow
349
+ * extremely deep work (400 iterations / 20M cumulative input tokens), which
350
+ * made healthy Slides -> Analytics calls take 4-13 minutes and consume up to
351
+ * millions of input tokens as tool results accumulated. These defaults leave
352
+ * ample room for multi-source analysis while failing before a delegated turn
353
+ * can silently become an unbounded research session.
354
+ */
355
+ export const DEFAULT_DELEGATED_MAX_ITERATIONS = 80;
356
+ export const DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKENS = 750_000;
357
+ export const DEFAULT_DELEGATED_MAX_TOOL_RESULT_CHARS = 20_000;
358
+
359
+ export const DELEGATED_AGENT_EXECUTION_CONTRACT = `
360
+ <delegated-agent-contract>
361
+ This request was delegated by another app. You are the specialist owner of the work.
362
+ - Interpret the caller's natural-language objective using your own instructions, skills, data dictionary, credentials, and tools. Choose providers, schemas, queries, and joins here; never ask the caller to invent SQL or source-specific implementation details for you.
363
+ - Minimize round trips. Filter, join, aggregate, paginate, stage, and reduce large datasets inside your own tools rather than returning raw records or transcripts.
364
+ - Return a concise caller-ready result with the answer, source and coverage details, relevant counts or IDs, caveats/partial gaps, and exact artifact URLs. Do not return tool transcripts or large raw payloads.
365
+ </delegated-agent-contract>`;
366
+
283
367
  export interface DelegatedAgentLoopTelemetry {
284
368
  runId: string;
285
369
  threadId: string | null;
@@ -302,14 +386,19 @@ async function runDelegatedAgentLoop(
302
386
  runOptions: Parameters<A2AAgentLoopRunner>[0],
303
387
  pluginOptions: Pick<
304
388
  AgentChatPluginOptions,
305
- "finalResponseGuard" | "runSoftTimeoutMs"
389
+ "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
306
390
  >,
307
391
  timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
308
392
  options: DelegatedAgentLoopOptions,
309
393
  ) {
310
394
  const runner = options.runner ?? runAgentLoopDirectWithSoftTimeout;
395
+ const policy = pluginOptions.delegatedRunPolicy;
396
+ const configuredHardToolResultCap = runOptions.toolLimits?.hardMaxResultChars;
397
+ const delegatedHardToolResultCap =
398
+ policy?.maxToolResultChars ?? DEFAULT_DELEGATED_MAX_TOOL_RESULT_CHARS;
311
399
  const resolvedRunOptions = {
312
400
  ...runOptions,
401
+ systemPrompt: runOptions.systemPrompt + DELEGATED_AGENT_EXECUTION_CONTRACT,
313
402
  // Delegated runs resolve their own model and do not pass through the
314
403
  // interactive request handler's output-token setup. Use the same
315
404
  // model-aware headroom here so reasoning models (notably GPT-5.x) do
@@ -321,6 +410,21 @@ async function runDelegatedAgentLoop(
321
410
  reasoningEffort:
322
411
  runOptions.reasoningEffort ??
323
412
  resolveAgentRequestReasoningEffort({ model: runOptions.model }),
413
+ maxIterations:
414
+ runOptions.maxIterations ??
415
+ policy?.maxIterations ??
416
+ DEFAULT_DELEGATED_MAX_ITERATIONS,
417
+ maxRunInputTokens:
418
+ runOptions.maxRunInputTokens ??
419
+ policy?.maxRunInputTokens ??
420
+ DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKENS,
421
+ toolLimits: {
422
+ ...(runOptions.toolLimits ?? {}),
423
+ hardMaxResultChars:
424
+ typeof configuredHardToolResultCap === "number"
425
+ ? Math.min(configuredHardToolResultCap, delegatedHardToolResultCap)
426
+ : delegatedHardToolResultCap,
427
+ },
324
428
  finalResponseGuard: pluginOptions.finalResponseGuard,
325
429
  };
326
430
  const execute = (loopOptions = resolvedRunOptions) =>
@@ -369,7 +473,7 @@ export function runA2AAgentLoop(
369
473
  runOptions: Parameters<A2AAgentLoopRunner>[0],
370
474
  pluginOptions: Pick<
371
475
  AgentChatPluginOptions,
372
- "finalResponseGuard" | "runSoftTimeoutMs"
476
+ "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
373
477
  >,
374
478
  timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
375
479
  options: DelegatedAgentLoopOptions = {},
@@ -391,7 +495,7 @@ export function runMCPAgentLoop(
391
495
  runOptions: Parameters<A2AAgentLoopRunner>[0],
392
496
  pluginOptions: Pick<
393
497
  AgentChatPluginOptions,
394
- "finalResponseGuard" | "runSoftTimeoutMs"
498
+ "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
395
499
  >,
396
500
  timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
397
501
  options: DelegatedAgentLoopOptions = {},
@@ -152,6 +152,8 @@ Prefer \`web-request\` for simple API calls and static pages. Use browser automa
152
152
 
153
153
  The \`call-agent\` tool sends a message to a DIFFERENT, separately-deployed app's agent (A2A protocol). It is **not** for calling actions within the current app.
154
154
 
155
+ Use a natural-language \`message\` by default. The receiving app owns interpretation and runs with its own instructions, skills, connected sources, data dictionary, and tools. Do not choose its provider, schema, query, joins, or SQL for it. A direct \`action\` + \`input\` call is only for an exact bounded read whose complete contract is already known; it is never a workaround for unreliable delegation.
156
+
155
157
  **NEVER use \`call-agent\` to:**
156
158
  - Call your own app by name
157
159
  - Perform tasks you can accomplish with your own registered tools