@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,115 @@
1
+ import { appendResponseHeader, defineEventHandler, getHeader, getMethod, setResponseHeader, setResponseStatus, } from "h3";
2
+ import { FIRST_TOUCH_SERVER_COOKIE_NAME, firstTouchServerExpiry, preferFirstTouch, } from "../shared/first-touch-persistence.js";
3
+ import { FIRST_TOUCH_COOKIE_NAME, LAST_TOUCH_COOKIE_NAME, decodeFirstTouchServerValue, decodeFirstTouchValue, decodeLastTouchValue, encodeFirstTouchServerCookieValue, parseCookieHeader, } from "./attribution.js";
4
+ import { requestAllowsConsentCategory } from "./consent-route.js";
5
+ import { readBodyWithSizeLimit } from "./h3-helpers.js";
6
+ import { getForwardedRequestOrigin, isSameOriginPost, } from "./request-origin.js";
7
+ const MAX_BODY_BYTES = 8 * 1024;
8
+ /** How long the browser's own `an_ft` and `an_lt` copies last. */
9
+ const BROWSER_COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
10
+ const BROWSER_COOKIES = [
11
+ { name: FIRST_TOUCH_COOKIE_NAME, decode: decodeFirstTouchValue },
12
+ { name: LAST_TOUCH_COOKIE_NAME, decode: decodeLastTouchValue },
13
+ ];
14
+ // What `encodeURIComponent` emits, so a value echoed into Set-Cookie can't
15
+ // carry an attribute or a header break.
16
+ const ENCODED_COOKIE_VALUE = /^[A-Za-z0-9\-_.!~*'()%]{1,1500}$/;
17
+ function cookieAttributes(event) {
18
+ const secure = getForwardedRequestOrigin(event).startsWith("https:");
19
+ return `Path=/; SameSite=Lax${secure ? "; Secure" : ""}`;
20
+ }
21
+ function clearAttributionCookies(event) {
22
+ for (const name of [
23
+ FIRST_TOUCH_SERVER_COOKIE_NAME,
24
+ FIRST_TOUCH_COOKIE_NAME,
25
+ LAST_TOUCH_COOKIE_NAME,
26
+ ]) {
27
+ appendResponseHeader(event, "set-cookie", `${name}=; Max-Age=0; ${cookieAttributes(event)}`);
28
+ }
29
+ }
30
+ /**
31
+ * Safari keeps a cookie page JavaScript wrote for seven days at most, so write
32
+ * the browser's own `an_ft` and `an_lt` again from here, for their full 30
33
+ * days. Only values that decode as touches are echoed back.
34
+ */
35
+ function resetBrowserCookies(event) {
36
+ const cookies = parseCookieHeader(getHeader(event, "cookie"));
37
+ for (const { name, decode } of BROWSER_COOKIES) {
38
+ const value = cookies[name];
39
+ if (!value || !ENCODED_COOKIE_VALUE.test(value) || !decode(value)) {
40
+ continue;
41
+ }
42
+ appendResponseHeader(event, "set-cookie", `${name}=${value}; Max-Age=${BROWSER_COOKIE_MAX_AGE_SECONDS}; ${cookieAttributes(event)}`);
43
+ }
44
+ }
45
+ function readCandidate(body) {
46
+ const firstTouch = body?.firstTouch;
47
+ if (!firstTouch || typeof firstTouch !== "object")
48
+ return null;
49
+ return decodeFirstTouchValue(encodeURIComponent(JSON.stringify(firstTouch)));
50
+ }
51
+ /** A touch more than a year old can't be kept, or win against one that can. */
52
+ function unexpired(touch) {
53
+ return touch && firstTouchServerExpiry(touch) ? touch : null;
54
+ }
55
+ /**
56
+ * `POST /_agent-native/auth/attribution` with `{ firstTouch }`: keep the
57
+ * browser's first touch in the year-long `an_fts` cookie. Anyone may call it,
58
+ * signed in or not, but only from this origin. The value already in the
59
+ * cookie wins unless `preferFirstTouch` picks the new one, so a slow request
60
+ * from another tab can't replace a better first touch it never saw. Where the
61
+ * banner applies and the visitor hasn't accepted targeting cookies, it stores
62
+ * nothing and clears every attribution cookie. Elsewhere it also writes the
63
+ * browser's `an_ft` and `an_lt` again, so Safari keeps them their 30 days.
64
+ */
65
+ export function createFirstTouchHandler() {
66
+ return defineEventHandler(async (event) => {
67
+ setResponseHeader(event, "cache-control", "no-store");
68
+ if (getMethod(event) !== "POST") {
69
+ setResponseStatus(event, 405);
70
+ return { error: "Method not allowed" };
71
+ }
72
+ if (!isSameOriginPost(event)) {
73
+ setResponseStatus(event, 403);
74
+ return { error: "Cross-origin request" };
75
+ }
76
+ let body;
77
+ try {
78
+ body = await readBodyWithSizeLimit(event, MAX_BODY_BYTES);
79
+ }
80
+ catch (error) {
81
+ if (error.statusCode === 413) {
82
+ return { error: "Body too large" };
83
+ }
84
+ setResponseStatus(event, 400);
85
+ return { error: "Invalid JSON" };
86
+ }
87
+ if (!requestAllowsConsentCategory(event, "targeting")) {
88
+ clearAttributionCookies(event);
89
+ return {
90
+ stored: false,
91
+ reason: "consent_required",
92
+ };
93
+ }
94
+ const candidate = readCandidate(body);
95
+ if (!candidate) {
96
+ setResponseStatus(event, 400);
97
+ return { error: "Missing firstTouch" };
98
+ }
99
+ resetBrowserCookies(event);
100
+ const existing = decodeFirstTouchServerValue(parseCookieHeader(getHeader(event, "cookie"))[FIRST_TOUCH_SERVER_COOKIE_NAME]);
101
+ const firstTouch = preferFirstTouch(unexpired(existing), unexpired(candidate));
102
+ const expires = firstTouch && firstTouchServerExpiry(firstTouch);
103
+ if (!firstTouch || !expires) {
104
+ return {
105
+ stored: false,
106
+ reason: "expired",
107
+ };
108
+ }
109
+ if (firstTouch !== existing) {
110
+ appendResponseHeader(event, "set-cookie", `${FIRST_TOUCH_SERVER_COOKIE_NAME}=${encodeFirstTouchServerCookieValue(firstTouch)}; ` +
111
+ `Expires=${expires.toUTCString()}; ${cookieAttributes(event)}`);
112
+ }
113
+ return { stored: true, firstTouch };
114
+ });
115
+ }
@@ -104,7 +104,7 @@ export { resolveDeployPlatform, type DeployPlatform, } from "./deploy-environmen
104
104
  export { isServerlessRuntime } from "../db/client.js";
105
105
  export { BUILDER_PUBLISH_MCP_RESOURCE, canAuthorizeBuilderApiRequest, hasBuilderApiCredentialCustody, resolveBuilderApiAuthorization, resolveBuilderLegacyRequestAuthorization, resolveBuilderRequestAuthorization, type BuilderLegacyCredentialKey, type BuilderRequestAuthorization, } from "./builder-api-auth.js";
106
106
  export { createFusionAgentFeed, fusionStepToAgentChatEvents, type FusionAgentFeedSummary, } from "./fusion-agent-feed.js";
107
- export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, type BuilderOAuthPermissionScope, } from "./builder-oauth.js";
107
+ export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_CONTENT_READ_SCOPE, BUILDER_CONTENT_WRITE_SCOPE, BUILDER_OAUTH_RESOURCE, BUILDER_OAUTH_SCOPE, type BuilderOAuthPermissionScope, } from "./builder-oauth.js";
108
108
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, type BuildBuilderDesignSystemIndexFilesOptions, type BuilderDesignSystemCodeFileInput, type BuilderDesignSystemDecodeJobStatus, type BuilderDesignSystemDocsOptions, type BuilderDesignSystemDocument, type BuilderDesignSystemDocumentCountResult, type BuilderDesignSystemHydratedReference, type BuilderDesignSystemIndexFile, type BuilderDesignSystemIndexFromSourcesOptions, type BuilderDesignSystemIndexOptions, type BuilderDesignSystemIndexResult, type BuilderDesignSystemRecord, type BuilderDesignSystemStatus, type BuilderDesignSystemTierLimit, type BuilderDesignSystemGitHubFile, type BuilderDesignSystemGitHubFileCollection, type BuilderDesignSystemGitHubSource, type BuilderDesignSystemUploadAttachment, type BuilderDesignSystemUploadSlot, type BuilderDesignSystemProxyFields, type BuilderDesignSystemProxyFieldsOptions, type BuilderDesignSystemProxyReference, type BuilderDesignSystemSourceKind, } from "./builder-design-systems.js";
109
109
  export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, type BuilderAgentAttachment, type BuilderAgentUploadAttachment, type BuilderAgentUrlAttachment, type BuilderProjectResult, type RunBuilderAgentResult, } from "./builder-browser.js";
