@agent-native/core 0.149.1 → 0.149.3

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 (422) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +54 -488
  3. package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +136 -0
  4. package/corpus/templates/analytics/.agents/skills/actions/references/examples.md +94 -0
  5. package/corpus/templates/analytics/.agents/skills/actions/references/provider-apis.md +95 -0
  6. package/corpus/templates/analytics/.agents/skills/create-skill/SKILL.md +9 -35
  7. package/corpus/templates/analytics/.agents/skills/creative-context/SKILL.md +4 -2
  8. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
  9. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +4 -5
  10. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +18 -31
  11. package/corpus/templates/analytics/AGENTS.md +25 -32
  12. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
  13. package/corpus/templates/analytics/app/global.css +1 -1
  14. package/corpus/templates/analytics/app/i18n-data.ts +37 -0
  15. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  16. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
  17. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
  18. package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
  19. package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  20. package/corpus/templates/analytics/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
  21. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +54 -488
  22. package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +136 -0
  23. package/corpus/templates/assets/.agents/skills/actions/references/examples.md +94 -0
  24. package/corpus/templates/assets/.agents/skills/actions/references/provider-apis.md +95 -0
  25. package/corpus/templates/assets/.agents/skills/create-skill/SKILL.md +9 -35
  26. package/corpus/templates/assets/.agents/skills/creative-context/SKILL.md +4 -2
  27. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +4 -5
  28. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +18 -31
  29. package/corpus/templates/assets/AGENTS.md +6 -9
  30. package/corpus/templates/assets/app/global.css +1 -1
  31. package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  32. package/corpus/templates/assets/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
  33. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +54 -488
  34. package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +136 -0
  35. package/corpus/templates/brain/.agents/skills/actions/references/examples.md +94 -0
  36. package/corpus/templates/brain/.agents/skills/actions/references/provider-apis.md +95 -0
  37. package/corpus/templates/brain/.agents/skills/create-skill/SKILL.md +9 -35
  38. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +4 -5
  39. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +18 -31
  40. package/corpus/templates/brain/AGENTS.md +5 -12
  41. package/corpus/templates/brain/app/global.css +1 -1
  42. package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  43. package/corpus/templates/brain/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
  44. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +54 -488
  45. package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +136 -0
  46. package/corpus/templates/calendar/.agents/skills/actions/references/examples.md +94 -0
  47. package/corpus/templates/calendar/.agents/skills/actions/references/provider-apis.md +95 -0
  48. package/corpus/templates/calendar/.agents/skills/create-skill/SKILL.md +9 -35
  49. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +4 -5
  50. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +18 -31
  51. package/corpus/templates/calendar/AGENTS.md +8 -9
  52. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +54 -488
  53. package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +136 -0
  54. package/corpus/templates/chat/.agents/skills/actions/references/examples.md +94 -0
  55. package/corpus/templates/chat/.agents/skills/actions/references/provider-apis.md +95 -0
  56. package/corpus/templates/chat/.agents/skills/create-skill/SKILL.md +9 -35
  57. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +4 -5
  58. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -31
  59. package/corpus/templates/chat/AGENTS.md +32 -80
  60. package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  61. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +54 -488
  62. package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +136 -0
  63. package/corpus/templates/clips/.agents/skills/actions/references/examples.md +94 -0
  64. package/corpus/templates/clips/.agents/skills/actions/references/provider-apis.md +95 -0
  65. package/corpus/templates/clips/.agents/skills/create-skill/SKILL.md +9 -35
  66. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +4 -5
  67. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +18 -31
  68. package/corpus/templates/clips/AGENTS.md +25 -31
  69. package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
  70. package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
  71. package/corpus/templates/clips/desktop/src/app.tsx +274 -137
  72. package/corpus/templates/clips/desktop/src/styles.css +35 -0
  73. package/corpus/templates/content/.agents/skills/actions/SKILL.md +54 -488
  74. package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +136 -0
  75. package/corpus/templates/content/.agents/skills/actions/references/examples.md +94 -0
  76. package/corpus/templates/content/.agents/skills/actions/references/provider-apis.md +95 -0
  77. package/corpus/templates/content/.agents/skills/create-skill/SKILL.md +9 -35
  78. package/corpus/templates/content/.agents/skills/creative-context/SKILL.md +4 -2
  79. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +4 -5
  80. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +18 -31
  81. package/corpus/templates/content/AGENTS.md +5 -9
  82. package/corpus/templates/content/app/global.css +1 -0
  83. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +18 -31
  84. package/corpus/templates/crm/AGENTS.md +7 -11
  85. package/corpus/templates/crm/app/global.css +1 -1
  86. package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  87. package/corpus/templates/crm/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
  88. package/corpus/templates/design/.agents/skills/actions/SKILL.md +54 -488
  89. package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +136 -0
  90. package/corpus/templates/design/.agents/skills/actions/references/examples.md +94 -0
  91. package/corpus/templates/design/.agents/skills/actions/references/provider-apis.md +95 -0
  92. package/corpus/templates/design/.agents/skills/create-skill/SKILL.md +9 -35
  93. package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +4 -2
  94. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +4 -5
  95. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +18 -31
  96. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +70 -3
  97. package/corpus/templates/design/AGENTS.md +13 -21
  98. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +12 -0
  99. package/corpus/templates/design/app/components/design/LayersPanel.tsx +19 -2
  100. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +115 -7
  101. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +82 -7
  102. package/corpus/templates/design/app/components/design/design-trace.ts +120 -0
  103. package/corpus/templates/design/app/components/design/dnd-debug.ts +2 -1
  104. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +40 -26
  105. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +4 -1
  106. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +12 -3
  107. package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +4 -0
  108. package/corpus/templates/design/app/components/design/multi-screen/types.ts +7 -0
  109. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -2
  110. package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -2
  111. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -0
  112. package/corpus/templates/design/app/i18n-data.ts +1 -0
  113. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -5
  114. package/corpus/templates/design/app/pages/DesignSystems.tsx +3 -2
  115. package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +100 -13
  116. package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +1 -1
  117. package/corpus/templates/design/app/pages/design-editor/data-operations.ts +14 -0
  118. package/corpus/templates/design/changelog/2026-08-10-marquee-selecting-layers-far-down-a-tall-screen-now-selects-.md +6 -0
  119. package/corpus/templates/design/changelog/2026-08-10-the-frame-tool-draws-a-plain-frame-by-default-with-screen-av.md +6 -0
  120. package/corpus/templates/design/e2e/global-setup.ts +27 -32
  121. package/corpus/templates/design/playwright.config.ts +3 -5
  122. package/corpus/templates/design/shared/breakpoint-media.ts +9 -2
  123. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +54 -488
  124. package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +136 -0
  125. package/corpus/templates/dispatch/.agents/skills/actions/references/examples.md +94 -0
  126. package/corpus/templates/dispatch/.agents/skills/actions/references/provider-apis.md +95 -0
  127. package/corpus/templates/dispatch/.agents/skills/create-skill/SKILL.md +9 -35
  128. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +4 -5
  129. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +18 -31
  130. package/corpus/templates/dispatch/AGENTS.md +9 -16
  131. package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  132. package/corpus/templates/factory/.agents/skills/actions/SKILL.md +54 -488
  133. package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +136 -0
  134. package/corpus/templates/factory/.agents/skills/actions/references/examples.md +94 -0
  135. package/corpus/templates/factory/.agents/skills/actions/references/provider-apis.md +95 -0
  136. package/corpus/templates/factory/.agents/skills/create-skill/SKILL.md +9 -35
  137. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +4 -5
  138. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +18 -31
  139. package/corpus/templates/factory/AGENTS.md +10 -24
  140. package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  141. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +54 -488
  142. package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +136 -0
  143. package/corpus/templates/forms/.agents/skills/actions/references/examples.md +94 -0
  144. package/corpus/templates/forms/.agents/skills/actions/references/provider-apis.md +95 -0
  145. package/corpus/templates/forms/.agents/skills/create-skill/SKILL.md +9 -35
  146. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +4 -5
  147. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +18 -31
  148. package/corpus/templates/forms/AGENTS.md +11 -17
  149. package/corpus/templates/forms/app/global.css +1 -0
  150. package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  151. package/corpus/templates/forms/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
  152. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +4 -5
  153. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +18 -31
  154. package/corpus/templates/macros/AGENTS.md +8 -8
  155. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +54 -488
  156. package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +136 -0
  157. package/corpus/templates/mail/.agents/skills/actions/references/examples.md +94 -0
  158. package/corpus/templates/mail/.agents/skills/actions/references/provider-apis.md +95 -0
  159. package/corpus/templates/mail/.agents/skills/create-skill/SKILL.md +9 -35
  160. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +4 -5
  161. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +18 -31
  162. package/corpus/templates/mail/AGENTS.md +5 -6
  163. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +54 -488
  164. package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +136 -0
  165. package/corpus/templates/plan/.agents/skills/actions/references/examples.md +94 -0
  166. package/corpus/templates/plan/.agents/skills/actions/references/provider-apis.md +95 -0
  167. package/corpus/templates/plan/.agents/skills/create-skill/SKILL.md +9 -35
  168. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +4 -5
  169. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +18 -31
  170. package/corpus/templates/plan/AGENTS.md +5 -14
  171. package/corpus/templates/plan/app/global.css +1 -0
  172. package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  173. package/corpus/templates/plan/changelog/2026-08-11-plan-no-longer-reloads-when-runtime-state-changes.md +6 -0
  174. package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
  175. package/corpus/templates/plan/vite.config.ts +5 -0
  176. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +54 -488
  177. package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +136 -0
  178. package/corpus/templates/slides/.agents/skills/actions/references/examples.md +94 -0
  179. package/corpus/templates/slides/.agents/skills/actions/references/provider-apis.md +95 -0
  180. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -2
  181. package/corpus/templates/slides/.agents/skills/create-skill/SKILL.md +9 -35
  182. package/corpus/templates/slides/.agents/skills/creative-context/SKILL.md +6 -3
  183. package/corpus/templates/slides/.agents/skills/deck-management/SKILL.md +6 -1
  184. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +4 -5
  185. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +3 -2
  186. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +3 -0
  187. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +3 -3
  188. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +18 -31
  189. package/corpus/templates/slides/AGENTS.md +33 -35
  190. package/corpus/templates/slides/actions/add-deck.ts +2 -0
  191. package/corpus/templates/slides/actions/add-slide.ts +15 -1
  192. package/corpus/templates/slides/actions/create-deck.ts +18 -6
  193. package/corpus/templates/slides/actions/get-deck-reference-context.ts +2 -2
  194. package/corpus/templates/slides/actions/get-deck.ts +83 -7
  195. package/corpus/templates/slides/actions/import-file.ts +2 -1
  196. package/corpus/templates/slides/actions/patch-deck.ts +165 -9
  197. package/corpus/templates/slides/actions/save-deck.ts +24 -1
  198. package/corpus/templates/slides/actions/update-slide.ts +1 -1
  199. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +2 -2
  200. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -23
  201. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +2 -2
  202. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +6 -6
  203. package/corpus/templates/slides/app/components/editor/AnimationsPanel.tsx +3 -3
  204. package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +4 -4
  205. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +1 -1
  206. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +432 -668
  207. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +296 -259
  208. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
  209. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
  210. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
  211. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +241 -116
  212. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
  213. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +42 -14
  214. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +6 -12
  215. package/corpus/templates/slides/app/components/editor/editor-command-model.ts +35 -0
  216. package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
  217. package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
  218. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +8 -10
  219. package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
  220. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
  221. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
  222. package/corpus/templates/slides/app/context/DeckContext.tsx +2 -1
  223. package/corpus/templates/slides/app/global.css +76 -1
  224. package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
  225. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  226. package/corpus/templates/slides/app/lib/create-deck-generation.ts +52 -23
  227. package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
  228. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +104 -3
  229. package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -33
  230. package/corpus/templates/slides/app/pages/DesignSystems.tsx +2 -2
  231. package/corpus/templates/slides/app/pages/Index.tsx +315 -151
  232. package/corpus/templates/slides/app/root.tsx +43 -0
  233. package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
  234. package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
  235. package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
  236. package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
  237. package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
  238. package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
  239. package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
  240. package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
  241. package/corpus/templates/slides/changelog/2026-08-11-faster-slides-chat-deck-reads.md +6 -0
  242. package/corpus/templates/slides/changelog/2026-08-11-slides-chat-can-read-one-slide-without-loading-the-full-deck.md +6 -0
  243. package/corpus/templates/slides/changelog/2026-08-11-slides-clearly-indicate-when-an-ai-agent-is-editing-alongsid.md +6 -0
  244. package/corpus/templates/slides/package.json +1 -0
  245. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  246. package/corpus/templates/slides/server/lib/validate-slide-animations.ts +163 -0
  247. package/corpus/templates/slides/server/plugins/agent-chat.ts +8 -5
  248. package/corpus/templates/slides/shared/api.ts +47 -15
  249. package/corpus/templates/slides/shared/deck-title.ts +135 -0
  250. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +54 -488
  251. package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +136 -0
  252. package/corpus/templates/tasks/.agents/skills/actions/references/examples.md +94 -0
  253. package/corpus/templates/tasks/.agents/skills/actions/references/provider-apis.md +95 -0
  254. package/corpus/templates/tasks/.agents/skills/agent-watchdog/SKILL.md +1 -0
  255. package/corpus/templates/tasks/.agents/skills/create-skill/SKILL.md +9 -35
  256. package/corpus/templates/tasks/.agents/skills/efficient-fable/SKILL.md +1 -0
  257. package/corpus/templates/tasks/.agents/skills/efficient-frontier/SKILL.md +1 -0
  258. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +4 -5
  259. package/corpus/templates/tasks/.agents/skills/plan-arbiter/SKILL.md +1 -0
  260. package/corpus/templates/tasks/.agents/skills/plow-ahead/SKILL.md +1 -0
  261. package/corpus/templates/tasks/.agents/skills/quick-recap/SKILL.md +1 -0
  262. package/corpus/templates/tasks/.agents/skills/read-the-damn-docs/SKILL.md +1 -0
  263. package/corpus/templates/tasks/.agents/skills/stay-within-limits/SKILL.md +1 -0
  264. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +18 -31
  265. package/corpus/templates/tasks/AGENTS.md +5 -20
  266. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
  267. package/dist/agent/engine/ai-sdk-engine.js +1 -1
  268. package/dist/agent/production-agent.d.ts +21 -1
  269. package/dist/agent/production-agent.js +505 -355
  270. package/dist/agent/run-manager.js +156 -14
  271. package/dist/agent/run-store.d.ts +11 -10
  272. package/dist/agent/run-store.js +17 -13
  273. package/dist/agent/tool-approval-store.d.ts +18 -0
  274. package/dist/agent/tool-approval-store.js +122 -0
  275. package/dist/agent/types.d.ts +1 -1
  276. package/dist/cli/code-agent-executor.js +11 -2
  277. package/dist/client/AgentPanel.js +31 -6
  278. package/dist/client/AssistantChat.d.ts +9 -4
  279. package/dist/client/AssistantChat.js +221 -149
  280. package/dist/client/active-run-state.d.ts +9 -1
  281. package/dist/client/active-run-state.js +34 -3
  282. package/dist/client/agent-chat/index.d.ts +1 -1
  283. package/dist/client/agent-chat-adapter.d.ts +8 -0
  284. package/dist/client/agent-chat-adapter.js +125 -28
  285. package/dist/client/agent-chat.d.ts +1 -1
  286. package/dist/client/chat/markdown-renderer.js +14 -2
  287. package/dist/client/chat/message-components.d.ts +13 -3
  288. package/dist/client/chat/message-components.js +57 -52
  289. package/dist/client/chat/run-recovery.d.ts +1 -0
  290. package/dist/client/chat/run-recovery.js +8 -0
  291. package/dist/client/chat/tool-call-display.d.ts +5 -5
  292. package/dist/client/chat/tool-call-display.js +9 -212
  293. package/dist/client/chat-first/apps-rail.js +3 -3
  294. package/dist/client/chat-first/browser-pane.js +4 -1
  295. package/dist/client/chat-first/chat-history.d.ts +3 -1
  296. package/dist/client/chat-first/chat-history.js +7 -4
  297. package/dist/client/chat-first/surface-tabs.d.ts +1 -1
  298. package/dist/client/chat-first/surface-tabs.js +52 -41
  299. package/dist/client/chat-first/types.d.ts +3 -0
  300. package/dist/client/chat-first.d.ts +10 -0
  301. package/dist/client/chat-first.js +37 -2
  302. package/dist/client/components/ui/message-scroller.js +4 -1
  303. package/dist/client/i18n.js +3 -1
  304. package/dist/client/resources/McpConnectionSuggestion.js +9 -1
  305. package/dist/client/sse-event-processor.d.ts +4 -2
  306. package/dist/client/sse-event-processor.js +46 -6
  307. package/dist/client/use-chat-models.js +156 -80
  308. package/dist/client/use-run-stuck-detection.d.ts +2 -0
  309. package/dist/client/use-run-stuck-detection.js +85 -9
  310. package/dist/collab/awareness.d.ts +2 -2
  311. package/dist/collab/routes.d.ts +1 -1
  312. package/dist/collab/struct-routes.d.ts +1 -1
  313. package/dist/integrations/adapters/slack.d.ts +0 -4
  314. package/dist/integrations/adapters/slack.js +6 -30
  315. package/dist/integrations/catalog.js +1 -1
  316. package/dist/integrations/pending-tasks-store.d.ts +0 -8
  317. package/dist/integrations/pending-tasks-store.js +0 -21
  318. package/dist/integrations/slack-manifest.d.ts +2 -2
  319. package/dist/integrations/slack-manifest.js +0 -3
  320. package/dist/integrations/webhook-handler.js +1 -1
  321. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  322. package/dist/observability/routes.d.ts +3 -3
  323. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  324. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  325. package/dist/scripts/docs/source-search.js +2 -2
  326. package/dist/server/agent-chat/framework-prompts.js +7 -2
  327. package/dist/server/agent-chat/prompt-resources.js +2 -2
  328. package/dist/server/agent-chat/skill-frontmatter.d.ts +31 -2
  329. package/dist/server/agent-chat/skill-frontmatter.js +41 -15
  330. package/dist/server/agent-chat-plugin.js +3 -3
  331. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  332. package/dist/server/agents-bundle.d.ts +16 -19
  333. package/dist/server/agents-bundle.js +19 -35
  334. package/dist/server/auth.js +262 -31
  335. package/dist/server/better-auth-instance.d.ts +1 -0
  336. package/dist/server/better-auth-instance.js +17 -2
  337. package/dist/server/onboarding-html.js +56 -9
  338. package/dist/server/prompts/framework-core-compact.d.ts +6 -1
  339. package/dist/server/prompts/framework-core-compact.js +12 -9
  340. package/dist/server/prompts/framework-core.d.ts +6 -1
  341. package/dist/server/prompts/framework-core.js +11 -9
  342. package/dist/server/prompts/index.d.ts +1 -1
  343. package/dist/server/prompts/index.js +1 -1
  344. package/dist/server/prompts/shared-rules.d.ts +15 -11
  345. package/dist/server/prompts/shared-rules.js +17 -13
  346. package/dist/server/realtime-token.d.ts +1 -1
  347. package/dist/server/transcribe-voice.d.ts +1 -1
  348. package/dist/shared/reasoning-effort.d.ts +8 -8
  349. package/dist/shared/reasoning-effort.js +10 -10
  350. package/dist/shared/streaming-text-smoothing.js +41 -11
  351. package/dist/styles/agent-native.css +19 -32
  352. package/dist/templates/chat/.agents/skills/actions/SKILL.md +54 -488
  353. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +136 -0
  354. package/dist/templates/chat/.agents/skills/actions/references/examples.md +94 -0
  355. package/dist/templates/chat/.agents/skills/actions/references/provider-apis.md +95 -0
  356. package/dist/templates/chat/.agents/skills/create-skill/SKILL.md +9 -35
  357. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +4 -5
  358. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -31
  359. package/dist/templates/chat/AGENTS.md +32 -80
  360. package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  361. package/dist/templates/default/.agents/skills/actions/SKILL.md +54 -488
  362. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +136 -0
  363. package/dist/templates/default/.agents/skills/actions/references/examples.md +94 -0
  364. package/dist/templates/default/.agents/skills/actions/references/provider-apis.md +95 -0
  365. package/dist/templates/default/.agents/skills/create-skill/SKILL.md +9 -35
  366. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +4 -5
  367. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +18 -31
  368. package/dist/templates/headless/.agents/skills/actions/SKILL.md +54 -488
  369. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +136 -0
  370. package/dist/templates/headless/.agents/skills/actions/references/examples.md +94 -0
  371. package/dist/templates/headless/.agents/skills/actions/references/provider-apis.md +95 -0
  372. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +18 -31
  373. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +54 -488
  374. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +136 -0
  375. package/dist/templates/workspace-core/.agents/skills/actions/references/examples.md +94 -0
  376. package/dist/templates/workspace-core/.agents/skills/actions/references/provider-apis.md +95 -0
  377. package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +1 -0
  378. package/dist/templates/workspace-core/.agents/skills/agent-page/SKILL.md +1 -0
  379. package/dist/templates/workspace-core/.agents/skills/create-skill/SKILL.md +9 -35
  380. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +4 -5
  381. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +21 -29
  382. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +18 -31
  383. package/dist/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +7 -17
  384. package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +3 -3
  385. package/dist/templates/workspace-core/AGENTS.md +7 -16
  386. package/docs/content/messaging.mdx +6 -7
  387. package/docs/content/template-chat.mdx +1 -1
  388. package/package.json +2 -2
  389. package/src/templates/chat/.agents/skills/actions/SKILL.md +54 -488
  390. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +136 -0
  391. package/src/templates/chat/.agents/skills/actions/references/examples.md +94 -0
  392. package/src/templates/chat/.agents/skills/actions/references/provider-apis.md +95 -0
  393. package/src/templates/chat/.agents/skills/create-skill/SKILL.md +9 -35
  394. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +4 -5
  395. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -31
  396. package/src/templates/chat/AGENTS.md +32 -80
  397. package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
  398. package/src/templates/default/.agents/skills/actions/SKILL.md +54 -488
  399. package/src/templates/default/.agents/skills/actions/references/action-fields.md +136 -0
  400. package/src/templates/default/.agents/skills/actions/references/examples.md +94 -0
  401. package/src/templates/default/.agents/skills/actions/references/provider-apis.md +95 -0
  402. package/src/templates/default/.agents/skills/create-skill/SKILL.md +9 -35
  403. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +4 -5
  404. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +18 -31
  405. package/src/templates/headless/.agents/skills/actions/SKILL.md +54 -488
  406. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +136 -0
  407. package/src/templates/headless/.agents/skills/actions/references/examples.md +94 -0
  408. package/src/templates/headless/.agents/skills/actions/references/provider-apis.md +95 -0
  409. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +18 -31
  410. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +54 -488
  411. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +136 -0
  412. package/src/templates/workspace-core/.agents/skills/actions/references/examples.md +94 -0
  413. package/src/templates/workspace-core/.agents/skills/actions/references/provider-apis.md +95 -0
  414. package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +1 -0
  415. package/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +1 -0
  416. package/src/templates/workspace-core/.agents/skills/create-skill/SKILL.md +9 -35
  417. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +4 -5
  418. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +21 -29
  419. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +18 -31
  420. package/src/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +7 -17
  421. package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +3 -3
  422. package/src/templates/workspace-core/AGENTS.md +7 -16
