@agent-native/core 0.99.3 → 0.100.1

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 (382) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +21 -0
  3. package/corpus/core/docs/content/agent-surfaces.mdx +43 -0
  4. package/corpus/core/docs/content/external-agents.mdx +11 -0
  5. package/corpus/core/docs/content/locales/ar-SA/agent-surfaces.mdx +30 -0
  6. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +9 -0
  7. package/corpus/core/docs/content/locales/de-DE/agent-surfaces.mdx +34 -0
  8. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +10 -0
  9. package/corpus/core/docs/content/locales/es-ES/agent-surfaces.mdx +34 -0
  10. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +10 -0
  11. package/corpus/core/docs/content/locales/fr-FR/agent-surfaces.mdx +34 -0
  12. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +11 -0
  13. package/corpus/core/docs/content/locales/hi-IN/agent-surfaces.mdx +31 -0
  14. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +9 -0
  15. package/corpus/core/docs/content/locales/ja-JP/agent-surfaces.mdx +32 -0
  16. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +10 -0
  17. package/corpus/core/docs/content/locales/ko-KR/agent-surfaces.mdx +32 -0
  18. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +9 -0
  19. package/corpus/core/docs/content/locales/pt-BR/agent-surfaces.mdx +33 -0
  20. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +10 -0
  21. package/corpus/core/docs/content/locales/zh-CN/agent-surfaces.mdx +29 -0
  22. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +8 -0
  23. package/corpus/core/docs/content/locales/zh-TW/agent-surfaces.mdx +29 -0
  24. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +8 -0
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/agent/context-xray/actions/context-evict.ts +11 -0
  27. package/corpus/core/src/agent/context-xray/actions/context-pin.ts +11 -0
  28. package/corpus/core/src/agent/context-xray/actions/context-preview-get.ts +134 -0
  29. package/corpus/core/src/agent/context-xray/actions/context-restore.ts +11 -0
  30. package/corpus/core/src/agent/context-xray/actions/errors.ts +20 -0
  31. package/corpus/core/src/agent/context-xray/manifest.ts +106 -7
  32. package/corpus/core/src/agent/engine/context-directives-transform.ts +5 -1
  33. package/corpus/core/src/agent/production-agent.ts +100 -38
  34. package/corpus/core/src/agent/run-loop-with-resume.ts +7 -2
  35. package/corpus/core/src/client/AgentPanel.tsx +35 -8
  36. package/corpus/core/src/client/agent-page/AgentContextTab.tsx +501 -0
  37. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +370 -0
  38. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +1084 -0
  39. package/corpus/core/src/client/agent-page/types.ts +18 -0
  40. package/corpus/core/src/client/agent-page/use-jobs.ts +150 -0
  41. package/corpus/core/src/client/context-xray/ContextMeter.tsx +22 -5
  42. package/corpus/core/src/client/context-xray/ContextTreemap.tsx +16 -1
  43. package/corpus/core/src/client/context-xray/ContextXRayPanel.tsx +168 -3
  44. package/corpus/core/src/client/context-xray/format.ts +6 -0
  45. package/corpus/core/src/client/index.ts +6 -0
  46. package/corpus/core/src/client/resources/ResourcesPanel.tsx +51 -23
  47. package/corpus/core/src/client/settings/AutomationsSection.tsx +407 -387
  48. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +105 -0
  49. package/corpus/core/src/jobs/actions/manage-recurring-job.ts +85 -0
  50. package/corpus/core/src/jobs/tools.ts +1 -1
  51. package/corpus/core/src/localization/default-messages.ts +110 -0
  52. package/corpus/core/src/mcp/build-server.ts +1 -0
  53. package/corpus/core/src/mcp/connect-route.ts +62 -65
  54. package/corpus/core/src/server/action-discovery.ts +23 -0
  55. package/corpus/core/src/server/agent-chat/prompt-resources.ts +174 -2
  56. package/corpus/core/src/server/agent-chat-plugin.ts +175 -2
  57. package/corpus/core/src/server/auth.ts +1 -7
  58. package/corpus/core/src/server/google-oauth.ts +1 -7
  59. package/corpus/core/src/server/workspace-oauth.ts +31 -0
  60. package/corpus/core/src/shared/context-xray.ts +84 -1
  61. package/corpus/core/src/shared/mcp-connect-content.ts +130 -0
  62. package/corpus/core/src/templates/default/app/routes/_index.tsx +11 -0
  63. package/corpus/core/src/templates/default/app/routes/agent.tsx +33 -0
  64. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  65. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
  66. package/corpus/core/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
  67. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
  68. package/corpus/core/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
  69. package/corpus/core/src/triggers/actions/list-automations.ts +99 -0
  70. package/corpus/core/src/triggers/actions/manage-automation.ts +78 -0
  71. package/corpus/templates/analytics/actions/data-source-status.ts +33 -15
  72. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  73. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +149 -24
  74. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +17 -0
  75. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  76. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -0
  77. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +8 -0
  78. package/corpus/templates/analytics/app/i18n/zh-TW.ts +1 -0
  79. package/corpus/templates/analytics/app/i18n-data.ts +1 -0
  80. package/corpus/templates/analytics/app/routes/agent.tsx +5 -0
  81. package/corpus/templates/analytics/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  82. package/corpus/templates/analytics/changelog/2026-07-13-analytics-answers-can-use-built-in-first-party-event-data.md +6 -0
  83. package/corpus/templates/analytics/changelog/2026-07-13-chart-legends-can-filter-to-one-series-or-hide-a-series-on-h.md +6 -0
  84. package/corpus/templates/analytics/changelog/2026-07-13-gpt-5-6-luna-answers-now-recover-normally-instead-of-showing.md +6 -0
  85. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
  86. package/corpus/templates/assets/app/components/layout/Header.tsx +1 -0
  87. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -0
  88. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -0
  89. package/corpus/templates/assets/app/routes/agent.tsx +5 -0
  90. package/corpus/templates/assets/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  91. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -0
  92. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +4 -1
  93. package/corpus/templates/brain/app/lib/brain.ts +11 -0
  94. package/corpus/templates/brain/app/root.tsx +15 -1
  95. package/corpus/templates/brain/app/routes/agent.tsx +11 -0
  96. package/corpus/templates/brain/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  97. package/corpus/templates/calendar/AGENTS.md +9 -0
  98. package/corpus/templates/calendar/actions/list-events.ts +659 -44
  99. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -0
  100. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +2 -0
  101. package/corpus/templates/calendar/app/root.tsx +17 -1
  102. package/corpus/templates/calendar/app/routes/_app.agent.tsx +25 -0
  103. package/corpus/templates/calendar/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  104. package/corpus/templates/calendar/changelog/2026-07-13-calendar-inventory-reads-report-source-coverage.md +6 -0
  105. package/corpus/templates/calendar/server/lib/calendar-connector-catalog.ts +8 -0
  106. package/corpus/templates/calendar/server/lib/google-calendar.ts +173 -51
  107. package/corpus/templates/calendar/server/lib/ical-fetcher.ts +13 -2
  108. package/corpus/templates/calendar/server/plugins/agent-chat.ts +5 -3
  109. package/corpus/templates/calendar/shared/api.ts +2 -0
  110. package/corpus/templates/chat/app/components/layout/Header.tsx +1 -0
  111. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -0
  112. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +7 -0
  113. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +3 -0
  114. package/corpus/templates/chat/app/root.tsx +24 -2
  115. package/corpus/templates/chat/app/routes/agent.tsx +15 -0
  116. package/corpus/templates/chat/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  117. package/corpus/templates/clips/app/components/library/library-layout.tsx +8 -0
  118. package/corpus/templates/clips/app/i18n/ar-SA.ts +2 -0
  119. package/corpus/templates/clips/app/i18n/de-DE.ts +2 -0
  120. package/corpus/templates/clips/app/i18n/en-US.ts +2 -0
  121. package/corpus/templates/clips/app/i18n/es-ES.ts +2 -0
  122. package/corpus/templates/clips/app/i18n/fr-FR.ts +2 -0
  123. package/corpus/templates/clips/app/i18n/hi-IN.ts +2 -0
  124. package/corpus/templates/clips/app/i18n/ja-JP.ts +2 -0
  125. package/corpus/templates/clips/app/i18n/ko-KR.ts +2 -0
  126. package/corpus/templates/clips/app/i18n/pt-BR.ts +2 -0
  127. package/corpus/templates/clips/app/i18n/zh-CN.ts +2 -0
  128. package/corpus/templates/clips/app/i18n/zh-TW.ts +2 -0
  129. package/corpus/templates/clips/app/root.tsx +7 -1
  130. package/corpus/templates/clips/app/routes/_app.agent.tsx +15 -0
  131. package/corpus/templates/clips/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  132. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -0
  133. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -0
  134. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  135. package/corpus/templates/content/app/i18n-data.ts +44 -6
  136. package/corpus/templates/content/app/root.tsx +8 -0
  137. package/corpus/templates/content/app/routes/_app.agent.tsx +15 -0
  138. package/corpus/templates/content/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  139. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  140. package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -0
  141. package/corpus/templates/design/app/i18n/zh-TW.ts +2 -0
  142. package/corpus/templates/design/app/i18n-data.ts +20 -0
  143. package/corpus/templates/design/app/root.tsx +7 -1
  144. package/corpus/templates/design/app/routes/agent.tsx +15 -0
  145. package/corpus/templates/design/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  146. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +10 -1
  147. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +2 -0
  148. package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
  149. package/corpus/templates/dispatch/app/root.tsx +7 -2
  150. package/corpus/templates/dispatch/app/routes/agent.tsx +18 -0
  151. package/corpus/templates/dispatch/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  152. package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -0
  153. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +35 -0
  154. package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
  155. package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
  156. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  157. package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
  158. package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
  159. package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
  160. package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
  161. package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
  162. package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
  163. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  164. package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
  165. package/corpus/templates/forms/app/root.tsx +6 -1
  166. package/corpus/templates/forms/app/routes/_app.agent.tsx +15 -0
  167. package/corpus/templates/forms/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  168. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +14 -2
  169. package/corpus/templates/macros/app/components/layout/Header.tsx +1 -0
  170. package/corpus/templates/macros/app/root.tsx +17 -2
  171. package/corpus/templates/macros/app/routes/agent.tsx +15 -0
  172. package/corpus/templates/macros/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  173. package/corpus/templates/mail/AGENTS.md +12 -0
  174. package/corpus/templates/mail/actions/list-emails.ts +486 -4
  175. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +42 -0
  176. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +6 -0
  177. package/corpus/templates/mail/app/routes/agent.tsx +15 -0
  178. package/corpus/templates/mail/changelog/2026-07-13-coverage-aware-connected-inbox-inventory.md +6 -0
  179. package/corpus/templates/mail/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  180. package/corpus/templates/mail/server/db/schema.ts +17 -0
  181. package/corpus/templates/mail/server/lib/google-auth.ts +33 -5
  182. package/corpus/templates/mail/server/lib/inventory-cursor.ts +406 -0
  183. package/corpus/templates/mail/server/lib/list-inbox-emails.ts +21 -2
  184. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -0
  185. package/corpus/templates/mail/server/plugins/agent-chat.ts +4 -9
  186. package/corpus/templates/mail/server/plugins/db.ts +20 -0
  187. package/corpus/templates/plan/app/components/layout/Header.tsx +1 -0
  188. package/corpus/templates/plan/app/components/layout/Layout.tsx +1 -0
  189. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -0
  190. package/corpus/templates/plan/app/root.tsx +8 -0
  191. package/corpus/templates/plan/app/routes/agent.tsx +15 -0
  192. package/corpus/templates/plan/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  193. package/corpus/templates/slides/app/components/layout/Header.tsx +1 -0
  194. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -0
  195. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +2 -0
  196. package/corpus/templates/slides/app/root.tsx +10 -1
  197. package/corpus/templates/slides/app/routes/agent.tsx +15 -0
  198. package/corpus/templates/slides/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  199. package/dist/agent/context-xray/actions/context-evict.js +5 -2
  200. package/dist/agent/context-xray/actions/context-evict.js.map +1 -1
  201. package/dist/agent/context-xray/actions/context-pin.js +5 -2
  202. package/dist/agent/context-xray/actions/context-pin.js.map +1 -1
  203. package/dist/agent/context-xray/actions/context-preview-get.d.ts +17 -0
  204. package/dist/agent/context-xray/actions/context-preview-get.d.ts.map +1 -0
  205. package/dist/agent/context-xray/actions/context-preview-get.js +94 -0
  206. package/dist/agent/context-xray/actions/context-preview-get.js.map +1 -0
  207. package/dist/agent/context-xray/actions/context-restore.js +5 -2
  208. package/dist/agent/context-xray/actions/context-restore.js.map +1 -1
  209. package/dist/agent/context-xray/actions/errors.d.ts +3 -0
  210. package/dist/agent/context-xray/actions/errors.d.ts.map +1 -1
  211. package/dist/agent/context-xray/actions/errors.js +6 -0
  212. package/dist/agent/context-xray/actions/errors.js.map +1 -1
  213. package/dist/agent/context-xray/manifest.d.ts +18 -1
  214. package/dist/agent/context-xray/manifest.d.ts.map +1 -1
  215. package/dist/agent/context-xray/manifest.js +68 -6
  216. package/dist/agent/context-xray/manifest.js.map +1 -1
  217. package/dist/agent/engine/context-directives-transform.d.ts +2 -0
  218. package/dist/agent/engine/context-directives-transform.d.ts.map +1 -1
  219. package/dist/agent/engine/context-directives-transform.js +2 -1
  220. package/dist/agent/engine/context-directives-transform.js.map +1 -1
  221. package/dist/agent/production-agent.d.ts +15 -0
  222. package/dist/agent/production-agent.d.ts.map +1 -1
  223. package/dist/agent/production-agent.js +74 -36
  224. package/dist/agent/production-agent.js.map +1 -1
  225. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  226. package/dist/agent/run-loop-with-resume.js +6 -3
  227. package/dist/agent/run-loop-with-resume.js.map +1 -1
  228. package/dist/client/AgentPanel.d.ts +5 -1
  229. package/dist/client/AgentPanel.d.ts.map +1 -1
  230. package/dist/client/AgentPanel.js +9 -11
  231. package/dist/client/AgentPanel.js.map +1 -1
  232. package/dist/client/agent-page/AgentContextTab.d.ts +5 -0
  233. package/dist/client/agent-page/AgentContextTab.d.ts.map +1 -0
  234. package/dist/client/agent-page/AgentContextTab.js +202 -0
  235. package/dist/client/agent-page/AgentContextTab.js.map +1 -0
  236. package/dist/client/agent-page/AgentJobsTab.d.ts +3 -0
  237. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -0
  238. package/dist/client/agent-page/AgentJobsTab.js +98 -0
  239. package/dist/client/agent-page/AgentJobsTab.js.map +1 -0
  240. package/dist/client/agent-page/AgentTabsPage.d.ts +20 -0
  241. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -0
  242. package/dist/client/agent-page/AgentTabsPage.js +420 -0
  243. package/dist/client/agent-page/AgentTabsPage.js.map +1 -0
  244. package/dist/client/agent-page/types.d.ts +17 -0
  245. package/dist/client/agent-page/types.d.ts.map +1 -0
  246. package/dist/client/agent-page/types.js +9 -0
  247. package/dist/client/agent-page/types.js.map +1 -0
  248. package/dist/client/agent-page/use-jobs.d.ts +61 -0
  249. package/dist/client/agent-page/use-jobs.d.ts.map +1 -0
  250. package/dist/client/agent-page/use-jobs.js +71 -0
  251. package/dist/client/agent-page/use-jobs.js.map +1 -0
  252. package/dist/client/context-xray/ContextMeter.d.ts +3 -1
  253. package/dist/client/context-xray/ContextMeter.d.ts.map +1 -1
  254. package/dist/client/context-xray/ContextMeter.js +14 -5
  255. package/dist/client/context-xray/ContextMeter.js.map +1 -1
  256. package/dist/client/context-xray/ContextTreemap.d.ts +3 -2
  257. package/dist/client/context-xray/ContextTreemap.d.ts.map +1 -1
  258. package/dist/client/context-xray/ContextTreemap.js +9 -1
  259. package/dist/client/context-xray/ContextTreemap.js.map +1 -1
  260. package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -1
  261. package/dist/client/context-xray/ContextXRayPanel.js +95 -20
  262. package/dist/client/context-xray/ContextXRayPanel.js.map +1 -1
  263. package/dist/client/context-xray/format.d.ts.map +1 -1
  264. package/dist/client/context-xray/format.js +12 -0
  265. package/dist/client/context-xray/format.js.map +1 -1
  266. package/dist/client/index.d.ts +3 -0
  267. package/dist/client/index.d.ts.map +1 -1
  268. package/dist/client/index.js +1 -0
  269. package/dist/client/index.js.map +1 -1
  270. package/dist/client/resources/ResourcesPanel.d.ts +9 -1
  271. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  272. package/dist/client/resources/ResourcesPanel.js +26 -10
  273. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  274. package/dist/client/settings/AutomationsSection.d.ts +9 -0
  275. package/dist/client/settings/AutomationsSection.d.ts.map +1 -1
  276. package/dist/client/settings/AutomationsSection.js +128 -147
  277. package/dist/client/settings/AutomationsSection.js.map +1 -1
  278. package/dist/collab/routes.d.ts +1 -1
  279. package/dist/collab/struct-routes.d.ts +1 -1
  280. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  281. package/dist/jobs/actions/list-recurring-jobs.d.ts +21 -0
  282. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -0
  283. package/dist/jobs/actions/list-recurring-jobs.js +74 -0
  284. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -0
  285. package/dist/jobs/actions/manage-recurring-job.d.ts +18 -0
  286. package/dist/jobs/actions/manage-recurring-job.d.ts.map +1 -0
  287. package/dist/jobs/actions/manage-recurring-job.js +68 -0
  288. package/dist/jobs/actions/manage-recurring-job.js.map +1 -0
  289. package/dist/jobs/tools.d.ts +13 -0
  290. package/dist/jobs/tools.d.ts.map +1 -1
  291. package/dist/jobs/tools.js +1 -1
  292. package/dist/jobs/tools.js.map +1 -1
  293. package/dist/localization/default-messages.d.ts +96 -0
  294. package/dist/localization/default-messages.d.ts.map +1 -1
  295. package/dist/localization/default-messages.js +96 -0
  296. package/dist/localization/default-messages.js.map +1 -1
  297. package/dist/mcp/build-server.d.ts.map +1 -1
  298. package/dist/mcp/build-server.js +1 -0
  299. package/dist/mcp/build-server.js.map +1 -1
  300. package/dist/mcp/connect-route.d.ts.map +1 -1
  301. package/dist/mcp/connect-route.js +44 -59
  302. package/dist/mcp/connect-route.js.map +1 -1
  303. package/dist/notifications/routes.d.ts +1 -1
  304. package/dist/observability/routes.d.ts +5 -5
  305. package/dist/progress/routes.d.ts +1 -1
  306. package/dist/resources/handlers.d.ts +3 -3
  307. package/dist/secrets/routes.d.ts +9 -9
  308. package/dist/server/action-discovery.d.ts.map +1 -1
  309. package/dist/server/action-discovery.js +23 -0
  310. package/dist/server/action-discovery.js.map +1 -1
  311. package/dist/server/agent-chat/prompt-resources.d.ts +14 -0
  312. package/dist/server/agent-chat/prompt-resources.d.ts.map +1 -1
  313. package/dist/server/agent-chat/prompt-resources.js +139 -2
  314. package/dist/server/agent-chat/prompt-resources.js.map +1 -1
  315. package/dist/server/agent-chat-plugin.d.ts +1 -0
  316. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  317. package/dist/server/agent-chat-plugin.js +136 -3
  318. package/dist/server/agent-chat-plugin.js.map +1 -1
  319. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  320. package/dist/server/auth.d.ts.map +1 -1
  321. package/dist/server/auth.js +1 -4
  322. package/dist/server/auth.js.map +1 -1
  323. package/dist/server/google-oauth.d.ts.map +1 -1
  324. package/dist/server/google-oauth.js +1 -4
  325. package/dist/server/google-oauth.js.map +1 -1
  326. package/dist/server/workspace-oauth.d.ts +11 -0
  327. package/dist/server/workspace-oauth.d.ts.map +1 -0
  328. package/dist/server/workspace-oauth.js +25 -0
  329. package/dist/server/workspace-oauth.js.map +1 -0
  330. package/dist/shared/context-xray.d.ts +45 -1
  331. package/dist/shared/context-xray.d.ts.map +1 -1
  332. package/dist/shared/context-xray.js +12 -0
  333. package/dist/shared/context-xray.js.map +1 -1
  334. package/dist/shared/mcp-connect-content.d.ts +37 -0
  335. package/dist/shared/mcp-connect-content.d.ts.map +1 -0
  336. package/dist/shared/mcp-connect-content.js +92 -0
  337. package/dist/shared/mcp-connect-content.js.map +1 -0
  338. package/dist/templates/default/app/routes/_index.tsx +11 -0
  339. package/dist/templates/default/app/routes/agent.tsx +33 -0
  340. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  341. package/dist/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
  342. package/dist/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
  343. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
  344. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
  345. package/dist/triggers/actions/list-automations.d.ts +24 -0
  346. package/dist/triggers/actions/list-automations.d.ts.map +1 -0
  347. package/dist/triggers/actions/list-automations.js +72 -0
  348. package/dist/triggers/actions/list-automations.js.map +1 -0
  349. package/dist/triggers/actions/manage-automation.d.ts +18 -0
  350. package/dist/triggers/actions/manage-automation.d.ts.map +1 -0
  351. package/dist/triggers/actions/manage-automation.js +60 -0
  352. package/dist/triggers/actions/manage-automation.js.map +1 -0
  353. package/docs/content/agent-surfaces.mdx +43 -0
  354. package/docs/content/external-agents.mdx +11 -0
  355. package/docs/content/locales/ar-SA/agent-surfaces.mdx +30 -0
  356. package/docs/content/locales/ar-SA/external-agents.mdx +9 -0
  357. package/docs/content/locales/de-DE/agent-surfaces.mdx +34 -0
  358. package/docs/content/locales/de-DE/external-agents.mdx +10 -0
  359. package/docs/content/locales/es-ES/agent-surfaces.mdx +34 -0
  360. package/docs/content/locales/es-ES/external-agents.mdx +10 -0
  361. package/docs/content/locales/fr-FR/agent-surfaces.mdx +34 -0
  362. package/docs/content/locales/fr-FR/external-agents.mdx +11 -0
  363. package/docs/content/locales/hi-IN/agent-surfaces.mdx +31 -0
  364. package/docs/content/locales/hi-IN/external-agents.mdx +9 -0
  365. package/docs/content/locales/ja-JP/agent-surfaces.mdx +32 -0
  366. package/docs/content/locales/ja-JP/external-agents.mdx +10 -0
  367. package/docs/content/locales/ko-KR/agent-surfaces.mdx +32 -0
  368. package/docs/content/locales/ko-KR/external-agents.mdx +9 -0
  369. package/docs/content/locales/pt-BR/agent-surfaces.mdx +33 -0
  370. package/docs/content/locales/pt-BR/external-agents.mdx +10 -0
  371. package/docs/content/locales/zh-CN/agent-surfaces.mdx +29 -0
  372. package/docs/content/locales/zh-CN/external-agents.mdx +8 -0
  373. package/docs/content/locales/zh-TW/agent-surfaces.mdx +29 -0
  374. package/docs/content/locales/zh-TW/external-agents.mdx +8 -0
  375. package/package.json +1 -1
  376. package/src/templates/default/app/routes/_index.tsx +11 -0
  377. package/src/templates/default/app/routes/agent.tsx +33 -0
  378. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  379. package/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
  380. package/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
  381. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
  382. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
