@agent-native/core 0.151.2 → 0.152.1

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 (259) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +24 -0
  4. package/corpus/templates/analytics/actions/bigquery-query.ts +21 -0
  5. package/corpus/templates/analytics/app/components/ConnectionTestStatus.tsx +65 -0
  6. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -3
  7. package/corpus/templates/analytics/app/pages/DataSources.tsx +15 -37
  8. package/corpus/templates/analytics/app/root.tsx +0 -1
  9. package/corpus/templates/analytics/changelog/2026-08-11-analytics-custom-blocks-keep-loading-with-legacy-bigquery-action.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-11-analytics-routes-company-knowledge-and-slack-context-questio.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  12. package/corpus/templates/analytics/public/manifest.json +0 -1
  13. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +5 -0
  14. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  15. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +1 -1
  16. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +24 -0
  17. package/corpus/templates/assets/app/root.tsx +0 -1
  18. package/corpus/templates/assets/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  19. package/corpus/templates/assets/public/manifest.json +0 -1
  20. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +1 -1
  21. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +24 -0
  22. package/corpus/templates/brain/app/root.tsx +0 -1
  23. package/corpus/templates/brain/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  24. package/corpus/templates/brain/public/manifest.json +0 -1
  25. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +1 -1
  26. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +24 -0
  27. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +0 -53
  28. package/corpus/templates/calendar/app/i18n-data.ts +0 -80
  29. package/corpus/templates/calendar/app/root.tsx +0 -1
  30. package/corpus/templates/calendar/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-08-11-google-calendar-connection-buttons-now-open-the-sign-in-flow.md +6 -0
  32. package/corpus/templates/calendar/public/manifest.json +0 -1
  33. package/corpus/templates/calendar/server/plugins/auth.ts +0 -10
  34. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  35. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  36. package/corpus/templates/chat/app/root.tsx +0 -1
  37. package/corpus/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  38. package/corpus/templates/chat/public/manifest.json +0 -1
  39. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +24 -0
  40. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +1 -1
  41. package/corpus/templates/clips/app/components/player/share-dialog.tsx +6 -0
  42. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +20 -3
  43. package/corpus/templates/clips/app/i18n/en-US.ts +4 -4
  44. package/corpus/templates/clips/app/root.tsx +0 -1
  45. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -3
  46. package/corpus/templates/clips/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-08-11-clips-desktop-settings-are-now-easier-to-scan-with-a-wider-t.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-08-11-google-calendar-connections-from-meetings-now-open-directly.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-08-11-recording-viewers-can-comment-and-react.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-08-11-system-audio-recording-now-lives-inside-the-microphone-menu-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-08-11-timestamped-comments-stay-on-the-video-for-three-seconds-wh.md +6 -0
  52. package/corpus/templates/clips/desktop/src/app.tsx +773 -1001
  53. package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +15 -27
  54. package/corpus/templates/clips/desktop/src/styles.css +364 -5
  55. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +1 -1
  56. package/corpus/templates/clips/public/manifest.json +0 -1
  57. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +1 -1
  58. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +24 -0
  59. package/corpus/templates/content/AGENTS.md +3 -0
  60. package/corpus/templates/content/actions/resolve-content-landing.ts +120 -0
  61. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +196 -30
  62. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +9 -0
  63. package/corpus/templates/content/app/i18n-data.ts +58 -0
  64. package/corpus/templates/content/app/lib/content-landing.ts +22 -0
  65. package/corpus/templates/content/app/root.tsx +0 -1
  66. package/corpus/templates/content/app/routes/_app._index.tsx +36 -26
  67. package/corpus/templates/content/changelog/2026-08-03-choose-text-or-a-heading-from-one-compact-formatting-control.md +6 -0
  68. package/corpus/templates/content/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  69. package/corpus/templates/content/changelog/2026-08-11-content-opens-your-last-page-on-return-and-gives-new-users-a.md +6 -0
  70. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +7 -1
  71. package/corpus/templates/content/docs/solutions/evidence/content-text-selector/README.md +26 -0
  72. package/corpus/templates/content/parity/matrix.md +1 -0
  73. package/corpus/templates/content/parity/matrix.ts +22 -0
  74. package/corpus/templates/content/public/manifest.json +0 -1
  75. package/corpus/templates/content/shared/content-landing.ts +17 -0
  76. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +1 -1
  77. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +24 -0
  78. package/corpus/templates/crm/app/root.tsx +0 -1
  79. package/corpus/templates/crm/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  80. package/corpus/templates/crm/public/manifest.json +0 -1
  81. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +1 -1
  82. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +24 -0
  83. package/corpus/templates/design/app/i18n/en-US.ts +5 -0
  84. package/corpus/templates/design/app/root.tsx +0 -1
  85. package/corpus/templates/design/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  86. package/corpus/templates/design/changelog/2026-08-11-design-sharing-now-identifies-viewers-who-can-add-review-com.md +6 -0
  87. package/corpus/templates/design/public/manifest.json +0 -1
  88. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +1 -1
  89. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +24 -0
  90. package/corpus/templates/dispatch/AGENTS.md +9 -0
  91. package/corpus/templates/dispatch/actions/list-dispatch-usage-metrics.ts +5 -19
  92. package/corpus/templates/dispatch/actions/view-screen.ts +10 -3
  93. package/corpus/templates/dispatch/app/root.tsx +0 -1
  94. package/corpus/templates/dispatch/app/routes/settings.tsx +13 -1
  95. package/corpus/templates/dispatch/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  96. package/corpus/templates/dispatch/changelog/2026-08-11-clicking-the-dispatch-logo-returns-to-the-overview-page.md +6 -0
  97. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-links-to-itself-from-the-app-switcher.md +6 -0
  98. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-settings-now-link-directly-to-admin.md +6 -0
  99. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-shows-usage-trends-recent-prompts-and-an-agent-revi.md +6 -0
  100. package/corpus/templates/dispatch/changelog/2026-08-11-human-friendly-app-names.md +6 -0
  101. package/corpus/templates/dispatch/public/manifest.json +0 -1
  102. package/corpus/templates/dispatch/server/lib/identity-sso.ts +432 -158
  103. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +204 -123
  104. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +1 -0
  105. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +1 -1
  106. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +24 -0
  107. package/corpus/templates/factory/app/root.tsx +0 -1
  108. package/corpus/templates/factory/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  109. package/corpus/templates/factory/public/manifest.json +0 -1
  110. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +1 -1
  111. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +24 -0
  112. package/corpus/templates/forms/app/root.tsx +0 -1
  113. package/corpus/templates/forms/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  114. package/corpus/templates/forms/public/manifest.json +0 -1
  115. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +1 -1
  116. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +24 -0
  117. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +1 -1
  118. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +24 -0
  119. package/corpus/templates/mail/app/root.tsx +0 -1
  120. package/corpus/templates/mail/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  121. package/corpus/templates/mail/changelog/2026-08-11-google-sign-in-now-opens-the-gmail-connection-flow-directly.md +6 -0
  122. package/corpus/templates/mail/public/manifest.json +0 -1
  123. package/corpus/templates/mail/server/plugins/auth.ts +0 -10
  124. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +1 -1
  125. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +24 -0
  126. package/corpus/templates/plan/app/i18n/en-US.ts +2 -0
  127. package/corpus/templates/plan/app/pages/PlansPage.tsx +6 -0
  128. package/corpus/templates/plan/app/root.tsx +0 -1
  129. package/corpus/templates/plan/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  130. package/corpus/templates/plan/changelog/2026-08-11-plan-sharing-now-identifies-viewers-who-can-add-comments.md +6 -0
  131. package/corpus/templates/plan/public/manifest.json +0 -1
  132. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +1 -1
  133. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +24 -0
  134. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -0
  135. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  136. package/corpus/templates/slides/app/root.tsx +0 -1
  137. package/corpus/templates/slides/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  138. package/corpus/templates/slides/changelog/2026-08-11-share-dialog-uses-commenter-terminology.md +6 -0
  139. package/corpus/templates/slides/public/manifest.json +0 -1
  140. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +1 -1
  141. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +24 -0
  142. package/corpus/templates/tasks/app/root.tsx +0 -1
  143. package/corpus/templates/tasks/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  144. package/corpus/templates/tasks/public/manifest.json +0 -1
  145. package/dist/agent/production-agent.d.ts +8 -0
  146. package/dist/agent/production-agent.js +67 -22
  147. package/dist/agent/run-manager.d.ts +7 -0
  148. package/dist/agent/run-manager.js +46 -3
  149. package/dist/agent/tool-approval-migrations.d.ts +40 -0
  150. package/dist/agent/tool-approval-migrations.js +51 -0
  151. package/dist/agent/tool-approval-store.js +5 -20
  152. package/dist/cli/templates-meta.js +2 -2
  153. package/dist/client/AgentPanel.js +10 -1
  154. package/dist/client/AssistantChat.d.ts +3 -0
  155. package/dist/client/AssistantChat.js +151 -141
  156. package/dist/client/agent-page/AgentTabsPage.js +2 -2
  157. package/dist/client/chat/attachment-adapters.js +8 -2
  158. package/dist/client/chat/tool-call-display.d.ts +1 -0
  159. package/dist/client/chat/tool-call-display.js +3 -1
  160. package/dist/client/chat-first.js +11 -5
  161. package/dist/client/guided-questions.d.ts +15 -1
  162. package/dist/client/guided-questions.js +33 -15
  163. package/dist/client/hooks/index.d.ts +1 -1
  164. package/dist/client/hooks/index.js +1 -1
  165. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  166. package/dist/client/org/OrgSwitcher.js +22 -16
  167. package/dist/client/org/WorkspaceNotice.js +3 -1
  168. package/dist/client/resources/index.d.ts +1 -1
  169. package/dist/client/resources/index.js +1 -1
  170. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  171. package/dist/client/resources/use-mcp-servers.js +14 -0
  172. package/dist/client/settings/AccountSettingsCard.js +8 -4
  173. package/dist/client/settings/SettingsPanel.js +46 -13
  174. package/dist/client/settings/SettingsTabsPage.js +8 -3
  175. package/dist/client/sharing/ShareButton.d.ts +7 -1
  176. package/dist/client/sharing/ShareButton.js +14 -7
  177. package/dist/client/sharing/ShareDialog.js +5 -1
  178. package/dist/client/sharing/useShareButtonController.d.ts +4 -0
  179. package/dist/client/sharing/useShareButtonController.js +15 -0
  180. package/dist/client/sharing/useShareDialogController.d.ts +7 -0
  181. package/dist/client/sharing/useShareDialogController.js +14 -0
  182. package/dist/client/sse-event-processor.js +37 -0
  183. package/dist/client/use-session.d.ts +2 -0
  184. package/dist/client/use-session.js +69 -5
  185. package/dist/deploy/build.js +1 -1
  186. package/dist/file-upload/pre-upload-attachments.d.ts +3 -4
  187. package/dist/file-upload/pre-upload-attachments.js +65 -17
  188. package/dist/ingestion/index.d.ts +1 -0
  189. package/dist/ingestion/index.js +1 -0
  190. package/dist/ingestion/office.d.ts +3 -3
  191. package/dist/ingestion/office.js +24 -2
  192. package/dist/ingestion/spreadsheet.d.ts +41 -0
  193. package/dist/ingestion/spreadsheet.js +172 -0
  194. package/dist/integrations/adapters/slack.js +7 -3
  195. package/dist/integrations/webhook-handler.js +11 -3
  196. package/dist/localization/default-messages.d.ts +3 -0
  197. package/dist/localization/default-messages.js +3 -0
  198. package/dist/mcp/embed-app.js +20 -9
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/oauth-tokens/migrations.d.ts +8 -0
  201. package/dist/oauth-tokens/migrations.js +36 -0
  202. package/dist/observability/routes.d.ts +5 -5
  203. package/dist/org/workspace-url.d.ts +8 -0
  204. package/dist/org/workspace-url.js +26 -2
  205. package/dist/progress/routes.d.ts +1 -1
  206. package/dist/secrets/routes.d.ts +6 -6
  207. package/dist/server/agent-chat/context-tools.js +27 -6
  208. package/dist/server/agent-chat-plugin.js +16 -8
  209. package/dist/server/auth.d.ts +0 -11
  210. package/dist/server/auth.js +37 -20
  211. package/dist/server/better-auth-migrations.js +23 -0
  212. package/dist/server/email-template.d.ts +5 -0
  213. package/dist/server/email-template.js +9 -0
  214. package/dist/server/embed-route.js +17 -5
  215. package/dist/server/identity-sso-store.d.ts +40 -66
  216. package/dist/server/identity-sso-store.js +187 -150
  217. package/dist/server/identity-sso.d.ts +17 -73
  218. package/dist/server/identity-sso.js +294 -297
  219. package/dist/server/onboarding-html.d.ts +0 -12
  220. package/dist/server/onboarding-html.js +46 -269
  221. package/dist/server/realtime-token.d.ts +1 -1
  222. package/dist/server/release-migrations.js +8 -0
  223. package/dist/server/transcribe-voice.d.ts +1 -1
  224. package/dist/shared/index.d.ts +1 -1
  225. package/dist/shared/index.js +1 -1
  226. package/dist/shared/workspace-app-id.d.ts +2 -0
  227. package/dist/shared/workspace-app-id.js +10 -0
  228. package/dist/sharing/actions/share-resource.d.ts +1 -0
  229. package/dist/sharing/actions/share-resource.js +20 -5
  230. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  231. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  232. package/dist/templates/chat/app/root.tsx +0 -1
  233. package/dist/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  234. package/dist/templates/chat/public/manifest.json +0 -1
  235. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  236. package/dist/templates/default/app/root.tsx +0 -1
  237. package/dist/templates/default/public/manifest.json +0 -1
  238. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  239. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  240. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  241. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  242. package/docs/content/cross-app-sso.mdx +85 -27
  243. package/docs/content/template-mail.mdx +0 -6
  244. package/package.json +4 -3
  245. package/src/templates/chat/.agents/skills/sharing/SKILL.md +1 -1
  246. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +24 -0
  247. package/src/templates/chat/app/root.tsx +0 -1
  248. package/src/templates/chat/changelog/2026-08-11-browser-no-longer-offers-desktop-install.md +6 -0
  249. package/src/templates/chat/public/manifest.json +0 -1
  250. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +24 -0
  251. package/src/templates/default/app/root.tsx +0 -1
  252. package/src/templates/default/public/manifest.json +0 -1
  253. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +24 -0
  254. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +22 -11
  255. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +1 -1
  256. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +24 -0
  257. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +0 -6
  258. package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +0 -51
  259. package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +0 -6
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
- ok?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
47
  error?: undefined;
