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