@@ -13,7 +13,7 @@ import { extractMcpToolResultImages } from "../mcp-client/index.js";
13
13
  import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
14
14
  import { shouldInferSentimentForTurn } from "../observability/sentiment.js";
15
15
  import { completeRun as completeProgressRun, startRun as startProgressRun, updateRunProgress, } from "../progress/registry.js";
16
- import { getFrontmatterValue, parseFrontmatter, } from "../resources/metadata.js";
16
+ import { isRuntimeVisibleScope, parseSkillFrontmatter, } from "../server/agent-chat/skill-frontmatter.js";
17
17
  import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, readDeployCredentialEnv, } from "../server/credential-provider.js";
18
18
  import { readBody } from "../server/h3-helpers.js";
19
19
  import { getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
@@ -39,6 +39,7 @@ import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js
39
39
  import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, abortRunDurably, abortTurnDurably, tryClaimRunSlot, isHostedRuntime, resolveRunSoftTimeoutMs, resolveRunToolTimeoutCeilingMs, } from "./run-manager.js";
40
40
  import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, insertRun, insertRunEvent, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, } from "./run-store.js";
41
41
  import { buildCurrentTimeUserContext } from "./runtime-context.js";
42
+ import { consumeAgentToolApproval, createAgentToolApproval, } from "./tool-approval-store.js";
42
43
  import { findCompletedJournalEntry, } from "./tool-call-journal.js";
