@agent-native/core 0.201.1 → 0.202.0-nightly-20261006033736

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 (98) hide show
  1. package/dist/automation/index.d.ts +1 -1
  2. package/dist/client/analytics/index.d.ts +1 -1
  3. package/dist/client/analytics/index.js +1 -1
  4. package/dist/client/analytics.d.ts +23 -3
  5. package/dist/client/analytics.js +189 -46
  6. package/dist/client/client-status-requests.d.ts +4 -1
  7. package/dist/client/client-status-requests.js +21 -5
  8. package/dist/client/embed-auth.d.ts +6 -0
  9. package/dist/client/embed-auth.js +13 -0
  10. package/dist/client/session-replay.js +48 -22
  11. package/dist/client/sharing/index.d.ts +2 -0
  12. package/dist/client/sharing/index.js +2 -0
  13. package/dist/client/sharing/useAccessRequestReview.d.ts +80 -0
  14. package/dist/client/sharing/useAccessRequestReview.js +100 -0
  15. package/dist/client/sharing/useResourceAccessGate.d.ts +57 -0
  16. package/dist/client/sharing/useResourceAccessGate.js +76 -0
  17. package/dist/client/use-agent-engine-configured.d.ts +2 -0
  18. package/dist/client/use-agent-engine-configured.js +4 -1
  19. package/dist/email-catalog/registry.d.ts +1 -1
  20. package/dist/email-catalog/registry.js +1 -1
  21. package/dist/email-catalog/system-emails.d.ts +2 -2
  22. package/dist/email-catalog/system-emails.js +47 -2
  23. package/dist/email-catalog/templates.d.ts +5 -1
  24. package/dist/email-catalog/templates.js +11 -1
  25. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  26. package/dist/framework-tools.js +6 -0
  27. package/dist/identity/rekey.js +9 -0
  28. package/dist/jobs/run-history.d.ts +27 -8
  29. package/dist/jobs/run-history.js +17 -10
  30. package/dist/jobs/scheduler.js +39 -10
  31. package/dist/mcp/build-server.d.ts +1 -0
  32. package/dist/mcp/build-server.js +30 -7
  33. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  34. package/dist/notifications/routes.d.ts +2 -2
  35. package/dist/observability/routes.d.ts +7 -7
  36. package/dist/org/index.d.ts +1 -1
  37. package/dist/progress/routes.d.ts +1 -1
  38. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  39. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  40. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  41. package/dist/review/suggestions/actions.js +14 -35
  42. package/dist/review/suggestions/store.d.ts +0 -1
  43. package/dist/review/suggestions/store.js +0 -7
  44. package/dist/secrets/routes.d.ts +11 -11
  45. package/dist/server/action-discovery.js +24 -0
  46. package/dist/server/agent-access.js +7 -3
  47. package/dist/server/agent-chat/mcp-options.d.ts +1 -0
  48. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  49. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  50. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  51. package/dist/server/attribution.d.ts +33 -1
  52. package/dist/server/attribution.js +93 -21
  53. package/dist/server/auth.js +35 -11
  54. package/dist/server/email-templates.d.ts +34 -0
  55. package/dist/server/email-templates.js +40 -0
  56. package/dist/server/magic-link-attribution.js +2 -1
  57. package/dist/server/onboarding-html.js +35 -67
  58. package/dist/server/release-schema.js +4 -0
  59. package/dist/server/short-lived-token.d.ts +7 -0
  60. package/dist/server/short-lived-token.js +59 -0
  61. package/dist/shared/analytics-anonymous-id.d.ts +1 -0
  62. package/dist/shared/analytics-anonymous-id.js +1 -1
  63. package/dist/shared/attribution-source.d.ts +30 -0
  64. package/dist/shared/attribution-source.js +65 -0
  65. package/dist/sharing/access-request-store.d.ts +96 -0
  66. package/dist/sharing/access-request-store.js +213 -0
  67. package/dist/sharing/access-requests.d.ts +113 -0
  68. package/dist/sharing/access-requests.js +490 -0
  69. package/dist/sharing/access.d.ts +36 -2
  70. package/dist/sharing/access.js +74 -12
  71. package/dist/sharing/actions/approve-resource-access-request.d.ts +10 -0
  72. package/dist/sharing/actions/approve-resource-access-request.js +15 -0
  73. package/dist/sharing/actions/decline-resource-access-request.d.ts +7 -0
  74. package/dist/sharing/actions/decline-resource-access-request.js +14 -0
  75. package/dist/sharing/actions/get-resource-access-request.d.ts +4 -0
  76. package/dist/sharing/actions/get-resource-access-request.js +13 -0
  77. package/dist/sharing/actions/get-resource-access-status.d.ts +5 -0
  78. package/dist/sharing/actions/get-resource-access-status.js +19 -0
  79. package/dist/sharing/actions/list-resource-access-requests.d.ts +5 -0
  80. package/dist/sharing/actions/list-resource-access-requests.js +16 -0
  81. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  82. package/dist/sharing/actions/request-resource-access.d.ts +6 -0
  83. package/dist/sharing/actions/request-resource-access.js +17 -0
  84. package/dist/sharing/actions/share-resource.d.ts +1 -1
  85. package/dist/sharing/actions/share-resource.js +18 -144
  86. package/dist/sharing/grant.d.ts +49 -0
  87. package/dist/sharing/grant.js +195 -0
  88. package/dist/sharing/index.d.ts +2 -1
  89. package/dist/sharing/index.js +2 -1
  90. package/dist/sharing/registry.d.ts +36 -0
  91. package/dist/templates/chat/server/plugins/auth.ts +2 -2
  92. package/dist/templates/default/app/routes/access-requests.$requestId.tsx +7 -0
  93. package/dist/templates/default/server/plugins/auth.ts +2 -2
  94. package/dist/vite/action-types-plugin.js +24 -0
  95. package/package.json +11 -3
  96. package/src/templates/chat/server/plugins/auth.ts +2 -2
  97. package/src/templates/default/app/routes/access-requests.$requestId.tsx +7 -0
  98. package/src/templates/default/server/plugins/auth.ts +2 -2
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
139
139
  duplicate?: undefined;
