@agent-native/core 0.20.9 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +61 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +14 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +4 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +1 -0
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/connect.d.ts +18 -3
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +619 -19
- package/dist/cli/connect.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +13 -6
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.js +1 -1
- package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +13 -8
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/agent-sidebar-state.d.ts +2 -0
- package/dist/client/agent-sidebar-state.d.ts.map +1 -1
- package/dist/client/agent-sidebar-state.js +40 -0
- package/dist/client/agent-sidebar-state.js.map +1 -1
- package/dist/client/code-agent-chat-adapter.js +1 -0
- package/dist/client/code-agent-chat-adapter.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/client/conversation/AgentConversation.js +3 -2
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.js +1 -0
- package/dist/client/conversation/code-agent-transcript.js.map +1 -1
- package/dist/client/conversation/types.d.ts +2 -0
- package/dist/client/conversation/types.d.ts.map +1 -1
- package/dist/client/conversation/types.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +10 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.js +301 -0
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -0
- package/dist/client/sse-event-processor.d.ts +3 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +2 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +5 -5
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +15 -5
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-db-sync.spec.d.ts +2 -0
- package/dist/client/use-db-sync.spec.d.ts.map +1 -0
- package/dist/client/use-db-sync.spec.js +80 -0
- package/dist/client/use-db-sync.spec.js.map +1 -0
- package/dist/code-agents/transcript-normalizer.d.ts +2 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +17 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +29 -21
- package/dist/db/client.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +62 -3
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/content-patch.d.ts +71 -0
- package/dist/extensions/content-patch.d.ts.map +1 -0
- package/dist/extensions/content-patch.js +251 -0
- package/dist/extensions/content-patch.js.map +1 -0
- package/dist/extensions/routes.js +6 -1
- package/dist/extensions/routes.js.map +1 -1
- package/dist/extensions/store.d.ts +4 -4
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +14 -18
- package/dist/extensions/store.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/build-server.d.ts +3 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +207 -8
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/oauth-route.d.ts +22 -0
- package/dist/mcp/oauth-route.d.ts.map +1 -0
- package/dist/mcp/oauth-route.js +618 -0
- package/dist/mcp/oauth-route.js.map +1 -0
- package/dist/mcp/oauth-store.d.ts +89 -0
- package/dist/mcp/oauth-store.d.ts.map +1 -0
- package/dist/mcp/oauth-store.js +391 -0
- package/dist/mcp/oauth-store.js.map +1 -0
- package/dist/mcp/oauth-token.d.ts +28 -0
- package/dist/mcp/oauth-token.d.ts.map +1 -0
- package/dist/mcp/oauth-token.js +83 -0
- package/dist/mcp/oauth-token.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +5 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +2 -2
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +26 -8
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp-client/app-result.d.ts +40 -0
- package/dist/mcp-client/app-result.d.ts.map +1 -0
- package/dist/mcp-client/app-result.js +19 -0
- package/dist/mcp-client/app-result.js.map +1 -0
- package/dist/mcp-client/index.d.ts +5 -2
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +201 -25
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/manager.d.ts +16 -0
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +58 -1
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/routes.d.ts +4 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +159 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/scripts/dev/shell.d.ts.map +1 -1
- package/dist/scripts/dev/shell.js +24 -1
- package/dist/scripts/dev/shell.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +3 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +14 -8
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +6 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +15 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -4
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +17 -2
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/styles/agent-conversation.css +53 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +193 -72
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +88 -38
- package/dist/templates/default/AGENTS.md +3 -3
- package/dist/templates/default/actions/hello.ts +13 -20
- package/dist/templates/default/actions/navigate.ts +19 -51
- package/dist/templates/default/actions/view-screen.ts +16 -33
- package/dist/templates/default/app/hooks/use-navigation-state.ts +13 -3
- package/dist/templates/default/app/lib/tab-id.ts +1 -0
- package/dist/templates/default/app/root.tsx +2 -1
- package/dist/templates/default/app/routes/_index.tsx +11 -0
- package/dist/templates/default/package.json +2 -1
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +9 -1
- package/dist/templates/workspace-core/AGENTS.md +8 -0
- package/dist/templates/workspace-root/AGENTS.md +7 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -2
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.md +26 -3
- package/docs/content/authentication.md +16 -1
- package/docs/content/client.md +11 -8
- package/docs/content/context-awareness.md +2 -3
- package/docs/content/creating-templates.md +2 -2
- package/docs/content/external-agents.md +106 -19
- package/docs/content/faq.md +2 -2
- package/docs/content/key-concepts.md +31 -23
- package/docs/content/mcp-clients.md +1 -1
- package/docs/content/mcp-protocol.md +65 -27
- package/docs/content/template-starter.md +3 -3
- package/docs/content/what-is-agent-native.md +4 -2
- package/package.json +3 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +193 -72
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +88 -38
- package/src/templates/default/AGENTS.md +3 -3
- package/src/templates/default/actions/hello.ts +13 -20
- package/src/templates/default/actions/navigate.ts +19 -51
- package/src/templates/default/actions/view-screen.ts +16 -33
- package/src/templates/default/app/hooks/use-navigation-state.ts +13 -3
- package/src/templates/default/app/lib/tab-id.ts +1 -0
- package/src/templates/default/app/root.tsx +2 -1
- package/src/templates/default/app/routes/_index.tsx +11 -0
- package/src/templates/default/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +9 -1
- package/src/templates/workspace-core/AGENTS.md +8 -0
- package/src/templates/workspace-root/AGENTS.md +7 -0
- package/dist/templates/default/server/routes/api/hello.get.ts +0 -5
- package/dist/templates/default/shared/api.ts +0 -6
- package/src/templates/default/server/routes/api/hello.get.ts +0 -5
- package/src/templates/default/shared/api.ts +0 -6
|
@@ -1,70 +1,141 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: actions
|
|
3
3
|
description: >-
|
|
4
|
-
How to create and run agent
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
How to create and run agent actions. Actions are the single source of truth
|
|
5
|
+
for app operations — the agent calls them as tools, the frontend calls them
|
|
6
|
+
as HTTP endpoints. Use when creating a new action, adding an API integration,
|
|
7
|
+
or wiring up frontend data fetching.
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Agent Actions
|
|
10
11
|
|
|
11
12
|
## Rule
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Actions in `actions/` are the **single source of truth** for app operations. The agent calls them as tools, and the framework auto-exposes them as HTTP endpoints at `/_agent-native/actions/:name`. The frontend calls those endpoints using React Query hooks. No duplicate `/api/` routes needed.
|
|
14
15
|
|
|
15
16
|
## Why
|
|
16
17
|
|
|
17
|
-
Actions give the agent callable tools with structured input/output. They keep the agent's chat context clean
|
|
18
|
+
Actions give the agent callable tools with structured input/output, AND they give the frontend type-safe HTTP endpoints automatically. One implementation serves both the agent and the UI. They keep the agent's chat context clean, they're reusable, and they can be tested independently.
|
|
18
19
|
|
|
19
20
|
## How to Create an Action
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
Use `defineAction` with a Zod schema (required for new actions):
|
|
22
23
|
|
|
23
24
|
```ts
|
|
24
|
-
|
|
25
|
-
import {
|
|
25
|
+
// actions/list-meals.ts
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
import { defineAction } from "@agent-native/core";
|
|
28
|
+
import { getDb } from "../server/db/index.js";
|
|
29
|
+
import { meals } from "../server/db/schema.js";
|
|
26
30
|
|
|
27
|
-
export default
|
|
28
|
-
|
|
31
|
+
export default defineAction({
|
|
32
|
+
description: "List all meals",
|
|
33
|
+
schema: z.object({
|
|
34
|
+
date: z.string().describe("Filter by date (YYYY-MM-DD)"),
|
|
35
|
+
}),
|
|
36
|
+
http: { method: "GET" },
|
|
37
|
+
run: async (args) => {
|
|
38
|
+
// args is fully typed: { date: string }
|
|
39
|
+
const db = getDb();
|
|
40
|
+
const rows = await db.select().from(meals);
|
|
41
|
+
return rows; // Return objects/arrays, NOT JSON.stringify()
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
The `schema` field accepts a Zod schema (or any Standard Schema-compatible library). It provides runtime validation with clear error messages (400 for HTTP, error result for agent), full TypeScript type inference for `run()` args, and auto-generated JSON Schema for the agent's tool definition. `zod` is a dependency of all templates.
|
|
47
|
+
|
|
48
|
+
Tips:
|
|
49
|
+
- Use `.describe()` for parameter descriptions
|
|
50
|
+
- Use `.optional()` for optional params
|
|
51
|
+
- Use `z.coerce.number()` / `z.coerce.boolean()` for params that arrive as strings from HTTP
|
|
52
|
+
- Use `z.enum(["draft", "published"])` for constrained values
|
|
53
|
+
|
|
54
|
+
The legacy `parameters` field (plain JSON Schema object) still works as a fallback but does not provide runtime validation or type inference.
|
|
55
|
+
|
|
56
|
+
### The `http` Option
|
|
57
|
+
|
|
58
|
+
Controls how the action is exposed as an HTTP endpoint:
|
|
59
|
+
|
|
60
|
+
| Value | Behavior | Use for |
|
|
61
|
+
| ------------------------- | ----------------------------------------------------------- | -------------------------------- |
|
|
62
|
+
| _(omitted)_ | Auto-exposed as `POST /_agent-native/actions/:name` | Write operations (default) |
|
|
63
|
+
| `{ method: "GET" }` | Auto-exposed as `GET /_agent-native/actions/:name` | Read-only queries |
|
|
64
|
+
| `{ method: "PUT" }` | Auto-exposed as `PUT /_agent-native/actions/:name` | Update operations |
|
|
65
|
+
| `{ method: "DELETE" }` | Auto-exposed as `DELETE /_agent-native/actions/:name` | Delete operations |
|
|
66
|
+
| `{ method: "GET", path: "custom" }` | Auto-exposed as `GET /_agent-native/actions/custom` | Custom route path |
|
|
67
|
+
| `false` | Agent-only, never exposed as HTTP | `navigate`, `view-screen`, internal actions |
|
|
68
|
+
|
|
69
|
+
### Screen Refresh (automatic)
|
|
70
|
+
|
|
71
|
+
The framework auto-refreshes the UI after any successful mutating action. On completion of a non-`GET` action, the server emits a poll event that the client's `useDbSync` picks up and uses to invalidate `["action"]` React Query keys — so `list-*` / `get-*` hooks refetch without a full page reload.
|
|
72
|
+
|
|
73
|
+
Rules:
|
|
74
|
+
|
|
75
|
+
- `http: { method: "GET" }` → read-only, does NOT trigger a refresh (inferred automatically).
|
|
76
|
+
- Any other action (default `POST`, `PUT`, `DELETE`, or `http: false`) → treated as mutating, triggers a refresh on success.
|
|
77
|
+
- To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
|
|
78
|
+
- To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
|
|
79
|
+
|
|
80
|
+
Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
|
|
81
|
+
|
|
82
|
+
### Return Values
|
|
33
83
|
|
|
34
|
-
|
|
35
|
-
const raw = fs.readFileSync(input, "utf-8");
|
|
36
|
-
const data = JSON.parse(raw) as unknown;
|
|
84
|
+
Actions should return **structured data** (objects, arrays) — not `JSON.stringify()`. The framework serializes the response automatically. If you return a string, the framework tries to parse it as JSON for a clean response.
|
|
37
85
|
|
|
38
|
-
|
|
39
|
-
|
|
86
|
+
```ts
|
|
87
|
+
// Good — return structured data
|
|
88
|
+
run: async (args) => {
|
|
89
|
+
const events = await fetchEvents(args.from, args.to);
|
|
90
|
+
return events;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Bad — don't stringify
|
|
94
|
+
run: async (args) => {
|
|
95
|
+
const events = await fetchEvents(args.from, args.to);
|
|
96
|
+
return JSON.stringify(events, null, 2);
|
|
40
97
|
}
|
|
41
98
|
```
|
|
42
99
|
|
|
43
|
-
|
|
100
|
+
## Frontend Hooks
|
|
101
|
+
|
|
102
|
+
The frontend calls action endpoints using React Query hooks from `@agent-native/core/client`:
|
|
103
|
+
|
|
104
|
+
### `useActionQuery` — for GET actions
|
|
44
105
|
|
|
45
106
|
```ts
|
|
46
|
-
import {
|
|
47
|
-
|
|
107
|
+
import { useActionQuery } from "@agent-native/core/client";
|
|
108
|
+
|
|
109
|
+
function MealList() {
|
|
110
|
+
// Types are auto-inferred from the action's schema + return type — no manual generic needed
|
|
111
|
+
const { data: meals } = useActionQuery("list-meals", {
|
|
112
|
+
date: "2025-01-01",
|
|
113
|
+
});
|
|
114
|
+
return <ul>{meals?.map((m) => <li key={m.id}>{m.name}</li>)}</ul>;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
48
117
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
118
|
+
### `useActionMutation` — for POST/PUT/DELETE actions
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { useActionMutation } from "@agent-native/core/client";
|
|
122
|
+
|
|
123
|
+
function AddMealButton() {
|
|
124
|
+
// Types are auto-inferred — no manual generic needed
|
|
125
|
+
const { mutate } = useActionMutation("log-meal");
|
|
126
|
+
return (
|
|
127
|
+
<button onClick={() => mutate({ name: "Salad", calories: 350 })}>
|
|
128
|
+
Log Meal
|
|
129
|
+
</button>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
61
132
|
```
|
|
62
133
|
|
|
63
|
-
|
|
134
|
+
**Do NOT use manual type generics** like `useActionQuery<Meal[]>(...)`. Types are inferred automatically from `.generated/action-types.d.ts`, which is auto-generated by a Vite plugin.
|
|
64
135
|
|
|
65
|
-
|
|
136
|
+
Mutations automatically invalidate all `["action"]` query keys on success, so GET queries refetch.
|
|
66
137
|
|
|
67
|
-
## How to Run
|
|
138
|
+
## How to Run (Agent)
|
|
68
139
|
|
|
69
140
|
```bash
|
|
70
141
|
pnpm action my-action --input data/source.json --output data/result.json
|
|
@@ -81,63 +152,113 @@ runScript();
|
|
|
81
152
|
|
|
82
153
|
This is the canonical approach for new apps. Action names must be lowercase with hyphens only (e.g., `my-action`).
|
|
83
154
|
|
|
155
|
+
## When You Still Need Custom `/api/` Routes
|
|
156
|
+
|
|
157
|
+
Most operations should be actions. You only need custom routes in `server/routes/api/` for:
|
|
158
|
+
|
|
159
|
+
- **File uploads** — actions receive JSON params, not multipart form data
|
|
160
|
+
- **Streaming responses** — SSE or chunked responses that need direct H3 control
|
|
161
|
+
- **Webhooks** — external services POST to a specific URL
|
|
162
|
+
- **OAuth callbacks** — redirect-based flows that need specific URL patterns
|
|
163
|
+
|
|
164
|
+
If it's a standard CRUD operation or data query, use an action instead.
|
|
165
|
+
|
|
166
|
+
## Legacy Pattern (bare export)
|
|
167
|
+
|
|
168
|
+
Older actions use a bare async function export with `parseArgs`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { parseArgs, loadEnv, fail } from "@agent-native/core";
|
|
172
|
+
|
|
173
|
+
export default async function myAction(args: string[]) {
|
|
174
|
+
loadEnv();
|
|
175
|
+
const parsed = parseArgs(args);
|
|
176
|
+
// ...
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This still works but is not auto-exposed as HTTP. Prefer `defineAction` for all new actions.
|
|
181
|
+
|
|
84
182
|
## Guidelines
|
|
85
183
|
|
|
86
184
|
- **One action, one job.** Keep actions focused on a single operation. The agent composes multiple action calls for complex operations.
|
|
87
|
-
- **
|
|
88
|
-
- **
|
|
185
|
+
- **Return structured data.** Return objects/arrays, not `JSON.stringify()`.
|
|
186
|
+
- **Use `http: { method: "GET" }`** for read-only actions. Default is POST.
|
|
187
|
+
- **Use `http: false`** for agent-only actions (`navigate`, `view-screen`).
|
|
89
188
|
- **Use `loadEnv()`** if the action needs environment variables (API keys, etc.).
|
|
90
189
|
- **Use `fail()`** for user-friendly error messages (exits with message, no stack trace).
|
|
91
|
-
- **
|
|
92
|
-
- **Use `agentChat.submit()`** to report results or errors back to the agent chat.
|
|
93
|
-
- **Import from `@agent-native/core`** -- Don't redefine `parseArgs()` or other utilities locally.
|
|
190
|
+
- **Import from `@agent-native/core`** — Don't redefine `parseArgs()` or other utilities locally.
|
|
94
191
|
|
|
95
192
|
## Common Patterns
|
|
96
193
|
|
|
97
|
-
**
|
|
194
|
+
**Read action (GET):**
|
|
98
195
|
|
|
99
196
|
```ts
|
|
100
|
-
import
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
export default async function generateImage(args: string[]) {
|
|
104
|
-
loadEnv();
|
|
105
|
-
const parsed = parseArgs(args);
|
|
106
|
-
const prompt = parsed.prompt;
|
|
107
|
-
if (!prompt) fail("--prompt is required");
|
|
197
|
+
import { z } from "zod";
|
|
198
|
+
import { defineAction } from "@agent-native/core";
|
|
108
199
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
200
|
+
export default defineAction({
|
|
201
|
+
description: "List calendar events",
|
|
202
|
+
schema: z.object({
|
|
203
|
+
from: z.string().describe("Start date"),
|
|
204
|
+
to: z.string().describe("End date"),
|
|
205
|
+
}),
|
|
206
|
+
http: { method: "GET" },
|
|
207
|
+
run: async (args) => {
|
|
208
|
+
return await fetchEvents(args.from, args.to);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
114
211
|
```
|
|
115
212
|
|
|
116
|
-
**
|
|
213
|
+
**Write action (POST, default):**
|
|
117
214
|
|
|
118
215
|
```ts
|
|
119
|
-
import
|
|
120
|
-
import {
|
|
216
|
+
import { z } from "zod";
|
|
217
|
+
import { defineAction } from "@agent-native/core";
|
|
121
218
|
|
|
122
|
-
export default
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
219
|
+
export default defineAction({
|
|
220
|
+
description: "Log a meal",
|
|
221
|
+
schema: z.object({
|
|
222
|
+
name: z.string().describe("Meal name"),
|
|
223
|
+
calories: z.coerce.number().describe("Calorie count"),
|
|
224
|
+
}),
|
|
225
|
+
run: async (args) => {
|
|
226
|
+
// args.calories is a number — z.coerce.number() handles string-to-number conversion from HTTP
|
|
227
|
+
const meal = await insertMeal(args);
|
|
228
|
+
return meal;
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
126
232
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
233
|
+
**Agent-only action:**
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { z } from "zod";
|
|
237
|
+
import { defineAction } from "@agent-native/core";
|
|
238
|
+
|
|
239
|
+
export default defineAction({
|
|
240
|
+
description: "Navigate the UI to a view",
|
|
241
|
+
schema: z.object({
|
|
242
|
+
view: z.string().describe("Target view"),
|
|
243
|
+
}),
|
|
244
|
+
http: false,
|
|
245
|
+
run: async (args) => {
|
|
246
|
+
await writeAppState("navigate", { command: "go", view: args.view });
|
|
247
|
+
return "Navigated";
|
|
248
|
+
},
|
|
249
|
+
});
|
|
131
250
|
```
|
|
132
251
|
|
|
133
252
|
## Troubleshooting
|
|
134
253
|
|
|
135
|
-
- **Action not found**
|
|
136
|
-
- **Args not parsing**
|
|
137
|
-
- **
|
|
254
|
+
- **Action not found** — Check that the filename matches the command name exactly. `pnpm action foo-bar` looks for `actions/foo-bar.ts`.
|
|
255
|
+
- **Args not parsing** — Ensure args use `--key value` or `--key=value` format. Boolean flags use `--flag` (sets value to `"true"`).
|
|
256
|
+
- **Frontend getting 405** — The action's `http.method` doesn't match the hook. Use `useActionQuery` for GET actions, `useActionMutation` for POST/PUT/DELETE.
|
|
257
|
+
- **Frontend getting undefined** — Make sure the action returns structured data, not `JSON.stringify()`.
|
|
138
258
|
|
|
139
259
|
## Related Skills
|
|
140
260
|
|
|
141
|
-
- **storing-data**
|
|
142
|
-
- **delegate-to-agent**
|
|
143
|
-
- **real-time-sync**
|
|
261
|
+
- **storing-data** — Actions read/write data in SQL
|
|
262
|
+
- **delegate-to-agent** — The agent invokes actions via `pnpm action <name>`
|
|
263
|
+
- **real-time-sync** — Database writes from actions trigger poll events to update the UI
|
|
264
|
+
- **adding-a-feature** — Actions are area 2 of the four-area checklist
|
|
@@ -1,71 +1,87 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: real-time-sync
|
|
3
3
|
description: >-
|
|
4
|
-
How to keep the UI in sync with agent changes via
|
|
5
|
-
query invalidation for new data models, debugging UI not
|
|
6
|
-
understanding jitter prevention.
|
|
4
|
+
How to keep the UI in sync with agent changes via SSE plus polling fallback.
|
|
5
|
+
Use when wiring query invalidation for new data models, debugging UI not
|
|
6
|
+
updating, or understanding jitter prevention.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# Real-Time Sync
|
|
9
|
+
# Real-Time Sync
|
|
10
10
|
|
|
11
11
|
## Rule
|
|
12
12
|
|
|
13
|
-
The UI stays in sync with agent/script changes through
|
|
13
|
+
The UI stays in sync with agent/script changes through `useDbSync()`. In-process writes stream over `/_agent-native/events` first; `/_agent-native/poll` remains the cross-process/serverless fallback. When the agent writes to the database, the UI detects the change and updates automatically — no manual refresh needed.
|
|
14
14
|
|
|
15
15
|
## Why
|
|
16
16
|
|
|
17
|
-
The agent modifies data in SQL, but the UI runs in the browser.
|
|
17
|
+
The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same-process writes immediately; polling bridges anything SSE cannot see, such as another serverless invocation, cron job, or external script. Every visible write increments a version counter, `useDbSync()` receives the change, and React Query invalidates the relevant caches. This is what makes database writes feel real-time without relying on aggressive polling.
|
|
18
18
|
|
|
19
19
|
## How It Works
|
|
20
20
|
|
|
21
|
-
1. **Server** increments a version counter on every database write.
|
|
21
|
+
1. **Server** increments a version counter on every database write. In-process events stream through the authenticated `/_agent-native/events` endpoint.
|
|
22
22
|
|
|
23
|
-
2. **Client**
|
|
23
|
+
2. **Client** listens for SSE/poll events and updates per-source change counters:
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
26
|
import { useDbSync } from "@agent-native/core";
|
|
27
27
|
useDbSync({ queryClient });
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
For each non-own event, `useDbSync` bumps a per-source counter (e.g. `dashboards`, `analyses`, `settings`, `action`) and invalidates a small fixed list of framework-internal prefixes (`["action"]`, `["app-state"]`, `["__set_url__"]`, etc.). It does **not** blanket-invalidate templates' own data queries for ordinary domain events — that caused a request storm in production. The exception is `source: "action"`: a successful mutating action is the framework-wide "agent changed app data" signal, so `useDbSync` also refreshes active React Query observers as a compatibility safety net for custom apps that have not yet moved every read to `useActionQuery` or source-versioned query keys.
|
|
31
|
+
|
|
32
|
+
3. **Templates fold per-source counters into their query keys.** This is the pattern that makes "agent writes show up without a manual refresh" reliable:
|
|
31
33
|
|
|
32
34
|
```ts
|
|
33
35
|
import { useChangeVersion } from "@agent-native/core/client";
|
|
36
|
+
import { useQuery } from "@tanstack/react-query";
|
|
34
37
|
|
|
35
|
-
const v = useChangeVersion("
|
|
36
|
-
const
|
|
37
|
-
queryKey: ["
|
|
38
|
-
queryFn:
|
|
39
|
-
placeholderData: (prev) => prev,
|
|
38
|
+
const v = useChangeVersion("dashboards");
|
|
39
|
+
const dashboard = useQuery({
|
|
40
|
+
queryKey: ["dashboard", id, v],
|
|
41
|
+
queryFn: () => fetchDashboard(id),
|
|
42
|
+
placeholderData: (prev) => prev, // no flicker on refetch
|
|
40
43
|
});
|
|
41
44
|
```
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
When the agent writes (`update-dashboard` action → server emits `source: "dashboards"`), the counter advances, the queryKey changes, and React Query refetches that one query. The old data stays on screen during the refetch thanks to `placeholderData`.
|
|
47
|
+
|
|
48
|
+
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
49
|
+
|
|
50
|
+
3. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds. If SSE is disabled or unavailable, polling continues at the normal cadence.
|
|
51
|
+
|
|
52
|
+
4. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
44
53
|
|
|
45
54
|
## Don't
|
|
46
55
|
|
|
47
|
-
- Don't create manual polling loops — `useDbSync()` handles
|
|
56
|
+
- Don't create manual polling loops — `useDbSync()` handles SSE plus fallback polling
|
|
48
57
|
- Don't create your own fetch-based polling alongside `useDbSync` — use the `onEvent` callback for custom handling
|
|
49
58
|
|
|
50
|
-
##
|
|
59
|
+
## Which sources to depend on
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
Common sources you'll fold into query keys:
|
|
62
|
+
|
|
63
|
+
| Source | Bumped by |
|
|
64
|
+
| ----------------- | --------------------------------------------------------------------------- |
|
|
65
|
+
| `action` | The agent runner after every successful mutating action tool call |
|
|
66
|
+
| `app-state` | Writes to `application_state` (navigation, selections, ephemeral UI state) |
|
|
67
|
+
| `settings` | Writes to the `settings` table |
|
|
68
|
+
| `dashboards` | Dashboard CRUD via `upsertDashboard` / `archiveDashboard` etc. |
|
|
69
|
+
| `analyses` | Analysis CRUD |
|
|
70
|
+
| `extensions` | Extension CRUD |
|
|
71
|
+
| `collab` | Yjs collaborative-doc updates |
|
|
72
|
+
| `screen-refresh` | Explicit `refresh-screen` agent tool call |
|
|
73
|
+
|
|
74
|
+
If a query reads data the agent can mutate via more than one path, depend on multiple sources with `useChangeVersions`:
|
|
53
75
|
|
|
54
76
|
```ts
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
onEvent: (data) => {
|
|
58
|
-
if (data.source === "settings") {
|
|
59
|
-
// Force a refetch even when not actively observed
|
|
60
|
-
queryClient.invalidateQueries({
|
|
61
|
-
queryKey: ["settings"],
|
|
62
|
-
refetchType: "all",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
});
|
|
77
|
+
const v = useChangeVersions(["dashboards", "action"]);
|
|
78
|
+
useQuery({ queryKey: ["dashboard", id, v], ... });
|
|
67
79
|
```
|
|
68
80
|
|
|
81
|
+
`useChangeVersions` returns a single integer that advances whenever any of the listed sources advance.
|
|
82
|
+
|
|
83
|
+
## Tuning refetch behavior
|
|
84
|
+
|
|
69
85
|
To prevent cache thrashing during rapid agent writes, set `staleTime` on your queries:
|
|
70
86
|
|
|
71
87
|
```ts
|
|
@@ -78,11 +94,12 @@ useQuery({
|
|
|
78
94
|
|
|
79
95
|
## Troubleshooting
|
|
80
96
|
|
|
81
|
-
| Symptom | Check
|
|
82
|
-
| ---------------------------------- |
|
|
83
|
-
| UI not updating after agent writes | Is `useDbSync` called with the correct `queryClient`? Does the affected query have an active observer?
|
|
84
|
-
| Poll endpoint not responding | Is `/_agent-native/poll` accessible? Is the server running?
|
|
85
|
-
|
|
|
97
|
+
| Symptom | Check |
|
|
98
|
+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| UI not updating after agent writes | Is `useDbSync` called with the correct `queryClient`? Does the affected query have an active observer? |
|
|
100
|
+
| Poll endpoint not responding | Is `/_agent-native/poll` accessible? Is the server running? |
|
|
101
|
+
| SSE not connecting | Is `/_agent-native/events` accessible and authenticated? Polling should still keep the UI fresh as fallback. |
|
|
102
|
+
| High CPU / event storms | The agent is writing rapidly. Add `staleTime` to queries to debounce refetches. |
|
|
86
103
|
|
|
87
104
|
## Jitter Prevention
|
|
88
105
|
|
|
@@ -92,7 +109,7 @@ When the agent writes to application-state via script helpers (`writeAppState`,
|
|
|
92
109
|
|
|
93
110
|
1. **Agent writes** are tagged: the script helpers in `@agent-native/core/application-state` pass `{ requestSource: "agent" }` to the store.
|
|
94
111
|
2. **UI writes** are tagged: templates send a per-tab ID via the `X-Request-Source` header on PUT/DELETE requests to application-state endpoints.
|
|
95
|
-
3. **
|
|
112
|
+
3. **Sync filters**: `useDbSync()` accepts an `ignoreSource` option. The UI passes its own tab ID so it ignores events from its own writes — but still picks up events from agents, other tabs, and scripts.
|
|
96
113
|
|
|
97
114
|
### Template setup
|
|
98
115
|
|
|
@@ -113,11 +130,44 @@ The `use-navigation-state.ts` hook sends the same `TAB_ID` in the `X-Request-Sou
|
|
|
113
130
|
|
|
114
131
|
### Why this matters
|
|
115
132
|
|
|
116
|
-
Without jitter prevention, a cycle occurs: the UI writes state,
|
|
133
|
+
Without jitter prevention, a cycle occurs: the UI writes state, sync detects the change, the UI refetches and re-renders, potentially overwriting what the user is actively editing. With `ignoreSource`, the UI only reacts to changes from other sources (agent scripts, other browser tabs, other users).
|
|
134
|
+
|
|
135
|
+
## Action Routes and Polling
|
|
136
|
+
|
|
137
|
+
Action routes (`/_agent-native/actions/:name`) work with the same sync system. When a POST/PUT/DELETE action writes to the database, the version counter increments and `useDbSync` picks up the change. Frontend mutations via `useActionMutation` automatically invalidate `["action"]` query keys on success, triggering refetches of `useActionQuery` hooks.
|
|
138
|
+
|
|
139
|
+
For custom apps, the best out-of-the-box path is:
|
|
140
|
+
|
|
141
|
+
1. Put read actions in `actions/` with `defineAction({ http: { method: "GET" } })`.
|
|
142
|
+
2. Put write actions in `actions/` with the default POST/PUT/DELETE behavior.
|
|
143
|
+
3. Call reads from React with `useActionQuery` and writes with `useActionMutation`.
|
|
144
|
+
|
|
145
|
+
This avoids duplicate `/api/*` JSON CRUD routes and makes agent-created records show up automatically. Raw `useQuery` can still work, but it should include `useChangeVersions(["action", "<domain-source>"])` in the query key for targeted refreshes.
|
|
146
|
+
|
|
147
|
+
### Auto-emit on mutating actions
|
|
148
|
+
|
|
149
|
+
The framework emits a poll event with `source: "action"` whenever any non-read-only action runs to completion — whether called via HTTP (`/_agent-native/actions/:name`) or as an agent tool call. Read-only actions (`http: { method: "GET" }` or explicit `readOnly: true`) are skipped.
|
|
150
|
+
|
|
151
|
+
This means UIs don't need the agent to remember to call `refresh-screen` after every mutation. A listener like this (used in the `macros` template) will refresh after any mutating agent call:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
useDbSync({
|
|
155
|
+
queryClient,
|
|
156
|
+
queryKeys: [],
|
|
157
|
+
ignoreSource: TAB_ID,
|
|
158
|
+
onEvent: (data) => {
|
|
159
|
+
if (data.requestSource === TAB_ID) return;
|
|
160
|
+
// Invalidate all useActionQuery caches so list-*, get-*, etc. refetch
|
|
161
|
+
queryClient.invalidateQueries({ queryKey: ["action"] });
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`refresh-screen` remains available for unusual cases — e.g. the agent mutated data via a path the framework can't see (external system the app mirrors), or the agent wants to pass a `scope` hint for narrower invalidation.
|
|
117
167
|
|
|
118
168
|
## Related Skills
|
|
119
169
|
|
|
120
170
|
- **storing-data** — Application-state and settings are the data stores that sync via polling
|
|
121
171
|
- **context-awareness** — Navigation state writes use jitter prevention to avoid overwriting active edits
|
|
122
|
-
- **
|
|
172
|
+
- **actions** — Action routes auto-expose actions as HTTP endpoints; database writes trigger poll events
|
|
123
173
|
- **self-modifying-code** — Agent code edits trigger poll events; rapid edits can cause event storms
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This app follows the agent-native core philosophy: the agent and UI are equal partners. Everything the UI can do, the agent can do via actions. The agent always knows what you're looking at via application state. See the root AGENTS.md for full framework documentation.
|
|
4
4
|
|
|
5
|
-
This is an **@agent-native/core** application -- the AI agent and UI share state through a SQL database, with
|
|
5
|
+
This is an **@agent-native/core** application -- the AI agent and UI share state through a SQL database, with SSE for in-process live sync and polling as the cross-process/serverless fallback.
|
|
6
6
|
|
|
7
7
|
### Core Principles
|
|
8
8
|
|
|
9
9
|
1. **Shared SQL database** -- All app state lives in SQL (SQLite locally, cloud DB via `DATABASE_URL` in production). Core stores: `application_state`, `settings`, `oauth_tokens`, `sessions`, `resources`.
|
|
10
10
|
2. **All AI through agent chat** -- No inline LLM calls. UI delegates to the AI via `sendToAgentChat()` / `agentChat.submit()`.
|
|
11
11
|
3. **Actions for agent operations** -- `pnpm action <name>` dispatches to callable action files in `actions/`.
|
|
12
|
-
4. **
|
|
12
|
+
4. **Live sync keeps the UI current** -- Database writes stream over `/_agent-native/events` first, with `/_agent-native/poll` as the fallback. **When you (the agent) write data, the UI must reflect the change without a manual refresh.** This is non-negotiable. Use `useActionQuery` / `useActionMutation` for action-backed data (preferred). If you use raw `useQuery`, fold `useChangeVersions([<source>, "action"])` into the key for targeted refreshes. See the `real-time-sync` and `adding-a-feature` skills.
|
|
13
13
|
5. **Agent can update code** -- The agent can modify this app's source code directly.
|
|
14
14
|
|
|
15
15
|
### Authentication
|
|
@@ -112,7 +112,7 @@ Skills in `.agents/skills/` provide detailed guidance for each architectural rul
|
|
|
112
112
|
1. **Add navigation state entries** — extend `app/hooks/use-navigation-state.ts` to track new routes
|
|
113
113
|
2. **Enhance view-screen** — make the view-screen script return relevant context for the new view
|
|
114
114
|
3. **Create domain actions** — add actions in `actions/` for CRUD operations on new data models
|
|
115
|
-
4. **Wire UI for auto-refresh** — use `useActionQuery`
|
|
115
|
+
4. **Wire UI for auto-refresh** — use `useActionQuery` / `useActionMutation` for normal CRUD. If a raw `useQuery` is unavoidable, fold `useChangeVersions([<source>, "action"])` into its key with `placeholderData`. When the agent mutates this data, the UI must reflect the change without a manual refresh. See `real-time-sync` skill.
|
|
116
116
|
5. **Create domain skills** — add `.agents/skills/<feature>/SKILL.md` documenting the data model, storage patterns, and agent operations
|
|
117
117
|
6. **Update this AGENTS.md** — add the new actions, state keys, and common tasks
|
|
118
118
|
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.log(`Hello, ${name}!`);
|
|
15
|
-
|
|
16
|
-
// Example: send a message to agent chat (works in Electron context)
|
|
17
|
-
if (parsed["send-chat"] === "true") {
|
|
18
|
-
agentChat.submit(`Hello from the script system! Name: ${name}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export default defineAction({
|
|
5
|
+
description: "Return a friendly greeting.",
|
|
6
|
+
schema: z.object({
|
|
7
|
+
name: z.string().default("world").describe("Name to greet"),
|
|
8
|
+
}),
|
|
9
|
+
http: { method: "GET" },
|
|
10
|
+
run: async ({ name }) => {
|
|
11
|
+
return { message: `Hello, ${name}!` };
|
|
12
|
+
},
|
|
13
|
+
});
|