@agent-native/core 0.98.4 → 0.98.5

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.
Files changed (217) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +37 -0
  4. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +2 -0
  5. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +2 -0
  6. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +2 -0
  7. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +4 -2
  8. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +2 -0
  9. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +2 -0
  10. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +2 -0
  11. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +2 -0
  12. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +2 -0
  13. package/corpus/core/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
  14. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +2 -0
  15. package/corpus/core/docs/content/tracking.mdx +2 -0
  16. package/corpus/core/package.json +1 -1
  17. package/corpus/core/src/agent/durable-background.ts +16 -16
  18. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +25 -8
  19. package/corpus/core/src/agent/engine/anthropic-engine.ts +17 -3
  20. package/corpus/core/src/agent/engine/output-tokens.ts +6 -6
  21. package/corpus/core/src/agent/production-agent.ts +200 -27
  22. package/corpus/core/src/agent/run-manager.ts +81 -1
  23. package/corpus/core/src/agent/run-store.ts +301 -41
  24. package/corpus/core/src/client/AgentPanel.tsx +6 -2
  25. package/corpus/core/src/client/AssistantChat.tsx +364 -69
  26. package/corpus/core/src/client/agent-chat-adapter.ts +169 -1
  27. package/corpus/core/src/client/chat/message-components.tsx +3 -0
  28. package/corpus/core/src/client/chat/tool-call-display.tsx +20 -36
  29. package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +7 -0
  30. package/corpus/core/src/client/extensions/AgentNativeExtensionFrame.tsx +2 -0
  31. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +2 -0
  32. package/corpus/core/src/client/extensions/ExtensionEditor.tsx +9 -2
  33. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +2 -0
  34. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +2 -0
  35. package/corpus/core/src/client/extensions/portable-extension.ts +2 -0
  36. package/corpus/core/src/client/session-replay.ts +361 -29
  37. package/corpus/core/src/client/sse-event-processor.ts +43 -1
  38. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +1 -1
  39. package/corpus/core/src/demo/config.ts +6 -6
  40. package/corpus/core/src/demo/fetch-interceptor.ts +13 -4
  41. package/corpus/core/src/demo/redact.ts +13 -149
  42. package/corpus/core/src/extensions/html-shell.ts +3 -0
  43. package/corpus/core/src/extensions/session-replay-iframe.ts +131 -0
  44. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -20
  45. package/corpus/core/src/session-replay-iframe-protocol.ts +58 -0
  46. package/corpus/core/src/shared/reasoning-effort.ts +35 -0
  47. package/corpus/core/src/styles/agent-conversation.css +26 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  49. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +30 -21
  50. package/corpus/templates/analytics/AGENTS.md +12 -0
  51. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
  52. package/corpus/templates/analytics/actions/get-session-replay-events.ts +1 -0
  53. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
  54. package/corpus/templates/analytics/actions/list-error-issues.ts +1 -0
  55. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
  56. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -1
  57. package/corpus/templates/analytics/app/global.css +2 -2
  58. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
  59. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +52 -203
  60. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +34 -12
  61. package/corpus/templates/analytics/changelog/2026-07-12-analytics-chat-retries-before-showing-a-generic-no-data-mess.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-07-12-analytics-sessions-search-no-longer-loses-fast-keystrokes-an.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-07-12-chart-selection-respects-chat-state.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-anonymizes-error-reporting-emails.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-07-12-session-replays-show-iframe-content.md +6 -0
  66. package/corpus/templates/analytics/netlify.toml +0 -3
  67. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +14 -0
  68. package/corpus/templates/analytics/server/lib/error-capture.ts +59 -2
  69. package/corpus/templates/analytics/server/lib/real-data-actions.ts +24 -0
  70. package/corpus/templates/analytics/server/lib/session-replay.ts +2 -9
  71. package/corpus/templates/analytics/server/plugins/agent-chat.ts +39 -6
  72. package/corpus/templates/assets/app/global.css +1 -1
  73. package/corpus/templates/brain/app/global.css +2 -2
  74. package/corpus/templates/brain/changelog/2026-07-12-fix-long-response-progress.md +6 -0
  75. package/corpus/templates/forms/app/components/CloudUpgrade.tsx +1 -1
  76. package/corpus/templates/forms/app/components/ThemeToggle.tsx +22 -6
  77. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +5 -5
  78. package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +3 -3
  79. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +16 -16
  80. package/corpus/templates/forms/app/global.css +110 -5
  81. package/corpus/templates/forms/app/pages/AskPage.tsx +3 -3
  82. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +134 -52
  83. package/corpus/templates/forms/app/pages/FormFillPage.tsx +44 -11
  84. package/corpus/templates/forms/app/pages/FormsListPage.tsx +42 -24
  85. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +28 -15
  86. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +38 -15
  87. package/corpus/templates/forms/app/routes/_app.settings.tsx +1 -1
  88. package/corpus/templates/forms/changelog/2026-07-12-form-builder-surfaces-are-calmer-fields-stay-stable-on-hover.md +6 -0
  89. package/corpus/templates/forms/changelog/2026-07-12-smoother-more-tactile-controls-throughout-animated-icon-and-.md +6 -0
  90. package/corpus/templates/mail/app/components/email/EmailThread.tsx +21 -13
  91. package/corpus/templates/mail/app/components/email/email-iframe-document.ts +18 -0
  92. package/dist/agent/durable-background.d.ts +10 -10
  93. package/dist/agent/durable-background.d.ts.map +1 -1
  94. package/dist/agent/durable-background.js +16 -16
  95. package/dist/agent/durable-background.js.map +1 -1
  96. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  97. package/dist/agent/engine/ai-sdk-engine.js +19 -8
  98. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  99. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  100. package/dist/agent/engine/anthropic-engine.js +11 -3
  101. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  102. package/dist/agent/engine/output-tokens.js +6 -6
  103. package/dist/agent/engine/output-tokens.js.map +1 -1
  104. package/dist/agent/production-agent.d.ts +52 -0
  105. package/dist/agent/production-agent.d.ts.map +1 -1
  106. package/dist/agent/production-agent.js +154 -26
  107. package/dist/agent/production-agent.js.map +1 -1
  108. package/dist/agent/run-manager.d.ts +9 -0
  109. package/dist/agent/run-manager.d.ts.map +1 -1
  110. package/dist/agent/run-manager.js +71 -1
  111. package/dist/agent/run-manager.js.map +1 -1
  112. package/dist/agent/run-store.d.ts +9 -0
  113. package/dist/agent/run-store.d.ts.map +1 -1
  114. package/dist/agent/run-store.js +243 -48
  115. package/dist/agent/run-store.js.map +1 -1
  116. package/dist/client/AgentPanel.d.ts.map +1 -1
  117. package/dist/client/AgentPanel.js +6 -2
  118. package/dist/client/AgentPanel.js.map +1 -1
  119. package/dist/client/AssistantChat.d.ts +21 -0
  120. package/dist/client/AssistantChat.d.ts.map +1 -1
  121. package/dist/client/AssistantChat.js +298 -67
  122. package/dist/client/AssistantChat.js.map +1 -1
  123. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  124. package/dist/client/agent-chat-adapter.js +146 -1
  125. package/dist/client/agent-chat-adapter.js.map +1 -1
  126. package/dist/client/chat/message-components.d.ts.map +1 -1
  127. package/dist/client/chat/message-components.js +2 -1
  128. package/dist/client/chat/message-components.js.map +1 -1
  129. package/dist/client/chat/tool-call-display.d.ts +3 -1
  130. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  131. package/dist/client/chat/tool-call-display.js +13 -37
  132. package/dist/client/chat/tool-call-display.js.map +1 -1
  133. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +1 -0
  134. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
  135. package/dist/client/conversation/use-near-bottom-autoscroll.js +6 -0
  136. package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
  137. package/dist/client/extensions/AgentNativeExtensionFrame.d.ts.map +1 -1
  138. package/dist/client/extensions/AgentNativeExtensionFrame.js +2 -1
  139. package/dist/client/extensions/AgentNativeExtensionFrame.js.map +1 -1
  140. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  141. package/dist/client/extensions/EmbeddedExtension.js +2 -1
  142. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  143. package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
  144. package/dist/client/extensions/ExtensionEditor.js +5 -2
  145. package/dist/client/extensions/ExtensionEditor.js.map +1 -1
  146. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  147. package/dist/client/extensions/ExtensionViewer.js +2 -1
  148. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  149. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
  150. package/dist/client/extensions/InlineExtensionFrame.js +2 -1
  151. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
  152. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  153. package/dist/client/extensions/portable-extension.js +2 -0
  154. package/dist/client/extensions/portable-extension.js.map +1 -1
  155. package/dist/client/session-replay.d.ts.map +1 -1
  156. package/dist/client/session-replay.js +308 -29
  157. package/dist/client/session-replay.js.map +1 -1
  158. package/dist/client/sse-event-processor.d.ts +5 -0
  159. package/dist/client/sse-event-processor.d.ts.map +1 -1
  160. package/dist/client/sse-event-processor.js +37 -1
  161. package/dist/client/sse-event-processor.js.map +1 -1
  162. package/dist/collab/routes.d.ts +1 -1
  163. package/dist/collab/struct-routes.d.ts +1 -1
  164. package/dist/demo/actions/toggle-demo-mode.js +1 -1
  165. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  166. package/dist/demo/config.js +6 -6
  167. package/dist/demo/config.js.map +1 -1
  168. package/dist/demo/fetch-interceptor.d.ts.map +1 -1
  169. package/dist/demo/fetch-interceptor.js +13 -4
  170. package/dist/demo/fetch-interceptor.js.map +1 -1
  171. package/dist/demo/redact.d.ts +7 -7
  172. package/dist/demo/redact.d.ts.map +1 -1
  173. package/dist/demo/redact.js +8 -145
  174. package/dist/demo/redact.js.map +1 -1
  175. package/dist/extensions/html-shell.d.ts.map +1 -1
  176. package/dist/extensions/html-shell.js +2 -0
  177. package/dist/extensions/html-shell.js.map +1 -1
  178. package/dist/extensions/session-replay-iframe.d.ts +10 -0
  179. package/dist/extensions/session-replay-iframe.d.ts.map +1 -0
  180. package/dist/extensions/session-replay-iframe.js +121 -0
  181. package/dist/extensions/session-replay-iframe.js.map +1 -0
  182. package/dist/progress/routes.d.ts +1 -1
  183. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  184. package/dist/resources/handlers.d.ts +3 -3
  185. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  186. package/dist/scripts/agent-engines/list-agent-engines.js +1 -19
  187. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  188. package/dist/session-replay-iframe-protocol.d.ts +36 -0
  189. package/dist/session-replay-iframe-protocol.d.ts.map +1 -0
  190. package/dist/session-replay-iframe-protocol.js +24 -0
  191. package/dist/session-replay-iframe-protocol.js.map +1 -0
  192. package/dist/shared/reasoning-effort.d.ts +11 -0
  193. package/dist/shared/reasoning-effort.d.ts.map +1 -1
  194. package/dist/shared/reasoning-effort.js +36 -0
  195. package/dist/shared/reasoning-effort.js.map +1 -1
  196. package/dist/styles/agent-conversation.css +26 -0
  197. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  198. package/docs/content/durable-background-runs.mdx +37 -0
  199. package/docs/content/locales/ar-SA/tracking.mdx +2 -0
  200. package/docs/content/locales/de-DE/tracking.mdx +2 -0
  201. package/docs/content/locales/es-ES/tracking.mdx +2 -0
  202. package/docs/content/locales/fr-FR/tracking.mdx +4 -2
  203. package/docs/content/locales/hi-IN/tracking.mdx +2 -0
  204. package/docs/content/locales/ja-JP/tracking.mdx +2 -0
  205. package/docs/content/locales/ko-KR/tracking.mdx +2 -0
  206. package/docs/content/locales/pt-BR/tracking.mdx +2 -0
  207. package/docs/content/locales/zh-CN/tracking.mdx +2 -0
  208. package/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
  209. package/docs/content/locales/zh-TW/tracking.mdx +2 -0
  210. package/docs/content/tracking.mdx +2 -0
  211. package/package.json +2 -2
  212. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
  213. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  214. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  215. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  216. package/dist/observability/hosted-model-experiment.js +0 -90
  217. package/dist/observability/hosted-model-experiment.js.map +0 -1