140
140
  eventId?: undefined;
141
141
  } | {
142
+ error?: undefined;
142
143
  accepted: boolean;
143
144
  duplicate: boolean;
144
145
  eventId: string;
145
- error?: undefined;
146
146
  }>;
@@ -1,2 +1,2 @@
1
- export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, type AgentChatLifecycleEvent, type CaptureExceptionContext, type CapturedExceptionEvent, type ClientCaptureContext, type ConfigureTrackingOptions, type ErrorCaptureConfigOptions, type ExceptionBreadcrumb, type ExceptionLevel, type FirstTouchAttribution, type SessionReplayContext, type SessionReplayLinkOptions, type SessionReplayOptions, type SessionReplayStartResult, type SessionReplayUrlMatcher, type TrackingIdentityUser, } from "../analytics.js";
1
+ export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getLastTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, type AgentChatLifecycleEvent, type CaptureExceptionContext, type CapturedExceptionEvent, type ClientCaptureContext, type ConfigureTrackingOptions, type ErrorCaptureConfigOptions, type ExceptionBreadcrumb, type ExceptionLevel, type FirstTouchAttribution, type LastTouchAttribution, type SessionReplayContext, type SessionReplayLinkOptions, type SessionReplayOptions, type SessionReplayStartResult, type SessionReplayUrlMatcher, type TrackingIdentityUser, } from "../analytics.js";
2
2
  export { track } from "../track.js";
@@ -1,2 +1,2 @@
1
- export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, } from "../analytics.js";
1
+ export { AGENT_NATIVE_EXCEPTION_EVENT_NAME, addErrorBreadcrumb, captureAttribution, captureClientException, captureError, captureException, captureMessage, clearAnalyticsSessionId, configureTracking, getAnalyticsAnonymousId, getAnalyticsSessionId, getFirstTouchAttribution, getLastTouchAttribution, getSessionReplayContext, getSessionReplayUrl, isErrorCaptureInstalled, maybeStartSessionReplay, setAnalyticsSessionId, setSentryUser, setTrackingContentCaptureEnabled, setTrackingIdentity, startSessionReplay, stopSessionReplay, trackAgentChatLifecycle, trackAnonymousEvent, trackEvent, trackLifecycleEvent, trackSessionStatus, } from "../analytics.js";
2
2
  export { track } from "../track.js";
@@ -86,14 +86,34 @@ export interface FirstTouchAttribution {
86
86
  landed_at?: string;
87
87
  capture_truncated?: string;
88
88
  }
89
+ /** The latest visit that had a source; see `captureLastTouchAttribution`. */
90
+ export interface LastTouchAttribution {
91
+ ref?: string;
92
+ via?: string;
93
+ utm_source?: string;
94
+ utm_medium?: string;
95
+ utm_campaign?: string;
96
+ utm_content?: string;
97
+ utm_term?: string;
98
+ gclid?: string;
99
+ msclkid?: string;
100
+ vector_source?: string;
101
+ landing_referrer?: string;
102
+ site_referrer?: string;
103
+ site_landing_path?: string;
104
+ landing_path?: string;
105
+ touched_at?: string;
106
+ capture_truncated?: string;
107
+ }
89
108
  export declare function getAnalyticsIdentityKey(): string | undefined;
90
109
  export declare function getAnalyticsAnonymousId(): string | undefined;
91
110
  export declare function getAnalyticsSessionId(): string | undefined;
92
111
  export declare function getFirstTouchAttribution(): FirstTouchAttribution | null;
112
+ export declare function getLastTouchAttribution(): LastTouchAttribution | null;
93
113
  /**
94
- * Store the visitor's first touch now instead of in `configureTracking()`,
95
- * for a page that reads it before tracking starts. It runs once per page
96
- * load, so tracking's own capture is then a no-op.
114
+ * Store the visitor's first and last touch now instead of in
115
+ * `configureTracking()`, for a page that reads them before tracking starts.
116
+ * It runs once per page load, so tracking's own capture is then a no-op.
97
117
  */
98
118
  export declare function captureAttribution(): void;
99
119
  export declare function setSentryUser(user: TrackingIdentityUser | null, orgId?: string | null): void;
@@ -1,6 +1,7 @@
1
1
  import { recordTrackingEvent } from "../observability/tracing.js";
