@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,12 @@
1
1
  /** One id for this page load, shared by every pageview until it reloads. */
2
2
  export declare function getAnalyticsPageLoadId(): string;
3
+ /**
4
+ * The visitor id, `an_aid`, waits for analytics consent where the banner
5
+ * applies, and for the server's region rather than the time zone's guess,
6
+ * since a sent id can't be recalled. Until then this returns `undefined` and
7
+ * stores nothing, so events still count but can't be tied to one visitor
8
+ * across pages.
9
+ */
3
10
  export declare function getOrCreateAnalyticsAnonymousId(): string | undefined;
4
11
  export declare function setAnalyticsSessionId(sessionId: string): string | undefined;
5
12
  export declare function clearAnalyticsSessionId(): void;
@@ -1,5 +1,6 @@
1
- import { serializeAnalyticsAnonymousIdCookie } from "../shared/analytics-anonymous-id.js";
1
+ import { ANALYTICS_ANONYMOUS_ID_COOKIE_NAME, serializeAnalyticsAnonymousIdCookie, } from "../shared/analytics-anonymous-id.js";
2
2
  import { ANALYTICS_SESSION_ID_COOKIE_NAME, ANALYTICS_SESSION_ID_MAX_LENGTH, normalizeAnalyticsSessionId, serializeAnalyticsSessionIdCookie, } from "../shared/analytics-session-id.js";
3
+ import { consentDecision, whenConsentAllows } from "./consent.js";
3
4
  const ANONYMOUS_ID_STORAGE_KEY = "agent-native.anonymous_id";
4
5
  const SESSION_ID_STORAGE_KEY = "agent-native.session_id";
5
6
  const SESSION_ID_PIN_STORAGE_KEY = "agent-native.session_id_pin";
@@ -72,9 +73,34 @@ export function getAnalyticsPageLoadId() {
72
73
  pageLoadId ??= generateVisitorId();
73
74
  return pageLoadId;
74
75
  }
