@agent-native/core 0.18.1 → 0.19.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/README.md +1 -11
- package/dist/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +3 -0
- package/dist/a2a/client.js.map +1 -1
- package/dist/cli/connect.d.ts +94 -0
- package/dist/cli/connect.d.ts.map +1 -0
- package/dist/cli/connect.js +443 -0
- package/dist/cli/connect.js.map +1 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +71 -0
- package/dist/cli/mcp-config-writers.d.ts.map +1 -0
- package/dist/cli/mcp-config-writers.js +210 -0
- package/dist/cli/mcp-config-writers.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -63
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +6 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +5 -4
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +6 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +20 -10
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts +18 -0
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -0
- package/dist/client/conversation/AgentConversation.js +94 -0
- package/dist/client/conversation/AgentConversation.js.map +1 -0
- package/dist/client/conversation/AgentConversation.spec.d.ts +2 -0
- package/dist/client/conversation/AgentConversation.spec.d.ts.map +1 -0
- package/dist/client/conversation/AgentConversation.spec.js +69 -0
- package/dist/client/conversation/AgentConversation.spec.js.map +1 -0
- package/dist/client/conversation/index.d.ts +4 -0
- package/dist/client/conversation/index.d.ts.map +1 -0
- package/dist/client/conversation/index.js +3 -0
- package/dist/client/conversation/index.js.map +1 -0
- package/dist/client/conversation/types.d.ts +54 -0
- package/dist/client/conversation/types.d.ts.map +1 -0
- package/dist/client/conversation/types.js +2 -0
- package/dist/client/conversation/types.js.map +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +15 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js +66 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -0
- 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/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +2 -2
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +4 -28
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/code-agents/index.d.ts +1 -0
- package/dist/code-agents/index.d.ts.map +1 -1
- package/dist/code-agents/index.js +1 -0
- package/dist/code-agents/index.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.d.ts +50 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -0
- package/dist/code-agents/transcript-normalizer.js +356 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -0
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +30 -0
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/connect-route.d.ts +43 -0
- package/dist/mcp/connect-route.d.ts.map +1 -0
- package/dist/mcp/connect-route.js +638 -0
- package/dist/mcp/connect-route.js.map +1 -0
- package/dist/mcp/connect-store.d.ts +132 -0
- package/dist/mcp/connect-store.d.ts.map +1 -0
- package/dist/mcp/connect-store.js +434 -0
- package/dist/mcp/connect-store.js.map +1 -0
- package/dist/server/auth.d.ts +17 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +149 -33
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +43 -0
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +25 -0
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +12 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +42 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts +86 -0
- package/dist/server/identity-sso-store.d.ts.map +1 -0
- package/dist/server/identity-sso-store.js +243 -0
- package/dist/server/identity-sso-store.js.map +1 -0
- package/dist/server/identity-sso.d.ts +78 -0
- package/dist/server/identity-sso.d.ts.map +1 -0
- package/dist/server/identity-sso.js +425 -0
- package/dist/server/identity-sso.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/docs/content/code-agents-ui.md +14 -3
- package/docs/content/cross-app-sso.md +118 -0
- package/docs/content/external-agents.md +130 -51
- package/docs/content/migration-workbench.md +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Cross-App SSO"
|
|
3
|
+
description: "Sign in once across every hosted agent-native app via identity federation with Dispatch as the identity authority — opt-in per app, reversible with a single env var."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cross-App SSO
|
|
7
|
+
|
|
8
|
+
Each hosted app at `*.agent-native.com` runs its own deployment with its **own separate user store**. `mail.agent-native.com` and `calendar.agent-native.com` do not share a database, a session table, or a cookie domain. So "sign in once, use every app" cannot be a shared cookie — it has to be **identity federation**, with [Dispatch](/docs/dispatch) acting as the identity authority for the workspace.
|
|
9
|
+
|
|
10
|
+
This is the same trust primitive [A2A](/docs/a2a-protocol) and [External Agents](/docs/external-agents) already use — an `A2A_SECRET`-signed JWT verified at the request boundary — applied to the human sign-in path instead of agent-to-agent calls.
|
|
11
|
+
|
|
12
|
+
## What & why {#what-why}
|
|
13
|
+
|
|
14
|
+
Per-app user stores mean there is no single place a browser cookie could live that every app trusts. The federation model instead names one app — **Dispatch** — as the identity authority. Any other app can delegate "who is this person?" to Dispatch, get back a short-lived signed assertion of the user's verified email, and then **link that to its own local account by email**.
|
|
15
|
+
|
|
16
|
+
The linking rule is deliberately narrow and additive:
|
|
17
|
+
|
|
18
|
+
- **Existing same-email user → linked.** The local account is matched by verified email and reused as-is. It is **never modified, renamed, or deleted** — the federation layer only ever reads it and mints a session for it.
|
|
19
|
+
- **New email → created.** A fresh local account is created for that verified email, then a normal local session is minted.
|
|
20
|
+
|
|
21
|
+
This makes the rollout safe even though it logs people out. **Logout is expected.** When an app turns this on, existing sessions end and users re-authenticate through Dispatch. But they always log back into the **same email-matched account, with all their data intact**, because identity rows are only ever _added to_ — never destroyed, renamed, or repointed. There is no migration, no table rename, no destructive write anywhere in this path. (See the auth invariants in [Authentication](/docs/authentication) and [Security & Data Scoping](/docs/security).)
|
|
22
|
+
|
|
23
|
+
## How it works {#how-it-works}
|
|
24
|
+
|
|
25
|
+
The flow is a standard authorize → signed-token → callback redirect, with email as the only thing that crosses the trust boundary.
|
|
26
|
+
|
|
27
|
+
1. **App → Dispatch (authorize).** The app sends the user to the identity authority:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
GET https://dispatch.agent-native.com/_agent-native/identity/authorize
|
|
31
|
+
?app=<requesting-app>
|
|
32
|
+
&redirect_uri=<app-callback-url>
|
|
33
|
+
&state=<csrf-state>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. **Dispatch authenticates the human.** If the user already has a Dispatch session, this is transparent. If not, Dispatch shows its own normal login (email/password, Google, etc. — see [Authentication](/docs/authentication)). Dispatch is just a regular agent-native app here; it is not running a special auth mode.
|
|
37
|
+
|
|
38
|
+
3. **Dispatch → App (signed identity token).** Dispatch validates `redirect_uri` against a **strict allowlist** (`*.agent-native.com` plus localhost — nothing else) and 302-redirects back to the app's `redirect_uri` carrying a short-lived **`A2A_SECRET`-signed identity JWT**. The token's claims are intentionally minimal:
|
|
39
|
+
|
|
40
|
+
| Claim | Meaning |
|
|
41
|
+
| ------------ | -------------------------------------------------------- |
|
|
42
|
+
| `sub` | Stable user id at the identity authority |
|
|
43
|
+
| `email` | The user's **verified** email — the only join key |
|
|
44
|
+
| `name` | Display name (non-authoritative, for UI only) |
|
|
45
|
+
| `org_domain` | Workspace/org domain, when present |
|
|
46
|
+
| `scope` | Always `"identity"` — this token authorizes sign-in only |
|
|
47
|
+
| `exp` | **≤ 5 minutes** from issue |
|
|
48
|
+
|
|
49
|
+
4. **App verifies and JIT-links by email.** The app verifies the token signature with its own `A2A_SECRET`, checks `scope: "identity"` and `exp`, then performs **just-in-time linking strictly by verified email**:
|
|
50
|
+
- If a local user with that email exists → reuse it unchanged.
|
|
51
|
+
- If not → create a local user for that email.
|
|
52
|
+
|
|
53
|
+
5. **App mints a normal local session.** From here on the user has an ordinary local session in that app's own store — every existing access check, org scoping, and action guard works exactly as before. The federation only happened at the front door.
|
|
54
|
+
|
|
55
|
+
### Opting in {#opt-in}
|
|
56
|
+
|
|
57
|
+
An app participates **only** when this environment variable is set on its deployment:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
AGENT_NATIVE_IDENTITY_HUB_URL=https://dispatch.agent-native.com
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- **Set** → the app shows a **"Sign in with Agent-Native"** option that runs the flow above. Direct local login (email/password, Google) still works alongside it.
|
|
64
|
+
- **Unset (default)** → **zero behavior change.** The app authenticates exactly as it did before; the federation code path is dormant. There is no schema change and nothing to migrate, so flipping the variable on or off is fully reversible at any time.
|
|
65
|
+
|
|
66
|
+
## Security {#security}
|
|
67
|
+
|
|
68
|
+
The whole model rests on a few deliberately small guarantees:
|
|
69
|
+
|
|
70
|
+
- **Short-lived signed token.** The identity assertion is an `A2A_SECRET`-signed JWT with a **≤ 5-minute** expiry and `scope: "identity"`. It authorizes a single sign-in and cannot be replayed for long or repurposed for API/A2A access. Verification runs at the request boundary before any session is minted — same boundary discipline as [A2A auth](/docs/a2a-protocol#auth-policy).
|
|
71
|
+
- **Strict `redirect_uri` allowlist.** Dispatch only ever redirects to `*.agent-native.com` or localhost. Arbitrary, scheme-relative (`//host`), and cross-origin redirect targets are rejected, so the authority can't be turned into an open-redirect or token-exfiltration oracle.
|
|
72
|
+
- **Email-only join from a verified token.** The _only_ thing that crosses the trust boundary is the verified email in a signed token. The app does not accept a user id, role, org membership, or any privileged state from the wire — it derives everything locally from the matched account.
|
|
73
|
+
- **Additive-only identity writes.** Linking either reuses an existing same-email account untouched or inserts a new one. No update, rename, repoint, or delete of identity rows ever happens on this path — consistent with the framework's "no breaking database changes" rule.
|
|
74
|
+
- **Off by default.** With `AGENT_NATIVE_IDENTITY_HUB_URL` unset the entire feature is inert. Enabling or disabling it is one env var on one deploy, with no data side effects.
|
|
75
|
+
|
|
76
|
+
## Canary rollout runbook {#canary-rollout}
|
|
77
|
+
|
|
78
|
+
Cutover and rollback are **a single environment variable per app deployment**. Roll out one app at a time, verify, then expand. Do not set the variable on every app at once.
|
|
79
|
+
|
|
80
|
+
**1. Deploy the code — no behavior change.**
|
|
81
|
+
Ship the release to every app with `AGENT_NATIVE_IDENTITY_HUB_URL` **unset everywhere**. Because the feature is off by default, this deploy is a no-op for users: every app keeps authenticating exactly as before. Confirm normal logins still work on a couple of apps.
|
|
82
|
+
|
|
83
|
+
**2. Enable the canary on ONE app (mail) only.**
|
|
84
|
+
Set, on the **mail** deployment only:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
AGENT_NATIVE_IDENTITY_HUB_URL=https://dispatch.agent-native.com
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Leave every other app's environment unset. Redeploy/restart mail so it picks up the variable.
|
|
91
|
+
|
|
92
|
+
**3. Verify the canary (checklist).**
|
|
93
|
+
On mail, walk the full loop:
|
|
94
|
+
|
|
95
|
+
- Log **out** of mail.
|
|
96
|
+
- The login screen now shows **"Sign in with Agent-Native"**. Click it.
|
|
97
|
+
- You are taken to **Dispatch** and complete its login (or pass straight through if already signed in there).
|
|
98
|
+
- You are redirected **back to mail, logged in** — and it is the **same pre-existing account** (same email) you had before, not a new one.
|
|
99
|
+
- **Mail data is intact** — your existing mailboxes, drafts, settings, and org scoping are all exactly as they were.
|
|
100
|
+
- **Existing direct logins still work** — email/password and Google sign-in on mail continue to function for users who don't use the SSO button.
|
|
101
|
+
|
|
102
|
+
If any check fails, go straight to step 5 (rollback) — it is instant and data-safe.
|
|
103
|
+
|
|
104
|
+
**4. Expand app-by-app.**
|
|
105
|
+
Once mail is verified, repeat steps 2–3 for the next app, then the next — setting `AGENT_NATIVE_IDENTITY_HUB_URL` on one deployment at a time and running the same checklist after each. Never batch-enable.
|
|
106
|
+
|
|
107
|
+
**5. Rollback = unset the env var on that app's deploy.**
|
|
108
|
+
To revert any app, **remove `AGENT_NATIVE_IDENTITY_HUB_URL` from that app's environment and redeploy/restart it.** The app immediately returns to its prior auth behavior. There is **no data change to undo** — identity rows were only ever added, and unsetting the variable simply makes the federation path dormant again. Each app's cutover and rollback are independent and reversible.
|
|
109
|
+
|
|
110
|
+
> The rollout logs users out as each app is enabled (they re-auth via Dispatch), but they always log back into the **same email-matched account with data intact**, because identity rows are never destroyed or renamed — only added.
|
|
111
|
+
|
|
112
|
+
## Related {#related}
|
|
113
|
+
|
|
114
|
+
- [Authentication](/docs/authentication) — local auth modes, sessions, orgs, the `A2A_SECRET` env var.
|
|
115
|
+
- [A2A Protocol](/docs/a2a-protocol) — the signed-JWT, verify-at-the-boundary trust model this reuses.
|
|
116
|
+
- [External Agents](/docs/external-agents) — the same `A2A_SECRET`-signed identity pattern applied to agent connections and deep links.
|
|
117
|
+
- [Dispatch](/docs/dispatch) — the workspace identity authority and routing hub.
|
|
118
|
+
- [Security & Data Scoping](/docs/security) — additive-only data writes and per-account scoping.
|
|
@@ -1,56 +1,90 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "External Agents"
|
|
3
|
-
description: "Connect Claude Code, Cowork,
|
|
3
|
+
description: "Connect your own Claude Code, Cowork, or Codex to a hosted agent-native app in one command — then round-trip artifacts back into the running UI with deep links."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# External Agents
|
|
7
7
|
|
|
8
8
|
An agent-native app is reachable by any external coding agent — Claude Code (desktop & CLI), Claude Cowork, Codex — over [MCP](/docs/mcp-protocol). External agents are great at producing artifacts (a draft, an event, a dashboard) but they live in a terminal or another app. Without a bridge, the user gets a wall of JSON and has to go find the thing.
|
|
9
9
|
|
|
10
|
-
The external-agent bridge closes the loop
|
|
10
|
+
The external-agent bridge closes the loop. First you connect your own agent to a **hosted** app — one command, no token copying. Then the agent does the work over MCP and hands the user a single **"Open in <app> →"** link that opens the real app focused on exactly what was produced. It reuses the existing `navigate` / `application_state` contract the UI already drains every 2s (see [Context Awareness](/docs/context-awareness)) — there is no second navigation mechanism.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Connect in one command {#connect}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- **`link` builder** — any action that produces or lists a navigable resource returns a deep link; MCP/A2A surfaces auto-append an "Open in … →" link.
|
|
16
|
-
- **`/_agent-native/open` route** — a pure pointer (view + record ids + filters); the record-focusing write is always scoped to the **browser session**, never the agent's token.
|
|
17
|
-
- **Ingest actions** — GET + `readOnly` + `publicAgent` actions let an external agent pull **live** app state into its own context.
|
|
18
|
-
- **Generic cross-app verbs** — a stable verb set (`list_apps`, `open_app`, `ask_app`, `create_workspace_app`, `list_templates`) so an external agent has a predictable surface without guessing per-app action names.
|
|
14
|
+
The first-party hosted apps live at `mail.agent-native.com`, `calendar.agent-native.com`, `analytics.agent-native.com`, and so on. To wire your own Claude Code (and Codex / Cowork if detected) to one of them:
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
```bash
|
|
17
|
+
npx @agent-native/core connect https://mail.agent-native.com
|
|
18
|
+
```
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
This opens your browser at the app. You are already logged in, so you just click **Authorize** once. The command detects every installed agent client and writes the MCP config for each — no token to copy, no local server to run. Connect every first-party hosted app at once with:
|
|
25
21
|
|
|
26
22
|
```bash
|
|
27
|
-
agent-native
|
|
28
|
-
[--app <id>] [--scope user|project]
|
|
23
|
+
npx @agent-native/core connect --all
|
|
29
24
|
```
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
The connection is **per-user, scoped, and revocable**. The browser session you authorized with is the identity the agent acts as; nothing exposes the deployment's shared secret.
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
- **cowork** — the same Claude Code JSON shape in `~/.cowork/mcp.json`.
|
|
35
|
-
- **codex** — an `[mcp_servers.<name>]` block in `~/.codex/config.toml`.
|
|
28
|
+
### No-CLI alternative {#no-cli}
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
If you'd rather not run a command, open the app in your browser and use its **Connect** affordance (served at `https://<app>/_agent-native/mcp/connect`). While logged in, click **Connect / Authorize**. The page hands you either a one-click deep link that configures a detected agent, or a ready-to-paste `.mcp.json` block:
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
```jsonc
|
|
33
|
+
// .mcp.json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"mail": {
|
|
37
|
+
"type": "url",
|
|
38
|
+
"url": "https://mail.agent-native.com/_agent-native/mcp",
|
|
39
|
+
"headers": { "Authorization": "Bearer <minted-token>" },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
```
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
| ----------------------------------------- | ------------------------------------------------------------------- |
|
|
43
|
-
| `agent-native mcp serve [--app <id>]` | Run the MCP stdio transport (what client configs spawn). |
|
|
44
|
-
| `agent-native mcp install --client <c>` | Provision a token + write the client's MCP config (idempotent). |
|
|
45
|
-
| `agent-native mcp uninstall --client <c>` | Remove the named MCP entry from a client's config (idempotent). |
|
|
46
|
-
| `agent-native mcp status` | Show resolved MCP URL/port, token state, and per-client entries. |
|
|
47
|
-
| `agent-native mcp token [--rotate]` | Print (or rotate) the local `ACCESS_TOKEN` in the workspace `.env`. |
|
|
45
|
+
Restart the agent client after connecting so it picks up the new MCP server.
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
## What you can do once connected {#what-you-can-do}
|
|
48
|
+
|
|
49
|
+
Once your agent is connected, the app's full action surface is available as MCP tools, plus the `ask-agent` meta-tool that runs the full agent loop (the same entry point [A2A](/docs/a2a-protocol) uses). Ask your agent to do real work and it hands back a link straight into the running app:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
> draft an email to John about the Q3 report
|
|
53
|
+
|
|
54
|
+
Claude Code calls: manage-draft(to: "john@example.com", subject: "Q3 Report", body: "…")
|
|
55
|
+
→ Open draft in Mail → https://mail.agent-native.com/_agent-native/open?app=mail&view=inbox&compose=…
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Click that link and Mail opens with the draft restored — focused exactly where you, the logged-in user, are. The agent never had to know your session; it just produced the artifact.
|
|
59
|
+
|
|
60
|
+
### Generic cross-app verbs {#cross-app}
|
|
61
|
+
|
|
62
|
+
On top of the per-action tools the MCP server exposes a stable verb set, so an external agent has a predictable surface without guessing per-app action names:
|
|
63
|
+
|
|
64
|
+
| Tool | Side effects | Returns |
|
|
65
|
+
| ------------------------------------------ | ------------ | ------------------------------------------------------------------------------------ |
|
|
66
|
+
| `list_apps` | none | workspace apps + their URLs / running state |
|
|
67
|
+
| `open_app({ app, view, params? })` | none | a `buildDeepLink` URL (surfaces as an "Open …" link) |
|
|
68
|
+
| `ask_app({ app, message })` | agent loop | routes a natural-language task to that app's in-app agent (delegates to `ask-agent`) |
|
|
69
|
+
| `create_workspace_app({ name, template })` | scaffolds | a new app booted via the workspace path, plus its running URL + deep link |
|
|
70
|
+
| `list_templates` | none | the allow-listed templates only |
|
|
71
|
+
|
|
72
|
+
`create_workspace_app` rejects any non-allow-listed template — the public template allow-list in `packages/shared-app-config/templates.ts` is authoritative and CI-guarded; an external agent cannot widen it. A same-named template action overrides a builtin (template-over-core precedence). Disable the whole set with `MCPConfig.builtinCrossAppTools: false`.
|
|
73
|
+
|
|
74
|
+
### Per-app tour {#tour}
|
|
75
|
+
|
|
76
|
+
Every allow-listed template that produces or lists a navigable resource ships a `link` builder, and the ingest-heavy ones ship a GET + `publicAgent` action so a connected agent can pull live state:
|
|
77
|
+
|
|
78
|
+
- **Mail** — `manage-draft` returns a `compose`-encoded deep link; clicking it opens the inbox with the draft restored into a `compose-<id>`. `list-emails` / `search-emails` point at a filtered inbox view.
|
|
79
|
+
- **Calendar** — `create-event` returns `buildDeepLink({ app: "calendar", view: "calendar", params: { eventId, date } })`; the click lands on the calendar with that event focused on its date.
|
|
80
|
+
- **Analytics** — `update-dashboard` / `save-analysis` return `buildDeepLink({ app: "analytics", view: "adhoc", params: { dashboardId } })`; the agent builds a dashboard over MCP and hands back "Open dashboard in Analytics".
|
|
81
|
+
- **Design** — `get-design-snapshot` is the GET + `publicAgent` ingest action: it returns the **live** Yjs file contents plus the resolved tweak values so the agent continues from the tuned design, not the original tokens. `apply-tweaks` round-trips back with an "Open design" editor link.
|
|
82
|
+
- **Content** — `pull-document` is the GET + `publicAgent` ingest action: it flushes any open live collaborative session to SQL first so the external agent ingests exactly what the user sees, then surfaces a deep link to the document.
|
|
83
|
+
- **Brain** — `ask-brain` / `search-everything` return a cited answer plus a deep link to the underlying knowledge/capture, so a terminal agent's lookup links straight back into the source in the running app.
|
|
50
84
|
|
|
51
|
-
##
|
|
85
|
+
## Authoring: the `link` builder {#link-builder}
|
|
52
86
|
|
|
53
|
-
`defineAction` accepts an optional `link` builder. When set, every MCP/A2A result for that tool auto-appends a markdown `[label →](absoluteUrl)` block and a structured `_meta["agent-native/openLink"] = { label, view, webUrl, desktopUrl }`. `tools/list` adds `annotations["agent-native/producesOpenLink"]` and a description suffix so the external agent knows the tool yields an openable link and should surface it.
|
|
87
|
+
This section is for template authors. `defineAction` accepts an optional `link` builder. When set, every MCP/A2A result for that tool auto-appends a markdown `[label →](absoluteUrl)` block and a structured `_meta["agent-native/openLink"] = { label, view, webUrl, desktopUrl }`. `tools/list` adds `annotations["agent-native/producesOpenLink"]` and a description suffix so the external agent knows the tool yields an openable link and should surface it.
|
|
54
88
|
|
|
55
89
|
Build the URL with `buildDeepLink(...)` — it is the single source of truth for the open-route format. Never hand-format the `/_agent-native/open` URL.
|
|
56
90
|
|
|
@@ -91,7 +125,7 @@ The `link` builder is **pure and synchronous — no I/O, no awaits**. It runs be
|
|
|
91
125
|
|
|
92
126
|
`buildDeepLink({ app, view, params?, to?, compose? })` returns the app-relative path `/_agent-native/open?app=…&view=…&<recordId>=…`. The MCP layer turns that into an absolute web URL (`toAbsoluteOpenUrl`, using the request origin) and a desktop `agentnative://open?…` URL (`toDesktopOpenUrl`); the markdown link uses the desktop URL when the client signals `target: "desktop"`.
|
|
93
127
|
|
|
94
|
-
|
|
128
|
+
### The `/_agent-native/open` route {#open-route}
|
|
95
129
|
|
|
96
130
|
When the user clicks the link in any browser or inline webview, `GET /_agent-native/open` (`createOpenRouteHandler`, mounted by the core routes plugin):
|
|
97
131
|
|
|
@@ -102,11 +136,11 @@ When the user clicks the link in any browser or inline webview, `GET /_agent-nat
|
|
|
102
136
|
|
|
103
137
|
Cross-origin, scheme-relative `//host`, and control-char redirects are rejected (open-redirect guard). The route can be disabled per app via `disableOpenRoute`.
|
|
104
138
|
|
|
105
|
-
|
|
139
|
+
#### The browser-session identity rule {#identity-rule}
|
|
106
140
|
|
|
107
141
|
The link carries **no privileged state** — it is just `view` + record ids + filters. The record-focusing `navigate` write is scoped to whoever is logged into the **browser**, never the external agent's MCP token. So an agent authenticated as one identity can hand a user a link, and when that user clicks it the record opens where _the user_ is logged in. This is what makes the deep link safe to surface in a terminal or chat transcript. See [Context Awareness](/docs/context-awareness) for the `navigate` / `application_state` contract this bridges to.
|
|
108
142
|
|
|
109
|
-
|
|
143
|
+
### Ingest actions {#ingest}
|
|
110
144
|
|
|
111
145
|
An action an external agent reads to pull live app state into its own context must be:
|
|
112
146
|
|
|
@@ -125,43 +159,86 @@ export default defineAction({
|
|
|
125
159
|
|
|
126
160
|
`GET` + `readOnly` keeps the action side-effect-free and out of the screen-refresh poll. `publicAgent` is the **explicit opt-in** — a public web route never implies public MCP/A2A exposure; see [Actions](/docs/actions). Design/content ingest actions MUST read **live** state (the Yjs collaborative document, not the stale DB snapshot column) so the external agent sees what the user actually has on screen. Content's `pull-document` flushes any open live collab session to SQL first; design's `get-design-snapshot` returns the live Yjs file contents plus the user's resolved tweak values.
|
|
127
161
|
|
|
128
|
-
##
|
|
162
|
+
## Advanced: local development & manual setup {#advanced}
|
|
129
163
|
|
|
130
|
-
|
|
164
|
+
The hosted `connect` flow above is the recommended path. The options below are for local development and hand-rolled setups.
|
|
131
165
|
|
|
132
|
-
|
|
133
|
-
| ------------------------------------------ | ------------ | ------------------------------------------------------------------------------------ |
|
|
134
|
-
| `list_apps` | none | workspace apps + their dev URLs / running state |
|
|
135
|
-
| `open_app({ app, view, params? })` | none | a `buildDeepLink` URL (surfaces as an "Open …" link) |
|
|
136
|
-
| `ask_app({ app, message })` | agent loop | routes a natural-language task to that app's in-app agent (delegates to `ask-agent`) |
|
|
137
|
-
| `create_workspace_app({ name, template })` | scaffolds | a new app booted via the workspace path, plus its running URL + deep link |
|
|
138
|
-
| `list_templates` | none | the allow-listed templates only |
|
|
166
|
+
### Local development {#local-dev}
|
|
139
167
|
|
|
140
|
-
|
|
168
|
+
Run your app locally (`pnpm dev` / `agent-native dev`), then point a local agent at it with one command:
|
|
141
169
|
|
|
142
|
-
|
|
170
|
+
```bash
|
|
171
|
+
agent-native mcp install --client claude-code|claude-code-cli|codex|cowork \
|
|
172
|
+
[--app <id>] [--scope user|project]
|
|
173
|
+
```
|
|
143
174
|
|
|
144
|
-
|
|
175
|
+
It provisions a token (a random `ACCESS_TOKEN` into the workspace `.env` for local dev, or a signed JWT if it detects a hosted origin) and writes an idempotent stdio server entry:
|
|
145
176
|
|
|
146
|
-
- **
|
|
147
|
-
- **
|
|
148
|
-
- **
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
- **claude-code / claude-code-cli** — an `mcpServers` entry in `.mcp.json` (project scope, default) or `~/.claude.json` (`--scope user`).
|
|
178
|
+
- **cowork** — the same Claude Code JSON shape in `~/.cowork/mcp.json`.
|
|
179
|
+
- **codex** — an `[mcp_servers.<name>]` block in `~/.codex/config.toml`.
|
|
180
|
+
|
|
181
|
+
The entry runs `agent-native mcp serve --app <id>`, which by default is a **thin stdio proxy** to the running local app's `/_agent-native/mcp` — so the live action registry, HMR, and correct deep links stay the single source of truth. Pass `--standalone` to build the registry in-process instead. When `agent-native mcp install` detects a hosted origin (a non-localhost `APP_URL` / `BETTER_AUTH_URL` / `AGENT_NATIVE_MCP_URL` in the workspace `.env`), it writes an `http` client entry pointing at `<origin>/_agent-native/mcp` with a `Bearer` JWT instead of a stdio entry.
|
|
182
|
+
|
|
183
|
+
Companion subcommands:
|
|
184
|
+
|
|
185
|
+
| Command | What it does |
|
|
186
|
+
| ----------------------------------------- | ------------------------------------------------------------------- |
|
|
187
|
+
| `agent-native mcp serve [--app <id>]` | Run the MCP stdio transport (what client configs spawn). |
|
|
188
|
+
| `agent-native mcp install --client <c>` | Provision a token + write the client's MCP config (idempotent). |
|
|
189
|
+
| `agent-native mcp uninstall --client <c>` | Remove the named MCP entry from a client's config (idempotent). |
|
|
190
|
+
| `agent-native mcp status` | Show resolved MCP URL/port, token state, and per-client entries. |
|
|
191
|
+
| `agent-native mcp token [--rotate]` | Print (or rotate) the local `ACCESS_TOKEN` in the workspace `.env`. |
|
|
192
|
+
|
|
193
|
+
Restart the client after `install` so it picks up the new MCP server.
|
|
194
|
+
|
|
195
|
+
### Manual `.mcp.json` HTTP entry {#manual-entry}
|
|
196
|
+
|
|
197
|
+
You can also write the MCP client config by hand against any deployed endpoint with a token you supply yourself (an `ACCESS_TOKEN`, or an `A2A_SECRET`-signed JWT carrying the caller's `sub` + `org_domain` so tool runs stay tenant-scoped):
|
|
198
|
+
|
|
199
|
+
```jsonc
|
|
200
|
+
// .mcp.json
|
|
201
|
+
{
|
|
202
|
+
"mcpServers": {
|
|
203
|
+
"analytics": {
|
|
204
|
+
"type": "url",
|
|
205
|
+
"url": "https://analytics.agent-native.com/_agent-native/mcp",
|
|
206
|
+
"headers": { "Authorization": "Bearer <ACCESS_TOKEN-or-JWT>" },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This is the unmanaged equivalent of what `connect` writes for you. See [MCP Protocol](/docs/mcp-protocol) for the full auth env-var matrix.
|
|
213
|
+
|
|
214
|
+
### Dev vs production tool surface {#dev-vs-prod}
|
|
215
|
+
|
|
216
|
+
In plain local dev (`NODE_ENV=development` and `AGENT_MODE !== "production"`) the MCP `tools/list` deliberately exposes only the generic builtins plus actions with `publicAgent.requiresAuth === false` — the per-app ingest actions (`requiresAuth: true`) and mutating actions (no `publicAgent`) are filtered out (`filterPublicAgentActions`). The full per-app surface appears when the request is authenticated as a real caller: a deployed / `AGENT_MODE=production` app, or a local app reached through `connect` / `agent-native mcp install` (which provisions a token so the caller has an identity). So if `tools/list` looks sparse, you are hitting an unauthenticated dev endpoint — connect (or present a token) rather than assuming the action is missing.
|
|
217
|
+
|
|
218
|
+
## How it works & security {#how-it-works}
|
|
219
|
+
|
|
220
|
+
The hosted `connect` flow never copies the deployment's shared secret. Instead:
|
|
221
|
+
|
|
222
|
+
- A logged-in browser session mints a **per-user, scoped, revocable** token — an `A2A_SECRET`-signed JWT carrying the caller's `sub` + `org_domain` and a unique `jti`, so every tool run stays tenant-scoped via `runWithRequestContext`.
|
|
223
|
+
- The existing `/_agent-native/mcp` endpoint accepts that token like any other bearer (see [MCP Protocol](/docs/mcp-protocol)) — no new endpoint, no new transport.
|
|
224
|
+
- The same Connect page lists every token you've minted and lets you **revoke** any of them by `jti`. Treat them like personal access tokens: one per agent client, revoke when a machine is decommissioned.
|
|
225
|
+
- The deep link the agent hands back carries no privileged state. The record-focusing `navigate` write is always scoped to the **browser** session, never the agent's token — so a link is safe to paste into a terminal or chat transcript.
|
|
152
226
|
|
|
153
227
|
## Do / Don't {#do-dont}
|
|
154
228
|
|
|
155
229
|
**Do**
|
|
156
230
|
|
|
231
|
+
- Connect your own agent to a hosted app with `npx @agent-native/core connect <url>` (or `--all`) — it's the frictionless path.
|
|
157
232
|
- Add a `link` builder to any action that produces or lists a navigable resource (draft, event, dashboard, document).
|
|
158
233
|
- Build the URL with `buildDeepLink(...)` — the single source of truth for the open-route format.
|
|
159
234
|
- Keep `link` pure and synchronous; return `null` when there's nothing to open.
|
|
160
235
|
- Make external-agent ingest actions GET + `readOnly` + `publicAgent`, and read live (Yjs) state, not the stale DB column.
|
|
161
236
|
- Let the open route resolve the browser session; pass record ids as deep-link params and let the UI focus them via the polled `navigate` command.
|
|
237
|
+
- Revoke a minted connect token by `jti` when an agent client is decommissioned.
|
|
162
238
|
|
|
163
239
|
**Don't**
|
|
164
240
|
|
|
241
|
+
- Copy a deployment's shared `ACCESS_TOKEN` / `A2A_SECRET` into a client config when `connect` can mint a per-user, revocable token instead.
|
|
165
242
|
- Hand-format the `/_agent-native/open` URL — always go through `buildDeepLink`.
|
|
166
243
|
- Do I/O, awaits, DB reads, or app-state reads inside a `link` builder.
|
|
167
244
|
- Scope the `navigate` write to the agent token, or pass privileged state through the deep link — it's a pure pointer.
|
|
@@ -175,3 +252,5 @@ Every allow-listed template that produces or lists a navigable resource ships a
|
|
|
175
252
|
- [A2A Protocol](/docs/a2a-protocol) — the `ask-agent` meta-tool and JSON-RPC peer calls.
|
|
176
253
|
- [Actions](/docs/actions) — defining actions, `publicAgent`, GET / `readOnly`.
|
|
177
254
|
- [Context Awareness](/docs/context-awareness) — the `navigate` / `application_state` contract the open route bridges to.
|
|
255
|
+
</content>
|
|
256
|
+
</invoke>
|
|
@@ -209,7 +209,7 @@ In Agent-Native Code, Desktop, or the internal run surface, connect providers th
|
|
|
209
209
|
|
|
210
210
|
## Agent-Native Code
|
|
211
211
|
|
|
212
|
-
Agent-Native Desktop includes a **Agent-Native Code** hub for long-running coding-agent sessions. It is the general Code app/surface in Desktop, and it pairs with the `agent-native code` shell as the primary CLI/Desktop coding experience. A bare prompt is the generic coding session, and `/migrate` is one specialized capability there: the hub shows recent and active runs, opens a transcript-first session view
|
|
212
|
+
Agent-Native Desktop includes a **Agent-Native Code** hub for long-running coding-agent sessions. It is the general Code app/surface in Desktop, and it pairs with the `agent-native code` shell as the primary CLI/Desktop coding experience. A bare prompt is the generic coding session, and `/migrate` is one specialized capability there: the hub shows recent and active runs, opens a transcript-first session view with compact tool summaries and artifacts, sends follow-up prompts, stops tracked runners, opens a terminal in the run workspace, and handles links like:
|
|
213
213
|
|
|
214
214
|
```text
|
|
215
215
|
agentnative://open?goal=migrate&run=<runId>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Framework for agent-native application development — where AI agents and UI share state via files",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"./usage": "./dist/usage/store.js",
|
|
79
79
|
"./connections": "./dist/connections/index.js",
|
|
80
80
|
"./code-agents": "./dist/code-agents/index.js",
|
|
81
|
+
"./code-agents/transcript-normalizer": "./dist/code-agents/transcript-normalizer.js",
|
|
81
82
|
"./workspace-connections": "./dist/workspace-connections/index.js",
|
|
82
83
|
"./notifications": "./dist/notifications/index.js",
|
|
83
84
|
"./client/notifications": "./dist/client/notifications/index.js",
|