@agent-native/core 0.98.4 → 0.98.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +37 -0
- package/corpus/core/docs/content/external-agents.mdx +62 -7
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +4 -2
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +38 -7
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +39 -7
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +37 -7
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +37 -7
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +2 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +95 -21
- package/corpus/core/src/a2a/task-store.ts +65 -3
- package/corpus/core/src/agent/durable-background.ts +16 -16
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +25 -8
- package/corpus/core/src/agent/engine/anthropic-engine.ts +17 -3
- package/corpus/core/src/agent/engine/output-tokens.ts +6 -6
- package/corpus/core/src/agent/production-agent.ts +200 -27
- package/corpus/core/src/agent/run-manager.ts +81 -1
- package/corpus/core/src/agent/run-store.ts +301 -41
- package/corpus/core/src/client/AgentPanel.tsx +6 -2
- package/corpus/core/src/client/AssistantChat.tsx +364 -69
- package/corpus/core/src/client/agent-chat-adapter.ts +169 -1
- package/corpus/core/src/client/chat/message-components.tsx +3 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +20 -36
- package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +7 -0
- package/corpus/core/src/client/extensions/AgentNativeExtensionFrame.tsx +2 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +2 -0
- package/corpus/core/src/client/extensions/ExtensionEditor.tsx +9 -2
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +2 -0
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +2 -0
- package/corpus/core/src/client/extensions/portable-extension.ts +2 -0
- package/corpus/core/src/client/session-replay.ts +361 -29
- package/corpus/core/src/client/sse-event-processor.ts +43 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +1 -1
- package/corpus/core/src/demo/config.ts +6 -6
- package/corpus/core/src/demo/fetch-interceptor.ts +13 -4
- package/corpus/core/src/demo/redact.ts +13 -149
- package/corpus/core/src/extensions/html-shell.ts +3 -0
- package/corpus/core/src/extensions/session-replay-iframe.ts +131 -0
- package/corpus/core/src/integrations/adapters/slack.ts +123 -13
- package/corpus/core/src/integrations/identity-links-store.ts +210 -0
- package/corpus/core/src/integrations/identity.ts +132 -0
- package/corpus/core/src/integrations/index.ts +6 -0
- package/corpus/core/src/integrations/plugin.ts +58 -1
- package/corpus/core/src/integrations/types.ts +7 -0
- package/corpus/core/src/mcp/build-server.ts +127 -22
- package/corpus/core/src/mcp/external-agent-policy.ts +18 -0
- package/corpus/core/src/mcp/index.ts +1 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -20
- package/corpus/core/src/server/agent-chat/plugin-options.ts +12 -1
- package/corpus/core/src/server/agent-chat/script-entries.ts +20 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +3 -0
- package/corpus/core/src/session-replay-iframe-protocol.ts +58 -0
- package/corpus/core/src/shared/reasoning-effort.ts +35 -0
- package/corpus/core/src/styles/agent-conversation.css +26 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +30 -21
- package/corpus/templates/analytics/AGENTS.md +27 -0
- package/corpus/templates/analytics/actions/create-session-replay-agent-link.ts +0 -1
- package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +40 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +13 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -0
- package/corpus/templates/analytics/app/global.css +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +52 -203
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +69 -16
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-chat-retries-before-showing-a-generic-no-data-mess.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-sessions-search-no-longer-loses-fast-keystrokes-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-chart-selection-respects-chat-state.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-connected-external-agents-can-now-look-up-sessions-error-iss.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-anonymizes-error-reporting-emails.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-identities-stay-visible-when-demo-mode-is-off.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-show-iframe-content.md +6 -0
- package/corpus/templates/analytics/netlify.toml +0 -3
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +14 -0
- package/corpus/templates/analytics/server/lib/error-capture.ts +101 -2
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +24 -0
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +49 -17
- package/corpus/templates/analytics/server/lib/session-replay.ts +18 -16
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +43 -6
- package/corpus/templates/analytics/server/plugins/db.ts +15 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/brain/app/global.css +2 -2
- package/corpus/templates/brain/changelog/2026-07-12-fix-long-response-progress.md +6 -0
- package/corpus/templates/forms/app/components/CloudUpgrade.tsx +1 -1
- package/corpus/templates/forms/app/components/ThemeToggle.tsx +22 -6
- package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +5 -5
- package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +3 -3
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +16 -16
- package/corpus/templates/forms/app/global.css +164 -13
- package/corpus/templates/forms/app/pages/AskPage.tsx +35 -13
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +143 -67
- package/corpus/templates/forms/app/pages/FormFillPage.tsx +44 -11
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +42 -24
- package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +28 -15
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +46 -20
- package/corpus/templates/forms/app/routes/_app.settings.tsx +1 -1
- package/corpus/templates/forms/changelog/2026-07-12-ask-forms-suggestions-now-sit-beneath-the-composer-for-a-tig.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-builder-response-tables-now-fill-the-view-without-a-redundan.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-form-builder-surfaces-are-calmer-fields-stay-stable-on-hover.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-response-tables-now-fill-the-available-pane-with-a-flexible-.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-smoother-more-tactile-controls-throughout-animated-icon-and-.md +6 -0
- package/corpus/templates/mail/AGENTS.md +3 -1
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +21 -13
- package/corpus/templates/mail/app/components/email/email-iframe-document.ts +18 -0
- package/corpus/templates/mail/server/lib/mail-integrations.ts +3 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +84 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/task-store.d.ts +20 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +57 -4
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/durable-background.d.ts +10 -10
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +16 -16
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +19 -8
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -3
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/output-tokens.js +6 -6
- package/dist/agent/engine/output-tokens.js.map +1 -1
- package/dist/agent/production-agent.d.ts +52 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +154 -26
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +71 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +9 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +243 -48
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +298 -67
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +146 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +2 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +3 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +13 -37
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +6 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.js +2 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionEditor.js +5 -2
- package/dist/client/extensions/ExtensionEditor.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +2 -1
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +2 -1
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js +2 -0
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +308 -29
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +5 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +37 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +1 -1
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +6 -6
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +13 -4
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/demo/redact.d.ts +7 -7
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +8 -145
- package/dist/demo/redact.js.map +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +2 -0
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/session-replay-iframe.d.ts +10 -0
- package/dist/extensions/session-replay-iframe.d.ts.map +1 -0
- package/dist/extensions/session-replay-iframe.js +121 -0
- package/dist/extensions/session-replay-iframe.js.map +1 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +98 -13
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/identity-links-store.d.ts +23 -0
- package/dist/integrations/identity-links-store.d.ts.map +1 -0
- package/dist/integrations/identity-links-store.js +158 -0
- package/dist/integrations/identity-links-store.js.map +1 -0
- package/dist/integrations/identity.d.ts +11 -0
- package/dist/integrations/identity.d.ts.map +1 -0
- package/dist/integrations/identity.js +100 -0
- package/dist/integrations/identity.js.map +1 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +43 -1
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +6 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/mcp/build-server.d.ts +9 -2
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +97 -20
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/external-agent-policy.d.ts +19 -0
- package/dist/mcp/external-agent-policy.d.ts.map +1 -0
- package/dist/mcp/external-agent-policy.js +2 -0
- package/dist/mcp/external-agent-policy.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/notifications/routes.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 +1 -19
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/server/agent-chat/plugin-options.d.ts +11 -1
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/script-entries.d.ts.map +1 -1
- package/dist/server/agent-chat/script-entries.js +16 -2
- package/dist/server/agent-chat/script-entries.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/session-replay-iframe-protocol.d.ts +36 -0
- package/dist/session-replay-iframe-protocol.d.ts.map +1 -0
- package/dist/session-replay-iframe-protocol.js +24 -0
- package/dist/session-replay-iframe-protocol.js.map +1 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.d.ts.map +1 -1
- package/dist/shared/reasoning-effort.js +36 -0
- package/dist/shared/reasoning-effort.js.map +1 -1
- package/dist/styles/agent-conversation.css +26 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/docs/content/durable-background-runs.mdx +37 -0
- package/docs/content/external-agents.mdx +62 -7
- package/docs/content/locales/ar-SA/external-agents.mdx +38 -7
- package/docs/content/locales/ar-SA/tracking.mdx +2 -0
- package/docs/content/locales/de-DE/external-agents.mdx +39 -7
- package/docs/content/locales/de-DE/tracking.mdx +2 -0
- package/docs/content/locales/es-ES/external-agents.mdx +39 -7
- package/docs/content/locales/es-ES/tracking.mdx +2 -0
- package/docs/content/locales/fr-FR/external-agents.mdx +39 -7
- package/docs/content/locales/fr-FR/tracking.mdx +4 -2
- package/docs/content/locales/hi-IN/external-agents.mdx +38 -7
- package/docs/content/locales/hi-IN/tracking.mdx +2 -0
- package/docs/content/locales/ja-JP/external-agents.mdx +38 -7
- package/docs/content/locales/ja-JP/tracking.mdx +2 -0
- package/docs/content/locales/ko-KR/external-agents.mdx +38 -7
- package/docs/content/locales/ko-KR/tracking.mdx +2 -0
- package/docs/content/locales/pt-BR/external-agents.mdx +39 -7
- package/docs/content/locales/pt-BR/tracking.mdx +2 -0
- package/docs/content/locales/zh-CN/external-agents.mdx +37 -7
- package/docs/content/locales/zh-CN/tracking.mdx +2 -0
- package/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
- package/docs/content/locales/zh-TW/external-agents.mdx +37 -7
- package/docs/content/locales/zh-TW/tracking.mdx +2 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +2 -2
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +34 -5
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
|
@@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
* reads go through `accessFilter` so an org-scoped key surfaces its issues to
|
|
19
19
|
* the whole org exactly like session recordings.
|
|
20
20
|
*/
|
|
21
|
+
import { appStateGet } from "@agent-native/core/application-state";
|
|
21
22
|
import { notifyWithDelivery } from "@agent-native/core/notifications";
|
|
22
23
|
import { recordChange } from "@agent-native/core/server";
|
|
23
24
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
and,
|
|
26
27
|
desc,
|
|
27
28
|
eq,
|
|
29
|
+
exists,
|
|
28
30
|
inArray,
|
|
29
31
|
isNull,
|
|
30
32
|
notInArray,
|
|
@@ -1072,10 +1074,62 @@ export interface ListErrorIssuesFilters {
|
|
|
1072
1074
|
status?: IssueStatus | "all";
|
|
1073
1075
|
query?: string;
|
|
1074
1076
|
app?: string;
|
|
1077
|
+
sessionRecordingId?: string;
|
|
1078
|
+
userId?: string;
|
|
1075
1079
|
sort?: "lastSeen" | "eventCount" | "firstSeen";
|
|
1076
1080
|
limit?: number;
|
|
1077
1081
|
}
|
|
1078
1082
|
|
|
1083
|
+
export const ERROR_REPORTING_ANONYMOUS_EMAIL = "anonymous@builder.io";
|
|
1084
|
+
const ERROR_REPORTING_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Error reports contain emails in identity fields, messages, stacks, URLs,
|
|
1088
|
+
* tags, extra context, and breadcrumbs. Redact every string at this owning
|
|
1089
|
+
* read seam so demo mode never depends on browser interception timing.
|
|
1090
|
+
*/
|
|
1091
|
+
export function anonymizeErrorReportingEmails<T>(value: T): T {
|
|
1092
|
+
if (typeof value === "string") {
|
|
1093
|
+
return value.replace(
|
|
1094
|
+
ERROR_REPORTING_EMAIL_PATTERN,
|
|
1095
|
+
ERROR_REPORTING_ANONYMOUS_EMAIL,
|
|
1096
|
+
) as T;
|
|
1097
|
+
}
|
|
1098
|
+
if (Array.isArray(value)) {
|
|
1099
|
+
return value.map((item) => anonymizeErrorReportingEmails(item)) as T;
|
|
1100
|
+
}
|
|
1101
|
+
if (value && typeof value === "object") {
|
|
1102
|
+
if (value instanceof Date) return value;
|
|
1103
|
+
const output: Record<string, unknown> = {};
|
|
1104
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1105
|
+
output[key] = anonymizeErrorReportingEmails(entry);
|
|
1106
|
+
}
|
|
1107
|
+
return output as T;
|
|
1108
|
+
}
|
|
1109
|
+
return value;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
async function isErrorDemoModeEnabled(
|
|
1113
|
+
userEmail?: string | null,
|
|
1114
|
+
): Promise<boolean> {
|
|
1115
|
+
if (process.env.DEMO_MODE === "true") return true;
|
|
1116
|
+
if (!userEmail) return false;
|
|
1117
|
+
try {
|
|
1118
|
+
// Keep demo state tied to the caller whose issues are being read.
|
|
1119
|
+
const state = await appStateGet(userEmail, "demo-mode");
|
|
1120
|
+
return state?.enabled === true;
|
|
1121
|
+
} catch {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function anonymizeErrorReportingEmailsInDemoMode<T>(
|
|
1127
|
+
value: T,
|
|
1128
|
+
enabled: boolean,
|
|
1129
|
+
): T {
|
|
1130
|
+
return enabled ? anonymizeErrorReportingEmails(value) : value;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1079
1133
|
export interface ErrorIssueSummary {
|
|
1080
1134
|
id: string;
|
|
1081
1135
|
fingerprint: string;
|
|
@@ -1148,6 +1202,7 @@ export async function listErrorIssues(
|
|
|
1148
1202
|
scope: ErrorReadScope,
|
|
1149
1203
|
filters: ListErrorIssuesFilters = {},
|
|
1150
1204
|
): Promise<ErrorIssueSummary[]> {
|
|
1205
|
+
const demoModeEnabled = isErrorDemoModeEnabled(scope.userEmail);
|
|
1151
1206
|
const db = getDb() as any;
|
|
1152
1207
|
const limit = Math.min(
|
|
1153
1208
|
MAX_ISSUE_LIMIT,
|
|
@@ -1158,6 +1213,41 @@ export async function listErrorIssues(
|
|
|
1158
1213
|
conditions.push(eq(schema.errorIssues.status, filters.status));
|
|
1159
1214
|
}
|
|
1160
1215
|
if (filters.app) conditions.push(eq(schema.errorIssues.app, filters.app));
|
|
1216
|
+
const sessionRecordingId = filters.sessionRecordingId?.trim();
|
|
1217
|
+
const userId = filters.userId?.trim();
|
|
1218
|
+
if (sessionRecordingId || userId) {
|
|
1219
|
+
const occurrenceConditions: any[] = [
|
|
1220
|
+
eq(schema.errorEvents.issueId, schema.errorIssues.id),
|
|
1221
|
+
// Keep the child occurrence in the same tenant as its parent issue even
|
|
1222
|
+
// when the issue is visible through an org share.
|
|
1223
|
+
eq(schema.errorEvents.ownerEmail, schema.errorIssues.ownerEmail),
|
|
1224
|
+
or(
|
|
1225
|
+
eq(schema.errorEvents.orgId, schema.errorIssues.orgId),
|
|
1226
|
+
and(isNull(schema.errorEvents.orgId), isNull(schema.errorIssues.orgId)),
|
|
1227
|
+
),
|
|
1228
|
+
];
|
|
1229
|
+
if (sessionRecordingId) {
|
|
1230
|
+
occurrenceConditions.push(
|
|
1231
|
+
eq(schema.errorEvents.sessionRecordingId, sessionRecordingId),
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
if (userId) {
|
|
1235
|
+
occurrenceConditions.push(
|
|
1236
|
+
or(
|
|
1237
|
+
eq(schema.errorEvents.userId, userId),
|
|
1238
|
+
eq(schema.errorEvents.userKey, userId),
|
|
1239
|
+
),
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
conditions.push(
|
|
1243
|
+
exists(
|
|
1244
|
+
db
|
|
1245
|
+
.select({ id: schema.errorEvents.id })
|
|
1246
|
+
.from(schema.errorEvents)
|
|
1247
|
+
.where(and(...occurrenceConditions)),
|
|
1248
|
+
),
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1161
1251
|
const query = filters.query?.trim();
|
|
1162
1252
|
if (query) {
|
|
1163
1253
|
conditions.push(
|
|
@@ -1184,7 +1274,7 @@ export async function listErrorIssues(
|
|
|
1184
1274
|
.limit(limit);
|
|
1185
1275
|
|
|
1186
1276
|
const sparklines = await sparklinesForIssues(rows.map((row: any) => row.id));
|
|
1187
|
-
|
|
1277
|
+
const issues = rows.map((row: any) => ({
|
|
1188
1278
|
id: row.id,
|
|
1189
1279
|
fingerprint: row.fingerprint,
|
|
1190
1280
|
type: row.type,
|
|
@@ -1203,6 +1293,7 @@ export async function listErrorIssues(
|
|
|
1203
1293
|
template: row.template ?? null,
|
|
1204
1294
|
sparkline: sparklines.get(row.id) ?? new Array(SPARKLINE_DAYS).fill(0),
|
|
1205
1295
|
}));
|
|
1296
|
+
return anonymizeErrorReportingEmailsInDemoMode(issues, await demoModeEnabled);
|
|
1206
1297
|
}
|
|
1207
1298
|
|
|
1208
1299
|
export interface ErrorEventDetail {
|
|
@@ -1294,6 +1385,7 @@ export async function getErrorIssue(
|
|
|
1294
1385
|
issueId: string,
|
|
1295
1386
|
options: { eventsLimit?: number } = {},
|
|
1296
1387
|
): Promise<ErrorIssueDetail> {
|
|
1388
|
+
const demoModeEnabled = isErrorDemoModeEnabled(scope.userEmail);
|
|
1297
1389
|
const db = getDb() as any;
|
|
1298
1390
|
const [issueRow] = await db
|
|
1299
1391
|
.select()
|
|
@@ -1419,7 +1511,14 @@ export async function getErrorIssue(
|
|
|
1419
1511
|
sparkline: sparklines.get(issueId) ?? new Array(SPARKLINE_DAYS).fill(0),
|
|
1420
1512
|
};
|
|
1421
1513
|
|
|
1422
|
-
return
|
|
1514
|
+
return anonymizeErrorReportingEmailsInDemoMode(
|
|
1515
|
+
{
|
|
1516
|
+
issue,
|
|
1517
|
+
events,
|
|
1518
|
+
sessions: Array.from(sessions.values()),
|
|
1519
|
+
},
|
|
1520
|
+
await demoModeEnabled,
|
|
1521
|
+
);
|
|
1423
1522
|
}
|
|
1424
1523
|
|
|
1425
1524
|
export interface UpdateErrorIssueInput {
|
|
@@ -8,6 +8,7 @@ const INJECTED_CONTEXT_BLOCKS = [
|
|
|
8
8
|
"available-agents",
|
|
9
9
|
"available-jobs",
|
|
10
10
|
"plan-mode-note",
|
|
11
|
+
"non-analytics-retry",
|
|
11
12
|
];
|
|
12
13
|
|
|
13
14
|
export const DATA_QUERY_ACTIONS = new Set([
|
|
@@ -231,12 +232,35 @@ export function looksLikeAnalyticsDataRequest(text: string): boolean {
|
|
|
231
232
|
const UNSUPPORTED_RESULT_CLAIM =
|
|
232
233
|
/(?:\b\d[\d,.]*(?:\.\d+)?\s*(?:%|percent|users?|customers?|accounts?|sessions?|events?|deals?|tickets?|issues?|calls?|messages?|signups?|pageviews?)\b|\$\s*\d|\b(?:data|query|results?)\s+(?:shows?|showed|indicates?|returned|found)\b|\b(?:i found|the top|the bottom|highest|lowest|increased|decreased|grew|declined|converted|churned|retained|averaged|total(?:ed)?|count(?:ed)?)\b)/i;
|
|
233
234
|
|
|
235
|
+
export const GENERIC_NO_DATA_FALLBACK_MESSAGE =
|
|
236
|
+
"I can't provide a grounded analytics result yet because no real data-source query ran successfully. Tell me which source to use or connect the missing source, and I'll run it before giving numbers or source-record conclusions.";
|
|
237
|
+
|
|
238
|
+
// The first sentence of the canned fallback, lowercased, with the trailing
|
|
239
|
+
// period dropped. Matching this prefix (rather than the whole message) still
|
|
240
|
+
// catches a model paraphrase that continues differently after "successfully".
|
|
241
|
+
const GENERIC_NO_DATA_FALLBACK_FIRST_SENTENCE =
|
|
242
|
+
GENERIC_NO_DATA_FALLBACK_MESSAGE.slice(
|
|
243
|
+
0,
|
|
244
|
+
GENERIC_NO_DATA_FALLBACK_MESSAGE.indexOf(". ") + 1,
|
|
245
|
+
).toLowerCase();
|
|
246
|
+
|
|
247
|
+
export function isGenericNoDataFallback(text: string): boolean {
|
|
248
|
+
return text
|
|
249
|
+
.trim()
|
|
250
|
+
.toLowerCase()
|
|
251
|
+
.startsWith(GENERIC_NO_DATA_FALLBACK_FIRST_SENTENCE.slice(0, -1));
|
|
252
|
+
}
|
|
253
|
+
|
|
234
254
|
const SAFE_NO_DATA_RESPONSE =
|
|
235
255
|
/\b(?:i can't|i cannot|can't retrieve|cannot retrieve|couldn't retrieve|unable to retrieve|don't have access|do not have access|not configured|missing credentials?|need (?:a|the)? ?data source|need to know which source|which source|which data source|clarify|can you|once (?:that'?s|it is) (?:connected|configured|available)|no data source|without a successful|query failed|source query failed|sql failed|error running|before (?:i|we) can (?:calculate|report|answer|analyze)|i need to query)\b/i;
|
|
236
256
|
|
|
237
257
|
export function isSafeNoDataAnalyticsResponse(text: string): boolean {
|
|
238
258
|
const trimmed = text.trim();
|
|
239
259
|
if (!trimmed) return false;
|
|
260
|
+
// This is the guard's own last-resort fallback. If a model emits it before
|
|
261
|
+
// attempting a query, it must go through the retry path instead of being
|
|
262
|
+
// accepted as an explicit unavailable-source or clarification response.
|
|
263
|
+
if (isGenericNoDataFallback(trimmed)) return false;
|
|
240
264
|
if (UNSUPPORTED_RESULT_CLAIM.test(trimmed)) return false;
|
|
241
265
|
if (SAFE_NO_DATA_RESPONSE.test(trimmed)) return true;
|
|
242
266
|
return /\?\s*$/.test(trimmed) && !UNSUPPORTED_RESULT_CLAIM.test(trimmed);
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "../../shared/session-replay-diagnostics.js";
|
|
23
23
|
import {
|
|
24
24
|
compactSessionRecordingSummary,
|
|
25
|
+
getSessionReplayEvents,
|
|
25
26
|
getSessionReplaySummary,
|
|
26
27
|
getSessionReplayTokenizedEvents,
|
|
27
28
|
getSessionReplayTokenizedSummary,
|
|
@@ -82,20 +83,16 @@ function replayStartedAt(events: AgentReplayEvent[]): number {
|
|
|
82
83
|
|
|
83
84
|
function pathLabel(href: string): string {
|
|
84
85
|
try {
|
|
85
|
-
const parsed = new URL(href);
|
|
86
|
-
return
|
|
86
|
+
const parsed = new URL(href, "https://placeholder.agent-native.local");
|
|
87
|
+
return boundedDiagnosticText(
|
|
88
|
+
parsed.pathname || parsed.hostname,
|
|
89
|
+
MAX_DIAGNOSTIC_URL_CHARS,
|
|
90
|
+
);
|
|
87
91
|
} catch {
|
|
88
|
-
return href;
|
|
92
|
+
return boundedDiagnosticText(href, MAX_DIAGNOSTIC_URL_CHARS);
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
function markerDetail(event: AgentReplayEvent): string | null {
|
|
93
|
-
const payload = event.data?.payload;
|
|
94
|
-
if (typeof payload?.message === "string") return payload.message;
|
|
95
|
-
if (typeof event.data?.href === "string") return event.data.href;
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
96
|
const TIMELINE_MARKER_CAP = 200;
|
|
100
97
|
const TIMELINE_ERROR_MARKER_RESERVE = 100;
|
|
101
98
|
const MAX_DIAGNOSTIC_MESSAGE_CHARS = 1_000;
|
|
@@ -528,10 +525,9 @@ function buildReplayTimeline(events: AgentReplayEvent[]) {
|
|
|
528
525
|
if (isFailedSessionReplayNetworkStatus(status)) {
|
|
529
526
|
const method =
|
|
530
527
|
boundedDiagnosticText(tagged.payload.method, 16) || "GET";
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
);
|
|
528
|
+
// External-agent timelines intentionally expose only a path label.
|
|
529
|
+
// Query strings commonly contain emails, tokens, and other secrets.
|
|
530
|
+
const url = pathLabel(String(tagged.payload.url ?? ""));
|
|
535
531
|
markers.push({
|
|
536
532
|
timestamp,
|
|
537
533
|
offsetMs: Math.max(0, timestamp - startedAt),
|
|
@@ -553,7 +549,7 @@ function buildReplayTimeline(events: AgentReplayEvent[]) {
|
|
|
553
549
|
offsetMs: Math.max(0, timestamp - startedAt),
|
|
554
550
|
kind: "navigation",
|
|
555
551
|
label: pathLabel(event.data.href),
|
|
556
|
-
detail: event.data.href,
|
|
552
|
+
detail: pathLabel(event.data.href),
|
|
557
553
|
});
|
|
558
554
|
} else if (
|
|
559
555
|
event.type === RRWEB_EVENT_TYPE.IncrementalSnapshot &&
|
|
@@ -606,8 +602,14 @@ function buildReplayTimeline(events: AgentReplayEvent[]) {
|
|
|
606
602
|
timestamp,
|
|
607
603
|
offsetMs: Math.max(0, timestamp - startedAt),
|
|
608
604
|
kind: "custom",
|
|
609
|
-
label:
|
|
610
|
-
|
|
605
|
+
label: boundedDiagnosticText(
|
|
606
|
+
String(event.data?.tag ?? "Custom event"),
|
|
607
|
+
MAX_DIAGNOSTIC_MESSAGE_CHARS,
|
|
608
|
+
),
|
|
609
|
+
// Custom-event payloads are application-defined and can contain input
|
|
610
|
+
// values or secrets. The tag and timestamp are sufficient metadata for
|
|
611
|
+
// this sanitized external-agent timeline.
|
|
612
|
+
detail: null,
|
|
611
613
|
});
|
|
612
614
|
}
|
|
613
615
|
}
|
|
@@ -615,6 +617,36 @@ function buildReplayTimeline(events: AgentReplayEvent[]) {
|
|
|
615
617
|
return capReplayTimelineMarkers(markers);
|
|
616
618
|
}
|
|
617
619
|
|
|
620
|
+
export async function getSessionReplayTimeline(
|
|
621
|
+
recordingId: string,
|
|
622
|
+
scope: ReplayScope,
|
|
623
|
+
options: { eventLimit?: number } = {},
|
|
624
|
+
) {
|
|
625
|
+
const eventLimit = Math.min(
|
|
626
|
+
10_000,
|
|
627
|
+
Math.max(1, Math.floor(options.eventLimit ?? 10_000)),
|
|
628
|
+
);
|
|
629
|
+
const eventsResponse = await getSessionReplayEvents(recordingId, scope, {
|
|
630
|
+
limit: eventLimit,
|
|
631
|
+
});
|
|
632
|
+
const events = eventsResponse.chunks.flatMap((chunk) =>
|
|
633
|
+
chunk.events.filter(
|
|
634
|
+
(event): event is AgentReplayEvent =>
|
|
635
|
+
Boolean(event) && typeof event === "object",
|
|
636
|
+
),
|
|
637
|
+
);
|
|
638
|
+
const markers = buildReplayTimeline(events);
|
|
639
|
+
|
|
640
|
+
return {
|
|
641
|
+
recording: compactSessionRecordingSummary(eventsResponse.recording),
|
|
642
|
+
markerCount: markers.length,
|
|
643
|
+
markers,
|
|
644
|
+
eventCount: eventsResponse.eventCount,
|
|
645
|
+
truncated: eventsResponse.truncated,
|
|
646
|
+
unavailableChunks: eventsResponse.unavailableChunks,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
618
650
|
export function verifySessionReplayAgentAccess(
|
|
619
651
|
recordingId: string,
|
|
620
652
|
token: string,
|
|
@@ -2,14 +2,18 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { appStateGet } from "@agent-native/core/application-state";
|
|
6
6
|
import {
|
|
7
7
|
deletePrivateBlob,
|
|
8
8
|
putPrivateBlob,
|
|
9
9
|
readPrivateBlob,
|
|
10
10
|
type PrivateBlobHandle,
|
|
11
11
|
} from "@agent-native/core/private-blob";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getRequestUserEmail,
|
|
14
|
+
recordChange,
|
|
15
|
+
runWithRequestContext,
|
|
16
|
+
} from "@agent-native/core/server";
|
|
13
17
|
import {
|
|
14
18
|
accessFilter,
|
|
15
19
|
resolveAccess,
|
|
@@ -255,6 +259,7 @@ const RETENTION_DELETE_BATCH_SIZE = 500;
|
|
|
255
259
|
const REPLAY_PRIVATE_BLOB_REF_KIND = "agent-native.session-replay.private-blob";
|
|
256
260
|
const REPLAY_PRIVATE_BLOB_REF_VERSION = 1;
|
|
257
261
|
const SESSION_DEMO_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
262
|
+
const SESSION_DEMO_ANONYMOUS_EMAIL = "anonymous@builder.io";
|
|
258
263
|
let inlineReplayFallbackWarned = false;
|
|
259
264
|
|
|
260
265
|
function replayError(message: string, statusCode: number): Error {
|
|
@@ -300,10 +305,15 @@ function isBuilderEmail(value: unknown): boolean {
|
|
|
300
305
|
return normalizedEmail(value)?.endsWith("@builder.io") === true;
|
|
301
306
|
}
|
|
302
307
|
|
|
303
|
-
async function isSessionDemoModeEnabled(
|
|
308
|
+
async function isSessionDemoModeEnabled(
|
|
309
|
+
userEmail?: string | null,
|
|
310
|
+
): Promise<boolean> {
|
|
304
311
|
if (process.env.DEMO_MODE === "true") return true;
|
|
312
|
+
if (!userEmail) return false;
|
|
305
313
|
try {
|
|
306
|
-
|
|
314
|
+
// These reads also run from custom routes and actions, so use the
|
|
315
|
+
// authenticated scope explicitly instead of ambient request state.
|
|
316
|
+
const state = await appStateGet(userEmail, "demo-mode");
|
|
307
317
|
return state?.enabled === true;
|
|
308
318
|
} catch {
|
|
309
319
|
return false;
|
|
@@ -318,20 +328,12 @@ function demoBuilderSessionCondition() {
|
|
|
318
328
|
}
|
|
319
329
|
|
|
320
330
|
class SessionDemoAnonymizer {
|
|
321
|
-
private readonly aliases = new Map<string, string>();
|
|
322
|
-
|
|
323
331
|
aliasFor(value: unknown): string | null {
|
|
324
332
|
const raw = replayString(value);
|
|
325
333
|
if (!raw || raw.match(SESSION_DEMO_EMAIL_PATTERN)?.[0] !== raw) {
|
|
326
334
|
return null;
|
|
327
335
|
}
|
|
328
|
-
|
|
329
|
-
let alias = this.aliases.get(email);
|
|
330
|
-
if (!alias) {
|
|
331
|
-
alias = `anonymized-${this.aliases.size + 1}@builder.io`;
|
|
332
|
-
this.aliases.set(email, alias);
|
|
333
|
-
}
|
|
334
|
-
return alias;
|
|
336
|
+
return SESSION_DEMO_ANONYMOUS_EMAIL;
|
|
335
337
|
}
|
|
336
338
|
|
|
337
339
|
summarize(row: any, role?: SessionReplayAccessRole): SessionRecordingSummary {
|
|
@@ -1684,7 +1686,7 @@ export async function listSessionRecordings(
|
|
|
1684
1686
|
filters: SessionReplayListFilters = {},
|
|
1685
1687
|
): Promise<SessionRecordingSummary[]> {
|
|
1686
1688
|
const db = getDb() as any;
|
|
1687
|
-
const demoMode = await isSessionDemoModeEnabled();
|
|
1689
|
+
const demoMode = await isSessionDemoModeEnabled(scope.userEmail);
|
|
1688
1690
|
const limit = Math.min(
|
|
1689
1691
|
MAX_SESSION_RECORDINGS_LIMIT,
|
|
1690
1692
|
Math.max(1, filters.limit ?? DEFAULT_SESSION_RECORDINGS_LIMIT),
|
|
@@ -1766,7 +1768,7 @@ export async function getSessionReplaySummary(
|
|
|
1766
1768
|
recordingId: string,
|
|
1767
1769
|
scope: SessionReplayScope,
|
|
1768
1770
|
): Promise<SessionRecordingSummary> {
|
|
1769
|
-
const demoMode = await isSessionDemoModeEnabled();
|
|
1771
|
+
const demoMode = await isSessionDemoModeEnabled(scope.userEmail);
|
|
1770
1772
|
const access = await resolveAccess("session-recording", recordingId, {
|
|
1771
1773
|
userEmail: scope.userEmail,
|
|
1772
1774
|
orgId: scope.orgId ?? undefined,
|
|
@@ -1785,7 +1787,7 @@ export async function getSessionReplaySummary(
|
|
|
1785
1787
|
export async function getSessionReplayTokenizedSummary(
|
|
1786
1788
|
recordingId: string,
|
|
1787
1789
|
): Promise<SessionRecordingSummary> {
|
|
1788
|
-
const demoMode = await isSessionDemoModeEnabled();
|
|
1790
|
+
const demoMode = await isSessionDemoModeEnabled(getRequestUserEmail());
|
|
1789
1791
|
const db = getDb() as any;
|
|
1790
1792
|
// guard:allow-unscoped -- called only after verifySessionReplayAgentAccess(recordingId, token) verifies a signed, recording-scoped agent_access token.
|
|
1791
1793
|
const [row] = await db
|
|
@@ -10,12 +10,15 @@ import {
|
|
|
10
10
|
applyAnalyticsPlanModePolicy,
|
|
11
11
|
INITIAL_TOOL_NAMES,
|
|
12
12
|
} from "../lib/agent-chat-plan-mode";
|
|
13
|
+
import { ANALYTICS_CONNECTOR_CATALOG } from "../lib/analytics-connector-catalog";
|
|
13
14
|
import {
|
|
14
15
|
failedDataQueryAttemptMessage,
|
|
16
|
+
GENERIC_NO_DATA_FALLBACK_MESSAGE,
|
|
15
17
|
hasExplicitPartialDisclosure,
|
|
16
18
|
hasFailedCorpusWorkflowEvidence,
|
|
17
19
|
hasDataQueryAttempt,
|
|
18
20
|
hasIncompleteDataEvidence,
|
|
21
|
+
isGenericNoDataFallback,
|
|
19
22
|
isSafeNoDataAnalyticsResponse,
|
|
20
23
|
hasOverstatedCoverageConfidenceClaim,
|
|
21
24
|
looksLikeCoverageSensitiveAnalyticsRequest,
|
|
@@ -24,16 +27,34 @@ import {
|
|
|
24
27
|
needsCorpusWorkflowForCoverageSensitiveRequest,
|
|
25
28
|
needsSourceRecordBodyWorkflowForCoverageSensitiveRequest,
|
|
26
29
|
} from "../lib/real-data-actions";
|
|
27
|
-
const ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
|
|
28
|
-
|
|
29
30
|
export const SIMPLE_TIME_BOUNDED_METRIC_FAST_PATH_GUIDANCE =
|
|
30
31
|
"SIMPLE TIME-BOUNDED METRIC FAST PATH — When the data dictionary or a known canonical source identifies the metric, run one bounded aggregate. Once it returns a valid result, answer the explicit question immediately with the source, time window, row count, and only necessary caveats. Do not schema-discover, retry, enrich, cross-check, or add breakdowns after that successful result unless the query failed or the result conflicts with the known metric definition. This does not waive the real-data requirement: never answer from a guess, stale value, or unverified result. ";
|
|
31
32
|
|
|
33
|
+
export const ANALYTICS_OBSERVABILITY_INCIDENT_GUIDANCE =
|
|
34
|
+
"OBSERVABILITY INCIDENT WORKFLOW — For a named user's session or error question, resolve the user's email from context, then use list-session-recordings with userId and hasErrors=true over a bounded recent window. Use list-error-issues with userId or sessionRecordingId to identify the grouped issue, then get-error-issue for stack, breadcrumbs, occurrences, and linked recordings. When the timeline, page navigation, console diagnostics, failed network requests, or clicks are needed, use create-session-replay-agent-link and follow its bounded diagnostics/context APIs. Prefer these first-party actions over generic SQL; use query-agent-native-analytics only to correlate first-party events. Report the matching evidence and do not claim a root cause without a corroborating error or replay signal. ";
|
|
35
|
+
|
|
36
|
+
export const NON_ANALYTICS_REQUEST_GUIDANCE =
|
|
37
|
+
"NON-ANALYTICS REQUESTS — If the user is not asking for a live metric, source record, or derived analytics claim, answer normally in chat. Greetings, general-knowledge questions, math, writing, coding, and conceptual questions do not need a data-source call. Do not use the no-grounded-data fallback for those requests. ";
|
|
38
|
+
|
|
39
|
+
// Deterministic backstop for the soft NON_ANALYTICS_REQUEST_GUIDANCE prompt
|
|
40
|
+
// above: if a model still parrots the canned no-grounded-data fallback on a
|
|
41
|
+
// non-analytics turn, retry once with this synthetic user message instead of
|
|
42
|
+
// letting the canned sentence reach the user. Wrapped in an injected-context
|
|
43
|
+
// tag (registered in INJECTED_CONTEXT_BLOCKS) so `looksLikeAnalyticsDataRequest`
|
|
44
|
+
// never classifies the guard's own retry turn as a data request and loops.
|
|
45
|
+
export const NON_ANALYTICS_FALLBACK_RETRY_MESSAGE =
|
|
46
|
+
"<non-analytics-retry>\nThe user's latest message is ordinary conversation. Reply to it directly and naturally. Never answer it with the no-grounded-data disclaimer.\n</non-analytics-retry>";
|
|
47
|
+
|
|
48
|
+
export const NON_ANALYTICS_FALLBACK_FINAL_MESSAGE =
|
|
49
|
+
"I got stuck generating a reply to that message. Please try again or rephrase it.";
|
|
50
|
+
|
|
32
51
|
export function analyticsSourceGuidanceOpening(): string {
|
|
33
52
|
return (
|
|
34
53
|
"<data-source-guidance>\n" +
|
|
35
54
|
"Apply real-data requirements only when presenting analytics results, source records, or derived metrics. Do not call data-source tools for workflow migration, recurring-job setup, UI/code fixes, settings help, conceptual planning, or other non-data tasks unless the user explicitly asks for data. " +
|
|
55
|
+
NON_ANALYTICS_REQUEST_GUIDANCE +
|
|
36
56
|
SIMPLE_TIME_BOUNDED_METRIC_FAST_PATH_GUIDANCE +
|
|
57
|
+
ANALYTICS_OBSERVABILITY_INCIDENT_GUIDANCE +
|
|
37
58
|
"SURFACE DIFFERENTIATION — You are the analytics assistant for definitions, deep-dive analysis, and action. For questions about what a metric, model, or table means, use the Data Dictionary and configured schema tools first. For trends, comparisons, anomalies, current data, or anything that requires querying live data, answer directly in chat with the relevant provider query, dashboard analysis, and inline charts when useful. "
|
|
38
59
|
);
|
|
39
60
|
}
|
|
@@ -124,7 +145,20 @@ export function realDataFinalGuard(
|
|
|
124
145
|
return null;
|
|
125
146
|
}
|
|
126
147
|
const userText = latestUserText(context.messages ?? []);
|
|
127
|
-
if (!looksLikeAnalyticsDataRequest(userText))
|
|
148
|
+
if (!looksLikeAnalyticsDataRequest(userText)) {
|
|
149
|
+
// Deterministic backstop: the soft NON_ANALYTICS_REQUEST_GUIDANCE prompt
|
|
150
|
+
// sentence is not always enough, and a model occasionally parrots the
|
|
151
|
+
// canned no-grounded-data fallback even for ordinary conversation. Catch
|
|
152
|
+
// that case here instead of letting it reach the user.
|
|
153
|
+
if (isGenericNoDataFallback(context.text)) {
|
|
154
|
+
return {
|
|
155
|
+
retryMessage: NON_ANALYTICS_FALLBACK_RETRY_MESSAGE,
|
|
156
|
+
fallbackMessage: NON_ANALYTICS_FALLBACK_FINAL_MESSAGE,
|
|
157
|
+
maxRetries: 2,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
128
162
|
const incompleteEvidence = hasIncompleteDataEvidence(context.toolResults);
|
|
129
163
|
if (
|
|
130
164
|
hasFailedCorpusWorkflowEvidence(context.toolResults) &&
|
|
@@ -200,7 +234,7 @@ export function realDataFinalGuard(
|
|
|
200
234
|
" If the right response is a clarification, plan, or explicit unavailable/credentials-missing message with no metrics or source-record claims, finalize that directly instead.",
|
|
201
235
|
fallbackMessage: configuredSources.length
|
|
202
236
|
? `I found connected data sources (${configuredSources.join(", ")}), but the model still did not run a real source query. Please retry the request; you do not need to reconnect those sources.`
|
|
203
|
-
:
|
|
237
|
+
: GENERIC_NO_DATA_FALLBACK_MESSAGE,
|
|
204
238
|
// Some models use separate turns for status, schema discovery, and the
|
|
205
239
|
// actual query. One corrective turn was enough for Sonnet but caused Luna
|
|
206
240
|
// to hit the fallback before it reached the query.
|
|
@@ -223,8 +257,11 @@ export default createAgentChatPlugin({
|
|
|
223
257
|
// Operators deploying to trusted internal environments can set
|
|
224
258
|
// AGENT_PROD_CODE_EXECUTION=trusted to also enable bash/read/edit/write.
|
|
225
259
|
codeExecution: { production: "sandboxed" },
|
|
226
|
-
|
|
227
|
-
|
|
260
|
+
connectorCatalog: [...ANALYTICS_CONNECTOR_CATALOG],
|
|
261
|
+
externalAgents: {
|
|
262
|
+
authenticatedReads: "auto",
|
|
263
|
+
writes: "ask_app_only",
|
|
264
|
+
},
|
|
228
265
|
resolveOrgId: async (event) => {
|
|
229
266
|
const ctx = await getOrgContext(event);
|
|
230
267
|
return ctx.orgId;
|
|
@@ -1249,6 +1249,21 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
1249
1249
|
sqlite: `ALTER TABLE monitor_incidents ADD COLUMN IF NOT EXISTS notification_delivered INTEGER NOT NULL DEFAULT 0`,
|
|
1250
1250
|
},
|
|
1251
1251
|
},
|
|
1252
|
+
{
|
|
1253
|
+
version: 118,
|
|
1254
|
+
name: "error-events-session-recording-filter-idx",
|
|
1255
|
+
sql: `CREATE INDEX IF NOT EXISTS error_events_session_recording_filter_idx ON error_events (session_recording_id, owner_email, org_id, issue_id)`,
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
version: 119,
|
|
1259
|
+
name: "error-events-user-id-filter-idx",
|
|
1260
|
+
sql: `CREATE INDEX IF NOT EXISTS error_events_user_id_filter_idx ON error_events (user_id, owner_email, org_id, issue_id)`,
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
version: 120,
|
|
1264
|
+
name: "error-events-user-key-filter-idx",
|
|
1265
|
+
sql: `CREATE INDEX IF NOT EXISTS error_events_user_key_filter_idx ON error_events (user_key, owner_email, org_id, issue_id)`,
|
|
1266
|
+
},
|
|
1252
1267
|
],
|
|
1253
1268
|
{ table: "analytics_migrations" },
|
|
1254
1269
|
);
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
.brain-chat-panel [data-agent-empty-state="centered"] .brain-chat-intro {
|
|
136
136
|
display: grid;
|
|
137
137
|
width: 100%;
|
|
138
|
-
max-width: min(
|
|
138
|
+
max-width: min(684px, 90%);
|
|
139
139
|
gap: 0.5rem;
|
|
140
140
|
margin: 0 auto 1rem;
|
|
141
141
|
text-align: center;
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
|
|
166
166
|
.brain-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
|
|
167
167
|
width: 100%;
|
|
168
|
-
max-width: min(
|
|
168
|
+
max-width: min(684px, 90%);
|
|
169
169
|
padding: 0;
|
|
170
170
|
}
|
|
171
171
|
|
|
@@ -168,7 +168,7 @@ export function CloudUpgrade({
|
|
|
168
168
|
key={p.id}
|
|
169
169
|
onClick={() => setSelectedProvider(p.id)}
|
|
170
170
|
className={cn(
|
|
171
|
-
"flex flex-col items-start rounded-lg border px-3 py-2.5 text-left min-h-[44px]",
|
|
171
|
+
"flex flex-col items-start rounded-lg border px-3 py-2.5 text-left min-h-[44px] transition-[border-color,background-color,scale] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100",
|
|
172
172
|
selectedProvider === p.id
|
|
173
173
|
? "border-primary bg-primary/5"
|
|
174
174
|
: "border-border hover:border-primary/30",
|
|
@@ -22,13 +22,29 @@ export function ThemeToggle({ className }: { className?: string }) {
|
|
|
22
22
|
variant="ghost"
|
|
23
23
|
size="icon"
|
|
24
24
|
onClick={() => setTheme(isDark ? "light" : "dark")}
|
|
25
|
-
className={cn(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<IconSun className="h-4 w-4" />
|
|
29
|
-
) : (
|
|
30
|
-
<IconMoon className="h-4 w-4" />
|
|
25
|
+
className={cn(
|
|
26
|
+
"relative text-muted-foreground before:absolute before:-inset-1.5",
|
|
27
|
+
className,
|
|
31
28
|
)}
|
|
29
|
+
>
|
|
30
|
+
<span className="relative inline-flex h-4 w-4 items-center justify-center">
|
|
31
|
+
<IconSun
|
|
32
|
+
className={cn(
|
|
33
|
+
"absolute inset-0 h-4 w-4 transition-[opacity,filter,scale] duration-200 ease-[cubic-bezier(0.2,0,0,1)]",
|
|
34
|
+
isDark
|
|
35
|
+
? "scale-100 opacity-100 blur-none"
|
|
36
|
+
: "scale-[0.25] opacity-0 blur-[4px]",
|
|
37
|
+
)}
|
|
38
|
+
/>
|
|
39
|
+
<IconMoon
|
|
40
|
+
className={cn(
|
|
41
|
+
"absolute inset-0 h-4 w-4 transition-[opacity,filter,scale] duration-200 ease-[cubic-bezier(0.2,0,0,1)]",
|
|
42
|
+
isDark
|
|
43
|
+
? "scale-[0.25] opacity-0 blur-[4px]"
|
|
44
|
+
: "scale-100 opacity-100 blur-none",
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
</span>
|
|
32
48
|
</Button>
|
|
33
49
|
</TooltipTrigger>
|
|
34
50
|
<TooltipContent>{t("root.toggleTheme")}</TooltipContent>
|
|
@@ -70,7 +70,7 @@ export function FieldPropertiesPanel({
|
|
|
70
70
|
<Button
|
|
71
71
|
variant="ghost"
|
|
72
72
|
size="sm"
|
|
73
|
-
className="
|
|
73
|
+
className="h-10 px-3 text-xs text-destructive active:scale-[0.96]"
|
|
74
74
|
onClick={onDelete}
|
|
75
75
|
>
|
|
76
76
|
{t("common.delete")}
|
|
@@ -181,13 +181,13 @@ export function FieldPropertiesPanel({
|
|
|
181
181
|
<Button
|
|
182
182
|
variant="ghost"
|
|
183
183
|
size="sm"
|
|
184
|
-
className="h-
|
|
184
|
+
className="relative h-10 w-10 shrink-0 p-0 transition-transform duration-150 ease-out before:absolute before:-inset-1 active:scale-[0.96] motion-reduce:active:scale-100 sm:h-7 sm:w-7"
|
|
185
185
|
onClick={() => removeOption(i)}
|
|
186
186
|
aria-label={t("fieldProperties.removeOption", {
|
|
187
187
|
option: opt,
|
|
188
188
|
})}
|
|
189
189
|
>
|
|
190
|
-
<IconX className="h-3 w-3" />
|
|
190
|
+
<IconX className="h-3.5 w-3.5 translate-y-px" />
|
|
191
191
|
</Button>
|
|
192
192
|
</div>
|
|
193
193
|
))}
|
|
@@ -202,11 +202,11 @@ export function FieldPropertiesPanel({
|
|
|
202
202
|
<Button
|
|
203
203
|
variant="ghost"
|
|
204
204
|
size="sm"
|
|
205
|
-
className="h-
|
|
205
|
+
className="relative h-10 w-10 shrink-0 p-0 transition-transform duration-150 ease-out before:absolute before:-inset-1 active:scale-[0.96] motion-reduce:active:scale-100 sm:h-7 sm:w-7"
|
|
206
206
|
onClick={addOption}
|
|
207
207
|
aria-label={t("fieldProperties.addOption")}
|
|
208
208
|
>
|
|
209
|
-
<IconPlus className="h-3 w-3" />
|
|
209
|
+
<IconPlus className="h-3.5 w-3.5 translate-y-px" />
|
|
210
210
|
</Button>
|
|
211
211
|
</div>
|
|
212
212
|
</div>
|