51
48
  ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
58
  id?: undefined;
60
59
  error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -22,6 +22,14 @@ export type ParsedWorkspaceUrl = {
22
22
  * app they arrive at.
23
23
  */
24
24
  export declare function parseWorkspaceUrl(raw: string): ParsedWorkspaceUrl;
25
+ /**
26
+ * Whether a URL points at a browser-local development origin.
27
+ *
28
+ * Local development may use a loopback hostname or a subdomain of the
29
+ * reserved `.localhost` domain. Hosted previews must remain eligible for
30
+ * workspace guidance.
31
+ */
32
+ export declare function isLocalDevelopmentOrigin(currentUrl: string): boolean;
25
33
  /**
26
34
  * Whether a member currently on `currentUrl` should be pointed at the org's
27
35
  * workspace. False when the org has no workspace, when the stored value is
@@ -28,7 +28,8 @@ export function parseWorkspaceUrl(raw) {
28
28
  try {
29
29
  parsed = new URL(withScheme);
30
30
  }
31
- catch {
31
+ catch (error) {
32
+ void error;
32
33
  return { ok: false, reason: "Not a valid URL" };
33
34
  }
34
35
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
@@ -39,6 +40,28 @@ export function parseWorkspaceUrl(raw) {
39
40
  }
40
41
  return { ok: true, url: parsed.origin };
41
42
  }
43
+ /**
44
+ * Whether a URL points at a browser-local development origin.
45
+ *
46
+ * Local development may use a loopback hostname or a subdomain of the
47
+ * reserved `.localhost` domain. Hosted previews must remain eligible for
48
+ * workspace guidance.
49
+ */
50
+ export function isLocalDevelopmentOrigin(currentUrl) {
51
+ let hostname;
52
+ try {
53
+ hostname = new URL(currentUrl).hostname.toLowerCase();
54
+ }
55
+ catch (error) {
56
+ void error;
57
+ return false;
58
+ }
59
+ return (hostname === "localhost" ||
60
+ hostname.endsWith(".localhost") ||
61
+ hostname === "127.0.0.1" ||
62
+ hostname === "::1" ||
63
+ hostname === "[::1]");
64
+ }
42
65
  /**
43
66
  * Whether a member currently on `currentUrl` should be pointed at the org's
44
67
  * workspace. False when the org has no workspace, when the stored value is
@@ -55,7 +78,8 @@ export function shouldOfferWorkspace(currentUrl, workspaceUrl) {
55
78
  try {
56
79
  currentOrigin = new URL(currentUrl).origin;
57
80
  }
58
- catch {
81
+ catch (error) {
82
+ void error;
59
83
  return false;
60
84
  }
61
85
  return currentOrigin !== parsed.url;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
37
37
  ok?: undefined;
38
38
  status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
44
  ok?: undefined;
45
45
  error: string;
46
46
  removed?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
58
58
  error: string;
59
59
  note?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  note: string;
67
- error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
+ error?: undefined;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;
@@ -376,8 +376,12 @@ export function createUrlTools() {
376
376
  },
377
377
  },
378
378
  "ask-question": {
379
+ // The turn is over once the question is on screen. Without this the loop
380
+ // asks the model for another step, and it keeps working (and re-asking)
381
+ // over an unanswered question no matter what the tool result says.
382
+ endsTurn: true,
379
383
  tool: {
380
- description: "Ask the user a multiple-choice clarifying question and render it inline in the chat. Use this ONLY when you are genuinely blocked on a decision you cannot resolve from context and a wrong guess would be costly — an ambiguous metric, date range, or grain; a real fork in approach. Present 2-5 concrete options and mark the most likely one recommended. Do NOT use it for confirmations, for things the user already specified, or to dodge easy work you could just do. Ask at most once per turn. Calling this yields the turn: stop and wait for the user's answer.",
384
+ description: "Ask the user a multiple-choice clarifying question and render it inline in the chat. Use this ONLY when you are genuinely blocked on a decision you cannot resolve from context and a wrong guess would be costly — an ambiguous metric, date range, or grain; a real fork in approach. Present 2-5 concrete options and mark the most likely one recommended. Do NOT use it for confirmations, for things the user already specified, or to dodge easy work you could just do. Calling this ends the turn: one question per turn, and any other tool call you emit alongside it will not run.",
381
385
  parameters: {
382
386
  type: "object",
383
387
  properties: {
@@ -408,9 +412,12 @@ export function createUrlTools() {
408
412
  },
409
413
  },
410
414
  run: async (args) => {
415
+ // These must throw, not return an error string: `endsTurn` yields the
416
+ // turn on any non-error result, so a returned string would leave the
417
+ // run waiting on a question card that was never written.
411
418
  const question = String(args?.question ?? "").trim();
412
419
  if (!question)
413
- return "Error: 'question' is required.";
420
+ throw new Error("'question' is required.");
414
421
  const header = String(args?.header ?? "").trim();
415
422
  const allowMultiple = String(args?.allowMultiple ?? "") === "true";
416
423
  const allowFreeText = String(args?.allowFreeText ?? "true") !== "false";
@@ -419,10 +426,10 @@ export function createUrlTools() {
419
426
  parsedOptions = JSON.parse(String(args?.options ?? "[]"));
420
427
  }
421
428
  catch {
422
- return "Error: 'options' must be a JSON array of { label, value?, description?, recommended? }.";
429
+ throw new Error("'options' must be a JSON array of { label, value?, description?, recommended? }.");
423
430
  }
424
431
  if (!Array.isArray(parsedOptions) || parsedOptions.length === 0) {
425
- return "Error: 'options' must be a non-empty JSON array of { label, value?, description?, recommended? }.";
432
+ throw new Error("'options' must be a non-empty JSON array of { label, value?, description?, recommended? }.");
426
433
  }
427
434
  const options = parsedOptions
428
435
  .map((raw) => {
@@ -450,14 +457,26 @@ export function createUrlTools() {
450
457
  })
451
458
  .filter((opt) => opt !== null);
452
459
  if (options.length === 0) {
453
- return "Error: 'options' must contain at least one option with a label.";
460
+ throw new Error("'options' must contain at least one option with a label.");
454
461
  }
455
462
  // Shape must match the GuidedQuestionFlow renderer in
456
463
  // client/guided-questions.tsx: a `text-options` question whose options
457
464
  // carry `value`, with `multiSelect` for multi-pick and `allowOther` for
458
465
  // free text. The renderer otherwise injects "Explore"/"Decide" options,
459
466
  // which would be noise for a focused clarifying question, so disable them.
467
+ // The application-state key is scoped per browser tab, not per chat, so
468
+ // the payload has to name the thread that asked. The client hides a
469
+ // pending question in every other conversation instead of following the
470
+ // user from chat to chat.
471
+ // A request that carried no thread id falls back to the run id (see
472
+ // `onRunStart`), and a per-run value would never match the chat the user
473
+ // is looking at. Leave those surfaces unbound — they have one chat.
474
+ const askingRunCtx = getRequestRunContext();
475
+ const askingThreadId = askingRunCtx?.threadId && askingRunCtx.threadId !== askingRunCtx.runId
476
+ ? askingRunCtx.threadId
477
+ : undefined;
460
478
  const payload = {
479
+ ...(askingThreadId ? { threadId: askingThreadId } : {}),
461
480
  questions: [
462
481
  {
463
482
  id: "q1",
@@ -475,7 +494,9 @@ export function createUrlTools() {
475
494
  };
476
495
  const { writeAppState } = await import("../../application-state/script-helpers.js");
477
496
  await writeAppState(appStateKeyForBrowserTab("guided-questions", getRequestRunContext()?.browserTabId), payload);
478
- return "Asked the user a clarifying question and rendered it in the chat. Stop here and wait for their answer — do not proceed or assume an answer.";
497
+ // The `startsWith` of this text is how integration surfaces recognize a
498
+ // delivered question (see `extractSlackInputRequest`). Keep the prefix.
499
+ return "Asked the user a clarifying question and rendered it in the chat. This turn is over — their answer arrives as a new message.";
479
500
  },
480
501
  },
481
502
  };
@@ -2269,15 +2269,19 @@ export function createAgentChatPlugin(options) {
2269
2269
  catch { }
2270
2270
  // Lean mode: only template actions + essential framework tools. Drop
2271
2271
  // web-request, browser tools, teams, jobs, automations, notifications,
2272
- // progress, call-agent, and MCP entries to keep the tool list tight and
2273
- // prevent the LLM from reaching for web-request instead of the
2274
- // template's native actions (e.g. log-meal).
2272
+ // progress, and MCP entries to keep the tool list tight and prevent the
2273
+ // LLM from reaching for web-request instead of the template's native
2274
+ // actions (e.g. log-meal). Cross-app delegation is the exception: when
2275
+ // it is enabled, keep its two discovery/delegation tools available even
2276
+ // in lean mode because the framework prompt and default starter surface
2277
+ // still promise sibling-app routing.
2275
2278
  const leanActionEntries = {
2276
2279
  ...templateScripts,
2277
2280
  ...resourceScripts,
2278
2281
  ...refreshScreenTool,
2279
2282
  ...urlTools,
2280
2283
  ...chatScripts,
2284
+ ...(a2aAgentDelegationEnabled ? callAgentScript : {}),
2281
2285
  ...toolActions,
2282
2286
  };
2283
2287
  const anonymousReadOnlyActions = attachToolSearch(filterReadOnlyActions(templateScripts));
@@ -2387,7 +2391,11 @@ export function createAgentChatPlugin(options) {
2387
2391
  // Lean mode: use only the template's systemPrompt + actions list.
2388
2392
  // Skip resource loading and schema block — those add DB round-trips
2389
2393
  // and tokens that minimal/voice apps don't need.
2390
- const leanBasePrompt = (options?.systemPrompt ?? "") + prodActionsPrompt;
2394
+ const leanActionsPrompt = prodActionsPrompt +
2395
+ (a2aAgentDelegationEnabled
2396
+ ? generateActionsPrompt(callAgentScript, "tool")
2397
+ : "");
2398
+ const leanBasePrompt = (options?.systemPrompt ?? "") + leanActionsPrompt;
2391
2399
  const anonymousReadOnlyPrompt = (options?.systemPrompt ?? PROD_FRAMEWORK_PROMPT_COMPACT) +
2392
2400
  generateActionsPrompt(filterReadOnlyActions(templateScripts), "tool", lazyContext ? effectiveInitialToolNames : undefined) +
2393
2401
  "\n\nYou are answering from a public shared page. Treat the visible resource as read-only: do not create, edit, delete, comment on, share, or otherwise mutate app data. If the user asks for a change, describe what you would change or suggest signing in to edit.";
@@ -2584,8 +2592,8 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2584
2592
  if (leanPrompt) {
2585
2593
  const leanRunPolicyPrompt = buildLeanRunPolicyPrompt(codeEditingSurfaceRestriction, prodCodeExecPromptNote);
2586
2594
  await emitContextXraySystemSections(event, {
2587
- frameworkPrompt: leanBasePrompt.slice(0, Math.max(0, leanBasePrompt.length - prodActionsPrompt.length)),
2588
- actionsPrompt: prodActionsPrompt,
2595
+ frameworkPrompt: leanBasePrompt.slice(0, Math.max(0, leanBasePrompt.length - leanActionsPrompt.length)),
2596
+ actionsPrompt: leanActionsPrompt,
2589
2597
  additionalFramework: leanRunPolicyPrompt,
2590
2598
  extra,
2591
2599
  modelOverlay,
@@ -2823,8 +2831,8 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2823
2831
  // for the same pattern.
2824
2832
  if (leanPrompt) {
2825
2833
  await emitContextXraySystemSections(event, {
2826
- frameworkPrompt: leanBasePrompt.slice(0, Math.max(0, leanBasePrompt.length - prodActionsPrompt.length)),
2827
- actionsPrompt: prodActionsPrompt,
2834
+ frameworkPrompt: leanBasePrompt.slice(0, Math.max(0, leanBasePrompt.length - leanActionsPrompt.length)),
2835
+ actionsPrompt: leanActionsPrompt,
2828
2836
  extra,
2829
2837
  modelOverlay,
2830
2838
  runtimeContext,
@@ -112,17 +112,6 @@ export interface AuthOptions {
112
112
  features?: string[];
113
113
  runLocalCommand?: string;
114
114
  };
115
- /**
116
- * Optional host-scoped notice shown before the built-in Google sign-in
117
- * redirects to Google.
118
- */
119
- googleSignInNotice?: {
120
- host?: string;
121
- title: string;
122
- body: string | string[];
123
- continueLabel?: string;
124
- cancelLabel?: string;
125
- };
126
115
  /**
127
116
  * Optional email signup legal copy for the built-in login page.
128
117
  * Leave unset to use Agent Native links only on `*.agent-native.com` hosts,
@@ -999,7 +999,6 @@ function getOnboardingHtmlOptions(options, event, rawPath, authMode) {
999
999
  authMode,
1000
1000
  googleOnly: options.googleOnly,
1001
1001
  marketing: options.marketing,
1002
- googleSignInNotice: options.googleSignInNotice,
1003
1002
  signupLegalNotice: options.signupLegalNotice,
1004
1003
  googleAuthMode: options.googleAuthMode,
1005
1004
  requestHost: event ? getRequestHost(event) : undefined,
@@ -1922,6 +1921,13 @@ async function hasAutoDevAccountUser(db) {
1922
1921
  });
1923
1922
  return rows.length > 0;
1924
1923
  }
1924
+ async function hasRealUser(db) {
1925
+ const { rows } = await db.execute({
1926
+ sql: 'SELECT 1 FROM "user" WHERE email NOT IN (?, ?) LIMIT 1',
1927
+ args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
1928
+ });
1929
+ return rows.length > 0;
1930
+ }
1925
1931
  const autoDevAccountCreationPromises = new Map();
1926
1932
  function getAutoDevAccountCreationKey() {
1927
1933
  return `${process.cwd()}:${process.env.APP_BASE_PATH ?? ""}`;
@@ -2012,11 +2018,7 @@ async function createOrReuseAutoDevSession(auth, config) {
2012
2018
  if (session)
2013
2019
  return { email: session.email, token: session.token };
2014
2020
  const db = getDbExec();
2015
- const { rows: realUsers } = await db.execute({
2016
- sql: 'SELECT 1 FROM "user" WHERE email NOT IN (?, ?) LIMIT 1',
2017
- args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
2018
- });
2019
- if (realUsers.length > 0)
2021
+ if (await hasRealUser(db))
2020
2022
  return null;
2021
2023
  const devPassword = await createAutoDevAccountForSession(auth, db);
2022
2024
  if (!devPassword && !(await hasAutoDevAccountUser(db)))
@@ -2027,8 +2029,30 @@ async function createOrReuseAutoDevSession(auth, config) {
2027
2029
  }
2028
2030
  return session ? { email: session.email, token: session.token } : null;
2029
2031
  }
2032
+ async function getLocalDevAuthAvailability(event, config) {
2033
+ if (!isLocalDevAuthAllowed(event)) {
2034
+ return { available: false, reason: "not-allowed" };
2035
+ }
2036
+ try {
2037
+ const db = getDbExec();
2038
+ await getBetterAuth(config);
2039
+ if (await hasAutoDevAccountUser(db))
2040
+ return { available: true };
2041
+ if (await hasRealUser(db)) {
2042
+ return { available: false, reason: "existing-user" };
2043
+ }
2044
+ return { available: true };
2045
+ }
2046
+ catch {
2047
+ return { available: false, reason: "unreadable" };
2048
+ }
2049
+ }
2030
2050
  function createLocalDevAuthHandler(config) {
2031
2051
  return defineEventHandler(async (event) => {
2052
+ if (getMethod(event) === "GET") {
2053
+ setResponseHeader(event, "Cache-Control", "no-store");
2054
+ return getLocalDevAuthAvailability(event, config);
2055
+ }
2032
2056
  if (getMethod(event) !== "POST") {
2033
2057
  setResponseStatus(event, 405);
2034
2058
  return { error: "Method not allowed" };
@@ -2105,11 +2129,7 @@ async function maybeAutoCreateDevSession(event, redirectTo) {
2105
2129
  // pre-fix local DB that still holds a `dev@local` row isn't treated
2106
2130
  // as having a "real user" (which would permanently disable
2107
2131
  // auto-create on that DB).
2108
- const { rows: realUsers } = await db.execute({
2109
- sql: 'SELECT 1 FROM "user" WHERE email NOT IN (?, ?) LIMIT 1',
2110
- args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
2111
- });
2112
- if (realUsers.length > 0)
2132
+ if (await hasRealUser(db))
2113
2133
  return null;
2114
2134
  // If the dev account already exists, this is not a freshly-scaffolded
2115
2135
  // app — the user has been through the auto-create flow at least
@@ -3086,7 +3106,7 @@ async function mountBetterAuthRoutes(app, options) {
3086
3106
  for (const key of callbackKeys) {
3087
3107
  if (typeof body[key] !== "string")
3088
3108
  continue;
3089
- const callbackURL = betterAuthCallbackURL(body[key], false, event);
3109
+ const callbackURL = betterAuthCallbackURL(body[key], true, event);
3090
3110
  if (callbackURL !== body[key]) {
3091
3111
  sanitizedBody[key] = callbackURL;
3092
3112
  changed = true;
@@ -3312,13 +3332,13 @@ async function mountBetterAuthRoutes(app, options) {
3312
3332
  desktopFlow = await issueDesktopMagicLinkFlow();
3313
3333
  callbackPath = withDesktopMagicLinkFlow(callbackPath, desktopFlow);
3314
3334
  }
3315
- const callbackURL = betterAuthCallbackURL(callbackPath, false, event);
3335
+ const callbackURL = betterAuthCallbackURL(callbackPath, true, event);
3316
3336
  const newUserCallbackPath = `${getAppBasePath()}/_agent-native/auth/magic-link/new-user?return=${encodeURIComponent(callbackPath)}`;
3317
3337
  await auth.api.signInMagicLink({
3318
3338
  body: {
3319
3339
  email,
3320
3340
  callbackURL,
3321
- newUserCallbackURL: betterAuthCallbackURL(newUserCallbackPath, callbackURL !== callbackPath, event),
3341
+ newUserCallbackURL: betterAuthCallbackURL(newUserCallbackPath, true, event),
3322
3342
  },
3323
3343
  headers: event.headers,
3324
3344
  });
@@ -3347,8 +3367,8 @@ async function mountBetterAuthRoutes(app, options) {
3347
3367
  const email = normalizeAuthEmail(rawEmail);
3348
3368
  const password = body?.password;
3349
3369
  const callbackURL = typeof body?.callbackURL === "string"
3350
- ? betterAuthCallbackURL(body.callbackURL, false, event)
3351
- : "/";
3370
+ ? betterAuthCallbackURL(body.callbackURL, true, event)
3371
+ : betterAuthCallbackURL("/", true, event);
3352
3372
  if (!email) {
3353
3373
  setResponseStatus(event, 400);
3354
3374
  return { error: VALID_AUTH_EMAIL_MESSAGE };
@@ -3715,10 +3735,7 @@ export async function autoMountAuth(app, options = {}) {
3715
3735
  customGetSession = options.getSession;
3716
3736
  }
3717
3737
  if (_authGuardConfig) {
3718
- if (options.googleOnly ||
3719
- options.loginHtml ||
3720
- options.marketing ||
3721
- options.googleSignInNotice) {
3738
+ if (options.googleOnly || options.loginHtml || options.marketing) {
3722
3739
  const loginHtmlConfig = getOnboardingLoginHtmlConfig(options, _authGuardConfig.authMode);
3723
3740
  _authGuardConfig.loginHtml = loginHtmlConfig.loginHtml;
3724
3741
  _authGuardConfig.getLoginHtml = loginHtmlConfig.getLoginHtml;
@@ -224,6 +224,29 @@ export const BETTER_AUTH_MIGRATIONS = [
224
224
  },
225
225
  run: assertBetterAuthUserIdentityColumns,
226
226
  },
227
+ {
228
+ version: 3,
229
+ name: "legacy-auth-sessions-table",
230
+ sql: {
231
+ // `addSession()` is still used by the mobile/deep-link OAuth flow and
232
+ // the workspace callback. Provision its legacy table in the release
233
+ // runtime so those request paths only perform normal row writes.
234
+ postgres: `
235
+ CREATE TABLE IF NOT EXISTS sessions (
236
+ token TEXT PRIMARY KEY,
237
+ email TEXT,
238
+ created_at BIGINT NOT NULL
239
+ )
240
+ `,
241
+ sqlite: `
242
+ CREATE TABLE IF NOT EXISTS sessions (
243
+ token TEXT PRIMARY KEY,
244
+ email TEXT,
245
+ created_at INTEGER NOT NULL
246
+ )
247
+ `,
248
+ },
249
+ },
227
250
  ];
228
251
  export async function runBetterAuthMigrations(nitroApp) {
229
252
  await runMigrations(BETTER_AUTH_MIGRATIONS, {
@@ -74,6 +74,11 @@ export declare function renderEmail(args: RenderEmailArgs): RenderedEmail;
74
74
  * paragraph strings passed to `renderEmail`. Escapes the content.
75
75
  */
76
76
  export declare function emailStrong(text: string): string;
77
+ /**
78
+ * Build a quoted message block for user-authored text in notification emails.
79
+ * The message stays escaped while preserving intentional line breaks.
80
+ */
81
+ export declare function emailQuote(text: string): string;
77
82
  /**
78
83
  * Build a labelled inline link for paragraph strings passed to `renderEmail`.
79
84
  * Use this instead of rendering raw URLs in the visible email body.
@@ -216,6 +216,15 @@ function stripTags(s) {
216
216
  export function emailStrong(text) {
217
217
  return `<strong style="color:#fafafa; font-weight:600;">${escapeHtml(text)}</strong>`;
218
218
  }
219
+ /**
220
+ * Build a quoted message block for user-authored text in notification emails.
221
+ * The message stays escaped while preserving intentional line breaks.
222
+ */
223
+ export function emailQuote(text) {
224
+ const content = escapeHtml(text.trim()).replace(/\r?\n/g, "<br />");
225
+ // guard:allow-raw-color — quoted blocks intentionally use a fixed email-safe accent
226
+ return `<div style="margin:0 0 16px 0; padding:12px 16px; border-left:3px solid #52525b; background:#18181b; color:#e4e4e7; font-size:16px; line-height:1.6;">${content}</div>`;
227
+ }
219
228
  /**
220
229
  * Build a labelled inline link for paragraph strings passed to `renderEmail`.
221
230
  * Use this instead of rendering raw URLs in the visible email body.
@@ -91,19 +91,31 @@ function expiredEmbedSessionResponse(event) {
91
91
  main { max-width: 520px; text-align: center; }
92
92
  h1 { margin: 0 0 8px; font-size: 16px; line-height: 1.25; }
93
93
  p { margin: 0; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.5; }
94
+ button { margin-top: 16px; border: 1px solid color-mix(in srgb, CanvasText 24%, Canvas); border-radius: 6px; padding: 7px 12px; background: Canvas; color: CanvasText; font: inherit; font-size: 12px; cursor: pointer; }
95
+ button:hover { background: color-mix(in srgb, CanvasText 8%, Canvas); }
94
96
  </style>
95
97
  </head>
96
98
  <body>
97
99
  <main>
98
100
  <h1>Embedded app session expired</h1>
99
- <p>This chat preview is refreshing. If it does not reload, ask the chat to open the app again.</p>
101
+ <p>This embedded app session expired. The app will try to reconnect automatically.</p>
102
+ <button type="button" id="retry">Retry</button>
100
103
  </main>
101
104
  <script>
102
- try {
103
- if (window.parent && window.parent !== window) {
104
- window.parent.postMessage({ type: "agentNative.embedSessionExpired" }, "*");
105
+ function notifyParent() {
106
+ try {
107
+ if (window.parent && window.parent !== window) {
108
+ window.parent.postMessage({
109
+ type: "agentNative.embedSessionExpired",
110
+ embedStartUrl: window.location.href
111
+ }, "*");
112
+ }
113
+ } catch (error) {
114
+ void error;
105
115
  }
106
- } catch {}
116
+ }
117
+ document.getElementById("retry")?.addEventListener("click", notifyParent);
118
+ notifyParent();
107
119
  </script>
108
120
  </body>
109
121
  </html>`, {