76
+ let anonymousIdConsentWatched = false;
77
+ function clearAnalyticsAnonymousId() {
78
+ safeStorageRemove(ANONYMOUS_ID_STORAGE_KEY);
79
+ try {
80
+ document.cookie = `${ANALYTICS_ANONYMOUS_ID_COOKIE_NAME}=; path=/; max-age=0; SameSite=Lax`;
81
+ }
82
+ catch {
83
+ // coercion-ok: a frame that can't read cookies never stored one.
84
+ }
85
+ }
86
+ /**
87
+ * The visitor id, `an_aid`, waits for analytics consent where the banner
88
+ * applies, and for the server's region rather than the time zone's guess,
89
+ * since a sent id can't be recalled. Until then this returns `undefined` and
90
+ * stores nothing, so events still count but can't be tied to one visitor
91
+ * across pages.
92
+ */
75
93
  export function getOrCreateAnalyticsAnonymousId() {
76
94
  if (typeof window === "undefined")
77
95
  return undefined;
96
+ if (!anonymousIdConsentWatched) {
97
+ anonymousIdConsentWatched = true;
98
+ whenConsentAllows("performance", () => { }, {
99
+ revoke: clearAnalyticsAnonymousId,
100
+ });
101
+ }
102
+ if (consentDecision("performance") !== true)
103
+ return undefined;
78
104
  let id = safeStorageGet(ANONYMOUS_ID_STORAGE_KEY);
79
105
  if (!id) {
80
106
  id = generateVisitorId();
@@ -4,6 +4,7 @@ import { ANALYTICS_CLIENT_PLATFORM_PROPERTY, } from "../shared/analytics-platfor
4
4
  import { hasAttributionSource, } from "../shared/attribution-source.js";
5
5
  import { resolveLaneEndpoint } from "../shared/environment-lanes.js";
6
6
  import { classifyErrorNoise, } from "../shared/error-noise.js";
7
+ import { FIRST_TOUCH_SERVER_COOKIE_NAME, FIRST_TOUCH_SERVER_COOKIE_VERSION, FIRST_TOUCH_SERVER_PATH, firstTouchServerExpiry, isSameFirstTouch, preferFirstTouch, } from "../shared/first-touch-persistence.js";
7
8
  import { llmConnectionTrackingProperties, } from "../shared/llm-connection.js";
8
9
  import { loadOptionalPeer } from "../shared/optional-peer.js";
9
10
  import { isTestIdentityEmail } from "../shared/qa-test-email.js";
@@ -11,8 +12,11 @@ import { isSyntheticTrafficValue } from "../shared/test-traffic.js";
11
12
  import { toPostHogExceptionProperties } from "../tracking/posthog-exception.js";
12
13
  import { getAnalyticsClientPlatform } from "./analytics-platform.js";
13
14
  import { getAnalyticsPageLoadId, getOrCreateAnalyticsAnonymousId, getOrCreateAnalyticsSessionId, } from "./analytics-session.js";
15
+ import { agentNativePath } from "./api-path.js";
16
+ import { agentNativeApiDisabledReason } from "./api-surface.js";
14
17
  import { injectedAgentNativeConfig } from "./app-config.js";
15
18
  import { clientBuildId } from "./build-compatibility.js";
19
+ import { consentAllowsNow, consentDecision, getConsentChoice, whenConsentAllows, whenConsentDecided, } from "./consent.js";
16
20
  import { clientFailureContext } from "./failure-report.js";
17
21
  import { replayEndpointFromAnalyticsEndpoint } from "./session-replay-endpoint.js";
18
22
  import { scheduleAfterPaint } from "./use-after-paint.js";
@@ -35,6 +39,7 @@ let _amplitudeModule = null;
35
39
  let _amplitudeLoadPromise = null;
36
40
  let _amplitudeApiKey = null;
37
41
  let _pendingAmplitudeEvents = [];
42
+ let _amplitudeConsent = "unasked";
38
43
  let _sentryInitialized = false;
39
44
  let _sentryModule = null;
40
45
  let _sentryLoadPromise = null;
@@ -174,6 +179,11 @@ const LAST_TOUCH_COOKIE = {
174
179
  priority: LAST_TOUCH_COOKIE_FIELD_PRIORITY,
175
180
  };
176
181
  let _firstTouchCaptured = false;
182
+ // Bounds the resend after a response race; see `syncFirstTouchToServer`.
183
+ let _firstTouchSyncsLeft = 2;
184
+ // Set when this page wrote `an_ft` or `an_lt`, which the server then writes
185
+ // again: Safari keeps a cookie page JavaScript wrote for seven days at most.
186
+ let _attributionCookieWritten = false;
177
187
  function safeStorageGet(key) {
178
188
  try {
179
189
  return window.localStorage.getItem(key);
@@ -190,6 +200,14 @@ function safeStorageSet(key, value) {
190
200
  // private browsing / storage disabled — best-effort
191
201
  }
192
202
  }
203
+ function safeStorageRemove(key) {
204
+ try {
205
+ window.localStorage.removeItem(key);
206
+ }
207
+ catch {
208
+ // coercion-ok: storage that can't be read holds nothing to remove.
209
+ }
210
+ }
193
211
  function readCachedLlmConnectionStatus() {
194
212
  if (typeof window === "undefined")
195
213
  return null;
@@ -536,6 +554,7 @@ function writeAttributionCookie(value, spec) {
536
554
  const encodedValue = fitAttributionCookieValue(value, spec);
537
555
  try {
538
556
  document.cookie = attributionCookieAssignment(spec.name, encodedValue);
557
+ _attributionCookieWritten = true;
539
558
  }
540
559
  catch {
541
560
  // best-effort
@@ -564,16 +583,132 @@ function backfillAttributionCookie(storageKey, spec) {
564
583
  // signup handoff goes without it.
565
584
  }
566
585
  }
586
+ /** Remove every stored touch, in storage and cookies. */
587
+ function clearStoredAttribution() {
588
+ safeStorageRemove(FIRST_TOUCH_STORAGE_KEY);
589
+ safeStorageRemove(LAST_TOUCH_STORAGE_KEY);
590
+ if (typeof document === "undefined")
591
+ return;
592
+ for (const name of [
593
+ FIRST_TOUCH_COOKIE_NAME,
594
+ LAST_TOUCH_COOKIE_NAME,
595
+ FIRST_TOUCH_SERVER_COOKIE_NAME,
596
+ ]) {
597
+ try {
598
+ document.cookie = `${name}=; path=/; max-age=0; SameSite=Lax`;
599
+ }
600
+ catch {
601
+ // coercion-ok: a sandboxed iframe can't read or write cookies, so
602
+ // there is none here to clear.
603
+ }
604
+ }
605
+ }
606
+ /** The year-long copy our server keeps in `an_fts`. */
607
+ function readServerFirstTouch() {
608
+ const touch = readAttributionCookieValue(FIRST_TOUCH_SERVER_COOKIE_NAME);
609
+ if (!touch || touch.v !== FIRST_TOUCH_SERVER_COOKIE_VERSION)
610
+ return null;
611
+ const { v: _version, ...firstTouch } = touch;
612
+ return firstTouch;
613
+ }
614
+ /**
615
+ * The browser's own copies: localStorage, then the 30-day `an_ft` cookie. A
616
+ * visit more than a year old no longer counts, or the cookie it's copied back
617
+ * into would keep it going.
618
+ */
619
+ function readLocalFirstTouch() {
620
+ const touch = readStoredAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE_NAME);
621
+ return touch && firstTouchServerExpiry(touch) ? touch : null;
622
+ }
567
623
  /**
568
- * Capture the visitor's referral attribution once per page load, into both
569
- * `localStorage` and a first-party cookie the signup boundary reads. Fully
570
- * defensive and SSR-safe — any failure is swallowed so it can never break app
571
- * boot.
624
+ * The server writes `an_lt` again from each request's cookies, so a slow
625
+ * response from an older page can put back an older last touch. The newest
626
+ * one is in localStorage. Returns whether it had to write it back.
627
+ */
628
+ function restoreNewestLastTouchCookie() {
629
+ const stored = safeStorageGet(LAST_TOUCH_STORAGE_KEY);
630
+ const newest = parseAttribution(stored);
631
+ if (!stored || !newest)
632
+ return false;
633
+ const cookie = readAttributionCookieValue(LAST_TOUCH_COOKIE_NAME);
634
+ if (Date.parse(cookie?.touched_at ?? "") >= Date.parse(newest.touched_at ?? "")) {
635
+ return false;
636
+ }
637
+ writeAttributionCookie(stored, LAST_TOUCH_COOKIE);
638
+ return true;
639
+ }
640
+ /**
641
+ * Ask our server to keep `firstTouch` in its year-long cookie, unless it
642
+ * already holds that visit and this page wrote no attribution cookie; the
643
+ * same request has the server write `an_ft` and `an_lt` again. Two tabs can each send a touch before either
644
+ * response lands, and the later response wins the cookie, so when ours
645
+ * leaves a worse touch there than this browser holds, send the better one
646
+ * again. Until the server holds it, signup still reads `an_ft`.
647
+ */
648
+ function syncFirstTouchToServer(firstTouch) {
649
+ if (!_attributionCookieWritten &&
650
+ isSameFirstTouch(readServerFirstTouch(), firstTouch)) {
651
+ return;
652
+ }
653
+ // An embed that must not call our API (the Builder shell canvas) keeps only
654
+ // the browser's copies.
655
+ if (agentNativeApiDisabledReason())
656
+ return;
657
+ if (_firstTouchSyncsLeft <= 0 || typeof fetch !== "function")
658
+ return;
659
+ _firstTouchSyncsLeft -= 1;
660
+ _attributionCookieWritten = false;
661
+ void fetch(agentNativePath(FIRST_TOUCH_SERVER_PATH), {
662
+ method: "POST",
663
+ credentials: "same-origin",
664
+ keepalive: true,
665
+ headers: { "Content-Type": "application/json" },
666
+ body: JSON.stringify({ firstTouch }),
667
+ })
668
+ .then(async (response) => {
669
+ if (!response.ok)
670
+ return;
671
+ const result = (await response.json());
672
+ // The visitor may have rejected while this request was out.
673
+ if (result.reason === "consent_required" ||
674
+ !consentAllowsNow("targeting")) {
675
+ clearStoredAttribution();
676
+ return;
677
+ }
678
+ // Written back by page JavaScript, Safari would keep it a week.
679
+ const repaired = restoreNewestLastTouchCookie();
680
+ const server = readServerFirstTouch();
681
+ const local = readLocalFirstTouch();
682
+ if (!server)
683
+ return;
684
+ if (isSameFirstTouch(server, local)) {
685
+ if (repaired && local)
686
+ syncFirstTouchToServer(local);
687
+ return;
688
+ }
689
+ if (preferFirstTouch(server, local) === server) {
690
+ storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, server);
691
+ }
692
+ else if (local) {
693
+ syncFirstTouchToServer(local);
694
+ }
695
+ })
696
+ .catch(() => {
697
+ // coercion-ok: the 30-day `an_ft` cookie still carries first touch to
698
+ // signup; the next page load tries again.
699
+ });
700
+ }
701
+ /**
702
+ * Capture the visitor's referral attribution once per page load, into
703
+ * `localStorage`, a first-party cookie the signup boundary reads, and the
704
+ * server's year-long copy. Fully defensive and SSR-safe — any failure is
705
+ * swallowed so it can never break app boot.
572
706
  *
573
- * First touch (`an_attribution` / `an_ft`) is first-write-wins, except over a
574
- * visit that had no source: the first visit that says where this person came
575
- * from replaces it. Without that, an untagged first visit would hide every
576
- * tagged visit after it.
707
+ * First touch (`an_attribution` / `an_ft` / `an_fts`) is the first visit that
708
+ * said where this person came from, else the first visit; see
709
+ * `preferFirstTouch`. Without that, an untagged first visit would hide every
710
+ * tagged visit after it. When the browser's copies are gone, as Safari clears
711
+ * them, the server's copy restores them.
577
712
  */
578
713
  function captureFirstTouchAttribution(options = {}) {
579
714
  if (_firstTouchCaptured)
@@ -582,16 +717,32 @@ function captureFirstTouchAttribution(options = {}) {
582
717
  if (typeof window === "undefined")
583
718
  return;
584
719
  try {
720
+ // Read the visit now: the URL can change before the visitor consents.
585
721
  const current = buildFirstTouchAttribution(options.landingPath ?? window.location.pathname);
586
- const existing = getFirstTouchAttribution();
587
- if (existing &&
588
- (hasAttributionSource(existing) || !hasAttributionSource(current))) {
722
+ const forwarded = readForwardedLastTouch();
723
+ const forwardedAt = readForwardedLastTouchAt();
724
+ // Where the banner applies, nothing is stored until the visitor accepts
725
+ // targeting cookies, and a later "reject" clears it again.
726
+ whenConsentAllows("targeting", () => storeTouches(current, forwarded, forwardedAt), { optimistic: true, revoke: clearStoredAttribution });
727
+ }
728
+ catch {
729
+ // coercion-ok: attribution is best-effort telemetry; never let it break
730
+ // boot.
731
+ }
732
+ }
733
+ function storeTouches(current, forwarded, forwardedAt) {
734
+ try {
735
+ const local = readLocalFirstTouch();
736
+ // The same visit in both keeps the local copy, which was never trimmed.
737
+ const firstTouch = preferFirstTouch(preferFirstTouch(local, readServerFirstTouch()), current);
738
+ if (firstTouch === local) {
589
739
  backfillAttributionCookie(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE);
590
740
  }
591
741
  else {
592
- storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, current);
742
+ storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, firstTouch);
593
743
  }
594
- captureLastTouchAttribution(current);
744
+ captureLastTouchAttribution(current, forwarded, forwardedAt);
745
+ syncFirstTouchToServer(firstTouch);
595
746
  }
596
747
  catch {
597
748
  // Attribution is best-effort telemetry; never let it break boot.
@@ -604,10 +755,8 @@ function captureFirstTouchAttribution(options = {}) {
604
755
  * visit keeps the time it happened, and loses to a newer visit the app has
605
756
  * already recorded.
606
757
  */
607
- function captureLastTouchAttribution(current) {
608
- const forwarded = readForwardedLastTouch();
758
+ function captureLastTouchAttribution(current, forwarded, forwardedAt) {
609
759
  const source = forwarded ?? (hasAttributionSource(current) ? current : null);
610
- const forwardedAt = readForwardedLastTouchAt();
611
760
  const existing = getLastTouchAttribution();
612
761
  const existingAt = Date.parse(existing?.touched_at ?? "");
613
762
  if (!source ||
@@ -702,7 +851,9 @@ function readStoredAttribution(storageKey, cookieName) {
702
851
  return hasAttributionSource(cookie) ? cookie : (stored ?? cookie);
703
852
  }
704
853
  export function getFirstTouchAttribution() {
705
- return readStoredAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE_NAME);
854
+ if (typeof window === "undefined")
855
+ return null;
856
+ return preferFirstTouch(readLocalFirstTouch(), readServerFirstTouch());
706
857
  }
707
858
  export function getLastTouchAttribution() {
708
859
  return readStoredAttribution(LAST_TOUCH_STORAGE_KEY, LAST_TOUCH_COOKIE_NAME);
@@ -731,20 +882,82 @@ function isSyntheticBrowserTraffic() {
731
882
  return (typeof window !== "undefined" &&
732
883
  isSyntheticTrafficValue(window.__AGENT_NATIVE_SYNTHETIC_TRAFFIC__));
733
884
  }
885
+ /**
886
+ * Amplitude keeps its device id in a cookie on the widest domain it can set,
887
+ * and its marketing touch and unsent events beside it, all named from the
888
+ * API key. A reject clears them at every level of this host.
889
+ */
890
+ function clearAmplitudeStorage(apiKey) {
891
+ const key = apiKey.slice(0, 10);
892
+ const names = [
893
+ `AMP_${key}`,
894
+ `AMP_MKTG_${key}`,
895
+ `AMP_MKTG_ORIGINAL_${key}`,
896
+ `AMP_unsent_${key}`,
897
+ `amp_${apiKey.slice(0, 6)}`,
898
+ ];
899
+ try {
900
+ const labels = window.location.hostname.split(".");
901
+ const domains = [""];
902
+ for (let i = 0; i < labels.length - 1; i += 1) {
903
+ domains.push(`; domain=.${labels.slice(i).join(".")}`);
904
+ }
905
+ for (const name of names) {
906
+ for (const domain of domains) {
907
+ document.cookie = `${name}=; path=/; max-age=0${domain}`;
908
+ }
909
+ }
910
+ }
911
+ catch {
912
+ // coercion-ok: a sandboxed iframe can't read or write cookies, so
913
+ // Amplitude stored none here.
914
+ }
915
+ for (const name of names)
916
+ safeStorageRemove(name);
917
+ }
734
918
  function ensureAmplitude() {
735
919
  if (isSyntheticBrowserTraffic())
736
920
  return false;
737
- if (_amplitudeInitialized)
738
- return true;
739
921
  const key = import.meta.env
740
922
  ?.VITE_AMPLITUDE_API_KEY;
741
923
  if (!key)
742
924
  return false;
743
925
  _amplitudeApiKey = key;
926
+ if (_amplitudeConsent !== "granted") {
927
+ if (_amplitudeConsent === "unasked") {
928
+ _amplitudeConsent = "waiting";
929
+ // Amplitude keeps a device id in the browser, so where the banner
930
+ // applies it waits for performance cookies.
931
+ whenConsentAllows("performance", () => {
932
+ _amplitudeConsent = "granted";
933
+ // Held before the region answered: with a choice on record, the
934
+ // visitor had to choose, so what came before it never goes out.
935
+ if (getConsentChoice())
936
+ _pendingAmplitudeEvents = [];
937
+ _amplitudeModule?.setOptOut(false);
938
+ ensureAmplitude();
939
+ }, {
940
+ revoke: () => {
941
+ _amplitudeConsent = "revoked";
942
+ _pendingAmplitudeEvents = [];
943
+ _amplitudeModule?.setOptOut(true);
944
+ // A later accept starts a new device id, not the one in memory.
945
+ _amplitudeModule?.reset();
946
+ clearAmplitudeStorage(key);
947
+ },
948
+ });
949
+ }
950
+ return false;
951
+ }
952
+ if (_amplitudeInitialized)
953
+ return true;
744
954
  if (_amplitudeLoadPromise)
745
955
  return false;
746
956
  _amplitudeLoadPromise = loadOptionalPeer("@amplitude/analytics-browser", () => import("@amplitude/analytics-browser"))
747
957
  .then((module) => {
958
+ // A reject while the SDK loaded: the next accept initializes it.
959
+ if (_amplitudeConsent !== "granted")
960
+ return null;
748
961
  module.init(key, { autocapture: false });
749
962
  _amplitudeModule = module;
750
963
  _amplitudeInitialized = true;
@@ -2005,15 +2218,30 @@ function sendAgentNativeAnalytics(name, properties) {
2005
2218
  ?.VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT ||
2006
2219
  AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT, window.location.hostname);
2007
2220
  const userId = typeof properties.userId === "string" ? properties.userId : undefined;
2008
- const body = JSON.stringify({
2009
- publicKey,
2010
- event: name,
2011
- properties,
2012
- userId,
2013
- anonymousId: getOrCreateAnonymousId(),
2014
- sessionId: getOrCreateSessionId(),
2015
- timestamp: new Date().toISOString(),
2221
+ const sessionId = getOrCreateSessionId();
2222
+ const timestamp = new Date().toISOString();
2223
+ whenConsentDecided((decided) => {
2224
+ const anonymousId = decided ? getOrCreateAnonymousId() : undefined;
2225
+ postAgentNativeAnalytics(endpoint, JSON.stringify({
2226
+ publicKey,
2227
+ event: name,
2228
+ // The visitor id waits for analytics consent; mark the event so
2229
+ // reports can tell it from one that lost its id some other way, and
2230
+ // a refusal from a page that closed before the region was known.
2231
+ properties: anonymousId
2232
+ ? properties
2233
+ : {
2234
+ ...properties,
2235
+ analytics_consent: decided ? "withheld" : "unknown",
2236
+ },
2237
+ userId,
2238
+ anonymousId,
2239
+ sessionId,
2240
+ timestamp,
2241
+ }));
2016
2242
  });
2243
+ }
2244
+ function postAgentNativeAnalytics(endpoint, body) {
2017
2245
  try {
2018
2246
  if (navigator.sendBeacon) {
2019
2247
  const sent = navigator.sendBeacon(endpoint, body);
@@ -2037,17 +2265,34 @@ function createTrackingAliasId() {
2037
2265
  ? crypto.randomUUID()
2038
2266
  : undefined;
2039
2267
  }
2268
+ function pageHasGoogleTags() {
2269
+ const holder = window;
2270
+ return Boolean(holder.__AN_CONSENT_LAYERS__ ||
2271
+ window.__AGENT_NATIVE_GA_GTAG__ ||
2272
+ window.gtag);
2273
+ }
2040
2274
  function emitBrowserTrackingEvent(name, props, options = {}) {
2041
2275
  const { agentNativeProperties = props, gtagProperties = props, sendGtag = true, } = options;
2042
2276
  const amplitudeProps = amplitudeEventProperties(name, props);
2043
- if (sendGtag) {
2044
- const gtag = window.__AGENT_NATIVE_GA_GTAG__ ?? window.gtag;
2045
- gtag?.("event", name.replace(/\s+/g, "_"), gtagProperties);
2277
+ // Without Google tags there is nothing to hold, and asking for the region
2278
+ // would be a request a scoped embed (an MCP App widget) may not make.
2279
+ if (sendGtag && pageHasGoogleTags()) {
2280
+ whenConsentDecided(() => {
2281
+ if (consentDecision("performance") !== true)
2282
+ return;
2283
+ const gtag = window.__AGENT_NATIVE_GA_GTAG__ ?? window.gtag;
2284
+ gtag?.("event", name.replace(/\s+/g, "_"), gtagProperties);
2285
+ });
2046
2286
  }
2047
2287
  if (ensureAmplitude()) {
2048
2288
  _amplitudeModule?.track(name, amplitudeProps);
2049
2289
  }
2050
- else if (_amplitudeApiKey) {
2290
+ else if (_amplitudeApiKey &&
2291
+ // Only while it loads after an accept, or before the region is known;
2292
+ // the region's answer clears what's held if the visitor must choose.
2293
+ (_amplitudeConsent === "granted" ||
2294
+ (_amplitudeConsent !== "revoked" &&
2295
+ consentDecision("performance") === null))) {
2051
2296
  if (_pendingAmplitudeEvents.length < 100) {
2052
2297
  _pendingAmplitudeEvents.push([name, amplitudeProps]);
2053
2298
  }
@@ -1,5 +1,5 @@
1
1
  import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
2
- import { AGENT_NATIVE_SUBMISSION_DEADLINES_CAPABILITY_ID, createCapabilityUnavailableError, createCapabilityUnsupportedError, agentEventSequenceEnd, isAgentKitProtocolVersion, negotiateAgentKitProtocolVersion, parseAgentQueuedMessage, parseAgentRunOptions, parseAgentThreadSnapshot, persistableFilePart, } from "@agent-native/agentkit/protocol";
2
+ import { AGENT_NATIVE_SUBMISSION_DEADLINES_CAPABILITY_ID, createCapabilityUnavailableError, createCapabilityUnsupportedError, agentEventSequenceEnd, isAgentKitProtocolVersion, negotiateAgentKitProtocolVersion, parseAgentQueuedMessage, parseNullableAgentQueuedSubmissionStatus, parseAgentRunOptions, parseAgentThreadSnapshot, persistableFilePart, } from "@agent-native/agentkit/protocol";
3
3
  import { failedRunIdsWithError, isRenderedRunNotStartedNotice, projectRootAssistantMessages, } from "../../agent/thread-message-projection.js";
4
4
  import { RUN_NOT_STARTED_METADATA_KEY, retryContextFromRequest, } from "../../shared/agent-chat-run-not-started.js";
5
5
  import { AGENT_CHAT_THREAD_ABSENCE_HEADER } from "../../shared/agent-chat.js";
@@ -1258,6 +1258,15 @@ function storedQueue(value, threadId, fallbackCreatedAt) {
1258
1258
  createdAt: timestamp(queued.createdAt, fallbackCreatedAt),
1259
1259
  requestAttachments: queued.requestAttachments,
1260
1260
  }).requestAttachments;
1261
+ const references = queued.references === undefined
1262
+ ? undefined
1263
+ : parseAgentQueuedMessage({
1264
+ id: queued.id,
1265
+ threadId,
1266
+ text: queued.text,
1267
+ createdAt: timestamp(queued.createdAt, fallbackCreatedAt),
1268
+ references: queued.references,
1269
+ }).references;
1261
1270
  return {
1262
1271
  id: queued.id,
1263
1272
  threadId,
@@ -1265,6 +1274,7 @@ function storedQueue(value, threadId, fallbackCreatedAt) {
1265
1274
  createdAt: timestamp(queued.createdAt, fallbackCreatedAt),
1266
1275
  ...(attachments?.length ? { attachments } : {}),
1267
1276
  ...(requestAttachments?.length ? { requestAttachments } : {}),
1277
+ ...(references?.length ? { references } : {}),
1268
1278
  ...(asRecord(queued.metadata)
1269
1279
  ? { metadata: asRecord(queued.metadata) }
1270
1280
  : {}),
@@ -3233,6 +3243,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3233
3243
  ? { alreadySubmitted: true, submittedRunId }
3234
3244
  : {}),
3235
3245
  ...(value.submissionExpired === true ? { submissionExpired: true } : {}),
3246
+ ...(value.receiptManaged === true ? { receiptManaged: true } : {}),
3236
3247
  };
3237
3248
  }
3238
3249
  // A server that predates `active` meaning "in flight" reports a run inside
@@ -3292,6 +3303,16 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3292
3303
  });
3293
3304
  try {
3294
3305
  const run = await startRun(input, context);
3306
+ if (!run.runId) {
3307
+ dispatchAgentChatRunning({
3308
+ isRunning: false,
3309
+ phase: "idle",
3310
+ threadId: input.threadId,
3311
+ tabId: input.threadId,
3312
+ reason: "submission_queued",
3313
+ });
3314
+ return run;
3315
+ }
3295
3316
  dispatchAgentChatRunning({
3296
3317
  isRunning: true,
3297
3318
  phase: "working",
@@ -3329,6 +3350,42 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3329
3350
  ? messageEvent.message
3330
3351
  : undefined));
3331
3352
  }
3353
+ async function readQueuedSubmissionStatus(threadId, messageId) {
3354
+ const requestHeaders = await headers({ sessionId: threadId });
3355
+ requestHeaders.set(AGENT_CHAT_THREAD_ABSENCE_HEADER, "typed");
3356
+ const response = await fetcher(scopedThreadEndpoint(`${apiUrl}/threads/${encodeURIComponent(threadId)}/queue/${encodeURIComponent(messageId)}/receipt`, options), { headers: requestHeaders });
3357
+ if (response.status === 404)
3358
+ return null;
3359
+ if (!response.ok)
3360
+ throw await responseError(response);
3361
+ return parseNullableAgentQueuedSubmissionStatus(await response.json());
3362
+ }
3363
+ async function reconcileQueuedSubmission(threadId, messageId) {
3364
+ const status = await readQueuedSubmissionStatus(threadId, messageId);
3365
+ if (!status) {
3366
+ throw new TypeError(`Receipt-managed queue message ${messageId} has no submission receipt.`);
3367
+ }
3368
+ if (status.state === "submitted") {
3369
+ if (!status.submittedRunId) {
3370
+ throw new TypeError(`Submitted queue message ${messageId} has no run ID.`);
3371
+ }
3372
+ return {
3373
+ runId: status.submittedRunId,
3374
+ alreadySubmitted: true,
3375
+ };
3376
+ }
3377
+ if (status.state === "removed" || status.state === "expired") {
3378
+ return { alreadyRemoved: true };
3379
+ }
3380
+ if (status.state === "blocked") {
3381
+ const error = new Error(`Queued submission ${messageId} is blocked in the server inbox.`);
3382
+ Object.assign(error, { code: "queue_item_blocked", retryable: false });
3383
+ throw error;
3384
+ }
3385
+ const error = new Error(`Queued submission ${messageId} is still waiting in the server inbox.`);
3386
+ Object.assign(error, { code: "queue_item_busy", retryable: true });
3387
+ throw error;
3388
+ }
3332
3389
  const runtime = options.runtime ?? createAgentNativeChatRuntime(options);
3333
3390
  const feedbackUrl = options.feedbackUrl ??
3334
3391
  agentNativePath("/_agent-native/observability/feedback");
@@ -3360,7 +3417,10 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3360
3417
  },
3361
3418
  getThreadSnapshot: ({ threadId }) => threadSnapshotWithActiveRun(threadId),
3362
3419
  listQueuedMessages: async ({ threadId }) => readQueue(threadId),
3363
- queueMessage: async ({ threadId, id, text, attachments, requestAttachments, metadata, options: runOptions, interruptActiveRun, submissionPayload, }) => {
3420
+ getQueuedSubmissionStatus: async ({ threadId, messageId }) => {
3421
+ return readQueuedSubmissionStatus(threadId, messageId);
3422
+ },
3423
+ queueMessage: async ({ threadId, id, text, attachments, requestAttachments, references, metadata, options: runOptions, interruptActiveRun, submissionPayload, }) => {
3364
3424
  const queueSubmissionPayload = submissionPayload
3365
3425
  ? { ...submissionPayload, metadata }
3366
3426
  : undefined;
@@ -3375,6 +3435,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3375
3435
  createdAt: now(),
3376
3436
  attachments,
3377
3437
  requestAttachments,
3438
+ references,
3378
3439
  metadata,
3379
3440
  options: runOptions,
3380
3441
  ...(interruptActiveRun ? { interruptActiveRun: true } : {}),
@@ -3405,7 +3466,8 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3405
3466
  return { message: result.message };
3406
3467
  },
3407
3468
  removeQueuedMessage: async ({ threadId, messageId }) => {
3408
- await persistQueueMutation(threadId, { type: "remove", messageId });
3469
+ await persistQueueMutation(threadId, { type: "cancel", messageId });
3470
+ clearPromotionClaimId(threadId, messageId);
3409
3471
  },
3410
3472
  moveQueuedMessageToTop: async ({ threadId, messageId }) => {
3411
3473
  await persistQueueMutation(threadId, { type: "moveToTop", messageId });
@@ -3457,6 +3519,11 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3457
3519
  clearPromotionClaimId(threadId, messageId);
3458
3520
  return { alreadyRemoved: true };
3459
3521
  }
3522
+ if (claim.receiptManaged) {
3523
+ clearPromotionClaimId(threadId, messageId);
3524
+ claimHeld = false;
3525
+ return await reconcileQueuedSubmission(threadId, messageId);
3526
+ }
3460
3527
  if (!claim.claimedMessage) {
3461
3528
  throw new TypeError("Agent chat queue claim response is invalid.");
3462
3529
  }
@@ -3524,6 +3591,11 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3524
3591
  clearPromotionClaimId(threadId, messageId);
3525
3592
  return { alreadyRemoved: true };
3526
3593
  }
3594
+ if (claim.receiptManaged) {
3595
+ clearPromotionClaimId(threadId, messageId);
3596
+ claimHeld = false;
3597
+ return await reconcileQueuedSubmission(threadId, messageId);
3598
+ }
3527
3599
  if (claim.alreadySubmitted && claim.submittedRunId) {
3528
3600
  clearPromotionClaimId(threadId, messageId);
3529
3601
  return {
@@ -3557,6 +3629,7 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3557
3629
  },
3558
3630
  ],
3559
3631
  requestAttachments: claimedMessage.requestAttachments,
3632
+ references: claimedMessage.references,
3560
3633
  options: claimedMessage.options,
3561
3634
  metadata: claimedMessage.metadata,
3562
3635
  queuePromotion: {
@@ -3565,6 +3638,9 @@ export function createAgentNativeAgentKitTransport(options = {}) {
3565
3638
  turnId: `queue-${messageId}`,
3566
3639
  },
3567
3640
  }, undefined);
3641
+ if (!run.runId) {
3642
+ throw new TypeError("A queue promotion cannot be acknowledged as a new queued submission.");
3643
+ }
3568
3644
  const latestThread = await snapshot(threadId);
3569
3645
  if (!latestThread) {
3570
3646
  throw new Error(`Unknown agent chat thread: ${threadId}`);