@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
@@ -1 +1 @@
1
- {"version":3,"file":"html-shell.js","sourceRoot":"","sources":["../../src/extensions/html-shell.ts"],"names":[],"mappings":"AAAA,MAAM,yBAAyB,GAC7B,mXAAmX,CAAC;AAEtX,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,QAAQ;IACR,0BAA0B;IAC1B,4BAA4B;IAC5B,oBAAoB;IACpB,oBAAoB;IACpB,gCAAgC;IAChC,0CAA0C;CAC3C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,yBAAyB,oBAAoB,yBAAyB,GAAG,CAAC;AAEjH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAqEnE,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,SAAiB,EACjB,MAAe,EACf,WAAoB,EACpB,OAAgC;IAEhC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAChC,OAAO,IAAI;QACT,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;KACd,CACF,CAAC;IAEF,OAAO;iBACQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;;;;wDAIU,yBAAyB;IAC7E,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,uDAAuD,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmFlI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA4TK,eAAe;8BACV,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+OjC,WAAW,CAAC,CAAC,CAAC,uBAAuB,eAAe,mBAAmB,eAAe,GAAG,CAAC,CAAC,CAAC,EAAE;GACnG,OAAO;;;;;;;;;;SAUD,CAAC;AACV,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["const EXTENSION_IFRAME_CSP_BASE =\n \"default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';\";\n\nexport const EXTENSION_FRAME_ANCESTORS = [\n \"'self'\",\n \"https://agent-native.com\",\n \"https://*.agent-native.com\",\n \"http://localhost:*\",\n \"http://127.0.0.1:*\",\n \"https://*.claudemcpcontent.com\",\n \"https://*.web-sandbox.oaiusercontent.com\",\n].join(\" \");\n\nexport const EXTENSION_IFRAME_CSP = `${EXTENSION_IFRAME_CSP_BASE} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;\n\nexport const EXTENSION_IFRAME_META_CSP = EXTENSION_IFRAME_CSP_BASE;\n\n/**\n * SECURITY — EXTENSION CONTENT IS UNTRUSTED.\n *\n * `${content}` (line ~Body) interpolates raw HTML/JS authored by a user. This\n * file is the boundary between framework-controlled HTML and user-controlled\n * HTML. Two non-negotiable invariants for every change here:\n *\n * 1. The iframe MUST be rendered with a `sandbox` attribute that does NOT\n * include `allow-same-origin`. The viewer (`ExtensionViewer.tsx`,\n * `EmbeddedExtension.tsx`) normalizes the sandbox through\n * `normalizeAgentNativeExtensionSandbox()`, which permits scripts, forms,\n * popups, and downloads but strips `allow-same-origin`. Adding\n * `allow-same-origin` would give the extension full DOM access to the\n * parent window via cross-frame script.\n *\n * 2. Every reachable parent action must treat the postMessage payload as\n * hostile. The bridge in `iframe-bridge.ts` enforces a path allowlist,\n * header sanitization, and method allowlist; do not relax those gates\n * for \"convenience\" in this file or any caller.\n *\n * For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the\n * `extensions` skill. When in doubt, fail closed.\n *\n * BACKWARDS COMPAT — the iframe injects helpers under both their canonical\n * `extension*` names (`extensionFetch`, `extensionData`, `extensionId`,\n * `extensionBinding`) AND legacy `tool*` aliases (`toolFetch`, `toolData`,\n * `toolId`, `toolBinding`) so existing user-authored extension bodies that\n * pre-date the rename keep working. Same for layout opt-ins:\n * `data-extension-layout=\"full-bleed\"` / `data-extension-padding=\"none\"` /\n * class `agent-native-extension-bleed` / CSS var\n * `--agent-native-extension-padding` are canonical; the `data-tool-*`,\n * `agent-native-tool-bleed`, and `--agent-native-tool-padding` variants are\n * accepted as aliases.\n */\n\nexport interface ExtensionRenderBinding {\n /** Email of the user who authored / owns the extension. */\n authorEmail: string;\n /** Email of the user currently viewing/running the extension. */\n viewerEmail: string;\n /** True when viewer === author. */\n isAuthor: boolean;\n /**\n * Resolved role for the viewer (\"owner\" | \"admin\" | \"editor\" | \"viewer\").\n *\n * TODO(security, audit H4): the host-side bridge does not yet gate any\n * helper based on this value — every viewer gets the same powers as the\n * author. The role is plumbed through so a follow-up PR can constrain\n * `appAction` / `dbExec` / `extensionFetch` for non-author viewers (and\n * eventually require an explicit consent step before running a shared\n * extension, audit C1). For now this is metadata only.\n */\n role: \"owner\" | \"admin\" | \"editor\" | \"viewer\";\n /** Where the extension definition came from. Database extensions are the default. */\n source?: \"database\" | \"local-files\";\n /**\n * Fine-grained helper permissions for local file extensions. Database-backed\n * extensions keep using the role table in the parent bridge.\n */\n permissions?: {\n appActions?: string[];\n extensionData?: boolean;\n sql?: boolean;\n externalFetch?: boolean;\n };\n}\n\nexport function buildExtensionHtml(\n content: string,\n themeVars: string,\n isDark: boolean,\n extensionId?: string,\n binding?: ExtensionRenderBinding,\n): string {\n const extensionIdJson = JSON.stringify(extensionId ?? \"\");\n const extensionIdAttr = escapeHtmlAttribute(extensionId ?? \"\");\n const bindingJson = JSON.stringify(\n binding ?? {\n authorEmail: \"\",\n viewerEmail: \"\",\n isAuthor: true,\n role: \"owner\",\n },\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\"${isDark ? ' class=\"dark\"' : \"\"}>\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta http-equiv=\"Content-Security-Policy\" content=\"${EXTENSION_IFRAME_META_CSP}\" />\n ${binding && !binding.isAuthor ? `<meta name=\"agent-native-extension-author\" content=\"${escapeHtmlAttribute(binding.authorEmail)}\" />` : \"\"}\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap\" rel=\"stylesheet\" />\n <script>\n var _extensionErrors = [];\n var _extensionErrorDetails = [];\n var _consoleLogs = [];\n var _networkLogs = [];\n\n var _origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };\n function _wrapConsole(level, orig) {\n return function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }\n catch(e) { return String(a); }\n }).join(' ');\n if (_consoleLogs.length >= 50) _consoleLogs.shift();\n _consoleLogs.push({ level: level, message: msg });\n orig.apply(console, arguments);\n };\n }\n console.log = _wrapConsole('log', _origConsole.log);\n console.warn = _wrapConsole('warn', _origConsole.warn);\n console.error = _wrapConsole('error', _origConsole.error);\n console.info = _wrapConsole('info', _origConsole.info);\n\n function _collectError(message, stack) {\n if (!message) return;\n if (message === 'Script error.' || message === 'Script error') message = 'Runtime error';\n if (_extensionErrors.indexOf(message) !== -1) return;\n _extensionErrors.push(message);\n _extensionErrorDetails.push({ message: message, stack: stack || '' });\n _renderErrorToast();\n }\n\n function _renderErrorToast() {\n var toast = document.getElementById('__extension-error-toast');\n if (!toast) return;\n var msg = document.getElementById('__extension-error-msg');\n if (!msg || _extensionErrors.length === 0) return;\n if (_extensionErrors.length === 1) {\n msg.textContent = _extensionErrors[0];\n } else {\n msg.textContent = _extensionErrors.length + ' errors — ' + _extensionErrors[_extensionErrors.length - 1];\n }\n toast.style.display = 'block';\n }\n\n window.addEventListener('error', function(event) {\n var msg = event.message || '';\n if (msg.indexOf('Alpine Expression Error') === 0) return;\n var stack = event.error && event.error.stack ? event.error.stack : '';\n _collectError(msg, stack);\n });\n\n window.addEventListener('unhandledrejection', function(event) {\n var msg = event.reason && event.reason.message ? event.reason.message : String(event.reason);\n var stack = event.reason && event.reason.stack ? event.reason.stack : '';\n _collectError(msg, stack);\n });\n </script>\n <!--\n SECURITY: pinned to exact patch versions + SRI integrity hashes. A\n malicious republish of @tailwindcss/browser@4.x or alpinejs@3.x would\n otherwise inject code into every extension. To bump these versions:\n 1. npm view @tailwindcss/browser version (or alpinejs)\n 2. curl -sL https://cdn.jsdelivr.net/npm/@tailwindcss/browser@<v> \\\\\n | openssl dgst -sha384 -binary | openssl base64 -A\n 3. Update the URL + integrity hash below in lockstep.\n -->\n <script\n src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4\"\n integrity=\"sha384-yNSZBFvuOWcmww494a9+1zNuvgUGEXoWkein7cxP8wHUTi3iXCU4vJ7hr3tzBCml\"\n crossorigin=\"anonymous\"\n ></script>\n <script\n defer\n src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js\"\n integrity=\"sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be\"\n crossorigin=\"anonymous\"\n ></script>\n <style>${themeVars}</style>\n <style type=\"text/tailwindcss\">\n @custom-variant dark (&:where(.dark, .dark *));\n @theme {\n --color-border: hsl(var(--border));\n --color-input: hsl(var(--input));\n --color-ring: hsl(var(--ring));\n --color-background: hsl(var(--background));\n --color-foreground: hsl(var(--foreground));\n --color-primary: hsl(var(--primary));\n --color-primary-foreground: hsl(var(--primary-foreground));\n --color-secondary: hsl(var(--secondary));\n --color-secondary-foreground: hsl(var(--secondary-foreground));\n --color-destructive: hsl(var(--destructive));\n --color-destructive-foreground: hsl(var(--destructive-foreground));\n --color-muted: hsl(var(--muted));\n --color-muted-foreground: hsl(var(--muted-foreground));\n --color-accent: hsl(var(--accent));\n --color-accent-foreground: hsl(var(--accent-foreground));\n --color-popover: hsl(var(--popover));\n --color-popover-foreground: hsl(var(--popover-foreground));\n --color-card: hsl(var(--card));\n --color-card-foreground: hsl(var(--card-foreground));\n --color-sidebar: hsl(var(--sidebar-background));\n --color-sidebar-foreground: hsl(var(--sidebar-foreground));\n --color-sidebar-primary: hsl(var(--sidebar-primary));\n --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));\n --color-sidebar-accent: hsl(var(--sidebar-accent));\n --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));\n --color-sidebar-border: hsl(var(--sidebar-border));\n --color-sidebar-ring: hsl(var(--sidebar-ring));\n --radius-lg: var(--radius);\n --radius-md: calc(var(--radius) - 2px);\n --radius-sm: calc(var(--radius) - 4px);\n }\n </style>\n\t <style>\n\t *, *::before, *::after { border-color: hsl(var(--border)); }\n\t html, body {\n\t /* Transparent so the iframe inherits the host surface (dashboard panel,\n\t sidebar, chat) instead of painting the browser's default white canvas.\n\t The dark class still flips the theme vars; content paints its own\n\t bg-background / bg-card surfaces. */\n\t background: transparent;\n\t }\n\t body {\n\t --agent-native-extension-padding: clamp(16px, 2vw, 24px);\n\t /* Legacy alias for pre-rename extension content (do not remove). */\n\t --agent-native-tool-padding: var(--agent-native-extension-padding);\n\t box-sizing: border-box;\n\t color: hsl(var(--foreground));\n\t font-family: 'Inter', sans-serif;\n\t margin: 0;\n\t min-height: 100vh;\n\t padding: var(--agent-native-extension-padding);\n\t }\n\t body:has(> [data-extension-layout=\"full-bleed\"]),\n\t body:has(> [data-extension-padding=\"none\"]),\n\t body:has(> .agent-native-extension-bleed),\n\t /* Legacy aliases (do not remove). */\n\t body:has(> [data-tool-layout=\"full-bleed\"]),\n\t body:has(> [data-tool-padding=\"none\"]),\n\t body:has(> .agent-native-tool-bleed) {\n\t padding: 0;\n\t }\n\t </style>\n\t <script>\n\t var _extensionRequestSeq = 0;\n\t var _extensionPendingRequests = {};\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var message = event.data || {};\n\t if (\n\t message.type !== 'agent-native-extension-response' &&\n\t message.type !== 'agent-native-tool-response'\n\t ) return;\n\t var pending = _extensionPendingRequests[message.requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[message.requestId];\n\t if (message.error) {\n\t pending.reject(new Error(message.error));\n\t } else {\n\t pending.resolve(message.response);\n\t }\n\t });\n\n\t function hostRequest(path, options) {\n\t options = options || {};\n\t return new Promise(function(resolve, reject) {\n\t var requestId = 'extension-req-' + (++_extensionRequestSeq);\n\t _extensionPendingRequests[requestId] = { resolve: resolve, reject: reject };\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-request',\n\t requestId: requestId,\n\t path: path,\n\t options: {\n\t method: options.method || 'GET',\n\t headers: options.headers || {},\n\t body: options.body,\n\t },\n\t }, '*');\n\t setTimeout(function() {\n\t var pending = _extensionPendingRequests[requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[requestId];\n\t pending.reject(new Error('Extension host request timed out'));\n\t }, 30000);\n\t });\n\t }\n\n\t var _origHostRequest = hostRequest;\n\t hostRequest = function(path, options) {\n\t var entry = { path: path, method: (options && options.method) || 'GET' };\n\t return _origHostRequest(path, options).then(function(res) {\n\t entry.ok = res.ok;\n\t entry.status = res.status;\n\t if (!res.ok && res.body) {\n\t try { entry.error = typeof res.body === 'string' ? res.body.slice(0, 200) : JSON.stringify(res.body).slice(0, 200); } catch(e) {}\n\t }\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t return res;\n\t }, function(err) {\n\t entry.ok = false;\n\t entry.error = err.message;\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t throw err;\n\t });\n\t };\n\n\t function extensionFetch(url, options) {\n\t var opts = options || {};\n\t return hostRequest('/_agent-native/extensions/proxy', {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({\n\t url: url,\n method: opts.method || 'GET',\n headers: opts.headers,\n body: opts.body,\n }),\n\t }).then(function(res) {\n\t var data = res.body;\n\t if (data.error && data.status === undefined) {\n\t throw new Error(data.error);\n\t }\n return {\n ok: data.status >= 200 && data.status < 300,\n status: data.status,\n\t json: function() { return Promise.resolve(data.body); },\n\t text: function() { return Promise.resolve(typeof data.body === 'string' ? data.body : JSON.stringify(data.body)); },\n\t };\n\t });\n\t }\n\n\t function _appendActionQuery(path, params) {\n\t var search = new URLSearchParams();\n\t params = params || {};\n\t Object.keys(params).forEach(function(key) {\n\t var value = params[key];\n\t if (value === undefined || value === null) return;\n\t if (Array.isArray(value)) {\n\t value.forEach(function(item) {\n\t if (item !== undefined && item !== null) {\n\t search.append(key, String(item));\n\t }\n\t });\n\t return;\n\t }\n\t search.set(key, String(value));\n\t });\n\t var qs = search.toString();\n\t return qs ? path + '?' + qs : path;\n\t }\n\n\t function _methodHintFromActionResponse(res) {\n\t if (!res || res.status !== 405) return null;\n\t var body = res.body || {};\n\t var message = typeof body === 'string' ? body : body.error;\n\t if (!message) return null;\n\t var match = String(message).match(/Use (GET|POST|PUT|PATCH|DELETE|HEAD)\\\\.?/i);\n\t return match ? match[1].toUpperCase() : null;\n\t }\n\n\t async function appAction(name, params) {\n\t params = params || {};\n\t if (name === 'navigate') {\n\t var navRes = await hostRequest('/_agent-native/application-state/navigate', {\n\t method: 'PUT',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\t if (!navRes.ok) {\n\t var navErr = navRes.body || { error: navRes.statusText };\n\t throw new Error(navErr.error || 'Navigation failed: ' + navRes.status);\n\t }\n\t return navRes.body;\n\t }\n\t var path = '/_agent-native/actions/' + encodeURIComponent(name);\n\t var res = await hostRequest(path, {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\n\t var retryMethod = _methodHintFromActionResponse(res);\n\t if (!res.ok && retryMethod && retryMethod !== 'POST') {\n\t var retryPath = path;\n\t var retryOptions = {\n\t method: retryMethod,\n\t headers: { 'Content-Type': 'application/json' },\n\t };\n\t if (retryMethod === 'GET' || retryMethod === 'HEAD') {\n\t retryPath = _appendActionQuery(path, params);\n\t } else {\n\t retryOptions.body = JSON.stringify(params);\n\t }\n\t res = await hostRequest(retryPath, retryOptions);\n\t }\n\n\t if (!res.ok) {\n\t var err = res.body || {};\n\t var rawError = typeof err === 'string' ? err : err.error;\n\t var message = (typeof rawError === 'string' && rawError.trim())\n\t ? rawError\n\t : (res.status === 404\n\t ? \"Action '\" + name + \"' is not available over HTTP (404). It may be agent-only (http: false); expose it with an HTTP-mounted action to call it from an extension.\"\n\t : \"Action '\" + name + \"' failed (\" + (res.status || 'network error') + \")\");\n\t throw new Error(message);\n\t }\n\t return res.body;\n\t }\n\n\t async function appFetch(path, options) {\n\t options = options || {};\n\t var res = await hostRequest(path, {\n\t ...options,\n\t headers: {\n\t 'Content-Type': 'application/json',\n\t ...(options.headers || {}),\n\t },\n\t });\n\t if (!res.ok) {\n\t var err = typeof res.body === 'object' && res.body ? res.body : { error: res.statusText };\n\t throw new Error(err.error || 'Request failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n\t function sendToChat(message, options) {\n\t options = options || {};\n\t var text = typeof message === 'string' ? message : JSON.stringify(message);\n\t window.parent.postMessage({\n\t type: 'agent-native-send-to-chat',\n\t message: text,\n\t context: options.context,\n\t submit: options.submit !== false,\n\t openSidebar: options.openSidebar !== false,\n\t }, '*');\n\t return { ok: true };\n\t }\n\n\t function inlineUiOutputKey() {\n\t var safeId = String(_extensionId || 'unknown').replace(/[^A-Za-z0-9_:-]/g, '') || 'unknown';\n\t return 'inline-ui:' + safeId + ':output';\n\t }\n\n\t async function outputToUi(value, options) {\n\t options = options || {};\n\t var key = inlineUiOutputKey();\n\t var payload = {\n\t value: value,\n\t updatedAt: new Date().toISOString(),\n\t extensionId: _extensionId,\n\t source: 'inline-ui',\n\t };\n\t if (options.label !== undefined) payload.label = options.label;\n\t if (options.context !== undefined) payload.context = options.context;\n\t if (options.meta !== undefined) payload.meta = options.meta;\n\t var output = await appFetch('/_agent-native/application-state/' + key, {\n\t method: 'PUT',\n\t headers: { 'X-Request-Source': 'inline-ui' },\n\t body: JSON.stringify(payload),\n\t });\n\t try {\n\t window.parent.postMessage({\n\t type: 'agent-native-ui-output',\n\t extensionId: _extensionId,\n\t key: key,\n\t value: value,\n\t output: output,\n\t }, '*');\n\t } catch (_) {}\n\t return { ok: true, key: key, output: output };\n\t }\n\n async function dbQuery(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/query', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n async function dbExec(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/exec', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n var _extensionId = ${extensionIdJson};\n var _extensionBinding = ${bindingJson};\n window.extensionBinding = _extensionBinding;\n // Legacy alias for extension bodies authored before the rename.\n window.toolBinding = _extensionBinding;\n // SECURITY (audit H4): announce the resolved binding to the parent so the\n // host bridge can gate dangerous helpers based on viewer role. Sent\n // BEFORE the user-authored content has a chance to run, so a malicious\n // extension body cannot suppress or rewrite the announcement. The parent\n // ignores subsequent announcements for the same iframe; see\n // ExtensionViewer.tsx / EmbeddedExtension.tsx.\n try {\n window.parent.postMessage(\n {\n type: 'agent-native-extension-binding',\n extensionId: _extensionId,\n binding: _extensionBinding,\n },\n '*',\n );\n } catch (_) {}\n // SECURITY: when the viewer is not the author of this extension, emit a\n // clear console warning. The bridge currently runs every helper with the\n // viewer's session — a malicious shared extension can call any action,\n // read any owned table row in scope, and resolve any user-scope secret.\n // A full consent step is tracked as TODO C1 in audit 05-tools-sandbox.md.\n if (_extensionBinding && !_extensionBinding.isAuthor) {\n try {\n console.warn(\n '[agent-native] Shared extension — running with viewer\\\\'s session. ' +\n 'Author: ' + (_extensionBinding.authorEmail || '<unknown>') + '. ' +\n 'Bridge calls (appAction, dbExec, extensionFetch) execute under ' +\n 'your account; they are gated by your permissions, not the ' +\n 'author\\\\'s. Do not run untrusted shared extensions.',\n );\n } catch (_) {}\n }\n\n var extensionData = {\n\t async list(collection, opts) {\n\t var limit = (opts && opts.limit) || 100;\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '?limit=' + limit + '&scope=' + scope);\n\t if (!res.ok) throw new Error('Failed to list extension data');\n\t return res.body;\n\t },\n async get(collection, id, opts) {\n var scope = (opts && opts.scope) || 'user';\n var items = await this.list(collection, { scope: scope });\n return (items || []).find(function(item) { return item.id === id; }) || null;\n },\n async set(collection, id, data, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection), {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({ id: id, data: data, scope: scope }),\n\t });\n\t if (!res.ok) throw new Error('Failed to save extension data');\n\t return res.body;\n\t },\n\t async remove(collection, id, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '/' + encodeURIComponent(id) + '?scope=' + scope, {\n\t method: 'DELETE',\n\t });\n\t if (!res.ok) throw new Error('Failed to delete extension data');\n\t return res.body;\n\t },\n\t };\n\n\t // Legacy aliases — extension bodies authored before the rename use\n\t // toolFetch, toolData, toolId. Keep these working forever.\n\t var toolFetch = extensionFetch;\n\t var toolData = extensionData;\n\t var _toolId = _extensionId;\n\t window.agentNative = Object.assign(window.agentNative || {}, {\n\t extensionId: _extensionId,\n\t extensionBinding: _extensionBinding,\n\t appAction: appAction,\n\t appFetch: appFetch,\n\t dbQuery: dbQuery,\n\t dbExec: dbExec,\n\t extensionFetch: extensionFetch,\n\t extensionData: extensionData,\n\t data: extensionData,\n\t sendToChat: sendToChat,\n\t chat: Object.assign({}, (window.agentNative && window.agentNative.chat) || {}, {\n\t send: sendToChat,\n\t }),\n\t ui: Object.assign({}, (window.agentNative && window.agentNative.ui) || {}, {\n\t output: outputToUi,\n\t }),\n\t });\n\t window.sendToAgentChat = sendToChat;\n\t </script>\n\t <style>\n\t #__extension-error-toast {\n\t display: none;\n\t position: fixed;\n\t bottom: 16px;\n\t right: 16px;\n\t max-width: 420px;\n\t background: hsl(var(--destructive));\n\t color: hsl(var(--destructive-foreground));\n\t border: 1px solid hsl(var(--destructive) / .6);\n\t border-radius: calc(var(--radius, .5rem) + 2px);\n\t padding: 12px 16px;\n\t font-size: 13px;\n\t line-height: 1.4;\n\t font-family: 'Inter', sans-serif;\n\t z-index: 9999;\n\t box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.1);\n\t animation: __toast-in 0.2s ease-out;\n\t }\n\t @keyframes __toast-in {\n\t from { opacity: 0; transform: translateY(8px); }\n\t to { opacity: 1; transform: translateY(0); }\n\t }\n\t </style>\n\t <script>\n\t // Extension-point slot context: when an extension is rendered embedded\n\t // inside an ExtensionSlot, the host pushes a context object via\n\t // postMessage. Extensions read it synchronously via window.slotContext\n\t // or subscribe to changes via window.onSlotContext(fn). When rendered\n\t // full-page (no ?slot= param), slotContext stays null and extensions\n\t // branch on that.\n\t window.slotContext = null;\n\t var _slotContextSubscribers = [];\n\t window.onSlotContext = function(fn) {\n\t _slotContextSubscribers.push(fn);\n\t if (window.slotContext !== null) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t }\n\t return function() {\n\t _slotContextSubscribers = _slotContextSubscribers.filter(function(f) { return f !== fn; });\n\t };\n\t };\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-slot-context') return;\n\t window.slotContext = msg.context || {};\n\t _slotContextSubscribers.forEach(function(fn) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t });\n\t });\n\n\t // Auto-resize iframe renders. Persisted extension slots include ?slot=;\n\t // transient inline chat UI uses srcdoc, so detect that by parent frame.\n\t // The host listens for agent-native-extension-resize and adjusts height.\n\t if (new URLSearchParams(location.search).get('slot') || window.parent !== window) {\n\t var _lastH = 0;\n\t var _reportHeight = function() {\n\t var h = Math.max(\n\t document.documentElement.scrollHeight,\n\t document.body ? document.body.scrollHeight : 0,\n\t );\n\t if (h !== _lastH) {\n\t _lastH = h;\n\t window.parent.postMessage({ type: 'agent-native-extension-resize', height: h }, '*');\n\t }\n\t };\n\t if (typeof ResizeObserver !== 'undefined') {\n\t var _ro = new ResizeObserver(_reportHeight);\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _ro.observe(document.documentElement);\n\t if (document.body) _ro.observe(document.body);\n\t });\n\t }\n\t // Initial reports — Alpine takes a tick to render after DOMContentLoaded.\n\t setTimeout(_reportHeight, 50);\n\t setTimeout(_reportHeight, 250);\n\t }\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-theme-update') return;\n\t var root = document.documentElement;\n\t if (msg.isDark !== undefined) {\n\t if (msg.isDark) root.classList.add('dark');\n\t else root.classList.remove('dark');\n\t }\n\t var vars = msg.vars || {};\n\t for (var key in vars) {\n\t if (vars.hasOwnProperty(key)) {\n\t root.style.setProperty(key, vars[key]);\n\t }\n\t }\n\t });\n\n\t document.addEventListener('keydown', function(e) {\n\t if ((e.metaKey || e.ctrlKey) && !e.altKey) {\n\t var key = (e.key || '').toLowerCase();\n\t if (key === 'c' || key === 'v' || key === 'x' || key === 'a' || key === 'z' || key === 'y') return;\n\t e.preventDefault();\n\t e.stopPropagation();\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: e.metaKey, ctrlKey: e.ctrlKey,\n\t shiftKey: e.shiftKey, altKey: e.altKey,\n\t }, '*');\n\t return;\n\t }\n\t if (e.key === 'Escape') {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: false, ctrlKey: false,\n\t shiftKey: false, altKey: false,\n\t }, '*');\n\t }\n\t });\n\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _renderErrorToast();\n\t var fixBtn = document.getElementById('__extension-error-fix');\n\t if (fixBtn) {\n\t fixBtn.addEventListener('click', function() {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-error-fix',\n\t errors: _extensionErrors,\n\t errorDetails: _extensionErrorDetails,\n\t consoleLogs: _consoleLogs.slice(-30),\n\t networkLogs: _networkLogs.slice(-15)\n\t }, '*');\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t var dismissBtn = document.getElementById('__extension-error-dismiss');\n\t if (dismissBtn) {\n\t dismissBtn.addEventListener('click', function() {\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t });\n\t </script>\n\t</head>\n\t<body${extensionId ? ` data-extension-id=\"${extensionIdAttr}\" data-tool-id=\"${extensionIdAttr}\"` : \"\"} class=\"text-foreground\">\n\t${content}\n\t<div id=\"__extension-error-toast\">\n\t <div style=\"display:flex;align-items:flex-start;gap:8px;\">\n\t <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0;margin-top:1px;\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/></svg>\n\t <span id=\"__extension-error-msg\" style=\"flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;\"></span>\n\t <button id=\"__extension-error-fix\" style=\"cursor:pointer;border:none;background:rgba(255,255,255,.9);color:hsl(0 84.2% 40%);font-size:12px;font-weight:500;padding:4px 12px;border-radius:4px;flex-shrink:0;\">Fix</button>\n\t <button id=\"__extension-error-dismiss\" style=\"cursor:pointer;border:none;background:transparent;color:inherit;font-size:16px;padding:2px 6px;opacity:0.7;flex-shrink:0;\">&#215;</button>\n\t </div>\n\t</div>\n\t</body>\n\t</html>`;\n}\n\nfunction escapeHtmlAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n"]}
1
+ {"version":3,"file":"html-shell.js","sourceRoot":"","sources":["../../src/extensions/html-shell.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AAE/E,MAAM,yBAAyB,GAC7B,mXAAmX,CAAC;AAEtX,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,QAAQ;IACR,0BAA0B;IAC1B,4BAA4B;IAC5B,oBAAoB;IACpB,oBAAoB;IACpB,gCAAgC;IAChC,0CAA0C;CAC3C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,yBAAyB,oBAAoB,yBAAyB,GAAG,CAAC;AAEjH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAqEnE,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,SAAiB,EACjB,MAAe,EACf,WAAoB,EACpB,OAAgC;IAEhC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC1D,MAAM,eAAe,GAAG,mBAAmB,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAChC,OAAO,IAAI;QACT,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,EAAE;QACf,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;KACd,CACF,CAAC;IAEF,OAAO;iBACQ,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE;;;;wDAIU,yBAAyB;IAC7E,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,uDAAuD,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmFlI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA4TK,eAAe;8BACV,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8OtC,iCAAiC,EAAE;;QAE9B,WAAW,CAAC,CAAC,CAAC,uBAAuB,eAAe,mBAAmB,eAAe,GAAG,CAAC,CAAC,CAAC,EAAE;GACnG,OAAO;;;;;;;;;;SAUD,CAAC;AACV,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["import { buildSessionReplayIframeBootstrap } from \"./session-replay-iframe.js\";\n\nconst EXTENSION_IFRAME_CSP_BASE =\n \"default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';\";\n\nexport const EXTENSION_FRAME_ANCESTORS = [\n \"'self'\",\n \"https://agent-native.com\",\n \"https://*.agent-native.com\",\n \"http://localhost:*\",\n \"http://127.0.0.1:*\",\n \"https://*.claudemcpcontent.com\",\n \"https://*.web-sandbox.oaiusercontent.com\",\n].join(\" \");\n\nexport const EXTENSION_IFRAME_CSP = `${EXTENSION_IFRAME_CSP_BASE} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;\n\nexport const EXTENSION_IFRAME_META_CSP = EXTENSION_IFRAME_CSP_BASE;\n\n/**\n * SECURITY — EXTENSION CONTENT IS UNTRUSTED.\n *\n * `${content}` (line ~Body) interpolates raw HTML/JS authored by a user. This\n * file is the boundary between framework-controlled HTML and user-controlled\n * HTML. Two non-negotiable invariants for every change here:\n *\n * 1. The iframe MUST be rendered with a `sandbox` attribute that does NOT\n * include `allow-same-origin`. The viewer (`ExtensionViewer.tsx`,\n * `EmbeddedExtension.tsx`) normalizes the sandbox through\n * `normalizeAgentNativeExtensionSandbox()`, which permits scripts, forms,\n * popups, and downloads but strips `allow-same-origin`. Adding\n * `allow-same-origin` would give the extension full DOM access to the\n * parent window via cross-frame script.\n *\n * 2. Every reachable parent action must treat the postMessage payload as\n * hostile. The bridge in `iframe-bridge.ts` enforces a path allowlist,\n * header sanitization, and method allowlist; do not relax those gates\n * for \"convenience\" in this file or any caller.\n *\n * For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the\n * `extensions` skill. When in doubt, fail closed.\n *\n * BACKWARDS COMPAT — the iframe injects helpers under both their canonical\n * `extension*` names (`extensionFetch`, `extensionData`, `extensionId`,\n * `extensionBinding`) AND legacy `tool*` aliases (`toolFetch`, `toolData`,\n * `toolId`, `toolBinding`) so existing user-authored extension bodies that\n * pre-date the rename keep working. Same for layout opt-ins:\n * `data-extension-layout=\"full-bleed\"` / `data-extension-padding=\"none\"` /\n * class `agent-native-extension-bleed` / CSS var\n * `--agent-native-extension-padding` are canonical; the `data-tool-*`,\n * `agent-native-tool-bleed`, and `--agent-native-tool-padding` variants are\n * accepted as aliases.\n */\n\nexport interface ExtensionRenderBinding {\n /** Email of the user who authored / owns the extension. */\n authorEmail: string;\n /** Email of the user currently viewing/running the extension. */\n viewerEmail: string;\n /** True when viewer === author. */\n isAuthor: boolean;\n /**\n * Resolved role for the viewer (\"owner\" | \"admin\" | \"editor\" | \"viewer\").\n *\n * TODO(security, audit H4): the host-side bridge does not yet gate any\n * helper based on this value — every viewer gets the same powers as the\n * author. The role is plumbed through so a follow-up PR can constrain\n * `appAction` / `dbExec` / `extensionFetch` for non-author viewers (and\n * eventually require an explicit consent step before running a shared\n * extension, audit C1). For now this is metadata only.\n */\n role: \"owner\" | \"admin\" | \"editor\" | \"viewer\";\n /** Where the extension definition came from. Database extensions are the default. */\n source?: \"database\" | \"local-files\";\n /**\n * Fine-grained helper permissions for local file extensions. Database-backed\n * extensions keep using the role table in the parent bridge.\n */\n permissions?: {\n appActions?: string[];\n extensionData?: boolean;\n sql?: boolean;\n externalFetch?: boolean;\n };\n}\n\nexport function buildExtensionHtml(\n content: string,\n themeVars: string,\n isDark: boolean,\n extensionId?: string,\n binding?: ExtensionRenderBinding,\n): string {\n const extensionIdJson = JSON.stringify(extensionId ?? \"\");\n const extensionIdAttr = escapeHtmlAttribute(extensionId ?? \"\");\n const bindingJson = JSON.stringify(\n binding ?? {\n authorEmail: \"\",\n viewerEmail: \"\",\n isAuthor: true,\n role: \"owner\",\n },\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\"${isDark ? ' class=\"dark\"' : \"\"}>\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta http-equiv=\"Content-Security-Policy\" content=\"${EXTENSION_IFRAME_META_CSP}\" />\n ${binding && !binding.isAuthor ? `<meta name=\"agent-native-extension-author\" content=\"${escapeHtmlAttribute(binding.authorEmail)}\" />` : \"\"}\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap\" rel=\"stylesheet\" />\n <script>\n var _extensionErrors = [];\n var _extensionErrorDetails = [];\n var _consoleLogs = [];\n var _networkLogs = [];\n\n var _origConsole = { log: console.log, warn: console.warn, error: console.error, info: console.info };\n function _wrapConsole(level, orig) {\n return function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }\n catch(e) { return String(a); }\n }).join(' ');\n if (_consoleLogs.length >= 50) _consoleLogs.shift();\n _consoleLogs.push({ level: level, message: msg });\n orig.apply(console, arguments);\n };\n }\n console.log = _wrapConsole('log', _origConsole.log);\n console.warn = _wrapConsole('warn', _origConsole.warn);\n console.error = _wrapConsole('error', _origConsole.error);\n console.info = _wrapConsole('info', _origConsole.info);\n\n function _collectError(message, stack) {\n if (!message) return;\n if (message === 'Script error.' || message === 'Script error') message = 'Runtime error';\n if (_extensionErrors.indexOf(message) !== -1) return;\n _extensionErrors.push(message);\n _extensionErrorDetails.push({ message: message, stack: stack || '' });\n _renderErrorToast();\n }\n\n function _renderErrorToast() {\n var toast = document.getElementById('__extension-error-toast');\n if (!toast) return;\n var msg = document.getElementById('__extension-error-msg');\n if (!msg || _extensionErrors.length === 0) return;\n if (_extensionErrors.length === 1) {\n msg.textContent = _extensionErrors[0];\n } else {\n msg.textContent = _extensionErrors.length + ' errors — ' + _extensionErrors[_extensionErrors.length - 1];\n }\n toast.style.display = 'block';\n }\n\n window.addEventListener('error', function(event) {\n var msg = event.message || '';\n if (msg.indexOf('Alpine Expression Error') === 0) return;\n var stack = event.error && event.error.stack ? event.error.stack : '';\n _collectError(msg, stack);\n });\n\n window.addEventListener('unhandledrejection', function(event) {\n var msg = event.reason && event.reason.message ? event.reason.message : String(event.reason);\n var stack = event.reason && event.reason.stack ? event.reason.stack : '';\n _collectError(msg, stack);\n });\n </script>\n <!--\n SECURITY: pinned to exact patch versions + SRI integrity hashes. A\n malicious republish of @tailwindcss/browser@4.x or alpinejs@3.x would\n otherwise inject code into every extension. To bump these versions:\n 1. npm view @tailwindcss/browser version (or alpinejs)\n 2. curl -sL https://cdn.jsdelivr.net/npm/@tailwindcss/browser@<v> \\\\\n | openssl dgst -sha384 -binary | openssl base64 -A\n 3. Update the URL + integrity hash below in lockstep.\n -->\n <script\n src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4\"\n integrity=\"sha384-yNSZBFvuOWcmww494a9+1zNuvgUGEXoWkein7cxP8wHUTi3iXCU4vJ7hr3tzBCml\"\n crossorigin=\"anonymous\"\n ></script>\n <script\n defer\n src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js\"\n integrity=\"sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be\"\n crossorigin=\"anonymous\"\n ></script>\n <style>${themeVars}</style>\n <style type=\"text/tailwindcss\">\n @custom-variant dark (&:where(.dark, .dark *));\n @theme {\n --color-border: hsl(var(--border));\n --color-input: hsl(var(--input));\n --color-ring: hsl(var(--ring));\n --color-background: hsl(var(--background));\n --color-foreground: hsl(var(--foreground));\n --color-primary: hsl(var(--primary));\n --color-primary-foreground: hsl(var(--primary-foreground));\n --color-secondary: hsl(var(--secondary));\n --color-secondary-foreground: hsl(var(--secondary-foreground));\n --color-destructive: hsl(var(--destructive));\n --color-destructive-foreground: hsl(var(--destructive-foreground));\n --color-muted: hsl(var(--muted));\n --color-muted-foreground: hsl(var(--muted-foreground));\n --color-accent: hsl(var(--accent));\n --color-accent-foreground: hsl(var(--accent-foreground));\n --color-popover: hsl(var(--popover));\n --color-popover-foreground: hsl(var(--popover-foreground));\n --color-card: hsl(var(--card));\n --color-card-foreground: hsl(var(--card-foreground));\n --color-sidebar: hsl(var(--sidebar-background));\n --color-sidebar-foreground: hsl(var(--sidebar-foreground));\n --color-sidebar-primary: hsl(var(--sidebar-primary));\n --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));\n --color-sidebar-accent: hsl(var(--sidebar-accent));\n --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));\n --color-sidebar-border: hsl(var(--sidebar-border));\n --color-sidebar-ring: hsl(var(--sidebar-ring));\n --radius-lg: var(--radius);\n --radius-md: calc(var(--radius) - 2px);\n --radius-sm: calc(var(--radius) - 4px);\n }\n </style>\n\t <style>\n\t *, *::before, *::after { border-color: hsl(var(--border)); }\n\t html, body {\n\t /* Transparent so the iframe inherits the host surface (dashboard panel,\n\t sidebar, chat) instead of painting the browser's default white canvas.\n\t The dark class still flips the theme vars; content paints its own\n\t bg-background / bg-card surfaces. */\n\t background: transparent;\n\t }\n\t body {\n\t --agent-native-extension-padding: clamp(16px, 2vw, 24px);\n\t /* Legacy alias for pre-rename extension content (do not remove). */\n\t --agent-native-tool-padding: var(--agent-native-extension-padding);\n\t box-sizing: border-box;\n\t color: hsl(var(--foreground));\n\t font-family: 'Inter', sans-serif;\n\t margin: 0;\n\t min-height: 100vh;\n\t padding: var(--agent-native-extension-padding);\n\t }\n\t body:has(> [data-extension-layout=\"full-bleed\"]),\n\t body:has(> [data-extension-padding=\"none\"]),\n\t body:has(> .agent-native-extension-bleed),\n\t /* Legacy aliases (do not remove). */\n\t body:has(> [data-tool-layout=\"full-bleed\"]),\n\t body:has(> [data-tool-padding=\"none\"]),\n\t body:has(> .agent-native-tool-bleed) {\n\t padding: 0;\n\t }\n\t </style>\n\t <script>\n\t var _extensionRequestSeq = 0;\n\t var _extensionPendingRequests = {};\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var message = event.data || {};\n\t if (\n\t message.type !== 'agent-native-extension-response' &&\n\t message.type !== 'agent-native-tool-response'\n\t ) return;\n\t var pending = _extensionPendingRequests[message.requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[message.requestId];\n\t if (message.error) {\n\t pending.reject(new Error(message.error));\n\t } else {\n\t pending.resolve(message.response);\n\t }\n\t });\n\n\t function hostRequest(path, options) {\n\t options = options || {};\n\t return new Promise(function(resolve, reject) {\n\t var requestId = 'extension-req-' + (++_extensionRequestSeq);\n\t _extensionPendingRequests[requestId] = { resolve: resolve, reject: reject };\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-request',\n\t requestId: requestId,\n\t path: path,\n\t options: {\n\t method: options.method || 'GET',\n\t headers: options.headers || {},\n\t body: options.body,\n\t },\n\t }, '*');\n\t setTimeout(function() {\n\t var pending = _extensionPendingRequests[requestId];\n\t if (!pending) return;\n\t delete _extensionPendingRequests[requestId];\n\t pending.reject(new Error('Extension host request timed out'));\n\t }, 30000);\n\t });\n\t }\n\n\t var _origHostRequest = hostRequest;\n\t hostRequest = function(path, options) {\n\t var entry = { path: path, method: (options && options.method) || 'GET' };\n\t return _origHostRequest(path, options).then(function(res) {\n\t entry.ok = res.ok;\n\t entry.status = res.status;\n\t if (!res.ok && res.body) {\n\t try { entry.error = typeof res.body === 'string' ? res.body.slice(0, 200) : JSON.stringify(res.body).slice(0, 200); } catch(e) {}\n\t }\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t return res;\n\t }, function(err) {\n\t entry.ok = false;\n\t entry.error = err.message;\n\t if (_networkLogs.length >= 20) _networkLogs.shift();\n\t _networkLogs.push(entry);\n\t throw err;\n\t });\n\t };\n\n\t function extensionFetch(url, options) {\n\t var opts = options || {};\n\t return hostRequest('/_agent-native/extensions/proxy', {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({\n\t url: url,\n method: opts.method || 'GET',\n headers: opts.headers,\n body: opts.body,\n }),\n\t }).then(function(res) {\n\t var data = res.body;\n\t if (data.error && data.status === undefined) {\n\t throw new Error(data.error);\n\t }\n return {\n ok: data.status >= 200 && data.status < 300,\n status: data.status,\n\t json: function() { return Promise.resolve(data.body); },\n\t text: function() { return Promise.resolve(typeof data.body === 'string' ? data.body : JSON.stringify(data.body)); },\n\t };\n\t });\n\t }\n\n\t function _appendActionQuery(path, params) {\n\t var search = new URLSearchParams();\n\t params = params || {};\n\t Object.keys(params).forEach(function(key) {\n\t var value = params[key];\n\t if (value === undefined || value === null) return;\n\t if (Array.isArray(value)) {\n\t value.forEach(function(item) {\n\t if (item !== undefined && item !== null) {\n\t search.append(key, String(item));\n\t }\n\t });\n\t return;\n\t }\n\t search.set(key, String(value));\n\t });\n\t var qs = search.toString();\n\t return qs ? path + '?' + qs : path;\n\t }\n\n\t function _methodHintFromActionResponse(res) {\n\t if (!res || res.status !== 405) return null;\n\t var body = res.body || {};\n\t var message = typeof body === 'string' ? body : body.error;\n\t if (!message) return null;\n\t var match = String(message).match(/Use (GET|POST|PUT|PATCH|DELETE|HEAD)\\\\.?/i);\n\t return match ? match[1].toUpperCase() : null;\n\t }\n\n\t async function appAction(name, params) {\n\t params = params || {};\n\t if (name === 'navigate') {\n\t var navRes = await hostRequest('/_agent-native/application-state/navigate', {\n\t method: 'PUT',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\t if (!navRes.ok) {\n\t var navErr = navRes.body || { error: navRes.statusText };\n\t throw new Error(navErr.error || 'Navigation failed: ' + navRes.status);\n\t }\n\t return navRes.body;\n\t }\n\t var path = '/_agent-native/actions/' + encodeURIComponent(name);\n\t var res = await hostRequest(path, {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify(params),\n\t });\n\n\t var retryMethod = _methodHintFromActionResponse(res);\n\t if (!res.ok && retryMethod && retryMethod !== 'POST') {\n\t var retryPath = path;\n\t var retryOptions = {\n\t method: retryMethod,\n\t headers: { 'Content-Type': 'application/json' },\n\t };\n\t if (retryMethod === 'GET' || retryMethod === 'HEAD') {\n\t retryPath = _appendActionQuery(path, params);\n\t } else {\n\t retryOptions.body = JSON.stringify(params);\n\t }\n\t res = await hostRequest(retryPath, retryOptions);\n\t }\n\n\t if (!res.ok) {\n\t var err = res.body || {};\n\t var rawError = typeof err === 'string' ? err : err.error;\n\t var message = (typeof rawError === 'string' && rawError.trim())\n\t ? rawError\n\t : (res.status === 404\n\t ? \"Action '\" + name + \"' is not available over HTTP (404). It may be agent-only (http: false); expose it with an HTTP-mounted action to call it from an extension.\"\n\t : \"Action '\" + name + \"' failed (\" + (res.status || 'network error') + \")\");\n\t throw new Error(message);\n\t }\n\t return res.body;\n\t }\n\n\t async function appFetch(path, options) {\n\t options = options || {};\n\t var res = await hostRequest(path, {\n\t ...options,\n\t headers: {\n\t 'Content-Type': 'application/json',\n\t ...(options.headers || {}),\n\t },\n\t });\n\t if (!res.ok) {\n\t var err = typeof res.body === 'object' && res.body ? res.body : { error: res.statusText };\n\t throw new Error(err.error || 'Request failed: ' + res.status);\n\t }\n\t return res.body;\n\t }\n\n\t function sendToChat(message, options) {\n\t options = options || {};\n\t var text = typeof message === 'string' ? message : JSON.stringify(message);\n\t window.parent.postMessage({\n\t type: 'agent-native-send-to-chat',\n\t message: text,\n\t context: options.context,\n\t submit: options.submit !== false,\n\t openSidebar: options.openSidebar !== false,\n\t }, '*');\n\t return { ok: true };\n\t }\n\n\t function inlineUiOutputKey() {\n\t var safeId = String(_extensionId || 'unknown').replace(/[^A-Za-z0-9_:-]/g, '') || 'unknown';\n\t return 'inline-ui:' + safeId + ':output';\n\t }\n\n\t async function outputToUi(value, options) {\n\t options = options || {};\n\t var key = inlineUiOutputKey();\n\t var payload = {\n\t value: value,\n\t updatedAt: new Date().toISOString(),\n\t extensionId: _extensionId,\n\t source: 'inline-ui',\n\t };\n\t if (options.label !== undefined) payload.label = options.label;\n\t if (options.context !== undefined) payload.context = options.context;\n\t if (options.meta !== undefined) payload.meta = options.meta;\n\t var output = await appFetch('/_agent-native/application-state/' + key, {\n\t method: 'PUT',\n\t headers: { 'X-Request-Source': 'inline-ui' },\n\t body: JSON.stringify(payload),\n\t });\n\t try {\n\t window.parent.postMessage({\n\t type: 'agent-native-ui-output',\n\t extensionId: _extensionId,\n\t key: key,\n\t value: value,\n\t output: output,\n\t }, '*');\n\t } catch (_) {}\n\t return { ok: true, key: key, output: output };\n\t }\n\n async function dbQuery(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/query', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n async function dbExec(sql, args) {\n var body = { sql: sql };\n if (args) body.args = args;\n return appFetch('/_agent-native/extensions/sql/exec', {\n method: 'POST',\n body: JSON.stringify(body),\n });\n }\n\n var _extensionId = ${extensionIdJson};\n var _extensionBinding = ${bindingJson};\n window.extensionBinding = _extensionBinding;\n // Legacy alias for extension bodies authored before the rename.\n window.toolBinding = _extensionBinding;\n // SECURITY (audit H4): announce the resolved binding to the parent so the\n // host bridge can gate dangerous helpers based on viewer role. Sent\n // BEFORE the user-authored content has a chance to run, so a malicious\n // extension body cannot suppress or rewrite the announcement. The parent\n // ignores subsequent announcements for the same iframe; see\n // ExtensionViewer.tsx / EmbeddedExtension.tsx.\n try {\n window.parent.postMessage(\n {\n type: 'agent-native-extension-binding',\n extensionId: _extensionId,\n binding: _extensionBinding,\n },\n '*',\n );\n } catch (_) {}\n // SECURITY: when the viewer is not the author of this extension, emit a\n // clear console warning. The bridge currently runs every helper with the\n // viewer's session — a malicious shared extension can call any action,\n // read any owned table row in scope, and resolve any user-scope secret.\n // A full consent step is tracked as TODO C1 in audit 05-tools-sandbox.md.\n if (_extensionBinding && !_extensionBinding.isAuthor) {\n try {\n console.warn(\n '[agent-native] Shared extension — running with viewer\\\\'s session. ' +\n 'Author: ' + (_extensionBinding.authorEmail || '<unknown>') + '. ' +\n 'Bridge calls (appAction, dbExec, extensionFetch) execute under ' +\n 'your account; they are gated by your permissions, not the ' +\n 'author\\\\'s. Do not run untrusted shared extensions.',\n );\n } catch (_) {}\n }\n\n var extensionData = {\n\t async list(collection, opts) {\n\t var limit = (opts && opts.limit) || 100;\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '?limit=' + limit + '&scope=' + scope);\n\t if (!res.ok) throw new Error('Failed to list extension data');\n\t return res.body;\n\t },\n async get(collection, id, opts) {\n var scope = (opts && opts.scope) || 'user';\n var items = await this.list(collection, { scope: scope });\n return (items || []).find(function(item) { return item.id === id; }) || null;\n },\n async set(collection, id, data, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection), {\n\t method: 'POST',\n\t headers: { 'Content-Type': 'application/json' },\n\t body: JSON.stringify({ id: id, data: data, scope: scope }),\n\t });\n\t if (!res.ok) throw new Error('Failed to save extension data');\n\t return res.body;\n\t },\n\t async remove(collection, id, opts) {\n\t var scope = (opts && opts.scope) || 'user';\n\t var res = await hostRequest('/_agent-native/extensions/data/' + _extensionId + '/' + encodeURIComponent(collection) + '/' + encodeURIComponent(id) + '?scope=' + scope, {\n\t method: 'DELETE',\n\t });\n\t if (!res.ok) throw new Error('Failed to delete extension data');\n\t return res.body;\n\t },\n\t };\n\n\t // Legacy aliases — extension bodies authored before the rename use\n\t // toolFetch, toolData, toolId. Keep these working forever.\n\t var toolFetch = extensionFetch;\n\t var toolData = extensionData;\n\t var _toolId = _extensionId;\n\t window.agentNative = Object.assign(window.agentNative || {}, {\n\t extensionId: _extensionId,\n\t extensionBinding: _extensionBinding,\n\t appAction: appAction,\n\t appFetch: appFetch,\n\t dbQuery: dbQuery,\n\t dbExec: dbExec,\n\t extensionFetch: extensionFetch,\n\t extensionData: extensionData,\n\t data: extensionData,\n\t sendToChat: sendToChat,\n\t chat: Object.assign({}, (window.agentNative && window.agentNative.chat) || {}, {\n\t send: sendToChat,\n\t }),\n\t ui: Object.assign({}, (window.agentNative && window.agentNative.ui) || {}, {\n\t output: outputToUi,\n\t }),\n\t });\n\t window.sendToAgentChat = sendToChat;\n\t </script>\n\t <style>\n\t #__extension-error-toast {\n\t display: none;\n\t position: fixed;\n\t bottom: 16px;\n\t right: 16px;\n\t max-width: 420px;\n\t background: hsl(var(--destructive));\n\t color: hsl(var(--destructive-foreground));\n\t border: 1px solid hsl(var(--destructive) / .6);\n\t border-radius: calc(var(--radius, .5rem) + 2px);\n\t padding: 12px 16px;\n\t font-size: 13px;\n\t line-height: 1.4;\n\t font-family: 'Inter', sans-serif;\n\t z-index: 9999;\n\t box-shadow: 0 4px 12px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.1);\n\t animation: __toast-in 0.2s ease-out;\n\t }\n\t @keyframes __toast-in {\n\t from { opacity: 0; transform: translateY(8px); }\n\t to { opacity: 1; transform: translateY(0); }\n\t }\n\t </style>\n\t <script>\n\t // Extension-point slot context: when an extension is rendered embedded\n\t // inside an ExtensionSlot, the host pushes a context object via\n\t // postMessage. Extensions read it synchronously via window.slotContext\n\t // or subscribe to changes via window.onSlotContext(fn). When rendered\n\t // full-page (no ?slot= param), slotContext stays null and extensions\n\t // branch on that.\n\t window.slotContext = null;\n\t var _slotContextSubscribers = [];\n\t window.onSlotContext = function(fn) {\n\t _slotContextSubscribers.push(fn);\n\t if (window.slotContext !== null) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t }\n\t return function() {\n\t _slotContextSubscribers = _slotContextSubscribers.filter(function(f) { return f !== fn; });\n\t };\n\t };\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-slot-context') return;\n\t window.slotContext = msg.context || {};\n\t _slotContextSubscribers.forEach(function(fn) {\n\t try { fn(window.slotContext); } catch(_) {}\n\t });\n\t });\n\n\t // Auto-resize iframe renders. Persisted extension slots include ?slot=;\n\t // transient inline chat UI uses srcdoc, so detect that by parent frame.\n\t // The host listens for agent-native-extension-resize and adjusts height.\n\t if (new URLSearchParams(location.search).get('slot') || window.parent !== window) {\n\t var _lastH = 0;\n\t var _reportHeight = function() {\n\t var h = Math.max(\n\t document.documentElement.scrollHeight,\n\t document.body ? document.body.scrollHeight : 0,\n\t );\n\t if (h !== _lastH) {\n\t _lastH = h;\n\t window.parent.postMessage({ type: 'agent-native-extension-resize', height: h }, '*');\n\t }\n\t };\n\t if (typeof ResizeObserver !== 'undefined') {\n\t var _ro = new ResizeObserver(_reportHeight);\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _ro.observe(document.documentElement);\n\t if (document.body) _ro.observe(document.body);\n\t });\n\t }\n\t // Initial reports — Alpine takes a tick to render after DOMContentLoaded.\n\t setTimeout(_reportHeight, 50);\n\t setTimeout(_reportHeight, 250);\n\t }\n\n\t window.addEventListener('message', function(event) {\n\t if (event.source !== window.parent) return;\n\t var msg = event.data;\n\t if (!msg || msg.type !== 'agent-native-theme-update') return;\n\t var root = document.documentElement;\n\t if (msg.isDark !== undefined) {\n\t if (msg.isDark) root.classList.add('dark');\n\t else root.classList.remove('dark');\n\t }\n\t var vars = msg.vars || {};\n\t for (var key in vars) {\n\t if (vars.hasOwnProperty(key)) {\n\t root.style.setProperty(key, vars[key]);\n\t }\n\t }\n\t });\n\n\t document.addEventListener('keydown', function(e) {\n\t if ((e.metaKey || e.ctrlKey) && !e.altKey) {\n\t var key = (e.key || '').toLowerCase();\n\t if (key === 'c' || key === 'v' || key === 'x' || key === 'a' || key === 'z' || key === 'y') return;\n\t e.preventDefault();\n\t e.stopPropagation();\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: e.metaKey, ctrlKey: e.ctrlKey,\n\t shiftKey: e.shiftKey, altKey: e.altKey,\n\t }, '*');\n\t return;\n\t }\n\t if (e.key === 'Escape') {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-keydown',\n\t key: e.key, code: e.code,\n\t metaKey: false, ctrlKey: false,\n\t shiftKey: false, altKey: false,\n\t }, '*');\n\t }\n\t });\n\n\t document.addEventListener('DOMContentLoaded', function() {\n\t _renderErrorToast();\n\t var fixBtn = document.getElementById('__extension-error-fix');\n\t if (fixBtn) {\n\t fixBtn.addEventListener('click', function() {\n\t window.parent.postMessage({\n\t type: 'agent-native-extension-error-fix',\n\t errors: _extensionErrors,\n\t errorDetails: _extensionErrorDetails,\n\t consoleLogs: _consoleLogs.slice(-30),\n\t networkLogs: _networkLogs.slice(-15)\n\t }, '*');\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t var dismissBtn = document.getElementById('__extension-error-dismiss');\n\t if (dismissBtn) {\n\t dismissBtn.addEventListener('click', function() {\n\t document.getElementById('__extension-error-toast').style.display = 'none';\n\t });\n\t }\n\t });\n\t </script>\n\t${buildSessionReplayIframeBootstrap()}\n\t</head>\n\t<body${extensionId ? ` data-extension-id=\"${extensionIdAttr}\" data-tool-id=\"${extensionIdAttr}\"` : \"\"} class=\"text-foreground\">\n\t${content}\n\t<div id=\"__extension-error-toast\">\n\t <div style=\"display:flex;align-items:flex-start;gap:8px;\">\n\t <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"flex-shrink:0;margin-top:1px;\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/></svg>\n\t <span id=\"__extension-error-msg\" style=\"flex:1;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;\"></span>\n\t <button id=\"__extension-error-fix\" style=\"cursor:pointer;border:none;background:rgba(255,255,255,.9);color:hsl(0 84.2% 40%);font-size:12px;font-weight:500;padding:4px 12px;border-radius:4px;flex-shrink:0;\">Fix</button>\n\t <button id=\"__extension-error-dismiss\" style=\"cursor:pointer;border:none;background:transparent;color:inherit;font-size:16px;padding:2px 6px;opacity:0.7;flex-shrink:0;\">&#215;</button>\n\t </div>\n\t</div>\n\t</body>\n\t</html>`;\n}\n\nfunction escapeHtmlAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n"]}
@@ -0,0 +1,10 @@
1
+ export declare const RRWEB_RECORD_IFRAME_CDN_URL = "https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js";
2
+ export declare const RRWEB_RECORD_IFRAME_SRI = "sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT";
3
+ /**
4
+ * Installs the cooperative side of rrweb's cross-origin iframe protocol.
5
+ * Nothing is fetched or recorded until a trusted first-party parent asks the
6
+ * frame to start. rrweb then forwards its events directly to that parent.
7
+ */
8
+ export declare function buildSessionReplayIframeBootstrap(): string;
9
+ export declare function injectSessionReplayIframeBootstrap(html: string): string;
10
+ //# sourceMappingURL=session-replay-iframe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-replay-iframe.d.ts","sourceRoot":"","sources":["../../src/extensions/session-replay-iframe.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,2BAA2B,uEAC8B,CAAC;AACvE,eAAO,MAAM,uBAAuB,4EACuC,CAAC;AAE5E;;;;GAIG;AACH,wBAAgB,iCAAiC,IAAI,MAAM,CAoG1D;AAED,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWvE"}
@@ -0,0 +1,121 @@
1
+ import { isTrustedSessionReplayIframeParentOrigin, SESSION_REPLAY_IFRAME_PROBE, SESSION_REPLAY_IFRAME_START, SESSION_REPLAY_IFRAME_STOP, } from "../session-replay-iframe-protocol.js";
2
+ export const RRWEB_RECORD_IFRAME_CDN_URL = "https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js";
3
+ export const RRWEB_RECORD_IFRAME_SRI = "sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT";
4
+ /**
5
+ * Installs the cooperative side of rrweb's cross-origin iframe protocol.
6
+ * Nothing is fetched or recorded until a trusted first-party parent asks the
7
+ * frame to start. rrweb then forwards its events directly to that parent.
8
+ */
9
+ export function buildSessionReplayIframeBootstrap() {
10
+ const probeType = JSON.stringify(SESSION_REPLAY_IFRAME_PROBE);
11
+ const startType = JSON.stringify(SESSION_REPLAY_IFRAME_START);
12
+ const stopType = JSON.stringify(SESSION_REPLAY_IFRAME_STOP);
13
+ const scriptUrl = JSON.stringify(RRWEB_RECORD_IFRAME_CDN_URL);
14
+ const integrity = JSON.stringify(RRWEB_RECORD_IFRAME_SRI);
15
+ return `<script>
16
+ (function() {
17
+ var recorderStop = null;
18
+ var recorderLoad = null;
19
+ var startGeneration = 0;
20
+ var isTrustedParentOrigin = ${isTrustedSessionReplayIframeParentOrigin.toString()};
21
+
22
+ function stopRecorder() {
23
+ startGeneration += 1;
24
+ if (typeof recorderStop === 'function') {
25
+ try { recorderStop(); } catch (_) {}
26
+ }
27
+ recorderStop = null;
28
+ }
29
+
30
+ function getRecord() {
31
+ return window.rrwebRecord && typeof window.rrwebRecord.record === 'function'
32
+ ? window.rrwebRecord.record
33
+ : null;
34
+ }
35
+
36
+ function loadRecorder() {
37
+ if (recorderLoad) return recorderLoad;
38
+ recorderLoad = new Promise(function(resolve, reject) {
39
+ var script = document.createElement('script');
40
+ script.src = ${scriptUrl};
41
+ script.integrity = ${integrity};
42
+ script.crossOrigin = 'anonymous';
43
+ script.referrerPolicy = 'no-referrer';
44
+ script.async = true;
45
+ script.onload = function() {
46
+ var record = getRecord();
47
+ if (record) resolve(record);
48
+ else reject(new Error('rrweb recorder did not initialize'));
49
+ };
50
+ script.onerror = function() {
51
+ recorderLoad = null;
52
+ reject(new Error('rrweb recorder failed to load'));
53
+ };
54
+ document.head.appendChild(script);
55
+ });
56
+ return recorderLoad;
57
+ }
58
+
59
+ function validOptions(options) {
60
+ return !!options &&
61
+ typeof options === 'object' &&
62
+ typeof options.blockSelector === 'string' &&
63
+ typeof options.ignoreSelector === 'string' &&
64
+ typeof options.maskTextSelector === 'string' &&
65
+ typeof options.maskAllInputs === 'boolean' &&
66
+ typeof options.recordCanvas === 'boolean' &&
67
+ typeof options.collectFonts === 'boolean' &&
68
+ typeof options.inlineImages === 'boolean' &&
69
+ !!options.sampling && typeof options.sampling === 'object';
70
+ }
71
+
72
+ function startRecorder(options) {
73
+ if (!validOptions(options)) return;
74
+ stopRecorder();
75
+ var generation = startGeneration;
76
+ loadRecorder().then(function(record) {
77
+ if (generation !== startGeneration) return;
78
+ recorderStop = record({
79
+ blockSelector: options.blockSelector,
80
+ ignoreSelector: options.ignoreSelector,
81
+ maskTextClass: options.maskTextClass,
82
+ maskTextSelector: options.maskTextSelector,
83
+ maskAllInputs: options.maskAllInputs,
84
+ maskInputOptions: options.maskInputOptions,
85
+ recordCanvas: options.recordCanvas,
86
+ collectFonts: options.collectFonts,
87
+ inlineImages: options.inlineImages,
88
+ sampling: options.sampling,
89
+ recordCrossOriginIframes: true
90
+ }) || null;
91
+ }).catch(function() {});
92
+ }
93
+
94
+ window.addEventListener('message', function(event) {
95
+ if (event.source !== window.parent || !isTrustedParentOrigin(event.origin, window.location.href)) return;
96
+ var message = event.data;
97
+ if (!message || typeof message !== 'object') return;
98
+ if (message.type === ${startType}) startRecorder(message.options);
99
+ else if (message.type === ${stopType}) stopRecorder();
100
+ });
101
+ window.addEventListener('pagehide', stopRecorder);
102
+
103
+ if (window.parent !== window) {
104
+ window.parent.postMessage({ type: ${probeType} }, '*');
105
+ }
106
+ })();
107
+ </script>`;
108
+ }
109
+ export function injectSessionReplayIframeBootstrap(html) {
110
+ const bootstrap = buildSessionReplayIframeBootstrap();
111
+ const headClose = html.search(/<\/head\s*>/i);
112
+ if (headClose >= 0) {
113
+ return `${html.slice(0, headClose)}${bootstrap}${html.slice(headClose)}`;
114
+ }
115
+ const bodyOpen = html.search(/<body(?:\s[^>]*)?>/i);
116
+ if (bodyOpen >= 0) {
117
+ return `${html.slice(0, bodyOpen)}${bootstrap}${html.slice(bodyOpen)}`;
118
+ }
119
+ return `${bootstrap}${html}`;
120
+ }
121
+ //# sourceMappingURL=session-replay-iframe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-replay-iframe.js","sourceRoot":"","sources":["../../src/extensions/session-replay-iframe.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wCAAwC,EACxC,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,sCAAsC,CAAC;AAE9C,MAAM,CAAC,MAAM,2BAA2B,GACtC,oEAAoE,CAAC;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAClC,yEAAyE,CAAC;AAE5E;;;;GAIG;AACH,MAAM,UAAU,iCAAiC;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAE1D,OAAO;;;;;oCAK2B,wCAAwC,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;yBAoB9D,SAAS;+BACH,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAyDT,SAAS;oCACJ,QAAQ;;;;;4CAKA,SAAS;;;YAGzC,CAAC;AACb,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,IAAY;IAC7D,MAAM,SAAS,GAAG,iCAAiC,EAAE,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC9C,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;IAC3E,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACpD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,GAAG,SAAS,GAAG,IAAI,EAAE,CAAC;AAC/B,CAAC","sourcesContent":["import {\n isTrustedSessionReplayIframeParentOrigin,\n SESSION_REPLAY_IFRAME_PROBE,\n SESSION_REPLAY_IFRAME_START,\n SESSION_REPLAY_IFRAME_STOP,\n} from \"../session-replay-iframe-protocol.js\";\n\nexport const RRWEB_RECORD_IFRAME_CDN_URL =\n \"https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js\";\nexport const RRWEB_RECORD_IFRAME_SRI =\n \"sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT\";\n\n/**\n * Installs the cooperative side of rrweb's cross-origin iframe protocol.\n * Nothing is fetched or recorded until a trusted first-party parent asks the\n * frame to start. rrweb then forwards its events directly to that parent.\n */\nexport function buildSessionReplayIframeBootstrap(): string {\n const probeType = JSON.stringify(SESSION_REPLAY_IFRAME_PROBE);\n const startType = JSON.stringify(SESSION_REPLAY_IFRAME_START);\n const stopType = JSON.stringify(SESSION_REPLAY_IFRAME_STOP);\n const scriptUrl = JSON.stringify(RRWEB_RECORD_IFRAME_CDN_URL);\n const integrity = JSON.stringify(RRWEB_RECORD_IFRAME_SRI);\n\n return `<script>\n (function() {\n var recorderStop = null;\n var recorderLoad = null;\n var startGeneration = 0;\n var isTrustedParentOrigin = ${isTrustedSessionReplayIframeParentOrigin.toString()};\n\n function stopRecorder() {\n startGeneration += 1;\n if (typeof recorderStop === 'function') {\n try { recorderStop(); } catch (_) {}\n }\n recorderStop = null;\n }\n\n function getRecord() {\n return window.rrwebRecord && typeof window.rrwebRecord.record === 'function'\n ? window.rrwebRecord.record\n : null;\n }\n\n function loadRecorder() {\n if (recorderLoad) return recorderLoad;\n recorderLoad = new Promise(function(resolve, reject) {\n var script = document.createElement('script');\n script.src = ${scriptUrl};\n script.integrity = ${integrity};\n script.crossOrigin = 'anonymous';\n script.referrerPolicy = 'no-referrer';\n script.async = true;\n script.onload = function() {\n var record = getRecord();\n if (record) resolve(record);\n else reject(new Error('rrweb recorder did not initialize'));\n };\n script.onerror = function() {\n recorderLoad = null;\n reject(new Error('rrweb recorder failed to load'));\n };\n document.head.appendChild(script);\n });\n return recorderLoad;\n }\n\n function validOptions(options) {\n return !!options &&\n typeof options === 'object' &&\n typeof options.blockSelector === 'string' &&\n typeof options.ignoreSelector === 'string' &&\n typeof options.maskTextSelector === 'string' &&\n typeof options.maskAllInputs === 'boolean' &&\n typeof options.recordCanvas === 'boolean' &&\n typeof options.collectFonts === 'boolean' &&\n typeof options.inlineImages === 'boolean' &&\n !!options.sampling && typeof options.sampling === 'object';\n }\n\n function startRecorder(options) {\n if (!validOptions(options)) return;\n stopRecorder();\n var generation = startGeneration;\n loadRecorder().then(function(record) {\n if (generation !== startGeneration) return;\n recorderStop = record({\n blockSelector: options.blockSelector,\n ignoreSelector: options.ignoreSelector,\n maskTextClass: options.maskTextClass,\n maskTextSelector: options.maskTextSelector,\n maskAllInputs: options.maskAllInputs,\n maskInputOptions: options.maskInputOptions,\n recordCanvas: options.recordCanvas,\n collectFonts: options.collectFonts,\n inlineImages: options.inlineImages,\n sampling: options.sampling,\n recordCrossOriginIframes: true\n }) || null;\n }).catch(function() {});\n }\n\n window.addEventListener('message', function(event) {\n if (event.source !== window.parent || !isTrustedParentOrigin(event.origin, window.location.href)) return;\n var message = event.data;\n if (!message || typeof message !== 'object') return;\n if (message.type === ${startType}) startRecorder(message.options);\n else if (message.type === ${stopType}) stopRecorder();\n });\n window.addEventListener('pagehide', stopRecorder);\n\n if (window.parent !== window) {\n window.parent.postMessage({ type: ${probeType} }, '*');\n }\n })();\n </script>`;\n}\n\nexport function injectSessionReplayIframeBootstrap(html: string): string {\n const bootstrap = buildSessionReplayIframeBootstrap();\n const headClose = html.search(/<\\/head\\s*>/i);\n if (headClose >= 0) {\n return `${html.slice(0, headClose)}${bootstrap}${html.slice(headClose)}`;\n }\n const bodyOpen = html.search(/<body(?:\\s[^>]*)?>/i);\n if (bodyOpen >= 0) {\n return `${html.slice(0, bodyOpen)}${bootstrap}${html.slice(bodyOpen)}`;\n }\n return `${bootstrap}${html}`;\n}\n"]}
@@ -17,12 +17,12 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
- error?: undefined;
21
20
  configured?: undefined;