43
44
  import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
44
45
  import { describeToolResultImages, extractAgentImagesFromActionResult, } from "./tool-result-images.js";
@@ -688,7 +689,7 @@ const VISIBLE_RETRY_THRESHOLD_MS = 10_000;
688
689
  */
689
690
  const FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS = 25_000;
690
691
  // Raised from 1 -> 2 now that each retry actually adapts (raises the token
691
- // ceiling and steps reasoning effort down a tier) instead of re-issuing the
692
+ // ceiling and steps effort down a tier) instead of re-issuing the
692
693
  // exact same doomed request twice.
693
694
  const EMPTY_FINAL_RESPONSE_RETRY_LIMIT = 2;
694
695
  const MAIN_CHAT_INTERNAL_CONTINUATION_LIMIT = 6;
@@ -1198,9 +1199,9 @@ function escapeReferenceAttribute(value) {
1198
1199
  return value.replace(/&/g, "&").replace(/"/g, """);
1199
1200
  }
1200
1201
  function isRuntimeVisibleSkillContent(content) {
1201
- const frontmatter = parseFrontmatter(content);
1202
- const scope = getFrontmatterValue(frontmatter, "scope")?.trim().toLowerCase();
1203
- return scope !== "dev";
1202
+ // Route through the shared normalizer so an unrecognized `scope:` stays
1203
+ // invisible here too. A local `!== "dev"` check silently readmits it.
1204
+ return isRuntimeVisibleScope(parseSkillFrontmatter(content).scope);
1204
1205
  }
1205
1206
  export async function resolveSkillReferenceContent(ref) {
1206
1207
  if (!ref.path && !ref.name)
@@ -2120,6 +2121,8 @@ export async function executeAgentToolCall(options) {
2120
2121
  threadId: options.threadId,
2121
2122
  turnId: options.turnId,
2122
2123
  approvedToolCalls: options.approvedToolCalls,
2124
+ onApprovalRequired: options.onApprovalRequired,
2125
+ consumeApproval: options.consumeApproval,
2123
2126
  });
2124
2127
  }
