@agent-native/core 0.102.1 → 0.103.0

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 (295) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/docs/content/local-file-mode.mdx +99 -489
  4. package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
  5. package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
  6. package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
  7. package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
  8. package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
  9. package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
  10. package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
  11. package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
  12. package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
  13. package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
  14. package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
  15. package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
  16. package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
  17. package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
  18. package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
  19. package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
  20. package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
  21. package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
  22. package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
  23. package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
  24. package/corpus/core/docs/content/template-content.mdx +26 -42
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/cli/content-local.ts +86 -18
  27. package/corpus/core/src/cli/index.ts +2 -2
  28. package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
  29. package/corpus/core/src/cli/skills.ts +47 -13
  30. package/corpus/core/src/client/AgentPanel.tsx +7 -0
  31. package/corpus/core/src/client/AssistantChat.tsx +7 -0
  32. package/corpus/core/src/client/agent-chat-adapter.ts +7 -1
  33. package/corpus/core/src/client/chat/message-components.tsx +17 -3
  34. package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +96 -6
  35. package/corpus/core/src/client/composer/asset-picker-url.ts +80 -0
  36. package/corpus/core/src/client/error-format.ts +24 -0
  37. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +8 -1
  38. package/corpus/core/src/local-artifacts/index.ts +65 -0
  39. package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
  40. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
  41. package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
  42. package/corpus/templates/assets/app/i18n-data.ts +215 -0
  43. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
  44. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
  45. package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
  46. package/corpus/templates/assets/app/routes/library.tsx +69 -0
  47. package/corpus/templates/assets/changelog/2026-07-15-fixed-top-level-image-picker-selections-returning-to-the-com.md +6 -0
  48. package/corpus/templates/clips/actions/disconnect-calendar.ts +57 -9
  49. package/corpus/templates/clips/actions/generate-workflow.ts +102 -59
  50. package/corpus/templates/clips/actions/get-recording-player-data.ts +64 -1
  51. package/corpus/templates/clips/actions/regenerate-summary.ts +7 -0
  52. package/corpus/templates/clips/actions/regenerate-title.ts +29 -11
  53. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +34 -13
  54. package/corpus/templates/clips/app/components/editor/thumbnail-picker.tsx +87 -9
  55. package/corpus/templates/clips/app/components/editor/trim-handles.tsx +15 -0
  56. package/corpus/templates/clips/app/components/editor/waveform.tsx +21 -8
  57. package/corpus/templates/clips/app/components/library/folder-tree.tsx +52 -0
  58. package/corpus/templates/clips/app/components/player/settings-panel.tsx +116 -58
  59. package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -37
  60. package/corpus/templates/clips/app/components/player/video-player.tsx +50 -2
  61. package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
  62. package/corpus/templates/clips/app/i18n/ar-SA.ts +5 -2
  63. package/corpus/templates/clips/app/i18n/de-DE.ts +6 -2
  64. package/corpus/templates/clips/app/i18n/en-US.ts +5 -2
  65. package/corpus/templates/clips/app/i18n/es-ES.ts +6 -2
  66. package/corpus/templates/clips/app/i18n/fr-FR.ts +5 -2
  67. package/corpus/templates/clips/app/i18n/hi-IN.ts +5 -2
  68. package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -1
  69. package/corpus/templates/clips/app/i18n/ko-KR.ts +5 -2
  70. package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -2
  71. package/corpus/templates/clips/app/i18n/zh-CN.ts +5 -2
  72. package/corpus/templates/clips/app/i18n/zh-TW.ts +5 -2
  73. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +14 -0
  74. package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -2
  75. package/corpus/templates/clips/app/routes/_index.tsx +1 -2
  76. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +98 -32
  77. package/corpus/templates/clips/app/routes/share.$shareId.tsx +4 -1
  78. package/corpus/templates/clips/changelog/2026-07-15-ai-workflow-generation-now-stays-visible-avoids-duplicate-re.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-15-clip-playback-and-cta-controls-remain-interactive-through-ed.md +6 -0
  80. package/corpus/templates/clips/changelog/2026-07-15-clip-titles-no-longer-describe-the-product-as-a-loom-alterna.md +6 -0
  81. package/corpus/templates/clips/changelog/2026-07-15-description-regeneration-now-opens-the-agent-chat-so-progres.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-07-15-direct-recording-urls-now-respect-share-link-and-password-ac.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-07-15-disconnecting-a-calendar-now-clears-unrecorded-synced-meetin.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-07-15-folders-now-expose-rename-and-delete-actions-directly-in-the.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-07-15-recording-pages-now-explain-sign-in-access-and-avoid-showing.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-07-15-the-clips-extension-quick-actions-toolbar-can-be-moved-out-o.md +6 -0
  87. package/corpus/templates/clips/changelog/2026-07-15-the-editor-now-skips-cut-sections-during-preview-and-keeps-t.md +6 -0
  88. package/corpus/templates/clips/changelog/2026-07-15-thumbnail-capture-now-updates-previews-while-scrubbing-and-l.md +6 -0
  89. package/corpus/templates/clips/changelog/2026-07-15-windows-permission-links-open-the-matching-settings-page.md +6 -0
  90. package/corpus/templates/clips/chrome-extension/src/content-script.ts +117 -2
  91. package/corpus/templates/clips/chrome-extension/src/overlay.ts +27 -0
  92. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -1
  93. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +139 -101
  94. package/corpus/templates/clips/server/lib/recording-page-access.ts +35 -0
  95. package/corpus/templates/clips/server/lib/share-password.ts +6 -3
  96. package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
  97. package/corpus/templates/content/AGENTS.md +25 -30
  98. package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
  99. package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
  100. package/corpus/templates/content/actions/_content-files.ts +338 -0
  101. package/corpus/templates/content/actions/_content-space-access.ts +135 -0
  102. package/corpus/templates/content/actions/_content-spaces.ts +640 -0
  103. package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
  104. package/corpus/templates/content/actions/_database-utils.ts +7 -1
  105. package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
  106. package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
  107. package/corpus/templates/content/actions/_property-utils.ts +9 -4
  108. package/corpus/templates/content/actions/add-database-item.ts +108 -65
  109. package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
  110. package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
  111. package/corpus/templates/content/actions/create-content-database.ts +129 -3
  112. package/corpus/templates/content/actions/create-document.ts +65 -55
  113. package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
  114. package/corpus/templates/content/actions/delete-content-database.ts +3 -0
  115. package/corpus/templates/content/actions/delete-document.ts +7 -11
  116. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
  117. package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
  118. package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
  119. package/corpus/templates/content/actions/edit-document.ts +2 -28
  120. package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
  121. package/corpus/templates/content/actions/export-content-source.ts +47 -9
  122. package/corpus/templates/content/actions/get-document.ts +0 -14
  123. package/corpus/templates/content/actions/import-content-source.ts +43 -3
  124. package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
  125. package/corpus/templates/content/actions/list-documents.ts +12 -9
  126. package/corpus/templates/content/actions/move-document.ts +3 -14
  127. package/corpus/templates/content/actions/pull-document.ts +0 -23
  128. package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
  129. package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
  130. package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
  131. package/corpus/templates/content/actions/search-documents.ts +0 -34
  132. package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
  133. package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
  134. package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
  135. package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
  136. package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
  137. package/corpus/templates/content/actions/update-document.ts +0 -15
  138. package/corpus/templates/content/actions/view-screen.ts +2 -50
  139. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
  140. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
  141. package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
  142. package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
  143. package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
  144. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
  145. package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
  146. package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
  147. package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
  148. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
  149. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
  150. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
  151. package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
  152. package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
  153. package/corpus/templates/content/app/i18n-data.ts +75 -0
  154. package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
  155. package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
  156. package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
  157. package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
  158. package/corpus/templates/content/parity/matrix.md +2 -1
  159. package/corpus/templates/content/parity/matrix.ts +37 -1
  160. package/corpus/templates/content/scripts/dev-database.mjs +7 -5
  161. package/corpus/templates/content/server/db/schema.ts +113 -40
  162. package/corpus/templates/content/server/lib/document-context.ts +15 -1
  163. package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
  164. package/corpus/templates/content/server/plugins/db.ts +153 -0
  165. package/corpus/templates/content/shared/api.ts +17 -3
  166. package/corpus/templates/content/shared/builder-mdx.ts +163 -16
  167. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
  168. package/corpus/templates/design/AGENTS.md +6 -1
  169. package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
  170. package/corpus/templates/design/actions/list-design-templates.ts +69 -21
  171. package/corpus/templates/design/actions/navigate.ts +2 -2
  172. package/corpus/templates/design/actions/view-screen.ts +15 -2
  173. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  174. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +7 -3
  175. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
  176. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
  177. package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
  178. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
  179. package/corpus/templates/design/app/i18n-data.ts +21 -2
  180. package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
  181. package/corpus/templates/design/app/pages/Index.tsx +195 -24
  182. package/corpus/templates/design/app/pages/Templates.tsx +144 -55
  183. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
  184. package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
  185. package/corpus/templates/design/changelog/2026-07-15-prototype-print-and-download-buttons-now-work-in-preview.md +6 -0
  186. package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
  187. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
  188. package/corpus/templates/mail/AGENTS.md +23 -18
  189. package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
  190. package/corpus/templates/mail/actions/mark-read.ts +131 -20
  191. package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
  192. package/corpus/templates/mail/actions/move-email.ts +30 -24
  193. package/corpus/templates/mail/actions/send-email.ts +73 -68
  194. package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
  195. package/corpus/templates/mail/server/handlers/emails.ts +218 -209
  196. package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
  197. package/corpus/templates/mail/server/lib/email-state.ts +198 -113
  198. package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
  199. package/corpus/templates/mail/server/lib/jobs.ts +69 -74
  200. package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
  201. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  202. package/corpus/templates/slides/actions/extract-pdf.ts +1 -1
  203. package/corpus/templates/slides/actions/import-file.ts +4 -1
  204. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
  205. package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +9 -12
  206. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -21
  207. package/corpus/templates/slides/app/i18n/ar-SA.ts +1 -0
  208. package/corpus/templates/slides/app/i18n/de-DE.ts +1 -0
  209. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  210. package/corpus/templates/slides/app/i18n/es-ES.ts +1 -0
  211. package/corpus/templates/slides/app/i18n/fr-FR.ts +1 -0
  212. package/corpus/templates/slides/app/i18n/hi-IN.ts +1 -0
  213. package/corpus/templates/slides/app/i18n/ja-JP.ts +1 -0
  214. package/corpus/templates/slides/app/i18n/ko-KR.ts +1 -0
  215. package/corpus/templates/slides/app/i18n/pt-BR.ts +1 -0
  216. package/corpus/templates/slides/app/i18n/zh-CN.ts +1 -0
  217. package/corpus/templates/slides/app/i18n/zh-TW.ts +1 -0
  218. package/corpus/templates/slides/changelog/2026-07-15-image-search-and-generation-controls-now-use-the-registered-.md +6 -0
  219. package/corpus/templates/slides/changelog/2026-07-15-pdf-deck-references-can-be-imported-for-slide-inspiration-ag.md +6 -0
  220. package/corpus/templates/slides/changelog/2026-07-15-stable-style-inspector-dock.md +6 -0
  221. package/dist/cli/content-local.d.ts +1 -0
  222. package/dist/cli/content-local.d.ts.map +1 -1
  223. package/dist/cli/content-local.js +75 -17
  224. package/dist/cli/content-local.js.map +1 -1
  225. package/dist/cli/index.js +2 -2
  226. package/dist/cli/index.js.map +1 -1
  227. package/dist/cli/skills-content/content-skill.d.ts +1 -1
  228. package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
  229. package/dist/cli/skills-content/content-skill.js +36 -25
  230. package/dist/cli/skills-content/content-skill.js.map +1 -1
  231. package/dist/cli/skills.d.ts.map +1 -1
  232. package/dist/cli/skills.js +47 -14
  233. package/dist/cli/skills.js.map +1 -1
  234. package/dist/client/AgentPanel.d.ts.map +1 -1
  235. package/dist/client/AgentPanel.js +5 -0
  236. package/dist/client/AgentPanel.js.map +1 -1
  237. package/dist/client/AssistantChat.d.ts.map +1 -1
  238. package/dist/client/AssistantChat.js +7 -0
  239. package/dist/client/AssistantChat.js.map +1 -1
  240. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  241. package/dist/client/agent-chat-adapter.js +8 -1
  242. package/dist/client/agent-chat-adapter.js.map +1 -1
  243. package/dist/client/chat/message-components.d.ts +4 -0
  244. package/dist/client/chat/message-components.d.ts.map +1 -1
  245. package/dist/client/chat/message-components.js +10 -3
  246. package/dist/client/chat/message-components.js.map +1 -1
  247. package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
  248. package/dist/client/composer/ComposerPlusMenu.js +58 -6
  249. package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
  250. package/dist/client/composer/asset-picker-url.d.ts +16 -0
  251. package/dist/client/composer/asset-picker-url.d.ts.map +1 -0
  252. package/dist/client/composer/asset-picker-url.js +64 -0
  253. package/dist/client/composer/asset-picker-url.js.map +1 -0
  254. package/dist/client/error-format.d.ts.map +1 -1
  255. package/dist/client/error-format.js +17 -0
  256. package/dist/client/error-format.js.map +1 -1
  257. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  258. package/dist/client/resources/McpIntegrationDialog.js +6 -1
  259. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  260. package/dist/collab/awareness.d.ts +2 -2
  261. package/dist/collab/awareness.d.ts.map +1 -1
  262. package/dist/collab/struct-routes.d.ts +1 -1
  263. package/dist/local-artifacts/index.d.ts +10 -0
  264. package/dist/local-artifacts/index.d.ts.map +1 -1
  265. package/dist/local-artifacts/index.js +39 -0
  266. package/dist/local-artifacts/index.js.map +1 -1
  267. package/dist/notifications/routes.d.ts +1 -1
  268. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  269. package/dist/resources/handlers.d.ts +3 -3
  270. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  271. package/dist/server/transcribe-voice.d.ts +1 -1
  272. package/docs/content/local-file-mode.mdx +99 -489
  273. package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
  274. package/docs/content/locales/ar-SA/template-content.mdx +10 -24
  275. package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
  276. package/docs/content/locales/de-DE/template-content.mdx +10 -24
  277. package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
  278. package/docs/content/locales/es-ES/template-content.mdx +10 -24
  279. package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
  280. package/docs/content/locales/fr-FR/template-content.mdx +10 -24
  281. package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
  282. package/docs/content/locales/hi-IN/template-content.mdx +10 -24
  283. package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
  284. package/docs/content/locales/ja-JP/template-content.mdx +10 -24
  285. package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
  286. package/docs/content/locales/ko-KR/template-content.mdx +10 -24
  287. package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
  288. package/docs/content/locales/pt-BR/template-content.mdx +10 -24
  289. package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
  290. package/docs/content/locales/zh-CN/template-content.mdx +9 -24
  291. package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
  292. package/docs/content/locales/zh-TW/template-content.mdx +9 -24
  293. package/docs/content/template-content.mdx +26 -42
  294. package/package.json +1 -1
  295. package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
