@agent-native/core 0.157.3 → 0.157.5
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +40 -1
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -7
- package/corpus/templates/clips/actions/approve-recording-access-request.ts +147 -0
- package/corpus/templates/clips/actions/request-recording-access.ts +121 -23
- package/corpus/templates/clips/actions/view-screen.ts +1 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +34 -53
- package/corpus/templates/clips/app/components/player/request-access-dialog.tsx +104 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +36 -45
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +3 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +26 -1
- package/corpus/templates/clips/app/lib/public-meeting.ts +1 -1
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +123 -37
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +41 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -0
- package/corpus/templates/clips/server/lib/emails.ts +40 -11
- package/corpus/templates/clips/shared/recording-link.ts +32 -1
- package/corpus/templates/clips/shared/share-attribution.ts +13 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +22 -10
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +2 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +245 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +185 -134
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +61 -45
- package/corpus/templates/content/app/components/editor/extensions/ImageNode.ts +6 -0
- package/corpus/templates/content/app/components/editor/image-upload.ts +137 -5
- package/corpus/templates/content/app/global.css +8 -0
- package/corpus/templates/content/changelog/2026-08-12-image-uploads-now-confirm-the-image-can-be-displayed-before-.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-fill-the-page-width-by-default-and-remain-visible-whe.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-inserted-from-the-slash-command-stay-saved-after-uplo.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-svg-files-can-be-selected-reliably-from-the-image-picker.md +6 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -1
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +25 -30
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +2 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +124 -180
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +1 -0
- package/corpus/templates/slides/app/global.css +99 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/chat-threads/schema.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +318 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +41 -3
- package/dist/client/AssistantChat.d.ts +9 -1
- package/dist/client/AssistantChat.js +26 -13
- package/dist/client/MultiTabAssistantChat.js +18 -5
- package/dist/client/agent-chat.d.ts +8 -0
- package/dist/client/agent-chat.js +15 -0
- package/dist/client/app-chat-sidebar.d.ts +23 -0
- package/dist/client/app-chat-sidebar.js +53 -0
- package/dist/client/app-providers.js +31 -2
- package/dist/client/frame-protocol.d.ts +20 -2
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/sharing/AgentShareSection.d.ts +8 -0
- package/dist/client/sharing/AgentShareSection.js +50 -0
- package/dist/client/sharing/ShareButton.js +2 -1
- package/dist/client/sharing/ShareDialog.d.ts +2 -2
- package/dist/client/sharing/ShareDialog.js +21 -29
- package/dist/client/sharing/index.d.ts +1 -0
- package/dist/client/sharing/index.js +1 -0
- package/dist/client/sharing/useShareButtonController.d.ts +1 -0
- package/dist/client/sharing/useShareDialogController.d.ts +3 -0
- package/dist/client/sharing/useShareDialogController.js +2 -1
- package/dist/client/theme.d.ts +21 -0
- package/dist/client/theme.js +60 -0
- package/dist/client/ui/index.d.ts +1 -1
- package/dist/client/ui/index.js +1 -1
- package/dist/client/use-session.js +23 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/dashboard-storage/schema.d.ts +1 -1
- package/dist/data-programs/schema.d.ts +1 -1
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +6 -0
- package/dist/localization/core-messages/de-DE.js +6 -0
- package/dist/localization/core-messages/en-US.d.ts +6 -0
- package/dist/localization/core-messages/en-US.js +6 -0
- package/dist/localization/core-messages/es-ES.js +6 -0
- package/dist/localization/core-messages/fr-FR.js +6 -0
- package/dist/localization/core-messages/hi-IN.js +6 -0
- package/dist/localization/core-messages/ja-JP.js +6 -0
- package/dist/localization/core-messages/ko-KR.js +6 -0
- package/dist/localization/core-messages/pt-BR.js +6 -0
- package/dist/localization/core-messages/zh-CN.js +6 -0
- package/dist/localization/core-messages/zh-TW.js +6 -0
- package/dist/localization/core-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/server/auth.js +8 -5
- package/dist/server/identity-sso-store.d.ts +6 -1
- package/dist/server/identity-sso-store.js +57 -14
- package/dist/server/identity-sso.d.ts +7 -8
- package/dist/server/identity-sso.js +15 -12
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +1 -0
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/sharing/schema.js +4 -1
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/actions-access-control.mdx +226 -0
- package/docs/content/actions-advanced.mdx +152 -0
- package/docs/content/actions-defining.mdx +275 -0
- package/docs/content/actions-other-surfaces.mdx +167 -0
- package/docs/content/actions-overview.mdx +214 -0
- package/docs/content/actions-run-context.mdx +180 -0
- package/docs/content/agent-surfaces.mdx +1 -1
- package/docs/content/agent-teams.mdx +1 -1
- package/docs/content/agent-web-surfaces.mdx +1 -1
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/automation-connectors.mdx +1 -1
- package/docs/content/automations.mdx +1 -1
- package/docs/content/blueprint-installer.mdx +1 -1
- package/docs/content/cli-adapters.mdx +1 -1
- package/docs/content/client-agent-chat.mdx +1 -1
- package/docs/content/client-data.mdx +2 -2
- package/docs/content/client-overview.mdx +2 -2
- package/docs/content/components.mdx +1 -1
- package/docs/content/cross-app-sso.mdx +24 -17
- package/docs/content/data-programs.mdx +2 -2
- package/docs/content/database.mdx +1 -1
- package/docs/content/deployment.mdx +1 -1
- package/docs/content/docs-components.mdx +14 -3
- package/docs/content/drop-in-agent.mdx +2 -2
- package/docs/content/durable-resume.mdx +1 -1
- package/docs/content/evals.mdx +1 -1
- package/docs/content/extensions.mdx +1 -1
- package/docs/content/external-agents-catalog.mdx +2 -2
- package/docs/content/external-agents.mdx +1 -1
- package/docs/content/generative-ui.mdx +1 -1
- package/docs/content/getting-started.mdx +1 -1
- package/docs/content/http-api.mdx +4 -4
- package/docs/content/human-approval.mdx +1 -1
- package/docs/content/key-concepts.mdx +1 -1
- package/docs/content/mcp-apps.mdx +1 -1
- package/docs/content/mcp-clients.mdx +1 -1
- package/docs/content/mcp-protocol.mdx +1 -1
- package/docs/content/native-chat-ui.mdx +1 -1
- package/docs/content/observability.mdx +1 -1
- package/docs/content/organizations-teams-permissions.mdx +1 -1
- package/docs/content/processors.mdx +1 -1
- package/docs/content/pure-agent-apps.mdx +1 -1
- package/docs/content/recurring-jobs.mdx +1 -1
- package/docs/content/server-database.mdx +1 -1
- package/docs/content/server-overview.mdx +3 -3
- package/docs/content/server-routes.mdx +2 -2
- package/docs/content/template-analytics-developers.mdx +1 -1
- package/docs/content/template-assets-developers.mdx +1 -1
- package/docs/content/template-brain-developers.mdx +1 -1
- package/docs/content/template-calendar-developers.mdx +1 -1
- package/docs/content/template-chat-first-edits.mdx +1 -1
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-content-developers.mdx +1 -1
- package/docs/content/template-forms-developers.mdx +1 -1
- package/docs/content/template-forms.mdx +1 -1
- package/docs/content/template-mail-developers.mdx +2 -2
- package/docs/content/toolkit-org-team.mdx +1 -1
- package/docs/content/tracking.mdx +1 -1
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/actions.mdx +0 -750
|
@@ -15,17 +15,22 @@
|
|
|
15
15
|
* development may initialize these tables lazily; production release
|
|
16
16
|
* migrations own their creation before serverless requests are served.
|
|
17
17
|
*/
|
|
18
|
+
export declare const CANONICAL_IDENTITY_SSO_HUB_URL = "https://dispatch.agent-native.com";
|
|
18
19
|
export declare const SSO_STATE_TTL_MS: number;
|
|
19
20
|
export declare const SSO_LOGIN_MAX = 60;
|
|
20
21
|
export declare const SSO_LOGIN_WINDOW_MS = 60000;
|
|
21
22
|
export declare function getIdentityHubUrl(): string | undefined;
|
|
23
|
+
export declare function isIdentitySsoExplicitlyEnabled(): boolean;
|
|
22
24
|
export declare function isIdentitySsoEnabled(): boolean;
|
|
23
25
|
export declare function isDesktopSsoCanaryUserAgent(userAgent: string | undefined): boolean;
|
|
24
26
|
export declare function isCanonicalAgentNativeAppOrigin(origin: string | undefined): boolean;
|
|
27
|
+
export declare function isCanonicalIdentitySsoClientOrigin(origin: string | undefined): boolean;
|
|
25
28
|
export declare function isCanonicalAgentNativeAppRequest(host: string | undefined, forwardedProtocol: string | undefined): boolean;
|
|
29
|
+
export declare function isCanonicalIdentitySsoClientRequest(host: string | undefined, forwardedProtocol: string | undefined): boolean;
|
|
26
30
|
/**
|
|
27
31
|
* The conditional login entry is the only browser UI this feature adds. It
|
|
28
|
-
* stays byte-for-byte absent
|
|
32
|
+
* stays byte-for-byte absent on self-hosted apps; canonical hosted clients
|
|
33
|
+
* use their exact registered origin as the implicit opt-in.
|
|
29
34
|
*/
|
|
30
35
|
export declare function identitySsoLoginButtonHtml(): string;
|
|
31
36
|
export interface CreateSsoStateInput {
|
|
@@ -20,6 +20,7 @@ import { getDbExec, intType, isConnectionError, isPostgres, isProductionServerle
|
|
|
20
20
|
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
21
21
|
let _initPromise;
|
|
22
22
|
const DESKTOP_SSO_CANARY_USER_AGENT = /AgentNativeDesktopSsoCanary\//i;
|
|
23
|
+
export const CANONICAL_IDENTITY_SSO_HUB_URL = "https://dispatch.agent-native.com";
|
|
23
24
|
const CANONICAL_IDENTITY_SSO_APP_ORIGINS = new Set([
|
|
24
25
|
"https://analytics.agent-native.com",
|
|
25
26
|
"https://assets.agent-native.com",
|
|
@@ -47,28 +48,61 @@ const CLIENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
|
47
48
|
const CODE_CHALLENGE_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
48
49
|
const CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
|
|
49
50
|
const LOCALHOST_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
51
|
+
const CANONICAL_IDENTITY_SSO_CLIENT_ORIGINS = new Set([...CANONICAL_IDENTITY_SSO_APP_ORIGINS].filter((origin) => origin !== CANONICAL_IDENTITY_SSO_HUB_URL));
|
|
50
52
|
// ---------------------------------------------------------------------------
|
|
51
53
|
// Feature switch — this module is intentionally dependency-light because the
|
|
52
54
|
// auth guard and the route handler both import the same pure switch.
|
|
53
55
|
// ---------------------------------------------------------------------------
|
|
56
|
+
function configuredAppOrigin() {
|
|
57
|
+
for (const raw of [
|
|
58
|
+
process.env.APP_URL,
|
|
59
|
+
process.env.BETTER_AUTH_URL,
|
|
60
|
+
process.env.VITE_APP_URL,
|
|
61
|
+
process.env.VITE_BETTER_AUTH_URL,
|
|
62
|
+
process.env.URL,
|
|
63
|
+
process.env.DEPLOY_PRIME_URL,
|
|
64
|
+
process.env.DEPLOY_URL,
|
|
65
|
+
]) {
|
|
66
|
+
const value = raw?.trim();
|
|
67
|
+
if (!value)
|
|
68
|
+
continue;
|
|
69
|
+
try {
|
|
70
|
+
const url = new URL(value);
|
|
71
|
+
return `${url.protocol}//${url.host}${url.pathname}`.replace(/\/+$/, "");
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
void error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
54
79
|
export function getIdentityHubUrl() {
|
|
55
80
|
const raw = process.env.AGENT_NATIVE_IDENTITY_HUB_URL?.trim();
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
if (raw) {
|
|
82
|
+
try {
|
|
83
|
+
const u = new URL(raw);
|
|
84
|
+
if (u.protocol !== "https:" &&
|
|
85
|
+
!(u.protocol === "http:" && LOCALHOST_HOSTS.has(u.hostname))) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
if (u.username || u.password || u.search || u.hash)
|
|
89
|
+
return undefined;
|
|
90
|
+
return `${u.protocol}//${u.host}${u.pathname}`.replace(/\/+$/, "");
|
|
63
91
|
}
|
|
64
|
-
|
|
92
|
+
catch (error) {
|
|
93
|
+
void error;
|
|
65
94
|
return undefined;
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
void error;
|
|
70
|
-
return undefined;
|
|
95
|
+
}
|
|
71
96
|
}
|
|
97
|
+
// Canonical hosted apps are all registered with Dispatch already. Keep
|
|
98
|
+
// self-hosted deployments opt-in, and never make Dispatch federate to itself.
|
|
99
|
+
const appOrigin = configuredAppOrigin();
|
|
100
|
+
return isCanonicalIdentitySsoClientOrigin(appOrigin)
|
|
101
|
+
? CANONICAL_IDENTITY_SSO_HUB_URL
|
|
102
|
+
: undefined;
|
|
103
|
+
}
|
|
104
|
+
export function isIdentitySsoExplicitlyEnabled() {
|
|
105
|
+
return Boolean(process.env.AGENT_NATIVE_IDENTITY_HUB_URL?.trim() && getIdentityHubUrl());
|
|
72
106
|
}
|
|
73
107
|
export function isIdentitySsoEnabled() {
|
|
74
108
|
return !!getIdentityHubUrl();
|
|
@@ -94,14 +128,23 @@ export function isCanonicalAgentNativeAppOrigin(origin) {
|
|
|
94
128
|
return false;
|
|
95
129
|
}
|
|
96
130
|
}
|
|
131
|
+
export function isCanonicalIdentitySsoClientOrigin(origin) {
|
|
132
|
+
return Boolean(origin && CANONICAL_IDENTITY_SSO_CLIENT_ORIGINS.has(origin));
|
|
133
|
+
}
|
|
97
134
|
export function isCanonicalAgentNativeAppRequest(host, forwardedProtocol) {
|
|
98
135
|
if (!host || forwardedProtocol !== "https")
|
|
99
136
|
return false;
|
|
100
137
|
return isCanonicalAgentNativeAppOrigin(`https://${host}`);
|
|
101
138
|
}
|
|
139
|
+
export function isCanonicalIdentitySsoClientRequest(host, forwardedProtocol) {
|
|
140
|
+
if (!host || forwardedProtocol !== "https")
|
|
141
|
+
return false;
|
|
142
|
+
return isCanonicalIdentitySsoClientOrigin(`https://${host}`);
|
|
143
|
+
}
|
|
102
144
|
/**
|
|
103
145
|
* The conditional login entry is the only browser UI this feature adds. It
|
|
104
|
-
* stays byte-for-byte absent
|
|
146
|
+
* stays byte-for-byte absent on self-hosted apps; canonical hosted clients
|
|
147
|
+
* use their exact registered origin as the implicit opt-in.
|
|
105
148
|
*/
|
|
106
149
|
export function identitySsoLoginButtonHtml() {
|
|
107
150
|
if (!isIdentitySsoEnabled())
|
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
* callback-scoped cookie and redeems the code server-to-server. Only that
|
|
8
8
|
* server-to-server response may contain the signed identity assertion.
|
|
9
9
|
*
|
|
10
|
-
* Direct browser federation
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Desktop implementation or any ordinary auth surface.
|
|
10
|
+
* Direct browser federation uses the canonical Dispatch authority for exact
|
|
11
|
+
* first-party hosted app origins and remains opt-in through
|
|
12
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL` for self-hosted deployments. The packaged
|
|
13
|
+
* Desktop Canary follows the same canonical-origin boundary.
|
|
15
14
|
*/
|
|
16
15
|
import type { H3Event } from "h3";
|
|
17
16
|
import { getIdentityHubUrl, identitySsoLoginButtonHtml, isIdentitySsoEnabled } from "./identity-sso-store.js";
|
|
@@ -22,9 +21,9 @@ export declare const IDENTITY_SSO_DESKTOP_COMPLETE_PATH = "/_agent-native/identi
|
|
|
22
21
|
export declare const IDENTITY_SSO_CALLBACK_PATH = "/_agent-native/identity/callback";
|
|
23
22
|
export declare const IDENTITY_SSO_TOKEN_PATH = "/_agent-native/identity/token";
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Canonical hosted apps may use Dispatch without per-app hub configuration.
|
|
25
|
+
* Self-hosted apps remain strictly env-gated. The exact-origin check is kept
|
|
26
|
+
* request-scoped so a missing deployment URL cannot broaden the trust set.
|
|
28
27
|
*/
|
|
29
28
|
export declare function resolveIdentityHubUrl(event: H3Event): string | undefined;
|
|
30
29
|
export declare function handleIdentitySso(event: H3Event, subpath: string): Promise<Response>;
|
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
* callback-scoped cookie and redeems the code server-to-server. Only that
|
|
8
8
|
* server-to-server response may contain the signed identity assertion.
|
|
9
9
|
*
|
|
10
|
-
* Direct browser federation
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Desktop implementation or any ordinary auth surface.
|
|
10
|
+
* Direct browser federation uses the canonical Dispatch authority for exact
|
|
11
|
+
* first-party hosted app origins and remains opt-in through
|
|
12
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL` for self-hosted deployments. The packaged
|
|
13
|
+
* Desktop Canary follows the same canonical-origin boundary.
|
|
15
14
|
*/
|
|
16
15
|
import { createHash, randomBytes } from "node:crypto";
|
|
17
16
|
import { deleteCookie, getCookie, getHeader, getMethod, setCookie } from "h3";
|
|
@@ -22,7 +21,7 @@ import { getAppName } from "./app-name.js";
|
|
|
22
21
|
import { getSession, isExpectedAuthFailure, safeReturnPath } from "./auth.js";
|
|
23
22
|
import { getBetterAuth, getBetterAuthInternalAdapter, } from "./better-auth-instance.js";
|
|
24
23
|
import { createOAuthSession, getOrigin } from "./google-oauth.js";
|
|
25
|
-
import { consumeSsoState, createSsoState, getIdentityHubUrl, identitySsoLoginButtonHtml, isCanonicalAgentNativeAppRequest, isDesktopSsoCanaryUserAgent, isIdentitySsoEnabled, isJtiReplayed, SSO_STATE_TTL_MS, } from "./identity-sso-store.js";
|
|
24
|
+
import { consumeSsoState, createSsoState, CANONICAL_IDENTITY_SSO_HUB_URL, getIdentityHubUrl, identitySsoLoginButtonHtml, isCanonicalAgentNativeAppRequest, isCanonicalIdentitySsoClientRequest, isDesktopSsoCanaryUserAgent, isIdentitySsoExplicitlyEnabled, isIdentitySsoEnabled, isJtiReplayed, SSO_STATE_TTL_MS, } from "./identity-sso-store.js";
|
|
26
25
|
export { getIdentityHubUrl, identitySsoLoginButtonHtml, isIdentitySsoEnabled };
|
|
27
26
|
export const IDENTITY_SSO_PROVIDER_ID = "agent-native";
|
|
28
27
|
export const IDENTITY_SSO_SCOPE = "identity";
|
|
@@ -33,7 +32,6 @@ const DESKTOP_COMPLETION_NONCE = /^[A-Za-z0-9_-]{32,128}$/;
|
|
|
33
32
|
const STATE_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
34
33
|
const CODE = /^[A-Za-z0-9_-]{43}$/;
|
|
35
34
|
const CODE_VERIFIER = /^[A-Za-z0-9._~-]{43,128}$/;
|
|
36
|
-
const DESKTOP_IDENTITY_HUB_URL = "https://dispatch.agent-native.com";
|
|
37
35
|
const SSO_VERIFIER_COOKIE_PREFIX = "agent_native_sso_verifier_";
|
|
38
36
|
const MAX_ASSERTION_AGE_SECONDS = 5 * 60;
|
|
39
37
|
const LOCALHOST_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
@@ -153,14 +151,19 @@ function resolveClientBinding(event, hub) {
|
|
|
153
151
|
return { appId, clientId, redirectUri, authority };
|
|
154
152
|
}
|
|
155
153
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
154
|
+
* Canonical hosted apps may use Dispatch without per-app hub configuration.
|
|
155
|
+
* Self-hosted apps remain strictly env-gated. The exact-origin check is kept
|
|
156
|
+
* request-scoped so a missing deployment URL cannot broaden the trust set.
|
|
159
157
|
*/
|
|
160
158
|
export function resolveIdentityHubUrl(event) {
|
|
161
|
-
const configured =
|
|
159
|
+
const configured = isIdentitySsoExplicitlyEnabled()
|
|
160
|
+
? getIdentityHubUrl()
|
|
161
|
+
: undefined;
|
|
162
162
|
if (configured)
|
|
163
163
|
return configured;
|
|
164
|
+
if (isCanonicalIdentitySsoClientRequest(getHeader(event, "host"), getHeader(event, "x-forwarded-proto"))) {
|
|
165
|
+
return CANONICAL_IDENTITY_SSO_HUB_URL;
|
|
166
|
+
}
|
|
164
167
|
if (!isDesktopSsoCanaryUserAgent(getHeader(event, "user-agent"))) {
|
|
165
168
|
return undefined;
|
|
166
169
|
}
|
|
@@ -168,7 +171,7 @@ export function resolveIdentityHubUrl(event) {
|
|
|
168
171
|
if (!isCanonicalAgentNativeAppRequest(getHeader(event, "host"), getHeader(event, "x-forwarded-proto"))) {
|
|
169
172
|
return undefined;
|
|
170
173
|
}
|
|
171
|
-
return
|
|
174
|
+
return CANONICAL_IDENTITY_SSO_HUB_URL;
|
|
172
175
|
}
|
|
173
176
|
catch (error) {
|
|
174
177
|
void error;
|
|
@@ -11,11 +11,13 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
11
11
|
};
|
|
12
12
|
orgId?: undefined;
|
|
13
13
|
role?: undefined;
|
|
14
|
+
agentReadable?: undefined;
|
|
14
15
|
} | {
|
|
15
16
|
ownerEmail: string;
|
|
16
17
|
orgId: string;
|
|
17
18
|
visibility: import("../schema.js").Visibility;
|
|
18
19
|
role: "owner" | import("../schema.js").ShareRole;
|
|
20
|
+
agentReadable: boolean;
|
|
19
21
|
shares: any;
|
|
20
22
|
policy: {
|
|
21
23
|
allowPublic: boolean;
|
|
@@ -60,6 +60,7 @@ export default defineAction({
|
|
|
60
60
|
orgId: access.resource.orgId ?? null,
|
|
61
61
|
visibility: access.resource.visibility ?? "private",
|
|
62
62
|
role: access.role,
|
|
63
|
+
agentReadable: Boolean(reg.agentReadable),
|
|
63
64
|
shares: shares.map((s) => ({
|
|
64
65
|
id: s.id,
|
|
65
66
|
principalType: s.principalType,
|
package/dist/sharing/schema.d.ts
CHANGED
|
@@ -160,7 +160,7 @@ export declare function createSharesTable(tableName: string): import("drizzle-or
|
|
|
160
160
|
data: string;
|
|
161
161
|
driverParam: string;
|
|
162
162
|
notNull: true;
|
|
163
|
-
hasDefault:
|
|
163
|
+
hasDefault: true;
|
|
164
164
|
isPrimaryKey: false;
|
|
165
165
|
isAutoincrement: false;
|
|
166
166
|
hasRuntimeDefault: false;
|
package/dist/sharing/schema.js
CHANGED
|
@@ -73,7 +73,10 @@ export function createSharesTable(tableName) {
|
|
|
73
73
|
})
|
|
74
74
|
.notNull()
|
|
75
75
|
.default("viewer"),
|
|
76
|
-
|
|
76
|
+
// Older app databases may already contain share rows from before this
|
|
77
|
+
// audit field existed. A literal default lets additive schema repair add
|
|
78
|
+
// the column without inventing an actor for those historical rows.
|
|
79
|
+
createdBy: text("created_by").notNull().default(""),
|
|
77
80
|
createdAt: text("created_at").notNull().default(now()),
|
|
78
81
|
});
|
|
79
82
|
}
|
|
@@ -182,6 +182,11 @@ in the moment of agent use.
|
|
|
182
182
|
five-item sidebar preview and a footer row with New chat followed by an
|
|
183
183
|
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
184
184
|
actions.
|
|
185
|
+
- **Data grid kit**: provider-agnostic spreadsheet mechanics belong in
|
|
186
|
+
`@agent-native/toolkit/data-grid`. Apps provide rows, typed columns, editor
|
|
187
|
+
slots, selection and width state, persistence callbacks, and product-level
|
|
188
|
+
row/body slots. Keep database models, access checks, grouping, drag/drop,
|
|
189
|
+
and domain actions in the app adapter.
|
|
185
190
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
186
191
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
187
192
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -184,6 +184,27 @@ import { ShareButton } from "@agent-native/core/client/sharing";
|
|
|
184
184
|
|
|
185
185
|
For list views, show `<VisibilityBadge visibility={row.visibility} />` next to each resource.
|
|
186
186
|
|
|
187
|
+
## Standard share surface
|
|
188
|
+
|
|
189
|
+
All app share popovers should use the same compact surface contract:
|
|
190
|
+
|
|
191
|
+
- Use the text-only `ShareTrigger` from `@agent-native/toolkit/sharing`.
|
|
192
|
+
- Render ordinary links with `ShareCopyRow`, which exposes a Copy action without
|
|
193
|
+
printing the raw URL.
|
|
194
|
+
- Keep general access and individual people access in the standard Core sharing
|
|
195
|
+
flow. The people flow supports email invites, roles, notifications, and
|
|
196
|
+
removal through the shared share actions.
|
|
197
|
+
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
|
+
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
|
+
content through the shared section shell.
|
|
200
|
+
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
|
+
action (for example, an embed-code editor), but they should retain the same
|
|
202
|
+
trigger, copy-row, access, and spacing language.
|
|
203
|
+
|
|
204
|
+
`ShareDisclosureSection` is the toolkit-owned shell for optional expandable
|
|
205
|
+
share details; use its `ShareAgentsSection` or `SharePeopleSection` wrappers
|
|
206
|
+
instead of creating another collapsible share panel in a template.
|
|
207
|
+
|
|
187
208
|
## Actions available everywhere
|
|
188
209
|
|
|
189
210
|
The framework auto-mounts these actions in every template — no per-template boilerplate:
|
|
@@ -182,6 +182,11 @@ in the moment of agent use.
|
|
|
182
182
|
five-item sidebar preview and a footer row with New chat followed by an
|
|
183
183
|
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
184
184
|
actions.
|
|
185
|
+
- **Data grid kit**: provider-agnostic spreadsheet mechanics belong in
|
|
186
|
+
`@agent-native/toolkit/data-grid`. Apps provide rows, typed columns, editor
|
|
187
|
+
slots, selection and width state, persistence callbacks, and product-level
|
|
188
|
+
row/body slots. Keep database models, access checks, grouping, drag/drop,
|
|
189
|
+
and domain actions in the app adapter.
|
|
185
190
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
186
191
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
187
192
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -184,6 +184,27 @@ import { ShareButton } from "@agent-native/core/client/sharing";
|
|
|
184
184
|
|
|
185
185
|
For list views, show `<VisibilityBadge visibility={row.visibility} />` next to each resource.
|
|
186
186
|
|
|
187
|
+
## Standard share surface
|
|
188
|
+
|
|
189
|
+
All app share popovers should use the same compact surface contract:
|
|
190
|
+
|
|
191
|
+
- Use the text-only `ShareTrigger` from `@agent-native/toolkit/sharing`.
|
|
192
|
+
- Render ordinary links with `ShareCopyRow`, which exposes a Copy action without
|
|
193
|
+
printing the raw URL.
|
|
194
|
+
- Keep general access and individual people access in the standard Core sharing
|
|
195
|
+
flow. The people flow supports email invites, roles, notifications, and
|
|
196
|
+
removal through the shared share actions.
|
|
197
|
+
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
|
+
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
|
+
content through the shared section shell.
|
|
200
|
+
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
|
+
action (for example, an embed-code editor), but they should retain the same
|
|
202
|
+
trigger, copy-row, access, and spacing language.
|
|
203
|
+
|
|
204
|
+
`ShareDisclosureSection` is the toolkit-owned shell for optional expandable
|
|
205
|
+
share details; use its `ShareAgentsSection` or `SharePeopleSection` wrappers
|
|
206
|
+
instead of creating another collapsible share panel in a template.
|
|
207
|
+
|
|
187
208
|
## Actions available everywhere
|
|
188
209
|
|
|
189
210
|
The framework auto-mounts these actions in every template — no per-template boilerplate:
|
|
@@ -182,6 +182,11 @@ in the moment of agent use.
|
|
|
182
182
|
five-item sidebar preview and a footer row with New chat followed by an
|
|
183
183
|
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
184
184
|
actions.
|
|
185
|
+
- **Data grid kit**: provider-agnostic spreadsheet mechanics belong in
|
|
186
|
+
`@agent-native/toolkit/data-grid`. Apps provide rows, typed columns, editor
|
|
187
|
+
slots, selection and width state, persistence callbacks, and product-level
|
|
188
|
+
row/body slots. Keep database models, access checks, grouping, drag/drop,
|
|
189
|
+
and domain actions in the app adapter.
|
|
185
190
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
186
191
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
187
192
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -182,6 +182,11 @@ in the moment of agent use.
|
|
|
182
182
|
five-item sidebar preview and a footer row with New chat followed by an
|
|
183
183
|
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
184
184
|
actions.
|
|
185
|
+
- **Data grid kit**: provider-agnostic spreadsheet mechanics belong in
|
|
186
|
+
`@agent-native/toolkit/data-grid`. Apps provide rows, typed columns, editor
|
|
187
|
+
slots, selection and width state, persistence callbacks, and product-level
|
|
188
|
+
row/body slots. Keep database models, access checks, grouping, drag/drop,
|
|
189
|
+
and domain actions in the app adapter.
|
|
185
190
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
186
191
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
187
192
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -184,6 +184,27 @@ import { ShareButton } from "@agent-native/core/client/sharing";
|
|
|
184
184
|
|
|
185
185
|
For list views, show `<VisibilityBadge visibility={row.visibility} />` next to each resource.
|
|
186
186
|
|
|
187
|
+
## Standard share surface
|
|
188
|
+
|
|
189
|
+
All app share popovers should use the same compact surface contract:
|
|
190
|
+
|
|
191
|
+
- Use the text-only `ShareTrigger` from `@agent-native/toolkit/sharing`.
|
|
192
|
+
- Render ordinary links with `ShareCopyRow`, which exposes a Copy action without
|
|
193
|
+
printing the raw URL.
|
|
194
|
+
- Keep general access and individual people access in the standard Core sharing
|
|
195
|
+
flow. The people flow supports email invites, roles, notifications, and
|
|
196
|
+
removal through the shared share actions.
|
|
197
|
+
- Add `ShareAgentsSection` only when the resource has a real agent-readable
|
|
198
|
+
link or prompt. Keep it collapsed by default and supply domain-specific
|
|
199
|
+
content through the shared section shell.
|
|
200
|
+
- App-specific tabs or controls may remain when they represent a real domain
|
|
201
|
+
action (for example, an embed-code editor), but they should retain the same
|
|
202
|
+
trigger, copy-row, access, and spacing language.
|
|
203
|
+
|
|
204
|
+
`ShareDisclosureSection` is the toolkit-owned shell for optional expandable
|
|
205
|
+
share details; use its `ShareAgentsSection` or `SharePeopleSection` wrappers
|
|
206
|
+
instead of creating another collapsible share panel in a template.
|
|
207
|
+
|
|
187
208
|
## Actions available everywhere
|
|
188
209
|
|
|
189
210
|
The framework auto-mounts these actions in every template — no per-template boilerplate:
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Access & Authorization"
|
|
3
|
+
description: "Who can call an action: exposure flags (agentTool, toolCallable), ctx-based scoping, accessFilter/assertAccess, and the authorize guard."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Access & Authorization
|
|
7
|
+
|
|
8
|
+
Five settings decide who can call an action: the agent, the frontend, an extension, an outside API caller, or nobody without a human's approval first. Most actions never need to touch any of these, since the defaults already fit normal UI-and-agent use.
|
|
9
|
+
|
|
10
|
+
### Exposure flags {#exposure-flags}
|
|
11
|
+
|
|
12
|
+
All five flags default to the permissive value, so you only set one when you need to tighten a specific surface. The table below is a quick summary. The sections after it add the one detail each flag needs.
|
|
13
|
+
|
|
14
|
+
| Flag | Default | Restrictive value → who can still call | Typical use |
|
|
15
|
+
| --------------- | ------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
16
|
+
| `agentTool` | `true` | `false` → UI, HTTP, and CLI only (hidden from the model, MCP, and A2A) | UI-only or programmatic actions that shouldn't take up a tool slot |
|
|
17
|
+
| `toolCallable` | `true` | `false` → everything **except** the sandboxed extension iframe bridge (403) | Sensitive account or org changes (delete account, change membership) |
|
|
18
|
+
| `publicAgent` | off (private) | `{ expose: true }` → adds the action to **public** MCP/A2A/OpenAPI surfaces | Safe read or ingest tools that don't require authentication |
|
|
19
|
+
| `needsApproval` | `false` | `true` → the agent **pauses**, and a human must approve the specific call | Consequential side effects (send email, charge a card, delete) |
|
|
20
|
+
| `authorize` | none | a guard function → only callers it accepts, on **every** surface | Operations restricted to a role (per-app RBAC, admin-only ops) |
|
|
21
|
+
|
|
22
|
+
These flags are independent, so setting one doesn't change the others. `agentTool` controls what the model sees, see [Hide from the model](#agent-tool) below. `toolCallable` controls only the extension iframe, see [Block extension iframes](#tool-callable) below. `publicAgent` adds an opt-in public surface, and a public web route never implies public tool exposure, see [Expose to public agents](#public-agent) below. `needsApproval` gates execution after the call is already made, see [Require human approval](#needs-approval) below. `authorize` decides whether this caller may run the action at all, on every dispatch path, before `run()` is entered, see [Restrict callers by role](#authorize) below.
|
|
23
|
+
|
|
24
|
+
## Hide from the model {#agent-tool}
|
|
25
|
+
|
|
26
|
+
By default, every action is a callable agent tool. Set `agentTool: false` to remove it from every agent tool list, while keeping it behind the framework's auth and action surface. It stays callable from the UI (`useActionMutation()` or `callAction()`), the CLI, and `/_agent-native/actions/<name>`:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export default defineAction({
|
|
30
|
+
description: "Persist the user's sidebar width.",
|
|
31
|
+
agentTool: false, // UI-only, not a tool in the model's context window
|
|
32
|
+
schema: z.object({ widthPx: z.number() }),
|
|
33
|
+
http: { method: "PUT" },
|
|
34
|
+
run: async ({ widthPx }) => {
|
|
35
|
+
/* ... */
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use this when you add a UI-only or purely programmatic action, or when the UI stops using an action that would otherwise stay exposed to the model.
|
|
41
|
+
|
|
42
|
+
## Block extension iframes {#tool-callable}
|
|
43
|
+
|
|
44
|
+
Extensions ([Alpine.js mini-apps in sandboxed iframes](/docs/extensions)) call actions through `appAction(name, params)`, running with the viewer's own permissions, secrets, and SQL scope. For sensitive operations, that's too much trust by default. Set `toolCallable: false` to make the extension bridge return a 403, while keeping the action callable from the UI, agent, CLI, MCP, and A2A:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export default defineAction({
|
|
48
|
+
description: "Delete the current user's account.",
|
|
49
|
+
toolCallable: false, // never callable from an extension iframe
|
|
50
|
+
schema: z.object({ confirm: z.literal("yes") }),
|
|
51
|
+
run: async () => {
|
|
52
|
+
/* ... */
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use it for actions that delete or transfer an account or org, change auth state, modify org membership, or grant share access. The framework's built-in `share-resource`, `unshare-resource`, and `set-resource-visibility` actions already opt out. The framework enforces this with a header the host sets on iframe calls, one an iframe can't fake. Regular UI, agent, CLI, MCP, and A2A calls are unaffected. See [Security](/docs/security) for details.
|
|
58
|
+
|
|
59
|
+
## Expose to public agents {#public-agent}
|
|
60
|
+
|
|
61
|
+
Every action stays off any public protocol surface by default, even one whose web route is public. Making a page crawlable never exposes a tool. Set `publicAgent: { expose: true, readOnly: true, ... }` to opt one safe action into the public MCP, A2A, and OpenAPI surfaces:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
export default defineAction({
|
|
65
|
+
description: "Search published docs.",
|
|
66
|
+
schema: z.object({ query: z.string() }),
|
|
67
|
+
http: { method: "GET" },
|
|
68
|
+
readOnly: true,
|
|
69
|
+
publicAgent: {
|
|
70
|
+
expose: true,
|
|
71
|
+
readOnly: true,
|
|
72
|
+
requiresAuth: false,
|
|
73
|
+
},
|
|
74
|
+
run: async ({ query }) => {
|
|
75
|
+
/* ... */
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use it only for safe, read-only, or ingest-only actions, the kind you'd let a stranger call without signing in. `requiresAuth: false` tells the public surface this action is safe to list before authentication. Leave it `true` for an action that still needs a connected caller's identity, like a per-account ingest tool. See [External Agents](/docs/external-agents) and [MCP Protocol](/docs/mcp-protocol) for the full catalog and auth model this opts into.
|
|
81
|
+
|
|
82
|
+
## Require human approval {#needs-approval}
|
|
83
|
+
|
|
84
|
+
A handful of actions are too consequential to let the agent run on its own, like sending an email, charging a card, or deleting an account. Set `needsApproval: true` (or a predicate `(args, ctx) => boolean | Promise<boolean>`) on `defineAction()`, and the loop pauses before `run()`, requiring a human to approve that specific call. The default is **off**, and almost every action should stay that way.
|
|
85
|
+
|
|
86
|
+
See [Human-in-the-Loop Approvals](/docs/human-approval) for the predicate API, the `approval_required` event, and the full end-to-end flow.
|
|
87
|
+
|
|
88
|
+
## Restrict callers by role {#authorize}
|
|
89
|
+
|
|
90
|
+
`authorize` answers one specific question: **may this caller run this operation at all?** Give it a guard function, and it runs before `run()` on every caller: agent tool, HTTP, frontend, MCP, A2A, and CLI.
|
|
91
|
+
|
|
92
|
+
Declare the role vocabulary once with `defineAppRoles()`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// server/lib/access.ts
|
|
96
|
+
import { defineAppRoles } from "@agent-native/core/org-team";
|
|
97
|
+
|
|
98
|
+
export const coachAccess = defineAppRoles({
|
|
99
|
+
appId: "coach",
|
|
100
|
+
roles: ["member", "coach-admin"] as const,
|
|
101
|
+
defaultRole: "member", // display only, never satisfies a guard
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then guard the action with an explicit list of accepted roles:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// actions/archive-plan.ts
|
|
109
|
+
import { coachAccess } from "../server/lib/access.js";
|
|
110
|
+
|
|
111
|
+
export default defineAction({
|
|
112
|
+
description: "Archive a training plan.",
|
|
113
|
+
schema: z.object({ id: z.string() }),
|
|
114
|
+
authorize: coachAccess.requireAny("coach-admin"),
|
|
115
|
+
run: async ({ id }) => {
|
|
116
|
+
/* ... */
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The guard wraps `run()` itself, instead of attaching to some other part of the action. That's because `run()` is the one thing all six dispatch sites go through, so there is no way for a caller to reach the body without passing the check. A guard that throws denies the call with its own error message. A guard that returns `false` denies the call with a generic 403. Anything else, including `undefined`, allows the call. Because the check needs a caller identity, an unattended CLI or cron invocation with no user email is denied rather than let through.
|
|
122
|
+
|
|
123
|
+
The wrappers compose in this order:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
validate input -> authorize -> run -> validate output -> audit
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Authorization sits _inside_ input validation, so a guard that inspects `args` gets the same parsed, type-checked value that `run()` receives, not whatever raw shape the caller sent. Auditing is outermost, so a denial is still recorded.
|
|
130
|
+
|
|
131
|
+
`requireAny()` fails at definition time, not at request time. Calling it with no roles, or with a role outside the declared list, throws as soon as the module loads. That way a mistake, like `authorize: coachAccess.requireAny()` with a dropped argument, breaks right where it happened, instead of showing up later as an unexplained 403.
|
|
132
|
+
|
|
133
|
+
### Access control in actions {#access-control}
|
|
134
|
+
|
|
135
|
+
`authorize` only answers whether the caller may run the operation at all. It says nothing about which rows they may touch, that's a separate, row-level check. A table built with `ownableColumns()` and registered with `registerShareableResource()` (see [Sharing](/docs/sharing#building)) gets two helpers for this: `accessFilter()` scopes a list or read query to the rows the caller may see, and `assertAccess()` throws before a write if the caller doesn't hold the required role on that row.
|
|
136
|
+
|
|
137
|
+
Use `accessFilter()` inside a list or read action's query:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// actions/list-decks.ts
|
|
141
|
+
import { defineAction } from "@agent-native/core/action";
|
|
142
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
143
|
+
import { z } from "zod";
|
|
144
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
145
|
+
|
|
146
|
+
export default defineAction({
|
|
147
|
+
description: "List the decks the current user can see.",
|
|
148
|
+
schema: z.object({}),
|
|
149
|
+
http: { method: "GET" },
|
|
150
|
+
run: async () => {
|
|
151
|
+
const db = getDb();
|
|
152
|
+
return await db
|
|
153
|
+
.select()
|
|
154
|
+
.from(schema.decks)
|
|
155
|
+
.where(accessFilter(schema.decks, schema.deckShares));
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use `assertAccess()` before an update or delete, since a `WHERE` clause alone can't stop a write to a row it excludes:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// actions/rename-deck.ts
|
|
164
|
+
import { defineAction } from "@agent-native/core/action";
|
|
165
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
166
|
+
import { eq } from "drizzle-orm";
|
|
167
|
+
import { z } from "zod";
|
|
168
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
169
|
+
|
|
170
|
+
export default defineAction({
|
|
171
|
+
description: "Rename a deck.",
|
|
172
|
+
schema: z.object({ id: z.string(), title: z.string() }),
|
|
173
|
+
run: async ({ id, title }) => {
|
|
174
|
+
await assertAccess("deck", id, "editor");
|
|
175
|
+
const db = getDb();
|
|
176
|
+
await db.update(schema.decks).set({ title }).where(eq(schema.decks.id, id));
|
|
177
|
+
return { ok: true };
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
A create action calls neither helper. It only needs to stamp `ownerEmail` (and `orgId`) from the request context when inserting the new row, since there's no existing row yet to check access against. See [Sharing](/docs/sharing#building) for the full registration and create pattern, and [Run Context](/docs/actions-run-context) for the full shape of the `ctx` second argument used above.
|
|
183
|
+
|
|
184
|
+
The three mechanisms are not interchangeable:
|
|
185
|
+
|
|
186
|
+
| Mechanism | Question it answers | Applies to |
|
|
187
|
+
| --------------------------------- | ---------------------------------------------------- | -------------------------------- |
|
|
188
|
+
| `authorize` | May this caller perform the operation at all? | All six dispatch sites |
|
|
189
|
+
| `needsApproval` | Should a human approve this specific permitted call? | The agent loop only |
|
|
190
|
+
| `accessFilter()`/`assertAccess()` | Which rows may this permitted caller see or write? | Inside `run()`, per query or row |
|
|
191
|
+
|
|
192
|
+
A restricted write action typically needs both `authorize` and `assertAccess()`. The first keeps non-coaches out of the operation. The second keeps a coach out of another org's plan. `needsApproval` is a separate axis. It never restricts who may call, only whether the agent may proceed unattended.
|
|
193
|
+
|
|
194
|
+
`coachAccess` above comes from `defineAppRoles()`, the per-app role overlay on org membership. See [Organizations, Teams & Permissions → App roles](/docs/organizations-teams-permissions#app-roles-per-app-rbac) for the role model and its guarantees.
|
|
195
|
+
|
|
196
|
+
## Audit logging {#audit}
|
|
197
|
+
|
|
198
|
+
Every mutating action is audited automatically. The framework records who ran it, when, from which surface, and, when it was the agent, which thread and turn, with credential-redacted inputs. Read-only (`GET`) actions are skipped. You don't write any code for this. It happens automatically inside `defineAction()`.
|
|
199
|
+
|
|
200
|
+
Add an `audit` block only to fine-tune capture. The most common reason is to declare the resource the action changed, so the change shows up in that resource owner's trail:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
export default defineAction({
|
|
204
|
+
description: "Delete a recording.",
|
|
205
|
+
schema: z.object({ id: z.string() }),
|
|
206
|
+
audit: {
|
|
207
|
+
target: (args, result) => ({ type: "recording", id: args.id }),
|
|
208
|
+
summary: (args) => `Deleted recording ${args.id}`,
|
|
209
|
+
},
|
|
210
|
+
run: async (args, ctx) => {
|
|
211
|
+
/* ...delete... */
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
A few other options. `audit: { onRead: true }` audits a sensitive read, like a secret access or bulk export. `audit: { enabled: false }` opts a noisy write out. `audit: { recordInputs: false }` skips capturing arguments. Read the trail back with the built-in `list-audit-events` and `get-audit-event` actions. See [Audit Log](/docs/audit-log) for full details.
|
|
217
|
+
|
|
218
|
+
## What's next
|
|
219
|
+
|
|
220
|
+
- [**Overview**](/docs/actions-overview) — what an action is and the hello-world walkthrough
|
|
221
|
+
- [**Defining Actions**](/docs/actions-defining) — `defineAction()` anatomy, schema, and HTTP config
|
|
222
|
+
- [**Run Context**](/docs/actions-run-context) — the `ctx` argument these checks read
|
|
223
|
+
- [**Human-in-the-Loop Approvals**](/docs/human-approval) — the `needsApproval` gate in depth
|
|
224
|
+
- [**Audit Log**](/docs/audit-log) — the automatic who-changed-what trail
|
|
225
|
+
- [**Security**](/docs/security) — `accessFilter`/`assertAccess` and the extension iframe trust boundary
|
|
226
|
+
- [**Sharing**](/docs/sharing) — the framework sharing system these helpers back
|