@agent-native/core 0.15.4 → 0.15.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 (166) hide show
  1. package/dist/client/AgentPanel.d.ts.map +1 -1
  2. package/dist/client/AgentPanel.js +3 -2
  3. package/dist/client/AgentPanel.js.map +1 -1
  4. package/dist/client/AssistantChat.d.ts.map +1 -1
  5. package/dist/client/AssistantChat.js +12 -10
  6. package/dist/client/AssistantChat.js.map +1 -1
  7. package/dist/client/components/CodeRequiredDialog.js +1 -1
  8. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  9. package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
  10. package/dist/client/settings/BackgroundAgentSection.js +2 -1
  11. package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
  12. package/dist/client/settings/BrowserSection.d.ts.map +1 -1
  13. package/dist/client/settings/BrowserSection.js +3 -2
  14. package/dist/client/settings/BrowserSection.js.map +1 -1
  15. package/dist/client/settings/SettingsPanel.js +1 -1
  16. package/dist/client/settings/SettingsPanel.js.map +1 -1
  17. package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
  18. package/dist/client/settings/VoiceTranscriptionSection.js +1 -0
  19. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
  20. package/dist/client/settings/useBuilderStatus.d.ts +1 -0
  21. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  22. package/dist/client/settings/useBuilderStatus.js +53 -12
  23. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  24. package/dist/client/settings/useBuilderStatus.spec.js +43 -7
  25. package/dist/client/settings/useBuilderStatus.spec.js.map +1 -1
  26. package/dist/client/transcription/BuilderTranscriptionCta.d.ts.map +1 -1
  27. package/dist/client/transcription/BuilderTranscriptionCta.js +7 -2
  28. package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
  29. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  30. package/dist/server/agent-chat-plugin.js +3 -1
  31. package/dist/server/agent-chat-plugin.js.map +1 -1
  32. package/dist/server/auth.d.ts.map +1 -1
  33. package/dist/server/auth.js +19 -2
  34. package/dist/server/auth.js.map +1 -1
  35. package/dist/server/builder-browser.d.ts +15 -0
  36. package/dist/server/builder-browser.d.ts.map +1 -1
  37. package/dist/server/builder-browser.js +80 -1
  38. package/dist/server/builder-browser.js.map +1 -1
  39. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  40. package/dist/server/core-routes-plugin.js +58 -18
  41. package/dist/server/core-routes-plugin.js.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/client/dev-mode.d.ts +0 -14
  44. package/dist/client/dev-mode.d.ts.map +0 -1
  45. package/dist/client/dev-mode.js +0 -14
  46. package/dist/client/dev-mode.js.map +0 -1
  47. package/dist/client/extensions/EmbeddedTool.d.ts +0 -20
  48. package/dist/client/extensions/EmbeddedTool.d.ts.map +0 -1
  49. package/dist/client/extensions/EmbeddedTool.js +0 -199
  50. package/dist/client/extensions/EmbeddedTool.js.map +0 -1
  51. package/dist/client/extensions/ToolEditor.d.ts +0 -5
  52. package/dist/client/extensions/ToolEditor.d.ts.map +0 -1
  53. package/dist/client/extensions/ToolEditor.js +0 -129
  54. package/dist/client/extensions/ToolEditor.js.map +0 -1
  55. package/dist/client/extensions/ToolViewer.d.ts +0 -5
  56. package/dist/client/extensions/ToolViewer.d.ts.map +0 -1
  57. package/dist/client/extensions/ToolViewer.js +0 -400
  58. package/dist/client/extensions/ToolViewer.js.map +0 -1
  59. package/dist/client/extensions/ToolViewerPage.d.ts +0 -2
  60. package/dist/client/extensions/ToolViewerPage.d.ts.map +0 -1
  61. package/dist/client/extensions/ToolViewerPage.js +0 -24
  62. package/dist/client/extensions/ToolViewerPage.js.map +0 -1
  63. package/dist/client/extensions/ToolsListPage.d.ts +0 -2
  64. package/dist/client/extensions/ToolsListPage.d.ts.map +0 -1
  65. package/dist/client/extensions/ToolsListPage.js +0 -67
  66. package/dist/client/extensions/ToolsListPage.js.map +0 -1
  67. package/dist/client/extensions/ToolsSidebarSection.d.ts +0 -2
  68. package/dist/client/extensions/ToolsSidebarSection.d.ts.map +0 -1
  69. package/dist/client/extensions/ToolsSidebarSection.js +0 -236
  70. package/dist/client/extensions/ToolsSidebarSection.js.map +0 -1
  71. package/dist/client/extensions/tool-order.d.ts +0 -7
  72. package/dist/client/extensions/tool-order.d.ts.map +0 -1
  73. package/dist/client/extensions/tool-order.js +0 -47
  74. package/dist/client/extensions/tool-order.js.map +0 -1
  75. package/dist/client/tools/EmbeddedTool.d.ts +0 -20
  76. package/dist/client/tools/EmbeddedTool.d.ts.map +0 -1
  77. package/dist/client/tools/EmbeddedTool.js +0 -199
  78. package/dist/client/tools/EmbeddedTool.js.map +0 -1
  79. package/dist/client/tools/ExtensionSlot.d.ts +0 -27
  80. package/dist/client/tools/ExtensionSlot.d.ts.map +0 -1
  81. package/dist/client/tools/ExtensionSlot.js +0 -96
  82. package/dist/client/tools/ExtensionSlot.js.map +0 -1
  83. package/dist/client/tools/ToolEditor.d.ts +0 -5
  84. package/dist/client/tools/ToolEditor.d.ts.map +0 -1
  85. package/dist/client/tools/ToolEditor.js +0 -129
  86. package/dist/client/tools/ToolEditor.js.map +0 -1
  87. package/dist/client/tools/ToolViewer.d.ts +0 -5
  88. package/dist/client/tools/ToolViewer.d.ts.map +0 -1
  89. package/dist/client/tools/ToolViewer.js +0 -400
  90. package/dist/client/tools/ToolViewer.js.map +0 -1
  91. package/dist/client/tools/ToolViewerPage.d.ts +0 -2
  92. package/dist/client/tools/ToolViewerPage.d.ts.map +0 -1
  93. package/dist/client/tools/ToolViewerPage.js +0 -24
  94. package/dist/client/tools/ToolViewerPage.js.map +0 -1
  95. package/dist/client/tools/ToolsListPage.d.ts +0 -2
  96. package/dist/client/tools/ToolsListPage.d.ts.map +0 -1
  97. package/dist/client/tools/ToolsListPage.js +0 -67
  98. package/dist/client/tools/ToolsListPage.js.map +0 -1
  99. package/dist/client/tools/ToolsSidebarSection.d.ts +0 -2
  100. package/dist/client/tools/ToolsSidebarSection.d.ts.map +0 -1
  101. package/dist/client/tools/ToolsSidebarSection.js +0 -236
  102. package/dist/client/tools/ToolsSidebarSection.js.map +0 -1
  103. package/dist/client/tools/iframe-bridge.d.ts +0 -38
  104. package/dist/client/tools/iframe-bridge.d.ts.map +0 -1
  105. package/dist/client/tools/iframe-bridge.js +0 -207
  106. package/dist/client/tools/iframe-bridge.js.map +0 -1
  107. package/dist/client/tools/index.d.ts +0 -8
  108. package/dist/client/tools/index.d.ts.map +0 -1
  109. package/dist/client/tools/index.js +0 -8
  110. package/dist/client/tools/index.js.map +0 -1
  111. package/dist/client/tools/tool-order.d.ts +0 -7
  112. package/dist/client/tools/tool-order.d.ts.map +0 -1
  113. package/dist/client/tools/tool-order.js +0 -47
  114. package/dist/client/tools/tool-order.js.map +0 -1
  115. package/dist/server/local-migration.d.ts +0 -41
  116. package/dist/server/local-migration.d.ts.map +0 -1
  117. package/dist/server/local-migration.js +0 -235
  118. package/dist/server/local-migration.js.map +0 -1
  119. package/dist/tools/actions.d.ts +0 -3
  120. package/dist/tools/actions.d.ts.map +0 -1
  121. package/dist/tools/actions.js +0 -272
  122. package/dist/tools/actions.js.map +0 -1
  123. package/dist/tools/fetch-tool.d.ts +0 -23
  124. package/dist/tools/fetch-tool.d.ts.map +0 -1
  125. package/dist/tools/fetch-tool.js +0 -178
  126. package/dist/tools/fetch-tool.js.map +0 -1
  127. package/dist/tools/html-shell.d.ts +0 -45
  128. package/dist/tools/html-shell.d.ts.map +0 -1
  129. package/dist/tools/html-shell.js +0 -514
  130. package/dist/tools/html-shell.js.map +0 -1
  131. package/dist/tools/proxy-security.d.ts +0 -12
  132. package/dist/tools/proxy-security.d.ts.map +0 -1
  133. package/dist/tools/proxy-security.js +0 -158
  134. package/dist/tools/proxy-security.js.map +0 -1
  135. package/dist/tools/routes.d.ts +0 -2
  136. package/dist/tools/routes.d.ts.map +0 -1
  137. package/dist/tools/routes.js +0 -627
  138. package/dist/tools/routes.js.map +0 -1
  139. package/dist/tools/schema.d.ts +0 -664
  140. package/dist/tools/schema.d.ts.map +0 -1
  141. package/dist/tools/schema.js +0 -146
  142. package/dist/tools/schema.js.map +0 -1
  143. package/dist/tools/slots/routes.d.ts +0 -15
  144. package/dist/tools/slots/routes.d.ts.map +0 -1
  145. package/dist/tools/slots/routes.js +0 -94
  146. package/dist/tools/slots/routes.js.map +0 -1
  147. package/dist/tools/slots/schema.d.ts +0 -303
  148. package/dist/tools/slots/schema.d.ts.map +0 -1
  149. package/dist/tools/slots/schema.js +0 -76
  150. package/dist/tools/slots/schema.js.map +0 -1
  151. package/dist/tools/slots/store.d.ts +0 -66
  152. package/dist/tools/slots/store.d.ts.map +0 -1
  153. package/dist/tools/slots/store.js +0 -227
  154. package/dist/tools/slots/store.js.map +0 -1
  155. package/dist/tools/store.d.ts +0 -40
  156. package/dist/tools/store.d.ts.map +0 -1
  157. package/dist/tools/store.js +0 -193
  158. package/dist/tools/store.js.map +0 -1
  159. package/dist/tools/theme.d.ts +0 -2
  160. package/dist/tools/theme.d.ts.map +0 -1
  161. package/dist/tools/theme.js +0 -67
  162. package/dist/tools/theme.js.map +0 -1
  163. package/dist/tools/url-safety.d.ts +0 -24
  164. package/dist/tools/url-safety.d.ts.map +0 -1
  165. package/dist/tools/url-safety.js +0 -224
  166. package/dist/tools/url-safety.js.map +0 -1
