@agent-native/core 0.214.0 → 0.217.0

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 (196) hide show
  1. package/dist/action.js +54 -1
  2. package/dist/agent/actions/disconnect-chatgpt-subscription.js +5 -0
  3. package/dist/agent/actions/get-agentkit-capabilities.js +3 -3
  4. package/dist/agent/actions/manage-builder-connection.js +5 -0
  5. package/dist/agent/actions/manage-provider-key-policy.js +5 -0
  6. package/dist/agent/actions/manage-provider-models.js +5 -0
  7. package/dist/agent/actions/manage-service-providers.js +5 -0
  8. package/dist/agent/actions/select-chatgpt-subscription-account.js +5 -0
  9. package/dist/agent/context-xray/actions/context-evict.js +5 -0
  10. package/dist/agent/context-xray/actions/context-pin.js +5 -0
  11. package/dist/agent/context-xray/actions/context-report.js +5 -0
  12. package/dist/agent/context-xray/actions/context-restore.js +5 -0
  13. package/dist/agent/execution-fence.d.ts +1 -0
  14. package/dist/agent/execution-fence.js +5 -0
  15. package/dist/agent/production-agent.d.ts +35 -2
  16. package/dist/agent/production-agent.js +629 -32
  17. package/dist/agent/run-manager.d.ts +2 -0
  18. package/dist/agent/run-manager.js +397 -115
  19. package/dist/agent/run-slot-lock.d.ts +2 -0
  20. package/dist/agent/run-slot-lock.js +6 -0
  21. package/dist/agent/run-store.d.ts +5 -0
  22. package/dist/agent/run-store.js +423 -156
  23. package/dist/agent/thread-data-builder.js +27 -5
  24. package/dist/agent/types.d.ts +6 -12
  25. package/dist/agentkit/capabilities.d.ts +1 -1
  26. package/dist/app-config/analytics.js +1 -1
  27. package/dist/application-state/store.d.ts +5 -0
  28. package/dist/application-state/store.js +26 -0
  29. package/dist/automation/index.d.ts +1 -1
  30. package/dist/chat-threads/schema-migrations.js +17 -0
  31. package/dist/chat-threads/store.d.ts +21 -1
  32. package/dist/chat-threads/store.js +223 -41
  33. package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
  34. package/dist/cli/skills-content/turn-into-app-skill.js +20 -18
  35. package/dist/cli/templates-meta.js +14 -0
  36. package/dist/client/analytics-session.d.ts +7 -0
  37. package/dist/client/analytics-session.js +27 -1
  38. package/dist/client/analytics.js +276 -31
  39. package/dist/client/chat/agentkit-agent-native.js +79 -3
  40. package/dist/client/chat/agentkit-protocol.js +80 -42
  41. package/dist/client/chat/runtime.d.ts +5 -1
  42. package/dist/client/chat/runtime.js +46 -3
  43. package/dist/client/consent.d.ts +48 -0
  44. package/dist/client/consent.js +413 -0
  45. package/dist/client/onboarding/index.d.ts +1 -0
  46. package/dist/client/onboarding/index.js +1 -0
  47. package/dist/client/onboarding/use-onboarding-checklist.d.ts +23 -0
  48. package/dist/client/onboarding/use-onboarding-checklist.js +115 -0
  49. package/dist/client/review/use-review.d.ts +2 -0
  50. package/dist/client/review/use-review.js +28 -1
  51. package/dist/client/session-replay.d.ts +1 -1
  52. package/dist/client/session-replay.js +142 -3
  53. package/dist/client/sharing/useResourceAccessGate.d.ts +3 -2
  54. package/dist/client/sharing/useResourceAccessGate.js +9 -1
  55. package/dist/client/use-chat-thread-list.d.ts +2 -0
  56. package/dist/client/use-chat-thread-list.js +12 -2
  57. package/dist/client/use-chat-threads.d.ts +1 -0
  58. package/dist/client/use-chat-threads.js +2 -0
  59. package/dist/collab/awareness.d.ts +2 -2
  60. package/dist/connections/catalog.d.ts +5 -5
  61. package/dist/connections/catalog.js +22 -3
  62. package/dist/data-widgets/index.d.ts +36 -2
  63. package/dist/data-widgets/index.js +94 -6
  64. package/dist/db/migrations.js +4 -1
  65. package/dist/db/schema-snapshot.generated.js +8 -1
  66. package/dist/feature-flags/actions/set-feature-flag.js +5 -0
  67. package/dist/feature-flags/index.d.ts +1 -1
  68. package/dist/feature-flags/index.js +1 -1
  69. package/dist/feature-flags/store.d.ts +12 -0
  70. package/dist/feature-flags/store.js +79 -7
  71. package/dist/file-upload/actions/manage-file-storage.js +5 -0
  72. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  73. package/dist/file-upload/actions/upload-image.js +5 -0
  74. package/dist/history/actions/create-resource-version.js +5 -0
  75. package/dist/history/actions/restore-resource-version.js +5 -0
  76. package/dist/ingestion/index.d.ts +1 -1
  77. package/dist/ingestion/index.js +1 -1
  78. package/dist/ingestion/pptx.d.ts +11 -0
  79. package/dist/ingestion/pptx.js +81 -1
  80. package/dist/integrations/actions/manage-messaging-channel.js +5 -0
  81. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  82. package/dist/jobs/scheduler.js +17 -1
  83. package/dist/labs/actions/set-lab.js +5 -0
  84. package/dist/localization/actions/set-localization-preference.js +5 -0
  85. package/dist/localization/consent-banner-messages.d.ts +16 -0
  86. package/dist/localization/consent-banner-messages.js +149 -0
  87. package/dist/localization/core-messages.js +4 -0
  88. package/dist/localization/default-messages.d.ts +1 -0
  89. package/dist/localization/default-messages.js +2 -0
  90. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  91. package/dist/notifications/routes.d.ts +2 -2
  92. package/dist/observability/actions/save-observability-instruction-update.js +5 -0
  93. package/dist/observability/actions/save-observability-review-summary.js +5 -0
  94. package/dist/observability/routes.d.ts +6 -6
  95. package/dist/onboarding/index.d.ts +2 -1
  96. package/dist/onboarding/index.js +1 -0
  97. package/dist/onboarding/plugin.js +55 -46
  98. package/dist/onboarding/status.d.ts +15 -0
  99. package/dist/onboarding/status.js +59 -0
  100. package/dist/onboarding/types.d.ts +12 -0
  101. package/dist/onboarding/types.js +2 -1
  102. package/dist/org/workspace-app-access.js +19 -9
  103. package/dist/progress/routes.d.ts +1 -1
  104. package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
  105. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  106. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  107. package/dist/provider-api/index.js +9 -2
  108. package/dist/resources/handlers.d.ts +1 -1
  109. package/dist/review/actions/consume-review-feedback.js +5 -0
  110. package/dist/review/actions/create-review-comment.js +5 -0
  111. package/dist/review/actions/delete-review-comment.js +5 -0
  112. package/dist/review/actions/react-to-review-comment.js +5 -0
  113. package/dist/review/actions/reply-review-comment.js +5 -0
  114. package/dist/review/actions/resolve-review-thread.js +5 -0
  115. package/dist/review/actions/send-review-thread-to-agent.js +5 -0
  116. package/dist/review/actions/set-review-status.js +5 -0
  117. package/dist/review/actions/set-review-thread-muted.js +5 -0
  118. package/dist/review/actions/set-review-thread-unread.js +5 -0
  119. package/dist/review/actions/set-review-threads-unread.js +5 -0
  120. package/dist/review/actions/update-review-comment-anchor.js +5 -0
  121. package/dist/review/actions/update-review-comment.js +5 -0
  122. package/dist/review/suggestions/actions.js +60 -21
  123. package/dist/review/suggestions/store.d.ts +2 -0
  124. package/dist/review/suggestions/store.js +28 -0
  125. package/dist/secrets/actions/delete-api-key.js +5 -0
  126. package/dist/secrets/routes.d.ts +6 -6
  127. package/dist/server/agent-capabilities.js +13 -6
  128. package/dist/server/agent-chat/context-tools.js +63 -2
  129. package/dist/server/agent-chat-plugin.d.ts +2 -0
  130. package/dist/server/agent-chat-plugin.js +242 -54
  131. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  132. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  133. package/dist/server/analytics.d.ts +0 -1
  134. package/dist/server/analytics.js +81 -20
  135. package/dist/server/application-state-maintenance.d.ts +2 -0
  136. package/dist/server/application-state-maintenance.js +2 -0
  137. package/dist/server/attribution.d.ts +14 -0
  138. package/dist/server/attribution.js +62 -2
  139. package/dist/server/auth.js +7 -0
  140. package/dist/server/builder-api-auth.d.ts +7 -0
  141. package/dist/server/builder-api-auth.js +24 -0
  142. package/dist/server/builder-oauth.d.ts +5 -1
  143. package/dist/server/builder-oauth.js +5 -0
  144. package/dist/server/consent-route.d.ts +41 -0
  145. package/dist/server/consent-route.js +102 -0
  146. package/dist/server/core-routes-plugin.d.ts +1 -1
  147. package/dist/server/first-touch-route.d.ts +39 -0
  148. package/dist/server/first-touch-route.js +115 -0
  149. package/dist/server/index.d.ts +2 -1
  150. package/dist/server/index.js +2 -1
  151. package/dist/server/queued-submission-inbox-drain.d.ts +9 -0
  152. package/dist/server/queued-submission-inbox-drain.js +12 -0
  153. package/dist/server/request-origin.d.ts +6 -0
  154. package/dist/server/request-origin.js +12 -0
  155. package/dist/settings/org-settings.d.ts +2 -1
  156. package/dist/settings/org-settings.js +4 -1
  157. package/dist/settings/store.d.ts +8 -0
  158. package/dist/settings/store.js +14 -5
  159. package/dist/shared/consent.d.ts +33 -0
  160. package/dist/shared/consent.js +149 -0
  161. package/dist/shared/first-touch-persistence.d.ts +31 -0
  162. package/dist/shared/first-touch-persistence.js +53 -0
  163. package/dist/sharing/actions/set-resource-visibility.js +5 -0
  164. package/dist/sharing/actions/share-resource.js +5 -0
  165. package/dist/sharing/actions/unshare-resource.js +5 -0
  166. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
  167. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
  168. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
  169. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
  170. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
  171. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
  172. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
  173. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
  174. package/dist/triggers/actions/manage-automation.d.ts +4 -4
  175. package/dist/usage/actions/get-usage-metrics.js +27 -4
  176. package/dist/usage/actions/manage-usage-alert.js +5 -0
  177. package/dist/vite/client.js +8 -6
  178. package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +5 -0
  179. package/dist/workspace-connections/actions/delete-workspace-user-group.js +5 -0
  180. package/dist/workspace-connections/actions/upsert-workspace-user-group.js +5 -0
  181. package/docs/content/actions-access-control.mdx +8 -8
  182. package/docs/content/actions-defining.mdx +9 -8
  183. package/docs/content/deployment-environment-variables.mdx +1 -1
  184. package/docs/content/environment-variables.mdx +1 -1
  185. package/docs/content/template-design-developers.mdx +9 -9
  186. package/docs/content/template-design-features.mdx +1 -3
  187. package/docs/content/turn-into-app.mdx +4 -0
  188. package/package.json +6 -3
  189. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
  190. package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
  191. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
  192. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
  193. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
  194. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
  195. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
  196. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
