@agent-native/core 0.141.4 → 0.141.6
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/data-querying/SKILL.md +13 -6
- package/corpus/templates/analytics/actions/get-first-party-analytics-health.ts +15 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/app/i18n-data.ts +328 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +66 -30
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-now-repair-automatically-after-.md +6 -0
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +342 -0
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +8 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +11 -29
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +55 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +117 -31
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +15 -9
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +5 -16
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +61 -0
- package/corpus/templates/analytics/server/plugins/db.ts +58 -187
- package/corpus/templates/analytics/server/routes/api/analytics-rollups/backfill.post.ts +53 -0
- package/corpus/templates/clips/changelog/2026-08-05-clips-overlays-now-use-clean-surfaces-without-clipped-shadow.md +6 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -2
- package/corpus/templates/clips/desktop/src/styles.css +3 -54
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -3
- package/corpus/templates/design/actions/get-design-system.ts +3 -1
- package/corpus/templates/design/changelog/2026-08-05-missing-design-systems-now-return-a-clear-not-found-response.md +6 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -2
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +5 -5
- package/corpus/templates/slides/changelog/2026-08-05-new-presentation-prompts-now-clearly-describe-presentation-g.md +6 -0
- package/dist/client/AssistantChat.js +1 -1
- package/dist/client/chat/run-recovery.d.ts +2 -1
- package/dist/client/chat/run-recovery.js +10 -3
- package/dist/client/error-format.d.ts +1 -0
- package/dist/client/error-format.js +4 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/docs/AGENTS.md +1 -1
- package/docs/content/actions.mdx +1 -1
- package/docs/content/agent-surfaces.mdx +1 -1
- package/docs/content/agents.mdx +9 -0
- package/docs/content/database.mdx +2 -2
- package/docs/content/drop-in-agent.mdx +1 -1
- package/docs/content/notifications.mdx +1 -1
- package/docs/content/routing.mdx +1 -1
- package/docs/content/server-database.mdx +437 -0
- package/docs/content/server-middleware.mdx +151 -0
- package/docs/content/server-overview.mdx +234 -0
- package/docs/content/server-plugins.mdx +147 -0
- package/docs/content/server-routes.mdx +194 -0
- package/package.json +1 -1
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +0 -6
- package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +0 -6
- package/docs/content/server.mdx +0 -469
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Server"
|
|
3
|
+
description: "The Nitro server layer in an agent-native app: what it's for, when to reach for custom routes, and how its pieces connect."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Server
|
|
7
|
+
|
|
8
|
+
The `server/` directory is the infrastructure layer of an agent-native app. It handles startup tasks, authentication, and the HTTP surfaces that actions don't cover. Most product behavior belongs in [Actions](/docs/actions). The server is what makes those actions available.
|
|
9
|
+
|
|
10
|
+
<Diagram id="doc-block-server-overview-1" title="How requests flow through the server" summary="Callers reach the Nitro server, which handles most requests through Actions. Middleware and Routes cover cases Actions can't. Both paths can query the database.">
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<div class="diagram-srv">
|
|
14
|
+
<div class="diagram-col">
|
|
15
|
+
<div class="diagram-node">Browser / UI</div>
|
|
16
|
+
<div class="diagram-node">Agent loop</div>
|
|
17
|
+
<div class="diagram-node">
|
|
18
|
+
External clients<br /><small class="diagram-muted"
|
|
19
|
+
>HTTP · MCP · A2A</small
|
|
20
|
+
>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">→</div>
|
|
24
|
+
<div class="diagram-panel" data-rough>
|
|
25
|
+
<strong>Nitro server</strong>
|
|
26
|
+
<div class="srv-path">
|
|
27
|
+
<span class="diagram-pill accent">Actions</span
|
|
28
|
+
><small class="diagram-muted">primary path</small>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="srv-or diagram-muted">or</div>
|
|
31
|
+
<div class="srv-path">
|
|
32
|
+
<span class="diagram-pill">Middleware</span
|
|
33
|
+
><span class="srv-inner-arrow diagram-muted" aria-hidden="true"
|
|
34
|
+
>→</span
|
|
35
|
+
><span class="diagram-pill">Routes</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="diagram-arrow diagram-muted" aria-hidden="true">→</div>
|
|
39
|
+
<div class="diagram-box" data-rough>
|
|
40
|
+
SQL database<br /><small class="diagram-muted">Drizzle</small>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
.diagram-srv {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 14px;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
}
|
|
52
|
+
.diagram-srv .diagram-col {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 10px;
|
|
56
|
+
}
|
|
57
|
+
.diagram-srv .diagram-panel {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
padding: 14px 16px;
|
|
62
|
+
}
|
|
63
|
+
.diagram-srv .diagram-arrow {
|
|
64
|
+
font-size: 22px;
|
|
65
|
+
line-height: 1;
|
|
66
|
+
}
|
|
67
|
+
.srv-path {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 8px;
|
|
71
|
+
}
|
|
72
|
+
.srv-or {
|
|
73
|
+
font-size: 11px;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
letter-spacing: 0.06em;
|
|
76
|
+
padding-left: 2px;
|
|
77
|
+
}
|
|
78
|
+
.srv-inner-arrow {
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
line-height: 1;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
</Diagram>
|
|
85
|
+
|
|
86
|
+
## Where the Server Fits {#role}
|
|
87
|
+
|
|
88
|
+
In a traditional web app, the server is where most logic lives. Business rules, data access, and presentation all happen inside route handlers. Agent-native apps work differently. Logic lives in actions, which the agent and UI share as equal partners. The server's job is coordination: run database migrations, configure authentication, mount the agent loop, and handle the narrow set of HTTP concerns that actions can't express on their own.
|
|
89
|
+
|
|
90
|
+
<Diagram id="doc-block-server-overview-2" title="Traditional server vs. Agent Native">
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<div class="diagram-cmp">
|
|
94
|
+
<div class="cmp-col">
|
|
95
|
+
<div class="cmp-label diagram-muted">Traditional</div>
|
|
96
|
+
<div class="diagram-node">Browser</div>
|
|
97
|
+
<div class="cmp-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
98
|
+
<div class="diagram-panel" data-rough>
|
|
99
|
+
<strong>Server</strong>
|
|
100
|
+
<small class="diagram-muted">logic lives here</small>
|
|
101
|
+
<div class="cmp-pills">
|
|
102
|
+
<span class="cmp-tag">Route handlers</span>
|
|
103
|
+
<span class="cmp-tag">Business logic</span>
|
|
104
|
+
<span class="cmp-tag">Data access</span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="cmp-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
108
|
+
<div class="diagram-box" data-rough>Database</div>
|
|
109
|
+
</div>
|
|
110
|
+
<div class="cmp-vs diagram-muted">vs</div>
|
|
111
|
+
<div class="cmp-col">
|
|
112
|
+
<div class="cmp-label diagram-muted">Agent Native</div>
|
|
113
|
+
<div class="cmp-callers">
|
|
114
|
+
<div class="diagram-node">Browser / UI</div>
|
|
115
|
+
<div class="diagram-node">Agent</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="cmp-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
118
|
+
<div class="diagram-panel" data-rough>
|
|
119
|
+
<strong>Server</strong>
|
|
120
|
+
<small class="diagram-muted">coordination only</small>
|
|
121
|
+
<div class="cmp-pills">
|
|
122
|
+
<span class="cmp-tag">Actions</span>
|
|
123
|
+
<span class="cmp-tag">Auth</span>
|
|
124
|
+
<span class="cmp-tag">Migrations</span>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="cmp-arrow diagram-muted" aria-hidden="true">↓</div>
|
|
128
|
+
<div class="diagram-box" data-rough>Database</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
.diagram-cmp {
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: flex-start;
|
|
137
|
+
gap: 28px;
|
|
138
|
+
flex-wrap: wrap;
|
|
139
|
+
}
|
|
140
|
+
.cmp-col {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 8px;
|
|
145
|
+
flex: 1;
|
|
146
|
+
min-width: 160px;
|
|
147
|
+
}
|
|
148
|
+
.cmp-label {
|
|
149
|
+
font-size: 11px;
|
|
150
|
+
text-transform: uppercase;
|
|
151
|
+
letter-spacing: 0.06em;
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
}
|
|
154
|
+
.cmp-callers {
|
|
155
|
+
display: flex;
|
|
156
|
+
gap: 8px;
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
}
|
|
160
|
+
.cmp-vs {
|
|
161
|
+
font-size: 13px;
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
padding-top: 52px;
|
|
164
|
+
}
|
|
165
|
+
.diagram-cmp .diagram-panel {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
gap: 6px;
|
|
169
|
+
padding: 12px 16px;
|
|
170
|
+
align-self: stretch;
|
|
171
|
+
align-items: flex-start;
|
|
172
|
+
}
|
|
173
|
+
.cmp-pills {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-wrap: wrap;
|
|
176
|
+
gap: 6px;
|
|
177
|
+
}
|
|
178
|
+
.cmp-tag {
|
|
179
|
+
font-size: 12px;
|
|
180
|
+
opacity: 0.7;
|
|
181
|
+
margin-right: 10px;
|
|
182
|
+
}
|
|
183
|
+
.cmp-arrow {
|
|
184
|
+
font-size: 18px;
|
|
185
|
+
line-height: 1;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
</Diagram>
|
|
190
|
+
|
|
191
|
+
Browser requests, the agent loop, and external API clients all route through the same Nitro server and read from the same SQL database. There is no separate agent backend or UI backend. The server layer is what exposes actions to the outside world.
|
|
192
|
+
|
|
193
|
+
## What's in the server/ directory {#server-directory}
|
|
194
|
+
|
|
195
|
+
These are the directories and files inside `server/` that every agent-native app ships. Each corresponds to a specific piece of the server layer: what it does, when to reach for it, and how it connects to the rest of the framework.
|
|
196
|
+
|
|
197
|
+
| Page | What it covers |
|
|
198
|
+
| ----------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
199
|
+
| **[Database](/docs/server-database)** | Schema definition with `table()` helpers, `getDb`, and ownable table patterns |
|
|
200
|
+
| **[Middleware](/docs/server-middleware)** | Auth guards in `server/middleware/` and why they run there instead of in a plugin |
|
|
201
|
+
| **[Plugins](/docs/server-plugins)** | The three startup plugins every app ships: db migrations, auth, and agent-chat |
|
|
202
|
+
| **[Routes](/docs/server-routes)** | File-based routes, the SSR catch-all, and scoping custom routes to the request user |
|
|
203
|
+
|
|
204
|
+
## Nitro {#nitro}
|
|
205
|
+
|
|
206
|
+
[Nitro](https://nitro.build) is the server toolkit that powers the agent-native HTTP layer. Most of what you'll configure in `server/` is Nitro: file-based routes, startup plugins, and middleware.
|
|
207
|
+
|
|
208
|
+
- **File-based routing**: the filename maps directly to an HTTP method and URL path, with no registration needed. `server/routes/api/health.get.ts` becomes `GET /api/health` automatically. Visit [Routes](/docs/server-routes) for more details.
|
|
209
|
+
- **Plugins**: code that runs once at server startup, before any request is served. Database migrations, auth configuration, and the agent-chat mount all happen in `server/plugins/`. Visit [Plugins](/docs/server-plugins) for more details.
|
|
210
|
+
- **Middleware**: code that runs on every incoming request before it reaches a route. The auth guard lives in `server/middleware/`. Visit [Middleware](/docs/server-middleware) for more details.
|
|
211
|
+
|
|
212
|
+
Nitro is also deployment-agnostic. The same app deploys to Node.js, Cloudflare Workers, Netlify, Vercel, and other runtimes with no code changes. Just set a preset. See [Deployment](/docs/deployment).
|
|
213
|
+
|
|
214
|
+
The underlying request/response API is [H3](https://h3.unjs.io). When you see `defineEventHandler`, `createError`, or `getQuery` in custom routes, that's H3. The [Nitro docs](https://nitro.build/guide) and [H3 docs](https://h3.unjs.io) are the reference for anything the framework doesn't cover directly.
|
|
215
|
+
|
|
216
|
+
## Default to Actions first {#actions-first}
|
|
217
|
+
|
|
218
|
+
Agent Native architecture is different. Whenever you think of a new feature for your application, consider first whether it can be an action. For example, if the UI and agent both need to do something, define an action instead of a custom API route. Actions automatically become:
|
|
219
|
+
|
|
220
|
+
- Agent tools.
|
|
221
|
+
- Typed frontend hooks.
|
|
222
|
+
- HTTP endpoints under `/_agent-native/actions/:name`.
|
|
223
|
+
- MCP and A2A-callable tools.
|
|
224
|
+
- CLI commands for development.
|
|
225
|
+
|
|
226
|
+
Use custom `/api/*` routes only when you need a route-shaped protocol or binary/streaming behavior. For more details, visit [Actions](/docs/actions).
|
|
227
|
+
|
|
228
|
+
## What's next {#whats-next}
|
|
229
|
+
|
|
230
|
+
- [**Actions**](/docs/actions): the default operation surface. Reach for a custom route only when it doesn't fit.
|
|
231
|
+
- [**Database**](/docs/server-database): the `getDb()`/schema pattern custom routes and actions both import
|
|
232
|
+
- [**Middleware**](/docs/server-middleware): auth guards and the `server/middleware/` directory
|
|
233
|
+
- [**Plugins**](/docs/server-plugins): startup plugins for migrations, auth, and the agent
|
|
234
|
+
- [**Routes**](/docs/server-routes): file-based routes, request context, and the SSR catch-all
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Plugins"
|
|
3
|
+
description: "Startup plugins in server/plugins/ — what they are, the two plugins every app ships, the optional database plugin, and how to write your own."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plugins
|
|
7
|
+
|
|
8
|
+
Plugins are startup hooks. Each file in `server/plugins/` exports a function that Nitro calls once when the server initializes, before any route is served. They are the right place for one-time setup: configuring auth, mounting the agent loop, running database migrations, or registering any other initialization code that needs to complete before requests arrive.
|
|
9
|
+
|
|
10
|
+
Nitro discovers plugins automatically from the filesystem. No registration or imports are required. Files run in alphabetical order, so execution order is controlled by filename prefix.
|
|
11
|
+
|
|
12
|
+
Every agent-native app ships two plugins by default:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
server/plugins/
|
|
16
|
+
auth.ts -> configure auth provider and public paths
|
|
17
|
+
agent-chat.ts -> mount the agent loop with your actions and system prompt
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Apps that need custom database tables add a third:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
server/plugins/
|
|
24
|
+
db.ts -> run migrations on startup
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Auth Plugin {#auth-plugin}
|
|
28
|
+
|
|
29
|
+
`server/plugins/auth.ts` configures how users sign in. `createAuthPlugin` from `@agent-native/core/server` wires up the auth provider and sets the sign-in page branding:
|
|
30
|
+
|
|
31
|
+
```ts filename="server/plugins/auth.ts"
|
|
32
|
+
import { createAuthPlugin } from "@agent-native/core/server";
|
|
33
|
+
|
|
34
|
+
export default createAuthPlugin({
|
|
35
|
+
marketing: {
|
|
36
|
+
appName: "My App",
|
|
37
|
+
tagline: "A short description shown on the sign-in page.",
|
|
38
|
+
features: ["Feature one", "Feature two", "Feature three"],
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
To open specific routes to unauthenticated users, pass `publicPaths`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
export default createAuthPlugin({
|
|
47
|
+
publicPaths: ["/public", "/api/webhook"],
|
|
48
|
+
marketing: { appName: "My App", tagline: "..." },
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The auth plugin configures the guard. The middleware in `server/middleware/auth.ts` enforces it on every request. See [Middleware](/docs/server-middleware).
|
|
53
|
+
|
|
54
|
+
## Agent-Chat Plugin {#agent-chat-plugin}
|
|
55
|
+
|
|
56
|
+
`server/plugins/agent-chat.ts` mounts the agent loop with your app's actions, system prompt, and initial tool set. This is the plugin that makes your app an agent-native app.
|
|
57
|
+
|
|
58
|
+
```ts filename="server/plugins/agent-chat.ts"
|
|
59
|
+
import { getOrgContext } from "@agent-native/core/org";
|
|
60
|
+
import {
|
|
61
|
+
createAgentChatPlugin,
|
|
62
|
+
loadActionsFromStaticRegistry,
|
|
63
|
+
} from "@agent-native/core/server";
|
|
64
|
+
|
|
65
|
+
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
66
|
+
|
|
67
|
+
export default createAgentChatPlugin({
|
|
68
|
+
appId: "my-app",
|
|
69
|
+
actions: loadActionsFromStaticRegistry(actionsRegistry),
|
|
70
|
+
initialToolNames: ["view-screen", "navigate"],
|
|
71
|
+
resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
|
|
72
|
+
systemPrompt: `You are the My App agent.
|
|
73
|
+
|
|
74
|
+
Use actions as the source of truth. Start by inspecting the current
|
|
75
|
+
screen when context matters.`,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`loadActionsFromStaticRegistry` picks up every action exported from `actions/` through the generated registry. `initialToolNames` controls which actions the agent has access to at the start of a conversation. It can still call others as needed.
|
|
80
|
+
|
|
81
|
+
## Database Plugin {#database-plugin}
|
|
82
|
+
|
|
83
|
+
If your app defines custom database tables, add `server/plugins/db.ts` to run schema migrations at startup. It uses `runMigrations` from `@agent-native/core/db`:
|
|
84
|
+
|
|
85
|
+
```ts filename="server/plugins/db.ts"
|
|
86
|
+
import { runMigrations } from "@agent-native/core/db";
|
|
87
|
+
|
|
88
|
+
export default runMigrations(
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
version: 1,
|
|
92
|
+
name: "projects-table",
|
|
93
|
+
sql: `CREATE TABLE IF NOT EXISTS projects (
|
|
94
|
+
id TEXT PRIMARY KEY,
|
|
95
|
+
title TEXT NOT NULL,
|
|
96
|
+
owner_email TEXT NOT NULL,
|
|
97
|
+
org_id TEXT,
|
|
98
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
99
|
+
)`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
{ table: "my_app_migrations" },
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Each entry needs a `version` number and a `sql` string. Give it a stable `name` too — `runMigrations` tracks named entries independently of `version`, so it still applies exactly once even if another branch reuses the same version number for unrelated DDL. The `table` option names the migration tracking table. Use a name unique to your app so it does not collide with framework migrations. See [Database](/docs/server-database#migrations) for the full schema authoring guide and the name-based tracking rationale.
|
|
107
|
+
|
|
108
|
+
<Callout tone="warning">
|
|
109
|
+
Migrations must be additive. Never put destructive SQL (`DROP TABLE`, `ALTER
|
|
110
|
+
TABLE ... DROP COLUMN`, `DELETE FROM`) in startup plugins.
|
|
111
|
+
</Callout>
|
|
112
|
+
|
|
113
|
+
## Writing Custom Plugins {#custom-plugins}
|
|
114
|
+
|
|
115
|
+
The framework helpers (`createAuthPlugin`, `createAgentChatPlugin`, `runMigrations`) each return a Nitro plugin function. For anything outside those helpers, write a plugin using `defineNitroPlugin`:
|
|
116
|
+
|
|
117
|
+
```ts filename="server/plugins/my-plugin.ts"
|
|
118
|
+
import { defineNitroPlugin } from "nitropack/runtime";
|
|
119
|
+
|
|
120
|
+
export default defineNitroPlugin((nitro) => {
|
|
121
|
+
nitro.hooks.hook("request", (event) => {
|
|
122
|
+
// runs on every request
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
nitro.hooks.hookOnce("close", async () => {
|
|
126
|
+
// runs when the server shuts down
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Plugins can use any of Nitro's lifecycle hooks: `request`, `beforeResponse`, `close`, and others. The [Nitro plugin docs](https://nitro.build/guide/plugins) cover the full hook API and the plugin contract in detail.
|
|
132
|
+
|
|
133
|
+
## Startup Order {#startup-order}
|
|
134
|
+
|
|
135
|
+
Nitro discovers plugins in lexical filename order, so with the default names it invokes `agent-chat.ts`, then `auth.ts`, then `db.ts` — not the dependency order you might expect.
|
|
136
|
+
|
|
137
|
+
File order controls only when each plugin _starts_, not when it _finishes_. Nitro does not await one plugin's async default export before invoking the next, so a later plugin's synchronous setup — and the first incoming request — can run while an earlier plugin's async work (like migrations) is still in flight. `createAuthPlugin` and `createAgentChatPlugin` coordinate internally so framework routes wait for both to finish initializing before serving requests. `runMigrations` has no equivalent coordination: nothing blocks an action or route from querying the database before `db.ts` finishes running its migrations.
|
|
138
|
+
|
|
139
|
+
If you add custom plugins, prefix their filenames to control invocation order, but don't rely on filename order alone to sequence async work across plugins.
|
|
140
|
+
|
|
141
|
+
## What's next {#whats-next}
|
|
142
|
+
|
|
143
|
+
- [**Database**](/docs/server-database): schema helpers and the `getDb` pattern used inside plugins
|
|
144
|
+
- [**Middleware**](/docs/server-middleware): the auth guard that enforces what the auth plugin configures
|
|
145
|
+
- [**Routes**](/docs/server-routes): custom file routes served after plugins complete
|
|
146
|
+
- [**Writing Agent Instructions**](/docs/writing-agent-instructions): how to craft the `systemPrompt` in the agent-chat plugin
|
|
147
|
+
- [**Nitro plugin docs**](https://nitro.build/guide/plugins): the full plugin API and lifecycle hooks
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Routes"
|
|
3
|
+
description: "File-based Nitro server routes, the SSR catch-all, naming conventions, and scoping custom routes to the authenticated user."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Routes
|
|
7
|
+
|
|
8
|
+
Server routing in an agent-native app is split across two surfaces.
|
|
9
|
+
|
|
10
|
+
**Framework routes** at `/_agent-native/*` are managed by the framework. They expose the agent chat loop, sync polling, action endpoints, and other core capabilities. You do not create or modify these routes.
|
|
11
|
+
|
|
12
|
+
**Custom file routes** in `server/routes/` are where you add your own endpoints. Nitro picks them up automatically based on where they sit in the directory tree — no registration or config needed. The filename sets the HTTP method, the directory path becomes the URL path, and each file exports a single handler.
|
|
13
|
+
|
|
14
|
+
## Framework-Mounted Routes {#framework-routes}
|
|
15
|
+
|
|
16
|
+
The framework reserves the `/_agent-native/` namespace for its own endpoints. Never put custom routes here. This table is representative, not exhaustive.
|
|
17
|
+
|
|
18
|
+
| Route prefix | Purpose |
|
|
19
|
+
| -------------------------------- | ------------------------------------------------------------------------------- |
|
|
20
|
+
| `/_agent-native/actions/:name` | Action HTTP endpoints |
|
|
21
|
+
| `/_agent-native/agent-chat` | Agent chat loop |
|
|
22
|
+
| `/_agent-native/poll` | SQL-backed UI sync |
|
|
23
|
+
| `/_agent-native/runs` | Progress primitive. The `RunsTray` polls this; see [Progress](/docs/progress) |
|
|
24
|
+
| `/_agent-native/resources/*` | Workspace resources |
|
|
25
|
+
| `/_agent-native/extensions/*` | Runtime extensions and extension proxy (legacy alias: `/_agent-native/tools/*`) |
|
|
26
|
+
| `/_agent-native/integrations/*` | Messaging/webhook integrations |
|
|
27
|
+
| `/_agent-native/a2a` | Agent-to-agent JSON-RPC |
|
|
28
|
+
| `/mcp` | MCP endpoint |
|
|
29
|
+
| `/_agent-native/onboarding/*` | Setup checklist |
|
|
30
|
+
| `/_agent-native/observability/*` | Traces, feedback, evals, experiments |
|
|
31
|
+
| `/_agent-native/file-upload` | File upload provider endpoint |
|
|
32
|
+
|
|
33
|
+
Custom app routes go in `/api/*` or another path that doesn't collide with `/_agent-native/`.
|
|
34
|
+
|
|
35
|
+
## When to Use a Custom Route {#when-custom-routes}
|
|
36
|
+
|
|
37
|
+
Actions cover the vast majority of app operations. Reach for a file route in `server/routes/` only when you need something an action can't express.
|
|
38
|
+
|
|
39
|
+
<Comparison>
|
|
40
|
+
|
|
41
|
+
### Use a custom route {#use-custom-route}
|
|
42
|
+
|
|
43
|
+
- File uploads or multipart form data
|
|
44
|
+
- Streaming responses or server-sent events (SSE)
|
|
45
|
+
- Inbound webhooks that need raw body verification
|
|
46
|
+
- OAuth callbacks
|
|
47
|
+
- Public unauthenticated pages
|
|
48
|
+
- External REST contracts that require a specific URL path or response shape
|
|
49
|
+
|
|
50
|
+
### Use an action instead {#use-action-instead}
|
|
51
|
+
|
|
52
|
+
- Operations the UI and agent both need
|
|
53
|
+
- Reading or writing app data
|
|
54
|
+
- HTTP-shaped endpoints (use the `http` option in [Actions](/docs/actions))
|
|
55
|
+
- Anything that should be an agent tool, MCP tool, or CLI command
|
|
56
|
+
- Standard JSON request/response APIs
|
|
57
|
+
|
|
58
|
+
</Comparison>
|
|
59
|
+
|
|
60
|
+
### Example: Inbound webhook {#example-webhook}
|
|
61
|
+
|
|
62
|
+
Webhooks are a clear case for a custom route. External services send a raw HTTP body that must be read as bytes before parsing, so you can verify the request signature. Actions receive pre-parsed JSON and cannot access the raw body, which makes signature verification impossible inside one.
|
|
63
|
+
|
|
64
|
+
The route below handles an inbound webhook at `POST /api/webhooks/events`. It reads the raw body, verifies an HMAC signature, then processes the payload.
|
|
65
|
+
|
|
66
|
+
```ts filename="server/routes/api/webhooks/events.post.ts"
|
|
67
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
68
|
+
import { createError, defineEventHandler, getHeader, readRawBody } from "h3";
|
|
69
|
+
|
|
70
|
+
export default defineEventHandler(async (event) => {
|
|
71
|
+
const secret = process.env.WEBHOOK_SECRET;
|
|
72
|
+
const signature = getHeader(event, "x-webhook-signature");
|
|
73
|
+
const rawBody = await readRawBody(event);
|
|
74
|
+
|
|
75
|
+
if (!secret || !signature || !rawBody) {
|
|
76
|
+
throw createError({ statusCode: 400, statusMessage: "Bad Request" });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
|
|
80
|
+
if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
|
|
81
|
+
throw createError({ statusCode: 401, statusMessage: "Invalid signature" });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const payload = JSON.parse(rawBody) as { type: string; data: unknown };
|
|
85
|
+
// handle payload.type ...
|
|
86
|
+
|
|
87
|
+
return { received: true };
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Nitro maps this file to `POST /api/webhooks/events` automatically. The `WEBHOOK_SECRET` environment variable should be set to the signing secret provided by the external service.
|
|
92
|
+
|
|
93
|
+
## File-Based Routes {#file-based-routes}
|
|
94
|
+
|
|
95
|
+
The directory path becomes the URL path, and the filename suffix sets the HTTP method. A file at `server/routes/api/items/[id].get.ts` maps to `GET /api/items/:id` — `items/` comes from the directory, `[id]` becomes the `:id` param, and `.get` sets the method.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
server/routes/
|
|
99
|
+
api/
|
|
100
|
+
items/
|
|
101
|
+
index.get.ts -> GET /api/items
|
|
102
|
+
index.post.ts -> POST /api/items
|
|
103
|
+
[id].get.ts -> GET /api/items/:id
|
|
104
|
+
[id].patch.ts -> PATCH /api/items/:id
|
|
105
|
+
[id].delete.ts -> DELETE /api/items/:id
|
|
106
|
+
webhooks/
|
|
107
|
+
stripe.post.ts -> POST /api/webhooks/stripe
|
|
108
|
+
[...page].get.ts -> SSR catch-all for public pages
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`defineEventHandler` is the H3 primitive for writing a route. It wraps your handler function and gives it access to the `event` object, which carries the request, response, router params, query string, headers, and body. Returning a value from the handler sends it as the response — a plain object becomes JSON automatically. Throwing a `createError` sends the appropriate HTTP error status.
|
|
112
|
+
|
|
113
|
+
Router params like `[id]` are read from `event` with `getRouterParam`:
|
|
114
|
+
|
|
115
|
+
```ts filename="server/routes/api/items/[id].get.ts"
|
|
116
|
+
import { defineEventHandler, getRouterParam, createError } from "h3";
|
|
117
|
+
|
|
118
|
+
export default defineEventHandler((event) => {
|
|
119
|
+
const id = getRouterParam(event, "id");
|
|
120
|
+
if (!id) throw createError({ statusCode: 400, statusMessage: "Missing id" });
|
|
121
|
+
// fetch and return the item by id
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Naming conventions {#naming-conventions}
|
|
126
|
+
|
|
127
|
+
| File path (relative to `server/routes/`) | HTTP method | URL path |
|
|
128
|
+
| ---------------------------------------- | ----------- | ---------------- |
|
|
129
|
+
| `api/items/index.get.ts` | GET | `/api/items` |
|
|
130
|
+
| `api/items/index.post.ts` | POST | `/api/items` |
|
|
131
|
+
| `api/items/[id].get.ts` | GET | `/api/items/:id` |
|
|
132
|
+
| `api/items/[id].patch.ts` | PATCH | `/api/items/:id` |
|
|
133
|
+
| `api/items/[id].delete.ts` | DELETE | `/api/items/:id` |
|
|
134
|
+
| `api/items/[...slug].get.ts` | GET | `/api/items/*` |
|
|
135
|
+
|
|
136
|
+
## Request Context and Access {#request-context}
|
|
137
|
+
|
|
138
|
+
Actions mounted by the framework automatically run with request context. Custom routes do not. If a custom route reads or writes ownable resources, load the session and wrap the work:
|
|
139
|
+
|
|
140
|
+
<AnnotatedCode
|
|
141
|
+
id="doc-block-server-routes-ctx"
|
|
142
|
+
title="Scoping a custom route to the request user"
|
|
143
|
+
filename="server/routes/api/projects.get.ts"
|
|
144
|
+
language="ts"
|
|
145
|
+
code={
|
|
146
|
+
'import { defineEventHandler, createError } from "h3";\nimport { getSession, runWithRequestContext } from "@agent-native/core/server";\nimport { getDb } from "../../db/index.js";\nimport { accessFilter } from "@agent-native/core/sharing";\nimport * as schema from "../../db/schema";\n\nexport default defineEventHandler(async (event) => {\n const session = await getSession(event);\n if (!session?.email) {\n throw createError({ statusCode: 401, statusMessage: "Unauthorized" });\n }\n\n return runWithRequestContext(\n { userEmail: session.email, orgId: session.orgId },\n async () => {\n const db = getDb();\n return db\n .select()\n .from(schema.projects)\n .where(accessFilter(schema.projects, schema.projectShares));\n },\n );\n});'
|
|
147
|
+
}
|
|
148
|
+
annotations={[
|
|
149
|
+
{
|
|
150
|
+
lines: "8-11",
|
|
151
|
+
label: "Custom routes have no auto-context",
|
|
152
|
+
note: "Unlike actions, a file route must load the session itself and fail closed when there is no authenticated user.",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
lines: "13",
|
|
156
|
+
label: "Establish request context",
|
|
157
|
+
note: "`runWithRequestContext` makes the user/org available to scoping helpers for the duration of the work.",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
lines: "17-20",
|
|
161
|
+
label: "Scope ownable reads",
|
|
162
|
+
note: "`accessFilter` constrains the query to rows the caller may see. Never run an unscoped `db.select().from(ownableTable)` here.",
|
|
163
|
+
},
|
|
164
|
+
]}
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
`getDb` is created per app via `createGetDb(schema)` in `server/db/index.ts`, so custom routes import it from the template path (`../../db/index.js`), not from `@agent-native/core/db`. See [Database](/docs/server-database#db-client).
|
|
168
|
+
|
|
169
|
+
<Callout tone="warning">
|
|
170
|
+
Do not run unscoped `db.select().from(ownableTable)` in custom routes. Always
|
|
171
|
+
establish request context with `runWithRequestContext` before querying ownable
|
|
172
|
+
data.
|
|
173
|
+
</Callout>
|
|
174
|
+
|
|
175
|
+
## SSR Catch-All {#ssr-catch-all}
|
|
176
|
+
|
|
177
|
+
The `[...page].get.ts` route at the root of `server/routes/` handles server-side rendering for public-facing pages. It uses `createH3SSRHandler` to hand off to the React Router build:
|
|
178
|
+
|
|
179
|
+
```ts filename="server/routes/[...page].get.ts"
|
|
180
|
+
import { createH3SSRHandler } from "@agent-native/core/server/ssr-handler";
|
|
181
|
+
|
|
182
|
+
export default createH3SSRHandler(
|
|
183
|
+
() => import("virtual:react-router/server-build"),
|
|
184
|
+
);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Every SSR response is an impersonal, public shell cached at the CDN. Never read cookies, session state, or auth branches in this handler. Personalization happens client-side after hydration.
|
|
188
|
+
|
|
189
|
+
## What's next {#whats-next}
|
|
190
|
+
|
|
191
|
+
- [**Actions**](/docs/actions): the preferred surface for app operations; reach for a route only when it doesn't fit
|
|
192
|
+
- [**Plugins**](/docs/server-plugins): startup plugins that run before routes are served
|
|
193
|
+
- [**Middleware**](/docs/server-middleware): auth guards that protect all routes including `/api/*`
|
|
194
|
+
- [**Security**](/docs/security#access-guards): `accessFilter`/`assertAccess` and the full data-scoping model
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.141.
|
|
3
|
+
"version": "0.141.6",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|