@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
@@ -0,0 +1,101 @@
1
+ import type {
2
+ ReviewComment,
3
+ ReviewResourceAccess,
4
+ ReviewResourceContext,
5
+ ReviewStatusEntry,
6
+ } from "./types.js";
7
+
8
+ export function reviewAuthorNameFromContext(
9
+ ctx: ReviewResourceContext | undefined,
10
+ ): string | null {
11
+ const value = ctx?.userName;
12
+ if (typeof value !== "string") return null;
13
+ const name = value.trim();
14
+ if (!name || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(name)) return null;
15
+ return name;
16
+ }
17
+
18
+ export function shouldRedactReviewIdentity(
19
+ ctx: ReviewResourceContext | undefined,
20
+ access: Pick<ReviewResourceAccess, "role" | "visibility">,
21
+ ): boolean {
22
+ return (
23
+ !ctx?.userEmail ||
24
+ (access.visibility === "public" && access.role === "viewer")
25
+ );
26
+ }
27
+
28
+ export function redactPublicReviewCommentIdentity(
29
+ comment: ReviewComment,
30
+ ): ReviewComment {
31
+ return {
32
+ ...comment,
33
+ authorEmail: null,
34
+ authorName: safeReviewDisplayName(comment.authorName),
35
+ mentions: comment.mentions.map((mention) => ({
36
+ label: safeReviewDisplayName(mention.label) ?? "Mentioned user",
37
+ })),
38
+ ownerEmail: null,
39
+ orgId: null,
40
+ resolvedBy: null,
41
+ deletedBy: null,
42
+ metadata: redactPublicReviewMetadata(comment.metadata),
43
+ };
44
+ }
45
+
46
+ export function redactPublicReviewStatusIdentity(
47
+ status: ReviewStatusEntry | null,
48
+ ): ReviewStatusEntry | null {
49
+ return status
50
+ ? {
51
+ ...status,
52
+ updatedBy: null,
53
+ ownerEmail: null,
54
+ orgId: null,
55
+ metadata: redactPublicReviewMetadata(status.metadata),
56
+ }
57
+ : null;
58
+ }
59
+
60
+ function safeReviewDisplayName(
61
+ value: string | null | undefined,
62
+ ): string | null {
63
+ const name = value?.trim();
64
+ if (!name || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(name)) {
65
+ return null;
66
+ }
67
+ return name;
68
+ }
69
+
70
+ function redactPublicReviewMetadata(
71
+ metadata: Record<string, unknown> | null,
72
+ ): Record<string, unknown> | null {
73
+ if (!metadata) {
74
+ return null;
75
+ }
76
+ return Object.fromEntries(
77
+ Object.entries(metadata).flatMap(([key, value]) =>
78
+ /^(authorEmail|ownerEmail|orgId|updatedBy|resolvedBy|deletedBy|email|userEmail|userId)$/i.test(
79
+ key,
80
+ )
81
+ ? []
82
+ : [[key, redactPublicReviewMetadataValue(value)]],
83
+ ),
84
+ );
85
+ }
86
+
87
+ function redactPublicReviewMetadataValue(value: unknown): unknown {
88
+ if (Array.isArray(value)) {
89
+ return value.map(redactPublicReviewMetadataValue);
90
+ }
91
+ if (typeof value === "object" && value !== null) {
92
+ return redactPublicReviewMetadata(value as Record<string, unknown>);
93
+ }
94
+ if (
95
+ typeof value === "string" &&
96
+ /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())
97
+ ) {
98
+ return null;
99
+ }
100
+ return value;
101
+ }
@@ -13,6 +13,12 @@ export type {
13
13
  ReviewStatusEntry,
14
14
  ReviewableResourceRegistration,
15
15
  } from "./types.js";
16
+ export {
17
+ redactPublicReviewCommentIdentity,
18
+ redactPublicReviewStatusIdentity,
19
+ reviewAuthorNameFromContext,
20
+ shouldRedactReviewIdentity,
21
+ } from "./identity.js";
16
22
  export { extractReviewMentions, normalizeReviewMentions } from "./mentions.js";
