@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
@@ -0,0 +1,413 @@
1
+ import { CONSENT_COOKIE_LIFETIME_SECONDS, CONSENT_COOKIE_NAME, CONSENT_PATH, CONSENT_REGION_PATH, consentCookieDomain, isConsentRegionTimeZone, parseConsent, serializeConsent, } from "../shared/consent.js";
2
+ import { agentNativePath } from "./api-path.js";
3
+ import { agentNativeApiDisabledReason } from "./api-surface.js";
4
+ /** Strictly necessary for the banner: which region this session is in. */
5
+ const REGION_STORAGE_KEY = "an_consent_region";
6
+ /**
7
+ * Scripts that run before this module loads, like the Google tags the server
8
+ * injects into the page, queue `[category, run]` here.
9
+ */
10
+ const SCRIPT_QUEUE_KEY = "__AN_CONSENT_QUEUE__";
11
+ /** The Google data layers the server's Consent Mode default script set up. */
12
+ const GOOGLE_CONSENT_LAYERS_KEY = "__AN_CONSENT_LAYERS__";
13
+ // One state per page, however many copies of this module a bundle carries.
14
+ const STATE_KEY = Symbol.for("agent-native.client.consent");
15
+ function state() {
16
+ const holder = globalThis;
17
+ return (holder[STATE_KEY] ??= {
18
+ region: null,
19
+ regionPromise: null,
20
+ waiters: [],
21
+ listeners: new Set(),
22
+ settingsOpen: false,
23
+ pageChoice: null,
24
+ });
25
+ }
26
+ function readCookie(name) {
27
+ if (typeof document === "undefined")
28
+ return null;
29
+ try {
30
+ for (const part of document.cookie ? document.cookie.split(";") : []) {
31
+ const eq = part.indexOf("=");
32
+ if (eq !== -1 && part.slice(0, eq).trim() === name) {
33
+ return part.slice(eq + 1).trim();
34
+ }
35
+ }
36
+ }
37
+ catch {
38
+ // coercion-ok: a sandboxed iframe can't read cookies, so it holds no
39
+ // choice and the visitor is asked.
40
+ }
41
+ return null;
42
+ }
43
+ /** The visitor's stored choice, or `null` before they've made one. */
44
+ export function getConsentChoice() {
45
+ return (parseConsent(readCookie(CONSENT_COOKIE_NAME)) ?? state().pageChoice ?? null);
46
+ }
47
+ function cookieDomain() {
48
+ const host = typeof window === "undefined" ? "" : window.location.hostname;
49
+ const domain = consentCookieDomain(host);
50
+ return domain ? `; domain=${domain}` : "";
51
+ }
52
+ function writeChoiceCookie(choice) {
53
+ try {
54
+ document.cookie =
55
+ `${CONSENT_COOKIE_NAME}=${serializeConsent(choice)}; path=/; ` +
56
+ `max-age=${CONSENT_COOKIE_LIFETIME_SECONDS}; SameSite=Lax${cookieDomain()}`;
57
+ }
58
+ catch {
59
+ // coercion-ok: a sandboxed iframe can't store the choice; it still
60
+ // applies to this page.
61
+ }
62
+ }
63
+ let serverChoiceWrites = Promise.resolve();
64
+ let latestChoice = null;
65
+ /**
66
+ * Safari keeps a cookie page JavaScript wrote for seven days at most, so the
67
+ * server writes the choice again for its full year. Writes go one at a time
68
+ * and only the newest is sent. A response for an older choice still sets that
69
+ * choice's cookie, so the newest is written back over it at once.
70
+ */
71
+ function storeChoiceOnServer(choice) {
72
+ latestChoice = choice;
73
+ if (agentNativeApiDisabledReason() || typeof fetch !== "function")
74
+ return;
75
+ serverChoiceWrites = serverChoiceWrites.then(async () => {
76
+ if (latestChoice !== choice)
77
+ return;
78
+ await fetch(agentNativePath(CONSENT_PATH), {
79
+ method: "POST",
80
+ credentials: "same-origin",
81
+ keepalive: true,
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ choice: serializeConsent(choice) }),
84
+ }).catch(() => {
85
+ // coercion-ok: the browser's own copy still holds the choice, for
86
+ // seven days in Safari; the visitor is asked again after that.
87
+ });
88
+ if (latestChoice && latestChoice !== choice) {
89
+ writeChoiceCookie(latestChoice);
90
+ }
91
+ });
92
+ }
93
+ function readCachedRegion() {
94
+ try {
95
+ const value = window.sessionStorage.getItem(REGION_STORAGE_KEY);
96
+ return value === "consent" || value === "none" ? value : null;
97
+ }
98
+ catch {
99
+ // coercion-ok: without session storage the region is asked again.
100
+ return null;
101
+ }
102
+ }
103
+ function cacheRegion(region) {
104
+ try {
105
+ window.sessionStorage.setItem(REGION_STORAGE_KEY, region);
106
+ }
107
+ catch {
108
+ // coercion-ok: without session storage the region is asked again.
109
+ }
110
+ }
111
+ function timeZoneRegion() {
112
+ try {
113
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
114
+ return isConsentRegionTimeZone(timeZone) ? "consent" : "none";
115
+ }
116
+ catch {
117
+ // An unreadable zone could be anywhere, so it gets the banner.
118
+ return "consent";
119
+ }
120
+ }
121
+ async function fetchRegion() {
122
+ if (agentNativeApiDisabledReason() || typeof fetch !== "function") {
123
+ return timeZoneRegion();
124
+ }
125
+ try {
126
+ const response = await fetch(agentNativePath(CONSENT_REGION_PATH), {
127
+ credentials: "same-origin",
128
+ });
129
+ const body = response.ok
130
+ ? (await response.json())
131
+ : {};
132
+ if (body.region === "consent" || body.region === "none") {
133
+ return body.region;
134
+ }
135
+ }
136
+ catch {
137
+ // coercion-ok: with no answer from the server, the time zone decides.
138
+ }
139
+ return timeZoneRegion();
140
+ }
141
+ /**
142
+ * Whether this visitor gets the banner: the server's answer from the
143
+ * request's country, else the browser's time zone. Asked once a session.
144
+ */
145
+ export function resolveConsentRegion() {
146
+ const current = state();
147
+ if (current.region)
148
+ return Promise.resolve(current.region);
149
+ const cached = typeof window === "undefined" ? null : readCachedRegion();
150
+ if (cached) {
151
+ current.region = cached;
152
+ // Another app or tab may have stored a reject since this page's waiters
153
+ // last looked; let them clear what they kept.
154
+ settle();
155
+ notify();
156
+ return Promise.resolve(cached);
157
+ }
158
+ return (current.regionPromise ??= fetchRegion().then((region) => {
159
+ current.region = region;
160
+ cacheRegion(region);
161
+ settle();
162
+ notify();
163
+ return region;
164
+ }));
165
+ }
166
+ /** The best answer available now, without waiting for the server. */
167
+ function regionGuess() {
168
+ const current = state();
169
+ return current.region ?? readCachedRegion() ?? timeZoneRegion();
170
+ }
171
+ function allows(category, region) {
172
+ const choice = getConsentChoice();
173
+ if (choice)
174
+ return choice[category];
175
+ return region === "none";
176
+ }
177
+ /**
178
+ * Whether `category` is allowed right now, on the time zone's guess until the
179
+ * server answers. Pair it with `whenConsentAllows` and a `revoke` that undoes
180
+ * whatever this let through.
181
+ */
182
+ export function consentAllowsNow(category) {
183
+ if (typeof window === "undefined")
184
+ return false;
185
+ if (!state().region)
186
+ void resolveConsentRegion();
187
+ return allows(category, regionGuess());
188
+ }
189
+ /**
190
+ * Whether `category` is allowed, from the visitor's choice or the server's
191
+ * region; `null` until one of them is known, where `consentAllowsNow` would
192
+ * guess from the time zone. For what `revoke` can't undo once it's sent.
193
+ */
194
+ export function consentDecision(category) {
195
+ if (typeof window === "undefined")
196
+ return false;
197
+ const choice = getConsentChoice();
198
+ if (choice)
199
+ return choice[category];
200
+ const region = state().region ?? readCachedRegion();
201
+ if (region)
202
+ return region === "none";
203
+ void resolveConsentRegion();
204
+ return null;
205
+ }
206
+ /**
207
+ * Google's tags can't be unloaded, but they follow Consent Mode: a reject
208
+ * stops what's already on the page from storing or sending identifiers.
209
+ */
210
+ function updateGoogleConsent(region) {
211
+ const holder = window;
212
+ const layers = holder[GOOGLE_CONSENT_LAYERS_KEY];
213
+ if (!Array.isArray(layers))
214
+ return;
215
+ const grant = (category) => allows(category, region) ? "granted" : "denied";
216
+ const update = {
217
+ analytics_storage: grant("performance"),
218
+ ad_storage: grant("targeting"),
219
+ ad_user_data: grant("targeting"),
220
+ ad_personalization: grant("targeting"),
221
+ };
222
+ for (const name of layers) {
223
+ if (typeof name !== "string")
224
+ continue;
225
+ const layer = (holder[name] ??= []);
226
+ // gtag reads an `arguments` object from the data layer, never an array.
227
+ (function gtag(..._args) {
228
+ layer.push(arguments);
229
+ })("consent", "update", update);
230
+ }
231
+ }
232
+ function settle() {
233
+ const region = state().region;
234
+ if (region || getConsentChoice())
235
+ updateGoogleConsent(region);
236
+ for (const waiter of state().waiters) {
237
+ const allowed = allows(waiter.category, region);
238
+ if (allowed && !waiter.ran) {
239
+ waiter.ran = true;
240
+ isolate(waiter.run);
241
+ }
242
+ else if (!allowed && (region || getConsentChoice())) {
243
+ // Also clears what an earlier page stored before the banner existed.
244
+ waiter.ran = false;
245
+ if (waiter.revoke)
246
+ isolate(waiter.revoke);
247
+ }
248
+ }
249
+ // A choice stored here or in another tab decides before the region does.
250
+ if (region || getConsentChoice())
251
+ releaseHeldForDecision?.(true);
252
+ }
253
+ /**
254
+ * One callback throwing must not skip the waiters after it, or a later revoke
255
+ * would never clear what a rejected category stored. Rethrow it on its own task
256
+ * so it still surfaces.
257
+ */
258
+ function isolate(callback) {
259
+ try {
260
+ callback();
261
+ }
262
+ catch (error) {
263
+ setTimeout(() => {
264
+ throw error;
265
+ });
266
+ }
267
+ }
268
+ function notify() {
269
+ for (const listener of state().listeners)
270
+ listener();
271
+ }
272
+ /**
273
+ * Run `run` once `category` is allowed: at once outside the consent region,
274
+ * and inside it when the visitor accepts. A later "reject" calls `revoke`.
275
+ */
276
+ export function whenConsentAllows(category, run, options = {}) {
277
+ if (typeof window === "undefined")
278
+ return;
279
+ const waiter = { category, run, revoke: options.revoke, ran: false };
280
+ state().waiters.push(waiter);
281
+ const region = state().region ??
282
+ readCachedRegion() ??
283
+ (options.optimistic ? regionGuess() : null);
284
+ if (allows(category, region)) {
285
+ waiter.ran = true;
286
+ isolate(run);
287
+ }
288
+ else if (state().region || getConsentChoice()) {
289
+ // A reject decided before this caller asked still clears what it stored.
290
+ if (options.revoke)
291
+ isolate(options.revoke);
292
+ }
293
+ if (!state().region)
294
+ void resolveConsentRegion();
295
+ }
296
+ let heldForDecision = null;
297
+ let releaseHeldForDecision = null;
298
+ /**
299
+ * Run `send` once the visitor's choice or the server's region is known, so
300
+ * the time zone's guess never sends what consent covers. `decided` is false
301
+ * when the page closes first; send then without what consent covers.
302
+ */
303
+ export function whenConsentDecided(send) {
304
+ if (consentDecision("performance") !== null) {
305
+ send(true);
306
+ return;
307
+ }
308
+ if (!heldForDecision) {
309
+ const held = [];
310
+ heldForDecision = held;
311
+ const release = (decided) => {
312
+ if (heldForDecision !== held)
313
+ return;
314
+ heldForDecision = null;
315
+ releaseHeldForDecision = null;
316
+ window.removeEventListener("pagehide", onPageHide);
317
+ for (const run of held)
318
+ run(decided);
319
+ };
320
+ const onPageHide = () => release(false);
321
+ releaseHeldForDecision = release;
322
+ window.addEventListener("pagehide", onPageHide);
323
+ void resolveConsentRegion().then(() => release(true));
324
+ }
325
+ heldForDecision.push(send);
326
+ }
327
+ const CONSENT_CHANNEL_NAME = "agent-native-consent";
328
+ let seenChoice = null;
329
+ let consentChannel = null;
330
+ /**
331
+ * Apply a choice another tab or app stored: at once in other tabs of this
332
+ * app, and on the next focus for another app on the site.
333
+ */
334
+ function recheckStoredChoice() {
335
+ const raw = readCookie(CONSENT_COOKIE_NAME);
336
+ if (raw === seenChoice)
337
+ return;
338
+ seenChoice = raw;
339
+ state().settingsOpen = false;
340
+ settle();
341
+ notify();
342
+ }
343
+ function watchOtherTabs() {
344
+ if (typeof window === "undefined" || typeof document === "undefined")
345
+ return;
346
+ seenChoice = readCookie(CONSENT_COOKIE_NAME);
347
+ window.addEventListener?.("focus", recheckStoredChoice);
348
+ document.addEventListener?.("visibilitychange", recheckStoredChoice);
349
+ if (typeof BroadcastChannel !== "function")
350
+ return;
351
+ try {
352
+ consentChannel = new BroadcastChannel(CONSENT_CHANNEL_NAME);
353
+ consentChannel.onmessage = recheckStoredChoice;
354
+ }
355
+ catch {
356
+ // coercion-ok: without the channel, other tabs recheck on focus.
357
+ }
358
+ }
359
+ /** Store the visitor's choice for every app on this site, and apply it. */
360
+ export function setConsentChoice(choice) {
361
+ if (typeof document === "undefined")
362
+ return;
363
+ writeChoiceCookie(choice);
364
+ storeChoiceOnServer(choice);
365
+ seenChoice = readCookie(CONSENT_COOKIE_NAME);
366
+ // Only a browser that won't keep the cookie falls back to this page's
367
+ // copy; a cookie the visitor clears later means they're asked again.
368
+ state().pageChoice = seenChoice === null ? choice : null;
369
+ try {
370
+ consentChannel?.postMessage("changed");
371
+ }
372
+ catch {
373
+ // coercion-ok: other tabs still recheck on focus.
374
+ }
375
+ state().settingsOpen = false;
376
+ if (!state().region)
377
+ state().region = "consent";
378
+ settle();
379
+ notify();
380
+ }
381
+ /** Reopen the banner so the visitor can change their choice. */
382
+ export function openConsentSettings() {
383
+ state().settingsOpen = true;
384
+ notify();
385
+ }
386
+ /** Whether the banner should show now, for the banner component. */
387
+ export function shouldShowConsentBanner() {
388
+ const current = state();
389
+ if (current.settingsOpen)
390
+ return true;
391
+ return current.region === "consent" && !getConsentChoice();
392
+ }
393
+ export function subscribeConsent(listener) {
394
+ state().listeners.add(listener);
395
+ return () => state().listeners.delete(listener);
396
+ }
397
+ /** Run the scripts the page queued before this module loaded. */
398
+ function drainScriptQueue() {
399
+ if (typeof window === "undefined")
400
+ return;
401
+ const holder = window;
402
+ const queued = holder[SCRIPT_QUEUE_KEY];
403
+ const push = (...entries) => {
404
+ for (const [category, run] of entries)
405
+ whenConsentAllows(category, run);
406
+ return 0;
407
+ };
408
+ holder[SCRIPT_QUEUE_KEY] = { push };
409
+ if (Array.isArray(queued))
410
+ push(...queued);
411
+ }
412
+ drainScriptQueue();
413
+ watchOtherTabs();
@@ -1,4 +1,5 @@
1
1
  export { useOnboarding, type UseOnboardingResult } from "./use-onboarding.js";
