@agent-native/core 0.95.1 → 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 +64 -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 +11 -2
- package/corpus/core/src/a2a/auth-policy.ts +33 -0
- package/corpus/core/src/a2a/client.ts +11 -1
- package/corpus/core/src/agent/durable-background.ts +15 -7
- package/corpus/core/src/agent/production-agent.ts +87 -27
- package/corpus/core/src/agent/run-store.ts +13 -4
- package/corpus/core/src/agent/thread-data-builder.ts +7 -5
- 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/chat-threads/store.ts +20 -1
- package/corpus/core/src/cli/code-agent-executor.ts +1 -0
- package/corpus/core/src/cli/code-agent-runs.ts +14 -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/AgentPanel.tsx +32 -25
- package/corpus/core/src/client/AssistantChat.tsx +36 -20
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +141 -90
- package/corpus/core/src/client/agent-chat-adapter.ts +9 -9
- package/corpus/core/src/client/chat/ChatHistoryList.tsx +404 -0
- package/corpus/core/src/client/chat/index.ts +6 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +37 -3
- package/corpus/core/src/client/code-agent-chat-adapter.ts +72 -4
- package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +52 -11
- package/corpus/core/src/client/composer/TiptapComposer.tsx +157 -20
- package/corpus/core/src/client/composer/VoiceButton.tsx +28 -0
- package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +5 -0
- package/corpus/core/src/client/conversation/code-agent-transcript.ts +4 -5
- package/corpus/core/src/client/guided-questions.tsx +2 -1
- package/corpus/core/src/client/index.ts +11 -0
- package/corpus/core/src/client/use-chat-threads.ts +10 -1
- package/corpus/core/src/client/voice-provider-status.ts +44 -0
- package/corpus/core/src/code-agents/transcript-normalizer.ts +76 -0
- package/corpus/core/src/deploy/build.ts +12 -11
- 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/integrations/a2a-continuation-processor.ts +35 -5
- package/corpus/core/src/integrations/adapters/slack.ts +74 -21
- package/corpus/core/src/integrations/webhook-handler.ts +14 -0
- package/corpus/core/src/localization/default-messages.ts +6 -0
- package/corpus/core/src/observability/hosted-model-experiment.ts +118 -0
- package/corpus/core/src/observability/routes.ts +17 -0
- package/corpus/core/src/observability/store.ts +4 -1
- package/corpus/core/src/observability/traces.ts +41 -2
- 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/scripts/agent-engines/list-agent-engines.ts +19 -1
- package/corpus/core/src/scripts/call-agent.ts +53 -3
- package/corpus/core/src/scripts/chat/search-chats.ts +11 -8
- package/corpus/core/src/secrets/register-framework-secrets.ts +1 -1
- 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 +95 -18
- package/corpus/core/src/server/core-routes-plugin.ts +45 -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 +80 -24
- package/corpus/core/src/server/request-origin.ts +63 -0
- package/corpus/core/src/server/transcribe-voice.ts +1 -50
- package/corpus/core/src/server/voice-providers-status.ts +4 -3
- package/corpus/core/src/shared/cache-control.ts +2 -8
- package/corpus/core/src/styles/agent-native.css +1 -0
- package/corpus/core/src/styles/chat-history-list.css +313 -0
- 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/.agents/skills/observability/SKILL.md +20 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
- 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/core/src/vite/client.ts +1 -0
- package/corpus/templates/analytics/AGENTS.md +7 -0
- package/corpus/templates/analytics/actions/bigquery.ts +26 -2
- package/corpus/templates/analytics/actions/compose-dashboard.ts +65 -37
- package/corpus/templates/analytics/actions/data-source-status.ts +18 -0
- 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/app/components/layout/Sidebar.tsx +5 -4
- package/corpus/templates/analytics/app/global.css +10 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +25 -2
- package/corpus/templates/analytics/changelog/2026-07-10-analytics-answers-simple-time-bounded-metrics-without-unnece.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-analytics-chat-reliably-uses-connected-data-sources-across-s.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-chat-threads-now-stay-clear-of-top-actions-and-fade-smoothly.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-context-chips-can-now-be-selected-and-removed-one-at-a-time-.md +6 -0
- 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-the-ask-composer-no-longer-carries-dashboard-or-panel-contex.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-the-ask-section-stays-highlighted-while-its-chat-history-is-.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 +91 -14
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +154 -11
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +82 -7
- 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/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +2 -1
- package/dist/a2a/client.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/production-agent.d.ts +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +63 -26
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +9 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +6 -5
- package/dist/agent/thread-data-builder.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/chat-threads/store.d.ts +10 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +6 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +1 -0
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-runs.d.ts +12 -0
- package/dist/cli/code-agent-runs.d.ts.map +1 -1
- package/dist/cli/code-agent-runs.js +1 -0
- package/dist/cli/code-agent-runs.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/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +3 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +11 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -4
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +83 -10
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.js +7 -3
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/ChatHistoryList.d.ts +88 -0
- package/dist/client/chat/ChatHistoryList.d.ts.map +1 -0
- package/dist/client/chat/ChatHistoryList.js +83 -0
- package/dist/client/chat/ChatHistoryList.js.map +1 -0
- package/dist/client/chat/index.d.ts +1 -0
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -0
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +13 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +10 -2
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.d.ts +17 -1
- package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/code-agent-chat-adapter.js +47 -3
- package/dist/client/code-agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts +11 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.js +5 -3
- package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +25 -2
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +96 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +21 -1
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.js +5 -0
- package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.d.ts +2 -0
- package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -1
- package/dist/client/conversation/code-agent-transcript.js +3 -5
- package/dist/client/conversation/code-agent-transcript.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +2 -1
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +9 -1
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/voice-provider-status.d.ts +10 -0
- package/dist/client/voice-provider-status.d.ts.map +1 -0
- package/dist/client/voice-provider-status.js +32 -0
- package/dist/client/voice-provider-status.js.map +1 -0
- package/dist/code-agents/transcript-normalizer.d.ts +29 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +55 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +13 -11
- package/dist/deploy/build.js.map +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/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +25 -5
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +55 -21
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +11 -0
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +5 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/hosted-model-experiment.d.ts +39 -0
- package/dist/observability/hosted-model-experiment.d.ts.map +1 -0
- package/dist/observability/hosted-model-experiment.js +90 -0
- package/dist/observability/hosted-model-experiment.js.map +1 -0
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +16 -0
- package/dist/observability/routes.js.map +1 -1
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +4 -1
- package/dist/observability/store.js.map +1 -1
- package/dist/observability/traces.d.ts +6 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +28 -2
- package/dist/observability/traces.js.map +1 -1
- 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/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +19 -1
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +43 -3
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/chat/search-chats.d.ts +1 -1
- package/dist/scripts/chat/search-chats.d.ts.map +1 -1
- package/dist/scripts/chat/search-chats.js +11 -8
- package/dist/scripts/chat/search-chats.js.map +1 -1
- package/dist/secrets/register-framework-secrets.js +1 -1
- package/dist/secrets/register-framework-secrets.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 +11 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +74 -19
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +3 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +36 -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 +64 -21
- 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/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +2 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/dist/shared/cache-control.d.ts +4 -6
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +2 -7
- package/dist/shared/cache-control.js.map +1 -1
- package/dist/styles/agent-native.css +1 -0
- package/dist/styles/chat-history-list.css +313 -0
- 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/.agents/skills/observability/SKILL.md +20 -0
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
- 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/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.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 +12 -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/.agents/skills/observability/SKILL.md +20 -0
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
- 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
|
@@ -13,12 +13,16 @@ import { z } from "zod";
|
|
|
13
13
|
|
|
14
14
|
import { interpolate } from "../app/pages/adhoc/sql-dashboard/interpolate";
|
|
15
15
|
import { dryRunQuery } from "../server/lib/bigquery";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
upsertDashboard,
|
|
18
|
+
upsertDashboardWithRetry,
|
|
19
|
+
} from "../server/lib/dashboards-store";
|
|
17
20
|
import { parseDemoDescriptor } from "../server/lib/demo-source";
|
|
18
21
|
import { validateFirstPartyAnalyticsSql } from "../server/lib/first-party-analytics.js";
|
|
19
22
|
import {
|
|
20
23
|
applyPanelOrder,
|
|
21
24
|
compactDashboardResult,
|
|
25
|
+
type PanelOrderResult,
|
|
22
26
|
} from "./dashboard-panel-order";
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -627,29 +631,27 @@ export default defineAction({
|
|
|
627
631
|
);
|
|
628
632
|
}
|
|
629
633
|
|
|
630
|
-
const existing = await getDashboard(dashboardId, ctx);
|
|
631
|
-
if (!existing) {
|
|
632
|
-
throw new Error(
|
|
633
|
-
`dashboard "${dashboardId}" not found (or you don't have access).`,
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const root = existing.config as any;
|
|
638
|
-
|
|
639
634
|
if (args.panelOrder) {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
635
|
+
// Recomputed on every retry attempt from the freshest dashboard config,
|
|
636
|
+
// so a concurrent writer's edit is never silently overwritten by this
|
|
637
|
+
// move.
|
|
638
|
+
let orderDetails!: PanelOrderResult;
|
|
639
|
+
const saved = await upsertDashboardWithRetry(
|
|
644
640
|
dashboardId,
|
|
645
|
-
existing.kind,
|
|
646
|
-
root as Record<string, unknown>,
|
|
647
641
|
ctx,
|
|
642
|
+
(existing) => {
|
|
643
|
+
const root = existing.config as Record<string, unknown>;
|
|
644
|
+
orderDetails = applyPanelOrder(root, args.panelOrder!);
|
|
645
|
+
const validation = validateDashboardConfig(root);
|
|
646
|
+
if (validation) throw new Error(validation);
|
|
647
|
+
return { kind: existing.kind, body: root };
|
|
648
|
+
},
|
|
648
649
|
);
|
|
649
|
-
|
|
650
|
+
const root = saved.config as Record<string, unknown>;
|
|
651
|
+
await syncToCollab(dashboardId, root);
|
|
650
652
|
return dashboardResult(
|
|
651
653
|
dashboardId,
|
|
652
|
-
root
|
|
654
|
+
root,
|
|
653
655
|
1,
|
|
654
656
|
`Moved ${orderDetails.movedPanelIds.length} panel id(s) to the front of dashboard "${dashboardId}"; it now has ${orderDetails.panelCount} panel(s).`,
|
|
655
657
|
orderDetails.movedPanelIds,
|
|
@@ -657,36 +659,45 @@ export default defineAction({
|
|
|
657
659
|
);
|
|
658
660
|
}
|
|
659
661
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
throw new Error(`applying op ${JSON.stringify(op)}: ${err.message}`);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const validation = validateDashboardConfig(root);
|
|
670
|
-
if (validation) throw new Error(validation);
|
|
671
|
-
if (args.ops!.some((op) => opCanChangePanelSql(op as JsonOp))) {
|
|
672
|
-
const sqlError = await validatePanelSql(root);
|
|
673
|
-
if (sqlError) throw new Error(sqlError);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
await upsertDashboard(
|
|
662
|
+
// Recomputed on every retry attempt from the freshest dashboard config —
|
|
663
|
+
// JSON-pointer ops are replayed against fresh state, not the stale config
|
|
664
|
+
// that produced the first (lost) attempt.
|
|
665
|
+
let appliedDetails: string[] = [];
|
|
666
|
+
const saved = await upsertDashboardWithRetry(
|
|
677
667
|
dashboardId,
|
|
678
|
-
existing.kind,
|
|
679
|
-
root as Record<string, unknown>,
|
|
680
668
|
ctx,
|
|
669
|
+
async (existing) => {
|
|
670
|
+
const root = existing.config as Record<string, unknown>;
|
|
671
|
+
const details: string[] = [];
|
|
672
|
+
for (const op of args.ops!) {
|
|
673
|
+
try {
|
|
674
|
+
details.push(applyJsonOp(root, op as JsonOp));
|
|
675
|
+
} catch (err: any) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`applying op ${JSON.stringify(op)}: ${err.message}`,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const validation = validateDashboardConfig(root);
|
|
683
|
+
if (validation) throw new Error(validation);
|
|
684
|
+
if (args.ops!.some((op) => opCanChangePanelSql(op as JsonOp))) {
|
|
685
|
+
const sqlError = await validatePanelSql(root);
|
|
686
|
+
if (sqlError) throw new Error(sqlError);
|
|
687
|
+
}
|
|
688
|
+
appliedDetails = details;
|
|
689
|
+
return { kind: existing.kind, body: root };
|
|
690
|
+
},
|
|
681
691
|
);
|
|
682
|
-
|
|
692
|
+
const root = saved.config as Record<string, unknown>;
|
|
693
|
+
await syncToCollab(dashboardId, root);
|
|
683
694
|
|
|
684
|
-
const panelCount = countPanels(root
|
|
695
|
+
const panelCount = countPanels(root);
|
|
685
696
|
return dashboardResult(
|
|
686
697
|
dashboardId,
|
|
687
|
-
root
|
|
688
|
-
|
|
689
|
-
`Applied ${
|
|
698
|
+
root,
|
|
699
|
+
appliedDetails.length,
|
|
700
|
+
`Applied ${appliedDetails.length} op(s); dashboard "${dashboardId}" now has ${panelCount} panel(s).`,
|
|
690
701
|
[],
|
|
691
702
|
args.returnConfig === true,
|
|
692
703
|
);
|
|
@@ -1295,7 +1295,7 @@ function persistedAnalyticsThreadId() {
|
|
|
1295
1295
|
}
|
|
1296
1296
|
}
|
|
1297
1297
|
|
|
1298
|
-
function AnalyticsChatsSection() {
|
|
1298
|
+
function AnalyticsChatsSection({ isAskRoute }: { isAskRoute: boolean }) {
|
|
1299
1299
|
const navigate = useNavigate();
|
|
1300
1300
|
const t = useT();
|
|
1301
1301
|
const {
|
|
@@ -1439,8 +1439,9 @@ function AnalyticsChatsSection() {
|
|
|
1439
1439
|
{visibleThreads.map((thread) => {
|
|
1440
1440
|
const title = threadTitle(thread, t("chat.untitledChat"));
|
|
1441
1441
|
const isActive =
|
|
1442
|
-
|
|
1443
|
-
thread.id ===
|
|
1442
|
+
isAskRoute &&
|
|
1443
|
+
(thread.id === activeThreadId ||
|
|
1444
|
+
thread.id === persistedAnalyticsThreadId());
|
|
1444
1445
|
const isRenaming = thread.id === renamingThreadId;
|
|
1445
1446
|
return (
|
|
1446
1447
|
<div
|
|
@@ -2592,7 +2593,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2592
2593
|
/>
|
|
2593
2594
|
</button>
|
|
2594
2595
|
</div>
|
|
2595
|
-
{askOpen && <AnalyticsChatsSection />}
|
|
2596
|
+
{askOpen && <AnalyticsChatsSection isAskRoute={isAskRoute} />}
|
|
2596
2597
|
</div>
|
|
2597
2598
|
|
|
2598
2599
|
{/* Sessions link */}
|
|
@@ -149,6 +149,16 @@
|
|
|
149
149
|
background-color: hsl(var(--background)) !important;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
.analytics-chat-panel .agent-thread-content {
|
|
153
|
+
padding-top: 4.5rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.analytics-chat-panel
|
|
157
|
+
[data-agent-page-chat-scrolled]
|
|
158
|
+
[data-agent-page-chat-fade] {
|
|
159
|
+
opacity: 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
152
162
|
@media (max-width: 767px) {
|
|
153
163
|
.analytics-chat-panel [data-agent-page-new-chat] {
|
|
154
164
|
display: none;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
AgentChatSurface,
|
|
3
|
+
useAgentChatContext,
|
|
4
|
+
useT,
|
|
5
|
+
} from "@agent-native/core/client";
|
|
6
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
7
|
|
|
4
8
|
import {
|
|
5
9
|
ANALYTICS_CHAT_STORAGE_KEY,
|
|
@@ -7,9 +11,28 @@ import {
|
|
|
7
11
|
} from "@/lib/chat-handoff";
|
|
8
12
|
import { TAB_ID } from "@/lib/tab-id";
|
|
9
13
|
|
|
14
|
+
const DASHBOARD_CONTEXT_KEYS = new Set([
|
|
15
|
+
"analytics-selected-dashboard",
|
|
16
|
+
"analytics-selected-dashboard-panel",
|
|
17
|
+
]);
|
|
18
|
+
|
|
10
19
|
export default function AskPage() {
|
|
11
20
|
const t = useT();
|
|
12
21
|
const [restoreActiveThread] = useState(() => hasRecentAnalyticsChat());
|
|
22
|
+
const { items: chatContextItems, remove: removeChatContextItem } =
|
|
23
|
+
useAgentChatContext();
|
|
24
|
+
const staleDashboardContextKey = useMemo(
|
|
25
|
+
() =>
|
|
26
|
+
chatContextItems.find((item) => DASHBOARD_CONTEXT_KEYS.has(item.key))
|
|
27
|
+
?.key ?? null,
|
|
28
|
+
[chatContextItems],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (staleDashboardContextKey) {
|
|
33
|
+
removeChatContextItem(staleDashboardContextKey);
|
|
34
|
+
}
|
|
35
|
+
}, [removeChatContextItem, staleDashboardContextKey]);
|
|
13
36
|
|
|
14
37
|
return (
|
|
15
38
|
<div className="analytics-ask-page flex h-full min-h-0 flex-col bg-background">
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@sparticuz/chromium-min": "^149.0.0",
|
|
26
26
|
"@tabler/icons-react": "catalog:",
|
|
27
27
|
"drizzle-orm": "^0.45.2",
|
|
28
|
-
"h3": "
|
|
28
|
+
"h3": "catalog:",
|
|
29
29
|
"isbot": "^5",
|
|
30
30
|
"playwright-core": "^1.61.1",
|
|
31
31
|
"postgres": "^3.4.9",
|
|
@@ -100,7 +100,6 @@
|
|
|
100
100
|
"tailwindcss": "catalog:",
|
|
101
101
|
"three": "^0.176.0",
|
|
102
102
|
"tsx": "^4.20.3",
|
|
103
|
-
"typescript": "catalog:",
|
|
104
103
|
"typescript-7": "catalog:",
|
|
105
104
|
"vaul": "^1.1.2",
|
|
106
105
|
"vite": "catalog:",
|
|
@@ -241,6 +241,15 @@ export interface QueryResult {
|
|
|
241
241
|
cached?: boolean;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
export interface RunQueryOptions {
|
|
245
|
+
/**
|
|
246
|
+
* The current agent run's abort signal. This cancels in-flight BigQuery
|
|
247
|
+
* requests and, importantly, stops the one-second job polling wait without
|
|
248
|
+
* starting another request after the parent run has ended.
|
|
249
|
+
*/
|
|
250
|
+
signal?: AbortSignal;
|
|
251
|
+
}
|
|
252
|
+
|
|
244
253
|
interface BigQueryField {
|
|
245
254
|
name: string;
|
|
246
255
|
type: string;
|
|
@@ -265,6 +274,57 @@ interface BigQueryGetQueryResultsResponse {
|
|
|
265
274
|
totalBytesProcessed?: string;
|
|
266
275
|
}
|
|
267
276
|
|
|
277
|
+
function createAbortError(): Error {
|
|
278
|
+
if (typeof DOMException !== "undefined") {
|
|
279
|
+
return new DOMException("BigQuery query aborted", "AbortError");
|
|
280
|
+
}
|
|
281
|
+
const error = new Error("BigQuery query aborted");
|
|
282
|
+
error.name = "AbortError";
|
|
283
|
+
return error;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
287
|
+
if (signal?.aborted) throw createAbortError();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function waitForPollInterval(signal?: AbortSignal): Promise<void> {
|
|
291
|
+
throwIfAborted(signal);
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const timeout = setTimeout(() => {
|
|
294
|
+
signal?.removeEventListener("abort", onAbort);
|
|
295
|
+
resolve();
|
|
296
|
+
}, 1000);
|
|
297
|
+
const onAbort = () => {
|
|
298
|
+
clearTimeout(timeout);
|
|
299
|
+
signal?.removeEventListener("abort", onAbort);
|
|
300
|
+
reject(createAbortError());
|
|
301
|
+
};
|
|
302
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function cancelQueryJob(
|
|
307
|
+
projectId: string,
|
|
308
|
+
jobId: string,
|
|
309
|
+
token: string,
|
|
310
|
+
): Promise<void> {
|
|
311
|
+
try {
|
|
312
|
+
await fetch(
|
|
313
|
+
`https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/jobs/${jobId}/cancel`,
|
|
314
|
+
{
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: {
|
|
317
|
+
Authorization: `Bearer ${token}`,
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
} catch {
|
|
323
|
+
// Cancellation is best-effort and must not hide the original abort or
|
|
324
|
+
// timeout reason if BigQuery or the network is unavailable.
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
268
328
|
/**
|
|
269
329
|
* Convert BigQuery REST API row format to plain objects.
|
|
270
330
|
* BigQuery returns rows as { f: [{ v: value }, ...] } arrays
|
|
@@ -361,7 +421,12 @@ export async function dryRunQuery(sql: string): Promise<string | null> {
|
|
|
361
421
|
return `BigQuery validation failed (${res.status})`;
|
|
362
422
|
}
|
|
363
423
|
|
|
364
|
-
export async function runQuery(
|
|
424
|
+
export async function runQuery(
|
|
425
|
+
sql: string,
|
|
426
|
+
options: RunQueryOptions = {},
|
|
427
|
+
): Promise<QueryResult> {
|
|
428
|
+
const { signal } = options;
|
|
429
|
+
throwIfAborted(signal);
|
|
365
430
|
const { projectId, cacheScope, appEventsTable } = await getProjectInfo();
|
|
366
431
|
const resolvedSql = await resolveTablePlaceholder(
|
|
367
432
|
sql,
|
|
@@ -383,8 +448,10 @@ export async function runQuery(sql: string): Promise<QueryResult> {
|
|
|
383
448
|
const token = await getAccessToken();
|
|
384
449
|
const url = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/queries`;
|
|
385
450
|
|
|
451
|
+
throwIfAborted(signal);
|
|
386
452
|
const res = await fetch(url, {
|
|
387
453
|
method: "POST",
|
|
454
|
+
...(signal ? { signal } : {}),
|
|
388
455
|
headers: {
|
|
389
456
|
Authorization: `Bearer ${token}`,
|
|
390
457
|
"Content-Type": "application/json",
|
|
@@ -409,23 +476,33 @@ export async function runQuery(sql: string): Promise<QueryResult> {
|
|
|
409
476
|
const resultsUrl = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/queries/${jobId}`;
|
|
410
477
|
|
|
411
478
|
let attempts = 0;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
479
|
+
try {
|
|
480
|
+
while (!data.jobComplete && attempts < 60) {
|
|
481
|
+
await waitForPollInterval(signal);
|
|
482
|
+
throwIfAborted(signal);
|
|
483
|
+
const pollRes = await fetch(resultsUrl, {
|
|
484
|
+
...(signal ? { signal } : {}),
|
|
485
|
+
headers: {
|
|
486
|
+
Authorization: `Bearer ${token}`,
|
|
487
|
+
"Content-Type": "application/json",
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
if (!pollRes.ok) {
|
|
491
|
+
const text = await pollRes.text();
|
|
492
|
+
throw new Error(`BigQuery poll error ${pollRes.status}: ${text}`);
|
|
493
|
+
}
|
|
494
|
+
data = (await pollRes.json()) as BigQueryGetQueryResultsResponse;
|
|
495
|
+
attempts++;
|
|
496
|
+
}
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (signal?.aborted) {
|
|
499
|
+
await cancelQueryJob(projectId, jobId, token);
|
|
423
500
|
}
|
|
424
|
-
|
|
425
|
-
attempts++;
|
|
501
|
+
throw error;
|
|
426
502
|
}
|
|
427
503
|
|
|
428
504
|
if (!data.jobComplete) {
|
|
505
|
+
await cancelQueryJob(projectId, jobId, token);
|
|
429
506
|
throw new Error("BigQuery query timed out after 60 seconds");
|
|
430
507
|
}
|
|
431
508
|
}
|
|
@@ -129,6 +129,45 @@ function nanoidFallback(): string {
|
|
|
129
129
|
);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Normalize affected-row metadata from every createGetDb backend: libSQL,
|
|
134
|
+
* PGlite, Neon, postgres.js, better-sqlite3, and D1. Mirrors
|
|
135
|
+
* templates/design/actions/update-design.ts's `affectedRowCount`.
|
|
136
|
+
*/
|
|
137
|
+
function affectedRowCount(result: unknown): number | undefined {
|
|
138
|
+
const candidate = result as
|
|
139
|
+
| {
|
|
140
|
+
rowsAffected?: unknown;
|
|
141
|
+
affectedRows?: unknown;
|
|
142
|
+
rowCount?: unknown;
|
|
143
|
+
count?: unknown;
|
|
144
|
+
changes?: unknown;
|
|
145
|
+
meta?: { changes?: unknown };
|
|
146
|
+
}
|
|
147
|
+
| undefined;
|
|
148
|
+
const value =
|
|
149
|
+
candidate?.rowsAffected ??
|
|
150
|
+
candidate?.affectedRows ??
|
|
151
|
+
candidate?.rowCount ??
|
|
152
|
+
candidate?.count ??
|
|
153
|
+
candidate?.changes ??
|
|
154
|
+
candidate?.meta?.changes;
|
|
155
|
+
return typeof value === "number" ? value : undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Thrown when a fenced `upsertDashboard` write (an `expectedUpdatedAt` was
|
|
160
|
+
* supplied) loses its compare-and-swap because another writer changed the
|
|
161
|
+
* row first. Callers should re-read the dashboard and re-apply their mutation
|
|
162
|
+
* against the fresh config — see `upsertDashboardWithRetry`.
|
|
163
|
+
*/
|
|
164
|
+
export class DashboardConflictError extends Error {
|
|
165
|
+
constructor(id: string) {
|
|
166
|
+
super(`Dashboard "${id}" changed between read and write.`);
|
|
167
|
+
this.name = "DashboardConflictError";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
132
171
|
function changeScope(
|
|
133
172
|
ownerEmail: string,
|
|
134
173
|
orgId: string | null,
|
|
@@ -466,15 +505,33 @@ async function snapshotDashboardRevision(
|
|
|
466
505
|
* Upsert a dashboard. On create, caller becomes owner and visibility defaults
|
|
467
506
|
* to `private`; users explicitly promote useful dashboards to org/public via
|
|
468
507
|
* sharing. On update, `assertAccess` requires `editor`.
|
|
508
|
+
*
|
|
509
|
+
* `expectedUpdatedAt` fences the update against concurrent writers: pass the
|
|
510
|
+
* `updatedAt` observed by an earlier `getDashboard` call and the UPDATE only
|
|
511
|
+
* applies `WHERE id = ? AND updated_at = ?`. If another writer already saved
|
|
512
|
+
* in between, the fenced UPDATE affects zero rows and this throws
|
|
513
|
+
* `DashboardConflictError` instead of silently clobbering their write. Omit
|
|
514
|
+
* it (the default) to keep the prior unconditional last-write-wins behavior,
|
|
515
|
+
* which existing callers (legacy migration, revision restore, and any
|
|
516
|
+
* one-shot write that isn't a read-modify-write) still rely on.
|
|
469
517
|
*/
|
|
470
518
|
export async function upsertDashboard(
|
|
471
519
|
id: string,
|
|
472
520
|
kind: DashboardKind,
|
|
473
521
|
body: Record<string, unknown>,
|
|
474
522
|
ctx: AccessCtx,
|
|
523
|
+
expectedUpdatedAt?: string,
|
|
475
524
|
): Promise<DashboardRecord> {
|
|
476
525
|
// If the row exists (or legacy-migrates), require editor.
|
|
477
526
|
const existing = await getDashboard(id, ctx);
|
|
527
|
+
if (!existing && expectedUpdatedAt !== undefined) {
|
|
528
|
+
// A fence was supplied against a specific prior version, but the row is
|
|
529
|
+
// gone (deleted, or a legacy key that failed to migrate) by the time we
|
|
530
|
+
// looked. Treat this as a conflict rather than silently creating a fresh
|
|
531
|
+
// row — the caller's mutation was computed against state that no longer
|
|
532
|
+
// exists.
|
|
533
|
+
throw new DashboardConflictError(id);
|
|
534
|
+
}
|
|
478
535
|
const db = getDb() as any;
|
|
479
536
|
const { title, config } = configFromSettings(body);
|
|
480
537
|
const configJson = JSON.stringify(config);
|
|
@@ -487,17 +544,43 @@ export async function upsertDashboard(
|
|
|
487
544
|
existing.kind !== kind ||
|
|
488
545
|
existing.title !== title ||
|
|
489
546
|
JSON.stringify(existing.config) !== configJson;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
.
|
|
547
|
+
const setValues = {
|
|
548
|
+
kind,
|
|
549
|
+
title,
|
|
550
|
+
config: configJson,
|
|
551
|
+
updatedAt: nowIso(),
|
|
552
|
+
updatedBy: ctx.email,
|
|
553
|
+
};
|
|
554
|
+
if (expectedUpdatedAt !== undefined) {
|
|
555
|
+
// Fenced write. Snapshot the revision only after we know this exact
|
|
556
|
+
// write actually landed — otherwise a lost race would record a
|
|
557
|
+
// revision for a save that never happened.
|
|
558
|
+
const updateResult = await db
|
|
559
|
+
.update(schema.dashboards)
|
|
560
|
+
.set(setValues)
|
|
561
|
+
.where(
|
|
562
|
+
and(
|
|
563
|
+
eq(schema.dashboards.id, id),
|
|
564
|
+
eq(schema.dashboards.updatedAt, expectedUpdatedAt),
|
|
565
|
+
),
|
|
566
|
+
);
|
|
567
|
+
const affected = affectedRowCount(updateResult);
|
|
568
|
+
if (affected === undefined) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
"The database driver did not report an affected-row count for the fenced dashboard update.",
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
if (affected === 0) {
|
|
574
|
+
throw new DashboardConflictError(id);
|
|
575
|
+
}
|
|
576
|
+
if (changed) await snapshotDashboardRevision(db, existing, ctx);
|
|
577
|
+
} else {
|
|
578
|
+
if (changed) await snapshotDashboardRevision(db, existing, ctx);
|
|
579
|
+
await db
|
|
580
|
+
.update(schema.dashboards)
|
|
581
|
+
.set(setValues)
|
|
582
|
+
.where(eq(schema.dashboards.id, id));
|
|
583
|
+
}
|
|
501
584
|
} else {
|
|
502
585
|
await db.insert(schema.dashboards).values({
|
|
503
586
|
id,
|
|
@@ -528,6 +611,66 @@ export async function upsertDashboard(
|
|
|
528
611
|
return dashboard;
|
|
529
612
|
}
|
|
530
613
|
|
|
614
|
+
/** Max attempts (first try + retries) for `upsertDashboardWithRetry`. */
|
|
615
|
+
export const DASHBOARD_SAVE_MAX_ATTEMPTS = 3;
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Read-modify-write helper for the four action call sites that fetch a
|
|
619
|
+
* dashboard, mutate its config in memory, then save it back. Fences every
|
|
620
|
+
* save with the `updatedAt` of the record `mutate` was given, so a
|
|
621
|
+
* concurrent writer (agent adds a panel while a human drags one, or two
|
|
622
|
+
* agent calls race) never gets silently clobbered.
|
|
623
|
+
*
|
|
624
|
+
* `mutate` is invoked with the freshest `DashboardRecord` on every attempt —
|
|
625
|
+
* re-fetched from `getDashboard` each time — and must recompute the
|
|
626
|
+
* `{ kind, body }` to save FROM THAT RECORD, not from a closure over an
|
|
627
|
+
* earlier read; only then does a retry actually merge both writers' changes
|
|
628
|
+
* instead of re-deriving the same stale result. `mutate` may throw a
|
|
629
|
+
* non-conflict error (e.g. validation) to abort immediately without
|
|
630
|
+
* retrying.
|
|
631
|
+
*
|
|
632
|
+
* On a lost race, this re-reads and re-invokes `mutate` up to `maxAttempts`
|
|
633
|
+
* times before failing loud with a clear error so callers never silently
|
|
634
|
+
* drop a write or loop forever.
|
|
635
|
+
*/
|
|
636
|
+
export async function upsertDashboardWithRetry(
|
|
637
|
+
id: string,
|
|
638
|
+
ctx: AccessCtx,
|
|
639
|
+
mutate: (
|
|
640
|
+
existing: DashboardRecord,
|
|
641
|
+
) =>
|
|
642
|
+
| { kind: DashboardKind; body: Record<string, unknown> }
|
|
643
|
+
| Promise<{ kind: DashboardKind; body: Record<string, unknown> }>,
|
|
644
|
+
maxAttempts: number = DASHBOARD_SAVE_MAX_ATTEMPTS,
|
|
645
|
+
): Promise<DashboardRecord> {
|
|
646
|
+
let lastConflict: unknown;
|
|
647
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
648
|
+
const existing = await getDashboard(id, ctx);
|
|
649
|
+
if (!existing) {
|
|
650
|
+
throw new Error(
|
|
651
|
+
`dashboard "${id}" not found (or you don't have access).`,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
const { kind, body } = await mutate(existing);
|
|
655
|
+
try {
|
|
656
|
+
return await upsertDashboard(id, kind, body, ctx, existing.updatedAt);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
if (err instanceof DashboardConflictError) {
|
|
659
|
+
lastConflict = err;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
throw err;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const finalError = new Error(
|
|
666
|
+
`Could not save dashboard "${id}" after ${maxAttempts} attempt(s); it kept changing concurrently. Re-read the dashboard and try again.`,
|
|
667
|
+
);
|
|
668
|
+
if (lastConflict !== undefined) {
|
|
669
|
+
(finalError as Error & { cause?: unknown }).cause = lastConflict;
|
|
670
|
+
}
|
|
671
|
+
throw finalError;
|
|
672
|
+
}
|
|
673
|
+
|
|
531
674
|
export async function listDashboardRevisions(
|
|
532
675
|
dashboardId: string,
|
|
533
676
|
ctx: AccessCtx,
|