@@ -1,5 +1,7 @@
1
1
  import { getAppConfig } from "../app-config/index.js";
2
+ import { CONSENT_REGION_PATH, CONSENT_REGION_TIME_ZONE_LIST, } from "../shared/consent.js";
2
3
  import { SYNTHETIC_TRAFFIC_BETA_E2E } from "../shared/test-traffic.js";
4
+ import { publicFrameworkPath } from "./framework-route-prefix.js";
3
5
  const AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT = "https://analytics.agent-native.com/track";
4
6
  function normalizeMeasurementId(value) {
5
7
  const trimmed = value?.trim();
@@ -54,18 +56,18 @@ export function getAgentNativeAnalyticsConfigScript() {
54
56
  ");</script>",
55
57
  ].join("");
56
58
  }
57
- function escapeHtmlAttribute(value) {
58
- return value
59
- .replace(/&/g, "&amp;")
60
- .replace(/"/g, "&quot;")
61
- .replace(/</g, "&lt;")
62
- .replace(/>/g, "&gt;");
63
- }
64
- export function getHeadScriptTag() {
59
+ // A deployment's widget may set cookies or track, so it waits for
60
+ // performance consent like GA does.
61
+ function getHeadScript() {
65
62
  const src = getAppConfig().analytics.headScriptSrc;
66
63
  if (!src)
67
64
  return null;
68
- return `<script src="${escapeHtmlAttribute(src)}" defer data-agent-native-head-script></script>`;
65
+ // "<" inside an inline script could close it early.
66
+ const jsSrc = JSON.stringify(src).replace(/</g, "\\x3c");
67
+ return consentGatedScript("performance", `var agentNativeHeadScript=document.createElement('script');` +
68
+ `agentNativeHeadScript.src=${jsSrc};` +
69
+ `agentNativeHeadScript.setAttribute('data-agent-native-head-script','');` +
70
+ `document.head.appendChild(agentNativeHeadScript);`);
69
71
  }
70
72
  export function getGaInlineConfigScriptBody(options) {
71
73
  const id = getGaMeasurementId();
@@ -100,47 +102,106 @@ export function getGaInlineConfigScriptBody(options) {
100
102
  `document.head.appendChild(agentNativeGtagScript);` +
101
103
  `}`);
102
104
  }
105
+ /**
106
+ * Queue `body` until the visitor's region and choice allow `category`; the
107
+ * bootstrap after it, or `client/consent.ts` once loaded, runs it. A reject
108
+ * then a fresh accept calls it again, so it loads its tag only once a page.
109
+ */
110
+ function consentGatedScript(category, body) {
111
+ return `<script>(window.__AN_CONSENT_QUEUE__=window.__AN_CONSENT_QUEUE__||[]).push([${JSON.stringify(category)},(function(){var ran=false;return function(){if(ran)return;ran=true;${body}}})()]);</script>`;
112
+ }
113
+ const GA_FALLBACK_DATA_LAYER = "__AGENT_NATIVE_GA_DATA_LAYER__";
114
+ // gtag reads an `arguments` object from the data layer, never an array.
115
+ const GOOGLE_CONSENT_PUSH = `function push(g){for(var i=0;i<L.length;i++){var l=w[L[i]]=w[L[i]]||[];` +
116
+ `(function(){l.push(arguments)})("consent",g.mode,{analytics_storage:g.p?"granted":"denied",ad_storage:g.t?"granted":"denied",ad_user_data:g.t?"granted":"denied",ad_personalization:g.t?"granted":"denied"})}}`;
117
+ /**
118
+ * Google Consent Mode: before any Google tag loads, tell each data layer
119
+ * what the stored choice allows (nothing, without one), so tags already on
120
+ * the page stop storing and sending when `client/consent.ts` later pushes a
121
+ * reject. Lists the layers there, under `__AN_CONSENT_LAYERS__`.
122
+ */
123
+ function getGoogleConsentDefaultScript(dataLayers) {
124
+ return (`<script data-agent-native-consent-mode>(function(w,d){var L=${JSON.stringify(dataLayers)};w.__AN_CONSENT_LAYERS__=L;` +
125
+ GOOGLE_CONSENT_PUSH +
126
+ `var m=/(?:^|;\\s*)an_consent=1\\.p([01])\\.t([01])(?:;|$)/.exec(d.cookie||"");` +
127
+ `push({mode:"default",p:!!m&&m[1]==="1",t:!!m&&m[2]==="1"})` +
128
+ `})(window,document);</script>`);
129
+ }
130
+ /**
131
+ * Runs the queued tags on pages that never load the app bundle, like the
132
+ * plain sign-in page, and runs them without waiting for it elsewhere: at once
133
+ * for a stored choice or a region already known this session, else after
134
+ * asking the server, with the time zone as fallback. Once
135
+ * `client/consent.ts` replaces the queue it owns what's left, so nothing runs
136
+ * twice. Keep its cookie and region rules in step with that module.
137
+ */
138
+ function getConsentBootstrapScript() {
139
+ const zones = JSON.stringify(CONSENT_REGION_TIME_ZONE_LIST);
140
+ const regionPath = JSON.stringify(publicFrameworkPath(CONSENT_REGION_PATH));
141
+ return (`<script data-agent-native-consent>(function(w,d){var Q="__AN_CONSENT_QUEUE__",K="an_consent_region";` +
142
+ `function choice(){var m=/(?:^|;\\s*)an_consent=1\\.p([01])\\.t([01])(?:;|$)/.exec(d.cookie||"");return m?{performance:m[1]==="1",targeting:m[2]==="1"}:null}` +
143
+ `function allows(c,k,region){return c?c[k]:region==="none"}` +
144
+ `function run(region){var q=w[Q];if(!Array.isArray(q))return;var c=choice(),keep=[],L=w.__AN_CONSENT_LAYERS__||[];` +
145
+ GOOGLE_CONSENT_PUSH +
146
+ `push({mode:"update",p:allows(c,"performance",region),t:allows(c,"targeting",region)});` +
147
+ `for(var i=0;i<q.length;i++){var e=q[i];if(allows(c,e[0],region)){e[1]()}else{keep.push(e)}}q.length=0;q.push.apply(q,keep)}` +
148
+ `function zone(){try{return ${zones}.indexOf(Intl.DateTimeFormat().resolvedOptions().timeZone)>-1?"consent":"none"}catch(e){return"consent"}}` +
149
+ // coercion-ok: a page without session storage asks the server again.
150
+ `var cached=null;try{cached=w.sessionStorage.getItem(K)}catch(e){}` +
151
+ `if(choice()||cached==="consent"||cached==="none"){run(cached);return}` +
152
+ // An unreachable server falls back to the time zone.
153
+ // coercion-ok: a region it can't save is asked again on the next page.
154
+ `fetch(${regionPath},{credentials:"same-origin"}).then(function(r){return r.ok?r.json():{}}).then(function(b){return b&&(b.region==="consent"||b.region==="none")?b.region:zone()},zone).then(function(region){try{w.sessionStorage.setItem(K,region)}catch(e){}run(region)})` +
155
+ `})(window,document);</script>`);
156
+ }
103
157
  function getGaScript() {
104
158
  const id = getGaMeasurementId();
105
159
  if (!id)
106
160
  return null;
107
161
  const inlineBody = getGaInlineConfigScriptBody();
108
- return `<script>${inlineBody}</script>`;
162
+ return inlineBody ? consentGatedScript("performance", inlineBody) : null;
109
163
  }
110
164
  function getGtmGaFallbackScript() {
111
165
  const inlineBody = getGaInlineConfigScriptBody({
112
- dataLayerName: "__AGENT_NATIVE_GA_DATA_LAYER__",
166
+ dataLayerName: GA_FALLBACK_DATA_LAYER,
113
167
  sendPageView: false,
114
168
  });
115
- return inlineBody ? `<script>${inlineBody}</script>` : null;
169
+ return inlineBody ? consentGatedScript("performance", inlineBody) : null;
116
170
  }
171
+ // The container carries the ad pixels (Meta, LinkedIn, Reddit, X, Bing,
172
+ // Google Ads, OpenAI) and B2B identification tags alongside GA events, so it
173
+ // all waits for targeting. Its <noscript> iframe can't wait, so there is none.
117
174
  function getGtmHeadScript(containerId) {
118
175
  const jsId = JSON.stringify(containerId);
119
- return `<script>if(${getSyntheticTrafficBrowserGuard()}){(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${jsId});}</script>`;
120
- }
121
- function getGtmBodyFallback(containerId) {
122
- const src = encodeURIComponent(containerId);
123
- return `<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${src}" height="0" width="0" style="display:none;visibility:hidden" title="Google Tag Manager"></iframe></noscript>`;
176
+ return consentGatedScript("targeting", `if(${getSyntheticTrafficBrowserGuard()}){(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${jsId});}`);
124
177
  }
125
178
  function getAnalyticsInjection() {
126
179
  const agentNativeAnalytics = getAgentNativeAnalyticsConfigScript();
127
- const headScript = getHeadScriptTag();
180
+ const headScript = getHeadScript();
128
181
  const containerId = getGtmContainerId();
129
182
  if (containerId) {
130
183
  return {
131
184
  head: [
132
185
  agentNativeAnalytics,
186
+ getGoogleConsentDefaultScript(["dataLayer", GA_FALLBACK_DATA_LAYER]),
133
187
  getGtmHeadScript(containerId),
134
188
  getGtmGaFallbackScript(),
135
189
  headScript,
190
+ getConsentBootstrapScript(),
136
191
  ]
137
192
  .filter(Boolean)
138
193
  .join(""),
139
- body: getGtmBodyFallback(containerId),
194
+ body: "",
140
195
  };
141
196
  }
142
197
  const gaScript = getGaScript();
143
- const head = [agentNativeAnalytics, gaScript, headScript]
198
+ const head = [
199
+ agentNativeAnalytics,
200
+ gaScript ? getGoogleConsentDefaultScript(["dataLayer"]) : null,
201
+ gaScript,
202
+ headScript,
203
+ gaScript || headScript ? getConsentBootstrapScript() : null,
204
+ ]
144
205
  .filter(Boolean)
145
206
  .join("");
146
207
  return head ? { head, body: "" } : null;
@@ -0,0 +1,2 @@
1
+ /** Server maintenance helpers stay within the current app-state namespace. */
2
+ export { appStateListKeysByKeySuffixPrefix } from "../application-state/store.js";
@@ -0,0 +1,2 @@
1
+ /** Server maintenance helpers stay within the current app-state namespace. */
2
+ export { appStateListKeysByKeySuffixPrefix } from "../application-state/store.js";
@@ -13,6 +13,10 @@
13
13
  * acquisition; last touch answers "what brought them to sign up?" and credits
14
14
  * the post, video, or person that converted them.
15
15
  *
16
+ * First touch also has a durable copy, `an_fts`, that our server sets for a
17
+ * year (see `first-touch-route.ts`). Signup reads both first-touch cookies and
18
+ * keeps the one `preferFirstTouch` picks.
19
+ *
16
20
  * This module is intentionally pure and dependency-free so it is trivially
17
21
  * unit-testable and can never throw into the signup path. The single hard rule:
18
22
  * parsing untrusted cookie input must NEVER throw — every accessor is defensive.
@@ -80,11 +84,21 @@ export declare const LAST_TOUCH_COOKIE_NAME = "an_lt";
80
84
  export declare function parseCookieHeader(cookieHeader: string | null | undefined): Record<string, string>;
81
85
  export declare function decodeFirstTouchValue(value: string | null | undefined): FirstTouchAttribution | null;
82
86
  export declare function decodeLastTouchValue(value: string | null | undefined): LastTouchAttribution | null;
87
+ /**
88
+ * Decode the year-long `an_fts` cookie. A value from another format version
89
+ * is unreadable here, so it holds no touch.
90
+ */
91
+ export declare function decodeFirstTouchServerValue(value: string | null | undefined): FirstTouchAttribution | null;
83
92
  /**
84
93
  * Read the `an_ft` first-touch attribution out of a raw `Cookie:` header.
85
94
  * Returns `null` when the cookie is absent or unparseable. Never throws.
86
95
  */
87
96
  export declare function readFirstTouchAttribution(cookieHeader: string | null | undefined): FirstTouchAttribution | null;
97
+ /**
98
+ * The `an_fts` cookie value for a first touch, dropping the lowest-priority
99
+ * fields until `name=value` fits its budget.
100
+ */
101
+ export declare function encodeFirstTouchServerCookieValue(touch: FirstTouchAttribution): string;
88
102
  /**
89
103
  * Read the `an_lt` last-touch attribution out of a raw `Cookie:` header.
90
104
  * Returns `null` when the cookie is absent or unparseable. Never throws.
@@ -1,6 +1,7 @@
1
1
  import { ANALYTICS_ANONYMOUS_ID_COOKIE_NAME, ANALYTICS_ANONYMOUS_ID_MAX_LENGTH, normalizeAnalyticsAnonymousId, } from "../shared/analytics-anonymous-id.js";
2
2
  import { ANALYTICS_SESSION_ID_COOKIE_NAME, normalizeAnalyticsSessionId, } from "../shared/analytics-session-id.js";
3
3
  import { isSourceReferrerHost, shareLandingSource, } from "../shared/attribution-source.js";
4
+ import { FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES, FIRST_TOUCH_SERVER_COOKIE_NAME, FIRST_TOUCH_SERVER_COOKIE_VERSION, preferFirstTouch, } from "../shared/first-touch-persistence.js";
4
5
  export const SIGNUP_ATTRIBUTION_HEADER_NAME = "x-agent-native-signup-attribution";
5
6
  const SIGNUP_ATTRIBUTION_HEADER_MAX_LENGTH = 4096;
6
7
  export const FIRST_TOUCH_COOKIE_NAME = "an_ft";
@@ -70,7 +71,14 @@ export function decodeFirstTouchValue(value) {
70
71
  export function decodeLastTouchValue(value) {
71
72
  return decodeAttributionCookieValue(value, LAST_TOUCH_STRING_FIELDS);
72
73
  }
73
- function decodeAttributionCookieValue(value, fields) {
74
+ /**
75
+ * Decode the year-long `an_fts` cookie. A value from another format version
76
+ * is unreadable here, so it holds no touch.
77
+ */
78
+ export function decodeFirstTouchServerValue(value) {
79
+ return decodeAttributionCookieValue(value, STRING_FIELDS, FIRST_TOUCH_SERVER_COOKIE_VERSION);
80
+ }
81
+ function decodeAttributionCookieValue(value, fields, version) {
74
82
  if (!value || typeof value !== "string")
75
83
  return null;
76
84
  let decoded = value;
@@ -91,6 +99,8 @@ function decodeAttributionCookieValue(value, fields) {
91
99
  return null;
92
100
  }
93
101
  const source = parsed;
102
+ if (version !== undefined && source.v !== version)
103
+ return null;
94
104
  const result = {};
95
105
  let any = false;
96
106
  for (const field of fields) {
@@ -109,12 +119,62 @@ function decodeAttributionCookieValue(value, fields) {
109
119
  export function readFirstTouchAttribution(cookieHeader) {
110
120
  try {
111
121
  const cookies = parseCookieHeader(cookieHeader);
112
- return decodeFirstTouchValue(cookies[FIRST_TOUCH_COOKIE_NAME]);
122
+ return preferFirstTouch(decodeFirstTouchServerValue(cookies[FIRST_TOUCH_SERVER_COOKIE_NAME]), decodeFirstTouchValue(cookies[FIRST_TOUCH_COOKIE_NAME]));
113
123
  }
114
124
  catch {
115
125
  return null;
116
126
  }
117
127
  }
128
+ // `landed_at` sets the cookie's expiry and identifies the visit, so it is
129
+ // never the field dropped to fit.
130
+ const FIRST_TOUCH_SERVER_FIELD_PRIORITY = [
131
+ "landed_at",
132
+ "gclid",
133
+ "msclkid",
134
+ "vector_source",
135
+ "utm_source",
136
+ "utm_medium",
137
+ "utm_campaign",
138
+ "ref",
139
+ "via",
140
+ "utm_content",
141
+ "utm_term",
142
+ "landing_path",
143
+ "landing_referrer",
144
+ "site_referrer",
145
+ "site_landing_path",
146
+ ];
147
+ /**
148
+ * The `an_fts` cookie value for a first touch, dropping the lowest-priority
149
+ * fields until `name=value` fits its budget.
150
+ */
151
+ export function encodeFirstTouchServerCookieValue(touch) {
152
+ const compact = {
153
+ v: FIRST_TOUCH_SERVER_COOKIE_VERSION,
154
+ };
155
+ let truncated = touch.capture_truncated === "1";
156
+ const encode = (value) => encodeURIComponent(JSON.stringify(value));
157
+ const fits = (value) => FIRST_TOUCH_SERVER_COOKIE_NAME.length + 1 + encode(value).length <=
158
+ FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES;
159
+ // Room for the marker, so adding it later can't push the value over.
160
+ const withMarker = (value) => ({
161
+ ...value,
162
+ capture_truncated: "1",
163
+ });
164
+ for (const field of FIRST_TOUCH_SERVER_FIELD_PRIORITY) {
165
+ const value = touch[field];
166
+ if (!value)
167
+ continue;
168
+ const candidate = { ...compact, [field]: value };
169
+ if (fits(withMarker(candidate))) {
170
+ compact[field] = value;
171
+ }
172
+ else {
173
+ truncated = true;
174
+ }
175
+ }
176
+ return encode(truncated ? withMarker(compact) : compact);
177
+ }
118
178
  /**
119
179
  * Read the `an_lt` last-touch attribution out of a raw `Cookie:` header.
120
180
  * Returns `null` when the cookie is absent or unparseable. Never throws.
@@ -5,14 +5,18 @@ import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
5
5
  import { newOrgSelection, ORG_SELECTION_COOKIE, } from "../org/request-org-cache.js";
6
6
  import { isWorkspaceAppAccessUnavailable, workspaceAppAccessUnavailableBody, } from "../org/workspace-app-access-outcome.js";
7
7
  import { checkWorkspaceAppAccessForRequest } from "../org/workspace-app-access-request.js";
8
+ import { CONSENT_PATH, CONSENT_REGION_PATH } from "../shared/consent.js";
8
9
  import { EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TARGET_HEADER, isMcpDirectoryWidgetCapabilityScope, } from "../shared/embed-auth.js";
9
10
  import { FIRST_RUN_ONBOARDING_COOKIE, FIRST_RUN_ONBOARDING_MAX_AGE, } from "../shared/first-run-onboarding.js";
11
+ import { FIRST_TOUCH_SERVER_PATH } from "../shared/first-touch-persistence.js";
10
12
  import { isGoogleProfileImageUrl } from "../shared/google-profile-image.js";
11
13
  import { EMBED_TRANSPLANT_HEADER, isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
14
+ import { createConsentHandler, createConsentRegionHandler, } from "./consent-route.js";
12
15
  import { readDevActionDiscoveryFile } from "./dev-action-discovery.js";
13
16
  import { devLoopbackAuthHint } from "./dev-origin-hint.js";
14
17
  import { EMAIL_AUTH_LINK_LANDING_PATH, emailAuthLinkFields, emailAuthLinkLandingPage, emailAuthVerificationPath, emailAuthVerificationUrl, } from "./email-auth-links.js";
15
18
  import { isEmbedCapabilityScope, revokeEmbedSessionsForOwners, requestHasEmbedAuthMarker, resolveEmbedSessionCookieOwners, resolveEmbedSessionFromRequest, verifyEmbedSessionToken, } from "./embed-session.js";
19
+ import { createFirstTouchHandler } from "./first-touch-route.js";
16
20
  import { getPublicFrameworkPathname } from "./framework-request-context.js";
17
21
  import { FRAMEWORK_INTERNAL_ROUTE_PREFIX, canonicalFrameworkPathname, getFrameworkRoutePrefix, publicFrameworkPath, } from "./framework-route-prefix.js";
18
22
  import { isHttpsRequest } from "./https-request.js";
@@ -5547,6 +5551,9 @@ export async function autoMountAuth(app, options = {}) {
5547
5551
  const workspaceAppAudience = resolveWorkspaceAppAudience(options);
5548
5552
  const workspaceAppRouteAccess = resolveWorkspaceAppRouteAccess(options);
5549
5553
  mountAuthCorsMiddleware(app);
5554
+ app.use(FIRST_TOUCH_SERVER_PATH, createFirstTouchHandler());
5555
+ app.use(CONSENT_REGION_PATH, createConsentRegionHandler());
5556
+ app.use(CONSENT_PATH, createConsentHandler());
5550
5557
  if (options.getSession) {
5551
5558
  customGetSession = options.getSession;
5552
5559
  trustCustomEmailVerification =
@@ -16,6 +16,13 @@ export interface BuilderRequestAuthorization {
16
16
  authorization: string;
17
17
  source: "oauth" | "legacy";
18
18
  oauthScope?: RemoteMcpScope;
19
+ oauthResource?: "general" | "publish";
20
+ oauthConnectionId?: string;
21
+ /**
22
+ * Space selected during Builder OAuth. This is only a request locator; the
23
+ * provider must verify the token and reject a different apiKey.
24
+ */
25
+ oauthSelectedPublicKey?: string;
19
26
  legacyCredentialKey?: BuilderLegacyCredentialKey;
20
27
  legacyPublicKey?: string;
21
28
  userId?: string;
@@ -51,6 +51,22 @@ function isBuilderPublishResource(value) {
51
51
  !url.search &&
52
52
  !url.hash);
53
53
  }
54
+ function selectedPublicKeyFromBuilderAccessToken(token) {
55
+ const payload = token.split(".")[1];
56
+ if (!payload)
57
+ return undefined;
58
+ try {
59
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
60
+ return typeof decoded.org === "string" && decoded.org.trim()
61
+ ? decoded.org.trim()
62
+ : undefined;
63
+ }
64
+ catch {
65
+ // coercion-ok: an opaque or malformed optional locator claim is absent;
66
+ // authorization remains the provider's signed-token responsibility.
67
+ return undefined;
68
+ }
69
+ }
54
70
  async function resolveBuilderPublishAuthorization(ownerEmail, orgId) {
55
71
  const candidates = orgId
56
72
  ? [{ scope: "org", scopeId: orgId }]
@@ -108,6 +124,7 @@ async function resolveBuilderPublishAuthorization(ownerEmail, orgId) {
108
124
  authorization: `Bearer ${match[1]}`,
109
125
  source: "oauth",
110
126
  oauthScope: candidate.scope,
127
+ oauthResource: "publish",
111
128
  };
112
129
  }
113
130
  if (ownerEmail) {
@@ -154,6 +171,7 @@ export async function resolveBuilderRequestAuthorization(input = {}) {
154
171
  throw new ActionContractError(`Builder.io access needs re-authorizing to grant ${input.requiredScope}. Open Settings and authorize Builder.io again.`, {
155
172
  errorCode: "builder_oauth_reauthorization_required",
156
173
  statusCode: 400,
174
+ details: { reason: "missing_scope", scope: input.requiredScope },
157
175
  });
158
176
  }
159
177
  throw err;
@@ -168,6 +186,7 @@ export async function resolveBuilderRequestAuthorization(input = {}) {
168
186
  throw new ActionContractError(`Builder.io access needs re-authorizing to grant ${input.requiredScope}. Open Settings and authorize Builder.io again.`, {
169
187
  errorCode: "builder_oauth_reauthorization_required",
170
188
  statusCode: 400,
189
+ details: { reason: "missing_scope", scope: input.requiredScope },
171
190
  });
172
191
  }
173
192
  return {
@@ -175,6 +194,11 @@ export async function resolveBuilderRequestAuthorization(input = {}) {
175
194
  authorization: `Bearer ${session.accessToken}`,
176
195
  source: "oauth",
177
196
  oauthScope: session.scope,
197
+ oauthResource: "general",
198
+ ...(session.connectionId
199
+ ? { oauthConnectionId: session.connectionId }
200
+ : {}),
201
+ oauthSelectedPublicKey: selectedPublicKeyFromBuilderAccessToken(session.accessToken),
178
202
  };
179
203
  }
180
204
  return resolveBuilderLegacyRequestAuthorization(input.legacyCredentialKeys);
@@ -3,7 +3,9 @@ export declare const BUILDER_OAUTH_ISSUER = "https://mcp.builder.io";
3
3
  export declare const BUILDER_OAUTH_RESOURCE = "https://api.builder.io";
4
4
  export declare const BUILDER_OAUTH_SCOPE = "builder:ai:invoke";
5
5
  export declare const BUILDER_ASSETS_WRITE_SCOPE = "builder:assets:write";
6
- export declare const BUILDER_OAUTH_SCOPES: readonly ["builder:ai:invoke", "builder:agents:run", "builder:browser:connect", "builder:assets:write", "builder:projects:read", "builder:projects:write", "builder:designsystem:read", "builder:designsystem:write"];
6
+ export declare const BUILDER_CONTENT_READ_SCOPE = "builder:content:read";
7
+ export declare const BUILDER_CONTENT_WRITE_SCOPE = "builder:content:write";
8
+ export declare const BUILDER_OAUTH_SCOPES: readonly ["builder:ai:invoke", "builder:agents:run", "builder:browser:connect", "builder:assets:write", "builder:content:read", "builder:content:write", "builder:projects:read", "builder:projects:write", "builder:designsystem:read", "builder:designsystem:write"];
7
9
  export type BuilderOAuthPermissionScope = (typeof BUILDER_OAUTH_SCOPES)[number];
8
10
  export type BuilderOAuthPendingFlow = {
9
11
  codeVerifier: string;
@@ -43,6 +45,8 @@ export type BuilderOAuthSession = {
43
45
  expiresAt?: number;
44
46
  scopes: string[];
45
47
  scope: BuilderOAuthScope;
48
+ /** Opaque identity of the selected stored OAuth credential lane. */
49
+ connectionId?: string;
46
50
  };
47
51
  export type BuilderOAuthRequestAccess = BuilderOAuthSession & {
48
52
  ownerEmail: string;
@@ -9,11 +9,15 @@ export const BUILDER_OAUTH_ISSUER = "https://mcp.builder.io";
9
9
  export const BUILDER_OAUTH_RESOURCE = "https://api.builder.io";
10
10
  export const BUILDER_OAUTH_SCOPE = "builder:ai:invoke";
11
11
  export const BUILDER_ASSETS_WRITE_SCOPE = "builder:assets:write";
12
+ export const BUILDER_CONTENT_READ_SCOPE = "builder:content:read";
13
+ export const BUILDER_CONTENT_WRITE_SCOPE = "builder:content:write";
12
14
  export const BUILDER_OAUTH_SCOPES = [
13
15
  BUILDER_OAUTH_SCOPE,
14
16
  "builder:agents:run",
15
17
  "builder:browser:connect",
16
18
  BUILDER_ASSETS_WRITE_SCOPE,
19
+ BUILDER_CONTENT_READ_SCOPE,
20
+ BUILDER_CONTENT_WRITE_SCOPE,
17
21
  "builder:projects:read",
18
22
  "builder:projects:write",
19
23
  "builder:designsystem:read",
@@ -290,6 +294,7 @@ export async function getBuilderOAuthSession(ownerEmail, orgId, requiredScope, a
290
294
  expiresAt: credentials.tokenExpiresAt,
291
295
  scopes,
292
296
  scope: options.scope,
297
+ connectionId: options.key,
293
298
  };
294
299
  }
295
300
  if (requiredScope && missingRequiredScope) {
@@ -0,0 +1,41 @@
1
+ import type { H3Event } from "h3";
2
+ import { type ConsentCategory } from "../shared/consent.js";
3
+ /**
4
+ * The visitor's country from the host's geo header, if it sent one. The
5
+ * headers a CDN always overwrites come first, so a visitor's own `x-country`
6
+ * can't outrank them.
7
+ */
8
+ export declare function requestCountry(event: H3Event): string | undefined;
9
+ /**
10
+ * A stored choice applies everywhere; without one, the country decides
11
+ * whether storing `category` cookies requires an opt-in.
12
+ */
13
+ export declare function requestAllowsConsentCategory(event: H3Event, category: ConsentCategory): boolean;
14
+ /**
15
+ * `GET /_agent-native/auth/consent-region`: `consent` when the visitor's
16
+ * country gets the banner, `none` when it doesn't, `unknown` when the host
17
+ * sent no country. The SSR shell is cached for everyone, so the browser asks
18
+ * here instead of reading it from the page.
19
+ */
20
+ export declare function createConsentRegionHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, {
21
+ error: string;
22
+ region?: undefined;
23
+ } | {
24
+ error?: undefined;
25
+ region: string;
26
+ }>;
27
+ /**
28
+ * `POST /_agent-native/auth/consent` with `{ choice: "1.p1.t0" }`: write the
29
+ * visitor's choice from the server. Safari keeps a cookie page JavaScript
30
+ * wrote for seven days at most, and builder.io asks again only after a year.
31
+ * Uses the same domain as the browser's copy, so it replaces that copy.
32
+ */
33
+ export declare function createConsentHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
34
+ error: string;
35
+ stored?: undefined;
36
+ choice?: undefined;
37
+ } | {
38
+ error?: undefined;
39
+ stored: boolean;
40
+ choice: any;
41
+ }>>;
@@ -0,0 +1,102 @@
1
+ import { appendResponseHeader, defineEventHandler, getHeader, getMethod, setResponseHeader, setResponseStatus, } from "h3";
2
+ import { CONSENT_COOKIE_LIFETIME_SECONDS, CONSENT_COOKIE_NAME, consentCookieDomain, isConsentRegionCountry, parseConsent, serializeConsent, } from "../shared/consent.js";
3
+ import { parseCookieHeader } from "./attribution.js";
4
+ import { readBodyWithSizeLimit } from "./h3-helpers.js";
5
+ import { getForwardedRequestOrigin, getForwardedRequestURL, isSameOriginPost, } from "./request-origin.js";
6
+ const MAX_CONSENT_BODY_BYTES = 256;
7
+ /**
8
+ * The visitor's country from the host's geo header, if it sent one. The
9
+ * headers a CDN always overwrites come first, so a visitor's own `x-country`
10
+ * can't outrank them.
11
+ */
12
+ export function requestCountry(event) {
13
+ return (netlifyGeoCountry(event) ??
14
+ getHeader(event, "cf-ipcountry") ??
15
+ getHeader(event, "x-vercel-ip-country") ??
16
+ getHeader(event, "x-country"));
17
+ }
18
+ function netlifyGeoCountry(event) {
19
+ const geo = getHeader(event, "x-nf-geo");
20
+ if (!geo)
21
+ return undefined;
22
+ try {
23
+ const parsed = JSON.parse(Buffer.from(geo, "base64").toString("utf8"));
24
+ const code = parsed?.country?.code;
25
+ return typeof code === "string" ? code : undefined;
26
+ }
27
+ catch {
28
+ // coercion-ok: an unreadable geo header is an unknown country; the next
29
+ // header, or the browser's time zone, decides.
30
+ return undefined;
31
+ }
32
+ }
33
+ /**
34
+ * A stored choice applies everywhere; without one, the country decides
35
+ * whether storing `category` cookies requires an opt-in.
36
+ */
37
+ export function requestAllowsConsentCategory(event, category) {
38
+ const consent = parseConsent(parseCookieHeader(getHeader(event, "cookie"))[CONSENT_COOKIE_NAME]);
39
+ if (consent)
40
+ return consent[category];
41
+ return !isConsentRegionCountry(requestCountry(event));
42
+ }
43
+ /**
44
+ * `GET /_agent-native/auth/consent-region`: `consent` when the visitor's
45
+ * country gets the banner, `none` when it doesn't, `unknown` when the host
46
+ * sent no country. The SSR shell is cached for everyone, so the browser asks
47
+ * here instead of reading it from the page.
48
+ */
49
+ export function createConsentRegionHandler() {
50
+ return defineEventHandler((event) => {
51
+ setResponseHeader(event, "cache-control", "no-store");
52
+ if (getMethod(event) !== "GET") {
53
+ setResponseStatus(event, 405);
54
+ return { error: "Method not allowed" };
55
+ }
56
+ const country = requestCountry(event);
57
+ if (!country)
58
+ return { region: "unknown" };
59
+ return { region: isConsentRegionCountry(country) ? "consent" : "none" };
60
+ });
61
+ }
62
+ /**
63
+ * `POST /_agent-native/auth/consent` with `{ choice: "1.p1.t0" }`: write the
64
+ * visitor's choice from the server. Safari keeps a cookie page JavaScript
65
+ * wrote for seven days at most, and builder.io asks again only after a year.
66
+ * Uses the same domain as the browser's copy, so it replaces that copy.
67
+ */
68
+ export function createConsentHandler() {
69
+ return defineEventHandler(async (event) => {
70
+ setResponseHeader(event, "cache-control", "no-store");
71
+ if (getMethod(event) !== "POST") {
72
+ setResponseStatus(event, 405);
73
+ return { error: "Method not allowed" };
74
+ }
75
+ if (!isSameOriginPost(event)) {
76
+ setResponseStatus(event, 403);
77
+ return { error: "Cross-origin request" };
78
+ }
79
+ let choice;
80
+ try {
81
+ const body = await readBodyWithSizeLimit(event, MAX_CONSENT_BODY_BYTES);
82
+ choice = parseConsent(body?.choice);
83
+ }
84
+ catch (error) {
85
+ if (error.statusCode === 413) {
86
+ return { error: "Body too large" };
87
+ }
88
+ setResponseStatus(event, 400);
89
+ return { error: "Invalid JSON" };
90
+ }
91
+ if (!choice) {
92
+ setResponseStatus(event, 400);
93
+ return { error: "Missing choice" };
94
+ }
95
+ const domain = consentCookieDomain(getForwardedRequestURL(event).hostname);
96
+ const secure = getForwardedRequestOrigin(event).startsWith("https:");
97
+ appendResponseHeader(event, "set-cookie", `${CONSENT_COOKIE_NAME}=${serializeConsent(choice)}; ` +
98
+ `Max-Age=${CONSENT_COOKIE_LIFETIME_SECONDS}; Path=/; SameSite=Lax` +
99
+ `${domain ? `; Domain=${domain}` : ""}${secure ? "; Secure" : ""}`);
100
+ return { stored: true, choice };
101
+ });
102
+ }
@@ -588,8 +588,8 @@ export declare function createPublicRemoteAgentsHandler(discover?: PublicAgentDi
588
588
  error: string;
589
589
  agents?: undefined;
590
590
  } | {
591
- error?: undefined;
592
591
  agents: Omit<import("./agent-discovery.js").DiscoveredAgent, "auth" | "kind">[];
592
+ error?: undefined;
593
593
  }>>;
594
594
  export declare function getBuilderConnectErrorDisposition(error: unknown, connectAttemptId: string | null): "correlated" | "legacy" | null;
595
595
  export declare function getBuilderConnectErrorKey(ownerEmail: string, connectAttemptId?: string | null): string;
@@ -0,0 +1,39 @@
1
+ import { type FirstTouchAttribution } from "./attribution.js";
2
+ export type FirstTouchSyncResult = {
3
+ stored: true;
4
+ firstTouch: FirstTouchAttribution;
5
+ } | {
6
+ stored: false;
7
+ reason: "consent_required" | "expired";
8
+ };
9
+ /**
10
+ * `POST /_agent-native/auth/attribution` with `{ firstTouch }`: keep the
11
+ * browser's first touch in the year-long `an_fts` cookie. Anyone may call it,
12
+ * signed in or not, but only from this origin. The value already in the
13
+ * cookie wins unless `preferFirstTouch` picks the new one, so a slow request
14
+ * from another tab can't replace a better first touch it never saw. Where the
15
+ * banner applies and the visitor hasn't accepted targeting cookies, it stores
16
+ * nothing and clears every attribution cookie. Elsewhere it also writes the
17
+ * browser's `an_ft` and `an_lt` again, so Safari keeps them their 30 days.
18
+ */
19
+ export declare function createFirstTouchHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
20
+ error: string;
21
+ reason?: undefined;
22
+ stored?: undefined;
23
+ firstTouch?: undefined;
24
+ } | {
25
+ error?: undefined;
26
+ stored: false;
27
+ reason: "consent_required";
28
+ firstTouch?: undefined;
29
+ } | {
30
+ error?: undefined;
31
+ stored: false;
32
+ reason: "expired";
33
+ firstTouch?: undefined;
34
+ } | {
35
+ error?: undefined;
36
+ reason?: undefined;
37
+ stored: true;
38
+ firstTouch: FirstTouchAttribution;
39
+ }>>;