@agent-native/core 0.169.1 → 0.171.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-native.eject.json +4 -1
- package/bin/agent-native.js +23 -5
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +28 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +25 -1
- package/corpus/templates/analytics/app/routes/$.tsx +10 -0
- package/corpus/templates/analytics/app/routes.ts +3 -1
- package/corpus/templates/analytics/changelog/2026-08-22-fixed-the-agent-native-templates-first-party-dashboard-panel.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-org-admin-panels-no-longer-report-a-database-error-as-a-per.md +9 -0
- package/corpus/templates/analytics/changelog/2026-08-22-slack-requests-report-invalid-inputs.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-unknown-analytics-links-now-return-a-real-not-found-status.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +2 -1
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +2 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +29 -7
- package/corpus/templates/analytics/server/handlers/slack.ts +59 -5
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +132 -2
- package/corpus/templates/analytics/server/lib/slack.ts +4 -2
- package/corpus/templates/analytics/vitest.config.ts +6 -1
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/assets/app/routes/library.tsx +5 -3
- package/corpus/templates/assets/app/routes.ts +3 -1
- package/corpus/templates/assets/changelog/2026-08-22-library-page-loads-again-instead-of-crashing-on-open.md +6 -0
- package/corpus/templates/assets/vitest.config.ts +6 -1
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/brain/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/brain/app/routes/sources.tsx +8 -3
- package/corpus/templates/brain/changelog/2026-08-22-tuning-or-adding-a-source-no-longer-leaves-the-sources-page.md +6 -0
- package/corpus/templates/brain/vitest.config.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/calendar/actions/list-booking-links.ts +83 -4
- package/corpus/templates/calendar/actions/update-booking-link.ts +4 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +1 -1
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +103 -44
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +28 -24
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +9 -2
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +47 -58
- package/corpus/templates/calendar/app/hooks/use-booking-links.ts +5 -1
- package/corpus/templates/calendar/app/lib/event-layout.ts +52 -26
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +126 -98
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +1 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +88 -81
- package/corpus/templates/calendar/app/routes.ts +3 -1
- package/corpus/templates/calendar/changelog/2026-08-22-booking-link-visibility-respects-shared-access.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-clicking-an-out-of-office-event-s-marker-no-longer-creates-a.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-event-detail-views-now-show-your-calendar-timezone.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-google-calendar-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-overlapping-events-now-show-each-others-boundaries.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-public-booking-pages-now-offer-time-slots-from-the-host-s-we.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +5 -7
- package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -2
- package/corpus/templates/calendar/server/lib/google-calendar.ts +139 -14
- package/corpus/templates/calendar/server/onboarding.ts +85 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/shared/api.ts +9 -1
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/chat/app/routes.ts +3 -1
- package/corpus/templates/chat/vitest.config.ts +8 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/clips/actions/get-meeting.ts +9 -4
- package/corpus/templates/clips/actions/list-recordings.ts +16 -3
- package/corpus/templates/clips/actions/search-meetings.ts +1 -0
- package/corpus/templates/clips/actions/view-screen.ts +8 -1
- package/corpus/templates/clips/app/components/meetings/meeting-history-row.tsx +32 -1
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +29 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +33 -4
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +17 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +17 -7
- package/corpus/templates/clips/changelog/2026-08-21-camera-bubble-black-circle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-screen-recordings-respect-the-microphone-toggle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-the-recordings-library-no-longer-fails-right-after-server-start.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-23-the-agent-can-see-your-library-right-after-server-start.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +1 -2
- package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +39 -7
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +21 -25
- package/corpus/templates/clips/desktop/src/app.tsx +53 -24
- package/corpus/templates/clips/desktop/src/lib/bubble-playback.ts +68 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +3 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +72 -14
- package/corpus/templates/clips/desktop/src/lib/updater.ts +8 -3
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +162 -20
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -6
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +150 -13
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +12 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +28 -3
- package/corpus/templates/clips/server/lib/emails.ts +17 -33
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +140 -15
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +12 -14
- package/corpus/templates/content/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +18 -2
- package/corpus/templates/content/app/hooks/use-local-storage.ts +38 -22
- package/corpus/templates/content/app/i18n-data.ts +25 -0
- package/corpus/templates/content/app/routes.ts +3 -1
- package/corpus/templates/content/changelog/2026-08-22-code-blocks-inserted-from-the-slash-menu-no-longer-get-stuck.md +6 -0
- package/corpus/templates/content/changelog/2026-08-22-the-sidebar-page-tree-no-longer-flashes-empty-after-creating-a-page.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-08-21-content-mermaid-rendering-shape.md +233 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +9 -2
- package/corpus/templates/content/vitest.config.ts +7 -1
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1383 -88
- package/corpus/templates/design/actions/generate-design.ts +98 -47
- package/corpus/templates/design/actions/get-design-system.ts +6 -1
- package/corpus/templates/design/actions/refresh-design-system-with-builder.ts +146 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +70 -26
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +284 -136
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2057 -141
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +6 -2
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +4 -4
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +10 -1
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +19 -2
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -2
- package/corpus/templates/design/app/components/layout/Header.tsx +15 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -4
- package/corpus/templates/design/app/i18n-data.ts +1 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +12 -0
- package/corpus/templates/design/app/lib/design-system-data.ts +68 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +77 -35
- package/corpus/templates/design/app/pages/DesignSystems.tsx +136 -42
- package/corpus/templates/design/app/pages/Index.tsx +2 -0
- package/corpus/templates/design/app/pages/Templates.tsx +0 -10
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +10 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +50 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +10 -8
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +18 -7
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +4 -90
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +2 -1
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +4 -3
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +12 -2
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +9 -2
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +11 -1
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +11 -19
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +12 -15
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -59
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +3 -3
- package/corpus/templates/design/app/routes.ts +3 -1
- package/corpus/templates/design/changelog/2026-08-20-canvas-edits-patch-the-frame-instead-of-rebuilding-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-builder-design-system-imports-reconcile-after-indexing.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-draw-mode-needs-shift-y.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-eyedropper-shortcut.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-figma-style-alignment-guides-and-snapping.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-group-transforms-keep-the-selection.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-inspector-fields-keep-focus-on-enter.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-alt-keeps-the-incoming-handle.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-on-the-board-creates-one-vector.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-a-multi-selection-from-the-group-handles.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-resizes-text-with-the-object.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-selecting-another-object-keeps-the-scale-tool-armed.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-the-shape-tool-remembers-the-shape-you-picked.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-vectors-drawn-in-a-frame-land-where-you-drew-them.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-multi-screen-generation-no-longer-loses-a-saved-screens-layo.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-rename-and-search-fields-are-labeled-for-screen-readers.md +6 -0
- package/corpus/templates/design/package.json +2 -0
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +311 -0
- package/corpus/templates/design/shared/canvas-math.ts +612 -206
- package/corpus/templates/design/shared/pen-path.ts +37 -0
- package/corpus/templates/design/vitest.config.ts +7 -1
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/dispatch/actions/list-workspace-connections.ts +34 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +55 -21
- package/corpus/templates/dispatch/app/routes.ts +3 -1
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/factory/.env.example +2 -7
- package/corpus/templates/factory/AGENTS.md +3 -0
- package/corpus/templates/factory/README.md +5 -3
- package/corpus/templates/factory/actions/create-factory.ts +16 -3
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +2 -0
- package/corpus/templates/factory/actions/get-triage-item.ts +2 -0
- package/corpus/templates/factory/actions/list-factory-audit.ts +116 -36
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
- package/corpus/templates/factory/actions/list-triage-items.ts +22 -19
- package/corpus/templates/factory/actions/run-factory-automation.ts +7 -3
- package/corpus/templates/factory/actions/save-factory-automation.ts +5 -2
- package/corpus/templates/factory/actions/save-triage-config.ts +2 -8
- package/corpus/templates/factory/actions/start-builder-for-item.ts +21 -0
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +229 -297
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +397 -247
- package/corpus/templates/factory/app/components/factory/FactorySourceSettingsGroup.tsx +33 -0
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +202 -162
- package/corpus/templates/factory/app/i18n/en-US.ts +32 -4
- package/corpus/templates/factory/app/routes/factory.tsx +84 -11
- package/corpus/templates/factory/app/routes.ts +3 -1
- package/corpus/templates/factory/changelog/2026-08-21-activity-runs-use-automation-display-names.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-create-and-settings-source-cards.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-keep-automations-visible-while-running.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-nested-factory-automations-can-poll.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-settings-sticky-unsaved-bar.md +6 -0
- package/corpus/templates/factory/server/connectors/credentials.ts +10 -5
- package/corpus/templates/factory/server/lib/factory-audit-report.ts +412 -0
- package/corpus/templates/factory/server/lib/factory-config-reconcile.ts +321 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +25 -2
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +13 -4
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +148 -4
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +4 -6
- package/corpus/templates/factory/vitest.config.ts +17 -2
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/forms/app/routes/_app.settings.tsx +4 -1
- package/corpus/templates/forms/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +11 -1
- package/corpus/templates/forms/vitest.config.ts +1 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/macros/app/routes.ts +3 -1
- package/corpus/templates/macros/vitest.config.ts +6 -1
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/mail/actions/manage-gmail-filters.ts +10 -2
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -37
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +14 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +66 -36
- package/corpus/templates/mail/app/hooks/use-google-auth.ts +2 -0
- package/corpus/templates/mail/app/lib/google-oauth-setup.ts +17 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +69 -9
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/routes/$view.$threadId.tsx +23 -1
- package/corpus/templates/mail/app/routes/$view.tsx +47 -1
- package/corpus/templates/mail/app/routes.ts +3 -1
- package/corpus/templates/mail/changelog/2026-08-22-gmail-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-gmail-filters-now-shows-the-real-error.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-unknown-mail-urls-now-show-the-404-page.md +6 -0
- package/corpus/templates/mail/server/handlers/google-auth.ts +11 -2
- package/corpus/templates/mail/server/lib/google-auth.ts +92 -12
- package/corpus/templates/mail/server/onboarding.ts +66 -18
- package/corpus/templates/mail/vitest.config.ts +6 -1
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/plan/app/routes/settings.tsx +4 -1
- package/corpus/templates/plan/app/routes.ts +3 -1
- package/corpus/templates/plan/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/plan/netlify.toml +1 -1
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/slides/actions/patch-deck.ts +79 -10
- package/corpus/templates/slides/actions/update-slide.ts +11 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +14 -4
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -52
- package/corpus/templates/slides/app/components/editor/GoogleDocImportHint.tsx +8 -53
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +9 -75
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +8 -60
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +10 -2
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +19 -7
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +49 -36
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/slides/app/routes.ts +3 -1
- package/corpus/templates/slides/changelog/2026-08-22-already-uploaded-chat-images-are-no-longer-dropped.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-google-drive-and-slides-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-multi-slide-repairs-now-report-slides-that-still-overflow.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-update-slide-now-reports-which-batched-edit-was-skipped.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +52 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +29 -3
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +66 -16
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +13 -2
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/slides/tsconfig.json +5 -0
- package/corpus/templates/slides/vitest.config.ts +6 -1
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/tasks/app/routes/settings.tsx +4 -1
- package/corpus/templates/tasks/app/routes.ts +3 -1
- package/corpus/templates/tasks/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/tasks/vitest.config.ts +7 -1
- package/dist/action.d.ts +91 -0
- package/dist/action.js +50 -0
- package/dist/agent/abort-reasons.d.ts +38 -0
- package/dist/agent/abort-reasons.js +49 -0
- package/dist/agent/engine/ai-sdk-engine.js +8 -5
- package/dist/agent/engine/anthropic-engine.js +7 -5
- package/dist/agent/engine/first-event-timeout.d.ts +42 -17
- package/dist/agent/engine/first-event-timeout.js +67 -25
- package/dist/agent/production-agent.d.ts +23 -0
- package/dist/agent/production-agent.js +107 -102
- package/dist/agent/run-loop-with-resume.d.ts +2 -28
- package/dist/agent/run-loop-with-resume.js +25 -40
- package/dist/agent/run-manager.js +5 -4
- package/dist/agent/run-store.d.ts +55 -0
- package/dist/agent/run-store.js +124 -1
- package/dist/agent/tool-search.js +2 -1
- package/dist/agent/types.d.ts +9 -4
- package/dist/app-config/auth.d.ts +1 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/configuration-error.d.ts +11 -0
- package/dist/app-config/configuration-error.js +14 -0
- package/dist/app-config/index.d.ts +1 -0
- package/dist/app-config/index.js +1 -0
- package/dist/app-config/integrations.d.ts +1 -0
- package/dist/app-config/integrations.js +10 -0
- package/dist/app-config/plugins.d.ts +34 -0
- package/dist/app-config/plugins.js +39 -0
- package/dist/app-config/run-lifecycle-invariants.d.ts +18 -9
- package/dist/app-config/run-lifecycle-invariants.js +18 -25
- package/dist/app-config/schema.d.ts +17 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/chat-threads/schema-migrations.js +20 -0
- package/dist/chat-threads/store.js +21 -4
- package/dist/cli/native-dependencies.d.ts +4 -0
- package/dist/cli/native-dependencies.js +15 -0
- package/dist/client/EnvironmentBadge.d.ts +1 -1
- package/dist/client/EnvironmentBadge.js +40 -5
- package/dist/client/MultiTabAssistantChat.d.ts +1 -1
- package/dist/client/MultiTabAssistantChat.js +35 -14
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/agent-page/use-jobs.d.ts +2 -1
- package/dist/client/agent-page/use-jobs.js +3 -5
- package/dist/client/agent-sidebar-state.js +3 -1
- package/dist/client/blocks/library/code-tabs.js +5 -0
- package/dist/client/blocks/library/code.js +1 -0
- package/dist/client/chat-first/apps-rail.js +2 -2
- package/dist/client/chat-model-groups.d.ts +12 -0
- package/dist/client/chat-model-groups.js +3 -2
- package/dist/client/integrations/GoogleProductLogo.d.ts +5 -0
- package/dist/client/integrations/GoogleProductLogo.js +45 -0
- package/dist/client/integrations/index.d.ts +2 -0
- package/dist/client/integrations/index.js +2 -0
- package/dist/client/integrations/workspace-provider-oauth.d.ts +8 -0
- package/dist/client/integrations/workspace-provider-oauth.js +13 -0
- package/dist/client/resources/mcp-integration-catalog.js +0 -29
- package/dist/client/settings/SettingsTabsPage.js +10 -6
- package/dist/client/sharing/ShareButton.js +11 -2
- package/dist/client/use-chat-models.d.ts +6 -0
- package/dist/client/use-chat-models.js +10 -0
- package/dist/client/use-db-sync.js +27 -9
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +111 -9
- package/dist/connections/catalog.js +86 -30
- package/dist/db/create-get-db.js +18 -0
- package/dist/db/ddl-guard.d.ts +0 -4
- package/dist/db/ddl-guard.js +46 -0
- package/dist/deploy/build.d.ts +19 -0
- package/dist/deploy/build.js +190 -16
- package/dist/deploy/function-bundle.d.ts +45 -0
- package/dist/deploy/function-bundle.js +337 -0
- package/dist/deploy/workspace-deploy.js +19 -1
- package/dist/eject/workspace-connections.js +4 -0
- package/dist/email-catalog/registry.d.ts +4 -0
- package/dist/email-catalog/registry.js +79 -14
- package/dist/extensions/content-patch.js +14 -2
- package/dist/integrations/plugin.d.ts +10 -0
- package/dist/integrations/plugin.js +40 -6
- package/dist/jobs/actions/run-automation-now.d.ts +2 -1
- package/dist/jobs/actions/run-automation-now.js +11 -5
- package/dist/jobs/frontmatter.js +53 -2
- package/dist/jobs/run-now.d.ts +11 -1
- package/dist/jobs/run-now.js +42 -7
- package/dist/jobs/scheduler.d.ts +1 -0
- package/dist/jobs/scheduler.js +2 -1
- package/dist/localization/core-messages/ar-SA.js +3 -0
- package/dist/localization/core-messages/de-DE.js +3 -0
- package/dist/localization/core-messages/en-US.d.ts +3 -0
- package/dist/localization/core-messages/en-US.js +3 -0
- package/dist/localization/core-messages/es-ES.js +3 -0
- package/dist/localization/core-messages/fr-FR.js +3 -0
- package/dist/localization/core-messages/hi-IN.js +3 -0
- package/dist/localization/core-messages/ja-JP.js +3 -0
- package/dist/localization/core-messages/ko-KR.js +3 -0
- package/dist/localization/core-messages/pt-BR.js +3 -0
- package/dist/localization/core-messages/zh-CN.js +3 -0
- package/dist/localization/core-messages/zh-TW.js +3 -0
- package/dist/localization/core-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts +2 -5
- package/dist/localization/default-messages.js +2 -5
- package/dist/mcp/build-server.d.ts +9 -0
- package/dist/mcp/build-server.js +37 -8
- package/dist/mcp-client/oauth-client.d.ts +7 -0
- package/dist/mcp-client/oauth-client.js +12 -1
- package/dist/oauth-tokens/index.d.ts +1 -1
- package/dist/oauth-tokens/index.js +1 -1
- package/dist/oauth-tokens/lifecycle.d.ts +11 -0
- package/dist/oauth-tokens/lifecycle.js +17 -0
- package/dist/oauth-tokens/store.js +7 -0
- package/dist/observability/posthog-ai.d.ts +15 -1
- package/dist/observability/posthog-ai.js +24 -12
- package/dist/observability/traces.js +158 -28
- package/dist/onboarding/app-profile.js +2 -2
- package/dist/onboarding/plugin.js +10 -2
- package/dist/onboarding/types.d.ts +2 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/provider-api/index.js +2 -0
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/resources/store.js +13 -4
- package/dist/scripts/agent-engines/list-agent-engines.js +29 -3
- package/dist/secrets/register-framework-secrets.js +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +27 -3
- package/dist/server/agent-chat/action-filters-a2a.js +48 -9
- package/dist/server/agent-chat-plugin.js +36 -9
- package/dist/server/agent-discovery.js +43 -20
- package/dist/server/auth.js +18 -16
- package/dist/server/beta-opt-out-html.js +45 -4
- package/dist/server/better-auth-instance.d.ts +2 -1
- package/dist/server/better-auth-instance.js +37 -7
- package/dist/server/builder-design-systems.d.ts +4 -1
- package/dist/server/builder-design-systems.js +87 -6
- package/dist/server/builder-oauth.d.ts +1 -7
- package/dist/server/builder-oauth.js +80 -163
- package/dist/server/core-routes-plugin.d.ts +29 -0
- package/dist/server/core-routes-plugin.js +100 -20
- package/dist/server/default-plugins.d.ts +18 -0
- package/dist/server/default-plugins.js +22 -0
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.js +1 -0
- package/dist/server/framework-request-handler.js +31 -3
- package/dist/server/google-oauth.d.ts +3 -0
- package/dist/server/google-oauth.js +3 -0
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/onboarding-html.js +37 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +6 -1
- package/dist/server/workspace-provider-oauth.js +72 -20
- package/dist/shared/agent-sidebar-url.d.ts +1 -0
- package/dist/shared/agent-sidebar-url.js +1 -0
- package/dist/shared/environment-lanes.d.ts +2 -0
- package/dist/shared/environment-lanes.js +3 -1
- package/dist/shared/index.d.ts +3 -1
- package/dist/shared/index.js +3 -1
- package/dist/shared/surface-visibility.d.ts +31 -0
- package/dist/shared/surface-visibility.js +57 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/chat/app/routes.ts +3 -1
- package/dist/templates/chat/vitest.config.ts +8 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/default/app/routes.ts +3 -1
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/tracking/registry.d.ts +11 -0
- package/dist/tracking/registry.js +5 -2
- package/dist/triggers/actions.js +10 -3
- package/dist/usage/store.js +23 -3
- package/dist/user-profile/actions/get-auth-methods.js +18 -5
- package/dist/vite/client.js +33 -7
- package/docs/content/actions-access-control.mdx +43 -4
- package/docs/content/actions-defining.mdx +16 -0
- package/docs/content/authentication.mdx +40 -21
- package/docs/content/environment-variables.mdx +1 -0
- package/docs/content/template-clips-capture-everywhere.mdx +29 -30
- package/docs/content/template-clips-developers.mdx +4 -4
- package/docs/content/template-clips.mdx +3 -1
- package/package.json +2 -2
- package/src/eject/workspace-connections.ts +4 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/chat/app/routes.ts +3 -1
- package/src/templates/chat/vitest.config.ts +8 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/default/app/routes.ts +3 -1
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When the camera bubble may declare its WebRTC video dead.
|
|
3
|
+
*
|
|
4
|
+
* WKWebView refuses to start a MediaStream-backed `<video>` that has had no
|
|
5
|
+
* user gesture in its page, and pauses one again whenever the owning window
|
|
6
|
+
* loses on-screen area. Neither shows up as an ICE failure, so the bubble's
|
|
7
|
+
* transport can be perfectly connected while the user stares at WebKit's
|
|
8
|
+
* start-playback overlay — a black circle with a white triangle. The bubble
|
|
9
|
+
* retries `play()` on a heartbeat, and uses the predicates here to decide when
|
|
10
|
+
* retrying has stopped being plausible and the popover should be asked for the
|
|
11
|
+
* canvas pump instead.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Re-play cadence for the WKWebView pause/block trap — matches `bubble-pump.ts`. */
|
|
15
|
+
export const BUBBLE_PLAY_HEARTBEAT_MS = 2000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* How long a WebRTC track may sit without producing frames before we give up
|
|
19
|
+
* on it. Long enough to cover a slow first decode, short enough that the user
|
|
20
|
+
* is not left wondering whether the camera is broken.
|
|
21
|
+
*/
|
|
22
|
+
export const BUBBLE_RENDER_GRACE_MS = 2500;
|
|
23
|
+
|
|
24
|
+
export type BubblePlaybackProbe = {
|
|
25
|
+
/** When the current track arrived, or `null` when there is no track yet. */
|
|
26
|
+
trackArrivedAt: number | null;
|
|
27
|
+
now: number;
|
|
28
|
+
paused: boolean;
|
|
29
|
+
/** `0` until WebKit has decoded a frame, so it is the honest render signal. */
|
|
30
|
+
videoWidth: number;
|
|
31
|
+
alreadyReported: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether WebRTC video is actually on screen. `videoWidth > 0` is the part
|
|
36
|
+
* that matters: a `<video>` reports `paused === false` the moment `play()`
|
|
37
|
+
* resolves, well before any frame is decoded, so "playing" alone is not proof
|
|
38
|
+
* the user can see anything.
|
|
39
|
+
*/
|
|
40
|
+
export function isRenderingWebrtc(
|
|
41
|
+
probe: Pick<BubblePlaybackProbe, "paused" | "videoWidth">,
|
|
42
|
+
): boolean {
|
|
43
|
+
return !probe.paused && probe.videoWidth > 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a `playing` event may give the WebRTC `<video>` the visible surface.
|
|
48
|
+
*
|
|
49
|
+
* Once the bubble has asked for the canvas pump the popover stops sending, so
|
|
50
|
+
* a later `playing` is the tail of a dead stream. Taking the surface there
|
|
51
|
+
* would park a still frame over the pump's live output. Only a fresh track
|
|
52
|
+
* clears `fallbackRequested` and re-arms this.
|
|
53
|
+
*/
|
|
54
|
+
export function shouldClaimWebrtcPath(input: {
|
|
55
|
+
fallbackRequested: boolean;
|
|
56
|
+
videoWidth: number;
|
|
57
|
+
}): boolean {
|
|
58
|
+
if (input.fallbackRequested) return false;
|
|
59
|
+
return input.videoWidth > 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Whether the bubble should ask the popover to fall back to the canvas pump. */
|
|
63
|
+
export function shouldReportUnrendered(probe: BubblePlaybackProbe): boolean {
|
|
64
|
+
if (probe.alreadyReported) return false;
|
|
65
|
+
if (probe.trackArrivedAt == null) return false;
|
|
66
|
+
if (isRenderingWebrtc(probe)) return false;
|
|
67
|
+
return probe.now - probe.trackArrivedAt >= BUBBLE_RENDER_GRACE_MS;
|
|
68
|
+
}
|
|
@@ -1672,6 +1672,9 @@ async function saveRecordingTranscript(
|
|
|
1672
1672
|
fullText: text,
|
|
1673
1673
|
segments: transcript.segments,
|
|
1674
1674
|
source: transcript.source ?? "whisper",
|
|
1675
|
+
...(transcript.failureReason
|
|
1676
|
+
? { failureReason: transcript.failureReason }
|
|
1677
|
+
: {}),
|
|
1675
1678
|
}),
|
|
1676
1679
|
signal: AbortSignal.timeout(TRANSCRIPT_SAVE_TIMEOUT_MS),
|
|
1677
1680
|
});
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
/** Grace period after stop for whisper to emit any flushed trailing finals. */
|
|
29
29
|
const WHISPER_STOP_SETTLE_MS = 1500;
|
|
30
30
|
const WEB_SPEECH_STOP_SETTLE_MS = 1200;
|
|
31
|
+
const WEB_SPEECH_RESTART_RETRY_BASE_MS = 400;
|
|
32
|
+
const WEB_SPEECH_MAX_RESTART_ATTEMPTS = 8;
|
|
31
33
|
|
|
32
34
|
function wait(ms: number): Promise<void> {
|
|
33
35
|
return new Promise((resolve) => window.setTimeout(resolve, ms));
|
|
@@ -42,6 +44,10 @@ export interface CapturedTranscript {
|
|
|
42
44
|
segments: SourcedTranscriptSegment[];
|
|
43
45
|
/** Source stored with `save-browser-transcript`. */
|
|
44
46
|
source?: "web-speech" | "macos-native" | "whisper";
|
|
47
|
+
/** Set when capture died partway. Text plus a reason is what tells the server
|
|
48
|
+
* this transcript is truncated and has to be re-transcribed in the cloud;
|
|
49
|
+
* omitting it saves a partial capture as a complete one. */
|
|
50
|
+
failureReason?: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export interface TranscriptionCapture {
|
|
@@ -170,15 +176,54 @@ async function startBrowserTranscriptionCapture(): Promise<TranscriptionCapture
|
|
|
170
176
|
const transcriptBuffer = createWebSpeechTranscriptBuffer();
|
|
171
177
|
let stopResolver: ((value: CapturedTranscript) => void) | null = null;
|
|
172
178
|
let settleTimer: ReturnType<typeof window.setTimeout> | null = null;
|
|
179
|
+
let restartTimer: ReturnType<typeof window.setTimeout> | null = null;
|
|
180
|
+
let restartFailures = 0;
|
|
181
|
+
let failureReason: string | null = null;
|
|
173
182
|
|
|
174
183
|
const captured = (): CapturedTranscript => ({
|
|
175
184
|
text: transcriptBuffer.text(),
|
|
176
185
|
segments: [],
|
|
177
186
|
source: "web-speech",
|
|
187
|
+
failureReason: failureReason ?? undefined,
|
|
178
188
|
});
|
|
179
189
|
|
|
190
|
+
const clearRestartTimer = () => {
|
|
191
|
+
if (restartTimer === null) return;
|
|
192
|
+
window.clearTimeout(restartTimer);
|
|
193
|
+
restartTimer = null;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Chrome ends the recognizer on its own roughly every minute, and always
|
|
197
|
+
// throws if start() is called synchronously from inside `onend`. Nothing
|
|
198
|
+
// starts, so no further `onend` arrives — the retry has to live here or the
|
|
199
|
+
// first failure ends transcription for the rest of the recording.
|
|
200
|
+
const scheduleRestart = (delayMs: number) => {
|
|
201
|
+
if (restartTimer !== null) return;
|
|
202
|
+
restartTimer = window.setTimeout(() => {
|
|
203
|
+
restartTimer = null;
|
|
204
|
+
if (disposed || stopped || paused) return;
|
|
205
|
+
try {
|
|
206
|
+
recognition.start();
|
|
207
|
+
restartFailures = 0;
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const attempt = ++restartFailures;
|
|
210
|
+
if (attempt >= WEB_SPEECH_MAX_RESTART_ATTEMPTS) {
|
|
211
|
+
failureReason =
|
|
212
|
+
"Web Speech transcription stopped mid-recording and could not be restarted.";
|
|
213
|
+
console.warn(
|
|
214
|
+
"[clips-recorder] Web Speech transcription restart failed:",
|
|
215
|
+
err,
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
scheduleRestart(WEB_SPEECH_RESTART_RETRY_BASE_MS * attempt);
|
|
220
|
+
}
|
|
221
|
+
}, delayMs);
|
|
222
|
+
};
|
|
223
|
+
|
|
180
224
|
const settleStop = () => {
|
|
181
225
|
if (!stopResolver) return;
|
|
226
|
+
clearRestartTimer();
|
|
182
227
|
if (settleTimer) {
|
|
183
228
|
window.clearTimeout(settleTimer);
|
|
184
229
|
settleTimer = null;
|
|
@@ -200,6 +245,12 @@ async function startBrowserTranscriptionCapture(): Promise<TranscriptionCapture
|
|
|
200
245
|
|
|
201
246
|
recognition.onerror = (event) => {
|
|
202
247
|
if (event.error === "no-speech" || event.error === "aborted") return;
|
|
248
|
+
// A non-benign error drops audio until `onend` restarts recognition. That
|
|
249
|
+
// restart usually succeeds, so without recording the gap here the transcript
|
|
250
|
+
// saves as complete and the server never schedules cloud transcription for
|
|
251
|
+
// the missing stretch. Deliberately not cleared by a later successful
|
|
252
|
+
// restart: recovering the engine does not recover the lost speech.
|
|
253
|
+
failureReason ??= `Web Speech transcription dropped audio after a "${event.error}" error.`;
|
|
203
254
|
console.warn(
|
|
204
255
|
"[clips-recorder] Web Speech transcription error:",
|
|
205
256
|
event.error,
|
|
@@ -215,14 +266,7 @@ async function startBrowserTranscriptionCapture(): Promise<TranscriptionCapture
|
|
|
215
266
|
}
|
|
216
267
|
// While paused, keep the committed transcript but don't restart the engine.
|
|
217
268
|
if (paused) return;
|
|
218
|
-
|
|
219
|
-
recognition.start();
|
|
220
|
-
} catch (err) {
|
|
221
|
-
console.warn(
|
|
222
|
-
"[clips-recorder] Web Speech transcription restart failed:",
|
|
223
|
-
err,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
269
|
+
scheduleRestart(0);
|
|
226
270
|
};
|
|
227
271
|
|
|
228
272
|
try {
|
|
@@ -250,6 +294,7 @@ async function startBrowserTranscriptionCapture(): Promise<TranscriptionCapture
|
|
|
250
294
|
async cancel() {
|
|
251
295
|
disposed = true;
|
|
252
296
|
stopped = true;
|
|
297
|
+
clearRestartTimer();
|
|
253
298
|
if (settleTimer) {
|
|
254
299
|
window.clearTimeout(settleTimer);
|
|
255
300
|
settleTimer = null;
|
|
@@ -273,14 +318,18 @@ async function startBrowserTranscriptionCapture(): Promise<TranscriptionCapture
|
|
|
273
318
|
async resume() {
|
|
274
319
|
if (disposed || stopped || !paused) return;
|
|
275
320
|
paused = false;
|
|
321
|
+
restartFailures = 0;
|
|
276
322
|
console.log("[clips-recorder] transcription resumed (web-speech)");
|
|
277
323
|
try {
|
|
278
324
|
recognition.start();
|
|
279
|
-
} catch
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
325
|
+
} catch {
|
|
326
|
+
// `paused` is already false, so the recording is live through the
|
|
327
|
+
// failed start and the retry delay. A later successful retry does not
|
|
328
|
+
// recover that speech, so the gap is recorded durably and the server
|
|
329
|
+
// still schedules cloud transcription.
|
|
330
|
+
failureReason ??=
|
|
331
|
+
"Web Speech transcription dropped audio while resuming after a pause.";
|
|
332
|
+
scheduleRestart(WEB_SPEECH_RESTART_RETRY_BASE_MS);
|
|
284
333
|
}
|
|
285
334
|
},
|
|
286
335
|
async resetTimeline() {
|
|
@@ -289,7 +338,10 @@ async function startBrowserTranscriptionCapture(): Promise<TranscriptionCapture
|
|
|
289
338
|
};
|
|
290
339
|
}
|
|
291
340
|
|
|
292
|
-
export const __test = {
|
|
341
|
+
export const __test = {
|
|
342
|
+
createWebSpeechTranscriptBuffer,
|
|
343
|
+
startBrowserTranscriptionCapture,
|
|
344
|
+
};
|
|
293
345
|
|
|
294
346
|
/**
|
|
295
347
|
* Local transcription opens a microphone capture of its own. System-only
|
|
@@ -321,6 +373,7 @@ export async function startTranscriptionCapture(
|
|
|
321
373
|
// asynchronously. Track when those are expected to have landed so a stop
|
|
322
374
|
// soon after a pause waits for them instead of dropping the last words.
|
|
323
375
|
let pauseFinalsSettleUntil = 0;
|
|
376
|
+
let transitionFailure: string | null = null;
|
|
324
377
|
const unlistens: UnlistenFn[] = [];
|
|
325
378
|
|
|
326
379
|
const cleanup = () => {
|
|
@@ -342,6 +395,7 @@ export async function startTranscriptionCapture(
|
|
|
342
395
|
text: transcriptFullText(lines),
|
|
343
396
|
segments: transcriptSegments(lines),
|
|
344
397
|
source: engine,
|
|
398
|
+
failureReason: transitionFailure ?? undefined,
|
|
345
399
|
});
|
|
346
400
|
|
|
347
401
|
let engine: TranscriptionEngine;
|
|
@@ -407,6 +461,7 @@ export async function startTranscriptionCapture(
|
|
|
407
461
|
// reset it) so the next pause/resume toggle retries and converges, and
|
|
408
462
|
// return early so we don't busy-loop re-applying a persistently failing
|
|
409
463
|
// transition. `paused` still reflects the real engine state.
|
|
464
|
+
transitionFailure = `Local transcription ${desiredPaused ? "pause" : "resume"} failed; engine still ${paused ? "paused" : "live"}.`;
|
|
410
465
|
console.warn(
|
|
411
466
|
`[clips-recorder] transcription ${desiredPaused ? "pause" : "resume"} failed; engine still ${paused ? "paused" : "live"}:`,
|
|
412
467
|
err,
|
|
@@ -416,6 +471,9 @@ export async function startTranscriptionCapture(
|
|
|
416
471
|
} finally {
|
|
417
472
|
transitioning = false;
|
|
418
473
|
}
|
|
474
|
+
// Reached only when the transition landed, so a failure the next toggle
|
|
475
|
+
// converged on is no longer a reason to re-transcribe in the cloud.
|
|
476
|
+
transitionFailure = null;
|
|
419
477
|
// Re-apply in case the desired state changed mid-transition.
|
|
420
478
|
void applyAudioState();
|
|
421
479
|
};
|
|
@@ -96,8 +96,9 @@ async function runCheck() {
|
|
|
96
96
|
// made "Restart to update" land on a binary that was already out of date and
|
|
97
97
|
// immediately ask to restart again — every check runs against the *running*
|
|
98
98
|
// version, so it re-offers the staged version until a newer one ships.
|
|
99
|
-
const
|
|
100
|
-
cachedStatus.state === "downloaded" ? cachedStatus
|
|
99
|
+
const stagedStatus =
|
|
100
|
+
cachedStatus.state === "downloaded" ? cachedStatus : null;
|
|
101
|
+
const staged = stagedStatus?.version ?? null;
|
|
101
102
|
|
|
102
103
|
lastCheckStartedAt = Date.now();
|
|
103
104
|
checkInFlight = (async () => {
|
|
@@ -114,7 +115,11 @@ async function runCheck() {
|
|
|
114
115
|
}
|
|
115
116
|
} catch (err) {
|
|
116
117
|
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
-
|
|
118
|
+
// A staged bundle is already on disk and `pendingUpdate` still points at
|
|
119
|
+
// it. Reporting the check failure here would replace the Restart banner
|
|
120
|
+
// with an error card that has no way back to it, and would make the next
|
|
121
|
+
// successful check re-download the same version from scratch.
|
|
122
|
+
setStatus(stagedStatus ?? { state: "error", message });
|
|
118
123
|
} finally {
|
|
119
124
|
checkInFlight = null;
|
|
120
125
|
}
|
|
@@ -3,6 +3,14 @@ import { invoke } from "@tauri-apps/api/core";
|
|
|
3
3
|
import { emit, listen } from "@tauri-apps/api/event";
|
|
4
4
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
BUBBLE_PLAY_HEARTBEAT_MS,
|
|
9
|
+
BUBBLE_RENDER_GRACE_MS,
|
|
10
|
+
shouldClaimWebrtcPath,
|
|
11
|
+
shouldReportUnrendered,
|
|
12
|
+
} from "../lib/bubble-playback";
|
|
13
|
+
|
|
6
14
|
type BubbleSize = "small" | "medium";
|
|
7
15
|
|
|
8
16
|
/**
|
|
@@ -41,10 +49,13 @@ type BubbleSize = "small" | "medium";
|
|
|
41
49
|
* feature degrades gracefully if the WebRTC handshake fails.
|
|
42
50
|
*
|
|
43
51
|
* The bubble always sets up BOTH receivers on mount. Whichever path
|
|
44
|
-
* delivers video first wins — the other stays passive.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
52
|
+
* delivers video first wins — the other stays passive. The popover
|
|
53
|
+
* starts the canvas pump instead when the WebRTC handshake fails (ICE
|
|
54
|
+
* timeout / failed state) OR when this bubble reports that a connected
|
|
55
|
+
* track never rendered (`clips:bubble-webrtc-unrendered`; see the
|
|
56
|
+
* playback watchdog below). Both signals are needed — ICE reaching
|
|
57
|
+
* `connected` says the transport works, never that WebKit painted a
|
|
58
|
+
* frame.
|
|
48
59
|
*
|
|
49
60
|
* # Hover controls (Loom-style)
|
|
50
61
|
*
|
|
@@ -59,16 +70,29 @@ type BubbleSize = "small" | "medium";
|
|
|
59
70
|
export function Bubble() {
|
|
60
71
|
// Dual-path rendering: <video> for WebRTC, <canvas> for the legacy
|
|
61
72
|
// JPEG stream. CSS stacks them in the same circle — whichever has a
|
|
62
|
-
// stream / frames visible fills the same space.
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// frames start arriving we flip to "canvas".
|
|
73
|
+
// stream / frames visible fills the same space. `data-path` on the root
|
|
74
|
+
// hides the loser. It flips to "webrtc" only once the <video> actually
|
|
75
|
+
// renders a frame, and to "canvas" on the first JPEG frame.
|
|
66
76
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
|
67
77
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
68
78
|
const firstFrameAtRef = useRef<number | null>(null);
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
//
|
|
79
|
+
const firstTrackAtRef = useRef<number | null>(null);
|
|
80
|
+
// When the current WebRTC track arrived, so the playback watchdog can tell
|
|
81
|
+
// "no track yet" from "track arrived and WebKit still isn't rendering it".
|
|
82
|
+
const trackArrivedAtRef = useRef<number | null>(null);
|
|
83
|
+
// Assigned by the playback watchdog effect; called from `ontrack`, which
|
|
84
|
+
// lives in a different effect and must not own the retry policy.
|
|
85
|
+
const attemptPlayRef = useRef<() => void>(() => {});
|
|
86
|
+
// True once we've told the popover this track is dead and asked for the
|
|
87
|
+
// canvas pump. Shared across both effects: the watchdog sets it, and only a
|
|
88
|
+
// fresh track clears it, so the surface can't be handed back to a stream the
|
|
89
|
+
// popover has already stopped sending.
|
|
90
|
+
const fallbackRequestedRef = useRef(false);
|
|
91
|
+
// Which transport last put pixels on screen. Starts as "none"; the
|
|
92
|
+
// playback watchdog flips it to "webrtc" on a `playing` event with real
|
|
93
|
+
// frame dimensions, and the canvas sink flips it on the first JPEG frame.
|
|
94
|
+
// A track merely arriving is NOT enough — claiming "webrtc" there hid the
|
|
95
|
+
// canvas fallback exactly when WebKit had refused to play the video.
|
|
72
96
|
const [activePath, setActivePath] = useState<"none" | "webrtc" | "canvas">(
|
|
73
97
|
"none",
|
|
74
98
|
);
|
|
@@ -153,6 +177,121 @@ export function Bubble() {
|
|
|
153
177
|
}
|
|
154
178
|
};
|
|
155
179
|
|
|
180
|
+
// ---- playback watchdog --------------------------------------------------
|
|
181
|
+
// WKWebView will not start a MediaStream-backed `<video>` that has had no
|
|
182
|
+
// user gesture in the page, and pauses one again whenever its window loses
|
|
183
|
+
// on-screen area. One `play()` at `ontrack` time therefore leaves the
|
|
184
|
+
// element parked behind WebKit's start-playback overlay — the black circle
|
|
185
|
+
// with a white triangle users report — and nothing ever retries it. The
|
|
186
|
+
// popover's offscreen pump video already defends itself the same way (see
|
|
187
|
+
// `bubble-pump.ts`); this is the bubble's copy of that defense.
|
|
188
|
+
//
|
|
189
|
+
// It also owns the `activePath` transition: only a `playing` event with real
|
|
190
|
+
// frame dimensions proves WebRTC is on screen. Flipping on `ontrack` instead
|
|
191
|
+
// hid the canvas fallback exactly when it was needed.
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
const video = videoRef.current;
|
|
194
|
+
if (!video) return;
|
|
195
|
+
let stopped = false;
|
|
196
|
+
let lastFailure: string | null = null;
|
|
197
|
+
|
|
198
|
+
const attemptPlay = () => {
|
|
199
|
+
if (stopped) return;
|
|
200
|
+
if (!video.srcObject || !video.paused) return;
|
|
201
|
+
video.play().catch((err: unknown) => {
|
|
202
|
+
// Name and message, not the raw object: `console.warn` on a
|
|
203
|
+
// DOMException logs only its stack, which cannot tell NotAllowedError
|
|
204
|
+
// (playback blocked) from AbortError (load interrupted) — the two
|
|
205
|
+
// point at different bugs. Log each distinct reason once so the retry
|
|
206
|
+
// loop cannot bury the rest of the log.
|
|
207
|
+
const name = (err as { name?: string } | null)?.name ?? "unknown";
|
|
208
|
+
const message =
|
|
209
|
+
(err as { message?: string } | null)?.message ?? String(err);
|
|
210
|
+
const signature = `${name}: ${message}`;
|
|
211
|
+
if (lastFailure === signature) return;
|
|
212
|
+
lastFailure = signature;
|
|
213
|
+
console.warn("[bubble] video.play() rejected —", signature);
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
attemptPlayRef.current = attemptPlay;
|
|
217
|
+
|
|
218
|
+
const maybeClaimWebrtcPath = () => {
|
|
219
|
+
const claims = shouldClaimWebrtcPath({
|
|
220
|
+
fallbackRequested: fallbackRequestedRef.current,
|
|
221
|
+
videoWidth: video.videoWidth,
|
|
222
|
+
});
|
|
223
|
+
if (!claims) return;
|
|
224
|
+
setActivePath((current) => {
|
|
225
|
+
if (current !== "webrtc") {
|
|
226
|
+
console.log(
|
|
227
|
+
"[bubble] webrtc playback started %dx%d",
|
|
228
|
+
video.videoWidth,
|
|
229
|
+
video.videoHeight,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return "webrtc";
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const onPlaying = () => {
|
|
237
|
+
if (stopped) return;
|
|
238
|
+
lastFailure = null;
|
|
239
|
+
maybeClaimWebrtcPath();
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
video.addEventListener("playing", onPlaying);
|
|
243
|
+
video.addEventListener("loadedmetadata", attemptPlay);
|
|
244
|
+
video.addEventListener("canplay", attemptPlay);
|
|
245
|
+
video.addEventListener("pause", attemptPlay);
|
|
246
|
+
document.addEventListener("visibilitychange", attemptPlay);
|
|
247
|
+
|
|
248
|
+
// Heartbeat: covers the resume cases no media event fires for (window
|
|
249
|
+
// shrunk to no visible area and back, a gesture landing on a control
|
|
250
|
+
// rather than the video). `play()` on an already-playing element is a
|
|
251
|
+
// no-op, so this is cheap. It doubles as the unrendered detector — if a
|
|
252
|
+
// track has been sitting there for BUBBLE_RENDER_GRACE_MS with no frames,
|
|
253
|
+
// tell the popover so it can start the proven canvas pump instead of
|
|
254
|
+
// leaving the user with a dead circle.
|
|
255
|
+
const heartbeat = setInterval(() => {
|
|
256
|
+
if (stopped) return;
|
|
257
|
+
attemptPlay();
|
|
258
|
+
maybeClaimWebrtcPath();
|
|
259
|
+
const unrendered = shouldReportUnrendered({
|
|
260
|
+
trackArrivedAt: trackArrivedAtRef.current,
|
|
261
|
+
now: Date.now(),
|
|
262
|
+
paused: video.paused,
|
|
263
|
+
videoWidth: video.videoWidth,
|
|
264
|
+
alreadyReported: fallbackRequestedRef.current,
|
|
265
|
+
});
|
|
266
|
+
if (!unrendered) return;
|
|
267
|
+
fallbackRequestedRef.current = true;
|
|
268
|
+
console.warn(
|
|
269
|
+
"[bubble] webrtc track is not rendering after %dms (paused=%o readyState=%d videoWidth=%d) — asking popover for the canvas pump",
|
|
270
|
+
BUBBLE_RENDER_GRACE_MS,
|
|
271
|
+
video.paused,
|
|
272
|
+
video.readyState,
|
|
273
|
+
video.videoWidth,
|
|
274
|
+
);
|
|
275
|
+
emit("clips:bubble-webrtc-unrendered", {
|
|
276
|
+
paused: video.paused,
|
|
277
|
+
readyState: video.readyState,
|
|
278
|
+
videoWidth: video.videoWidth,
|
|
279
|
+
lastPlayFailure: lastFailure,
|
|
280
|
+
}).catch(() => {});
|
|
281
|
+
}, BUBBLE_PLAY_HEARTBEAT_MS);
|
|
282
|
+
|
|
283
|
+
return () => {
|
|
284
|
+
stopped = true;
|
|
285
|
+
clearInterval(heartbeat);
|
|
286
|
+
video.removeEventListener("playing", onPlaying);
|
|
287
|
+
video.removeEventListener("loadedmetadata", attemptPlay);
|
|
288
|
+
video.removeEventListener("canplay", attemptPlay);
|
|
289
|
+
video.removeEventListener("pause", attemptPlay);
|
|
290
|
+
document.removeEventListener("visibilitychange", attemptPlay);
|
|
291
|
+
attemptPlayRef.current = () => {};
|
|
292
|
+
};
|
|
293
|
+
}, []);
|
|
294
|
+
|
|
156
295
|
// ---- WebRTC receiver ----------------------------------------------------
|
|
157
296
|
// Sets up a fresh RTCPeerConnection on mount, emits `bubble-ready`
|
|
158
297
|
// so the popover knows to start the handshake, and renegotiates on
|
|
@@ -187,6 +326,7 @@ export function Bubble() {
|
|
|
187
326
|
};
|
|
188
327
|
|
|
189
328
|
function teardownPeer() {
|
|
329
|
+
trackArrivedAtRef.current = null;
|
|
190
330
|
if (pc) {
|
|
191
331
|
// Detach the incoming video <video>.srcObject BEFORE closing the
|
|
192
332
|
// peer — otherwise WKWebView's media pipeline keeps the decoder
|
|
@@ -272,17 +412,19 @@ export function Bubble() {
|
|
|
272
412
|
const incomingStream = ev.streams[0];
|
|
273
413
|
if (!incomingStream) return;
|
|
274
414
|
videoEl.srcObject = incomingStream;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
415
|
+
// Do NOT flip `activePath` here. A track arriving proves the
|
|
416
|
+
// transport works, not that WebKit will render it — see the
|
|
417
|
+
// playback watchdog below, which owns that transition and the
|
|
418
|
+
// fallback when playback never starts.
|
|
419
|
+
trackArrivedAtRef.current = Date.now();
|
|
420
|
+
// A fresh track is a fresh chance: re-arm the watchdog so it can both
|
|
421
|
+
// grant this one the surface and report it dead in turn.
|
|
422
|
+
fallbackRequestedRef.current = false;
|
|
423
|
+
attemptPlayRef.current();
|
|
424
|
+
if (firstTrackAtRef.current == null) {
|
|
425
|
+
firstTrackAtRef.current = Date.now();
|
|
283
426
|
console.log("[bubble] first webrtc track received");
|
|
284
427
|
}
|
|
285
|
-
setActivePath("webrtc");
|
|
286
428
|
};
|
|
287
429
|
|
|
288
430
|
try {
|
|
@@ -1275,11 +1275,13 @@ pub async fn resize_popover(app: AppHandle, height: f64, width: Option<f64>) ->
|
|
|
1275
1275
|
return Ok(());
|
|
1276
1276
|
}
|
|
1277
1277
|
if let Some(w) = app.get_webview_window("popover") {
|
|
1278
|
-
let
|
|
1278
|
+
let monitor = w
|
|
1279
1279
|
.current_monitor()
|
|
1280
1280
|
.ok()
|
|
1281
1281
|
.flatten()
|
|
1282
|
-
.or_else(|| w.primary_monitor().ok().flatten())
|
|
1282
|
+
.or_else(|| w.primary_monitor().ok().flatten());
|
|
1283
|
+
let max_logical_height = monitor
|
|
1284
|
+
.as_ref()
|
|
1283
1285
|
.map(|monitor| {
|
|
1284
1286
|
let scale = monitor.scale_factor().max(1.0);
|
|
1285
1287
|
((monitor.size().height as f64) / scale
|
|
@@ -1288,16 +1290,39 @@ pub async fn resize_popover(app: AppHandle, height: f64, width: Option<f64>) ->
|
|
|
1288
1290
|
.clamp(260.0, 820.0)
|
|
1289
1291
|
})
|
|
1290
1292
|
.unwrap_or(820.0);
|
|
1293
|
+
// Same idea as height: a monitor narrower than the requested width
|
|
1294
|
+
// (e.g. settings' 720) must not let the window grow past the screen
|
|
1295
|
+
// edge, since `position_popover`'s x-clamp can only slide a
|
|
1296
|
+
// too-wide window, not shrink it back onto the display.
|
|
1297
|
+
let max_logical_width = monitor
|
|
1298
|
+
.map(|monitor| {
|
|
1299
|
+
let scale = monitor.scale_factor().max(1.0);
|
|
1300
|
+
((monitor.size().width as f64) / scale - 16.0 - POPOVER_SHADOW_GUTTER_LOGICAL * 2.0)
|
|
1301
|
+
.clamp(320.0, 960.0)
|
|
1302
|
+
})
|
|
1303
|
+
.unwrap_or(960.0);
|
|
1291
1304
|
let clamped = height.clamp(200.0, max_logical_height);
|
|
1292
|
-
let width = width
|
|
1305
|
+
let width = width
|
|
1306
|
+
.unwrap_or(320.0)
|
|
1307
|
+
.clamp(320.0, max_logical_width.max(320.0));
|
|
1293
1308
|
let (window_width, window_height) = popover_window_size_logical(width, clamped);
|
|
1294
1309
|
let _ = w.set_size(tauri::Size::Logical(tauri::LogicalSize::new(
|
|
1295
1310
|
window_width,
|
|
1296
1311
|
window_height,
|
|
1297
1312
|
)));
|
|
1298
1313
|
// Re-anchor to the tray icon so the window doesn't drift below the
|
|
1299
|
-
// bottom of the monitor after a growth.
|
|
1300
|
-
position_popover(
|
|
1314
|
+
// bottom of the monitor after a growth. Pass the size we just asked
|
|
1315
|
+
// for rather than letting `position_popover` re-read `outer_size()`:
|
|
1316
|
+
// macOS doesn't always commit `set_size()` synchronously, so a
|
|
1317
|
+
// stale (pre-resize) read here would center/clamp against the old,
|
|
1318
|
+
// narrower width and let the window balloon past the screen edge
|
|
1319
|
+
// once the real resize lands a moment later.
|
|
1320
|
+
let target_scale = w.scale_factor().unwrap_or(1.0).max(1.0);
|
|
1321
|
+
let target_physical = PhysicalSize::new(
|
|
1322
|
+
(window_width * target_scale).round() as u32,
|
|
1323
|
+
(window_height * target_scale).round() as u32,
|
|
1324
|
+
);
|
|
1325
|
+
position_popover_with_size(&app, &w, target_physical);
|
|
1301
1326
|
}
|
|
1302
1327
|
Ok(())
|
|
1303
1328
|
}
|
|
@@ -2668,13 +2693,25 @@ pub fn toggle_popover(app: &AppHandle) {
|
|
|
2668
2693
|
}
|
|
2669
2694
|
|
|
2670
2695
|
pub fn position_popover(app: &AppHandle, window: &WebviewWindow) {
|
|
2696
|
+
let win_size = window.outer_size().unwrap_or(PhysicalSize::new(360, 440));
|
|
2697
|
+
position_popover_with_size(app, window, win_size);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
/// Same as `position_popover`, but takes the window's size explicitly instead
|
|
2701
|
+
/// of querying `window.outer_size()`. Callers that just called `set_size()`
|
|
2702
|
+
/// must pass the size they requested — see the comment at that call site in
|
|
2703
|
+
/// `resize_popover` for why re-querying there is unsafe.
|
|
2704
|
+
pub fn position_popover_with_size(
|
|
2705
|
+
app: &AppHandle,
|
|
2706
|
+
window: &WebviewWindow,
|
|
2707
|
+
win_size: PhysicalSize<u32>,
|
|
2708
|
+
) {
|
|
2671
2709
|
// If we have a recent tray icon rect, anchor the popover's top edge just
|
|
2672
2710
|
// below the icon and center it horizontally on the icon — same feel as
|
|
2673
2711
|
// Loom / Raycast / 1Password.
|
|
2674
2712
|
let anchor = app.state::<TrayAnchor>();
|
|
2675
2713
|
let tray_rect = anchor.0.lock().ok().and_then(|g| *g);
|
|
2676
2714
|
|
|
2677
|
-
let win_size: PhysicalSize<u32> = window.outer_size().unwrap_or(PhysicalSize::new(360, 440));
|
|
2678
2715
|
// IMPORTANT: `current_monitor()` returns None when the window is offscreen
|
|
2679
2716
|
// (we park it at 99999,99999 on boot to hide the initial flash). Fall back
|
|
2680
2717
|
// to the primary monitor so we can still position correctly on first show.
|