@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
@@ -10,25 +10,29 @@
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: `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then
17
+ * the legacy shared `SECRETS_ENCRYPTION_KEY`, then material derived from the
18
+ * workspace-wide `A2A_SECRET`, then the app-local auth/key fallbacks retained
19
+ * for backward-compatible reads. A previous workspace key can be supplied
20
+ * during rotation with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS`. This keeps
21
+ * sibling apps on one stable key even when each app correctly has a different
22
+ * auth secret, and prevents A2A trust rotation from stranding vault data.
23
+ *
24
+ * In production we refuse to start without configured key material a
25
+ * CWD-derived fallback would be effectively static (e.g. `/var/task` on
26
+ * Lambda), so anyone with read access to the DB could decrypt every secret.
26
27
  *
27
28
  * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
28
29
  * lets readers distinguish ciphertext from legacy plaintext during migration.
29
30
  */
30
31
 
31
- import { getWorkspaceA2ADerivedSecret } from "../server/derived-secret.js";
32
+ import {
33
+ deriveServerSecret,
34
+ getWorkspaceA2ADerivedSecret,
35
+ } from "../server/derived-secret.js";
32
36
 
33
37
  type NodeCryptoModule = typeof import("node:crypto");
34
38
 
@@ -80,7 +84,12 @@ function appScopedEncryptionKey(): string | undefined {
80
84
  : undefined;
81
85
  }
82
86
 
