@agent-native/core 0.96.0 → 0.97.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/README.md +6 -0
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +39 -0
- package/corpus/core/docs/content/audit-log.mdx +2 -1
- package/corpus/core/docs/content/automations.mdx +7 -7
- package/corpus/core/docs/content/creating-templates.mdx +1 -1
- package/corpus/core/docs/content/internationalization.mdx +2 -1
- package/corpus/core/docs/content/locales/ar-SA/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/ar-SA/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/ar-SA/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/ar-SA/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/package-lifecycle.mdx +19 -0
- package/corpus/core/docs/content/locales/ar-SA/sandbox-adapters.mdx +18 -7
- package/corpus/core/docs/content/locales/ar-SA/security.mdx +8 -0
- package/corpus/core/docs/content/locales/de-DE/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/de-DE/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/de-DE/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/de-DE/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/package-lifecycle.mdx +22 -0
- package/corpus/core/docs/content/locales/de-DE/sandbox-adapters.mdx +18 -7
- package/corpus/core/docs/content/locales/de-DE/security.mdx +8 -0
- package/corpus/core/docs/content/locales/es-ES/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/es-ES/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/es-ES/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/es-ES/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/package-lifecycle.mdx +22 -0
- package/corpus/core/docs/content/locales/es-ES/sandbox-adapters.mdx +18 -7
- package/corpus/core/docs/content/locales/es-ES/security.mdx +8 -0
- package/corpus/core/docs/content/locales/fr-FR/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/fr-FR/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/fr-FR/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/fr-FR/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/package-lifecycle.mdx +21 -0
- package/corpus/core/docs/content/locales/fr-FR/sandbox-adapters.mdx +19 -8
- package/corpus/core/docs/content/locales/fr-FR/security.mdx +8 -0
- package/corpus/core/docs/content/locales/hi-IN/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/hi-IN/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/hi-IN/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/hi-IN/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/package-lifecycle.mdx +20 -0
- package/corpus/core/docs/content/locales/hi-IN/sandbox-adapters.mdx +18 -7
- package/corpus/core/docs/content/locales/hi-IN/security.mdx +8 -0
- package/corpus/core/docs/content/locales/ja-JP/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/ja-JP/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/ja-JP/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/ja-JP/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/package-lifecycle.mdx +21 -0
- package/corpus/core/docs/content/locales/ja-JP/sandbox-adapters.mdx +17 -6
- package/corpus/core/docs/content/locales/ja-JP/security.mdx +8 -0
- package/corpus/core/docs/content/locales/ko-KR/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/ko-KR/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/ko-KR/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/ko-KR/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/package-lifecycle.mdx +20 -0
- package/corpus/core/docs/content/locales/ko-KR/sandbox-adapters.mdx +17 -6
- package/corpus/core/docs/content/locales/ko-KR/security.mdx +8 -0
- package/corpus/core/docs/content/locales/pt-BR/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/pt-BR/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/pt-BR/getting-started.mdx +7 -1
- package/corpus/core/docs/content/locales/pt-BR/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/package-lifecycle.mdx +21 -0
- package/corpus/core/docs/content/locales/pt-BR/sandbox-adapters.mdx +18 -7
- package/corpus/core/docs/content/locales/pt-BR/security.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-CN/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/zh-CN/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/zh-CN/getting-started.mdx +6 -4
- package/corpus/core/docs/content/locales/zh-CN/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/package-lifecycle.mdx +19 -0
- package/corpus/core/docs/content/locales/zh-CN/sandbox-adapters.mdx +17 -6
- package/corpus/core/docs/content/locales/zh-CN/security.mdx +8 -0
- package/corpus/core/docs/content/locales/zh-TW/audit-log.mdx +2 -1
- package/corpus/core/docs/content/locales/zh-TW/automations.mdx +7 -7
- package/corpus/core/docs/content/locales/zh-TW/getting-started.mdx +6 -4
- package/corpus/core/docs/content/locales/zh-TW/internationalization.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/package-lifecycle.mdx +19 -0
- package/corpus/core/docs/content/locales/zh-TW/sandbox-adapters.mdx +18 -7
- package/corpus/core/docs/content/locales/zh-TW/security.mdx +8 -0
- package/corpus/core/docs/content/package-lifecycle.mdx +51 -0
- package/corpus/core/docs/content/sandbox-adapters.mdx +16 -5
- package/corpus/core/package.json +9 -2
- package/corpus/core/src/a2a/auth-policy.ts +33 -0
- package/corpus/core/src/agent/durable-background.ts +15 -7
- package/corpus/core/src/agent/run-store.ts +13 -4
- package/corpus/core/src/audit/actions/export-audit-events.ts +162 -0
- package/corpus/core/src/audit/store.ts +8 -3
- package/corpus/core/src/audit/types.ts +3 -0
- package/corpus/core/src/cli/doctor.ts +396 -0
- package/corpus/core/src/cli/index.ts +56 -0
- package/corpus/core/src/cli/info.ts +3 -3
- package/corpus/core/src/cli/package-lifecycle.ts +821 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +9 -9
- package/corpus/core/src/guards/db-tool-scoping.ts +129 -0
- package/corpus/core/src/guards/index.ts +23 -0
- package/corpus/core/src/guards/no-drizzle-push.ts +116 -0
- package/corpus/core/src/guards/no-env-credentials.ts +229 -0
- package/corpus/core/src/guards/no-env-mutation.ts +114 -0
- package/corpus/core/src/guards/no-localhost-fallback.ts +124 -0
- package/corpus/core/src/guards/no-unscoped-credentials.ts +227 -0
- package/corpus/core/src/guards/no-unscoped-queries.ts +834 -0
- package/corpus/core/src/guards/scan-utils.ts +105 -0
- package/corpus/core/src/guards/types.ts +30 -0
- package/corpus/core/src/org/accept-pending.ts +9 -1
- package/corpus/core/src/org/migrations.ts +41 -0
- package/corpus/core/src/package-lifecycle/index.ts +1 -0
- package/corpus/core/src/package-lifecycle/manifest.ts +120 -0
- package/corpus/core/src/provider-api/custom-registry.ts +56 -0
- package/corpus/core/src/provider-api/index.ts +2 -0
- package/corpus/core/src/secrets/storage.ts +47 -27
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +54 -12
- package/corpus/core/src/server/core-routes-plugin.ts +30 -14
- package/corpus/core/src/server/csrf.ts +24 -5
- package/corpus/core/src/server/framework-request-handler.ts +34 -0
- package/corpus/core/src/server/google-realtime-session.ts +1 -36
- package/corpus/core/src/server/poll.ts +24 -2
- package/corpus/core/src/server/realtime-voice.ts +9 -0
- package/corpus/core/src/server/request-origin.ts +63 -0
- package/corpus/core/src/server/transcribe-voice.ts +1 -50
- package/corpus/core/src/templates/default/package.json +2 -2
- package/corpus/core/src/templates/headless/package.json +0 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +3 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/package.json +0 -1
- package/corpus/core/src/templates/workspace-root/package.json +0 -1
- package/corpus/core/src/triggers/actions.ts +11 -3
- package/corpus/core/src/triggers/types.ts +7 -1
- package/corpus/templates/analytics/actions/compose-dashboard.ts +65 -37
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +73 -24
- package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +16 -13
- package/corpus/templates/analytics/actions/update-dashboard.ts +53 -42
- package/corpus/templates/analytics/changelog/2026-07-10-dashboard-edits-from-the-agent-and-the-ui-at-the-same-time-n.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-timed-out-bigquery-jobs-are-now-cancelled-so-they-stop-consu.md +6 -0
- package/corpus/templates/analytics/package.json +1 -2
- package/corpus/templates/analytics/server/lib/bigquery.ts +3 -2
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +154 -11
- package/corpus/templates/assets/package.json +1 -2
- package/corpus/templates/brain/package.json +1 -2
- package/corpus/templates/calendar/package.json +1 -2
- package/corpus/templates/chat/package.json +1 -2
- package/corpus/templates/clips/changelog/2026-07-10-atomic-view-tracking.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +3 -2
- package/corpus/templates/clips/desktop/package.json +6 -4
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +26 -13
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +4 -1
- package/corpus/templates/clips/package.json +1 -2
- package/corpus/templates/clips/server/db/schema.ts +29 -17
- package/corpus/templates/clips/server/plugins/db.ts +8 -0
- package/corpus/templates/clips/server/routes/api/view-event.post.ts +260 -105
- package/corpus/templates/content/.agents/skills/notion-integration/SKILL.md +16 -0
- package/corpus/templates/content/AGENTS.md +72 -62
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +84 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +206 -96
- package/corpus/templates/content/actions/_notion-database-source-adapter.ts +216 -0
- package/corpus/templates/content/actions/_position-utils.ts +76 -0
- package/corpus/templates/content/actions/_property-utils.ts +31 -20
- package/corpus/templates/content/actions/add-database-item.ts +52 -39
- package/corpus/templates/content/actions/attach-content-database-source.ts +95 -11
- package/corpus/templates/content/actions/change-content-database-source-role.ts +24 -1
- package/corpus/templates/content/actions/configure-document-property.ts +36 -27
- package/corpus/templates/content/actions/create-content-database.ts +40 -30
- package/corpus/templates/content/actions/create-document.ts +39 -32
- package/corpus/templates/content/actions/duplicate-document-property.ts +37 -28
- package/corpus/templates/content/actions/list-notion-database-sources.ts +80 -0
- package/corpus/templates/content/actions/move-document.ts +64 -48
- package/corpus/templates/content/actions/refresh-content-database-source.ts +42 -3
- package/corpus/templates/content/actions/share-local-file-document.ts +36 -27
- package/corpus/templates/content/actions/suggest-source-join-key.ts +39 -16
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +89 -23
- package/corpus/templates/content/app/components/editor/database/settings.tsx +1 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +18 -1
- package/corpus/templates/content/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/content/app/i18n-data.ts +40 -0
- package/corpus/templates/content/changelog/2026-07-10-failed-row-creation-and-source-attach-now-show-an-error-inst.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-items-added-at-the-same-time-no-longer-end-up-with-identical.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-notion-databases-can-add-read-only-details-to-content-tables.md +6 -0
- package/corpus/templates/content/package.json +1 -2
- package/corpus/templates/content/parity/matrix.md +25 -25
- package/corpus/templates/content/parity/matrix.ts +1 -0
- package/corpus/templates/content/server/routes/api/documents/[id]/move.patch.ts +43 -26
- package/corpus/templates/content/shared/api.ts +16 -1
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +8 -4
- package/corpus/templates/design/AGENTS.md +12 -2
- package/corpus/templates/design/DEVELOPING.md +10 -6
- package/corpus/templates/design/actions/apply-visual-edit.ts +191 -6
- package/corpus/templates/design/actions/duplicate-design.ts +31 -25
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +5 -1
- package/corpus/templates/design/changelog/2026-07-10-duplicating-a-design-can-no-longer-produce-a-copy-with-missi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-local-visual-edits-can-now-save-safe-leaf-text-class-and.md +6 -0
- package/corpus/templates/design/package.json +1 -1
- package/corpus/templates/design/shared/local-jsx-visual-edit.ts +388 -0
- package/corpus/templates/dispatch/package.json +1 -2
- package/corpus/templates/forms/actions/patch-form-fields.ts +82 -44
- package/corpus/templates/forms/changelog/2026-07-10-simultaneous-edits-to-different-form-fields-no-longer-overwr.md +6 -0
- package/corpus/templates/forms/package.json +1 -2
- package/corpus/templates/macros/package.json +1 -2
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +3 -1
- package/corpus/templates/mail/changelog/2026-07-10-fixed-calendar-rsvp-buttons-in-emails-firing-duplicate-respo.md +6 -0
- package/corpus/templates/mail/package.json +1 -2
- package/corpus/templates/plan/actions/restore-plan-version.ts +126 -96
- package/corpus/templates/plan/changelog/2026-07-10-restoring-a-plan-version-can-no-longer-leave-a-plan-half-res.md +6 -0
- package/corpus/templates/plan/package.json +1 -2
- package/corpus/templates/plan/server/plans.ts +9 -2
- package/corpus/templates/slides/actions/list-decks.ts +28 -0
- package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +41 -26
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +4 -8
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +2 -4
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -8
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -12
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +3 -6
- package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +3 -6
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +2 -5
- package/corpus/templates/slides/app/context/DeckContext.tsx +47 -4
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +11 -30
- package/corpus/templates/slides/app/pages/Index.tsx +2 -3
- package/corpus/templates/slides/app/root.tsx +0 -5
- package/corpus/templates/slides/changelog/2026-07-10-deck-lists-load-much-faster-and-no-longer-re-download-slide-.md +6 -0
- package/corpus/templates/slides/package.json +1 -2
- package/dist/a2a/auth-policy.d.ts +19 -0
- package/dist/a2a/auth-policy.d.ts.map +1 -1
- package/dist/a2a/auth-policy.js +32 -0
- package/dist/a2a/auth-policy.js.map +1 -1
- package/dist/agent/durable-background.d.ts +8 -5
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +15 -8
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +9 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/audit/actions/export-audit-events.d.ts +27 -0
- package/dist/audit/actions/export-audit-events.d.ts.map +1 -0
- package/dist/audit/actions/export-audit-events.js +144 -0
- package/dist/audit/actions/export-audit-events.js.map +1 -0
- package/dist/audit/store.d.ts +1 -0
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +8 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/audit/types.d.ts +3 -0
- package/dist/audit/types.d.ts.map +1 -1
- package/dist/audit/types.js.map +1 -1
- package/dist/cli/doctor.d.ts +83 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +277 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/info.js +3 -3
- package/dist/cli/info.js.map +1 -1
- package/dist/cli/package-lifecycle.d.ts +50 -0
- package/dist/cli/package-lifecycle.d.ts.map +1 -0
- package/dist/cli/package-lifecycle.js +560 -0
- package/dist/cli/package-lifecycle.js.map +1 -0
- package/dist/client/agent-chat-adapter.js +7 -3
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/guards/db-tool-scoping.d.ts +38 -0
- package/dist/guards/db-tool-scoping.d.ts.map +1 -0
- package/dist/guards/db-tool-scoping.js +114 -0
- package/dist/guards/db-tool-scoping.js.map +1 -0
- package/dist/guards/index.d.ts +23 -0
- package/dist/guards/index.d.ts.map +1 -0
- package/dist/guards/index.js +19 -0
- package/dist/guards/index.js.map +1 -0
- package/dist/guards/no-drizzle-push.d.ts +14 -0
- package/dist/guards/no-drizzle-push.d.ts.map +1 -0
- package/dist/guards/no-drizzle-push.js +97 -0
- package/dist/guards/no-drizzle-push.js.map +1 -0
- package/dist/guards/no-env-credentials.d.ts +36 -0
- package/dist/guards/no-env-credentials.d.ts.map +1 -0
- package/dist/guards/no-env-credentials.js +204 -0
- package/dist/guards/no-env-credentials.js.map +1 -0
- package/dist/guards/no-env-mutation.d.ts +23 -0
- package/dist/guards/no-env-mutation.d.ts.map +1 -0
- package/dist/guards/no-env-mutation.js +90 -0
- package/dist/guards/no-env-mutation.js.map +1 -0
- package/dist/guards/no-localhost-fallback.d.ts +30 -0
- package/dist/guards/no-localhost-fallback.d.ts.map +1 -0
- package/dist/guards/no-localhost-fallback.js +105 -0
- package/dist/guards/no-localhost-fallback.js.map +1 -0
- package/dist/guards/no-unscoped-credentials.d.ts +28 -0
- package/dist/guards/no-unscoped-credentials.d.ts.map +1 -0
- package/dist/guards/no-unscoped-credentials.js +199 -0
- package/dist/guards/no-unscoped-credentials.js.map +1 -0
- package/dist/guards/no-unscoped-queries.d.ts +32 -0
- package/dist/guards/no-unscoped-queries.d.ts.map +1 -0
- package/dist/guards/no-unscoped-queries.js +720 -0
- package/dist/guards/no-unscoped-queries.js.map +1 -0
- package/dist/guards/scan-utils.d.ts +28 -0
- package/dist/guards/scan-utils.d.ts.map +1 -0
- package/dist/guards/scan-utils.js +92 -0
- package/dist/guards/scan-utils.js.map +1 -0
- package/dist/guards/types.d.ts +28 -0
- package/dist/guards/types.d.ts.map +1 -0
- package/dist/guards/types.js +11 -0
- package/dist/guards/types.js.map +1 -0
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +9 -1
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/migrations.d.ts +7 -2
- package/dist/org/migrations.d.ts.map +1 -1
- package/dist/org/migrations.js +41 -0
- package/dist/org/migrations.js.map +1 -1
- package/dist/package-lifecycle/index.d.ts +2 -0
- package/dist/package-lifecycle/index.d.ts.map +1 -0
- package/dist/package-lifecycle/index.js +2 -0
- package/dist/package-lifecycle/index.js.map +1 -0
- package/dist/package-lifecycle/manifest.d.ts +24 -0
- package/dist/package-lifecycle/manifest.d.ts.map +1 -0
- package/dist/package-lifecycle/manifest.js +81 -0
- package/dist/package-lifecycle/manifest.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/custom-registry.d.ts +32 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +41 -1
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/provider-api/index.d.ts +1 -1
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +1 -1
- package/dist/provider-api/index.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +45 -27
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +47 -13
- 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 +23 -12
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +18 -5
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +34 -0
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/google-realtime-session.d.ts.map +1 -1
- package/dist/server/google-realtime-session.js +2 -34
- package/dist/server/google-realtime-session.js.map +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +24 -2
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-voice.d.ts.map +1 -1
- package/dist/server/realtime-voice.js +9 -0
- package/dist/server/realtime-voice.js.map +1 -1
- package/dist/server/request-origin.d.ts +10 -0
- package/dist/server/request-origin.d.ts.map +1 -0
- package/dist/server/request-origin.js +60 -0
- package/dist/server/request-origin.js.map +1 -0
- package/dist/server/transcribe-voice.d.ts.map +1 -1
- package/dist/server/transcribe-voice.js +2 -48
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/templates/default/package.json +2 -2
- package/dist/templates/headless/package.json +0 -1
- package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +3 -0
- package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -1
- package/dist/templates/workspace-core/package.json +0 -1
- package/dist/templates/workspace-root/package.json +0 -1
- package/dist/triggers/actions.d.ts.map +1 -1
- package/dist/triggers/actions.js +8 -3
- package/dist/triggers/actions.js.map +1 -1
- package/dist/triggers/types.d.ts +7 -1
- package/dist/triggers/types.d.ts.map +1 -1
- package/dist/triggers/types.js.map +1 -1
- package/docs/content/audit-log.mdx +2 -1
- package/docs/content/automations.mdx +7 -7
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/internationalization.mdx +2 -1
- package/docs/content/locales/ar-SA/audit-log.mdx +2 -1
- package/docs/content/locales/ar-SA/automations.mdx +7 -7
- package/docs/content/locales/ar-SA/getting-started.mdx +7 -1
- package/docs/content/locales/ar-SA/internationalization.mdx +1 -1
- package/docs/content/locales/ar-SA/package-lifecycle.mdx +19 -0
- package/docs/content/locales/ar-SA/sandbox-adapters.mdx +18 -7
- package/docs/content/locales/ar-SA/security.mdx +8 -0
- package/docs/content/locales/de-DE/audit-log.mdx +2 -1
- package/docs/content/locales/de-DE/automations.mdx +7 -7
- package/docs/content/locales/de-DE/getting-started.mdx +7 -1
- package/docs/content/locales/de-DE/internationalization.mdx +1 -1
- package/docs/content/locales/de-DE/package-lifecycle.mdx +22 -0
- package/docs/content/locales/de-DE/sandbox-adapters.mdx +18 -7
- package/docs/content/locales/de-DE/security.mdx +8 -0
- package/docs/content/locales/es-ES/audit-log.mdx +2 -1
- package/docs/content/locales/es-ES/automations.mdx +7 -7
- package/docs/content/locales/es-ES/getting-started.mdx +7 -1
- package/docs/content/locales/es-ES/internationalization.mdx +1 -1
- package/docs/content/locales/es-ES/package-lifecycle.mdx +22 -0
- package/docs/content/locales/es-ES/sandbox-adapters.mdx +18 -7
- package/docs/content/locales/es-ES/security.mdx +8 -0
- package/docs/content/locales/fr-FR/audit-log.mdx +2 -1
- package/docs/content/locales/fr-FR/automations.mdx +7 -7
- package/docs/content/locales/fr-FR/getting-started.mdx +7 -1
- package/docs/content/locales/fr-FR/internationalization.mdx +1 -1
- package/docs/content/locales/fr-FR/package-lifecycle.mdx +21 -0
- package/docs/content/locales/fr-FR/sandbox-adapters.mdx +19 -8
- package/docs/content/locales/fr-FR/security.mdx +8 -0
- package/docs/content/locales/hi-IN/audit-log.mdx +2 -1
- package/docs/content/locales/hi-IN/automations.mdx +7 -7
- package/docs/content/locales/hi-IN/getting-started.mdx +7 -1
- package/docs/content/locales/hi-IN/internationalization.mdx +1 -1
- package/docs/content/locales/hi-IN/package-lifecycle.mdx +20 -0
- package/docs/content/locales/hi-IN/sandbox-adapters.mdx +18 -7
- package/docs/content/locales/hi-IN/security.mdx +8 -0
- package/docs/content/locales/ja-JP/audit-log.mdx +2 -1
- package/docs/content/locales/ja-JP/automations.mdx +7 -7
- package/docs/content/locales/ja-JP/getting-started.mdx +7 -1
- package/docs/content/locales/ja-JP/internationalization.mdx +1 -1
- package/docs/content/locales/ja-JP/package-lifecycle.mdx +21 -0
- package/docs/content/locales/ja-JP/sandbox-adapters.mdx +17 -6
- package/docs/content/locales/ja-JP/security.mdx +8 -0
- package/docs/content/locales/ko-KR/audit-log.mdx +2 -1
- package/docs/content/locales/ko-KR/automations.mdx +7 -7
- package/docs/content/locales/ko-KR/getting-started.mdx +7 -1
- package/docs/content/locales/ko-KR/internationalization.mdx +1 -1
- package/docs/content/locales/ko-KR/package-lifecycle.mdx +20 -0
- package/docs/content/locales/ko-KR/sandbox-adapters.mdx +17 -6
- package/docs/content/locales/ko-KR/security.mdx +8 -0
- package/docs/content/locales/pt-BR/audit-log.mdx +2 -1
- package/docs/content/locales/pt-BR/automations.mdx +7 -7
- package/docs/content/locales/pt-BR/getting-started.mdx +7 -1
- package/docs/content/locales/pt-BR/internationalization.mdx +1 -1
- package/docs/content/locales/pt-BR/package-lifecycle.mdx +21 -0
- package/docs/content/locales/pt-BR/sandbox-adapters.mdx +18 -7
- package/docs/content/locales/pt-BR/security.mdx +8 -0
- package/docs/content/locales/zh-CN/audit-log.mdx +2 -1
- package/docs/content/locales/zh-CN/automations.mdx +7 -7
- package/docs/content/locales/zh-CN/getting-started.mdx +6 -4
- package/docs/content/locales/zh-CN/internationalization.mdx +1 -1
- package/docs/content/locales/zh-CN/package-lifecycle.mdx +19 -0
- package/docs/content/locales/zh-CN/sandbox-adapters.mdx +17 -6
- package/docs/content/locales/zh-CN/security.mdx +8 -0
- package/docs/content/locales/zh-TW/audit-log.mdx +2 -1
- package/docs/content/locales/zh-TW/automations.mdx +7 -7
- package/docs/content/locales/zh-TW/getting-started.mdx +6 -4
- package/docs/content/locales/zh-TW/internationalization.mdx +1 -1
- package/docs/content/locales/zh-TW/package-lifecycle.mdx +19 -0
- package/docs/content/locales/zh-TW/sandbox-adapters.mdx +18 -7
- package/docs/content/locales/zh-TW/security.mdx +8 -0
- package/docs/content/package-lifecycle.mdx +51 -0
- package/docs/content/sandbox-adapters.mdx +16 -5
- package/package.json +10 -3
- package/src/templates/default/package.json +2 -2
- package/src/templates/headless/package.json +0 -1
- package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +3 -0
- package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +1 -1
- package/src/templates/workspace-core/package.json +0 -1
- package/src/templates/workspace-root/package.json +0 -1
- package/corpus/templates/slides/app/components/ui/toast.tsx +0 -1
- package/corpus/templates/slides/app/components/ui/toaster.tsx +0 -1
- package/corpus/templates/slides/app/hooks/use-toast.ts +0 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* | "cta-click" | "reaction",
|
|
12
12
|
* timestampMs?: number,
|
|
13
13
|
* payload?: object,
|
|
14
|
-
* viewerEmail?: string, //
|
|
14
|
+
* viewerEmail?: string, // ignored; authenticated session is authoritative
|
|
15
15
|
* viewerName?: string,
|
|
16
16
|
* sessionId: string, // anonymous-viewer key (persisted in browser)
|
|
17
17
|
* viewSessionId?: string, // per-player-open key for counted visits
|
|
@@ -27,10 +27,14 @@
|
|
|
27
27
|
|
|
28
28
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
29
29
|
import { emit } from "@agent-native/core/event-bus";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
getSession,
|
|
32
|
+
readBodyWithSizeLimit,
|
|
33
|
+
runWithRequestContext,
|
|
34
|
+
} from "@agent-native/core/server";
|
|
31
35
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
32
|
-
import { and, eq } from "drizzle-orm";
|
|
33
|
-
import { defineEventHandler,
|
|
36
|
+
import { and, asc, eq, isNull, sql } from "drizzle-orm";
|
|
37
|
+
import { defineEventHandler, getRequestIP, setResponseStatus } from "h3";
|
|
34
38
|
|
|
35
39
|
import { getDb, schema } from "../../db/index.js";
|
|
36
40
|
import { nanoid, shouldCountView } from "../../lib/recordings.js";
|
|
@@ -69,12 +73,29 @@ const ALLOWED_KINDS = new Set([
|
|
|
69
73
|
// Simple in-memory rate limiter — per IP per 10s window.
|
|
70
74
|
const RATE_LIMIT_WINDOW_MS = 10_000;
|
|
71
75
|
const RATE_LIMIT_MAX = 60;
|
|
76
|
+
const RATE_LIMIT_MAX_BUCKETS = 5000;
|
|
77
|
+
const MAX_BODY_BYTES = 16 * 1024;
|
|
78
|
+
const MAX_ID_CHARS = 256;
|
|
79
|
+
const MAX_VIEWER_NAME_CHARS = 200;
|
|
80
|
+
const MAX_EVENT_TIME_MS = 7 * 24 * 60 * 60 * 1000;
|
|
81
|
+
const MAX_PAYLOAD_BYTES = 8 * 1024;
|
|
72
82
|
const rateBuckets = new Map<string, { count: number; reset: number }>();
|
|
73
83
|
|
|
84
|
+
function pruneExpiredRateBuckets(now: number): void {
|
|
85
|
+
for (const [key, bucket] of rateBuckets) {
|
|
86
|
+
if (bucket.reset < now) rateBuckets.delete(key);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
function rateLimit(key: string): boolean {
|
|
75
91
|
const now = Date.now();
|
|
76
92
|
const existing = rateBuckets.get(key);
|
|
77
93
|
if (!existing || existing.reset < now) {
|
|
94
|
+
if (existing) rateBuckets.delete(key);
|
|
95
|
+
if (rateBuckets.size >= RATE_LIMIT_MAX_BUCKETS) {
|
|
96
|
+
pruneExpiredRateBuckets(now);
|
|
97
|
+
if (rateBuckets.size >= RATE_LIMIT_MAX_BUCKETS) return false;
|
|
98
|
+
}
|
|
78
99
|
rateBuckets.set(key, { count: 1, reset: now + RATE_LIMIT_WINDOW_MS });
|
|
79
100
|
return true;
|
|
80
101
|
}
|
|
@@ -83,47 +104,126 @@ function rateLimit(key: string): boolean {
|
|
|
83
104
|
return true;
|
|
84
105
|
}
|
|
85
106
|
|
|
107
|
+
export function __resetViewEventRateLimitForTests(): void {
|
|
108
|
+
rateBuckets.clear();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function boundedString(value: unknown, maxChars: number): string | null {
|
|
112
|
+
if (typeof value !== "string") return null;
|
|
113
|
+
const trimmed = value.trim();
|
|
114
|
+
return trimmed && trimmed.length <= maxChars ? trimmed : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function boundedNumber(
|
|
118
|
+
value: unknown,
|
|
119
|
+
defaultValue: number,
|
|
120
|
+
max: number,
|
|
121
|
+
): number | null {
|
|
122
|
+
const resolved = value === undefined ? defaultValue : value;
|
|
123
|
+
return typeof resolved === "number" &&
|
|
124
|
+
Number.isFinite(resolved) &&
|
|
125
|
+
resolved >= 0 &&
|
|
126
|
+
resolved <= max
|
|
127
|
+
? resolved
|
|
128
|
+
: null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function serializedPayload(value: unknown): string | null {
|
|
132
|
+
if (
|
|
133
|
+
value === null ||
|
|
134
|
+
typeof value !== "object" ||
|
|
135
|
+
Array.isArray(value) ||
|
|
136
|
+
(Object.getPrototypeOf(value) !== Object.prototype &&
|
|
137
|
+
Object.getPrototypeOf(value) !== null)
|
|
138
|
+
) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const serialized = JSON.stringify(value);
|
|
143
|
+
return new TextEncoder().encode(serialized).byteLength <= MAX_PAYLOAD_BYTES
|
|
144
|
+
? serialized
|
|
145
|
+
: null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
86
151
|
export default defineEventHandler(async (event) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
152
|
+
let body: ViewEventBody | null;
|
|
153
|
+
try {
|
|
154
|
+
body = await readBodyWithSizeLimit<ViewEventBody>(event, MAX_BODY_BYTES);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const statusCode = (error as { statusCode?: number })?.statusCode;
|
|
157
|
+
setResponseStatus(event, statusCode === 413 ? 413 : 400);
|
|
158
|
+
return {
|
|
159
|
+
error:
|
|
160
|
+
statusCode === 413 ? "Request body too large" : "Invalid request body",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
90
163
|
if (!body || typeof body !== "object") {
|
|
91
164
|
setResponseStatus(event, 400);
|
|
92
165
|
return { error: "Invalid body" };
|
|
93
166
|
}
|
|
94
167
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
168
|
+
const recordingId = boundedString(body.recordingId, MAX_ID_CHARS);
|
|
169
|
+
const sessionId = boundedString(body.sessionId, MAX_ID_CHARS);
|
|
170
|
+
const viewSessionId =
|
|
171
|
+
body.viewSessionId === undefined
|
|
172
|
+
? null
|
|
173
|
+
: boundedString(body.viewSessionId, MAX_ID_CHARS);
|
|
174
|
+
const viewerName =
|
|
175
|
+
body.viewerName === undefined
|
|
176
|
+
? null
|
|
177
|
+
: boundedString(body.viewerName, MAX_VIEWER_NAME_CHARS);
|
|
178
|
+
const timestampMs = boundedNumber(body.timestampMs, 0, MAX_EVENT_TIME_MS);
|
|
179
|
+
const totalWatchMs = boundedNumber(body.totalWatchMs, 0, MAX_EVENT_TIME_MS);
|
|
180
|
+
const completedPct = boundedNumber(body.completedPct, 0, 100);
|
|
181
|
+
const payload = serializedPayload(body.payload ?? {});
|
|
106
182
|
|
|
107
|
-
if (!recordingId
|
|
183
|
+
if (!recordingId) {
|
|
108
184
|
setResponseStatus(event, 400);
|
|
109
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
error: "recordingId is required and must be at most 256 characters",
|
|
187
|
+
};
|
|
110
188
|
}
|
|
111
|
-
if (!kind || !ALLOWED_KINDS.has(kind)) {
|
|
189
|
+
if (!body.kind || !ALLOWED_KINDS.has(body.kind)) {
|
|
112
190
|
setResponseStatus(event, 400);
|
|
113
|
-
return { error: `Invalid kind: ${kind}` };
|
|
191
|
+
return { error: `Invalid kind: ${body.kind}` };
|
|
114
192
|
}
|
|
115
|
-
if (!sessionId
|
|
193
|
+
if (!sessionId) {
|
|
116
194
|
setResponseStatus(event, 400);
|
|
117
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
error: "sessionId is required and must be at most 256 characters",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (body.viewSessionId !== undefined && !viewSessionId) {
|
|
200
|
+
setResponseStatus(event, 400);
|
|
201
|
+
return { error: "viewSessionId must be at most 256 characters" };
|
|
202
|
+
}
|
|
203
|
+
if (body.viewerName !== undefined && !viewerName) {
|
|
204
|
+
setResponseStatus(event, 400);
|
|
205
|
+
return { error: "viewerName must be at most 200 characters" };
|
|
206
|
+
}
|
|
207
|
+
if (timestampMs === null || totalWatchMs === null || completedPct === null) {
|
|
208
|
+
setResponseStatus(event, 400);
|
|
209
|
+
return { error: "Invalid view metrics" };
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
body.scrubbedToEnd !== undefined &&
|
|
213
|
+
typeof body.scrubbedToEnd !== "boolean"
|
|
214
|
+
) {
|
|
215
|
+
setResponseStatus(event, 400);
|
|
216
|
+
return { error: "scrubbedToEnd must be a boolean" };
|
|
217
|
+
}
|
|
218
|
+
if (payload === null) {
|
|
219
|
+
setResponseStatus(event, 400);
|
|
220
|
+
return { error: "payload must be a plain object no larger than 8 KiB" };
|
|
118
221
|
}
|
|
119
222
|
|
|
120
223
|
// Rate limit by IP + sessionId.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
?.trim() ||
|
|
125
|
-
event.node?.req?.socket?.remoteAddress ||
|
|
126
|
-
"unknown";
|
|
224
|
+
// Deliberately do not opt into x-forwarded-for parsing: only the hosting
|
|
225
|
+
// adapter's resolved peer address is trusted for this process-local guard.
|
|
226
|
+
const ip = getRequestIP(event) || "unknown";
|
|
127
227
|
if (!rateLimit(`${ip}:${sessionId}`)) {
|
|
128
228
|
setResponseStatus(event, 429);
|
|
129
229
|
return { error: "Rate limit exceeded" };
|
|
@@ -132,8 +232,10 @@ export default defineEventHandler(async (event) => {
|
|
|
132
232
|
const session = await getSession(event).catch(() => null);
|
|
133
233
|
const sessionEmail = session?.email;
|
|
134
234
|
const viewerEmail = sessionEmail ?? null;
|
|
135
|
-
const
|
|
235
|
+
const resolvedViewerName = viewerName ?? sessionEmail?.split("@")[0] ?? null;
|
|
136
236
|
const now = new Date().toISOString();
|
|
237
|
+
const kind = body.kind;
|
|
238
|
+
const scrubbedToEnd = body.scrubbedToEnd ?? false;
|
|
137
239
|
|
|
138
240
|
return runWithRequestContext(
|
|
139
241
|
{ userEmail: sessionEmail, orgId: session?.orgId },
|
|
@@ -152,106 +254,159 @@ export default defineEventHandler(async (event) => {
|
|
|
152
254
|
// present) else sessionId. We store the session id in the viewer_name
|
|
153
255
|
// column as a best-effort fallback so anon sessions don't conflate.
|
|
154
256
|
const viewerKey = viewerEmail ?? `anon:${sessionId}`;
|
|
155
|
-
const countedViewSessionId =
|
|
156
|
-
typeof viewSessionId === "string" && viewSessionId.trim()
|
|
157
|
-
? viewSessionId.trim()
|
|
158
|
-
: `legacy:${sessionId}`;
|
|
257
|
+
const countedViewSessionId = viewSessionId ?? `legacy:${sessionId}`;
|
|
159
258
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
259
|
+
const selectViewerByKey = () =>
|
|
260
|
+
db
|
|
261
|
+
.select({
|
|
262
|
+
id: schema.recordingViewers.id,
|
|
263
|
+
totalWatchMs: schema.recordingViewers.totalWatchMs,
|
|
264
|
+
completedPct: schema.recordingViewers.completedPct,
|
|
265
|
+
countedView: schema.recordingViewers.countedView,
|
|
266
|
+
ctaClicked: schema.recordingViewers.ctaClicked,
|
|
267
|
+
})
|
|
268
|
+
.from(schema.recordingViewers)
|
|
269
|
+
.where(
|
|
270
|
+
and(
|
|
271
|
+
eq(schema.recordingViewers.recordingId, recordingId),
|
|
272
|
+
eq(schema.recordingViewers.viewerKey, viewerKey),
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
.limit(1);
|
|
276
|
+
|
|
277
|
+
let [existing] = await selectViewerByKey();
|
|
278
|
+
if (!existing) {
|
|
279
|
+
const legacyIdentity = viewerEmail
|
|
280
|
+
? eq(schema.recordingViewers.viewerEmail, viewerEmail)
|
|
281
|
+
: and(
|
|
282
|
+
isNull(schema.recordingViewers.viewerEmail),
|
|
283
|
+
eq(schema.recordingViewers.viewerName, viewerKey),
|
|
284
|
+
);
|
|
285
|
+
const [legacy] = await db
|
|
286
|
+
.select({ id: schema.recordingViewers.id })
|
|
287
|
+
.from(schema.recordingViewers)
|
|
288
|
+
.where(
|
|
289
|
+
and(
|
|
290
|
+
eq(schema.recordingViewers.recordingId, recordingId),
|
|
291
|
+
isNull(schema.recordingViewers.viewerKey),
|
|
292
|
+
legacyIdentity,
|
|
293
|
+
),
|
|
294
|
+
)
|
|
295
|
+
.orderBy(
|
|
296
|
+
asc(schema.recordingViewers.firstViewedAt),
|
|
297
|
+
asc(schema.recordingViewers.id),
|
|
298
|
+
)
|
|
299
|
+
.limit(1);
|
|
300
|
+
|
|
301
|
+
if (legacy) {
|
|
302
|
+
await db
|
|
303
|
+
.update(schema.recordingViewers)
|
|
304
|
+
.set({ viewerKey })
|
|
305
|
+
.where(
|
|
306
|
+
and(
|
|
307
|
+
eq(schema.recordingViewers.id, legacy.id),
|
|
308
|
+
eq(schema.recordingViewers.recordingId, recordingId),
|
|
309
|
+
isNull(schema.recordingViewers.viewerKey),
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
} else {
|
|
313
|
+
await db
|
|
314
|
+
.insert(schema.recordingViewers)
|
|
315
|
+
.values({
|
|
316
|
+
id: nanoid(),
|
|
317
|
+
recordingId,
|
|
318
|
+
viewerKey,
|
|
319
|
+
viewerEmail,
|
|
320
|
+
viewerName: viewerEmail ? resolvedViewerName : viewerKey,
|
|
321
|
+
firstViewedAt: now,
|
|
322
|
+
lastViewedAt: now,
|
|
323
|
+
totalWatchMs: 0,
|
|
324
|
+
completedPct: 0,
|
|
325
|
+
countedView: false,
|
|
326
|
+
ctaClicked: false,
|
|
327
|
+
})
|
|
328
|
+
.onConflictDoNothing();
|
|
329
|
+
}
|
|
330
|
+
[existing] = await selectViewerByKey();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!existing) {
|
|
334
|
+
throw new Error("Failed to resolve canonical recording viewer");
|
|
335
|
+
}
|
|
170
336
|
|
|
171
|
-
|
|
172
|
-
const wasCountedBefore = existing?.countedView ?? false;
|
|
173
|
-
let countedView = wasCountedBefore;
|
|
337
|
+
const viewerId = existing.id;
|
|
174
338
|
const newTotalWatchMs = Math.max(
|
|
175
|
-
existing
|
|
339
|
+
existing.totalWatchMs,
|
|
176
340
|
Math.floor(totalWatchMs),
|
|
177
341
|
);
|
|
178
342
|
const newCompletedPct = Math.max(
|
|
179
|
-
existing
|
|
343
|
+
existing.completedPct,
|
|
180
344
|
Math.floor(completedPct),
|
|
181
345
|
);
|
|
182
|
-
|
|
183
346
|
const meetsThreshold = shouldCountView(
|
|
184
347
|
newTotalWatchMs,
|
|
185
348
|
newCompletedPct,
|
|
186
|
-
|
|
349
|
+
scrubbedToEnd,
|
|
187
350
|
);
|
|
188
|
-
if (meetsThreshold) countedView = true;
|
|
189
|
-
|
|
190
|
-
const ctaClicked =
|
|
191
|
-
kind === "cta-click" ? true : (existing?.ctaClicked ?? false);
|
|
192
351
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
await db
|
|
352
|
+
const persisted = await db.transaction(async (tx) => {
|
|
353
|
+
await tx
|
|
196
354
|
.update(schema.recordingViewers)
|
|
197
355
|
.set({
|
|
198
356
|
lastViewedAt: now,
|
|
199
|
-
totalWatchMs:
|
|
200
|
-
completedPct:
|
|
201
|
-
countedView,
|
|
202
|
-
ctaClicked,
|
|
357
|
+
totalWatchMs: sql`CASE WHEN ${schema.recordingViewers.totalWatchMs} > ${Math.floor(totalWatchMs)} THEN ${schema.recordingViewers.totalWatchMs} ELSE ${Math.floor(totalWatchMs)} END`,
|
|
358
|
+
completedPct: sql`CASE WHEN ${schema.recordingViewers.completedPct} > ${Math.floor(completedPct)} THEN ${schema.recordingViewers.completedPct} ELSE ${Math.floor(completedPct)} END`,
|
|
359
|
+
...(meetsThreshold ? { countedView: true } : {}),
|
|
360
|
+
...(kind === "cta-click" ? { ctaClicked: true } : {}),
|
|
203
361
|
})
|
|
204
362
|
.where(
|
|
205
363
|
and(
|
|
206
|
-
eq(schema.recordingViewers.id, existing.id),
|
|
207
364
|
eq(schema.recordingViewers.recordingId, recordingId),
|
|
365
|
+
eq(schema.recordingViewers.viewerKey, viewerKey),
|
|
208
366
|
),
|
|
209
367
|
);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
id: viewerId,
|
|
368
|
+
|
|
369
|
+
await tx.insert(schema.recordingEvents).values({
|
|
370
|
+
id: nanoid(),
|
|
214
371
|
recordingId,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
completedPct: newCompletedPct,
|
|
221
|
-
countedView,
|
|
222
|
-
ctaClicked,
|
|
372
|
+
viewerId,
|
|
373
|
+
kind,
|
|
374
|
+
timestampMs: Math.floor(timestampMs),
|
|
375
|
+
payload,
|
|
376
|
+
createdAt: now,
|
|
223
377
|
});
|
|
224
|
-
}
|
|
225
378
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
379
|
+
if (meetsThreshold) {
|
|
380
|
+
await tx
|
|
381
|
+
.insert(schema.recordingViews)
|
|
382
|
+
.values({
|
|
383
|
+
id: nanoid(),
|
|
384
|
+
recordingId,
|
|
385
|
+
viewerId,
|
|
386
|
+
viewerKey,
|
|
387
|
+
viewSessionId: countedViewSessionId,
|
|
388
|
+
viewerEmail,
|
|
389
|
+
viewerName: viewerEmail ? resolvedViewerName : viewerKey,
|
|
390
|
+
viewedAt: now,
|
|
391
|
+
})
|
|
392
|
+
.onConflictDoNothing();
|
|
393
|
+
}
|
|
235
394
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// same player-open session collapse through the unique index.
|
|
240
|
-
if (meetsThreshold) {
|
|
241
|
-
await db
|
|
242
|
-
.insert(schema.recordingViews)
|
|
243
|
-
.values({
|
|
244
|
-
id: nanoid(),
|
|
245
|
-
recordingId,
|
|
246
|
-
viewerId,
|
|
247
|
-
viewerKey,
|
|
248
|
-
viewSessionId: countedViewSessionId,
|
|
249
|
-
viewerEmail,
|
|
250
|
-
viewerName: viewerEmail ? viewerName : viewerKey,
|
|
251
|
-
viewedAt: now,
|
|
395
|
+
const [updated] = await tx
|
|
396
|
+
.select({
|
|
397
|
+
countedView: schema.recordingViewers.countedView,
|
|
252
398
|
})
|
|
253
|
-
.
|
|
254
|
-
|
|
399
|
+
.from(schema.recordingViewers)
|
|
400
|
+
.where(
|
|
401
|
+
and(
|
|
402
|
+
eq(schema.recordingViewers.recordingId, recordingId),
|
|
403
|
+
eq(schema.recordingViewers.viewerKey, viewerKey),
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
.limit(1);
|
|
407
|
+
if (!updated) throw new Error("Canonical recording viewer disappeared");
|
|
408
|
+
return updated;
|
|
409
|
+
});
|
|
255
410
|
|
|
256
411
|
// Only broadcast a refresh signal on "meaningful" events to avoid
|
|
257
412
|
// spamming the polling clients every 2s with watch-progress
|
|
@@ -279,7 +434,7 @@ export default defineEventHandler(async (event) => {
|
|
|
279
434
|
}
|
|
280
435
|
}
|
|
281
436
|
|
|
282
|
-
return { ok: true, viewerId, countedView };
|
|
437
|
+
return { ok: true, viewerId, countedView: persisted.countedView };
|
|
283
438
|
},
|
|
284
439
|
);
|
|
285
440
|
});
|
|
@@ -114,6 +114,22 @@ find a page to link to).
|
|
|
114
114
|
pnpm action search-notion-pages --query "meeting notes"
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
### list-notion-database-sources
|
|
118
|
+
|
|
119
|
+
List Notion data sources visible to the current user's OAuth connection before
|
|
120
|
+
attaching one to a Content database:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pnpm action list-notion-database-sources --query "projects"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The database-source pilot is read-only and uses the same per-user OAuth
|
|
127
|
+
connection as page sync. Choose a returned data-source ID, run
|
|
128
|
+
`suggest-source-join-key`, then attach it with
|
|
129
|
+
`attach-content-database-source --sourceType notion-database
|
|
130
|
+
--relationshipMode details`. Use `refresh-content-database-source` to pull a
|
|
131
|
+
new bounded snapshot. Never use a pasted token or claim Notion write-back.
|
|
132
|
+
|
|
117
133
|
### disconnect-notion
|
|
118
134
|
|
|
119
135
|
Disconnect the current user's Notion OAuth connection.
|