2
2
  import { AGENT_NATIVE_LIFECYCLE_EVENTS, canonicalTrackingEvent, legacyLifecycleEvent, normalizeTrackingDimension, withCanonicalTrackingProperties, } from "../shared/analytics-events.js";
3
3
  import { ANALYTICS_CLIENT_PLATFORM_PROPERTY, } from "../shared/analytics-platform.js";
4
+ import { hasAttributionSource } from "../shared/attribution-source.js";
4
5
  import { resolveLaneEndpoint } from "../shared/environment-lanes.js";
5
6
  import { classifyErrorNoise, } from "../shared/error-noise.js";
6
7
  import { llmConnectionTrackingProperties, } from "../shared/llm-connection.js";
@@ -101,6 +102,72 @@ const FIRST_TOUCH_COOKIE_FIELD_PRIORITY = [
101
102
  "site_landing_path",
102
103
  "landed_at",
103
104
  ];
105
+ const LAST_TOUCH_STORAGE_KEY = "an_last_touch";
106
+ const LAST_TOUCH_COOKIE_NAME = "an_lt";
107
+ // Small enough that both cookies still fit the signup handoff header.
108
+ const LAST_TOUCH_MAX_COOKIE_BYTES = 700;
109
+ // What a sourced visit keeps as last touch, besides its path and time.
110
+ const LAST_TOUCH_SOURCE_FIELDS = [
111
+ "ref",
112
+ "via",
113
+ "utm_source",
114
+ "utm_medium",
115
+ "utm_campaign",
116
+ "utm_content",
117
+ "utm_term",
118
+ "gclid",
119
+ "msclkid",
120
+ "vector_source",
121
+ "landing_referrer",
122
+ "site_referrer",
123
+ ];
124
+ // Which fields the cookie keeps first when they don't all fit.
125
+ const LAST_TOUCH_COOKIE_FIELD_PRIORITY = [
126
+ "ref",
127
+ "gclid",
128
+ "msclkid",
129
+ "vector_source",
130
+ "utm_source",
131
+ "utm_medium",
132
+ "utm_campaign",
133
+ "via",
134
+ "utm_content",
135
+ "utm_term",
136
+ "landing_referrer",
137
+ "site_referrer",
138
+ "landing_path",
139
+ "touched_at",
140
+ "site_landing_path",
141
+ ];
142
+ // The marketing site forwards its own last touch under these names when it
143
+ // differs from the first touch it forwards as plain campaign params.
144
+ const FORWARDED_LAST_TOUCH_FIELDS = {
145
+ last_ref: "ref",
146
+ last_via: "via",
147
+ last_utm_source: "utm_source",
148
+ last_utm_medium: "utm_medium",
149
+ last_utm_campaign: "utm_campaign",
150
+ last_utm_content: "utm_content",
151
+ last_utm_term: "utm_term",
152
+ last_gclid: "gclid",
153
+ last_msclkid: "msclkid",
154
+ last_vector_source: "vector_source",
155
+ last_referrer: "site_referrer",
156
+ last_landing_path: "site_landing_path",
157
+ };
158
+ // When the site's latest sourced visit happened, so an older site visit can't
159
+ // replace a newer one the app already has.
160
+ const FORWARDED_LAST_TOUCH_AT_PARAM = "last_at";
161
+ const FIRST_TOUCH_COOKIE = {
162
+ name: FIRST_TOUCH_COOKIE_NAME,
163
+ maxBytes: FIRST_TOUCH_MAX_COOKIE_BYTES,
164
+ priority: FIRST_TOUCH_COOKIE_FIELD_PRIORITY,
165
+ };
166
+ const LAST_TOUCH_COOKIE = {
167
+ name: LAST_TOUCH_COOKIE_NAME,
168
+ maxBytes: LAST_TOUCH_MAX_COOKIE_BYTES,
169
+ priority: LAST_TOUCH_COOKIE_FIELD_PRIORITY,
170
+ };
104
171
  let _firstTouchCaptured = false;