83
- function sharedEncryptionKeyMaterial(): string | undefined {
87
+ /**
88
+ * Preserve the generic/app-local key precedence. Generic encrypted values are
89
+ * not a cross-app boundary, and changing their preferred key would strand
90
+ * existing OAuth tokens and credentials.
91
+ */
92
+ function genericEncryptionKeyMaterial(): string | undefined {
84
93
  if (typeof process === "undefined") return undefined;
85
94
  return (
86
95
  process.env.SECRETS_ENCRYPTION_KEY ||
@@ -89,13 +98,77 @@ function sharedEncryptionKeyMaterial(): string | undefined {
89
98
  );
90
99
  }
91
100
 
101
+ function workspaceSharedEncryptionKeyMaterial(): string | undefined {
102
+ if (typeof process === "undefined") return undefined;
103
+ return process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY?.trim() || undefined;
104
+ }
105
+
106
+ function previousWorkspaceSharedEncryptionKeyMaterial(): string | undefined {
107
+ if (typeof process === "undefined") return undefined;
108
+ return (
109
+ process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS?.trim() || undefined
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Vault sharing follows configured A2A trust even on independently deployed
115
+ * sibling apps that are not launched by the workspace wrapper. The wrapper's
116
+ * `AGENT_NATIVE_WORKSPACE` flag remains required for auth/OAuth-derived
117
+ * secrets, but it must not be a hidden prerequisite for decrypting vault rows
118
+ * when every app already has the same explicit `A2A_SECRET`.
119
+ */
120
+ function a2aSharedEncryptionKeyMaterial(): string | undefined {
121
+ const workspaceDerived = getWorkspaceA2ADerivedSecret("secrets-encryption");
122
+ if (workspaceDerived) return workspaceDerived;
123
+ if (typeof process === "undefined") return undefined;
124
+ const rootSecret = process.env.A2A_SECRET?.trim();
125
+ return rootSecret
126
+ ? deriveServerSecret(rootSecret, "secrets-encryption")
127
+ : undefined;
128
+ }
129
+
130
+ /**
131
+ * Stable materials that may be used for workspace-shared vault rows, ordered
132
+ * from current preference to legacy compatibility fallbacks.
133
+ *
134
+ * `BETTER_AUTH_SECRET` deliberately comes after the workspace and A2A-derived
135
+ * materials: sibling apps normally have different auth secrets but share
136
+ * workspace trust.
137
+ */
138
+ function sharedEncryptionKeyMaterials(): string[] {
139
+ if (typeof process === "undefined") return [];
140
+ const candidates = [
141
+ workspaceSharedEncryptionKeyMaterial(),
142
+ process.env.SECRETS_ENCRYPTION_KEY,
143
+ a2aSharedEncryptionKeyMaterial(),
144
+ previousWorkspaceSharedEncryptionKeyMaterial(),
145
+ process.env.BETTER_AUTH_SECRET,
146
+ appScopedEncryptionKey(),
147
+ ].filter((value): value is string => Boolean(value));
148
+ return [...new Set(candidates)];
149
+ }
150
+
151
+ function preferredSharedEncryptionKeyMaterial(): string | undefined {
152
+ if (typeof process === "undefined") return undefined;
153
+ return (
154
+ workspaceSharedEncryptionKeyMaterial() ||
155
+ process.env.SECRETS_ENCRYPTION_KEY ||
156
+ a2aSharedEncryptionKeyMaterial() ||
157
+ process.env.BETTER_AUTH_SECRET ||
158
+ appScopedEncryptionKey()
159
+ );
160
+ }
161
+
162
+ function hashSecretEncryptionKey(material: string): Buffer {
163
+ const { createHash } = requireNodeCrypto();
164
+ return createHash("sha256").update(material).digest();
165
+ }
166
+
92
167
  function deriveSecretEncryptionKey(
93
168
  explicit: string | undefined,
94
169
  errorMessage: string,
95
170
  warningMessage: string,
96
171
  ): Buffer {
97
- const { createHash } = requireNodeCrypto();
98
-
99
172
  if (!explicit) {
100
173
  if (processNodeEnv() === "production") {
101
174
  throw new Error(errorMessage);
@@ -108,7 +181,7 @@ function deriveSecretEncryptionKey(
108
181
  }
109
182
 
110
183
  const material = explicit || `agent-native-secrets:${processCwd()}`;
111
- return createHash("sha256").update(material).digest();
184
+ return hashSecretEncryptionKey(material);
112
185
  }
113
186
 
114
187
  /**
@@ -125,7 +198,7 @@ export function getSecretEncryptionKey(): Buffer {
125
198
  .replace(/[^A-Z0-9]+/g, "_")
126
199
  .replace(/^_+|_+$/g, "");
127
200
  return deriveSecretEncryptionKey(
128
- appScopedEncryptionKey() || sharedEncryptionKeyMaterial(),
201
+ appScopedEncryptionKey() || genericEncryptionKeyMaterial(),
129
202
  "[agent-native/secrets] Refusing to start in production without an encryption key. " +
130
203
  `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
131
204
  "The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
@@ -138,30 +211,36 @@ export function getSecretEncryptionKey(): Buffer {
138
211
 
139
212
  /**
140
213
  * 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.
214
+ * Unlike generic column-level encryption, workspace vault data must decrypt
215
+ * in sibling apps. An explicit `WORKSPACE_SECRETS_ENCRYPTION_KEY` wins without
216
+ * changing the key for app-local OAuth and credential ciphertext. The legacy
217
+ * shared `SECRETS_ENCRYPTION_KEY` remains supported; hosted workspaces
218
+ * otherwise derive material from their shared `A2A_SECRET` before considering
219
+ * app-local `BETTER_AUTH_SECRET` or app-scoped key fallbacks. Reads try every
220
+ * configured legacy/previous candidate and report when the ciphertext should
221
+ * be refreshed under the preferred key.
149
222
  */
150
223
  export function getSharedSecretEncryptionKey(): Buffer {
151
224
  return deriveSecretEncryptionKey(
152
- sharedEncryptionKeyMaterial() || appScopedEncryptionKey(),
225
+ preferredSharedEncryptionKeyMaterial(),
153
226
  "[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.",
156
- "[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.",
227
+ "Set WORKSPACE_SECRETS_ENCRYPTION_KEY (preferred), SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET, " +
228
+ "or set BETTER_AUTH_SECRET / an app-scoped " +
229
+ "*_SECRETS_ENCRYPTION_KEY compatibility fallback in the deploy environment.",
230
+ "[agent-native/secrets] Workspace encryption key not set using app-scoped or machine-local fallback for workspace secrets. " +
231
+ "Set WORKSPACE_SECRETS_ENCRYPTION_KEY or rely on A2A_SECRET-derived material on hosted workspace deploys so sibling apps share vault rows.",
159
232
  );
160
233
  }
161
234
 
162
235
  /** Whether this deployment has stable workspace-shared key material. */
163
236
  export function hasSharedSecretEncryptionKeyMaterial(): boolean {
164
- return Boolean(sharedEncryptionKeyMaterial());
237
+ if (typeof process === "undefined") return false;
238
+ return Boolean(
239
+ workspaceSharedEncryptionKeyMaterial() ||
240
+ process.env.SECRETS_ENCRYPTION_KEY ||
241
+ a2aSharedEncryptionKeyMaterial() ||
242
+ process.env.BETTER_AUTH_SECRET,
243
+ );
165
244
  }
166
245
 
167
246
  function encryptWithKey(plaintext: string, key: Buffer): string {
@@ -212,7 +291,49 @@ export function encryptSharedSecretValue(plaintext: string): string {
212
291
 
213
292
  /** Decrypt a workspace-shared `app_secrets` value. */
214
293
  export function decryptSharedSecretValue(encrypted: string): string {
215
- return decryptWithKey(encrypted, getSharedSecretEncryptionKey());
294
+ return decryptSharedSecretValueDetailed(encrypted).value;
295
+ }
296
+
297
+ export interface DecryptedSharedSecretValue {
298
+ value: string;
299
+ /**
300
+ * True when a legacy app-local candidate decrypted the value. Callers that
301
+ * own the row should opportunistically re-encrypt it with the preferred
302
+ * workspace key.
303
+ */
304
+ needsReencrypt: boolean;
305
+ }
306
+
307
+ /**
308
+ * Decrypt workspace ciphertext with the preferred key first, then configured
309
+ * legacy candidates. The result never exposes which secret material matched.
310
+ */
311
+ export function decryptSharedSecretValueDetailed(
312
+ encrypted: string,
313
+ ): DecryptedSharedSecretValue {
314
+ const preferredKey = getSharedSecretEncryptionKey();
315
+ const candidateKeys = [preferredKey];
316
+ for (const material of sharedEncryptionKeyMaterials()) {
317
+ const candidate = hashSecretEncryptionKey(material);
318
+ if (!candidateKeys.some((existing) => existing.equals(candidate))) {
319
+ candidateKeys.push(candidate);
320
+ }
321
+ }
322
+
323
+ let lastError: unknown;
324
+ for (const candidate of candidateKeys) {
325
+ try {
326
+ return {
327
+ value: decryptWithKey(encrypted, candidate),
328
+ needsReencrypt: !candidate.equals(preferredKey),
329
+ };
330
+ } catch (error) {
331
+ lastError = error;
332
+ }
333
+ }
334
+ throw lastError instanceof Error
335
+ ? lastError
336
+ : new Error("Unable to decrypt workspace secret");
216
337
  }
217
338
 
218
339
  /**
@@ -2,12 +2,12 @@
2
2
  * Storage layer for the framework secrets registry.
3
3
  *
4
4
  * Values are encrypted at rest with AES-256-GCM. The workspace-shared
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.
5
+ * encryption key prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy
6
+ * combined `SECRETS_ENCRYPTION_KEY`, then the workspace-wide `A2A_SECRET`.
7
+ * A configured previous workspace key, `BETTER_AUTH_SECRET`, and app-scoped
8
+ * keys remain read fallbacks for rows written before sibling apps converged on
9
+ * the shared key. Successful fallback reads race-safely refresh the shared
10
+ * ciphertext.
11
11
  *
12
12
  * Secret values are NEVER logged and NEVER returned from any route handler.
13
13
  */
@@ -19,7 +19,7 @@ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
19
19
  import {
20
20
  encryptSecretValue as encryptLegacyValue,
21
21
  encryptSharedSecretValue as encryptValue,
22
- decryptSharedSecretValue as decryptValue,
22
+ decryptSharedSecretValueDetailed as decryptValue,
23
23
  decryptSecretValue as decryptLegacyValue,
24
24
  hasSharedSecretEncryptionKeyMaterial,
25
25
  } from "./crypto.js";
@@ -193,8 +193,9 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
193
193
  // pointing at data that's no longer current. Siblings then get an honest
194
194
  // cache miss (falling back to the legacy column or reporting missing) until
195
195
  // 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.
196
+ // `populateSharedAppSecret`, which fills a NULL column or compare-and-swap
197
+ // replaces the exact legacy ciphertext it just decrypted. A temporary miss
198
+ // is safer than serving rotated-away plaintext.
198
199
  const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)
199
200
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
200
201
  ON CONFLICT (scope, scope_id, key) DO UPDATE SET
@@ -253,8 +254,13 @@ interface DecryptedAppSecretValue {
253
254
  value: string;
254
255
  /** True when the app-scoped fallback decrypted encrypted_value. */
255
256
  usedLegacyKey: boolean;
256
- /** True when shared_encrypted_value was not the source of the plaintext. */
257
+ /** True when shared_encrypted_value needs to be created or refreshed. */
257
258
  needsSharedCiphertext: boolean;
259
+ /**
260
+ * Existing ciphertext that must still match before a refresh. Null means the
261
+ * migration may only fill an empty shared column.
262
+ */
263
+ sharedCiphertextToReplace?: string | null;
258
264
  }
259
265
 
260
266
  function decryptAppSecretValue(
@@ -263,10 +269,14 @@ function decryptAppSecretValue(
263
269
  ): DecryptedAppSecretValue {
264
270
  if (sharedEncrypted) {
265
271
  try {
272
+ const decrypted = decryptValue(sharedEncrypted);
266
273
  return {
267
- value: decryptValue(sharedEncrypted),
274
+ value: decrypted.value,
268
275
  usedLegacyKey: false,
269
- needsSharedCiphertext: false,
276
+ needsSharedCiphertext: decrypted.needsReencrypt,
277
+ sharedCiphertextToReplace: decrypted.needsReencrypt
278
+ ? sharedEncrypted
279
+ : undefined,
270
280
  };
271
281
  } catch {
272
282
  // Fall through to the legacy column. A partially migrated row may have
@@ -274,30 +284,34 @@ function decryptAppSecretValue(
274
284
  }
275
285
  }
276
286
  try {
287
+ const decrypted = decryptValue(encrypted);
277
288
  return {
278
- value: decryptValue(encrypted),
289
+ value: decrypted.value,
279
290
  usedLegacyKey: false,
280
291
  needsSharedCiphertext: true,
292
+ sharedCiphertextToReplace: sharedEncrypted ?? null,
281
293
  };
282
294
  } catch {
283
295
  return {
284
296
  value: decryptLegacyValue(encrypted),
285
297
  usedLegacyKey: true,
286
298
  needsSharedCiphertext: true,
299
+ sharedCiphertextToReplace: sharedEncrypted ?? null,
287
300
  };
288
301
  }
289
302
  }
290
303
 
291
304
  /**
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.
305
+ * Create or refresh the shared column without touching encrypted_value. This
306
+ * is intentionally best-effort: a read must still succeed if a deployment's
307
+ * DB role cannot update the row. The compare-and-swap predicate prevents a
308
+ * concurrent writer from being overwritten, and leaving updated_at untouched
309
+ * preserves the row's user-visible ordering/metadata.
297
310
  */
298
311
  async function populateSharedAppSecret(
299
312
  id: unknown,
300
313
  value: string,
314
+ sharedCiphertextToReplace: string | null,
301
315
  ): Promise<void> {
302
316
  if (
303
317
  id === undefined ||
@@ -307,10 +321,18 @@ async function populateSharedAppSecret(
307
321
  return;
308
322
  }
309
323
  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
- });
324
+ const encrypted = encryptValue(value);
325
+ if (sharedCiphertextToReplace === null) {
326
+ await getDbExec().execute({
327
+ sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,
328
+ args: [encrypted, id],
329
+ });
330
+ } else {
331
+ await getDbExec().execute({
332
+ sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value = ?`,
333
+ args: [encrypted, id, sharedCiphertextToReplace],
334
+ });
335
+ }
314
336
  } catch {
315
337
  // Migration is opportunistic. Preserve the successful read if the update
316
338
  // is unavailable due to a read-only role, transient DB failure, or race.
@@ -371,7 +393,11 @@ export async function readAppSecret(
371
393
  rows[0].shared_encrypted_value as string | null,
372
394
  );
373
395
  if (decrypted.needsSharedCiphertext) {
374
- await populateSharedAppSecret(rows[0].id, decrypted.value);
396
+ await populateSharedAppSecret(
397
+ rows[0].id,
398
+ decrypted.value,
399
+ decrypted.sharedCiphertextToReplace ?? null,
400
+ );
375
401
  }
376
402
  return {
377
403
  value: decrypted.value,
@@ -410,7 +436,11 @@ export async function readAppSecrets(args: {
410
436
  row.shared_encrypted_value as string | null,
411
437
  );
412
438
  if (decrypted.needsSharedCiphertext) {
413
- await populateSharedAppSecret(row.id, decrypted.value);
439
+ await populateSharedAppSecret(
440
+ row.id,
441
+ decrypted.value,
442
+ decrypted.sharedCiphertextToReplace ?? null,
443
+ );
414
444
  }
415
445
  results.set(key, {
416
446
  value: decrypted.value,
@@ -473,7 +503,11 @@ export async function readAppSecretMeta(
473
503
  row.shared_encrypted_value as string | null,
474
504
  );
475
505
  if (decrypted.needsSharedCiphertext) {
476
- await populateSharedAppSecret(row.id, decrypted.value);
506
+ await populateSharedAppSecret(
507
+ row.id,
508
+ decrypted.value,
509
+ decrypted.sharedCiphertextToReplace ?? null,
510
+ );
477
511
  }
478
512
  last4Value = last4(decrypted.value);
479
513
  } catch {
@@ -516,7 +550,11 @@ export async function listAppSecretsForScope(
516
550
  row.shared_encrypted_value as string | null,
517
551
  );
518
552
  if (decrypted.needsSharedCiphertext) {
519
- await populateSharedAppSecret(row.id, decrypted.value);
553
+ await populateSharedAppSecret(
554
+ row.id,
555
+ decrypted.value,
556
+ decrypted.sharedCiphertextToReplace ?? null,
557
+ );
520
558
  }
521
559
  last4Value = last4(decrypted.value);
522
560
  } 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,29 @@ 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
+ - Finish the objective autonomously in this delegated turn whenever a safe, reasonable default exists. Do not bounce the work back with UI-navigation requests, implementation questions, or intermediate status. Ask for input only when authorization, missing credentials, or a consequential user choice truly blocks progress.
364
+ - When evidence is incomplete, return the best grounded partial answer with explicit coverage gaps instead of refusing or asking the caller to choose your source, filter, dashboard, or workflow.
365
+ - Minimize round trips. Filter, join, aggregate, paginate, stage, and reduce large datasets inside your own tools rather than returning raw records or transcripts.
366
+ - 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.
367
+ </delegated-agent-contract>`;
368
+
283
369
  export interface DelegatedAgentLoopTelemetry {
284
370
  runId: string;
285
371
  threadId: string | null;
@@ -302,14 +388,19 @@ async function runDelegatedAgentLoop(
302
388
  runOptions: Parameters<A2AAgentLoopRunner>[0],
303
389
  pluginOptions: Pick<
304
390
  AgentChatPluginOptions,
305
- "finalResponseGuard" | "runSoftTimeoutMs"
391
+ "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
306
392
  >,
307
393
  timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
308
394
  options: DelegatedAgentLoopOptions,
309
395
  ) {
310
396
  const runner = options.runner ?? runAgentLoopDirectWithSoftTimeout;
397
+ const policy = pluginOptions.delegatedRunPolicy;
398
+ const configuredHardToolResultCap = runOptions.toolLimits?.hardMaxResultChars;
399
+ const delegatedHardToolResultCap =
400
+ policy?.maxToolResultChars ?? DEFAULT_DELEGATED_MAX_TOOL_RESULT_CHARS;
311
401
  const resolvedRunOptions = {
312
402
  ...runOptions,
403
+ systemPrompt: runOptions.systemPrompt + DELEGATED_AGENT_EXECUTION_CONTRACT,
313
404
  // Delegated runs resolve their own model and do not pass through the
314
405
  // interactive request handler's output-token setup. Use the same
315
406
  // model-aware headroom here so reasoning models (notably GPT-5.x) do
@@ -321,6 +412,21 @@ async function runDelegatedAgentLoop(
321
412
  reasoningEffort:
322
413
  runOptions.reasoningEffort ??
323
414
  resolveAgentRequestReasoningEffort({ model: runOptions.model }),
415
+ maxIterations:
416
+ runOptions.maxIterations ??
417
+ policy?.maxIterations ??
418
+ DEFAULT_DELEGATED_MAX_ITERATIONS,
419
+ maxRunInputTokens:
420
+ runOptions.maxRunInputTokens ??
421
+ policy?.maxRunInputTokens ??
422
+ DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKENS,
423
+ toolLimits: {
424
+ ...(runOptions.toolLimits ?? {}),
425
+ hardMaxResultChars:
426
+ typeof configuredHardToolResultCap === "number"
427
+ ? Math.min(configuredHardToolResultCap, delegatedHardToolResultCap)
428
+ : delegatedHardToolResultCap,
429
+ },
324
430
  finalResponseGuard: pluginOptions.finalResponseGuard,
325
431
  };
326
432
  const execute = (loopOptions = resolvedRunOptions) =>
@@ -369,7 +475,7 @@ export function runA2AAgentLoop(
369
475
  runOptions: Parameters<A2AAgentLoopRunner>[0],
370
476
  pluginOptions: Pick<
371
477
  AgentChatPluginOptions,
372
- "finalResponseGuard" | "runSoftTimeoutMs"
478
+ "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
373
479
  >,
374
480
  timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
375
481
  options: DelegatedAgentLoopOptions = {},
@@ -391,7 +497,7 @@ export function runMCPAgentLoop(
391
497
  runOptions: Parameters<A2AAgentLoopRunner>[0],
392
498
  pluginOptions: Pick<
393
499
  AgentChatPluginOptions,
394
- "finalResponseGuard" | "runSoftTimeoutMs"
500
+ "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
395
501
  >,
396
502
  timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
397
503
  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