17
23
  export {
18
24
  __resetReviewableResourcesForTests,
@@ -27,5 +33,13 @@ export {
27
33
  ensureReviewTables,
28
34
  getReviewCommentById,
29
35
  getReviewStatus,
36
+ getReviewThreadSummary,
37
+ getReviewThreadRoot,
30
38
  queryReviewComments,
39
+ routeReviewThread,
40
+ sendReviewThreadToAgent,
41
+ } from "./store.js";
42
+ export type {
43
+ GetReviewThreadSummaryInput,
44
+ ReviewThreadSummary,
31
45
  } from "./store.js";
@@ -1,4 +1,4 @@
1
- import { getDbExec, isPostgres } from "../db/client.js";
1
+ import { getDbExec, isPostgres, type DbExec } from "../db/client.js";
2
2
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
3
  import type { Visibility } from "../sharing/schema.js";
4
4
  import type {
@@ -43,9 +43,25 @@ export interface QueryReviewCommentsInput {
43
43
  includeResolved?: boolean;
44
44
  includeDeleted?: boolean;
45
45
  targetId?: string | null;
46
+ rootOnly?: boolean;
47
+ resolutionTargets?: readonly (ReviewResolutionTarget | null)[];
48
+ unconsumedOnly?: boolean;
46
49
  limit?: number;
47
50
  }
48
51
 
52
+ export interface GetReviewThreadSummaryInput {
53
+ resourceType: string;
54
+ resourceId: string;
55
+ scope: ReviewScope;
56
+ bypassScope?: boolean;
57
+ targetId?: string | null;
58
+ }
59
+
60
+ export interface ReviewThreadSummary {
61
+ openCount: number;
62
+ agentQueueCount: number;
63
+ }
64
+
49
65
  export interface UpsertReviewStatusInput {
50
66
  resourceType: string;
51
67
  resourceId: string;
@@ -112,6 +128,15 @@ export async function ensureReviewTables(): Promise<void> {
112
128
  ON agent_review_comments (owner_email, created_at)`,
113
129
  `CREATE INDEX IF NOT EXISTS idx_agent_review_comments_org
114
130
  ON agent_review_comments (org_id, created_at)`,
131
+ `CREATE INDEX IF NOT EXISTS idx_agent_review_comments_queue
132
+ ON agent_review_comments (
133
+ resource_type,
134
+ resource_id,
135
+ parent_comment_id,
136
+ resolution_target,
137
+ consumed_at,
138
+ created_at
139
+ )`,
115
140
  `CREATE INDEX IF NOT EXISTS idx_agent_review_statuses_resource
116
141
  ON agent_review_statuses (resource_type, resource_id)`,
117
142
  ];
@@ -126,9 +151,10 @@ export async function ensureReviewTables(): Promise<void> {
126
151
  await ensureIndexExists("idx_agent_review_comments_thread", indexes[1]);
127
152
  await ensureIndexExists("idx_agent_review_comments_owner", indexes[2]);
128
153
  await ensureIndexExists("idx_agent_review_comments_org", indexes[3]);
154
+ await ensureIndexExists("idx_agent_review_comments_queue", indexes[4]);
129
155
  await ensureIndexExists(
130
156
  "idx_agent_review_statuses_resource",
131
- indexes[4],
157
+ indexes[5],
132
158
  );
133
159
  } else {
134
160
  await client.execute(createCommentsSql);
@@ -145,9 +171,62 @@ export async function ensureReviewTables(): Promise<void> {
145
171
 
146
172
  export async function insertReviewComment(
147
173
  input: InsertReviewCommentInput,
174
+ ): Promise<ReviewComment> {
175
+ await ensureReviewTables();
176
+ return insertReviewCommentWithClient(input, getDbExec());
177
+ }
178
+
179
+ export async function insertReviewReply(
180
+ input: InsertReviewCommentInput & {
181
+ threadId: string;
182
+ parentCommentId: string;
183
+ },
184
+ routeTarget: ReviewResolutionTarget | null,
185
+ resource: { resourceType: string; resourceId: string },
148
186
  ): Promise<ReviewComment> {
149
187
  await ensureReviewTables();
150
188
  const client = getDbExec();
189
+ const insertAndRoute = async (tx: DbExec) => {
190
+ if (routeTarget) {
191
+ const routedCount = await routeReviewThreadWithClient(
192
+ tx,
193
+ input.threadId,
194
+ routeTarget,
195
+ resource,
196
+ );
197
+ if (routedCount < 1) {
198
+ throw new Error("Open review thread not found");
199
+ }
200
+ }
201
+ return insertReviewCommentWithClient(input, tx);
202
+ };
203
+ if (client.transaction) return client.transaction(insertAndRoute);
204
+
205
+ const reply = await insertReviewCommentWithClient(input, client);
206
+ if (!routeTarget) return reply;
207
+ try {
208
+ const routedCount = await routeReviewThreadWithClient(
209
+ client,
210
+ input.threadId,
211
+ routeTarget,
212
+ resource,
213
+ );
214
+ if (routedCount < 1) throw new Error("Open review thread not found");
215
+ return reply;
216
+ } catch (error) {
217
+ await client.execute({
218
+ sql: `DELETE FROM agent_review_comments
219
+ WHERE id = ? AND resource_type = ? AND resource_id = ?`,
220
+ args: [reply.id, resource.resourceType, resource.resourceId],
221
+ });
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ async function insertReviewCommentWithClient(
227
+ input: InsertReviewCommentInput,
228
+ client: DbExec,
229
+ ): Promise<ReviewComment> {
151
230
  const id = createReviewId("comment");
152
231
  const now = new Date().toISOString();
153
232
  const comment: ReviewComment = {
@@ -180,6 +259,10 @@ export async function insertReviewComment(
180
259
  createdAt: now,
181
260
  updatedAt: now,
182
261
  metadata: input.metadata ?? null,
262
+ resolutionNote:
263
+ typeof input.metadata?.resolutionNote === "string"
264
+ ? input.metadata.resolutionNote
265
+ : null,
183
266
  };
184
267
 
185
268
  await client.execute({
@@ -273,18 +356,107 @@ export async function queryReviewComments(
273
356
  filterParams.push(input.targetId);
274
357
  }
275
358
  }
359
+ if (input.rootOnly) {
360
+ filters.push("parent_comment_id IS NULL");
361
+ }
362
+ if (input.unconsumedOnly) {
363
+ filters.push("consumed_at IS NULL");
364
+ }
365
+ if (input.resolutionTargets !== undefined) {
366
+ const targets = Array.from(
367
+ new Set(
368
+ input.resolutionTargets.filter(
369
+ (target): target is ReviewResolutionTarget => target !== null,
370
+ ),
371
+ ),
372
+ );
373
+ const resolutionFilters: string[] = [];
374
+ if (targets.length > 0) {
375
+ resolutionFilters.push(
376
+ `resolution_target IN (${targets.map(() => "?").join(", ")})`,
377
+ );
378
+ filterParams.push(...targets);
379
+ }
380
+ if (input.resolutionTargets.includes(null)) {
381
+ resolutionFilters.push("resolution_target IS NULL");
382
+ }
383
+ filters.push(
384
+ resolutionFilters.length > 0
385
+ ? `(${resolutionFilters.join(" OR ")})`
386
+ : "1 = 0",
387
+ );
388
+ }
276
389
 
390
+ const selectSql = input.rootOnly
391
+ ? `SELECT ${commentColumns()}
392
+ FROM (
393
+ SELECT ${commentColumns()},
394
+ ROW_NUMBER() OVER (
395
+ PARTITION BY thread_id
396
+ ORDER BY created_at ASC, id ASC
397
+ ) AS review_thread_rank
398
+ FROM agent_review_comments
399
+ WHERE ${filters.join(" AND ")}
400
+ ) AS distinct_review_threads
401
+ WHERE review_thread_rank = 1`
402
+ : `SELECT ${commentColumns()}
403
+ FROM agent_review_comments
404
+ WHERE ${filters.join(" AND ")}`;
277
405
  const result = await client.execute({
278
- sql: `SELECT ${commentColumns()}
279
- FROM agent_review_comments
280
- WHERE ${filters.join(" AND ")}
281
- ORDER BY created_at ASC
406
+ sql: `${selectSql}
407
+ ORDER BY created_at ASC${input.rootOnly ? ", id ASC" : ""}
282
408
  LIMIT ?`,
283
409
  args: [...filterParams, clampLimit(input.limit)],
284
410
  });
285
411
  return (result.rows ?? []).map(mapCommentRow);
286
412
  }
287
413
 
414
+ export async function getReviewThreadSummary(
415
+ input: GetReviewThreadSummaryInput,
416
+ ): Promise<ReviewThreadSummary> {
417
+ await ensureReviewTables();
418
+ const { clause, params } = input.bypassScope
419
+ ? { clause: "1 = 1", params: [] as unknown[] }
420
+ : scopedReviewClause(input.scope);
421
+ const filters = [
422
+ "resource_type = ?",
423
+ "resource_id = ?",
424
+ clause,
425
+ "parent_comment_id IS NULL",
426
+ "deleted_at IS NULL",
427
+ "status = 'open'",
428
+ ];
429
+ const filterParams: unknown[] = [
430
+ input.resourceType,
431
+ input.resourceId,
432
+ ...params,
433
+ ];
434
+ if (input.targetId !== undefined) {
435
+ if (input.targetId === null) {
436
+ filters.push("target_id IS NULL");
437
+ } else {
438
+ filters.push("target_id = ?");
439
+ filterParams.push(input.targetId);
440
+ }
441
+ }
442
+ const result = await getDbExec().execute({
443
+ sql: `SELECT COUNT(DISTINCT thread_id) AS open_count,
444
+ COUNT(DISTINCT CASE
445
+ WHEN (resolution_target IS NULL OR resolution_target <> 'human')
446
+ AND consumed_at IS NULL
447
+ THEN thread_id
448
+ END) AS agent_queue_count
449
+ FROM agent_review_comments
450
+ WHERE ${filters.join(" AND ")}`,
451
+ args: filterParams,
452
+ });
453
+ const row = result.rows?.[0];
454
+ return {
455
+ openCount: Number(row?.open_count ?? 0),
456
+ agentQueueCount: Number(row?.agent_queue_count ?? 0),
457
+ };
458
+ }
459
+
288
460
  export async function getReviewCommentById(
289
461
  id: string,
290
462
  scope: ReviewScope,
@@ -306,27 +478,106 @@ export async function getReviewCommentById(
306
478
  return row ? mapCommentRow(row) : null;
307
479
  }
308
480
 
481
+ export async function getReviewThreadRoot(
482
+ threadId: string,
483
+ resource: { resourceType: string; resourceId: string },
484
+ scope: ReviewScope,
485
+ options: { bypassScope?: boolean } = {},
486
+ ): Promise<ReviewComment | null> {
487
+ await ensureReviewTables();
488
+ const { clause, params } = options.bypassScope
489
+ ? { clause: "1 = 1", params: [] as unknown[] }
490
+ : scopedReviewClause(scope);
491
+ const result = await getDbExec().execute({
492
+ sql: `SELECT ${commentColumns()}
493
+ FROM agent_review_comments
494
+ WHERE thread_id = ?
495
+ AND resource_type = ?
496
+ AND resource_id = ?
497
+ AND parent_comment_id IS NULL
498
+ AND ${clause}
499
+ ORDER BY created_at ASC, id ASC
500
+ LIMIT 1`,
501
+ args: [threadId, resource.resourceType, resource.resourceId, ...params],
502
+ });
503
+ const row = result.rows?.[0];
504
+ return row ? mapCommentRow(row) : null;
505
+ }
506
+
309
507
  export async function resolveReviewThread(
310
508
  threadId: string,
311
509
  resolvedBy?: string | null,
312
510
  resource?: { resourceType: string; resourceId: string },
511
+ resolutionNote?: string,
313
512
  ): Promise<number> {
314
513
  await ensureReviewTables();
514
+ const client = getDbExec();
515
+ const resolve = (tx: DbExec) =>
516
+ resolveReviewThreadWithClient(
517
+ tx,
518
+ threadId,
519
+ resolvedBy,
520
+ resource,
521
+ resolutionNote,
522
+ );
523
+ return client.transaction ? client.transaction(resolve) : resolve(client);
524
+ }
525
+
526
+ async function resolveReviewThreadWithClient(
527
+ client: DbExec,
528
+ threadId: string,
529
+ resolvedBy?: string | null,
530
+ resource?: { resourceType: string; resourceId: string },
531
+ resolutionNote?: string,
532
+ ): Promise<number> {
315
533
  const now = new Date().toISOString();
316
534
  const resourceClause = resource
317
535
  ? "AND resource_type = ? AND resource_id = ?"
318
536
  : "";
319
- const result = await getDbExec().execute({
537
+ let rootMetadata: Record<string, unknown> | null = null;
538
+ if (resolutionNote !== undefined) {
539
+ const root = await client.execute({
540
+ sql: `SELECT metadata_json
541
+ FROM agent_review_comments
542
+ WHERE thread_id = ?
543
+ AND parent_comment_id IS NULL
544
+ AND deleted_at IS NULL
545
+ ${resourceClause}
546
+ ORDER BY created_at ASC, id ASC
547
+ LIMIT 1`,
548
+ args: [
549
+ threadId,
550
+ ...(resource ? [resource.resourceType, resource.resourceId] : []),
551
+ ],
552
+ });
553
+ if (!root.rows?.[0]) {
554
+ return 0;
555
+ }
556
+ rootMetadata = {
557
+ ...(parseObject(root.rows[0].metadata_json) ?? {}),
558
+ resolutionNote,
559
+ };
560
+ }
561
+
562
+ const metadataAssignment =
563
+ resolutionNote === undefined
564
+ ? ""
565
+ : ", metadata_json = CASE WHEN parent_comment_id IS NULL THEN ? ELSE metadata_json END";
566
+ const result = await client.execute({
320
567
  sql: `UPDATE agent_review_comments
321
568
  SET status = 'resolved',
322
569
  resolved_by = ?,
323
570
  resolved_at = ?,
324
571
  updated_at = ?
572
+ ${metadataAssignment}
325
573
  WHERE thread_id = ? AND deleted_at IS NULL ${resourceClause}`,
326
574
  args: [
327
575
  resolvedBy ?? null,
328
576
  now,
329
577
  now,
578
+ ...(resolutionNote === undefined
579
+ ? []
580
+ : [stringifyOptionalJson(rootMetadata)]),
330
581
  threadId,
331
582
  ...(resource ? [resource.resourceType, resource.resourceId] : []),
332
583
  ],
@@ -334,6 +585,57 @@ export async function resolveReviewThread(
334
585
  return result.rowsAffected ?? 0;
335
586
  }
336
587
 
588
+ export async function routeReviewThread(
589
+ threadId: string,
590
+ resolutionTarget: ReviewResolutionTarget,
591
+ resource: { resourceType: string; resourceId: string },
592
+ ): Promise<number> {
593
+ await ensureReviewTables();
594
+ return routeReviewThreadWithClient(
595
+ getDbExec(),
596
+ threadId,
597
+ resolutionTarget,
598
+ resource,
599
+ );
600
+ }
601
+
602
+ async function routeReviewThreadWithClient(
603
+ client: DbExec,
604
+ threadId: string,
605
+ resolutionTarget: ReviewResolutionTarget,
606
+ resource: { resourceType: string; resourceId: string },
607
+ ): Promise<number> {
608
+ const now = new Date().toISOString();
609
+ const result = await client.execute({
610
+ sql: `UPDATE agent_review_comments
611
+ SET resolution_target = ?,
612
+ consumed_at = CASE WHEN ? = 'agent' THEN NULL ELSE consumed_at END,
613
+ updated_at = ?
614
+ WHERE thread_id = ?
615
+ AND resource_type = ?
616
+ AND resource_id = ?
617
+ AND parent_comment_id IS NULL
618
+ AND status = 'open'
619
+ AND deleted_at IS NULL`,
620
+ args: [
621
+ resolutionTarget,
622
+ resolutionTarget,
623
+ now,
624
+ threadId,
625
+ resource.resourceType,
626
+ resource.resourceId,
627
+ ],
628
+ });
629
+ return result.rowsAffected ?? 0;
630
+ }
631
+
632
+ export async function sendReviewThreadToAgent(
633
+ threadId: string,
634
+ resource: { resourceType: string; resourceId: string },
635
+ ): Promise<number> {
636
+ return routeReviewThread(threadId, "agent", resource);
637
+ }
638
+
337
639
  export async function deleteReviewComment(
338
640
  id: string,
339
641
  deletedBy?: string | null,
@@ -560,6 +862,7 @@ function scopedReviewClause(scope: ReviewScope): {
560
862
  }
561
863
 
562
864
  function mapCommentRow(row: Record<string, unknown>): ReviewComment {
865
+ const metadata = parseObject(row.metadata_json);
563
866
  return {
564
867
  id: String(row.id),
565
868
  resourceType: String(row.resource_type),
@@ -586,7 +889,11 @@ function mapCommentRow(row: Record<string, unknown>): ReviewComment {
586
889
  deletedAt: nullableString(row.deleted_at),
587
890
  createdAt: String(row.created_at),
588
891
  updatedAt: String(row.updated_at),
589
- metadata: parseObject(row.metadata_json),
892
+ metadata,
893
+ resolutionNote:
894
+ typeof metadata?.resolutionNote === "string"
895
+ ? metadata.resolutionNote
896
+ : null,
590
897
  };
591
898
  }
592
899
 
@@ -75,6 +75,10 @@ export interface ReviewComment {
75
75
  createdAt: string;
76
76
  updatedAt: string;
77
77
  metadata: Record<string, unknown> | null;
78
+ /** Persisted on the root comment's metadata when a thread is resolved. */
79
+ resolutionNote?: string | null;
80
+ /** Caller-specific capability computed by list-review-comments. */
81
+ canDelete?: boolean;
78
82
  }
79
83
 
80
84
  export interface ReviewStatusEntry {
@@ -14,15 +14,22 @@
14
14
  * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,
15
15
  * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app
16
16
  * workspace read one app's encrypted production data without replacing the
17
- * shared local auth secret. In production we refuse to start without
18
- * configured key material a CWD-derived fallback would be effectively
19
- * static (e.g. `/var/task` on Lambda), so anyone with read access to the DB
20
- * could decrypt every secret.
17
+ * shared local auth secret. Hosted workspace deploys (`AGENT_NATIVE_WORKSPACE`)
18
+ * typically set none of those literal env vars per app; in that case the
19
+ * shared-key variant falls back further to material derived from the
20
+ * workspace-wide `A2A_SECRET` (see `getWorkspaceA2ADerivedSecret`), so sibling
21
+ * apps in the same workspace still land on the same shared key without any
22
+ * extra configuration. In production we refuse to start without configured
23
+ * key material — a CWD-derived fallback would be effectively static (e.g.
24
+ * `/var/task` on Lambda), so anyone with read access to the DB could decrypt
25
+ * every secret.
21
26
  *
22
27
  * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
23
28
  * lets readers distinguish ciphertext from legacy plaintext during migration.
24
29
  */
25
30
 
31
+ import { getWorkspaceA2ADerivedSecret } from "../server/derived-secret.js";
32
+
26
33
  type NodeCryptoModule = typeof import("node:crypto");
27
34
 
28
35
  function getNodeCrypto(): NodeCryptoModule | undefined {
@@ -75,7 +82,11 @@ function appScopedEncryptionKey(): string | undefined {
75
82
 
76
83
  function sharedEncryptionKeyMaterial(): string | undefined {
77
84
  if (typeof process === "undefined") return undefined;
78
- return process.env.SECRETS_ENCRYPTION_KEY || process.env.BETTER_AUTH_SECRET;
85
+ return (
86
+ process.env.SECRETS_ENCRYPTION_KEY ||
87
+ process.env.BETTER_AUTH_SECRET ||
88
+ getWorkspaceA2ADerivedSecret("secrets-encryption")
89
+ );
79
90
  }
80
91
 
81
92
  function deriveSecretEncryptionKey(
@@ -129,17 +140,22 @@ export function getSecretEncryptionKey(): Buffer {
129
140
  * Derive the preferred workspace-shared key used by `app_secrets` rows.
130
141
  * Unlike generic column-level encryption, workspace vault data should decrypt
131
142
  * in sibling apps, so `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` take
132
- * precedence. The app-scoped key remains a compatibility fallback for
133
- * deployments that have not configured shared material yet; once the shared
134
- * key is configured, writes use it and reads still fall back to old rows.
143
+ * precedence. Hosted workspace deploys (`AGENT_NATIVE_WORKSPACE`) that set
144
+ * neither literal var still get stable shared material derived from the
145
+ * workspace-wide `A2A_SECRET` before falling further back. The app-scoped key
146
+ * remains a compatibility fallback for deployments that have not configured
147
+ * shared material yet; once the shared key is configured, writes use it and
148
+ * reads still fall back to old rows.
135
149
  */
136
150
  export function getSharedSecretEncryptionKey(): Buffer {
137
151
  return deriveSecretEncryptionKey(
138
152
  sharedEncryptionKeyMaterial() || appScopedEncryptionKey(),
139
153
  "[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. " +
140
- "Set SECRETS_ENCRYPTION_KEY, BETTER_AUTH_SECRET, or the app-scoped *_SECRETS_ENCRYPTION_KEY in the deploy environment.",
154
+ "Set SECRETS_ENCRYPTION_KEY, BETTER_AUTH_SECRET, or the app-scoped *_SECRETS_ENCRYPTION_KEY in the deploy environment " +
155
+ "— or, on a hosted workspace deploy, ensure A2A_SECRET is set so shared material can be derived from it.",
141
156
  "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using app-scoped or machine-local fallback for workspace secrets. " +
142
- "Set SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET in every workspace app so sibling apps share vault rows.",
157
+ "Set SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET in every workspace app so sibling apps share vault rows, " +
158
+ "or rely on A2A_SECRET-derived material on hosted workspace deploys.",
143
159
  );
144
160
  }
145
161
 
@@ -163,8 +163,9 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
163
163
  const now = Date.now();
164
164
  // Dual-write during rollout: old readers continue using encrypted_value,
165
165
  // while new readers prefer the nullable shared ciphertext. An app-only
166
- // deployment leaves the shared column null; on an update, an existing
167
- // shared ciphertext is preserved by the upsert below.
166
+ // deployment leaves the shared column null; on an update, a writer without
167
+ // shared key material clears any existing shared ciphertext rather than
168
+ // preserving it (see the upsert SQL below for why).
168
169
  const encrypted = encryptLegacyValue(value);
169
170
  const sharedEncrypted = hasSharedSecretEncryptionKeyMaterial()
170
171
  ? encryptValue(value)
@@ -182,11 +183,23 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
182
183
  // keeps its original id (any stored references stay stable); only a
183
184
  // genuinely new row gets the freshly generated `id`. This syntax is
184
185
  // portable across SQLite (UPSERT since 3.24) and Postgres.
186
+ //
187
+ // shared_encrypted_value is overwritten with `excluded.shared_encrypted_value`
188
+ // (NULL when this writer lacks shared key material) rather than preserved
189
+ // via COALESCE. Preserving an existing shared ciphertext across a value
190
+ // update would let a sibling app silently decrypt a STALE value after the
191
+ // owner rotates it — a material-less writer has no way to produce the new
192
+ // shared ciphertext, so it must clear the old one instead of leaving it
193
+ // pointing at data that's no longer current. Siblings then get an honest
194
+ // cache miss (falling back to the legacy column or reporting missing) until
195
+ // the owning app's next read repopulates shared_encrypted_value via
196
+ // `populateSharedAppSecret`, which only ever fills a NULL column. A
197
+ // temporary miss is safer than serving rotated-away plaintext.
185
198
  const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)
186
199
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
187
200
  ON CONFLICT (scope, scope_id, key) DO UPDATE SET
188
201
  encrypted_value = excluded.encrypted_value,
189
- shared_encrypted_value = COALESCE(excluded.shared_encrypted_value, app_secrets.shared_encrypted_value),
202
+ shared_encrypted_value = excluded.shared_encrypted_value,
190
203
  description = excluded.description,
191
204
  url_allowlist = excluded.url_allowlist,
192
205
  updated_at = excluded.updated_at`;
@@ -686,6 +686,10 @@ export async function mergeCoreSharingActions(
686
686
  "set-review-status",
687
687
  () => import("../review/actions/set-review-status.js"),
688
688
  ],
689
+ [
690
+ "send-review-thread-to-agent",
691
+ () => import("../review/actions/send-review-thread-to-agent.js"),
692
+ ],
689
693
  // Org service tokens (CI credentials, e.g. PLAN_RECAP_TOKEN). Mint/revoke
690
694
  // are toolCallable:false — preserved via preserveActionFlags below.
691
695
  [