@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
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_REPLAY_IFRAME_ATTRIBUTE,
|
|
3
|
+
SESSION_REPLAY_IFRAME_PROBE,
|
|
4
|
+
SESSION_REPLAY_IFRAME_START,
|
|
5
|
+
SESSION_REPLAY_IFRAME_STOP,
|
|
6
|
+
type SessionReplayIframePrivacyOptions,
|
|
7
|
+
type SessionReplayIframeStartMessage,
|
|
8
|
+
type SessionReplayIframeStopMessage,
|
|
9
|
+
} from "../session-replay-iframe-protocol.js";
|
|
1
10
|
import {
|
|
2
11
|
getOrCreateAnalyticsAnonymousId,
|
|
3
12
|
getOrCreateAnalyticsSessionId,
|
|
@@ -7,6 +16,7 @@ import { scrubUrl } from "./url-scrub.js";
|
|
|
7
16
|
type ReplayEvent = Record<string, unknown>;
|
|
8
17
|
type QueuedReplayEvent = {
|
|
9
18
|
json: string;
|
|
19
|
+
byteLength: number;
|
|
10
20
|
timestampMs: number;
|
|
11
21
|
type: number | null;
|
|
12
22
|
};
|
|
@@ -65,9 +75,17 @@ interface SessionReplayState {
|
|
|
65
75
|
flushTimer: number | null;
|
|
66
76
|
maxDurationTimer: number | null;
|
|
67
77
|
flushing: boolean;
|
|
78
|
+
/** Highest-priority flush requested while an upload was already in flight. */
|
|
79
|
+
pendingFlushReason: string | null;
|
|
80
|
+
/** Callers awaiting the coalesced flush tail. */
|
|
81
|
+
pendingFlushWaiters: Array<() => void>;
|
|
82
|
+
/** Drop incremental events until a new FullSnapshot can re-anchor the DOM. */
|
|
83
|
+
awaitingFullSnapshot: boolean;
|
|
68
84
|
stopRecorder: ReplayStopFn | null;
|
|
69
85
|
restoreUrlMonitor: (() => void) | null;
|
|
70
86
|
removeLifecycleListeners: (() => void) | null;
|
|
87
|
+
/** Stops the cooperative child-iframe bridge and notifies active children. */
|
|
88
|
+
restoreIframeBridge: (() => void) | null;
|
|
71
89
|
/** rrweb's `record.addCustomEvent`, captured at start (null when absent). */
|
|
72
90
|
addCustomEvent: ((tag: string, payload: unknown) => void) | null;
|
|
73
91
|
/** Uninstalls console/network interceptors and flushes pending duplicates. */
|
|
@@ -291,7 +309,9 @@ const SESSION_REPLAY_STATE_KEY = Symbol.for(
|
|
|
291
309
|
"agent-native.client.sessionReplay",
|
|
292
310
|
);
|
|
293
311
|
const SESSION_REPLAY_ID_STORAGE_KEY = "agent-native.session_replay_id";
|
|
312
|
+
const SESSION_REPLAY_IFRAME_BLOCK_SELECTOR = `iframe[${SESSION_REPLAY_IFRAME_ATTRIBUTE}]`;
|
|
294
313
|
const DEFAULT_BLOCK_SELECTOR = [
|
|
314
|
+
SESSION_REPLAY_IFRAME_BLOCK_SELECTOR,
|
|
295
315
|
"[data-sensitive]",
|
|
296
316
|
"[data-an-block]",
|
|
297
317
|
"[data-an-private]",
|
|
@@ -311,11 +331,29 @@ const DEFAULT_BLOCK_SELECTOR = [
|
|
|
311
331
|
const DEFAULT_IGNORE_SELECTOR = ".an-ignore, [data-an-ignore]";
|
|
312
332
|
const DEFAULT_MASK_TEXT_CLASS = "an-mask";
|
|
313
333
|
const DEFAULT_MASK_TEXT_SELECTOR = "[data-an-mask]";
|
|
334
|
+
const DEFAULT_MASK_INPUT_OPTIONS: Record<string, boolean> = {
|
|
335
|
+
color: true,
|
|
336
|
+
date: true,
|
|
337
|
+
"datetime-local": true,
|
|
338
|
+
email: true,
|
|
339
|
+
month: true,
|
|
340
|
+
number: true,
|
|
341
|
+
password: true,
|
|
342
|
+
range: true,
|
|
343
|
+
search: true,
|
|
344
|
+
tel: true,
|
|
345
|
+
text: true,
|
|
346
|
+
time: true,
|
|
347
|
+
url: true,
|
|
348
|
+
week: true,
|
|
349
|
+
};
|
|
314
350
|
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
315
351
|
const DEFAULT_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
316
352
|
const DEFAULT_MAX_EVENTS_PER_BATCH = 50;
|
|
317
353
|
const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
|
|
318
354
|
const MAX_KEEPALIVE_REPLAY_UPLOAD_BYTES = 60 * 1024;
|
|
355
|
+
const REPLAY_TEXT_ENCODER =
|
|
356
|
+
typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
319
357
|
const RRWEB_FULL_SNAPSHOT_EVENT_TYPE = 2;
|
|
320
358
|
/** Cross-tab channel name used by the duplicated-tab claim guard. */
|
|
321
359
|
const SESSION_REPLAY_BROADCAST_CHANNEL_NAME = "agent-native-session-replay";
|
|
@@ -403,9 +441,13 @@ function getState(): SessionReplayState {
|
|
|
403
441
|
flushTimer: null,
|
|
404
442
|
maxDurationTimer: null,
|
|
405
443
|
flushing: false,
|
|
444
|
+
pendingFlushReason: null,
|
|
445
|
+
pendingFlushWaiters: [],
|
|
446
|
+
awaitingFullSnapshot: false,
|
|
406
447
|
stopRecorder: null,
|
|
407
448
|
restoreUrlMonitor: null,
|
|
408
449
|
removeLifecycleListeners: null,
|
|
450
|
+
restoreIframeBridge: null,
|
|
409
451
|
addCustomEvent: null,
|
|
410
452
|
restoreCaptures: null,
|
|
411
453
|
options: null,
|
|
@@ -418,6 +460,10 @@ function getState(): SessionReplayState {
|
|
|
418
460
|
const state = g[SESSION_REPLAY_STATE_KEY]!;
|
|
419
461
|
// Keep Vite HMR safe when an older recorder state survives a module reload.
|
|
420
462
|
state.resourceNodes ??= new Map();
|
|
463
|
+
state.restoreIframeBridge ??= null;
|
|
464
|
+
state.pendingFlushReason ??= null;
|
|
465
|
+
state.pendingFlushWaiters ??= [];
|
|
466
|
+
state.awaitingFullSnapshot ??= false;
|
|
421
467
|
return state;
|
|
422
468
|
}
|
|
423
469
|
|
|
@@ -881,13 +927,20 @@ function normalizeOptions(
|
|
|
881
927
|
checkoutEveryNth: options.checkoutEveryNth,
|
|
882
928
|
checkoutEveryNms: options.checkoutEveryNms,
|
|
883
929
|
inlineStylesheet: options.inlineStylesheet ?? true,
|
|
884
|
-
blockSelector:
|
|
930
|
+
blockSelector: mergeReplayBlockSelector(
|
|
931
|
+
options.blockSelector || DEFAULT_BLOCK_SELECTOR,
|
|
932
|
+
),
|
|
885
933
|
ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
|
|
886
934
|
maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
|
|
887
935
|
maskTextSelector: options.maskTextSelector || DEFAULT_MASK_TEXT_SELECTOR,
|
|
888
936
|
maskAllInputs: options.maskAllInputs ?? true,
|
|
889
937
|
recordCanvas: options.recordCanvas ?? false,
|
|
890
|
-
|
|
938
|
+
// A top-level app can safely aggregate cooperative child recorders. An
|
|
939
|
+
// app embedded by an unrelated cross-origin host must continue emitting
|
|
940
|
+
// and uploading its own events, so it only forwards to a parent when the
|
|
941
|
+
// caller explicitly opts in.
|
|
942
|
+
recordCrossOriginIframes:
|
|
943
|
+
options.recordCrossOriginIframes ?? window.parent === window,
|
|
891
944
|
collectFonts: options.collectFonts ?? false,
|
|
892
945
|
inlineImages: options.inlineImages ?? false,
|
|
893
946
|
eventSampling: options.eventSampling ?? DEFAULT_EVENT_SAMPLING,
|
|
@@ -905,6 +958,12 @@ function normalizeOptions(
|
|
|
905
958
|
};
|
|
906
959
|
}
|
|
907
960
|
|
|
961
|
+
function mergeReplayBlockSelector(blockSelector: string): string {
|
|
962
|
+
return blockSelector.includes(SESSION_REPLAY_IFRAME_BLOCK_SELECTOR)
|
|
963
|
+
? blockSelector
|
|
964
|
+
: `${blockSelector}, ${SESSION_REPLAY_IFRAME_BLOCK_SELECTOR}`;
|
|
965
|
+
}
|
|
966
|
+
|
|
908
967
|
/**
|
|
909
968
|
* Console/network capture default to ON whenever session replay records;
|
|
910
969
|
* `false` disables a category, an object form overrides its caps. Network
|
|
@@ -1163,9 +1222,14 @@ function enqueueReplayEvent(
|
|
|
1163
1222
|
event: ReplayEvent,
|
|
1164
1223
|
): void {
|
|
1165
1224
|
if (!state.options) return;
|
|
1225
|
+
const eventType = typeof event.type === "number" ? event.type : null;
|
|
1226
|
+
if (state.awaitingFullSnapshot) {
|
|
1227
|
+
if (eventType !== RRWEB_FULL_SNAPSHOT_EVENT_TYPE) return;
|
|
1228
|
+
state.awaitingFullSnapshot = false;
|
|
1229
|
+
}
|
|
1166
1230
|
const serialized = serializeReplayEvent(event, state.resourceNodes);
|
|
1167
1231
|
if (!serialized) return;
|
|
1168
|
-
const estimatedBytes = serialized
|
|
1232
|
+
const estimatedBytes = replaySerializedBytes(serialized);
|
|
1169
1233
|
if (
|
|
1170
1234
|
state.queue.length > 0 &&
|
|
1171
1235
|
state.queuedBytes + estimatedBytes > state.options.maxBatchBytes
|
|
@@ -1174,8 +1238,9 @@ function enqueueReplayEvent(
|
|
|
1174
1238
|
}
|
|
1175
1239
|
state.queue.push({
|
|
1176
1240
|
json: serialized,
|
|
1241
|
+
byteLength: estimatedBytes,
|
|
1177
1242
|
timestampMs: replayEventTimestampMs(event),
|
|
1178
|
-
type:
|
|
1243
|
+
type: eventType,
|
|
1179
1244
|
});
|
|
1180
1245
|
state.queuedBytes += estimatedBytes;
|
|
1181
1246
|
flushQueuedReplayIfNeeded(state);
|
|
@@ -1443,6 +1508,25 @@ function isFinalFlushReason(reason: string): boolean {
|
|
|
1443
1508
|
].includes(reason);
|
|
1444
1509
|
}
|
|
1445
1510
|
|
|
1511
|
+
function flushReasonPriority(reason: string): number {
|
|
1512
|
+
// Unload reasons must retain their keepalive sequence reservation even when
|
|
1513
|
+
// another final request (for example, an explicit manual stop) is coalesced.
|
|
1514
|
+
if (reason === "pagehide" || reason === "beforeunload") return 3;
|
|
1515
|
+
if (isFinalFlushReason(reason)) return 2;
|
|
1516
|
+
if (reason === "visibility-hidden") return 1;
|
|
1517
|
+
return 0;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
function mergePendingFlushReason(
|
|
1521
|
+
current: string | null,
|
|
1522
|
+
requested: string,
|
|
1523
|
+
): string {
|
|
1524
|
+
if (!current) return requested;
|
|
1525
|
+
return flushReasonPriority(requested) > flushReasonPriority(current)
|
|
1526
|
+
? requested
|
|
1527
|
+
: current;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1446
1530
|
function shouldReserveSequenceBeforeKeepalive(reason: string): boolean {
|
|
1447
1531
|
return (
|
|
1448
1532
|
reason === "pagehide" ||
|
|
@@ -1482,7 +1566,99 @@ function flushQueuedReplayIfNeeded(state: SessionReplayState): void {
|
|
|
1482
1566
|
}
|
|
1483
1567
|
|
|
1484
1568
|
function queuedReplayBytes(events: QueuedReplayEvent[]): number {
|
|
1485
|
-
return events.reduce(
|
|
1569
|
+
return events.reduce(
|
|
1570
|
+
(total, event) => total + queuedReplayEventBytes(event),
|
|
1571
|
+
0,
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function replaySerializedBytes(value: string): number {
|
|
1576
|
+
if (REPLAY_TEXT_ENCODER) return REPLAY_TEXT_ENCODER.encode(value).byteLength;
|
|
1577
|
+
if (typeof Blob !== "undefined") return new Blob([value]).size;
|
|
1578
|
+
return value.length;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function queuedReplayEventBytes(event: QueuedReplayEvent): number {
|
|
1582
|
+
return Number.isFinite(event.byteLength)
|
|
1583
|
+
? event.byteLength
|
|
1584
|
+
: replaySerializedBytes(event.json);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Remove one bounded FIFO prefix from the live queue.
|
|
1589
|
+
*
|
|
1590
|
+
* Threshold-triggered flushes may arrive while another upload is active. The
|
|
1591
|
+
* old `queue.splice(0)` drained that entire accumulated backlog on the next
|
|
1592
|
+
* flush, bypassing both byte and event caps. Keep FullSnapshots isolated and
|
|
1593
|
+
* always take at least one event so an individually large snapshot can still
|
|
1594
|
+
* make progress.
|
|
1595
|
+
*/
|
|
1596
|
+
function takeQueuedReplayBatch(state: SessionReplayState): QueuedReplayEvent[] {
|
|
1597
|
+
const options = state.options;
|
|
1598
|
+
if (!options || state.queue.length === 0) return [];
|
|
1599
|
+
|
|
1600
|
+
let count = 0;
|
|
1601
|
+
let bytes = 0;
|
|
1602
|
+
for (const event of state.queue) {
|
|
1603
|
+
if (count > 0 && event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE) break;
|
|
1604
|
+
if (
|
|
1605
|
+
count > 0 &&
|
|
1606
|
+
(count >= options.maxEventsPerBatch ||
|
|
1607
|
+
bytes + queuedReplayEventBytes(event) > options.maxBatchBytes)
|
|
1608
|
+
) {
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
count += 1;
|
|
1612
|
+
bytes += queuedReplayEventBytes(event);
|
|
1613
|
+
if (event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE) break;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const events = state.queue.splice(0, Math.max(1, count));
|
|
1617
|
+
state.queuedBytes = queuedReplayBytes(state.queue);
|
|
1618
|
+
return events;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function splitReplayBatch(
|
|
1622
|
+
events: QueuedReplayEvent[],
|
|
1623
|
+
): [QueuedReplayEvent[], QueuedReplayEvent[]] | null {
|
|
1624
|
+
if (events.length < 2) return null;
|
|
1625
|
+
const targetBytes = queuedReplayBytes(events) / 2;
|
|
1626
|
+
let splitAt = 1;
|
|
1627
|
+
let bytes = events[0] ? queuedReplayEventBytes(events[0]) : 0;
|
|
1628
|
+
while (splitAt < events.length - 1 && bytes < targetBytes) {
|
|
1629
|
+
const event = events[splitAt];
|
|
1630
|
+
if (event) bytes += queuedReplayEventBytes(event);
|
|
1631
|
+
splitAt += 1;
|
|
1632
|
+
}
|
|
1633
|
+
return [events.slice(0, splitAt), events.slice(splitAt)];
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function replayBatchNeedsDomReset(events: QueuedReplayEvent[]): boolean {
|
|
1637
|
+
return events.some((event) => {
|
|
1638
|
+
if (event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE) return true;
|
|
1639
|
+
if (event.type !== 3) return false;
|
|
1640
|
+
try {
|
|
1641
|
+
const parsed = JSON.parse(event.json) as { data?: { source?: unknown } };
|
|
1642
|
+
// rrweb IncrementalSource.Mutation is zero. Dropping one may leave later
|
|
1643
|
+
// node references dangling until another FullSnapshot resets the mirror.
|
|
1644
|
+
return parsed.data?.source === 0;
|
|
1645
|
+
} catch {
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function quarantinePendingReplayUntilFullSnapshot(
|
|
1652
|
+
state: SessionReplayState,
|
|
1653
|
+
): void {
|
|
1654
|
+
const pending = [...state.retryBatches.flat(), ...state.queue];
|
|
1655
|
+
const resetAt = pending.findIndex(
|
|
1656
|
+
(event) => event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE,
|
|
1657
|
+
);
|
|
1658
|
+
state.retryBatches = [];
|
|
1659
|
+
state.queue = resetAt >= 0 ? pending.slice(resetAt) : [];
|
|
1660
|
+
state.queuedBytes = queuedReplayBytes(state.queue);
|
|
1661
|
+
state.awaitingFullSnapshot = resetAt < 0;
|
|
1486
1662
|
}
|
|
1487
1663
|
|
|
1488
1664
|
function restoreReplayEvents(
|
|
@@ -1523,9 +1699,18 @@ function rollbackReplaySequenceReservation(
|
|
|
1523
1699
|
|
|
1524
1700
|
export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
1525
1701
|
const state = getState();
|
|
1526
|
-
if (!state.options
|
|
1527
|
-
|
|
1528
|
-
|
|
1702
|
+
if (!state.options) return;
|
|
1703
|
+
if (state.flushing) {
|
|
1704
|
+
state.pendingFlushReason = mergePendingFlushReason(
|
|
1705
|
+
state.pendingFlushReason,
|
|
1706
|
+
reason,
|
|
1707
|
+
);
|
|
1708
|
+
return new Promise<void>((resolve) => {
|
|
1709
|
+
state.pendingFlushWaiters.push(resolve);
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
if (!hasPendingReplayBatch(state)) return;
|
|
1713
|
+
const events = state.retryBatches.shift() ?? takeQueuedReplayBatch(state);
|
|
1529
1714
|
const payload = buildReplayBody(state, reason, events);
|
|
1530
1715
|
if (!payload || !state.options) {
|
|
1531
1716
|
restoreReplayEvents(state, events);
|
|
@@ -1534,6 +1719,8 @@ export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
|
1534
1719
|
state.flushing = true;
|
|
1535
1720
|
let uploaded = false;
|
|
1536
1721
|
let reservedSequence = false;
|
|
1722
|
+
let splitRejectedBatch = false;
|
|
1723
|
+
let droppedOversizedBatch = false;
|
|
1537
1724
|
let definitiveClientErrorStatus: number | null = null;
|
|
1538
1725
|
try {
|
|
1539
1726
|
await sendReplayUpload(state.options, payload.body, {
|
|
@@ -1553,10 +1740,33 @@ export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
|
1553
1740
|
// never succeed by retrying the exact same batch -- requeuing it would
|
|
1554
1741
|
// just spin forever, blocking every later batch behind it (flushes are
|
|
1555
1742
|
// FIFO via `retryBatches`). Drop it and move on instead.
|
|
1743
|
+
const rejectedStatus =
|
|
1744
|
+
error instanceof ReplayUploadHttpError ? error.status : null;
|
|
1745
|
+
const splitBatch = rejectedStatus === 413 ? splitReplayBatch(events) : null;
|
|
1746
|
+
const isUnsplittableOversizedBatch =
|
|
1747
|
+
rejectedStatus === 413 && splitBatch === null;
|
|
1556
1748
|
const isDefinitiveClientError =
|
|
1557
1749
|
error instanceof ReplayUploadHttpError &&
|
|
1558
|
-
isDefinitiveReplayUploadClientError(error.status)
|
|
1559
|
-
|
|
1750
|
+
isDefinitiveReplayUploadClientError(error.status) &&
|
|
1751
|
+
!splitBatch &&
|
|
1752
|
+
!isUnsplittableOversizedBatch;
|
|
1753
|
+
if (splitBatch) {
|
|
1754
|
+
// A server or platform can enforce a stricter decompressed-body limit
|
|
1755
|
+
// than the recorder's configured queue cap. Bisect in FIFO order and
|
|
1756
|
+
// retry both halves at the same sequence; only successful halves advance
|
|
1757
|
+
// it, so no event is duplicated or skipped.
|
|
1758
|
+
state.retryBatches.unshift(...splitBatch);
|
|
1759
|
+
splitRejectedBatch = true;
|
|
1760
|
+
} else if (isUnsplittableOversizedBatch) {
|
|
1761
|
+
// This one event cannot be made any smaller. Drop only the rejected
|
|
1762
|
+
// singleton and keep later retry halves/live batches in FIFO order. If
|
|
1763
|
+
// it carried DOM structure, quarantine dependent mutations until a new
|
|
1764
|
+
// FullSnapshot can safely re-anchor rrweb's node mirror.
|
|
1765
|
+
droppedOversizedBatch = true;
|
|
1766
|
+
if (replayBatchNeedsDomReset(events)) {
|
|
1767
|
+
quarantinePendingReplayUntilFullSnapshot(state);
|
|
1768
|
+
}
|
|
1769
|
+
} else if (isDefinitiveClientError) {
|
|
1560
1770
|
// Continuing after a checksum/sequence conflict would reuse the same
|
|
1561
1771
|
// rejected sequence forever. More importantly, advancing past it would
|
|
1562
1772
|
// append mutations to a replay whose DOM stream may belong to another
|
|
@@ -1575,7 +1785,17 @@ export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
|
1575
1785
|
const previousInternal = replayCaptureInternal;
|
|
1576
1786
|
replayCaptureInternal = true;
|
|
1577
1787
|
try {
|
|
1578
|
-
if (
|
|
1788
|
+
if (splitRejectedBatch) {
|
|
1789
|
+
console.warn(
|
|
1790
|
+
"[session-replay] splitting oversized upload (HTTP 413)",
|
|
1791
|
+
error,
|
|
1792
|
+
);
|
|
1793
|
+
} else if (droppedOversizedBatch) {
|
|
1794
|
+
console.warn(
|
|
1795
|
+
"[session-replay] dropping oversized replay event (HTTP 413)",
|
|
1796
|
+
error,
|
|
1797
|
+
);
|
|
1798
|
+
} else if (isDefinitiveClientError) {
|
|
1579
1799
|
console.warn(
|
|
1580
1800
|
`[session-replay] dropping upload (HTTP ${(error as ReplayUploadHttpError).status})`,
|
|
1581
1801
|
error,
|
|
@@ -1589,8 +1809,33 @@ export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
|
1589
1809
|
} finally {
|
|
1590
1810
|
state.flushing = false;
|
|
1591
1811
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1812
|
+
const coalescedReason = state.pendingFlushReason;
|
|
1813
|
+
const coalescedWaiters = coalescedReason
|
|
1814
|
+
? state.pendingFlushWaiters.splice(0)
|
|
1815
|
+
: [];
|
|
1816
|
+
if (coalescedReason) state.pendingFlushReason = null;
|
|
1817
|
+
|
|
1818
|
+
if (coalescedReason) {
|
|
1819
|
+
// A stop/unload request that arrived during this upload owns the tail.
|
|
1820
|
+
// Its higher-priority reason must replace threshold/internal reasons so a
|
|
1821
|
+
// small final batch is not stranded or mislabeled as active.
|
|
1822
|
+
await flushSessionReplay(coalescedReason);
|
|
1823
|
+
} else if (splitRejectedBatch || droppedOversizedBatch) {
|
|
1824
|
+
// Preserve final-status and unload semantics through every half. In
|
|
1825
|
+
// particular, pagehide/beforeunload retries must still reserve their
|
|
1826
|
+
// sequence before a keepalive fetch, and completed flushes must not be
|
|
1827
|
+
// rewritten to an internal recovery reason/status.
|
|
1828
|
+
await flushSessionReplay(reason);
|
|
1829
|
+
} else if (uploaded && hasPendingReplayBatch(state)) {
|
|
1830
|
+
const mustContinue =
|
|
1831
|
+
state.retryBatches.length > 0 ||
|
|
1832
|
+
isFinalFlushReason(reason) ||
|
|
1833
|
+
shouldFlushQueuedReplay(state);
|
|
1834
|
+
if (mustContinue) {
|
|
1835
|
+
if (isFinalFlushReason(reason)) {
|
|
1836
|
+
await flushSessionReplay(reason);
|
|
1837
|
+
} else void flushSessionReplay(reason);
|
|
1838
|
+
}
|
|
1594
1839
|
}
|
|
1595
1840
|
if (
|
|
1596
1841
|
definitiveClientErrorStatus !== null &&
|
|
@@ -1639,6 +1884,7 @@ export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
|
1639
1884
|
// best-effort telemetry must never interfere with recording recovery
|
|
1640
1885
|
}
|
|
1641
1886
|
}
|
|
1887
|
+
for (const resolve of coalescedWaiters) resolve();
|
|
1642
1888
|
}
|
|
1643
1889
|
|
|
1644
1890
|
/**
|
|
@@ -1737,6 +1983,101 @@ function installLifecycleListeners(state: SessionReplayState): void {
|
|
|
1737
1983
|
};
|
|
1738
1984
|
}
|
|
1739
1985
|
|
|
1986
|
+
function markedSessionReplayIframes(): HTMLIFrameElement[] {
|
|
1987
|
+
if (typeof document.querySelectorAll !== "function") return [];
|
|
1988
|
+
return Array.from(
|
|
1989
|
+
document.querySelectorAll<HTMLIFrameElement>(
|
|
1990
|
+
`iframe[${SESSION_REPLAY_IFRAME_ATTRIBUTE}]`,
|
|
1991
|
+
),
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
function markedSessionReplayIframeForSource(
|
|
1996
|
+
source: MessageEventSource | null,
|
|
1997
|
+
): HTMLIFrameElement | null {
|
|
1998
|
+
if (!source) return null;
|
|
1999
|
+
return (
|
|
2000
|
+
markedSessionReplayIframes().find(
|
|
2001
|
+
(iframe) => iframe.contentWindow === source,
|
|
2002
|
+
) ?? null
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
function sessionReplayIframePrivacyOptions(
|
|
2007
|
+
options: NormalizedSessionReplayOptions,
|
|
2008
|
+
): SessionReplayIframePrivacyOptions {
|
|
2009
|
+
return {
|
|
2010
|
+
blockSelector: options.blockSelector,
|
|
2011
|
+
ignoreSelector: options.ignoreSelector,
|
|
2012
|
+
maskTextClass: options.maskTextClass,
|
|
2013
|
+
maskTextSelector: options.maskTextSelector,
|
|
2014
|
+
maskAllInputs: options.maskAllInputs,
|
|
2015
|
+
maskInputOptions: DEFAULT_MASK_INPUT_OPTIONS,
|
|
2016
|
+
recordCanvas: options.recordCanvas,
|
|
2017
|
+
collectFonts: options.collectFonts,
|
|
2018
|
+
inlineImages: options.inlineImages,
|
|
2019
|
+
sampling: options.eventSampling,
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
function postSessionReplayIframeMessage(
|
|
2024
|
+
iframe: HTMLIFrameElement,
|
|
2025
|
+
message: SessionReplayIframeStartMessage | SessionReplayIframeStopMessage,
|
|
2026
|
+
): void {
|
|
2027
|
+
try {
|
|
2028
|
+
iframe.contentWindow?.postMessage(message, "*");
|
|
2029
|
+
} catch {
|
|
2030
|
+
// The frame may have navigated or detached between discovery and send.
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
/**
|
|
2035
|
+
* Cooperative opaque/cross-origin iframe recording.
|
|
2036
|
+
*
|
|
2037
|
+
* rrweb cannot inspect these documents from the host. Framework-owned child
|
|
2038
|
+
* frames carry a marker and inject a tiny recorder that probes its direct
|
|
2039
|
+
* parent. Only an active host recorder responds, and only when the probe's
|
|
2040
|
+
* source is the contentWindow of a currently marked direct iframe.
|
|
2041
|
+
*/
|
|
2042
|
+
function installSessionReplayIframeBridge(
|
|
2043
|
+
state: SessionReplayState,
|
|
2044
|
+
options: NormalizedSessionReplayOptions,
|
|
2045
|
+
): void {
|
|
2046
|
+
if (!options.recordCrossOriginIframes || state.restoreIframeBridge) return;
|
|
2047
|
+
|
|
2048
|
+
const startMessage: SessionReplayIframeStartMessage = {
|
|
2049
|
+
type: SESSION_REPLAY_IFRAME_START,
|
|
2050
|
+
options: sessionReplayIframePrivacyOptions(options),
|
|
2051
|
+
};
|
|
2052
|
+
const stopMessage: SessionReplayIframeStopMessage = {
|
|
2053
|
+
type: SESSION_REPLAY_IFRAME_STOP,
|
|
2054
|
+
};
|
|
2055
|
+
const sendStart = (iframe: HTMLIFrameElement) =>
|
|
2056
|
+
postSessionReplayIframeMessage(iframe, startMessage);
|
|
2057
|
+
const onMessage = (event: MessageEvent) => {
|
|
2058
|
+
if (!state.active) return;
|
|
2059
|
+
if (
|
|
2060
|
+
!event.data ||
|
|
2061
|
+
typeof event.data !== "object" ||
|
|
2062
|
+
event.data.type !== SESSION_REPLAY_IFRAME_PROBE
|
|
2063
|
+
) {
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
const iframe = markedSessionReplayIframeForSource(event.source);
|
|
2067
|
+
if (iframe) sendStart(iframe);
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
window.addEventListener("message", onMessage);
|
|
2071
|
+
for (const iframe of markedSessionReplayIframes()) sendStart(iframe);
|
|
2072
|
+
state.restoreIframeBridge = () => {
|
|
2073
|
+
window.removeEventListener("message", onMessage);
|
|
2074
|
+
for (const iframe of markedSessionReplayIframes()) {
|
|
2075
|
+
postSessionReplayIframeMessage(iframe, stopMessage);
|
|
2076
|
+
}
|
|
2077
|
+
state.restoreIframeBridge = null;
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
|
|
1740
2081
|
function truncateCaptureText(value: string, maxLength: number): string {
|
|
1741
2082
|
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
1742
2083
|
}
|
|
@@ -2616,6 +2957,9 @@ async function startSessionReplayRecorder(
|
|
|
2616
2957
|
state.queue = [];
|
|
2617
2958
|
state.queuedBytes = 0;
|
|
2618
2959
|
state.retryBatches = [];
|
|
2960
|
+
state.pendingFlushReason = null;
|
|
2961
|
+
for (const resolve of state.pendingFlushWaiters.splice(0)) resolve();
|
|
2962
|
+
state.awaitingFullSnapshot = false;
|
|
2619
2963
|
state.resourceNodes.clear();
|
|
2620
2964
|
state.stopRecorder = null;
|
|
2621
2965
|
state.broadcastChannel = replayChannel;
|
|
@@ -2640,22 +2984,7 @@ async function startSessionReplayRecorder(
|
|
|
2640
2984
|
recordCrossOriginIframes: normalized.recordCrossOriginIframes,
|
|
2641
2985
|
collectFonts: normalized.collectFonts,
|
|
2642
2986
|
inlineImages: normalized.inlineImages,
|
|
2643
|
-
maskInputOptions:
|
|
2644
|
-
color: true,
|
|
2645
|
-
date: true,
|
|
2646
|
-
"datetime-local": true,
|
|
2647
|
-
email: true,
|
|
2648
|
-
month: true,
|
|
2649
|
-
number: true,
|
|
2650
|
-
password: true,
|
|
2651
|
-
range: true,
|
|
2652
|
-
search: true,
|
|
2653
|
-
tel: true,
|
|
2654
|
-
text: true,
|
|
2655
|
-
time: true,
|
|
2656
|
-
url: true,
|
|
2657
|
-
week: true,
|
|
2658
|
-
},
|
|
2987
|
+
maskInputOptions: DEFAULT_MASK_INPUT_OPTIONS,
|
|
2659
2988
|
});
|
|
2660
2989
|
if (typeof stopRecorder !== "function") {
|
|
2661
2990
|
state.active = false;
|
|
@@ -2682,6 +3011,7 @@ async function startSessionReplayRecorder(
|
|
|
2682
3011
|
);
|
|
2683
3012
|
installUrlMonitor(state);
|
|
2684
3013
|
installLifecycleListeners(state);
|
|
3014
|
+
installSessionReplayIframeBridge(state, normalized);
|
|
2685
3015
|
state.addCustomEvent =
|
|
2686
3016
|
typeof rrweb.record.addCustomEvent === "function"
|
|
2687
3017
|
? rrweb.record.addCustomEvent
|
|
@@ -2700,6 +3030,7 @@ async function startSessionReplayRecorder(
|
|
|
2700
3030
|
// best-effort interceptor teardown
|
|
2701
3031
|
}
|
|
2702
3032
|
state.restoreCaptures = null;
|
|
3033
|
+
state.restoreIframeBridge?.();
|
|
2703
3034
|
state.addCustomEvent = null;
|
|
2704
3035
|
state.active = false;
|
|
2705
3036
|
state.options = null;
|
|
@@ -2728,6 +3059,7 @@ export async function stopSessionReplay(reason = "manual"): Promise<void> {
|
|
|
2728
3059
|
// best-effort interceptor teardown
|
|
2729
3060
|
}
|
|
2730
3061
|
state.restoreCaptures = null;
|
|
3062
|
+
state.restoreIframeBridge?.();
|
|
2731
3063
|
state.active = false;
|
|
2732
3064
|
state.addCustomEvent = null;
|
|
2733
3065
|
try {
|
|
@@ -707,6 +707,22 @@ function dispatchActivityClear(tabId: string | undefined) {
|
|
|
707
707
|
);
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
+
// Fires once per chunk (see ProcessEventState.streamProgressDispatched) the
|
|
711
|
+
// moment a chunk produces real model output (visible text or a reasoning
|
|
712
|
+
// delta). Unlike `agent-chat:activity-clear` — which also fires for
|
|
713
|
+
// old-chunk `tool_done` replays and server-retry `clear` events, and so
|
|
714
|
+
// cannot be used to detect genuine forward progress — this event exists
|
|
715
|
+
// solely so listeners can distinguish "the run is actually producing new
|
|
716
|
+
// output" from those replay/retry cases.
|
|
717
|
+
function dispatchStreamProgress(tabId: string | undefined) {
|
|
718
|
+
if (typeof window === "undefined") return;
|
|
719
|
+
window.dispatchEvent(
|
|
720
|
+
new CustomEvent("agent-chat:stream-progress", {
|
|
721
|
+
detail: { tabId },
|
|
722
|
+
}),
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
710
726
|
function dispatchMissingApiKey(tabId: string | undefined) {
|
|
711
727
|
if (typeof window === "undefined") return;
|
|
712
728
|
window.dispatchEvent(
|
|
@@ -946,6 +962,11 @@ function completedToolOnlyMessage(toolNames: string[]): string | null {
|
|
|
946
962
|
|
|
947
963
|
interface ProcessEventState {
|
|
948
964
|
completedToolsAfterLastAssistantText: Set<string>;
|
|
965
|
+
/** Set once `agent-chat:stream-progress` has been dispatched for the
|
|
966
|
+
* current chunk, so a burst of per-token text/reasoning deltas only fires
|
|
967
|
+
* it once. Cleared by `resetProcessEventState` on a server `clear` retry
|
|
968
|
+
* so the next batch of real output re-arms it. */
|
|
969
|
+
streamProgressDispatched: boolean;
|
|
949
970
|
}
|
|
950
971
|
|
|
951
972
|
function markAssistantText(state: ProcessEventState | undefined) {
|
|
@@ -961,6 +982,18 @@ function markCompletedToolAfterAssistantText(
|
|
|
961
982
|
|
|
962
983
|
function resetProcessEventState(state: ProcessEventState | undefined) {
|
|
963
984
|
state?.completedToolsAfterLastAssistantText.clear();
|
|
985
|
+
if (state) state.streamProgressDispatched = false;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Returns true the first time it's called for a given state (or every time
|
|
989
|
+
// when state is unavailable, since there is nothing to dedupe against) and
|
|
990
|
+
// false on subsequent calls until `resetProcessEventState` re-arms it.
|
|
991
|
+
function shouldDispatchStreamProgress(
|
|
992
|
+
state: ProcessEventState | undefined,
|
|
993
|
+
): boolean {
|
|
994
|
+
if (state?.streamProgressDispatched) return false;
|
|
995
|
+
if (state) state.streamProgressDispatched = true;
|
|
996
|
+
return true;
|
|
964
997
|
}
|
|
965
998
|
|
|
966
999
|
/**
|
|
@@ -1005,7 +1038,13 @@ export function processEvent(
|
|
|
1005
1038
|
// activity label so a transient "Contacting model" / "Still generating
|
|
1006
1039
|
// image" doesn't linger beside streamed text. Idempotent (clears once, then
|
|
1007
1040
|
// no-ops) so per-token text deltas stay cheap.
|
|
1008
|
-
if (ev.text)
|
|
1041
|
+
if (ev.text) {
|
|
1042
|
+
dispatchActivityClear(tabId);
|
|
1043
|
+
// Real output resumed — let listeners (e.g. the auto-resume "Resuming…"
|
|
1044
|
+
// indicator) clear state that must NOT be cleared by activity-clear
|
|
1045
|
+
// alone, since activity-clear also fires for old-chunk/retry replays.
|
|
1046
|
+
if (shouldDispatchStreamProgress(state)) dispatchStreamProgress(tabId);
|
|
1047
|
+
}
|
|
1009
1048
|
if (ev.text?.trim()) markAssistantText(state);
|
|
1010
1049
|
const lastPart = content[content.length - 1];
|
|
1011
1050
|
if (lastPart && lastPart.type === "text") {
|
|
@@ -1024,6 +1063,7 @@ export function processEvent(
|
|
|
1024
1063
|
// part so the UI can render a single collapsible "Thinking" cell.
|
|
1025
1064
|
const delta = ev.text ?? "";
|
|
1026
1065
|
if (!delta) return { action: "continue" };
|
|
1066
|
+
if (shouldDispatchStreamProgress(state)) dispatchStreamProgress(tabId);
|
|
1027
1067
|
const lastPart = content[content.length - 1];
|
|
1028
1068
|
if (lastPart && lastPart.type === "reasoning") {
|
|
1029
1069
|
lastPart.text += delta;
|
|
@@ -1567,6 +1607,7 @@ export async function* readSSEStream(
|
|
|
1567
1607
|
options?.preparingActionState ?? {};
|
|
1568
1608
|
const processEventState: ProcessEventState = {
|
|
1569
1609
|
completedToolsAfterLastAssistantText: new Set(),
|
|
1610
|
+
streamProgressDispatched: false,
|
|
1570
1611
|
};
|
|
1571
1612
|
|
|
1572
1613
|
const withStreamMetadata = (r: ChatModelRunResult): ChatModelRunResult => {
|
|
@@ -1769,6 +1810,7 @@ export async function readSSEStreamRaw(
|
|
|
1769
1810
|
options?.preparingActionState ?? {};
|
|
1770
1811
|
const processEventState: ProcessEventState = {
|
|
1771
1812
|
completedToolsAfterLastAssistantText: new Set(),
|
|
1813
|
+
streamProgressDispatched: false,
|
|
1772
1814
|
};
|
|
1773
1815
|
// Tracks whether the most recent content state was already pushed via
|
|
1774
1816
|
// onUpdate inside the loop, so the post-loop flush below doesn't emit the
|
|
@@ -5,7 +5,7 @@ import { writeAppState } from "../../application-state/script-helpers.js";
|
|
|
5
5
|
|
|
6
6
|
export default defineAction({
|
|
7
7
|
description:
|
|
8
|
-
"Turn demo mode on or off. When demo mode is on, the UI replaces email
|
|
8
|
+
"Turn demo mode on or off. When demo mode is on, the UI replaces every email address with anonymous@builder.io and supported dashboards reshape chart values for presentations. Agent-visible results use the same anonymous email and anonymize numbers while keeping names, free text, labels, IDs, dates, and structure intact. Use when the user asks to 'hide my data', 'turn on demo mode', 'anonymize this for a screen share / recording', or similar. This is the same toggle as the Demo mode switch in settings.",
|
|
9
9
|
schema: z.object({
|
|
10
10
|
enabled: z
|
|
11
11
|
.boolean()
|