@@ -1,5 +1,22 @@
1
1
  import { table, text, integer } from "@agent-native/core/db/schema";
2
2
 
3
+ /**
4
+ * Short-lived, owner-scoped continuation state for the external Mail
5
+ * inventory. It intentionally contains compact metadata only; credentials,
6
+ * bodies, HTML and attachments never enter this table.
7
+ */
8
+ export const mailInventoryCursors = table("mail_inventory_cursors", {
9
+ id: text("id").primaryKey(),
10
+ ownerEmail: text("owner_email").notNull(),
11
+ queryFingerprint: text("query_fingerprint").notNull(),
12
+ state: text("state").notNull(),
13
+ version: integer("version").notNull().default(1),
14
+ claimId: text("claim_id"),
15
+ claimedAt: integer("claimed_at"),
16
+ expiresAt: integer("expires_at").notNull(),
17
+ updatedAt: integer("updated_at").notNull(),
18
+ });
19
+
3
20
  export const scheduledJobs = table("scheduled_jobs", {
4
21
  id: text("id").primaryKey(),
5
22
  type: text("type", { enum: ["snooze", "send_later"] }).notNull(),
@@ -375,12 +375,23 @@ export async function getClients(
375
375
  * handler uses this to return a 502 with the underlying reason instead
376
376
  * of silently rendering an empty inbox.
377
377
  */
378
- export async function getClientsWithErrors(forEmail?: string): Promise<{
378
+ export async function getClientsWithErrors(
379
+ forEmail?: string,
380
+ accountEmails?: string[],
381
+ ): Promise<{
379
382
  clients: Array<{ email: string; accessToken: string; refreshToken: string }>;
380
383
  errors: Array<{ email: string; error: string }>;
381
384
  }> {
382
385
  if (!forEmail) return { clients: [], errors: [] };
383
- const accounts = await listOAuthAccountsByOwner("google", forEmail);
386
+ const requested = accountEmails
387
+ ? new Set(accountEmails.map((email) => email.toLowerCase()))
388
+ : null;
389
+ // Filtering happens before getValidAccessToken. This is important: token
390
+ // refreshes are writes and an explicitly scoped inventory read must not
391
+ // refresh unrelated accounts.
392
+ const accounts = (await listOAuthAccountsByOwner("google", forEmail)).filter(
393
+ (account) => !requested || requested.has(account.accountId.toLowerCase()),
394
+ );
384
395
 
385
396
  const clients: Array<{
386
397
  email: string;
@@ -571,6 +582,13 @@ type ListOptions = {
571
582
  * without hydrating a large metadata ranking window on every inbox poll.
572
583
  */
573
584
  threadRecentMessageCandidateLimit?: number;
585
+ /**
586
+ * Restrict this read before OAuth refreshes or provider calls. This is
587
+ * deliberately an account-id allow-list rather than a post-fetch filter:
588
+ * a Mail user can have several connected inboxes and an explicitly scoped
589
+ * read must not wake up the others.
590
+ */
591
+ accountEmails?: string[];
574
592
  };
575
593
 
576
594
  const LIST_CACHE_TTL = 45_000;
@@ -791,6 +809,10 @@ async function fetchThreadBatchWithRefill(
791
809
  }
792
810
  }
793
811
 
812
+ if (batchResults.some((result) => !result.data)) {
813
+ throw new Error("Gmail thread metadata response was incomplete");
814
+ }
815
+
794
816
  return batchResults;
795
817
  }
796
818
 
@@ -852,7 +874,11 @@ function listCacheKey(
852
874
  .join("|")
853
875
  : "";
854
876
  const queryPart = query === undefined ? "<default>" : query;
855
- return `${forEmail ?? ""}::${queryPart}::${maxResults}::${tokenPart}::${options?.mode ?? "messages"}::${options?.threadFormat ?? ""}::${options?.messageFormat ?? ""}::${options?.threadCandidateLimit ?? ""}::${options?.threadRecentMessageCandidateLimit ?? ""}`;
877
+ const accounts = options?.accountEmails
878
+ ?.map((email) => email.toLowerCase())
879
+ .sort()
880
+ .join(",");
881
+ return `${forEmail ?? ""}::${queryPart}::${maxResults}::${tokenPart}::${options?.mode ?? "messages"}::${options?.threadFormat ?? ""}::${options?.messageFormat ?? ""}::${options?.threadCandidateLimit ?? ""}::${options?.threadRecentMessageCandidateLimit ?? ""}::${accounts ?? ""}`;
856
882
  }
857
883
 
858
884
  export async function listGmailMessages(
@@ -1542,8 +1568,10 @@ async function listGmailMessagesUncached(
1542
1568
  pageTokens?: Record<string, string>,
1543
1569
  options?: ListOptions,
1544
1570
  ): Promise<ListResult> {
1545
- const { clients, errors: refreshErrors } =
1546
- await getClientsWithErrors(forEmail);
1571
+ const { clients, errors: refreshErrors } = await getClientsWithErrors(
1572
+ forEmail,
1573
+ options?.accountEmails,
1574
+ );
1547
1575
  // Seed the per-fetch error list with refresh failures so a fully-dead
1548
1576
  // connection (every account's refresh_token revoked or invalidated by a
1549
1577
  // GOOGLE_CLIENT_ID rotation) reaches the handler — otherwise the list
@@ -0,0 +1,406 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { and, eq, gt, lt, or, sql } from "drizzle-orm";
4
+
5
+ import { getDb } from "../db/index.js";
6
+ import { mailInventoryCursors } from "../db/schema.js";
7
+
8
+ const TTL_MS = 10 * 60 * 1000;
9
+ const CLAIM_TTL_MS = 60 * 1000;
10
+ const PAGE_ITEMS_BUDGET_BYTES = 12 * 1024;
11
+
12
+ export interface MailInventoryItem {
13
+ id: string;
14
+ threadId: string;
15
+ accountEmail: string;
16
+ date: string;
17
+ from: { name?: string; email: string };
18
+ subject: string;
19
+ isUnread: boolean;
20
+ isStarred?: boolean;
21
+ messageCount: number;
22
+ unreadCount: number;
23
+ snippet?: string;
24
+ }
25
+
26
+ export interface MailInventoryError {
27
+ code: string;
28
+ message: string;
29
+ retryable: boolean;
30
+ }
31
+
32
+ export interface MailInventoryAccountState {
33
+ accountEmail: string;
34
+ status: "ok" | "error";
35
+ error?: MailInventoryError;
36
+ providerPageToken?: string;
37
+ exhausted: boolean;
38
+ pending: MailInventoryItem[];
39
+ emittedCount: number;
40
+ /** Unique rows discovered so coverage remains visible before emission. */
41
+ knownCount?: number;
42
+ /** Account-local thread ids already emitted by an earlier output page. */
43
+ emittedThreadIds?: string[];
44
+ }
45
+
46
+ export interface MailInventoryCursorState {
47
+ queryFingerprint: string;
48
+ requestedAccounts: string[] | null;
49
+ accounts: MailInventoryAccountState[];
50
+ firstPage: boolean;
51
+ }
52
+
53
+ export interface MailInventoryCursorClaim {
54
+ id: string;
55
+ claimId: string;
56
+ ownerEmail: string;
57
+ queryFingerprint: string;
58
+ version: number;
59
+ state: MailInventoryCursorState;
60
+ }
61
+
62
+ export interface MailInventoryFetchResult {
63
+ items: MailInventoryItem[];
64
+ errors: Record<string, MailInventoryError>;
65
+ nextPageTokens: Record<string, string>;
66
+ }
67
+
68
+ function stableValue(value: unknown): unknown {
69
+ if (Array.isArray(value)) return value.map(stableValue);
70
+ if (value && typeof value === "object") {
71
+ return Object.fromEntries(
72
+ Object.entries(value as Record<string, unknown>)
73
+ .sort(([a], [b]) => a.localeCompare(b))
74
+ .map(([key, child]) => [key, stableValue(child)]),
75
+ );
76
+ }
77
+ return value;
78
+ }
79
+
80
+ export function inventoryQueryFingerprint(input: unknown): string {
81
+ return createHash("sha256")
82
+ .update(JSON.stringify(stableValue(input)))
83
+ .digest("hex");
84
+ }
85
+
86
+ export function compareInventoryItems(
87
+ a: MailInventoryItem,
88
+ b: MailInventoryItem,
89
+ ): number {
90
+ const date = new Date(b.date).getTime() - new Date(a.date).getTime();
91
+ if (date) return date;
92
+ const account = a.accountEmail.localeCompare(b.accountEmail);
93
+ if (account) return account;
94
+ const thread = a.threadId.localeCompare(b.threadId);
95
+ if (thread) return thread;
96
+ return a.id.localeCompare(b.id);
97
+ }
98
+
99
+ function dedupeAndSort(items: MailInventoryItem[]): MailInventoryItem[] {
100
+ const unique = new Map<string, MailInventoryItem>();
101
+ for (const item of items) {
102
+ const key = `${item.accountEmail.toLowerCase()}:${item.threadId}`;
103
+ const current = unique.get(key);
104
+ if (!current || compareInventoryItems(item, current) < 0) {
105
+ unique.set(key, item);
106
+ }
107
+ }
108
+ return [...unique.values()].sort(compareInventoryItems);
109
+ }
110
+
111
+ function applyFetch(
112
+ states: MailInventoryAccountState[],
113
+ result: MailInventoryFetchResult,
114
+ ): void {
115
+ const byAccount = new Map<string, MailInventoryItem[]>();
116
+ for (const item of result.items) {
117
+ const key = item.accountEmail.toLowerCase();
118
+ const accountItems = byAccount.get(key) ?? [];
119
+ accountItems.push(item);
120
+ byAccount.set(key, accountItems);
121
+ }
122
+
123
+ for (const state of states) {
124
+ const key = state.accountEmail.toLowerCase();
125
+ const error = result.errors[key];
126
+ if (error) {
127
+ state.status = "error";
128
+ state.error = error;
129
+ state.exhausted = true;
130
+ state.providerPageToken = undefined;
131
+ continue;
132
+ }
133
+ const emitted = new Set(state.emittedThreadIds ?? []);
134
+ const before = new Set(
135
+ state.pending.map((item) => item.threadId).concat([...emitted]),
136
+ );
137
+ const additions = (byAccount.get(key) ?? []).filter(
138
+ (item) => !before.has(item.threadId),
139
+ );
140
+ state.knownCount =
141
+ (state.knownCount ?? state.emittedCount) + additions.length;
142
+ state.pending = dedupeAndSort([...state.pending, ...additions]);
143
+ state.providerPageToken = result.nextPageTokens[key];
144
+ state.exhausted = !state.providerPageToken;
145
+ }
146
+ }
147
+
148
+ export async function buildMailInventoryPage(
149
+ state: MailInventoryCursorState,
150
+ limit: number,
151
+ fetch: (
152
+ accounts: Array<{ accountEmail: string; pageToken?: string }>,
153
+ ) => Promise<MailInventoryFetchResult>,
154
+ ): Promise<{ items: MailInventoryItem[]; hasMore: boolean }> {
155
+ const pageLimit = Math.max(1, Math.min(Math.floor(limit), 100));
156
+ const refillCounts = new Map<string, number>();
157
+ const refillFrontiers = async () => {
158
+ while (true) {
159
+ const needsFetch = state.accounts.filter(
160
+ (account) =>
161
+ account.status === "ok" &&
162
+ account.pending.length === 0 &&
163
+ !account.exhausted,
164
+ );
165
+ if (needsFetch.length === 0) return;
166
+ const fetchable = needsFetch.filter((account) => {
167
+ const key = account.accountEmail.toLowerCase();
168
+ const count = refillCounts.get(key) ?? 0;
169
+ if (count < 4) {
170
+ refillCounts.set(key, count + 1);
171
+ return true;
172
+ }
173
+ account.status = "error";
174
+ account.error = {
175
+ code: "pagination_limit",
176
+ message:
177
+ "Mail pagination did not expose a row frontier within four provider pages.",
178
+ retryable: true,
179
+ };
180
+ account.exhausted = true;
181
+ account.providerPageToken = undefined;
182
+ return false;
183
+ });
184
+ if (fetchable.length === 0) continue;
185
+ const prior = fetchable.map((account) => ({
186
+ account,
187
+ token: account.providerPageToken,
188
+ }));
189
+ applyFetch(
190
+ fetchable,
191
+ await fetch(
192
+ fetchable.map((account) => ({
193
+ accountEmail: account.accountEmail,
194
+ pageToken: account.providerPageToken,
195
+ })),
196
+ ),
197
+ );
198
+ for (const { account, token } of prior) {
199
+ if (
200
+ account.status === "ok" &&
201
+ !account.exhausted &&
202
+ account.pending.length === 0 &&
203
+ account.providerPageToken === token
204
+ ) {
205
+ throw new Error(
206
+ `Mail provider pagination made no progress for ${account.accountEmail}.`,
207
+ );
208
+ }
209
+ }
210
+ }
211
+ };
212
+
213
+ const page: MailInventoryItem[] = [];
214
+ const emittedKeys = new Set<string>();
215
+ let pageBytes = 2; // JSON array brackets
216
+ const emit = (
217
+ account: MailInventoryAccountState,
218
+ item: MailInventoryItem,
219
+ ) => {
220
+ const key = `${item.accountEmail.toLowerCase()}:${item.threadId}`;
221
+ if (emittedKeys.has(key)) return;
222
+ emittedKeys.add(key);
223
+ page.push(item);
224
+ account.emittedCount += 1;
225
+ account.emittedThreadIds = [
226
+ ...(account.emittedThreadIds ?? []),
227
+ item.threadId,
228
+ ];
229
+ };
230
+
231
+ while (page.length < pageLimit) {
232
+ // A global merge is only safe when every live account has a known
233
+ // frontier. Refill an emptied account before emitting an older candidate
234
+ // from another account; otherwise an unseen newer provider row can be
235
+ // skipped across the page boundary.
236
+ await refillFrontiers();
237
+ const candidates = state.accounts
238
+ .filter((account) => account.pending.length > 0)
239
+ .map((account) => ({ account, item: account.pending[0] }))
240
+ .sort((a, b) => compareInventoryItems(a.item, b.item));
241
+ if (candidates.length === 0) {
242
+ break;
243
+ }
244
+ const winner = candidates[0];
245
+ const itemBytes = new TextEncoder().encode(
246
+ JSON.stringify(winner.item),
247
+ ).byteLength;
248
+ if (
249
+ page.length > 0 &&
250
+ pageBytes + itemBytes + 1 > PAGE_ITEMS_BUDGET_BYTES
251
+ ) {
252
+ break;
253
+ }
254
+ winner.account.pending.shift();
255
+ emit(winner.account, winner.item);
256
+ pageBytes += itemBytes + (page.length > 1 ? 1 : 0);
257
+ }
258
+
259
+ page.sort(compareInventoryItems);
260
+ state.firstPage = false;
261
+ return {
262
+ items: page,
263
+ hasMore: state.accounts.some(
264
+ (account) => account.pending.length > 0 || !account.exhausted,
265
+ ),
266
+ };
267
+ }
268
+
269
+ export async function createInventoryCursor(
270
+ ownerEmail: string,
271
+ state: MailInventoryCursorState,
272
+ ): Promise<string> {
273
+ const now = Date.now();
274
+ const id = crypto.randomUUID();
275
+ await getDb()
276
+ .insert(mailInventoryCursors)
277
+ .values({
278
+ id,
279
+ ownerEmail,
280
+ queryFingerprint: state.queryFingerprint,
281
+ state: JSON.stringify(state),
282
+ version: 1,
283
+ expiresAt: now + TTL_MS,
284
+ updatedAt: now,
285
+ });
286
+ await getDb()
287
+ .delete(mailInventoryCursors)
288
+ .where(lt(mailInventoryCursors.expiresAt, now));
289
+ return id;
290
+ }
291
+
292
+ /**
293
+ * Atomically leases a cursor without consuming it. Provider work happens only
294
+ * after this short-lived CAS. Failures can release the lease; success settles
295
+ * it into a distinct successor id (or deletes it at exhaustion).
296
+ */
297
+ export async function claimInventoryCursor(
298
+ ownerEmail: string,
299
+ id: string,
300
+ queryFingerprint: string,
301
+ ): Promise<MailInventoryCursorClaim | null> {
302
+ const now = Date.now();
303
+ const claimId = crypto.randomUUID();
304
+ const staleBefore = now - CLAIM_TTL_MS;
305
+ const rows = await getDb()
306
+ .update(mailInventoryCursors)
307
+ .set({ claimId, claimedAt: now, updatedAt: now })
308
+ .where(
309
+ and(
310
+ eq(mailInventoryCursors.id, id),
311
+ eq(mailInventoryCursors.ownerEmail, ownerEmail),
312
+ eq(mailInventoryCursors.queryFingerprint, queryFingerprint),
313
+ gt(mailInventoryCursors.expiresAt, now),
314
+ or(
315
+ sql`${mailInventoryCursors.claimId} IS NULL`,
316
+ lt(mailInventoryCursors.claimedAt, staleBefore),
317
+ ),
318
+ ),
319
+ )
320
+ .returning({
321
+ state: mailInventoryCursors.state,
322
+ expiresAt: mailInventoryCursors.expiresAt,
323
+ version: mailInventoryCursors.version,
324
+ });
325
+ const row = rows[0];
326
+ if (!row || row.expiresAt <= now) return null;
327
+ try {
328
+ return {
329
+ id,
330
+ claimId,
331
+ ownerEmail,
332
+ queryFingerprint,
333
+ version: row.version,
334
+ state: JSON.parse(row.state) as MailInventoryCursorState,
335
+ };
336
+ } catch {
337
+ await releaseInventoryCursorClaim({
338
+ id,
339
+ claimId,
340
+ ownerEmail,
341
+ queryFingerprint,
342
+ version: row.version,
343
+ state: {} as MailInventoryCursorState,
344
+ });
345
+ return null;
346
+ }
347
+ }
348
+
349
+ export async function releaseInventoryCursorClaim(
350
+ claim: MailInventoryCursorClaim,
351
+ ): Promise<void> {
352
+ await getDb()
353
+ .update(mailInventoryCursors)
354
+ .set({ claimId: null, claimedAt: null, updatedAt: Date.now() })
355
+ .where(
356
+ and(
357
+ eq(mailInventoryCursors.id, claim.id),
358
+ eq(mailInventoryCursors.ownerEmail, claim.ownerEmail),
359
+ eq(mailInventoryCursors.queryFingerprint, claim.queryFingerprint),
360
+ eq(mailInventoryCursors.version, claim.version),
361
+ eq(mailInventoryCursors.claimId, claim.claimId),
362
+ ),
363
+ );
364
+ }
365
+
366
+ /** Atomically consumes a leased id and optionally creates its successor. */
367
+ export async function settleInventoryCursorClaim(
368
+ claim: MailInventoryCursorClaim,
369
+ state: MailInventoryCursorState,
370
+ hasMore: boolean,
371
+ ): Promise<string | undefined> {
372
+ const db = getDb();
373
+ const successorId = hasMore ? crypto.randomUUID() : undefined;
374
+ const now = Date.now();
375
+ return db.transaction(async (tx: any) => {
376
+ const consumed = await tx
377
+ .delete(mailInventoryCursors)
378
+ .where(
379
+ and(
380
+ eq(mailInventoryCursors.id, claim.id),
381
+ eq(mailInventoryCursors.ownerEmail, claim.ownerEmail),
382
+ eq(mailInventoryCursors.queryFingerprint, claim.queryFingerprint),
383
+ eq(mailInventoryCursors.version, claim.version),
384
+ eq(mailInventoryCursors.claimId, claim.claimId),
385
+ ),
386
+ )
387
+ .returning({ id: mailInventoryCursors.id });
388
+ if (consumed.length !== 1) {
389
+ throw new Error("Inventory cursor lease was lost before settlement.");
390
+ }
391
+ if (successorId) {
392
+ await tx.insert(mailInventoryCursors).values({
393
+ id: successorId,
394
+ ownerEmail: claim.ownerEmail,
395
+ queryFingerprint: claim.queryFingerprint,
396
+ state: JSON.stringify(state),
397
+ version: claim.version + 1,
398
+ claimId: null,
399
+ claimedAt: null,
400
+ expiresAt: now + TTL_MS,
401
+ updatedAt: now,
402
+ });
403
+ }
404
+ return successorId;
405
+ });
406
+ }
@@ -45,7 +45,11 @@ export interface ListInboxEmailsParams {
45
45
  pageTokens?: Record<string, string>;
46
46
  threadFormat?: "full" | "metadata" | "minimal";
47
47
  threadCandidateLimit?: number;
48
+ /** Disable the extra recent-message candidate pass for bounded inventory. */
49
+ includeRecentMessageCandidates?: boolean;
48
50
  accountTokens: ListInboxEmailsAccountToken[];
51
+ /** Selected account ids are forwarded to Gmail before token refresh. */
52
+ accountEmails?: string[];
49
53
  labelMap: Map<string, string>;
50
54
  }
51
55
 
@@ -108,7 +112,9 @@ export async function listInboxEmails(
108
112
  pageTokens,
109
113
  threadFormat,
110
114
  threadCandidateLimit,
115
+ includeRecentMessageCandidates = true,
111
116
  accountTokens,
117
+ accountEmails,
112
118
  labelMap,
113
119
  } = params;
114
120
 
@@ -123,12 +129,25 @@ export async function listInboxEmails(
123
129
  threadFormat,
124
130
  threadCandidateLimit,
125
131
  threadRecentMessageCandidateLimit:
126
- !q && (view === "inbox" || view === "unread")
132
+ includeRecentMessageCandidates &&
133
+ !q &&
134
+ (view === "inbox" || view === "unread")
127
135
  ? DEFAULT_THREAD_RECENT_MESSAGE_CANDIDATE_LIMIT
128
136
  : undefined,
137
+ accountEmails,
129
138
  });
130
139
 
131
- if (messages.length === 0 && errors.length > 0) {
140
+ const failedAccounts = new Set(
141
+ errors.map((error) => error.email.toLowerCase()),
142
+ );
143
+ const everySelectedAccountFailed = [...connectedEmails].every((email) =>
144
+ failedAccounts.has(email),
145
+ );
146
+ if (
147
+ messages.length === 0 &&
148
+ errors.length > 0 &&
149
+ everySelectedAccountFailed
150
+ ) {
132
151
  const isQuotaError = errors.every((e) => isGmailQuotaError(e.error));
133
152
  return {
134
153
  ok: false,
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Deliberately narrow authenticated MCP surface for Mail.
3
+ *
4
+ * External callers may read inbox coverage through list-emails. Other actions
5
+ * remain available through the in-app agent, ask_app, or an explicit
6
+ * full-catalog connection; tool-search alone never makes them callable.
7
+ */
8
+ export const MAIL_CONNECTOR_CATALOG = ["list-emails"] as const;
@@ -6,6 +6,7 @@ import {
6
6
  } from "@agent-native/core/server";
7
7
 
8
8
  import actionsRegistry from "../../.generated/actions-registry.js";
9
+ import { MAIL_CONNECTOR_CATALOG } from "../lib/mail-connector-catalog.js";
9
10
 
10
11
  const INITIAL_TOOL_NAMES = [
11
12
  "view-screen",
@@ -33,6 +34,7 @@ export default createAgentChatPlugin({
33
34
  actions: loadActionsFromStaticRegistry(actionsRegistry),
34
35
  appId: "mail",
35
36
  initialToolNames: INITIAL_TOOL_NAMES,
37
+ connectorCatalog: [...MAIL_CONNECTOR_CATALOG],
36
38
  resolveOrgId: async (event) => {
37
39
  const ctx = await getOrgContext(event);
38
40
  return ctx.orgId;
@@ -90,16 +92,9 @@ export default createAgentChatPlugin({
90
92
 
91
93
  Some less-common tool schemas are loaded on demand. Use tool-search with a specific query when you need a capability that is not already available as a direct tool.
92
94
 
93
- ## Google Connection Check — CRITICAL
95
+ ## Deterministic Mail Reads
94
96
 
95
- BEFORE doing anything else, run view-screen to check if Google is connected.
96
- If view-screen shows 0 emails or indicates Google is not connected:
97
- - Do NOT run list-emails, search-emails, send-email, or any email operation scripts
98
- - Do NOT pretend to have access to emails
99
- - Tell the user: "You need to connect your Google account first. Click the 'Connect Google' button on the main screen to get started."
100
- - You can still answer general questions, but you cannot perform any email operations
101
-
102
- Only proceed with email operations if view-screen confirms real emails are available.
97
+ For deterministic headless email reads, call list-emails directly in inventory/coverage mode. Do not require view-screen as a Google connection preflight: list-emails selects the connected Gmail or synthetic local-mail backend for the user and returns the relevant result. Use view-screen only when the answer depends on visible UI state, such as the active thread, selected message, draft, queue item, or current inbox view. Treat real action errors as the evidence for an unavailable connection; do not infer it from a zero-email screen.
103
98
 
104
99
  Available operations:
105
100
  - List and search emails
@@ -188,6 +188,26 @@ CREATE INDEX IF NOT EXISTS idx_snippets_owner_name ON snippets(owner_email, name
188
188
  sql: `ALTER TABLE queued_email_drafts ADD COLUMN IF NOT EXISTS send_claim_id TEXT;
189
189
  ALTER TABLE queued_email_drafts ADD COLUMN IF NOT EXISTS send_claimed_at ${intType()}`,
190
190
  },
191
+ {
192
+ version: 18,
193
+ name: "mail-inventory-cursors",
194
+ sql: `CREATE TABLE IF NOT EXISTS mail_inventory_cursors (
195
+ id TEXT PRIMARY KEY,
196
+ owner_email TEXT NOT NULL,
197
+ query_fingerprint TEXT NOT NULL,
198
+ state TEXT NOT NULL,
199
+ version ${intType()} NOT NULL DEFAULT 1,
200
+ expires_at ${intType()} NOT NULL,
201
+ updated_at ${intType()} NOT NULL
202
+ );
203
+ CREATE INDEX IF NOT EXISTS idx_mail_inventory_cursors_owner_expiry ON mail_inventory_cursors(owner_email, expires_at);`,
204
+ },
205
+ {
206
+ version: 19,
207
+ name: "mail-inventory-cursor-leases",
208
+ sql: `ALTER TABLE mail_inventory_cursors ADD COLUMN IF NOT EXISTS claim_id TEXT;
209
+ ALTER TABLE mail_inventory_cursors ADD COLUMN IF NOT EXISTS claimed_at ${intType()}`,
210
+ },
191
211
  ],
192
212
  { table: "mail_migrations" },
193
213
  );
@@ -11,6 +11,7 @@ import { APP_TITLE } from "@/lib/app-config";
11
11
  const pageTitleKeys: Record<string, string> = {
12
12
  "/": "header.plan",
13
13
  "/plans": "header.plan",
14
+ "/agent": "settings.agentTitle",
14
15
  "/settings": "header.settings",
15
16
  };
16
17
 
@@ -204,6 +204,7 @@ export function Layout({ children }: LayoutProps) {
204
204
  chatViewTransition
205
205
  storageKey="plans"
206
206
  openOnChatRunning={chatHomeHandoffActive}
207
+ agentPageHref="/agent"
207
208
  emptyStateText={t("agent.emptyState")}
208
209
  suggestions={[
209
210
  t("agent.suggestionShipped"),
@@ -18,6 +18,7 @@ import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
18
18
  import { OrgSwitcher } from "@agent-native/core/client/org";
19
19
  import {
20
20
  IconArchive,
21
+ IconBrain,
21
22
  IconClipboardCheck,
22
23
  IconDots,
23
24
  IconEdit,
@@ -87,6 +88,7 @@ function buildBrandingCustomizationMessage(request: string) {
87
88
  const navItems = [
88
89
  { icon: IconMessageCircle, labelKey: "navigation.ask", href: "/" },
89
90
  { icon: IconClipboardCheck, labelKey: "navigation.plan", href: "/plans" },
91
+ { icon: IconBrain, labelKey: "settings.agentTitle", href: "/agent" },
90
92
  { icon: IconSettings, labelKey: "navigation.settings", href: "/settings" },
91
93
  ];
92
94
 
@@ -13,6 +13,7 @@ import {
13
13
  } from "@agent-native/core/client";
14
14
  import { configureTracking } from "@agent-native/core/client";
15
15
  import {
16
+ IconBrain,
16
17
  IconMoon,
17
18
  IconScribble,
18
19
  IconShape2,
@@ -176,6 +177,13 @@ function AppContent() {
176
177
  <CommandMenu.Item onSelect={() => go("/recaps")}>
177
178
  {t("root.openRecaps")}
178
179
  </CommandMenu.Item>
180
+ <CommandMenu.Item
181
+ onSelect={() => go("/agent")}
182
+ keywords={["agent", "context", "connections", "jobs", "access"]}
183
+ >
184
+ <IconBrain size={16} />
185
+ {t("settings.openAgentSettings")}
186
+ </CommandMenu.Item>
179
187
  </CommandMenu.Group>
180
188
  <CommandMenu.Group heading={t("root.commandAppearance")}>
181
189
  <CommandMenu.Item