@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.
- package/dist/action.js +54 -1
- package/dist/agent/actions/disconnect-chatgpt-subscription.js +5 -0
- package/dist/agent/actions/get-agentkit-capabilities.js +3 -3
- package/dist/agent/actions/manage-builder-connection.js +5 -0
- package/dist/agent/actions/manage-provider-key-policy.js +5 -0
- package/dist/agent/actions/manage-provider-models.js +5 -0
- package/dist/agent/actions/manage-service-providers.js +5 -0
- package/dist/agent/actions/select-chatgpt-subscription-account.js +5 -0
- package/dist/agent/context-xray/actions/context-evict.js +5 -0
- package/dist/agent/context-xray/actions/context-pin.js +5 -0
- package/dist/agent/context-xray/actions/context-report.js +5 -0
- package/dist/agent/context-xray/actions/context-restore.js +5 -0
- package/dist/agent/execution-fence.d.ts +1 -0
- package/dist/agent/execution-fence.js +5 -0
- package/dist/agent/production-agent.d.ts +35 -2
- package/dist/agent/production-agent.js +629 -32
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.js +397 -115
- package/dist/agent/run-slot-lock.d.ts +2 -0
- package/dist/agent/run-slot-lock.js +6 -0
- package/dist/agent/run-store.d.ts +5 -0
- package/dist/agent/run-store.js +423 -156
- package/dist/agent/thread-data-builder.js +27 -5
- package/dist/agent/types.d.ts +6 -12
- package/dist/agentkit/capabilities.d.ts +1 -1
- package/dist/app-config/analytics.js +1 -1
- package/dist/application-state/store.d.ts +5 -0
- package/dist/application-state/store.js +26 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/chat-threads/schema-migrations.js +17 -0
- package/dist/chat-threads/store.d.ts +21 -1
- package/dist/chat-threads/store.js +223 -41
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-skill.js +20 -18
- package/dist/cli/templates-meta.js +14 -0
- package/dist/client/analytics-session.d.ts +7 -0
- package/dist/client/analytics-session.js +27 -1
- package/dist/client/analytics.js +276 -31
- package/dist/client/chat/agentkit-agent-native.js +79 -3
- package/dist/client/chat/agentkit-protocol.js +80 -42
- package/dist/client/chat/runtime.d.ts +5 -1
- package/dist/client/chat/runtime.js +46 -3
- package/dist/client/consent.d.ts +48 -0
- package/dist/client/consent.js +413 -0
- package/dist/client/onboarding/index.d.ts +1 -0
- package/dist/client/onboarding/index.js +1 -0
- package/dist/client/onboarding/use-onboarding-checklist.d.ts +23 -0
- package/dist/client/onboarding/use-onboarding-checklist.js +115 -0
- package/dist/client/review/use-review.d.ts +2 -0
- package/dist/client/review/use-review.js +28 -1
- package/dist/client/session-replay.d.ts +1 -1
- package/dist/client/session-replay.js +142 -3
- package/dist/client/sharing/useResourceAccessGate.d.ts +3 -2
- package/dist/client/sharing/useResourceAccessGate.js +9 -1
- package/dist/client/use-chat-thread-list.d.ts +2 -0
- package/dist/client/use-chat-thread-list.js +12 -2
- package/dist/client/use-chat-threads.d.ts +1 -0
- package/dist/client/use-chat-threads.js +2 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/connections/catalog.d.ts +5 -5
- package/dist/connections/catalog.js +22 -3
- package/dist/data-widgets/index.d.ts +36 -2
- package/dist/data-widgets/index.js +94 -6
- package/dist/db/migrations.js +4 -1
- package/dist/db/schema-snapshot.generated.js +8 -1
- package/dist/feature-flags/actions/set-feature-flag.js +5 -0
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +12 -0
- package/dist/feature-flags/store.js +79 -7
- package/dist/file-upload/actions/manage-file-storage.js +5 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.js +5 -0
- package/dist/history/actions/create-resource-version.js +5 -0
- package/dist/history/actions/restore-resource-version.js +5 -0
- package/dist/ingestion/index.d.ts +1 -1
- package/dist/ingestion/index.js +1 -1
- package/dist/ingestion/pptx.d.ts +11 -0
- package/dist/ingestion/pptx.js +81 -1
- package/dist/integrations/actions/manage-messaging-channel.js +5 -0
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/jobs/scheduler.js +17 -1
- package/dist/labs/actions/set-lab.js +5 -0
- package/dist/localization/actions/set-localization-preference.js +5 -0
- package/dist/localization/consent-banner-messages.d.ts +16 -0
- package/dist/localization/consent-banner-messages.js +149 -0
- package/dist/localization/core-messages.js +4 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +2 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/actions/save-observability-instruction-update.js +5 -0
- package/dist/observability/actions/save-observability-review-summary.js +5 -0
- package/dist/observability/routes.d.ts +6 -6
- package/dist/onboarding/index.d.ts +2 -1
- package/dist/onboarding/index.js +1 -0
- package/dist/onboarding/plugin.js +55 -46
- package/dist/onboarding/status.d.ts +15 -0
- package/dist/onboarding/status.js +59 -0
- package/dist/onboarding/types.d.ts +12 -0
- package/dist/onboarding/types.js +2 -1
- package/dist/org/workspace-app-access.js +19 -9
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/provider-api/index.js +9 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/review/actions/consume-review-feedback.js +5 -0
- package/dist/review/actions/create-review-comment.js +5 -0
- package/dist/review/actions/delete-review-comment.js +5 -0
- package/dist/review/actions/react-to-review-comment.js +5 -0
- package/dist/review/actions/reply-review-comment.js +5 -0
- package/dist/review/actions/resolve-review-thread.js +5 -0
- package/dist/review/actions/send-review-thread-to-agent.js +5 -0
- package/dist/review/actions/set-review-status.js +5 -0
- package/dist/review/actions/set-review-thread-muted.js +5 -0
- package/dist/review/actions/set-review-thread-unread.js +5 -0
- package/dist/review/actions/set-review-threads-unread.js +5 -0
- package/dist/review/actions/update-review-comment-anchor.js +5 -0
- package/dist/review/actions/update-review-comment.js +5 -0
- package/dist/review/suggestions/actions.js +60 -21
- package/dist/review/suggestions/store.d.ts +2 -0
- package/dist/review/suggestions/store.js +28 -0
- package/dist/secrets/actions/delete-api-key.js +5 -0
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-capabilities.js +13 -6
- package/dist/server/agent-chat/context-tools.js +63 -2
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.js +242 -54
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/analytics.d.ts +0 -1
- package/dist/server/analytics.js +81 -20
- package/dist/server/application-state-maintenance.d.ts +2 -0
- package/dist/server/application-state-maintenance.js +2 -0
- package/dist/server/attribution.d.ts +14 -0
- package/dist/server/attribution.js +62 -2
- package/dist/server/auth.js +7 -0
- package/dist/server/builder-api-auth.d.ts +7 -0
- package/dist/server/builder-api-auth.js +24 -0
- package/dist/server/builder-oauth.d.ts +5 -1
- package/dist/server/builder-oauth.js +5 -0
- package/dist/server/consent-route.d.ts +41 -0
- package/dist/server/consent-route.js +102 -0
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/dist/server/first-touch-route.d.ts +39 -0
- package/dist/server/first-touch-route.js +115 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +2 -1
- package/dist/server/queued-submission-inbox-drain.d.ts +9 -0
- package/dist/server/queued-submission-inbox-drain.js +12 -0
- package/dist/server/request-origin.d.ts +6 -0
- package/dist/server/request-origin.js +12 -0
- package/dist/settings/org-settings.d.ts +2 -1
- package/dist/settings/org-settings.js +4 -1
- package/dist/settings/store.d.ts +8 -0
- package/dist/settings/store.js +14 -5
- package/dist/shared/consent.d.ts +33 -0
- package/dist/shared/consent.js +149 -0
- package/dist/shared/first-touch-persistence.d.ts +31 -0
- package/dist/shared/first-touch-persistence.js +53 -0
- package/dist/sharing/actions/set-resource-visibility.js +5 -0
- package/dist/sharing/actions/share-resource.js +5 -0
- package/dist/sharing/actions/unshare-resource.js +5 -0
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
- package/dist/triggers/actions/manage-automation.d.ts +4 -4
- package/dist/usage/actions/get-usage-metrics.js +27 -4
- package/dist/usage/actions/manage-usage-alert.js +5 -0
- package/dist/vite/client.js +8 -6
- package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +5 -0
- package/dist/workspace-connections/actions/delete-workspace-user-group.js +5 -0
- package/dist/workspace-connections/actions/upsert-workspace-user-group.js +5 -0
- package/docs/content/actions-access-control.mdx +8 -8
- package/docs/content/actions-defining.mdx +9 -8
- package/docs/content/deployment-environment-variables.mdx +1 -1
- package/docs/content/environment-variables.mdx +1 -1
- package/docs/content/template-design-developers.mdx +9 -9
- package/docs/content/template-design-features.mdx +1 -3
- package/docs/content/turn-into-app.mdx +4 -0
- package/package.json +6 -3
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
- 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
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/settings/store.d.ts
CHANGED
|
@@ -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;
|
package/dist/settings/store.js
CHANGED
|
@@ -63,12 +63,10 @@ export const settingsStore = defineStore({
|
|
|
63
63
|
export function ensureTable() {
|
|
64
64
|
return settingsStore.ready();
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
async function readSettingRaw(key, options) {
|
|
67
67
|
const cache = options?.transaction ? null : requestSettingsCache();
|
|
68
|
-
if (!options?.bypassCache && cache?.has(key))
|
|
69
|
-
|
|
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,
|
|
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,
|
|
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.
|