@agent-native/core 0.164.11 → 0.164.15
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/calendar/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +25 -2
- package/corpus/templates/content/actions/_database-utils.ts +2 -0
- package/corpus/templates/content/actions/_local-folder-source.ts +186 -2
- package/corpus/templates/content/actions/connect-local-folder-source.ts +23 -8
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +3 -3
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +2 -2
- package/corpus/templates/content/actions/sync-local-folder-source.ts +130 -12
- package/corpus/templates/content/app/components/LocalFolderLiveSync.tsx +77 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +354 -8
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +23 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +78 -1
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +36 -0
- package/corpus/templates/content/app/components/editor/local-source-write-state.ts +52 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +244 -6
- package/corpus/templates/content/app/components/sidebar/local-source-hierarchy.ts +189 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +26 -4
- package/corpus/templates/content/app/hooks/use-documents.ts +83 -0
- package/corpus/templates/content/app/i18n-data.ts +79 -0
- package/corpus/templates/content/app/lib/desktop-content-files.ts +57 -1
- package/corpus/templates/content/app/lib/local-content-source-files.ts +210 -34
- package/corpus/templates/content/app/lib/local-folder-live-sync.ts +191 -0
- package/corpus/templates/content/app/root.tsx +2 -1
- package/corpus/templates/content/app/routes/_app.local-files.tsx +103 -38
- package/corpus/templates/content/docs/product/capabilities/content.source.file-folder.md +2 -2
- package/corpus/templates/content/docs/product/features/content.feature.bring-your-local-work.md +3 -3
- package/corpus/templates/content/docs/product/roadmap.md +2 -2
- package/corpus/templates/content/docs/solutions/2026-08-18-content-navigation-coherence-shape.md +482 -0
- package/corpus/templates/content/shared/api.ts +25 -0
- package/corpus/templates/factory/AGENTS.md +2 -1
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +43 -0
- package/corpus/templates/factory/app/routes/factory.tsx +115 -15
- package/corpus/templates/factory/server/plugins/agent-chat.ts +3 -1
- package/dist/app-config/auth.d.ts +4 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-mcp-config.d.ts +2 -2
- package/dist/cli/code-agent-mcp-config.js +34 -9
- package/dist/client/org/OrgSwitcher.js +8 -4
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp-client/config.d.ts +11 -0
- package/dist/mcp-client/config.js +33 -26
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +15 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +1 -1
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -96,39 +96,46 @@ export function loadMcpConfig(startDir) {
|
|
|
96
96
|
* the explicit child gate, loopback URL, and strong per-run bearer are all
|
|
97
97
|
* required. Existing user servers always win on key collisions.
|
|
98
98
|
*/
|
|
99
|
-
function
|
|
100
|
-
if (
|
|
101
|
-
return
|
|
102
|
-
const url =
|
|
103
|
-
const token =
|
|
99
|
+
export function resolveDesktopChildComputerMcpServer(existingServers, environment = process.env) {
|
|
100
|
+
if (environment.AGENT_NATIVE_DESKTOP_CHILD !== "1")
|
|
101
|
+
return null;
|
|
102
|
+
const url = environment.AGENT_NATIVE_DESKTOP_COMPUTER_MCP_URL?.trim();
|
|
103
|
+
const token = environment.AGENT_NATIVE_DESKTOP_COMPUTER_MCP_TOKEN?.trim();
|
|
104
104
|
if (!url || !token || !/^[A-Za-z0-9_-]{32,}$/.test(token))
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return base;
|
|
105
|
+
return null;
|
|
106
|
+
if (!URL.canParse(url))
|
|
107
|
+
return null;
|
|
108
|
+
const parsed = new URL(url);
|
|
109
|
+
if (parsed.protocol !== "http:" ||
|
|
110
|
+
parsed.hostname !== "127.0.0.1" ||
|
|
111
|
+
parsed.pathname !== "/mcp" ||
|
|
112
|
+
parsed.username ||
|
|
113
|
+
parsed.password) {
|
|
114
|
+
return null;
|
|
118
115
|
}
|
|
119
|
-
const servers = { ...(base?.servers ?? {}) };
|
|
120
116
|
let serverId = DESKTOP_COMPUTER_SERVER_ID;
|
|
121
|
-
for (let suffix = 2;
|
|
117
|
+
for (let suffix = 2; existingServers[serverId]; suffix += 1) {
|
|
122
118
|
serverId = `${DESKTOP_COMPUTER_SERVER_ID}-${suffix}`;
|
|
123
119
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
return {
|
|
121
|
+
id: serverId,
|
|
122
|
+
config: {
|
|
123
|
+
type: "http",
|
|
124
|
+
url,
|
|
125
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
126
|
+
description: "Authenticated computer control for this Agent Native desktop task",
|
|
127
|
+
},
|
|
129
128
|
};
|
|
129
|
+
}
|
|
130
|
+
function mergeDesktopChildComputerConfig(base) {
|
|
131
|
+
const desktopServer = resolveDesktopChildComputerMcpServer(base?.servers ?? {});
|
|
132
|
+
if (!desktopServer)
|
|
133
|
+
return base;
|
|
130
134
|
return {
|
|
131
|
-
servers
|
|
135
|
+
servers: {
|
|
136
|
+
...(base?.servers ?? {}),
|
|
137
|
+
[desktopServer.id]: desktopServer.config,
|
|
138
|
+
},
|
|
132
139
|
source: base?.source ? `${base.source}+desktop-child` : "desktop-child",
|
|
133
140
|
};
|
|
134
141
|
}
|
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
* DELETE /_agent-native/notifications/:id — delete
|
|
12
12
|
*/
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
|
+
error?: undefined;
|
|
14
15
|
count: number;
|
|
15
16
|
updated?: undefined;
|
|
16
17
|
ok?: undefined;
|
|
17
|
-
error?: undefined;
|
|
18
18
|
} | {
|
|
19
|
+
error?: undefined;
|
|
19
20
|
count?: undefined;
|
|
20
21
|
updated: number;
|
|
21
22
|
ok?: undefined;
|
|
22
|
-
error?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
|
26
26
|
error: string;
|
|
27
27
|
ok?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
error?: undefined;
|
|
29
30
|
count?: undefined;
|
|
30
31
|
updated?: undefined;
|
|
31
32
|
ok: boolean;
|
|
32
|
-
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -42,22 +42,22 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
44
|
error?: undefined;
|
|
45
|
+
ok?: undefined;
|
|
45
46
|
summary: import("./types.js").TraceSummary;
|
|
46
47
|
spans: import("./types.js").TraceSpan[];
|
|
47
48
|
id?: undefined;
|
|
48
|
-
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
50
|
error?: undefined;
|
|
51
|
+
ok?: undefined;
|
|
51
52
|
summary?: undefined;
|
|
52
53
|
spans?: undefined;
|
|
53
54
|
id: string;
|
|
54
|
-
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
|
+
ok?: undefined;
|
|
56
57
|
summary?: undefined;
|
|
57
58
|
spans?: undefined;
|
|
58
59
|
id?: undefined;
|
|
59
60
|
error: any;
|
|
60
|
-
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
62
|
error?: undefined;
|
|
63
63
|
summary?: undefined;
|
|
@@ -51,8 +51,8 @@ export declare function handleDeleteResource(event: any): Promise<{
|
|
|
51
51
|
error: string;
|
|
52
52
|
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
|
-
ok: boolean;
|
|
55
54
|
error?: undefined;
|
|
55
|
+
ok: boolean;
|
|
56
56
|
}>;
|
|
57
57
|
/** POST /_agent-native/resources/upload — upload a file as a resource */
|
|
58
58
|
export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
|
|
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
|
|
|
73
73
|
runId: string | null;
|
|
74
74
|
expiresAt: number | null;
|
|
75
75
|
metadata: string | null;
|
|
76
|
+
error?: undefined;
|
|
76
77
|
url: string;
|
|
77
78
|
provider: string;
|
|
78
79
|
storageSetupRequired?: undefined;
|
|
79
|
-
error?: undefined;
|
|
80
80
|
} | {
|
|
81
81
|
error: string;
|
|
82
82
|
storageSetupRequired: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
ok?: undefined;
|
|
38
37
|
status?: undefined;
|
|
38
|
+
ok?: undefined;
|
|
39
39
|
} | {
|
|
40
|
-
error?: undefined;
|
|
41
40
|
ok: boolean;
|
|
42
41
|
status: string;
|
|
42
|
+
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
44
|
error: string;
|
|
46
45
|
removed?: undefined;
|
|
46
|
+
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
error?: undefined;
|
|
49
48
|
ok: boolean;
|
|
50
49
|
removed: boolean;
|
|
50
|
+
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
54
54
|
* or the current stored value without changing anything.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
-
ok?: undefined;
|
|
58
57
|
error: string;
|
|
59
58
|
note?: undefined;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
error?: undefined;
|
|
62
61
|
ok: boolean;
|
|
63
62
|
note?: undefined;
|
|
64
|
-
} | {
|
|
65
63
|
error?: undefined;
|
|
64
|
+
} | {
|
|
66
65
|
ok: boolean;
|
|
67
66
|
note: string;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
-
error?: undefined;
|
|
99
98
|
ok: boolean;
|
|
100
99
|
key: string;
|
|
101
|
-
} | {
|
|
102
100
|
error?: undefined;
|
|
101
|
+
} | {
|
|
103
102
|
ok: boolean;
|
|
104
103
|
removed: boolean;
|
|
104
|
+
error?: undefined;
|
|
105
105
|
}>>;
|
|
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
27
27
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
28
28
|
error: any;
|
|
29
29
|
} | {
|
|
30
|
-
error?: undefined;
|
|
31
30
|
ok: boolean;
|
|
32
31
|
key: string;
|
|
33
32
|
baseUrlKey?: string;
|
|
34
33
|
scope: AgentEngineApiKeyScope;
|
|
34
|
+
error?: undefined;
|
|
35
35
|
}>>;
|
|
36
36
|
export {};
|
package/dist/server/auth.js
CHANGED
|
@@ -405,6 +405,8 @@ export function isLoopbackRequest(event) {
|
|
|
405
405
|
async function readDesktopSsoSafely(event) {
|
|
406
406
|
if (process.env.NODE_ENV === "production")
|
|
407
407
|
return null;
|
|
408
|
+
if (getAppConfig().auth.disableDesktopSsoFallbackInDevelopment)
|
|
409
|
+
return null;
|
|
408
410
|
if (!isElectronRequest(event))
|
|
409
411
|
return null;
|
|
410
412
|
if (!isLoopbackRequest(event))
|
|
@@ -1242,7 +1244,7 @@ export function prepareDesktopOAuthBrowserBinding(event) {
|
|
|
1242
1244
|
if (!binding || !/^[A-Za-z0-9_-]{43}$/.test(binding)) {
|
|
1243
1245
|
binding = crypto.randomBytes(32).toString("base64url");
|
|
1244
1246
|
setCookie(event, DESKTOP_OAUTH_BROWSER_BINDING_COOKIE, binding, {
|
|
1245
|
-
...
|
|
1247
|
+
...desktopOAuthBrowserBindingCookieAttrs(event),
|
|
1246
1248
|
httpOnly: true,
|
|
1247
1249
|
path: "/",
|
|
1248
1250
|
maxAge: Math.floor(DESKTOP_EXCHANGE_TTL_MS / 1_000),
|
|
@@ -2810,6 +2812,18 @@ function crossSiteCookieAttrs(event) {
|
|
|
2810
2812
|
? { sameSite: "none", secure: true, partitioned: true }
|
|
2811
2813
|
: { sameSite: "lax", secure: false };
|
|
2812
2814
|
}
|
|
2815
|
+
/**
|
|
2816
|
+
* The binding cookie is set before navigating to Google and read after the
|
|
2817
|
+
* provider redirects back. A partitioned cookie uses the top-level site from
|
|
2818
|
+
* the bootstrap request, so it is unavailable when the callback starts from
|
|
2819
|
+
* Google's top-level site. Keep this host-scoped cookie unpartitioned while
|
|
2820
|
+
* retaining the cross-site and transport protections required by the flow.
|
|
2821
|
+
*/
|
|
2822
|
+
function desktopOAuthBrowserBindingCookieAttrs(event) {
|
|
2823
|
+
return isHttpsRequest(event)
|
|
2824
|
+
? { sameSite: "none", secure: true }
|
|
2825
|
+
: { sameSite: "lax", secure: false };
|
|
2826
|
+
}
|
|
2813
2827
|
function setFirstRunOnboardingCookie(event) {
|
|
2814
2828
|
setCookie(event, FIRST_RUN_ONBOARDING_COOKIE, "1", {
|
|
2815
2829
|
...crossSiteCookieAttrs(event),
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
error?: undefined;
|
|
29
30
|
token: string;
|
|
30
31
|
expiresAt: string;
|
|
31
32
|
ttlSeconds: number;
|
|
32
|
-
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -83,7 +83,141 @@ Use it only for safe, read-only, or ingest-only actions, the kind you'd let a st
|
|
|
83
83
|
|
|
84
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
85
|
|
|
86
|
-
|
|
86
|
+
<Callout tone="warning">
|
|
87
|
+
Keep approvals rare. Every gated action is a hard stop in the agent loop. It
|
|
88
|
+
interrupts the run and demands a human round-trip. Use `needsApproval` only
|
|
89
|
+
for genuinely high-consequence, hard-to-undo, outward-facing operations. If
|
|
90
|
+
you find yourself gating reads or routine writes, you're holding it wrong. The
|
|
91
|
+
default is **off**, and almost every action should leave it off.
|
|
92
|
+
</Callout>
|
|
93
|
+
|
|
94
|
+
<AnnotatedCode
|
|
95
|
+
id="doc-block-13p4g9y"
|
|
96
|
+
title="Gating the one consequential action"
|
|
97
|
+
filename="actions/send-email.ts"
|
|
98
|
+
language="ts"
|
|
99
|
+
code={
|
|
100
|
+
'export default defineAction({\n description: "Send an email via Gmail.",\n schema: z.object({\n to: z.string(),\n subject: z.string(),\n body: z.string(),\n }),\n // Sending is outward-facing and hard to undo, so the agent can never send\n // without a human approving the specific call. Drafting and queueing are\n // not affected. Only the real send is gated.\n needsApproval: true,\n run: async (args) => {\n /* ...actually send... */\n },\n});'
|
|
101
|
+
}
|
|
102
|
+
annotations={[
|
|
103
|
+
{
|
|
104
|
+
lines: "10",
|
|
105
|
+
label: "The whole gate",
|
|
106
|
+
note: "One flag. When it is truthy and the call has not been approved, the loop stops before `run`. The model never reaches the side effect on its own.",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
lines: "11-13",
|
|
110
|
+
label: "run() is untouched",
|
|
111
|
+
note: "The handler stays the same. Approval is enforced by the loop around it, not by anything inside `run`.",
|
|
112
|
+
},
|
|
113
|
+
]}
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
- **`needsApproval: true`** always requires approval.
|
|
117
|
+
- **`needsApproval: (args, ctx) => boolean | Promise<boolean>`** requires approval only when the predicate returns true. Use this to gate conditionally, for example only for external recipients or only above a dollar threshold:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
needsApproval: (args) => !args.to.endsWith("@your-company.com"),
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Keep the predicate pure and fast. If the predicate throws an error, the framework treats the call the same as if the predicate had returned true, so the action pauses for approval instead of running. This means a broken predicate can never silently let a high-consequence action run without a human approving it first.
|
|
124
|
+
|
|
125
|
+
When `needsApproval` is omitted, behavior is byte-for-byte unchanged. There is no extra cost on the common path.
|
|
126
|
+
|
|
127
|
+
This works the same way for legacy `parameters`-style actions and for schema-based actions. It also works the same way for the in-app agent, sub-agents, A2A, and MCP callers, because every agent surface routes through the same loop.
|
|
128
|
+
|
|
129
|
+
### How the loop pauses {#loop}
|
|
130
|
+
|
|
131
|
+
When the agent calls a gated action and this specific call has **not** already been approved, the loop does **not** execute `run()`. Instead it:
|
|
132
|
+
|
|
133
|
+
1. Resolves the gate. For a predicate, the loop calls `needsApproval(input, ctx)`. If that call throws an error, the loop treats it the same as a `true` result, so the action requires approval.
|
|
134
|
+
2. Emits a `tool_start` event (so the UI shows the call) followed immediately by an **`approval_required`** event, then stops the turn. The action's side effect never happens.
|
|
135
|
+
|
|
136
|
+
The `approval_required` event carries everything the client needs to render an affordance:
|
|
137
|
+
|
|
138
|
+
| Field | Type | Notes |
|
|
139
|
+
| ------------- | -------- | ------------------------------------------------------------------- |
|
|
140
|
+
| `tool` | `string` | The action name the agent tried to call. |
|
|
141
|
+
| `input` | object | The arguments the agent passed. |
|
|
142
|
+
| `approvalKey` | `string` | **Stable key** the client echoes back to approve _this exact call_. |
|
|
143
|
+
| `toolCallId` | `string` | The model-side tool-call id, when available. |
|
|
144
|
+
|
|
145
|
+
The `approvalKey` is derived deterministically from the tool name plus its input, so the same logical call always produces the same key. The model never sees or sets it. It exists purely as a handshake between the framework and the human's Approve affordance.
|
|
146
|
+
|
|
147
|
+
The paused tool returns a result telling the model the turn is paused and not to retry, so the model does not spin.
|
|
148
|
+
|
|
149
|
+
### How the human approves {#approve}
|
|
150
|
+
|
|
151
|
+
On `approval_required`, the chat UI renders an **Approve / Deny** affordance on the paused tool call. This is wired automatically in `AssistantChat`. You do not need to build it per template.
|
|
152
|
+
|
|
153
|
+
<WireframeBlock id="doc-block-humanapprovalwf1">
|
|
154
|
+
<Screen
|
|
155
|
+
surface="popover"
|
|
156
|
+
html={
|
|
157
|
+
"<div style='display:flex;flex-direction:column;box-sizing:border-box;padding:16px;gap:12px'><div class='wf-card' style='display:flex;flex-direction:column;gap:8px'><div style='display:flex;align-items:center;gap:8px'><span class='wf-pill warn'>Needs approval</span><strong>send-email</strong></div><div style='display:flex;flex-direction:column;gap:2px'><span class='wf-muted'>to: alex@acme.com</span><span class='wf-muted'>subject: Following up on the proposal</span></div><div style='display:flex;gap:8px'><button>Deny</button><button class='primary'>Approve</button></div></div></div>"
|
|
158
|
+
}
|
|
159
|
+
/>
|
|
160
|
+
</WireframeBlock>
|
|
161
|
+
|
|
162
|
+
- **Approve** re-issues the turn as an ordinary continuation message that carries the call's key in `approvedToolCalls: [approvalKey]`. On the re-issued turn, the gate sees the key in the approved set and lets that specific call run normally.
|
|
163
|
+
- **Deny** dismisses the affordance locally. Nothing is re-issued, so the action never runs.
|
|
164
|
+
|
|
165
|
+
`approvedToolCalls` is a field on the chat request (`AgentChatRequest.approvedToolCalls`). Keys that are not present in it stay paused. Approving one call never approves any other call. Because the key is content-addressed, an approval only authorizes that exact call with those exact arguments. If the model later proposes a different send, that becomes a new key and needs its own fresh approval.
|
|
166
|
+
|
|
167
|
+
### End-to-end {#flow}
|
|
168
|
+
|
|
169
|
+
<Diagram id="doc-block-2nor1g" title="The approval interrupt" summary={"A gated call pauses the turn before run() fires. Approval re-issues the turn carrying the call's key. Only then does the side effect happen."}>
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<div class="diagram-approve">
|
|
173
|
+
<div class="diagram-box" data-rough>Agent calls send-email</div>
|
|
174
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
175
|
+
<div class="diagram-panel warn" data-rough>
|
|
176
|
+
<strong>Gate truthy, call not yet approved</strong
|
|
177
|
+
><small class="diagram-muted"
|
|
178
|
+
>loop emits tool_start + approval_required { tool, input, approvalKey
|
|
179
|
+
}</small
|
|
180
|
+
><span class="diagram-pill warn">run() has not executed</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
183
|
+
<div class="diagram-box" data-rough>
|
|
184
|
+
Human clicks Approve in chat<br /><small class="diagram-muted"
|
|
185
|
+
>client re-issues the turn with approvedToolCalls: [approvalKey]</small
|
|
186
|
+
>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
189
|
+
<div class="diagram-panel ok" data-rough>
|
|
190
|
+
<span class="diagram-pill ok"
|
|
191
|
+
>Gate sees the key → run() executes → email sends</span
|
|
192
|
+
>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
.diagram-approve {
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 8px;
|
|
203
|
+
}
|
|
204
|
+
.diagram-approve .diagram-panel {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-direction: column;
|
|
207
|
+
gap: 6px;
|
|
208
|
+
align-items: center;
|
|
209
|
+
padding: 12px 16px;
|
|
210
|
+
text-align: center;
|
|
211
|
+
}
|
|
212
|
+
.diagram-approve .diagram-arrow {
|
|
213
|
+
font-size: 22px;
|
|
214
|
+
line-height: 1;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
</Diagram>
|
|
219
|
+
|
|
220
|
+
The Mail template's `send-email` action is the canonical use of this gate in the framework, and it is intentionally rare. It sets `needsApproval: true` so the agent can draft and queue emails freely. It can never actually send a message without a human approving that specific send.
|
|
87
221
|
|
|
88
222
|
## Restrict callers by role {#authorize}
|
|
89
223
|
|
|
@@ -220,7 +354,7 @@ A few other options. `audit: { onRead: true }` audits a sensitive read, like a s
|
|
|
220
354
|
- [**Overview**](/docs/actions-overview) — what an action is and the hello-world walkthrough
|
|
221
355
|
- [**Defining Actions**](/docs/actions-defining) — `defineAction()` anatomy, schema, and HTTP config
|
|
222
356
|
- [**Run Context**](/docs/actions-run-context) — the `ctx` argument these checks read
|
|
223
|
-
- [**
|
|
357
|
+
- [**Mail template**](/docs/template-mail) — `send-email` is the reference example for `needsApproval`
|
|
224
358
|
- [**Audit Log**](/docs/audit-log) — the automatic who-changed-what trail
|
|
225
359
|
- [**Security**](/docs/security) — `accessFilter`/`assertAccess` and the extension iframe trust boundary
|
|
226
360
|
- [**Sharing**](/docs/sharing) — the framework sharing system these helpers back
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Production Agent Access"
|
|
3
|
+
description: "Control which framework tools and code-execution capabilities a deployed production agent has, through frameworkTools and codeExecution."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Production Agent Access
|
|
7
|
+
|
|
8
|
+
## Production Agent Tools {#production-agent-tools}
|
|
9
|
+
|
|
10
|
+
Production agents get the app's registered actions plus the framework's own
|
|
11
|
+
tools. `frameworkTools` selects which of those framework tools this app exposes.
|
|
12
|
+
Every group defaults to today's behavior, so omitting the option changes
|
|
13
|
+
nothing:
|
|
14
|
+
|
|
15
|
+
```ts filename="server/plugins/agent-chat.ts"
|
|
16
|
+
export default createAgentChatPlugin({
|
|
17
|
+
frameworkTools: {
|
|
18
|
+
database: "read", // "write" | "read" | "off", default "read"
|
|
19
|
+
extensions: true, // default false
|
|
20
|
+
sharing: false, // this app has no share surface
|
|
21
|
+
review: false,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```ts filename="server/plugins/core-routes.ts"
|
|
27
|
+
export default createCoreRoutesPlugin({
|
|
28
|
+
extensionTools: true,
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`database` and `extensions` accept the values the old top-level flags did:
|
|
33
|
+
|
|
34
|
+
- `database: "read"`. Default. Registers only `db-schema` and `db-query`.
|
|
35
|
+
Agents inspect data with SQL but must use typed app actions for writes.
|
|
36
|
+
- `database: "write"` or `true`. Additionally registers `db-exec` and
|
|
37
|
+
`db-patch` for deliberate raw SQL maintenance. Writes are scoped to the
|
|
38
|
+
current user/org and schema changes are blocked.
|
|
39
|
+
- `database: "off"` or `false`. Removes raw database tools from the
|
|
40
|
+
agent surface so the app's actions are the only data access path.
|
|
41
|
+
- `extensions: true`. Opts the app into framework extension-management
|
|
42
|
+
actions and prompt guidance (`create-extension`, `update-extension`, etc.).
|
|
43
|
+
Set `extensionTools: true` on the core-routes plugin as well to allow
|
|
44
|
+
authenticated REST creation. Extensions are disabled by default.
|
|
45
|
+
|
|
46
|
+
The remaining groups are booleans, all on by default: `sharing`, `review`,
|
|
47
|
+
`history`, `featureFlags`, `localization`, `audit`, `contextXray`,
|
|
48
|
+
`userProfile`, `automation`, `docs`, `resources`, `web`, `workspaceApps`,
|
|
49
|
+
`chat`, and `email`. Use `frameworkTools: "minimal"` to turn every group off at
|
|
50
|
+
once for a voice-first or single-purpose app, or
|
|
51
|
+
`frameworkTools: { preset: "minimal", resources: true }` to keep just one. An
|
|
52
|
+
explicit group key always wins over the preset.
|
|
53
|
+
|
|
54
|
+
Switching a group off removes it from the **agent** surfaces only: interactive
|
|
55
|
+
chat, MCP, A2A, and background runs. The matching HTTP action routes stay
|
|
56
|
+
mounted, because the UI reaches them through client hooks. Disabling `sharing`
|
|
57
|
+
must not 404 a share dialog that is still on screen. Weigh that parity cost
|
|
58
|
+
before using it: the framework's contract is that anything the UI can do the
|
|
59
|
+
agent can do, so `sharing: false` alongside a visible share button is a
|
|
60
|
+
deliberate break, and only makes sense when the app has no such surface at all.
|
|
61
|
+
|
|
62
|
+
Framework tools are no longer promoted into the first model request by default,
|
|
63
|
+
independent of this option. They stay in the searchable registry and load
|
|
64
|
+
through `tool-search`, or immediately if the app names them in
|
|
65
|
+
`initialToolNames`.
|
|
66
|
+
|
|
67
|
+
<Callout tone="warning">
|
|
68
|
+
The top-level `databaseTools` and `extensionTools` options are deprecated in
|
|
69
|
+
favor of `frameworkTools.database` and `frameworkTools.extensions`. They are
|
|
70
|
+
still honored, but setting a top-level flag and its `frameworkTools`
|
|
71
|
+
equivalent to conflicting values throws at plugin startup rather than booting
|
|
72
|
+
with a tool surface nobody chose.
|
|
73
|
+
</Callout>
|
|
74
|
+
|
|
75
|
+
## Production Code Execution {#production-code-execution}
|
|
76
|
+
|
|
77
|
+
By default, production agents run without code-execution tools. They can call app actions, database tools, MCP tools, browser/session tools, and other registered framework tools, but they do not get shell or filesystem access.
|
|
78
|
+
|
|
79
|
+
Node-compatible deployments can opt into production code execution through the agent chat plugin or an environment override:
|
|
80
|
+
|
|
81
|
+
```ts filename="server/plugins/agent-chat.ts"
|
|
82
|
+
export default createAgentChatPlugin({
|
|
83
|
+
codeExecution: { production: "sandboxed" },
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The available modes are:
|
|
88
|
+
|
|
89
|
+
- `off`. The default. No code-execution tools are registered in production.
|
|
90
|
+
- `sandboxed`. Registers `run-code`, an isolated Node.js JavaScript runner with a scrubbed environment, a fresh temp directory, output/time limits, and a localhost bridge to allowlisted registered tools such as `provider-api-request`, `provider-api-docs`, `provider-api-catalog`, `web-request`, and the Resources-backed workspace file bridge used by `workspaceRead` / `workspaceWrite`.
|
|
91
|
+
- `trusted`. Registers `run-code` plus the full coding tool registry (`bash`, `read`, `edit`, `write`). Use this only for single-tenant or operator-controlled deployments where full shell access to the host is intentional.
|
|
92
|
+
|
|
93
|
+
Set `AGENT_PROD_CODE_EXECUTION=sandboxed` or `AGENT_PROD_CODE_EXECUTION=trusted` to override the plugin option for a specific deployment without a code change. `AGENT_PROD_CODE_EXECUTION=off` forces code execution off even when the plugin option enables it.
|
|
94
|
+
|
|
95
|
+
The `run-code` sandbox is process-level isolation, not an OS container. It strips app secrets from the child process environment and uses the Node permission model when available, but outbound network is not blocked by Node itself. Authenticated calls should go through the bridge helpers the tool exposes.
|
|
96
|
+
|
|
97
|
+
## What's next
|
|
98
|
+
|
|
99
|
+
- [**Actions: Access & Authorization**](/docs/actions-access-control) — the per-action access-control axes this page's tool-level gating complements
|
|
100
|
+
- [**Updating UI in Production**](/docs/updating-ui-in-production) — what a deployed agent can and can't edit with code execution left off
|
|
101
|
+
- [**Deployment: Environment Variables**](/docs/deployment-environment-variables) — `AGENT_PROD_CODE_EXECUTION` and other production env vars
|
|
@@ -29,7 +29,7 @@ Every action starts with a call to `defineAction()`. It bundles a description fo
|
|
|
29
29
|
{
|
|
30
30
|
lines: "12-16",
|
|
31
31
|
label: "One implementation",
|
|
32
|
-
note: "The `run` body is the single source of truth. The UI button and the agent tool both execute exactly this. `getDb()` and `schema` come from your app's `server/db/index.ts`. See [Database](/docs/database).",
|
|
32
|
+
note: "The `run` body is the single source of truth. The UI button and the agent tool both execute exactly this. `getDb()` and `schema` come from your app's `server/db/index.ts`. See [Database](/docs/server-database).",
|
|
33
33
|
},
|
|
34
34
|
]}
|
|
35
35
|
/>
|
|
@@ -271,5 +271,5 @@ The repo includes an advisory helper, `node scripts/audit-template-actions.mjs [
|
|
|
271
271
|
- [**Access & Authorization**](/docs/actions-access-control) — `agentTool`, `toolCallable`, `authorize`, and `ctx` scoping
|
|
272
272
|
- [**Run Context**](/docs/actions-run-context) — the full `ctx` field table
|
|
273
273
|
- [**Other Surfaces**](/docs/actions-other-surfaces) — native chat UI, CLI, A2A, MCP, and standard actions
|
|
274
|
-
- [**Database**](/docs/database) — `getDb()`/`schema` used inside `run()`
|
|
274
|
+
- [**Database**](/docs/server-database) — `getDb()`/`schema` used inside `run()`
|
|
275
275
|
- [**Data & Sync**](/docs/client-data) — calling this action from the UI
|
|
@@ -183,7 +183,7 @@ Configure via environment:
|
|
|
183
183
|
## What's next
|
|
184
184
|
|
|
185
185
|
- [**Actions**](/docs/actions-overview) — the `defineAction` seam where capture happens
|
|
186
|
-
- [**Human-in-the-Loop Approvals**](/docs/
|
|
186
|
+
- [**Human-in-the-Loop Approvals**](/docs/actions-access-control#needs-approval) — gated actions, recorded as `denied`
|
|
187
187
|
- [**Security & Data Scoping**](/docs/security) — the ownership model audit reads reuse
|
|
188
188
|
- [**Observability**](/docs/observability) — agent-run telemetry (the other "what happened")
|
|
189
189
|
- [**Toolkit piece: History Kit**](/docs/toolkit-history) — the version-history timeline and undo/redo UI built on this trail
|
|
@@ -456,7 +456,7 @@ function SignInCta() {
|
|
|
456
456
|
|
|
457
457
|
Normal app pages use one impersonal, public-cacheable SSR shell for every visitor. `AppProviders` checks the session on the client; on a private path such as `/dashboard`, it redirects an anonymous visitor to `/sign-in?return=%2Fdashboard` before mounting private app UI. After sign-in, the visitor returns to `/dashboard`. Pass `isPublicPath` for public/SEO routes, or `sessionBypass` for a surface that authenticates through another scoped mechanism such as an MCP embed token.
|
|
458
458
|
|
|
459
|
-
How long that shell is cached is a deployment-wide setting — see [SSR Caching](/docs/
|
|
459
|
+
How long that shell is cached is a deployment-wide setting — see [SSR Caching](/docs/ssr-caching) for `AGENT_NATIVE_SSR_CACHE`. Shortening or disabling the cache changes duration only; SSR still renders the anonymous branch with cookies stripped.
|
|
460
460
|
|
|
461
461
|
### Behind the scenes: Google OAuth
|
|
462
462
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Cloudflare"
|
|
3
|
+
description: "Deploy an agent-native app to Cloudflare Pages or Cloudflare Workers with the matching Nitro preset."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cloudflare
|
|
7
|
+
|
|
8
|
+
## Cloudflare Pages {#cloudflare-pages}
|
|
9
|
+
|
|
10
|
+
```ts filename="vite.config.ts"
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
plugins: [agentNative({ nitro: { preset: "cloudflare_pages" } })],
|
|
13
|
+
});
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Cloudflare Workers {#cloudflare-workers}
|
|
17
|
+
|
|
18
|
+
Use Nitro's native module output for a Cloudflare Worker. This keeps SSR
|
|
19
|
+
enabled and emits the Worker entry point plus static assets under
|
|
20
|
+
`.output/server` and `.output/public`:
|
|
21
|
+
|
|
22
|
+
```ts filename="vite.config.ts"
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
plugins: [agentNative({ nitro: { preset: "cloudflare_module" } })],
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Build and deploy the generated Worker with Wrangler:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @agent-native/core@latest build
|
|
32
|
+
npx wrangler deploy --config .output/server/wrangler.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You can also select the preset explicitly with
|
|
36
|
+
`NITRO_PRESET=cloudflare_module`. Configure a durable external SQL database
|
|
37
|
+
before deploying. A Worker filesystem is not a persistent application store.
|
|
38
|
+
|
|
39
|
+
## What's next
|
|
40
|
+
|
|
41
|
+
- [**Deployment**](/docs/deployment) — pick a persistent database and a preset before your first production deploy
|
|
42
|
+
- [**Workspace Deployment**](/docs/workspace-deployment) — Cloudflare Pages is the default preset for the unified `deploy` command
|
|
43
|
+
- [**Node.js & Docker**](/docs/node-docker), [**Vercel**](/docs/vercel), [**Netlify**](/docs/netlify), and [**Other Platforms**](/docs/other-platforms) — the other Nitro presets
|
|
@@ -238,7 +238,7 @@ export default runMigrations(
|
|
|
238
238
|
);
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
-
Use the [Database](/docs/database) and [Security](/docs/security) docs before adding schemas that hold user or org data.
|
|
241
|
+
Use the [Database](/docs/server-database) and [Security](/docs/security) docs before adding schemas that hold user or org data.
|
|
242
242
|
|
|
243
243
|
## Define Operations As Actions {#actions}
|
|
244
244
|
|