@agent-native/core 0.100.1 → 0.101.2

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 (387) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +73 -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/AssistantChat.tsx +4 -0
  37. package/corpus/core/src/client/analytics.ts +8 -1
  38. package/corpus/core/src/client/chat/markdown-renderer.tsx +54 -8
  39. package/corpus/core/src/client/chat/message-components.tsx +8 -2
  40. package/corpus/core/src/client/clipboard.ts +28 -1
  41. package/corpus/core/src/client/composer/PromptComposer.tsx +4 -0
  42. package/corpus/core/src/client/composer/TiptapComposer.tsx +30 -3
  43. package/corpus/core/src/client/guided-questions.tsx +4 -0
  44. package/corpus/core/src/client/i18n.tsx +2 -1
  45. package/corpus/core/src/client/index.ts +4 -0
  46. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +108 -0
  47. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +482 -150
  48. package/corpus/core/src/client/review/index.ts +8 -0
  49. package/corpus/core/src/client/review/use-review.ts +35 -1
  50. package/corpus/core/src/client/use-agent-chat-context.ts +3 -2
  51. package/corpus/core/src/integrations/webhook-handler.ts +4 -2
  52. package/corpus/core/src/review/actions/create-review-comment.ts +2 -1
  53. package/corpus/core/src/review/actions/get-review-feedback.ts +6 -11
  54. package/corpus/core/src/review/actions/list-review-comments.ts +38 -4
  55. package/corpus/core/src/review/actions/reply-review-comment.ts +33 -22
  56. package/corpus/core/src/review/actions/resolve-review-thread.ts +29 -2
  57. package/corpus/core/src/review/actions/send-review-thread-to-agent.ts +62 -0
  58. package/corpus/core/src/review/identity.ts +101 -0
  59. package/corpus/core/src/review/index.ts +14 -0
  60. package/corpus/core/src/review/store.ts +315 -8
  61. package/corpus/core/src/review/types.ts +4 -0
  62. package/corpus/core/src/secrets/crypto.ts +98 -47
  63. package/corpus/core/src/secrets/index.ts +2 -1
  64. package/corpus/core/src/secrets/schema.ts +3 -0
  65. package/corpus/core/src/secrets/storage.ts +164 -31
  66. package/corpus/core/src/server/action-discovery.ts +4 -0
  67. package/corpus/core/src/server/action-routes.ts +125 -4
  68. package/corpus/core/src/server/agent-chat/plugin-options.ts +16 -0
  69. package/corpus/core/src/server/agent-chat-plugin.ts +1 -0
  70. package/corpus/core/src/server/index.ts +8 -0
  71. package/corpus/core/src/vite/action-types-plugin.ts +4 -0
  72. package/corpus/templates/analytics/changelog/2026-07-14-daily-dashboard-email-captures-authenticate-with-a-sessi.md +6 -0
  73. package/corpus/templates/analytics/server/lib/dashboard-report.ts +156 -3
  74. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +402 -142
  75. package/corpus/templates/assets/app/components/layout/Layout.tsx +4 -0
  76. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
  77. package/corpus/templates/assets/app/i18n/zh-TW.ts +1 -0
  78. package/corpus/templates/assets/app/i18n-data.ts +1 -0
  79. package/corpus/templates/assets/app/routes/library.tsx +187 -48
  80. package/corpus/templates/assets/changelog/2026-07-13-assets-keeps-the-right-chat-sidebar-closed-when-the-full-pag.md +6 -0
  81. package/corpus/templates/assets/changelog/2026-07-13-merged-the-candidates-panel-into-the-library-as-a-drafts-tab.md +6 -0
  82. package/corpus/templates/chat/app/lib/agent-page.tsx +40 -0
  83. package/corpus/templates/chat/app/routes/agent.tsx +5 -3
  84. package/corpus/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
  85. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +5 -0
  86. package/corpus/templates/clips/AGENTS.md +4 -0
  87. package/corpus/templates/clips/actions/get-feature-flags.ts +30 -0
  88. package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
  89. package/corpus/templates/clips/actions/request-transcript.ts +41 -1
  90. package/corpus/templates/clips/actions/view-screen.ts +5 -9
  91. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  92. package/corpus/templates/clips/app/components/player/video-player.tsx +39 -1
  93. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -12
  94. package/corpus/templates/clips/app/hooks/use-mse-video-source.ts +146 -0
  95. package/corpus/templates/clips/app/lib/capture-install-options.ts +21 -0
  96. package/corpus/templates/clips/app/lib/fmp4.ts +271 -0
  97. package/corpus/templates/clips/app/lib/mse-video-loader.ts +585 -0
  98. package/corpus/templates/clips/app/routes/_app.dictate.tsx +0 -4
  99. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -13
  100. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +2 -37
  101. package/corpus/templates/clips/app/routes/download.tsx +3 -2
  102. package/corpus/templates/clips/changelog/2026-07-10-shared-clips-now-start-playing-instantly-instead-of-showing-.md +6 -0
  103. package/corpus/templates/clips/changelog/2026-07-14-clips-now-distinguishes-transcript-previews-from-incomplete-.md +6 -0
  104. package/corpus/templates/clips/changelog/2026-07-14-desktop-app-prompts-stay-hidden-after-you-download-the-insta.md +6 -0
  105. package/corpus/templates/clips/desktop/src/lib/recorder.ts +5 -0
  106. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +10 -0
  107. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -0
  108. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +2518 -0
  109. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +353 -0
  110. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +558 -67
  111. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +11 -1
  112. package/corpus/templates/clips/desktop/src-tauri/src/remote_flags.rs +188 -0
  113. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +1 -1
  114. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +1 -0
  115. package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -0
  116. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +5 -1
  117. package/corpus/templates/clips/shared/feature-flags.ts +41 -0
  118. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +3 -0
  119. package/corpus/templates/design/.agents/skills/design-review-feedback/SKILL.md +42 -0
  120. package/corpus/templates/design/AGENTS.md +6 -0
  121. package/corpus/templates/design/README.md +1 -0
  122. package/corpus/templates/design/actions/navigate.ts +4 -4
  123. package/corpus/templates/design/actions/view-screen.ts +116 -2
  124. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  125. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +52 -59
  126. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +13 -9
  127. package/corpus/templates/design/app/components/design/EditPanel.tsx +79 -11
  128. package/corpus/templates/design/app/components/design/LayersPanel.tsx +78 -65
  129. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +161 -0
  130. package/corpus/templates/design/app/components/design/ReviewStatusControl.tsx +96 -0
  131. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +166 -0
  132. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  133. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +1077 -0
  134. package/corpus/templates/design/app/components/visual-editor/index.ts +1 -0
  135. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +42 -0
  136. package/corpus/templates/design/app/hooks/use-design-systems.ts +2 -2
  137. package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
  138. package/corpus/templates/design/app/hooks/use-question-flow.ts +7 -1
  139. package/corpus/templates/design/app/i18n/ar-SA.ts +56 -1
  140. package/corpus/templates/design/app/i18n/de-DE.ts +57 -1
  141. package/corpus/templates/design/app/i18n/en-US.ts +56 -1
  142. package/corpus/templates/design/app/i18n/es-ES.ts +56 -1
  143. package/corpus/templates/design/app/i18n/fr-FR.ts +57 -1
  144. package/corpus/templates/design/app/i18n/hi-IN.ts +56 -1
  145. package/corpus/templates/design/app/i18n/ja-JP.ts +56 -1
  146. package/corpus/templates/design/app/i18n/ko-KR.ts +56 -1
  147. package/corpus/templates/design/app/i18n/pt-BR.ts +57 -1
  148. package/corpus/templates/design/app/i18n/zh-CN.ts +56 -1
  149. package/corpus/templates/design/app/i18n/zh-TW.ts +51 -0
  150. package/corpus/templates/design/app/pages/Present.tsx +172 -14
  151. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +23 -0
  152. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +10 -0
  153. package/corpus/templates/design/app/pages/present-review-state.ts +18 -0
  154. package/corpus/templates/design/app/public-routes.ts +8 -0
  155. package/corpus/templates/design/app/root.tsx +7 -6
  156. package/corpus/templates/design/changelog/2026-07-13-dragged-design-assets-now-appear-at-the-visible-drop-locatio.md +6 -0
  157. package/corpus/templates/design/changelog/2026-07-13-reviewers-can-pin-comments-on-shared-designs-and-apply-verif.md +6 -0
  158. package/corpus/templates/design/server/plugins/agent-chat.ts +10 -0
  159. package/corpus/templates/design/server/plugins/auth.ts +7 -5
  160. package/corpus/templates/design/server/plugins/review.ts +9 -0
  161. package/corpus/templates/design/shared/review-anchor.ts +83 -0
  162. package/corpus/templates/design/shared/review-summary.ts +25 -0
  163. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +2 -0
  164. package/corpus/templates/forms/AGENTS.md +4 -0
  165. package/corpus/templates/forms/actions/create-form.ts +2 -1
  166. package/corpus/templates/forms/actions/update-form.ts +5 -2
  167. package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
  168. package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
  169. package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
  170. package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
  171. package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
  172. package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
  173. package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
  174. package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
  175. package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
  176. package/corpus/templates/forms/app/i18n/zh-CN.ts +3 -0
  177. package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
  178. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +19 -0
  179. package/corpus/templates/forms/changelog/2026-07-14-fixed-the-ask-forms-loading-input-appearing-above-the-welcom.md +6 -0
  180. package/corpus/templates/forms/changelog/2026-07-14-form-social-previews-now-show-the-form-title-description-and.md +6 -0
  181. package/corpus/templates/forms/changelog/2026-07-14-forms-can-email-the-form-owner-when-new-responses-arrive.md +6 -0
  182. package/corpus/templates/forms/server/handlers/submissions.ts +17 -0
  183. package/corpus/templates/forms/server/lib/form-og-image.ts +64 -6
  184. package/corpus/templates/forms/server/lib/public-form-ssr.ts +6 -2
  185. package/corpus/templates/forms/server/lib/response-email.ts +65 -0
  186. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  187. package/corpus/templates/forms/server/routes/api/forms/og/[...slug]/og.png.get.ts +184 -0
  188. package/corpus/templates/forms/shared/types.ts +2 -0
  189. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +6 -4
  190. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +7 -1
  191. package/dist/a2a/index.d.ts +2 -1
  192. package/dist/a2a/index.d.ts.map +1 -1
  193. package/dist/a2a/index.js +1 -1
  194. package/dist/a2a/index.js.map +1 -1
  195. package/dist/a2a/server.d.ts +25 -0
  196. package/dist/a2a/server.d.ts.map +1 -1
  197. package/dist/a2a/server.js +30 -6
  198. package/dist/a2a/server.js.map +1 -1
  199. package/dist/agent/production-agent.d.ts +2 -0
  200. package/dist/agent/production-agent.d.ts.map +1 -1
  201. package/dist/agent/production-agent.js +37 -12
  202. package/dist/agent/production-agent.js.map +1 -1
  203. package/dist/cli/create.d.ts +5 -3
  204. package/dist/cli/create.d.ts.map +1 -1
  205. package/dist/cli/create.js +44 -19
  206. package/dist/cli/create.js.map +1 -1
  207. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
  208. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  209. package/dist/cli/pr-visual-recap-workflow.js +2 -2
  210. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  211. package/dist/cli/recap.d.ts +3 -562
  212. package/dist/cli/recap.d.ts.map +1 -1
  213. package/dist/cli/recap.js +3 -3875
  214. package/dist/cli/recap.js.map +1 -1
  215. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  216. package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
  217. package/dist/cli/skills-content/visual-plan-skill.js +6 -4
  218. package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
  219. package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
  220. package/dist/cli/skills-content/visual-recap-skill.d.ts.map +1 -1
  221. package/dist/cli/skills-content/visual-recap-skill.js +7 -1
  222. package/dist/cli/skills-content/visual-recap-skill.js.map +1 -1
  223. package/dist/client/AgentPanel.d.ts.map +1 -1
  224. package/dist/client/AgentPanel.js +3 -3
  225. package/dist/client/AgentPanel.js.map +1 -1
  226. package/dist/client/AssistantChat.d.ts +2 -0
  227. package/dist/client/AssistantChat.d.ts.map +1 -1
  228. package/dist/client/AssistantChat.js +2 -2
  229. package/dist/client/AssistantChat.js.map +1 -1
  230. package/dist/client/analytics.d.ts +5 -0
  231. package/dist/client/analytics.d.ts.map +1 -1
  232. package/dist/client/analytics.js +3 -1
  233. package/dist/client/analytics.js.map +1 -1
  234. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  235. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  236. package/dist/client/chat/markdown-renderer.js +36 -1
  237. package/dist/client/chat/markdown-renderer.js.map +1 -1
  238. package/dist/client/chat/message-components.d.ts.map +1 -1
  239. package/dist/client/chat/message-components.js +5 -2
  240. package/dist/client/chat/message-components.js.map +1 -1
  241. package/dist/client/clipboard.d.ts +3 -1
  242. package/dist/client/clipboard.d.ts.map +1 -1
  243. package/dist/client/clipboard.js +24 -1
  244. package/dist/client/clipboard.js.map +1 -1
  245. package/dist/client/composer/PromptComposer.d.ts +2 -0
  246. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  247. package/dist/client/composer/PromptComposer.js +2 -2
  248. package/dist/client/composer/PromptComposer.js.map +1 -1
  249. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  250. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  251. package/dist/client/composer/TiptapComposer.js +20 -5
  252. package/dist/client/composer/TiptapComposer.js.map +1 -1
  253. package/dist/client/guided-questions.d.ts +3 -1
  254. package/dist/client/guided-questions.d.ts.map +1 -1
  255. package/dist/client/guided-questions.js +2 -1
  256. package/dist/client/guided-questions.js.map +1 -1
  257. package/dist/client/i18n.d.ts.map +1 -1
  258. package/dist/client/i18n.js +3 -1
  259. package/dist/client/i18n.js.map +1 -1
  260. package/dist/client/index.d.ts +1 -1
  261. package/dist/client/index.d.ts.map +1 -1
  262. package/dist/client/index.js +1 -1
  263. package/dist/client/index.js.map +1 -1
  264. package/dist/client/review/ReviewCommentComposer.d.ts +18 -0
  265. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -0
  266. package/dist/client/review/ReviewCommentComposer.js +30 -0
  267. package/dist/client/review/ReviewCommentComposer.js.map +1 -0
  268. package/dist/client/review/ReviewThreadPanel.d.ts +28 -1
  269. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  270. package/dist/client/review/ReviewThreadPanel.js +151 -39
  271. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  272. package/dist/client/review/index.d.ts +3 -2
  273. package/dist/client/review/index.d.ts.map +1 -1
  274. package/dist/client/review/index.js +2 -1
  275. package/dist/client/review/index.js.map +1 -1
  276. package/dist/client/review/use-review.d.ts +23 -0
  277. package/dist/client/review/use-review.d.ts.map +1 -1
  278. package/dist/client/review/use-review.js +3 -0
  279. package/dist/client/review/use-review.js.map +1 -1
  280. package/dist/client/use-agent-chat-context.d.ts +1 -1
  281. package/dist/client/use-agent-chat-context.d.ts.map +1 -1
  282. package/dist/client/use-agent-chat-context.js +4 -2
  283. package/dist/client/use-agent-chat-context.js.map +1 -1
  284. package/dist/collab/awareness.d.ts +2 -2
  285. package/dist/collab/awareness.d.ts.map +1 -1
  286. package/dist/collab/struct-routes.d.ts +1 -1
  287. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  288. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  289. package/dist/integrations/webhook-handler.js +3 -2
  290. package/dist/integrations/webhook-handler.js.map +1 -1
  291. package/dist/notifications/routes.d.ts +3 -3
  292. package/dist/observability/routes.d.ts +5 -5
  293. package/dist/progress/routes.d.ts +1 -1
  294. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  295. package/dist/resources/handlers.d.ts +2 -2
  296. package/dist/review/actions/create-review-comment.js +2 -1
  297. package/dist/review/actions/create-review-comment.js.map +1 -1
  298. package/dist/review/actions/get-review-feedback.js +6 -11
  299. package/dist/review/actions/get-review-feedback.js.map +1 -1
  300. package/dist/review/actions/list-review-comments.d.ts +1 -0
  301. package/dist/review/actions/list-review-comments.js +25 -4
  302. package/dist/review/actions/list-review-comments.js.map +1 -1
  303. package/dist/review/actions/reply-review-comment.js +12 -5
  304. package/dist/review/actions/reply-review-comment.js.map +1 -1
  305. package/dist/review/actions/resolve-review-thread.d.ts +3 -0
  306. package/dist/review/actions/resolve-review-thread.js +20 -3
  307. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  308. package/dist/review/actions/send-review-thread-to-agent.d.ts +17 -0
  309. package/dist/review/actions/send-review-thread-to-agent.d.ts.map +1 -0
  310. package/dist/review/actions/send-review-thread-to-agent.js +49 -0
  311. package/dist/review/actions/send-review-thread-to-agent.js.map +1 -0
  312. package/dist/review/identity.d.ts +6 -0
  313. package/dist/review/identity.d.ts.map +1 -0
  314. package/dist/review/identity.js +68 -0
  315. package/dist/review/identity.js.map +1 -0
  316. package/dist/review/index.d.ts +3 -1
  317. package/dist/review/index.d.ts.map +1 -1
  318. package/dist/review/index.js +2 -1
  319. package/dist/review/index.js.map +1 -1
  320. package/dist/review/store.d.ts +36 -0
  321. package/dist/review/store.d.ts.map +1 -1
  322. package/dist/review/store.js +223 -8
  323. package/dist/review/store.js.map +1 -1
  324. package/dist/review/types.d.ts +4 -0
  325. package/dist/review/types.d.ts.map +1 -1
  326. package/dist/review/types.js.map +1 -1
  327. package/dist/secrets/crypto.d.ts +35 -13
  328. package/dist/secrets/crypto.d.ts.map +1 -1
  329. package/dist/secrets/crypto.js +78 -41
  330. package/dist/secrets/crypto.js.map +1 -1
  331. package/dist/secrets/index.d.ts.map +1 -1
  332. package/dist/secrets/index.js +2 -1
  333. package/dist/secrets/index.js.map +1 -1
  334. package/dist/secrets/routes.d.ts +9 -9
  335. package/dist/secrets/schema.d.ts +20 -1
  336. package/dist/secrets/schema.d.ts.map +1 -1
  337. package/dist/secrets/schema.js +3 -0
  338. package/dist/secrets/schema.js.map +1 -1
  339. package/dist/secrets/storage.d.ts +7 -7
  340. package/dist/secrets/storage.d.ts.map +1 -1
  341. package/dist/secrets/storage.js +121 -30
  342. package/dist/secrets/storage.js.map +1 -1
  343. package/dist/server/action-discovery.d.ts.map +1 -1
  344. package/dist/server/action-discovery.js +4 -0
  345. package/dist/server/action-discovery.js.map +1 -1
  346. package/dist/server/action-routes.d.ts +45 -0
  347. package/dist/server/action-routes.d.ts.map +1 -1
  348. package/dist/server/action-routes.js +71 -5
  349. package/dist/server/action-routes.js.map +1 -1
  350. package/dist/server/agent-chat/plugin-options.d.ts +16 -0
  351. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  352. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  353. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  354. package/dist/server/agent-chat-plugin.js +1 -0
  355. package/dist/server/agent-chat-plugin.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/forms/server/routes/api/forms/og/[slug]/og.png.get.ts +0 -45