105
172
  function safeStorageGet(key) {
106
173
  try {
@@ -390,7 +457,7 @@ function buildFirstTouchAttribution() {
390
457
  attribution.landed_at = new Date().toISOString();
391
458
  return attribution;
392
459
  }
393
- function readFirstTouchCookie() {
460
+ function readAttributionCookie(cookieName) {
394
461
  if (typeof document === "undefined")
395
462
  return null;
396
463
  try {
@@ -400,7 +467,7 @@ function readFirstTouchCookie() {
400
467
  if (eq === -1)
401
468
  continue;
402
469
  const name = part.slice(0, eq).trim();
403
- if (name === FIRST_TOUCH_COOKIE_NAME) {
470
+ if (name === cookieName) {
404
471
  return part.slice(eq + 1).trim();
405
472
  }
406
473
  }
@@ -410,15 +477,16 @@ function readFirstTouchCookie() {
410
477
  }
411
478
  return null;
412
479
  }
413
- function firstTouchCookieAssignment(encodedValue) {
414
- return (`${FIRST_TOUCH_COOKIE_NAME}=${encodedValue}; path=/; ` +
480
+ function attributionCookieAssignment(cookieName, encodedValue) {
481
+ return (`${cookieName}=${encodedValue}; path=/; ` +
415
482
  `max-age=${FIRST_TOUCH_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`);
416
483
  }
417
- function fitFirstTouchCookieValue(value) {
484
+ function fitAttributionCookieValue(value, spec) {
418
485
  const source = JSON.parse(value);
419
486
  const compact = {};
420
487
  let truncated = false;
421
- for (const field of FIRST_TOUCH_COOKIE_FIELD_PRIORITY) {
488
+ const fits = (encoded) => attributionCookieAssignment(spec.name, encoded).length <= spec.maxBytes;
489
+ for (const field of spec.priority) {
422
490
  const rawValue = source[field];
423
491
  if (typeof rawValue !== "string" || !rawValue)
424
492
  continue;
@@ -426,9 +494,8 @@ function fitFirstTouchCookieValue(value) {
426
494
  ...compact,
427
495
  [field]: rawValue.slice(0, FIRST_TOUCH_MAX_FIELD_LENGTH),
428
496
  };
429
- const encoded = encodeURIComponent(JSON.stringify(candidate));
430
- if (firstTouchCookieAssignment(encoded).length <= FIRST_TOUCH_MAX_COOKIE_BYTES) {
431
- Object.assign(compact, { [field]: candidate[field] });
497
+ if (fits(encodeURIComponent(JSON.stringify(candidate)))) {
498
+ compact[field] = candidate[field];
432
499
  }
433
500
  else {
434
501
  truncated = true;
@@ -437,38 +504,63 @@ function fitFirstTouchCookieValue(value) {
437
504
  if (truncated) {
438
505
  compact.capture_truncated = "1";
439
506
  // Keep the auth handoff under its 4 KB header limit after re-encoding.
440
- for (const field of [...FIRST_TOUCH_COOKIE_FIELD_PRIORITY].reverse()) {
507
+ for (const field of [...spec.priority].reverse()) {
441
508
  const encoded = encodeURIComponent(JSON.stringify(compact));
442
- if (firstTouchCookieAssignment(encoded).length <=
443
- FIRST_TOUCH_MAX_COOKIE_BYTES) {
509
+ if (fits(encoded))
444
510
  return encoded;
445
- }
446
511
  delete compact[field];
447
512
  }
448
513
  }
449
514
  const encoded = encodeURIComponent(JSON.stringify(compact));
450
- if (firstTouchCookieAssignment(encoded).length > FIRST_TOUCH_MAX_COOKIE_BYTES) {
451
- throw new Error("First-touch attribution exceeded the cookie budget");
515
+ if (!fits(encoded)) {
516
+ throw new Error(`Attribution exceeded the ${spec.name} cookie budget`);
452
517
  }
453
518
  return encoded;
454
519
  }
455
- function writeFirstTouchCookie(value) {
520
+ function writeAttributionCookie(value, spec) {
456
521
  if (typeof document === "undefined")
457
522
  return;
458
- const encodedValue = fitFirstTouchCookieValue(value);
523
+ const encodedValue = fitAttributionCookieValue(value, spec);
459
524
  try {
460
- document.cookie = firstTouchCookieAssignment(encodedValue);
525
+ document.cookie = attributionCookieAssignment(spec.name, encodedValue);
461
526
  }
462
527
  catch {
463
528
  // best-effort
464
529
  }
465
530
  }
531
+ function storeAttribution(storageKey, spec, attribution) {
532
+ const json = JSON.stringify(attribution);
533
+ safeStorageSet(storageKey, json);
534
+ writeAttributionCookie(json, spec);
535
+ }
536
+ /**
537
+ * Restore a cookie that expired or was cleared from its stored value, so the
538
+ * signup boundary still sees it.
539
+ */
540
+ function backfillAttributionCookie(storageKey, spec) {
541
+ if (readAttributionCookie(spec.name))
542
+ return;
543
+ const stored = safeStorageGet(storageKey);
544
+ if (!stored)
545
+ return;
546
+ try {
547
+ writeAttributionCookie(stored, spec);
548
+ }
549
+ catch {
550
+ // coercion-ok: localStorage still holds the value; only this page's
551
+ // signup handoff goes without it.
552
+ }
553
+ }
466
554
  /**
467
- * Capture the visitor's first-touch referral attribution exactly once. Reads
468
- * the current URL query params + landing info and, IF no attribution is
469
- * already stored (first-write-wins), persists it to both `localStorage`
470
- * (`an_attribution`) and the first-party `an_ft` cookie. Fully defensive and
471
- * SSR-safe — any failure is swallowed so it can never break app boot.
555
+ * Capture the visitor's referral attribution once per page load, into both
556
+ * `localStorage` and a first-party cookie the signup boundary reads. Fully
557
+ * defensive and SSR-safe — any failure is swallowed so it can never break app
558
+ * boot.
559
+ *
560
+ * First touch (`an_attribution` / `an_ft`) is first-write-wins, except over a
561
+ * visit that had no source: the first visit that says where this person came
562
+ * from replaces it. Without that, an untagged first visit would hide every
563
+ * tagged visit after it.
472
564
  */
473
565
  function captureFirstTouchAttribution() {
474
566
  if (_firstTouchCaptured)
@@ -477,35 +569,80 @@ function captureFirstTouchAttribution() {
477
569
  if (typeof window === "undefined")
478
570
  return;
479
571
  try {
480
- const existing = safeStorageGet(FIRST_TOUCH_STORAGE_KEY);
481
- if (existing) {
482
- // Already captured in a prior visit. Backfill the cookie if it expired
483
- // or was cleared so the signup boundary still sees first-touch data, but
484
- // never overwrite the stored value itself (first-write-wins).
485
- if (!readFirstTouchCookie()) {
486
- try {
487
- writeFirstTouchCookie(existing);
488
- }
489
- catch {
490
- // ignore
491
- }
492
- }
493
- return;
572
+ const current = buildFirstTouchAttribution();
573
+ const existing = getFirstTouchAttribution();
574
+ if (existing &&
575
+ (hasAttributionSource(existing) || !hasAttributionSource(current))) {
576
+ backfillAttributionCookie(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE);
577
+ }
578
+ else {
579
+ storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, current);
494
580
  }
495
- const attribution = buildFirstTouchAttribution();
496
- const json = JSON.stringify(attribution);
497
- safeStorageSet(FIRST_TOUCH_STORAGE_KEY, json);
498
- writeFirstTouchCookie(json);
581
+ captureLastTouchAttribution(current);
499
582
  }
500
583
  catch {
501
584
  // Attribution is best-effort telemetry; never let it break boot.
502
585
  }
503
586
  }
504
- export function getFirstTouchAttribution() {
587
+ /**
588
+ * Last touch (`an_last_touch` / `an_lt`) is the latest visit that had a
589
+ * source. When the marketing site forwards its own last touch, that visit is
590
+ * the one to keep, not the site's first touch riding on the same link. A site
591
+ * visit keeps the time it happened, and loses to a newer visit the app has
592
+ * already recorded.
593
+ */
594
+ function captureLastTouchAttribution(current) {
595
+ const forwarded = readForwardedLastTouch();
596
+ const source = forwarded ?? (hasAttributionSource(current) ? current : null);
597
+ const forwardedAt = readForwardedLastTouchAt();
598
+ const existing = getLastTouchAttribution();
599
+ const existingAt = Date.parse(existing?.touched_at ?? "");
600
+ if (!source ||
601
+ (forwardedAt &&
602
+ hasAttributionSource(existing) &&
603
+ existingAt > Date.parse(forwardedAt))) {
604
+ backfillAttributionCookie(LAST_TOUCH_STORAGE_KEY, LAST_TOUCH_COOKIE);
605
+ return;
606
+ }
607
+ const lastTouch = {};
608
+ for (const field of LAST_TOUCH_SOURCE_FIELDS) {
609
+ const value = source[field];
610
+ if (value)
611
+ lastTouch[field] = value;
612
+ }
613
+ // As for first touch, `landing_path` is where the visitor entered this app
614
+ // and `site_landing_path` the marketing-site page the touch landed on.
615
+ if (current.landing_path)
616
+ lastTouch.landing_path = current.landing_path;
617
+ if (source.site_landing_path) {
618
+ lastTouch.site_landing_path = source.site_landing_path;
619
+ }
620
+ lastTouch.touched_at = forwardedAt ?? current.landed_at;
621
+ storeAttribution(LAST_TOUCH_STORAGE_KEY, LAST_TOUCH_COOKIE, lastTouch);
622
+ }
623
+ function readForwardedLastTouchAt() {
624
+ const raw = new URLSearchParams(window.location.search).get(FORWARDED_LAST_TOUCH_AT_PARAM);
625
+ const time = Date.parse(raw ?? "");
626
+ // A visit can't be in the future; a bad clock or value means "now".
627
+ if (!Number.isFinite(time) || time > Date.now())
628
+ return undefined;
629
+ return new Date(time).toISOString();
630
+ }
631
+ function readForwardedLastTouch() {
632
+ const params = new URLSearchParams(window.location.search);
633
+ const forwarded = {};
634
+ for (const [param, field] of Object.entries(FORWARDED_LAST_TOUCH_FIELDS)) {
635
+ const value = truncateFirstTouchField(params.get(param));
636
+ if (value)
637
+ forwarded[field] = value;
638
+ }
639
+ return hasAttributionSource(forwarded) ? forwarded : null;
640
+ }
641
+ function readStoredAttribution(storageKey) {
505
642
  if (typeof window === "undefined")
506
643
  return null;
507
644
  try {
508
- const raw = safeStorageGet(FIRST_TOUCH_STORAGE_KEY);
645
+ const raw = safeStorageGet(storageKey);
509
646
  if (!raw)
510
647
  return null;
511
648
  const parsed = JSON.parse(raw);
@@ -518,10 +655,16 @@ export function getFirstTouchAttribution() {
518
655
  return null;
519
656
  }
520
657
  }
658
+ export function getFirstTouchAttribution() {
659
+ return readStoredAttribution(FIRST_TOUCH_STORAGE_KEY);
660
+ }
661
+ export function getLastTouchAttribution() {
662
+ return readStoredAttribution(LAST_TOUCH_STORAGE_KEY);
663
+ }
521
664
  /**
522
- * Store the visitor's first touch now instead of in `configureTracking()`,
523
- * for a page that reads it before tracking starts. It runs once per page
524
- * load, so tracking's own capture is then a no-op.
665
+ * Store the visitor's first and last touch now instead of in
666
+ * `configureTracking()`, for a page that reads them before tracking starts.
667
+ * It runs once per page load, so tracking's own capture is then a no-op.
525
668
  */
526
669
  export function captureAttribution() {
527
670
  if (isSyntheticBrowserTraffic())
@@ -4,6 +4,7 @@ export type ClientStatusResult<T> = {
4
4
  } | {
5
5
  state: "unavailable";
6
6
  status?: number;
7
+ stale?: boolean;
7
8
  };
8
9
  declare global {
9
10
  interface Window {
@@ -26,7 +27,9 @@ export declare function invalidateClientStatusRequest(path: string): void;
26
27
  */
27
28
  export declare function expireClientStatusResult(path: string): void;
28
29
  export declare function invalidateClientStatusRequests(): void;
29
- export declare function fetchAgentEngineStatus<T = unknown>(): Promise<ClientStatusResult<T>>;
30
+ export declare function fetchAgentEngineStatus<T = unknown>(options?: {
31
+ fresh?: boolean;
32
+ }): Promise<ClientStatusResult<T>>;
30
33
  export declare function fetchEnvironmentStatus<T = unknown>(): Promise<ClientStatusResult<T>>;
31
34
  export declare function fetchBuilderStatus<T = unknown>(): Promise<ClientStatusResult<T>>;
32
35
  export declare function fetchFileUploadStatus<T = unknown>(): Promise<ClientStatusResult<T>>;
@@ -14,6 +14,8 @@ const REQUEST_TIMEOUT_MS = 15_000;
14
14
  const SESSION_STATUS_PATH = "/_agent-native/auth/session";
15
15
  const cache = new Map();
16
16
  const requests = new Map();
17
+ const supersededRequests = new WeakSet();
18
+ const freshRequests = new WeakSet();
17
19
  const requestControllers = new Map();
18
20
  const requestGenerations = new Map();
19
21
  let invalidationListenersInstalled = false;
@@ -54,19 +56,28 @@ function installInvalidationListeners() {
54
56
  });
55
57
  }
56
58
  }
57
- async function fetchClientStatus(path) {
59
+ async function fetchClientStatus(path, options) {
58
60
  if (agentNativeApiDisabledReason())
59
61
  return { state: "unavailable" };
60
62
  installInvalidationListeners();
61
63
  const url = agentNativePath(path);
62
64
  const cached = cache.get(url);
63
- if (cached && cached.expiresAt > Date.now()) {
65
+ if (!options?.fresh && cached && cached.expiresAt > Date.now()) {
64
66
  return cached.result;
65
67
  }
66
68
  cache.delete(url);
67
69
  const pending = requests.get(url);
68
- if (pending)
70
+ if (pending && options?.fresh && freshRequests.has(pending)) {
69
71
  return pending;
72
+ }
73
+ if (pending && !options?.fresh) {
74
+ return pending;
75
+ }
76
+ if (options?.fresh) {
77
+ if (pending)
78
+ supersededRequests.add(pending);
79
+ requestGenerations.set(url, (requestGenerations.get(url) ?? 0) + 1);
80
+ }
70
81
  const sessionRead = path === SESSION_STATUS_PATH;
71
82
  const currentGeneration = () => sessionRead ? sessionGeneration : statusGeneration;
72
83
  const requestGeneration = currentGeneration();
@@ -104,6 +115,9 @@ async function fetchClientStatus(path) {
104
115
  .catch(() => ({ state: "unavailable" }));
105
116
  const request = Promise.race([transport, timeout])
106
117
  .then((result) => {
118
+ if (supersededRequests.has(request)) {
119
+ return fetchClientStatus(path);
120
+ }
107
121
  if (currentGeneration() === requestGeneration &&
108
122
  (requestGenerations.get(url) ?? 0) === requestUrlGeneration &&
109
123
  result.state === "available") {
@@ -127,6 +141,8 @@ async function fetchClientStatus(path) {
127
141
  }
128
142
  });
129
143
  requests.set(url, request);
144
+ if (options?.fresh)
145
+ freshRequests.add(request);
130
146
  if (controller)
131
147
  requestControllers.set(url, controller);
132
148
  return request;
@@ -162,8 +178,8 @@ export function invalidateClientStatusRequests() {
162
178
  requestControllers.clear();
163
179
  requests.clear();
164
180
  }
165
- export function fetchAgentEngineStatus() {
166
- return fetchClientStatus("/_agent-native/agent-engine/status");
181
+ export function fetchAgentEngineStatus(options) {
182
+ return fetchClientStatus("/_agent-native/agent-engine/status", options);
167
183
  }
168
184
  export function fetchEnvironmentStatus() {
169
185
  return fetchClientStatus("/_agent-native/env-status");
@@ -4,4 +4,10 @@ export declare function isEmbedMcpChatBridgeActive(): boolean;
4
4
  export declare function getEmbedAuthToken(): string | null;
5
5
  export declare function isEmbedAuthActive(): boolean;
6
6
  export declare function _resetEmbedAuthForTests(): void;
7
+ /**
8
+ * Forgets every replayed refusal. Call it when access is known to have
9
+ * changed, such as a link's status turning `allowed`: a refusal recorded
10
+ * before then would otherwise fail the next read for up to a minute.
11
+ */
12
+ export declare function forgetAuthFailures(): void;
7
13
  export declare function ensureEmbedAuthFetchInterceptor(): void;
@@ -349,6 +349,11 @@ function isAuthFailureStatus(status) {
349
349
  return status === 401 || status === 403;
350
350
  }
351
351
  function shouldGuardAuthFailure(method, url) {
352
+ // Only an embed replays refusals, so a missing or expired embed token can't
353
+ // set off a retry storm. Elsewhere a refusal can lift mid-session, as when
354
+ // someone shares the page, and a replayed one would hide that for a minute.
355
+ if (!isEmbedAuthActive())
356
+ return false;
352
357
  if (!GUARDED_METHODS.has(method))
353
358
  return false;
354
359
  if (url.pathname === EMBED_START_PATH)
@@ -415,6 +420,14 @@ async function recordAuthFailure(key, response) {
415
420
  function clearAuthFailure(key) {
416
421
  authFailureCache.delete(key);
417
422
  }
423
+ /**
424
+ * Forgets every replayed refusal. Call it when access is known to have
425
+ * changed, such as a link's status turning `allowed`: a refusal recorded
426
+ * before then would otherwise fail the next read for up to a minute.
427
+ */
428
+ export function forgetAuthFailures() {
429
+ authFailureCache.clear();
430
+ }
418
431
  function withEmbedAuthHeaders(input, init, token, win) {
419
432
  const method = requestMethod(input, init);
420
433
  const url = inputUrl(input, win);
@@ -1114,8 +1114,9 @@ async function awaitReplayUpload(request, timeout) {
1114
1114
  async function sendReplayUpload(options, body, callbacks = {}) {
1115
1115
  if (isCrossOriginReplayEndpoint(options.endpoint)) {
1116
1116
  const canUseKeepalive = canUseReplayKeepalive(body);
1117
- if (canUseKeepalive)
1118
- callbacks.beforeKeepaliveUpload?.();
1117
+ if (callbacks.beforeUpload?.(canUseKeepalive) === "defer") {
1118
+ return "deferred";
1119
+ }
1119
1120
  const timeout = startReplayUploadTimeout();
1120
1121
  await awaitReplayUploadRequest(timeout, () => fetch(options.endpoint, {
1121
1122
  method: "POST",
@@ -1124,12 +1125,12 @@ async function sendReplayUpload(options, body, callbacks = {}) {
1124
1125
  headers: { "Content-Type": "text/plain;charset=UTF-8" },
1125
1126
  signal: timeout.signal,
1126
1127
  }));
1127
- return;
1128
+ return "sent";
1128
1129
  }
1129
1130
  const upload = await buildReplayUploadBody(body);
1130
1131
  const canUseKeepalive = canUseReplayKeepalive(upload.body);
1131
- if (canUseKeepalive)
1132
- callbacks.beforeKeepaliveUpload?.();
1132
+ if (callbacks.beforeUpload?.(canUseKeepalive) === "defer")
1133
+ return "deferred";
1133
1134
  const timeout = startReplayUploadTimeout();
1134
1135
  await awaitReplayUploadRequest(timeout, () => fetch(options.endpoint, {
1135
1136
  method: "POST",
@@ -1141,6 +1142,7 @@ async function sendReplayUpload(options, body, callbacks = {}) {
1141
1142
  },
1142
1143
  signal: timeout.signal,
1143
1144
  }));
1145
+ return "sent";
1144
1146
  }
1145
1147
  function isFinalFlushReason(reason) {
1146
1148
  return [
@@ -1157,6 +1159,16 @@ function isFinalFlushReason(reason) {
1157
1159
  function isTerminalReplayFlushReason(reason) {
1158
1160
  return reason === "pagehide" || reason === "beforeunload";
1159
1161
  }
1162
+ /**
1163
+ * A navigation fires `visibilitychange` before or after `pagehide`, depending
1164
+ * on the browser, and a mobile page may get only `visibilitychange`. A page in
1165
+ * the back/forward cache can be evicted without running again.
1166
+ */
1167
+ function isPageLeaveReplayFlushReason(reason) {
1168
+ return (isTerminalReplayFlushReason(reason) ||
1169
+ reason === "pagehide-persisted" ||
1170
+ reason === "visibility-hidden");
1171
+ }
1160
1172
  function flushReasonPriority(reason) {
1161
1173
  if (reason === "pagehide" ||
1162
1174
  reason === "pagehide-persisted" ||
@@ -1176,12 +1188,6 @@ function mergePendingFlushReason(current, requested) {
1176
1188
  ? requested
1177
1189
  : current;
1178
1190
  }
1179
- function shouldReserveSequenceBeforeKeepalive(reason) {
1180
- return (reason === "pagehide" ||
1181
- reason === "pagehide-persisted" ||
1182
- reason === "beforeunload" ||
1183
- reason === "visibility-hidden");
1184
- }
1185
1191
  function hasFullSnapshot(events) {
1186
1192
  return events.some((event) => event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE);
1187
1193
  }
@@ -1449,19 +1455,39 @@ export async function flushSessionReplay(reason = "manual") {
1449
1455
  let pausedForQuota = false;
1450
1456
  let quotaRetryAfterSeconds = null;
1451
1457
  try {
1452
- await sendReplayUpload(state.options, payload.body, {
1453
- beforeKeepaliveUpload: shouldReserveSequenceBeforeKeepalive(reason)
1454
- ? () => {
1455
- advanceReplaySequence(state, payload);
1456
- reservedSequence = true;
1458
+ // An upload can be stored after a navigation has destroyed this page and
1459
+ // its response handler: a keepalive one because the browser finishes
1460
+ // sending it, and a larger one when the server already has its body.
1461
+ // Persist the next index first, or the next page resends this chunk number
1462
+ // with different content and the server rejects it (HTTP 409). A flush as
1463
+ // the page hides or leaves never starts an upload too large for keepalive:
1464
+ // a navigation usually cancels it, so a reserved number would leave a gap,
1465
+ // and an unreserved one that still arrives would be reused. Its events stay
1466
+ // queued for the next flush of a page that survives. A same-origin body is
1467
+ // compressed before this check, so a hide or leave event can arrive in
1468
+ // between; it is queued as the pending reason.
1469
+ const outcome = await sendReplayUpload(state.options, payload.body, {
1470
+ beforeUpload: (keepalive) => {
1471
+ const pendingReason = state.pendingFlushReason;
1472
+ if (!keepalive &&
1473
+ (isPageLeaveReplayFlushReason(reason) ||
1474
+ (pendingReason !== null &&
1475
+ isPageLeaveReplayFlushReason(pendingReason)))) {
1476
+ return "defer";
1457
1477
  }
1458
- : undefined,
1478
+ advanceReplaySequence(state, payload);
1479
+ reservedSequence = true;
1480
+ return "send";
1481
+ },
1459
1482
  });
1460
- if (!reservedSequence)
1461
- advanceReplaySequence(state, payload);
1462
- state.automaticConflictRestartAttempted = false;
1463
- state.transientClientErrorFailures = 0;
1464
- uploaded = true;
1483
+ if (outcome === "deferred") {
1484
+ restoreReplayEvents(state, events);
1485
+ }
1486
+ else {
1487
+ state.automaticConflictRestartAttempted = false;
1488
+ state.transientClientErrorFailures = 0;
1489
+ uploaded = true;
1490
+ }
1465
1491
  }
1466
1492
  catch (error) {
1467
1493
  if (error instanceof ReplayUploadInFlightTimeoutError) {
@@ -1,4 +1,6 @@
1
1
  export { SHARE_LINK_REF_PARAM, SHARE_LINK_VIA_PARAM, withShareLinkAttribution, } from "./share-link-attribution.js";
2
+ export { useAccessRequestDecisions, useAccessRequestReview, useResourceAccessRequests, type AccessRequestDecisionFailure, type AccessRequestDecisions, type AccessRequestReview, type AccessRequestReviewController, type AccessRequestRole, type ResourceAccessRequestsController, } from "./useAccessRequestReview.js";
3
+ export { useResourceAccessGate, type ResourceAccessGateController, type ResourceAccessGateOptions, type ResourceAccessGateRequest, type ResourceAccessRequestFailure, type ResourceAccessGateRole, type ResourceAccessGateState, type ResourceAccessGateStatus, } from "./useResourceAccessGate.js";
2
4
  export { useShareButtonController, type ShareButtonController, type ShareButtonControllerOptions, type ShareButtonOrgMember, type ShareButtonOrgMemberSearch, type ShareButtonRole, type ShareButtonShare, type ShareButtonSharesResponse, type ShareButtonVisibility, } from "./useShareButtonController.js";
3
5
  export { useShareDialogController, type ResourceShare, type ResourceSharesResponse, type ShareDialogController, type ShareDialogControllerOptions, type ShareDialogPerson, type ShareDialogTab, type ShareOption, type ShareRole, type ShareVisibility, } from "./useShareDialogController.js";
4
6
  export { createShareQueryKey, createShareQueryParams, extractShareErrorMessage, fetchOrgMemberPage, mergeOrgMembers, normalizeOrgMembers, optimisticallyUpdateShareCache, rollbackShareCache, useShareMutationGuard, useShareMutations, useShareOrgMemberSearch, useShareQuery, type OrgMemberPage, type ShareOrgMember, type ShareOrgMemberSearchResult, type ShareQueryKey, type ShareQueryParams, } from "./share-controller-helpers.js";
@@ -1,4 +1,6 @@
1
1
  export { SHARE_LINK_REF_PARAM, SHARE_LINK_VIA_PARAM, withShareLinkAttribution, } from "./share-link-attribution.js";
2
+ export { useAccessRequestDecisions, useAccessRequestReview, useResourceAccessRequests, } from "./useAccessRequestReview.js";
3
+ export { useResourceAccessGate, } from "./useResourceAccessGate.js";
2
4
  export { useShareButtonController, } from "./useShareButtonController.js";
3
5
  export { useShareDialogController, } from "./useShareDialogController.js";
4
6
  export { createShareQueryKey, createShareQueryParams, extractShareErrorMessage, fetchOrgMemberPage, mergeOrgMembers, normalizeOrgMembers, optimisticallyUpdateShareCache, rollbackShareCache, useShareMutationGuard, useShareMutations, useShareOrgMemberSearch, useShareQuery, } from "./share-controller-helpers.js";