@agent-native/dispatch 0.14.3 → 0.14.4
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 +114 -128
- package/dist/actions/list_apps.d.ts +1 -0
- package/dist/actions/list_apps.js +16 -8
- package/dist/actions/list_apps.js.map +1 -1
- package/dist/actions/set-mcp-app-access.js +1 -4
- package/dist/actions/set-mcp-app-access.js.map +1 -1
- package/dist/components/action-query-error.d.ts +8 -0
- package/dist/components/action-query-error.d.ts.map +1 -0
- package/dist/components/action-query-error.js +10 -0
- package/dist/components/action-query-error.js.map +1 -0
- package/dist/components/app-keys-popover.d.ts +4 -0
- package/dist/components/app-keys-popover.d.ts.map +1 -1
- package/dist/components/app-keys-popover.js +26 -14
- package/dist/components/app-keys-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +31 -27
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +19 -21
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/ui/sheet.js +1 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/toast.js +1 -1
- package/dist/components/ui/toast.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +8 -5
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +6 -1
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -1
- package/dist/components/workspace-resource-impact-preview.js +6 -1
- package/dist/components/workspace-resource-impact-preview.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +6 -1
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +5 -8
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/approval.d.ts.map +1 -1
- package/dist/routes/pages/approval.js +6 -1
- package/dist/routes/pages/approval.js.map +1 -1
- package/dist/routes/pages/approvals.d.ts.map +1 -1
- package/dist/routes/pages/approvals.js +8 -5
- package/dist/routes/pages/approvals.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +4 -2
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +9 -3
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +4 -2
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/chat.d.ts.map +1 -1
- package/dist/routes/pages/chat.js +16 -2
- package/dist/routes/pages/chat.js.map +1 -1
- package/dist/routes/pages/destinations.d.ts.map +1 -1
- package/dist/routes/pages/destinations.js +5 -3
- package/dist/routes/pages/destinations.js.map +1 -1
- package/dist/routes/pages/identities.d.ts.map +1 -1
- package/dist/routes/pages/identities.js +5 -3
- package/dist/routes/pages/identities.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +9 -3
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +7 -6
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +23 -7
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +9 -3
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +2 -2
- package/dist/server/lib/dispatch-store.js.map +1 -1
- package/dist/server/lib/mcp-access-store.d.ts.map +1 -1
- package/dist/server/lib/mcp-access-store.js +30 -2
- package/dist/server/lib/mcp-access-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.d.ts +1 -1
- package/package.json +2 -2
- package/src/actions/list_apps.spec.ts +52 -0
- package/src/actions/list_apps.ts +16 -8
- package/src/actions/set-mcp-app-access.spec.ts +73 -0
- package/src/actions/set-mcp-app-access.ts +1 -5
- package/src/components/action-query-error.spec.tsx +56 -0
- package/src/components/action-query-error.tsx +32 -0
- package/src/components/app-keys-popover.spec.tsx +85 -0
- package/src/components/app-keys-popover.tsx +76 -50
- package/src/components/dispatch-control-plane.spec.tsx +106 -0
- package/src/components/dispatch-control-plane.tsx +71 -115
- package/src/components/index.ts +1 -0
- package/src/components/layout/Layout.spec.tsx +42 -2
- package/src/components/layout/Layout.tsx +22 -27
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/workspace-app-card.spec.tsx +80 -0
- package/src/components/workspace-app-card.tsx +36 -25
- package/src/components/workspace-resource-effective-stack.tsx +13 -1
- package/src/components/workspace-resource-impact-preview.tsx +12 -1
- package/src/routes/pages/$appId.tsx +17 -1
- package/src/routes/pages/agents.tsx +24 -11
- package/src/routes/pages/approval.tsx +16 -4
- package/src/routes/pages/approvals.tsx +108 -89
- package/src/routes/pages/apps.$appId.tsx +9 -2
- package/src/routes/pages/apps.tsx +20 -7
- package/src/routes/pages/audit.tsx +29 -20
- package/src/routes/pages/chat.spec.tsx +94 -0
- package/src/routes/pages/chat.tsx +30 -10
- package/src/routes/pages/destinations.tsx +75 -62
- package/src/routes/pages/identities.tsx +74 -63
- package/src/routes/pages/integrations.tsx +17 -10
- package/src/routes/pages/thread-debug.tsx +18 -12
- package/src/routes/pages/vault.tsx +59 -26
- package/src/routes/pages/workspace.tsx +69 -55
- package/src/server/lib/approval-fencing.spec.ts +113 -2
- package/src/server/lib/dispatch-store.ts +2 -0
- package/src/server/lib/mcp-access-store.spec.ts +116 -4
- package/src/server/lib/mcp-access-store.ts +37 -2
- package/src/server/lib/mcp-gateway.spec.ts +79 -3
package/README.md
CHANGED
|
@@ -1,146 +1,132 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agent-native/dispatch
|
|
2
|
+
|
|
3
|
+
Workspace control plane for agent-native apps — vault, integrations,
|
|
4
|
+
destinations, cross-app workspace resources, recurring "dream" report jobs,
|
|
5
|
+
and cross-app delegation, shipped as a single drop-in package.
|
|
6
|
+
|
|
7
|
+
Powers the `dispatch` template. Provides:
|
|
8
|
+
|
|
9
|
+
- **Drizzle schemas** — destinations, identity links, link tokens, approval
|
|
10
|
+
requests, audit events, dream reports/proposals, vault secrets/grants/
|
|
11
|
+
requests/audit, workspace resources/grants
|
|
12
|
+
- **Server layer** — `setupDispatch(config)` plus Nitro plugins for auth,
|
|
13
|
+
integrations, agent chat, DB, and core routes
|
|
14
|
+
- **Actions** — ~90 `defineAction` modules (vault grants/requests, workspace
|
|
15
|
+
resource grants, destinations, dream jobs, provider-api catalog/docs/
|
|
16
|
+
request, connected-agent discovery, audit/approvals, platform messaging,
|
|
17
|
+
and more) consumed as agent tools + HTTP endpoints
|
|
18
|
+
- **Routes** — a full React Router 7 `RouteConfig[]` (chat, overview, apps,
|
|
19
|
+
vault, integrations, agents, workspace, messaging, destinations,
|
|
20
|
+
identities, approvals, automations, audit, settings, dreams, extensions,
|
|
21
|
+
...) to splat into a consumer's `app/routes.ts`
|
|
22
|
+
- **React components** — `DispatchShell`, `Layout`/`NavContent`,
|
|
23
|
+
`CreateAppPopover`/`CreateAppFlow`, `AppKeysPopover`, plus a full
|
|
24
|
+
shadcn/ui-based `components/ui/*` primitive set
|
|
25
|
+
- **Styles** — `dispatch.css` Tailwind layer
|
|
26
|
+
|
|
27
|
+
## Install
|
|
2
28
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Agent-Native is an open-source framework for rapidly building robust applications with agents at their core.
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
// One action powers every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
|
|
9
|
-
export default defineAction({
|
|
10
|
-
schema: z.object({
|
|
11
|
-
emailId: z.string(),
|
|
12
|
-
body: z.string(),
|
|
13
|
-
}),
|
|
14
|
-
run: async ({ emailId, body }) => {
|
|
15
|
-
await db.insert(replies).values({ emailId, body });
|
|
16
|
-
},
|
|
17
|
-
});
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add @agent-native/dispatch
|
|
18
31
|
```
|
|
19
32
|
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
- **[Backend agnostic](https://agent-native.com/docs/database)**: Plug in any Drizzle-supported SQL database and Nitro-compatible host.
|
|
23
|
-
- **[Toolkits](https://agent-native.com/docs/agent-native-toolkit)**: Reusable building blocks for collaboration, sharing, settings, teams, and observability.
|
|
24
|
-
|
|
25
|
-
## Don't pick between apps or agents.
|
|
26
|
-
|
|
27
|
-
Agent-native apps are both
|
|
28
|
-
|
|
29
|
-
| | SaaS Tools | Raw AI Agents | Internal Tools | Agent-Native App |
|
|
30
|
-
| ----------------- | ------------------ | ----------------------- | -------------------------- | ----------------------- |
|
|
31
|
-
| **UI** | Polished but rigid | None | Mixed quality | Full UI, fork & go |
|
|
32
|
-
| **AI** | Bolted on | Powerful | Shallowly connected | Agent-first, integrated |
|
|
33
|
-
| **Customization** | Can't | Instructions and skills | Full, but high maintenance | Agent modifies the app |
|
|
34
|
-
| **Ownership** | Rented | Somewhat yours | You own the code | You own the code |
|
|
35
|
-
|
|
36
|
-
## Try an Agent-Native app
|
|
37
|
-
|
|
38
|
-
Fork a working app and let the agent evolve it. **You can customize everything.**
|
|
39
|
-
|
|
40
|
-
<table>
|
|
41
|
-
<tr>
|
|
42
|
-
<td width="33%" align="center" valign="top">
|
|
43
|
-
|
|
44
|
-
**Clips**
|
|
45
|
-
|
|
46
|
-
<a href="https://agent-native.com/templates/clips"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F189ebd9b2f2b4f0ead3b33138d4e4c10?format=webp&width=800" alt="Clips app" width="100%" /></a>
|
|
47
|
-
|
|
48
|
-
**Agent-Native Loom**
|
|
49
|
-
|
|
50
|
-
Record your screen with auto-transcripts and captured browser debug logs, share a link, and let an agent read the transcript, see timestamped frames, and fix the bug.
|
|
51
|
-
|
|
52
|
-
</td>
|
|
53
|
-
<td width="33%" align="center" valign="top">
|
|
54
|
-
|
|
55
|
-
**Plans**
|
|
56
|
-
|
|
57
|
-
<a href="https://agent-native.com/templates/plan"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fefc6a3ac908149fa92e2b9392c0bb372?format=webp&width=800" alt="Plans app" width="100%" /></a>
|
|
58
|
-
|
|
59
|
-
**Visual plan mode for coding agents**
|
|
60
|
-
|
|
61
|
-
Install `/visual-plan` and `/visual-recap` so your coding agent can plan before it builds and recap changes after they land. High-level code reviews with diagrams, wireframes, annotations, and review links.
|
|
62
|
-
|
|
63
|
-
</td>
|
|
64
|
-
<td width="33%" align="center" valign="top">
|
|
65
|
-
|
|
66
|
-
**Design**
|
|
67
|
-
|
|
68
|
-
<a href="https://agent-native.com/templates/design"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fe2c86908c2fa4f119ee4aa90b4823944?format=webp&width=800" alt="Design app" width="100%" /></a>
|
|
69
|
-
|
|
70
|
-
**Agent-Native Figma**
|
|
33
|
+
Peer-depends on `@agent-native/core` (`>=0.8.0`), `react`/`react-dom`
|
|
34
|
+
(`>=19.2.7`), and `react-router` (`>=8`).
|
|
71
35
|
|
|
72
|
-
|
|
36
|
+
## Compose
|
|
73
37
|
|
|
74
|
-
|
|
75
|
-
</tr>
|
|
76
|
-
<tr>
|
|
77
|
-
<td width="33%" align="center" valign="top">
|
|
38
|
+
Template's `app/routes.ts`:
|
|
78
39
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
**Agent-Native Notion/Obsidian**
|
|
84
|
-
|
|
85
|
-
Edit local Markdown/MDX files, generate rich interactive custom blocks, and draft, rewrite, or publish with an agent.
|
|
86
|
-
|
|
87
|
-
</td>
|
|
88
|
-
<td width="33%" align="center" valign="top">
|
|
89
|
-
|
|
90
|
-
**Analytics**
|
|
91
|
-
|
|
92
|
-
<a href="https://agent-native.com/templates/analytics"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F4933a80cc3134d7e874631f688be828a?format=webp&width=800" alt="Analytics app" width="100%" /></a>
|
|
93
|
-
|
|
94
|
-
**Agent-Native Amplitude, Fullstory**
|
|
95
|
-
|
|
96
|
-
Connect analytics data sources, prompt for real charts, and build reusable dashboards.
|
|
97
|
-
|
|
98
|
-
</td>
|
|
99
|
-
<td width="33%" align="center" valign="top">
|
|
100
|
-
|
|
101
|
-
**Chat**
|
|
102
|
-
|
|
103
|
-
<a href="https://agent-native.com/templates/chat"><img src="https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F6b36dc596fca4799815fa34c31e1c406?format=webp&width=800" alt="Chat app" width="100%" /></a>
|
|
104
|
-
|
|
105
|
-
**A minimal ChatGPT-style app for your own agent**
|
|
106
|
-
|
|
107
|
-
Chat-first app scaffold with durable threads, actions, auth, live sync, and a clean path to add screens or plug in your own agent backend.
|
|
108
|
-
|
|
109
|
-
</td>
|
|
110
|
-
</tr>
|
|
111
|
-
</table>
|
|
112
|
-
|
|
113
|
-
View the full app gallery at **[agent-native.com/apps](https://agent-native.com/apps)**, or build from scratch with the **[framework guide](https://agent-native.com/docs/getting-started)**.
|
|
114
|
-
|
|
115
|
-
## Quick Start
|
|
116
|
-
|
|
117
|
-
One command to start a new app locally.
|
|
40
|
+
```ts
|
|
41
|
+
import { type RouteConfig } from "@react-router/dev/routes";
|
|
42
|
+
import { dispatchRoutes } from "@agent-native/dispatch/routes";
|
|
118
43
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
pnpm dev
|
|
44
|
+
export default [
|
|
45
|
+
...localRoutes, // consumer's own routes win on collision
|
|
46
|
+
...dispatchRoutes, // dispatch fills in everything else
|
|
47
|
+
] satisfies RouteConfig;
|
|
124
48
|
```
|
|
125
49
|
|
|
126
|
-
|
|
50
|
+
Template's `server/plugins/*.ts` (each export lives in its own file so Nitro
|
|
51
|
+
auto-loads it):
|
|
127
52
|
|
|
128
|
-
|
|
53
|
+
```ts
|
|
54
|
+
// server/plugins/setup-dispatch.ts
|
|
55
|
+
import { setupDispatch } from "@agent-native/dispatch/server";
|
|
56
|
+
export default setupDispatch({ auth: { googleOnly: true } });
|
|
129
57
|
|
|
130
|
-
|
|
58
|
+
// server/plugins/auth.ts
|
|
59
|
+
export { dispatchAuthPlugin as default } from "@agent-native/dispatch/server";
|
|
131
60
|
|
|
132
|
-
|
|
61
|
+
// server/plugins/integrations.ts
|
|
62
|
+
export { dispatchIntegrationsPlugin as default } from "@agent-native/dispatch/server";
|
|
133
63
|
|
|
134
|
-
|
|
64
|
+
// server/plugins/agent-chat.ts
|
|
65
|
+
export { dispatchAgentChatPlugin as default } from "@agent-native/dispatch/server";
|
|
135
66
|
|
|
136
|
-
|
|
67
|
+
// server/plugins/db.ts
|
|
68
|
+
export { dispatchDbPlugin as default } from "@agent-native/dispatch/server";
|
|
137
69
|
|
|
138
|
-
|
|
70
|
+
// server/plugins/core-routes.ts
|
|
71
|
+
export { dispatchCoreRoutesPlugin as default } from "@agent-native/dispatch/server";
|
|
72
|
+
```
|
|
139
73
|
|
|
140
|
-
|
|
141
|
-
**[DEVELOPMENT.md](./DEVELOPMENT.md)** for local setup, workspace structure,
|
|
142
|
-
and guard scripts.
|
|
74
|
+
Template's `server/db/schema.ts`:
|
|
143
75
|
|
|
144
|
-
|
|
76
|
+
```ts
|
|
77
|
+
export * from "@agent-native/dispatch/db";
|
|
78
|
+
```
|
|
145
79
|
|
|
146
|
-
|
|
80
|
+
`@agent-native/dispatch/actions` doesn't need a manual import for most
|
|
81
|
+
consumers: `import "@agent-native/dispatch/server"` (pulled in by the plugin
|
|
82
|
+
wiring above) side-effect-registers every Dispatch action via
|
|
83
|
+
`registerPackageActions`, and the framework's `autoDiscoverActions` merges
|
|
84
|
+
them in after the consumer's local `actions/` directory. Drop a same-named
|
|
85
|
+
file in the consumer's own `actions/` to override any single action.
|
|
86
|
+
|
|
87
|
+
## Exports
|
|
88
|
+
|
|
89
|
+
| Subpath | What it is |
|
|
90
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `.` | Shared config types only — `DispatchConfig`, `DispatchAuthConfig`, `DispatchIntegrationsConfig`. Import this where a type is needed without pulling in server/client code. |
|
|
92
|
+
| `./routes` | `dispatchRoutes` — a programmatic `RouteConfig[]` covering every Dispatch page (chat, overview, metrics, operations, apps, vault, integrations, agents, workspace, messaging, destinations, identities, approvals, automations, audit, settings, dreams, extensions, thread-debug, team, and a workspace-app catch-all route). |
|
|
93
|
+
| `./routes/pages/*` | Individual compiled page modules the routes above point at (e.g. `./routes/pages/vault.js`). Not normally imported directly. |
|
|
94
|
+
| `./server` | `setupDispatch(config)` plus the Nitro plugin set: `dispatchAuthPlugin`, `dispatchIntegrationsPlugin`, `dispatchAgentChatPlugin`, `dispatchDbPlugin`, `dispatchCoreRoutesPlugin`. Importing this module also side-effect-registers all Dispatch actions. |
|
|
95
|
+
| `./server/lib/thread-link-preview` | `loadThreadLinkPreview(threadId)` — server-only helper (reads request context and checks thread ownership) that builds link-preview metadata for shared thread URLs. |
|
|
96
|
+
| `./actions` | `dispatchActions` — the flat name → `ActionEntry` map of every Dispatch action (vault, workspace resources, destinations, dream jobs, provider-api catalog/docs/request, connected agents, audit, approvals, platform messaging, and more). |
|
|
97
|
+
| `./db` | `getDb()` / `db()`, the Drizzle `schema` namespace, the Dispatch table exports (`dispatchDestinations`, `dispatchIdentityLinks`, `dispatchLinkTokens`, `dispatchApprovalRequests`, `dispatchAuditEvents`, `dispatchDreams`, `dispatchDreamProposals`, `vaultSecrets`, `vaultGrants`, `vaultRequests`, `vaultAuditLog`, `workspaceResources`, `workspaceResourceGrants`), and migrations. |
|
|
98
|
+
| `./lib/thread-link-preview` | Isomorphic helpers behind the server one above — `extractThreadPreviewImageUrl`, `buildThreadLinkPreviewMeta`, and the `ThreadLinkPreview` type. Safe to import from client code. |
|
|
99
|
+
| `./components` | Escape-hatch UI pieces for custom layouts: `DispatchShell`, `Layout`/`NavContent` (plus `DispatchExtensionConfig`/`DispatchNavIcon`/`DispatchNavItem`/`DispatchNavSection` types), `CreateAppPopover`/`CreateAppFlow`, `AppKeysPopover`. Most consumers only need `dispatchRoutes` and never import from here directly. |
|
|
100
|
+
| `./components/ui/*` | The shadcn/ui primitive set Dispatch's own UI is built on (`button`, `dialog`, `dropdown-menu`, `card`, `command`, `calendar`, `chart`, ...), exported per-file so a consumer can reuse one primitive without pulling in the rest. |
|
|
101
|
+
| `./styles/dispatch.css` | Tailwind layer with Dispatch-specific styles. Import once in the consumer's global CSS entry. |
|
|
102
|
+
|
|
103
|
+
## What it's for
|
|
104
|
+
|
|
105
|
+
Dispatch is the workspace control plane sitting above individual template
|
|
106
|
+
apps:
|
|
107
|
+
|
|
108
|
+
- **Vault** — shared workspace secrets with a request → grant → audit flow,
|
|
109
|
+
scoped per app.
|
|
110
|
+
- **Workspace resources** — cross-app resources (e.g. shared instructions or
|
|
111
|
+
data) with explicit per-app grants and effective-context resolution.
|
|
112
|
+
- **Integrations & destinations** — outbound messaging channel setup (Slack,
|
|
113
|
+
Teams, Discord, Telegram, WhatsApp, email, and more) and a destinations
|
|
114
|
+
queue for delivering agent output.
|
|
115
|
+
- **Dream jobs** — a recurring background job (`ensure-dream-job`) that scans
|
|
116
|
+
agent thread-debug history across connected sources and produces pending,
|
|
117
|
+
evidence-backed proposals (`create-dream-report`, `list-dream-candidates`,
|
|
118
|
+
`apply-dream-proposal` / `reject-dream-proposal`) without an extra LLM call.
|
|
119
|
+
- **Connected agents & provider APIs** — discovery of other workspace apps
|
|
120
|
+
(`list-connected-agents`, `open_app`, `ask_app`) and a generic provider-api
|
|
121
|
+
catalog/docs/request surface for calling third-party APIs through stored
|
|
122
|
+
credentials.
|
|
123
|
+
- **Approvals & audit** — a shared approval-request/audit-event model used
|
|
124
|
+
across the above surfaces.
|
|
125
|
+
|
|
126
|
+
## Eject
|
|
127
|
+
|
|
128
|
+
There is no `agent-native.package.json` / CLI package-lifecycle wiring for
|
|
129
|
+
this package yet (unlike `@agent-native/scheduling`). To customize beyond the
|
|
130
|
+
`setupDispatch` config surface, copy the files you want to own out of
|
|
131
|
+
`node_modules/@agent-native/dispatch/src` into your own package and swap the
|
|
132
|
+
dependency manually.
|
|
@@ -9,17 +9,25 @@ export default defineAction({
|
|
|
9
9
|
parallelSafe: true,
|
|
10
10
|
run: async () => {
|
|
11
11
|
const apps = await listGrantedDispatchMcpApps();
|
|
12
|
+
const appSummaries = apps.map((app) => ({
|
|
13
|
+
id: app.id,
|
|
14
|
+
name: app.name,
|
|
15
|
+
description: app.description,
|
|
16
|
+
url: app.url,
|
|
17
|
+
running: true,
|
|
18
|
+
source: "dispatch-mcp-grant",
|
|
19
|
+
}));
|
|
12
20
|
return {
|
|
13
21
|
workspace: true,
|
|
14
22
|
gateway: "dispatch",
|
|
15
|
-
apps:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
})
|
|
23
|
+
apps: appSummaries,
|
|
24
|
+
// MCP model-visible results are deliberately text-only unless a tool is
|
|
25
|
+
// app-only or opens an MCP App. Supplying a compact, valid JSON message
|
|
26
|
+
// keeps large app catalogs machine-readable instead of letting the
|
|
27
|
+
// generic text limiter cut the full result into invalid JSON.
|
|
28
|
+
message: JSON.stringify({
|
|
29
|
+
apps: appSummaries.map(({ id, name }) => ({ id, name })),
|
|
30
|
+
}),
|
|
23
31
|
};
|
|
24
32
|
},
|
|
25
33
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list_apps.js","sourceRoot":"","sources":["../../src/actions/list_apps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,6LAA6L;IAC/L,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;IACvB,QAAQ,EAAE,IAAI;IACd,YAAY,EAAE,IAAI;IAClB,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,MAAM,IAAI,GAAG,MAAM,0BAA0B,EAAE,CAAC;QAChD,
|
|
1
|
+
{"version":3,"file":"list_apps.js","sourceRoot":"","sources":["../../src/actions/list_apps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,6LAA6L;IAC/L,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;IACvB,QAAQ,EAAE,IAAI;IACd,YAAY,EAAE,IAAI;IAClB,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,MAAM,IAAI,GAAG,MAAM,0BAA0B,EAAE,CAAC;QAChD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACtC,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,oBAAoB;SAC7B,CAAC,CAAC,CAAC;QACJ,OAAO;YACL,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,YAAY;YAClB,wEAAwE;YACxE,wEAAwE;YACxE,mEAAmE;YACnE,8DAA8D;YAC9D,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;gBACtB,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;aACzD,CAAC;SACH,CAAC;IACJ,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { defineAction } from \"@agent-native/core\";\nimport { z } from \"zod\";\n\nimport { listGrantedDispatchMcpApps } from \"../server/lib/mcp-gateway.js\";\n\nexport default defineAction({\n description:\n 'List the apps this Dispatch MCP gateway can route to, including \"dispatch\" itself for Dispatch-owned pages such as extensions. The result is filtered by Dispatch\\'s MCP app access policy.',\n schema: z.object({}),\n http: { method: \"GET\" },\n readOnly: true,\n parallelSafe: true,\n run: async () => {\n const apps = await listGrantedDispatchMcpApps();\n const appSummaries = apps.map((app) => ({\n id: app.id,\n name: app.name,\n description: app.description,\n url: app.url,\n running: true,\n source: \"dispatch-mcp-grant\",\n }));\n return {\n workspace: true,\n gateway: \"dispatch\",\n apps: appSummaries,\n // MCP model-visible results are deliberately text-only unless a tool is\n // app-only or opens an MCP App. Supplying a compact, valid JSON message\n // keeps large app catalogs machine-readable instead of letting the\n // generic text limiter cut the full result into invalid JSON.\n message: JSON.stringify({\n apps: appSummaries.map(({ id, name }) => ({ id, name })),\n }),\n };\n },\n});\n"]}
|
|
@@ -16,7 +16,7 @@ export default defineAction({
|
|
|
16
16
|
}),
|
|
17
17
|
run: async (args) => {
|
|
18
18
|
const agents = await discoverAgents("dispatch");
|
|
19
|
-
const knownIds = new Set(agents.map((agent) => agent.id));
|
|
19
|
+
const knownIds = new Set(["dispatch", ...agents.map((agent) => agent.id)]);
|
|
20
20
|
const selectedAppIds = Array.from(new Set(args.selectedAppIds
|
|
21
21
|
.map((id) => id.trim().toLowerCase())
|
|
22
22
|
.filter(Boolean)));
|
|
@@ -24,9 +24,6 @@ export default defineAction({
|
|
|
24
24
|
if (unknown.length > 0) {
|
|
25
25
|
throw new Error(`Unknown app(s): ${unknown.join(", ")}. Use list-mcp-app-access to see available app IDs.`);
|
|
26
26
|
}
|
|
27
|
-
if (args.mode === "selected-apps" && selectedAppIds.length === 0) {
|
|
28
|
-
throw new Error("selected-apps mode requires at least one app ID.");
|
|
29
|
-
}
|
|
30
27
|
await setDispatchMcpAppAccessSettings({
|
|
31
28
|
mode: args.mode,
|
|
32
29
|
selectedAppIds,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"set-mcp-app-access.js","sourceRoot":"","sources":["../../src/actions/set-mcp-app-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAC3E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,gBAAgB,MAAM,0BAA0B,CAAC;AAExD,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;AAEzD,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,sEAAsE;IACxE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,UAAU,CAAC,QAAQ,CACvB,6FAA6F,CAC9F;QACD,cAAc,EAAE,CAAC;aACd,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CAAC,+CAA+C,CAAC;KAC7D,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"set-mcp-app-access.js","sourceRoot":"","sources":["../../src/actions/set-mcp-app-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAC;AAC3E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,gBAAgB,MAAM,0BAA0B,CAAC;AAExD,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;AAEzD,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,sEAAsE;IACxE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,UAAU,CAAC,QAAQ,CACvB,6FAA6F,CAC9F;QACD,cAAc,EAAE,CAAC;aACd,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CAAC,+CAA+C,CAAC;KAC7D,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAC/B,IAAI,GAAG,CACL,IAAI,CAAC,cAAc;aAChB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;aACpC,MAAM,CAAC,OAAO,CAAC,CACnB,CACF,CAAC;QACF,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,qDAAqD,CAC3F,CAAC;QACJ,CAAC;QACD,MAAM,+BAA+B,CAAC;YACpC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,cAAc;SACf,CAAC,CAAC;QACH,MAAM,WAAW,CAAC;YAChB,MAAM,EAAE,oBAAoB;YAC5B,UAAU,EAAE,qBAAqB;YACjC,QAAQ,EAAE,aAAa;YACvB,OAAO,EACL,IAAI,CAAC,IAAI,KAAK,UAAU;gBACtB,CAAC,CAAC,yCAAyC;gBAC3C,CAAC,CAAC,kCAAkC,cAAc,CAAC,MAAM,kBAAkB;YAC/E,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE;SAC9C,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnB,OAAO,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { defineAction } from \"@agent-native/core\";\nimport { discoverAgents } from \"@agent-native/core/server/agent-discovery\";\nimport { z } from \"zod\";\n\nimport { recordAudit } from \"../server/lib/dispatch-store.js\";\nimport { setDispatchMcpAppAccessSettings } from \"../server/lib/mcp-access-store.js\";\nimport listMcpAppAccess from \"./list-mcp-app-access.js\";\n\nconst modeSchema = z.enum([\"all-apps\", \"selected-apps\"]);\n\nexport default defineAction({\n description:\n \"Set which apps are available through Dispatch's unified MCP gateway.\",\n schema: z.object({\n mode: modeSchema.describe(\n \"Use all-apps to expose every discovered app, or selected-apps to allow only selectedAppIds.\",\n ),\n selectedAppIds: z\n .array(z.string())\n .default([])\n .describe(\"App IDs to expose when mode is selected-apps.\"),\n }),\n run: async (args) => {\n const agents = await discoverAgents(\"dispatch\");\n const knownIds = new Set([\"dispatch\", ...agents.map((agent) => agent.id)]);\n const selectedAppIds = Array.from(\n new Set(\n args.selectedAppIds\n .map((id) => id.trim().toLowerCase())\n .filter(Boolean),\n ),\n );\n const unknown = selectedAppIds.filter((id) => !knownIds.has(id));\n if (unknown.length > 0) {\n throw new Error(\n `Unknown app(s): ${unknown.join(\", \")}. Use list-mcp-app-access to see available app IDs.`,\n );\n }\n await setDispatchMcpAppAccessSettings({\n mode: args.mode,\n selectedAppIds,\n });\n await recordAudit({\n action: \"mcp-access.updated\",\n targetType: \"dispatch-mcp-access\",\n targetId: \"unified-mcp\",\n summary:\n args.mode === \"all-apps\"\n ? \"Allowed Dispatch MCP access to all apps\"\n : `Allowed Dispatch MCP access to ${selectedAppIds.length} selected app(s)`,\n metadata: { mode: args.mode, selectedAppIds },\n }).catch(() => {});\n\n return listMcpAppAccess.run({});\n },\n});\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface ActionQueryErrorProps {
|
|
2
|
+
error?: unknown;
|
|
3
|
+
onRetry: () => unknown;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function ActionQueryError({ onRetry, className, }: ActionQueryErrorProps): import("node_modules/@types/react").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=action-query-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-query-error.d.ts","sourceRoot":"","sources":["../../src/components/action-query-error.tsx"],"names":[],"mappings":"AAMA,UAAU,qBAAqB;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,GACV,EAAE,qBAAqB,mDAgBvB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useT } from "@agent-native/core/client";
|
|
3
|
+
import { IconAlertTriangle, IconRefresh } from "@tabler/icons-react";
|
|
4
|
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert.js";
|
|
5
|
+
import { Button } from "./ui/button.js";
|
|
6
|
+
export function ActionQueryError({ onRetry, className, }) {
|
|
7
|
+
const t = useT();
|
|
8
|
+
return (_jsxs(Alert, { variant: "destructive", className: className, children: [_jsx(IconAlertTriangle, { className: "size-4" }), _jsx(AlertTitle, { children: t("dispatch.pages.dataLoadFailed") }), _jsxs(AlertDescription, { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("span", { children: t("dispatch.pages.dataLoadFailedDescription") }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, children: [_jsx(IconRefresh, { className: "size-4" }), t("dispatch.pages.tryAgain")] })] })] }));
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=action-query-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-query-error.js","sourceRoot":"","sources":["../../src/components/action-query-error.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC,MAAM,UAAU,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,GACa;IACtB,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IAEjB,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,aAAa,EAAC,SAAS,EAAE,SAAS,aAC/C,KAAC,iBAAiB,IAAC,SAAS,EAAC,QAAQ,GAAG,EACxC,KAAC,UAAU,cAAE,CAAC,CAAC,+BAA+B,CAAC,GAAc,EAC7D,MAAC,gBAAgB,IAAC,SAAS,EAAC,mDAAmD,aAC7E,yBAAO,CAAC,CAAC,0CAA0C,CAAC,GAAQ,EAC5D,MAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,OAAO,aAChE,KAAC,WAAW,IAAC,SAAS,EAAC,QAAQ,GAAG,EACjC,CAAC,CAAC,yBAAyB,CAAC,IACtB,IACQ,IACb,CACT,CAAC;AACJ,CAAC","sourcesContent":["import { useT } from \"@agent-native/core/client\";\nimport { IconAlertTriangle, IconRefresh } from \"@tabler/icons-react\";\n\nimport { Alert, AlertDescription, AlertTitle } from \"./ui/alert\";\nimport { Button } from \"./ui/button\";\n\ninterface ActionQueryErrorProps {\n error?: unknown;\n onRetry: () => unknown;\n className?: string;\n}\n\nexport function ActionQueryError({\n onRetry,\n className,\n}: ActionQueryErrorProps) {\n const t = useT();\n\n return (\n <Alert variant=\"destructive\" className={className}>\n <IconAlertTriangle className=\"size-4\" />\n <AlertTitle>{t(\"dispatch.pages.dataLoadFailed\")}</AlertTitle>\n <AlertDescription className=\"flex flex-wrap items-center justify-between gap-3\">\n <span>{t(\"dispatch.pages.dataLoadFailedDescription\")}</span>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={onRetry}>\n <IconRefresh className=\"size-4\" />\n {t(\"dispatch.pages.tryAgain\")}\n </Button>\n </AlertDescription>\n </Alert>\n );\n}\n"]}
|
|
@@ -7,5 +7,9 @@ interface AppKeysPopoverProps {
|
|
|
7
7
|
side?: "top" | "right" | "bottom" | "left";
|
|
8
8
|
}
|
|
9
9
|
export declare function AppKeysPopover({ appId, appName, trigger, align, side, }: AppKeysPopoverProps): import("node_modules/@types/react").JSX.Element;
|
|
10
|
+
export declare function AppKeysPanel({ appId, appName, }: {
|
|
11
|
+
appId: string;
|
|
12
|
+
appName: string;
|
|
13
|
+
}): import("node_modules/@types/react").JSX.Element;
|
|
10
14
|
export {};
|
|
11
15
|
//# sourceMappingURL=app-keys-popover.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-keys-popover.d.ts","sourceRoot":"","sources":["../../src/components/app-keys-popover.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"app-keys-popover.d.ts","sourceRoot":"","sources":["../../src/components/app-keys-popover.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAwB1D,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IACnC,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC5C;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,OAAO,EACP,KAAa,EACb,IAAe,GAChB,EAAE,mBAAmB,mDAwCrB;AAED,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GACR,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,mDA8LA"}
|
|
@@ -3,22 +3,27 @@ import { useActionMutation, useActionQuery } from "@agent-native/core/client";
|
|
|
3
3
|
import { IconCheck, IconLoader2, IconRefresh, IconSettings, } from "@tabler/icons-react";
|
|
4
4
|
import { useMemo, useState } from "react";
|
|
5
5
|
import { toast } from "sonner";
|
|
6
|
+
import { ActionQueryError } from "./action-query-error.js";
|
|
6
7
|
import { Button } from "./ui/button.js";
|
|
7
8
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover.js";
|
|
8
9
|
import { Skeleton } from "./ui/skeleton.js";
|
|
10
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip.js";
|
|
9
11
|
export function AppKeysPopover({ appId, appName, trigger, align = "end", side = "bottom", }) {
|
|
10
12
|
const [open, setOpen] = useState(false);
|
|
11
|
-
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: trigger ?? (_jsx(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: trigger ?? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Manage keys for ${appName}`, onClick: (event) => {
|
|
14
|
+
// Keep parent card click handlers from also firing. Do not
|
|
15
|
+
// preventDefault here: Radix uses the same click to open the
|
|
16
|
+
// popover trigger.
|
|
17
|
+
event.stopPropagation();
|
|
18
|
+
}, className: "size-7 rounded-md p-0 text-muted-foreground transition-[background-color,color] hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground", children: _jsx(IconSettings, { size: 14 }) })) }) }), _jsx(TooltipContent, { children: "Manage keys" })] }), _jsx(PopoverContent, { align: align, side: side, sideOffset: 6, className: "w-80 p-3", onClick: (event) => event.stopPropagation(), children: open ? _jsx(AppKeysPanel, { appId: appId, appName: appName }) : null })] }));
|
|
17
19
|
}
|
|
18
|
-
function AppKeysPanel({ appId, appName }) {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
export function AppKeysPanel({ appId, appName, }) {
|
|
21
|
+
const secretsQuery = useActionQuery("list-vault-secret-options", {});
|
|
22
|
+
const grantsQuery = useActionQuery("list-vault-grants", { appId });
|
|
23
|
+
const accessQuery = useActionQuery("get-vault-access-settings", {});
|
|
24
|
+
const { data: secrets = [], isLoading: secretsLoading } = secretsQuery;
|
|
25
|
+
const { data: grants = [], isLoading: grantsLoading, refetch: refetchGrants, } = grantsQuery;
|
|
26
|
+
const { data: accessSettings, isLoading: accessLoading } = accessQuery;
|
|
22
27
|
const accessMode = accessSettings?.mode === "manual" ? "manual" : "all-apps";
|
|
23
28
|
const grantBySecretId = useMemo(() => {
|
|
24
29
|
const map = new Map();
|
|
@@ -59,6 +64,7 @@ function AppKeysPanel({ appId, appName }) {
|
|
|
59
64
|
onError: (err) => toast.error(`Sync failed: ${String(err)}`),
|
|
60
65
|
});
|
|
61
66
|
const isLoading = secretsLoading || grantsLoading || accessLoading;
|
|
67
|
+
const error = secretsQuery.error ?? grantsQuery.error ?? accessQuery.error;
|
|
62
68
|
const grantedCount = grantBySecretId.size;
|
|
63
69
|
const typedSecrets = secrets;
|
|
64
70
|
const allApps = accessMode !== "manual";
|
|
@@ -77,11 +83,17 @@ function AppKeysPanel({ appId, appName }) {
|
|
|
77
83
|
grantMutation.mutate({ secretId: secret.id, appId }, { onSettled });
|
|
78
84
|
}
|
|
79
85
|
};
|
|
80
|
-
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-1", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm font-semibold text-foreground", children: ["Keys for ", appName] }), _jsx("p", { className: "text-[11px] text-muted-foreground", children:
|
|
81
|
-
?
|
|
82
|
-
:
|
|
86
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 px-1", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "truncate text-sm font-semibold text-foreground", children: ["Keys for ", appName] }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: error
|
|
87
|
+
? null
|
|
88
|
+
: allApps
|
|
89
|
+
? `${typedSecrets.length} available`
|
|
90
|
+
: `${grantedCount} of ${typedSecrets.length} granted` })] }), !error ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: syncMutation.isPending ||
|
|
83
91
|
typedSecrets.length === 0 ||
|
|
84
|
-
(!allApps && grantedCount === 0), onClick: () => syncMutation.mutate({ appId }), className: "h-7 px-2", children: [syncMutation.isPending ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : (_jsx(IconRefresh, { className: "h-3 w-3" })), _jsx("span", { className: "ml-1 text-xs", children: "Sync" })] })] }), _jsx("div", { className: "max-h-[320px] space-y-1.5 overflow-y-auto rounded-md border border-border bg-card p-1.5", children:
|
|
92
|
+
(!allApps && grantedCount === 0), onClick: () => syncMutation.mutate({ appId }), className: "h-7 px-2", children: [syncMutation.isPending ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : (_jsx(IconRefresh, { className: "h-3 w-3" })), _jsx("span", { className: "ml-1 text-xs", children: "Sync" })] })) : null] }), _jsx("div", { className: "max-h-[320px] space-y-1.5 overflow-y-auto rounded-md border border-border bg-card p-1.5", children: error ? (_jsx(ActionQueryError, { error: error, onRetry: () => {
|
|
93
|
+
void secretsQuery.refetch();
|
|
94
|
+
void grantsQuery.refetch();
|
|
95
|
+
void accessQuery.refetch();
|
|
96
|
+
} })) : isLoading ? (_jsx("div", { className: "space-y-1.5 p-1.5", children: Array.from({ length: 3 }).map((_, index) => (_jsxs("div", { className: "flex items-start gap-3 rounded-md px-2.5 py-2", children: [_jsx(Skeleton, { className: "mt-0.5 h-4 w-4 shrink-0 rounded" }), _jsxs("div", { className: "flex-1 space-y-1.5", children: [_jsx(Skeleton, { className: "h-3 w-1/2" }), _jsx(Skeleton, { className: "h-3 w-3/4" })] })] }, index))) })) : typedSecrets.length === 0 ? (_jsx("p", { className: "rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground", children: "No vault keys yet. Add one from the Vault page." })) : (typedSecrets.map((secret) => {
|
|
85
97
|
const granted = allApps || grantBySecretId.has(secret.id);
|
|
86
98
|
const pending = pendingSecretIds.has(secret.id);
|
|
87
99
|
return (_jsxs("button", { type: "button", "aria-pressed": granted, disabled: pending || allApps, onClick: () => toggleSecret(secret), className: `flex w-full items-start gap-3 rounded-md px-2.5 py-2 text-left text-sm disabled:cursor-not-allowed disabled:opacity-60 ${pending || allApps ? "" : "cursor-pointer"} ${granted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-keys-popover.js","sourceRoot":"","sources":["../../src/components/app-keys-popover.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useActionMutation, useActionQuery } from \"@agent-native/core/client\";\nimport {\n IconCheck,\n IconLoader2,\n IconRefresh,\n IconSettings,\n} from \"@tabler/icons-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { Button } from \"./ui/button\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./ui/popover\";\nimport { Skeleton } from \"./ui/skeleton\";\n\ninterface VaultSecret {\n id: string;\n name?: string | null;\n credentialKey: string;\n provider?: string | null;\n description?: string | null;\n}\n\ninterface VaultGrant {\n id: string;\n secretId: string;\n appId: string;\n status?: string | null;\n}\n\ninterface AppKeysPopoverProps {\n appId: string;\n appName: string;\n trigger?: ReactNode;\n align?: \"start\" | \"center\" | \"end\";\n side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n}\n\nexport function AppKeysPopover({\n appId,\n appName,\n trigger,\n align = \"end\",\n side = \"bottom\",\n}: AppKeysPopoverProps) {\n const [open, setOpen] = useState(false);\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n {trigger ?? (\n <button\n type=\"button\"\n aria-label={`Manage keys for ${appName}`}\n onClick={(event) => {\n // Keep parent card click handlers from also firing. Do not\n // preventDefault here: Radix uses the same click to open the\n // popover trigger.\n event.stopPropagation();\n }}\n className=\"flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-transparent text-muted-foreground/70 hover:border-border hover:bg-accent/40 hover:text-foreground\"\n >\n <IconSettings size={14} />\n </button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align={align}\n side={side}\n sideOffset={6}\n className=\"w-80 p-3\"\n onClick={(event) => event.stopPropagation()}\n >\n {open ? <AppKeysPanel appId={appId} appName={appName} /> : null}\n </PopoverContent>\n </Popover>\n );\n}\n\nfunction AppKeysPanel({ appId, appName }: { appId: string; appName: string }) {\n const { data: secrets = [], isLoading: secretsLoading } = useActionQuery(\n \"list-vault-secret-options\",\n {},\n );\n const {\n data: grants = [],\n isLoading: grantsLoading,\n refetch: refetchGrants,\n } = useActionQuery(\"list-vault-grants\", { appId });\n const { data: accessSettings, isLoading: accessLoading } = useActionQuery(\n \"get-vault-access-settings\",\n {},\n );\n const accessMode =\n (accessSettings as any)?.mode === \"manual\" ? \"manual\" : \"all-apps\";\n\n const grantBySecretId = useMemo(() => {\n const map = new Map<string, VaultGrant>();\n for (const grant of grants as VaultGrant[]) {\n if (grant.status && grant.status !== \"active\") continue;\n map.set(grant.secretId, grant);\n }\n return map;\n }, [grants]);\n\n // Track per-secret pending state so a fast double-click on the same row\n // can't queue two `create-vault-grant` requests (which would silently\n // create duplicate active grants — a later revoke only clears one).\n const [pendingSecretIds, setPendingSecretIds] = useState<Set<string>>(\n () => new Set(),\n );\n const markPending = (secretId: string, pending: boolean) =>\n setPendingSecretIds((prev) => {\n const next = new Set(prev);\n if (pending) next.add(secretId);\n else next.delete(secretId);\n return next;\n });\n\n const grantMutation = useActionMutation(\"create-vault-grant\", {\n onSuccess: () => refetchGrants(),\n onError: (err) => toast.error(`Could not grant: ${String(err)}`),\n });\n\n const revokeMutation = useActionMutation(\"revoke-vault-grant\", {\n onSuccess: () => refetchGrants(),\n onError: (err) => toast.error(`Could not revoke: ${String(err)}`),\n });\n\n const syncMutation = useActionMutation(\"sync-vault-to-app\", {\n onSuccess: (result: any) => {\n const synced = result?.synced ?? 0;\n toast.success(\n synced > 0\n ? `Synced ${synced} key${synced === 1 ? \"\" : \"s\"} to ${appName}`\n : `${appName} is up to date`,\n );\n },\n onError: (err) => toast.error(`Sync failed: ${String(err)}`),\n });\n\n const isLoading = secretsLoading || grantsLoading || accessLoading;\n const grantedCount = grantBySecretId.size;\n const typedSecrets = secrets as VaultSecret[];\n const allApps = accessMode !== \"manual\";\n\n const toggleSecret = (secret: VaultSecret) => {\n if (allApps) return;\n if (pendingSecretIds.has(secret.id)) return;\n const existing = grantBySecretId.get(secret.id);\n markPending(secret.id, true);\n const onSettled = () => markPending(secret.id, false);\n if (existing) {\n revokeMutation.mutate({ grantId: existing.id }, { onSettled });\n } else {\n grantMutation.mutate({ secretId: secret.id, appId }, { onSettled });\n }\n };\n\n return (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-center justify-between gap-2 px-1\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-foreground\">\n Keys for {appName}\n </p>\n <p className=\"text-[11px] text-muted-foreground\">\n {allApps\n ? `${typedSecrets.length} available`\n : `${grantedCount} of ${typedSecrets.length} granted`}\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={\n syncMutation.isPending ||\n typedSecrets.length === 0 ||\n (!allApps && grantedCount === 0)\n }\n onClick={() => syncMutation.mutate({ appId })}\n className=\"h-7 px-2\"\n >\n {syncMutation.isPending ? (\n <IconLoader2 className=\"h-3 w-3 animate-spin\" />\n ) : (\n <IconRefresh className=\"h-3 w-3\" />\n )}\n <span className=\"ml-1 text-xs\">Sync</span>\n </Button>\n </div>\n\n <div className=\"max-h-[320px] space-y-1.5 overflow-y-auto rounded-md border border-border bg-card p-1.5\">\n {isLoading ? (\n <div className=\"space-y-1.5 p-1.5\">\n {Array.from({ length: 3 }).map((_, index) => (\n <div\n key={index}\n className=\"flex items-start gap-3 rounded-md px-2.5 py-2\"\n >\n <Skeleton className=\"mt-0.5 h-4 w-4 shrink-0 rounded\" />\n <div className=\"flex-1 space-y-1.5\">\n <Skeleton className=\"h-3 w-1/2\" />\n <Skeleton className=\"h-3 w-3/4\" />\n </div>\n </div>\n ))}\n </div>\n ) : typedSecrets.length === 0 ? (\n <p className=\"rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground\">\n No vault keys yet. Add one from the Vault page.\n </p>\n ) : (\n typedSecrets.map((secret) => {\n const granted = allApps || grantBySecretId.has(secret.id);\n const pending = pendingSecretIds.has(secret.id);\n return (\n <button\n key={secret.id}\n type=\"button\"\n aria-pressed={granted}\n disabled={pending || allApps}\n onClick={() => toggleSecret(secret)}\n className={`flex w-full items-start gap-3 rounded-md px-2.5 py-2 text-left text-sm disabled:cursor-not-allowed disabled:opacity-60 ${\n pending || allApps ? \"\" : \"cursor-pointer\"\n } ${\n granted\n ? \"border border-primary/45 bg-primary/5\"\n : \"border border-transparent hover:border-muted-foreground/30 hover:bg-accent/35\"\n }`}\n >\n <span\n className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border ${\n granted\n ? \"border-primary/60 bg-primary/10 text-primary\"\n : \"border-muted-foreground/35 text-transparent\"\n }`}\n >\n {granted ? <IconCheck className=\"h-3 w-3\" /> : null}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate font-medium\">\n {secret.credentialKey}\n </span>\n <span className=\"block truncate text-xs text-muted-foreground/70\">\n {allApps\n ? \"Available to this app\"\n : secret.provider || secret.name || \"Vault secret\"}\n </span>\n </span>\n </button>\n );\n })\n )}\n </div>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"app-keys-popover.js","sourceRoot":"","sources":["../../src/components/app-keys-popover.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EACL,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAyBvE,MAAM,UAAU,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,OAAO,EACP,KAAK,GAAG,KAAK,EACb,IAAI,GAAG,QAAQ,GACK;IACpB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAExC,OAAO,CACL,MAAC,OAAO,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,aACxC,MAAC,OAAO,eACN,KAAC,cAAc,IAAC,OAAO,kBACrB,KAAC,cAAc,IAAC,OAAO,kBACpB,OAAO,IAAI,CACV,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,gBACG,mBAAmB,OAAO,EAAE,EACxC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oCACjB,2DAA2D;oCAC3D,6DAA6D;oCAC7D,mBAAmB;oCACnB,KAAK,CAAC,eAAe,EAAE,CAAC;gCAC1B,CAAC,EACD,SAAS,EAAC,6PAA6P,YAEvQ,KAAC,YAAY,IAAC,IAAI,EAAE,EAAE,GAAI,GACnB,CACV,GACc,GACF,EACjB,KAAC,cAAc,8BAA6B,IACpC,EACV,KAAC,cAAc,IACb,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,CAAC,EACb,SAAS,EAAC,UAAU,EACpB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,YAE1C,IAAI,CAAC,CAAC,CAAC,KAAC,YAAY,IAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CAAC,CAAC,CAAC,IAAI,GAChD,IACT,CACX,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,OAAO,GAIR;IACC,MAAM,YAAY,GAAG,cAAc,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IACrE,MAAM,WAAW,GAAG,cAAc,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,cAAc,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC;IACpE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,YAAY,CAAC;IACvE,MAAM,EACJ,IAAI,EAAE,MAAM,GAAG,EAAE,EACjB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,aAAa,GACvB,GAAG,WAAW,CAAC;IAChB,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC;IACvE,MAAM,UAAU,GACb,cAAsB,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;IAErE,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,MAAsB,EAAE,CAAC;YAC3C,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;gBAAE,SAAS;YACxD,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,wEAAwE;IACxE,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CACtD,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,CAChB,CAAC;IACF,MAAM,WAAW,GAAG,CAAC,QAAgB,EAAE,OAAgB,EAAE,EAAE,CACzD,mBAAmB,CAAC,CAAC,IAAI,EAAE,EAAE;QAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,OAAO;YAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;;YAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEL,MAAM,aAAa,GAAG,iBAAiB,CAAC,oBAAoB,EAAE;QAC5D,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE;QAChC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,oBAAoB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;KACjE,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,iBAAiB,CAAC,oBAAoB,EAAE;QAC7D,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE;QAChC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,qBAAqB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;KAClE,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,iBAAiB,CAAC,mBAAmB,EAAE;QAC1D,SAAS,EAAE,CAAC,MAAW,EAAE,EAAE;YACzB,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC;YACnC,KAAK,CAAC,OAAO,CACX,MAAM,GAAG,CAAC;gBACR,CAAC,CAAC,UAAU,MAAM,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,OAAO,EAAE;gBAChE,CAAC,CAAC,GAAG,OAAO,gBAAgB,CAC/B,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;KAC7D,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,cAAc,IAAI,aAAa,IAAI,aAAa,CAAC;IACnE,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC;IAC3E,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC;IAC1C,MAAM,YAAY,GAAG,OAAwB,CAAC;IAC9C,MAAM,OAAO,GAAG,UAAU,KAAK,QAAQ,CAAC;IAExC,MAAM,YAAY,GAAG,CAAC,MAAmB,EAAE,EAAE;QAC3C,IAAI,OAAO;YAAE,OAAO;QACpB,IAAI,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAAE,OAAO;QAC5C,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChD,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC7B,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACtD,IAAI,QAAQ,EAAE,CAAC;YACb,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QACjE,CAAC;aAAM,CAAC;YACN,aAAa,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aAClC,eAAK,SAAS,EAAC,8CAA8C,aAC3D,eAAK,SAAS,EAAC,SAAS,aACtB,aAAG,SAAS,EAAC,gDAAgD,0BACjD,OAAO,IACf,EACJ,YAAG,SAAS,EAAC,mCAAmC,YAC7C,KAAK;oCACJ,CAAC,CAAC,IAAI;oCACN,CAAC,CAAC,OAAO;wCACP,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,YAAY;wCACpC,CAAC,CAAC,GAAG,YAAY,OAAO,YAAY,CAAC,MAAM,UAAU,GACvD,IACA,EACL,CAAC,KAAK,CAAC,CAAC,CAAC,CACR,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,IAAI,EAAC,IAAI,EACT,QAAQ,EACN,YAAY,CAAC,SAAS;4BACtB,YAAY,CAAC,MAAM,KAAK,CAAC;4BACzB,CAAC,CAAC,OAAO,IAAI,YAAY,KAAK,CAAC,CAAC,EAElC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAC7C,SAAS,EAAC,UAAU,aAEnB,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CACxB,KAAC,WAAW,IAAC,SAAS,EAAC,sBAAsB,GAAG,CACjD,CAAC,CAAC,CAAC,CACF,KAAC,WAAW,IAAC,SAAS,EAAC,SAAS,GAAG,CACpC,EACD,eAAM,SAAS,EAAC,cAAc,qBAAY,IACnC,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,EAEN,cAAK,SAAS,EAAC,yFAAyF,YACrG,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,gBAAgB,IACf,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,GAAG,EAAE;wBACZ,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC;wBAC5B,KAAK,WAAW,CAAC,OAAO,EAAE,CAAC;wBAC3B,KAAK,WAAW,CAAC,OAAO,EAAE,CAAC;oBAC7B,CAAC,GACD,CACH,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CACd,cAAK,SAAS,EAAC,mBAAmB,YAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAC3C,eAEE,SAAS,EAAC,+CAA+C,aAEzD,KAAC,QAAQ,IAAC,SAAS,EAAC,iCAAiC,GAAG,EACxD,eAAK,SAAS,EAAC,oBAAoB,aACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,EAClC,KAAC,QAAQ,IAAC,SAAS,EAAC,WAAW,GAAG,IAC9B,KAPD,KAAK,CAQN,CACP,CAAC,GACE,CACP,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAC9B,YAAG,SAAS,EAAC,uFAAuF,gEAEhG,CACL,CAAC,CAAC,CAAC,CACF,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;oBAC1B,MAAM,OAAO,GAAG,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBAC1D,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;oBAChD,OAAO,CACL,kBAEE,IAAI,EAAC,QAAQ,kBACC,OAAO,EACrB,QAAQ,EAAE,OAAO,IAAI,OAAO,EAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,EACnC,SAAS,EAAE,0HACT,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAC5B,IACE,OAAO;4BACL,CAAC,CAAC,uCAAuC;4BACzC,CAAC,CAAC,+EACN,EAAE,aAEF,eACE,SAAS,EAAE,2EACT,OAAO;oCACL,CAAC,CAAC,8CAA8C;oCAChD,CAAC,CAAC,6CACN,EAAE,YAED,OAAO,CAAC,CAAC,CAAC,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,GAC9C,EACP,gBAAM,SAAS,EAAC,gBAAgB,aAC9B,eAAM,SAAS,EAAC,4BAA4B,YACzC,MAAM,CAAC,aAAa,GAChB,EACP,eAAM,SAAS,EAAC,iDAAiD,YAC9D,OAAO;4CACN,CAAC,CAAC,uBAAuB;4CACzB,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,IAAI,cAAc,GAC/C,IACF,KA/BF,MAAM,CAAC,EAAE,CAgCP,CACV,CAAC;gBACJ,CAAC,CAAC,CACH,GACG,IACF,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { useActionMutation, useActionQuery } from \"@agent-native/core/client\";\nimport {\n IconCheck,\n IconLoader2,\n IconRefresh,\n IconSettings,\n} from \"@tabler/icons-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { ActionQueryError } from \"./action-query-error\";\nimport { Button } from \"./ui/button\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./ui/popover\";\nimport { Skeleton } from \"./ui/skeleton\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"./ui/tooltip\";\n\ninterface VaultSecret {\n id: string;\n name?: string | null;\n credentialKey: string;\n provider?: string | null;\n description?: string | null;\n}\n\ninterface VaultGrant {\n id: string;\n secretId: string;\n appId: string;\n status?: string | null;\n}\n\ninterface AppKeysPopoverProps {\n appId: string;\n appName: string;\n trigger?: ReactNode;\n align?: \"start\" | \"center\" | \"end\";\n side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n}\n\nexport function AppKeysPopover({\n appId,\n appName,\n trigger,\n align = \"end\",\n side = \"bottom\",\n}: AppKeysPopoverProps) {\n const [open, setOpen] = useState(false);\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <Tooltip>\n <TooltipTrigger asChild>\n <PopoverTrigger asChild>\n {trigger ?? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Manage keys for ${appName}`}\n onClick={(event) => {\n // Keep parent card click handlers from also firing. Do not\n // preventDefault here: Radix uses the same click to open the\n // popover trigger.\n event.stopPropagation();\n }}\n className=\"size-7 rounded-md p-0 text-muted-foreground transition-[background-color,color] hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground\"\n >\n <IconSettings size={14} />\n </Button>\n )}\n </PopoverTrigger>\n </TooltipTrigger>\n <TooltipContent>Manage keys</TooltipContent>\n </Tooltip>\n <PopoverContent\n align={align}\n side={side}\n sideOffset={6}\n className=\"w-80 p-3\"\n onClick={(event) => event.stopPropagation()}\n >\n {open ? <AppKeysPanel appId={appId} appName={appName} /> : null}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport function AppKeysPanel({\n appId,\n appName,\n}: {\n appId: string;\n appName: string;\n}) {\n const secretsQuery = useActionQuery(\"list-vault-secret-options\", {});\n const grantsQuery = useActionQuery(\"list-vault-grants\", { appId });\n const accessQuery = useActionQuery(\"get-vault-access-settings\", {});\n const { data: secrets = [], isLoading: secretsLoading } = secretsQuery;\n const {\n data: grants = [],\n isLoading: grantsLoading,\n refetch: refetchGrants,\n } = grantsQuery;\n const { data: accessSettings, isLoading: accessLoading } = accessQuery;\n const accessMode =\n (accessSettings as any)?.mode === \"manual\" ? \"manual\" : \"all-apps\";\n\n const grantBySecretId = useMemo(() => {\n const map = new Map<string, VaultGrant>();\n for (const grant of grants as VaultGrant[]) {\n if (grant.status && grant.status !== \"active\") continue;\n map.set(grant.secretId, grant);\n }\n return map;\n }, [grants]);\n\n // Track per-secret pending state so a fast double-click on the same row\n // can't queue two `create-vault-grant` requests (which would silently\n // create duplicate active grants — a later revoke only clears one).\n const [pendingSecretIds, setPendingSecretIds] = useState<Set<string>>(\n () => new Set(),\n );\n const markPending = (secretId: string, pending: boolean) =>\n setPendingSecretIds((prev) => {\n const next = new Set(prev);\n if (pending) next.add(secretId);\n else next.delete(secretId);\n return next;\n });\n\n const grantMutation = useActionMutation(\"create-vault-grant\", {\n onSuccess: () => refetchGrants(),\n onError: (err) => toast.error(`Could not grant: ${String(err)}`),\n });\n\n const revokeMutation = useActionMutation(\"revoke-vault-grant\", {\n onSuccess: () => refetchGrants(),\n onError: (err) => toast.error(`Could not revoke: ${String(err)}`),\n });\n\n const syncMutation = useActionMutation(\"sync-vault-to-app\", {\n onSuccess: (result: any) => {\n const synced = result?.synced ?? 0;\n toast.success(\n synced > 0\n ? `Synced ${synced} key${synced === 1 ? \"\" : \"s\"} to ${appName}`\n : `${appName} is up to date`,\n );\n },\n onError: (err) => toast.error(`Sync failed: ${String(err)}`),\n });\n\n const isLoading = secretsLoading || grantsLoading || accessLoading;\n const error = secretsQuery.error ?? grantsQuery.error ?? accessQuery.error;\n const grantedCount = grantBySecretId.size;\n const typedSecrets = secrets as VaultSecret[];\n const allApps = accessMode !== \"manual\";\n\n const toggleSecret = (secret: VaultSecret) => {\n if (allApps) return;\n if (pendingSecretIds.has(secret.id)) return;\n const existing = grantBySecretId.get(secret.id);\n markPending(secret.id, true);\n const onSettled = () => markPending(secret.id, false);\n if (existing) {\n revokeMutation.mutate({ grantId: existing.id }, { onSettled });\n } else {\n grantMutation.mutate({ secretId: secret.id, appId }, { onSettled });\n }\n };\n\n return (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-center justify-between gap-2 px-1\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-foreground\">\n Keys for {appName}\n </p>\n <p className=\"text-[11px] text-muted-foreground\">\n {error\n ? null\n : allApps\n ? `${typedSecrets.length} available`\n : `${grantedCount} of ${typedSecrets.length} granted`}\n </p>\n </div>\n {!error ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={\n syncMutation.isPending ||\n typedSecrets.length === 0 ||\n (!allApps && grantedCount === 0)\n }\n onClick={() => syncMutation.mutate({ appId })}\n className=\"h-7 px-2\"\n >\n {syncMutation.isPending ? (\n <IconLoader2 className=\"h-3 w-3 animate-spin\" />\n ) : (\n <IconRefresh className=\"h-3 w-3\" />\n )}\n <span className=\"ml-1 text-xs\">Sync</span>\n </Button>\n ) : null}\n </div>\n\n <div className=\"max-h-[320px] space-y-1.5 overflow-y-auto rounded-md border border-border bg-card p-1.5\">\n {error ? (\n <ActionQueryError\n error={error}\n onRetry={() => {\n void secretsQuery.refetch();\n void grantsQuery.refetch();\n void accessQuery.refetch();\n }}\n />\n ) : isLoading ? (\n <div className=\"space-y-1.5 p-1.5\">\n {Array.from({ length: 3 }).map((_, index) => (\n <div\n key={index}\n className=\"flex items-start gap-3 rounded-md px-2.5 py-2\"\n >\n <Skeleton className=\"mt-0.5 h-4 w-4 shrink-0 rounded\" />\n <div className=\"flex-1 space-y-1.5\">\n <Skeleton className=\"h-3 w-1/2\" />\n <Skeleton className=\"h-3 w-3/4\" />\n </div>\n </div>\n ))}\n </div>\n ) : typedSecrets.length === 0 ? (\n <p className=\"rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground\">\n No vault keys yet. Add one from the Vault page.\n </p>\n ) : (\n typedSecrets.map((secret) => {\n const granted = allApps || grantBySecretId.has(secret.id);\n const pending = pendingSecretIds.has(secret.id);\n return (\n <button\n key={secret.id}\n type=\"button\"\n aria-pressed={granted}\n disabled={pending || allApps}\n onClick={() => toggleSecret(secret)}\n className={`flex w-full items-start gap-3 rounded-md px-2.5 py-2 text-left text-sm disabled:cursor-not-allowed disabled:opacity-60 ${\n pending || allApps ? \"\" : \"cursor-pointer\"\n } ${\n granted\n ? \"border border-primary/45 bg-primary/5\"\n : \"border border-transparent hover:border-muted-foreground/30 hover:bg-accent/35\"\n }`}\n >\n <span\n className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border ${\n granted\n ? \"border-primary/60 bg-primary/10 text-primary\"\n : \"border-muted-foreground/35 text-transparent\"\n }`}\n >\n {granted ? <IconCheck className=\"h-3 w-3\" /> : null}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate font-medium\">\n {secret.credentialKey}\n </span>\n <span className=\"block truncate text-xs text-muted-foreground/70\">\n {allApps\n ? \"Available to this app\"\n : secret.provider || secret.name || \"Vault secret\"}\n </span>\n </span>\n </button>\n );\n })\n )}\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-control-plane.d.ts","sourceRoot":"","sources":["../../src/components/dispatch-control-plane.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatch-control-plane.d.ts","sourceRoot":"","sources":["../../src/components/dispatch-control-plane.tsx"],"names":[],"mappings":"AAkKA,wBAAgB,oBAAoB,oDAqBnC"}
|