@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/docs/content/agent-surfaces.mdx +43 -0
- package/corpus/core/docs/content/external-agents.mdx +11 -0
- package/corpus/core/docs/content/locales/ar-SA/agent-surfaces.mdx +30 -0
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +9 -0
- package/corpus/core/docs/content/locales/de-DE/agent-surfaces.mdx +34 -0
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +10 -0
- package/corpus/core/docs/content/locales/es-ES/agent-surfaces.mdx +34 -0
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +10 -0
- package/corpus/core/docs/content/locales/fr-FR/agent-surfaces.mdx +34 -0
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +11 -0
- package/corpus/core/docs/content/locales/hi-IN/agent-surfaces.mdx +31 -0
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +9 -0
- package/corpus/core/docs/content/locales/ja-JP/agent-surfaces.mdx +32 -0
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +10 -0
- package/corpus/core/docs/content/locales/ko-KR/agent-surfaces.mdx +32 -0
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +9 -0
- package/corpus/core/docs/content/locales/pt-BR/agent-surfaces.mdx +33 -0
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +10 -0
- package/corpus/core/docs/content/locales/zh-CN/agent-surfaces.mdx +29 -0
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-TW/agent-surfaces.mdx +29 -0
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/context-xray/actions/context-evict.ts +11 -0
- package/corpus/core/src/agent/context-xray/actions/context-pin.ts +11 -0
- package/corpus/core/src/agent/context-xray/actions/context-preview-get.ts +134 -0
- package/corpus/core/src/agent/context-xray/actions/context-restore.ts +11 -0
- package/corpus/core/src/agent/context-xray/actions/errors.ts +20 -0
- package/corpus/core/src/agent/context-xray/manifest.ts +106 -7
- package/corpus/core/src/agent/engine/context-directives-transform.ts +5 -1
- package/corpus/core/src/agent/production-agent.ts +100 -38
- package/corpus/core/src/agent/run-loop-with-resume.ts +7 -2
- package/corpus/core/src/client/AgentPanel.tsx +35 -8
- package/corpus/core/src/client/agent-page/AgentContextTab.tsx +501 -0
- package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +370 -0
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +1084 -0
- package/corpus/core/src/client/agent-page/types.ts +18 -0
- package/corpus/core/src/client/agent-page/use-jobs.ts +150 -0
- package/corpus/core/src/client/context-xray/ContextMeter.tsx +22 -5
- package/corpus/core/src/client/context-xray/ContextTreemap.tsx +16 -1
- package/corpus/core/src/client/context-xray/ContextXRayPanel.tsx +168 -3
- package/corpus/core/src/client/context-xray/format.ts +6 -0
- package/corpus/core/src/client/index.ts +6 -0
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +51 -23
- package/corpus/core/src/client/settings/AutomationsSection.tsx +407 -387
- package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +105 -0
- package/corpus/core/src/jobs/actions/manage-recurring-job.ts +85 -0
- package/corpus/core/src/jobs/tools.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +110 -0
- package/corpus/core/src/mcp/build-server.ts +1 -0
- package/corpus/core/src/mcp/connect-route.ts +62 -65
- package/corpus/core/src/server/action-discovery.ts +23 -0
- package/corpus/core/src/server/agent-chat/prompt-resources.ts +174 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +175 -2
- package/corpus/core/src/server/auth.ts +1 -7
- package/corpus/core/src/server/google-oauth.ts +1 -7
- package/corpus/core/src/server/workspace-oauth.ts +31 -0
- package/corpus/core/src/shared/context-xray.ts +84 -1
- package/corpus/core/src/shared/mcp-connect-content.ts +130 -0
- package/corpus/core/src/templates/default/app/routes/_index.tsx +11 -0
- package/corpus/core/src/templates/default/app/routes/agent.tsx +33 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
- package/corpus/core/src/triggers/actions/list-automations.ts +99 -0
- package/corpus/core/src/triggers/actions/manage-automation.ts +78 -0
- package/corpus/templates/analytics/actions/data-source-status.ts +33 -15
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +149 -24
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +17 -0
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +8 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/analytics/app/i18n-data.ts +1 -0
- package/corpus/templates/analytics/app/routes/agent.tsx +5 -0
- package/corpus/templates/analytics/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-analytics-answers-can-use-built-in-first-party-event-data.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-chart-legends-can-filter-to-one-series-or-hide-a-series-on-h.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-gpt-5-6-luna-answers-now-recover-normally-instead-of-showing.md +6 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
- package/corpus/templates/assets/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/assets/app/routes/agent.tsx +5 -0
- package/corpus/templates/assets/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +4 -1
- package/corpus/templates/brain/app/lib/brain.ts +11 -0
- package/corpus/templates/brain/app/root.tsx +15 -1
- package/corpus/templates/brain/app/routes/agent.tsx +11 -0
- package/corpus/templates/brain/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +9 -0
- package/corpus/templates/calendar/actions/list-events.ts +659 -44
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/calendar/app/root.tsx +17 -1
- package/corpus/templates/calendar/app/routes/_app.agent.tsx +25 -0
- package/corpus/templates/calendar/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-13-calendar-inventory-reads-report-source-coverage.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-connector-catalog.ts +8 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +173 -51
- package/corpus/templates/calendar/server/lib/ical-fetcher.ts +13 -2
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/chat/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/chat/app/root.tsx +24 -2
- package/corpus/templates/chat/app/routes/agent.tsx +15 -0
- package/corpus/templates/chat/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +8 -0
- package/corpus/templates/clips/app/i18n/ar-SA.ts +2 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +2 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +2 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +2 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +2 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +2 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +2 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +2 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +2 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +2 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/clips/app/root.tsx +7 -1
- package/corpus/templates/clips/app/routes/_app.agent.tsx +15 -0
- package/corpus/templates/clips/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/content/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +44 -6
- package/corpus/templates/content/app/root.tsx +8 -0
- package/corpus/templates/content/app/routes/_app.agent.tsx +15 -0
- package/corpus/templates/content/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/design/app/i18n-data.ts +20 -0
- package/corpus/templates/design/app/root.tsx +7 -1
- package/corpus/templates/design/app/routes/agent.tsx +15 -0
- package/corpus/templates/design/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +10 -1
- package/corpus/templates/dispatch/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
- package/corpus/templates/dispatch/app/root.tsx +7 -2
- package/corpus/templates/dispatch/app/routes/agent.tsx +18 -0
- package/corpus/templates/dispatch/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +35 -0
- package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
- package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
- package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
- package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
- package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
- package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
- package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
- package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
- package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
- package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
- package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/forms/app/root.tsx +6 -1
- package/corpus/templates/forms/app/routes/_app.agent.tsx +15 -0
- package/corpus/templates/forms/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +14 -2
- package/corpus/templates/macros/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/macros/app/root.tsx +17 -2
- package/corpus/templates/macros/app/routes/agent.tsx +15 -0
- package/corpus/templates/macros/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/mail/AGENTS.md +12 -0
- package/corpus/templates/mail/actions/list-emails.ts +486 -4
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +42 -0
- package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +6 -0
- package/corpus/templates/mail/app/routes/agent.tsx +15 -0
- package/corpus/templates/mail/changelog/2026-07-13-coverage-aware-connected-inbox-inventory.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/mail/server/db/schema.ts +17 -0
- package/corpus/templates/mail/server/lib/google-auth.ts +33 -5
- package/corpus/templates/mail/server/lib/inventory-cursor.ts +406 -0
- package/corpus/templates/mail/server/lib/list-inbox-emails.ts +21 -2
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +4 -9
- package/corpus/templates/mail/server/plugins/db.ts +20 -0
- package/corpus/templates/plan/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/plan/app/root.tsx +8 -0
- package/corpus/templates/plan/app/routes/agent.tsx +15 -0
- package/corpus/templates/plan/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/corpus/templates/slides/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/slides/app/root.tsx +10 -1
- package/corpus/templates/slides/app/routes/agent.tsx +15 -0
- package/corpus/templates/slides/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
- package/dist/agent/context-xray/actions/context-evict.js +5 -2
- package/dist/agent/context-xray/actions/context-evict.js.map +1 -1
- package/dist/agent/context-xray/actions/context-pin.js +5 -2
- package/dist/agent/context-xray/actions/context-pin.js.map +1 -1
- package/dist/agent/context-xray/actions/context-preview-get.d.ts +17 -0
- package/dist/agent/context-xray/actions/context-preview-get.d.ts.map +1 -0
- package/dist/agent/context-xray/actions/context-preview-get.js +94 -0
- package/dist/agent/context-xray/actions/context-preview-get.js.map +1 -0
- package/dist/agent/context-xray/actions/context-restore.js +5 -2
- package/dist/agent/context-xray/actions/context-restore.js.map +1 -1
- package/dist/agent/context-xray/actions/errors.d.ts +3 -0
- package/dist/agent/context-xray/actions/errors.d.ts.map +1 -1
- package/dist/agent/context-xray/actions/errors.js +6 -0
- package/dist/agent/context-xray/actions/errors.js.map +1 -1
- package/dist/agent/context-xray/manifest.d.ts +18 -1
- package/dist/agent/context-xray/manifest.d.ts.map +1 -1
- package/dist/agent/context-xray/manifest.js +68 -6
- package/dist/agent/context-xray/manifest.js.map +1 -1
- package/dist/agent/engine/context-directives-transform.d.ts +2 -0
- package/dist/agent/engine/context-directives-transform.d.ts.map +1 -1
- package/dist/agent/engine/context-directives-transform.js +2 -1
- package/dist/agent/engine/context-directives-transform.js.map +1 -1
- package/dist/agent/production-agent.d.ts +15 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +74 -36
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +6 -3
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +9 -11
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-page/AgentContextTab.d.ts +5 -0
- package/dist/client/agent-page/AgentContextTab.d.ts.map +1 -0
- package/dist/client/agent-page/AgentContextTab.js +202 -0
- package/dist/client/agent-page/AgentContextTab.js.map +1 -0
- package/dist/client/agent-page/AgentJobsTab.d.ts +3 -0
- package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -0
- package/dist/client/agent-page/AgentJobsTab.js +98 -0
- package/dist/client/agent-page/AgentJobsTab.js.map +1 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts +20 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -0
- package/dist/client/agent-page/AgentTabsPage.js +420 -0
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -0
- package/dist/client/agent-page/types.d.ts +17 -0
- package/dist/client/agent-page/types.d.ts.map +1 -0
- package/dist/client/agent-page/types.js +9 -0
- package/dist/client/agent-page/types.js.map +1 -0
- package/dist/client/agent-page/use-jobs.d.ts +61 -0
- package/dist/client/agent-page/use-jobs.d.ts.map +1 -0
- package/dist/client/agent-page/use-jobs.js +71 -0
- package/dist/client/agent-page/use-jobs.js.map +1 -0
- package/dist/client/context-xray/ContextMeter.d.ts +3 -1
- package/dist/client/context-xray/ContextMeter.d.ts.map +1 -1
- package/dist/client/context-xray/ContextMeter.js +14 -5
- package/dist/client/context-xray/ContextMeter.js.map +1 -1
- package/dist/client/context-xray/ContextTreemap.d.ts +3 -2
- package/dist/client/context-xray/ContextTreemap.d.ts.map +1 -1
- package/dist/client/context-xray/ContextTreemap.js +9 -1
- package/dist/client/context-xray/ContextTreemap.js.map +1 -1
- package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -1
- package/dist/client/context-xray/ContextXRayPanel.js +95 -20
- package/dist/client/context-xray/ContextXRayPanel.js.map +1 -1
- package/dist/client/context-xray/format.d.ts.map +1 -1
- package/dist/client/context-xray/format.js +12 -0
- package/dist/client/context-xray/format.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts +9 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +26 -10
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/AutomationsSection.d.ts +9 -0
- package/dist/client/settings/AutomationsSection.d.ts.map +1 -1
- package/dist/client/settings/AutomationsSection.js +128 -147
- package/dist/client/settings/AutomationsSection.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/actions/list-recurring-jobs.d.ts +21 -0
- package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -0
- package/dist/jobs/actions/list-recurring-jobs.js +74 -0
- package/dist/jobs/actions/list-recurring-jobs.js.map +1 -0
- package/dist/jobs/actions/manage-recurring-job.d.ts +18 -0
- package/dist/jobs/actions/manage-recurring-job.d.ts.map +1 -0
- package/dist/jobs/actions/manage-recurring-job.js +68 -0
- package/dist/jobs/actions/manage-recurring-job.js.map +1 -0
- package/dist/jobs/tools.d.ts +13 -0
- package/dist/jobs/tools.d.ts.map +1 -1
- package/dist/jobs/tools.js +1 -1
- package/dist/jobs/tools.js.map +1 -1
- package/dist/localization/default-messages.d.ts +96 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +96 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +1 -0
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +44 -59
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +23 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat/prompt-resources.d.ts +14 -0
- package/dist/server/agent-chat/prompt-resources.d.ts.map +1 -1
- package/dist/server/agent-chat/prompt-resources.js +139 -2
- package/dist/server/agent-chat/prompt-resources.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +1 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +136 -3
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +1 -4
- package/dist/server/auth.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +1 -4
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/workspace-oauth.d.ts +11 -0
- package/dist/server/workspace-oauth.d.ts.map +1 -0
- package/dist/server/workspace-oauth.js +25 -0
- package/dist/server/workspace-oauth.js.map +1 -0
- package/dist/shared/context-xray.d.ts +45 -1
- package/dist/shared/context-xray.d.ts.map +1 -1
- package/dist/shared/context-xray.js +12 -0
- package/dist/shared/context-xray.js.map +1 -1
- package/dist/shared/mcp-connect-content.d.ts +37 -0
- package/dist/shared/mcp-connect-content.d.ts.map +1 -0
- package/dist/shared/mcp-connect-content.js +92 -0
- package/dist/shared/mcp-connect-content.js.map +1 -0
- package/dist/templates/default/app/routes/_index.tsx +11 -0
- package/dist/templates/default/app/routes/agent.tsx +33 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
- package/dist/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
- package/dist/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
- package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
- package/dist/triggers/actions/list-automations.d.ts +24 -0
- package/dist/triggers/actions/list-automations.d.ts.map +1 -0
- package/dist/triggers/actions/list-automations.js +72 -0
- package/dist/triggers/actions/list-automations.js.map +1 -0
- package/dist/triggers/actions/manage-automation.d.ts +18 -0
- package/dist/triggers/actions/manage-automation.d.ts.map +1 -0
- package/dist/triggers/actions/manage-automation.js +60 -0
- package/dist/triggers/actions/manage-automation.js.map +1 -0
- package/docs/content/agent-surfaces.mdx +43 -0
- package/docs/content/external-agents.mdx +11 -0
- package/docs/content/locales/ar-SA/agent-surfaces.mdx +30 -0
- package/docs/content/locales/ar-SA/external-agents.mdx +9 -0
- package/docs/content/locales/de-DE/agent-surfaces.mdx +34 -0
- package/docs/content/locales/de-DE/external-agents.mdx +10 -0
- package/docs/content/locales/es-ES/agent-surfaces.mdx +34 -0
- package/docs/content/locales/es-ES/external-agents.mdx +10 -0
- package/docs/content/locales/fr-FR/agent-surfaces.mdx +34 -0
- package/docs/content/locales/fr-FR/external-agents.mdx +11 -0
- package/docs/content/locales/hi-IN/agent-surfaces.mdx +31 -0
- package/docs/content/locales/hi-IN/external-agents.mdx +9 -0
- package/docs/content/locales/ja-JP/agent-surfaces.mdx +32 -0
- package/docs/content/locales/ja-JP/external-agents.mdx +10 -0
- package/docs/content/locales/ko-KR/agent-surfaces.mdx +32 -0
- package/docs/content/locales/ko-KR/external-agents.mdx +9 -0
- package/docs/content/locales/pt-BR/agent-surfaces.mdx +33 -0
- package/docs/content/locales/pt-BR/external-agents.mdx +10 -0
- package/docs/content/locales/zh-CN/agent-surfaces.mdx +29 -0
- package/docs/content/locales/zh-CN/external-agents.mdx +8 -0
- package/docs/content/locales/zh-TW/agent-surfaces.mdx +29 -0
- package/docs/content/locales/zh-TW/external-agents.mdx +8 -0
- package/package.json +1 -1
- package/src/templates/default/app/routes/_index.tsx +11 -0
- package/src/templates/default/app/routes/agent.tsx +33 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
- package/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
- package/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
- 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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
95
|
+
## Deterministic Mail Reads
|
|
94
96
|
|
|
95
|
-
|
|
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
|
);
|
|
@@ -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
|