@agent-native/core 0.214.0 → 0.216.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 (161) 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/application-state/store.d.ts +5 -0
  27. package/dist/application-state/store.js +26 -0
  28. package/dist/chat-threads/schema-migrations.js +17 -0
  29. package/dist/chat-threads/store.d.ts +21 -1
  30. package/dist/chat-threads/store.js +223 -41
  31. package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
  32. package/dist/cli/skills-content/turn-into-app-skill.js +20 -18
  33. package/dist/cli/templates-meta.js +14 -0
  34. package/dist/client/analytics.js +167 -17
  35. package/dist/client/chat/agentkit-agent-native.js +79 -3
  36. package/dist/client/chat/agentkit-protocol.js +80 -42
  37. package/dist/client/chat/runtime.d.ts +5 -1
  38. package/dist/client/chat/runtime.js +46 -3
  39. package/dist/client/consent.d.ts +36 -0
  40. package/dist/client/consent.js +283 -0
  41. package/dist/client/review/use-review.d.ts +2 -0
  42. package/dist/client/review/use-review.js +28 -1
  43. package/dist/client/sharing/useResourceAccessGate.d.ts +3 -2
  44. package/dist/client/sharing/useResourceAccessGate.js +9 -1
  45. package/dist/client/use-chat-thread-list.d.ts +2 -0
  46. package/dist/client/use-chat-thread-list.js +12 -2
  47. package/dist/client/use-chat-threads.d.ts +1 -0
  48. package/dist/client/use-chat-threads.js +2 -0
  49. package/dist/collab/awareness.d.ts +2 -2
  50. package/dist/collab/routes.d.ts +2 -2
  51. package/dist/collab/struct-routes.d.ts +1 -1
  52. package/dist/connections/catalog.d.ts +5 -5
  53. package/dist/connections/catalog.js +22 -3
  54. package/dist/data-widgets/index.d.ts +36 -2
  55. package/dist/data-widgets/index.js +94 -6
  56. package/dist/db/migrations.js +4 -1
  57. package/dist/db/schema-snapshot.generated.js +8 -1
  58. package/dist/feature-flags/actions/set-feature-flag.js +5 -0
  59. package/dist/feature-flags/index.d.ts +1 -1
  60. package/dist/feature-flags/index.js +1 -1
  61. package/dist/feature-flags/store.d.ts +12 -0
  62. package/dist/feature-flags/store.js +79 -7
  63. package/dist/file-upload/actions/manage-file-storage.js +5 -0
  64. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  65. package/dist/file-upload/actions/upload-image.js +5 -0
  66. package/dist/history/actions/create-resource-version.js +5 -0
  67. package/dist/history/actions/restore-resource-version.js +5 -0
  68. package/dist/ingestion/index.d.ts +1 -1
  69. package/dist/ingestion/index.js +1 -1
  70. package/dist/ingestion/pptx.d.ts +11 -0
  71. package/dist/ingestion/pptx.js +81 -1
  72. package/dist/integrations/actions/manage-messaging-channel.js +5 -0
  73. package/dist/jobs/scheduler.js +17 -1
  74. package/dist/labs/actions/set-lab.js +5 -0
  75. package/dist/localization/actions/set-localization-preference.js +5 -0
  76. package/dist/observability/actions/save-observability-instruction-update.js +5 -0
  77. package/dist/observability/actions/save-observability-review-summary.js +5 -0
  78. package/dist/progress/routes.d.ts +1 -1
  79. package/dist/provider-api/actions/custom-provider-registration.d.ts +18 -18
  80. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  81. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  82. package/dist/provider-api/index.js +9 -2
  83. package/dist/review/actions/consume-review-feedback.js +5 -0
  84. package/dist/review/actions/create-review-comment.js +5 -0
  85. package/dist/review/actions/delete-review-comment.js +5 -0
  86. package/dist/review/actions/react-to-review-comment.js +5 -0
  87. package/dist/review/actions/reply-review-comment.js +5 -0
  88. package/dist/review/actions/resolve-review-thread.js +5 -0
  89. package/dist/review/actions/send-review-thread-to-agent.js +5 -0
  90. package/dist/review/actions/set-review-status.js +5 -0
  91. package/dist/review/actions/set-review-thread-muted.js +5 -0
  92. package/dist/review/actions/set-review-thread-unread.js +5 -0
  93. package/dist/review/actions/set-review-threads-unread.js +5 -0
  94. package/dist/review/actions/update-review-comment-anchor.js +5 -0
  95. package/dist/review/actions/update-review-comment.js +5 -0
  96. package/dist/review/suggestions/actions.js +60 -21
  97. package/dist/review/suggestions/store.d.ts +2 -0
  98. package/dist/review/suggestions/store.js +28 -0
  99. package/dist/secrets/actions/delete-api-key.js +5 -0
  100. package/dist/secrets/routes.d.ts +6 -6
  101. package/dist/server/agent-chat/context-tools.js +63 -2
  102. package/dist/server/agent-chat-plugin.d.ts +2 -0
  103. package/dist/server/agent-chat-plugin.js +242 -54
  104. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  105. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  106. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  107. package/dist/server/application-state-maintenance.d.ts +2 -0
  108. package/dist/server/application-state-maintenance.js +2 -0
  109. package/dist/server/attribution.d.ts +14 -0
  110. package/dist/server/attribution.js +62 -2
  111. package/dist/server/auth.js +7 -0
  112. package/dist/server/consent-route.d.ts +41 -0
  113. package/dist/server/consent-route.js +102 -0
  114. package/dist/server/first-touch-route.d.ts +39 -0
  115. package/dist/server/first-touch-route.js +115 -0
  116. package/dist/server/index.d.ts +1 -0
  117. package/dist/server/index.js +1 -0
  118. package/dist/server/queued-submission-inbox-drain.d.ts +9 -0
  119. package/dist/server/queued-submission-inbox-drain.js +12 -0
  120. package/dist/server/request-origin.d.ts +6 -0
  121. package/dist/server/request-origin.js +12 -0
  122. package/dist/settings/org-settings.d.ts +2 -1
  123. package/dist/settings/org-settings.js +4 -1
  124. package/dist/settings/store.d.ts +8 -0
  125. package/dist/settings/store.js +14 -5
  126. package/dist/shared/consent.d.ts +31 -0
  127. package/dist/shared/consent.js +145 -0
  128. package/dist/shared/first-touch-persistence.d.ts +31 -0
  129. package/dist/shared/first-touch-persistence.js +53 -0
  130. package/dist/sharing/actions/set-resource-visibility.js +5 -0
  131. package/dist/sharing/actions/share-resource.js +5 -0
  132. package/dist/sharing/actions/unshare-resource.js +5 -0
  133. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
  134. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
  135. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
  136. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
  137. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
  138. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
  139. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
  140. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
  141. package/dist/triggers/actions/manage-automation.d.ts +1 -1
  142. package/dist/usage/actions/get-usage-metrics.js +27 -4
  143. package/dist/usage/actions/manage-usage-alert.js +5 -0
  144. package/dist/vite/client.js +8 -6
  145. package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +5 -0
  146. package/dist/workspace-connections/actions/delete-workspace-user-group.js +5 -0
  147. package/dist/workspace-connections/actions/upsert-workspace-user-group.js +5 -0
  148. package/docs/content/actions-access-control.mdx +8 -8
  149. package/docs/content/actions-defining.mdx +9 -8
  150. package/docs/content/template-design-developers.mdx +9 -9
  151. package/docs/content/template-design-features.mdx +1 -3
  152. package/docs/content/turn-into-app.mdx +4 -0
  153. package/package.json +4 -3
  154. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
  155. package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
  156. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
  157. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
  158. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
  159. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
  160. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
  161. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