@@ -106,6 +106,18 @@ details live in `.agents/skills/`.
106
106
  / `$ai_latency`, `status`, `tool_calls`, `successful_tools`, `failed_tools`,
107
107
  and `$ai_error` / `error_message`. Do not expect prompts, tool args, or model
108
108
  responses in these tracked events by default.
109
+ - External MCP callers should use `ask_app` as the default for multi-step
110
+ Analytics work. It can resolve a person's known email, correlate sessions
111
+ with captured errors, mint a temporary replay-context link, and inspect the
112
+ timeline, page navigation, console diagnostics, failed network requests, and
113
+ clicks when needed. The authenticated connector catalog is the fast fallback
114
+ and exposes only bounded, user/org-scoped read actions for incident triage:
115
+ `list-session-recordings`, `get-session-replay-summary`,
116
+ `get-session-replay-events`, `query-agent-native-analytics`,
117
+ `list-error-issues`, and `get-error-issue`. Fetch the summary before raw
118
+ events, use the event filters/limits to keep the response bounded, and do not
119
+ add replay blob or dashboard mutation actions to this catalog without an
120
+ explicit security review.
109
121
  - `/agents` is the Analytics home for admin surfaces. The default Monitoring
110
122
  view embeds the shared observability dashboard for traces, conversations,
