@agent-native/core-corpus 0.210.0 → 0.210.1-nightly-20261020010058
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 +1 -1
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +11 -0
- package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/analytics/actions/estimate-query-cost.ts +421 -0
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +9 -1
- package/corpus/templates/analytics/changelog/2026-10-19-composer-focus-remains-safe-when-an-editor-changes-during-na.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-dashboard-api-guidance-works-without-an-open-dashboard.md +6 -0
- package/corpus/templates/analytics/server/lib/analytics-mcp.ts +1 -0
- package/corpus/templates/analytics/server/lib/bigquery.ts +20 -4
- package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +38 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +30 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +35 -14
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +66 -0
- package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +311 -0
- package/corpus/templates/assets/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/brain/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/calendar/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/chat/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/chat/app/components/chat/ChatHomeRedirect.tsx +2 -2
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +51 -22
- package/corpus/templates/chat/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +17 -2
- package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/clips/AGENTS.md +1 -1
- package/corpus/templates/clips/actions/get-recording-player-data.ts +41 -9
- package/corpus/templates/clips/actions/import-loom-recording.ts +9 -3
- package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +2 -1
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +2 -1
- package/corpus/templates/clips/actions/lib/native-media.ts +22 -6
- package/corpus/templates/clips/actions/lib/youtube-import.ts +247 -0
- package/corpus/templates/clips/actions/request-transcript.ts +11 -2
- package/corpus/templates/clips/actions/set-embed-duration.ts +55 -0
- package/corpus/templates/clips/actions/view-screen.ts +12 -1
- package/corpus/templates/clips/app/components/library/import-loom-dialog.tsx +13 -7
- package/corpus/templates/clips/app/components/player/player-controls.tsx +50 -15
- package/corpus/templates/clips/app/components/player/use-optimistic-reactions.ts +87 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +48 -68
- package/corpus/templates/clips/app/components/player/youtube-embed-player.tsx +690 -0
- package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +13 -21
- package/corpus/templates/clips/app/i18n/en-US.ts +12 -1
- package/corpus/templates/clips/app/lib/youtube-iframe-api.ts +135 -0
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +59 -24
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -4
- package/corpus/templates/clips/app/routes/import.tsx +13 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +16 -8
- package/corpus/templates/clips/changelog/2026-10-16-youtube-embeds.md +6 -0
- package/corpus/templates/clips/server/lib/agent-frame-request.ts +11 -6
- package/corpus/templates/clips/server/lib/ensure-recording-thumbnail.ts +7 -4
- package/corpus/templates/clips/server/lib/player-video-url.ts +9 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +18 -21
- package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +3 -3
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +15 -0
- package/corpus/templates/clips/shared/comment-transcript-context.ts +25 -0
- package/corpus/templates/clips/shared/embed-provider.ts +32 -0
- package/corpus/templates/clips/shared/youtube.ts +103 -0
- package/corpus/templates/content/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +5 -3
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +27 -6
- package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/configure-document-property.ts +1 -1
- package/corpus/templates/content/actions/delete-document-property.ts +1 -1
- package/corpus/templates/content/actions/get-blocks-field-word-count.ts +1 -1
- package/corpus/templates/content/actions/list-content-database-blocks.ts +1 -1
- package/corpus/templates/content/actions/mutate-content-database-block.ts +1 -1
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +3 -2
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +1090 -154
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +69 -32
- package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +13 -0
- package/corpus/templates/content/app/components/editor/blockFieldTabs.ts +315 -0
- package/corpus/templates/content/app/global.css +0 -5
- package/corpus/templates/content/app/hooks/use-document-properties.ts +16 -2
- package/corpus/templates/content/app/hooks/use-navigation-state.ts +28 -11
- package/corpus/templates/content/app/i18n-data.ts +15 -2
- package/corpus/templates/content/changelog/2026-10-18-collection-pages-with-more-than-one-blocks-field-show-each-f.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-renaming-a-blocks-field-from-a-collection-column-saves-again.md +6 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/content/shared/properties.ts +2 -0
- package/corpus/templates/crm/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +14 -0
- package/corpus/templates/design/.agents/skills/native-effects/SKILL.md +25 -0
- package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.generated/bridge/native-builtin-metadata.generated.ts +301 -0
- package/corpus/templates/design/.generated/bridge/native-worker-source.generated.ts +6 -0
- package/corpus/templates/design/AGENTS.md +5 -1
- package/corpus/templates/design/actions/_native-local-export-server.ts +212 -0
- package/corpus/templates/design/actions/_native-render-contexts.ts +150 -0
- package/corpus/templates/design/actions/apply-builder-edits.ts +85 -32
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +10 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +3 -0
- package/corpus/templates/design/actions/cancel-native-local-export.ts +85 -0
- package/corpus/templates/design/actions/claim-native-local-export.ts +80 -0
- package/corpus/templates/design/actions/edit-native-shader.ts +847 -0
- package/corpus/templates/design/actions/export-png.ts +5 -2
- package/corpus/templates/design/actions/finish-native-local-export.ts +171 -0
- package/corpus/templates/design/actions/get-native-local-export.ts +66 -0
- package/corpus/templates/design/actions/get-native-render-contexts.ts +16 -0
- package/corpus/templates/design/actions/get-shader.ts +219 -8
- package/corpus/templates/design/actions/list-builder-edits.ts +8 -3
- package/corpus/templates/design/actions/open-builder-branch-design.ts +4 -15
- package/corpus/templates/design/actions/prepare-native-scene-export.ts +138 -0
- package/corpus/templates/design/actions/register-native-render-context.ts +19 -0
- package/corpus/templates/design/actions/request-native-local-export.ts +99 -0
- package/corpus/templates/design/actions/send-builder-message.ts +4 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +12 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +73 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +49 -9
- package/corpus/templates/design/app/components/design/EditPanel.tsx +41 -15
- package/corpus/templates/design/app/components/design/MotionCodeView.tsx +405 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +131 -164
- package/corpus/templates/design/app/components/design/MotionNativeStyleEditor.tsx +395 -149
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +41 -14
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +79 -13
- package/corpus/templates/design/app/components/design/bridge/native-authored-paint.ts +66 -0
- package/corpus/templates/design/app/components/design/bridge/native-bounded-frame-scheduler.ts +170 -0
- package/corpus/templates/design/app/components/design/bridge/native-camera-recorder.ts +170 -0
- package/corpus/templates/design/app/components/design/bridge/native-capture-roi.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/native-color-mode.ts +165 -0
- package/corpus/templates/design/app/components/design/bridge/native-composition-budget.ts +112 -0
- package/corpus/templates/design/app/components/design/bridge/native-composition-clock.ts +849 -0
- package/corpus/templates/design/app/components/design/bridge/native-composition-session.ts +83 -0
- package/corpus/templates/design/app/components/design/bridge/native-data-mipmaps.ts +81 -0
- package/corpus/templates/design/app/components/design/bridge/native-device-lifecycle.ts +158 -0
- package/corpus/templates/design/app/components/design/bridge/native-effect-extent.ts +321 -0
- package/corpus/templates/design/app/components/design/bridge/native-effect-output-geometry.ts +135 -0
- package/corpus/templates/design/app/components/design/bridge/native-export-selection.ts +65 -0
- package/corpus/templates/design/app/components/design/bridge/native-feedback-resume.ts +141 -0
- package/corpus/templates/design/app/components/design/bridge/native-frame-resource-scratch.ts +38 -0
- package/corpus/templates/design/app/components/design/bridge/native-gpu-profiler.ts +424 -0
- package/corpus/templates/design/app/components/design/bridge/native-gpu-sample-window.ts +286 -0
- package/corpus/templates/design/app/components/design/bridge/native-gpu-scope-profiler.ts +260 -0
- package/corpus/templates/design/app/components/design/bridge/native-group-local-backdrop-window.ts +299 -0
- package/corpus/templates/design/app/components/design/bridge/native-image-replay-safety.ts +134 -0
- package/corpus/templates/design/app/components/design/bridge/native-intrinsic-image.ts +126 -0
- package/corpus/templates/design/app/components/design/bridge/native-linear-golden.ts +91 -0
- package/corpus/templates/design/app/components/design/bridge/native-live-camera-source.ts +160 -0
- package/corpus/templates/design/app/components/design/bridge/native-mounted-benchmark.ts +730 -0
- package/corpus/templates/design/app/components/design/bridge/native-mounted-output-summary.ts +243 -0
- package/corpus/templates/design/app/components/design/bridge/native-packed-uniform-arena.ts +150 -0
- package/corpus/templates/design/app/components/design/bridge/native-parameter-preview-queue.ts +66 -0
- package/corpus/templates/design/app/components/design/bridge/native-pixel-readback.ts +45 -0
- package/corpus/templates/design/app/components/design/bridge/native-playback-clock.ts +29 -0
- package/corpus/templates/design/app/components/design/bridge/native-presentation-pair.ts +128 -0
- package/corpus/templates/design/app/components/design/bridge/native-presentation-readback.ts +104 -0
- package/corpus/templates/design/app/components/design/bridge/native-presentation-slots.ts +177 -0
- package/corpus/templates/design/app/components/design/bridge/native-preview-budget.ts +95 -0
- package/corpus/templates/design/app/components/design/bridge/native-preview-geometry.ts +165 -0
- package/corpus/templates/design/app/components/design/bridge/native-preview-policy.ts +235 -0
- package/corpus/templates/design/app/components/design/bridge/native-raf-interval.ts +21 -0
- package/corpus/templates/design/app/components/design/bridge/native-receiver-distance.ts +257 -0
- package/corpus/templates/design/app/components/design/bridge/native-region-export.ts +11 -0
- package/corpus/templates/design/app/components/design/bridge/native-render-placement.ts +41 -0
- package/corpus/templates/design/app/components/design/bridge/native-runtime-epoch.ts +20 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +22595 -0
- package/corpus/templates/design/app/components/design/bridge/native-simulation-timeline.ts +86 -0
- package/corpus/templates/design/app/components/design/bridge/native-simulation-transaction.ts +165 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-affine-geometry.ts +333 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-coordinate-scale.ts +47 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-frame-witness.ts +26 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-local-geometry.ts +75 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +6980 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-sampling-cases.ts +687 -0
- package/corpus/templates/design/app/components/design/bridge/native-source-sampling.ts +135 -0
- package/corpus/templates/design/app/components/design/bridge/native-stateful-resource.ts +936 -0
- package/corpus/templates/design/app/components/design/bridge/native-stateless-compute.ts +226 -0
- package/corpus/templates/design/app/components/design/bridge/native-stateless-frame-transaction.ts +181 -0
- package/corpus/templates/design/app/components/design/bridge/native-submission-pipeline.ts +67 -0
- package/corpus/templates/design/app/components/design/bridge/native-svg-geometry.ts +90 -0
- package/corpus/templates/design/app/components/design/bridge/native-svg-pattern-fixtures.ts +114 -0
- package/corpus/templates/design/app/components/design/bridge/native-svg-pattern-work.ts +114 -0
- package/corpus/templates/design/app/components/design/bridge/native-svg-source.ts +1092 -0
- package/corpus/templates/design/app/components/design/bridge/native-texture-feedback.ts +916 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-backdrop-route.ts +9 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-broker.ts +242 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-compositor.ts +934 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-entry.ts +2295 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-fill.ts +479 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-gpu-wgsl.ts +650 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-gpu.ts +1059 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-host.ts +598 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-protocol.ts +185 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +1813 -0
- package/corpus/templates/design/app/components/design/bridge/native-worker-source-snapshot.ts +274 -0
- package/corpus/templates/design/app/components/design/design-canvas/interact-document.ts +9 -5
- package/corpus/templates/design/app/components/design/design-canvas/local-runtime.ts +18 -1
- package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +27 -6
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +70 -9
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +278 -14
- package/corpus/templates/design/app/components/design/edit-panel/motion-controls.ts +126 -0
- package/corpus/templates/design/app/components/design/edit-panel/motion-inspector-properties.tsx +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +6 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +18 -16
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -3
- package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +35 -3
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +181 -118
- package/corpus/templates/design/app/components/design/inspector/NativeShaderCatalogPreview.tsx +191 -0
- package/corpus/templates/design/app/components/design/inspector/NativeShaderColorControls.tsx +301 -0
- package/corpus/templates/design/app/components/design/inspector/NativeShaderPaintControls.tsx +95 -0
- package/corpus/templates/design/app/components/design/inspector/NativeShaderPanel.tsx +1263 -0
- package/corpus/templates/design/app/components/design/inspector/color-picker-nested.ts +6 -0
- package/corpus/templates/design/app/components/design/inspector/color-picker-shader-pane.tsx +44 -3
- package/corpus/templates/design/app/components/design/inspector/native-catalog-display.ts +150 -0
- package/corpus/templates/design/app/components/design/inspector/native-shader-catalog-preview.ts +332 -0
- package/corpus/templates/design/app/components/design/inspector/native-shader-paint.ts +131 -0
- package/corpus/templates/design/app/components/design/inspector/native-shader-property-draft.ts +74 -0
- package/corpus/templates/design/app/components/design/native-preview-iframe-lifecycle.ts +23 -0
- package/corpus/templates/design/app/components/design/use-native-shader-approvals.ts +67 -0
- package/corpus/templates/design/app/i18n/en-US.ts +222 -0
- package/corpus/templates/design/app/lib/design-sync-invalidation.ts +17 -7
- package/corpus/templates/design/app/lib/svg-paste.ts +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +29 -23
- package/corpus/templates/design/app/pages/design-editor/agent-chat-context-bookkeeping.tsx +18 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +5 -1
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +56 -2
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +15 -3
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +3 -2
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +15 -2
- package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +7 -5
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +6 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +12 -5
- package/corpus/templates/design/app/pages/design-editor/commands/paste-svg-layer.ts +27 -6
- package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +64 -0
- package/corpus/templates/design/app/pages/design-editor/design-editor-view.tsx +188 -67
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-export-and-handoff.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-generation-and-access.ts +3 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-live-edits-and-presence.ts +11 -2
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-tools-and-vectors.ts +5 -11
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +17 -2
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +9 -2
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +12 -1
- package/corpus/templates/design/app/pages/design-editor/motion-add-menu.ts +213 -0
- package/corpus/templates/design/app/pages/design-editor/motion-code-model.ts +106 -0
- package/corpus/templates/design/app/pages/design-editor/native-current-frame-png.ts +219 -0
- package/corpus/templates/design/app/pages/design-editor/native-png-attachment-client.ts +50 -0
- package/corpus/templates/design/app/pages/design-editor/native-png-encoding.ts +172 -0
- package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +45 -1
- package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +82 -2
- package/corpus/templates/design/app/pages/design-editor/read-design-editor-source.ts +11 -3
- package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +6 -2
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +14 -2
- package/corpus/templates/design/app/pages/design-editor/use-native-local-export-requests.ts +356 -0
- package/corpus/templates/design/app/pages/design-editor/view/editor-canvas-area.tsx +23 -1
- package/corpus/templates/design/changelog/2026-10-14-add-liquid-metal-fluted-glass-image-dithering-and-waves-to-t.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-apply-curated-webgpu-fills-and-effects-to-design-layers-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-choose-each-native-shader-once-and-customize-its-parameters.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-liquid-metal-adds-fluid-contours-and-separated-color-reflections.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-mesh-gradient-now-blends-four-deliberate-color-points-into-a.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-native-fills-can-combine-shader-and-css-layers-while-preserv.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-native-shader-choices-now-show-visual-previews-and-use-the-s.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-shader-controls-use-familiar-units-and-reliable-numeric-edit.md +6 -0
- package/corpus/templates/design/changelog/2026-10-14-static-content-extending-beyond-the-viewport-no-longer-preve.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-design-adds-nebula-and-glowing-waves.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-design-adds-original-webgpu-textures-patterns-and-effects.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-glass-refracts-the-backdrop-through-rounded-layers-an.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-metal-deforms-in-place-instead-of-drifting.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-metal-flows-as-a-continuous-reflective-surface-across.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-metal-flows-through-curved-chrome-bands-with-multicolor-rims.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-metal-gains-sculpted-reflective-cells-and-crisp-chrom.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-metal-reflections-wrap-around-svg-shapes.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-liquid-metal-separates-tint-from-solid-and-gradient-fills.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-native-shader-fills-follow-svg-artwork-including-transparent.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-pasted-svg-artwork-no-longer-hides-native-shader-fills-and-e.md +6 -0
- package/corpus/templates/design/changelog/2026-10-15-svg-artwork-stays-sharp-when-zooming-in-on-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-animated-shader-fills-render-together-more-efficiently-and-p.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-create-custom-webgpu-shaders-from-the-shader-picker-with-ai.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-export-selected-shader-frames-and-liquid-glass-as-transparen.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-glass-backdrops-and-animated-shaders-stay-stable-on-large-de.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-hidden-layers-no-longer-interrupt-effects-on-visible-layers.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-new-glowing-waves-lens-distortion-and-water-shaders-preserve.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-pasted-svgs-keep-their-position-inside-freeform-frames.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-shader-fills-stay-visible-when-frames-are-wrapped-or-moved-i.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-shader-previews-remain-active-when-the-canvas-viewport-chang.md +6 -0
- package/corpus/templates/design/changelog/2026-10-16-shader-property-sliders-preview-changes-while-dragging.md +6 -0
- package/corpus/templates/design/changelog/2026-10-19-motion-shows-a-layer-s-animation-as-read-only-css-or-data-in.md +6 -0
- package/corpus/templates/design/changelog/2026-10-19-right-click-selected-layers-to-add-an-animation-and-the-anim.md +6 -0
- package/corpus/templates/design/changelog/2026-10-19-text-stagger-has-a-settings-panel-with-overlap-order-crop-an.md +6 -0
- package/corpus/templates/design/package.json +6 -6
- package/corpus/templates/design/public/shaders/gate-image-contrast.svg +8 -0
- package/corpus/templates/design/scripts/runtime-budget.ts +88 -10
- package/corpus/templates/design/server/db/schema.ts +5 -1
- package/corpus/templates/design/server/lib/builder-identity.ts +37 -5
- package/corpus/templates/design/server/lib/design-export.ts +29 -7
- package/corpus/templates/design/server/lib/design-response-guard.ts +4 -3
- package/corpus/templates/design/server/routes/api/native-png-attachment.get.ts +76 -0
- package/corpus/templates/design/server/routes/api/native-png-attachment.post.ts +195 -0
- package/corpus/templates/design/server/source-workspace.ts +25 -1
- package/corpus/templates/design/shared/code-layer.ts +5 -1
- package/corpus/templates/design/shared/css-box-shadow.ts +52 -0
- package/corpus/templates/design/shared/effect-graph.ts +790 -0
- package/corpus/templates/design/shared/html-integrity.ts +21 -0
- package/corpus/templates/design/shared/motion-layer-code.ts +167 -0
- package/corpus/templates/design/shared/motion-native-style-runtime.ts +46 -27
- package/corpus/templates/design/shared/motion-text-code.ts +68 -0
- package/corpus/templates/design/shared/native-authored-source-bindings.ts +20 -0
- package/corpus/templates/design/shared/native-benchmark-contract.ts +2 -0
- package/corpus/templates/design/shared/native-capture-cache.ts +125 -0
- package/corpus/templates/design/shared/native-clip-geometry.ts +201 -0
- package/corpus/templates/design/shared/native-css-paint-outsets.ts +134 -0
- package/corpus/templates/design/shared/native-draft-preview-contract.ts +63 -0
- package/corpus/templates/design/shared/native-editor-preferences.ts +54 -0
- package/corpus/templates/design/shared/native-effect-animation-capability.ts +526 -0
- package/corpus/templates/design/shared/native-effect-catalog-generators.ts +342 -0
- package/corpus/templates/design/shared/native-effect-catalog-kit.ts +271 -0
- package/corpus/templates/design/shared/native-effect-catalog-presets.ts +629 -0
- package/corpus/templates/design/shared/native-effect-catalog-processors.ts +443 -0
- package/corpus/templates/design/shared/native-effect-cloning.ts +141 -0
- package/corpus/templates/design/shared/native-effect-color-grade-v2.ts +89 -0
- package/corpus/templates/design/shared/native-effect-density-corrected.ts +87 -0
- package/corpus/templates/design/shared/native-effect-document-pixels-v2.ts +55 -0
- package/corpus/templates/design/shared/native-effect-edits.ts +723 -0
- package/corpus/templates/design/shared/native-effect-frame-policy.ts +58 -0
- package/corpus/templates/design/shared/native-effect-gallery-additions.ts +651 -0
- package/corpus/templates/design/shared/native-effect-liquid-glass-v1.ts +105 -0
- package/corpus/templates/design/shared/native-effect-liquid-glass-v2.ts +107 -0
- package/corpus/templates/design/shared/native-effect-liquid-glass-v3.ts +108 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v1.ts +109 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v10.ts +155 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v11.ts +167 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v2.ts +111 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v3.ts +129 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v4.ts +143 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v5.ts +151 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v6.ts +176 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v7.ts +184 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v8.ts +184 -0
- package/corpus/templates/design/shared/native-effect-liquid-metal-v9.ts +159 -0
- package/corpus/templates/design/shared/native-effect-multipass.ts +207 -0
- package/corpus/templates/design/shared/native-effect-owned-cc-source-glints.ts +190 -0
- package/corpus/templates/design/shared/native-effect-owned-next-edge-sampling.ts +79 -0
- package/corpus/templates/design/shared/native-effect-paint.ts +270 -0
- package/corpus/templates/design/shared/native-effect-parameter-constraints.ts +274 -0
- package/corpus/templates/design/shared/native-effect-position.ts +159 -0
- package/corpus/templates/design/shared/native-effect-presets.ts +456 -0
- package/corpus/templates/design/shared/native-effect-property-presentation.ts +522 -0
- package/corpus/templates/design/shared/native-effect-pruning.ts +56 -0
- package/corpus/templates/design/shared/native-effect-raster-density-policy.ts +17 -0
- package/corpus/templates/design/shared/native-effect-transform.ts +71 -0
- package/corpus/templates/design/shared/native-effect-trust.ts +165 -0
- package/corpus/templates/design/shared/native-effect-v1-graphics.ts +222 -0
- package/corpus/templates/design/shared/native-effect-v1-materials.ts +271 -0
- package/corpus/templates/design/shared/native-effect-wgsl.ts +42 -0
- package/corpus/templates/design/shared/native-effect-wide-color.ts +54 -0
- package/corpus/templates/design/shared/native-effects.ts +2819 -0
- package/corpus/templates/design/shared/native-embedded-assets.ts +182 -0
- package/corpus/templates/design/shared/native-export-region.ts +52 -0
- package/corpus/templates/design/shared/native-feedback-plan.ts +279 -0
- package/corpus/templates/design/shared/native-input-resources.ts +269 -0
- package/corpus/templates/design/shared/native-instance-preview-contract.ts +55 -0
- package/corpus/templates/design/shared/native-linear-resource-graph.ts +249 -0
- package/corpus/templates/design/shared/native-local-export.ts +153 -0
- package/corpus/templates/design/shared/native-parameter-animation.ts +290 -0
- package/corpus/templates/design/shared/native-pipeline-cache.ts +148 -0
- package/corpus/templates/design/shared/native-pointer-history.ts +193 -0
- package/corpus/templates/design/shared/native-pointer-input.ts +199 -0
- package/corpus/templates/design/shared/native-presentation-fault-gate.ts +99 -0
- package/corpus/templates/design/shared/native-presentation-fault-pin.ts +16 -0
- package/corpus/templates/design/shared/native-shader-validation.ts +1356 -0
- package/corpus/templates/design/shared/native-source-clip-plan.ts +484 -0
- package/corpus/templates/design/shared/native-source-composition-tree.ts +228 -0
- package/corpus/templates/design/shared/native-source-composition.ts +136 -0
- package/corpus/templates/design/shared/native-source-sizing.ts +141 -0
- package/corpus/templates/design/shared/native-stateful-reset-values.ts +190 -0
- package/corpus/templates/design/shared/native-stateful-resource-graph.ts +334 -0
- package/corpus/templates/design/shared/native-stateless-compute.ts +334 -0
- package/corpus/templates/design/shared/native-static-generator-pass-cache.ts +122 -0
- package/corpus/templates/design/shared/native-uniform-timing.ts +44 -0
- package/corpus/templates/design/shared/shader-fills.ts +105 -0
- package/corpus/templates/design/vitest.config.ts +1 -0
- package/corpus/templates/design/vitest.setup.ts +8 -0
- package/corpus/templates/dispatch/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/factory/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/mail/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/plan/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/slides/.agents/skills/context-awareness/SKILL.md +5 -0
- package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/tasks/.agents/skills/context-awareness/SKILL.md +5 -0
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -78,10 +78,20 @@ Never label a current partial-month snapshot as last month's completed usage.
|
|
|
78
78
|
| `data-source-status --key bigquery` | Check whether BigQuery credentials and project are configured. |
|
|
79
79
|
| `find-data --question <topic>` | Find ranked metric definitions and saved query examples before SQL. |
|
|
80
80
|
| `search-bigquery-schema` | List datasets, list tables in a dataset, or describe table columns. |
|
|
81
|
+
| `estimate-query-cost --sql "<sql>"` | Free dry run: bytes, $ per run, and $ per month for scheduled runs. |
|
|
81
82
|
| `bigquery --sql "<sql>"` | Run a real warehouse query after table/column names are known. |
|
|
82
83
|
| `query-dbt-semantic-metric --metricName <exact dbt name>` | Run an owner-defined dbt Semantic Layer metric before warehouse SQL. |
|
|
83
84
|
| `get-dbt-connection` | Check dbt readiness (`readiness`) and the missing setup step. |
|
|
84
85
|
|
|
86
|
+
## Check Cost Before Big Queries
|
|
87
|
+
|
|
88
|
+
Before running a query over a large event or pageview table, call
|
|
89
|
+
`estimate-query-cost` with the same SQL. It dry-runs the query (BigQuery reads
|
|
90
|
+
and bills nothing) and prices it at on-demand rates. Pass `runsPerMonth` for
|
|
91
|
+
anything scheduled, such as 8640 for an alert that polls every 5 minutes.
|
|
92
|
+
Estimates over $1 per run or $50 per month are flagged: tell the user the number
|
|
93
|
+
before going ahead.
|
|
94
|
+
|
|
85
95
|
For direct Amplitude/PostHog/Mixpanel product-event data (outside the warehouse
|
|
86
96
|
copy), there is no first-class action — call `provider-api-catalog` /
|
|
87
97
|
`provider-api-docs` then `provider-api-request` (provider `amplitude`, `posthog`,
|
|
@@ -196,6 +196,11 @@ changes. If a command can arrive while a chat stream is rendering, prefer
|
|
|
196
196
|
`navigate(path, { replace: true, flushSync: true })` over a view-transition
|
|
197
197
|
wrapper so the URL and visible route commit together.
|
|
198
198
|
|
|
199
|
+
Keep `path` router-local. A segment matching the app mount prefix remains part
|
|
200
|
+
of the local route unless a matching `view` route or explicit `publicPath`
|
|
201
|
+
identifies the mounted URL. Carry the mounted form separately in `publicPath`;
|
|
202
|
+
never infer that `path` is mounted from its prefix alone.
|
|
203
|
+
|
|
199
204
|
## Jitter Prevention
|
|
200
205
|
|
|
201
206
|
Agent-side application-state writes are tagged with
|
|
@@ -944,6 +944,17 @@ unavailable" placeholder.
|
|
|
944
944
|
`mutate-dashboard` for existing edits, or `update-dashboard` for new/full
|
|
945
945
|
config saves, and fix the action arguments.
|
|
946
946
|
- Never set `panel.source` to a table name or unsupported backend.
|
|
947
|
+
- Before saving a BigQuery or first-party panel that reads a large table, or
|
|
948
|
+
adding an email report to a dashboard, check its cost with
|
|
949
|
+
`estimate-query-cost --dashboardId <id>` (add `--panelId` for one panel). It
|
|
950
|
+
is a free dry run; tell the user any panel flagged over $1 per run or $50 per
|
|
951
|
+
month. Dashboard estimates count email reports but not alert rules: before
|
|
952
|
+
adding an alert, estimate an `analytics_events` query with the alert's
|
|
953
|
+
filters and its window's `event_date` and `timestamp` bounds, as alerts
|
|
954
|
+
send both (the dry run skips only partitions the `event_date` filter rules
|
|
955
|
+
out), using `--source first-party --eventsSource raw` (alerts run as
|
|
956
|
+
first-party SQL on the raw table) and `--runsPerMonth` set from the poll
|
|
957
|
+
interval (8640 for every 5 minutes).
|
|
947
958
|
- Use `first-party` for `/track` data and `query-agent-native-analytics` for ad-hoc first-party event questions.
|
|
948
959
|
- Use `update-dashboard` for new dashboard config saves and full config
|
|
949
960
|
replacements. Use `mutate-dashboard` for existing dashboard edits.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","analytics-alert-jobs","analytics-rollup-backfill-jobs","auth","collab","config","core-routes","creative-context","dashboard-report-jobs","db","feature-flags","labs","onboarding","otel","session-replay-retention-jobs","transactional-emails","uptime-monitor-jobs"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { defineAction, fail } from "@agent-native/core/action";
|
|
2
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
3
|
+
import {
|
|
4
|
+
getCredentialContext,
|
|
5
|
+
getRequestOrgId,
|
|
6
|
+
getRequestUserEmail,
|
|
7
|
+
} from "@agent-native/core/server";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
import { isSafeDashboardId } from "../server/lib/dashboard-id";
|
|
11
|
+
import {
|
|
12
|
+
pageDashboardConfig,
|
|
13
|
+
resolveDashboardPanelQueries,
|
|
14
|
+
type ResolvedDashboardPanelQuery,
|
|
15
|
+
} from "../server/lib/dashboard-panel-verification";
|
|
16
|
+
import { countEnabledDashboardReportSubscriptions } from "../server/lib/dashboard-report-subscriptions";
|
|
17
|
+
import { loadDashboardSeed } from "../server/lib/dashboard-seeds";
|
|
18
|
+
import { getDashboard } from "../server/lib/dashboards-store";
|
|
19
|
+
import type { AnalyticsScope } from "../server/lib/first-party-analytics";
|
|
20
|
+
import {
|
|
21
|
+
DAYS_PER_MONTH,
|
|
22
|
+
estimateBigQuerySql,
|
|
23
|
+
estimateFirstPartySql,
|
|
24
|
+
estimateResolvedPanel,
|
|
25
|
+
formatBytes,
|
|
26
|
+
formatUsd,
|
|
27
|
+
ON_DEMAND_USD_PER_TIB,
|
|
28
|
+
QUERY_COST_CAVEATS,
|
|
29
|
+
QUERY_COST_THRESHOLDS,
|
|
30
|
+
totalCost,
|
|
31
|
+
type PanelCostEstimate,
|
|
32
|
+
type QueryCostEstimate,
|
|
33
|
+
} from "../server/lib/query-cost-estimate";
|
|
34
|
+
import { clipHead } from "../shared/panel-render-contract";
|
|
35
|
+
|
|
36
|
+
const MAX_PANELS = 60;
|
|
37
|
+
const DRY_RUN_CONCURRENCY = 6;
|
|
38
|
+
/** Stop starting dry runs here so the action returns before its 55 s timeout. */
|
|
39
|
+
const DASHBOARD_DEADLINE_MS = 40_000;
|
|
40
|
+
|
|
41
|
+
function timedOutPanel(
|
|
42
|
+
panel: { panelId: string; title: string; source: string },
|
|
43
|
+
error: string,
|
|
44
|
+
): PanelCostEstimate {
|
|
45
|
+
return {
|
|
46
|
+
panelId: panel.panelId,
|
|
47
|
+
title: panel.title,
|
|
48
|
+
source: panel.source,
|
|
49
|
+
status: "failed",
|
|
50
|
+
failure: "timeout",
|
|
51
|
+
error,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Estimate one panel, but give up when the dashboard deadline passes, even
|
|
57
|
+
* mid-way through credential, table, or backend lookups, so finished panels
|
|
58
|
+
* still come back before the action's own timeout.
|
|
59
|
+
*/
|
|
60
|
+
async function estimatePanelBefore(
|
|
61
|
+
deadline: number,
|
|
62
|
+
panel: ResolvedDashboardPanelQuery,
|
|
63
|
+
scope: AnalyticsScope,
|
|
64
|
+
runsPerMonth: number | null,
|
|
65
|
+
signal?: AbortSignal,
|
|
66
|
+
): Promise<PanelCostEstimate> {
|
|
67
|
+
const remaining = deadline - Date.now();
|
|
68
|
+
if (remaining <= 0) {
|
|
69
|
+
return timedOutPanel(
|
|
70
|
+
panel,
|
|
71
|
+
"Skipped: this request ran out of time. Estimate this panel on its own with panelId.",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
const forwardAbort = () => controller.abort(signal?.reason);
|
|
76
|
+
if (signal?.aborted) forwardAbort();
|
|
77
|
+
signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
78
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
79
|
+
const timedOut = new Promise<PanelCostEstimate>((resolve) => {
|
|
80
|
+
timer = setTimeout(() => {
|
|
81
|
+
resolve(
|
|
82
|
+
timedOutPanel(
|
|
83
|
+
panel,
|
|
84
|
+
"Stopped: this panel's dry run didn't finish in time. Estimate it on its own with panelId.",
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
controller.abort();
|
|
88
|
+
}, remaining);
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
return await Promise.race([
|
|
92
|
+
estimateResolvedPanel(panel, scope, runsPerMonth, controller.signal),
|
|
93
|
+
timedOut,
|
|
94
|
+
]);
|
|
95
|
+
} finally {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
signal?.removeEventListener("abort", forwardAbort);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function mapPool<T, R>(
|
|
102
|
+
items: readonly T[],
|
|
103
|
+
concurrency: number,
|
|
104
|
+
fn: (item: T) => Promise<R>,
|
|
105
|
+
): Promise<R[]> {
|
|
106
|
+
const results: R[] = Array(items.length);
|
|
107
|
+
let next = 0;
|
|
108
|
+
const worker = async () => {
|
|
109
|
+
while (next < items.length) {
|
|
110
|
+
const index = next++;
|
|
111
|
+
results[index] = await fn(items[index]);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
await Promise.all(
|
|
115
|
+
Array.from({ length: Math.min(concurrency, items.length) }, worker),
|
|
116
|
+
);
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function sentence(text: string): string {
|
|
121
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function describeEstimate(estimate: QueryCostEstimate): string {
|
|
125
|
+
if (
|
|
126
|
+
estimate.status === "not_bigquery" ||
|
|
127
|
+
estimate.status === "not_estimated"
|
|
128
|
+
) {
|
|
129
|
+
return estimate.reason;
|
|
130
|
+
}
|
|
131
|
+
if (estimate.status === "failed") {
|
|
132
|
+
return `no estimate (${estimate.failure}): ${estimate.error}`;
|
|
133
|
+
}
|
|
134
|
+
const monthly =
|
|
135
|
+
estimate.usdPerMonth === null
|
|
136
|
+
? "not scheduled"
|
|
137
|
+
: `~${formatUsd(estimate.usdPerMonth)}/month over ${estimate.runsPerMonth} scheduled runs`;
|
|
138
|
+
const flags = [
|
|
139
|
+
estimate.overPerRunThreshold
|
|
140
|
+
? `over the ${formatUsd(QUERY_COST_THRESHOLDS.perRunUsd)}/run threshold`
|
|
141
|
+
: null,
|
|
142
|
+
estimate.overPerMonthThreshold
|
|
143
|
+
? `over the ${formatUsd(QUERY_COST_THRESHOLDS.perMonthUsd)}/month threshold`
|
|
144
|
+
: null,
|
|
145
|
+
].filter(Boolean);
|
|
146
|
+
return `~${formatBytes(estimate.estimatedBytes)}, ${formatUsd(estimate.usdPerRun)} per run, ${monthly}${flags.length ? ` (${flags.join("; ")})` : ""}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default defineAction({
|
|
150
|
+
description:
|
|
151
|
+
"Free cost estimate for BigQuery SQL. Dry-runs the query through Analytics' own BigQuery connection (BigQuery reads and bills nothing) and returns the bytes it would scan, dollars per run at on-demand pricing, and dollars per month for scheduled runs. " +
|
|
152
|
+
"Pass `sql` to estimate any query (source 'bigquery' for warehouse SQL, 'first-party' for analytics_events SQL), or `dashboardId` (plus optional `panelId`) to estimate saved panels exactly as the dashboard runs them. " +
|
|
153
|
+
`Call it before running or saving a query that may scan a large table, and before setting a byte cap (about 1.5x the estimate is common). Results over $${QUERY_COST_THRESHOLDS.perRunUsd}/run or $${QUERY_COST_THRESHOLDS.perMonthUsd}/month are flagged; tell the user the number before going ahead.`,
|
|
154
|
+
schema: z.object({
|
|
155
|
+
sql: z
|
|
156
|
+
.string()
|
|
157
|
+
.min(1)
|
|
158
|
+
.optional()
|
|
159
|
+
.describe(
|
|
160
|
+
"SQL to estimate. Use this or dashboardId, not both. Dashboard {{filter}} tokens are not resolved here; pass dashboardId for saved panels.",
|
|
161
|
+
),
|
|
162
|
+
source: z
|
|
163
|
+
.enum(["bigquery", "first-party"])
|
|
164
|
+
.optional()
|
|
165
|
+
.default("bigquery")
|
|
166
|
+
.describe(
|
|
167
|
+
"Dialect of `sql`: 'bigquery' (standard SQL against the warehouse, the default) or 'first-party' (Analytics' analytics_events SQL, rendered the way first-party panels send it).",
|
|
168
|
+
),
|
|
169
|
+
eventsSource: z
|
|
170
|
+
.enum(["raw"])
|
|
171
|
+
.optional()
|
|
172
|
+
.describe(
|
|
173
|
+
"First-party sql only: 'raw' prices the raw events table even when the events model covers the query. Alerts read the raw table, so pass it when estimating an alert.",
|
|
174
|
+
),
|
|
175
|
+
dashboardId: z
|
|
176
|
+
.string()
|
|
177
|
+
.min(1)
|
|
178
|
+
.optional()
|
|
179
|
+
.describe(
|
|
180
|
+
"Saved SQL dashboard to estimate. Without panelId, every queryable panel is estimated.",
|
|
181
|
+
),
|
|
182
|
+
panelId: z
|
|
183
|
+
.string()
|
|
184
|
+
.min(1)
|
|
185
|
+
.optional()
|
|
186
|
+
.describe("One panel inside dashboardId."),
|
|
187
|
+
filters: z
|
|
188
|
+
.record(z.string(), z.string())
|
|
189
|
+
.optional()
|
|
190
|
+
.describe(
|
|
191
|
+
"Dashboard filter values keyed by filter id, e.g. {timeRange:'90d'}. Omit for the defaults a fresh tab shows.",
|
|
192
|
+
),
|
|
193
|
+
// null and "" mean "not given", not 0, so the report-schedule default holds.
|
|
194
|
+
runsPerMonth: z
|
|
195
|
+
.preprocess(
|
|
196
|
+
(value) => (value === null || value === "" ? undefined : value),
|
|
197
|
+
z.coerce.number().int().min(0).max(100_000).optional(),
|
|
198
|
+
)
|
|
199
|
+
.describe(
|
|
200
|
+
"Scheduled runs per month, e.g. 30 for a daily job or 8640 for a 5-minute alert. For a dashboard this defaults to one run a day per enabled email report; for raw SQL there is no monthly figure without it.",
|
|
201
|
+
),
|
|
202
|
+
}),
|
|
203
|
+
http: { method: "POST" },
|
|
204
|
+
readOnly: true,
|
|
205
|
+
mcpTool: true,
|
|
206
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
207
|
+
timeoutMs: 55_000,
|
|
208
|
+
run: async (args, context?: ActionRunContext) => {
|
|
209
|
+
const startedAt = Date.now();
|
|
210
|
+
if (Boolean(args.sql) === Boolean(args.dashboardId)) {
|
|
211
|
+
fail("Pass exactly one of sql or dashboardId.", {
|
|
212
|
+
errorCode: "invalid_target",
|
|
213
|
+
statusCode: 400,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (args.panelId && !args.dashboardId) {
|
|
217
|
+
fail("panelId needs dashboardId.", {
|
|
218
|
+
errorCode: "invalid_target",
|
|
219
|
+
statusCode: 400,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (args.eventsSource && !(args.sql && args.source === "first-party")) {
|
|
223
|
+
fail("eventsSource applies only to first-party sql.", {
|
|
224
|
+
errorCode: "invalid_target",
|
|
225
|
+
statusCode: 400,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const userEmail = getRequestUserEmail();
|
|
229
|
+
if (!userEmail) {
|
|
230
|
+
fail("Sign in to use this action.", {
|
|
231
|
+
errorCode: "unauthenticated",
|
|
232
|
+
statusCode: 401,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const orgId = getRequestOrgId() || null;
|
|
236
|
+
const credentialContext = getCredentialContext();
|
|
237
|
+
const scope: AnalyticsScope = {
|
|
238
|
+
userEmail,
|
|
239
|
+
orgId,
|
|
240
|
+
...(credentialContext?.credentialScope === "org"
|
|
241
|
+
? { credentialScope: "org" as const }
|
|
242
|
+
: {}),
|
|
243
|
+
};
|
|
244
|
+
const signal = context?.signal;
|
|
245
|
+
const pricing = {
|
|
246
|
+
model: "on-demand",
|
|
247
|
+
usdPerTiB: ON_DEMAND_USD_PER_TIB,
|
|
248
|
+
thresholds: QUERY_COST_THRESHOLDS,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
if (args.sql) {
|
|
252
|
+
const runsPerMonth = args.runsPerMonth ?? null;
|
|
253
|
+
const estimate =
|
|
254
|
+
args.source === "first-party"
|
|
255
|
+
? await estimateFirstPartySql(args.sql, scope, runsPerMonth, signal, {
|
|
256
|
+
eventsSource: args.eventsSource,
|
|
257
|
+
})
|
|
258
|
+
: await estimateBigQuerySql(args.sql, runsPerMonth, signal);
|
|
259
|
+
return {
|
|
260
|
+
target: "sql",
|
|
261
|
+
source: args.source,
|
|
262
|
+
...estimate,
|
|
263
|
+
pricing,
|
|
264
|
+
caveats: QUERY_COST_CAVEATS,
|
|
265
|
+
summary: sentence(`Estimate: ${describeEstimate(estimate)}`),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const dashboardId = args.dashboardId!;
|
|
270
|
+
if (!isSafeDashboardId(dashboardId)) {
|
|
271
|
+
fail(
|
|
272
|
+
`${JSON.stringify(clipHead(dashboardId, 60))} is not a dashboard id: ids hold only letters, digits, dot, dash and underscore.`,
|
|
273
|
+
{ errorCode: "invalid_dashboard_id", statusCode: 400 },
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const dashboard = await getDashboard(dashboardId, {
|
|
277
|
+
email: userEmail,
|
|
278
|
+
orgId,
|
|
279
|
+
});
|
|
280
|
+
// Like get-sql-dashboard, a seed with the same id stands in for a stored
|
|
281
|
+
// dashboard that isn't SQL.
|
|
282
|
+
const stored = dashboard?.kind === "sql" ? dashboard : null;
|
|
283
|
+
const seed = stored ? null : loadDashboardSeed(dashboardId);
|
|
284
|
+
if (!stored && !seed) {
|
|
285
|
+
if (dashboard) {
|
|
286
|
+
fail(
|
|
287
|
+
`estimate-query-cost only supports SQL dashboards; "${dashboardId}" is ${dashboard.kind}.`,
|
|
288
|
+
{ errorCode: "dashboard_not_sql", statusCode: 400 },
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
fail(
|
|
292
|
+
`Dashboard "${dashboardId}" was not found, or you don't have access to it.`,
|
|
293
|
+
{ errorCode: "dashboard_not_found", statusCode: 404 },
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const config = pageDashboardConfig(
|
|
298
|
+
stored ? stored.config : seed!,
|
|
299
|
+
dashboardId,
|
|
300
|
+
);
|
|
301
|
+
const resolved = resolveDashboardPanelQueries(config, args.filters);
|
|
302
|
+
const selected = args.panelId
|
|
303
|
+
? resolved.filter((panel) => panel.panelId === args.panelId)
|
|
304
|
+
: resolved;
|
|
305
|
+
if (args.panelId && selected.length === 0) {
|
|
306
|
+
fail(
|
|
307
|
+
`Panel "${args.panelId}" is not a queryable panel in dashboard "${dashboardId}". Queryable panel ids: ${resolved
|
|
308
|
+
.slice(0, 40)
|
|
309
|
+
.map((panel) => panel.panelId)
|
|
310
|
+
.join(", ")}.`,
|
|
311
|
+
{ errorCode: "panel_not_found", statusCode: 404 },
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const reportSchedules = await countEnabledDashboardReportSubscriptions(
|
|
316
|
+
dashboardId,
|
|
317
|
+
{ email: userEmail, orgId },
|
|
318
|
+
);
|
|
319
|
+
const runsPerMonth =
|
|
320
|
+
args.runsPerMonth ??
|
|
321
|
+
(reportSchedules > 0 ? reportSchedules * DAYS_PER_MONTH : null);
|
|
322
|
+
const estimated = selected.slice(0, MAX_PANELS);
|
|
323
|
+
const deadline = startedAt + DASHBOARD_DEADLINE_MS;
|
|
324
|
+
const panels: PanelCostEstimate[] = await mapPool(
|
|
325
|
+
estimated,
|
|
326
|
+
DRY_RUN_CONCURRENCY,
|
|
327
|
+
(panel) =>
|
|
328
|
+
estimatePanelBefore(deadline, panel, scope, runsPerMonth, signal),
|
|
329
|
+
);
|
|
330
|
+
const total = totalCost(panels);
|
|
331
|
+
const flagged = panels.filter(
|
|
332
|
+
(panel) =>
|
|
333
|
+
panel.status === "estimated" &&
|
|
334
|
+
(panel.overPerRunThreshold || panel.overPerMonthThreshold),
|
|
335
|
+
);
|
|
336
|
+
const failed = panels.filter((panel) => panel.status === "failed").length;
|
|
337
|
+
const priced = panels.filter(
|
|
338
|
+
(panel) => panel.status === "estimated",
|
|
339
|
+
).length;
|
|
340
|
+
const notEstimated = panels.filter(
|
|
341
|
+
(panel) => panel.status === "not_estimated",
|
|
342
|
+
).length;
|
|
343
|
+
const omitted = Math.max(0, selected.length - MAX_PANELS);
|
|
344
|
+
// Panels outside BigQuery cost nothing; any other gap makes the total partial.
|
|
345
|
+
const partial = failed + notEstimated + omitted > 0;
|
|
346
|
+
// With panelId the total is that panel's, never the dashboard's.
|
|
347
|
+
const onePanel = args.panelId !== undefined;
|
|
348
|
+
const totalScope = onePanel
|
|
349
|
+
? "per refresh of this panel"
|
|
350
|
+
: partial
|
|
351
|
+
? "per refresh of the priced panels only"
|
|
352
|
+
: "per full refresh";
|
|
353
|
+
// Report schedules keep their own saved filters; this prices them at the
|
|
354
|
+
// filters passed here (or the defaults), so say so.
|
|
355
|
+
const monthly =
|
|
356
|
+
total.usdPerMonth === null
|
|
357
|
+
? "no scheduled runs"
|
|
358
|
+
: args.runsPerMonth === undefined
|
|
359
|
+
? `~${formatUsd(total.usdPerMonth)}/month if its email reports use these filters`
|
|
360
|
+
: `~${formatUsd(total.usdPerMonth)}/month in scheduled runs`;
|
|
361
|
+
const totalFlags = {
|
|
362
|
+
overPerRunThreshold: total.usdPerRun > QUERY_COST_THRESHOLDS.perRunUsd,
|
|
363
|
+
overPerMonthThreshold:
|
|
364
|
+
total.usdPerMonth !== null &&
|
|
365
|
+
total.usdPerMonth > QUERY_COST_THRESHOLDS.perMonthUsd,
|
|
366
|
+
};
|
|
367
|
+
const totalOver = [
|
|
368
|
+
totalFlags.overPerRunThreshold &&
|
|
369
|
+
`the ${formatUsd(QUERY_COST_THRESHOLDS.perRunUsd)}/run`,
|
|
370
|
+
totalFlags.overPerMonthThreshold &&
|
|
371
|
+
`the ${formatUsd(QUERY_COST_THRESHOLDS.perMonthUsd)}/month`,
|
|
372
|
+
].filter(Boolean);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
target: "dashboard",
|
|
376
|
+
dashboardId,
|
|
377
|
+
filterState:
|
|
378
|
+
args.filters && Object.keys(args.filters).length > 0
|
|
379
|
+
? "overridden"
|
|
380
|
+
: "defaults",
|
|
381
|
+
runsPerMonth,
|
|
382
|
+
runsPerMonthBasis:
|
|
383
|
+
args.runsPerMonth !== undefined
|
|
384
|
+
? "caller"
|
|
385
|
+
: reportSchedules > 0
|
|
386
|
+
? `${reportSchedules} enabled daily email report${reportSchedules === 1 ? "" : "s"} ${orgId ? "in your org" : "of yours"}`
|
|
387
|
+
: "none",
|
|
388
|
+
total: {
|
|
389
|
+
...total,
|
|
390
|
+
...totalFlags,
|
|
391
|
+
partial,
|
|
392
|
+
scope: onePanel ? "panel" : "dashboard",
|
|
393
|
+
},
|
|
394
|
+
panels,
|
|
395
|
+
...(selected.length > MAX_PANELS
|
|
396
|
+
? { omittedPanelIds: selected.slice(MAX_PANELS).map((p) => p.panelId) }
|
|
397
|
+
: {}),
|
|
398
|
+
pricing,
|
|
399
|
+
caveats: QUERY_COST_CAVEATS,
|
|
400
|
+
summary:
|
|
401
|
+
(priced === 0
|
|
402
|
+
? `${panels.length} panel${panels.length === 1 ? "" : "s"}: ${partial ? "none could be priced." : "none read BigQuery, so nothing was priced."}`
|
|
403
|
+
: `${panels.length} panel${panels.length === 1 ? "" : "s"} (${priced} priced): ~${formatBytes(total.estimatedBytes)} and ${formatUsd(total.usdPerRun)} ${totalScope}, ${monthly}.`) +
|
|
404
|
+
(totalOver.length && !onePanel
|
|
405
|
+
? ` The dashboard as a whole is over ${totalOver.join(" and ")} threshold${totalOver.length > 1 ? "s" : ""}${partial ? ", even counting only the priced panels" : ""}.`
|
|
406
|
+
: "") +
|
|
407
|
+
(flagged.length
|
|
408
|
+
? ` Over threshold: ${flagged.map((panel) => `"${panel.title}" (${describeEstimate(panel)})`).join("; ")}.`
|
|
409
|
+
: "") +
|
|
410
|
+
(failed
|
|
411
|
+
? ` ${failed} panel${failed === 1 ? "" : "s"} could not be estimated.`
|
|
412
|
+
: "") +
|
|
413
|
+
(notEstimated
|
|
414
|
+
? ` ${notEstimated} panel${notEstimated === 1 ? "" : "s"} may read BigQuery but ${notEstimated === 1 ? "isn't" : "aren't"} priced here.`
|
|
415
|
+
: "") +
|
|
416
|
+
(omitted
|
|
417
|
+
? ` ${omitted} panel${omitted === 1 ? "" : "s"} past the first ${MAX_PANELS} ${omitted === 1 ? "wasn't" : "weren't"} estimated (see omittedPanelIds).`
|
|
418
|
+
: ""),
|
|
419
|
+
};
|
|
420
|
+
},
|
|
421
|
+
});
|
|
@@ -233,7 +233,11 @@ const allowEmptyResultHelp =
|
|
|
233
233
|
"Set true only when the user expects an edited panel to have no rows right now; the save then reports verified:false. It never overrides missing columns or query errors.";
|
|
234
234
|
|
|
235
235
|
const agentInputSchema = z.object({
|
|
236
|
-
dashboardId: z
|
|
236
|
+
dashboardId: z
|
|
237
|
+
.string()
|
|
238
|
+
.min(1)
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("Dashboard id. Omit only when requesting returnTypes."),
|
|
237
241
|
operations: z
|
|
238
242
|
.array(mutationOperationSchema)
|
|
239
243
|
.max(MAX_DASHBOARD_MUTATION_OPERATIONS)
|
|
@@ -250,6 +254,10 @@ const agentInputSchema = z.object({
|
|
|
250
254
|
.boolean()
|
|
251
255
|
.optional()
|
|
252
256
|
.describe("Include the full resulting config only when needed."),
|
|
257
|
+
returnTypes: z
|
|
258
|
+
.boolean()
|
|
259
|
+
.optional()
|
|
260
|
+
.describe("With no dashboardId, return the `code` API and examples."),
|
|
253
261
|
});
|
|
254
262
|
|
|
255
263
|
function resolveScope() {
|
|
@@ -4,6 +4,7 @@ import type { AgentChatMcpOptions } from "@agent-native/core/server";
|
|
|
4
4
|
// framework's cross-app builtins. `mcp-action-contract.spec.ts` serves this
|
|
5
5
|
// config through the real MCP handler and pins the resulting tools/list.
|
|
6
6
|
export const ANALYTICS_MCP = {
|
|
7
|
+
connectorCatalog: [],
|
|
7
8
|
externalAgents: { writes: "allowlisted" },
|
|
8
9
|
keyToolNames: [
|
|
9
10
|
"list-sql-dashboards",
|
|
@@ -1121,15 +1121,24 @@ export interface DryRunQueryOptions {
|
|
|
1121
1121
|
}
|
|
1122
1122
|
|
|
1123
1123
|
export class BigQueryDryRunPreparationError extends Error {
|
|
1124
|
-
|
|
1124
|
+
/** True when the failure was missing BigQuery credentials or project. */
|
|
1125
|
+
readonly notConfigured: boolean;
|
|
1126
|
+
|
|
1127
|
+
constructor(options: { notConfigured?: boolean } = {}) {
|
|
1125
1128
|
super("BigQuery dry-run request preparation failed");
|
|
1126
1129
|
this.name = "BigQueryDryRunPreparationError";
|
|
1130
|
+
this.notConfigured = options.notConfigured === true;
|
|
1127
1131
|
}
|
|
1128
1132
|
}
|
|
1129
1133
|
|
|
1134
|
+
const BIGQUERY_NOT_CONFIGURED_PATTERN =
|
|
1135
|
+
/GOOGLE_APPLICATION_CREDENTIALS_JSON not configured|BIGQUERY_PROJECT_ID|service account|Token exchange failed/i;
|
|
1136
|
+
|
|
1130
1137
|
export interface DryRunQueryResult {
|
|
1131
1138
|
error: string | null;
|
|
1132
1139
|
timedOut?: true;
|
|
1140
|
+
/** HTTP status of a failed dry-run request; 400 means BigQuery rejected the SQL. */
|
|
1141
|
+
status?: number;
|
|
1133
1142
|
/** Absent when BigQuery's dry-run response carried no schema. */
|
|
1134
1143
|
schema?: { name: string; type: string }[];
|
|
1135
1144
|
/** Absent when BigQuery's dry-run response carried no byte estimate. */
|
|
@@ -1208,7 +1217,11 @@ export async function dryRunQuerySchema(
|
|
|
1208
1217
|
throw error;
|
|
1209
1218
|
}
|
|
1210
1219
|
if (options.wrapPreparationErrors) {
|
|
1211
|
-
throw new BigQueryDryRunPreparationError(
|
|
1220
|
+
throw new BigQueryDryRunPreparationError({
|
|
1221
|
+
notConfigured:
|
|
1222
|
+
error instanceof Error &&
|
|
1223
|
+
BIGQUERY_NOT_CONFIGURED_PATTERN.test(error.message),
|
|
1224
|
+
});
|
|
1212
1225
|
}
|
|
1213
1226
|
throw error;
|
|
1214
1227
|
}
|
|
@@ -1252,12 +1265,15 @@ export async function dryRunQuerySchema(
|
|
|
1252
1265
|
error?: { message?: string };
|
|
1253
1266
|
};
|
|
1254
1267
|
const msg = parsed.error?.message?.trim();
|
|
1255
|
-
if (msg) return { error: msg };
|
|
1268
|
+
if (msg) return { error: msg, status: res.status };
|
|
1256
1269
|
// coercion-ok: malformed BigQuery error bodies use the status fallback below.
|
|
1257
1270
|
} catch {
|
|
1258
1271
|
// Fall through
|
|
1259
1272
|
}
|
|
1260
|
-
return {
|
|
1273
|
+
return {
|
|
1274
|
+
error: `BigQuery validation failed (${res.status})`,
|
|
1275
|
+
status: res.status,
|
|
1276
|
+
};
|
|
1261
1277
|
} catch (error) {
|
|
1262
1278
|
if (timedOut) return { error: timeoutMessage, timedOut: true };
|
|
1263
1279
|
throw error;
|
|
@@ -350,6 +350,44 @@ function interpolationFailure(
|
|
|
350
350
|
return null;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
export type ResolvedDashboardPanelQuery =
|
|
354
|
+
| { panelId: string; title: string; source: string; query: string }
|
|
355
|
+
| { panelId: string; title: string; source: string; error: string };
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The SQL each queryable panel sends for the given filters (dashboard defaults
|
|
359
|
+
* when omitted), resolved the way verification does, without running anything.
|
|
360
|
+
* Section and extension panels have no query and are left out.
|
|
361
|
+
*/
|
|
362
|
+
export function resolveDashboardPanelQueries(
|
|
363
|
+
rawConfig: Record<string, unknown>,
|
|
364
|
+
filters?: Record<string, string>,
|
|
365
|
+
): ResolvedDashboardPanelQuery[] {
|
|
366
|
+
const config = normalizeDashboardConfig(rawConfig);
|
|
367
|
+
const vars = resolveVerificationVars(config, filters);
|
|
368
|
+
return panelsOf(config)
|
|
369
|
+
.filter((panel) => typeof panel.id === "string" && hasSqlToRun(panel))
|
|
370
|
+
.map((panel) => {
|
|
371
|
+
const panelId = String(panel.id);
|
|
372
|
+
const title = typeof panel.title === "string" ? panel.title : panelId;
|
|
373
|
+
const source = typeof panel.source === "string" ? panel.source : "";
|
|
374
|
+
try {
|
|
375
|
+
const query = buildPanelQuery(panel as unknown as SqlPanel, vars);
|
|
376
|
+
const failure = interpolationFailure(query, panel.sql, vars);
|
|
377
|
+
return failure
|
|
378
|
+
? { panelId, title, source, error: failure }
|
|
379
|
+
: { panelId, title, source, query };
|
|
380
|
+
} catch (error) {
|
|
381
|
+
return {
|
|
382
|
+
panelId,
|
|
383
|
+
title,
|
|
384
|
+
source,
|
|
385
|
+
error: error instanceof Error ? error.message : String(error),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
353
391
|
function truncateCell(value: unknown): unknown {
|
|
354
392
|
const text =
|
|
355
393
|
typeof value === "string"
|
|
@@ -434,6 +434,36 @@ export async function listDashboardReportSubscriptions(
|
|
|
434
434
|
return rows.map(rowToSubscription);
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Enabled daily report schedules on a dashboard in the caller's tenant: every
|
|
439
|
+
* owner's in the caller's org, or only the caller's own without an org. Each
|
|
440
|
+
* one re-runs the dashboard's panels once a day on its owner's BigQuery
|
|
441
|
+
* connection, so other tenants' schedules bill other tenants and never count.
|
|
442
|
+
* Only the number is returned, never the subscriptions themselves.
|
|
443
|
+
*/
|
|
444
|
+
export async function countEnabledDashboardReportSubscriptions(
|
|
445
|
+
dashboardId: string,
|
|
446
|
+
ctx: AccessCtx,
|
|
447
|
+
): Promise<number> {
|
|
448
|
+
const db = getDb() as any;
|
|
449
|
+
const [row] = await db
|
|
450
|
+
.select({ count: sql<number>`count(*)` })
|
|
451
|
+
.from(schema.dashboardReportSubscriptions)
|
|
452
|
+
.where(
|
|
453
|
+
and(
|
|
454
|
+
eq(schema.dashboardReportSubscriptions.dashboardId, dashboardId),
|
|
455
|
+
eq(schema.dashboardReportSubscriptions.enabled, true),
|
|
456
|
+
ctx.orgId
|
|
457
|
+
? eq(schema.dashboardReportSubscriptions.orgId, ctx.orgId)
|
|
458
|
+
: and(
|
|
459
|
+
isNull(schema.dashboardReportSubscriptions.orgId),
|
|
460
|
+
sql`lower(${schema.dashboardReportSubscriptions.ownerEmail}) = ${ctx.email.toLowerCase()}`,
|
|
461
|
+
),
|
|
462
|
+
),
|
|
463
|
+
);
|
|
464
|
+
return Number(row?.count ?? 0);
|
|
465
|
+
}
|
|
466
|
+
|
|
437
467
|
export async function getDashboardReportSubscription(
|
|
438
468
|
id: string,
|
|
439
469
|
ctx: AccessCtx,
|