@@ -25,6 +25,13 @@ export interface BuilderBrowserStatus {
25
25
  credentialSource?: "user" | "org" | "env";
26
26
  appHost: string;
27
27
  apiHost: string;
28
+ /**
29
+ * Ready-to-open Builder CLI auth URL for this request owner. This points
30
+ * directly at builder.io/cli-auth and carries signed callback state in the
31
+ * nested redirect_url so the popup never has to visit the app's connect
32
+ * trampoline first.
33
+ */
34
+ cliAuthUrl?: string;
28
35
  connectUrl: string;
29
36
  publicKeyConfigured: boolean;
30
37
  privateKeyConfigured: boolean;
@@ -57,6 +64,7 @@ export declare function signBuilderCallbackState(sessionEmail: string): string;
57
64
  * false on any malformed, forged, expired, or cross-session token.
58
65
  */
59
66
  export declare function verifyBuilderCallbackState(token: string | null | undefined, sessionEmail: string): boolean;
67
+ export declare function verifyBuilderCallbackStateAndGetOwner(token: string | null | undefined): string | null;
60
68
  export declare function signBuilderConnectToken(ownerEmail: string): string;
61
69
  export declare function verifyBuilderConnectToken(token: string | null | undefined, ownerEmail: string): boolean;
62
70
  export declare function verifyBuilderConnectTokenAndGetOwner(token: string | null | undefined): string | null;
@@ -88,6 +96,13 @@ export declare function buildBuilderCliAuthUrl(origin: string, state?: string |
88
96
  * request-bound owner and the connect route can fall back to Fetch Metadata.
89
97
  */
90
98
  export declare function getBuilderBrowserConnectUrl(origin: string): string;
99
+ /**
100
+ * Builder CLI-auth does not need the workspace OAuth relay that Google uses.
101
+ * In Builder/Fusion previews, keep connect + callback URLs on the actual app
102
+ * preview origin so the signed connect token and pending row are verified by
103
+ * the same deployment that minted them.
104
+ */
105
+ export declare function getBuilderBrowserOriginForEvent(event: H3Event): string;
91
106
  export declare function getBuilderBrowserStatus(origin: string): BuilderBrowserStatus;
92
107
  export declare function getBuilderBrowserStatusForEvent(event: H3Event): BuilderBrowserStatus;
93
108
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"builder-browser.d.ts","sourceRoot":"","sources":["../../src/server/builder-browser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builder-browser.d.ts","sourceRoot":"","sources":["../../src/server/builder-browser.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { getHeader } from "h3";
2
3
  import { getAuthSecret } from "./better-auth-instance.js";
3
4
  import { getAppBasePath, getOrigin } from "./google-oauth.js";
4
5
  const DEFAULT_BUILDER_APP_HOST = "https://builder.io";
@@ -105,6 +106,26 @@ export function signBuilderCallbackState(sessionEmail) {
105
106
  export function verifyBuilderCallbackState(token, sessionEmail) {
106
107
  return verifyEmailBoundBuilderToken(token, sessionEmail, "callback");
107
108
  }
109
+ export function verifyBuilderCallbackStateAndGetOwner(token) {
110
+ if (typeof token !== "string" || token.length === 0)
111
+ return null;
112
+ const parts = token.split(".");
113
+ if (parts.length !== 4)
114
+ return null;
115
+ const emailEncoded = parts[1];
116
+ if (!emailEncoded)
117
+ return null;
118
+ let ownerEmail;
119
+ try {
120
+ ownerEmail = Buffer.from(emailEncoded, "base64url").toString("utf8");
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ if (!ownerEmail)
126
+ return null;
127
+ return verifyBuilderCallbackState(token, ownerEmail) ? ownerEmail : null;
128
+ }
108
129
  export function signBuilderConnectToken(ownerEmail) {
109
130
  return signEmailBoundBuilderToken(ownerEmail, "connect");
110
131
  }
@@ -241,6 +262,64 @@ export function buildBuilderCliAuthUrl(origin, state = null) {
241
262
  export function getBuilderBrowserConnectUrl(origin) {
242
263
  return `${normalizeOrigin(origin)}${getAppBasePath()}/_agent-native/builder/connect`;
243
264
  }
265
+ function firstHeaderValue(value) {
266
+ return value?.split(",")[0]?.trim() || undefined;
267
+ }
268
+ function readEventHeader(event, name) {
269
+ try {
270
+ return getHeader(event, name) ?? undefined;
271
+ }
272
+ catch {
273
+ const headers = event.node?.req?.headers;
274
+ const value = headers?.[name.toLowerCase()] ?? headers?.[name];
275
+ if (Array.isArray(value))
276
+ return value[0];
277
+ return typeof value === "string" ? value : undefined;
278
+ }
279
+ }
280
+ function isTrustedBuilderRequestHost(host) {
281
+ if (!host)
282
+ return false;
283
+ try {
284
+ const hostname = new URL(`http://${host}`).hostname.toLowerCase();
285
+ return (hostname === "localhost" ||
286
+ hostname === "127.0.0.1" ||
287
+ hostname === "::1" ||
288
+ hostname === "[::1]" ||
289
+ hostname === "builderio.xyz" ||
290
+ hostname.endsWith(".builderio.xyz") ||
291
+ hostname === "builderio.dev" ||
292
+ hostname.endsWith(".builderio.dev") ||
293
+ hostname === "builder.codes" ||
294
+ hostname.endsWith(".builder.codes") ||
295
+ hostname === "builder.io" ||
296
+ hostname.endsWith(".builder.io") ||
297
+ hostname === "builder.my" ||
298
+ hostname.endsWith(".builder.my"));
299
+ }
300
+ catch {
301
+ return false;
302
+ }
303
+ }
304
+ /**
305
+ * Builder CLI-auth does not need the workspace OAuth relay that Google uses.
306
+ * In Builder/Fusion previews, keep connect + callback URLs on the actual app
307
+ * preview origin so the signed connect token and pending row are verified by
308
+ * the same deployment that minted them.
309
+ */
310
+ export function getBuilderBrowserOriginForEvent(event) {
311
+ const headerHost = firstHeaderValue(readEventHeader(event, "x-forwarded-host") ||
312
+ readEventHeader(event, "host"));
313
+ if (!isTrustedBuilderRequestHost(headerHost))
314
+ return getOrigin(event);
315
+ const rawProto = firstHeaderValue(readEventHeader(event, "x-forwarded-proto"));
316
+ const proto = rawProto === "http" || rawProto === "https"
317
+ ? rawProto
318
+ : process.env.NODE_ENV === "production"
319
+ ? "https"
320
+ : "http";
321
+ return `${proto}://${headerHost}`;
322
+ }
244
323
  export function getBuilderBrowserStatus(origin) {
245
324
  const branchProjectId = getConfiguredBuilderBranchProjectId();
246
325
  const envManaged = !!process.env.BUILDER_PRIVATE_KEY;
@@ -262,7 +341,7 @@ export function getBuilderBrowserStatus(origin) {
262
341
  };
263
342
  }
264
343
  export function getBuilderBrowserStatusForEvent(event) {
265
- return getBuilderBrowserStatus(getOrigin(event));
344
+ return getBuilderBrowserStatus(getBuilderBrowserOriginForEvent(event));
266
345
  }
267
346
  /**
268
347
  * Env vars written by the Builder CLI-auth callback. Single source of truth
@@ -1 +1 @@
1
- {"version":3,"file":"builder-browser.js","sourceRoot":"","sources":["../../src/server/builder-browser.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type { H3Event } from \"h3\";\nimport { getAuthSecret } from \"./better-auth-instance.js\";\nimport { getAppBasePath, getOrigin } from \"./google-oauth.js\";\n\nconst DEFAULT_BUILDER_APP_HOST = \"https://builder.io\";\nconst DEFAULT_BUILDER_API_HOST = \"https://api.builder.io\";\nconst BUILDER_BROWSER_HOST = \"agent-native-browser\";\nconst BUILDER_BROWSER_CLIENT_ID = \"Agent Native Browser\";\n\nexport const BUILDER_CALLBACK_PATH = \"/_agent-native/builder/callback\";\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n}\n\n/**\n * Query-param name carrying the signed CSRF state on the connect→callback\n * round-trip. Prefixed with `_an_` to avoid collisions if Builder ever\n * adds standard OAuth `state` support to cli-auth. Builder preserves\n * the path/query of `redirect_url` verbatim when redirecting back, so\n * we embed `_an_state=…` inside the redirect_url query string at\n * connect time and read it back on the callback.\n */\nexport const BUILDER_STATE_PARAM = \"_an_state\";\nexport const BUILDER_CONNECT_PARAM = \"_an_connect\";\nexport const BUILDER_CONNECT_OWNER_COOKIE = \"an_builder_connect_owner\";\n\nconst BUILDER_STATE_TTL_MS = 10 * 60 * 1000;\n\nexport interface BuilderBrowserStatus {\n configured: boolean;\n builderEnabled: boolean;\n branchProjectIdConfigured: boolean;\n branchProjectId?: string;\n /**\n * True when `BUILDER_PRIVATE_KEY` is set at the deployment level. This is a\n * fallback credential; signed-in users can still connect their own Builder\n * account, which takes precedence for their request.\n */\n envManaged: boolean;\n credentialSource?: \"user\" | \"org\" | \"env\";\n appHost: string;\n apiHost: string;\n connectUrl: string;\n publicKeyConfigured: boolean;\n privateKeyConfigured: boolean;\n userId?: string;\n orgName?: string;\n orgKind?: string;\n}\n\nexport interface BrowserConnectionArgs {\n sessionId?: string;\n projectId?: string;\n branchName?: string;\n proxyOrigin?: string;\n proxyDefaultOrigin?: string;\n proxyDestination?: string;\n}\n\ntype BuilderSignedTokenPurpose = \"callback\" | \"connect\";\n\nfunction signingKeyForPurpose(purpose: BuilderSignedTokenPurpose): string {\n // Preserve the original callback-state signing key for any in-flight legacy\n // callbacks; use a separate key domain for connect-entry tokens.\n return purpose === \"callback\"\n ? `builder-csrf:${getAuthSecret()}`\n : `builder-connect:${getAuthSecret()}`;\n}\n\nfunction macForParts(\n purpose: BuilderSignedTokenPurpose,\n nonce: string,\n emailEncoded: string,\n ts: number,\n): string {\n return createHmac(\"sha256\", signingKeyForPurpose(purpose))\n .update(`${nonce}.${emailEncoded}.${ts}`)\n .digest(\"base64url\");\n}\n\nfunction signEmailBoundBuilderToken(\n ownerEmail: string,\n purpose: BuilderSignedTokenPurpose,\n): string {\n const nonce = randomBytes(16).toString(\"base64url\");\n const ts = Date.now();\n const emailEncoded = Buffer.from(ownerEmail, \"utf8\").toString(\"base64url\");\n const mac = macForParts(purpose, nonce, emailEncoded, ts);\n return `${nonce}.${emailEncoded}.${ts}.${mac}`;\n}\n\nfunction verifyEmailBoundBuilderToken(\n token: string | null | undefined,\n ownerEmail: string,\n purpose: BuilderSignedTokenPurpose,\n): boolean {\n if (typeof token !== \"string\" || token.length === 0) return false;\n const parts = token.split(\".\");\n if (parts.length !== 4) return false;\n const [nonce, emailEncoded, tsStr, mac] = parts;\n if (!nonce || !emailEncoded || !tsStr || !mac) return false;\n\n let boundEmail: string;\n try {\n boundEmail = Buffer.from(emailEncoded, \"base64url\").toString(\"utf8\");\n } catch {\n return false;\n }\n if (boundEmail !== ownerEmail) return false;\n\n const ts = Number(tsStr);\n if (!Number.isFinite(ts)) return false;\n // Reject expired AND far-future timestamps so leaked tokens do not gain an\n // arbitrary lifetime through clock skew or forged future issue times.\n if (Math.abs(Date.now() - ts) > BUILDER_STATE_TTL_MS) return false;\n\n const expected = Buffer.from(macForParts(purpose, nonce, emailEncoded, ts));\n const candidate = Buffer.from(mac);\n if (expected.length !== candidate.length) return false;\n return timingSafeEqual(expected, candidate);\n}\n\n/**\n * Mint a signed CSRF state token bound to the current session's email\n * and a fresh nonce. Round-trips through Builder's cli-auth flow inside\n * the redirect_url query string and is verified on the callback before\n * any keys are written.\n *\n * Why bind to email: it's the only stable, universally-available\n * identity field across all auth modes (Better Auth, BYOA, AUTH_MODE=local).\n * Binding to the session token instead would put the cookie value in a\n * URL that may end up in server logs / browser history.\n */\nexport function signBuilderCallbackState(sessionEmail: string): string {\n return signEmailBoundBuilderToken(sessionEmail, \"callback\");\n}\n\n/**\n * Verify a state token produced by `signBuilderCallbackState`. Returns\n * false on any malformed, forged, expired, or cross-session token.\n */\nexport function verifyBuilderCallbackState(\n token: string | null | undefined,\n sessionEmail: string,\n): boolean {\n return verifyEmailBoundBuilderToken(token, sessionEmail, \"callback\");\n}\n\nexport function signBuilderConnectToken(ownerEmail: string): string {\n return signEmailBoundBuilderToken(ownerEmail, \"connect\");\n}\n\nexport function verifyBuilderConnectToken(\n token: string | null | undefined,\n ownerEmail: string,\n): boolean {\n return verifyEmailBoundBuilderToken(token, ownerEmail, \"connect\");\n}\n\nexport function verifyBuilderConnectTokenAndGetOwner(\n token: string | null | undefined,\n): string | null {\n if (typeof token !== \"string\" || token.length === 0) return null;\n const parts = token.split(\".\");\n if (parts.length !== 4) return null;\n const emailEncoded = parts[1];\n if (!emailEncoded) return null;\n\n let ownerEmail: string;\n try {\n ownerEmail = Buffer.from(emailEncoded, \"base64url\").toString(\"utf8\");\n } catch {\n return null;\n }\n if (!ownerEmail) return null;\n return verifyBuilderConnectToken(token, ownerEmail) ? ownerEmail : null;\n}\n\nexport function appendBuilderConnectToken(\n connectUrl: string,\n ownerEmail: string,\n): string {\n const url = new URL(connectUrl);\n url.searchParams.set(\n BUILDER_CONNECT_PARAM,\n signBuilderConnectToken(ownerEmail),\n );\n return url.toString();\n}\n\nfunction isAllowedBrowserReturnUrl(urlString: string): boolean {\n try {\n const parsed = new URL(urlString);\n const hostname = parsed.hostname.toLowerCase();\n const isAllowedProtocol =\n parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\n const isLocalhost =\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\";\n const isBuilderDomain =\n hostname === \"builder.io\" || hostname.endsWith(\".builder.io\");\n const isAgentNativeDomain =\n hostname === \"agent-native.com\" || hostname.endsWith(\".agent-native.com\");\n return (\n isAllowedProtocol &&\n (isLocalhost || isBuilderDomain || isAgentNativeDomain)\n );\n } catch {\n return false;\n }\n}\n\nfunction normalizeOrigin(origin: string): string {\n return origin.replace(/\\/+$/, \"\");\n}\n\nexport function getBuilderAppHost(): string {\n return (\n process.env.BUILDER_APP_HOST ||\n process.env.BUILDER_PUBLIC_APP_HOST ||\n DEFAULT_BUILDER_APP_HOST\n );\n}\n\nexport function getBuilderApiHost(): string {\n return (\n process.env.AIR_HOST ||\n process.env.BUILDER_HOST ||\n process.env.BUILDER_API_HOST ||\n DEFAULT_BUILDER_API_HOST\n );\n}\n\nfunction getConfiguredBuilderBranchProjectId(): string | undefined {\n const projectId =\n process.env.DISPATCH_BUILDER_PROJECT_ID ||\n process.env.BUILDER_BRANCH_PROJECT_ID ||\n process.env.BUILDER_PROJECT_ID;\n return projectId?.trim() || undefined;\n}\n\nexport function getBuilderBranchProjectId(): string {\n return getConfiguredBuilderBranchProjectId() || \"\";\n}\n\nexport function isBuilderBranchingEnabled(): boolean {\n return !!getConfiguredBuilderBranchProjectId();\n}\n\nexport async function resolveBuilderBranchProjectId(): Promise<string> {\n const envProjectId = getConfiguredBuilderBranchProjectId();\n if (envProjectId) return envProjectId;\n\n try {\n const { resolveSecret } = await import(\"./credential-provider.js\");\n for (const key of [\n \"DISPATCH_BUILDER_PROJECT_ID\",\n \"BUILDER_BRANCH_PROJECT_ID\",\n \"BUILDER_PROJECT_ID\",\n ]) {\n const value = await resolveSecret(key);\n if (value?.trim()) return value.trim();\n }\n } catch {\n // Secrets table or request context not ready — treat as not configured.\n }\n\n return \"\";\n}\n\nexport async function resolveIsBuilderBranchingEnabled(): Promise<boolean> {\n return !!(await resolveBuilderBranchProjectId());\n}\n\n/**\n * Build the Builder cli-auth URL for the connect popup. When a signed\n * `state` token is supplied it is embedded inside the `redirect_url`\n * query string so it survives Builder's redirect verbatim — Builder\n * preserves the redirect_url's existing query when appending p-key /\n * api-key / etc., so we don't depend on Builder echoing a top-level\n * `state` parameter (it doesn't).\n *\n * The user-facing connect entry point is `/_agent-native/builder/connect`\n * (a server-side 302). Status / chat-card responses surface that path\n * rather than the cli-auth URL directly, so the 302 handler can mint a\n * fresh state bound to the current session on every click.\n */\nexport function buildBuilderCliAuthUrl(\n origin: string,\n state: string | null = null,\n): string {\n const normalizedOrigin = normalizeOrigin(origin);\n const appBasePath = getAppBasePath();\n const callbackUrl = new URL(\n `${appBasePath}${BUILDER_CALLBACK_PATH}`,\n normalizedOrigin,\n );\n if (state) {\n callbackUrl.searchParams.set(BUILDER_STATE_PARAM, state);\n }\n const url = new URL(\"/cli-auth\", getBuilderAppHost());\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"host\", BUILDER_BROWSER_HOST);\n url.searchParams.set(\"client_id\", BUILDER_BROWSER_CLIENT_ID);\n url.searchParams.set(\"redirect_url\", callbackUrl.toString());\n url.searchParams.set(\"preview_url\", `${normalizedOrigin}${appBasePath}`);\n url.searchParams.set(\"framework\", \"agent-native\");\n return url.toString();\n}\n\n/**\n * The bare URL surfaced to clients as `connectUrl`. The status route appends\n * a short-lived signed connect token when it knows the current owner; this\n * helper stays bare so server-rendered cards can still render without a\n * request-bound owner and the connect route can fall back to Fetch Metadata.\n */\nexport function getBuilderBrowserConnectUrl(origin: string): string {\n return `${normalizeOrigin(origin)}${getAppBasePath()}/_agent-native/builder/connect`;\n}\n\nexport function getBuilderBrowserStatus(origin: string): BuilderBrowserStatus {\n const branchProjectId = getConfiguredBuilderBranchProjectId();\n const envManaged = !!process.env.BUILDER_PRIVATE_KEY;\n return {\n configured: !!(\n process.env.BUILDER_PRIVATE_KEY && process.env.BUILDER_PUBLIC_KEY\n ),\n builderEnabled: isBuilderBranchingEnabled(),\n branchProjectIdConfigured: !!branchProjectId,\n branchProjectId: branchProjectId || undefined,\n envManaged,\n credentialSource: envManaged ? \"env\" : undefined,\n appHost: getBuilderAppHost(),\n apiHost: getBuilderApiHost(),\n connectUrl: getBuilderBrowserConnectUrl(origin),\n publicKeyConfigured: !!process.env.BUILDER_PUBLIC_KEY,\n privateKeyConfigured: !!process.env.BUILDER_PRIVATE_KEY,\n userId: process.env.BUILDER_USER_ID || undefined,\n orgName: process.env.BUILDER_ORG_NAME || undefined,\n orgKind: process.env.BUILDER_ORG_KIND || undefined,\n };\n}\n\nexport function getBuilderBrowserStatusForEvent(\n event: H3Event,\n): BuilderBrowserStatus {\n return getBuilderBrowserStatus(getOrigin(event));\n}\n\n/**\n * Env vars written by the Builder CLI-auth callback. Single source of truth\n * for the connect/disconnect key set — `getBuilderCallbackEnvVars` and the\n * disconnect handler's scrub loop both derive from this list, so drift\n * (e.g. disconnect silently leaving `BUILDER_USER_ID` behind because\n * someone added a key to one site but not the other) is impossible.\n */\nexport const BUILDER_ENV_KEYS = [\n \"BUILDER_PRIVATE_KEY\",\n \"BUILDER_PUBLIC_KEY\",\n \"BUILDER_USER_ID\",\n \"BUILDER_ORG_NAME\",\n \"BUILDER_ORG_KIND\",\n] as const;\n\nexport type BuilderEnvKey = (typeof BUILDER_ENV_KEYS)[number];\n\nexport function getBuilderCallbackEnvVars(params: {\n privateKey?: string | null;\n publicKey?: string | null;\n userId?: string | null;\n orgName?: string | null;\n orgKind?: string | null;\n}) {\n const values: Record<BuilderEnvKey, string> = {\n BUILDER_PRIVATE_KEY: params.privateKey?.trim() || \"\",\n BUILDER_PUBLIC_KEY: params.publicKey?.trim() || \"\",\n BUILDER_USER_ID: params.userId?.trim() || \"\",\n BUILDER_ORG_NAME: params.orgName?.trim() || \"\",\n BUILDER_ORG_KIND: params.orgKind?.trim() || \"\",\n };\n return BUILDER_ENV_KEYS.map((key) => ({ key, value: values[key] }));\n}\n\nexport function resolveSafePreviewUrl(\n previewUrl: string | null | undefined,\n event: H3Event,\n): string {\n if (previewUrl && isAllowedBrowserReturnUrl(previewUrl)) {\n return previewUrl;\n }\n return getOrigin(event);\n}\n\n/**\n * Inline theme-detection script that runs before the body paints. Reads the\n * app's stored theme preference (same `localStorage.theme` key used by the\n * client-side theme manager) and falls back to `prefers-color-scheme`. This\n * way the popup matches whatever theme the user already picked in the app\n * — light, dark, or auto — instead of always rendering in OS-default mode.\n */\nconst BUILDER_CALLBACK_THEME_SCRIPT = `<script>\n(function () {\n try {\n var stored = window.localStorage && window.localStorage.getItem(\"theme\");\n var resolved;\n if (stored === \"light\" || stored === \"dark\") {\n resolved = stored;\n } else {\n var mq = window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\");\n resolved = mq && mq.matches ? \"dark\" : \"light\";\n }\n document.documentElement.classList.add(resolved);\n document.documentElement.style.colorScheme = resolved;\n } catch (e) {}\n})();\n</script>`;\n\n/**\n * Brand-aligned CSS for the Builder connect callback / error pages.\n *\n * Uses the same neutral-zinc palette and Inter font as the rest of the\n * framework's templates (see `templates/*\\/app/global.css`). Tokens map to\n * the same HSL values the templates set on `:root` / `.dark`, so the popup\n * reads as part of the same app — not a stranded marketing page.\n */\nconst BUILDER_CALLBACK_BASE_CSS = `\n :root {\n --bg: hsl(0 0% 100%);\n --fg: hsl(220 10% 10%);\n --muted-fg: hsl(220 5% 45%);\n --card: hsl(0 0% 100%);\n --border: hsl(220 10% 90%);\n --primary: hsl(220 10% 15%);\n --primary-fg: hsl(0 0% 100%);\n --primary-hover: hsl(220 10% 25%);\n --success-bg: hsl(143 50% 96%);\n --success-fg: hsl(143 60% 32%);\n --error-fg: hsl(0 75% 45%);\n --error-bg: hsl(0 80% 97%);\n --error-border: hsl(0 80% 92%);\n }\n :root.dark {\n --bg: hsl(220 6% 6%);\n --fg: hsl(0 0% 92%);\n --muted-fg: hsl(220 4% 60%);\n --card: hsl(220 5% 8%);\n --border: hsl(220 4% 14%);\n --primary: hsl(0 0% 92%);\n --primary-fg: hsl(220 6% 6%);\n --primary-hover: hsl(0 0% 75%);\n --success-bg: hsl(143 30% 12%);\n --success-fg: hsl(143 50% 70%);\n --error-fg: hsl(0 80% 75%);\n --error-bg: hsl(0 35% 12%);\n --error-border: hsl(0 30% 20%);\n }\n *, *::before, *::after { box-sizing: border-box; }\n html, body { height: 100%; }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n background: var(--bg);\n color: var(--fg);\n font-family: \"Inter\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif;\n font-size: 14px;\n line-height: 1.55;\n font-feature-settings: \"cv02\", \"cv03\", \"cv04\", \"cv11\";\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n padding: 24px;\n }\n .card {\n width: min(420px, 100%);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 32px 28px;\n background: var(--card);\n text-align: center;\n }\n .icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n border-radius: 999px;\n margin-bottom: 16px;\n }\n .icon svg { width: 22px; height: 22px; display: block; }\n .icon-success { background: var(--success-bg); color: var(--success-fg); }\n .icon-error { background: var(--error-bg); color: var(--error-fg); }\n h1 {\n margin: 0 0 6px;\n font-size: 17px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--fg);\n }\n p {\n margin: 0 0 4px;\n color: var(--fg);\n font-size: 14px;\n }\n p.muted { color: var(--muted-fg); }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 0 16px;\n margin-top: 20px;\n background: var(--primary);\n color: var(--primary-fg);\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n text-decoration: none;\n border: none;\n cursor: pointer;\n }\n .btn:hover { background: var(--primary-hover); }\n pre.error-detail {\n margin: 16px 0 0;\n padding: 10px 12px;\n background: var(--error-bg);\n border: 1px solid var(--error-border);\n border-radius: 8px;\n color: var(--error-fg);\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 12px;\n line-height: 1.5;\n text-align: left;\n white-space: pre-wrap;\n word-break: break-word;\n }\n`;\n\nexport function createBuilderBrowserCallbackPage(previewUrl: string): string {\n const escapedUrl = JSON.stringify(previewUrl);\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n <title>Builder connected</title>\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@400;500;600&display=swap\" rel=\"stylesheet\" />\n ${BUILDER_CALLBACK_THEME_SCRIPT}\n <style>${BUILDER_CALLBACK_BASE_CSS}</style>\n </head>\n <body>\n <main class=\"card\" role=\"status\" aria-live=\"polite\">\n <span class=\"icon icon-success\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>\n </span>\n <h1>Builder connected</h1>\n <p>Browser access is now available to your app.</p>\n <p class=\"muted\">You can close this tab and return to the workspace.</p>\n <a class=\"btn\" href=${escapedUrl}>Open the workspace</a>\n </main>\n <script>\n // Tell the opener tab the connect succeeded. The parent has two ways\n // to learn this:\n // 1. The popup-based connect flow (window.open + 2s polling on\n // /builder/status) — picks it up via the next poll within ~2s.\n // 2. The link-based \"Use Builder\" flow (target=\"_blank\" tab) — the\n // AgentPanel only fetches /builder/status once on mount, so it\n // stays stuck on \"Use Builder\" unless we explicitly signal.\n // BroadcastChannel + postMessage cover both cases. Use the same channel\n // name as the error path (createBuilderBrowserCallbackErrorPage) and\n // mirror the parent-side listener in useBuilderStatus / useBuilderConnectUrl.\n try {\n var bc = new BroadcastChannel(\"builder-connect:\" + window.location.host);\n bc.postMessage({ type: \"builder-connect-success\" });\n bc.close();\n } catch (e) {}\n try {\n if (window.opener && !window.opener.closed) {\n window.opener.postMessage(\n { type: \"builder-connect-success\" },\n window.location.origin,\n );\n }\n } catch (e) {}\n // If we're a popup opened by the app, close ourselves and let the\n // parent tab keep polling for connection status. If close() is\n // blocked (e.g. we're the top-level tab because popups were\n // downgraded), fall back to navigating back to the workspace.\n window.setTimeout(function () {\n try { window.close(); } catch (e) {}\n window.setTimeout(function () {\n if (!window.closed) {\n window.location.replace(${escapedUrl});\n }\n }, 200);\n }, 700);\n </script>\n </body>\n</html>`;\n}\n\n/**\n * HTML page rendered inside the OAuth popup when the callback handler caught\n * an error persisting the per-user Builder credentials. Without this, the\n * popup would show the success page even though the write failed — leaving\n * the parent window stuck on \"Waiting for Builder…\" until the 5-minute poll\n * timeout fires (Midhun reported this on 2026-04-28).\n *\n * The page does two things:\n * 1. Shows the user a clear \"couldn't save credentials\" message with the\n * underlying error so they can retry or report.\n * 2. `postMessage`s the parent (same-origin opener) so the connect-flow\n * polling stops immediately rather than waiting for the next /status\n * poll to surface the SQL `builder-connect-error:<email>` row.\n */\nexport function createBuilderBrowserCallbackErrorPage(\n message: string,\n opts: {\n title?: string;\n body?: string;\n closeHint?: string;\n } = {},\n): string {\n const escapedMessage = JSON.stringify(message);\n const title = opts.title ?? \"Couldn't save Builder connection\";\n const body =\n opts.body ??\n \"Builder authorized your account but the server couldn't persist the credentials.\";\n const closeHint =\n opts.closeHint ?? \"You can close this tab and try again from settings.\";\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n <title>Builder connect failed</title>\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@400;500;600&display=swap\" rel=\"stylesheet\" />\n ${BUILDER_CALLBACK_THEME_SCRIPT}\n <style>${BUILDER_CALLBACK_BASE_CSS}</style>\n </head>\n <body>\n <main class=\"card\" role=\"alert\" aria-live=\"assertive\">\n <span class=\"icon icon-error\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\"></path><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"></line><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line></svg>\n </span>\n <h1>${escapeHtml(title)}</h1>\n <p class=\"muted\">${escapeHtml(body)}</p>\n <pre class=\"error-detail\" id=\"msg\"></pre>\n <p class=\"muted\" style=\"margin-top:12px\">${escapeHtml(closeHint)}</p>\n </main>\n <script>\n try {\n var msg = ${escapedMessage};\n document.getElementById(\"msg\").textContent = msg;\n // Notify the parent tab immediately so its polling loop stops\n // without waiting for the next /builder/status tick.\n //\n // BroadcastChannel works across same-origin windows regardless of\n // opener access — it is the only reliable channel here because\n // popups opened with window.open(..., \"noopener\") or links with\n // rel=\"noopener\" have window.opener === null. The legacy\n // window.opener.postMessage path is kept as a belt-and-suspenders\n // fallback for non-BroadcastChannel environments.\n try {\n var bc = new BroadcastChannel(\"builder-connect:\" + window.location.host);\n bc.postMessage({ type: \"builder-connect-error\", message: msg });\n bc.close();\n } catch (e) {}\n if (window.opener && !window.opener.closed) {\n try {\n window.opener.postMessage(\n { type: \"builder-connect-error\", message: msg },\n window.location.origin,\n );\n } catch (e) {}\n }\n } catch (e) {}\n </script>\n </body>\n</html>`;\n}\n\nexport interface RunBuilderAgentArgs {\n prompt: string;\n projectId?: string;\n branchName?: string;\n userEmail?: string;\n userId?: string;\n}\n\nexport interface RunBuilderAgentResult {\n branchName: string;\n projectId: string;\n url: string;\n status: string;\n}\n\nfunction normalizeBuilderApiString(value: unknown, fieldName: string): string {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`Builder agent run returned a blank ${fieldName}`);\n }\n const trimmed = value.trim();\n if (/[\\u0000-\\u001f\\u007f]/.test(trimmed)) {\n throw new Error(`Builder agent run returned a malformed ${fieldName}`);\n }\n return trimmed;\n}\n\nfunction normalizeBuilderBranchUrl(value: unknown): string {\n const urlString = normalizeBuilderApiString(value, \"url\");\n let parsed: URL;\n try {\n parsed = new URL(urlString);\n } catch {\n throw new Error(\"Builder agent run returned a malformed url\");\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\"Builder agent run returned a malformed url\");\n }\n if (\n parsed.hostname !== \"builder.io\" &&\n !parsed.hostname.endsWith(\".builder.io\")\n ) {\n throw new Error(\"Builder agent run returned a non-Builder url\");\n }\n return parsed.toString();\n}\n\n/**\n * POST a prompt to the Builder agents-run API. The Builder agent runs in a\n * cloud sandbox and writes code to a branch; the returned URL opens that\n * branch in the Visual Editor so the user can watch progress.\n *\n * Spec: https://www.builder.io/c/docs/agents-run-api\n */\nexport async function runBuilderAgent(\n args: RunBuilderAgentArgs,\n): Promise<RunBuilderAgentResult> {\n const { resolveBuilderCredentials } =\n await import(\"./credential-provider.js\");\n const creds = await resolveBuilderCredentials();\n if (!creds.privateKey || !creds.publicKey) {\n throw new Error(\"Builder keys are not configured\");\n }\n if (!args.prompt || !args.prompt.trim()) {\n throw new Error(\"prompt is required\");\n }\n const projectId = args.projectId?.trim();\n if (!projectId) {\n throw new Error(\n \"Builder project ID is not configured. Set DISPATCH_BUILDER_PROJECT_ID, BUILDER_BRANCH_PROJECT_ID, or BUILDER_PROJECT_ID.\",\n );\n }\n const builderUserId = args.userId || creds.userId || undefined;\n const builderUserEmail = builderUserId ? undefined : args.userEmail;\n if (!builderUserEmail && !builderUserId) {\n throw new Error(\"userEmail or userId is required\");\n }\n\n const url = new URL(\"/agents/run\", getBuilderApiHost());\n url.searchParams.set(\"apiKey\", creds.publicKey);\n\n const body: Record<string, unknown> = {\n userMessage: { userPrompt: args.prompt },\n projectId,\n };\n if (args.branchName) body.branchName = args.branchName;\n if (builderUserEmail) body.userEmail = builderUserEmail;\n if (builderUserId) body.userId = builderUserId;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${creds.privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const parsed = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n if (!response.ok) {\n const msg =\n typeof parsed.error === \"string\"\n ? parsed.error\n : `Builder agent run failed (${response.status})`;\n throw new Error(msg);\n }\n\n return {\n branchName: normalizeBuilderApiString(parsed.branchName, \"branchName\"),\n projectId:\n typeof parsed.projectId === \"string\" && parsed.projectId.trim()\n ? parsed.projectId.trim()\n : projectId,\n url: normalizeBuilderBranchUrl(parsed.url),\n status:\n typeof parsed.status === \"string\" && parsed.status.trim()\n ? parsed.status.trim()\n : \"processing\",\n };\n}\n\nexport async function requestBuilderBrowserConnection(\n args: BrowserConnectionArgs,\n): Promise<Record<string, unknown>> {\n const { resolveBuilderCredentials } =\n await import(\"./credential-provider.js\");\n const creds = await resolveBuilderCredentials();\n if (!creds.privateKey || !creds.publicKey) {\n throw new Error(\"Builder browser access is not configured\");\n }\n\n const sessionId = args.sessionId?.trim();\n if (!sessionId) {\n throw new Error(\"sessionId is required\");\n }\n\n const url = new URL(\"/codegen/get-browser-connection\", getBuilderApiHost());\n url.searchParams.set(\"apiKey\", creds.publicKey);\n if (creds.userId) {\n url.searchParams.set(\"userId\", creds.userId);\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${creds.privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n sessionId,\n projectId: args.projectId || undefined,\n branchName: args.branchName || undefined,\n proxyOrigin: args.proxyOrigin || undefined,\n proxyDefaultOrigin: args.proxyDefaultOrigin || undefined,\n proxyDst: args.proxyDestination || undefined,\n }),\n });\n\n const body = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n if (!response.ok) {\n const error =\n typeof body.error === \"string\"\n ? body.error\n : `Builder browser request failed (${response.status})`;\n throw new Error(error);\n }\n\n return body;\n}\n"]}
1
+ {"version":3,"file":"builder-browser.js","sourceRoot":"","sources":["../../src/server/builder-browser.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type { H3Event } from \"h3\";\nimport { getHeader } from \"h3\";\nimport { getAuthSecret } from \"./better-auth-instance.js\";\nimport { getAppBasePath, getOrigin } from \"./google-oauth.js\";\n\nconst DEFAULT_BUILDER_APP_HOST = \"https://builder.io\";\nconst DEFAULT_BUILDER_API_HOST = \"https://api.builder.io\";\nconst BUILDER_BROWSER_HOST = \"agent-native-browser\";\nconst BUILDER_BROWSER_CLIENT_ID = \"Agent Native Browser\";\n\nexport const BUILDER_CALLBACK_PATH = \"/_agent-native/builder/callback\";\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n}\n\n/**\n * Query-param name carrying the signed CSRF state on the connect→callback\n * round-trip. Prefixed with `_an_` to avoid collisions if Builder ever\n * adds standard OAuth `state` support to cli-auth. Builder preserves\n * the path/query of `redirect_url` verbatim when redirecting back, so\n * we embed `_an_state=…` inside the redirect_url query string at\n * connect time and read it back on the callback.\n */\nexport const BUILDER_STATE_PARAM = \"_an_state\";\nexport const BUILDER_CONNECT_PARAM = \"_an_connect\";\nexport const BUILDER_CONNECT_OWNER_COOKIE = \"an_builder_connect_owner\";\n\nconst BUILDER_STATE_TTL_MS = 10 * 60 * 1000;\n\nexport interface BuilderBrowserStatus {\n configured: boolean;\n builderEnabled: boolean;\n branchProjectIdConfigured: boolean;\n branchProjectId?: string;\n /**\n * True when `BUILDER_PRIVATE_KEY` is set at the deployment level. This is a\n * fallback credential; signed-in users can still connect their own Builder\n * account, which takes precedence for their request.\n */\n envManaged: boolean;\n credentialSource?: \"user\" | \"org\" | \"env\";\n appHost: string;\n apiHost: string;\n /**\n * Ready-to-open Builder CLI auth URL for this request owner. This points\n * directly at builder.io/cli-auth and carries signed callback state in the\n * nested redirect_url so the popup never has to visit the app's connect\n * trampoline first.\n */\n cliAuthUrl?: string;\n connectUrl: string;\n publicKeyConfigured: boolean;\n privateKeyConfigured: boolean;\n userId?: string;\n orgName?: string;\n orgKind?: string;\n}\n\nexport interface BrowserConnectionArgs {\n sessionId?: string;\n projectId?: string;\n branchName?: string;\n proxyOrigin?: string;\n proxyDefaultOrigin?: string;\n proxyDestination?: string;\n}\n\ntype BuilderSignedTokenPurpose = \"callback\" | \"connect\";\n\nfunction signingKeyForPurpose(purpose: BuilderSignedTokenPurpose): string {\n // Preserve the original callback-state signing key for any in-flight legacy\n // callbacks; use a separate key domain for connect-entry tokens.\n return purpose === \"callback\"\n ? `builder-csrf:${getAuthSecret()}`\n : `builder-connect:${getAuthSecret()}`;\n}\n\nfunction macForParts(\n purpose: BuilderSignedTokenPurpose,\n nonce: string,\n emailEncoded: string,\n ts: number,\n): string {\n return createHmac(\"sha256\", signingKeyForPurpose(purpose))\n .update(`${nonce}.${emailEncoded}.${ts}`)\n .digest(\"base64url\");\n}\n\nfunction signEmailBoundBuilderToken(\n ownerEmail: string,\n purpose: BuilderSignedTokenPurpose,\n): string {\n const nonce = randomBytes(16).toString(\"base64url\");\n const ts = Date.now();\n const emailEncoded = Buffer.from(ownerEmail, \"utf8\").toString(\"base64url\");\n const mac = macForParts(purpose, nonce, emailEncoded, ts);\n return `${nonce}.${emailEncoded}.${ts}.${mac}`;\n}\n\nfunction verifyEmailBoundBuilderToken(\n token: string | null | undefined,\n ownerEmail: string,\n purpose: BuilderSignedTokenPurpose,\n): boolean {\n if (typeof token !== \"string\" || token.length === 0) return false;\n const parts = token.split(\".\");\n if (parts.length !== 4) return false;\n const [nonce, emailEncoded, tsStr, mac] = parts;\n if (!nonce || !emailEncoded || !tsStr || !mac) return false;\n\n let boundEmail: string;\n try {\n boundEmail = Buffer.from(emailEncoded, \"base64url\").toString(\"utf8\");\n } catch {\n return false;\n }\n if (boundEmail !== ownerEmail) return false;\n\n const ts = Number(tsStr);\n if (!Number.isFinite(ts)) return false;\n // Reject expired AND far-future timestamps so leaked tokens do not gain an\n // arbitrary lifetime through clock skew or forged future issue times.\n if (Math.abs(Date.now() - ts) > BUILDER_STATE_TTL_MS) return false;\n\n const expected = Buffer.from(macForParts(purpose, nonce, emailEncoded, ts));\n const candidate = Buffer.from(mac);\n if (expected.length !== candidate.length) return false;\n return timingSafeEqual(expected, candidate);\n}\n\n/**\n * Mint a signed CSRF state token bound to the current session's email\n * and a fresh nonce. Round-trips through Builder's cli-auth flow inside\n * the redirect_url query string and is verified on the callback before\n * any keys are written.\n *\n * Why bind to email: it's the only stable, universally-available\n * identity field across all auth modes (Better Auth, BYOA, AUTH_MODE=local).\n * Binding to the session token instead would put the cookie value in a\n * URL that may end up in server logs / browser history.\n */\nexport function signBuilderCallbackState(sessionEmail: string): string {\n return signEmailBoundBuilderToken(sessionEmail, \"callback\");\n}\n\n/**\n * Verify a state token produced by `signBuilderCallbackState`. Returns\n * false on any malformed, forged, expired, or cross-session token.\n */\nexport function verifyBuilderCallbackState(\n token: string | null | undefined,\n sessionEmail: string,\n): boolean {\n return verifyEmailBoundBuilderToken(token, sessionEmail, \"callback\");\n}\n\nexport function verifyBuilderCallbackStateAndGetOwner(\n token: string | null | undefined,\n): string | null {\n if (typeof token !== \"string\" || token.length === 0) return null;\n const parts = token.split(\".\");\n if (parts.length !== 4) return null;\n const emailEncoded = parts[1];\n if (!emailEncoded) return null;\n\n let ownerEmail: string;\n try {\n ownerEmail = Buffer.from(emailEncoded, \"base64url\").toString(\"utf8\");\n } catch {\n return null;\n }\n if (!ownerEmail) return null;\n return verifyBuilderCallbackState(token, ownerEmail) ? ownerEmail : null;\n}\n\nexport function signBuilderConnectToken(ownerEmail: string): string {\n return signEmailBoundBuilderToken(ownerEmail, \"connect\");\n}\n\nexport function verifyBuilderConnectToken(\n token: string | null | undefined,\n ownerEmail: string,\n): boolean {\n return verifyEmailBoundBuilderToken(token, ownerEmail, \"connect\");\n}\n\nexport function verifyBuilderConnectTokenAndGetOwner(\n token: string | null | undefined,\n): string | null {\n if (typeof token !== \"string\" || token.length === 0) return null;\n const parts = token.split(\".\");\n if (parts.length !== 4) return null;\n const emailEncoded = parts[1];\n if (!emailEncoded) return null;\n\n let ownerEmail: string;\n try {\n ownerEmail = Buffer.from(emailEncoded, \"base64url\").toString(\"utf8\");\n } catch {\n return null;\n }\n if (!ownerEmail) return null;\n return verifyBuilderConnectToken(token, ownerEmail) ? ownerEmail : null;\n}\n\nexport function appendBuilderConnectToken(\n connectUrl: string,\n ownerEmail: string,\n): string {\n const url = new URL(connectUrl);\n url.searchParams.set(\n BUILDER_CONNECT_PARAM,\n signBuilderConnectToken(ownerEmail),\n );\n return url.toString();\n}\n\nfunction isAllowedBrowserReturnUrl(urlString: string): boolean {\n try {\n const parsed = new URL(urlString);\n const hostname = parsed.hostname.toLowerCase();\n const isAllowedProtocol =\n parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\n const isLocalhost =\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\";\n const isBuilderDomain =\n hostname === \"builder.io\" || hostname.endsWith(\".builder.io\");\n const isAgentNativeDomain =\n hostname === \"agent-native.com\" || hostname.endsWith(\".agent-native.com\");\n return (\n isAllowedProtocol &&\n (isLocalhost || isBuilderDomain || isAgentNativeDomain)\n );\n } catch {\n return false;\n }\n}\n\nfunction normalizeOrigin(origin: string): string {\n return origin.replace(/\\/+$/, \"\");\n}\n\nexport function getBuilderAppHost(): string {\n return (\n process.env.BUILDER_APP_HOST ||\n process.env.BUILDER_PUBLIC_APP_HOST ||\n DEFAULT_BUILDER_APP_HOST\n );\n}\n\nexport function getBuilderApiHost(): string {\n return (\n process.env.AIR_HOST ||\n process.env.BUILDER_HOST ||\n process.env.BUILDER_API_HOST ||\n DEFAULT_BUILDER_API_HOST\n );\n}\n\nfunction getConfiguredBuilderBranchProjectId(): string | undefined {\n const projectId =\n process.env.DISPATCH_BUILDER_PROJECT_ID ||\n process.env.BUILDER_BRANCH_PROJECT_ID ||\n process.env.BUILDER_PROJECT_ID;\n return projectId?.trim() || undefined;\n}\n\nexport function getBuilderBranchProjectId(): string {\n return getConfiguredBuilderBranchProjectId() || \"\";\n}\n\nexport function isBuilderBranchingEnabled(): boolean {\n return !!getConfiguredBuilderBranchProjectId();\n}\n\nexport async function resolveBuilderBranchProjectId(): Promise<string> {\n const envProjectId = getConfiguredBuilderBranchProjectId();\n if (envProjectId) return envProjectId;\n\n try {\n const { resolveSecret } = await import(\"./credential-provider.js\");\n for (const key of [\n \"DISPATCH_BUILDER_PROJECT_ID\",\n \"BUILDER_BRANCH_PROJECT_ID\",\n \"BUILDER_PROJECT_ID\",\n ]) {\n const value = await resolveSecret(key);\n if (value?.trim()) return value.trim();\n }\n } catch {\n // Secrets table or request context not ready — treat as not configured.\n }\n\n return \"\";\n}\n\nexport async function resolveIsBuilderBranchingEnabled(): Promise<boolean> {\n return !!(await resolveBuilderBranchProjectId());\n}\n\n/**\n * Build the Builder cli-auth URL for the connect popup. When a signed\n * `state` token is supplied it is embedded inside the `redirect_url`\n * query string so it survives Builder's redirect verbatim — Builder\n * preserves the redirect_url's existing query when appending p-key /\n * api-key / etc., so we don't depend on Builder echoing a top-level\n * `state` parameter (it doesn't).\n *\n * The user-facing connect entry point is `/_agent-native/builder/connect`\n * (a server-side 302). Status / chat-card responses surface that path\n * rather than the cli-auth URL directly, so the 302 handler can mint a\n * fresh state bound to the current session on every click.\n */\nexport function buildBuilderCliAuthUrl(\n origin: string,\n state: string | null = null,\n): string {\n const normalizedOrigin = normalizeOrigin(origin);\n const appBasePath = getAppBasePath();\n const callbackUrl = new URL(\n `${appBasePath}${BUILDER_CALLBACK_PATH}`,\n normalizedOrigin,\n );\n if (state) {\n callbackUrl.searchParams.set(BUILDER_STATE_PARAM, state);\n }\n const url = new URL(\"/cli-auth\", getBuilderAppHost());\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"host\", BUILDER_BROWSER_HOST);\n url.searchParams.set(\"client_id\", BUILDER_BROWSER_CLIENT_ID);\n url.searchParams.set(\"redirect_url\", callbackUrl.toString());\n url.searchParams.set(\"preview_url\", `${normalizedOrigin}${appBasePath}`);\n url.searchParams.set(\"framework\", \"agent-native\");\n return url.toString();\n}\n\n/**\n * The bare URL surfaced to clients as `connectUrl`. The status route appends\n * a short-lived signed connect token when it knows the current owner; this\n * helper stays bare so server-rendered cards can still render without a\n * request-bound owner and the connect route can fall back to Fetch Metadata.\n */\nexport function getBuilderBrowserConnectUrl(origin: string): string {\n return `${normalizeOrigin(origin)}${getAppBasePath()}/_agent-native/builder/connect`;\n}\n\nfunction firstHeaderValue(value: string | undefined): string | undefined {\n return value?.split(\",\")[0]?.trim() || undefined;\n}\n\nfunction readEventHeader(event: H3Event, name: string): string | undefined {\n try {\n return getHeader(event, name) ?? undefined;\n } catch {\n const headers = (\n event as unknown as {\n node?: {\n req?: { headers?: Record<string, string | string[] | undefined> };\n };\n }\n ).node?.req?.headers;\n const value = headers?.[name.toLowerCase()] ?? headers?.[name];\n if (Array.isArray(value)) return value[0];\n return typeof value === \"string\" ? value : undefined;\n }\n}\n\nfunction isTrustedBuilderRequestHost(host: string | undefined): boolean {\n if (!host) return false;\n try {\n const hostname = new URL(`http://${host}`).hostname.toLowerCase();\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname === \"[::1]\" ||\n hostname === \"builderio.xyz\" ||\n hostname.endsWith(\".builderio.xyz\") ||\n hostname === \"builderio.dev\" ||\n hostname.endsWith(\".builderio.dev\") ||\n hostname === \"builder.codes\" ||\n hostname.endsWith(\".builder.codes\") ||\n hostname === \"builder.io\" ||\n hostname.endsWith(\".builder.io\") ||\n hostname === \"builder.my\" ||\n hostname.endsWith(\".builder.my\")\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Builder CLI-auth does not need the workspace OAuth relay that Google uses.\n * In Builder/Fusion previews, keep connect + callback URLs on the actual app\n * preview origin so the signed connect token and pending row are verified by\n * the same deployment that minted them.\n */\nexport function getBuilderBrowserOriginForEvent(event: H3Event): string {\n const headerHost = firstHeaderValue(\n readEventHeader(event, \"x-forwarded-host\") ||\n readEventHeader(event, \"host\"),\n );\n if (!isTrustedBuilderRequestHost(headerHost)) return getOrigin(event);\n\n const rawProto = firstHeaderValue(\n readEventHeader(event, \"x-forwarded-proto\"),\n );\n const proto =\n rawProto === \"http\" || rawProto === \"https\"\n ? rawProto\n : process.env.NODE_ENV === \"production\"\n ? \"https\"\n : \"http\";\n return `${proto}://${headerHost}`;\n}\n\nexport function getBuilderBrowserStatus(origin: string): BuilderBrowserStatus {\n const branchProjectId = getConfiguredBuilderBranchProjectId();\n const envManaged = !!process.env.BUILDER_PRIVATE_KEY;\n return {\n configured: !!(\n process.env.BUILDER_PRIVATE_KEY && process.env.BUILDER_PUBLIC_KEY\n ),\n builderEnabled: isBuilderBranchingEnabled(),\n branchProjectIdConfigured: !!branchProjectId,\n branchProjectId: branchProjectId || undefined,\n envManaged,\n credentialSource: envManaged ? \"env\" : undefined,\n appHost: getBuilderAppHost(),\n apiHost: getBuilderApiHost(),\n connectUrl: getBuilderBrowserConnectUrl(origin),\n publicKeyConfigured: !!process.env.BUILDER_PUBLIC_KEY,\n privateKeyConfigured: !!process.env.BUILDER_PRIVATE_KEY,\n userId: process.env.BUILDER_USER_ID || undefined,\n orgName: process.env.BUILDER_ORG_NAME || undefined,\n orgKind: process.env.BUILDER_ORG_KIND || undefined,\n };\n}\n\nexport function getBuilderBrowserStatusForEvent(\n event: H3Event,\n): BuilderBrowserStatus {\n return getBuilderBrowserStatus(getBuilderBrowserOriginForEvent(event));\n}\n\n/**\n * Env vars written by the Builder CLI-auth callback. Single source of truth\n * for the connect/disconnect key set — `getBuilderCallbackEnvVars` and the\n * disconnect handler's scrub loop both derive from this list, so drift\n * (e.g. disconnect silently leaving `BUILDER_USER_ID` behind because\n * someone added a key to one site but not the other) is impossible.\n */\nexport const BUILDER_ENV_KEYS = [\n \"BUILDER_PRIVATE_KEY\",\n \"BUILDER_PUBLIC_KEY\",\n \"BUILDER_USER_ID\",\n \"BUILDER_ORG_NAME\",\n \"BUILDER_ORG_KIND\",\n] as const;\n\nexport type BuilderEnvKey = (typeof BUILDER_ENV_KEYS)[number];\n\nexport function getBuilderCallbackEnvVars(params: {\n privateKey?: string | null;\n publicKey?: string | null;\n userId?: string | null;\n orgName?: string | null;\n orgKind?: string | null;\n}) {\n const values: Record<BuilderEnvKey, string> = {\n BUILDER_PRIVATE_KEY: params.privateKey?.trim() || \"\",\n BUILDER_PUBLIC_KEY: params.publicKey?.trim() || \"\",\n BUILDER_USER_ID: params.userId?.trim() || \"\",\n BUILDER_ORG_NAME: params.orgName?.trim() || \"\",\n BUILDER_ORG_KIND: params.orgKind?.trim() || \"\",\n };\n return BUILDER_ENV_KEYS.map((key) => ({ key, value: values[key] }));\n}\n\nexport function resolveSafePreviewUrl(\n previewUrl: string | null | undefined,\n event: H3Event,\n): string {\n if (previewUrl && isAllowedBrowserReturnUrl(previewUrl)) {\n return previewUrl;\n }\n return getOrigin(event);\n}\n\n/**\n * Inline theme-detection script that runs before the body paints. Reads the\n * app's stored theme preference (same `localStorage.theme` key used by the\n * client-side theme manager) and falls back to `prefers-color-scheme`. This\n * way the popup matches whatever theme the user already picked in the app\n * — light, dark, or auto — instead of always rendering in OS-default mode.\n */\nconst BUILDER_CALLBACK_THEME_SCRIPT = `<script>\n(function () {\n try {\n var stored = window.localStorage && window.localStorage.getItem(\"theme\");\n var resolved;\n if (stored === \"light\" || stored === \"dark\") {\n resolved = stored;\n } else {\n var mq = window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\");\n resolved = mq && mq.matches ? \"dark\" : \"light\";\n }\n document.documentElement.classList.add(resolved);\n document.documentElement.style.colorScheme = resolved;\n } catch (e) {}\n})();\n</script>`;\n\n/**\n * Brand-aligned CSS for the Builder connect callback / error pages.\n *\n * Uses the same neutral-zinc palette and Inter font as the rest of the\n * framework's templates (see `templates/*\\/app/global.css`). Tokens map to\n * the same HSL values the templates set on `:root` / `.dark`, so the popup\n * reads as part of the same app — not a stranded marketing page.\n */\nconst BUILDER_CALLBACK_BASE_CSS = `\n :root {\n --bg: hsl(0 0% 100%);\n --fg: hsl(220 10% 10%);\n --muted-fg: hsl(220 5% 45%);\n --card: hsl(0 0% 100%);\n --border: hsl(220 10% 90%);\n --primary: hsl(220 10% 15%);\n --primary-fg: hsl(0 0% 100%);\n --primary-hover: hsl(220 10% 25%);\n --success-bg: hsl(143 50% 96%);\n --success-fg: hsl(143 60% 32%);\n --error-fg: hsl(0 75% 45%);\n --error-bg: hsl(0 80% 97%);\n --error-border: hsl(0 80% 92%);\n }\n :root.dark {\n --bg: hsl(220 6% 6%);\n --fg: hsl(0 0% 92%);\n --muted-fg: hsl(220 4% 60%);\n --card: hsl(220 5% 8%);\n --border: hsl(220 4% 14%);\n --primary: hsl(0 0% 92%);\n --primary-fg: hsl(220 6% 6%);\n --primary-hover: hsl(0 0% 75%);\n --success-bg: hsl(143 30% 12%);\n --success-fg: hsl(143 50% 70%);\n --error-fg: hsl(0 80% 75%);\n --error-bg: hsl(0 35% 12%);\n --error-border: hsl(0 30% 20%);\n }\n *, *::before, *::after { box-sizing: border-box; }\n html, body { height: 100%; }\n body {\n margin: 0;\n min-height: 100vh;\n display: grid;\n place-items: center;\n background: var(--bg);\n color: var(--fg);\n font-family: \"Inter\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif;\n font-size: 14px;\n line-height: 1.55;\n font-feature-settings: \"cv02\", \"cv03\", \"cv04\", \"cv11\";\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n padding: 24px;\n }\n .card {\n width: min(420px, 100%);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 32px 28px;\n background: var(--card);\n text-align: center;\n }\n .icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n border-radius: 999px;\n margin-bottom: 16px;\n }\n .icon svg { width: 22px; height: 22px; display: block; }\n .icon-success { background: var(--success-bg); color: var(--success-fg); }\n .icon-error { background: var(--error-bg); color: var(--error-fg); }\n h1 {\n margin: 0 0 6px;\n font-size: 17px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--fg);\n }\n p {\n margin: 0 0 4px;\n color: var(--fg);\n font-size: 14px;\n }\n p.muted { color: var(--muted-fg); }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 36px;\n padding: 0 16px;\n margin-top: 20px;\n background: var(--primary);\n color: var(--primary-fg);\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n text-decoration: none;\n border: none;\n cursor: pointer;\n }\n .btn:hover { background: var(--primary-hover); }\n pre.error-detail {\n margin: 16px 0 0;\n padding: 10px 12px;\n background: var(--error-bg);\n border: 1px solid var(--error-border);\n border-radius: 8px;\n color: var(--error-fg);\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 12px;\n line-height: 1.5;\n text-align: left;\n white-space: pre-wrap;\n word-break: break-word;\n }\n`;\n\nexport function createBuilderBrowserCallbackPage(previewUrl: string): string {\n const escapedUrl = JSON.stringify(previewUrl);\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n <title>Builder connected</title>\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@400;500;600&display=swap\" rel=\"stylesheet\" />\n ${BUILDER_CALLBACK_THEME_SCRIPT}\n <style>${BUILDER_CALLBACK_BASE_CSS}</style>\n </head>\n <body>\n <main class=\"card\" role=\"status\" aria-live=\"polite\">\n <span class=\"icon icon-success\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>\n </span>\n <h1>Builder connected</h1>\n <p>Browser access is now available to your app.</p>\n <p class=\"muted\">You can close this tab and return to the workspace.</p>\n <a class=\"btn\" href=${escapedUrl}>Open the workspace</a>\n </main>\n <script>\n // Tell the opener tab the connect succeeded. The parent has two ways\n // to learn this:\n // 1. The popup-based connect flow (window.open + 2s polling on\n // /builder/status) — picks it up via the next poll within ~2s.\n // 2. The link-based \"Use Builder\" flow (target=\"_blank\" tab) — the\n // AgentPanel only fetches /builder/status once on mount, so it\n // stays stuck on \"Use Builder\" unless we explicitly signal.\n // BroadcastChannel + postMessage cover both cases. Use the same channel\n // name as the error path (createBuilderBrowserCallbackErrorPage) and\n // mirror the parent-side listener in useBuilderStatus / useBuilderConnectUrl.\n try {\n var bc = new BroadcastChannel(\"builder-connect:\" + window.location.host);\n bc.postMessage({ type: \"builder-connect-success\" });\n bc.close();\n } catch (e) {}\n try {\n if (window.opener && !window.opener.closed) {\n window.opener.postMessage(\n { type: \"builder-connect-success\" },\n window.location.origin,\n );\n }\n } catch (e) {}\n // If we're a popup opened by the app, close ourselves and let the\n // parent tab keep polling for connection status. If close() is\n // blocked (e.g. we're the top-level tab because popups were\n // downgraded), fall back to navigating back to the workspace.\n window.setTimeout(function () {\n try { window.close(); } catch (e) {}\n window.setTimeout(function () {\n if (!window.closed) {\n window.location.replace(${escapedUrl});\n }\n }, 200);\n }, 700);\n </script>\n </body>\n</html>`;\n}\n\n/**\n * HTML page rendered inside the OAuth popup when the callback handler caught\n * an error persisting the per-user Builder credentials. Without this, the\n * popup would show the success page even though the write failed — leaving\n * the parent window stuck on \"Waiting for Builder…\" until the 5-minute poll\n * timeout fires (Midhun reported this on 2026-04-28).\n *\n * The page does two things:\n * 1. Shows the user a clear \"couldn't save credentials\" message with the\n * underlying error so they can retry or report.\n * 2. `postMessage`s the parent (same-origin opener) so the connect-flow\n * polling stops immediately rather than waiting for the next /status\n * poll to surface the SQL `builder-connect-error:<email>` row.\n */\nexport function createBuilderBrowserCallbackErrorPage(\n message: string,\n opts: {\n title?: string;\n body?: string;\n closeHint?: string;\n } = {},\n): string {\n const escapedMessage = JSON.stringify(message);\n const title = opts.title ?? \"Couldn't save Builder connection\";\n const body =\n opts.body ??\n \"Builder authorized your account but the server couldn't persist the credentials.\";\n const closeHint =\n opts.closeHint ?? \"You can close this tab and try again from settings.\";\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n <title>Builder connect failed</title>\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@400;500;600&display=swap\" rel=\"stylesheet\" />\n ${BUILDER_CALLBACK_THEME_SCRIPT}\n <style>${BUILDER_CALLBACK_BASE_CSS}</style>\n </head>\n <body>\n <main class=\"card\" role=\"alert\" aria-live=\"assertive\">\n <span class=\"icon icon-error\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\"></path><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"></line><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"></line></svg>\n </span>\n <h1>${escapeHtml(title)}</h1>\n <p class=\"muted\">${escapeHtml(body)}</p>\n <pre class=\"error-detail\" id=\"msg\"></pre>\n <p class=\"muted\" style=\"margin-top:12px\">${escapeHtml(closeHint)}</p>\n </main>\n <script>\n try {\n var msg = ${escapedMessage};\n document.getElementById(\"msg\").textContent = msg;\n // Notify the parent tab immediately so its polling loop stops\n // without waiting for the next /builder/status tick.\n //\n // BroadcastChannel works across same-origin windows regardless of\n // opener access — it is the only reliable channel here because\n // popups opened with window.open(..., \"noopener\") or links with\n // rel=\"noopener\" have window.opener === null. The legacy\n // window.opener.postMessage path is kept as a belt-and-suspenders\n // fallback for non-BroadcastChannel environments.\n try {\n var bc = new BroadcastChannel(\"builder-connect:\" + window.location.host);\n bc.postMessage({ type: \"builder-connect-error\", message: msg });\n bc.close();\n } catch (e) {}\n if (window.opener && !window.opener.closed) {\n try {\n window.opener.postMessage(\n { type: \"builder-connect-error\", message: msg },\n window.location.origin,\n );\n } catch (e) {}\n }\n } catch (e) {}\n </script>\n </body>\n</html>`;\n}\n\nexport interface RunBuilderAgentArgs {\n prompt: string;\n projectId?: string;\n branchName?: string;\n userEmail?: string;\n userId?: string;\n}\n\nexport interface RunBuilderAgentResult {\n branchName: string;\n projectId: string;\n url: string;\n status: string;\n}\n\nfunction normalizeBuilderApiString(value: unknown, fieldName: string): string {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`Builder agent run returned a blank ${fieldName}`);\n }\n const trimmed = value.trim();\n if (/[\\u0000-\\u001f\\u007f]/.test(trimmed)) {\n throw new Error(`Builder agent run returned a malformed ${fieldName}`);\n }\n return trimmed;\n}\n\nfunction normalizeBuilderBranchUrl(value: unknown): string {\n const urlString = normalizeBuilderApiString(value, \"url\");\n let parsed: URL;\n try {\n parsed = new URL(urlString);\n } catch {\n throw new Error(\"Builder agent run returned a malformed url\");\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\"Builder agent run returned a malformed url\");\n }\n if (\n parsed.hostname !== \"builder.io\" &&\n !parsed.hostname.endsWith(\".builder.io\")\n ) {\n throw new Error(\"Builder agent run returned a non-Builder url\");\n }\n return parsed.toString();\n}\n\n/**\n * POST a prompt to the Builder agents-run API. The Builder agent runs in a\n * cloud sandbox and writes code to a branch; the returned URL opens that\n * branch in the Visual Editor so the user can watch progress.\n *\n * Spec: https://www.builder.io/c/docs/agents-run-api\n */\nexport async function runBuilderAgent(\n args: RunBuilderAgentArgs,\n): Promise<RunBuilderAgentResult> {\n const { resolveBuilderCredentials } =\n await import(\"./credential-provider.js\");\n const creds = await resolveBuilderCredentials();\n if (!creds.privateKey || !creds.publicKey) {\n throw new Error(\"Builder keys are not configured\");\n }\n if (!args.prompt || !args.prompt.trim()) {\n throw new Error(\"prompt is required\");\n }\n const projectId = args.projectId?.trim();\n if (!projectId) {\n throw new Error(\n \"Builder project ID is not configured. Set DISPATCH_BUILDER_PROJECT_ID, BUILDER_BRANCH_PROJECT_ID, or BUILDER_PROJECT_ID.\",\n );\n }\n const builderUserId = args.userId || creds.userId || undefined;\n const builderUserEmail = builderUserId ? undefined : args.userEmail;\n if (!builderUserEmail && !builderUserId) {\n throw new Error(\"userEmail or userId is required\");\n }\n\n const url = new URL(\"/agents/run\", getBuilderApiHost());\n url.searchParams.set(\"apiKey\", creds.publicKey);\n\n const body: Record<string, unknown> = {\n userMessage: { userPrompt: args.prompt },\n projectId,\n };\n if (args.branchName) body.branchName = args.branchName;\n if (builderUserEmail) body.userEmail = builderUserEmail;\n if (builderUserId) body.userId = builderUserId;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${creds.privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const parsed = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n if (!response.ok) {\n const msg =\n typeof parsed.error === \"string\"\n ? parsed.error\n : `Builder agent run failed (${response.status})`;\n throw new Error(msg);\n }\n\n return {\n branchName: normalizeBuilderApiString(parsed.branchName, \"branchName\"),\n projectId:\n typeof parsed.projectId === \"string\" && parsed.projectId.trim()\n ? parsed.projectId.trim()\n : projectId,\n url: normalizeBuilderBranchUrl(parsed.url),\n status:\n typeof parsed.status === \"string\" && parsed.status.trim()\n ? parsed.status.trim()\n : \"processing\",\n };\n}\n\nexport async function requestBuilderBrowserConnection(\n args: BrowserConnectionArgs,\n): Promise<Record<string, unknown>> {\n const { resolveBuilderCredentials } =\n await import(\"./credential-provider.js\");\n const creds = await resolveBuilderCredentials();\n if (!creds.privateKey || !creds.publicKey) {\n throw new Error(\"Builder browser access is not configured\");\n }\n\n const sessionId = args.sessionId?.trim();\n if (!sessionId) {\n throw new Error(\"sessionId is required\");\n }\n\n const url = new URL(\"/codegen/get-browser-connection\", getBuilderApiHost());\n url.searchParams.set(\"apiKey\", creds.publicKey);\n if (creds.userId) {\n url.searchParams.set(\"userId\", creds.userId);\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${creds.privateKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n sessionId,\n projectId: args.projectId || undefined,\n branchName: args.branchName || undefined,\n proxyOrigin: args.proxyOrigin || undefined,\n proxyDefaultOrigin: args.proxyDefaultOrigin || undefined,\n proxyDst: args.proxyDestination || undefined,\n }),\n });\n\n const body = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n if (!response.ok) {\n const error =\n typeof body.error === \"string\"\n ? body.error\n : `Builder browser request failed (${response.status})`;\n throw new Error(error);\n }\n\n return body;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"core-routes-plugin.d.ts","sourceRoot":"","sources":["../../src/server/core-routes-plugin.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAKlC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAkFvD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AAoIvD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,IAAI,CAWf;AAUD,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qEAAqE;IACrE,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,cAAc,CAy8DhB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,cAAyC,CAAC"}
1
+ {"version":3,"file":"core-routes-plugin.d.ts","sourceRoot":"","sources":["../../src/server/core-routes-plugin.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAKlC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAqFvD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AAoIvD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,IAAI,CAWf;AAUD,KAAK,cAAc,GAAG,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qEAAqE;IACrE,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,cAAc,CAggEhB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,cAAyC,CAAC"}
@@ -6,7 +6,7 @@ import { createPollHandler } from "./poll.js";
6
6
  import { createPollEventsHandler } from "./poll-events.js";
7
7
  import { upsertEnvFile } from "./create-server.js";
8
8
  import { readBody } from "./h3-helpers.js";
9
- import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_OWNER_COOKIE, BUILDER_ENV_KEYS, appendBuilderConnectToken, buildBuilderCliAuthUrl, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, getBuilderBrowserStatusForEvent, resolveBuilderBranchProjectId, resolveSafePreviewUrl, runBuilderAgent, verifyBuilderConnectToken, verifyBuilderConnectTokenAndGetOwner, signBuilderConnectToken, } from "./builder-browser.js";
9
+ import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_OWNER_COOKIE, BUILDER_ENV_KEYS, BUILDER_STATE_PARAM, appendBuilderConnectToken, buildBuilderCliAuthUrl, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, resolveBuilderBranchProjectId, resolveSafePreviewUrl, runBuilderAgent, signBuilderCallbackState, verifyBuilderConnectTokenAndGetOwner, verifyBuilderCallbackStateAndGetOwner, signBuilderConnectToken, } from "./builder-browser.js";
10
10
  import { getState, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "../application-state/handlers.js";
11
11
  import { getSetting, putSetting, deleteSetting } from "../settings/store.js";
12
12
  import { getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/user-settings.js";
@@ -375,6 +375,21 @@ export function createCoreRoutesPlugin(options = {}) {
375
375
  }
376
376
  }
377
377
  if (path === `${P}/builder/callback`) {
378
+ // Prefer the signed _an_state owner over the legacy
379
+ // an_builder_connect_owner cookie. The cookie can be stale on a
380
+ // shared browser — user A signed in earlier, user B starts a fresh
381
+ // callback with a signed state for B — and using the cookie first
382
+ // would mis-attribute B's Builder credentials to A. The signed
383
+ // state is per-flow and TTL-bounded, so it's authoritative when
384
+ // both are present.
385
+ const ownerFromCallbackState = verifyBuilderCallbackStateAndGetOwner(new URLSearchParams(search).get(BUILDER_STATE_PARAM));
386
+ if (ownerFromCallbackState) {
387
+ return {
388
+ email: ownerFromCallbackState,
389
+ session: null,
390
+ anonymous: false,
391
+ };
392
+ }
378
393
  const ownerFromCookie = readBuilderConnectOwnerCookie(event);
379
394
  if (ownerFromCookie) {
380
395
  return { email: ownerFromCookie, session: null, anonymous: false };
@@ -393,9 +408,17 @@ export function createCoreRoutesPlugin(options = {}) {
393
408
  const withConnectToken = (status) => {
394
409
  if (!userEmail)
395
410
  return status;
411
+ // Use the preview-aware origin (same one connectUrl uses) so
412
+ // Builder's CLI auth flow returns to the active preview deployment
413
+ // instead of bouncing through the gateway. getOrigin(event) falls
414
+ // back to the gateway on Builder preview hosts, which defeats the
415
+ // whole preview-aware isolation goal of this PR.
416
+ const callbackOrigin = getBuilderBrowserOriginForEvent(event);
417
+ const cliAuthUrl = buildBuilderCliAuthUrl(callbackOrigin, signBuilderCallbackState(userEmail));
396
418
  return {
397
419
  ...status,
398
420
  connectUrl: appendBuilderConnectToken(status.connectUrl, userEmail),
421
+ cliAuthUrl,
399
422
  };
400
423
  };
401
424
  // Pass the user's active orgId so status reads can fall back to
@@ -530,7 +553,7 @@ export function createCoreRoutesPlugin(options = {}) {
530
553
  return true;
531
554
  if (fetchSite)
532
555
  return false; // browser told us it's cross-site/same-site
533
- const expected = getOrigin(event).replace(/\/+$/, "");
556
+ const expected = getBuilderBrowserOriginForEvent(event).replace(/\/+$/, "");
534
557
  const origin = getHeader(event, "origin");
535
558
  if (origin)
536
559
  return origin.replace(/\/+$/, "") === expected;
@@ -579,9 +602,14 @@ export function createCoreRoutesPlugin(options = {}) {
579
602
  setResponseStatus(event, 401);
580
603
  return { error: "Authentication required" };
581
604
  }
582
- const requestUrl = new URL(`${event.url?.pathname || "/"}${event.url?.search || ""}`, getOrigin(event));
605
+ const requestUrl = new URL(`${event.url?.pathname || "/"}${event.url?.search || ""}`, getBuilderBrowserOriginForEvent(event));
583
606
  const connectToken = requestUrl.searchParams.get(BUILDER_CONNECT_PARAM);
584
- const hasValidConnectToken = verifyBuilderConnectToken(connectToken, ownerEmail);
607
+ const connectTokenOwner = verifyBuilderConnectTokenAndGetOwner(connectToken);
608
+ // The token must both be well-formed AND minted for the current
609
+ // session owner. Without the owner check, an attacker holding any
610
+ // valid signed token could trick a victim into hitting this route
611
+ // with that token to bypass the cross-origin gate.
612
+ const hasValidConnectToken = Boolean(connectTokenOwner) && connectTokenOwner === ownerEmail;
585
613
  // Same-origin gate. Sec-Fetch-Site remains the fast path; the signed
586
614
  // connect token is the compatibility path for legitimate embedded or
587
615
  // local desktop popups stamped as same-site/cross-site by the browser.
@@ -594,6 +622,7 @@ export function createCoreRoutesPlugin(options = {}) {
594
622
  stage: "connect",
595
623
  has_connect_token: Boolean(connectToken),
596
624
  has_valid_connect_token: false,
625
+ connect_token_owner_matches_context: false,
597
626
  sec_fetch_site: getHeader(event, "sec-fetch-site") ?? null,
598
627
  });
599
628
  await putSetting(`builder-connect-error:${ownerEmail}`, {
@@ -651,12 +680,14 @@ export function createCoreRoutesPlugin(options = {}) {
651
680
  }
652
681
  await trackBuilderLifecycle(event, "builder connect started", ownerEmail, {
653
682
  stage: "connect",
683
+ connect_token_owner_matches_context: !connectTokenOwner || connectTokenOwner === ownerEmail,
654
684
  });
655
685
  setBuilderConnectOwnerCookie(event, ownerEmail);
656
- // Build the cli-auth URL without embedding state in redirect_url:
657
- // Builder's /cli-auth appends params directly to redirect_url and
658
- // does not preserve any pre-existing query string we put there.
659
- const cliAuthUrl = buildBuilderCliAuthUrl(getOrigin(event), null);
686
+ // The primary UI now opens the signed Builder /cli-auth URL directly
687
+ // from /builder/status. Keep this legacy trampoline working for older
688
+ // clients, but still send it to Builder immediately and include signed
689
+ // callback state so the callback does not depend on popup cookies.
690
+ const cliAuthUrl = buildBuilderCliAuthUrl(getBuilderBrowserOriginForEvent(event), signBuilderCallbackState(ownerEmail));
660
691
  setResponseStatus(event, 302);
661
692
  setResponseHeader(event, "Location", cliAuthUrl);
662
693
  return "";
@@ -762,18 +793,22 @@ export function createCoreRoutesPlugin(options = {}) {
762
793
  }
763
794
  clearBuilderConnectOwnerCookie(event);
764
795
  const requestUrl = new URL(`${event.url?.pathname || "/"}${event.url?.search || ""}`, getOrigin(event));
765
- // Verify and consume the server-side pending-connect row that the
766
- // /builder/connect route stored. This replaces the old URL-embedded
767
- // signed CSRF state (_an_state) which Builder's /cli-auth page was
768
- // stripping from the redirect_url query string.
796
+ const callbackStateOwner = verifyBuilderCallbackStateAndGetOwner(requestUrl.searchParams.get(BUILDER_STATE_PARAM));
797
+ const hasValidCallbackState = callbackStateOwner === ownerEmail;
798
+ // Verify either:
799
+ // 1. the signed callback state embedded in redirect_url by
800
+ // /builder/status (primary flow), or
801
+ // 2. the server-side pending-connect row written by the legacy
802
+ // /builder/connect trampoline.
769
803
  //
770
- // The delete must succeed before we proceed — otherwise a DB blip
804
+ // For the pending-row path, delete must succeed before we proceed;
805
+ // otherwise a DB blip
771
806
  // leaves the row in place and the same callback URL can be
772
807
  // replayed against the same session for up to 10 minutes (the
773
808
  // TTL window). Treat a delete failure as a hard failure: the
774
809
  // user retries, the next /builder/connect call rewrites the
775
810
  // pending row.
776
- let pendingValid = false;
811
+ let pendingValid = hasValidCallbackState;
777
812
  let pendingError = null;
778
813
  try {
779
814
  const pending = (await getSetting(`builder-pending-connect:${ownerEmail}`));
@@ -785,9 +820,11 @@ export function createCoreRoutesPlugin(options = {}) {
785
820
  pendingValid = true;
786
821
  }
787
822
  catch (err) {
788
- pendingError =
789
- "Could not consume pending-connect token (storage error). Please retry.";
790
- console.error("[builder] deleteSetting failed for pending-connect refusing to proceed (replay risk):", err?.message ?? err);
823
+ if (!hasValidCallbackState) {
824
+ pendingError =
825
+ "Could not consume pending-connect token (storage error). Please retry.";
826
+ console.error("[builder] deleteSetting failed for pending-connect — refusing to proceed (replay risk):", err?.message ?? err);
827
+ }
791
828
  }
792
829
  }
793
830
  }
@@ -811,8 +848,11 @@ export function createCoreRoutesPlugin(options = {}) {
811
848
  }
812
849
  if (!pendingValid) {
813
850
  await trackBuilderLifecycle(event, "builder connect failed", ownerEmail, {
814
- reason: "missing_pending_connect",
851
+ reason: hasValidCallbackState
852
+ ? "callback_state_unexpectedly_rejected"
853
+ : "missing_pending_connect",
815
854
  stage: "callback",
855
+ has_callback_state: Boolean(requestUrl.searchParams.get(BUILDER_STATE_PARAM)),
816
856
  });
817
857
  const msg = "No active connect flow found. Restart the Builder connect flow from Settings.";
818
858
  // Write an error signal so the polling loop in the parent tab