@@ -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 {
@@ -1,17 +1,23 @@
1
1
  /**
2
2
  * Shared AES-256-GCM encryption for secret values at rest.
3
3
  *
4
- * Used by both the framework secrets vault (`app_secrets`) and per-user/per-org
5
- * credentials (`resolveCredential` / `saveCredential`, stored in `settings`) so
6
- * there is a single crypto implementation and a single key story.
4
+ * Used by the framework secrets vault, per-user/per-org credentials
5
+ * (`resolveCredential` / `saveCredential`, stored in `settings`), and other
6
+ * column-level encrypted values. The `app_secrets` storage layer uses the
7
+ * shared-key variant below because workspace-scoped vault rows are readable by
8
+ * sibling apps in the same workspace. When a deployment has not been given
9
+ * shared key material yet, that variant falls back to the app-scoped key so
10
+ * existing single-app deployments can keep reading and writing secrets while
11
+ * they migrate to the shared key.
7
12
  *
8
- * The encryption key is derived from `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when
9
- * set, then `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. The app-scoped
10
- * key lets a local multi-app workspace read one app's encrypted production data
11
- * without replacing the shared local auth secret. In production we refuse to
12
- * start without configured key material a CWD-derived fallback would be
13
- * effectively static (e.g. `/var/task` on Lambda), so anyone with read access
14
- * to the DB could decrypt every secret.
13
+ * The default encryption key is derived from
14
+ * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,
15
+ * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app
16
+ * workspace read one app's encrypted production data without replacing the
17
+ * shared local auth secret. 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.
15
21
  *
16
22
  * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
17
23
  * lets readers distinguish ciphertext from legacy plaintext during migration.
@@ -67,45 +73,26 @@ function appScopedEncryptionKey(): string | undefined {
67
73
  : undefined;
68
74
  }
69
75
 
70
- /**
71
- * Derive a 32-byte AES key from the configured secret material via SHA-256.
72
- * Re-derived per call (cheap, stateless, and makes rotation easy).
73
- */
74
- export function getSecretEncryptionKey(): Buffer {
76
+ function sharedEncryptionKeyMaterial(): string | undefined {
77
+ if (typeof process === "undefined") return undefined;
78
+ return process.env.SECRETS_ENCRYPTION_KEY || process.env.BETTER_AUTH_SECRET;
79
+ }
80
+
81
+ function deriveSecretEncryptionKey(
82
+ explicit: string | undefined,
83
+ errorMessage: string,
84
+ warningMessage: string,
85
+ ): Buffer {
75
86
  const { createHash } = requireNodeCrypto();
76
- const explicit =
77
- appScopedEncryptionKey() ||
78
- (typeof process === "undefined"
79
- ? undefined
80
- : process.env.SECRETS_ENCRYPTION_KEY) ||
81
- (typeof process === "undefined"
82
- ? undefined
83
- : process.env.BETTER_AUTH_SECRET);
84
87
 
85
88
  if (!explicit) {
86
89
  if (processNodeEnv() === "production") {
87
- const appName =
88
- typeof process === "undefined"
89
- ? undefined
90
- : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.
91
- .toUpperCase()
92
- .replace(/[^A-Z0-9]+/g, "_")
93
- .replace(/^_+|_+$/g, "");
94
- throw new Error(
95
- "[agent-native/secrets] Refusing to start in production without an encryption key. " +
96
- `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
97
- "The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
98
- "which means anyone with read access to the secrets table could decrypt every user's secrets.",
99
- );
90
+ throw new Error(errorMessage);
100
91
  }
101
92
  if (!_warnedFallback) {
102
93
  _warnedFallback = true;
103
94
  // eslint-disable-next-line no-console
104
- console.warn(
105
- "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. " +
106
- "Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. " +
107
- "Production deploys without one of these env vars now hard-fail.",
108
- );
95
+ console.warn(warningMessage);
109
96
  }
110
97
  }
111
98
 
@@ -113,10 +100,56 @@ export function getSecretEncryptionKey(): Buffer {
113
100
  return createHash("sha256").update(material).digest();
114
101
  }
115
102
 
116
- /** Encrypt a plain-text value. Returns `v1:<iv-hex>:<ct-hex>:<tag-hex>`. */
117
- export function encryptSecretValue(plaintext: string): string {
103
+ /**
104
+ * Derive the key used by generic encrypted values such as credentials and
105
+ * OAuth tokens. App-specific key material is allowed for these app-local
106
+ * values.
107
+ */
108
+ export function getSecretEncryptionKey(): Buffer {
109
+ const appName =
110
+ typeof process === "undefined"
111
+ ? undefined
112
+ : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.
113
+ .toUpperCase()
114
+ .replace(/[^A-Z0-9]+/g, "_")
115
+ .replace(/^_+|_+$/g, "");
116
+ return deriveSecretEncryptionKey(
117
+ appScopedEncryptionKey() || sharedEncryptionKeyMaterial(),
118
+ "[agent-native/secrets] Refusing to start in production without an encryption key. " +
119
+ `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
120
+ "The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
121
+ "which means anyone with read access to the DB could decrypt every secret.",
122
+ "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. " +
123
+ "Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. " +
124
+ "Production deploys without one of these env vars now hard-fail.",
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Derive the preferred workspace-shared key used by `app_secrets` rows.
130
+ * Unlike generic column-level encryption, workspace vault data should decrypt
131
+ * 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.
135
+ */
136
+ export function getSharedSecretEncryptionKey(): Buffer {
137
+ return deriveSecretEncryptionKey(
138
+ sharedEncryptionKeyMaterial() || appScopedEncryptionKey(),
139
+ "[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.",
141
+ "[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.",
143
+ );
144
+ }
145
+
146
+ /** Whether this deployment has stable workspace-shared key material. */
147
+ export function hasSharedSecretEncryptionKeyMaterial(): boolean {
148
+ return Boolean(sharedEncryptionKeyMaterial());
149
+ }
150
+
151
+ function encryptWithKey(plaintext: string, key: Buffer): string {
118
152
  const { createCipheriv, randomBytes } = requireNodeCrypto();
119
- const key = getSecretEncryptionKey();
120
153
  const iv = randomBytes(12);
121
154
  const cipher = createCipheriv("aes-256-gcm", key, iv);
122
155
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
@@ -124,8 +157,7 @@ export function encryptSecretValue(plaintext: string): string {
124
157
  return `v1:${iv.toString("hex")}:${ct.toString("hex")}:${tag.toString("hex")}`;
125
158
  }
126
159
 
127
- /** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */
128
- export function decryptSecretValue(encrypted: string): string {
160
+ function decryptWithKey(encrypted: string, key: Buffer): string {
129
161
  const { createDecipheriv } = requireNodeCrypto();
130
162
  if (!encrypted.startsWith("v1:")) {
131
163
  throw new Error("Unrecognised secret encoding");
@@ -134,7 +166,6 @@ export function decryptSecretValue(encrypted: string): string {
134
166
  if (!ivHex || !ctHex || !tagHex) {
135
167
  throw new Error("Corrupt secret payload");
136
168
  }
137
- const key = getSecretEncryptionKey();
138
169
  const decipher = createDecipheriv(
139
170
  "aes-256-gcm",
140
171
  key,
@@ -148,6 +179,26 @@ export function decryptSecretValue(encrypted: string): string {
148
179
  return pt.toString("utf8");
149
180
  }
150
181
 
182
+ /** Encrypt a plain-text value with the generic app-local key. */
183
+ export function encryptSecretValue(plaintext: string): string {
184
+ return encryptWithKey(plaintext, getSecretEncryptionKey());
185
+ }
186
+
187
+ /** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */
188
+ export function decryptSecretValue(encrypted: string): string {
189
+ return decryptWithKey(encrypted, getSecretEncryptionKey());
190
+ }
191
+
192
+ /** Encrypt a workspace-shared `app_secrets` value. */
193
+ export function encryptSharedSecretValue(plaintext: string): string {
194
+ return encryptWithKey(plaintext, getSharedSecretEncryptionKey());
195
+ }
196
+
197
+ /** Decrypt a workspace-shared `app_secrets` value. */
198
+ export function decryptSharedSecretValue(encrypted: string): string {
199
+ return decryptWithKey(encrypted, getSharedSecretEncryptionKey());
200
+ }
201
+
151
202
  /**
152
203
  * Strict check for a value produced by `encryptSecretValue`: `v1:` followed by
153
204
  * three hex segments. Intentionally strict so a legacy plaintext credential
@@ -38,7 +38,8 @@ export { APP_SECRETS_CREATE_SQL, appSecrets } from "./schema.js";
38
38
 
39
39
  // AES-256-GCM helpers for column-level encryption of per-row secret values
40
40
  // (e.g. a per-record share password) that don't fit the keyed app_secrets /
41
- // credentials stores. Same key story as the vault.
41
+ // credentials stores. These use the app-local key, unlike the workspace-shared
42
+ // app_secrets storage layer.
42
43
  export {
43
44
  encryptSecretValue,
44
45
  decryptSecretValue,
@@ -22,6 +22,8 @@ export const appSecrets = table("app_secrets", {
22
22
  key: text("key").notNull(),
23
23
  /** Encrypted value — never return this through any API. */
24
24
  encryptedValue: text("encrypted_value").notNull(),
25
+ /** Preferred workspace-shared ciphertext; nullable during key migration. */
26
+ sharedEncryptedValue: text("shared_encrypted_value"),
25
27
  /** Optional human-readable description (used for ad-hoc keys). */
26
28
  description: text("description"),
27
29
  /** JSON array of allowed URL origins. Null = allow all. */
@@ -41,6 +43,7 @@ export const APP_SECRETS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS app_secrets (
41
43
  scope_id TEXT NOT NULL,
42
44
  key TEXT NOT NULL,
43
45
  encrypted_value TEXT NOT NULL,
46
+ shared_encrypted_value TEXT,
44
47
  description TEXT,
45
48
  url_allowlist TEXT,
46
49
  created_at INTEGER NOT NULL,