2
+ export { useOnboardingChecklist, type UseOnboardingChecklistResult, } from "./use-onboarding-checklist.js";
2
3
  export { isFirstRunOnboardingEnabled } from "./first-run-enabled.js";
3
4
  export { dispatchFirstRunOnboardingStatus, fetchFirstRunOnboardingStatus, FIRST_RUN_ONBOARDING_STATUS_RESOLVED_EVENT, } from "./first-run-status.js";
4
5
  export { useOnboardingPreviewMode, useOnboardingPreviewStep, ONBOARDING_PREVIEW_STORAGE_KEY, ONBOARDING_PREVIEW_QUERY_PARAM, ONBOARDING_PREVIEW_QUERY_VALUE, ONBOARDING_PREVIEW_STEP_QUERY_PARAM, ONBOARDING_PREVIEW_STEPS, getOnboardingPreviewStep, isOnboardingPreviewQuery, } from "./use-preview-mode.js";
@@ -1,4 +1,5 @@
1
1
  export { useOnboarding } from "./use-onboarding.js";
2
+ export { useOnboardingChecklist, } from "./use-onboarding-checklist.js";
2
3
  export { isFirstRunOnboardingEnabled } from "./first-run-enabled.js";
3
4
  export { dispatchFirstRunOnboardingStatus, fetchFirstRunOnboardingStatus, FIRST_RUN_ONBOARDING_STATUS_RESOLVED_EVENT, } from "./first-run-status.js";
