@agent-native/core 0.103.1 → 0.105.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +61 -0
- package/corpus/core/package.json +9 -2
- package/corpus/core/src/agent/engine/credential-errors.ts +2 -2
- package/corpus/core/src/agent/production-agent.ts +30 -0
- package/corpus/core/src/agent/run-store.ts +12 -2
- package/corpus/core/src/cli/create.ts +1 -0
- package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +30 -11
- package/corpus/core/src/cli/templates-meta.ts +4 -3
- package/corpus/core/src/client/AgentAskPopover.tsx +89 -0
- package/corpus/core/src/client/AgentChatHome.tsx +4 -0
- package/corpus/core/src/client/AgentPanel.tsx +321 -223
- package/corpus/core/src/client/AssistantChat.tsx +6 -10
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +53 -372
- package/corpus/core/src/client/agent-chat-adapter.ts +13 -1
- package/corpus/core/src/client/agent-page/AgentContextTab.tsx +133 -149
- package/corpus/core/src/client/agent-page/AgentEmptyState.tsx +44 -0
- package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +153 -82
- package/corpus/core/src/client/agent-page/AgentTabFrame.tsx +39 -0
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +368 -356
- package/corpus/core/src/client/agent-page/types.ts +1 -1
- package/corpus/core/src/client/analytics.ts +51 -5
- package/corpus/core/src/client/chat/run-recovery.tsx +12 -4
- package/corpus/core/src/client/composer/PromptComposer.tsx +5 -15
- package/corpus/core/src/client/composer/TiptapComposer.tsx +32 -3
- package/corpus/core/src/client/dynamic-suggestions.ts +3 -3
- package/corpus/core/src/client/index.ts +16 -1
- package/corpus/core/src/client/resources/ResourceTree.tsx +249 -38
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +307 -140
- package/corpus/core/src/client/session-replay.ts +158 -5
- package/corpus/core/src/client/settings/AutomationsSection.tsx +30 -10
- package/corpus/core/src/client/settings/SettingsPanel.tsx +3 -2
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +2 -0
- package/corpus/core/src/client/settings/agent-settings-search.ts +2 -2
- package/corpus/core/src/client/use-chat-threads.ts +2 -0
- package/corpus/core/src/connections/catalog.ts +49 -7
- package/corpus/core/src/db/index.ts +6 -1
- package/corpus/core/src/deploy/build.ts +23 -0
- package/corpus/core/src/extensions/actions.ts +106 -16
- package/corpus/core/src/extensions/content-patch.ts +22 -0
- package/corpus/core/src/extensions/routes.ts +18 -0
- package/corpus/core/src/extensions/store.ts +8 -0
- package/corpus/core/src/index.ts +3 -0
- package/corpus/core/src/ingestion/docx.ts +158 -0
- package/corpus/core/src/ingestion/figma-node-to-html.ts +1860 -0
- package/corpus/core/src/ingestion/figma.ts +548 -0
- package/corpus/core/src/ingestion/index.ts +107 -0
- package/corpus/core/src/ingestion/media.ts +264 -0
- package/corpus/core/src/ingestion/notion.ts +121 -0
- package/corpus/core/src/ingestion/office.ts +214 -0
- package/corpus/core/src/ingestion/orchestration.ts +160 -0
- package/corpus/core/src/ingestion/pptx.ts +333 -0
- package/corpus/core/src/ingestion/selection.ts +81 -0
- package/corpus/core/src/ingestion/website.ts +151 -0
- package/corpus/core/src/integrations/plugin.ts +45 -37
- package/corpus/core/src/localization/default-messages.ts +35 -12
- package/corpus/core/src/observability/hosted-model-experiment.ts +118 -0
- package/corpus/core/src/observability/traces.ts +5 -3
- package/corpus/core/src/provider-api/index.ts +452 -8
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +18 -1
- package/corpus/core/src/search-utils/index.ts +112 -0
- package/corpus/core/src/secrets/register-framework-secrets.ts +61 -0
- package/corpus/core/src/server/action-discovery.ts +33 -8
- package/corpus/core/src/server/agent-chat/framework-prompts.ts +2 -2
- package/corpus/core/src/server/agent-chat/prompt-resources.ts +144 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
- package/corpus/core/src/server/core-routes-plugin.ts +12 -0
- package/corpus/core/src/server/index.ts +30 -0
- package/corpus/core/src/server/workspace-provider-oauth.ts +583 -0
- package/corpus/core/src/styles/agent-native.css +9 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +3 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +14 -5
- package/corpus/core/src/templates/workspace-root/.env.example +12 -0
- package/corpus/core/src/workspace-connections/index.ts +6 -0
- package/corpus/core/src/workspace-connections/lifecycle.ts +43 -0
- package/corpus/core/src/workspace-connections/store.ts +16 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +46 -3
- package/corpus/templates/analytics/AGENTS.md +9 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +120 -6
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +92 -30
- package/corpus/templates/analytics/actions/update-dashboard.ts +5 -5
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +313 -220
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +9 -6
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +90 -64
- package/corpus/templates/analytics/app/i18n-data.ts +3 -3
- package/corpus/templates/analytics/app/lib/dashboard-list-loading.ts +25 -0
- package/corpus/templates/analytics/app/lib/item-popularity.ts +13 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +8 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +15 -3
- package/corpus/templates/analytics/changelog/2026-07-16-dashboard-filter-defaults-can-now-be-changed-reliably-withou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-16-dashboard-lists-load-in-one-stable-order-without-popping-bet.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-16-fixed-dashboard-filter-dropdown-text-alignment.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-16-recurring-signed-in-users-can-now-be-viewed-as-weekly-bars-b.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-16-the-analytics-agent-can-now-duplicate-a-chart-and-place-the-.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +41 -1
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +47 -1
- package/corpus/templates/analytics/server/lib/session-replay.ts +68 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/analytics/server/routes/sessions/lookup.get.ts +47 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +16 -9
- package/corpus/templates/assets/.agents/skills/creative-context/SKILL.md +86 -0
- package/corpus/templates/assets/AGENTS.md +10 -0
- package/corpus/templates/assets/actions/create-generation-session.ts +59 -1
- package/corpus/templates/assets/actions/edit-image.ts +2 -0
- package/corpus/templates/assets/actions/generate-asset.ts +52 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +55 -0
- package/corpus/templates/assets/actions/generate-image.ts +219 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +14 -0
- package/corpus/templates/assets/actions/refine-image.ts +3 -0
- package/corpus/templates/assets/actions/restyle-image.ts +2 -0
- package/corpus/templates/assets/agent-native.app-skill.json +4 -0
- package/corpus/templates/assets/app/components/layout/Layout.tsx +2 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/assets/app/i18n-data.ts +29 -13
- package/corpus/templates/assets/app/routes/agent.tsx +7 -1
- package/corpus/templates/assets/app/routes/library.tsx +9 -0
- package/corpus/templates/assets/app/routes/settings.tsx +3 -0
- package/corpus/templates/assets/changelog/2026-07-16-creative-context-library.md +6 -0
- package/corpus/templates/assets/package.json +1 -0
- package/corpus/templates/assets/server/lib/image-processing.ts +1 -22
- package/corpus/templates/assets/server/plugins/creative-context.ts +205 -0
- package/corpus/templates/brain/app/i18n-data.ts +3 -3
- package/corpus/templates/brain/app/lib/brain.ts +1 -1
- package/corpus/templates/brain/server/lib/search.ts +17 -67
- package/corpus/templates/calendar/app/i18n-data.ts +3 -3
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +19 -18
- package/corpus/templates/chat/app/i18n/en-US.ts +3 -3
- package/corpus/templates/chat/app/lib/agent-page.tsx +10 -1
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +1 -1
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/cleanup-transcript.ts +35 -4
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-organization.ts +3 -2
- package/corpus/templates/clips/actions/create-recording.ts +4 -1
- package/corpus/templates/clips/actions/finalize-recording.ts +44 -7
- package/corpus/templates/clips/actions/import-loom-recording.ts +4 -1
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +2 -2
- package/corpus/templates/clips/actions/list-meetings.ts +23 -5
- package/corpus/templates/clips/actions/list-organization-state.ts +1 -1
- package/corpus/templates/clips/actions/remove-silences.ts +10 -1
- package/corpus/templates/clips/actions/request-transcript.ts +53 -11
- package/corpus/templates/clips/actions/search-recordings-utils.ts +7 -0
- package/corpus/templates/clips/actions/search-recordings.ts +5 -8
- package/corpus/templates/clips/actions/set-organization-branding.ts +2 -2
- package/corpus/templates/clips/actions/stitch-recordings.ts +10 -5
- package/corpus/templates/clips/actions/update-ai-request-status.ts +38 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +3 -4
- package/corpus/templates/clips/app/components/editor/editor-selection.ts +19 -0
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +10 -10
- package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +1 -2
- package/corpus/templates/clips/app/components/library/library-grid.tsx +101 -93
- package/corpus/templates/clips/app/components/player/video-player.tsx +11 -0
- package/corpus/templates/clips/app/components/workspace/branding-editor.tsx +53 -1
- package/corpus/templates/clips/app/i18n/ar-SA.ts +7 -1
- package/corpus/templates/clips/app/i18n/de-DE.ts +7 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -4
- package/corpus/templates/clips/app/i18n/es-ES.ts +7 -1
- package/corpus/templates/clips/app/i18n/fr-FR.ts +7 -1
- package/corpus/templates/clips/app/i18n/hi-IN.ts +7 -1
- package/corpus/templates/clips/app/i18n/ja-JP.ts +7 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +7 -1
- package/corpus/templates/clips/app/i18n/pt-BR.ts +7 -1
- package/corpus/templates/clips/app/i18n/zh-CN.ts +6 -1
- package/corpus/templates/clips/app/i18n/zh-TW.ts +6 -1
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +5 -9
- package/corpus/templates/clips/app/routes/_app.settings.organization.tsx +6 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +129 -27
- package/corpus/templates/clips/app/routes/record.tsx +23 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -2
- package/corpus/templates/clips/app/routes.ts +3 -1
- package/corpus/templates/clips/changelog/2026-07-16-authorized-viewers-can-open-shared-private-clips-without-a-m.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-camera-bubble-hover-controls-appear-above-the-face.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-chrome-extension-recovers-stale-recorder-state.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-clips-agent-chat-no-longer-shows-a-separate-workspace-header.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-clips-search-now-matches-recording-content-without-requiring.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-declined-meetings-no-longer-show-desktop-reminders.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-failed-desktop-saves-now-show-their-local-recovery-copy.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-long-desktop-clips-now-preserve-complete-playback-synced-aud.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-long-recording-lists-keep-stitch-and-library-actions-visible.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-organization-admins-can-choose-new-recording-visibility.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-remove-silences-now-shows-queued-active-completed-and-failed.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-settings-no-longer-repeats-the-builder-connection-status-in-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-16-transcripts-now-appear-from-native-browser-speech-sooner-wit.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +73 -1
- package/corpus/templates/clips/chrome-extension/src/native-recording-state.ts +32 -0
- package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +248 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +106 -0
- package/corpus/templates/clips/desktop/src/app.tsx +66 -28
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +335 -285
- package/corpus/templates/clips/desktop/src/lib/single-flight.ts +7 -0
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +51 -0
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +2 -2
- package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +10 -2
- package/corpus/templates/clips/desktop/src/styles.css +13 -11
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +8 -9
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +88 -13
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +13 -2
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +241 -6
- package/corpus/templates/clips/server/db/schema.ts +2 -2
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +64 -67
- package/corpus/templates/clips/server/lib/calendar-event-classification.ts +19 -0
- package/corpus/templates/clips/server/lib/google-calendar-client.ts +1 -0
- package/corpus/templates/clips/server/lib/recordings.ts +40 -1
- package/corpus/templates/clips/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/clips/server/plugins/db.ts +2 -2
- package/corpus/templates/clips/server/routes/api/auth/google-calendar.get.ts +1 -1
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +14 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +51 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +1 -64
- package/corpus/templates/clips/shared/media-worker-contract.ts +32 -15
- package/corpus/templates/content/.agents/skills/content/SKILL.md +16 -0
- package/corpus/templates/content/.agents/skills/creative-context/SKILL.md +85 -0
- package/corpus/templates/content/.agents/skills/notion-integration/SKILL.md +8 -0
- package/corpus/templates/content/AGENTS.md +9 -0
- package/corpus/templates/content/actions/create-document.ts +94 -0
- package/corpus/templates/content/actions/edit-document.ts +144 -4
- package/corpus/templates/content/actions/update-document.ts +138 -0
- package/corpus/templates/content/agent-native.app-skill.json +4 -0
- package/corpus/templates/content/app/components/layout/Layout.tsx +2 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/content/app/i18n-data.ts +13 -4
- package/corpus/templates/content/app/routes/_app.agent.tsx +2 -1
- package/corpus/templates/content/app/routes/_app.settings.tsx +3 -0
- package/corpus/templates/content/changelog/2026-07-16-creative-context-library.md +6 -0
- package/corpus/templates/content/package.json +1 -0
- package/corpus/templates/content/server/plugins/creative-context.ts +31 -0
- package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +116 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +16 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
- package/corpus/templates/design/AGENTS.md +6 -0
- package/corpus/templates/design/actions/clone-creative-context-design.ts +176 -0
- package/corpus/templates/design/actions/edit-design.ts +128 -0
- package/corpus/templates/design/actions/generate-design.ts +278 -14
- package/corpus/templates/design/actions/generate-screens.ts +42 -1
- package/corpus/templates/design/agent-native.app-skill.json +4 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +10 -4
- package/corpus/templates/design/app/routes/agent.tsx +2 -1
- package/corpus/templates/design/app/routes/settings.tsx +3 -0
- package/corpus/templates/design/changelog/2026-07-16-creative-context-library.md +6 -0
- package/corpus/templates/design/changelog/2026-07-16-design-agent-chat-no-longer-shows-a-separate-workspace-header.md +6 -0
- package/corpus/templates/design/package.json +1 -0
- package/corpus/templates/design/server/lib/figma-design-context.ts +5 -335
- package/corpus/templates/design/server/lib/figma-node-to-html.ts +1 -1859
- package/corpus/templates/design/server/lib/import-design-files.ts +6 -8
- package/corpus/templates/design/server/plugins/creative-context.ts +31 -0
- package/corpus/templates/design/shared/generation-session.ts +7 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +3 -3
- package/corpus/templates/forms/app/i18n/en-US.ts +3 -3
- package/corpus/templates/macros/app/i18n/en-US.ts +3 -3
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +35 -5
- package/corpus/templates/mail/app/i18n/en-US.ts +3 -3
- package/corpus/templates/mail/changelog/2026-07-16-mail-previews-now-expand-to-show-the-full-message-without-an.md +6 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +30 -11
- package/corpus/templates/plan/AGENTS.md +21 -1
- package/corpus/templates/plan/actions/get-visual-plan.ts +1 -1
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +161 -0
- package/corpus/templates/plan/actions/read-visual-plan-source.ts +1 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +126 -10
- package/corpus/templates/plan/app/hooks/use-plans.ts +2 -0
- package/corpus/templates/plan/app/i18n/en-US.ts +3 -3
- package/corpus/templates/plan/app/pages/PlansPage.tsx +65 -62
- package/corpus/templates/plan/changelog/2026-07-16-local-plan-previews-now-load-annotations-containing-html-lik.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-16-plan-agents-now-prevent-stale-edits-from-overwriting-newer-p.md +6 -0
- package/corpus/templates/plan/server/plan-mdx.ts +75 -2
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +21 -4
- package/corpus/templates/slides/.agents/skills/creative-context/SKILL.md +122 -0
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +11 -0
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +12 -3
- package/corpus/templates/slides/AGENTS.md +6 -0
- package/corpus/templates/slides/actions/add-slide.ts +230 -14
- package/corpus/templates/slides/actions/clone-context-slide.ts +302 -0
- package/corpus/templates/slides/actions/create-deck.ts +130 -0
- package/corpus/templates/slides/actions/extract-pdf.ts +6 -2
- package/corpus/templates/slides/actions/import-file.ts +5 -4
- package/corpus/templates/slides/actions/patch-deck.ts +200 -10
- package/corpus/templates/slides/actions/update-slide.ts +208 -15
- package/corpus/templates/slides/app/components/layout/Layout.tsx +2 -0
- package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +16 -1
- package/corpus/templates/slides/app/i18n/ar-SA.ts +3 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +3 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -3
- package/corpus/templates/slides/app/i18n/es-ES.ts +3 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +3 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +3 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +3 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +3 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +3 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +3 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/slides/app/lib/validate-native-slide-html.ts +176 -0
- package/corpus/templates/slides/app/routes/agent.tsx +7 -1
- package/corpus/templates/slides/app/routes/settings.tsx +3 -0
- package/corpus/templates/slides/changelog/2026-07-16-creative-context-library.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-16-google-slides-native-cloning.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/handlers/import/docx-parser.ts +4 -85
- package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +7 -421
- package/corpus/templates/slides/server/plugins/creative-context.ts +166 -0
- package/dist/agent/engine/credential-errors.d.ts +1 -1
- package/dist/agent/engine/credential-errors.d.ts.map +1 -1
- package/dist/agent/engine/credential-errors.js +2 -2
- package/dist/agent/engine/credential-errors.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +25 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +12 -2
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +1 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +30 -11
- package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +4 -3
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AgentAskPopover.d.ts +11 -0
- package/dist/client/AgentAskPopover.d.ts.map +1 -0
- package/dist/client/AgentAskPopover.js +30 -0
- package/dist/client/AgentAskPopover.js.map +1 -0
- package/dist/client/AgentChatHome.d.ts +1 -1
- package/dist/client/AgentChatHome.d.ts.map +1 -1
- package/dist/client/AgentChatHome.js +2 -2
- package/dist/client/AgentChatHome.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +9 -19
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +119 -80
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +4 -4
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -9
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +49 -189
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-page/AgentContextTab.d.ts.map +1 -1
- package/dist/client/agent-page/AgentContextTab.js +26 -28
- package/dist/client/agent-page/AgentContextTab.js.map +1 -1
- package/dist/client/agent-page/AgentEmptyState.d.ts +13 -0
- package/dist/client/agent-page/AgentEmptyState.d.ts.map +1 -0
- package/dist/client/agent-page/AgentEmptyState.js +6 -0
- package/dist/client/agent-page/AgentEmptyState.js.map +1 -0
- package/dist/client/agent-page/AgentJobsTab.d.ts +1 -1
- package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
- package/dist/client/agent-page/AgentJobsTab.js +90 -58
- package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
- package/dist/client/agent-page/AgentTabFrame.d.ts +12 -0
- package/dist/client/agent-page/AgentTabFrame.d.ts.map +1 -0
- package/dist/client/agent-page/AgentTabFrame.js +7 -0
- package/dist/client/agent-page/AgentTabFrame.js.map +1 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts +9 -1
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.js +135 -99
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
- package/dist/client/agent-page/types.d.ts +1 -1
- package/dist/client/agent-page/types.d.ts.map +1 -1
- package/dist/client/agent-page/types.js.map +1 -1
- package/dist/client/analytics.d.ts +10 -3
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +37 -2
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +10 -3
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +2 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +15 -3
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/dynamic-suggestions.js +3 -3
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/index.d.ts +4 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +5 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +74 -5
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts +11 -2
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +105 -31
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/session-replay.d.ts +28 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +122 -8
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/AutomationsSection.d.ts +3 -1
- package/dist/client/settings/AutomationsSection.d.ts.map +1 -1
- package/dist/client/settings/AutomationsSection.js +16 -6
- package/dist/client/settings/AutomationsSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +3 -3
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts +2 -0
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/client/settings/agent-settings-search.js +2 -2
- package/dist/client/settings/agent-settings-search.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts +2 -0
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/connections/catalog.d.ts +41 -7
- package/dist/connections/catalog.d.ts.map +1 -1
- package/dist/connections/catalog.js +40 -7
- package/dist/connections/catalog.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +17 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +85 -15
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/content-patch.d.ts +4 -0
- package/dist/extensions/content-patch.d.ts.map +1 -1
- package/dist/extensions/content-patch.js +18 -0
- package/dist/extensions/content-patch.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +14 -0
- package/dist/extensions/routes.js.map +1 -1
- package/dist/extensions/store.d.ts +1 -0
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +4 -1
- package/dist/extensions/store.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ingestion/docx.d.ts +16 -0
- package/dist/ingestion/docx.d.ts.map +1 -0
- package/dist/ingestion/docx.js +129 -0
- package/dist/ingestion/docx.js.map +1 -0
- package/dist/ingestion/figma-node-to-html.d.ts +304 -0
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -0
- package/dist/ingestion/figma-node-to-html.js +1292 -0
- package/dist/ingestion/figma-node-to-html.js.map +1 -0
- package/dist/ingestion/figma.d.ts +115 -0
- package/dist/ingestion/figma.d.ts.map +1 -0
- package/dist/ingestion/figma.js +399 -0
- package/dist/ingestion/figma.js.map +1 -0
- package/dist/ingestion/index.d.ts +11 -0
- package/dist/ingestion/index.d.ts.map +1 -0
- package/dist/ingestion/index.js +11 -0
- package/dist/ingestion/index.js.map +1 -0
- package/dist/ingestion/media.d.ts +36 -0
- package/dist/ingestion/media.d.ts.map +1 -0
- package/dist/ingestion/media.js +178 -0
- package/dist/ingestion/media.js.map +1 -0
- package/dist/ingestion/notion.d.ts +12 -0
- package/dist/ingestion/notion.d.ts.map +1 -0
- package/dist/ingestion/notion.js +104 -0
- package/dist/ingestion/notion.js.map +1 -0
- package/dist/ingestion/office.d.ts +45 -0
- package/dist/ingestion/office.d.ts.map +1 -0
- package/dist/ingestion/office.js +141 -0
- package/dist/ingestion/office.js.map +1 -0
- package/dist/ingestion/orchestration.d.ts +52 -0
- package/dist/ingestion/orchestration.d.ts.map +1 -0
- package/dist/ingestion/orchestration.js +89 -0
- package/dist/ingestion/orchestration.js.map +1 -0
- package/dist/ingestion/pptx.d.ts +28 -0
- package/dist/ingestion/pptx.d.ts.map +1 -0
- package/dist/ingestion/pptx.js +255 -0
- package/dist/ingestion/pptx.js.map +1 -0
- package/dist/ingestion/selection.d.ts +20 -0
- package/dist/ingestion/selection.d.ts.map +1 -0
- package/dist/ingestion/selection.js +52 -0
- package/dist/ingestion/selection.js.map +1 -0
- package/dist/ingestion/website.d.ts +27 -0
- package/dist/ingestion/website.d.ts.map +1 -0
- package/dist/ingestion/website.js +100 -0
- package/dist/ingestion/website.js.map +1 -0
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +32 -27
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/localization/default-messages.d.ts +22 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +31 -9
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/hosted-model-experiment.d.ts +39 -0
- package/dist/observability/hosted-model-experiment.d.ts.map +1 -0
- package/dist/observability/hosted-model-experiment.js +90 -0
- package/dist/observability/hosted-model-experiment.js.map +1 -0
- package/dist/observability/routes.d.ts +5 -5
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +5 -3
- package/dist/observability/traces.js.map +1 -1
- package/dist/provider-api/index.d.ts +35 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +339 -8
- package/dist/provider-api/index.js.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +18 -1
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/search-utils/index.d.ts +12 -0
- package/dist/search-utils/index.d.ts.map +1 -0
- package/dist/search-utils/index.js +95 -0
- package/dist/search-utils/index.js.map +1 -0
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +57 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts +8 -0
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +26 -9
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat/framework-prompts.js +2 -2
- package/dist/server/agent-chat/framework-prompts.js.map +1 -1
- package/dist/server/agent-chat/prompt-resources.d.ts +19 -0
- package/dist/server/agent-chat/prompt-resources.d.ts.map +1 -1
- package/dist/server/agent-chat/prompt-resources.js +95 -0
- package/dist/server/agent-chat/prompt-resources.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +7 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +5 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +48 -0
- package/dist/server/workspace-provider-oauth.d.ts.map +1 -0
- package/dist/server/workspace-provider-oauth.js +415 -0
- package/dist/server/workspace-provider-oauth.js.map +1 -0
- package/dist/styles/agent-native.css +9 -0
- package/dist/templates/chat/app/i18n/en-US.ts +3 -3
- package/dist/templates/chat/app/lib/agent-page.tsx +10 -1
- package/dist/templates/default/app/i18n/en-US.ts +3 -3
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +14 -5
- package/dist/templates/workspace-root/.env.example +12 -0
- package/dist/workspace-connections/index.d.ts +1 -0
- package/dist/workspace-connections/index.d.ts.map +1 -1
- package/dist/workspace-connections/index.js +1 -0
- package/dist/workspace-connections/index.js.map +1 -1
- package/dist/workspace-connections/lifecycle.d.ts +16 -0
- package/dist/workspace-connections/lifecycle.d.ts.map +1 -0
- package/dist/workspace-connections/lifecycle.js +14 -0
- package/dist/workspace-connections/lifecycle.js.map +1 -0
- package/dist/workspace-connections/store.d.ts.map +1 -1
- package/dist/workspace-connections/store.js +16 -0
- package/dist/workspace-connections/store.js.map +1 -1
- package/package.json +10 -3
- package/src/templates/chat/app/i18n/en-US.ts +3 -3
- package/src/templates/chat/app/lib/agent-page.spec.tsx +10 -1
- package/src/templates/chat/app/lib/agent-page.tsx +10 -1
- package/src/templates/default/app/i18n/en-US.ts +3 -3
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +14 -5
- package/src/templates/workspace-root/.env.example +12 -0
|
@@ -1318,13 +1318,68 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
1318
1318
|
// final post instead of re-uploading the whole file.
|
|
1319
1319
|
#[cfg(target_os = "macos")]
|
|
1320
1320
|
if let Some(live) = session.live_upload.take() {
|
|
1321
|
+
let verified_duration_ms = match probe_local_media_duration_ms(&saved.file_path) {
|
|
1322
|
+
Ok(media_duration_ms)
|
|
1323
|
+
if media_durations_materially_match(duration_ms, media_duration_ms) =>
|
|
1324
|
+
{
|
|
1325
|
+
media_duration_ms
|
|
1326
|
+
}
|
|
1327
|
+
Ok(media_duration_ms) => {
|
|
1328
|
+
let error = format!(
|
|
1329
|
+
"Clip may be incomplete. The local media duration ({media_duration_ms} ms) did not match the recorded duration ({duration_ms} ms)."
|
|
1330
|
+
);
|
|
1331
|
+
live.ctrl.cancelled.store(true, Ordering::SeqCst);
|
|
1332
|
+
let _ = live.result_rx.await;
|
|
1333
|
+
saved.last_attempt_at = Some(now_iso());
|
|
1334
|
+
saved.last_error = Some(error.clone());
|
|
1335
|
+
saved.retry_count = saved.retry_count.saturating_add(1);
|
|
1336
|
+
let _ = write_saved_recording_metadata(&app, &saved);
|
|
1337
|
+
emit_native_upload_progress(&app, "failed", "Upload paused", None, None);
|
|
1338
|
+
let error = format!(
|
|
1339
|
+
"{error} The clip was saved locally and can be retried from the Clips menu."
|
|
1340
|
+
);
|
|
1341
|
+
emit_native_upload_finished(
|
|
1342
|
+
&app,
|
|
1343
|
+
&server_url,
|
|
1344
|
+
&recording_id,
|
|
1345
|
+
false,
|
|
1346
|
+
Some(error.clone()),
|
|
1347
|
+
Some(&saved.file_path),
|
|
1348
|
+
);
|
|
1349
|
+
return Err(error);
|
|
1350
|
+
}
|
|
1351
|
+
Err(probe_error) => {
|
|
1352
|
+
let error = format!(
|
|
1353
|
+
"Clip may be incomplete. The local media duration could not be verified ({probe_error})."
|
|
1354
|
+
);
|
|
1355
|
+
live.ctrl.cancelled.store(true, Ordering::SeqCst);
|
|
1356
|
+
let _ = live.result_rx.await;
|
|
1357
|
+
saved.last_attempt_at = Some(now_iso());
|
|
1358
|
+
saved.last_error = Some(error.clone());
|
|
1359
|
+
saved.retry_count = saved.retry_count.saturating_add(1);
|
|
1360
|
+
let _ = write_saved_recording_metadata(&app, &saved);
|
|
1361
|
+
emit_native_upload_progress(&app, "failed", "Upload paused", None, None);
|
|
1362
|
+
let error = format!(
|
|
1363
|
+
"{error} The clip was saved locally and can be retried from the Clips menu."
|
|
1364
|
+
);
|
|
1365
|
+
emit_native_upload_finished(
|
|
1366
|
+
&app,
|
|
1367
|
+
&server_url,
|
|
1368
|
+
&recording_id,
|
|
1369
|
+
false,
|
|
1370
|
+
Some(error.clone()),
|
|
1371
|
+
Some(&saved.file_path),
|
|
1372
|
+
);
|
|
1373
|
+
return Err(error);
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1321
1376
|
eprintln!(
|
|
1322
|
-
"[live-upload] stop: signalling finalize for {recording_id} (
|
|
1377
|
+
"[live-upload] stop: signalling finalize for {recording_id} (measured_duration_ms={verified_duration_ms})"
|
|
1323
1378
|
);
|
|
1324
1379
|
emit_native_upload_progress(&app, "uploading", "Uploading clip", None, None);
|
|
1325
1380
|
live.ctrl
|
|
1326
1381
|
.duration_ms
|
|
1327
|
-
.store(
|
|
1382
|
+
.store(verified_duration_ms as u64, Ordering::SeqCst);
|
|
1328
1383
|
live.ctrl.finalize.store(true, Ordering::SeqCst);
|
|
1329
1384
|
let result = match live.result_rx.await {
|
|
1330
1385
|
Ok(inner) => inner,
|
|
@@ -1343,7 +1398,7 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
1343
1398
|
emit_native_upload_finished(&app, &server_url, &recording_id, true, None, None);
|
|
1344
1399
|
Ok(NativeFullscreenUploadResult {
|
|
1345
1400
|
recording_id,
|
|
1346
|
-
duration_ms,
|
|
1401
|
+
duration_ms: verified_duration_ms,
|
|
1347
1402
|
width: session.width,
|
|
1348
1403
|
height: session.height,
|
|
1349
1404
|
bytes,
|
|
@@ -3830,6 +3885,75 @@ fn concat_saved_recording_segments(_segments: &[PathBuf], _output: &Path) -> Res
|
|
|
3830
3885
|
Err("Segment concat is only available on macOS.".into())
|
|
3831
3886
|
}
|
|
3832
3887
|
|
|
3888
|
+
#[cfg(target_os = "macos")]
|
|
3889
|
+
fn probe_local_media_duration_ms(path: &Path) -> Result<u128, String> {
|
|
3890
|
+
use std::ffi::CString;
|
|
3891
|
+
|
|
3892
|
+
use objc2::encode::{Encode, Encoding, RefEncode};
|
|
3893
|
+
use objc2::rc::Retained;
|
|
3894
|
+
use objc2::runtime::{AnyClass, AnyObject};
|
|
3895
|
+
use objc2::{class, msg_send};
|
|
3896
|
+
|
|
3897
|
+
#[repr(C)]
|
|
3898
|
+
#[derive(Copy, Clone)]
|
|
3899
|
+
struct CMTime {
|
|
3900
|
+
value: i64,
|
|
3901
|
+
timescale: i32,
|
|
3902
|
+
flags: u32,
|
|
3903
|
+
epoch: i64,
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
unsafe impl RefEncode for CMTime {
|
|
3907
|
+
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
|
|
3908
|
+
}
|
|
3909
|
+
unsafe impl Encode for CMTime {
|
|
3910
|
+
const ENCODING: Encoding = Encoding::Struct(
|
|
3911
|
+
"CMTime",
|
|
3912
|
+
&[i64::ENCODING, i32::ENCODING, u32::ENCODING, i64::ENCODING],
|
|
3913
|
+
);
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
let path_str = path
|
|
3917
|
+
.to_str()
|
|
3918
|
+
.ok_or_else(|| format!("recording path is not valid UTF-8: {}", path.display()))?;
|
|
3919
|
+
let path_cstr = CString::new(path_str)
|
|
3920
|
+
.map_err(|_| format!("recording path contains a null byte: {}", path.display()))?;
|
|
3921
|
+
let asset_class_name = CString::new("AVURLAsset").expect("static class name");
|
|
3922
|
+
let asset_class =
|
|
3923
|
+
AnyClass::get(&asset_class_name).ok_or_else(|| "AVURLAsset is unavailable".to_string())?;
|
|
3924
|
+
|
|
3925
|
+
unsafe {
|
|
3926
|
+
let string_allocated: *mut AnyObject = msg_send![class!(NSString), alloc];
|
|
3927
|
+
let string_raw: *mut AnyObject =
|
|
3928
|
+
msg_send![string_allocated, initWithUTF8String: path_cstr.as_ptr()];
|
|
3929
|
+
let path_string = Retained::<AnyObject>::from_raw(string_raw)
|
|
3930
|
+
.ok_or_else(|| format!("could not represent recording path: {}", path.display()))?;
|
|
3931
|
+
let url_raw: *mut AnyObject = msg_send![class!(NSURL), fileURLWithPath: &*path_string];
|
|
3932
|
+
let url = Retained::<AnyObject>::retain(url_raw)
|
|
3933
|
+
.ok_or_else(|| format!("could not open recording path: {}", path.display()))?;
|
|
3934
|
+
let asset_raw: *mut AnyObject =
|
|
3935
|
+
msg_send![asset_class, URLAssetWithURL: &*url, options: std::ptr::null::<AnyObject>()];
|
|
3936
|
+
let asset = Retained::<AnyObject>::retain(asset_raw)
|
|
3937
|
+
.ok_or_else(|| format!("could not inspect local recording: {}", path.display()))?;
|
|
3938
|
+
let duration: CMTime = msg_send![&*asset, duration];
|
|
3939
|
+
if duration.flags & 1 == 0 || duration.timescale <= 0 || duration.value <= 0 {
|
|
3940
|
+
return Err(format!(
|
|
3941
|
+
"Clip may be incomplete. The local media duration could not be verified; the local backup was kept ({})",
|
|
3942
|
+
path.display()
|
|
3943
|
+
));
|
|
3944
|
+
}
|
|
3945
|
+
let duration_ms = (i128::from(duration.value) * 1_000)
|
|
3946
|
+
.checked_div(i128::from(duration.timescale))
|
|
3947
|
+
.and_then(|value| u128::try_from(value).ok())
|
|
3948
|
+
.filter(|value| *value > 0)
|
|
3949
|
+
.ok_or_else(|| {
|
|
3950
|
+
"Clip may be incomplete. The local media duration was invalid; the local backup was kept."
|
|
3951
|
+
.to_string()
|
|
3952
|
+
})?;
|
|
3953
|
+
Ok(duration_ms)
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3833
3957
|
async fn upload_prepared_recording_file(
|
|
3834
3958
|
app: &AppHandle,
|
|
3835
3959
|
prepared: &PreparedRecordingFile,
|
|
@@ -3844,6 +3968,19 @@ async fn upload_prepared_recording_file(
|
|
|
3844
3968
|
has_audio: bool,
|
|
3845
3969
|
has_camera: bool,
|
|
3846
3970
|
) -> Result<NativeFullscreenUploadResult, String> {
|
|
3971
|
+
#[cfg(target_os = "macos")]
|
|
3972
|
+
let verified_local_duration_ms = {
|
|
3973
|
+
let media_duration_ms = probe_local_media_duration_ms(&prepared.path)?;
|
|
3974
|
+
if !media_durations_materially_match(duration_ms, media_duration_ms) {
|
|
3975
|
+
return Err(format!(
|
|
3976
|
+
"Clip may be incomplete. The local media duration ({media_duration_ms} ms) did not match the recorded duration ({duration_ms} ms); the local backup was kept."
|
|
3977
|
+
));
|
|
3978
|
+
}
|
|
3979
|
+
media_duration_ms
|
|
3980
|
+
};
|
|
3981
|
+
#[cfg(not(target_os = "macos"))]
|
|
3982
|
+
let verified_local_duration_ms = duration_ms;
|
|
3983
|
+
|
|
3847
3984
|
let total_bytes = prepared.bytes;
|
|
3848
3985
|
let total_bytes_usize = usize::try_from(total_bytes)
|
|
3849
3986
|
.map_err(|_| "Native recording is too large to upload on this system.".to_string())?;
|
|
@@ -3892,6 +4029,7 @@ async fn upload_prepared_recording_file(
|
|
|
3892
4029
|
has_camera,
|
|
3893
4030
|
upload_mode,
|
|
3894
4031
|
false,
|
|
4032
|
+
None,
|
|
3895
4033
|
buffer,
|
|
3896
4034
|
)
|
|
3897
4035
|
.await?;
|
|
@@ -3926,7 +4064,7 @@ async fn upload_prepared_recording_file(
|
|
|
3926
4064
|
streaming_full_chunks,
|
|
3927
4065
|
total_posts,
|
|
3928
4066
|
true,
|
|
3929
|
-
Some(
|
|
4067
|
+
Some(verified_local_duration_ms),
|
|
3930
4068
|
&prepared.mime_type,
|
|
3931
4069
|
width,
|
|
3932
4070
|
height,
|
|
@@ -3934,6 +4072,7 @@ async fn upload_prepared_recording_file(
|
|
|
3934
4072
|
has_camera,
|
|
3935
4073
|
upload_mode,
|
|
3936
4074
|
prepared.locally_transcoded,
|
|
4075
|
+
Some(total_bytes),
|
|
3937
4076
|
final_body,
|
|
3938
4077
|
)
|
|
3939
4078
|
.await?;
|
|
@@ -3964,6 +4103,7 @@ async fn upload_prepared_recording_file(
|
|
|
3964
4103
|
has_camera,
|
|
3965
4104
|
upload_mode,
|
|
3966
4105
|
false,
|
|
4106
|
+
None,
|
|
3967
4107
|
buffer,
|
|
3968
4108
|
)
|
|
3969
4109
|
.await?;
|
|
@@ -3992,7 +4132,7 @@ async fn upload_prepared_recording_file(
|
|
|
3992
4132
|
total_chunks,
|
|
3993
4133
|
total_posts,
|
|
3994
4134
|
true,
|
|
3995
|
-
Some(
|
|
4135
|
+
Some(verified_local_duration_ms),
|
|
3996
4136
|
&prepared.mime_type,
|
|
3997
4137
|
width,
|
|
3998
4138
|
height,
|
|
@@ -4000,6 +4140,7 @@ async fn upload_prepared_recording_file(
|
|
|
4000
4140
|
has_camera,
|
|
4001
4141
|
upload_mode,
|
|
4002
4142
|
prepared.locally_transcoded,
|
|
4143
|
+
Some(total_bytes),
|
|
4003
4144
|
Vec::new(),
|
|
4004
4145
|
)
|
|
4005
4146
|
.await?;
|
|
@@ -4008,7 +4149,7 @@ async fn upload_prepared_recording_file(
|
|
|
4008
4149
|
emit_native_upload_progress(app, "opening", "Uploading clip", None, Some(1.0));
|
|
4009
4150
|
Ok(NativeFullscreenUploadResult {
|
|
4010
4151
|
recording_id,
|
|
4011
|
-
duration_ms,
|
|
4152
|
+
duration_ms: verified_local_duration_ms,
|
|
4012
4153
|
width,
|
|
4013
4154
|
height,
|
|
4014
4155
|
bytes: total_bytes,
|
|
@@ -4127,6 +4268,7 @@ async fn send_upload_post(
|
|
|
4127
4268
|
has_camera: bool,
|
|
4128
4269
|
upload_mode: NativeUploadMode,
|
|
4129
4270
|
locally_transcoded: bool,
|
|
4271
|
+
expected_source_bytes: Option<u64>,
|
|
4130
4272
|
body: Vec<u8>,
|
|
4131
4273
|
) -> Result<(), String> {
|
|
4132
4274
|
let body_len = body.len();
|
|
@@ -4177,6 +4319,17 @@ async fn send_upload_post(
|
|
|
4177
4319
|
body.chars().take(400).collect::<String>()
|
|
4178
4320
|
));
|
|
4179
4321
|
}
|
|
4322
|
+
if is_final {
|
|
4323
|
+
verify_native_finalize_receipt(
|
|
4324
|
+
&body,
|
|
4325
|
+
expected_source_bytes.ok_or_else(|| {
|
|
4326
|
+
"Clip may be incomplete: final upload had no local byte count".to_string()
|
|
4327
|
+
})?,
|
|
4328
|
+
duration_ms.ok_or_else(|| {
|
|
4329
|
+
"Clip may be incomplete: final upload had no local duration".to_string()
|
|
4330
|
+
})?,
|
|
4331
|
+
)?;
|
|
4332
|
+
}
|
|
4180
4333
|
eprintln!(
|
|
4181
4334
|
"[clips-tray] native upload post ok recording={recording_id} mode={} index={index}/{total} final={is_final}",
|
|
4182
4335
|
upload_mode.label()
|
|
@@ -4184,6 +4337,88 @@ async fn send_upload_post(
|
|
|
4184
4337
|
Ok(())
|
|
4185
4338
|
}
|
|
4186
4339
|
|
|
4340
|
+
#[derive(Deserialize)]
|
|
4341
|
+
#[serde(rename_all = "camelCase")]
|
|
4342
|
+
struct NativeFinalizeReceipt {
|
|
4343
|
+
ok: bool,
|
|
4344
|
+
finalized: bool,
|
|
4345
|
+
status: Option<String>,
|
|
4346
|
+
source_size_bytes: Option<u64>,
|
|
4347
|
+
duration_ms: Option<u128>,
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
fn verify_native_finalize_receipt(
|
|
4351
|
+
body: &str,
|
|
4352
|
+
expected_source_bytes: u64,
|
|
4353
|
+
expected_duration_ms: u128,
|
|
4354
|
+
) -> Result<(), String> {
|
|
4355
|
+
let receipt: NativeFinalizeReceipt = serde_json::from_str(body).map_err(|_| {
|
|
4356
|
+
"Clip may be incomplete. The final upload receipt was unreadable; the local backup was kept."
|
|
4357
|
+
.to_string()
|
|
4358
|
+
})?;
|
|
4359
|
+
if !receipt.ok || !receipt.finalized || receipt.status.as_deref() != Some("ready") {
|
|
4360
|
+
return Err(
|
|
4361
|
+
"Clip may be incomplete. Finalization was not confirmed; the local backup was kept."
|
|
4362
|
+
.to_string(),
|
|
4363
|
+
);
|
|
4364
|
+
}
|
|
4365
|
+
if receipt.source_size_bytes != Some(expected_source_bytes) {
|
|
4366
|
+
return Err(format!(
|
|
4367
|
+
"Clip may be incomplete. The server did not confirm all {expected_source_bytes} source bytes; the local backup was kept."
|
|
4368
|
+
));
|
|
4369
|
+
}
|
|
4370
|
+
let actual_duration_ms = receipt.duration_ms.unwrap_or(0);
|
|
4371
|
+
if !media_durations_materially_match(expected_duration_ms, actual_duration_ms) {
|
|
4372
|
+
return Err(
|
|
4373
|
+
"Clip may be incomplete. The uploaded duration did not match the local recording; the local backup was kept."
|
|
4374
|
+
.to_string(),
|
|
4375
|
+
);
|
|
4376
|
+
}
|
|
4377
|
+
Ok(())
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
fn media_durations_materially_match(expected_ms: u128, actual_ms: u128) -> bool {
|
|
4381
|
+
if expected_ms == 0 || actual_ms == 0 {
|
|
4382
|
+
return false;
|
|
4383
|
+
}
|
|
4384
|
+
let tolerance_ms = 5_000_u128.max(expected_ms / 50);
|
|
4385
|
+
actual_ms.abs_diff(expected_ms) <= tolerance_ms
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
#[cfg(test)]
|
|
4389
|
+
mod native_finalize_receipt_tests {
|
|
4390
|
+
use super::{media_durations_materially_match, verify_native_finalize_receipt};
|
|
4391
|
+
|
|
4392
|
+
#[test]
|
|
4393
|
+
fn compares_measured_media_duration_with_a_bounded_tolerance() {
|
|
4394
|
+
assert!(media_durations_materially_match(1_592_773, 1_593_259));
|
|
4395
|
+
assert!(media_durations_materially_match(10_000, 15_000));
|
|
4396
|
+
assert!(!media_durations_materially_match(10_000, 15_001));
|
|
4397
|
+
assert!(!media_durations_materially_match(0, 10_000));
|
|
4398
|
+
assert!(!media_durations_materially_match(10_000, 0));
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
#[test]
|
|
4402
|
+
fn accepts_only_matching_ready_receipts() {
|
|
4403
|
+
let ready = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":581614005,"durationMs":1593259}"#;
|
|
4404
|
+
assert!(verify_native_finalize_receipt(ready, 581_614_005, 1_592_773).is_ok());
|
|
4405
|
+
|
|
4406
|
+
let short = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":581614005,"durationMs":483000}"#;
|
|
4407
|
+
assert!(
|
|
4408
|
+
verify_native_finalize_receipt(short, 581_614_005, 1_592_773)
|
|
4409
|
+
.unwrap_err()
|
|
4410
|
+
.contains("duration")
|
|
4411
|
+
);
|
|
4412
|
+
|
|
4413
|
+
let partial = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":183000000,"durationMs":1592773}"#;
|
|
4414
|
+
assert!(
|
|
4415
|
+
verify_native_finalize_receipt(partial, 581_614_005, 1_592_773)
|
|
4416
|
+
.unwrap_err()
|
|
4417
|
+
.contains("581614005")
|
|
4418
|
+
);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4187
4422
|
fn upload_url(
|
|
4188
4423
|
server_url: &str,
|
|
4189
4424
|
recording_id: &str,
|
|
@@ -37,7 +37,7 @@ export const organizationSettings = table("organization_settings", {
|
|
|
37
37
|
enum: ["private", "org", "public"],
|
|
38
38
|
})
|
|
39
39
|
.notNull()
|
|
40
|
-
.default("
|
|
40
|
+
.default("public"),
|
|
41
41
|
createdAt: text("created_at").notNull().default(now()),
|
|
42
42
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
43
43
|
});
|
|
@@ -52,7 +52,7 @@ export const workspaces = table("workspaces", {
|
|
|
52
52
|
enum: ["private", "org", "public"],
|
|
53
53
|
})
|
|
54
54
|
.notNull()
|
|
55
|
-
.default("
|
|
55
|
+
.default("public"),
|
|
56
56
|
createdAt: text("created_at").notNull().default(now()),
|
|
57
57
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
58
58
|
...ownableColumns(),
|
|
@@ -25,7 +25,6 @@ import { ownerEmailMatches } from "./recordings.js";
|
|
|
25
25
|
|
|
26
26
|
const BUILDER_CDN_HOST_RE = /^cdn(?:-qa)?\.builder\.io$/i;
|
|
27
27
|
const DEFAULT_TRIGGER_TIMEOUT_MS = 45_000;
|
|
28
|
-
const DEFAULT_PROBE_TIMEOUT_MS = 10_000;
|
|
29
28
|
const DEFAULT_MAX_SOURCE_BYTES = 512 * 1024 * 1024;
|
|
30
29
|
const MAX_TRIGGER_ATTEMPTS = 5;
|
|
31
30
|
const TRIGGERED_POLL_DELAY_MS = 5 * 60 * 1000;
|
|
@@ -102,13 +101,6 @@ function triggerTimeoutMs(): number {
|
|
|
102
101
|
);
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
function probeTimeoutMs(): number {
|
|
106
|
-
return envNumber(
|
|
107
|
-
"CLIPS_BUILDER_COMPRESSION_PROBE_TIMEOUT_MS",
|
|
108
|
-
DEFAULT_PROBE_TIMEOUT_MS,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
104
|
function maxSourceBytes(): number {
|
|
113
105
|
return envNumber(
|
|
114
106
|
"CLIPS_BUILDER_BACKGROUND_COMPRESSION_MAX_BYTES",
|
|
@@ -162,14 +154,6 @@ function parsePositiveNumber(value: unknown): number | undefined {
|
|
|
162
154
|
return undefined;
|
|
163
155
|
}
|
|
164
156
|
|
|
165
|
-
function headerByteLength(headers: Headers): number | undefined {
|
|
166
|
-
const length = parsePositiveNumber(headers.get("content-length"));
|
|
167
|
-
if (length) return length;
|
|
168
|
-
const range = headers.get("content-range") ?? "";
|
|
169
|
-
const total = range.match(/\/(\d+)$/)?.[1];
|
|
170
|
-
return parsePositiveNumber(total);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
157
|
function timeoutError(label: string, timeoutMs: number): Error {
|
|
174
158
|
const err = new Error(`${label} timed out after ${timeoutMs}ms`);
|
|
175
159
|
err.name = "AbortError";
|
|
@@ -358,38 +342,13 @@ export function builderCompressedMediaUrl(sourceUrl: string): string | null {
|
|
|
358
342
|
return extractBuilderMediaTarget(sourceUrl)?.compressedUrl ?? null;
|
|
359
343
|
}
|
|
360
344
|
|
|
361
|
-
async function probeCompressedMedia(
|
|
362
|
-
compressedUrl: string,
|
|
363
|
-
): Promise<{ exists: boolean; sizeBytes?: number }> {
|
|
364
|
-
const timeoutMs = probeTimeoutMs();
|
|
365
|
-
let res = await fetchWithTimeout(
|
|
366
|
-
compressedUrl,
|
|
367
|
-
{ method: "HEAD" },
|
|
368
|
-
timeoutMs,
|
|
369
|
-
"Builder compressed-media probe",
|
|
370
|
-
);
|
|
371
|
-
if (res.status === 405) {
|
|
372
|
-
res = await fetchWithTimeout(
|
|
373
|
-
compressedUrl,
|
|
374
|
-
{ method: "GET", headers: { Range: "bytes=0-0" } },
|
|
375
|
-
timeoutMs,
|
|
376
|
-
"Builder compressed-media probe",
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
if (
|
|
380
|
-
(res.status >= 200 && res.status < 300) ||
|
|
381
|
-
res.status === 304 ||
|
|
382
|
-
res.status === 206
|
|
383
|
-
) {
|
|
384
|
-
return { exists: true, sizeBytes: headerByteLength(res.headers) };
|
|
385
|
-
}
|
|
386
|
-
if ([403, 404, 416].includes(res.status)) return { exists: false };
|
|
387
|
-
throw new Error(`Compressed media probe failed (${res.status})`);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
345
|
async function triggerBuilderCompression(
|
|
391
346
|
state: BuilderMediaCompressionState,
|
|
392
|
-
): Promise<
|
|
347
|
+
): Promise<{
|
|
348
|
+
url: string;
|
|
349
|
+
durationMs?: number;
|
|
350
|
+
sizeBytes?: number;
|
|
351
|
+
} | null> {
|
|
393
352
|
const privateKey = await resolveBuilderPrivateKey();
|
|
394
353
|
if (!privateKey) {
|
|
395
354
|
throw new Error("Builder private key is not configured");
|
|
@@ -414,8 +373,18 @@ async function triggerBuilderCompression(
|
|
|
414
373
|
`Builder media compression trigger failed (${res.status}): ${body || res.statusText}`,
|
|
415
374
|
);
|
|
416
375
|
}
|
|
417
|
-
const json = (await res.json().catch(() => null)) as {
|
|
418
|
-
|
|
376
|
+
const json = (await res.json().catch(() => null)) as {
|
|
377
|
+
url?: string;
|
|
378
|
+
durationMs?: unknown;
|
|
379
|
+
sizeBytes?: unknown;
|
|
380
|
+
} | null;
|
|
381
|
+
return typeof json?.url === "string"
|
|
382
|
+
? {
|
|
383
|
+
url: json.url,
|
|
384
|
+
durationMs: parsePositiveNumber(json.durationMs),
|
|
385
|
+
sizeBytes: parsePositiveNumber(json.sizeBytes),
|
|
386
|
+
}
|
|
387
|
+
: null;
|
|
419
388
|
}
|
|
420
389
|
|
|
421
390
|
async function enqueueWorkerCompression(
|
|
@@ -499,10 +468,14 @@ async function swapRecordingToCompressed(
|
|
|
499
468
|
state: BuilderMediaCompressionState,
|
|
500
469
|
compressedUrl: string,
|
|
501
470
|
sizeBytes?: number,
|
|
471
|
+
durationMs?: number,
|
|
502
472
|
): Promise<BuilderMediaCompressionState> {
|
|
503
473
|
const db = getDb();
|
|
504
474
|
const [row] = await db
|
|
505
|
-
.select({
|
|
475
|
+
.select({
|
|
476
|
+
videoUrl: schema.recordings.videoUrl,
|
|
477
|
+
durationMs: schema.recordings.durationMs,
|
|
478
|
+
})
|
|
506
479
|
.from(schema.recordings)
|
|
507
480
|
.where(
|
|
508
481
|
and(
|
|
@@ -516,6 +489,18 @@ async function swapRecordingToCompressed(
|
|
|
516
489
|
return markCompressionFailed(state, "Recording row no longer exists");
|
|
517
490
|
}
|
|
518
491
|
|
|
492
|
+
if (
|
|
493
|
+
!durationMs ||
|
|
494
|
+
!mediaDurationsMateriallyMatch(row.durationMs, durationMs)
|
|
495
|
+
) {
|
|
496
|
+
return markCompressionFailed(
|
|
497
|
+
state,
|
|
498
|
+
durationMs
|
|
499
|
+
? `Compressed media duration mismatch (${durationMs}ms output vs ${row.durationMs}ms source)`
|
|
500
|
+
: "Compressed media completion did not include a verified duration",
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
519
504
|
if (row.videoUrl === compressedUrl) {
|
|
520
505
|
return writeCompressionState(state, {
|
|
521
506
|
status: "ready",
|
|
@@ -587,11 +572,6 @@ async function processMediaWorkerState(
|
|
|
587
572
|
if (!worker) return state;
|
|
588
573
|
if (!isDue(state)) return state;
|
|
589
574
|
|
|
590
|
-
const probe = await probeCompressedMedia(worker.outputUrl).catch(() => null);
|
|
591
|
-
if (probe?.exists) {
|
|
592
|
-
return swapRecordingToCompressed(state, worker.outputUrl, probe.sizeBytes);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
575
|
if (worker.attempts >= MEDIA_WORKER_MAX_ENQUEUE_ATTEMPTS) {
|
|
596
576
|
return markCompressionFailed(
|
|
597
577
|
state,
|
|
@@ -652,15 +632,6 @@ async function processCompressionState(
|
|
|
652
632
|
});
|
|
653
633
|
}
|
|
654
634
|
|
|
655
|
-
const probe = await probeCompressedMedia(state.compressedUrl);
|
|
656
|
-
if (probe.exists) {
|
|
657
|
-
return swapRecordingToCompressed(
|
|
658
|
-
state,
|
|
659
|
-
state.compressedUrl,
|
|
660
|
-
probe.sizeBytes,
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
635
|
if (state.status === "triggered" && state.lastTriggeredAt) {
|
|
665
636
|
const lastTriggeredAt = new Date(state.lastTriggeredAt).getTime();
|
|
666
637
|
if (
|
|
@@ -675,9 +646,14 @@ async function processCompressionState(
|
|
|
675
646
|
|
|
676
647
|
const attempts = state.attempts + 1;
|
|
677
648
|
try {
|
|
678
|
-
const
|
|
679
|
-
if (
|
|
680
|
-
return swapRecordingToCompressed(
|
|
649
|
+
const compressed = await triggerBuilderCompression(state);
|
|
650
|
+
if (compressed) {
|
|
651
|
+
return swapRecordingToCompressed(
|
|
652
|
+
state,
|
|
653
|
+
compressed.url,
|
|
654
|
+
compressed.sizeBytes,
|
|
655
|
+
compressed.durationMs,
|
|
656
|
+
);
|
|
681
657
|
}
|
|
682
658
|
return writeCompressionState(state, {
|
|
683
659
|
status: "triggered",
|
|
@@ -920,7 +896,12 @@ export async function applyMediaWorkerCallback(
|
|
|
920
896
|
};
|
|
921
897
|
}
|
|
922
898
|
|
|
923
|
-
const next = await swapRecordingToCompressed(
|
|
899
|
+
const next = await swapRecordingToCompressed(
|
|
900
|
+
state,
|
|
901
|
+
outputUrl,
|
|
902
|
+
undefined,
|
|
903
|
+
callback.durationMs,
|
|
904
|
+
);
|
|
924
905
|
if (next.status !== "ready") {
|
|
925
906
|
return {
|
|
926
907
|
ok: false,
|
|
@@ -934,6 +915,22 @@ export async function applyMediaWorkerCallback(
|
|
|
934
915
|
);
|
|
935
916
|
}
|
|
936
917
|
|
|
918
|
+
export function mediaDurationsMateriallyMatch(
|
|
919
|
+
expectedDurationMs: number,
|
|
920
|
+
actualDurationMs: number,
|
|
921
|
+
): boolean {
|
|
922
|
+
if (
|
|
923
|
+
!Number.isFinite(expectedDurationMs) ||
|
|
924
|
+
!Number.isFinite(actualDurationMs) ||
|
|
925
|
+
expectedDurationMs <= 0 ||
|
|
926
|
+
actualDurationMs <= 0
|
|
927
|
+
) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
const toleranceMs = Math.max(5_000, expectedDurationMs * 0.02);
|
|
931
|
+
return Math.abs(expectedDurationMs - actualDurationMs) <= toleranceMs;
|
|
932
|
+
}
|
|
933
|
+
|
|
937
934
|
export async function runBuilderMediaCompressionSweepOnce(): Promise<void> {
|
|
938
935
|
const exec = getDbExec();
|
|
939
936
|
const { rows } = await exec.execute({
|
|
@@ -55,6 +55,25 @@ function normalizeEmail(email: string | null | undefined): string | null {
|
|
|
55
55
|
return trimmed || null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function isDeclinedCalendarEvent(args: {
|
|
59
|
+
account: CalendarAccountForEventClassification;
|
|
60
|
+
event: CalendarEvent;
|
|
61
|
+
currentUserEmail?: string | null;
|
|
62
|
+
}): boolean {
|
|
63
|
+
const selfEmails = new Set(
|
|
64
|
+
[args.currentUserEmail, args.account.email, args.account.ownerEmail]
|
|
65
|
+
.map(normalizeEmail)
|
|
66
|
+
.filter((email): email is string => Boolean(email)),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (args.event.attendees ?? []).some((attendee) => {
|
|
70
|
+
if (attendee.responseStatus !== "declined") return false;
|
|
71
|
+
if (attendee.self === true) return true;
|
|
72
|
+
const email = normalizeEmail(attendee.email);
|
|
73
|
+
return Boolean(email && selfEmails.has(email));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
58
77
|
export function isPersonalSoloCalendarEvent(args: {
|
|
59
78
|
account: CalendarAccountForEventClassification;
|
|
60
79
|
event: CalendarEvent;
|
|
@@ -60,6 +60,45 @@ export async function getEventOwnerEmail(event: H3Event): Promise<string> {
|
|
|
60
60
|
|
|
61
61
|
export type OrganizationAccessRole = "owner" | "admin" | "member";
|
|
62
62
|
|
|
63
|
+
export type RecordingVisibility = "private" | "org" | "public";
|
|
64
|
+
|
|
65
|
+
export const DEFAULT_RECORDING_VISIBILITY: RecordingVisibility = "public";
|
|
66
|
+
|
|
67
|
+
export function isRecordingVisibility(
|
|
68
|
+
value: unknown,
|
|
69
|
+
): value is RecordingVisibility {
|
|
70
|
+
return value === "private" || value === "org" || value === "public";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolveRecordingVisibility(
|
|
74
|
+
explicit: RecordingVisibility | null | undefined,
|
|
75
|
+
configured: unknown,
|
|
76
|
+
): RecordingVisibility {
|
|
77
|
+
if (explicit) return explicit;
|
|
78
|
+
return isRecordingVisibility(configured)
|
|
79
|
+
? configured
|
|
80
|
+
: DEFAULT_RECORDING_VISIBILITY;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function getOrganizationDefaultVisibility(
|
|
84
|
+
organizationId: string | null | undefined,
|
|
85
|
+
): Promise<RecordingVisibility> {
|
|
86
|
+
if (!organizationId) return DEFAULT_RECORDING_VISIBILITY;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const [row] = await getDb()
|
|
90
|
+
.select({
|
|
91
|
+
defaultVisibility: schema.organizationSettings.defaultVisibility,
|
|
92
|
+
})
|
|
93
|
+
.from(schema.organizationSettings)
|
|
94
|
+
.where(eq(schema.organizationSettings.organizationId, organizationId))
|
|
95
|
+
.limit(1);
|
|
96
|
+
return resolveRecordingVisibility(undefined, row?.defaultVisibility);
|
|
97
|
+
} catch {
|
|
98
|
+
return DEFAULT_RECORDING_VISIBILITY;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
63
102
|
const ORG_ROLE_RANK: Record<OrganizationAccessRole, number> = {
|
|
64
103
|
member: 1,
|
|
65
104
|
admin: 2,
|
|
@@ -231,7 +270,7 @@ export interface RecordingRow {
|
|
|
231
270
|
durationMs: number;
|
|
232
271
|
videoUrl: string | null;
|
|
233
272
|
status: "uploading" | "processing" | "ready" | "failed";
|
|
234
|
-
visibility:
|
|
273
|
+
visibility: RecordingVisibility;
|
|
235
274
|
ownerEmail: string;
|
|
236
275
|
folderId: string | null;
|
|
237
276
|
spaceIds: string[];
|