@agent-native/core 0.100.2 → 0.101.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 (388) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +83 -0
  3. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
  4. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +7 -0
  5. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
  6. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +7 -0
  7. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
  8. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +14 -0
  9. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
  10. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +7 -0
  11. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
  12. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +7 -0
  13. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
  14. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +7 -0
  15. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
  16. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +7 -0
  17. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
  18. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +14 -0
  19. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
  20. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +7 -0
  21. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
  22. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +7 -0
  23. package/corpus/core/docs/content/pr-visual-recap.mdx +50 -32
  24. package/corpus/core/docs/content/template-plan.mdx +14 -0
  25. package/corpus/core/docs/content/toolkit-comments-review.mdx +16 -2
  26. package/corpus/core/package.json +4 -1
  27. package/corpus/core/src/a2a/index.ts +2 -1
  28. package/corpus/core/src/a2a/server.ts +34 -9
  29. package/corpus/core/src/agent/production-agent.ts +56 -10
  30. package/corpus/core/src/cli/create.ts +47 -19
  31. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
  32. package/corpus/core/src/cli/recap.ts +3 -4932
  33. package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +6 -4
  34. package/corpus/core/src/cli/skills-content/visual-recap-skill.ts +7 -1
  35. package/corpus/core/src/client/AgentPanel.tsx +19 -17
  36. package/corpus/core/src/client/analytics.ts +8 -1
  37. package/corpus/core/src/client/chat/markdown-renderer.tsx +54 -8
  38. package/corpus/core/src/client/chat/message-components.tsx +8 -2
  39. package/corpus/core/src/client/clipboard.ts +28 -1
  40. package/corpus/core/src/client/guided-questions.tsx +4 -0
  41. package/corpus/core/src/client/i18n.tsx +2 -1
  42. package/corpus/core/src/client/index.ts +4 -0
  43. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +108 -0
  44. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +482 -150
  45. package/corpus/core/src/client/review/index.ts +8 -0
  46. package/corpus/core/src/client/review/use-review.ts +35 -1
  47. package/corpus/core/src/client/use-agent-chat-context.ts +3 -2
  48. package/corpus/core/src/credentials/index.ts +41 -8
  49. package/corpus/core/src/extensions/fetch-tool.ts +33 -3
  50. package/corpus/core/src/notifications/channels.ts +20 -6
  51. package/corpus/core/src/review/actions/create-review-comment.ts +2 -1
  52. package/corpus/core/src/review/actions/get-review-feedback.ts +6 -11
  53. package/corpus/core/src/review/actions/list-review-comments.ts +38 -4
  54. package/corpus/core/src/review/actions/reply-review-comment.ts +33 -22
  55. package/corpus/core/src/review/actions/resolve-review-thread.ts +29 -2
  56. package/corpus/core/src/review/actions/send-review-thread-to-agent.ts +62 -0
  57. package/corpus/core/src/review/identity.ts +101 -0
  58. package/corpus/core/src/review/index.ts +14 -0
  59. package/corpus/core/src/review/store.ts +315 -8
  60. package/corpus/core/src/review/types.ts +4 -0
  61. package/corpus/core/src/secrets/crypto.ts +26 -10
  62. package/corpus/core/src/secrets/storage.ts +16 -3
  63. package/corpus/core/src/server/action-discovery.ts +4 -0
  64. package/corpus/core/src/server/action-routes.ts +125 -4
  65. package/corpus/core/src/server/agent-chat/plugin-options.ts +16 -0
  66. package/corpus/core/src/server/agent-chat-plugin.ts +61 -7
  67. package/corpus/core/src/server/derived-secret.ts +5 -1
  68. package/corpus/core/src/server/index.ts +8 -0
  69. package/corpus/core/src/vite/action-types-plugin.ts +4 -0
  70. package/corpus/templates/analytics/changelog/2026-07-14-daily-dashboard-email-captures-authenticate-with-a-sessi.md +6 -0
  71. package/corpus/templates/analytics/changelog/2026-07-14-long-running-cross-app-analyses-now-continue-reliably-in-the.md +6 -0
  72. package/corpus/templates/analytics/server/lib/dashboard-report.ts +156 -3
  73. package/corpus/templates/analytics/server/plugins/agent-chat.ts +8 -4
  74. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +402 -142
  75. package/corpus/templates/assets/app/i18n/zh-TW.ts +1 -0
  76. package/corpus/templates/assets/app/i18n-data.ts +1 -0
  77. package/corpus/templates/assets/app/routes/library.tsx +187 -48
  78. package/corpus/templates/assets/changelog/2026-07-13-merged-the-candidates-panel-into-the-library-as-a-drafts-tab.md +6 -0
  79. package/corpus/templates/chat/app/lib/agent-page.tsx +40 -0
  80. package/corpus/templates/chat/app/routes/agent.tsx +5 -3
  81. package/corpus/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
  82. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +5 -0
  83. package/corpus/templates/clips/AGENTS.md +4 -0
  84. package/corpus/templates/clips/actions/get-feature-flags.ts +30 -0
  85. package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
  86. package/corpus/templates/clips/actions/request-transcript.ts +41 -1
  87. package/corpus/templates/clips/actions/view-screen.ts +5 -9
  88. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  89. package/corpus/templates/clips/app/components/player/share-dialog.tsx +44 -81
  90. package/corpus/templates/clips/app/components/player/video-player.tsx +39 -1
  91. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -12
  92. package/corpus/templates/clips/app/hooks/use-mse-video-source.ts +146 -0
  93. package/corpus/templates/clips/app/i18n/ar-SA.ts +0 -8
  94. package/corpus/templates/clips/app/i18n/de-DE.ts +0 -9
  95. package/corpus/templates/clips/app/i18n/en-US.ts +0 -8
  96. package/corpus/templates/clips/app/i18n/es-ES.ts +0 -9
  97. package/corpus/templates/clips/app/i18n/fr-FR.ts +0 -9
  98. package/corpus/templates/clips/app/i18n/hi-IN.ts +0 -8
  99. package/corpus/templates/clips/app/i18n/ja-JP.ts +0 -9
  100. package/corpus/templates/clips/app/i18n/ko-KR.ts +0 -9
  101. package/corpus/templates/clips/app/i18n/pt-BR.ts +0 -9
  102. package/corpus/templates/clips/app/i18n/zh-CN.ts +0 -7
  103. package/corpus/templates/clips/app/i18n/zh-TW.ts +0 -7
  104. package/corpus/templates/clips/app/lib/capture-install-options.ts +21 -0
  105. package/corpus/templates/clips/app/lib/fmp4.ts +271 -0
  106. package/corpus/templates/clips/app/lib/mse-video-loader.ts +585 -0
  107. package/corpus/templates/clips/app/routes/_app.dictate.tsx +0 -4
  108. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -13
  109. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +2 -37
  110. package/corpus/templates/clips/app/routes/download.tsx +3 -2
  111. package/corpus/templates/clips/changelog/2026-07-10-shared-clips-now-start-playing-instantly-instead-of-showing-.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-07-14-clips-now-distinguishes-transcript-previews-from-incomplete-.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-07-14-desktop-app-prompts-stay-hidden-after-you-download-the-insta.md +6 -0
  114. package/corpus/templates/clips/changelog/2026-07-14-share-dialogs-show-separate-links-for-people-and-agents.md +6 -0
  115. package/corpus/templates/clips/desktop/src/lib/recorder.ts +5 -0
  116. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +10 -0
  117. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -0
  118. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +2518 -0
  119. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +353 -0
  120. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +558 -67
  121. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +11 -1
  122. package/corpus/templates/clips/desktop/src-tauri/src/remote_flags.rs +188 -0
  123. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +1 -1
  124. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +1 -0
  125. package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -0
  126. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +5 -1
  127. package/corpus/templates/clips/shared/feature-flags.ts +41 -0
  128. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +3 -0
  129. package/corpus/templates/design/.agents/skills/design-review-feedback/SKILL.md +42 -0
  130. package/corpus/templates/design/AGENTS.md +6 -0
  131. package/corpus/templates/design/README.md +1 -0
  132. package/corpus/templates/design/actions/navigate.ts +4 -4
  133. package/corpus/templates/design/actions/view-screen.ts +116 -2
  134. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  135. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +52 -59
  136. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +13 -9
  137. package/corpus/templates/design/app/components/design/EditPanel.tsx +79 -11
  138. package/corpus/templates/design/app/components/design/LayersPanel.tsx +78 -65
  139. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +161 -0
  140. package/corpus/templates/design/app/components/design/ReviewStatusControl.tsx +96 -0
  141. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +166 -0
  142. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  143. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +1077 -0
  144. package/corpus/templates/design/app/components/visual-editor/index.ts +1 -0
  145. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +42 -0
  146. package/corpus/templates/design/app/hooks/use-design-systems.ts +2 -2
  147. package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
  148. package/corpus/templates/design/app/hooks/use-question-flow.ts +7 -1
  149. package/corpus/templates/design/app/i18n/ar-SA.ts +56 -1
  150. package/corpus/templates/design/app/i18n/de-DE.ts +57 -1
  151. package/corpus/templates/design/app/i18n/en-US.ts +56 -1
  152. package/corpus/templates/design/app/i18n/es-ES.ts +56 -1
  153. package/corpus/templates/design/app/i18n/fr-FR.ts +57 -1
  154. package/corpus/templates/design/app/i18n/hi-IN.ts +56 -1
  155. package/corpus/templates/design/app/i18n/ja-JP.ts +56 -1
  156. package/corpus/templates/design/app/i18n/ko-KR.ts +56 -1
  157. package/corpus/templates/design/app/i18n/pt-BR.ts +57 -1
  158. package/corpus/templates/design/app/i18n/zh-CN.ts +56 -1
  159. package/corpus/templates/design/app/i18n/zh-TW.ts +51 -0
  160. package/corpus/templates/design/app/pages/Present.tsx +172 -14
  161. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +23 -0
  162. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +10 -0
  163. package/corpus/templates/design/app/pages/present-review-state.ts +18 -0
  164. package/corpus/templates/design/app/public-routes.ts +8 -0
  165. package/corpus/templates/design/app/root.tsx +7 -6
  166. package/corpus/templates/design/changelog/2026-07-13-dragged-design-assets-now-appear-at-the-visible-drop-locatio.md +6 -0
  167. package/corpus/templates/design/changelog/2026-07-13-reviewers-can-pin-comments-on-shared-designs-and-apply-verif.md +6 -0
  168. package/corpus/templates/design/server/plugins/agent-chat.ts +10 -0
  169. package/corpus/templates/design/server/plugins/auth.ts +7 -5
  170. package/corpus/templates/design/server/plugins/review.ts +9 -0
  171. package/corpus/templates/design/shared/review-anchor.ts +83 -0
  172. package/corpus/templates/design/shared/review-summary.ts +25 -0
  173. package/corpus/templates/dispatch/changelog/2026-07-14-dispatch-now-distinguishes-connected-agents-from-locally-mou.md +6 -0
  174. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +2 -0
  175. package/corpus/templates/forms/AGENTS.md +4 -0
  176. package/corpus/templates/forms/actions/create-form.ts +2 -1
  177. package/corpus/templates/forms/actions/update-form.ts +5 -2
  178. package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
  179. package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
  180. package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
  181. package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
  182. package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
  183. package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
  184. package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
  185. package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
  186. package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
  187. package/corpus/templates/forms/app/i18n/zh-CN.ts +3 -0
  188. package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
  189. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +19 -0
  190. package/corpus/templates/forms/changelog/2026-07-14-fixed-the-ask-forms-loading-input-appearing-above-the-welcom.md +6 -0
  191. package/corpus/templates/forms/changelog/2026-07-14-form-social-previews-now-show-the-form-title-description-and.md +6 -0
  192. package/corpus/templates/forms/changelog/2026-07-14-forms-can-email-the-form-owner-when-new-responses-arrive.md +6 -0
  193. package/corpus/templates/forms/server/handlers/submissions.ts +17 -0
  194. package/corpus/templates/forms/server/lib/form-og-image.ts +64 -6
  195. package/corpus/templates/forms/server/lib/public-form-ssr.ts +6 -2
  196. package/corpus/templates/forms/server/lib/response-email.ts +65 -0
  197. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  198. package/corpus/templates/forms/server/routes/api/forms/og/[...slug]/og.png.get.ts +184 -0
  199. package/corpus/templates/forms/shared/types.ts +2 -0
  200. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +6 -4
  201. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +7 -1
  202. package/dist/a2a/index.d.ts +2 -1
  203. package/dist/a2a/index.d.ts.map +1 -1
  204. package/dist/a2a/index.js +1 -1
  205. package/dist/a2a/index.js.map +1 -1
  206. package/dist/a2a/server.d.ts +25 -0
  207. package/dist/a2a/server.d.ts.map +1 -1
  208. package/dist/a2a/server.js +30 -6
  209. package/dist/a2a/server.js.map +1 -1
  210. package/dist/agent/production-agent.d.ts +2 -0
  211. package/dist/agent/production-agent.d.ts.map +1 -1
  212. package/dist/agent/production-agent.js +37 -12
  213. package/dist/agent/production-agent.js.map +1 -1
  214. package/dist/cli/create.d.ts +5 -3
  215. package/dist/cli/create.d.ts.map +1 -1
  216. package/dist/cli/create.js +44 -19
  217. package/dist/cli/create.js.map +1 -1
  218. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
  219. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  220. package/dist/cli/pr-visual-recap-workflow.js +2 -2
  221. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  222. package/dist/cli/recap.d.ts +3 -562
  223. package/dist/cli/recap.d.ts.map +1 -1
  224. package/dist/cli/recap.js +3 -3875
  225. package/dist/cli/recap.js.map +1 -1
  226. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  227. package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
  228. package/dist/cli/skills-content/visual-plan-skill.js +6 -4
  229. package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
  230. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  231. package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -1
  232. package/dist/cli/skills-content/visual-recap-skill.js +7 -1
  233. package/dist/cli/skills-content/visual-recap-skill.js.map +1 -1
  234. package/dist/client/AgentPanel.d.ts.map +1 -1
  235. package/dist/client/AgentPanel.js +3 -3
  236. package/dist/client/AgentPanel.js.map +1 -1
  237. package/dist/client/analytics.d.ts +5 -0
  238. package/dist/client/analytics.d.ts.map +1 -1
  239. package/dist/client/analytics.js +3 -1
  240. package/dist/client/analytics.js.map +1 -1
  241. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  242. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  243. package/dist/client/chat/markdown-renderer.js +36 -1
  244. package/dist/client/chat/markdown-renderer.js.map +1 -1
  245. package/dist/client/chat/message-components.d.ts.map +1 -1
  246. package/dist/client/chat/message-components.js +5 -2
  247. package/dist/client/chat/message-components.js.map +1 -1
  248. package/dist/client/clipboard.d.ts +3 -1
  249. package/dist/client/clipboard.d.ts.map +1 -1
  250. package/dist/client/clipboard.js +24 -1
  251. package/dist/client/clipboard.js.map +1 -1
  252. package/dist/client/guided-questions.d.ts +3 -1
  253. package/dist/client/guided-questions.d.ts.map +1 -1
  254. package/dist/client/guided-questions.js +2 -1
  255. package/dist/client/guided-questions.js.map +1 -1
  256. package/dist/client/i18n.d.ts.map +1 -1
  257. package/dist/client/i18n.js +3 -1
  258. package/dist/client/i18n.js.map +1 -1
  259. package/dist/client/index.d.ts +1 -1
  260. package/dist/client/index.d.ts.map +1 -1
  261. package/dist/client/index.js +1 -1
  262. package/dist/client/index.js.map +1 -1
  263. package/dist/client/review/ReviewCommentComposer.d.ts +18 -0
  264. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -0
  265. package/dist/client/review/ReviewCommentComposer.js +30 -0
  266. package/dist/client/review/ReviewCommentComposer.js.map +1 -0
  267. package/dist/client/review/ReviewThreadPanel.d.ts +28 -1
  268. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  269. package/dist/client/review/ReviewThreadPanel.js +151 -39
  270. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  271. package/dist/client/review/index.d.ts +3 -2
  272. package/dist/client/review/index.d.ts.map +1 -1
  273. package/dist/client/review/index.js +2 -1
  274. package/dist/client/review/index.js.map +1 -1
  275. package/dist/client/review/use-review.d.ts +23 -0
  276. package/dist/client/review/use-review.d.ts.map +1 -1
  277. package/dist/client/review/use-review.js +3 -0
  278. package/dist/client/review/use-review.js.map +1 -1
  279. package/dist/client/use-agent-chat-context.d.ts +1 -1
  280. package/dist/client/use-agent-chat-context.d.ts.map +1 -1
  281. package/dist/client/use-agent-chat-context.js +4 -2
  282. package/dist/client/use-agent-chat-context.js.map +1 -1
  283. package/dist/collab/awareness.d.ts +2 -2
  284. package/dist/collab/awareness.d.ts.map +1 -1
  285. package/dist/collab/struct-routes.d.ts +1 -1
  286. package/dist/credentials/index.d.ts +12 -7
  287. package/dist/credentials/index.d.ts.map +1 -1
  288. package/dist/credentials/index.js +33 -8
  289. package/dist/credentials/index.js.map +1 -1
  290. package/dist/extensions/fetch-tool.d.ts +13 -1
  291. package/dist/extensions/fetch-tool.d.ts.map +1 -1
  292. package/dist/extensions/fetch-tool.js +9 -2
  293. package/dist/extensions/fetch-tool.js.map +1 -1
  294. package/dist/notifications/channels.js +20 -4
  295. package/dist/notifications/channels.js.map +1 -1
  296. package/dist/observability/routes.d.ts +5 -5
  297. package/dist/progress/routes.d.ts +1 -1
  298. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  299. package/dist/resources/handlers.d.ts +3 -3
  300. package/dist/review/actions/create-review-comment.js +2 -1
  301. package/dist/review/actions/create-review-comment.js.map +1 -1
  302. package/dist/review/actions/get-review-feedback.js +6 -11
  303. package/dist/review/actions/get-review-feedback.js.map +1 -1
  304. package/dist/review/actions/list-review-comments.d.ts +1 -0
  305. package/dist/review/actions/list-review-comments.js +25 -4
  306. package/dist/review/actions/list-review-comments.js.map +1 -1
  307. package/dist/review/actions/reply-review-comment.js +12 -5
  308. package/dist/review/actions/reply-review-comment.js.map +1 -1
  309. package/dist/review/actions/resolve-review-thread.d.ts +3 -0
  310. package/dist/review/actions/resolve-review-thread.js +20 -3
  311. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  312. package/dist/review/actions/send-review-thread-to-agent.d.ts +17 -0
  313. package/dist/review/actions/send-review-thread-to-agent.d.ts.map +1 -0
  314. package/dist/review/actions/send-review-thread-to-agent.js +49 -0
  315. package/dist/review/actions/send-review-thread-to-agent.js.map +1 -0
  316. package/dist/review/identity.d.ts +6 -0
  317. package/dist/review/identity.d.ts.map +1 -0
  318. package/dist/review/identity.js +68 -0
  319. package/dist/review/identity.js.map +1 -0
  320. package/dist/review/index.d.ts +3 -1
  321. package/dist/review/index.d.ts.map +1 -1
  322. package/dist/review/index.js +2 -1
  323. package/dist/review/index.js.map +1 -1
  324. package/dist/review/store.d.ts +36 -0
  325. package/dist/review/store.d.ts.map +1 -1
  326. package/dist/review/store.js +223 -8
  327. package/dist/review/store.js.map +1 -1
  328. package/dist/review/types.d.ts +4 -0
  329. package/dist/review/types.d.ts.map +1 -1
  330. package/dist/review/types.js.map +1 -1
  331. package/dist/secrets/crypto.d.ts +15 -7
  332. package/dist/secrets/crypto.d.ts.map +1 -1
  333. package/dist/secrets/crypto.js +23 -10
  334. package/dist/secrets/crypto.js.map +1 -1
  335. package/dist/secrets/routes.d.ts +9 -9
  336. package/dist/secrets/storage.d.ts.map +1 -1
  337. package/dist/secrets/storage.js +16 -3
  338. package/dist/secrets/storage.js.map +1 -1
  339. package/dist/server/action-discovery.d.ts.map +1 -1
  340. package/dist/server/action-discovery.js +4 -0
  341. package/dist/server/action-discovery.js.map +1 -1
  342. package/dist/server/action-routes.d.ts +45 -0
  343. package/dist/server/action-routes.d.ts.map +1 -1
  344. package/dist/server/action-routes.js +71 -5
  345. package/dist/server/action-routes.js.map +1 -1
  346. package/dist/server/agent-chat/plugin-options.d.ts +16 -0
  347. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  348. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  349. package/dist/server/agent-chat-plugin.d.ts +20 -0
  350. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  351. package/dist/server/agent-chat-plugin.js +39 -4
  352. package/dist/server/agent-chat-plugin.js.map +1 -1
  353. package/dist/server/derived-secret.d.ts +1 -1
  354. package/dist/server/derived-secret.d.ts.map +1 -1
  355. package/dist/server/derived-secret.js.map +1 -1
  356. package/dist/server/index.d.ts +3 -1
  357. package/dist/server/index.d.ts.map +1 -1
  358. package/dist/server/index.js +2 -0
  359. package/dist/server/index.js.map +1 -1
  360. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  361. package/dist/vite/action-types-plugin.js +4 -0
  362. package/dist/vite/action-types-plugin.js.map +1 -1
  363. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +48 -30
  364. package/docs/content/locales/ar-SA/template-plan.mdx +7 -0
  365. package/docs/content/locales/de-DE/pr-visual-recap.mdx +48 -30
  366. package/docs/content/locales/de-DE/template-plan.mdx +7 -0
  367. package/docs/content/locales/es-ES/pr-visual-recap.mdx +48 -30
  368. package/docs/content/locales/es-ES/template-plan.mdx +14 -0
  369. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +48 -30
  370. package/docs/content/locales/fr-FR/template-plan.mdx +7 -0
  371. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +48 -30
  372. package/docs/content/locales/hi-IN/template-plan.mdx +7 -0
  373. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +48 -30
  374. package/docs/content/locales/ja-JP/template-plan.mdx +7 -0
  375. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +48 -30
  376. package/docs/content/locales/ko-KR/template-plan.mdx +7 -0
  377. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +48 -30
  378. package/docs/content/locales/pt-BR/template-plan.mdx +14 -0
  379. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +48 -30
  380. package/docs/content/locales/zh-CN/template-plan.mdx +7 -0
  381. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +48 -30
  382. package/docs/content/locales/zh-TW/template-plan.mdx +7 -0
  383. package/docs/content/pr-visual-recap.mdx +50 -32
  384. package/docs/content/template-plan.mdx +14 -0
  385. package/docs/content/toolkit-comments-review.mdx +16 -2
  386. package/package.json +4 -1
  387. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +0 -68
  388. package/corpus/templates/forms/server/routes/api/forms/og/[slug]/og.png.get.ts +0 -45