110
110
  export { BuilderBranchError, getBuilderBranchStatus, sendBuilderBranchMessage, type BuilderBranchAgentState, type BuilderBranchErrorCode, type BuilderBranchOperation, type BuilderBranchPrStatus, type BuilderBranchRef, type BuilderBranchState, type BuilderBranchStatus, type GetBuilderBranchStatusOptions, type SendBuilderBranchMessageOptions, type SendBuilderBranchMessageResult, } from "./builder-branch.js";
@@ -127,3 +127,4 @@ export { AGENT_READABLE_RESOURCE_PAYLOAD_TYPE, AGENT_READABLE_RESOURCE_SCRIPT_TY
127
127
  export { registerObservabilityProvider, type ObservabilityMeterProvider, type ObservabilityProvider, type ObservabilityTracerProvider, } from "../observability/otel-provider.js";
128
128
  export type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
129
129
  export declare function defineNitroPlugin(def: NitroPluginDef): NitroPluginDef;
130
+ export { appStateListKeysByKeySuffixPrefix } from "./application-state-maintenance.js";
@@ -98,7 +98,7 @@ export { resolveDeployPlatform, } from "./deploy-environment.js";
98
98
  export { isServerlessRuntime } from "../db/client.js";
99
99
  export { BUILDER_PUBLISH_MCP_RESOURCE, canAuthorizeBuilderApiRequest, hasBuilderApiCredentialCustody, resolveBuilderApiAuthorization, resolveBuilderLegacyRequestAuthorization, resolveBuilderRequestAuthorization, } from "./builder-api-auth.js";
100
100
  export { createFusionAgentFeed, fusionStepToAgentChatEvents, } from "./fusion-agent-feed.js";
101
- export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, } from "./builder-oauth.js";
101
+ export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_CONTENT_READ_SCOPE, BUILDER_CONTENT_WRITE_SCOPE, BUILDER_OAUTH_RESOURCE, BUILDER_OAUTH_SCOPE, } from "./builder-oauth.js";
102
102
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, } from "./builder-design-systems.js";
103
103
  export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, } from "./builder-browser.js";
