@agent-native/core 0.69.0 → 0.70.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/README.md +15 -15
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +109 -0
- package/corpus/core/docs/design/durable-agent-runs.md +217 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +89 -0
- package/corpus/core/src/agent/model-config.ts +0 -2
- package/corpus/core/src/agent/run-loop-with-resume.ts +48 -0
- package/corpus/core/src/changelog/parse.ts +245 -0
- package/corpus/core/src/cli/changelog.ts +199 -0
- package/corpus/core/src/cli/index.ts +19 -0
- package/corpus/core/src/cli/recap.ts +24 -1
- package/corpus/core/src/cli/skills.ts +74 -47
- package/corpus/core/src/client/AgentPanel.tsx +5 -3
- package/corpus/core/src/client/AssistantChat.tsx +8 -0
- package/corpus/core/src/client/CommandMenu.tsx +183 -75
- package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
- package/corpus/core/src/client/analytics.ts +181 -0
- package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
- package/corpus/core/src/client/client-surface.ts +41 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +123 -12
- package/corpus/core/src/client/feedback-context.ts +4 -0
- package/corpus/core/src/client/index.ts +12 -0
- package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
- package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
- package/corpus/core/src/client/sse-event-processor.ts +7 -1
- package/corpus/core/src/mcp/build-server.ts +57 -1
- package/corpus/core/src/mcp/embed-app.ts +197 -7
- package/corpus/core/src/mcp/oauth-route.ts +109 -13
- package/corpus/core/src/server/attribution.ts +211 -0
- package/corpus/core/src/server/auth.ts +6 -0
- package/corpus/core/src/server/better-auth-instance.ts +49 -5
- package/corpus/core/src/server/builder-browser.ts +27 -1
- package/corpus/core/src/server/core-routes-plugin.ts +136 -0
- package/corpus/core/src/server/google-oauth.ts +19 -6
- package/corpus/core/src/server/onboarding-html.ts +35 -0
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/styles/agent-conversation.css +21 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +22 -0
- package/corpus/templates/analytics/AGENTS.md +8 -1
- package/corpus/templates/analytics/CHANGELOG.md +10 -0
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +99 -4
- package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +39 -8
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -3
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
- package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -3
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -215
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -6
- package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
- package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
- package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
- package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
- package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
- package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
- package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-06-23-long-dashboard-and-analysis-names-in-the-sidebar-now-truncat.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-23-open-any-dashboard-chart-full-screen-in-a-modal-from-the-pan.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
- package/corpus/templates/analytics/server/db/index.ts +1 -1
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
- package/corpus/templates/assets/CHANGELOG.md +10 -0
- package/corpus/templates/assets/actions/generate-image.ts +24 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
- package/corpus/templates/assets/app/global.css +17 -1
- package/corpus/templates/assets/app/root.tsx +7 -1
- package/corpus/templates/assets/app/routes/_index.tsx +94 -14
- package/corpus/templates/assets/app/routes/settings.tsx +6 -0
- package/corpus/templates/assets/changelog/2026-06-23-chat-dates-in-the-sidebar-no-longer-wrap-onto-two-lines.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-23-clicking-image-video-or-refine-on-the-start-screen-now-drops.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-23-pick-your-image-generation-model-right-from-the-chat-model-m.md +6 -0
- package/corpus/templates/brain/CHANGELOG.md +10 -0
- package/corpus/templates/brain/app/root.tsx +7 -1
- package/corpus/templates/brain/app/routes/settings.tsx +8 -1
- package/corpus/templates/calendar/CHANGELOG.md +10 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
- package/corpus/templates/calendar/app/root.tsx +7 -1
- package/corpus/templates/chat/CHANGELOG.md +10 -0
- package/corpus/templates/chat/app/root.tsx +7 -1
- package/corpus/templates/clips/CHANGELOG.md +10 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -20
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
- package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
- package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +72 -48
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +7 -11
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
- package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
- package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +35 -17
- package/corpus/templates/clips/app/root.tsx +7 -1
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
- package/corpus/templates/clips/app/routes/record.tsx +78 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
- package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-removed-select-button-from-library-hover-a-clip-to-select-it.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-shared-video-links-now-play-for-anyone-without-signing-in.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-skip-or-cancel-the-recording-countdown-with-buttons-beside-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-the-desktop-camera-bubble-now-looks-sharp-the-instant-it-ope.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-the-desktop-recording-widget-now-grows-downward-only-when-yo.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-the-recording-start-sound-is-now-a-softer-more-modern-chime.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +17 -8
- package/corpus/templates/clips/chrome-extension/src/background.ts +769 -180
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +177 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +466 -329
- package/corpus/templates/clips/chrome-extension/src/overlay.css +377 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.html +12 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +330 -0
- package/corpus/templates/clips/chrome-extension/src/permission.html +227 -0
- package/corpus/templates/clips/chrome-extension/src/permission.ts +105 -0
- package/corpus/templates/clips/chrome-extension/src/popup.html +3 -0
- package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -2
- package/corpus/templates/clips/chrome-extension/src/styles.css +50 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +3 -0
- package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +41 -24
- package/corpus/templates/clips/desktop/src/lib/bubble-webrtc.ts +119 -2
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +21 -2
- package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
- package/corpus/templates/clips/desktop/src/styles.css +119 -2
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +140 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +85 -6
- package/corpus/templates/clips/shared/share-attribution.ts +79 -0
- package/corpus/templates/content/AGENTS.md +20 -3
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/actions/_database-utils.ts +15 -0
- package/corpus/templates/content/actions/_property-utils.ts +371 -4
- package/corpus/templates/content/actions/configure-document-property.ts +35 -1
- package/corpus/templates/content/actions/create-content-database.ts +5 -1
- package/corpus/templates/content/actions/delete-document-property.ts +52 -2
- package/corpus/templates/content/actions/duplicate-document-property.ts +34 -17
- package/corpus/templates/content/actions/reorder-document-property.ts +79 -0
- package/corpus/templates/content/actions/set-document-property.ts +39 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +806 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +226 -68
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +59 -30
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +25 -1
- package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +180 -0
- package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +179 -0
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +244 -0
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +132 -0
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +21 -0
- package/corpus/templates/content/app/root.tsx +7 -1
- package/corpus/templates/content/server/db/schema.ts +30 -0
- package/corpus/templates/content/server/plugins/db.ts +87 -0
- package/corpus/templates/content/shared/api.ts +7 -0
- package/corpus/templates/content/shared/properties.ts +109 -0
- package/corpus/templates/design/CHANGELOG.md +10 -0
- package/corpus/templates/design/actions/generate-design.ts +4 -0
- package/corpus/templates/design/app/root.tsx +7 -1
- package/corpus/templates/design/app/routes/settings.tsx +10 -2
- package/corpus/templates/dispatch/CHANGELOG.md +10 -0
- package/corpus/templates/dispatch/app/root.tsx +7 -1
- package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +10 -0
- package/corpus/templates/forms/CHANGELOG.md +10 -0
- package/corpus/templates/forms/actions/export-responses.ts +6 -0
- package/corpus/templates/forms/actions/list-responses.ts +2 -0
- package/corpus/templates/forms/actions/response-insights.ts +2 -0
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +138 -1
- package/corpus/templates/forms/app/root.tsx +7 -1
- package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-the-page-each-submission-came-from-.md +6 -0
- package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-whether-feedback-came-from-the-web-.md +6 -0
- package/corpus/templates/forms/server/db/schema.ts +7 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +19 -2
- package/corpus/templates/forms/server/lib/integrations.ts +76 -2
- package/corpus/templates/forms/server/plugins/db.ts +20 -0
- package/corpus/templates/forms/shared/types.ts +12 -0
- package/corpus/templates/macros/CHANGELOG.md +10 -0
- package/corpus/templates/macros/app/root.tsx +7 -1
- package/corpus/templates/mail/CHANGELOG.md +10 -0
- package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
- package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
- package/corpus/templates/plan/AGENTS.md +4 -0
- package/corpus/templates/plan/CHANGELOG.md +10 -0
- package/corpus/templates/plan/actions/view-screen.ts +5 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
- package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +3 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
- package/corpus/templates/plan/app/root.tsx +7 -1
- package/corpus/templates/plan/changelog/2026-06-23-large-diagrams-in-a-plan-now-scroll-within-their-block-inste.md +6 -0
- package/corpus/templates/plan/shared/share-attribution.ts +72 -0
- package/corpus/templates/slides/CHANGELOG.md +10 -0
- package/corpus/templates/slides/app/root.tsx +7 -1
- package/corpus/templates/videos/CHANGELOG.md +4 -151
- package/corpus/templates/videos/app/root.tsx +7 -1
- package/dist/action.js +87 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/model-config.d.ts +2 -2
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +0 -2
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +13 -0
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +44 -0
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/changelog/parse.d.ts +70 -0
- package/dist/changelog/parse.d.ts.map +1 -0
- package/dist/changelog/parse.js +200 -0
- package/dist/changelog/parse.js.map +1 -0
- package/dist/cli/changelog.d.ts +4 -0
- package/dist/cli/changelog.d.ts.map +1 -0
- package/dist/cli/changelog.js +166 -0
- package/dist/cli/changelog.js.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +22 -1
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/skills.d.ts +3 -3
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +74 -47
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +5 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +17 -1
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +49 -13
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +43 -6
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +18 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +177 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/changelog/Changelog.d.ts +47 -0
- package/dist/client/changelog/Changelog.d.ts.map +1 -0
- package/dist/client/changelog/Changelog.js +138 -0
- package/dist/client/changelog/Changelog.js.map +1 -0
- package/dist/client/client-surface.d.ts +17 -0
- package/dist/client/client-surface.d.ts.map +1 -0
- package/dist/client/client-surface.js +24 -0
- package/dist/client/client-surface.js.map +1 -0
- package/dist/client/composer/TiptapComposer.d.ts +26 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +27 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/feedback-context.d.ts +3 -0
- package/dist/client/feedback-context.d.ts.map +1 -1
- package/dist/client/feedback-context.js +2 -0
- package/dist/client/feedback-context.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +2 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +58 -10
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +7 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/mcp/build-server.d.ts +21 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +43 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +197 -7
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/oauth-route.d.ts.map +1 -1
- package/dist/mcp/oauth-route.js +95 -12
- package/dist/mcp/oauth-route.js.map +1 -1
- package/dist/server/attribution.d.ts +83 -0
- package/dist/server/attribution.d.ts.map +1 -0
- package/dist/server/attribution.js +178 -0
- package/dist/server/attribution.js.map +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +7 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +9 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +32 -2
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/builder-browser.d.ts +4 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +20 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +101 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +16 -2
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +35 -0
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/styles/agent-conversation.css +21 -0
- package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- package/docs/design/durable-agent-runs.md +217 -0
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
- package/corpus/templates/analytics/app/pages/About.tsx +0 -108
- package/corpus/templates/analytics/app/routes/about.tsx +0 -9
- /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
|
@@ -67,6 +67,8 @@ export function embedApp(
|
|
|
67
67
|
.title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }
|
|
68
68
|
.actions { display: flex; align-items: center; gap: 6px; }
|
|
69
69
|
button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }
|
|
70
|
+
button.primary { min-height: 32px; padding: 0 14px; background: CanvasText; color: Canvas; border-color: CanvasText; }
|
|
71
|
+
button.primary:hover:not(:disabled) { background: color-mix(in srgb, CanvasText 86%, Canvas); }
|
|
70
72
|
button:disabled { opacity: .55; cursor: default; }
|
|
71
73
|
.stage { position: relative; min-height: var(--agent-native-viewport-height); }
|
|
72
74
|
iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }
|
|
@@ -455,6 +457,49 @@ export function embedApp(
|
|
|
455
457
|
};
|
|
456
458
|
}
|
|
457
459
|
|
|
460
|
+
function externalOpenUrlForAppUrl(appUrl) {
|
|
461
|
+
if (openUrl) return openUrl;
|
|
462
|
+
try {
|
|
463
|
+
const url = new URL(appUrl.href);
|
|
464
|
+
url.searchParams.delete("embedded");
|
|
465
|
+
url.searchParams.delete("__an_embed_token");
|
|
466
|
+
url.searchParams.delete(chatBridgeParam);
|
|
467
|
+
return url.toString();
|
|
468
|
+
} catch (_err) {
|
|
469
|
+
return "";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function installExternalOpenControl(appUrl) {
|
|
474
|
+
const href = externalOpenUrlForAppUrl(appUrl);
|
|
475
|
+
if (!href || !document.body || document.getElementById("agent-native-external-open-control")) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const host = document.createElement("div");
|
|
479
|
+
host.id = "agent-native-external-open-control";
|
|
480
|
+
host.style.position = "fixed";
|
|
481
|
+
host.style.top = "10px";
|
|
482
|
+
host.style.right = "10px";
|
|
483
|
+
host.style.zIndex = "2147483647";
|
|
484
|
+
host.style.pointerEvents = "auto";
|
|
485
|
+
const root = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
|
|
486
|
+
root.innerHTML =
|
|
487
|
+
'<style>' +
|
|
488
|
+
':host{all:initial;}' +
|
|
489
|
+
'button{box-sizing:border-box;min-height:30px;border:1px solid color-mix(in srgb, CanvasText 18%, Canvas);border-radius:7px;background:Canvas;color:CanvasText;box-shadow:0 6px 22px color-mix(in srgb, CanvasText 14%, transparent);cursor:pointer;font:700 12px ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:0 10px;white-space:nowrap;}' +
|
|
490
|
+
'button:hover{background:color-mix(in srgb, CanvasText 6%, Canvas);}' +
|
|
491
|
+
'</style>' +
|
|
492
|
+
'<button type="button">Open in new tab</button>';
|
|
493
|
+
const button = root.querySelector("button");
|
|
494
|
+
if (button) {
|
|
495
|
+
button.addEventListener("click", (event) => {
|
|
496
|
+
event.preventDefault();
|
|
497
|
+
void openHostLink({ url: href });
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
document.body.appendChild(host);
|
|
501
|
+
}
|
|
502
|
+
|
|
458
503
|
function installReactRefreshPreambleFallback() {
|
|
459
504
|
window.__vite_plugin_react_preamble_installed__ = true;
|
|
460
505
|
if (typeof window.$RefreshReg$ !== "function") {
|
|
@@ -795,6 +840,7 @@ export function embedApp(
|
|
|
795
840
|
base.href = config.baseHref;
|
|
796
841
|
document.head.prepend(base);
|
|
797
842
|
importChildren(parsed.body, document.body);
|
|
843
|
+
installExternalOpenControl(appUrl);
|
|
798
844
|
for (const script of scripts) {
|
|
799
845
|
if (isRunnableClassicScript(script)) runClassicScript(script);
|
|
800
846
|
}
|
|
@@ -851,9 +897,21 @@ export function embedApp(
|
|
|
851
897
|
});
|
|
852
898
|
if (!response.ok) {
|
|
853
899
|
if (response.status === 401 && isEmbedStartUrl(src)) {
|
|
900
|
+
reportEmbedError(
|
|
901
|
+
"transplant-auth",
|
|
902
|
+
"Embedded app session expired (HTTP 401).",
|
|
903
|
+
"Opaque-origin sandbox could not authenticate the embed.",
|
|
904
|
+
401
|
|
905
|
+
);
|
|
854
906
|
refreshExpiredEmbedSession();
|
|
855
907
|
return;
|
|
856
908
|
}
|
|
909
|
+
reportEmbedError(
|
|
910
|
+
"transplant-http",
|
|
911
|
+
"Embedded app returned HTTP " + response.status + ".",
|
|
912
|
+
undefined,
|
|
913
|
+
response.status
|
|
914
|
+
);
|
|
857
915
|
throw new Error("Embedded app returned HTTP " + response.status + ".");
|
|
858
916
|
}
|
|
859
917
|
const html = await response.text();
|
|
@@ -954,6 +1012,52 @@ export function embedApp(
|
|
|
954
1012
|
}, frameReadyTimeoutMs);
|
|
955
1013
|
}
|
|
956
1014
|
|
|
1015
|
+
// Best-effort telemetry sink so inline-embed failures are visible in
|
|
1016
|
+
// Sentry (handshake timeout, transplant fetch status/CORS, auth, CSP).
|
|
1017
|
+
// The shell is a sandboxed opaque-origin iframe, so it POSTs to the app
|
|
1018
|
+
// origin's CORS-open /_agent-native/mcp/embed-error route, derived from the
|
|
1019
|
+
// known app URLs. Deduped per stage+message so a retry loop can't spam.
|
|
1020
|
+
const reportedEmbedErrorKeys = new Set();
|
|
1021
|
+
let embedErrorReportCount = 0;
|
|
1022
|
+
function embedReportOrigin() {
|
|
1023
|
+
for (const value of [openStartUrl, openUrl, lastFrameSrc]) {
|
|
1024
|
+
if (typeof value === "string" && value) {
|
|
1025
|
+
try { return new URL(value, window.location.href).origin; } catch {}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return "";
|
|
1029
|
+
}
|
|
1030
|
+
function reportEmbedError(stage, message, detail, status) {
|
|
1031
|
+
try {
|
|
1032
|
+
const key = String(stage) + "|" + String(message || "");
|
|
1033
|
+
if (reportedEmbedErrorKeys.has(key) || embedErrorReportCount >= 8) return;
|
|
1034
|
+
reportedEmbedErrorKeys.add(key);
|
|
1035
|
+
embedErrorReportCount += 1;
|
|
1036
|
+
const origin = embedReportOrigin();
|
|
1037
|
+
if (!origin) return;
|
|
1038
|
+
let renderMode = "";
|
|
1039
|
+
try { renderMode = renderModeSource().mode || ""; } catch {}
|
|
1040
|
+
const body = JSON.stringify({
|
|
1041
|
+
stage: String(stage || ""),
|
|
1042
|
+
message: String(message || "").slice(0, 500),
|
|
1043
|
+
detail: detail ? String(detail).slice(0, 1200) : undefined,
|
|
1044
|
+
url: openStartUrl || openUrl || lastFrameSrc || "",
|
|
1045
|
+
status: typeof status === "number" ? status : undefined,
|
|
1046
|
+
host: window.location.hostname || "",
|
|
1047
|
+
renderMode: renderMode,
|
|
1048
|
+
bridge: openAiBridge ? "openai" : app ? "native" : "none",
|
|
1049
|
+
userAgent: navigator.userAgent || ""
|
|
1050
|
+
});
|
|
1051
|
+
fetch(origin + "/_agent-native/mcp/embed-error", {
|
|
1052
|
+
method: "POST",
|
|
1053
|
+
credentials: "omit",
|
|
1054
|
+
keepalive: true,
|
|
1055
|
+
headers: { "Content-Type": "application/json" },
|
|
1056
|
+
body: body
|
|
1057
|
+
}).catch(() => {});
|
|
1058
|
+
} catch {}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
957
1061
|
function renderFrameFallback() {
|
|
958
1062
|
clearFrameReadyTimer();
|
|
959
1063
|
clearFrameLoadTimer();
|
|
@@ -961,12 +1065,13 @@ export function embedApp(
|
|
|
961
1065
|
const fallbackCopy = openUrl
|
|
962
1066
|
? "This chat host did not allow the embedded app frame to load inline. You can still open the same app route through the host or use the URL below."
|
|
963
1067
|
: "This chat host did not allow the embedded app frame to load inline.";
|
|
1068
|
+
reportEmbedError("frame-fallback", fallbackCopy);
|
|
964
1069
|
stage.innerHTML =
|
|
965
1070
|
'<div class="fallback">' +
|
|
966
1071
|
'<div class="fallback-title">Open this app in its own tab</div>' +
|
|
967
1072
|
'<div class="fallback-copy">' + esc(fallbackCopy) + '</div>' +
|
|
968
1073
|
'<div class="fallback-actions">' +
|
|
969
|
-
'<button type="button" data-fallback-open>Open
|
|
1074
|
+
'<button type="button" class="primary" data-fallback-open>Open in new tab</button>' +
|
|
970
1075
|
'<button type="button" data-fallback-retry>Try inline again</button>' +
|
|
971
1076
|
'</div>' +
|
|
972
1077
|
(openUrl ? '<a class="fallback-url" href="' + esc(openUrl) + '" target="_blank" rel="noreferrer">' + esc(openUrl) + '</a>' : '') +
|
|
@@ -987,6 +1092,43 @@ export function embedApp(
|
|
|
987
1092
|
}
|
|
988
1093
|
}
|
|
989
1094
|
|
|
1095
|
+
function renderAppLaunchError(message) {
|
|
1096
|
+
clearFrameReadyTimer();
|
|
1097
|
+
clearFrameLoadTimer();
|
|
1098
|
+
appFrame = null;
|
|
1099
|
+
const fallbackCopy = openUrl
|
|
1100
|
+
? "The inline MCP app could not load in this chat host. You can open the same app route in a new tab or retry the inline load."
|
|
1101
|
+
: "The inline MCP app could not load in this chat host.";
|
|
1102
|
+
reportEmbedError("app-launch-error", message || fallbackCopy, message);
|
|
1103
|
+
const copyHtml = message
|
|
1104
|
+
? '<div>' + esc(message) + '</div><div>' + esc(fallbackCopy) + '</div>'
|
|
1105
|
+
: esc(fallbackCopy);
|
|
1106
|
+
stage.innerHTML =
|
|
1107
|
+
'<div class="fallback">' +
|
|
1108
|
+
'<div class="fallback-title">App did not load</div>' +
|
|
1109
|
+
'<div class="fallback-copy">' + copyHtml + '</div>' +
|
|
1110
|
+
'<div class="fallback-actions">' +
|
|
1111
|
+
'<button type="button" class="primary" data-fallback-open>Open in new tab</button>' +
|
|
1112
|
+
'<button type="button" data-fallback-retry>Retry</button>' +
|
|
1113
|
+
'</div>' +
|
|
1114
|
+
(openUrl ? '<a class="fallback-url" href="' + esc(openUrl) + '" target="_blank" rel="noreferrer">' + esc(openUrl) + '</a>' : '') +
|
|
1115
|
+
'</div>';
|
|
1116
|
+
const fallbackOpen = stage.querySelector("[data-fallback-open]");
|
|
1117
|
+
const fallbackRetry = stage.querySelector("[data-fallback-retry]");
|
|
1118
|
+
if (fallbackOpen) {
|
|
1119
|
+
fallbackOpen.disabled = !openUrl;
|
|
1120
|
+
fallbackOpen.onclick = () => {
|
|
1121
|
+
if (openUrl) void openHostLink({ url: openUrl });
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
if (fallbackRetry) {
|
|
1125
|
+
fallbackRetry.onclick = () => {
|
|
1126
|
+
startedFor = "";
|
|
1127
|
+
void launchEmbed();
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
990
1132
|
async function openFallbackExternal() {
|
|
991
1133
|
if (!openUrl) return;
|
|
992
1134
|
let url = withChatBridgeParam(openUrl);
|
|
@@ -1051,12 +1193,29 @@ export function embedApp(
|
|
|
1051
1193
|
return true;
|
|
1052
1194
|
}
|
|
1053
1195
|
|
|
1196
|
+
// We have connected to a standards-track MCP Apps host (Codex, Cursor,
|
|
1197
|
+
// Claude over the SDK, our own renderer, …) through the postMessage
|
|
1198
|
+
// \`ui/*\` bridge rather than ChatGPT's \`window.openai\` global. These hosts
|
|
1199
|
+
// render the resource in a strict sandboxed iframe (typically
|
|
1200
|
+
// \`sandbox="allow-scripts"\`, opaque origin). Self-navigating that iframe to
|
|
1201
|
+
// the real app origin tears down the host bridge and loses the opaque-origin
|
|
1202
|
+
// auth context, which shows up as a permanent / flashing loading state.
|
|
1203
|
+
// Transplanting the app document into the shell keeps the bridge alive and
|
|
1204
|
+
// works under the opaque origin via embed-token auth, exactly like Claude.
|
|
1205
|
+
function isNativeMcpAppsBridgeHost() {
|
|
1206
|
+
return !!app && !openAiBridge;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1054
1209
|
function shouldTransplantAppDocument() {
|
|
1055
1210
|
const render = renderModeSource();
|
|
1056
1211
|
const mode = render.mode;
|
|
1212
|
+
if (mode === "iframe" || mode === "nested" || render.frame === "iframe" || render.nested) {
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1057
1215
|
return (
|
|
1058
1216
|
isClaudeMcpContentHost() ||
|
|
1059
1217
|
isChatGptSandboxHost() ||
|
|
1218
|
+
isNativeMcpAppsBridgeHost() ||
|
|
1060
1219
|
mode === "transplant" ||
|
|
1061
1220
|
render.frame === "transplant"
|
|
1062
1221
|
);
|
|
@@ -1074,7 +1233,7 @@ export function embedApp(
|
|
|
1074
1233
|
try {
|
|
1075
1234
|
const host = window.location.hostname || "";
|
|
1076
1235
|
const appParam = new URL(window.location.href).searchParams.get("app");
|
|
1077
|
-
return /^[^.]+\\.web-sandbox\\.oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
|
|
1236
|
+
return /^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
|
|
1078
1237
|
} catch {
|
|
1079
1238
|
return false;
|
|
1080
1239
|
}
|
|
@@ -1297,7 +1456,7 @@ export function embedApp(
|
|
|
1297
1456
|
async function launchEmbed() {
|
|
1298
1457
|
const launchUrl = openStartUrl || openUrl;
|
|
1299
1458
|
if (!launchUrl) {
|
|
1300
|
-
|
|
1459
|
+
renderAppLaunchError("Open link was not available.");
|
|
1301
1460
|
return;
|
|
1302
1461
|
}
|
|
1303
1462
|
if (!wantsEmbed()) {
|
|
@@ -1328,7 +1487,7 @@ export function embedApp(
|
|
|
1328
1487
|
const data = parseToolResult(result);
|
|
1329
1488
|
if (typeof data.startUrl !== "string" || !data.startUrl) {
|
|
1330
1489
|
startedFor = "";
|
|
1331
|
-
|
|
1490
|
+
renderAppLaunchError(data.error || "This app can be opened, but not embedded from this MCP server.");
|
|
1332
1491
|
return;
|
|
1333
1492
|
}
|
|
1334
1493
|
const startUrl = withChatBridgeParam(data.startUrl);
|
|
@@ -1345,7 +1504,7 @@ export function embedApp(
|
|
|
1345
1504
|
}
|
|
1346
1505
|
} catch (err) {
|
|
1347
1506
|
startedFor = "";
|
|
1348
|
-
|
|
1507
|
+
renderAppLaunchError(err && err.message ? err.message : "Could not launch embedded app.");
|
|
1349
1508
|
}
|
|
1350
1509
|
}
|
|
1351
1510
|
|
|
@@ -1395,6 +1554,9 @@ export function embedApp(
|
|
|
1395
1554
|
try {
|
|
1396
1555
|
if (new URLSearchParams(window.location.search).get("app") === "chatgpt") return true;
|
|
1397
1556
|
} catch (_err) {}
|
|
1557
|
+
try {
|
|
1558
|
+
if (/^(?:[^.]+\\.)?web-sandbox\\.oaiusercontent\\.com$/i.test(window.location.hostname || "")) return true;
|
|
1559
|
+
} catch (_err) {}
|
|
1398
1560
|
try {
|
|
1399
1561
|
return /chatgpt/i.test(String(navigator.userAgent || ""));
|
|
1400
1562
|
} catch (_err) {
|
|
@@ -1578,8 +1740,13 @@ export function embedApp(
|
|
|
1578
1740
|
async openLink(params) {
|
|
1579
1741
|
const url = typeof (params && params.url) === "string" ? params.url : "";
|
|
1580
1742
|
if (!url) return { isError: true };
|
|
1581
|
-
|
|
1582
|
-
|
|
1743
|
+
try {
|
|
1744
|
+
return await rpcRequest("ui/open-link", { url }, wrapperRequestTimeoutMs);
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
console.warn("[agent-native] MCP host open-link request failed", err);
|
|
1747
|
+
}
|
|
1748
|
+
const opened = window.open(url, "_blank", "noopener,noreferrer");
|
|
1749
|
+
return opened ? { ok: true } : { isError: true };
|
|
1583
1750
|
},
|
|
1584
1751
|
async requestDisplayMode(params) {
|
|
1585
1752
|
return await rpcRequest("ui/request-display-mode", objectValue(params));
|
|
@@ -1615,6 +1782,7 @@ export function embedApp(
|
|
|
1615
1782
|
return;
|
|
1616
1783
|
}
|
|
1617
1784
|
if (
|
|
1785
|
+
message.method === "ui/notifications/host-context-changed" ||
|
|
1618
1786
|
message.method === "ui/notifications/host-context" ||
|
|
1619
1787
|
message.method === "ui/notifications/context"
|
|
1620
1788
|
) {
|
|
@@ -1695,8 +1863,30 @@ export function embedApp(
|
|
|
1695
1863
|
}
|
|
1696
1864
|
} catch (err) {
|
|
1697
1865
|
console.error("[agent-native] MCP app shell failed", err);
|
|
1866
|
+
reportEmbedError(
|
|
1867
|
+
"bridge-init",
|
|
1868
|
+
err && err.message ? err.message : "Could not initialize app.",
|
|
1869
|
+
err && err.stack ? String(err.stack) : undefined
|
|
1870
|
+
);
|
|
1698
1871
|
setMessage(err && err.message ? err.message : "Could not initialize app.");
|
|
1699
1872
|
}
|
|
1873
|
+
|
|
1874
|
+
window.addEventListener("error", (event) => {
|
|
1875
|
+
const err = event && event.error;
|
|
1876
|
+
reportEmbedError(
|
|
1877
|
+
"window-error",
|
|
1878
|
+
(err && err.message) || (event && event.message) || "Uncaught error in MCP app embed.",
|
|
1879
|
+
err && err.stack ? String(err.stack) : undefined
|
|
1880
|
+
);
|
|
1881
|
+
});
|
|
1882
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
1883
|
+
const reason = event && event.reason;
|
|
1884
|
+
reportEmbedError(
|
|
1885
|
+
"unhandled-rejection",
|
|
1886
|
+
(reason && reason.message) || String(reason || "Unhandled promise rejection in MCP app embed."),
|
|
1887
|
+
reason && reason.stack ? String(reason.stack) : undefined
|
|
1888
|
+
);
|
|
1889
|
+
});
|
|
1700
1890
|
})();
|
|
1701
1891
|
</script>
|
|
1702
1892
|
</body>
|
|
@@ -423,15 +423,23 @@ function redirectWithOAuthError(params: {
|
|
|
423
423
|
return redirect(url.toString());
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
function
|
|
426
|
+
function buildCodeRedirectUrl(params: {
|
|
427
427
|
redirectUri: string;
|
|
428
428
|
code: string;
|
|
429
429
|
state?: string;
|
|
430
|
-
}):
|
|
430
|
+
}): string {
|
|
431
431
|
const url = new URL(params.redirectUri);
|
|
432
432
|
url.searchParams.set("code", params.code);
|
|
433
433
|
if (params.state) url.searchParams.set("state", params.state);
|
|
434
|
-
return
|
|
434
|
+
return url.toString();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function redirectWithCode(params: {
|
|
438
|
+
redirectUri: string;
|
|
439
|
+
code: string;
|
|
440
|
+
state?: string;
|
|
441
|
+
}): Response {
|
|
442
|
+
return redirect(buildCodeRedirectUrl(params));
|
|
435
443
|
}
|
|
436
444
|
|
|
437
445
|
function codeChallengeForVerifier(verifier: string): string {
|
|
@@ -530,6 +538,19 @@ function isValidCodeVerifier(value: unknown): value is string {
|
|
|
530
538
|
);
|
|
531
539
|
}
|
|
532
540
|
|
|
541
|
+
// Shared styling for the browser-facing OAuth pages (consent + post-authorize
|
|
542
|
+
// confirmation) so they read as one coherent dark surface.
|
|
543
|
+
const OAUTH_PAGE_BASE_STYLE = `
|
|
544
|
+
:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #09090b; color: #f4f4f5; }
|
|
545
|
+
body { min-height: 100vh; display: grid; place-items: center; margin: 0; padding: 24px; }
|
|
546
|
+
main { width: min(520px, 100%); border: 1px solid #27272a; border-radius: 8px; background: #111113; padding: 24px; box-shadow: 0 24px 80px rgba(0,0,0,.35); }
|
|
547
|
+
h1 { font-size: 22px; line-height: 1.2; margin: 0 0 10px; }
|
|
548
|
+
p { color: #a1a1aa; line-height: 1.5; margin: 0 0 18px; }
|
|
549
|
+
.actions { display: flex; gap: 10px; justify-content: flex-end; }
|
|
550
|
+
button, .btn { border: 0; border-radius: 6px; padding: 10px 14px; font: inherit; font-weight: 650; cursor: pointer; text-decoration: none; display: inline-block; }
|
|
551
|
+
.primary { background: #f4f4f5; color: #09090b; }
|
|
552
|
+
.secondary { background: #27272a; color: #f4f4f5; }`;
|
|
553
|
+
|
|
533
554
|
function renderConsentPage(params: {
|
|
534
555
|
appName: string;
|
|
535
556
|
email: string;
|
|
@@ -552,18 +573,9 @@ function renderConsentPage(params: {
|
|
|
552
573
|
<meta charset="utf-8">
|
|
553
574
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
554
575
|
<title>Authorize ${escapeHtml(params.appName)}</title>
|
|
555
|
-
<style
|
|
556
|
-
:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #09090b; color: #f4f4f5; }
|
|
557
|
-
body { min-height: 100vh; display: grid; place-items: center; margin: 0; padding: 24px; }
|
|
558
|
-
main { width: min(520px, 100%); border: 1px solid #27272a; border-radius: 8px; background: #111113; padding: 24px; box-shadow: 0 24px 80px rgba(0,0,0,.35); }
|
|
559
|
-
h1 { font-size: 22px; line-height: 1.2; margin: 0 0 10px; }
|
|
560
|
-
p { color: #a1a1aa; line-height: 1.5; margin: 0 0 18px; }
|
|
576
|
+
<style>${OAUTH_PAGE_BASE_STYLE}
|
|
561
577
|
ul { margin: 0 0 22px; padding-left: 22px; color: #d4d4d8; }
|
|
562
578
|
code { color: #67e8f9; }
|
|
563
|
-
.actions { display: flex; gap: 10px; justify-content: flex-end; }
|
|
564
|
-
button { border: 0; border-radius: 6px; padding: 10px 14px; font-weight: 650; cursor: pointer; }
|
|
565
|
-
.primary { background: #f4f4f5; color: #09090b; }
|
|
566
|
-
.secondary { background: #27272a; color: #f4f4f5; }
|
|
567
579
|
</style>
|
|
568
580
|
</head>
|
|
569
581
|
<body>
|
|
@@ -583,6 +595,61 @@ function renderConsentPage(params: {
|
|
|
583
595
|
</html>`;
|
|
584
596
|
}
|
|
585
597
|
|
|
598
|
+
// Shown after the user approves a native/desktop client (cursor://, vscode://, …)
|
|
599
|
+
// whose redirect is a private-use scheme. A bare 302 to a custom scheme hands the
|
|
600
|
+
// code to the OS app but leaves the browser tab dangling on a blank/error page, so
|
|
601
|
+
// we render a friendly confirmation that also re-fires the deep link (so the client
|
|
602
|
+
// still receives the code) and tells the user they can return to their agent.
|
|
603
|
+
function renderAuthorizedPage(params: {
|
|
604
|
+
appName: string;
|
|
605
|
+
clientName: string | null;
|
|
606
|
+
redirectUrl: string;
|
|
607
|
+
}): string {
|
|
608
|
+
const friendlyClient = params.clientName?.trim() || null;
|
|
609
|
+
const returnTarget = friendlyClient ?? "your agent";
|
|
610
|
+
const openLabel = friendlyClient
|
|
611
|
+
? `Open ${escapeHtml(friendlyClient)}`
|
|
612
|
+
: "Return to your agent";
|
|
613
|
+
return `<!doctype html>
|
|
614
|
+
<html lang="en">
|
|
615
|
+
<head>
|
|
616
|
+
<meta charset="utf-8">
|
|
617
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
618
|
+
<title>Authorized</title>
|
|
619
|
+
<style>${OAUTH_PAGE_BASE_STYLE}
|
|
620
|
+
main.success { text-align: center; }
|
|
621
|
+
.check { width: 56px; height: 56px; margin: 4px auto 16px; display: grid; place-items: center; border-radius: 999px; background: rgba(34,197,94,.12); }
|
|
622
|
+
.check svg { width: 30px; height: 30px; }
|
|
623
|
+
.success .actions { justify-content: center; margin-top: 4px; }
|
|
624
|
+
.hint { color: #71717a; font-size: 13px; margin: 18px 0 0; }
|
|
625
|
+
</style>
|
|
626
|
+
</head>
|
|
627
|
+
<body>
|
|
628
|
+
<main class="success">
|
|
629
|
+
<div class="check">
|
|
630
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
631
|
+
<path d="M20 6 9 17l-5-5"></path>
|
|
632
|
+
</svg>
|
|
633
|
+
</div>
|
|
634
|
+
<h1>You're all set</h1>
|
|
635
|
+
<p>${escapeHtml(params.appName)} has authorized ${escapeHtml(returnTarget)}. You can return to ${escapeHtml(returnTarget)} to continue.</p>
|
|
636
|
+
<div class="actions">
|
|
637
|
+
<a class="btn primary" id="return-link" href="${escapeHtml(params.redirectUrl)}">${openLabel}</a>
|
|
638
|
+
</div>
|
|
639
|
+
<p class="hint">You can close this tab.</p>
|
|
640
|
+
</main>
|
|
641
|
+
<script>
|
|
642
|
+
(function () {
|
|
643
|
+
var link = document.getElementById("return-link");
|
|
644
|
+
if (link && link.href) {
|
|
645
|
+
try { window.location.href = link.href; } catch (e) {}
|
|
646
|
+
}
|
|
647
|
+
})();
|
|
648
|
+
</script>
|
|
649
|
+
</body>
|
|
650
|
+
</html>`;
|
|
651
|
+
}
|
|
652
|
+
|
|
586
653
|
async function resolveOrgDomain(
|
|
587
654
|
orgId: string | undefined,
|
|
588
655
|
): Promise<string | undefined> {
|
|
@@ -744,6 +811,35 @@ async function handleAuthorize(
|
|
|
744
811
|
scope,
|
|
745
812
|
resource,
|
|
746
813
|
});
|
|
814
|
+
|
|
815
|
+
// Native/desktop clients register a private-use scheme (cursor://, vscode://, …).
|
|
816
|
+
// A 302 to that scheme opens the app but leaves the browser tab dangling, so we
|
|
817
|
+
// render a friendly confirmation page that re-fires the deep link instead. For
|
|
818
|
+
// https/loopback callbacks the client (or its local server) renders its own page,
|
|
819
|
+
// so keep the standard redirect there.
|
|
820
|
+
let isDeepLinkRedirect = false;
|
|
821
|
+
try {
|
|
822
|
+
const protocol = new URL(redirectUri).protocol;
|
|
823
|
+
isDeepLinkRedirect =
|
|
824
|
+
protocol !== "http:" &&
|
|
825
|
+
protocol !== "https:" &&
|
|
826
|
+
isPrivateUseRedirectScheme(protocol);
|
|
827
|
+
} catch {
|
|
828
|
+
isDeepLinkRedirect = false;
|
|
829
|
+
}
|
|
830
|
+
if (isDeepLinkRedirect) {
|
|
831
|
+
return html(
|
|
832
|
+
renderAuthorizedPage({
|
|
833
|
+
appName: options.appName || options.appId || "Agent Native",
|
|
834
|
+
clientName: client.clientName ?? null,
|
|
835
|
+
redirectUrl: buildCodeRedirectUrl({
|
|
836
|
+
redirectUri,
|
|
837
|
+
state,
|
|
838
|
+
code: code.code,
|
|
839
|
+
}),
|
|
840
|
+
}),
|
|
841
|
+
);
|
|
842
|
+
}
|
|
747
843
|
return redirectWithCode({ redirectUri, state, code: code.code });
|
|
748
844
|
}
|
|
749
845
|
|