22
21
  connectPath?: undefined;
23
22
  url: string;
24
23
  id: string;
25
24
  provider: string;
25
+ error?: undefined;
26
26
  }>;
27
27
  export default _default;
28
28
  //# sourceMappingURL=upload-image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/adapters/slack.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAQhB,MAAM,aAAa,CAAC;AAcrB,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7E,kEAAkE;IAClE,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,eAAe,CA2fjB"}
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/adapters/slack.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAQhB,MAAM,aAAa,CAAC;AA+BrB,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7E,kEAAkE;IAClE,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,eAAe,CAmgBjB"}
@@ -18,6 +18,13 @@ const SLACK_PERMALINK_TIMEOUT_MS = 1_000;
18
18
  const SLACK_CONTEXT_MESSAGE_LIMIT = 15;
19
19
  const SLACK_CONTEXT_TEXT_LIMIT = 2_000;
20
20
  const SLACK_CALM_PROGRESS_THRESHOLD_SECONDS = 30;
21
+ const SLACK_IDENTITY_TIMEOUT_MS = 1_000;
22
+ const SLACK_IDENTITY_CACHE_TTL_MS = 10 * 60 * 1_000;
23
+ // Failed users.info lookups only get a short negative TTL: a transient Slack
24
+ // API blip must not fail-close a sender's identity (and DMs) for 10 minutes.
25
+ const SLACK_IDENTITY_NEGATIVE_CACHE_TTL_MS = 30 * 1_000;
26
+ const SLACK_IDENTITY_CACHE_MAX_ENTRIES = 1_000;
27
+ const slackIdentityCache = new Map();
21
28
  /**
22
29
  * Create a Slack platform adapter.
23
30
  *
@@ -283,6 +290,12 @@ export function slackAdapter(options = {}) {
283
290
  return incoming;
284
291
  return hydrateSlackContext(token, incoming);
285
292
  },
293
+ async hydrateIncomingIdentity(incoming) {
294
+ const token = await resolveBotToken(incoming);
295
+ if (!token)
296
+ return incoming;
297
+ return hydrateSlackIdentity(token, incoming);
298
+ },
286
299
  async startRunProgress(incoming) {
287
300
  const token = await resolveBotToken(incoming);
288
301
  if (!token)
@@ -873,6 +886,81 @@ async function slackJson(token, method, params, timeoutMs = 2_000) {
873
886
  return null;
874
887
  }
875
888
  }
889
+ function slackIdentityCacheKey(incoming) {
890
+ const teamId = incoming.platformContext.teamId;
891
+ const senderId = incoming.senderId;
892
+ if (typeof senderId !== "string" || !senderId.trim())
893
+ return null;
894
+ return `${typeof teamId === "string" && teamId ? teamId : "unknown"}:${senderId}`;
895
+ }
896
+ async function resolveSlackUserIdentity(token, incoming) {
897
+ const cacheKey = slackIdentityCacheKey(incoming);
898
+ if (!cacheKey)
899
+ return null;
900
+ const cached = slackIdentityCache.get(cacheKey);
901
+ if (cached && cached.expiresAt > Date.now())
902
+ return cached.identity;
903
+ if (cached)
904
+ slackIdentityCache.delete(cacheKey);
905
+ const user = await slackJson(token, "users.info", { user: incoming.senderId }, SLACK_IDENTITY_TIMEOUT_MS);
906
+ const profile = user?.user?.profile;
907
+ const identity = user?.user
908
+ ? {
909
+ email: typeof profile?.email === "string" && profile.email.trim()
910
+ ? profile.email.trim().toLowerCase()
911
+ : null,
912
+ name: typeof profile?.real_name === "string" && profile.real_name.trim()
913
+ ? profile.real_name.trim()
914
+ : typeof profile?.display_name === "string" &&
915
+ profile.display_name.trim()
916
+ ? profile.display_name.trim()
917
+ : typeof user.user.real_name === "string" &&
918
+ user.user.real_name.trim()
919
+ ? user.user.real_name.trim()
920
+ : typeof user.user.name === "string" && user.user.name.trim()
921
+ ? user.user.name.trim()
922
+ : null,
923
+ memberType: user.user.is_ultra_restricted
924
+ ? "external"
925
+ : user.user.is_restricted
926
+ ? "guest"
927
+ : user.user.is_owner
928
+ ? "owner"
929
+ : user.user.is_admin
930
+ ? "admin"
931
+ : "member",
932
+ }
933
+ : null;
934
+ if (slackIdentityCache.size >= SLACK_IDENTITY_CACHE_MAX_ENTRIES) {
935
+ const oldestKey = slackIdentityCache.keys().next().value;
936
+ if (oldestKey)
937
+ slackIdentityCache.delete(oldestKey);
938
+ }
939
+ slackIdentityCache.set(cacheKey, {
940
+ identity,
941
+ expiresAt: Date.now() +
942
+ (identity
943
+ ? SLACK_IDENTITY_CACHE_TTL_MS
944
+ : SLACK_IDENTITY_NEGATIVE_CACHE_TTL_MS),
945
+ });
946
+ return identity;
947
+ }
948
+ async function hydrateSlackIdentity(token, incoming) {
949
+ const identity = await resolveSlackUserIdentity(token, incoming);
950
+ if (!identity)
951
+ return incoming;
952
+ return {
953
+ ...incoming,
954
+ ...(identity.name ? { senderName: identity.name } : {}),
955
+ ...(identity.email
956
+ ? { senderEmail: identity.email, senderVerified: true }
957
+ : { senderVerified: false }),
958
+ actorTrust: {
959
+ memberType: identity.memberType,
960
+ verified: true,
961
+ },
962
+ };
963
+ }
876
964
  async function hydrateSlackContext(token, incoming) {
877
965
  const channelId = incoming.platformContext.channelId;
878
966
  const threadTs = incoming.platformContext.threadTs;
@@ -880,7 +968,7 @@ async function hydrateSlackContext(token, incoming) {
880
968
  if (typeof channelId !== "string" || typeof threadTs !== "string") {
881
969
  return incoming;
882
970
  }
883
- const [thread, history, pins, conversation, user] = await Promise.all([
971
+ const [thread, history, pins, conversation, identity] = await Promise.all([
884
972
  slackJson(token, "conversations.replies", {
885
973
  channel: channelId,
886
974
  ts: threadTs,
@@ -894,7 +982,7 @@ async function hydrateSlackContext(token, incoming) {
894
982
  }),
895
983
  slackJson(token, "pins.list", { channel: channelId }),
896
984
  slackJson(token, "conversations.info", { channel: channelId }),
897
- senderId ? slackJson(token, "users.info", { user: senderId }) : null,
985
+ senderId ? resolveSlackUserIdentity(token, incoming) : null,
898
986
  ]);
899
987
  // Agent View can attach the Slack surface the user is currently viewing to
900
988
  // a DM. Treat it only as a context hint: Slack requires us to prove the bot
@@ -968,9 +1056,9 @@ async function hydrateSlackContext(token, incoming) {
968
1056
  };
969
1057
  })
970
1058
  : [];
971
- const profile = user?.user?.profile;
972
- const isGuest = user?.user?.is_restricted === true;
973
- const isExternal = user?.user?.is_ultra_restricted === true;
1059
+ const profile = identity;
1060
+ const isGuest = identity?.memberType === "guest";
1061
+ const isExternal = identity?.memberType === "external";
974
1062
  const isPrivate = conversation?.channel?.is_private === true;
975
1063
  const isExternalShared = conversation?.channel?.is_ext_shared === true;
976
1064
  const isMpim = conversation?.channel?.is_mpim === true;
@@ -1006,17 +1094,14 @@ async function hydrateSlackContext(token, incoming) {
1006
1094
  const hydratedFiles = hydratedMessages.flatMap((message) => message.files ?? []);
1007
1095
  return {
1008
1096
  ...incoming,
1009
- ...(typeof profile?.display_name === "string" && profile.display_name
1010
- ? { senderName: profile.display_name }
1011
- : typeof profile?.real_name === "string" && profile.real_name
1012
- ? { senderName: profile.real_name }
1013
- : {}),
1014
- ...(typeof profile?.email === "string"
1097
+ ...(profile?.name ? { senderName: profile.name } : {}),
1098
+ ...(profile?.email
1015
1099
  ? { senderEmail: profile.email, senderVerified: true }
1016
- : {}),
1100
+ : { senderVerified: false }),
1017
1101
  conversationType,
1018
1102
  actorTrust: {
1019
- memberType: isExternal ? "external" : isGuest ? "guest" : "member",
1103
+ memberType: identity?.memberType ??
1104
+ (isExternal ? "external" : isGuest ? "guest" : "member"),
1020
1105
  verified: true,
1021
1106
  },
1022
1107
  contextMessages: hydratedMessages,