104
104
  export { BuilderBranchError, getBuilderBranchStatus, sendBuilderBranchMessage, } from "./builder-branch.js";
@@ -122,3 +122,4 @@ export { registerObservabilityProvider, } from "../observability/otel-provider.j
122
122
  export function defineNitroPlugin(def) {
123
123
  return def;
124
124
  }
125
+ export { appStateListKeysByKeySuffixPrefix } from "./application-state-maintenance.js";
@@ -0,0 +1,9 @@
1
+ export declare function scheduleQueuedSubmissionInboxDrain(input: {
2
+ threadId: string;
3
+ turnContinues: boolean;
4
+ waitUntil?: (promise: Promise<unknown>) => void;
5
+ drain: (input: {
6
+ threadId: string;
7
+ }) => Promise<unknown>;
8
+ onError: (error: unknown) => void;
9
+ }): void;
@@ -0,0 +1,12 @@
1
+ import { runWithoutExecutionFence } from "../agent/execution-fence.js";
2
+ export function scheduleQueuedSubmissionInboxDrain(input) {
3
+ if (input.turnContinues)
4
+ return;
5
+ const drain = Promise.resolve()
6
+ .then(() => runWithoutExecutionFence(() => input.drain({ threadId: input.threadId })))
7
+ .catch(input.onError);
8
+ if (input.waitUntil)
9
+ input.waitUntil(drain);
10
+ else
11
+ void drain;
12
+ }
@@ -4,4 +4,10 @@ export declare function getForwardedRequestOrigin(event: H3Event): string;
4
4
  export declare function getForwardedRequestURL(event: H3Event): URL;
5
5
  export declare function getForwardedRequestHostname(event: H3Event): string;
6
6
  export declare function getForwardedRequestHostnameFromHeaders(headers: Headers | IncomingHttpHeaders, remoteAddress?: string): string;