2125
2128
  catch (error) {
@@ -2307,6 +2310,51 @@ function stableStringify(value) {
2307
2310
  export function toolCallCacheKey(toolName, input) {
2308
2311
  return `${toolName}:${stableStringify(normalizeToolCallInputForHistory(input))}`;
2309
2312
  }
2313
+ export function findApprovedStructuredToolCall(history, approvedToolCalls) {
2314
+ if (!Array.isArray(history) || !approvedToolCalls?.length)
2315
+ return undefined;
2316
+ const approved = new Set(approvedToolCalls);
2317
+ const calls = new Map();
2318
+ let match;
2319
+ for (const message of history) {
2320
+ if (message.role === "assistant") {
2321
+ for (const part of message.content) {
2322
+ if (part.type !== "tool-call")
2323
+ continue;
2324
+ const callId = typeof part.id === "string"
2325
+ ? part.id
2326
+ : typeof part.toolCallId === "string"
2327
+ ? part.toolCallId
2328
+ : "";
2329
+ const name = typeof part.name === "string"
2330
+ ? part.name
2331
+ : typeof part.toolName === "string"
2332
+ ? part.toolName
2333
+ : "";
2334
+ if (!callId || !name)
2335
+ continue;
2336
+ calls.set(callId, { name, input: part.input ?? part.args ?? {} });
2337
+ }
2338
+ continue;
2339
+ }
2340
+ for (const part of message.content) {
2341
+ if (part.type !== "tool-result")
2342
+ continue;
2343
+ const call = calls.get(part.toolCallId);
2344
+ if (!call || !approved.has(toolCallCacheKey(call.name, call.input))) {
2345
+ continue;
2346
+ }
2347
+ const result = part.content.toLowerCase();
2348
+ if (!result.includes("did not execute") &&
2349
+ !result.includes("awaiting human approval") &&
2350
+ !result.includes("waiting for your approval")) {
2351
+ continue;
2352
+ }
2353
+ match = { ...call, callId: part.toolCallId };
2354
+ }
2355
+ }
2356
+ return match;
2357
+ }
2310
2358
  const INTERRUPTED_TOOL_LEDGER_POLL_MS = process.env.NODE_ENV === "test" ? 0 : 2_000;
2311
2359
  async function waitForInterruptedToolLedgerEntry(opts) {
2312
2360
  const pollMs = INTERRUPTED_TOOL_LEDGER_POLL_MS;
@@ -3965,7 +4013,16 @@ export async function runAgentLoop(opts) {
3965
4013
  const approvalKey = toolCallCacheKey(toolCall.name, toolCall.input);
3966
4014
  // Consume a grant on its first exact match. A second identical call in
3967
4015
  // the same continuation must request its own human approval.
3968
- const wasApproved = approvedToolCallKeys.delete(approvalKey);
4016
+ const approvalBinding = {
4017
+ toolName: toolCall.name,
4018
+ input: toolCall.input,
4019
+ callId: toolCall.id,
4020
+ approvalKey,
4021
+ };
4022
+ const requestedApproval = approvedToolCallKeys.delete(approvalKey);
4023
+ const wasApproved = requestedApproval && opts.consumeApproval
4024
+ ? await opts.consumeApproval(approvalBinding)
4025
+ : requestedApproval;
3969
4026
  if (actionEntry.needsApproval && !wasApproved) {
3970
4027
  let mustApprove = false;
3971
4028
  try {
@@ -3989,6 +4046,7 @@ export async function runAgentLoop(opts) {
3989
4046
  mustApprove = true;
3990
4047
  }
3991
4048
  if (mustApprove) {
4049
+ await opts.onApprovalRequired?.(approvalBinding);
3992
4050
  send({
3993
4051
  type: "tool_start",
3994
4052
  id: toolCall.id,
@@ -4231,370 +4289,401 @@ export async function runAgentLoop(opts) {
4231
4289
  tool: toolCall.name,
4232
4290
  input: toolCall.input,
4233
4291
  });
4234
- const toolCallSchemaError = toolCallErrors.get(toolCall.id);
4235
- if (toolCallSchemaError && !toolInputNormalized) {
4236
- const result = finalizeToolErrorResult(toolInputSchemaErrorResult(toolCall.name, toolCallSchemaError.input, toolCallSchemaError.error, actionEntry?.tool.parameters));
4237
- send({
4238
- type: "tool_done",
4239
- id: toolCall.id,
4240
- tool: toolCall.name,
4241
- input: toolCall.input,
4242
- result,
4243
- isError: true,
4244
- completedSideEffect: false,
4245
- });
4246
- recordToolResult(result, true);
4247
- return {
4248
- type: "tool-result",
4249
- toolCallId: toolCall.id,
4250
- toolName: toolCall.name,
4251
- toolInput: wireToolInput,
4252
- content: result,
4253
- isError: true,
4254
- };
4255
- }
4256
- const rawToolInputError = validateRawToolInput(actionEntry, toolCall.input);
4257
- if (rawToolInputError) {
4258
- const result = finalizeToolErrorResult(toolInputSchemaErrorResult(toolCall.name, toolCall.input, rawToolInputError, actionEntry.tool.parameters));
4259
- send({
4260
- type: "tool_done",
4261
- id: toolCall.id,
4262
- tool: toolCall.name,
4263
- input: toolCall.input,
4264
- result,
4265
- isError: true,
4266
- completedSideEffect: false,
4267
- });
4268
- recordToolResult(result, true);
4269
- return {
4270
- type: "tool-result",
4271
- toolCallId: toolCall.id,
4272
- toolName: toolCall.name,
4273
- toolInput: wireToolInput,
4274
- content: result,
4275
- isError: true,
4276
- };
4277
- }
4278
- // dedupe: false opts a read-only tool out of the guard entirely — the
4279
- // cacheKey stays null so it never gets skipped-as-duplicate and never
4280
- // populates the cache (see the success handler below, which also
4281
- // leaves dedupe:false results uncached and un-cleared).
4282
- const cacheKey = actionEntry.readOnly === true && actionEntry.dedupe !== false
4283
- ? toolCallCacheKey(toolCall.name, toolCall.input)
4284
- : null;
4285
- if (cacheKey && readOnlyToolResultCache.has(cacheKey)) {
4286
- const previousResult = readOnlyToolResultCache.get(cacheKey) ?? "";
4287
- // `contextMessages` (not `messages`) is what the model actually sees
4288
- // this iteration — context-xray eviction/summarization and
4289
- // observational-memory trimming can drop the earlier result from
4290
- // view even though it's still cached here. Only strike-count the
4291
- // repeat when the model could have looked back and found it itself.
4292
- const visible = isCachedToolResultVisibleInContext(contextMessages, toolCall, previousResult);
4292
+ let toolDoneEmitted = false;
4293
+ const emitToolDone = (event) => {
4294
+ send(event);
4295
+ toolDoneEmitted = true;
4296
+ };
4297
+ // Every tool_start must have exactly one matching tool_done. Keeping the
4298
+ // whole post-start path inside this finally block turns unexpected
4299
+ // synchronous failures in validation, cache handling, or future branches
4300
+ // into an explicit tool error instead of leaving run-manager's in-flight
4301
+ // counter latched forever.
4302
+ try {
4303
+ const toolCallSchemaError = toolCallErrors.get(toolCall.id);
4304
+ if (toolCallSchemaError && !toolInputNormalized) {
4305
+ const result = finalizeToolErrorResult(toolInputSchemaErrorResult(toolCall.name, toolCallSchemaError.input, toolCallSchemaError.error, actionEntry?.tool.parameters));
4306
+ emitToolDone({
4307
+ type: "tool_done",
4308
+ id: toolCall.id,
4309
+ tool: toolCall.name,
4310
+ input: toolCall.input,
4311
+ result,
4312
+ isError: true,
4313
+ completedSideEffect: false,
4314
+ });
4315
+ recordToolResult(result, true);
4316
+ return {
4317
+ type: "tool-result",
4318
+ toolCallId: toolCall.id,
4319
+ toolName: toolCall.name,
4320
+ toolInput: wireToolInput,
4321
+ content: result,
4322
+ isError: true,
4323
+ };
4324
+ }
4325
+ const rawToolInputError = validateRawToolInput(actionEntry, toolCall.input);
4326
+ if (rawToolInputError) {
4327
+ const result = finalizeToolErrorResult(toolInputSchemaErrorResult(toolCall.name, toolCall.input, rawToolInputError, actionEntry.tool.parameters));
4328
+ emitToolDone({
4329
+ type: "tool_done",
4330
+ id: toolCall.id,
4331
+ tool: toolCall.name,
4332
+ input: toolCall.input,
4333
+ result,
4334
+ isError: true,
4335
+ completedSideEffect: false,
4336
+ });
4337
+ recordToolResult(result, true);
4338
+ return {
4339
+ type: "tool-result",
4340
+ toolCallId: toolCall.id,
4341
+ toolName: toolCall.name,
4342
+ toolInput: wireToolInput,
4343
+ content: result,
4344
+ isError: true,
4345
+ };
4346
+ }
4347
+ // dedupe: false opts a read-only tool out of the guard entirely — the
4348
+ // cacheKey stays null so it never gets skipped-as-duplicate and never
4349
+ // populates the cache (see the success handler below, which also
4350
+ // leaves dedupe:false results uncached and un-cleared).
4351
+ const cacheKey = actionEntry.readOnly === true && actionEntry.dedupe !== false
4352
+ ? toolCallCacheKey(toolCall.name, toolCall.input)
4353
+ : null;
4354
+ if (cacheKey && readOnlyToolResultCache.has(cacheKey)) {
4355
+ const previousResult = readOnlyToolResultCache.get(cacheKey) ?? "";
4356
+ // `contextMessages` (not `messages`) is what the model actually sees
4357
+ // this iteration — context-xray eviction/summarization and
4358
+ // observational-memory trimming can drop the earlier result from
4359
+ // view even though it's still cached here. Only strike-count the
4360
+ // repeat when the model could have looked back and found it itself.
4361
+ const visible = isCachedToolResultVisibleInContext(contextMessages, toolCall, previousResult);
4362
+ let result;
4363
+ if (visible) {
4364
+ const repeats = (duplicateReadOnlyToolCalls.get(cacheKey) ?? 0) + 1;
4365
+ duplicateReadOnlyToolCalls.set(cacheKey, repeats);
4366
+ result = visibleDuplicateReadOnlyToolResult(toolCall.name);
4367
+ if (repeats >= 3) {
4368
+ requestedActionStop ??= {
4369
+ message: "I stopped because the agent kept asking for the same read-only context it already had. Please send the request again if you want me to retry from a fresh turn.",
4370
+ errorCode: "duplicate_read_only_tool",
4371
+ };
4372
+ }
4373
+ }
4374
+ else {
4375
+ // The earlier result was trimmed out of the model's visible
4376
+ // context — this isn't a repetitive loop, the model legitimately
4377
+ // can't see the answer anymore. Re-serve it in full and don't
4378
+ // count a strike.
4379
+ duplicateReadOnlyToolCalls.set(cacheKey, 0);
4380
+ result = resurfacedDuplicateReadOnlyToolResult(toolCall.name, previousResult);
4381
+ }
4382
+ emitToolDone({
4383
+ type: "tool_done",
4384
+ id: toolCall.id,
4385
+ tool: toolCall.name,
4386
+ input: toolCall.input,
4387
+ result,
4388
+ completedSideEffect: false,
4389
+ });
4390
+ recordToolResult(result, false);
4391
+ return {
4392
+ type: "tool-result",
4393
+ toolCallId: toolCall.id,
4394
+ toolName: toolCall.name,
4395
+ toolInput: wireToolInput,
4396
+ content: result,
4397
+ };
4398
+ }
4399
+ if (opts.executionMode === "plan" &&
4400
+ !isPlanModeToolCallAllowed(toolCall.name, toolCall.input, actionEntry)) {
4401
+ const result = planModeBlockedMessage(toolCall.name);
4402
+ emitToolDone({
4403
+ type: "tool_done",
4404
+ id: toolCall.id,
4405
+ tool: toolCall.name,
4406
+ input: toolCall.input,
4407
+ result,
4408
+ isError: true,
4409
+ completedSideEffect: false,
4410
+ });
4411
+ recordToolResult(result, true);
4412
+ return {
4413
+ type: "tool-result",
4414
+ toolCallId: toolCall.id,
4415
+ toolName: toolCall.name,
4416
+ toolInput: wireToolInput,
4417
+ content: result,
4418
+ isError: true,
4419
+ };
4420
+ }
4293
4421
  let result;
4294
- if (visible) {
4295
- const repeats = (duplicateReadOnlyToolCalls.get(cacheKey) ?? 0) + 1;
4296
- duplicateReadOnlyToolCalls.set(cacheKey, repeats);
4297
- result = visibleDuplicateReadOnlyToolResult(toolCall.name);
4298
- if (repeats >= 3) {
4422
+ let isError = false;
4423
+ let mcpApp;
4424
+ let toolResultImages;
4425
+ try {
4426
+ // The run may have been aborted while we waited above for an
4427
+ // interrupted tool's ledger result (the wait can poll for minutes).
4428
+ // Re-check before invoking the action: starting it now would spawn a
4429
+ // fresh zombie execution — a duplicate side effect / double charge —
4430
+ // which the ledger-recovery path exists to prevent. The Promise.race
4431
+ // "Run aborted" leg below only rejects AFTER the action is invoked, so
4432
+ // it cannot guard this. Throw here instead, handled like any abort.
4433
+ if (signal.aborted) {
4434
+ throw new Error("Run aborted");
4435
+ }
4436
+ const timeoutSignal = AbortSignal.timeout(toolTimeoutMs);
4437
+ const actionUserEmail = opts.ownerEmail ?? getRequestUserEmail();
4438
+ const actionOrgId = opts.orgId ?? getRequestOrgId() ?? null;
4439
+ const actionContext = {
4440
+ send,
4441
+ userEmail: actionUserEmail ?? undefined,
4442
+ orgId: actionOrgId,
4443
+ appId: opts.appId,
4444
+ caller: opts.actionCaller ?? "tool",
4445
+ automation: opts.automation,
4446
+ networkProtocol: opts.networkProtocol,
4447
+ networkId: opts.networkId,
4448
+ networkPeer: opts.networkPeer,
4449
+ delegationDepth: opts.delegationDepth,
4450
+ visitedApps: opts.visitedApps,
4451
+ attachments: opts.attachments,
4452
+ signal,
4453
+ // Audit attribution: the action name + the agent thread/turn that
4454
+ // triggered this call, so a mutation can be traced to its run.
4455
+ actionName: toolCall.name,
4456
+ ...(wasApproved ? { approvedToolCallKey: approvalKey } : {}),
4457
+ ...(opts.threadId ? { threadId: opts.threadId } : {}),
4458
+ ...(opts.runId ? { runId: opts.runId } : {}),
4459
+ ...(opts.turnId ? { turnId: opts.turnId } : {}),
4460
+ };
4461
+ const requestContext = getRequestContext();
4462
+ const invokeAction = () => actionEntry.run(toolCall.input, actionContext);
4463
+ // Keep a reference to the action promise so we can attach a zombie-
4464
+ // detection continuation AFTER Promise.race abandons it on run abort.
4465
+ // The promise itself is not awaited here — Promise.race owns the await.
4466
+ const actionPromise = Promise.resolve(runWithRequestContext({
4467
+ ...(requestContext ?? {}),
4468
+ ...(actionUserEmail ? { userEmail: actionUserEmail } : {}),
4469
+ ...(actionOrgId ? { orgId: actionOrgId } : {}),
4470
+ ...(requestContext?.run ? { run: requestContext.run } : {}),
4471
+ }, invokeAction));
4472
+ // When the run is aborted (soft-timeout / user cancel) while this tool
4473
+ // call is in flight, Promise.race below will throw "Run aborted" and the
4474
+ // action's promise becomes a zombie — it keeps running but its result is
4475
+ // never returned to the loop. If the zombie eventually resolves, write
4476
+ // the result to the durable ledger keyed by (threadId, toolKey) so the
4477
+ // next continuation chunk can recover it instead of re-executing the
4478
+ // side effect.
4479
+ if (opts.threadId && !actionEntry.readOnly) {
4480
+ const ledgerThreadId = opts.threadId;
4481
+ const ledgerToolKey = toolCallCacheKey(toolCall.name, toolCall.input);
4482
+ actionPromise
4483
+ .then((zombieRaw) => {
4484
+ const zombieMcp = isMcpActionResult(zombieRaw)
4485
+ ? zombieRaw
4486
+ : null;
4487
+ if (zombieMcp &&
4488
+ zombieMcp.raw &&
4489
+ typeof zombieMcp.raw === "object" &&
4490
+ zombieMcp.raw.isError === true) {
4491
+ return;
4492
+ }
4493
+ const zombieText = zombieMcp ? zombieMcp.text : zombieRaw;
4494
+ const zombieStr = typeof zombieText === "string"
4495
+ ? zombieText
4496
+ : JSON.stringify(zombieText, null, 2);
4497
+ void writeLedgerEntry(ledgerThreadId, ledgerToolKey, zombieStr);
4498
+ })
4499
+ .catch(() => {
4500
+ // Action errored in the zombie — no result to ledger.
4501
+ });
4502
+ }
4503
+ const raw = await Promise.race([
4504
+ actionPromise,
4505
+ new Promise((_, reject) => {
4506
+ timeoutSignal.addEventListener("abort", () => reject(new Error(`Tool call timed out after ${toolTimeoutMs / 1000} seconds`)));
4507
+ }),
4508
+ // Stop waiting on the tool when the run itself is aborted (e.g. the
4509
+ // run-manager soft timeout, or a user cancel). Without this leg the
4510
+ // loop blocks on an in-flight tool for up to TOOL_TIMEOUT_MS after
4511
+ // the run signal has already fired.
4512
+ new Promise((_, reject) => {
4513
+ if (signal.aborted) {
4514
+ reject(new Error("Run aborted"));
4515
+ return;
4516
+ }
4517
+ signal.addEventListener("abort", () => reject(new Error("Run aborted")), { once: true });
4518
+ }),
4519
+ ]);
4520
+ const mcpResult = isMcpActionResult(raw) ? raw : null;
4521
+ const rawForAgent = mcpResult ? mcpResult.text : raw;
4522
+ if (mcpResult &&
4523
+ mcpResult.raw &&
4524
+ typeof mcpResult.raw === "object" &&
4525
+ mcpResult.raw.isError === true) {
4526
+ isError = true;
4527
+ }
4528
+ mcpApp = mcpResult?.mcpApp;
4529
+ // Demo mode is browser-local presentation state. The agent and MCP
4530
+ // layers always receive the real, access-scoped tool result.
4531
+ let resultForAgent = rawForAgent;
4532
+ // Vision images for the model: MCP tools return standard `image`
4533
+ // content parts; first-party actions opt in via the well-known
4534
+ // `_agentImages` result field (stripped from the JSON the model
4535
+ // reads). The images array
4536
+ // never touches the ledger — only the compact text notes appended
4537
+ // below are persisted.
4538
+ let imageNotes = [];
4539
+ if (mcpResult) {
4540
+ const mcpImages = extractMcpToolResultImages(mcpResult.raw);
4541
+ if (mcpImages.length > 0)
4542
+ toolResultImages = mcpImages;
4543
+ }
4544
+ else {
4545
+ const extracted = extractAgentImagesFromActionResult(resultForAgent);
4546
+ resultForAgent = extracted.value;
4547
+ imageNotes = extracted.notes;
4548
+ if (extracted.images.length > 0) {
4549
+ toolResultImages = extracted.images;
4550
+ }
4551
+ }
4552
+ if (toolResultImages) {
4553
+ imageNotes = [
4554
+ ...describeToolResultImages(toolResultImages),
4555
+ ...imageNotes,
4556
+ ];
4557
+ }
4558
+ let resultStr = typeof resultForAgent === "string"
4559
+ ? resultForAgent
4560
+ : JSON.stringify(resultForAgent, null, 2);
4561
+ if (resultStr.length > toolMaxResultChars) {
4562
+ const truncated = resultStr.slice(0, toolMaxResultChars);
4563
+ resultStr = `${truncated}\n\n...[truncated — full result was ${resultStr.length.toLocaleString()} chars; only first ${toolMaxResultChars.toLocaleString()} shown]`;
4564
+ }
4565
+ // Image notes go after truncation so they always survive into the
4566
+ // persisted result string (the durable record that an image existed).
4567
+ if (imageNotes.length > 0) {
4568
+ resultStr = `${resultStr}\n\n${imageNotes.join("\n")}`;
4569
+ }
4570
+ result = resultStr;
4571
+ if (toolCall.name === TOOL_SEARCH_ACTION_NAME && !isError) {
4572
+ const added = expandActiveTools(extractToolSearchResultNames(rawForAgent));
4573
+ if (added.length > 0) {
4574
+ result += `\n\nLoaded matching tool schemas for next step: ${added.join(", ")}`;
4575
+ }
4576
+ }
4577
+ }
4578
+ catch (err) {
4579
+ if (isAgentActionStopError(err)) {
4580
+ const message = sanitizeToolErrorValue(err.message) ||
4581
+ `Stopped after ${toolCall.name} failed.`;
4582
+ result = sanitizeToolErrorValue(err.toolResult || message);
4299
4583
  requestedActionStop ??= {
4300
- message: "I stopped because the agent kept asking for the same read-only context it already had. Please send the request again if you want me to retry from a fresh turn.",
4301
- errorCode: "duplicate_read_only_tool",
4584
+ message,
4585
+ ...(err.errorCode ? { errorCode: err.errorCode } : {}),
4302
4586
  };
4303
4587
  }
4588
+ else {
4589
+ const message = sanitizeToolErrorValue(err);
4590
+ result = `Error running ${toolCall.name}: ${message}${rateLimitRecoveryHint(message)}`;
4591
+ }
4592
+ isError = true;
4304
4593
  }
4305
- else {
4306
- // The earlier result was trimmed out of the model's visible
4307
- // context — this isn't a repetitive loop, the model legitimately
4308
- // can't see the answer anymore. Re-serve it in full and don't
4309
- // count a strike.
4310
- duplicateReadOnlyToolCalls.set(cacheKey, 0);
4311
- result = resurfacedDuplicateReadOnlyToolResult(toolCall.name, previousResult);
4594
+ if (isError) {
4595
+ result = finalizeToolErrorResult(result);
4312
4596
  }
4313
- send({
4314
- type: "tool_done",
4315
- id: toolCall.id,
4316
- tool: toolCall.name,
4317
- input: toolCall.input,
4318
- result,
4319
- completedSideEffect: false,
4320
- });
4321
- recordToolResult(result, false);
4322
- return {
4323
- type: "tool-result",
4324
- toolCallId: toolCall.id,
4325
- toolName: toolCall.name,
4326
- toolInput: wireToolInput,
4327
- content: result,
4328
- };
4329
- }
4330
- if (opts.executionMode === "plan" &&
4331
- !isPlanModeToolCallAllowed(toolCall.name, toolCall.input, actionEntry)) {
4332
- const result = planModeBlockedMessage(toolCall.name);
4333
- send({
4597
+ // Side-channel warnings raised anywhere inside the action's call stack
4598
+ // (see `action-warnings.ts`). Drained here rather than in the success
4599
+ // branch so an action that warns and then throws still surfaces it, and
4600
+ // so nothing stays pending to bleed into a later tool's result.
4601
+ const agentWarnings = drainAgentWarnings();
4602
+ if (agentWarnings.length > 0) {
4603
+ result = `${result}\n\n${formatAgentWarningsForToolResult(agentWarnings)}`;
4604
+ }
4605
+ // Auto-refresh the UI after a successful mutating tool call. Any call
4606
+ // that isn't read-only — by its own per-call Plan-mode effect, else the
4607
+ // action's readOnly flag — is assumed to mutate. The client's useDbSync
4608
+ // listener sees a change event with source:"action" and invalidates
4609
+ // ["action"] queries so list-* / get-* refetch. This makes refresh after
4610
+ // agent writes reliable without the model needing to remember to call
4611
+ // `refresh-screen` itself.
4612
+ if (!isError) {
4613
+ try {
4614
+ const { actionCallIsReadOnly, notifyActionChangeInBackground } = await import("../server/action-change.js");
4615
+ if (!actionCallIsReadOnly(actionEntry, toolCall.input, false)) {
4616
+ const owner = opts.ownerEmail ?? getRequestUserEmail() ?? undefined;
4617
+ const orgId = opts.orgId ?? getRequestOrgId() ?? undefined;
4618
+ notifyActionChangeInBackground({
4619
+ actionName: toolCall.name,
4620
+ ...(owner ? { owner } : {}),
4621
+ ...(orgId ? { orgId } : {}),
4622
+ });
4623
+ }
4624
+ }
4625
+ catch (error) {
4626
+ // poll module may be unavailable in non-server contexts — keep the
4627
+ // tool result, but make the failed background refresh observable.
4628
+ console.warn("Could not notify the action-change poller after a tool call", error);
4629
+ }
4630
+ }
4631
+ emitToolDone({
4334
4632
  type: "tool_done",
4335
4633
  id: toolCall.id,
4336
4634
  tool: toolCall.name,
4337
4635
  input: toolCall.input,
4338
4636
  result,
4339
- isError: true,
4340
- completedSideEffect: false,
4637
+ ...(isError ? { isError: true } : {}),
4638
+ ...(isError
4639
+ ? { completedSideEffect: false }
4640
+ : actionEntry.readOnly !== true
4641
+ ? { completedSideEffect: true }
4642
+ : {}),
4643
+ ...(mcpApp ? { mcpApp } : {}),
4644
+ ...(actionEntry.chatUI ? { chatUI: actionEntry.chatUI } : {}),
4341
4645
  });
4342
- recordToolResult(result, true);
4646
+ recordToolResult(result, isError);
4647
+ if (!isError) {
4648
+ if (cacheKey) {
4649
+ readOnlyToolResultCache.set(cacheKey, result);
4650
+ }
4651
+ else if (actionEntry.readOnly !== true) {
4652
+ // A genuine write invalidates all cached reads. A dedupe:false
4653
+ // read-only tool also has a null cacheKey (see above) but must NOT
4654
+ // clear the cache — it isn't a write and other tools' cached reads
4655
+ // are still valid.
4656
+ readOnlyToolResultCache.clear();
4657
+ duplicateReadOnlyToolCalls.clear();
4658
+ }
4659
+ }
4343
4660
  return {
4344
4661
  type: "tool-result",
4345
4662
  toolCallId: toolCall.id,
4346
4663
  toolName: toolCall.name,
4347
4664
  toolInput: wireToolInput,
4348
4665
  content: result,
4349
- isError: true,
4666
+ ...(isError ? { isError } : {}),
4667
+ ...(!isError && toolResultImages && toolResultImages.length > 0
4668
+ ? { images: toolResultImages }
4669
+ : {}),
4350
4670
  };
4351
4671
  }
4352
- let result;
4353
- let isError = false;
4354
- let mcpApp;
4355
- let toolResultImages;
4356
- try {
4357
- // The run may have been aborted while we waited above for an
4358
- // interrupted tool's ledger result (the wait can poll for minutes).
4359
- // Re-check before invoking the action: starting it now would spawn a
4360
- // fresh zombie execution — a duplicate side effect / double charge —
4361
- // which the ledger-recovery path exists to prevent. The Promise.race
4362
- // "Run aborted" leg below only rejects AFTER the action is invoked, so
4363
- // it cannot guard this. Throw here instead, handled like any abort.
4364
- if (signal.aborted) {
4365
- throw new Error("Run aborted");
4366
- }
4367
- const timeoutSignal = AbortSignal.timeout(toolTimeoutMs);
4368
- const actionUserEmail = opts.ownerEmail ?? getRequestUserEmail();
4369
- const actionOrgId = opts.orgId ?? getRequestOrgId() ?? null;
4370
- const actionContext = {
4371
- send,
4372
- userEmail: actionUserEmail ?? undefined,
4373
- orgId: actionOrgId,
4374
- appId: opts.appId,
4375
- caller: opts.actionCaller ?? "tool",
4376
- automation: opts.automation,
4377
- networkProtocol: opts.networkProtocol,
4378
- networkId: opts.networkId,
4379
- networkPeer: opts.networkPeer,
4380
- delegationDepth: opts.delegationDepth,
4381
- visitedApps: opts.visitedApps,
4382
- attachments: opts.attachments,
4383
- signal,
4384
- // Audit attribution: the action name + the agent thread/turn that
4385
- // triggered this call, so a mutation can be traced to its run.
4386
- actionName: toolCall.name,
4387
- ...(wasApproved ? { approvedToolCallKey: approvalKey } : {}),
4388
- ...(opts.threadId ? { threadId: opts.threadId } : {}),
4389
- ...(opts.runId ? { runId: opts.runId } : {}),
4390
- ...(opts.turnId ? { turnId: opts.turnId } : {}),
4391
- };
4392
- const requestContext = getRequestContext();
4393
- const invokeAction = () => actionEntry.run(toolCall.input, actionContext);
4394
- // Keep a reference to the action promise so we can attach a zombie-
4395
- // detection continuation AFTER Promise.race abandons it on run abort.
4396
- // The promise itself is not awaited here — Promise.race owns the await.
4397
- const actionPromise = Promise.resolve(runWithRequestContext({
4398
- ...(requestContext ?? {}),
4399
- ...(actionUserEmail ? { userEmail: actionUserEmail } : {}),
4400
- ...(actionOrgId ? { orgId: actionOrgId } : {}),
4401
- ...(requestContext?.run ? { run: requestContext.run } : {}),
4402
- }, invokeAction));
4403
- // When the run is aborted (soft-timeout / user cancel) while this tool
4404
- // call is in flight, Promise.race below will throw "Run aborted" and the
4405
- // action's promise becomes a zombie — it keeps running but its result is
4406
- // never returned to the loop. If the zombie eventually resolves, write
4407
- // the result to the durable ledger keyed by (threadId, toolKey) so the
4408
- // next continuation chunk can recover it instead of re-executing the
4409
- // side effect.
4410
- if (opts.threadId && !actionEntry.readOnly) {
4411
- const ledgerThreadId = opts.threadId;
4412
- const ledgerToolKey = toolCallCacheKey(toolCall.name, toolCall.input);
4413
- actionPromise
4414
- .then((zombieRaw) => {
4415
- const zombieMcp = isMcpActionResult(zombieRaw) ? zombieRaw : null;
4416
- if (zombieMcp &&
4417
- zombieMcp.raw &&
4418
- typeof zombieMcp.raw === "object" &&
4419
- zombieMcp.raw.isError === true) {
4420
- return;
4421
- }
4422
- const zombieText = zombieMcp ? zombieMcp.text : zombieRaw;
4423
- const zombieStr = typeof zombieText === "string"
4424
- ? zombieText
4425
- : JSON.stringify(zombieText, null, 2);
4426
- void writeLedgerEntry(ledgerThreadId, ledgerToolKey, zombieStr);
4427
- })
4428
- .catch(() => {
4429
- // Action errored in the zombie — no result to ledger.
4672
+ finally {
4673
+ if (!toolDoneEmitted) {
4674
+ const result = `Error running ${toolCall.name}: tool execution ended before returning a result.`;
4675
+ emitToolDone({
4676
+ type: "tool_done",
4677
+ id: toolCall.id,
4678
+ tool: toolCall.name,
4679
+ input: toolCall.input,
4680
+ result,
4681
+ isError: true,
4682
+ completedSideEffect: false,
4430
4683
  });
4431
- }
4432
- const raw = await Promise.race([
4433
- actionPromise,
4434
- new Promise((_, reject) => {
4435
- timeoutSignal.addEventListener("abort", () => reject(new Error(`Tool call timed out after ${toolTimeoutMs / 1000} seconds`)));
4436
- }),
4437
- // Stop waiting on the tool when the run itself is aborted (e.g. the
4438
- // run-manager soft timeout, or a user cancel). Without this leg the
4439
- // loop blocks on an in-flight tool for up to TOOL_TIMEOUT_MS after
4440
- // the run signal has already fired.
4441
- new Promise((_, reject) => {
4442
- if (signal.aborted) {
4443
- reject(new Error("Run aborted"));
4444
- return;
4445
- }
4446
- signal.addEventListener("abort", () => reject(new Error("Run aborted")), { once: true });
4447
- }),
4448
- ]);
4449
- const mcpResult = isMcpActionResult(raw) ? raw : null;
4450
- const rawForAgent = mcpResult ? mcpResult.text : raw;
4451
- if (mcpResult &&
4452
- mcpResult.raw &&
4453
- typeof mcpResult.raw === "object" &&
4454
- mcpResult.raw.isError === true) {
4455
- isError = true;
4456
- }
4457
- mcpApp = mcpResult?.mcpApp;
4458
- // Demo mode is browser-local presentation state. The agent and MCP
4459
- // layers always receive the real, access-scoped tool result.
4460
- let resultForAgent = rawForAgent;
4461
- // Vision images for the model: MCP tools return standard `image`
4462
- // content parts; first-party actions opt in via the well-known
4463
- // `_agentImages` result field (stripped from the JSON the model
4464
- // reads). The images array
4465
- // never touches the ledger — only the compact text notes appended
4466
- // below are persisted.
4467
- let imageNotes = [];
4468
- if (mcpResult) {
4469
- const mcpImages = extractMcpToolResultImages(mcpResult.raw);
4470
- if (mcpImages.length > 0)
4471
- toolResultImages = mcpImages;
4472
- }
4473
- else {
4474
- const extracted = extractAgentImagesFromActionResult(resultForAgent);
4475
- resultForAgent = extracted.value;
4476
- imageNotes = extracted.notes;
4477
- if (extracted.images.length > 0) {
4478
- toolResultImages = extracted.images;
4479
- }
4480
- }
4481
- if (toolResultImages) {
4482
- imageNotes = [
4483
- ...describeToolResultImages(toolResultImages),
4484
- ...imageNotes,
4485
- ];
4486
- }
4487
- let resultStr = typeof resultForAgent === "string"
4488
- ? resultForAgent
4489
- : JSON.stringify(resultForAgent, null, 2);
4490
- if (resultStr.length > toolMaxResultChars) {
4491
- const truncated = resultStr.slice(0, toolMaxResultChars);
4492
- resultStr = `${truncated}\n\n...[truncated — full result was ${resultStr.length.toLocaleString()} chars; only first ${toolMaxResultChars.toLocaleString()} shown]`;
4493
- }
4494
- // Image notes go after truncation so they always survive into the
4495
- // persisted result string (the durable record that an image existed).
4496
- if (imageNotes.length > 0) {
4497
- resultStr = `${resultStr}\n\n${imageNotes.join("\n")}`;
4498
- }
4499
- result = resultStr;
4500
- if (toolCall.name === TOOL_SEARCH_ACTION_NAME && !isError) {
4501
- const added = expandActiveTools(extractToolSearchResultNames(rawForAgent));
4502
- if (added.length > 0) {
4503
- result += `\n\nLoaded matching tool schemas for next step: ${added.join(", ")}`;
4504
- }
4505
- }
4506
- }
4507
- catch (err) {
4508
- if (isAgentActionStopError(err)) {
4509
- const message = sanitizeToolErrorValue(err.message) ||
4510
- `Stopped after ${toolCall.name} failed.`;
4511
- result = sanitizeToolErrorValue(err.toolResult || message);
4512
- requestedActionStop ??= {
4513
- message,
4514
- ...(err.errorCode ? { errorCode: err.errorCode } : {}),
4515
- };
4516
- }
4517
- else {
4518
- const message = sanitizeToolErrorValue(err);
4519
- result = `Error running ${toolCall.name}: ${message}${rateLimitRecoveryHint(message)}`;
4520
- }
4521
- isError = true;
4522
- }
4523
- if (isError) {
4524
- result = finalizeToolErrorResult(result);
4525
- }
4526
- // Side-channel warnings raised anywhere inside the action's call stack
4527
- // (see `action-warnings.ts`). Drained here rather than in the success
4528
- // branch so an action that warns and then throws still surfaces it, and
4529
- // so nothing stays pending to bleed into a later tool's result.
4530
- const agentWarnings = drainAgentWarnings();
4531
- if (agentWarnings.length > 0) {
4532
- result = `${result}\n\n${formatAgentWarningsForToolResult(agentWarnings)}`;
4533
- }
4534
- // Auto-refresh the UI after a successful mutating tool call. Any call
4535
- // that isn't read-only — by its own per-call Plan-mode effect, else the
4536
- // action's readOnly flag — is assumed to mutate. The client's useDbSync
4537
- // listener sees a change event with source:"action" and invalidates
4538
- // ["action"] queries so list-* / get-* refetch. This makes refresh after
4539
- // agent writes reliable without the model needing to remember to call
4540
- // `refresh-screen` itself.
4541
- if (!isError) {
4542
- try {
4543
- const { actionCallIsReadOnly, notifyActionChangeInBackground } = await import("../server/action-change.js");
4544
- if (!actionCallIsReadOnly(actionEntry, toolCall.input, false)) {
4545
- const owner = opts.ownerEmail ?? getRequestUserEmail() ?? undefined;
4546
- const orgId = opts.orgId ?? getRequestOrgId() ?? undefined;
4547
- notifyActionChangeInBackground({
4548
- actionName: toolCall.name,
4549
- ...(owner ? { owner } : {}),
4550
- ...(orgId ? { orgId } : {}),
4551
- });
4552
- }
4553
- }
4554
- catch {
4555
- // poll module may be unavailable in non-server contexts — ignore
4556
- }
4557
- }
4558
- send({
4559
- type: "tool_done",
4560
- id: toolCall.id,
4561
- tool: toolCall.name,
4562
- input: toolCall.input,
4563
- result,
4564
- ...(isError ? { isError: true } : {}),
4565
- ...(isError
4566
- ? { completedSideEffect: false }
4567
- : actionEntry.readOnly !== true
4568
- ? { completedSideEffect: true }
4569
- : {}),
4570
- ...(mcpApp ? { mcpApp } : {}),
4571
- ...(actionEntry.chatUI ? { chatUI: actionEntry.chatUI } : {}),
4572
- });
4573
- recordToolResult(result, isError);
4574
- if (!isError) {
4575
- if (cacheKey) {
4576
- readOnlyToolResultCache.set(cacheKey, result);
4577
- }
4578
- else if (actionEntry.readOnly !== true) {
4579
- // A genuine write invalidates all cached reads. A dedupe:false
4580
- // read-only tool also has a null cacheKey (see above) but must NOT
4581
- // clear the cache — it isn't a write and other tools' cached reads
4582
- // are still valid.
4583
- readOnlyToolResultCache.clear();
4584
- duplicateReadOnlyToolCalls.clear();
4684
+ recordToolResult(result, true);
4585
4685
  }
4586
4686
  }
4587
- return {
4588
- type: "tool-result",
4589
- toolCallId: toolCall.id,
4590
- toolName: toolCall.name,
4591
- toolInput: wireToolInput,
4592
- content: result,
4593
- ...(isError ? { isError } : {}),
4594
- ...(!isError && toolResultImages && toolResultImages.length > 0
4595
- ? { images: toolResultImages }
4596
- : {}),
4597
- };
4598
4687
  };
4599
4688
  const getParallelBatchKind = (toolCall) => {
4600
4689
  const entry = actions[toolCall.name];
@@ -6433,6 +6522,12 @@ export function createProductionAgentHandler(options) {
6433
6522
  ...historyMessages,
6434
6523
  { role: "user", content: userContent },
6435
6524
  ];
6525
+ const requestedApprovedToolCalls = Array.isArray(body.approvedToolCalls) && body.approvedToolCalls.length > 0
6526
+ ? body.approvedToolCalls
6527
+ .filter((key) => typeof key === "string")
6528
+ .slice(0, 200)
6529
+ : undefined;
6530
+ const exactApprovedToolCall = findApprovedStructuredToolCall(structuredHistory, requestedApprovedToolCalls);
6436
6531
  const firstRequestPayloadDetail = buildFirstRequestPayloadDetail({
6437
6532
  isFirstRequest: history.length === 0,
6438
6533
  systemPrompt: requestSystemPrompt,
@@ -6486,6 +6581,38 @@ export function createProductionAgentHandler(options) {
6486
6581
  : typeof requestTurnId === "string" && requestTurnId.trim()
6487
6582
  ? requestTurnId.trim()
6488
6583
  : runId;
6584
+ const approvalStoreBinding = (binding) => {
6585
+ if (!ownerEmail) {
6586
+ throw new Error("Cannot create or consume an approval without an authenticated owner");
6587
+ }
6588
+ return {
6589
+ ownerEmail,
6590
+ orgId: getRequestOrgId() ?? null,
6591
+ threadId: effectiveThreadId,
6592
+ turnId: effectiveTurnId,
6593
+ toolName: binding.toolName,
6594
+ callId: binding.callId,
6595
+ approvalKey: binding.approvalKey,
6596
+ };
6597
+ };
6598
+ const approvalHooks = {
6599
+ onApprovalRequired: async (binding) => {
6600
+ await createAgentToolApproval(approvalStoreBinding(binding));
6601
+ },
6602
+ consumeApproval: async (binding) => {
6603
+ if (!ownerEmail)
6604
+ return false;
6605
+ return consumeAgentToolApproval(approvalStoreBinding(binding));
6606
+ },
6607
+ };
6608
+ const exactApprovedToolEntry = exactApprovedToolCall
6609
+ ? requestActions[exactApprovedToolCall.name]
6610
+ : undefined;
6611
+ const approvedToolCallsForExecution = exactApprovedToolCall && exactApprovedToolEntry?.needsApproval
6612
+ ? [
6613
+ toolCallCacheKey(exactApprovedToolCall.name, exactApprovedToolCall.input),
6614
+ ]
6615
+ : undefined;
6489
6616
  if (isBackgroundWorker &&
6490
6617
  (await isTurnAborted(effectiveThreadId, effectiveTurnId))) {
6491
6618
  await markRunAborted(runId, "user").catch(() => { });
@@ -7236,15 +7363,13 @@ export function createProductionAgentHandler(options) {
7236
7363
  ? { threadId: effectiveThreadId, turnId: effectiveTurnId }
7237
7364
  : {}),
7238
7365
  // Human-in-the-loop approval grants for this turn (sanitized — the
7239
- // request is untrusted; accept only a bounded list of string keys).
7240
- ...(Array.isArray(body.approvedToolCalls) &&
7241
- body.approvedToolCalls.length
7242
- ? {
7243
- approvedToolCalls: body.approvedToolCalls
7244
- .filter((k) => typeof k === "string")
7245
- .slice(0, 200),
7246
- }
7366
+ // request is untrusted; only the exact structured call is passed to
7367
+ // the loop, where the durable grant is consumed atomically.
7368
+ ...(approvedToolCallsForExecution
7369
+ ? { approvedToolCalls: approvedToolCallsForExecution }
7247
7370
  : {}),
7371
+ onApprovalRequired: approvalHooks.onApprovalRequired,
7372
+ consumeApproval: approvalHooks.consumeApproval,
7248
7373
  // A worker PROVEN to be running inside the real 15-min Netlify
7249
7374
  // `-background` function (`runsInBackgroundFunction`) has minutes of
7250
7375
  // budget left on THIS invocation. Let it catch a recoverable
@@ -7269,6 +7394,31 @@ export function createProductionAgentHandler(options) {
7269
7394
  send({ type: "activity", label: "Contacting model" });
7270
7395
  let instrumented = false;
7271
7396
  try {
7397
+ if (exactApprovedToolCall &&
7398
+ approvedToolCallsForExecution &&
7399
+ requestActions[exactApprovedToolCall.name]) {
7400
+ send({
7401
+ type: "activity",
7402
+ label: `Running approved ${exactApprovedToolCall.name} action`,
7403
+ tool: exactApprovedToolCall.name,
7404
+ });
7405
+ await executeAgentToolCall({
7406
+ actions: requestActions,
7407
+ name: exactApprovedToolCall.name,
7408
+ input: exactApprovedToolCall.input,
7409
+ callId: exactApprovedToolCall.callId,
7410
+ signal: agentLoopOpts.signal,
7411
+ ownerEmail,
7412
+ orgId: getRequestOrgId() ?? null,
7413
+ threadId: effectiveThreadId,
7414
+ turnId: effectiveTurnId,
7415
+ approvedToolCalls: approvedToolCallsForExecution,
7416
+ onApprovalRequired: approvalHooks.onApprovalRequired,
7417
+ consumeApproval: approvalHooks.consumeApproval,
7418
+ send,
7419
+ });
7420
+ return;
7421
+ }
7272
7422
  try {
7273
7423
  const { getObservabilityConfig, instrumentAgentLoop } = await import("../observability/traces.js");
7274
7424
  const obsConfig = await getObservabilityConfig();