@@ -1 +1 @@
1
- {"version":3,"file":"use-review.js","sourceRoot":"","sources":["../../../src/client/review/use-review.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA8ErE,MAAM,UAAU,iBAAiB,CAC/B,MAAgC,EAChC,OAA+B;IAE/B,OAAO,cAAc,CACnB,sBAAsB,EACtB,MAAM,EACN;QACE,OAAO,EACL,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,UAAU,CAAC;KACxE,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA+B,EAC/B,OAA+B;IAE/B,OAAO,cAAc,CACnB,qBAAqB,EACrB,MAAM,EACN;QACE,OAAO,EACL,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,UAAU,CAAC;KACxE,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,CACtB,uBAAuB,CACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,iBAAiB,CACtB,sBAAsB,CACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,CAGtB,uBAAuB,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,CAGtB,uBAAuB,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,iBAAiB,CAGtB,yBAAyB,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,iBAAiB,CACtB,mBAAmB,CACpB,CAAC;AACJ,CAAC","sourcesContent":["import type {\n ReviewComment,\n ReviewCommentKind,\n ReviewMention,\n ReviewResolutionTarget,\n ReviewStatus,\n ReviewStatusEntry,\n} from \"../../review/types.js\";\nimport { useActionMutation, useActionQuery } from \"../use-action.js\";\n\nexport interface ListReviewCommentsParams {\n resourceType: string;\n resourceId: string;\n includeResolved?: boolean;\n includeDeleted?: boolean;\n targetId?: string | null;\n limit?: number;\n}\n\nexport interface ListReviewCommentsResult {\n comments: ReviewComment[];\n reviewStatus: ReviewStatusEntry | null;\n}\n\nexport interface GetReviewFeedbackParams {\n resourceType: string;\n resourceId: string;\n includeHumanTargeted?: boolean;\n limit?: number;\n}\n\nexport interface GetReviewFeedbackResult {\n comments: ReviewComment[];\n}\n\nexport interface CreateReviewCommentInput {\n resourceType: string;\n resourceId: string;\n targetId?: string | null;\n kind?: ReviewCommentKind;\n anchor?: unknown | null;\n body: string;\n authorName?: string | null;\n resolutionTarget?: ReviewResolutionTarget | null;\n mentions?: ReviewMention[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface ReplyReviewCommentInput {\n resourceType: string;\n resourceId: string;\n commentId: string;\n body: string;\n authorName?: string | null;\n resolutionTarget?: ReviewResolutionTarget | null;\n mentions?: ReviewMention[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface ResolveReviewThreadInput {\n resourceType: string;\n resourceId: string;\n threadId?: string;\n commentId?: string;\n}\n\nexport interface DeleteReviewCommentInput {\n resourceType: string;\n resourceId: string;\n commentId: string;\n}\n\nexport interface ConsumeReviewFeedbackInput {\n resourceType: string;\n resourceId: string;\n commentIds: string[];\n}\n\nexport interface SetReviewStatusInput {\n resourceType: string;\n resourceId: string;\n status: ReviewStatus;\n note?: string | null;\n metadata?: Record<string, unknown>;\n}\n\nexport function useReviewComments(\n params: ListReviewCommentsParams,\n options?: { enabled?: boolean },\n) {\n return useActionQuery<ListReviewCommentsResult>(\n \"list-review-comments\",\n params,\n {\n enabled:\n options?.enabled ?? Boolean(params.resourceType && params.resourceId),\n },\n );\n}\n\nexport function useReviewFeedback(\n params: GetReviewFeedbackParams,\n options?: { enabled?: boolean },\n) {\n return useActionQuery<GetReviewFeedbackResult>(\n \"get-review-feedback\",\n params,\n {\n enabled:\n options?.enabled ?? Boolean(params.resourceType && params.resourceId),\n },\n );\n}\n\nexport function useCreateReviewComment() {\n return useActionMutation<ReviewComment, CreateReviewCommentInput>(\n \"create-review-comment\",\n );\n}\n\nexport function useReplyReviewComment() {\n return useActionMutation<ReviewComment, ReplyReviewCommentInput>(\n \"reply-review-comment\",\n );\n}\n\nexport function useResolveReviewThread() {\n return useActionMutation<\n { threadId: string; resolved: true; updatedCount: number },\n ResolveReviewThreadInput\n >(\"resolve-review-thread\");\n}\n\nexport function useDeleteReviewComment() {\n return useActionMutation<\n { commentId: string; deleted: true; updatedCount: number },\n DeleteReviewCommentInput\n >(\"delete-review-comment\");\n}\n\nexport function useConsumeReviewFeedback() {\n return useActionMutation<\n { consumedCommentIds: string[]; updatedCount: number },\n ConsumeReviewFeedbackInput\n >(\"consume-review-feedback\");\n}\n\nexport function useSetReviewStatus() {\n return useActionMutation<ReviewStatusEntry, SetReviewStatusInput>(\n \"set-review-status\",\n );\n}\n"]}
1
+ {"version":3,"file":"use-review.js","sourceRoot":"","sources":["../../../src/client/review/use-review.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAyFrE,MAAM,UAAU,iBAAiB,CAC/B,MAAgC,EAChC,OAA+B;IAE/B,OAAO,cAAc,CACnB,sBAAsB,EACtB,MAAM,EACN;QACE,OAAO,EACL,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,UAAU,CAAC;KACxE,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA+B,EAC/B,OAA+B;IAE/B,OAAO,cAAc,CACnB,qBAAqB,EACrB,MAAM,EACN;QACE,OAAO,EACL,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,UAAU,CAAC;KACxE,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,CACtB,uBAAuB,CACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,iBAAiB,CACtB,sBAAsB,CACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,CAStB,uBAAuB,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,CAGtB,uBAAuB,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,iBAAiB,CAGtB,yBAAyB,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,OAAO,iBAAiB,CAatB,6BAA6B,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,iBAAiB,CACtB,mBAAmB,CACpB,CAAC;AACJ,CAAC","sourcesContent":["import type {\n ReviewComment,\n ReviewCommentKind,\n ReviewMention,\n ReviewResolutionTarget,\n ReviewStatus,\n ReviewStatusEntry,\n} from \"../../review/types.js\";\nimport { useActionMutation, useActionQuery } from \"../use-action.js\";\n\nexport interface ListReviewCommentsParams {\n resourceType: string;\n resourceId: string;\n includeResolved?: boolean;\n includeDeleted?: boolean;\n targetId?: string | null;\n limit?: number;\n}\n\nexport interface ListReviewCommentsResult {\n comments: ReviewComment[];\n reviewStatus: ReviewStatusEntry | null;\n summary: {\n openCount: number;\n agentQueueCount: number;\n };\n}\n\nexport interface GetReviewFeedbackParams {\n resourceType: string;\n resourceId: string;\n includeHumanTargeted?: boolean;\n limit?: number;\n}\n\nexport interface GetReviewFeedbackResult {\n comments: ReviewComment[];\n}\n\nexport interface CreateReviewCommentInput {\n resourceType: string;\n resourceId: string;\n targetId?: string | null;\n kind?: ReviewCommentKind;\n anchor?: unknown | null;\n body: string;\n authorName?: string | null;\n resolutionTarget?: ReviewResolutionTarget | null;\n mentions?: ReviewMention[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface ReplyReviewCommentInput {\n resourceType: string;\n resourceId: string;\n commentId: string;\n body: string;\n authorName?: string | null;\n resolutionTarget?: ReviewResolutionTarget | null;\n mentions?: ReviewMention[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface ResolveReviewThreadInput {\n resourceType: string;\n resourceId: string;\n threadId?: string;\n commentId?: string;\n resolutionNote?: string;\n}\n\nexport interface DeleteReviewCommentInput {\n resourceType: string;\n resourceId: string;\n commentId: string;\n}\n\nexport interface ConsumeReviewFeedbackInput {\n resourceType: string;\n resourceId: string;\n commentIds: string[];\n}\n\nexport interface SendReviewThreadToAgentInput {\n resourceType: string;\n resourceId: string;\n threadId: string;\n}\n\nexport interface SetReviewStatusInput {\n resourceType: string;\n resourceId: string;\n status: ReviewStatus;\n note?: string | null;\n metadata?: Record<string, unknown>;\n}\n\nexport function useReviewComments(\n params: ListReviewCommentsParams,\n options?: { enabled?: boolean },\n) {\n return useActionQuery<ListReviewCommentsResult>(\n \"list-review-comments\",\n params,\n {\n enabled:\n options?.enabled ?? Boolean(params.resourceType && params.resourceId),\n },\n );\n}\n\nexport function useReviewFeedback(\n params: GetReviewFeedbackParams,\n options?: { enabled?: boolean },\n) {\n return useActionQuery<GetReviewFeedbackResult>(\n \"get-review-feedback\",\n params,\n {\n enabled:\n options?.enabled ?? Boolean(params.resourceType && params.resourceId),\n },\n );\n}\n\nexport function useCreateReviewComment() {\n return useActionMutation<ReviewComment, CreateReviewCommentInput>(\n \"create-review-comment\",\n );\n}\n\nexport function useReplyReviewComment() {\n return useActionMutation<ReviewComment, ReplyReviewCommentInput>(\n \"reply-review-comment\",\n );\n}\n\nexport function useResolveReviewThread() {\n return useActionMutation<\n {\n threadId: string;\n resolved: true;\n updatedCount: number;\n resolutionNote: string | null;\n comment: ReviewComment;\n },\n ResolveReviewThreadInput\n >(\"resolve-review-thread\");\n}\n\nexport function useDeleteReviewComment() {\n return useActionMutation<\n { commentId: string; deleted: true; updatedCount: number },\n DeleteReviewCommentInput\n >(\"delete-review-comment\");\n}\n\nexport function useConsumeReviewFeedback() {\n return useActionMutation<\n { consumedCommentIds: string[]; updatedCount: number },\n ConsumeReviewFeedbackInput\n >(\"consume-review-feedback\");\n}\n\nexport function useSendReviewThreadToAgent() {\n return useActionMutation<\n {\n resourceType: string;\n resourceId: string;\n threadId: string;\n resolutionTarget: \"agent\";\n consumedAt: null;\n updatedCount: number;\n ownerEmail: string | null;\n orgId: string | null;\n visibility: \"private\" | \"org\" | \"public\";\n },\n SendReviewThreadToAgentInput\n >(\"send-review-thread-to-agent\");\n}\n\nexport function useSetReviewStatus() {\n return useActionMutation<ReviewStatusEntry, SetReviewStatusInput>(\n \"set-review-status\",\n );\n}\n"]}
@@ -10,6 +10,6 @@ export interface UseAgentChatContextResult extends AgentChatContextState {
10
10
  * composer's staged context chips. Simple send/prefill flows should use
11
11
  * `sendToAgentChat({ message, context, submit })` directly.
12
12
  */
13
- export declare function useAgentChatContext(): UseAgentChatContextResult;
13
+ export declare function useAgentChatContext(enabled?: boolean): UseAgentChatContextResult;
14
14
  export type { AgentChatContextItem };
15
15
  //# sourceMappingURL=use-agent-chat-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-chat-context.d.ts","sourceRoot":"","sources":["../../src/client/use-agent-chat-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,oBAAoB,EACzB,KAAK,+BAA+B,EACpC,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,GAAG,CAAC,IAAI,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,OAAO,CAAC,EAAE,+BAA+B,GAAG,IAAI,CAAC;IACvD,OAAO,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,yBAAyB,CA+B/D;AAED,YAAY,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"use-agent-chat-context.d.ts","sourceRoot":"","sources":["../../src/client/use-agent-chat-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,oBAAoB,EACzB,KAAK,+BAA+B,EACpC,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,GAAG,CAAC,IAAI,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,OAAO,CAAC,EAAE,+BAA+B,GAAG,IAAI,CAAC;IACvD,OAAO,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,UAAO,GAAG,yBAAyB,CAgC7E;AAED,YAAY,EAAE,oBAAoB,EAAE,CAAC"}
@@ -6,12 +6,14 @@ import { useChangeVersion } from "./use-change-version.js";
6
6
  * composer's staged context chips. Simple send/prefill flows should use
7
7
  * `sendToAgentChat({ message, context, submit })` directly.
8
8
  */
9
- export function useAgentChatContext() {
9
+ export function useAgentChatContext(enabled = true) {
10
10
  const appStateVersion = useChangeVersion("app-state");
11
11
  const state = useSyncExternalStore(subscribeAgentChatContext, getAgentChatContextState, getAgentChatContextState);
12
12
  useEffect(() => {
13
+ if (!enabled)
14
+ return;
13
15
  void refreshAgentChatContext();
14
- }, [appStateVersion]);
16
+ }, [appStateVersion, enabled]);
15
17
  const set = useCallback((item) => {
16
18
  setAgentChatContextItem(item);
17
19
  }, []);
@@ -1 +1 @@
1
- {"version":3,"file":"use-agent-chat-context.js","sourceRoot":"","sources":["../../src/client/use-agent-chat-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAErE,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,yBAAyB,GAK1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAS3D;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,eAAe,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,oBAAoB,CAChC,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,CACzB,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,uBAAuB,EAAE,CAAC;IACjC,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,IAAgC,EAAE,EAAE;QAC3D,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,GAAW,EAAE,EAAE;QACzC,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,OAAyC,EAAE,EAAE;QACtE,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO;QACL,GAAG,KAAK;QACR,GAAG;QACH,MAAM;QACN,KAAK;QACL,OAAO,EAAE,uBAAuB;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useSyncExternalStore } from \"react\";\n\nimport {\n clearAgentChatContext,\n getAgentChatContextState,\n refreshAgentChatContext,\n removeAgentChatContextItem,\n setAgentChatContextItem,\n subscribeAgentChatContext,\n type AgentChatContextItem,\n type AgentChatContextMutationOptions,\n type AgentChatContextSetOptions,\n type AgentChatContextState,\n} from \"./agent-chat.js\";\nimport { useChangeVersion } from \"./use-change-version.js\";\n\nexport interface UseAgentChatContextResult extends AgentChatContextState {\n set(item: AgentChatContextSetOptions): void;\n remove(key: string): void;\n clear(options?: AgentChatContextMutationOptions): void;\n refresh(): Promise<AgentChatContextState>;\n}\n\n/**\n * Advanced hook for UIs that need to stay in sync with the active chat\n * composer's staged context chips. Simple send/prefill flows should use\n * `sendToAgentChat({ message, context, submit })` directly.\n */\nexport function useAgentChatContext(): UseAgentChatContextResult {\n const appStateVersion = useChangeVersion(\"app-state\");\n const state = useSyncExternalStore(\n subscribeAgentChatContext,\n getAgentChatContextState,\n getAgentChatContextState,\n );\n\n useEffect(() => {\n void refreshAgentChatContext();\n }, [appStateVersion]);\n\n const set = useCallback((item: AgentChatContextSetOptions) => {\n setAgentChatContextItem(item);\n }, []);\n\n const remove = useCallback((key: string) => {\n removeAgentChatContextItem(key);\n }, []);\n\n const clear = useCallback((options?: AgentChatContextMutationOptions) => {\n clearAgentChatContext(options);\n }, []);\n\n return {\n ...state,\n set,\n remove,\n clear,\n refresh: refreshAgentChatContext,\n };\n}\n\nexport type { AgentChatContextItem };\n"]}
1
+ {"version":3,"file":"use-agent-chat-context.js","sourceRoot":"","sources":["../../src/client/use-agent-chat-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAErE,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,yBAAyB,GAK1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAS3D;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAO,GAAG,IAAI;IAChD,MAAM,eAAe,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,oBAAoB,CAChC,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,CACzB,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,KAAK,uBAAuB,EAAE,CAAC;IACjC,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;IAE/B,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,IAAgC,EAAE,EAAE;QAC3D,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,GAAW,EAAE,EAAE;QACzC,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,OAAyC,EAAE,EAAE;QACtE,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO;QACL,GAAG,KAAK;QACR,GAAG;QACH,MAAM;QACN,KAAK;QACL,OAAO,EAAE,uBAAuB;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useSyncExternalStore } from \"react\";\n\nimport {\n clearAgentChatContext,\n getAgentChatContextState,\n refreshAgentChatContext,\n removeAgentChatContextItem,\n setAgentChatContextItem,\n subscribeAgentChatContext,\n type AgentChatContextItem,\n type AgentChatContextMutationOptions,\n type AgentChatContextSetOptions,\n type AgentChatContextState,\n} from \"./agent-chat.js\";\nimport { useChangeVersion } from \"./use-change-version.js\";\n\nexport interface UseAgentChatContextResult extends AgentChatContextState {\n set(item: AgentChatContextSetOptions): void;\n remove(key: string): void;\n clear(options?: AgentChatContextMutationOptions): void;\n refresh(): Promise<AgentChatContextState>;\n}\n\n/**\n * Advanced hook for UIs that need to stay in sync with the active chat\n * composer's staged context chips. Simple send/prefill flows should use\n * `sendToAgentChat({ message, context, submit })` directly.\n */\nexport function useAgentChatContext(enabled = true): UseAgentChatContextResult {\n const appStateVersion = useChangeVersion(\"app-state\");\n const state = useSyncExternalStore(\n subscribeAgentChatContext,\n getAgentChatContextState,\n getAgentChatContextState,\n );\n\n useEffect(() => {\n if (!enabled) return;\n void refreshAgentChatContext();\n }, [appStateVersion, enabled]);\n\n const set = useCallback((item: AgentChatContextSetOptions) => {\n setAgentChatContextItem(item);\n }, []);\n\n const remove = useCallback((key: string) => {\n removeAgentChatContextItem(key);\n }, []);\n\n const clear = useCallback((options?: AgentChatContextMutationOptions) => {\n clearAgentChatContext(options);\n }, []);\n\n return {\n ...state,\n set,\n remove,\n clear,\n refresh: refreshAgentChatContext,\n };\n}\n\nexport type { AgentChatContextItem };\n"]}
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,10 +77,10 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
86
86
  //# sourceMappingURL=awareness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;kBAwDa,MAAM;eAAS,MAAM;;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;kBAYM,MAAM;kBAAY,MAAM;;;GAMvD,CAAC"}
1
+ {"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;;kBAwDa,MAAM;eAAS,MAAM;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;kBAYM,MAAM;kBAAY,MAAM;;GAMvD,CAAC"}
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -14,16 +14,21 @@ export declare function resolveCredentialForScope(key: string, ctx: CredentialCo
14
14
  scope: CredentialStorageScope;
15
15
  }): Promise<string | undefined>;
16
16
  /**
17
- * Resolve a credential, scoped to the caller's user (and falling back to
18
- * the active org's shared credential, if any).
17
+ * Resolve a credential across the encrypted app_secrets store and the legacy
18
+ * settings-backed credential store. User overrides win, followed by the
19
+ * active org/workspace shared value.
19
20
  *
20
21
  * SECURITY: NEVER reads from process.env. Env vars are global to the
21
- * deployment and would leak across users in a multi-tenant app. The only
22
- * sources are per-user / per-org rows in the SQL `settings` table.
22
+ * deployment and would leak across users in a multi-tenant app.
23
23
  *
24
- * Storage keys (priority order):
25
- * 1. u:<email>:credential:<KEY> — per-user override
26
- * 2. o:<orgId>:credential:<KEY> — per-org shared credential (if orgId given)
24
+ * Read order:
25
+ * 1. user-scoped app_secrets
26
+ * 2. user-scoped legacy settings credential
27
+ * 3. org-scoped app_secrets
28
+ * 4. legacy workspace-scoped app_secrets for the org
29
+ * 5. org-scoped legacy settings credential
30
+ *
31
+ * Without an active org, step 3 is replaced by the solo workspace scope.
27
32
  */
28
33
  export declare function resolveCredential(key: string, ctx: CredentialContext): Promise<string | undefined>;
29
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,KAAK,CAAC;AA6BpD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAc7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAcf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,KAAK,CAAC;AA2CpD;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2B7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAcf"}
@@ -1,4 +1,5 @@
1
1
  import { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "../secrets/crypto.js";
2
+ import { readAppSecret } from "../secrets/storage.js";
2
3
  import { getSetting, putSetting, deleteSetting } from "../settings/store.js";
3
4
  const SETTING_PREFIX = "credential:";
4
5
  function userCredentialSettingKey(email, key) {
@@ -26,6 +27,16 @@ async function readCredentialSetting(settingKey) {
26
27
  return undefined;
27
28
  }
28
29
  }
30
+ async function readScopedAppSecret(key, scope, scopeId) {
31
+ try {
32
+ return (await readAppSecret({ key, scope, scopeId }))?.value;
33
+ }
34
+ catch {
35
+ // Older databases may not have app_secrets yet. Keep the legacy
36
+ // credential store available while the table bootstraps.
37
+ return undefined;
38
+ }
39
+ }
29
40
  /**
30
41
  * Resolve a credential from one explicit legacy SQL credential scope.
31
42
  *
@@ -44,20 +55,28 @@ export async function resolveCredentialForScope(key, ctx) {
44
55
  return readCredentialSetting(userCredentialSettingKey(ctx.userEmail, key));
45
56
  }
46
57
  /**
47
- * Resolve a credential, scoped to the caller's user (and falling back to
48
- * the active org's shared credential, if any).
58
+ * Resolve a credential across the encrypted app_secrets store and the legacy
59
+ * settings-backed credential store. User overrides win, followed by the
60
+ * active org/workspace shared value.
49
61
  *
50
62
  * SECURITY: NEVER reads from process.env. Env vars are global to the
51
- * deployment and would leak across users in a multi-tenant app. The only
52
- * sources are per-user / per-org rows in the SQL `settings` table.
63
+ * deployment and would leak across users in a multi-tenant app.
64
+ *
65
+ * Read order:
66
+ * 1. user-scoped app_secrets
67
+ * 2. user-scoped legacy settings credential
68
+ * 3. org-scoped app_secrets
69
+ * 4. legacy workspace-scoped app_secrets for the org
70
+ * 5. org-scoped legacy settings credential
53
71
  *
54
- * Storage keys (priority order):
55
- * 1. u:<email>:credential:<KEY> — per-user override
56
- * 2. o:<orgId>:credential:<KEY> — per-org shared credential (if orgId given)
72
+ * Without an active org, step 3 is replaced by the solo workspace scope.
57
73
  */
58
74
  export async function resolveCredential(key, ctx) {
59
75
  if (!ctx?.userEmail)
60
76
  return undefined;
77
+ const userSecret = await readScopedAppSecret(key, "user", ctx.userEmail);
78
+ if (userSecret)
79
+ return userSecret;
61
80
  const userSetting = await resolveCredentialForScope(key, {
62
81
  ...ctx,
63
82
  scope: "user",
@@ -65,9 +84,15 @@ export async function resolveCredential(key, ctx) {
65
84
  if (userSetting)
66
85
  return userSetting;
67
86
  if (ctx.orgId) {
87
+ const orgSecret = await readScopedAppSecret(key, "org", ctx.orgId);
88
+ if (orgSecret)
89
+ return orgSecret;
90
+ const workspaceSecret = await readScopedAppSecret(key, "workspace", ctx.orgId);
91
+ if (workspaceSecret)
92
+ return workspaceSecret;
68
93
  return resolveCredentialForScope(key, { ...ctx, scope: "org" });
69
94
  }
70
- return undefined;
95
+ return readScopedAppSecret(key, "workspace", `solo:${ctx.userEmail}`);
71
96
  }
72
97
  /**
73
98
  * Check if a credential is available for the given context.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\nimport { getSetting, putSetting, deleteSetting } from \"../settings/store.js\";\n\nconst SETTING_PREFIX = \"credential:\";\n\nexport interface CredentialContext {\n userEmail: string;\n orgId?: string | null;\n}\n\nexport type CredentialStorageScope = \"user\" | \"org\";\n\nfunction userCredentialSettingKey(email: string, key: string): string {\n return `u:${email.toLowerCase()}:${SETTING_PREFIX}${key}`;\n}\n\nfunction orgCredentialSettingKey(orgId: string, key: string): string {\n return `o:${orgId}:${SETTING_PREFIX}${key}`;\n}\n\nasync function readCredentialSetting(\n settingKey: string,\n): Promise<string | undefined> {\n const setting = await getSetting(settingKey);\n if (!setting || typeof setting.value !== \"string\") return undefined;\n const stored = setting.value;\n // Values written by saveCredential are AES-256-GCM encrypted at rest.\n // Rows that predate encryption are plaintext — read them transparently\n // (the migrate-encrypt-credentials script re-encrypts them in place).\n if (!isEncryptedSecretValue(stored)) return stored;\n try {\n return decryptSecretValue(stored);\n } catch {\n // Key rotated, corrupt, or tampered row — treat as not set rather than\n // surfacing ciphertext or throwing into every credential lookup.\n return undefined;\n }\n}\n\n/**\n * Resolve a credential from one explicit legacy SQL credential scope.\n *\n * Prefer `resolveCredential()` for normal app-local credential lookup. This\n * helper exists for workspace connection refs, where a ref can explicitly say\n * \"use the org-scoped key\" and must not accidentally read a user override.\n */\nexport async function resolveCredentialForScope(\n key: string,\n ctx: CredentialContext & { scope: CredentialStorageScope },\n): Promise<string | undefined> {\n if (!ctx?.userEmail) return undefined;\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) return undefined;\n return readCredentialSetting(orgCredentialSettingKey(ctx.orgId, key));\n }\n return readCredentialSetting(userCredentialSettingKey(ctx.userEmail, key));\n}\n\n/**\n * Resolve a credential, scoped to the caller's user (and falling back to\n * the active org's shared credential, if any).\n *\n * SECURITY: NEVER reads from process.env. Env vars are global to the\n * deployment and would leak across users in a multi-tenant app. The only\n * sources are per-user / per-org rows in the SQL `settings` table.\n *\n * Storage keys (priority order):\n * 1. u:<email>:credential:<KEY> — per-user override\n * 2. o:<orgId>:credential:<KEY> — per-org shared credential (if orgId given)\n */\nexport async function resolveCredential(\n key: string,\n ctx: CredentialContext,\n): Promise<string | undefined> {\n if (!ctx?.userEmail) return undefined;\n\n const userSetting = await resolveCredentialForScope(key, {\n ...ctx,\n scope: \"user\",\n });\n if (userSetting) return userSetting;\n\n if (ctx.orgId) {\n return resolveCredentialForScope(key, { ...ctx, scope: \"org\" });\n }\n\n return undefined;\n}\n\n/**\n * Check if a credential is available for the given context.\n */\nexport async function hasCredential(\n key: string,\n ctx: CredentialContext,\n): Promise<boolean> {\n return (await resolveCredential(key, ctx)) !== undefined;\n}\n\n/**\n * Save a credential. By default writes to the per-user store; pass\n * `scope: \"org\"` to write to the active org's shared credentials.\n */\nexport async function saveCredential(\n key: string,\n value: string,\n ctx: CredentialContext & { scope?: \"user\" | \"org\" },\n): Promise<void> {\n if (!ctx?.userEmail) {\n throw new Error(\"saveCredential requires CredentialContext with userEmail\");\n }\n // Encrypt at rest (AES-256-GCM) so a leaked DB backup / pg_dump / read\n // replica doesn't expose plaintext keys. resolveCredential decrypts\n // transparently on read.\n const encrypted = encryptSecretValue(value);\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) {\n throw new Error(\"saveCredential scope='org' requires orgId\");\n }\n await putSetting(orgCredentialSettingKey(ctx.orgId, key), {\n value: encrypted,\n });\n return;\n }\n await putSetting(userCredentialSettingKey(ctx.userEmail, key), {\n value: encrypted,\n });\n}\n\n/**\n * Delete a credential from the per-user (default) or per-org store.\n */\nexport async function deleteCredential(\n key: string,\n ctx: CredentialContext & { scope?: \"user\" | \"org\" },\n): Promise<void> {\n if (!ctx?.userEmail) {\n throw new Error(\n \"deleteCredential requires CredentialContext with userEmail\",\n );\n }\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) {\n throw new Error(\"deleteCredential scope='org' requires orgId\");\n }\n await deleteSetting(orgCredentialSettingKey(ctx.orgId, key));\n return;\n }\n await deleteSetting(userCredentialSettingKey(ctx.userEmail, key));\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/credentials/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"../secrets/crypto.js\";\nimport { readAppSecret, type SecretRef } from \"../secrets/storage.js\";\nimport { getSetting, putSetting, deleteSetting } from \"../settings/store.js\";\n\nconst SETTING_PREFIX = \"credential:\";\n\nexport interface CredentialContext {\n userEmail: string;\n orgId?: string | null;\n}\n\nexport type CredentialStorageScope = \"user\" | \"org\";\n\nfunction userCredentialSettingKey(email: string, key: string): string {\n return `u:${email.toLowerCase()}:${SETTING_PREFIX}${key}`;\n}\n\nfunction orgCredentialSettingKey(orgId: string, key: string): string {\n return `o:${orgId}:${SETTING_PREFIX}${key}`;\n}\n\nasync function readCredentialSetting(\n settingKey: string,\n): Promise<string | undefined> {\n const setting = await getSetting(settingKey);\n if (!setting || typeof setting.value !== \"string\") return undefined;\n const stored = setting.value;\n // Values written by saveCredential are AES-256-GCM encrypted at rest.\n // Rows that predate encryption are plaintext — read them transparently\n // (the migrate-encrypt-credentials script re-encrypts them in place).\n if (!isEncryptedSecretValue(stored)) return stored;\n try {\n return decryptSecretValue(stored);\n } catch {\n // Key rotated, corrupt, or tampered row — treat as not set rather than\n // surfacing ciphertext or throwing into every credential lookup.\n return undefined;\n }\n}\n\nasync function readScopedAppSecret(\n key: string,\n scope: SecretRef[\"scope\"],\n scopeId: string,\n): Promise<string | undefined> {\n try {\n return (await readAppSecret({ key, scope, scopeId }))?.value;\n } catch {\n // Older databases may not have app_secrets yet. Keep the legacy\n // credential store available while the table bootstraps.\n return undefined;\n }\n}\n\n/**\n * Resolve a credential from one explicit legacy SQL credential scope.\n *\n * Prefer `resolveCredential()` for normal app-local credential lookup. This\n * helper exists for workspace connection refs, where a ref can explicitly say\n * \"use the org-scoped key\" and must not accidentally read a user override.\n */\nexport async function resolveCredentialForScope(\n key: string,\n ctx: CredentialContext & { scope: CredentialStorageScope },\n): Promise<string | undefined> {\n if (!ctx?.userEmail) return undefined;\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) return undefined;\n return readCredentialSetting(orgCredentialSettingKey(ctx.orgId, key));\n }\n return readCredentialSetting(userCredentialSettingKey(ctx.userEmail, key));\n}\n\n/**\n * Resolve a credential across the encrypted app_secrets store and the legacy\n * settings-backed credential store. User overrides win, followed by the\n * active org/workspace shared value.\n *\n * SECURITY: NEVER reads from process.env. Env vars are global to the\n * deployment and would leak across users in a multi-tenant app.\n *\n * Read order:\n * 1. user-scoped app_secrets\n * 2. user-scoped legacy settings credential\n * 3. org-scoped app_secrets\n * 4. legacy workspace-scoped app_secrets for the org\n * 5. org-scoped legacy settings credential\n *\n * Without an active org, step 3 is replaced by the solo workspace scope.\n */\nexport async function resolveCredential(\n key: string,\n ctx: CredentialContext,\n): Promise<string | undefined> {\n if (!ctx?.userEmail) return undefined;\n\n const userSecret = await readScopedAppSecret(key, \"user\", ctx.userEmail);\n if (userSecret) return userSecret;\n\n const userSetting = await resolveCredentialForScope(key, {\n ...ctx,\n scope: \"user\",\n });\n if (userSetting) return userSetting;\n\n if (ctx.orgId) {\n const orgSecret = await readScopedAppSecret(key, \"org\", ctx.orgId);\n if (orgSecret) return orgSecret;\n\n const workspaceSecret = await readScopedAppSecret(\n key,\n \"workspace\",\n ctx.orgId,\n );\n if (workspaceSecret) return workspaceSecret;\n\n return resolveCredentialForScope(key, { ...ctx, scope: \"org\" });\n }\n\n return readScopedAppSecret(key, \"workspace\", `solo:${ctx.userEmail}`);\n}\n\n/**\n * Check if a credential is available for the given context.\n */\nexport async function hasCredential(\n key: string,\n ctx: CredentialContext,\n): Promise<boolean> {\n return (await resolveCredential(key, ctx)) !== undefined;\n}\n\n/**\n * Save a credential. By default writes to the per-user store; pass\n * `scope: \"org\"` to write to the active org's shared credentials.\n */\nexport async function saveCredential(\n key: string,\n value: string,\n ctx: CredentialContext & { scope?: \"user\" | \"org\" },\n): Promise<void> {\n if (!ctx?.userEmail) {\n throw new Error(\"saveCredential requires CredentialContext with userEmail\");\n }\n // Encrypt at rest (AES-256-GCM) so a leaked DB backup / pg_dump / read\n // replica doesn't expose plaintext keys. resolveCredential decrypts\n // transparently on read.\n const encrypted = encryptSecretValue(value);\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) {\n throw new Error(\"saveCredential scope='org' requires orgId\");\n }\n await putSetting(orgCredentialSettingKey(ctx.orgId, key), {\n value: encrypted,\n });\n return;\n }\n await putSetting(userCredentialSettingKey(ctx.userEmail, key), {\n value: encrypted,\n });\n}\n\n/**\n * Delete a credential from the per-user (default) or per-org store.\n */\nexport async function deleteCredential(\n key: string,\n ctx: CredentialContext & { scope?: \"user\" | \"org\" },\n): Promise<void> {\n if (!ctx?.userEmail) {\n throw new Error(\n \"deleteCredential requires CredentialContext with userEmail\",\n );\n }\n if (ctx.scope === \"org\") {\n if (!ctx.orgId) {\n throw new Error(\"deleteCredential scope='org' requires orgId\");\n }\n await deleteSetting(orgCredentialSettingKey(ctx.orgId, key));\n return;\n }\n await deleteSetting(userCredentialSettingKey(ctx.userEmail, key));\n}\n"]}
@@ -10,15 +10,27 @@
10
10
  * the raw secret never enters the model's context.
11
11
  */
12
12
  import type { ActionEntry } from "../agent/production-agent.js";
13
+ import type { ResolvedKeyReference } from "../secrets/substitution.js";
13
14
  export interface FetchToolOptions {
14
15
  /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */
15
16
  resolveKeys?: (text: string) => Promise<{
16
17
  resolved: string;
17
18
  usedKeys: string[];
18
19
  secretValues?: string[];
20
+ /**
21
+ * Optional: which scope (user/org/workspace) each used key actually
22
+ * resolved at. Populated by resolvers like
23
+ * `resolveKeyReferencesWithRequestScopes` so `validateUrl` can look up
24
+ * the allowlist at the scope the value came from rather than assuming
25
+ * user scope. Backwards compatible — callers that omit this (or a
26
+ * resolver that doesn't report it) are unaffected.
27
+ */
28
+ resolvedKeys?: ResolvedKeyReference[];
19
29
  }>;
20
30
  /** Validate URL against per-key allowlists. */
21
- validateUrl?: (url: string, usedKeys: string[]) => Promise<boolean>;
31
+ validateUrl?: (url: string, usedKeys: string[],
32
+ /** Accumulated `resolvedKeys` from every `resolveKeys` call this request made, when the resolver reported them. */
33
+ resolvedKeys?: ResolvedKeyReference[]) => Promise<boolean>;
22
34
  }
23
35
  /**
24
36
  * Create the fetch tool entry for the agent tool registry.
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-tool.d.ts","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AA6DhE,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QACtC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC,CAAC;IACH,+CAA+C;IAC/C,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,gBAAqB,GAC1B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CA+U7B"}
1
+ {"version":3,"file":"fetch-tool.d.ts","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AA6DvE,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QACtC,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB;;;;;;;WAOG;QACH,YAAY,CAAC,EAAE,oBAAoB,EAAE,CAAC;KACvC,CAAC,CAAC;IACH,+CAA+C;IAC/C,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAAE;IAClB,mHAAmH;IACnH,YAAY,CAAC,EAAE,oBAAoB,EAAE,KAClC,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,gBAAqB,GAC1B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CA8V7B"}
@@ -153,21 +153,25 @@ export function createFetchToolEntry(opts = {}) {
153
153
  let resolvedBody = rawBody;
154
154
  const allUsedKeys = [];
155
155
  const allSecretValues = [];
156
+ const allResolvedKeys = [];
156
157
  if (opts.resolveKeys) {
157
158
  try {
158
159
  const urlResult = await opts.resolveKeys(rawUrl);
159
160
  resolvedUrl = urlResult.resolved;
160
161
  allUsedKeys.push(...urlResult.usedKeys);
161
162
  allSecretValues.push(...(urlResult.secretValues ?? []));
163
+ allResolvedKeys.push(...(urlResult.resolvedKeys ?? []));
162
164
  const headerResult = await opts.resolveKeys(rawHeaders);
163
165
  resolvedHeaders = headerResult.resolved;
164
166
  allUsedKeys.push(...headerResult.usedKeys);
165
167
  allSecretValues.push(...(headerResult.secretValues ?? []));
168
+ allResolvedKeys.push(...(headerResult.resolvedKeys ?? []));
166
169
  if (rawBody) {
167
170
  const bodyResult = await opts.resolveKeys(rawBody);
168
171
  resolvedBody = bodyResult.resolved;
169
172
  allUsedKeys.push(...bodyResult.usedKeys);
170
173
  allSecretValues.push(...(bodyResult.secretValues ?? []));
174
+ allResolvedKeys.push(...(bodyResult.resolvedKeys ?? []));
171
175
  }
172
176
  }
173
177
  catch (err) {
@@ -175,6 +179,9 @@ export function createFetchToolEntry(opts = {}) {
175
179
  }
176
180
  }
177
181
  const secretValues = collectSecretValues(allSecretValues);
182
+ const resolvedKeys = allResolvedKeys.length
183
+ ? allResolvedKeys
184
+ : undefined;
178
185
  // Block SSRF targets regardless of key usage
179
186
  if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {
180
187
  return `Requests to private/internal addresses are not allowed: "${rawUrl}".`;
@@ -182,7 +189,7 @@ export function createFetchToolEntry(opts = {}) {
182
189
  // Validate URL against per-key allowlists
183
190
  if (opts.validateUrl && allUsedKeys.length > 0) {
184
191
  try {
185
- const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys);
192
+ const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys, resolvedKeys);
186
193
  if (!allowed) {
187
194
  return `URL "${rawUrl}" is not in the allowlist for the referenced keys. Check your key settings.`;
188
195
  }
@@ -236,7 +243,7 @@ export function createFetchToolEntry(opts = {}) {
236
243
  return "Redirect to private/internal address blocked.";
237
244
  }
238
245
  if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {
239
- const allowed = await opts.validateUrl(redirectUrl, allUsedKeys);
246
+ const allowed = await opts.validateUrl(redirectUrl, allUsedKeys, resolvedKeys);
240
247
  if (!allowed) {
241
248
  return "Redirect URL is not in the allowlist for the referenced keys.";
242
249
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-tool.js","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fetch tool — outbound HTTP for automations and agent use.\n *\n * NOTE: this is an *agent* tool (LLM function call), not an *extension* (the\n * sandboxed Alpine.js mini-app primitive). It lives in this directory because\n * it shares SSRF-safe URL/proxy helpers with the extension iframe proxy.\n *\n * Supports ${keys.NAME} reference substitution in URL, headers, and body.\n * Values are resolved server-side AFTER the model emits the tool call —\n * the raw secret never enters the model's context.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n collectSecretValues,\n MAX_EXTENSION_PROXY_RESPONSE_SIZE,\n normalizeExtensionProxyMethod,\n readResponseTextWithLimit,\n redactSecrets,\n redactString,\n sanitizeOutboundHeaders,\n} from \"./proxy-security.js\";\nimport {\n createSsrfSafeDispatcher,\n isBlockedExtensionUrlWithDns,\n} from \"./url-safety.js\";\nimport {\n formatWebContentResult,\n parseWebContentSearchOptions,\n processWebContent,\n} from \"./web-content.js\";\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Headers that mimic a current Chrome on macOS so anti-bot middleware (Cloudflare,\n * PerimeterX, Akamai) treats the request as a real user. We only fill in fields\n * the caller hasn't supplied — explicit headers (e.g. an `Authorization` header\n * for an API call) always win.\n *\n * `Accept-Encoding` deliberately omits `zstd` because Node's undici fetch only\n * decompresses `gzip`, `deflate`, and `br`. Advertising `zstd` would let some\n * servers send bytes we can't decode.\n */\nconst BROWSER_DEFAULT_HEADERS: Record<string, string> = {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Accept:\n \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Language\": \"en-US,en;q=0.9\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Sec-Ch-Ua\":\n '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n};\n\nfunction applyBrowserDefaults(\n headers: Record<string, string>,\n): Record<string, string> {\n const seen = new Set(Object.keys(headers).map((k) => k.toLowerCase()));\n const merged = { ...headers };\n for (const [name, value] of Object.entries(BROWSER_DEFAULT_HEADERS)) {\n if (!seen.has(name.toLowerCase())) merged[name] = value;\n }\n return merged;\n}\n\nexport interface FetchToolOptions {\n /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */\n resolveKeys?: (text: string) => Promise<{\n resolved: string;\n usedKeys: string[];\n secretValues?: string[];\n }>;\n /** Validate URL against per-key allowlists. */\n validateUrl?: (url: string, usedKeys: string[]) => Promise<boolean>;\n}\n\n/**\n * Create the fetch tool entry for the agent tool registry.\n */\nexport function createFetchToolEntry(\n opts: FetchToolOptions = {},\n): Record<string, ActionEntry> {\n return {\n \"web-request\": {\n tool: {\n description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \\${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \\${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \\`search-records\\`, \\`provider-api-request\\`, \\`update-resource\\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,\n parameters: {\n type: \"object\" as const,\n properties: {\n url: {\n type: \"string\",\n description:\n 'Full URL. May contain ${keys.NAME} references, e.g. \"${keys.SLACK_WEBHOOK}\".',\n },\n method: {\n type: \"string\",\n description: \"HTTP method. Default: GET.\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\"],\n },\n headers: {\n type: \"string\",\n description:\n 'JSON object of headers. May contain ${keys.NAME} references. Example: \\'{\"Authorization\": \"Bearer ${keys.API_TOKEN}\"}\\'.',\n },\n body: {\n type: \"string\",\n description:\n \"Request body (for POST/PUT/PATCH). May contain ${keys.NAME} references.\",\n },\n timeout_ms: {\n type: \"number\",\n description: `Timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: 30000.`,\n },\n maxChars: {\n type: \"number\",\n description:\n \"Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.\",\n },\n responseMode: {\n type: \"string\",\n description:\n \"How to return the response. Default: auto (HTML pages become clean markdown; JSON/text stays raw). Use raw for exact bytes, markdown/text for extracted readable content, links for just links, metadata for page metadata, or matches with search.\",\n enum: [\n \"auto\",\n \"raw\",\n \"text\",\n \"markdown\",\n \"links\",\n \"metadata\",\n \"matches\",\n ],\n },\n extract: {\n type: \"string\",\n description:\n \"HTML extraction strategy. Default: readability. Use all-visible for visible body text/markdown, or none to convert the full HTML document.\",\n enum: [\"readability\", \"all-visible\", \"none\"],\n },\n includeLinks: {\n type: \"boolean\",\n description:\n \"Whether extracted HTML responses should include a compact links list. Default: true for extracted pages.\",\n },\n search: {\n type: \"object\",\n description:\n \"Optional post-fetch search over extracted content by default. Supports {query, queries, terms, regex, regexFlags, source:'extracted'|'raw', maxMatches, contextChars, caseSensitive}. Regex is safety-checked and bounded; prefer query/terms for simple grep-like searches.\",\n properties: {\n query: { type: \"string\" },\n queries: { type: \"array\", items: { type: \"string\" } },\n terms: { type: \"array\", items: { type: \"string\" } },\n regex: { type: \"string\" },\n regexFlags: { type: \"string\" },\n source: { type: \"string\", enum: [\"extracted\", \"raw\"] },\n maxMatches: { type: \"number\" },\n contextChars: { type: \"number\" },\n caseSensitive: { type: \"boolean\" },\n },\n } as any,\n saveToFile: {\n type: \"string\",\n description:\n \"Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/page.html'). When set, returns only a compact summary {savedTo, status, bytes, preview}. Useful for large web pages or API responses that would overflow context.\",\n },\n },\n required: [\"url\"],\n },\n },\n run: async (args: Record<string, unknown>) => {\n const startTime = Date.now();\n const rawUrl = String(args.url ?? \"\");\n const method = normalizeExtensionProxyMethod(args.method || \"GET\");\n if (!method) {\n return \"Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.\";\n }\n const rawHeaders =\n typeof args.headers === \"string\"\n ? args.headers\n : JSON.stringify(args.headers ?? {});\n const rawBody =\n typeof args.body === \"string\"\n ? args.body\n : args.body === undefined || args.body === null\n ? undefined\n : JSON.stringify(args.body);\n const timeoutMs = Math.min(\n Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS,\n 30_000,\n );\n const requestedMaxChars = Number(args.maxChars);\n const maxChars =\n Number.isFinite(requestedMaxChars) && requestedMaxChars > 0\n ? Math.min(requestedMaxChars, 200_000)\n : 32_000;\n\n // Resolve key references\n let resolvedUrl = rawUrl;\n let resolvedHeaders = rawHeaders;\n let resolvedBody = rawBody;\n const allUsedKeys: string[] = [];\n const allSecretValues: string[] = [];\n\n if (opts.resolveKeys) {\n try {\n const urlResult = await opts.resolveKeys(rawUrl);\n resolvedUrl = urlResult.resolved;\n allUsedKeys.push(...urlResult.usedKeys);\n allSecretValues.push(...(urlResult.secretValues ?? []));\n\n const headerResult = await opts.resolveKeys(rawHeaders);\n resolvedHeaders = headerResult.resolved;\n allUsedKeys.push(...headerResult.usedKeys);\n allSecretValues.push(...(headerResult.secretValues ?? []));\n\n if (rawBody) {\n const bodyResult = await opts.resolveKeys(rawBody);\n resolvedBody = bodyResult.resolved;\n allUsedKeys.push(...bodyResult.usedKeys);\n allSecretValues.push(...(bodyResult.secretValues ?? []));\n }\n } catch (err: any) {\n return `Error resolving key references: ${err?.message ?? err}`;\n }\n }\n const secretValues = collectSecretValues(allSecretValues);\n\n // Block SSRF targets regardless of key usage\n if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {\n return `Requests to private/internal addresses are not allowed: \"${rawUrl}\".`;\n }\n\n // Validate URL against per-key allowlists\n if (opts.validateUrl && allUsedKeys.length > 0) {\n try {\n const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys);\n if (!allowed) {\n return `URL \"${rawUrl}\" is not in the allowlist for the referenced keys. Check your key settings.`;\n }\n } catch (err: any) {\n return `URL validation error: ${err?.message ?? err}`;\n }\n }\n\n // Parse headers, then merge in browser-like defaults for any header the\n // caller didn't already specify. Real-browser headers (User-Agent,\n // Accept, Sec-Fetch-*) are what gets you past Cloudflare / PerimeterX /\n // generic UA-sniffing middleware on sites the user pastes in chat;\n // explicit caller headers always win so API calls keep their auth\n // headers untouched.\n let headers: Record<string, string>;\n try {\n headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));\n } catch {\n return `Invalid headers JSON: ${rawHeaders}`;\n }\n headers = applyBrowserDefaults(headers);\n\n // Make the request\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n method,\n headers,\n signal: controller.signal,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n if (resolvedBody && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOpts.body = resolvedBody;\n if (!headers[\"content-type\"] && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n const response = await fetch(resolvedUrl, fetchOpts);\n const elapsed = Date.now() - startTime;\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n const redirectUrl = location\n ? new URL(location, resolvedUrl).href\n : null;\n if (\n redirectUrl &&\n (await isBlockedExtensionUrlWithDns(redirectUrl))\n ) {\n return \"Redirect to private/internal address blocked.\";\n }\n if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {\n const allowed = await opts.validateUrl(redirectUrl, allUsedKeys);\n if (!allowed) {\n return \"Redirect URL is not in the allowlist for the referenced keys.\";\n }\n }\n return `HTTP ${response.status} ${response.statusText}\\n\\nRedirect: ${\n redirectUrl ? redactString(redirectUrl, secretValues) : \"(none)\"\n }`;\n }\n\n // Check if caller wants to save to workspace file (before truncation).\n const saveToFilePath =\n typeof (args as Record<string, unknown>).saveToFile === \"string\"\n ? ((args as Record<string, unknown>).saveToFile as string).trim()\n : \"\";\n\n let body: string;\n try {\n // When saving to file allow larger reads (20MB), otherwise cap at proxy limit.\n const readLimit = saveToFilePath\n ? 20 * 1024 * 1024\n : MAX_EXTENSION_PROXY_RESPONSE_SIZE;\n const result = await readResponseTextWithLimit(response, readLimit);\n body = result.text;\n } catch {\n body = \"(could not read response body)\";\n }\n body = redactString(body, secretValues);\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\")[0].trim() ??\n \"text/plain\";\n let displayBody: string;\n let processedMode = \"raw\";\n try {\n const processed = processWebContent({\n url: resolvedUrl,\n body,\n contentType,\n responseMode: String(args.responseMode ?? \"auto\"),\n extract: String(args.extract ?? \"readability\"),\n includeLinks:\n args.includeLinks === undefined\n ? true\n : parseBooleanArg(args.includeLinks),\n search: parseWebContentSearchOptions(args.search),\n maxChars,\n });\n processedMode = processed.mode;\n displayBody = formatWebContentResult(processed);\n } catch (err: any) {\n return `web-request post-processing error: ${err?.message ?? String(err)}`;\n }\n\n // Audit log\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(\",\") || \"none\"})`,\n );\n\n // saveToFile: write full body to workspace and return compact summary.\n if (saveToFilePath) {\n try {\n const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } =\n await import(\"../workspace-files/store.js\");\n const { getRequestOrgId, getRequestUserEmail } =\n await import(\"../server/request-context.js\");\n const orgId = getRequestOrgId();\n const email = getRequestUserEmail();\n const scope = orgId\n ? { scope: \"org\" as const, scopeId: orgId }\n : email\n ? { scope: \"user\" as const, scopeId: email }\n : null;\n if (!scope)\n throw new Error(\"No authenticated context for saveToFile\");\n await writeWorkspaceFile(\n scope,\n saveToFilePath,\n body,\n contentType,\n {\n maxFileBytes: SAVE_TO_FILE_MAX_BYTES,\n },\n );\n const bytes = Buffer.byteLength(body, \"utf8\");\n const preview = displayBody.slice(0, 2000);\n return JSON.stringify({\n savedToFile: true,\n savedTo: saveToFilePath,\n status: response.status,\n bytes,\n contentType,\n responseMode: processedMode,\n preview:\n preview.length < displayBody.length ? `${preview}…` : preview,\n });\n } catch (saveErr: any) {\n return `saveToFile error: ${saveErr?.message ?? String(saveErr)}\\n\\nHTTP ${response.status} ${response.statusText}\\n\\n${body.slice(0, maxChars)}`;\n }\n }\n\n return `HTTP ${response.status} ${response.statusText}\\n\\n${displayBody}`;\n } catch (err: any) {\n const elapsed = Date.now() - startTime;\n if (err?.name === \"AbortError\") {\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → TIMEOUT (${elapsed}ms)`,\n );\n return `Request timed out after ${timeoutMs}ms.`;\n }\n const message = redactSecrets(\n err?.message ?? String(err),\n secretValues,\n );\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ERROR: ${message} (${elapsed}ms)`,\n );\n return `Request failed: ${message}`;\n } finally {\n clearTimeout(timeout);\n }\n },\n readOnly: true,\n },\n };\n}\n\nfunction parseBooleanArg(value: unknown): boolean {\n if (typeof value === \"boolean\") return value;\n const normalized = String(value).trim().toLowerCase();\n return normalized === \"true\" || normalized === \"1\" || normalized === \"yes\";\n}\n"]}
1
+ {"version":3,"file":"fetch-tool.js","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fetch tool — outbound HTTP for automations and agent use.\n *\n * NOTE: this is an *agent* tool (LLM function call), not an *extension* (the\n * sandboxed Alpine.js mini-app primitive). It lives in this directory because\n * it shares SSRF-safe URL/proxy helpers with the extension iframe proxy.\n *\n * Supports ${keys.NAME} reference substitution in URL, headers, and body.\n * Values are resolved server-side AFTER the model emits the tool call —\n * the raw secret never enters the model's context.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport type { ResolvedKeyReference } from \"../secrets/substitution.js\";\nimport {\n collectSecretValues,\n MAX_EXTENSION_PROXY_RESPONSE_SIZE,\n normalizeExtensionProxyMethod,\n readResponseTextWithLimit,\n redactSecrets,\n redactString,\n sanitizeOutboundHeaders,\n} from \"./proxy-security.js\";\nimport {\n createSsrfSafeDispatcher,\n isBlockedExtensionUrlWithDns,\n} from \"./url-safety.js\";\nimport {\n formatWebContentResult,\n parseWebContentSearchOptions,\n processWebContent,\n} from \"./web-content.js\";\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Headers that mimic a current Chrome on macOS so anti-bot middleware (Cloudflare,\n * PerimeterX, Akamai) treats the request as a real user. We only fill in fields\n * the caller hasn't supplied — explicit headers (e.g. an `Authorization` header\n * for an API call) always win.\n *\n * `Accept-Encoding` deliberately omits `zstd` because Node's undici fetch only\n * decompresses `gzip`, `deflate`, and `br`. Advertising `zstd` would let some\n * servers send bytes we can't decode.\n */\nconst BROWSER_DEFAULT_HEADERS: Record<string, string> = {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Accept:\n \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Language\": \"en-US,en;q=0.9\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Sec-Ch-Ua\":\n '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n};\n\nfunction applyBrowserDefaults(\n headers: Record<string, string>,\n): Record<string, string> {\n const seen = new Set(Object.keys(headers).map((k) => k.toLowerCase()));\n const merged = { ...headers };\n for (const [name, value] of Object.entries(BROWSER_DEFAULT_HEADERS)) {\n if (!seen.has(name.toLowerCase())) merged[name] = value;\n }\n return merged;\n}\n\nexport interface FetchToolOptions {\n /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */\n resolveKeys?: (text: string) => Promise<{\n resolved: string;\n usedKeys: string[];\n secretValues?: string[];\n /**\n * Optional: which scope (user/org/workspace) each used key actually\n * resolved at. Populated by resolvers like\n * `resolveKeyReferencesWithRequestScopes` so `validateUrl` can look up\n * the allowlist at the scope the value came from rather than assuming\n * user scope. Backwards compatible — callers that omit this (or a\n * resolver that doesn't report it) are unaffected.\n */\n resolvedKeys?: ResolvedKeyReference[];\n }>;\n /** Validate URL against per-key allowlists. */\n validateUrl?: (\n url: string,\n usedKeys: string[],\n /** Accumulated `resolvedKeys` from every `resolveKeys` call this request made, when the resolver reported them. */\n resolvedKeys?: ResolvedKeyReference[],\n ) => Promise<boolean>;\n}\n\n/**\n * Create the fetch tool entry for the agent tool registry.\n */\nexport function createFetchToolEntry(\n opts: FetchToolOptions = {},\n): Record<string, ActionEntry> {\n return {\n \"web-request\": {\n tool: {\n description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \\${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \\${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \\`search-records\\`, \\`provider-api-request\\`, \\`update-resource\\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,\n parameters: {\n type: \"object\" as const,\n properties: {\n url: {\n type: \"string\",\n description:\n 'Full URL. May contain ${keys.NAME} references, e.g. \"${keys.SLACK_WEBHOOK}\".',\n },\n method: {\n type: \"string\",\n description: \"HTTP method. Default: GET.\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\"],\n },\n headers: {\n type: \"string\",\n description:\n 'JSON object of headers. May contain ${keys.NAME} references. Example: \\'{\"Authorization\": \"Bearer ${keys.API_TOKEN}\"}\\'.',\n },\n body: {\n type: \"string\",\n description:\n \"Request body (for POST/PUT/PATCH). May contain ${keys.NAME} references.\",\n },\n timeout_ms: {\n type: \"number\",\n description: `Timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: 30000.`,\n },\n maxChars: {\n type: \"number\",\n description:\n \"Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.\",\n },\n responseMode: {\n type: \"string\",\n description:\n \"How to return the response. Default: auto (HTML pages become clean markdown; JSON/text stays raw). Use raw for exact bytes, markdown/text for extracted readable content, links for just links, metadata for page metadata, or matches with search.\",\n enum: [\n \"auto\",\n \"raw\",\n \"text\",\n \"markdown\",\n \"links\",\n \"metadata\",\n \"matches\",\n ],\n },\n extract: {\n type: \"string\",\n description:\n \"HTML extraction strategy. Default: readability. Use all-visible for visible body text/markdown, or none to convert the full HTML document.\",\n enum: [\"readability\", \"all-visible\", \"none\"],\n },\n includeLinks: {\n type: \"boolean\",\n description:\n \"Whether extracted HTML responses should include a compact links list. Default: true for extracted pages.\",\n },\n search: {\n type: \"object\",\n description:\n \"Optional post-fetch search over extracted content by default. Supports {query, queries, terms, regex, regexFlags, source:'extracted'|'raw', maxMatches, contextChars, caseSensitive}. Regex is safety-checked and bounded; prefer query/terms for simple grep-like searches.\",\n properties: {\n query: { type: \"string\" },\n queries: { type: \"array\", items: { type: \"string\" } },\n terms: { type: \"array\", items: { type: \"string\" } },\n regex: { type: \"string\" },\n regexFlags: { type: \"string\" },\n source: { type: \"string\", enum: [\"extracted\", \"raw\"] },\n maxMatches: { type: \"number\" },\n contextChars: { type: \"number\" },\n caseSensitive: { type: \"boolean\" },\n },\n } as any,\n saveToFile: {\n type: \"string\",\n description:\n \"Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/page.html'). When set, returns only a compact summary {savedTo, status, bytes, preview}. Useful for large web pages or API responses that would overflow context.\",\n },\n },\n required: [\"url\"],\n },\n },\n run: async (args: Record<string, unknown>) => {\n const startTime = Date.now();\n const rawUrl = String(args.url ?? \"\");\n const method = normalizeExtensionProxyMethod(args.method || \"GET\");\n if (!method) {\n return \"Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.\";\n }\n const rawHeaders =\n typeof args.headers === \"string\"\n ? args.headers\n : JSON.stringify(args.headers ?? {});\n const rawBody =\n typeof args.body === \"string\"\n ? args.body\n : args.body === undefined || args.body === null\n ? undefined\n : JSON.stringify(args.body);\n const timeoutMs = Math.min(\n Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS,\n 30_000,\n );\n const requestedMaxChars = Number(args.maxChars);\n const maxChars =\n Number.isFinite(requestedMaxChars) && requestedMaxChars > 0\n ? Math.min(requestedMaxChars, 200_000)\n : 32_000;\n\n // Resolve key references\n let resolvedUrl = rawUrl;\n let resolvedHeaders = rawHeaders;\n let resolvedBody = rawBody;\n const allUsedKeys: string[] = [];\n const allSecretValues: string[] = [];\n const allResolvedKeys: ResolvedKeyReference[] = [];\n\n if (opts.resolveKeys) {\n try {\n const urlResult = await opts.resolveKeys(rawUrl);\n resolvedUrl = urlResult.resolved;\n allUsedKeys.push(...urlResult.usedKeys);\n allSecretValues.push(...(urlResult.secretValues ?? []));\n allResolvedKeys.push(...(urlResult.resolvedKeys ?? []));\n\n const headerResult = await opts.resolveKeys(rawHeaders);\n resolvedHeaders = headerResult.resolved;\n allUsedKeys.push(...headerResult.usedKeys);\n allSecretValues.push(...(headerResult.secretValues ?? []));\n allResolvedKeys.push(...(headerResult.resolvedKeys ?? []));\n\n if (rawBody) {\n const bodyResult = await opts.resolveKeys(rawBody);\n resolvedBody = bodyResult.resolved;\n allUsedKeys.push(...bodyResult.usedKeys);\n allSecretValues.push(...(bodyResult.secretValues ?? []));\n allResolvedKeys.push(...(bodyResult.resolvedKeys ?? []));\n }\n } catch (err: any) {\n return `Error resolving key references: ${err?.message ?? err}`;\n }\n }\n const secretValues = collectSecretValues(allSecretValues);\n const resolvedKeys = allResolvedKeys.length\n ? allResolvedKeys\n : undefined;\n\n // Block SSRF targets regardless of key usage\n if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {\n return `Requests to private/internal addresses are not allowed: \"${rawUrl}\".`;\n }\n\n // Validate URL against per-key allowlists\n if (opts.validateUrl && allUsedKeys.length > 0) {\n try {\n const allowed = await opts.validateUrl(\n resolvedUrl,\n allUsedKeys,\n resolvedKeys,\n );\n if (!allowed) {\n return `URL \"${rawUrl}\" is not in the allowlist for the referenced keys. Check your key settings.`;\n }\n } catch (err: any) {\n return `URL validation error: ${err?.message ?? err}`;\n }\n }\n\n // Parse headers, then merge in browser-like defaults for any header the\n // caller didn't already specify. Real-browser headers (User-Agent,\n // Accept, Sec-Fetch-*) are what gets you past Cloudflare / PerimeterX /\n // generic UA-sniffing middleware on sites the user pastes in chat;\n // explicit caller headers always win so API calls keep their auth\n // headers untouched.\n let headers: Record<string, string>;\n try {\n headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));\n } catch {\n return `Invalid headers JSON: ${rawHeaders}`;\n }\n headers = applyBrowserDefaults(headers);\n\n // Make the request\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n method,\n headers,\n signal: controller.signal,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n if (resolvedBody && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOpts.body = resolvedBody;\n if (!headers[\"content-type\"] && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n const response = await fetch(resolvedUrl, fetchOpts);\n const elapsed = Date.now() - startTime;\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n const redirectUrl = location\n ? new URL(location, resolvedUrl).href\n : null;\n if (\n redirectUrl &&\n (await isBlockedExtensionUrlWithDns(redirectUrl))\n ) {\n return \"Redirect to private/internal address blocked.\";\n }\n if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {\n const allowed = await opts.validateUrl(\n redirectUrl,\n allUsedKeys,\n resolvedKeys,\n );\n if (!allowed) {\n return \"Redirect URL is not in the allowlist for the referenced keys.\";\n }\n }\n return `HTTP ${response.status} ${response.statusText}\\n\\nRedirect: ${\n redirectUrl ? redactString(redirectUrl, secretValues) : \"(none)\"\n }`;\n }\n\n // Check if caller wants to save to workspace file (before truncation).\n const saveToFilePath =\n typeof (args as Record<string, unknown>).saveToFile === \"string\"\n ? ((args as Record<string, unknown>).saveToFile as string).trim()\n : \"\";\n\n let body: string;\n try {\n // When saving to file allow larger reads (20MB), otherwise cap at proxy limit.\n const readLimit = saveToFilePath\n ? 20 * 1024 * 1024\n : MAX_EXTENSION_PROXY_RESPONSE_SIZE;\n const result = await readResponseTextWithLimit(response, readLimit);\n body = result.text;\n } catch {\n body = \"(could not read response body)\";\n }\n body = redactString(body, secretValues);\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\")[0].trim() ??\n \"text/plain\";\n let displayBody: string;\n let processedMode = \"raw\";\n try {\n const processed = processWebContent({\n url: resolvedUrl,\n body,\n contentType,\n responseMode: String(args.responseMode ?? \"auto\"),\n extract: String(args.extract ?? \"readability\"),\n includeLinks:\n args.includeLinks === undefined\n ? true\n : parseBooleanArg(args.includeLinks),\n search: parseWebContentSearchOptions(args.search),\n maxChars,\n });\n processedMode = processed.mode;\n displayBody = formatWebContentResult(processed);\n } catch (err: any) {\n return `web-request post-processing error: ${err?.message ?? String(err)}`;\n }\n\n // Audit log\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(\",\") || \"none\"})`,\n );\n\n // saveToFile: write full body to workspace and return compact summary.\n if (saveToFilePath) {\n try {\n const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } =\n await import(\"../workspace-files/store.js\");\n const { getRequestOrgId, getRequestUserEmail } =\n await import(\"../server/request-context.js\");\n const orgId = getRequestOrgId();\n const email = getRequestUserEmail();\n const scope = orgId\n ? { scope: \"org\" as const, scopeId: orgId }\n : email\n ? { scope: \"user\" as const, scopeId: email }\n : null;\n if (!scope)\n throw new Error(\"No authenticated context for saveToFile\");\n await writeWorkspaceFile(\n scope,\n saveToFilePath,\n body,\n contentType,\n {\n maxFileBytes: SAVE_TO_FILE_MAX_BYTES,\n },\n );\n const bytes = Buffer.byteLength(body, \"utf8\");\n const preview = displayBody.slice(0, 2000);\n return JSON.stringify({\n savedToFile: true,\n savedTo: saveToFilePath,\n status: response.status,\n bytes,\n contentType,\n responseMode: processedMode,\n preview:\n preview.length < displayBody.length ? `${preview}…` : preview,\n });\n } catch (saveErr: any) {\n return `saveToFile error: ${saveErr?.message ?? String(saveErr)}\\n\\nHTTP ${response.status} ${response.statusText}\\n\\n${body.slice(0, maxChars)}`;\n }\n }\n\n return `HTTP ${response.status} ${response.statusText}\\n\\n${displayBody}`;\n } catch (err: any) {\n const elapsed = Date.now() - startTime;\n if (err?.name === \"AbortError\") {\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → TIMEOUT (${elapsed}ms)`,\n );\n return `Request timed out after ${timeoutMs}ms.`;\n }\n const message = redactSecrets(\n err?.message ?? String(err),\n secretValues,\n );\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ERROR: ${message} (${elapsed}ms)`,\n );\n return `Request failed: ${message}`;\n } finally {\n clearTimeout(timeout);\n }\n },\n readOnly: true,\n },\n };\n}\n\nfunction parseBooleanArg(value: unknown): boolean {\n if (typeof value === \"boolean\") return value;\n const normalized = String(value).trim().toLowerCase();\n return normalized === \"true\" || normalized === \"1\" || normalized === \"yes\";\n}\n"]}
@@ -27,7 +27,7 @@
27
27
  * `metadata.emailRecipients`.
28
28
  */
29
29
  import { ssrfSafeFetch } from "../extensions/url-safety.js";
30
- import { resolveKeyReferences, validateUrlAllowlist, getKeyAllowlist, } from "../secrets/substitution.js";
30
+ import { resolveKeyReferencesWithRequestScopes, validateUrlAllowlist, getKeyAllowlist, } from "../secrets/substitution.js";
31
31
  import { sendEmail } from "../server/email.js";
32
32
  import { registerNotificationChannel } from "./registry.js";
33
33
  let _registered = false;
@@ -153,19 +153,35 @@ function createEmailChannel() {
153
153
  };
154
154
  }
155
155
  async function resolveWebhookRequest(urlTemplate, authTemplate, owner, label) {
156
- // Resolve `${keys.NAME}` references against the owner's user-scope secrets.
156
+ // Resolve `${keys.NAME}` references through the same request-scope
157
+ // cascade already used by extension fetches (extensions/routes.ts) and
158
+ // automation connector headers (automation/index.ts): user scope first
159
+ // (a personal override always wins), then the active org scope — where
160
+ // the Dispatch vault syncs workspace secrets — then workspace scope.
161
+ // Org/workspace vault rows are write-gated (org-admin + Dispatch vault
162
+ // UI), so reading them here is safe by default; this is unrelated to the
163
+ // opt-in-only user→workspace fallback in resolveKeyReferences (audit 05
164
+ // H2 in secrets/substitution.ts), which stays off. In headless contexts
165
+ // (e.g. a scheduled monitor check) getRequestOrgId() may be unset, in
166
+ // which case the cascade checks user + solo-workspace scopes only — a
167
+ // strict superset of the previous user-scope-only behavior.
157
168
  // Missing keys throw — the error surfaces in logs and the channel is marked
158
169
  // un-delivered, but other channels still run.
159
- const { resolved: url } = await resolveKeyReferences(urlTemplate, "user", owner);
170
+ const { resolved: url } = await resolveKeyReferencesWithRequestScopes(urlTemplate, owner);
160
171
  const headers = {
161
172
  "Content-Type": "application/json",
162
173
  };
163
174
  if (authTemplate) {
164
- const { resolved: auth } = await resolveKeyReferences(authTemplate, "user", owner);
175
+ const { resolved: auth } = await resolveKeyReferencesWithRequestScopes(authTemplate, owner);
165
176
  headers.Authorization = auth;
166
177
  }
167
178
  // If the user set an allowlist on a referenced key, enforce it here —
168
179
  // origin-level check, same rule the automations fetch-tool applies.
180
+ // NOTE: this still checks the allowlist at user scope only (unlike the
181
+ // fetch tool's validateUrl, which now consults getResolvedKeyAllowlist at
182
+ // the scope the key actually resolved at — see agent-chat-plugin.ts). A
183
+ // key that resolves at org/workspace scope with an allowlist configured
184
+ // on that org/workspace row will not have it enforced here.
169
185
  const keyNames = Array.from(new Set(Array.from(urlTemplate.matchAll(/\$\{keys\.([A-Za-z0-9_-]+)\}/g), (m) => String(m[1]))));
170
186
  const allowlists = await Promise.all(keyNames.map((name) => getKeyAllowlist(name, "user", owner)));
171
187
  keyNames.forEach((name, i) => {