7
+ /**
8
+ * Stricter than `isSameOriginRequest`, for a POST that writes cookies: a
9
+ * request with neither header, a navigation, or a desktop shell's origin
10
+ * can't prove it came from this site's pages.
11
+ */
12
+ export declare function isSameOriginPost(event: H3Event): boolean;
7
13
  export declare function isSameOriginRequest(event: H3Event): boolean;
@@ -95,6 +95,18 @@ export function getForwardedRequestHostnameFromHeaders(headers, remoteAddress) {
95
95
  function isLoopbackHost(host) {
96
96
  return host.startsWith("localhost:") || host.startsWith("127.0.0.1:");
97
97
  }
98
+ /**
99
+ * Stricter than `isSameOriginRequest`, for a POST that writes cookies: a
100
+ * request with neither header, a navigation, or a desktop shell's origin
101
+ * can't prove it came from this site's pages.
102
+ */
103
+ export function isSameOriginPost(event) {
104
+ const fetchSite = getRequestHeader(event, "sec-fetch-site");
105
+ if (fetchSite)
106
+ return fetchSite === "same-origin";
107
+ const origin = getRequestHeader(event, "origin");
108
+ return !!origin && origin === getForwardedRequestOrigin(event);
109
+ }
98
110
  export function isSameOriginRequest(event) {
99
111
  const fetchSite = getRequestHeader(event, "sec-fetch-site");
100
112
  if (fetchSite)
@@ -1,5 +1,6 @@
1
- import { type StoreWriteOptions, type StoreReadOptions } from "./store.js";
1
+ import { type SettingEntry, type StoreWriteOptions, type StoreReadOptions } from "./store.js";
2
2
  export declare function getOrgSetting(orgId: string, key: string, options?: StoreReadOptions): Promise<Record<string, unknown> | null>;
3
+ export declare function getOrgSettingEntry(orgId: string, key: string, options?: StoreReadOptions): Promise<SettingEntry>;
3
4
  export declare function putOrgSetting(orgId: string, key: string, value: Record<string, unknown>, options?: StoreWriteOptions): Promise<void>;
4
5
  export declare function mutateOrgSetting(orgId: string, key: string, updater: (current: Record<string, unknown> | null) => Record<string, unknown> | Promise<Record<string, unknown>>, options?: StoreWriteOptions): Promise<Record<string, unknown>>;
5
6
  export declare function deleteOrgSetting(orgId: string, key: string, options?: StoreWriteOptions): Promise<boolean>;
@@ -1,4 +1,4 @@
1
- import { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingsByPrefix, listSettingsByPrefix, } from "./store.js";
1
+ import { getSetting, getSettingEntry, mutateSetting, putSetting, deleteSetting, deleteSettingsByPrefix, listSettingsByPrefix, } from "./store.js";
2
2
  function orgKey(orgId, key) {
3
3
  return `o:${orgId}:${key}`;
4
4
  }
@@ -6,6 +6,9 @@ const ORG_PREFIX_RE = /^o:([^:]+):(.+)$/;
6
6
  export async function getOrgSetting(orgId, key, options) {
7
7
  return getSetting(orgKey(orgId, key), options);
8
8
  }
9
+ export async function getOrgSettingEntry(orgId, key, options) {
10
+ return getSettingEntry(orgKey(orgId, key), options);
11
+ }
9
12
  export async function putOrgSetting(orgId, key, value, options) {
10
13
  return putSetting(orgKey(orgId, key), value, options);
11
14
  }
@@ -9,6 +9,14 @@ export interface StoreReadOptions {
9
9
  transaction?: DbExec;
10
10
  }
11
11
  export declare function getSetting(key: string, options?: StoreReadOptions): Promise<Record<string, unknown> | null>;
12
+ export type SettingEntry = {
13
+ found: false;
14
+ } | {
15
+ found: true;
16
+ value: unknown;
17
+ };
18
+ /** Like `getSetting`, but a stored JSON `null` is found, not missing. */
19
+ export declare function getSettingEntry(key: string, options?: StoreReadOptions): Promise<SettingEntry>;
12
20
  export declare function getSettings(keys: readonly string[], options?: StoreReadOptions): Promise<Map<string, Record<string, unknown> | null>>;
13
21
  export interface StoreWriteOptions {
14
22
  requestSource?: string;
@@ -63,12 +63,10 @@ export const settingsStore = defineStore({
63
63
  export function ensureTable() {
64
64
  return settingsStore.ready();
65
65
  }
66
- export async function getSetting(key, options) {
66
+ async function readSettingRaw(key, options) {
67
67
  const cache = options?.transaction ? null : requestSettingsCache();
68
- if (!options?.bypassCache && cache?.has(key)) {
69
- const cached = cache.get(key);
70
- return cached == null ? null : JSON.parse(cached);
71
- }
68
+ if (!options?.bypassCache && cache?.has(key))
69
+ return cache.get(key) ?? null;
72
70
  if (!options?.transaction)
73
71
  await ensureTable();
74
72
  const client = options?.transaction ?? getDbExec();
@@ -80,8 +78,19 @@ export async function getSetting(key, options) {
80
78
  const raw = rows.length === 0 ? null : rows[0].value;
81
79
  if (!options?.bypassCache)
82
80
  cache?.set(key, raw);
81
+ return raw;
82
+ }
83
+ export async function getSetting(key, options) {
84
+ const raw = await readSettingRaw(key, options);
83
85
  return raw == null ? null : JSON.parse(raw);
84
86
  }
87
+ /** Like `getSetting`, but a stored JSON `null` is found, not missing. */
88
+ export async function getSettingEntry(key, options) {
89
+ const raw = await readSettingRaw(key, options);
90
+ return raw == null
91
+ ? { found: false }
92
+ : { found: true, value: JSON.parse(raw) };
93
+ }
85
94
  const SETTINGS_IN_LIST_CHUNK_SIZE = 500;
86
95
  function parseSettingValue(key, raw) {
87
96
  try {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Cookie consent for the regions that need it. The regions and categories
3
+ * follow builder.io's consent banner: visitors in these countries opt in, and
4
+ * every other visitor is tracked as before, with no banner.
5
+ */
6
+ /** For the inline bootstrap the server writes into each page. */
7
+ export declare const CONSENT_REGION_TIME_ZONE_LIST: readonly string[];
8
+ export declare function isConsentRegionCountry(code: string | null | undefined): boolean;
9
+ export declare function isConsentRegionTimeZone(timeZone: string | null | undefined): boolean;
10
+ /**
11
+ * What the banner asks about. `performance` covers analytics (Google
12
+ * Analytics, Amplitude); `targeting` covers advertising and attribution (Tag
13
+ * Manager's ad tags, the referral attribution cookies). Strictly necessary
14
+ * cookies need no consent.
15
+ */
16
+ export type ConsentCategory = "performance" | "targeting";
17
+ export type ConsentChoice = Record<ConsentCategory, boolean>;
18
+ /** `consent` shows the banner; `none` tracks as before. */
19
+ export type ConsentRegion = "consent" | "none";
20
+ export declare const CONSENT_COOKIE_NAME = "an_consent";
21
+ /** Asked again a year later, like builder.io. */
22
+ export declare const CONSENT_COOKIE_LIFETIME_SECONDS: number;
23
+ export declare const CONSENT_REGION_PATH = "/_agent-native/auth/consent-region";
24
+ export declare const CONSENT_PATH = "/_agent-native/auth/consent";
25
+ /**
26
+ * One choice covers the site and every app on agent-native.com. The browser
27
+ * and the server must agree, or the two copies become two cookies.
28
+ */
29
+ export declare function consentCookieDomain(hostname: string): string | null;
30
+ /** `1.p1.t0`: version, then one flag per category. */
31
+ export declare function serializeConsent(choice: ConsentChoice): string;
32
+ /** The stored choice, or `null` when there is none this version can read. */
33
+ export declare function parseConsent(value: string | null | undefined): ConsentChoice | null;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Cookie consent for the regions that need it. The regions and categories
3
+ * follow builder.io's consent banner: visitors in these countries opt in, and
4
+ * every other visitor is tracked as before, with no banner.
5
+ */
6
+ /**
7
+ * Countries that see the banner: builder.io's "GDPR" rule, i.e. the EU, the
8
+ * rest of the EEA, Monaco, San Marino, Åland and France's overseas regions.
9
+ * The UK and Switzerland are not on it, matching builder.io.
10
+ */
11
+ const CONSENT_REGION_COUNTRY_CODES = new Set([
12
+ "AT",
13
+ "AX",
14
+ "BE",
15
+ "BG",
16
+ "CY",
17
+ "CZ",
18
+ "DE",
19
+ "DK",
20
+ "EE",
21
+ "ES",
22
+ "FI",
23
+ "FR",
24
+ "GF",
25
+ "GP",
26
+ "GR",
27
+ "HR",
28
+ "HU",
29
+ "IE",
30
+ "IS",
31
+ "IT",
32
+ "LI",
33
+ "LT",
34
+ "LU",
35
+ "LV",
36
+ "MC",
37
+ "MF",
38
+ "MQ",
39
+ "MT",
40
+ "NL",
41
+ "NO",
42
+ "PL",
43
+ "PT",
44
+ "RE",
45
+ "RO",
46
+ "SE",
47
+ "SI",
48
+ "SK",
49
+ "SM",
50
+ "YT",
51
+ ]);
52
+ /** The IANA zones of those countries, for a request with no country header. */
53
+ const CONSENT_REGION_TIME_ZONES = new Set([
54
+ "Africa/Ceuta",
55
+ "America/Cayenne",
56
+ "America/Guadeloupe",
57
+ "America/Marigot",
58
+ "America/Martinique",
59
+ "Asia/Famagusta",
60
+ "Asia/Nicosia",
61
+ "Atlantic/Azores",
62
+ "Atlantic/Canary",
63
+ "Atlantic/Madeira",
64
+ "Atlantic/Reykjavik",
65
+ "Europe/Amsterdam",
66
+ "Europe/Athens",
67
+ "Europe/Berlin",
68
+ "Europe/Bratislava",
69
+ "Europe/Brussels",
70
+ "Europe/Bucharest",
71
+ "Europe/Budapest",
72
+ "Europe/Busingen",
73
+ "Europe/Copenhagen",
74
+ "Europe/Dublin",
75
+ "Europe/Helsinki",
76
+ "Europe/Lisbon",
77
+ "Europe/Ljubljana",
78
+ "Europe/Luxembourg",
79
+ "Europe/Madrid",
80
+ "Europe/Malta",
81
+ "Europe/Mariehamn",
82
+ "Europe/Monaco",
83
+ "Europe/Nicosia",
84
+ "Europe/Oslo",
85
+ "Europe/Paris",
86
+ "Europe/Prague",
87
+ "Europe/Riga",
88
+ "Europe/Rome",
89
+ "Europe/San_Marino",
90
+ "Europe/Sofia",
91
+ "Europe/Stockholm",
92
+ "Europe/Tallinn",
93
+ "Europe/Vaduz",
94
+ "Europe/Vienna",
95
+ "Europe/Vilnius",
96
+ "Europe/Warsaw",
97
+ "Europe/Zagreb",
98
+ "Indian/Mayotte",
99
+ "Indian/Reunion",
100
+ "CET",
101
+ "EET",
102
+ "Eire",
103
+ "Iceland",
104
+ "MET",
105
+ "Poland",
106
+ "Portugal",
107
+ "WET",
108
+ ]);
109
+ /** For the inline bootstrap the server writes into each page. */
110
+ export const CONSENT_REGION_TIME_ZONE_LIST = [
111
+ ...CONSENT_REGION_TIME_ZONES,
112
+ ];
113
+ export function isConsentRegionCountry(code) {
114
+ return CONSENT_REGION_COUNTRY_CODES.has(code?.trim().toUpperCase() ?? "");
115
+ }
116
+ export function isConsentRegionTimeZone(timeZone) {
117
+ return !!timeZone && CONSENT_REGION_TIME_ZONES.has(timeZone);
118
+ }
119
+ export const CONSENT_COOKIE_NAME = "an_consent";
120
+ /** Asked again a year later, like builder.io. */
121
+ export const CONSENT_COOKIE_LIFETIME_SECONDS = 365 * 24 * 60 * 60;
122
+ export const CONSENT_REGION_PATH = "/_agent-native/auth/consent-region";
123
+ export const CONSENT_PATH = "/_agent-native/auth/consent";
124
+ /**
125
+ * One choice covers the site and every app on agent-native.com. The browser
126
+ * and the server must agree, or the two copies become two cookies.
127
+ */
128
+ export function consentCookieDomain(hostname) {
129
+ const host = hostname.toLowerCase();
130
+ return host === "agent-native.com" || host.endsWith(".agent-native.com")
131
+ ? "agent-native.com"
132
+ : null;
133
+ }
134
+ const CONSENT_COOKIE_VERSION = "1";
135
+ /** `1.p1.t0`: version, then one flag per category. */
136
+ export function serializeConsent(choice) {
137
+ return [
138
+ CONSENT_COOKIE_VERSION,
139
+ `p${choice.performance ? 1 : 0}`,
140
+ `t${choice.targeting ? 1 : 0}`,
141
+ ].join(".");
142
+ }
143
+ /** The stored choice, or `null` when there is none this version can read. */
144
+ export function parseConsent(value) {
145
+ const match = /^1\.p([01])\.t([01])$/.exec(value?.trim() ?? "");
146
+ if (!match)
147
+ return null;
148
+ return { performance: match[1] === "1", targeting: match[2] === "1" };
149
+ }
@@ -0,0 +1,31 @@
1
+ import { type AttributionTouch } from "./attribution-source.js";
2
+ /**
3
+ * The durable copy of first touch. Our server sets it, because Safari caps
4
+ * cookies written by page JavaScript at seven days; the browser's own `an_ft`
5
+ * cookie and localStorage copy stay the immediate, 30-day copies. Signup and
6
+ * the browser both pick between the copies with `preferFirstTouch`, so they
7
+ * agree on which visit is first touch.
8
+ */
9
+ export declare const FIRST_TOUCH_SERVER_COOKIE_NAME = "an_fts";
10
+ export declare const FIRST_TOUCH_SERVER_COOKIE_VERSION = "1";
11
+ export declare const FIRST_TOUCH_SERVER_PATH = "/_agent-native/auth/attribution";
12
+ /** Counted from the first visit, never extended by later ones. */
13
+ export declare const FIRST_TOUCH_SERVER_LIFETIME_SECONDS: number;
14
+ /** `name=value` budget, so this and the browser's cookies fit one header. */
15
+ export declare const FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES = 1500;
16
+ export interface TimedFirstTouch extends AttributionTouch {
17
+ landed_at?: string;
18
+ }
19
+ /**
20
+ * Which of two records is first touch: the first visit that had a source,
21
+ * else the first visit. `incumbent` wins a tie, so a copy never displaces the
22
+ * value it came from.
23
+ */
24
+ export declare function preferFirstTouch<T extends TimedFirstTouch>(incumbent: T | null | undefined, challenger: T | null | undefined): T | null;
25
+ /** Whether two records are the same visit, however each copy was trimmed. */
26
+ export declare function isSameFirstTouch(a: TimedFirstTouch | null | undefined, b: TimedFirstTouch | null | undefined): boolean;
27
+ /**
28
+ * When the durable copy expires: a year after the visit, or `null` once that
29
+ * year is over. A missing or future visit time counts from now.
30
+ */
31
+ export declare function firstTouchServerExpiry(touch: TimedFirstTouch, now?: number): Date | null;
@@ -0,0 +1,53 @@
1
+ import { hasAttributionSource, } from "./attribution-source.js";
2
+ /**
3
+ * The durable copy of first touch. Our server sets it, because Safari caps
4
+ * cookies written by page JavaScript at seven days; the browser's own `an_ft`
5
+ * cookie and localStorage copy stay the immediate, 30-day copies. Signup and
6
+ * the browser both pick between the copies with `preferFirstTouch`, so they
7
+ * agree on which visit is first touch.
8
+ */
9
+ export const FIRST_TOUCH_SERVER_COOKIE_NAME = "an_fts";
10
+ export const FIRST_TOUCH_SERVER_COOKIE_VERSION = "1";
11
+ export const FIRST_TOUCH_SERVER_PATH = "/_agent-native/auth/attribution";
12
+ /** Counted from the first visit, never extended by later ones. */
13
+ export const FIRST_TOUCH_SERVER_LIFETIME_SECONDS = 365 * 24 * 60 * 60;
14
+ /** `name=value` budget, so this and the browser's cookies fit one header. */
15
+ export const FIRST_TOUCH_SERVER_COOKIE_MAX_BYTES = 1500;
16
+ // A record with no readable visit time was trimmed to fit its cookie, so it
17
+ // was stored before any dated record it is compared with.
18
+ function landedAtMs(touch) {
19
+ const time = Date.parse(touch.landed_at ?? "");
20
+ return Number.isFinite(time) ? time : Number.NEGATIVE_INFINITY;
21
+ }
22
+ /**
23
+ * Which of two records is first touch: the first visit that had a source,
24
+ * else the first visit. `incumbent` wins a tie, so a copy never displaces the
25
+ * value it came from.
26
+ */
27
+ export function preferFirstTouch(incumbent, challenger) {
28
+ if (!incumbent)
29
+ return challenger ?? null;
30
+ if (!challenger)
31
+ return incumbent;
32
+ const incumbentSourced = hasAttributionSource(incumbent);
33
+ if (incumbentSourced !== hasAttributionSource(challenger)) {
34
+ return incumbentSourced ? incumbent : challenger;
35
+ }
36
+ return landedAtMs(challenger) < landedAtMs(incumbent)
37
+ ? challenger
38
+ : incumbent;
39
+ }
40
+ /** Whether two records are the same visit, however each copy was trimmed. */
41
+ export function isSameFirstTouch(a, b) {
42
+ return !!a?.landed_at && a.landed_at === b?.landed_at;
43
+ }
44
+ /**
45
+ * When the durable copy expires: a year after the visit, or `null` once that
46
+ * year is over. A missing or future visit time counts from now.
47
+ */
48
+ export function firstTouchServerExpiry(touch, now = Date.now()) {
49
+ const landed = Date.parse(touch.landed_at ?? "");
50
+ const start = Number.isFinite(landed) && landed <= now ? landed : now;
51
+ const expires = start + FIRST_TOUCH_SERVER_LIFETIME_SECONDS * 1000;
52
+ return expires > now ? new Date(expires) : null;
53
+ }
@@ -29,6 +29,11 @@ export default defineAction({
29
29
  const access = await assertAccess(args.resourceType, args.resourceId, "admin", undefined, { skipResourceBody: true });
30
30
  return access.resource.visibility !== "public";
31
31
  },
32
+ mcpAnnotations: {
33
+ readOnlyHint: false,
34
+ destructiveHint: true,
35
+ openWorldHint: true,
36
+ },
32
37
  run: async (args, ctx) => {
33
38
  assertWidgetShareWriteGrant(ctx, "set-resource-visibility", args);
34
39
  const reg = requireShareableResource(args.resourceType);
@@ -124,6 +124,11 @@ export default defineAction({
124
124
  .describe("Optional short note included in the notification email to an individual recipient."),
125
125
  }),
126
126
  needsApproval: needsExternalShareApproval,
127
+ mcpAnnotations: {
128
+ readOnlyHint: false,
129
+ destructiveHint: true,
130
+ openWorldHint: true,
131
+ },
127
132
  run: async (args, ctx) => {
128
133
  assertWidgetShareWriteGrant(ctx, "share-resource", args);
129
134
  const reg = requireShareableResource(args.resourceType);
@@ -26,6 +26,11 @@ export default defineAction({
26
26
  principalType: z.enum(["user", "group", "org"]),
27
27
  principalId: z.string(),
28
28
  }),
29
+ mcpAnnotations: {
30
+ readOnlyHint: false,
31
+ destructiveHint: true,
32
+ openWorldHint: false,
33
+ },
29
34
  run: async (args, ctx) => {
30
35
  assertWidgetShareWriteGrant(ctx, "unshare-resource", args);
31
36
  const reg = requireShareableResource(args.resourceType);
@@ -140,6 +140,6 @@ export default defineAction({
140
140
 
141
141
  When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
142
142
 
143
- Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
143
+ Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other direct HTTP caller (bearer or API key, MCP OAuth token, org service token, extension iframe, or mobile app) gets `409 approval_required`, and a service identity can never approve. A2A tasks use their separate approval flow. The task pauses until its owner approves, then replays the approved tool call under its approval lease. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. The agent loop, plan mode registry, HTTP action route, generated Workers entry, and MCP dispatch evaluate the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. The local `pnpm action ...` CLI calls `handler.run()` directly and does not evaluate `needsApproval`. Treat it as a local development tool, not an approval boundary. A caller that must run unattended needs a different action, never a bypass of the gate.
144
144
 
145
145
  **Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
@@ -140,6 +140,6 @@ export default defineAction({
140
140
 
141
141
  When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
142
142
 
143
- Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
143
+ Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other direct HTTP caller (bearer or API key, MCP OAuth token, org service token, extension iframe, or mobile app) gets `409 approval_required`, and a service identity can never approve. A2A tasks use their separate approval flow. The task pauses until its owner approves, then replays the approved tool call under its approval lease. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. The agent loop, plan mode registry, HTTP action route, generated Workers entry, and MCP dispatch evaluate the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. The local `pnpm action ...` CLI calls `handler.run()` directly and does not evaluate `needsApproval`. Treat it as a local development tool, not an approval boundary. A caller that must run unattended needs a different action, never a bypass of the gate.
144
144
 
145
145
  **Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.