@@ -14,6 +14,7 @@ import {
14
14
  import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
15
15
  import { decodeCommonHtmlEntities } from "@shared/markdown.js";
16
16
 
17
+ import type { BulkMarkReadResult } from "./bulk-mark-read.js";
17
18
  import {
18
19
  createOAuth2Client,
19
20
  gmailGetProfile,
@@ -31,6 +32,7 @@ import {
31
32
  peopleGetProfile,
32
33
  } from "./google-api.js";
33
34
  import { resolveGoogleSenderIdentity } from "./sender-identity.js";
35
+ import { invalidateThreadCache } from "./thread-cache.js";
34
36
 
35
37
  const SCOPES = [
36
38
  "https://www.googleapis.com/auth/gmail.readonly",
@@ -1776,25 +1778,44 @@ export async function fetchGmailLabelMap(
1776
1778
  // instead of one per message.
1777
1779
  const GMAIL_BATCH_MODIFY_MAX_IDS = 1000;
1778
1780
 
1781
+ interface GmailBatchModifyResult {
1782
+ succeeded: string[];
1783
+ failed: Array<{ id: string; error: string }>;
1784
+ batchCount: number;
1785
+ }
1786
+
1779
1787
  async function gmailBatchModify(
1780
1788
  accessToken: string,
1781
1789
  ids: string[],
1782
1790
  addLabelIds?: string[],
1783
1791
  removeLabelIds?: string[],
1784
- ): Promise<void> {
1785
- if (ids.length === 0) return;
1792
+ ): Promise<GmailBatchModifyResult> {
1793
+ const result: GmailBatchModifyResult = {
1794
+ succeeded: [],
1795
+ failed: [],
1796
+ batchCount: 0,
1797
+ };
1798
+ if (ids.length === 0) return result;
1786
1799
  for (let i = 0; i < ids.length; i += GMAIL_BATCH_MODIFY_MAX_IDS) {
1787
1800
  const chunk = ids.slice(i, i + GMAIL_BATCH_MODIFY_MAX_IDS);
1788
- await googleFetch(
1789
- "https://gmail.googleapis.com/gmail/v1/users/me/messages/batchModify",
1790
- accessToken,
1791
- {
1792
- method: "POST",
1793
- headers: { "Content-Type": "application/json" },
1794
- body: JSON.stringify({ ids: chunk, addLabelIds, removeLabelIds }),
1795
- },
1796
- );
1801
+ result.batchCount += 1;
1802
+ try {
1803
+ await googleFetch(
1804
+ "https://gmail.googleapis.com/gmail/v1/users/me/messages/batchModify",
1805
+ accessToken,
1806
+ {
1807
+ method: "POST",
1808
+ headers: { "Content-Type": "application/json" },
1809
+ body: JSON.stringify({ ids: chunk, addLabelIds, removeLabelIds }),
1810
+ },
1811
+ );
1812
+ result.succeeded.push(...chunk);
1813
+ } catch (err: any) {
1814
+ const error = err?.message ?? "batchModify failed";
1815
+ result.failed.push(...chunk.map((id) => ({ id, error })));
1816
+ }
1797
1817
  }
1818
+ return result;
1798
1819
  }
1799
1820
 
1800
1821
  export interface BatchModifyTarget {
@@ -1826,7 +1847,7 @@ export async function gmailBatchModifyByAccount(
1826
1847
  ): Promise<BatchModifyByAccountResult> {
1827
1848
  const byAccount = new Map<string, BatchModifyTarget[]>();
1828
1849
  for (const target of targets) {
1829
- const key = target.accountEmail || ownerEmail;
1850
+ const key = target.accountEmail || "";
1830
1851
  const list = byAccount.get(key);
1831
1852
  if (list) list.push(target);
1832
1853
  else byAccount.set(key, [target]);
@@ -1836,24 +1857,18 @@ export async function gmailBatchModifyByAccount(
1836
1857
  const failed: Array<{ id: string; error: string }> = [];
1837
1858
 
1838
1859
  for (const [accountEmail, accountTargets] of byAccount) {
1839
- const client = await getClient(accountEmail);
1840
- if (!client) {
1841
- for (const t of accountTargets) {
1842
- failed.push({
1843
- id: t.id,
1844
- error: `Account ${accountEmail} not connected`,
1845
- });
1846
- }
1847
- continue;
1848
- }
1849
1860
  try {
1850
- await gmailBatchModify(
1851
- client.accessToken,
1861
+ const accessToken = accountEmail
1862
+ ? await getOwnedAccountAccessToken(ownerEmail, accountEmail)
1863
+ : await getDefaultOwnedAccountAccessToken(ownerEmail);
1864
+ const result = await gmailBatchModify(
1865
+ accessToken,
1852
1866
  accountTargets.map((t) => t.id),
1853
1867
  addLabelIds,
1854
1868
  removeLabelIds,
1855
1869
  );
1856
- for (const t of accountTargets) succeeded.push(t.id);
1870
+ succeeded.push(...result.succeeded);
1871
+ failed.push(...result.failed);
1857
1872
  } catch (err: any) {
1858
1873
  const message = err?.message ?? "batchModify failed";
1859
1874
  for (const t of accountTargets) failed.push({ id: t.id, error: message });
@@ -1863,6 +1878,184 @@ export async function gmailBatchModifyByAccount(
1863
1878
  return { succeeded, failed };
1864
1879
  }
1865
1880
 
1881
+ interface GmailMessageReference {
1882
+ id: string;
1883
+ threadId: string;
1884
+ }
1885
+
1886
+ async function getDefaultOwnedAccountAccessToken(
1887
+ ownerEmail: string,
1888
+ ): Promise<string> {
1889
+ const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
1890
+ const account =
1891
+ accounts.find(
1892
+ (candidate) =>
1893
+ candidate.accountId.toLowerCase() === ownerEmail.toLowerCase(),
1894
+ ) ?? accounts[0];
1895
+ if (!account) throw new Error("No Google account connected");
1896
+ const tokens = account.tokens as unknown as GoogleTokens;
1897
+ if (!tokens?.access_token && !tokens?.refresh_token) {
1898
+ throw new Error(`No valid access token for ${account.accountId}`);
1899
+ }
1900
+ return getValidAccessToken(account.accountId, tokens, ownerEmail);
1901
+ }
1902
+
1903
+ async function getOwnedAccountAccessToken(
1904
+ ownerEmail: string,
1905
+ accountEmail: string,
1906
+ ): Promise<string> {
1907
+ const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
1908
+ const account = accounts.find(
1909
+ (candidate) =>
1910
+ candidate.accountId.toLowerCase() === accountEmail.toLowerCase(),
1911
+ );
1912
+ if (!account) {
1913
+ throw new Error(`Account ${accountEmail} is not connected for this user`);
1914
+ }
1915
+ const tokens = account.tokens as unknown as GoogleTokens;
1916
+ if (!tokens?.access_token && !tokens?.refresh_token) {
1917
+ throw new Error(`No valid access token for ${account.accountId}`);
1918
+ }
1919
+ return getValidAccessToken(account.accountId, tokens, ownerEmail);
1920
+ }
1921
+
1922
+ async function listGmailMessageReferences(
1923
+ accessToken: string,
1924
+ query: string,
1925
+ ): Promise<GmailMessageReference[]> {
1926
+ const references = new Map<string, GmailMessageReference>();
1927
+ const seenPageTokens = new Set<string>();
1928
+ let pageToken: string | undefined;
1929
+
1930
+ do {
1931
+ const response = await gmailListMessages(accessToken, {
1932
+ q: query,
1933
+ maxResults: 500,
1934
+ pageToken,
1935
+ });
1936
+ for (const message of response.messages ?? []) {
1937
+ if (
1938
+ typeof message.id !== "string" ||
1939
+ typeof message.threadId !== "string"
1940
+ )
1941
+ continue;
1942
+ references.set(message.id, {
1943
+ id: message.id,
1944
+ threadId: message.threadId,
1945
+ });
1946
+ }
1947
+
1948
+ const nextPageToken = response.nextPageToken;
1949
+ if (typeof nextPageToken !== "string" || !nextPageToken) break;
1950
+ if (seenPageTokens.has(nextPageToken)) {
1951
+ throw new Error(
1952
+ "Gmail repeated a pagination token while listing unread mail",
1953
+ );
1954
+ }
1955
+ seenPageTokens.add(nextPageToken);
1956
+ pageToken = nextPageToken;
1957
+ } while (pageToken);
1958
+
1959
+ return [...references.values()];
1960
+ }
1961
+
1962
+ export async function markAllUnreadReadForAccount(input: {
1963
+ ownerEmail: string;
1964
+ accountEmail: string;
1965
+ excludeThreadIds: string[];
1966
+ }): Promise<BulkMarkReadResult> {
1967
+ const { ownerEmail, accountEmail } = input;
1968
+ const excludedThreadIds = new Set(input.excludeThreadIds.filter(Boolean));
1969
+ const accessToken = await getOwnedAccountAccessToken(
1970
+ ownerEmail,
1971
+ accountEmail,
1972
+ );
1973
+ const matched = await listGmailMessageReferences(accessToken, "is:unread");
1974
+ const excluded = matched.filter((message) =>
1975
+ excludedThreadIds.has(message.threadId),
1976
+ );
1977
+ const selected = matched.filter(
1978
+ (message) => !excludedThreadIds.has(message.threadId),
1979
+ );
1980
+
1981
+ const mutation = await gmailBatchModify(
1982
+ accessToken,
1983
+ selected.map((message) => message.id),
1984
+ undefined,
1985
+ ["UNREAD"],
1986
+ );
1987
+ for (const threadId of new Set(selected.map((message) => message.threadId))) {
1988
+ invalidateThreadCache(ownerEmail, threadId);
1989
+ }
1990
+ invalidateListCacheForOwner(ownerEmail);
1991
+
1992
+ let remaining: GmailMessageReference[];
1993
+ try {
1994
+ remaining = await listGmailMessageReferences(accessToken, "is:unread");
1995
+ } catch (err: any) {
1996
+ return {
1997
+ mode: "all-unread",
1998
+ accountEmail,
1999
+ matchedMessages: matched.length,
2000
+ matchedThreads: new Set(matched.map((message) => message.threadId)).size,
2001
+ excludedMessages: excluded.length,
2002
+ excludedThreads: new Set(excluded.map((message) => message.threadId))
2003
+ .size,
2004
+ changedMessages: mutation.succeeded.length,
2005
+ batchCount: mutation.batchCount,
2006
+ failures: mutation.failed,
2007
+ remainingUnreadMessages: null,
2008
+ remainingUnreadThreads: null,
2009
+ remainingProtectedMessages: null,
2010
+ remainingProtectedThreads: null,
2011
+ unexpectedUnreadMessages: null,
2012
+ unexpectedUnreadThreads: null,
2013
+ newUnreadMessages: null,
2014
+ newUnreadThreads: null,
2015
+ verificationComplete: false,
2016
+ verificationError: err?.message ?? "Unread verification failed",
2017
+ };
2018
+ }
2019
+ const matchedIds = new Set(matched.map((message) => message.id));
2020
+ const selectedIds = new Set(selected.map((message) => message.id));
2021
+ const remainingProtected = remaining.filter((message) =>
2022
+ excludedThreadIds.has(message.threadId),
2023
+ );
2024
+ const unexpectedRemaining = remaining.filter((message) =>
2025
+ selectedIds.has(message.id),
2026
+ );
2027
+ const newUnread = remaining.filter((message) => !matchedIds.has(message.id));
2028
+
2029
+ return {
2030
+ mode: "all-unread",
2031
+ accountEmail,
2032
+ matchedMessages: matched.length,
2033
+ matchedThreads: new Set(matched.map((message) => message.threadId)).size,
2034
+ excludedMessages: excluded.length,
2035
+ excludedThreads: new Set(excluded.map((message) => message.threadId)).size,
2036
+ changedMessages: mutation.succeeded.length,
2037
+ batchCount: mutation.batchCount,
2038
+ failures: mutation.failed,
2039
+ remainingUnreadMessages: remaining.length,
2040
+ remainingUnreadThreads: new Set(
2041
+ remaining.map((message) => message.threadId),
2042
+ ).size,
2043
+ remainingProtectedMessages: remainingProtected.length,
2044
+ remainingProtectedThreads: new Set(
2045
+ remainingProtected.map((message) => message.threadId),
2046
+ ).size,
2047
+ unexpectedUnreadMessages: unexpectedRemaining.length,
2048
+ unexpectedUnreadThreads: new Set(
2049
+ unexpectedRemaining.map((message) => message.threadId),
2050
+ ).size,
2051
+ newUnreadMessages: newUnread.length,
2052
+ newUnreadThreads: new Set(newUnread.map((message) => message.threadId))
2053
+ .size,
2054
+ verificationComplete:
2055
+ mutation.failed.length === 0 && unexpectedRemaining.length === 0,
2056
+ };
2057
+ }
2058
+
1866
2059
  /** Extract regular (non-inline) attachments from a Gmail message payload */
1867
2060
  function getAttachments(
1868
2061
  payload: any,
@@ -5,7 +5,6 @@ import {
5
5
  listOAuthAccountsByOwner,
6
6
  setOAuthDisplayName,
7
7
  } from "@agent-native/core/oauth-tokens";
8
- import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
9
8
  import { markdownPreviewSnippet } from "@shared/markdown.js";
10
9
  import type { ComposeAttachment, EmailMessage } from "@shared/types.js";
11
10
  import { and, eq, inArray, isNull, lte, or } from "drizzle-orm";
@@ -28,6 +27,11 @@ import {
28
27
  getOAuth2Credentials,
29
28
  setAccountDisplayName,
30
29
  } from "./google-auth.js";
30
+ import {
31
+ readLocalEmails as readEmails,
32
+ withLocalEmailMutationLock,
33
+ writeLocalEmails as writeEmails,
34
+ } from "./local-email-store.js";
31
35
  import {
32
36
  bodyToHtml as outgoingBodyToHtml,
33
37
  buildRawEmail as buildOutgoingRawEmail,
@@ -135,21 +139,6 @@ async function getFirstAccountToken(
135
139
  return null;
136
140
  }
137
141
 
138
- async function readEmails(ownerEmail: string): Promise<EmailMessage[]> {
139
- const data = await getUserSetting(ownerEmail, "local-emails");
140
- if (data && Array.isArray((data as any).emails)) {
141
- return (data as any).emails;
142
- }
143
- return [];
144
- }
145
-
146
- async function writeEmails(
147
- ownerEmail: string,
148
- emails: EmailMessage[],
149
- ): Promise<void> {
150
- await putUserSetting(ownerEmail, "local-emails", { emails });
151
- }
152
-
153
142
  async function fetchLabelMap(
154
143
  accessToken: string,
155
144
  ): Promise<Map<string, string>> {
@@ -211,18 +200,20 @@ async function archiveThreadForSnooze(
211
200
  }
212
201
  }
213
202
 
214
- const emails = await readEmails(ownerEmail);
215
- for (let i = 0; i < emails.length; i++) {
216
- const currentThreadId = emails[i].threadId || emails[i].id;
217
- if (currentThreadId === threadId) {
218
- emails[i] = {
219
- ...emails[i],
220
- isArchived: true,
221
- labelIds: emails[i].labelIds.filter((label) => label !== "inbox"),
222
- };
203
+ await withLocalEmailMutationLock(ownerEmail, async () => {
204
+ const emails = await readEmails(ownerEmail);
205
+ for (let i = 0; i < emails.length; i++) {
206
+ const currentThreadId = emails[i].threadId || emails[i].id;
207
+ if (currentThreadId === threadId) {
208
+ emails[i] = {
209
+ ...emails[i],
210
+ isArchived: true,
211
+ labelIds: emails[i].labelIds.filter((label) => label !== "inbox"),
212
+ };
213
+ }
223
214
  }
224
- }
225
- await writeEmails(ownerEmail, emails);
215
+ await writeEmails(ownerEmail, emails);
216
+ });
226
217
  }
227
218
 
228
219
  async function threadHasReplySinceSnooze(
@@ -396,22 +387,24 @@ export async function resurfaceEmail(
396
387
  }
397
388
  }
398
389
 
399
- const emails = await readEmails(ownerEmail);
400
- const targetThreadId = threadId || emailId;
401
- for (let i = 0; i < emails.length; i++) {
402
- const currentThreadId = emails[i].threadId || emails[i].id;
403
- if (currentThreadId === targetThreadId) {
404
- emails[i] = {
405
- ...emails[i],
406
- isArchived: false,
407
- isRead: false,
408
- labelIds: emails[i].labelIds.includes("inbox")
409
- ? emails[i].labelIds
410
- : ["inbox", ...emails[i].labelIds],
411
- };
390
+ await withLocalEmailMutationLock(ownerEmail, async () => {
391
+ const emails = await readEmails(ownerEmail);
392
+ const targetThreadId = threadId || emailId;
393
+ for (let i = 0; i < emails.length; i++) {
394
+ const currentThreadId = emails[i].threadId || emails[i].id;
395
+ if (currentThreadId === targetThreadId) {
396
+ emails[i] = {
397
+ ...emails[i],
398
+ isArchived: false,
399
+ isRead: false,
400
+ labelIds: emails[i].labelIds.includes("inbox")
401
+ ? emails[i].labelIds
402
+ : ["inbox", ...emails[i].labelIds],
403
+ };
404
+ }
412
405
  }
413
- }
414
- await writeEmails(ownerEmail, emails);
406
+ await writeEmails(ownerEmail, emails);
407
+ });
415
408
  }
416
409
 
417
410
  /**
@@ -638,39 +631,41 @@ export async function sendScheduledEmail(
638
631
  if (!fallbackOwner) {
639
632
  throw new Error("scheduleEmail: no owner email available");
640
633
  }
641
- const emails = await readEmails(fallbackOwner);
642
- emails.push({
643
- id: `msg-${nanoid(8)}`,
644
- threadId: threadId || `thread-${nanoid(8)}`,
645
- from: { name: fallbackOwner, email: fallbackOwner },
646
- to: to.split(",").map((item) => {
647
- const email = item.trim();
648
- return { name: email, email };
649
- }),
650
- subject,
651
- snippet: markdownPreviewSnippet(body),
652
- body,
653
- bodyHtml: outgoingBodyToHtml(body),
654
- date: new Date().toISOString(),
655
- isRead: true,
656
- isStarred: false,
657
- isSent: true,
658
- isArchived: false,
659
- isTrashed: false,
660
- labelIds: ["sent"],
661
- ...(attachments.length > 0
662
- ? {
663
- attachments: attachments.map((att) => ({
664
- id: att.filename,
665
- filename: att.originalName,
666
- mimeType: att.mimeType,
667
- size: att.size,
668
- url: att.url,
669
- })),
670
- }
671
- : {}),
634
+ await withLocalEmailMutationLock(fallbackOwner, async () => {
635
+ const emails = await readEmails(fallbackOwner);
636
+ emails.push({
637
+ id: `msg-${nanoid(8)}`,
638
+ threadId: threadId || `thread-${nanoid(8)}`,
639
+ from: { name: fallbackOwner, email: fallbackOwner },
640
+ to: to.split(",").map((item) => {
641
+ const email = item.trim();
642
+ return { name: email, email };
643
+ }),
644
+ subject,
645
+ snippet: markdownPreviewSnippet(body),
646
+ body,
647
+ bodyHtml: outgoingBodyToHtml(body),
648
+ date: new Date().toISOString(),
649
+ isRead: true,
650
+ isStarred: false,
651
+ isSent: true,
652
+ isArchived: false,
653
+ isTrashed: false,
654
+ labelIds: ["sent"],
655
+ ...(attachments.length > 0
656
+ ? {
657
+ attachments: attachments.map((att) => ({
658
+ id: att.filename,
659
+ filename: att.originalName,
660
+ mimeType: att.mimeType,
661
+ size: att.size,
662
+ url: att.url,
663
+ })),
664
+ }
665
+ : {}),
666
+ });
667
+ await writeEmails(fallbackOwner, emails);
672
668
  });
673
- await writeEmails(fallbackOwner, emails);
674
669
  }
675
670
 
676
671
  export async function cancelScheduledJobForOwner(