111
123
  evals, experiments, and feedback. `/agents?view=dashboards` shows the
@@ -28,6 +28,7 @@ export default defineAction({
28
28
  }),
29
29
  http: { method: "GET" },
30
30
  readOnly: true,
31
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
31
32
  run: async (args) => {
32
33
  return getErrorIssue(resolveScope(), args.id, {
33
34
  eventsLimit: args.eventsLimit,
@@ -40,6 +40,7 @@ export default defineAction({
40
40
  }),
41
41
  http: { method: "GET" },
42
42
  readOnly: true,
43
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
43
44
  run: async (args) => {
44
45
  return getSessionReplayEvents(args.recordingId, resolveScope(), {
45
46
  startSeq: args.startSeq,
@@ -21,6 +21,7 @@ export default defineAction({
21
21
  }),
22
22
  http: { method: "GET" },
23
23
  readOnly: true,
24
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
24
25
  run: async (args) => {
25
26
  return getSessionReplaySummary(args.recordingId, resolveScope());
26
27
  },
@@ -34,6 +34,7 @@ export default defineAction({
34
34
  }),
35
35
  http: { method: "GET" },
36
36
  readOnly: true,
37
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
37
38
  run: async (args) => {
38
39
  return listErrorIssues(resolveScope(), args);
39
40
  },
@@ -58,6 +58,7 @@ export default defineAction({
58
58
  }),
59
59
  http: { method: "GET" },
60
60
  readOnly: true,
61
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
61
62
  run: async (args) => {
62
63
  return listSessionRecordings(resolveScope(), args);
63
64
  },
@@ -33,7 +33,8 @@ export default defineAction({
33
33
  ),
34
34
  }),
35
35
  readOnly: true,
36
- http: false,
36
+ http: { method: "GET" },
37
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
37
38
  run: async (args) => {
38
39
  return queryFirstPartyAnalytics(args.sql, resolveScope());
39
40
  },
@@ -186,7 +186,7 @@
186
186
  .analytics-chat-intro {
187
187
  display: grid;
188
188
  width: 100%;
189
- max-width: min(570px, 90%);
189
+ max-width: min(684px, 90%);
190
190
  gap: 0.5rem;
191
191
  margin: 0 auto 1rem;
192
192
  text-align: center;
@@ -223,7 +223,7 @@
223
223
  [data-agent-empty-state="centered"]
224
224
  .agent-plan-mode-callout {
225
225
  width: 100%;
226
- max-width: min(570px, 90%);
226
+ max-width: min(684px, 90%);
227
227
  padding: 0;
228
228
  }
229
229
 
@@ -1,12 +1,14 @@
1
1
  import {
2
+ SIDEBAR_STATE_CHANGE_EVENT,
2
3
  deleteClientAppState,
3
4
  readClientAppState,
4
5
  removeAgentChatContextItem,
5
6
  setAgentChatContextItem,
6
7
  setClientAppState,
8
+ type AgentSidebarStateChangeDetail,
7
9
  useAgentChatContext,
8
10
  } from "@agent-native/core/client";
9
- import { useCallback, useEffect } from "react";
11
+ import { useCallback, useEffect, useState } from "react";
10
12
 
11
13
  import { TAB_ID } from "@/lib/tab-id";
12
14
 
@@ -120,10 +122,22 @@ function parsePanelSelection(
120
122
  }
121
123
  }
122
124
 
125
+ function isAgentSidebarOpenInDocument(): boolean {
126
+ if (typeof document === "undefined") return false;
127
+ return Boolean(
128
+ document.querySelector(
129
+ '.agent-sidebar-panel[data-agent-sidebar-state="open"]',
130
+ ),
131
+ );
132
+ }
133
+
123
134
  export function useDashboardChatContext(
124
135
  args: DashboardChatContextArgs,
125
136
  ): DashboardChatContextResult {
126
137
  const { id, kind, title, panelCount, canEdit } = args;
138
+ const [isAgentSidebarOpen, setIsAgentSidebarOpen] = useState(
139
+ isAgentSidebarOpenInDocument,
140
+ );
127
141
  const { items } = useAgentChatContext();
128
142
  const panelContext = items.find(
129
143
  (item) => item.key === DASHBOARD_PANEL_CONTEXT_KEY,
@@ -132,7 +146,9 @@ export function useDashboardChatContext(
132
146
  ? parsePanelSelection(panelContext.context)
133
147
  : null;
134
148
  const selectedPanelId =
135
- id && panelSelection?.dashboardId === id ? panelSelection.panelId : null;
149
+ isAgentSidebarOpen && id && panelSelection?.dashboardId === id
150
+ ? panelSelection.panelId
151
+ : null;
136
152
  const hasSelectedPanel = selectedPanelId !== null;
137
153
 
138
154
  const selectPanelForChat = useCallback(
@@ -141,6 +157,7 @@ export function useDashboardChatContext(
141
157
  options: SelectDashboardPanelOptions = {},
142
158
  ) => {
143
159
  if (!id) return;
160
+ if (options.openSidebar !== true && !isAgentSidebarOpen) return;
144
161
  const displayDashboardTitle = title?.trim() || id;
145
162
  const displayPanelTitle = panel.panelTitle.trim() || panel.panelId;
146
163
  const contextLines = [
@@ -166,7 +183,7 @@ export function useDashboardChatContext(
166
183
  key: DASHBOARD_PANEL_CONTEXT_KEY,
167
184
  title: displayPanelTitle,
168
185
  context: contextLines.join("\n"),
169
- openSidebar: options.openSidebar ?? true,
186
+ openSidebar: options.openSidebar === true,
170
187
  focus: options.focus ?? false,
171
188
  });
172
189
  setClientAppState(
@@ -191,9 +208,29 @@ export function useDashboardChatContext(
191
208
  },
192
209
  ).catch(() => {});
193
210
  },
194
- [id, kind, title],
211
+ [id, isAgentSidebarOpen, kind, title],
195
212
  );
196
213
 
214
+ useEffect(() => {
215
+ const handleSidebarStateChange = (event: Event) => {
216
+ const detail = (event as CustomEvent<AgentSidebarStateChangeDetail>)
217
+ .detail;
218
+ if (typeof detail?.open === "boolean") {
219
+ setIsAgentSidebarOpen(detail.open);
220
+ }
221
+ };
222
+
223
+ window.addEventListener(
224
+ SIDEBAR_STATE_CHANGE_EVENT,
225
+ handleSidebarStateChange,
226
+ );
227
+ return () =>
228
+ window.removeEventListener(
229
+ SIDEBAR_STATE_CHANGE_EVENT,
230
+ handleSidebarStateChange,
231
+ );
232
+ }, []);
233
+
197
234
  useEffect(() => {
198
235
  if (!id) return;
199
236
  const displayTitle = title?.trim() || id;
@@ -166,16 +166,6 @@ type ReplayViewportChange = ReplayViewportDimensions & {
166
166
 
167
167
  const DEFAULT_PLAYER_WIDTH = 1024;
168
168
  const DEFAULT_PLAYER_HEIGHT = 640;
169
- const MALFORMED_REPLAY_MIN_WIDTH = 4000;
170
- const MALFORMED_REPLAY_MIN_ASPECT_RATIO = 4;
171
- // Evidence-backed exception for the one pre-fix viewport shape observed in a
172
- // stored recording. Do not broaden this to an aspect-ratio range: valid
173
- // 3440x900 ultrawide browser viewports occupy the same range.
174
- const LEGACY_MALFORMED_REPLAY_WIDTH = 3189;
175
- const LEGACY_MALFORMED_REPLAY_HEIGHT = 885;
176
- const MIN_REPLAY_DISPLAY_DIMENSION = 240;
177
- const RECOVERED_REPLAY_DISPLAY_ASPECT_RATIO =
178
- DEFAULT_PLAYER_WIDTH / DEFAULT_PLAYER_HEIGHT;
179
169
  const DEFAULT_SPEED = 2;
180
170
  const SPEED_OPTIONS = [0.5, 1, 2, 4, 8];
181
171
  const SKIP_STEP_MS = 5000;
@@ -509,7 +499,7 @@ function ReplayPlayer({
509
499
  } | null>(null);
510
500
  const [fitScale, setFitScale] = useState(1);
511
501
  const initialDims = useMemo(
512
- () => replayInitialDisplayDimensions(events),
502
+ () => replayInitialViewportDimensions(events),
513
503
  [events],
514
504
  );
515
505
  const viewportTimeline = useMemo(
@@ -531,17 +521,14 @@ function ReplayPlayer({
531
521
  const scrubbingRef = useRef(false);
532
522
  const scrubResumePlayingRef = useRef(false);
533
523
 
534
- // IMPORTANT DO NOT REMOVE THIS LEGACY VIEWPORT CORRECTION.
535
- // Some stored sessions contain impossible 3,000–7,500px-wide Meta/resize
536
- // values even though the browser was a normal desktop viewport. Rendering
537
- // those raw values collapses the replay into a tiny horizontal ribbon. Keep
538
- // normal recordings exact—including 32:9 and mobile portrait. Recover only
539
- // widths >=4,000px with aspect >4:1, plus the exact known 3,189x885 legacy
540
- // shape, to the standard 16:10 viewport. The same dimensions are applied to
541
- // rrweb's iframe so CSS breakpoints/camera agree.
542
- const displayDims = clampReplayDisplayDimensions(streamedDims ?? initialDims);
543
- const playerWidth = displayDims?.width ?? DEFAULT_PLAYER_WIDTH;
544
- const playerHeight = displayDims?.height ?? DEFAULT_PLAYER_HEIGHT;
524
+ // Fall back to the default player size only when the viewport is unknown;
525
+ // otherwise render the raw recorded dimensions untouched. See the Replayer
526
+ // construction below for why "recovery" heuristics were removed here.
527
+ const displayDims = resolveReplayDisplayDimensions(
528
+ streamedDims ?? initialDims,
529
+ );
530
+ const playerWidth = displayDims.width;
531
+ const playerHeight = displayDims.height;
545
532
  const skipRanges = useMemo(() => buildIdleSkipRanges(events), [events]);
546
533
  const skipRangesRef = useLiveRef(skipRanges);
547
534
  const skipInactiveRef = useLiveRef(skipInactive);
@@ -671,18 +658,16 @@ function ReplayPlayer({
671
658
  clamped,
672
659
  );
673
660
  if (seekDims) {
674
- // rrweb 2.1 does not reliably re-emit Resize when seeking backwards.
675
- // IMPORTANT: correct both layers together. Correcting only the outer
676
- // stage or only rrweb's iframe recreates the ultra-wide/clipped bug.
677
- const correctedSeekDims = clampReplayDisplayDimensions(seekDims);
661
+ // rrweb 2.1 does not reliably re-emit its resize event when seeking
662
+ // backwards, so mirror the raw viewport-timeline lookup onto both
663
+ // rrweb's iframe and the outer stage state to keep them in sync.
678
664
  const currentDims = streamedDimsRef.current;
679
665
  if (
680
- correctedSeekDims &&
681
- (currentDims?.width !== correctedSeekDims.width ||
682
- currentDims?.height !== correctedSeekDims.height)
666
+ currentDims?.width !== seekDims.width ||
667
+ currentDims?.height !== seekDims.height
683
668
  ) {
684
- replayer.handleResize?.(correctedSeekDims);
685
- setStreamedDims(correctedSeekDims);
669
+ replayer.handleResize?.(seekDims);
670
+ setStreamedDims(seekDims);
686
671
  }
687
672
  }
688
673
  updateTime(clamped);
@@ -748,24 +733,18 @@ function ReplayPlayer({
748
733
  if (cancelled || !stageRootRef.current) return;
749
734
 
750
735
  stageRootRef.current.innerHTML = "";
751
- // Match builder-internal: pass events through untouched and let rrweb own
752
- // normal iframe sizing via Meta / ViewportResize. Never rewrite Meta or
753
- // force arbitrary iframe dimensions (that mismatches the FullSnapshot DOM
754
- // and blanks the stage). The one exception below mirrors the narrowly
755
- // detected legacy malformed viewport onto both the iframe and outer stage.
756
- const rawInitialDims = replayInitialViewportDimensions(replayEvents);
757
- const correctedInitialDims = replayInitialDisplayDimensions(replayEvents);
758
- setStreamedDims(correctedInitialDims);
759
- // The legacy malformed-width recordings need one paired exception to
760
- // raw-event pass-through: pointer coordinates were captured in the same
761
- // impossible viewport coordinate space. Correcting only the iframe makes
762
- // rrweb cast clicks thousands of pixels off-stage. Project pointer data
763
- // with each malformed Meta/resize while retaining every other raw field.
764
- const playerEvents = projectReplayPointerCoordinates(replayEvents);
765
- // Keep this loosely typed because the legacy viewport recovery below
766
- // intentionally calls rrweb's runtime handleResize hook, which its
767
- // TypeScript declaration marks private.
768
- localReplayer = new Replayer(playerEvents as any[], {
736
+ // Viewport and pointer events must pass through to rrweb untouched.
737
+ // The 2026-07 "ultra-wide replay" bugs (e.g. a stored 1,152px-wide
738
+ // recording rendering as a 4,491px Meta width) were caused by demo
739
+ // mode's fetch redaction faking numbers >= 1000 in raw replay JSON at
740
+ // view time, not by malformed stored geometry fixed in
741
+ // packages/core/src/demo/fetch-interceptor.ts. Stored recordings were
742
+ // always sane, so never add viewport "recovery" heuristics here; they
743
+ // can only corrupt genuine recordings (e.g. a real ultrawide window).
744
+ setStreamedDims(replayInitialViewportDimensions(replayEvents));
745
+ // Keep this loosely typed: our internal AnyReplayEvent shape doesn't
746
+ // exactly match rrweb's declared eventWithTime type.
747
+ localReplayer = new Replayer(replayEvents as any[], {
769
748
  root: stageRootRef.current,
770
749
  speed: speedRef.current,
771
750
  skipInactive: false,
@@ -778,17 +757,6 @@ function ReplayPlayer({
778
757
  triggerFocus: true,
779
758
  insertStyleRules: REPLAY_OVERLAY_STYLE_RULES,
780
759
  });
781
- // IMPORTANT: rrweb constructs its iframe from the raw initial Meta event.
782
- // When that legacy Meta width is malformed, correcting only streamedDims
783
- // fixes the outer stage but leaves CSS breakpoints and clicks ultra-wide.
784
- // Apply the same correction to rrweb immediately, before first playback.
785
- if (
786
- correctedInitialDims &&
787
- (rawInitialDims?.width !== correctedInitialDims.width ||
788
- rawInitialDims?.height !== correctedInitialDims.height)
789
- ) {
790
- (localReplayer as any).handleResize?.(correctedInitialDims);
791
- }
792
760
  // rrweb already sandboxes the replay document without script execution.
793
761
  // Do not mutate recorded URLs/CSS; suppress viewer-page referrer leakage
794
762
  // at the iframe boundary while retaining historical visual resources.
@@ -821,30 +789,17 @@ function ReplayPlayer({
821
789
  width: Math.round(dims.width),
822
790
  height: Math.round(dims.height),
823
791
  };
824
- const correctedDims = clampReplayDisplayDimensions(rawDims);
825
- if (!correctedDims) return;
826
- // rrweb handles the raw resize before notifying us. Override only
827
- // impossible legacy geometry so its iframe and our stage stay equal.
828
- // DO NOT clamp just one layer; that breaks responsive CSS and clicks.
829
- if (
830
- correctedDims.width !== rawDims.width ||
831
- correctedDims.height !== rawDims.height
832
- ) {
833
- // rrweb applies the raw Meta/ViewportResize to its iframe before
834
- // emitting this callback. Re-apply the correction every time the
835
- // raw event is malformed, even when React already stores the same
836
- // corrected dimensions. Returning early here recreates the exact
837
- // bug where the outer stage is normal but the iframe is ultra-wide.
838
- (localReplayer as any).handleResize?.(correctedDims);
839
- }
792
+ // rrweb owns its iframe geometry from raw Meta/ViewportResize
793
+ // events. Only mirror the raw dims into React state here, for the
794
+ // outer stage's fit-to-container scaling.
840
795
  const currentDims = streamedDimsRef.current;
841
796
  if (
842
- currentDims?.width === correctedDims.width &&
843
- currentDims?.height === correctedDims.height
797
+ currentDims?.width === rawDims.width &&
798
+ currentDims?.height === rawDims.height
844
799
  ) {
845
800
  return;
846
801
  }
847
- setStreamedDims(correctedDims);
802
+ setStreamedDims(rawDims);
848
803
  }
849
804
  });
850
805
  updateTime(startAt);
@@ -1994,102 +1949,6 @@ export function normalizeReplayEvents(events: unknown[]): AnyReplayEvent[] {
1994
1949
  .sort((a, b) => Number(a.timestamp ?? 0) - Number(b.timestamp ?? 0));
1995
1950
  }
1996
1951
 
1997
- /**
1998
- * Pair pointer coordinates with the narrowly gated legacy viewport recovery.
1999
- *
2000
- * IMPORTANT: do not apply this to normal recordings and do not rewrite the
2001
- * Meta/resize events themselves. rrweb must still rebuild the captured DOM
2002
- * from raw events; only pointer coordinates need projection into the corrected
2003
- * iframe camera. Returning original references for untouched events keeps this
2004
- * compatible with stock rrweb and avoids cloning large snapshots.
2005
- */
2006
- export function projectReplayPointerCoordinates(
2007
- events: AnyReplayEvent[],
2008
- ): AnyReplayEvent[] {
2009
- let rawDims: ReplayViewportDimensions | null = null;
2010
- let displayDims: ReplayViewportDimensions | null = null;
2011
-
2012
- return events.map((event) => {
2013
- const nextDims = dimensionsFromReplayEvent(event);
2014
- if (nextDims) {
2015
- rawDims = nextDims;
2016
- displayDims = clampReplayDisplayDimensions(nextDims);
2017
- return event;
2018
- }
2019
- if (
2020
- !rawDims ||
2021
- !displayDims ||
2022
- (rawDims.width === displayDims.width &&
2023
- rawDims.height === displayDims.height) ||
2024
- event.type !== RRWEB_EVENT_TYPE.IncrementalSnapshot
2025
- ) {
2026
- return event;
2027
- }
2028
-
2029
- const source = event.data?.source;
2030
- if (source === INCREMENTAL_SOURCE.MouseInteraction) {
2031
- const data = projectReplayPointerData(event.data, rawDims, displayDims);
2032
- return data === event.data ? event : { ...event, data };
2033
- }
2034
- if (
2035
- source !== INCREMENTAL_SOURCE.MouseMove &&
2036
- source !== INCREMENTAL_SOURCE.TouchMove &&
2037
- source !== INCREMENTAL_SOURCE.Drag
2038
- ) {
2039
- return event;
2040
- }
2041
- const positions = Array.isArray(event.data?.positions)
2042
- ? event.data.positions
2043
- : [];
2044
- let changed = false;
2045
- const projectedPositions = positions.map((position: unknown) => {
2046
- if (!isRecord(position)) return position;
2047
- const projected = projectReplayPointerData(
2048
- position,
2049
- rawDims!,
2050
- displayDims!,
2051
- );
2052
- if (projected !== position) changed = true;
2053
- return projected;
2054
- });
2055
- return changed
2056
- ? { ...event, data: { ...event.data, positions: projectedPositions } }
2057
- : event;
2058
- });
2059
- }
2060
-
2061
- function projectReplayPointerData(
2062
- data: AnyReplayEvent,
2063
- rawDims: ReplayViewportDimensions,
2064
- displayDims: ReplayViewportDimensions,
2065
- ): AnyReplayEvent {
2066
- const projectedX = projectReplayCoordinate(
2067
- data.x,
2068
- displayDims.width / rawDims.width,
2069
- displayDims.width,
2070
- );
2071
- const projectedY = projectReplayCoordinate(
2072
- data.y,
2073
- displayDims.height / rawDims.height,
2074
- displayDims.height,
2075
- );
2076
- if (projectedX === data.x && projectedY === data.y) return data;
2077
- return {
2078
- ...data,
2079
- ...(projectedX !== undefined ? { x: projectedX } : {}),
2080
- ...(projectedY !== undefined ? { y: projectedY } : {}),
2081
- };
2082
- }
2083
-
2084
- function projectReplayCoordinate(
2085
- value: unknown,
2086
- scale: number,
2087
- maximum: number,
2088
- ): number | undefined {
2089
- if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
2090
- return clamp(value * scale, 0, Math.max(0, maximum - 1));
2091
- }
2092
-
2093
1952
  export function buildReplayMarkers(events: AnyReplayEvent[]): ReplayMarker[] {
2094
1953
  const startedAt = replayStartedAt(events);
2095
1954
  const markers: ReplayMarker[] = [];
@@ -2447,14 +2306,6 @@ export function replayInitialViewportDimensions(
2447
2306
  return null;
2448
2307
  }
2449
2308
 
2450
- export function replayInitialDisplayDimensions(
2451
- events: AnyReplayEvent[],
2452
- ): ReplayViewportDimensions | null {
2453
- // Keep the initial outer stage and rrweb iframe on one corrected camera.
2454
- // DO NOT use the raw initial Meta dimensions for only one of those layers.
2455
- return clampReplayDisplayDimensions(replayInitialViewportDimensions(events));
2456
- }
2457
-
2458
2309
  export function buildReplayViewportTimeline(
2459
2310
  events: AnyReplayEvent[],
2460
2311
  ): ReplayViewportChange[] {
@@ -2540,30 +2391,28 @@ export function normalizeReplayDimensions(
2540
2391
  };
2541
2392
  }
2542
2393
 
2543
- export function clampReplayDisplayDimensions(
2394
+ /**
2395
+ * Fall back to the default player size only when the viewport is entirely
2396
+ * unknown (no Meta/ViewportResize event yet). This is not a "recovery"
2397
+ * heuristic for real recorded geometry — it never rewrites valid dims, no
2398
+ * matter how wide, narrow, or unusual the aspect ratio. See the Replayer
2399
+ * construction in ReplayPlayer for why viewport "correction" was removed:
2400
+ * the 2026-07 ultra-wide replay bugs were caused by demo mode's fetch
2401
+ * redaction faking numbers at view time, not by malformed stored geometry.
2402
+ */
2403
+ export function resolveReplayDisplayDimensions(
2544
2404
  dims: ReplayViewportDimensions | null,
2545
- ): ReplayViewportDimensions | null {
2546
- if (!dims) return null;
2405
+ ): ReplayViewportDimensions {
2547
2406
  if (
2548
- dims.width < MIN_REPLAY_DISPLAY_DIMENSION ||
2549
- dims.height < MIN_REPLAY_DISPLAY_DIMENSION
2407
+ dims &&
2408
+ Number.isFinite(dims.width) &&
2409
+ Number.isFinite(dims.height) &&
2410
+ dims.width > 0 &&
2411
+ dims.height > 0
2550
2412
  ) {
2551
- return { width: DEFAULT_PLAYER_WIDTH, height: DEFAULT_PLAYER_HEIGHT };
2552
- }
2553
- const aspect = dims.width / dims.height;
2554
- const hasMalformedWideGeometry =
2555
- dims.width >= MALFORMED_REPLAY_MIN_WIDTH &&
2556
- aspect > MALFORMED_REPLAY_MIN_ASPECT_RATIO;
2557
- const hasLegacyMalformedWideGeometry =
2558
- dims.width === LEGACY_MALFORMED_REPLAY_WIDTH &&
2559
- dims.height === LEGACY_MALFORMED_REPLAY_HEIGHT;
2560
- if (hasMalformedWideGeometry || hasLegacyMalformedWideGeometry) {
2561
- return {
2562
- width: Math.round(dims.height * RECOVERED_REPLAY_DISPLAY_ASPECT_RATIO),
2563
- height: dims.height,
2564
- };
2413
+ return dims;
2565
2414
  }
2566
- return dims;
2415
+ return { width: DEFAULT_PLAYER_WIDTH, height: DEFAULT_PLAYER_HEIGHT };
2567
2416
  }
2568
2417
 
2569
2418
  export function filterReplayMarkers(
@@ -21,7 +21,7 @@ import {
21
21
  IconSettings,
22
22
  } from "@tabler/icons-react";
23
23
  import { useQuery } from "@tanstack/react-query";
24
- import { useMemo, useState } from "react";
24
+ import { useCallback, useEffect, useMemo, useState } from "react";
25
25
  import { Link, useNavigate, useSearchParams } from "react-router";
26
26
  import { toast } from "sonner";
27
27
 
@@ -167,6 +167,7 @@ type SessionRecordingIdentity = Pick<
167
167
  type SessionRecordingDevice = Pick<SessionRecordingSummary, "metadata">;
168
168
 
169
169
  const RANGE_OPTIONS: ReplayRange[] = ["24h", "7d", "30d", "90d", "all"];
170
+ const SESSION_QUERY_DEBOUNCE_MS = 250;
170
171
 
171
172
  export default function SessionsPage() {
172
173
  const t = useT();
@@ -175,8 +176,38 @@ export default function SessionsPage() {
175
176
  const range = readRange(searchParams.get("range"));
176
177
  const app = searchParams.get("app") ?? "";
177
178
  const query = searchParams.get("q") ?? "";
179
+ const [queryInput, setQueryInput] = useState(query);
178
180
  const from = useMemo(() => rangeToFrom(range), [range]);
179
181
 
182
+ useEffect(() => {
183
+ setQueryInput(query);
184
+ }, [query]);
185
+
186
+ const updateFilter = useCallback(
187
+ (key: string, value: string) => {
188
+ setSearchParams(
189
+ (current) => {
190
+ const next = new URLSearchParams(current);
191
+ const emptyDefault =
192
+ (key === "range" && value === "30d") || value.trim() === "";
193
+ if (emptyDefault) next.delete(key);
194
+ else next.set(key, value);
195
+ return next;
196
+ },
197
+ { replace: true },
198
+ );
199
+ },
200
+ [setSearchParams],
201
+ );
202
+
203
+ useEffect(() => {
204
+ if (queryInput === query) return;
205
+ const timeout = window.setTimeout(() => {
206
+ updateFilter("q", queryInput);
207
+ }, SESSION_QUERY_DEBOUNCE_MS);
208
+ return () => window.clearTimeout(timeout);
209
+ }, [query, queryInput, updateFilter]);
210
+
180
211
  const { data, isLoading, isFetching, refetch, error } = useActionQuery<
181
212
  SessionRecordingSummary[]
182
213
  >(
@@ -192,15 +223,6 @@ export default function SessionsPage() {
192
223
 
193
224
  const recordings = data ?? [];
194
225
 
195
- function updateFilter(key: string, value: string) {
196
- const next = new URLSearchParams(searchParams);
197
- const emptyDefault =
198
- (key === "range" && value === "30d") || value.trim() === "";
199
- if (emptyDefault) next.delete(key);
200
- else next.set(key, value);
201
- setSearchParams(next, { replace: true });
202
- }
203
-
204
226
  return (
205
227
  <div className="analytics-sessions-page mx-auto flex w-full max-w-7xl flex-col gap-4 px-4 py-5">
206
228
  <Card>
@@ -213,8 +235,8 @@ export default function SessionsPage() {
213
235
  <div className="analytics-sessions-filter-search relative min-w-0">
214
236
  <IconSearch className="pointer-events-none absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
215
237
  <Input
216
- value={query}
217
- onChange={(event) => updateFilter("q", event.target.value)}
238
+ value={queryInput}
239
+ onChange={(event) => setQueryInput(event.target.value)}
218
240
  placeholder={t("sessions.searchPlaceholder")}
219
241
  className="h-9 ps-9"
220
242
  />
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Analytics chat retries before showing a generic no-data message
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Analytics sessions search no longer loses fast keystrokes, and agent incident lookups no longer stall after background dispatch failures.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Dashboard charts only select for chat when the chat sidebar is already open.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: security
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Demo mode now reliably anonymizes email addresses throughout error reports.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Session replays now show activity inside extensions and email content.
@@ -9,14 +9,11 @@ GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
9
  NITRO_PRESET = "netlify"
10
10
  NPM_CONFIG_PRODUCTION = "false"
11
11
  AGENT_PROD_CODE_EXECUTION = "sandboxed"
12
- AGENT_CHAT_DURABLE_BACKGROUND = "true"
13
12
 
14
13
  # A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
15
14
  # Hosted agent chat uses a soft timeout before this function timeout so the
16
15
  # hidden auto-continuation path can hand off before Netlify kills the function.
17
16
  # If AGENT_RUN_SOFT_TIMEOUT_MS is customized, keep it below this timeout.
18
- # Agent chat runs are dispatched to the emitted Netlify background function;
19
- # the normal server timeout still protects non-background HTTP handlers.
20
17
  [functions."*"]
21
18
  timeout = 75
22
19