4
5
  export { useOnboardingPreviewMode, useOnboardingPreviewStep, ONBOARDING_PREVIEW_STORAGE_KEY, ONBOARDING_PREVIEW_QUERY_PARAM, ONBOARDING_PREVIEW_QUERY_VALUE, ONBOARDING_PREVIEW_STEP_QUERY_PARAM, ONBOARDING_PREVIEW_STEPS, getOnboardingPreviewStep, isOnboardingPreviewQuery, } from "./use-preview-mode.js";
@@ -0,0 +1,23 @@
1
+ import type { OnboardingStepStatus } from "../../onboarding/types.js";
2
+ export interface UseOnboardingChecklistResult {
3
+ steps: OnboardingStepStatus[];
4
+ completeCount: number;
5
+ totalCount: number;
6
+ dismissed: boolean;
7
+ collapsed: boolean;
8
+ /** `loading` only before the first answer; a refresh keeps the last one. */
9
+ status: "loading" | "ready" | "error";
10
+ error: Error | null;
11
+ refresh: () => Promise<unknown>;
12
+ dismiss: () => Promise<void>;
13
+ reopen: () => Promise<void>;
14
+ setCollapsed: (collapsed: boolean) => Promise<void>;
15
+ }
16
+ /**
17
+ * Reads one named onboarding checklist (steps registered with
18
+ * `registerOnboardingStep({ checklist })`). Rows are re-read whenever a local
19
+ * action or an agent tool finishes, so a step checks itself off after the work
20
+ * that completes it. Dismissal is stored by the onboarding plugin and the
21
+ * collapsed state in application state, so both follow the user across devices.
22
+ */
23
+ export declare function useOnboardingChecklist(checklist: string): UseOnboardingChecklistResult;
@@ -0,0 +1,115 @@
1
+ import { useMutation, useQuery, useQueryClient, } from "@tanstack/react-query";
2
+ import { useCallback } from "react";
3
+ import { agentNativePath } from "../api-path.js";
4
+ import { readClientAppState, writeClientAppState, } from "../application-state.js";
5
+ import { useChangeVersions } from "../use-change-version.js";
6
+ const CHECKLIST_TIMEOUT_MS = 10_000;
7
+ function summaryKey(checklist) {
8
+ return ["onboarding-checklist", checklist];
9
+ }
10
+ function collapsedKey(checklist) {
11
+ return ["onboarding-checklist-collapsed", checklist];
12
+ }
13
+ function collapsedStateKey(checklist) {
14
+ return `onboarding:collapsed:${checklist}`;
15
+ }
16
+ function checklistUrl(path, checklist) {
17
+ return agentNativePath(`/_agent-native/onboarding/${path}?checklist=${encodeURIComponent(checklist)}`);
18
+ }
19
+ async function fetchChecklist(checklist, signal) {
20
+ const controller = new AbortController();
21
+ const abort = () => controller.abort();
22
+ signal.addEventListener("abort", abort);
23
+ const timer = setTimeout(abort, CHECKLIST_TIMEOUT_MS);
24
+ try {
25
+ const response = await fetch(checklistUrl("summary", checklist), {
26
+ signal: controller.signal,
27
+ });
28
+ if (!response.ok)
29
+ throw new Error(`checklist: ${response.status}`);
30
+ const body = (await response.json());
31
+ return { steps: body.steps, dismissed: !!body.dismissed };
32
+ }
33
+ finally {
34
+ clearTimeout(timer);
35
+ signal.removeEventListener("abort", abort);
36
+ }
37
+ }
38
+ async function postChecklist(path, checklist) {
39
+ const response = await fetch(checklistUrl(path, checklist), {
40
+ method: "POST",
41
+ });
42
+ if (!response.ok)
43
+ throw new Error(`checklist ${path}: ${response.status}`);
44
+ }
45
+ function patchSummary(queryClient, checklist, patch) {
46
+ const snapshot = queryClient.getQueriesData({
47
+ queryKey: summaryKey(checklist),
48
+ });
49
+ queryClient.setQueriesData({ queryKey: summaryKey(checklist) }, (current) => (current ? { ...current, ...patch } : current));
50
+ return snapshot;
51
+ }
52
+ /**
53
+ * Reads one named onboarding checklist (steps registered with
54
+ * `registerOnboardingStep({ checklist })`). Rows are re-read whenever a local
55
+ * action or an agent tool finishes, so a step checks itself off after the work
56
+ * that completes it. Dismissal is stored by the onboarding plugin and the
57
+ * collapsed state in application state, so both follow the user across devices.
58
+ */
59
+ export function useOnboardingChecklist(checklist) {
60
+ const queryClient = useQueryClient();
61
+ const changeVersion = useChangeVersions(["action"]);
62
+ const summary = useQuery({
63
+ queryKey: [...summaryKey(checklist), changeVersion],
64
+ queryFn: ({ signal }) => fetchChecklist(checklist, signal),
65
+ placeholderData: (previous) => previous,
66
+ });
67
+ const collapsedQuery = useQuery({
68
+ queryKey: collapsedKey(checklist),
69
+ queryFn: async () => {
70
+ const value = await readClientAppState(collapsedStateKey(checklist));
71
+ return value?.collapsed === true;
72
+ },
73
+ staleTime: Infinity,
74
+ });
75
+ const dismissMutation = useMutation({
76
+ mutationFn: (dismissed) => postChecklist(dismissed ? "dismiss" : "reopen", checklist),
77
+ onMutate: (dismissed) => ({
78
+ previous: patchSummary(queryClient, checklist, { dismissed }),
79
+ }),
80
+ onError: (_error, _dismissed, context) => {
81
+ for (const [key, data] of context?.previous ?? []) {
82
+ queryClient.setQueryData(key, data);
83
+ }
84
+ },
85
+ onSettled: () => queryClient.invalidateQueries({ queryKey: summaryKey(checklist) }),
86
+ });
87
+ const collapseMutation = useMutation({
88
+ mutationFn: (collapsed) => writeClientAppState(collapsedStateKey(checklist), { collapsed }),
89
+ onMutate: (collapsed) => {
90
+ const previous = queryClient.getQueryData(collapsedKey(checklist));
91
+ queryClient.setQueryData(collapsedKey(checklist), collapsed);
92
+ return { previous };
93
+ },
94
+ onError: (_error, _collapsed, context) => {
95
+ queryClient.setQueryData(collapsedKey(checklist), context?.previous);
96
+ },
97
+ });
98
+ const dismiss = useCallback(() => dismissMutation.mutateAsync(true), [dismissMutation]);
99
+ const reopen = useCallback(() => dismissMutation.mutateAsync(false), [dismissMutation]);
100
+ const setCollapsed = useCallback((collapsed) => collapseMutation.mutateAsync(collapsed).then(() => undefined), [collapseMutation]);
101
+ const steps = summary.data?.steps ?? [];
102
+ return {
103
+ steps,
104
+ completeCount: steps.filter((step) => step.complete).length,
105
+ totalCount: steps.length,
106
+ dismissed: summary.data?.dismissed ?? false,
107
+ collapsed: collapsedQuery.data ?? false,
108
+ status: summary.data ? "ready" : summary.isError ? "error" : "loading",
109
+ error: summary.error,
110
+ refresh: () => queryClient.invalidateQueries({ queryKey: summaryKey(checklist) }),
111
+ dismiss,
112
+ reopen,
113
+ setCollapsed,
114
+ };
115
+ }
@@ -269,6 +269,8 @@ export declare function useResourceSuggestions(params: ListResourceSuggestionsPa
269
269
  export declare function useCreateResourceSuggestion(): import("@tanstack/react-query").UseMutationResult<ResourceSuggestion, Error, CreateResourceSuggestionInput, unknown>;
270
270
  export declare function useCreateResourceSuggestionProposal(): import("@tanstack/react-query").UseMutationResult<ResourceSuggestionProposalResult, Error, CreateResourceSuggestionProposalInput, unknown>;
271
271
  export declare function useDecideResourceSuggestionProposal(): import("@tanstack/react-query").UseMutationResult<ResourceSuggestionProposalResult, Error, DecideResourceSuggestionProposalInput, unknown>;
272
+ /** Shows every listed proposal member as decided until the server answers. */
273
+ export declare function proposalDecisionOverlay(input: DecideResourceSuggestionProposalInput, queryClient: QueryClient): ReviewOptimisticMutation;
272
274
  export declare function useDecideResourceSuggestion(): import("@tanstack/react-query").UseMutationResult<{
273
275
  suggestion: ResourceSuggestion;
274
276
  decision: unknown;
@@ -547,7 +547,34 @@ export function useCreateResourceSuggestionProposal() {
547
547
  return useActionMutation("create-resource-suggestion-proposal");
548
548
  }
549
549
  export function useDecideResourceSuggestionProposal() {
550
- return useActionMutation("decide-resource-suggestion-proposal");
550
+ return useOptimisticReviewMutation("decide-resource-suggestion-proposal", proposalDecisionOverlay);
551
+ }
552
+ /** Shows every listed proposal member as decided until the server answers. */
553
+ export function proposalDecisionOverlay(input, queryClient) {
554
+ const [first] = input.members;
555
+ const resource = first && resourceForSuggestion(queryClient, first.id);
556
+ if (!resource)
557
+ return noSuggestionOverlay();
558
+ const memberIds = new Set(input.members.map((member) => member.id));
559
+ return {
560
+ action: "list-resource-suggestions",
561
+ resource,
562
+ transform: (data, params) => updateSuggestions(data, (suggestions) => suggestions.flatMap((suggestion) => {
563
+ if (!memberIds.has(suggestion.id))
564
+ return [suggestion];
565
+ const next = { ...suggestion, status: input.decision };
566
+ return matchesSuggestionQuery(params, next) ? [next] : [];
567
+ })),
568
+ onSuccess: (result) => (data, params) => {
569
+ const decided = new Map(result.suggestions.map((suggestion) => [suggestion.id, suggestion]));
570
+ return updateSuggestions(data, (suggestions) => suggestions.flatMap((suggestion) => {
571
+ const next = decided.get(suggestion.id);
572
+ if (!next)
573
+ return [suggestion];
574
+ return matchesSuggestionQuery(params, next) ? [next] : [];
575
+ }));
576
+ },
577
+ };
551
578
  }
552
579
  export function useDecideResourceSuggestion() {
553
580
  return useOptimisticReviewMutation("decide-resource-suggestion", (input, queryClient) => {
@@ -64,7 +64,7 @@ export interface SessionReplayOptions {
64
64
  }
65
65
  export interface SessionReplayStartResult {
66
66
  started: boolean;
67
- reason?: "disabled" | "not-browser" | "missing-public-key" | "missing-session-id" | "missing-user-id" | "sampled-out" | "url-blocked" | "already-active" | "import-failed" | "record-failed";
67
+ reason?: "disabled" | "not-browser" | "missing-public-key" | "missing-session-id" | "missing-user-id" | "sampled-out" | "url-blocked" | "already-active" | "import-failed" | "record-failed" | "consent-required";
68
68
  replayId?: string;
69
69
  sessionId?: string;
70
70
  sampled?: boolean;