@agent-native/core 0.108.0 → 0.109.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +25 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +162 -17
- package/corpus/core/src/a2a/correlation.ts +50 -0
- package/corpus/core/src/a2a/handlers.ts +200 -14
- package/corpus/core/src/a2a/index.ts +7 -1
- package/corpus/core/src/a2a/invoke.ts +77 -1
- package/corpus/core/src/a2a/server.ts +13 -1
- package/corpus/core/src/a2a/task-store.ts +146 -6
- package/corpus/core/src/a2a/types.ts +32 -0
- package/corpus/core/src/action.ts +5 -2
- package/corpus/core/src/agent/production-agent.ts +33 -4
- package/corpus/core/src/client/AssistantChat.tsx +648 -603
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -4
- package/corpus/core/src/client/agent-chat-adapter.ts +23 -37
- package/corpus/core/src/client/agent-sidebar-state.ts +4 -0
- package/corpus/core/src/client/analytics.ts +84 -0
- package/corpus/core/src/client/chat/message-components.tsx +94 -22
- package/corpus/core/src/client/chat/tool-call-display.tsx +170 -47
- package/corpus/core/src/client/chat/use-agent-chat-lifecycle-tracking.ts +74 -0
- package/corpus/core/src/client/chat-thread-url.ts +11 -0
- package/corpus/core/src/client/error-capture.ts +12 -0
- package/corpus/core/src/client/extensions/ExtensionSlot.tsx +37 -1
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/route-chunk-recovery.ts +20 -8
- package/corpus/core/src/client/session-replay.ts +31 -0
- package/corpus/core/src/client/use-run-stuck-detection.ts +27 -1
- package/corpus/core/src/db/client.ts +55 -0
- package/corpus/core/src/extensions/actions.ts +2 -14
- package/corpus/core/src/extensions/store.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +3 -0
- package/corpus/core/src/mcp/build-server.ts +2 -2
- package/corpus/core/src/observability/traces.ts +172 -19
- package/corpus/core/src/progress/actions.ts +4 -2
- package/corpus/core/src/provider-api/index.ts +205 -105
- package/corpus/core/src/scripts/call-agent.ts +190 -19
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +112 -25
- package/corpus/core/src/server/agent-chat-plugin.ts +136 -4
- package/corpus/core/src/styles/agent-conversation.css +7 -0
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +19 -0
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +19 -0
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +20 -0
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +19 -0
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +49 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +5 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +13 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +29 -10
- package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +54 -23
- package/corpus/templates/analytics/.builder/skills/apollo/SKILL.md +19 -41
- package/corpus/templates/analytics/.builder/skills/commonroom/SKILL.md +20 -35
- package/corpus/templates/analytics/.builder/skills/github/SKILL.md +23 -67
- package/corpus/templates/analytics/.builder/skills/pylon/SKILL.md +25 -43
- package/corpus/templates/analytics/AGENTS.md +22 -13
- package/corpus/templates/analytics/actions/account-deep-dive.ts +1 -0
- package/corpus/templates/analytics/actions/configure-gong-native-insights.ts +29 -0
- package/corpus/templates/analytics/actions/content-calendar-schema.ts +8 -4
- package/corpus/templates/analytics/actions/content-calendar.ts +12 -4
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -1
- package/corpus/templates/analytics/actions/gong-calls.ts +113 -22
- package/corpus/templates/analytics/actions/gong-native-insights.ts +166 -0
- package/corpus/templates/analytics/actions/hubspot-deals.ts +190 -46
- package/corpus/templates/analytics/actions/pylon-issues.ts +123 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/actions/slack-messages.ts +179 -27
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +48 -152
- package/corpus/templates/analytics/app/components/ui/chart.tsx +1 -1
- package/corpus/templates/analytics/app/hooks/use-chart-tooltip-flip.ts +21 -6
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +13 -0
- package/corpus/templates/analytics/app/i18n-data.ts +134 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +181 -30
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +6 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +4 -3
- package/corpus/templates/analytics/changelog/2026-07-17-analytics-now-includes-a-default-alert-for-spikes-in-stuck-agent-chats.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-dashboard-editors-can-add-shared-extensions-or-opt-into-per-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-dashboard-extensions-show-a-loading-skeleton.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-errors-now-group-repeat-failures-consistently-and-keep-priva.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-gong-transcript-reviews-now-batch-bounded-call-windows-for-f.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-hubspot-deal-filters-now-run-in-hubspot-before-paging-result.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-notion-content-calendars-now-discover-the-matching-database-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-pylon-dashboard-reads-now-use-the-paginated-issue-search-api.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-17-slack-reads-no-longer-join-channels-and-report-pagination-coverage.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +23 -14
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +5 -1
- package/corpus/templates/analytics/server/handlers/notion.ts +14 -3
- package/corpus/templates/analytics/server/handlers/slack.ts +182 -29
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +3 -3
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -6
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +209 -87
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +6 -2
- package/corpus/templates/analytics/server/lib/error-capture.ts +75 -8
- package/corpus/templates/analytics/server/lib/gong-native-policy.ts +72 -0
- package/corpus/templates/analytics/server/lib/gong.ts +54 -66
- package/corpus/templates/analytics/server/lib/hubspot.ts +40 -3
- package/corpus/templates/analytics/server/lib/notion.ts +83 -8
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +4 -0
- package/corpus/templates/analytics/server/lib/slack.ts +242 -90
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -3
- package/corpus/templates/analytics/server/routes/api/pylon/accounts.get.ts +42 -1
- package/corpus/templates/analytics/server/routes/api/pylon/issues.get.ts +71 -1
- package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +44 -4
- package/corpus/templates/clips/actions/finalize-recording.ts +541 -62
- package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +19 -1
- package/corpus/templates/clips/app/components/library/create-folder-dialog.tsx +123 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +42 -4
- package/corpus/templates/clips/app/components/library/recording-card.tsx +30 -0
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +17 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +14 -2
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +2 -2
- package/corpus/templates/clips/app/entry.client.tsx +6 -1
- package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -1
- package/corpus/templates/clips/app/i18n/de-DE.ts +12 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +12 -1
- package/corpus/templates/clips/app/i18n/es-ES.ts +11 -1
- package/corpus/templates/clips/app/i18n/fr-FR.ts +11 -1
- package/corpus/templates/clips/app/i18n/hi-IN.ts +10 -1
- package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +11 -1
- package/corpus/templates/clips/app/i18n/pt-BR.ts +11 -1
- package/corpus/templates/clips/app/i18n/zh-CN.ts +11 -1
- package/corpus/templates/clips/app/i18n/zh-TW.ts +11 -1
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +72 -1
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/app/routes/record.tsx +3 -3
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -3
- package/corpus/templates/clips/changelog/2026-07-17-choose-which-workspace-receives-new-clips-recordings-and-des.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-clips-now-retries-storage-verification-in-the-background-ins.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-create-folders-directly-from-the-move-menu.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-failed-desktop-uploads-now-offer-a-direct-way-to-reopen-clip.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-fixed-broken-thumbnail-icons-appearing-over-playable-clip-em.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-playback-comments-stay-readable-at-faster-playback-speeds.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +3 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +3 -3
- package/corpus/templates/clips/desktop/src/app.tsx +19 -7
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +111 -13
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +16 -7
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -4
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +32 -15
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +45 -18
- package/corpus/templates/clips/server/jobs/media-verification.ts +130 -0
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +15 -0
- package/corpus/templates/clips/server/lib/media-verification-state.ts +38 -0
- package/corpus/templates/clips/server/lib/player-thumbnail-url.ts +31 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +10 -3
- package/corpus/templates/clips/server/plugins/auth.ts +5 -5
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +12 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +6 -1
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +340 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +24 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +91 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +14 -0
- package/corpus/templates/clips/shared/finalize-recovery.ts +61 -2
- package/corpus/templates/clips/shared/share-meta.ts +1 -1
- package/corpus/templates/content/app/entry.client.tsx +6 -1
- package/corpus/templates/dispatch/AGENTS.md +5 -0
- package/corpus/templates/dispatch/changelog/2026-07-17-apps-page-links-to-other-available-apps.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-17-curated-template-remixes.md +6 -0
- package/corpus/templates/mail/app/entry.client.tsx +6 -1
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +11 -7
- package/corpus/templates/slides/.env.example +0 -1
- package/corpus/templates/slides/AGENTS.md +4 -0
- package/corpus/templates/slides/DEVELOPING.md +2 -2
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +1 -1
- package/corpus/templates/slides/actions/logo-config.ts +6 -7
- package/corpus/templates/slides/actions/search-images.ts +11 -39
- package/corpus/templates/slides/actions/search-logos.ts +5 -52
- package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/LogoSearchPanel.tsx +16 -26
- package/corpus/templates/slides/app/entry.client.tsx +6 -1
- package/corpus/templates/slides/server/lib/media-search-providers.ts +216 -0
- package/corpus/templates/slides/server/register-secrets.ts +30 -0
- package/dist/a2a/client.d.ts +47 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +94 -12
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +10 -0
- package/dist/a2a/correlation.d.ts.map +1 -0
- package/dist/a2a/correlation.js +40 -0
- package/dist/a2a/correlation.js.map +1 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +147 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/index.d.ts +4 -4
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +2 -2
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/invoke.d.ts +29 -2
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +33 -1
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +14 -1
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/task-store.d.ts +12 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +96 -6
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +27 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +5 -2
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +14 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +14 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +158 -136
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +27 -4
- 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 +21 -39
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-sidebar-state.d.ts +1 -0
- package/dist/client/agent-sidebar-state.d.ts.map +1 -1
- package/dist/client/agent-sidebar-state.js +4 -0
- package/dist/client/agent-sidebar-state.js.map +1 -1
- package/dist/client/analytics.d.ts +14 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +67 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +8 -0
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +62 -19
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +6 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +83 -14
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat/use-agent-chat-lifecycle-tracking.d.ts +8 -0
- package/dist/client/chat/use-agent-chat-lifecycle-tracking.d.ts.map +1 -0
- package/dist/client/chat/use-agent-chat-lifecycle-tracking.js +51 -0
- package/dist/client/chat/use-agent-chat-lifecycle-tracking.js.map +1 -0
- package/dist/client/chat-thread-url.d.ts +2 -0
- package/dist/client/chat-thread-url.d.ts.map +1 -0
- package/dist/client/chat-thread-url.js +12 -0
- package/dist/client/chat-thread-url.js.map +1 -0
- package/dist/client/error-capture.d.ts.map +1 -1
- package/dist/client/error-capture.js +7 -0
- package/dist/client/error-capture.js.map +1 -1
- package/dist/client/extensions/ExtensionSlot.d.ts +3 -1
- package/dist/client/extensions/ExtensionSlot.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionSlot.js +28 -3
- package/dist/client/extensions/ExtensionSlot.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/route-chunk-recovery.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery.js +14 -9
- package/dist/client/route-chunk-recovery.js.map +1 -1
- package/dist/client/session-replay.d.ts +15 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +20 -0
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +25 -1
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/db/client.d.ts +7 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +33 -0
- package/dist/db/client.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +2 -10
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/store.js +1 -1
- package/dist/extensions/store.js.map +1 -1
- package/dist/localization/default-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +3 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +2 -2
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +137 -19
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/actions.d.ts.map +1 -1
- package/dist/progress/actions.js +3 -2
- package/dist/progress/actions.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +91 -8
- package/dist/provider-api/index.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +140 -17
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/action-filters-a2a.d.ts +24 -2
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +62 -7
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +118 -5
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-conversation.css +7 -0
- package/dist/templates/default/app/i18n/ar-SA.ts +20 -0
- package/dist/templates/default/app/i18n/de-DE.ts +20 -0
- package/dist/templates/default/app/i18n/en-US.ts +20 -0
- package/dist/templates/default/app/i18n/es-ES.ts +20 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +19 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +20 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +19 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +20 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +19 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +49 -0
- package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +13 -0
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/templates/default/app/i18n/ar-SA.ts +20 -0
- package/src/templates/default/app/i18n/de-DE.ts +20 -0
- package/src/templates/default/app/i18n/en-US.ts +20 -0
- package/src/templates/default/app/i18n/es-ES.ts +20 -0
- package/src/templates/default/app/i18n/fr-FR.ts +21 -0
- package/src/templates/default/app/i18n/hi-IN.ts +19 -0
- package/src/templates/default/app/i18n/ja-JP.ts +20 -0
- package/src/templates/default/app/i18n/ko-KR.ts +19 -0
- package/src/templates/default/app/i18n/pt-BR.ts +20 -0
- package/src/templates/default/app/i18n/zh-CN.ts +19 -0
- package/src/templates/default/app/i18n/zh-TW.ts +19 -0
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +49 -0
- package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +13 -0
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/server/handlers/github.ts +0 -200
- package/corpus/templates/analytics/server/handlers/pylon.ts +0 -43
- package/corpus/templates/analytics/server/lib/apollo.ts +0 -167
- package/corpus/templates/analytics/server/lib/commonroom.ts +0 -149
- package/corpus/templates/analytics/server/lib/github.ts +0 -660
- package/corpus/templates/analytics/server/lib/mixpanel.ts +0 -156
- package/corpus/templates/analytics/server/lib/posthog.ts +0 -194
- package/corpus/templates/analytics/server/lib/pylon.ts +0 -109
- package/corpus/templates/analytics/server/routes/api/github/graphql.post.ts +0 -1
- package/corpus/templates/analytics/server/routes/api/github/issue.get.ts +0 -1
- package/corpus/templates/analytics/server/routes/api/github/org-prs.get.ts +0 -1
- package/corpus/templates/analytics/server/routes/api/github/pr.get.ts +0 -1
- package/corpus/templates/analytics/server/routes/api/github/prs.get.ts +0 -1
- package/corpus/templates/analytics/server/routes/api/github/search.get.ts +0 -1
- package/corpus/templates/slides/actions/fetch-logos.ts +0 -90
- package/corpus/templates/slides/actions/image-search.ts +0 -64
- package/corpus/templates/slides/actions/import-github.ts +0 -182
- package/corpus/templates/slides/actions/logo-lookup.ts +0 -113
|
@@ -2,6 +2,7 @@ export interface FinalizeReceipt {
|
|
|
2
2
|
ok?: unknown;
|
|
3
3
|
finalized?: unknown;
|
|
4
4
|
status?: unknown;
|
|
5
|
+
verificationPending?: unknown;
|
|
5
6
|
sourceSizeBytes?: unknown;
|
|
6
7
|
durationMs?: unknown;
|
|
7
8
|
}
|
|
@@ -11,16 +12,22 @@ export interface LocalRecordingProof {
|
|
|
11
12
|
durationMs: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
export type FinalizeReceiptStatus = "processing" | "ready";
|
|
16
|
+
|
|
14
17
|
export function verifyFinalizeReceipt(
|
|
15
18
|
receipt: FinalizeReceipt | null,
|
|
16
19
|
local: LocalRecordingProof,
|
|
17
|
-
):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
receipt.
|
|
21
|
-
receipt.
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
): FinalizeReceiptStatus {
|
|
21
|
+
const ready =
|
|
22
|
+
receipt?.ok === true &&
|
|
23
|
+
receipt.finalized === true &&
|
|
24
|
+
receipt.status === "ready";
|
|
25
|
+
const processing =
|
|
26
|
+
receipt?.ok === true &&
|
|
27
|
+
receipt.finalized === false &&
|
|
28
|
+
receipt.status === "processing" &&
|
|
29
|
+
receipt.verificationPending === true;
|
|
30
|
+
if (!ready && !processing) {
|
|
24
31
|
throw new Error(
|
|
25
32
|
"Clip may be incomplete. Finalization was not confirmed; the local backup was kept.",
|
|
26
33
|
);
|
|
@@ -48,4 +55,6 @@ export function verifyFinalizeReceipt(
|
|
|
48
55
|
"Clip may be incomplete. The uploaded duration did not match the local recording; the local backup was kept.",
|
|
49
56
|
);
|
|
50
57
|
}
|
|
58
|
+
|
|
59
|
+
return ready ? "ready" : "processing";
|
|
51
60
|
}
|
|
@@ -249,8 +249,7 @@ pub fn run() {
|
|
|
249
249
|
use tauri_plugin_deep_link::DeepLinkExt;
|
|
250
250
|
let dl_handle = app.handle().clone();
|
|
251
251
|
app.deep_link().on_open_url(move |event| {
|
|
252
|
-
let urls: Vec<String> =
|
|
253
|
-
event.urls().iter().map(|u| u.to_string()).collect();
|
|
252
|
+
let urls: Vec<String> = event.urls().iter().map(|u| u.to_string()).collect();
|
|
254
253
|
dlog!("[clips-tray] deep link opened: {:?}", urls);
|
|
255
254
|
present_popover(&dl_handle);
|
|
256
255
|
let _ = dl_handle.emit("clips:deep-link", urls);
|
|
@@ -263,8 +262,7 @@ pub fn run() {
|
|
|
263
262
|
// arguments rather than an on_open_url event. Consume it here so
|
|
264
263
|
// the popover appears and the browser does not hit its fallback.
|
|
265
264
|
if let Ok(Some(urls)) = app.deep_link().get_current() {
|
|
266
|
-
let url_strings: Vec<String> =
|
|
267
|
-
urls.iter().map(|u| u.to_string()).collect();
|
|
265
|
+
let url_strings: Vec<String> = urls.iter().map(|u| u.to_string()).collect();
|
|
268
266
|
dlog!("[clips-tray] deep link cold start: {:?}", url_strings);
|
|
269
267
|
present_popover(app.handle());
|
|
270
268
|
let _ = app.handle().emit("clips:deep-link", url_strings);
|
|
@@ -36,8 +36,12 @@ pub(super) struct LiveUploadCtrl {
|
|
|
36
36
|
|
|
37
37
|
pub(super) struct LiveUpload {
|
|
38
38
|
pub(super) ctrl: Arc<LiveUploadCtrl>,
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
pub(super) result_rx: tokio::sync::oneshot::Receiver<Result<LiveUploadResult, String>>,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub(super) struct LiveUploadResult {
|
|
43
|
+
pub(super) bytes: u64,
|
|
44
|
+
pub(super) verification_pending: bool,
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
struct LiveUploadParams {
|
|
@@ -163,7 +167,7 @@ async fn send_live_upload_post_with_retry(
|
|
|
163
167
|
duration_ms: Option<u128>,
|
|
164
168
|
expected_source_bytes: Option<u64>,
|
|
165
169
|
bytes: &[u8],
|
|
166
|
-
) -> Result<
|
|
170
|
+
) -> Result<bool, String> {
|
|
167
171
|
let rec = ¶ms.recording_id;
|
|
168
172
|
let mut attempt: u32 = 0;
|
|
169
173
|
loop {
|
|
@@ -198,7 +202,7 @@ async fn send_live_upload_post_with_retry(
|
|
|
198
202
|
)
|
|
199
203
|
.await;
|
|
200
204
|
let err = match result {
|
|
201
|
-
Ok(
|
|
205
|
+
Ok(verification_pending) => return Ok(verification_pending),
|
|
202
206
|
Err(err) => err,
|
|
203
207
|
};
|
|
204
208
|
if attempt >= LIVE_UPLOAD_CHUNK_ATTEMPTS {
|
|
@@ -219,7 +223,7 @@ async fn live_upload_loop(
|
|
|
219
223
|
app: AppHandle,
|
|
220
224
|
ctrl: Arc<LiveUploadCtrl>,
|
|
221
225
|
params: LiveUploadParams,
|
|
222
|
-
) -> Result<
|
|
226
|
+
) -> Result<LiveUploadResult, String> {
|
|
223
227
|
let rec = params.recording_id.clone();
|
|
224
228
|
eprintln!(
|
|
225
229
|
"[live-upload] loop started for {rec}: file={} chunk={} bytes",
|
|
@@ -290,6 +294,7 @@ async fn live_upload_loop(
|
|
|
290
294
|
// sentinel makes GCS silently commit only the aligned prefix, and
|
|
291
295
|
// the session close then fails forever with 308 (incomplete).
|
|
292
296
|
let mut final_sent = false;
|
|
297
|
+
let mut verification_pending = false;
|
|
293
298
|
while final_len > offset {
|
|
294
299
|
let take = chunk.min(final_len - offset);
|
|
295
300
|
let is_last = offset + take >= final_len;
|
|
@@ -302,7 +307,7 @@ async fn live_upload_loop(
|
|
|
302
307
|
eprintln!(
|
|
303
308
|
"[live-upload] {rec}: sending tail chunk #{index} offset={offset} size={take} final={is_last}"
|
|
304
309
|
);
|
|
305
|
-
|
|
310
|
+
let receipt = send_live_upload_post_with_retry(
|
|
306
311
|
&client,
|
|
307
312
|
&ctrl,
|
|
308
313
|
¶ms,
|
|
@@ -313,10 +318,16 @@ async fn live_upload_loop(
|
|
|
313
318
|
is_last.then_some(final_len),
|
|
314
319
|
&bytes,
|
|
315
320
|
)
|
|
316
|
-
.await
|
|
317
|
-
{
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
.await;
|
|
322
|
+
let pending = match receipt {
|
|
323
|
+
Ok(pending) => pending,
|
|
324
|
+
Err(e) => {
|
|
325
|
+
eprintln!("[live-upload] {rec}: tail chunk #{index} failed: {e}");
|
|
326
|
+
return Err(e);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
if is_last {
|
|
330
|
+
verification_pending = pending;
|
|
320
331
|
}
|
|
321
332
|
offset += take;
|
|
322
333
|
index += 1;
|
|
@@ -331,7 +342,7 @@ async fn live_upload_loop(
|
|
|
331
342
|
"[live-upload] {rec}: sending final post #{index} (total={}, duration_ms={duration_ms})",
|
|
332
343
|
index + 1
|
|
333
344
|
);
|
|
334
|
-
|
|
345
|
+
verification_pending = match send_live_upload_post_with_retry(
|
|
335
346
|
&client,
|
|
336
347
|
&ctrl,
|
|
337
348
|
¶ms,
|
|
@@ -344,13 +355,19 @@ async fn live_upload_loop(
|
|
|
344
355
|
)
|
|
345
356
|
.await
|
|
346
357
|
{
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
Ok(pending) => pending,
|
|
359
|
+
Err(e) => {
|
|
360
|
+
eprintln!("[live-upload] {rec}: final post failed: {e}");
|
|
361
|
+
return Err(e);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
350
364
|
}
|
|
351
365
|
emit_native_upload_progress(&app, "opening", "Uploading clip", None, Some(1.0));
|
|
352
366
|
eprintln!("[live-upload] {rec}: done — {index} post(s), {final_len} bytes total");
|
|
353
|
-
return Ok(
|
|
367
|
+
return Ok(LiveUploadResult {
|
|
368
|
+
bytes: final_len,
|
|
369
|
+
verification_pending,
|
|
370
|
+
});
|
|
354
371
|
}
|
|
355
372
|
|
|
356
373
|
if !wait_logged {
|
|
@@ -692,6 +692,7 @@ pub struct NativeFullscreenUploadResult {
|
|
|
692
692
|
width: Option<u32>,
|
|
693
693
|
height: Option<u32>,
|
|
694
694
|
bytes: u64,
|
|
695
|
+
verification_pending: bool,
|
|
695
696
|
}
|
|
696
697
|
|
|
697
698
|
#[derive(Serialize, Clone)]
|
|
@@ -1388,20 +1389,23 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
1388
1389
|
eprintln!(
|
|
1389
1390
|
"[live-upload] stop: finalize result for {recording_id}: {}",
|
|
1390
1391
|
match &result {
|
|
1391
|
-
Ok(
|
|
1392
|
+
Ok(upload) => format!("ok ({} bytes)", upload.bytes),
|
|
1392
1393
|
Err(e) => format!("error: {e}"),
|
|
1393
1394
|
}
|
|
1394
1395
|
);
|
|
1395
1396
|
return match result {
|
|
1396
|
-
Ok(
|
|
1397
|
-
|
|
1397
|
+
Ok(upload) => {
|
|
1398
|
+
if !upload.verification_pending {
|
|
1399
|
+
clear_saved_recording_after_success(&app, &saved);
|
|
1400
|
+
}
|
|
1398
1401
|
emit_native_upload_finished(&app, &server_url, &recording_id, true, None, None);
|
|
1399
1402
|
Ok(NativeFullscreenUploadResult {
|
|
1400
1403
|
recording_id,
|
|
1401
1404
|
duration_ms: verified_duration_ms,
|
|
1402
1405
|
width: session.width,
|
|
1403
1406
|
height: session.height,
|
|
1404
|
-
bytes,
|
|
1407
|
+
bytes: upload.bytes,
|
|
1408
|
+
verification_pending: upload.verification_pending,
|
|
1405
1409
|
})
|
|
1406
1410
|
}
|
|
1407
1411
|
Err(err) => {
|
|
@@ -1481,7 +1485,9 @@ pub async fn native_fullscreen_recording_stop_and_upload(
|
|
|
1481
1485
|
|
|
1482
1486
|
match result {
|
|
1483
1487
|
Ok(result) => {
|
|
1484
|
-
|
|
1488
|
+
if !result.verification_pending {
|
|
1489
|
+
clear_saved_recording_after_success(&app, &saved);
|
|
1490
|
+
}
|
|
1485
1491
|
emit_native_upload_finished(&app, &server_url, &recording_id, true, None, None);
|
|
1486
1492
|
Ok(result)
|
|
1487
1493
|
}
|
|
@@ -2599,7 +2605,9 @@ pub async fn native_fullscreen_recording_retry_upload(
|
|
|
2599
2605
|
|
|
2600
2606
|
match result {
|
|
2601
2607
|
Ok(result) => {
|
|
2602
|
-
|
|
2608
|
+
if !result.verification_pending {
|
|
2609
|
+
clear_saved_recording_after_success(&app, &saved);
|
|
2610
|
+
}
|
|
2603
2611
|
Ok(result)
|
|
2604
2612
|
}
|
|
2605
2613
|
Err(err) => {
|
|
@@ -4003,6 +4011,7 @@ async fn upload_prepared_recording_file(
|
|
|
4003
4011
|
.map_err(|e| format!("upload client failed: {e}"))?;
|
|
4004
4012
|
let mut file =
|
|
4005
4013
|
File::open(&prepared.path).map_err(|e| format!("native recording open failed: {e}"))?;
|
|
4014
|
+
let verification_pending;
|
|
4006
4015
|
|
|
4007
4016
|
if upload_mode == NativeUploadMode::Streaming {
|
|
4008
4017
|
// Resumable providers require every non-final body to be aligned. The
|
|
@@ -4055,7 +4064,7 @@ async fn upload_prepared_recording_file(
|
|
|
4055
4064
|
None,
|
|
4056
4065
|
Some(streaming_full_chunks as f32 / total_posts as f32),
|
|
4057
4066
|
);
|
|
4058
|
-
send_upload_post(
|
|
4067
|
+
verification_pending = send_upload_post(
|
|
4059
4068
|
&client,
|
|
4060
4069
|
&server_url,
|
|
4061
4070
|
&recording_id,
|
|
@@ -4123,7 +4132,7 @@ async fn upload_prepared_recording_file(
|
|
|
4123
4132
|
None,
|
|
4124
4133
|
Some(total_chunks as f32 / total_posts as f32),
|
|
4125
4134
|
);
|
|
4126
|
-
send_upload_post(
|
|
4135
|
+
verification_pending = send_upload_post(
|
|
4127
4136
|
&client,
|
|
4128
4137
|
&server_url,
|
|
4129
4138
|
&recording_id,
|
|
@@ -4153,6 +4162,7 @@ async fn upload_prepared_recording_file(
|
|
|
4153
4162
|
width,
|
|
4154
4163
|
height,
|
|
4155
4164
|
bytes: total_bytes,
|
|
4165
|
+
verification_pending,
|
|
4156
4166
|
})
|
|
4157
4167
|
}
|
|
4158
4168
|
|
|
@@ -4270,7 +4280,7 @@ async fn send_upload_post(
|
|
|
4270
4280
|
locally_transcoded: bool,
|
|
4271
4281
|
expected_source_bytes: Option<u64>,
|
|
4272
4282
|
body: Vec<u8>,
|
|
4273
|
-
) -> Result<
|
|
4283
|
+
) -> Result<bool, String> {
|
|
4274
4284
|
let body_len = body.len();
|
|
4275
4285
|
let url = upload_url(
|
|
4276
4286
|
server_url,
|
|
@@ -4319,7 +4329,7 @@ async fn send_upload_post(
|
|
|
4319
4329
|
body.chars().take(400).collect::<String>()
|
|
4320
4330
|
));
|
|
4321
4331
|
}
|
|
4322
|
-
if is_final {
|
|
4332
|
+
let verification_pending = if is_final {
|
|
4323
4333
|
verify_native_finalize_receipt(
|
|
4324
4334
|
&body,
|
|
4325
4335
|
expected_source_bytes.ok_or_else(|| {
|
|
@@ -4328,13 +4338,15 @@ async fn send_upload_post(
|
|
|
4328
4338
|
duration_ms.ok_or_else(|| {
|
|
4329
4339
|
"Clip may be incomplete: final upload had no local duration".to_string()
|
|
4330
4340
|
})?,
|
|
4331
|
-
)
|
|
4332
|
-
}
|
|
4341
|
+
)?
|
|
4342
|
+
} else {
|
|
4343
|
+
false
|
|
4344
|
+
};
|
|
4333
4345
|
eprintln!(
|
|
4334
4346
|
"[clips-tray] native upload post ok recording={recording_id} mode={} index={index}/{total} final={is_final}",
|
|
4335
4347
|
upload_mode.label()
|
|
4336
4348
|
);
|
|
4337
|
-
Ok(
|
|
4349
|
+
Ok(verification_pending)
|
|
4338
4350
|
}
|
|
4339
4351
|
|
|
4340
4352
|
#[derive(Deserialize)]
|
|
@@ -4343,6 +4355,7 @@ struct NativeFinalizeReceipt {
|
|
|
4343
4355
|
ok: bool,
|
|
4344
4356
|
finalized: bool,
|
|
4345
4357
|
status: Option<String>,
|
|
4358
|
+
verification_pending: Option<bool>,
|
|
4346
4359
|
source_size_bytes: Option<u64>,
|
|
4347
4360
|
duration_ms: Option<u128>,
|
|
4348
4361
|
}
|
|
@@ -4351,12 +4364,17 @@ fn verify_native_finalize_receipt(
|
|
|
4351
4364
|
body: &str,
|
|
4352
4365
|
expected_source_bytes: u64,
|
|
4353
4366
|
expected_duration_ms: u128,
|
|
4354
|
-
) -> Result<
|
|
4367
|
+
) -> Result<bool, String> {
|
|
4355
4368
|
let receipt: NativeFinalizeReceipt = serde_json::from_str(body).map_err(|_| {
|
|
4356
4369
|
"Clip may be incomplete. The final upload receipt was unreadable; the local backup was kept."
|
|
4357
4370
|
.to_string()
|
|
4358
4371
|
})?;
|
|
4359
|
-
|
|
4372
|
+
let ready = receipt.ok && receipt.finalized && receipt.status.as_deref() == Some("ready");
|
|
4373
|
+
let processing = receipt.ok
|
|
4374
|
+
&& !receipt.finalized
|
|
4375
|
+
&& receipt.status.as_deref() == Some("processing")
|
|
4376
|
+
&& receipt.verification_pending == Some(true);
|
|
4377
|
+
if !ready && !processing {
|
|
4360
4378
|
return Err(
|
|
4361
4379
|
"Clip may be incomplete. Finalization was not confirmed; the local backup was kept."
|
|
4362
4380
|
.to_string(),
|
|
@@ -4374,7 +4392,7 @@ fn verify_native_finalize_receipt(
|
|
|
4374
4392
|
.to_string(),
|
|
4375
4393
|
);
|
|
4376
4394
|
}
|
|
4377
|
-
Ok(
|
|
4395
|
+
Ok(processing)
|
|
4378
4396
|
}
|
|
4379
4397
|
|
|
4380
4398
|
fn media_durations_materially_match(expected_ms: u128, actual_ms: u128) -> bool {
|
|
@@ -4399,9 +4417,18 @@ mod native_finalize_receipt_tests {
|
|
|
4399
4417
|
}
|
|
4400
4418
|
|
|
4401
4419
|
#[test]
|
|
4402
|
-
fn
|
|
4420
|
+
fn accepts_matching_ready_and_pending_verification_receipts() {
|
|
4403
4421
|
let ready = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":581614005,"durationMs":1593259}"#;
|
|
4404
|
-
|
|
4422
|
+
assert_eq!(
|
|
4423
|
+
verify_native_finalize_receipt(ready, 581_614_005, 1_592_773),
|
|
4424
|
+
Ok(false)
|
|
4425
|
+
);
|
|
4426
|
+
|
|
4427
|
+
let processing = r#"{"ok":true,"finalized":false,"status":"processing","verificationPending":true,"sourceSizeBytes":581614005,"durationMs":1593259}"#;
|
|
4428
|
+
assert_eq!(
|
|
4429
|
+
verify_native_finalize_receipt(processing, 581_614_005, 1_592_773),
|
|
4430
|
+
Ok(true)
|
|
4431
|
+
);
|
|
4405
4432
|
|
|
4406
4433
|
let short = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":581614005,"durationMs":483000}"#;
|
|
4407
4434
|
assert!(
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getDbExec } from "@agent-native/core/db";
|
|
2
|
+
import { runWithRequestContext } from "@agent-native/core/server";
|
|
3
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import finalizeRecording from "../../actions/finalize-recording.js";
|
|
6
|
+
import { getDb, schema } from "../db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
MEDIA_VERIFICATION_STATE_PREFIX,
|
|
9
|
+
parseMediaVerificationMarker,
|
|
10
|
+
} from "../lib/media-verification-state.js";
|
|
11
|
+
import { ownerEmailMatches } from "../lib/recordings.js";
|
|
12
|
+
|
|
13
|
+
const SWEEP_INTERVAL_MS = 60_000;
|
|
14
|
+
const DISPATCH_FALLBACK_GRACE_MS = 30_000;
|
|
15
|
+
const MAX_ATTEMPTS = 10;
|
|
16
|
+
let skippingLogged = false;
|
|
17
|
+
|
|
18
|
+
export async function runMediaVerificationSweepOnce(): Promise<void> {
|
|
19
|
+
const { rows } = await getDbExec().execute({
|
|
20
|
+
sql: `SELECT session_id, key, value FROM application_state WHERE key LIKE ?`,
|
|
21
|
+
args: [`${MEDIA_VERIFICATION_STATE_PREFIX}%`],
|
|
22
|
+
});
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
|
|
25
|
+
for (const row of rows as Array<{
|
|
26
|
+
session_id?: unknown;
|
|
27
|
+
key?: unknown;
|
|
28
|
+
value?: unknown;
|
|
29
|
+
}>) {
|
|
30
|
+
const sessionId =
|
|
31
|
+
typeof row.session_id === "string" ? row.session_id.trim() : "";
|
|
32
|
+
const key = typeof row.key === "string" ? row.key : "";
|
|
33
|
+
const recordingId = key.startsWith(MEDIA_VERIFICATION_STATE_PREFIX)
|
|
34
|
+
? key.slice(MEDIA_VERIFICATION_STATE_PREFIX.length)
|
|
35
|
+
: "";
|
|
36
|
+
const rawValue = typeof row.value === "string" ? row.value : "";
|
|
37
|
+
let rawState: unknown;
|
|
38
|
+
try {
|
|
39
|
+
rawState = JSON.parse(rawValue);
|
|
40
|
+
} catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const marker = parseMediaVerificationMarker(rawState);
|
|
44
|
+
if (
|
|
45
|
+
!sessionId ||
|
|
46
|
+
!recordingId ||
|
|
47
|
+
!marker ||
|
|
48
|
+
marker.recordingId !== recordingId
|
|
49
|
+
) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const nextAttemptAt = Date.parse(marker.nextAttemptAt);
|
|
54
|
+
const leaseUntil = marker.leaseUntil
|
|
55
|
+
? Date.parse(marker.leaseUntil)
|
|
56
|
+
: Number.NEGATIVE_INFINITY;
|
|
57
|
+
const due =
|
|
58
|
+
marker.status === "pending"
|
|
59
|
+
? now >= nextAttemptAt + DISPATCH_FALLBACK_GRACE_MS
|
|
60
|
+
: now >= leaseUntil;
|
|
61
|
+
if (marker.completedAttempts >= MAX_ATTEMPTS || !due) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const [recording] = await getDb()
|
|
67
|
+
.select({
|
|
68
|
+
ownerEmail: schema.recordings.ownerEmail,
|
|
69
|
+
orgId: schema.recordings.orgId,
|
|
70
|
+
})
|
|
71
|
+
.from(schema.recordings)
|
|
72
|
+
.where(
|
|
73
|
+
and(
|
|
74
|
+
eq(schema.recordings.id, recordingId),
|
|
75
|
+
ownerEmailMatches(schema.recordings.ownerEmail, sessionId),
|
|
76
|
+
eq(schema.recordings.status, "processing"),
|
|
77
|
+
isNull(schema.recordings.trashedAt),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
.limit(1);
|
|
81
|
+
if (!recording) continue;
|
|
82
|
+
|
|
83
|
+
await runWithRequestContext(
|
|
84
|
+
{
|
|
85
|
+
userEmail: recording.ownerEmail,
|
|
86
|
+
orgId: recording.orgId ?? undefined,
|
|
87
|
+
},
|
|
88
|
+
async () => {
|
|
89
|
+
await finalizeRecording.run({
|
|
90
|
+
id: recordingId,
|
|
91
|
+
mediaVerificationRetryAttempt: Math.min(
|
|
92
|
+
MAX_ATTEMPTS,
|
|
93
|
+
marker.completedAttempts + 1,
|
|
94
|
+
),
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.warn("[media-verification] sweep item failed", {
|
|
100
|
+
key: String(row.key ?? ""),
|
|
101
|
+
recordingId,
|
|
102
|
+
error: err instanceof Error ? err.message : String(err),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default function registerMediaVerificationJob(): void {
|
|
109
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
110
|
+
const flag = process.env.RUN_BACKGROUND_JOBS;
|
|
111
|
+
const enabled = flag === "1" || (isProd && flag !== "0");
|
|
112
|
+
if (!enabled) {
|
|
113
|
+
if (process.env.DEBUG && !skippingLogged) {
|
|
114
|
+
console.log(
|
|
115
|
+
"[media-verification] Skipping background sweep (set RUN_BACKGROUND_JOBS=1 to enable in dev).",
|
|
116
|
+
);
|
|
117
|
+
skippingLogged = true;
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setInterval(() => {
|
|
123
|
+
runMediaVerificationSweepOnce().catch((err) =>
|
|
124
|
+
console.error("[media-verification] interval failed:", err),
|
|
125
|
+
);
|
|
126
|
+
}, SWEEP_INTERVAL_MS);
|
|
127
|
+
console.log(
|
|
128
|
+
`[media-verification] Recurring recovery sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -648,6 +648,21 @@ async function processCompressionState(
|
|
|
648
648
|
try {
|
|
649
649
|
const compressed = await triggerBuilderCompression(state);
|
|
650
650
|
if (compressed) {
|
|
651
|
+
if (!compressed.durationMs) {
|
|
652
|
+
if (attempts >= MAX_TRIGGER_ATTEMPTS) {
|
|
653
|
+
return markCompressionFailed(
|
|
654
|
+
{ ...state, attempts },
|
|
655
|
+
`Builder media compression did not provide a verified duration after ${attempts} attempts`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return writeCompressionState(state, {
|
|
659
|
+
status: "triggered",
|
|
660
|
+
attempts,
|
|
661
|
+
lastTriggeredAt: new Date().toISOString(),
|
|
662
|
+
nextAttemptAt: isoAfter(TRIGGERED_POLL_DELAY_MS),
|
|
663
|
+
detail: "Compressed media is not ready with a verified duration yet",
|
|
664
|
+
});
|
|
665
|
+
}
|
|
651
666
|
return swapRecordingToCompressed(
|
|
652
667
|
state,
|
|
653
668
|
compressed.url,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const MEDIA_VERIFICATION_STATE_PREFIX = "recording-media-verification-";
|
|
2
|
+
|
|
3
|
+
export type MediaVerificationMarker = {
|
|
4
|
+
recordingId: string;
|
|
5
|
+
status: "pending" | "leased";
|
|
6
|
+
completedAttempts: number;
|
|
7
|
+
nextAttemptAt: string;
|
|
8
|
+
leaseUntil: string | null;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function mediaVerificationStateKey(recordingId: string): string {
|
|
13
|
+
return `${MEDIA_VERIFICATION_STATE_PREFIX}${recordingId}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseMediaVerificationMarker(
|
|
17
|
+
value: unknown,
|
|
18
|
+
): MediaVerificationMarker | null {
|
|
19
|
+
if (!value || typeof value !== "object") return null;
|
|
20
|
+
const state = value as Record<string, unknown>;
|
|
21
|
+
if (
|
|
22
|
+
typeof state.recordingId !== "string" ||
|
|
23
|
+
!state.recordingId ||
|
|
24
|
+
(state.status !== "pending" && state.status !== "leased") ||
|
|
25
|
+
typeof state.completedAttempts !== "number" ||
|
|
26
|
+
!Number.isInteger(state.completedAttempts) ||
|
|
27
|
+
state.completedAttempts < 0 ||
|
|
28
|
+
typeof state.nextAttemptAt !== "string" ||
|
|
29
|
+
!Number.isFinite(Date.parse(state.nextAttemptAt)) ||
|
|
30
|
+
(state.leaseUntil !== null &&
|
|
31
|
+
(typeof state.leaseUntil !== "string" ||
|
|
32
|
+
!Number.isFinite(Date.parse(state.leaseUntil)))) ||
|
|
33
|
+
typeof state.updatedAt !== "string"
|
|
34
|
+
) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return state as MediaVerificationMarker;
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type PlayerThumbnailRecording = {
|
|
2
|
+
id: string;
|
|
3
|
+
thumbnailUrl?: string | null;
|
|
4
|
+
animatedThumbnailUrl?: string | null;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function appendQueryParam(url: string, key: string, value: string): string {
|
|
8
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
9
|
+
return `${url}${separator}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function localRecordingThumbnailRoute(recordingId: string): string {
|
|
13
|
+
return `/api/thumbnail/${encodeURIComponent(recordingId)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolvePlayerThumbnailUrl(
|
|
17
|
+
recording: PlayerThumbnailRecording,
|
|
18
|
+
options: {
|
|
19
|
+
accessToken?: string | null;
|
|
20
|
+
appPath?: (path: string) => string;
|
|
21
|
+
} = {},
|
|
22
|
+
): string | null {
|
|
23
|
+
if (!recording.thumbnailUrl && !recording.animatedThumbnailUrl) return null;
|
|
24
|
+
|
|
25
|
+
let resolved = localRecordingThumbnailRoute(recording.id);
|
|
26
|
+
if (options.accessToken) {
|
|
27
|
+
resolved = appendQueryParam(resolved, "t", options.accessToken);
|
|
28
|
+
}
|
|
29
|
+
if (options.appPath) resolved = options.appPath(resolved);
|
|
30
|
+
return resolved;
|
|
31
|
+
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
signScopedAgentAccessToken,
|
|
8
8
|
} from "@agent-native/core/server";
|
|
9
9
|
|
|
10
|
-
export type PostFinalizeJobKind = "seekable" | "transcript";
|
|
10
|
+
export type PostFinalizeJobKind = "media-ready" | "seekable" | "transcript";
|
|
11
11
|
|
|
12
12
|
export const POST_FINALIZE_JOB_TOKEN_KIND = "clips-post-finalize-job";
|
|
13
13
|
|
|
@@ -47,10 +47,12 @@ export async function dispatchPostFinalizeJob(args: {
|
|
|
47
47
|
delayMs?: number;
|
|
48
48
|
retryAttempt?: number;
|
|
49
49
|
regenerate?: boolean;
|
|
50
|
+
requireAccepted?: boolean;
|
|
50
51
|
}): Promise<void> {
|
|
52
|
+
const { requireAccepted = false, ...job } = args;
|
|
51
53
|
const token = signScopedAgentAccessToken({
|
|
52
54
|
resourceKind: POST_FINALIZE_JOB_TOKEN_KIND,
|
|
53
|
-
resourceId: postFinalizeJobResourceId(
|
|
55
|
+
resourceId: postFinalizeJobResourceId(job.recordingId, job.kind),
|
|
54
56
|
ttlSeconds: 10 * 60,
|
|
55
57
|
});
|
|
56
58
|
const basePath = normalizeBasePath(
|
|
@@ -62,7 +64,7 @@ export async function dispatchPostFinalizeJob(args: {
|
|
|
62
64
|
const usesDurableBackground =
|
|
63
65
|
dispatchPathTargetsNetlifyBackgroundFunction(dispatchPath);
|
|
64
66
|
const body = JSON.stringify({
|
|
65
|
-
...
|
|
67
|
+
...job,
|
|
66
68
|
token,
|
|
67
69
|
...(usesDurableBackground
|
|
68
70
|
? {
|
|
@@ -100,6 +102,11 @@ export async function dispatchPostFinalizeJob(args: {
|
|
|
100
102
|
});
|
|
101
103
|
});
|
|
102
104
|
|
|
105
|
+
if (requireAccepted) {
|
|
106
|
+
await dispatch;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
103
110
|
await Promise.race([
|
|
104
111
|
dispatch,
|
|
105
112
|
new Promise<void>((resolve) => setTimeout(resolve, DISPATCH_SETTLE_MS)),
|
|
@@ -37,12 +37,12 @@ export default createAuthPlugin({
|
|
|
37
37
|
"/api/media",
|
|
38
38
|
"/api/clips-latest.json",
|
|
39
39
|
"/api/clips-updater.json",
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// upload POSTs stay behind auth under /api/uploads/*.
|
|
40
|
+
// Public media-serving routes enforce resolveAccess + password/expiry
|
|
41
|
+
// checks themselves. They need to bypass the app auth shell so anonymous
|
|
42
|
+
// viewers and crawlers can fetch public share media. Chunk upload POSTs
|
|
43
|
+
// stay behind auth under /api/uploads/*.
|
|
45
44
|
"/api/video",
|
|
45
|
+
"/api/thumbnail",
|
|
46
46
|
"/api/auth/google-calendar",
|
|
47
47
|
"/_agent-native/google/auth-url",
|
|
48
48
|
"/_agent-native/google/callback",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import registerBuilderMediaCompressionJob from "../jobs/builder-media-compression.js";
|
|
9
|
+
import registerMediaVerificationJob from "../jobs/media-verification.js";
|
|
9
10
|
import registerMeetingRemindersJob from "../jobs/meeting-reminders.js";
|
|
10
11
|
import registerPollCalendarsJob from "../jobs/poll-calendars.js";
|
|
11
12
|
import registerStaleMeetingSweeperJob from "../jobs/stale-meeting-sweeper.js";
|
|
@@ -16,6 +17,7 @@ export default () => {
|
|
|
16
17
|
// background loop is off.
|
|
17
18
|
registerMeetingRemindersJob();
|
|
18
19
|
registerBuilderMediaCompressionJob();
|
|
20
|
+
registerMediaVerificationJob();
|
|
19
21
|
registerPollCalendarsJob();
|
|
20
22
|
registerStaleMeetingSweeperJob();
|
|
21
23
|
};
|