@@ -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, whenConsentAllows } 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";
@@ -174,6 +178,11 @@ const LAST_TOUCH_COOKIE = {
174
178
  priority: LAST_TOUCH_COOKIE_FIELD_PRIORITY,
175
179
  };
176
180
  let _firstTouchCaptured = false;
181
+ // Bounds the resend after a response race; see `syncFirstTouchToServer`.
182
+ let _firstTouchSyncsLeft = 2;
183
+ // Set when this page wrote `an_ft` or `an_lt`, which the server then writes
184
+ // again: Safari keeps a cookie page JavaScript wrote for seven days at most.
185
+ let _attributionCookieWritten = false;
177
186
  function safeStorageGet(key) {
178
187
  try {
179
188
  return window.localStorage.getItem(key);
@@ -190,6 +199,14 @@ function safeStorageSet(key, value) {
190
199
  // private browsing / storage disabled — best-effort
191
200
  }
192
201
  }
202
+ function safeStorageRemove(key) {
203
+ try {
204
+ window.localStorage.removeItem(key);
205
+ }
206
+ catch {
207
+ // coercion-ok: storage that can't be read holds nothing to remove.
208
+ }
209
+ }
193
210
  function readCachedLlmConnectionStatus() {
194
211
  if (typeof window === "undefined")
195
212
  return null;
@@ -536,6 +553,7 @@ function writeAttributionCookie(value, spec) {
536
553
  const encodedValue = fitAttributionCookieValue(value, spec);
537
554
  try {
538
555
  document.cookie = attributionCookieAssignment(spec.name, encodedValue);
556
+ _attributionCookieWritten = true;
539
557
  }
540
558
  catch {
541
559
  // best-effort
@@ -564,16 +582,132 @@ function backfillAttributionCookie(storageKey, spec) {
564
582
  // signup handoff goes without it.
565
583
  }
566
584
  }
585
+ /** Remove every stored touch, in storage and cookies. */
586
+ function clearStoredAttribution() {
587
+ safeStorageRemove(FIRST_TOUCH_STORAGE_KEY);
588
+ safeStorageRemove(LAST_TOUCH_STORAGE_KEY);
589
+ if (typeof document === "undefined")
590
+ return;
591
+ for (const name of [
592
+ FIRST_TOUCH_COOKIE_NAME,
593
+ LAST_TOUCH_COOKIE_NAME,
594
+ FIRST_TOUCH_SERVER_COOKIE_NAME,
595
+ ]) {
596
+ try {
597
+ document.cookie = `${name}=; path=/; max-age=0; SameSite=Lax`;
598
+ }
599
+ catch {
600
+ // coercion-ok: a sandboxed iframe can't read or write cookies, so
601
+ // there is none here to clear.
602
+ }
603
+ }
604
+ }
605
+ /** The year-long copy our server keeps in `an_fts`. */
606
+ function readServerFirstTouch() {
607
+ const touch = readAttributionCookieValue(FIRST_TOUCH_SERVER_COOKIE_NAME);
608
+ if (!touch || touch.v !== FIRST_TOUCH_SERVER_COOKIE_VERSION)
609
+ return null;
610
+ const { v: _version, ...firstTouch } = touch;
611
+ return firstTouch;
612
+ }
567
613
  /**
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.
614
+ * The browser's own copies: localStorage, then the 30-day `an_ft` cookie. A
615
+ * visit more than a year old no longer counts, or the cookie it's copied back
616
+ * into would keep it going.
617
+ */
618
+ function readLocalFirstTouch() {
619
+ const touch = readStoredAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE_NAME);
620
+ return touch && firstTouchServerExpiry(touch) ? touch : null;
621
+ }
622
+ /**
623
+ * The server writes `an_lt` again from each request's cookies, so a slow
624
+ * response from an older page can put back an older last touch. The newest
625
+ * one is in localStorage. Returns whether it had to write it back.
626
+ */
627
+ function restoreNewestLastTouchCookie() {
628
+ const stored = safeStorageGet(LAST_TOUCH_STORAGE_KEY);
629
+ const newest = parseAttribution(stored);
630
+ if (!stored || !newest)
631
+ return false;
632
+ const cookie = readAttributionCookieValue(LAST_TOUCH_COOKIE_NAME);
633
+ if (Date.parse(cookie?.touched_at ?? "") >= Date.parse(newest.touched_at ?? "")) {
634
+ return false;
635
+ }
636
+ writeAttributionCookie(stored, LAST_TOUCH_COOKIE);
637
+ return true;
638
+ }
639
+ /**
640
+ * Ask our server to keep `firstTouch` in its year-long cookie, unless it
641
+ * already holds that visit and this page wrote no attribution cookie; the
642
+ * same request has the server write `an_ft` and `an_lt` again. Two tabs can each send a touch before either
643
+ * response lands, and the later response wins the cookie, so when ours
644
+ * leaves a worse touch there than this browser holds, send the better one
645
+ * again. Until the server holds it, signup still reads `an_ft`.
646
+ */
647
+ function syncFirstTouchToServer(firstTouch) {
648
+ if (!_attributionCookieWritten &&
649
+ isSameFirstTouch(readServerFirstTouch(), firstTouch)) {
650
+ return;
651
+ }
652
+ // An embed that must not call our API (the Builder shell canvas) keeps only
653
+ // the browser's copies.
654
+ if (agentNativeApiDisabledReason())
655
+ return;
656
+ if (_firstTouchSyncsLeft <= 0 || typeof fetch !== "function")
657
+ return;
658
+ _firstTouchSyncsLeft -= 1;
659
+ _attributionCookieWritten = false;
660
+ void fetch(agentNativePath(FIRST_TOUCH_SERVER_PATH), {
661
+ method: "POST",
662
+ credentials: "same-origin",
663
+ keepalive: true,
664
+ headers: { "Content-Type": "application/json" },
665
+ body: JSON.stringify({ firstTouch }),
666
+ })
667
+ .then(async (response) => {
668
+ if (!response.ok)
669
+ return;
670
+ const result = (await response.json());
671
+ // The visitor may have rejected while this request was out.
672
+ if (result.reason === "consent_required" ||
673
+ !consentAllowsNow("targeting")) {
674
+ clearStoredAttribution();
675
+ return;
676
+ }
677
+ // Written back by page JavaScript, Safari would keep it a week.
678
+ const repaired = restoreNewestLastTouchCookie();
679
+ const server = readServerFirstTouch();
680
+ const local = readLocalFirstTouch();
681
+ if (!server)
682
+ return;
683
+ if (isSameFirstTouch(server, local)) {
684
+ if (repaired && local)
685
+ syncFirstTouchToServer(local);
686
+ return;
687
+ }
688
+ if (preferFirstTouch(server, local) === server) {
689
+ storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, server);
690
+ }
691
+ else if (local) {
692
+ syncFirstTouchToServer(local);
693
+ }
694
+ })
695
+ .catch(() => {
696
+ // coercion-ok: the 30-day `an_ft` cookie still carries first touch to
697
+ // signup; the next page load tries again.
698
+ });
699
+ }
700
+ /**
701
+ * Capture the visitor's referral attribution once per page load, into
702
+ * `localStorage`, a first-party cookie the signup boundary reads, and the
703
+ * server's year-long copy. Fully defensive and SSR-safe — any failure is
704
+ * swallowed so it can never break app boot.
572
705
  *
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.
706
+ * First touch (`an_attribution` / `an_ft` / `an_fts`) is the first visit that
707
+ * said where this person came from, else the first visit; see
708
+ * `preferFirstTouch`. Without that, an untagged first visit would hide every
709
+ * tagged visit after it. When the browser's copies are gone, as Safari clears
710
+ * them, the server's copy restores them.
577
711
  */
578
712
  function captureFirstTouchAttribution(options = {}) {
579
713
  if (_firstTouchCaptured)
@@ -582,16 +716,32 @@ function captureFirstTouchAttribution(options = {}) {
582
716
  if (typeof window === "undefined")
583
717
  return;
584
718
  try {
719
+ // Read the visit now: the URL can change before the visitor consents.
585
720
  const current = buildFirstTouchAttribution(options.landingPath ?? window.location.pathname);
586
- const existing = getFirstTouchAttribution();
587
- if (existing &&
588
- (hasAttributionSource(existing) || !hasAttributionSource(current))) {
721
+ const forwarded = readForwardedLastTouch();
722
+ const forwardedAt = readForwardedLastTouchAt();
723
+ // Where the banner applies, nothing is stored until the visitor accepts
724
+ // targeting cookies, and a later "reject" clears it again.
725
+ whenConsentAllows("targeting", () => storeTouches(current, forwarded, forwardedAt), { optimistic: true, revoke: clearStoredAttribution });
726
+ }
727
+ catch {
728
+ // coercion-ok: attribution is best-effort telemetry; never let it break
729
+ // boot.
730
+ }
731
+ }
732
+ function storeTouches(current, forwarded, forwardedAt) {
733
+ try {
734
+ const local = readLocalFirstTouch();
735
+ // The same visit in both keeps the local copy, which was never trimmed.
736
+ const firstTouch = preferFirstTouch(preferFirstTouch(local, readServerFirstTouch()), current);
737
+ if (firstTouch === local) {
589
738
  backfillAttributionCookie(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE);
590
739
  }
591
740
  else {
592
- storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, current);
741
+ storeAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE, firstTouch);
593
742
  }
594
- captureLastTouchAttribution(current);
743
+ captureLastTouchAttribution(current, forwarded, forwardedAt);
744
+ syncFirstTouchToServer(firstTouch);
595
745
  }
596
746
  catch {
597
747
  // Attribution is best-effort telemetry; never let it break boot.
@@ -604,10 +754,8 @@ function captureFirstTouchAttribution(options = {}) {
604
754
  * visit keeps the time it happened, and loses to a newer visit the app has
605
755
  * already recorded.
606
756
  */
607
- function captureLastTouchAttribution(current) {
608
- const forwarded = readForwardedLastTouch();
757
+ function captureLastTouchAttribution(current, forwarded, forwardedAt) {
609
758
  const source = forwarded ?? (hasAttributionSource(current) ? current : null);
610
- const forwardedAt = readForwardedLastTouchAt();
611
759
  const existing = getLastTouchAttribution();
612
760
  const existingAt = Date.parse(existing?.touched_at ?? "");
613
761
  if (!source ||
@@ -702,7 +850,9 @@ function readStoredAttribution(storageKey, cookieName) {
702
850
  return hasAttributionSource(cookie) ? cookie : (stored ?? cookie);
703
851
  }
704
852
  export function getFirstTouchAttribution() {
705
- return readStoredAttribution(FIRST_TOUCH_STORAGE_KEY, FIRST_TOUCH_COOKIE_NAME);
853
+ if (typeof window === "undefined")
854
+ return null;
855
+ return preferFirstTouch(readLocalFirstTouch(), readServerFirstTouch());
706
856
  }
707
857
  export function getLastTouchAttribution() {
708
858
  return readStoredAttribution(LAST_TOUCH_STORAGE_KEY, LAST_TOUCH_COOKIE_NAME);
@@ -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}`);
@@ -81,53 +81,56 @@ function runtimeAttachmentFromFilePart(part) {
81
81
  function runtimeRequestAttachments(args) {
82
82
  const attachments = args.message?.parts.flatMap((part) => part.type === "file" ? [runtimeAttachmentFromFilePart(part)] : []) ?? [];
83
83
  for (const requestAttachment of args.requestAttachments ?? []) {
84
- const originalUrl = requestAttachment.referenceUrl;
85
- const originalIndex = originalUrl
86
- ? attachments.findIndex((attachment) => attachment.url === originalUrl)
84
+ const referenceUrl = requestAttachment.referenceUrl;
85
+ const originalUrl = referenceUrl;
86
+ const originalLookupUrl = originalUrl ?? requestAttachment.url;
87
+ const originalIndex = originalLookupUrl
88
+ ? attachments.findIndex((attachment) => attachment.url === originalLookupUrl)
87
89
  : -1;
90
+ const addReferenceOnly = (replaceOriginal) => {
91
+ const original = originalIndex >= 0 ? attachments[originalIndex] : null;
92
+ const reference = {
93
+ type: "file",
94
+ name: original?.name || requestAttachment.name,
95
+ contentType: original?.contentType || requestAttachment.contentType,
96
+ ...(original?.id ? { id: original.id } : {}),
97
+ ...(original?.mediaType ? { mediaType: original.mediaType } : {}),
98
+ url: originalLookupUrl,
99
+ referenceOnly: true,
100
+ };
101
+ if (replaceOriginal && originalIndex >= 0) {
102
+ attachments.splice(originalIndex, 1, reference);
103
+ }
104
+ else {
105
+ attachments.push(reference);
106
+ }
107
+ };
88
108
  if (typeof requestAttachment.data === "string") {
89
- const imageUrl = requestAttachment.url ?? originalUrl;
90
- const keepsOriginalReference = originalUrl !== undefined &&
91
- imageUrl !== undefined &&
92
- originalUrl !== imageUrl;
109
+ // The image's identity is exactly what the client fingerprinted: its own
110
+ // url, never one derived from referenceUrl. An original file part is kept
111
+ // as a reference (even when the image shares its url), so the submitted
112
+ // file fingerprint still matches the mapped attachments.
113
+ const imageUrl = requestAttachment.url;
114
+ const keepsOriginalReference = originalIndex >= 0 ||
115
+ (originalUrl !== undefined &&
116
+ imageUrl !== undefined &&
117
+ originalUrl !== imageUrl);
93
118
  if (keepsOriginalReference) {
94
- const original = originalIndex >= 0 ? attachments[originalIndex] : null;
95
- const reference = {
96
- type: "file",
97
- name: original?.name || requestAttachment.name,
98
- contentType: original?.contentType || requestAttachment.contentType,
99
- url: originalUrl,
100
- referenceOnly: true,
101
- };
102
- if (originalIndex >= 0)
103
- attachments.splice(originalIndex, 1, reference);
104
- else
105
- attachments.push(reference);
119
+ addReferenceOnly(originalIndex >= 0);
106
120
  }
107
- const image = {
121
+ attachments.push({
108
122
  type: "image",
109
123
  name: requestAttachment.name,
110
124
  contentType: requestAttachment.contentType,
111
125
  data: requestAttachment.data,
112
126
  ...(imageUrl ? { url: imageUrl } : {}),
113
- };
114
- if (originalIndex >= 0 && !keepsOriginalReference) {
115
- attachments.splice(originalIndex, 1, image);
116
- }
117
- else {
118
- attachments.push(image);
119
- }
127
+ ...(referenceUrl ? { referenceUrl } : {}),
128
+ });
120
129
  continue;
121
130
  }
122
- if (originalIndex >= 0 && originalUrl) {
123
- const original = attachments[originalIndex];
124
- attachments.splice(originalIndex, 1, {
125
- type: "file",
126
- name: original.name || requestAttachment.name,
127
- contentType: original.contentType,
128
- url: originalUrl,
129
- referenceOnly: true,
130
- });
131
+ if (originalUrl &&
132
+ (originalIndex < 0 || requestAttachment.url !== originalUrl)) {
133
+ addReferenceOnly(originalIndex >= 0);
131
134
  }
132
135
  if (typeof requestAttachment.url === "string") {
133
136
  attachments.push({
@@ -135,6 +138,7 @@ function runtimeRequestAttachments(args) {
135
138
  name: requestAttachment.name,
136
139
  contentType: requestAttachment.contentType,
137
140
  url: requestAttachment.url,
141
+ ...(referenceUrl ? { referenceUrl } : {}),
138
142
  });
139
143
  }
140
144
  }
@@ -189,6 +193,17 @@ function mergeProtocolMetadata(...sources) {
189
193
  }
190
194
  return merged;
191
195
  }
196
+ function withQueuedMessageIdMetadata(metadata, messageId) {
197
+ if (messageId === undefined)
198
+ return metadata;
199
+ return {
200
+ ...metadata,
201
+ custom: {
202
+ ...asRecord(metadata?.custom),
203
+ agentNativeQueuedMessageId: messageId,
204
+ },
205
+ };
206
+ }
192
207
  const PROTECTED_PROTOCOL_METADATA_KEYS = [
193
208
  "actor",
194
209
  "workspace",
@@ -1504,7 +1519,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1504
1519
  */
1505
1520
  async function openStartedRun(threadId, session, turn, turnMetadata, observability, start = {}) {
1506
1521
  const runId = turn.runId ?? turn.id ?? createId("run");
1507
- const runMetadata = mergeTrustedProtocolMetadata(options.metadata, turnMetadata, turn.metadata, {
1522
+ const runMetadata = withQueuedMessageIdMetadata(mergeTrustedProtocolMetadata(options.metadata, turnMetadata, turn.metadata, {
1508
1523
  [AGENT_NATIVE_PROTOCOL_METADATA_KEY]: {
1509
1524
  observability: {
1510
1525
  protocolRunId: runId,
@@ -1516,7 +1531,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
1516
1531
  ...observability,
1517
1532
  },
1518
1533
  },
1519
- });
1534
+ }), start.queuedMessageId);
1520
1535
  setRuntimeRunIdMetadata(runMetadata, turn.runId);
1521
1536
  setRuntimeTurnIdMetadata(runMetadata, turn.id);
1522
1537
  const run = {
@@ -3127,7 +3142,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3127
3142
  const latestUserMetadata = asRecord(latestUserMessage?.metadata);
3128
3143
  const recoveryMetadata = asRecord(latestUserMetadata?.custom);
3129
3144
  const isRecoveryRetry = recoveryMetadata?.agentNativeRecoveryAction === "retry";
3130
- const turnMetadata = mergeTrustedProtocolMetadata(options.metadata, input.metadata, input.options?.metadata, input.options?.agentId ? { agentId: input.options.agentId } : undefined, input.options?.locale ? { locale: input.options.locale } : undefined, input.options?.mode ? { mode: input.options.mode } : undefined, isRecoveryRetry ? { agentNativeInternalContinuation: true } : undefined);
3145
+ const turnMetadata = withQueuedMessageIdMetadata(mergeTrustedProtocolMetadata(options.metadata, input.metadata, input.options?.metadata, input.options?.agentId
3146
+ ? { agentId: input.options.agentId }
3147
+ : undefined, input.options?.locale ? { locale: input.options.locale } : undefined, input.options?.mode ? { mode: input.options.mode } : undefined, isRecoveryRetry
3148
+ ? { agentNativeInternalContinuation: true }
3149
+ : undefined), input.queuePromotion?.messageId);
3131
3150
  const queuePromotionKey = input.queuePromotion
3132
3151
  ? JSON.stringify([input.threadId, input.queuePromotion.turnId])
3133
3152
  : undefined;
@@ -3153,9 +3172,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3153
3172
  const turn = await session.startTurn({
3154
3173
  prompt: latestUserPrompt(input.messages),
3155
3174
  messages,
3175
+ ...(input.options ? { options: input.options } : {}),
3156
3176
  ...(input.submissionPayload
3157
3177
  ? { submissionPayload: input.submissionPayload }
3158
3178
  : {}),
3179
+ ...(input.references?.length ? { references: input.references } : {}),
3159
3180
  ...(input.queuePromotion
3160
3181
  ? { queuePromotion: input.queuePromotion }
3161
3182
  : {}),
@@ -3174,15 +3195,29 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3174
3195
  metadata: turnMetadata,
3175
3196
  abortSignal: readers.signal,
3176
3197
  });
3198
+ if (turn.queuedSubmission)
3199
+ return turn.queuedSubmission;
3177
3200
  return openStartedRun(input.threadId, session, turn, turnMetadata, undefined, {
3178
3201
  attachReplayedRun: true,
3179
3202
  messageId: latestUserMessage?.id,
3203
+ ...(input.queuePromotion
3204
+ ? { queuedMessageId: input.queuePromotion.messageId }
3205
+ : {}),
3180
3206
  });
3181
3207
  };
3182
3208
  if (!queuePromotionKey) {
3183
- return { runId: await createRun(), capabilities };
3184
- }
3185
- const pendingRun = Promise.resolve().then(createRun);
3209
+ const started = await createRun();
3210
+ return typeof started === "string"
3211
+ ? { runId: started, capabilities }
3212
+ : { queuedSubmission: started, capabilities };
3213
+ }
3214
+ const pendingRun = Promise.resolve().then(async () => {
3215
+ const started = await createRun();
3216
+ if (typeof started !== "string") {
3217
+ throw new TypeError("A queue promotion cannot be acknowledged as a new queued submission.");
3218
+ }
3219
+ return started;
3220
+ });
3186
3221
  pendingQueuePromotionRuns.set(queuePromotionKey, pendingRun);
3187
3222
  try {
3188
3223
  return { runId: await pendingRun, capabilities };
@@ -3806,6 +3841,9 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3806
3841
  transport.cancelUpload = hostOperations.cancelUpload;
3807
3842
  if (hostOperations?.listQueuedMessages)
3808
3843
  transport.listQueuedMessages = hostOperations.listQueuedMessages;
3844
+ if (hostOperations?.getQueuedSubmissionStatus)
3845
+ transport.getQueuedSubmissionStatus =
3846
+ hostOperations.getQueuedSubmissionStatus;
3809
3847
  if (hostOperations?.queueMessage) {
3810
3848
  transport.queueMessage = (input, context) => {
3811
3849
  const metadata = mergeTrustedProtocolMetadata(options.metadata, input.metadata);
@@ -1,4 +1,4 @@
1
- import type { AgentSuggestion } from "@agent-native/agentkit/protocol";
1
+ import { type AgentRunOptions, type AgentSubmissionReference, type AgentQueuedSubmissionReceipt, type AgentSuggestion } from "@agent-native/agentkit/protocol";
2
2
  import type { ActionChatUIConfig } from "../../action-ui.js";
3
3
  import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
4
4
  import type { ReasoningEffort } from "../../shared/reasoning-effort.js";
@@ -89,6 +89,7 @@ export interface AgentChatRuntimeAttachment {
89
89
  readonly contentType?: string;
90
90
  readonly data?: string;
91
91
  readonly url?: string;
92
+ readonly referenceUrl?: string;
92
93
  readonly text?: string;
93
94
  readonly metadata?: AgentChatRuntimeMetadata;
94
95
  }
@@ -218,6 +219,8 @@ export interface AgentChatRuntimeTurnInput {
218
219
  readonly prompt?: string;
219
220
  readonly messages?: readonly AgentChatRuntimeMessage[];
220
221
  readonly submissionPayload?: unknown;
222
+ readonly options?: AgentRunOptions;
223
+ readonly references?: readonly AgentSubmissionReference[];
221
224
  readonly queuePromotion?: {
222
225
  readonly messageId: string;
223
226
  readonly claimId: string;
@@ -549,6 +552,7 @@ export interface AgentChatRuntimeTurn<TEvent extends AgentChatRuntimeEventBase =
549
552
  readonly sessionId: AgentChatRuntimeSessionId;
550
553
  readonly runId?: string;
551
554
  readonly metadata?: AgentChatRuntimeMetadata;
555
+ readonly queuedSubmission?: AgentQueuedSubmissionReceipt;
552
556
  readonly events: AsyncIterable<TEvent>;
553
557
  /** Close the local event stream without cancelling the underlying run. */
554
558
  closeEvents?(): Promise<void> | void;