@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,7 +1,11 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import { defineAction } from "@agent-native/core";
|
|
2
4
|
import {
|
|
3
5
|
getRequestTimezone,
|
|
4
6
|
getRequestUserEmail,
|
|
7
|
+
signShortLivedToken,
|
|
8
|
+
verifyShortLivedToken,
|
|
5
9
|
} from "@agent-native/core/server";
|
|
6
10
|
import { getUserSetting } from "@agent-native/core/settings";
|
|
7
11
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
@@ -44,6 +48,7 @@ async function fetchICalEventsCached(
|
|
|
44
48
|
cal.color,
|
|
45
49
|
from,
|
|
46
50
|
to,
|
|
51
|
+
{ throwOnError: true },
|
|
47
52
|
);
|
|
48
53
|
icalCache.set(cacheKey, { events, fetchedAt: Date.now() });
|
|
49
54
|
return events;
|
|
@@ -60,14 +65,285 @@ interface ListCalendarEventsArgs {
|
|
|
60
65
|
from?: string;
|
|
61
66
|
to?: string;
|
|
62
67
|
query?: string;
|
|
63
|
-
overlayEmails?: string;
|
|
68
|
+
overlayEmails?: string | string[];
|
|
69
|
+
accountEmails?: string[];
|
|
70
|
+
sources?: CalendarInventorySource[];
|
|
71
|
+
providerPageSize?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface ListCalendarEventsOptions {
|
|
75
|
+
ownedAccounts?: string[];
|
|
76
|
+
range?: CalendarEventRange;
|
|
64
77
|
}
|
|
65
78
|
|
|
79
|
+
type CalendarInventorySource = "google" | "bookings" | "ics" | "overlays";
|
|
80
|
+
|
|
66
81
|
interface CalendarEventsResult {
|
|
67
82
|
events: CalendarEvent[];
|
|
68
83
|
errors: Array<{ email: string; error: string }>;
|
|
69
84
|
googleConnected: boolean;
|
|
70
85
|
range: CalendarEventRange;
|
|
86
|
+
icalErrors: Array<{ id: string; name: string; error: string }>;
|
|
87
|
+
icalSources: Array<{
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
status: "ok" | "error";
|
|
91
|
+
error?: string;
|
|
92
|
+
}>;
|
|
93
|
+
overlaySources: Array<{
|
|
94
|
+
email: string;
|
|
95
|
+
status: "ok" | "error";
|
|
96
|
+
error?: string;
|
|
97
|
+
}>;
|
|
98
|
+
requestedAccounts: string[] | null;
|
|
99
|
+
resolvedAccounts: string[];
|
|
100
|
+
queriedAccounts: string[];
|
|
101
|
+
sources: CalendarInventorySource[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface CalendarInventoryItem {
|
|
105
|
+
key: string;
|
|
106
|
+
id: string;
|
|
107
|
+
title: string;
|
|
108
|
+
start: string;
|
|
109
|
+
end: string;
|
|
110
|
+
allDay: boolean;
|
|
111
|
+
eventType?: CalendarEvent["eventType"];
|
|
112
|
+
status?: CalendarEvent["status"];
|
|
113
|
+
transparency?: CalendarEvent["transparency"];
|
|
114
|
+
source: "google" | "booking" | "ics" | "overlay";
|
|
115
|
+
sourceId?: string;
|
|
116
|
+
accountEmail?: string;
|
|
117
|
+
overlayEmail?: string;
|
|
118
|
+
organizer?: { email?: string; displayName?: string; self?: boolean };
|
|
119
|
+
selfResponseStatus?: CalendarEvent["responseStatus"];
|
|
120
|
+
attendeeCount: number;
|
|
121
|
+
attendeeStatusCounts: Record<string, number>;
|
|
122
|
+
attendees: Array<{
|
|
123
|
+
email?: string;
|
|
124
|
+
displayName?: string;
|
|
125
|
+
responseStatus?: string;
|
|
126
|
+
optional?: boolean;
|
|
127
|
+
self?: boolean;
|
|
128
|
+
organizer?: boolean;
|
|
129
|
+
}>;
|
|
130
|
+
attendeesComplete: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface InventoryCursor {
|
|
134
|
+
owner: string;
|
|
135
|
+
query: string;
|
|
136
|
+
start: string;
|
|
137
|
+
key: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const INVENTORY_VERSION = 1;
|
|
141
|
+
const INVENTORY_CURSOR_PREFIX = "calendar-inventory:";
|
|
142
|
+
const INVENTORY_PAGE_SIZE = 100;
|
|
143
|
+
const INVENTORY_MAX_PAGE_SIZE = 250;
|
|
144
|
+
const INVENTORY_ITEM_BUDGET_BYTES = 12_000;
|
|
145
|
+
const INVENTORY_STRING_LIMIT = 240;
|
|
146
|
+
const INVENTORY_ATTENDEE_PREVIEW_LIMIT = 8;
|
|
147
|
+
|
|
148
|
+
function cap(
|
|
149
|
+
value: string | undefined,
|
|
150
|
+
limit = INVENTORY_STRING_LIMIT,
|
|
151
|
+
): string | undefined {
|
|
152
|
+
if (!value) return undefined;
|
|
153
|
+
return value.length > limit ? `${value.slice(0, limit - 1)}…` : value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sanitizeError(message: unknown, fallback: string) {
|
|
157
|
+
return (
|
|
158
|
+
cap(
|
|
159
|
+
String(message ?? fallback)
|
|
160
|
+
.replace(/\bBearer\s+\S+/gi, "Bearer [redacted]")
|
|
161
|
+
.replace(
|
|
162
|
+
/\b(access_token|refresh_token|id_token|token)=([^\s&]+)/gi,
|
|
163
|
+
"$1=[redacted]",
|
|
164
|
+
),
|
|
165
|
+
) ?? fallback
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function sourceCoverageError(message: string, fallback: string) {
|
|
170
|
+
const bounded = sanitizeError(message, fallback);
|
|
171
|
+
const notConnected = /not connected|reconnect|authentication/i.test(bounded);
|
|
172
|
+
return {
|
|
173
|
+
code: notConnected ? "NOT_CONNECTED" : "SOURCE_READ_FAILED",
|
|
174
|
+
message: bounded,
|
|
175
|
+
retryable: !notConnected,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizedEmails(values: string[] | undefined): string[] {
|
|
180
|
+
return Array.from(
|
|
181
|
+
new Set(
|
|
182
|
+
(values ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean),
|
|
183
|
+
),
|
|
184
|
+
).sort();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function mergeAccountErrors(
|
|
188
|
+
...groups: Array<Array<{ email: string; error: string }>>
|
|
189
|
+
): Array<{ email: string; error: string }> {
|
|
190
|
+
return Array.from(
|
|
191
|
+
new Map(
|
|
192
|
+
groups
|
|
193
|
+
.flat()
|
|
194
|
+
.map((entry) => [
|
|
195
|
+
`${entry.email.trim().toLowerCase()}\0${entry.error}`,
|
|
196
|
+
entry,
|
|
197
|
+
]),
|
|
198
|
+
).values(),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizedOverlayEmails(value: string | string[] | undefined) {
|
|
203
|
+
return normalizedEmails(
|
|
204
|
+
Array.isArray(value)
|
|
205
|
+
? value
|
|
206
|
+
: value?.split(",").map((email) => email.trim()),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function resolveInventorySources(
|
|
211
|
+
sources: CalendarInventorySource[] | undefined,
|
|
212
|
+
): CalendarInventorySource[] {
|
|
213
|
+
return Array.from(
|
|
214
|
+
new Set<CalendarInventorySource>(
|
|
215
|
+
sources ?? ["google", "bookings", "ics", "overlays"],
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function inventoryQueryKey(args: {
|
|
221
|
+
from: string;
|
|
222
|
+
to: string;
|
|
223
|
+
query?: string;
|
|
224
|
+
accountEmails: string[];
|
|
225
|
+
overlayEmails?: string | string[];
|
|
226
|
+
sources: CalendarInventorySource[];
|
|
227
|
+
}): string {
|
|
228
|
+
const canonical = JSON.stringify({
|
|
229
|
+
v: INVENTORY_VERSION,
|
|
230
|
+
from: args.from,
|
|
231
|
+
to: args.to,
|
|
232
|
+
query: args.query?.trim().toLowerCase() || "",
|
|
233
|
+
accountEmails: normalizedEmails(args.accountEmails),
|
|
234
|
+
overlayEmails: normalizedOverlayEmails(args.overlayEmails),
|
|
235
|
+
sources: [...args.sources].sort(),
|
|
236
|
+
});
|
|
237
|
+
return createHash("sha256").update(canonical).digest("base64url");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function encodeInventoryCursor(cursor: InventoryCursor): string {
|
|
241
|
+
const resourceId = `${INVENTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString("base64url")}`;
|
|
242
|
+
return signShortLivedToken({ resourceId, ttlSeconds: 600 });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function decodeInventoryCursor(
|
|
246
|
+
token: string,
|
|
247
|
+
owner: string,
|
|
248
|
+
query: string,
|
|
249
|
+
): InventoryCursor {
|
|
250
|
+
const [payload] = token.split(".", 1);
|
|
251
|
+
if (!payload) throw new Error("Invalid inventory cursor");
|
|
252
|
+
let resourceId: unknown;
|
|
253
|
+
try {
|
|
254
|
+
resourceId = JSON.parse(
|
|
255
|
+
Buffer.from(payload, "base64url").toString("utf8"),
|
|
256
|
+
).resourceId;
|
|
257
|
+
} catch {
|
|
258
|
+
throw new Error("Invalid inventory cursor");
|
|
259
|
+
}
|
|
260
|
+
if (
|
|
261
|
+
typeof resourceId !== "string" ||
|
|
262
|
+
!resourceId.startsWith(INVENTORY_CURSOR_PREFIX)
|
|
263
|
+
)
|
|
264
|
+
throw new Error("Invalid inventory cursor");
|
|
265
|
+
if (!verifyShortLivedToken(token, resourceId).ok)
|
|
266
|
+
throw new Error("Expired or invalid inventory cursor");
|
|
267
|
+
let cursor: InventoryCursor;
|
|
268
|
+
try {
|
|
269
|
+
cursor = JSON.parse(
|
|
270
|
+
Buffer.from(
|
|
271
|
+
resourceId.slice(INVENTORY_CURSOR_PREFIX.length),
|
|
272
|
+
"base64url",
|
|
273
|
+
).toString("utf8"),
|
|
274
|
+
);
|
|
275
|
+
} catch {
|
|
276
|
+
throw new Error("Invalid inventory cursor");
|
|
277
|
+
}
|
|
278
|
+
if (
|
|
279
|
+
cursor.owner !== owner ||
|
|
280
|
+
cursor.query !== query ||
|
|
281
|
+
!cursor.start ||
|
|
282
|
+
!cursor.key
|
|
283
|
+
)
|
|
284
|
+
throw new Error("Inventory cursor does not match this query");
|
|
285
|
+
return cursor;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function compactInventoryEvent(event: CalendarEvent): CalendarInventoryItem {
|
|
289
|
+
const attendees = event.attendees ?? [];
|
|
290
|
+
const attendeeStatusCounts = attendees.reduce<Record<string, number>>(
|
|
291
|
+
(counts, attendee) => {
|
|
292
|
+
const status = attendee.responseStatus ?? "unknown";
|
|
293
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
294
|
+
return counts;
|
|
295
|
+
},
|
|
296
|
+
{},
|
|
297
|
+
);
|
|
298
|
+
const key = [
|
|
299
|
+
event.source,
|
|
300
|
+
event.accountEmail ?? event.overlayEmail ?? "local",
|
|
301
|
+
event.googleEventId ?? event.id,
|
|
302
|
+
event.start,
|
|
303
|
+
].join(":");
|
|
304
|
+
const source = event.overlayEmail
|
|
305
|
+
? "overlay"
|
|
306
|
+
: event.source === "local"
|
|
307
|
+
? "booking"
|
|
308
|
+
: event.source === "ical"
|
|
309
|
+
? "ics"
|
|
310
|
+
: event.source;
|
|
311
|
+
return {
|
|
312
|
+
key,
|
|
313
|
+
id: event.googleEventId ?? event.id,
|
|
314
|
+
title: cap(event.title) ?? "Untitled",
|
|
315
|
+
start: event.start,
|
|
316
|
+
end: event.end,
|
|
317
|
+
allDay: event.allDay,
|
|
318
|
+
eventType: event.eventType,
|
|
319
|
+
status: event.status,
|
|
320
|
+
transparency: event.transparency,
|
|
321
|
+
source,
|
|
322
|
+
sourceId: event.sourceId,
|
|
323
|
+
accountEmail: event.accountEmail,
|
|
324
|
+
overlayEmail: event.overlayEmail,
|
|
325
|
+
organizer: event.organizer
|
|
326
|
+
? {
|
|
327
|
+
email: cap(event.organizer.email, 320) ?? "",
|
|
328
|
+
displayName: cap(event.organizer.displayName),
|
|
329
|
+
self: event.organizer.self,
|
|
330
|
+
}
|
|
331
|
+
: undefined,
|
|
332
|
+
selfResponseStatus: event.responseStatus,
|
|
333
|
+
attendeeCount: attendees.length,
|
|
334
|
+
attendeeStatusCounts,
|
|
335
|
+
attendees: attendees
|
|
336
|
+
.slice(0, INVENTORY_ATTENDEE_PREVIEW_LIMIT)
|
|
337
|
+
.map((attendee) => ({
|
|
338
|
+
email: cap(attendee.email, 320) ?? "",
|
|
339
|
+
displayName: cap(attendee.displayName),
|
|
340
|
+
responseStatus: attendee.responseStatus,
|
|
341
|
+
optional: attendee.optional,
|
|
342
|
+
self: attendee.self,
|
|
343
|
+
organizer: attendee.organizer,
|
|
344
|
+
})),
|
|
345
|
+
attendeesComplete: attendees.length <= INVENTORY_ATTENDEE_PREVIEW_LIMIT,
|
|
346
|
+
};
|
|
71
347
|
}
|
|
72
348
|
|
|
73
349
|
function normalizeTimezone(timezone?: string): string {
|
|
@@ -298,66 +574,167 @@ function shouldShowLocalBookingEvent({
|
|
|
298
574
|
|
|
299
575
|
export async function listCalendarEvents(
|
|
300
576
|
args: ListCalendarEventsArgs = {},
|
|
577
|
+
options: ListCalendarEventsOptions = {},
|
|
301
578
|
): Promise<CalendarEventsResult> {
|
|
302
579
|
const email = getRequestUserEmail();
|
|
303
580
|
if (!email) throw new Error("no authenticated user");
|
|
304
|
-
const range =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
581
|
+
const range =
|
|
582
|
+
options.range ??
|
|
583
|
+
resolveCalendarEventRange({
|
|
584
|
+
from: args.from,
|
|
585
|
+
to: args.to,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const sources = resolveInventorySources(args.sources);
|
|
589
|
+
const includeGoogle = sources.includes("google");
|
|
590
|
+
const includeOverlays = sources.includes("overlays");
|
|
308
591
|
|
|
309
|
-
//
|
|
592
|
+
// Resolve owned accounts before any token refresh or provider call.
|
|
310
593
|
let googleEvents: CalendarEvent[] = [];
|
|
311
594
|
let errors: Array<{ email: string; error: string }> = [];
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
595
|
+
let overlaySources: Array<{
|
|
596
|
+
email: string;
|
|
597
|
+
status: "ok" | "error";
|
|
598
|
+
error?: string;
|
|
599
|
+
}> = [];
|
|
600
|
+
const normalizedRequestedAccounts = normalizedEmails(args.accountEmails);
|
|
601
|
+
const requestedAccounts = args.accountEmails
|
|
602
|
+
? normalizedRequestedAccounts
|
|
603
|
+
: null;
|
|
604
|
+
let resolvedAccounts: string[] = [];
|
|
605
|
+
// Resolve/validate ownership before `isConnected` or token refreshes. A
|
|
606
|
+
// rejected filter is therefore atomic even when every token is expired.
|
|
607
|
+
const [ownedAccounts, connected] = await Promise.all([
|
|
608
|
+
options.ownedAccounts ?? googleCalendar.getOwnedAccountEmails(email),
|
|
609
|
+
googleCalendar.isConnected(email),
|
|
610
|
+
]);
|
|
611
|
+
if (normalizedRequestedAccounts.length > 0) {
|
|
612
|
+
const unowned = normalizedRequestedAccounts.filter(
|
|
613
|
+
(account) =>
|
|
614
|
+
!ownedAccounts.some((owned) => owned.trim().toLowerCase() === account),
|
|
615
|
+
);
|
|
616
|
+
if (unowned.length > 0) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`Google Calendar account not connected for this user: ${unowned.join(", ")}`,
|
|
619
|
+
);
|
|
333
620
|
}
|
|
621
|
+
resolvedAccounts = ownedAccounts.filter((account) =>
|
|
622
|
+
normalizedRequestedAccounts.includes(account.trim().toLowerCase()),
|
|
623
|
+
);
|
|
624
|
+
} else {
|
|
625
|
+
resolvedAccounts = ownedAccounts;
|
|
626
|
+
}
|
|
627
|
+
const requestedOverlayEmails = normalizedOverlayEmails(
|
|
628
|
+
args.overlayEmails,
|
|
629
|
+
).slice(0, 10);
|
|
630
|
+
if (includeOverlays && requestedOverlayEmails.length > 0 && !connected) {
|
|
631
|
+
overlaySources = requestedOverlayEmails.map((overlayEmail) => ({
|
|
632
|
+
email: overlayEmail,
|
|
633
|
+
status: "error",
|
|
634
|
+
error: "Google Calendar is not connected",
|
|
635
|
+
}));
|
|
334
636
|
}
|
|
637
|
+
// Once account ownership is validated, independent providers and local SQL
|
|
638
|
+
// can run together. The slowest source should set latency, not their sum.
|
|
639
|
+
const googleRead =
|
|
640
|
+
connected && includeGoogle
|
|
641
|
+
? googleCalendar.listEvents(range.from, range.to, email, {
|
|
642
|
+
accountEmails: args.accountEmails,
|
|
643
|
+
maxResults: args.providerPageSize,
|
|
644
|
+
})
|
|
645
|
+
: Promise.resolve({ events: [], errors: [] });
|
|
646
|
+
const overlayRead =
|
|
647
|
+
connected && includeOverlays && requestedOverlayEmails.length > 0
|
|
648
|
+
? googleCalendar.listOverlayEvents(
|
|
649
|
+
range.from,
|
|
650
|
+
range.to,
|
|
651
|
+
requestedOverlayEmails,
|
|
652
|
+
email,
|
|
653
|
+
{ accountEmails: args.accountEmails },
|
|
654
|
+
)
|
|
655
|
+
: Promise.resolve({ events: [], errors: [], accountErrors: [] });
|
|
656
|
+
const icalRead = sources.includes("ics")
|
|
657
|
+
? Promise.resolve(getUserSetting(email, "external-calendars")).then(
|
|
658
|
+
async (setting) => {
|
|
659
|
+
const calendars =
|
|
660
|
+
(setting as unknown as ExternalCalendar[] | null) ?? [];
|
|
661
|
+
return {
|
|
662
|
+
calendars,
|
|
663
|
+
results: await Promise.allSettled(
|
|
664
|
+
calendars.map((calendar) =>
|
|
665
|
+
fetchICalEventsCached(calendar, range.from, range.to),
|
|
666
|
+
),
|
|
667
|
+
),
|
|
668
|
+
};
|
|
669
|
+
},
|
|
670
|
+
)
|
|
671
|
+
: Promise.resolve({ calendars: [], results: [] });
|
|
672
|
+
const bookingRead = sources.includes("bookings")
|
|
673
|
+
? listLocalBookingEvents(range.from, range.to)
|
|
674
|
+
: Promise.resolve([]);
|
|
335
675
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
676
|
+
const [googleResult, overlayResult, icalResult, rawBookingEvents] =
|
|
677
|
+
await Promise.all([googleRead, overlayRead, icalRead, bookingRead]);
|
|
678
|
+
googleEvents = [...googleResult.events, ...overlayResult.events];
|
|
679
|
+
errors = mergeAccountErrors(googleResult.errors, overlayResult.accountErrors);
|
|
680
|
+
if (connected && includeOverlays && requestedOverlayEmails.length > 0) {
|
|
681
|
+
overlaySources = requestedOverlayEmails.map((overlayEmail) => {
|
|
682
|
+
const error = overlayResult.errors.find(
|
|
683
|
+
(entry) => entry.email.toLowerCase() === overlayEmail.toLowerCase(),
|
|
684
|
+
);
|
|
685
|
+
return error
|
|
686
|
+
? {
|
|
687
|
+
email: overlayEmail,
|
|
688
|
+
status: "error" as const,
|
|
689
|
+
error: error.error,
|
|
690
|
+
}
|
|
691
|
+
: { email: overlayEmail, status: "ok" as const };
|
|
692
|
+
});
|
|
693
|
+
}
|
|
341
694
|
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
fetchICalEventsCached(cal, range.from, range.to),
|
|
345
|
-
),
|
|
346
|
-
);
|
|
695
|
+
const externalCalendars = icalResult.calendars;
|
|
696
|
+
const icalResults = icalResult.results;
|
|
347
697
|
|
|
348
698
|
const icalEvents: CalendarEvent[] = icalResults.flatMap((r) =>
|
|
349
699
|
r.status === "fulfilled" ? r.value : [],
|
|
350
700
|
);
|
|
701
|
+
const icalErrors = icalResults.flatMap((result, index) =>
|
|
702
|
+
result.status === "rejected"
|
|
703
|
+
? [
|
|
704
|
+
{
|
|
705
|
+
id: externalCalendars[index]!.id,
|
|
706
|
+
name: externalCalendars[index]!.name,
|
|
707
|
+
error:
|
|
708
|
+
result.reason instanceof Error
|
|
709
|
+
? result.reason.message
|
|
710
|
+
: "Unable to load ICS feed",
|
|
711
|
+
},
|
|
712
|
+
]
|
|
713
|
+
: [],
|
|
714
|
+
);
|
|
715
|
+
const icalSources = externalCalendars.map((calendar, index) => {
|
|
716
|
+
const result = icalResults[index]!;
|
|
717
|
+
return result.status === "fulfilled"
|
|
718
|
+
? { id: calendar.id, name: calendar.name, status: "ok" as const }
|
|
719
|
+
: {
|
|
720
|
+
id: calendar.id,
|
|
721
|
+
name: calendar.name,
|
|
722
|
+
status: "error" as const,
|
|
723
|
+
error:
|
|
724
|
+
result.reason instanceof Error
|
|
725
|
+
? result.reason.message
|
|
726
|
+
: "Unable to load ICS feed",
|
|
727
|
+
};
|
|
728
|
+
});
|
|
351
729
|
|
|
352
730
|
const googleEventIds = new Set(
|
|
353
731
|
googleEvents
|
|
354
732
|
.map((event) => event.googleEventId)
|
|
355
733
|
.filter((id): id is string => Boolean(id)),
|
|
356
734
|
);
|
|
357
|
-
const googleReadAuthoritative =
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
).filter((event) =>
|
|
735
|
+
const googleReadAuthoritative =
|
|
736
|
+
includeGoogle && connected && errors.length === 0;
|
|
737
|
+
const bookingEvents = rawBookingEvents.filter((event) =>
|
|
361
738
|
shouldShowLocalBookingEvent({
|
|
362
739
|
event,
|
|
363
740
|
googleEventIds,
|
|
@@ -385,6 +762,16 @@ export async function listCalendarEvents(
|
|
|
385
762
|
errors,
|
|
386
763
|
googleConnected: connected,
|
|
387
764
|
range,
|
|
765
|
+
icalErrors,
|
|
766
|
+
icalSources,
|
|
767
|
+
overlaySources,
|
|
768
|
+
requestedAccounts,
|
|
769
|
+
resolvedAccounts,
|
|
770
|
+
queriedAccounts:
|
|
771
|
+
includeGoogle || (includeOverlays && requestedOverlayEmails.length > 0)
|
|
772
|
+
? resolvedAccounts
|
|
773
|
+
: [],
|
|
774
|
+
sources,
|
|
388
775
|
};
|
|
389
776
|
}
|
|
390
777
|
|
|
@@ -396,16 +783,244 @@ export default defineAction({
|
|
|
396
783
|
to: z.string().optional().describe("End date (ISO string)"),
|
|
397
784
|
query: z
|
|
398
785
|
.string()
|
|
786
|
+
.max(500)
|
|
399
787
|
.optional()
|
|
400
788
|
.describe("Case-insensitive title/attendee/organizer search term"),
|
|
401
789
|
overlayEmails: z
|
|
790
|
+
.union([z.string().max(3_200), z.array(z.string().email()).max(10)])
|
|
791
|
+
.optional()
|
|
792
|
+
.describe(
|
|
793
|
+
"Overlay calendar emails (an array, or legacy comma-separated string)",
|
|
794
|
+
),
|
|
795
|
+
accountEmails: z
|
|
796
|
+
.array(z.string().email())
|
|
797
|
+
.min(1)
|
|
798
|
+
.max(20)
|
|
799
|
+
.optional()
|
|
800
|
+
.describe(
|
|
801
|
+
"Connected Google accounts to read; omitted reads every connected account",
|
|
802
|
+
),
|
|
803
|
+
sources: z
|
|
804
|
+
.array(z.enum(["google", "bookings", "ics", "overlays"]))
|
|
805
|
+
.max(4)
|
|
806
|
+
.optional()
|
|
807
|
+
.describe("Calendar sources to query; omitted reads every source"),
|
|
808
|
+
format: z
|
|
809
|
+
.enum(["legacy", "inventory"])
|
|
810
|
+
.optional()
|
|
811
|
+
.describe("Use inventory for compact, coverage-aware external reads"),
|
|
812
|
+
cursor: z
|
|
402
813
|
.string()
|
|
814
|
+
.max(4096)
|
|
403
815
|
.optional()
|
|
404
|
-
.describe("
|
|
816
|
+
.describe("Opaque cursor from an inventory response"),
|
|
817
|
+
pageSize: z.coerce
|
|
818
|
+
.number()
|
|
819
|
+
.int()
|
|
820
|
+
.min(1)
|
|
821
|
+
.max(INVENTORY_MAX_PAGE_SIZE)
|
|
822
|
+
.optional(),
|
|
405
823
|
}),
|
|
406
824
|
http: { method: "GET" },
|
|
407
|
-
|
|
408
|
-
|
|
825
|
+
readOnly: true,
|
|
826
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
827
|
+
run: async (args, ctx) => {
|
|
828
|
+
const inventory =
|
|
829
|
+
args.format === "inventory" || (ctx?.caller === "mcp" && !args.format);
|
|
830
|
+
const owner = inventory ? getRequestUserEmail() : undefined;
|
|
831
|
+
if (inventory && !owner) throw new Error("no authenticated user");
|
|
832
|
+
|
|
833
|
+
// Reject invalid, expired, owner-bound, and query-bound cursors before any
|
|
834
|
+
// provider call. Omitted account filters require the cheap owned-account
|
|
835
|
+
// lookup to reproduce the exact query key, but token refreshes and calendar
|
|
836
|
+
// reads remain behind this gate.
|
|
837
|
+
let preparedCursor: InventoryCursor | undefined;
|
|
838
|
+
let preparedQuery: string | undefined;
|
|
839
|
+
let preparedRange: CalendarEventRange | undefined;
|
|
840
|
+
let preparedOwnedAccounts: string[] | undefined;
|
|
841
|
+
if (inventory && args.cursor) {
|
|
842
|
+
preparedRange = resolveCalendarEventRange({
|
|
843
|
+
from: args.from,
|
|
844
|
+
to: args.to,
|
|
845
|
+
});
|
|
846
|
+
preparedOwnedAccounts = args.accountEmails
|
|
847
|
+
? undefined
|
|
848
|
+
: await googleCalendar.getOwnedAccountEmails(owner!);
|
|
849
|
+
preparedQuery = inventoryQueryKey({
|
|
850
|
+
from: preparedRange.from,
|
|
851
|
+
to: preparedRange.to,
|
|
852
|
+
query: args.query,
|
|
853
|
+
accountEmails: args.accountEmails ?? preparedOwnedAccounts ?? [],
|
|
854
|
+
overlayEmails: args.overlayEmails,
|
|
855
|
+
sources: resolveInventorySources(args.sources),
|
|
856
|
+
});
|
|
857
|
+
preparedCursor = decodeInventoryCursor(
|
|
858
|
+
args.cursor,
|
|
859
|
+
owner!,
|
|
860
|
+
preparedQuery,
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const result = await listCalendarEvents(
|
|
865
|
+
{
|
|
866
|
+
...args,
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
ownedAccounts: preparedOwnedAccounts,
|
|
870
|
+
range: preparedRange,
|
|
871
|
+
},
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
if (inventory) {
|
|
875
|
+
const query =
|
|
876
|
+
preparedQuery ??
|
|
877
|
+
inventoryQueryKey({
|
|
878
|
+
from: result.range.from,
|
|
879
|
+
to: result.range.to,
|
|
880
|
+
query: args.query,
|
|
881
|
+
accountEmails: result.requestedAccounts ?? result.resolvedAccounts,
|
|
882
|
+
overlayEmails: args.overlayEmails,
|
|
883
|
+
sources: result.sources,
|
|
884
|
+
});
|
|
885
|
+
const compact = result.events.map(compactInventoryEvent);
|
|
886
|
+
// Provider ids are only unique within an account. Prefer the owned Google
|
|
887
|
+
// occurrence to a duplicate local booking; otherwise keep first after a
|
|
888
|
+
// stable source/key sort.
|
|
889
|
+
const unique = Array.from(
|
|
890
|
+
new Map(
|
|
891
|
+
compact
|
|
892
|
+
.sort(
|
|
893
|
+
(a, b) =>
|
|
894
|
+
a.start.localeCompare(b.start) || a.key.localeCompare(b.key),
|
|
895
|
+
)
|
|
896
|
+
.map((item) => [item.key, item]),
|
|
897
|
+
).values(),
|
|
898
|
+
);
|
|
899
|
+
const cursor = preparedCursor;
|
|
900
|
+
const afterCursor = cursor
|
|
901
|
+
? unique.filter(
|
|
902
|
+
(item) =>
|
|
903
|
+
item.start > cursor.start ||
|
|
904
|
+
(item.start === cursor.start && item.key > cursor.key),
|
|
905
|
+
)
|
|
906
|
+
: unique;
|
|
907
|
+
const pageSize = args.pageSize ?? INVENTORY_PAGE_SIZE;
|
|
908
|
+
const items: CalendarInventoryItem[] = [];
|
|
909
|
+
for (const item of afterCursor) {
|
|
910
|
+
if (items.length >= pageSize) break;
|
|
911
|
+
const nextSize = Buffer.byteLength(
|
|
912
|
+
JSON.stringify([...items, item]),
|
|
913
|
+
"utf8",
|
|
914
|
+
);
|
|
915
|
+
if (items.length > 0 && nextSize > INVENTORY_ITEM_BUDGET_BYTES) break;
|
|
916
|
+
items.push(item);
|
|
917
|
+
}
|
|
918
|
+
const last = items[items.length - 1];
|
|
919
|
+
const hasMore = afterCursor.length > items.length;
|
|
920
|
+
const nextCursor =
|
|
921
|
+
hasMore && last
|
|
922
|
+
? encodeInventoryCursor({
|
|
923
|
+
owner: owner!,
|
|
924
|
+
query,
|
|
925
|
+
start: last.start,
|
|
926
|
+
key: last.key,
|
|
927
|
+
})
|
|
928
|
+
: undefined;
|
|
929
|
+
const accounts = result.queriedAccounts.map((accountEmail) => {
|
|
930
|
+
const error = result.errors.find(
|
|
931
|
+
(entry) =>
|
|
932
|
+
entry.email.trim().toLowerCase() ===
|
|
933
|
+
accountEmail.trim().toLowerCase(),
|
|
934
|
+
);
|
|
935
|
+
return {
|
|
936
|
+
accountEmail,
|
|
937
|
+
status: error ? ("error" as const) : ("ok" as const),
|
|
938
|
+
count: compact.filter(
|
|
939
|
+
(item) => item.accountEmail?.trim().toLowerCase() === accountEmail,
|
|
940
|
+
).length,
|
|
941
|
+
exhausted: !error,
|
|
942
|
+
...(error
|
|
943
|
+
? {
|
|
944
|
+
error: {
|
|
945
|
+
code: "PROVIDER_READ_FAILED",
|
|
946
|
+
message: sanitizeError(error.error, "Calendar read failed"),
|
|
947
|
+
retryable: true,
|
|
948
|
+
},
|
|
949
|
+
}
|
|
950
|
+
: {}),
|
|
951
|
+
};
|
|
952
|
+
});
|
|
953
|
+
const sourceCoverage = [
|
|
954
|
+
...(result.sources.includes("google") && !result.googleConnected
|
|
955
|
+
? [
|
|
956
|
+
{
|
|
957
|
+
source: "google" as const,
|
|
958
|
+
id: "owned-accounts",
|
|
959
|
+
status: "error" as const,
|
|
960
|
+
error: {
|
|
961
|
+
code: "NOT_CONNECTED",
|
|
962
|
+
message: "Google Calendar is not connected",
|
|
963
|
+
retryable: false,
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
]
|
|
967
|
+
: []),
|
|
968
|
+
...result.icalSources.map((feed) => ({
|
|
969
|
+
source: "ics" as const,
|
|
970
|
+
id: feed.id,
|
|
971
|
+
status: feed.status,
|
|
972
|
+
...(feed.error
|
|
973
|
+
? {
|
|
974
|
+
error: {
|
|
975
|
+
...sourceCoverageError(feed.error, "ICS read failed"),
|
|
976
|
+
},
|
|
977
|
+
}
|
|
978
|
+
: {}),
|
|
979
|
+
})),
|
|
980
|
+
...result.overlaySources.map((overlay) => ({
|
|
981
|
+
source: "overlay" as const,
|
|
982
|
+
id: overlay.email,
|
|
983
|
+
status: overlay.status,
|
|
984
|
+
...(overlay.error
|
|
985
|
+
? {
|
|
986
|
+
error: {
|
|
987
|
+
...sourceCoverageError(overlay.error, "Overlay read failed"),
|
|
988
|
+
},
|
|
989
|
+
}
|
|
990
|
+
: {}),
|
|
991
|
+
})),
|
|
992
|
+
...(result.sources.includes("bookings")
|
|
993
|
+
? [
|
|
994
|
+
{
|
|
995
|
+
source: "booking" as const,
|
|
996
|
+
id: "bookings",
|
|
997
|
+
status: "ok" as const,
|
|
998
|
+
},
|
|
999
|
+
]
|
|
1000
|
+
: []),
|
|
1001
|
+
];
|
|
1002
|
+
const coverageComplete =
|
|
1003
|
+
accounts.every((account) => account.status === "ok") &&
|
|
1004
|
+
sourceCoverage.every((entry) => entry.status === "ok");
|
|
1005
|
+
return {
|
|
1006
|
+
version: INVENTORY_VERSION,
|
|
1007
|
+
query: {
|
|
1008
|
+
...result.range,
|
|
1009
|
+
...(args.query ? { text: args.query } : {}),
|
|
1010
|
+
sources: result.sources,
|
|
1011
|
+
overlayEmails: normalizedOverlayEmails(args.overlayEmails),
|
|
1012
|
+
},
|
|
1013
|
+
requestedAccounts: result.requestedAccounts,
|
|
1014
|
+
resolvedAccounts: result.resolvedAccounts,
|
|
1015
|
+
queriedAccounts: result.queriedAccounts,
|
|
1016
|
+
accounts,
|
|
1017
|
+
sourceCoverage,
|
|
1018
|
+
coverageComplete,
|
|
1019
|
+
complete: !hasMore && coverageComplete,
|
|
1020
|
+
items,
|
|
1021
|
+
page: { returned: items.length, nextCursor, hasMore },
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
409
1024
|
|
|
410
1025
|
if (result.events.length === 0 && result.errors.length > 0) {
|
|
411
1026
|
throw new Error(
|