@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 { getUserSetting, putUserSetting } from "@agent-native/core/settings";
14
14
  */
15
15
  import type { EmailMessage, Label } from "@shared/types.js";
16
16
 
17
+ import type { BulkMarkReadResult } from "./bulk-mark-read.js";
17
18
  import {
18
19
  createOAuth2Client,
19
20
  gmailGetMessage,
@@ -23,6 +24,11 @@ import {
23
24
  gmailUntrashThread,
24
25
  } from "./google-api.js";
25
26
  import { getOAuth2Credentials, isConnected } from "./google-auth.js";
27
+ import {
28
+ readLocalEmails,
29
+ withLocalEmailMutationLock,
30
+ writeLocalEmails,
31
+ } from "./local-email-store.js";
26
32
  import { invalidateThreadCache } from "./thread-cache.js";
27
33
 
28
34
  // ---------------------------------------------------------------------------
@@ -106,21 +112,6 @@ export async function getAccountToken(
106
112
  // Local-mode helpers
107
113
  // ---------------------------------------------------------------------------
108
114
 
109
- async function readLocalEmails(ownerEmail: string): Promise<EmailMessage[]> {
110
- const data = await getUserSetting(ownerEmail, "local-emails");
111
- if (data && Array.isArray((data as any).emails)) {
112
- return (data as any).emails as EmailMessage[];
113
- }
114
- return [];
115
- }
116
-
117
- async function writeLocalEmails(
118
- ownerEmail: string,
119
- emails: EmailMessage[],
120
- ): Promise<void> {
121
- await putUserSetting(ownerEmail, "local-emails", { emails });
122
- }
123
-
124
115
  async function readLocalLabels(ownerEmail: string): Promise<Label[]> {
125
116
  const data = await getUserSetting(ownerEmail, "labels");
126
117
  if (data && Array.isArray((data as any).labels)) {
@@ -205,24 +196,26 @@ export async function archiveEmail(
205
196
  } = input;
206
197
 
207
198
  if (!(await isConnected(ownerEmail))) {
208
- const emails = await readLocalEmails(ownerEmail);
209
- const target = emails.find((e) => e.id === id);
210
- if (!target) throw new Error(`Email ${id} not found`);
211
- const threadId = target.threadId || target.id;
212
- const updated = emails.map((e) => {
213
- if ((e.threadId || e.id) !== threadId) return e;
214
- return {
215
- ...e,
216
- isArchived: true,
217
- labelIds: e.labelIds.filter((l) => l !== "inbox"),
218
- };
199
+ return withLocalEmailMutationLock(ownerEmail, async () => {
200
+ const emails = await readLocalEmails(ownerEmail);
201
+ const target = emails.find((e) => e.id === id);
202
+ if (!target) throw new Error(`Email ${id} not found`);
203
+ const threadId = target.threadId || target.id;
204
+ const updated = emails.map((e) => {
205
+ if ((e.threadId || e.id) !== threadId) return e;
206
+ return {
207
+ ...e,
208
+ isArchived: true,
209
+ labelIds: e.labelIds.filter((l) => l !== "inbox"),
210
+ };
211
+ });
212
+ await writeLocalEmails(ownerEmail, updated);
213
+ await writeLocalLabels(
214
+ ownerEmail,
215
+ recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
216
+ );
217
+ return { id, threadId, isArchived: true };
219
218
  });
220
- await writeLocalEmails(ownerEmail, updated);
221
- await writeLocalLabels(
222
- ownerEmail,
223
- recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
224
- );
225
- return { id, threadId, isArchived: true };
226
219
  }
227
220
 
228
221
  // Try accountEmail-scoped account first, then fall through to other accounts
@@ -301,26 +294,28 @@ export async function unarchiveEmail(
301
294
  const { id, ownerEmail, accountEmail } = input;
302
295
 
303
296
  if (!(await isConnected(ownerEmail))) {
304
- const emails = await readLocalEmails(ownerEmail);
305
- const target = emails.find((e) => e.id === id);
306
- if (!target) throw new Error(`Email ${id} not found`);
307
- const threadId = target.threadId || target.id;
308
- const updated = emails.map((e) => {
309
- if ((e.threadId || e.id) !== threadId) return e;
310
- return {
311
- ...e,
312
- isArchived: false,
313
- labelIds: e.labelIds.includes("inbox")
314
- ? e.labelIds
315
- : ["inbox", ...e.labelIds],
316
- };
297
+ return withLocalEmailMutationLock(ownerEmail, async () => {
298
+ const emails = await readLocalEmails(ownerEmail);
299
+ const target = emails.find((e) => e.id === id);
300
+ if (!target) throw new Error(`Email ${id} not found`);
301
+ const threadId = target.threadId || target.id;
302
+ const updated = emails.map((e) => {
303
+ if ((e.threadId || e.id) !== threadId) return e;
304
+ return {
305
+ ...e,
306
+ isArchived: false,
307
+ labelIds: e.labelIds.includes("inbox")
308
+ ? e.labelIds
309
+ : ["inbox", ...e.labelIds],
310
+ };
311
+ });
312
+ await writeLocalEmails(ownerEmail, updated);
313
+ await writeLocalLabels(
314
+ ownerEmail,
315
+ recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
316
+ );
317
+ return { id, threadId, isArchived: false };
317
318
  });
318
- await writeLocalEmails(ownerEmail, updated);
319
- await writeLocalLabels(
320
- ownerEmail,
321
- recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
322
- );
323
- return { id, threadId, isArchived: false };
324
319
  }
325
320
 
326
321
  const token = await getAccountToken(accountEmail ?? ownerEmail, ownerEmail);
@@ -371,12 +366,14 @@ export async function toggleStar(
371
366
  } = input;
372
367
 
373
368
  if (!(await isConnected(ownerEmail))) {
374
- const emails = await readLocalEmails(ownerEmail);
375
- const idx = emails.findIndex((e) => e.id === id);
376
- if (idx === -1) throw new Error(`Email ${id} not found`);
377
- emails[idx] = { ...emails[idx], isStarred };
378
- await writeLocalEmails(ownerEmail, emails);
379
- return { id, threadId: emails[idx].threadId, isStarred };
369
+ return withLocalEmailMutationLock(ownerEmail, async () => {
370
+ const emails = await readLocalEmails(ownerEmail);
371
+ const idx = emails.findIndex((e) => e.id === id);
372
+ if (idx === -1) throw new Error(`Email ${id} not found`);
373
+ emails[idx] = { ...emails[idx], isStarred };
374
+ await writeLocalEmails(ownerEmail, emails);
375
+ return { id, threadId: emails[idx].threadId, isStarred };
376
+ });
380
377
  }
381
378
 
382
379
  const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
@@ -444,20 +441,22 @@ export async function trashEmail(
444
441
  const { id, ownerEmail, accountEmail } = input;
445
442
 
446
443
  if (!(await isConnected(ownerEmail))) {
447
- const emails = await readLocalEmails(ownerEmail);
448
- const target = emails.find((e) => e.id === id);
449
- if (!target) throw new Error(`Email ${id} not found`);
450
- const threadId = target.threadId || target.id;
451
- const updated = emails.map((e) => {
452
- if ((e.threadId || e.id) !== threadId) return e;
453
- return { ...e, isTrashed: true, isArchived: false };
444
+ return withLocalEmailMutationLock(ownerEmail, async () => {
445
+ const emails = await readLocalEmails(ownerEmail);
446
+ const target = emails.find((e) => e.id === id);
447
+ if (!target) throw new Error(`Email ${id} not found`);
448
+ const threadId = target.threadId || target.id;
449
+ const updated = emails.map((e) => {
450
+ if ((e.threadId || e.id) !== threadId) return e;
451
+ return { ...e, isTrashed: true, isArchived: false };
452
+ });
453
+ await writeLocalEmails(ownerEmail, updated);
454
+ await writeLocalLabels(
455
+ ownerEmail,
456
+ recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
457
+ );
458
+ return { id, threadId, isTrashed: true };
454
459
  });
455
- await writeLocalEmails(ownerEmail, updated);
456
- await writeLocalLabels(
457
- ownerEmail,
458
- recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
459
- );
460
- return { id, threadId, isTrashed: true };
461
460
  }
462
461
 
463
462
  const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
@@ -515,26 +514,28 @@ export async function untrashEmail(
515
514
  const { id, ownerEmail, accountEmail } = input;
516
515
 
517
516
  if (!(await isConnected(ownerEmail))) {
518
- const emails = await readLocalEmails(ownerEmail);
519
- const target = emails.find((e) => e.id === id);
520
- if (!target) throw new Error(`Email ${id} not found`);
521
- const threadId = target.threadId || target.id;
522
- const updated = emails.map((e) => {
523
- if ((e.threadId || e.id) !== threadId) return e;
524
- return {
525
- ...e,
526
- isTrashed: false,
527
- labelIds: e.labelIds.includes("inbox")
528
- ? e.labelIds
529
- : ["inbox", ...e.labelIds],
530
- };
517
+ return withLocalEmailMutationLock(ownerEmail, async () => {
518
+ const emails = await readLocalEmails(ownerEmail);
519
+ const target = emails.find((e) => e.id === id);
520
+ if (!target) throw new Error(`Email ${id} not found`);
521
+ const threadId = target.threadId || target.id;
522
+ const updated = emails.map((e) => {
523
+ if ((e.threadId || e.id) !== threadId) return e;
524
+ return {
525
+ ...e,
526
+ isTrashed: false,
527
+ labelIds: e.labelIds.includes("inbox")
528
+ ? e.labelIds
529
+ : ["inbox", ...e.labelIds],
530
+ };
531
+ });
532
+ await writeLocalEmails(ownerEmail, updated);
533
+ await writeLocalLabels(
534
+ ownerEmail,
535
+ recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
536
+ );
537
+ return { id, threadId, isTrashed: false };
531
538
  });
532
- await writeLocalEmails(ownerEmail, updated);
533
- await writeLocalLabels(
534
- ownerEmail,
535
- recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
536
- );
537
- return { id, threadId, isTrashed: false };
538
539
  }
539
540
 
540
541
  const token = await getAccountToken(accountEmail ?? ownerEmail, ownerEmail);
@@ -575,16 +576,18 @@ export async function markRead(input: MarkReadInput): Promise<MarkReadResult> {
575
576
  const { id, ownerEmail, isRead, accountEmail } = input;
576
577
 
577
578
  if (!(await isConnected(ownerEmail))) {
578
- const emails = await readLocalEmails(ownerEmail);
579
- const idx = emails.findIndex((e) => e.id === id);
580
- if (idx === -1) throw new Error(`Email ${id} not found`);
581
- emails[idx] = { ...emails[idx], isRead };
582
- await writeLocalEmails(ownerEmail, emails);
583
- await writeLocalLabels(
584
- ownerEmail,
585
- recomputeUnreadCounts(emails, await readLocalLabels(ownerEmail)),
586
- );
587
- return { id, isRead };
579
+ return withLocalEmailMutationLock(ownerEmail, async () => {
580
+ const emails = await readLocalEmails(ownerEmail);
581
+ const idx = emails.findIndex((e) => e.id === id);
582
+ if (idx === -1) throw new Error(`Email ${id} not found`);
583
+ emails[idx] = { ...emails[idx], isRead };
584
+ await writeLocalEmails(ownerEmail, emails);
585
+ await writeLocalLabels(
586
+ ownerEmail,
587
+ recomputeUnreadCounts(emails, await readLocalLabels(ownerEmail)),
588
+ );
589
+ return { id, isRead };
590
+ });
588
591
  }
589
592
 
590
593
  const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
@@ -616,6 +619,86 @@ export async function markRead(input: MarkReadInput): Promise<MarkReadResult> {
616
619
  throw lastErr ?? new Error("Mark read failed");
617
620
  }
618
621
 
622
+ export async function markAllLocalUnreadRead(input: {
623
+ ownerEmail: string;
624
+ accountEmail: string;
625
+ excludeThreadIds: string[];
626
+ }): Promise<BulkMarkReadResult> {
627
+ const { ownerEmail, accountEmail } = input;
628
+ if (accountEmail.toLowerCase() !== ownerEmail.toLowerCase()) {
629
+ throw new Error("Local mail only supports the authenticated owner account");
630
+ }
631
+
632
+ const excludedThreadIds = new Set(input.excludeThreadIds.filter(Boolean));
633
+ return withLocalEmailMutationLock(ownerEmail, async () => {
634
+ const emails = await readLocalEmails(ownerEmail);
635
+ const matched = emails.filter((email) => !email.isRead);
636
+ const excluded = matched.filter((email) =>
637
+ excludedThreadIds.has(email.threadId || email.id),
638
+ );
639
+ const selectedIds = new Set(
640
+ matched
641
+ .filter((email) => !excludedThreadIds.has(email.threadId || email.id))
642
+ .map((email) => email.id),
643
+ );
644
+ const updated = emails.map((email) =>
645
+ selectedIds.has(email.id) ? { ...email, isRead: true } : email,
646
+ );
647
+
648
+ if (selectedIds.size > 0) {
649
+ await writeLocalEmails(ownerEmail, updated);
650
+ await writeLocalLabels(
651
+ ownerEmail,
652
+ recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
653
+ );
654
+ }
655
+
656
+ const persisted = await readLocalEmails(ownerEmail);
657
+ const remaining = persisted.filter((email) => !email.isRead);
658
+ const matchedIds = new Set(matched.map((email) => email.id));
659
+ const remainingProtected = remaining.filter((email) =>
660
+ excludedThreadIds.has(email.threadId || email.id),
661
+ );
662
+ const unexpectedRemaining = remaining.filter((email) =>
663
+ selectedIds.has(email.id),
664
+ );
665
+ const newUnread = remaining.filter((email) => !matchedIds.has(email.id));
666
+
667
+ return {
668
+ mode: "all-unread",
669
+ accountEmail,
670
+ matchedMessages: matched.length,
671
+ matchedThreads: new Set(
672
+ matched.map((email) => email.threadId || email.id),
673
+ ).size,
674
+ excludedMessages: excluded.length,
675
+ excludedThreads: new Set(
676
+ excluded.map((email) => email.threadId || email.id),
677
+ ).size,
678
+ changedMessages: selectedIds.size,
679
+ batchCount: selectedIds.size > 0 ? 1 : 0,
680
+ failures: [],
681
+ remainingUnreadMessages: remaining.length,
682
+ remainingUnreadThreads: new Set(
683
+ remaining.map((email) => email.threadId || email.id),
684
+ ).size,
685
+ remainingProtectedMessages: remainingProtected.length,
686
+ remainingProtectedThreads: new Set(
687
+ remainingProtected.map((email) => email.threadId || email.id),
688
+ ).size,
689
+ unexpectedUnreadMessages: unexpectedRemaining.length,
690
+ unexpectedUnreadThreads: new Set(
691
+ unexpectedRemaining.map((email) => email.threadId || email.id),
692
+ ).size,
693
+ newUnreadMessages: newUnread.length,
694
+ newUnreadThreads: new Set(
695
+ newUnread.map((email) => email.threadId || email.id),
696
+ ).size,
697
+ verificationComplete: unexpectedRemaining.length === 0,
698
+ };
699
+ });
700
+ }
701
+
619
702
  // ---------------------------------------------------------------------------
620
703
  // Mark thread read (all messages in a thread)
621
704
  // ---------------------------------------------------------------------------
@@ -647,21 +730,23 @@ export async function markThreadRead(
647
730
  const { threadId, ownerEmail, isRead, accountEmail } = input;
648
731
 
649
732
  if (!(await isConnected(ownerEmail))) {
650
- const emails = await readLocalEmails(ownerEmail);
651
- let changed = false;
652
- const updated = emails.map((e) => {
653
- if ((e.threadId || e.id) !== threadId || e.isRead === isRead) return e;
654
- changed = true;
655
- return { ...e, isRead };
733
+ return withLocalEmailMutationLock(ownerEmail, async () => {
734
+ const emails = await readLocalEmails(ownerEmail);
735
+ let changed = false;
736
+ const updated = emails.map((e) => {
737
+ if ((e.threadId || e.id) !== threadId || e.isRead === isRead) return e;
738
+ changed = true;
739
+ return { ...e, isRead };
740
+ });
741
+ if (changed) {
742
+ await writeLocalEmails(ownerEmail, updated);
743
+ await writeLocalLabels(
744
+ ownerEmail,
745
+ recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
746
+ );
747
+ }
748
+ return { threadId, isRead };
656
749
  });
657
- if (changed) {
658
- await writeLocalEmails(ownerEmail, updated);
659
- await writeLocalLabels(
660
- ownerEmail,
661
- recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
662
- );
663
- }
664
- return { threadId, isRead };
665
750
  }
666
751
 
667
752
  const token = await getAccountToken(accountEmail ?? ownerEmail, ownerEmail);