@hanphone/dsh-a2a 0.1.0 → 0.3.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 +133 -87
- package/README.zh.md +64 -87
- package/cordis.patch.yml +4 -7
- package/lib/client.js +228 -36
- package/lib/index.js +1271 -625
- package/lib/types/api.d.ts +43 -13
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +60 -20
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts +50 -14
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +59 -46
- package/lib/types/commands.js.map +1 -1
- package/lib/types/events.d.ts +32 -9
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -32
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +178 -213
- package/lib/types/index.js.map +1 -1
- package/lib/types/outbound/calls.d.ts.map +1 -1
- package/lib/types/outbound/calls.js +8 -6
- package/lib/types/outbound/calls.js.map +1 -1
- package/lib/types/protocol.d.ts +174 -109
- package/lib/types/protocol.d.ts.map +1 -1
- package/lib/types/protocol.js +58 -34
- package/lib/types/protocol.js.map +1 -1
- package/lib/types/server/a2a-server.d.ts +7 -1
- package/lib/types/server/a2a-server.d.ts.map +1 -1
- package/lib/types/server/a2a-server.js +11 -3
- package/lib/types/server/a2a-server.js.map +1 -1
- package/lib/types/server/card.d.ts +6 -30
- package/lib/types/server/card.d.ts.map +1 -1
- package/lib/types/server/card.js +13 -49
- package/lib/types/server/card.js.map +1 -1
- package/lib/types/server/exec/agent-runtime.d.ts +3 -0
- package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
- package/lib/types/server/exec/agent-runtime.js +2 -1
- package/lib/types/server/exec/agent-runtime.js.map +1 -1
- package/lib/types/server/identity.d.ts +38 -0
- package/lib/types/server/identity.d.ts.map +1 -0
- package/lib/types/server/identity.js +89 -0
- package/lib/types/server/identity.js.map +1 -0
- package/lib/types/server/inbound-registry.d.ts +60 -0
- package/lib/types/server/inbound-registry.d.ts.map +1 -0
- package/lib/types/server/inbound-registry.js +86 -0
- package/lib/types/server/inbound-registry.js.map +1 -0
- package/lib/types/server/routes.d.ts +5 -1
- package/lib/types/server/routes.d.ts.map +1 -1
- package/lib/types/server/routes.js +9 -5
- package/lib/types/server/routes.js.map +1 -1
- package/lib/types/server/store.d.ts +37 -1
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js +14 -0
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +131 -0
- package/lib/types/servers/inbound-manager.d.ts.map +1 -0
- package/lib/types/servers/inbound-manager.js +312 -0
- package/lib/types/servers/inbound-manager.js.map +1 -0
- package/lib/types/servers/outbound-manager.d.ts +129 -0
- package/lib/types/servers/outbound-manager.d.ts.map +1 -0
- package/lib/types/servers/outbound-manager.js +238 -0
- package/lib/types/servers/outbound-manager.js.map +1 -0
- package/lib/types/service.d.ts +108 -23
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +39 -18
- package/lib/types/service.js.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +70 -31
- package/src/client/index.ts +339 -81
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +204 -249
- package/src/outbound/calls.ts +8 -6
- package/src/protocol.ts +204 -95
- package/src/server/a2a-server.ts +16 -4
- package/src/server/card.ts +13 -62
- package/src/server/exec/agent-runtime.ts +5 -2
- package/src/server/identity.ts +109 -0
- package/src/server/inbound-registry.ts +129 -0
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +50 -4
- package/src/servers/inbound-manager.ts +385 -0
- package/src/servers/outbound-manager.ts +289 -0
- package/src/service.ts +136 -28
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -1,36 +1,65 @@
|
|
|
1
1
|
# dsh-a2a
|
|
2
2
|
|
|
3
|
-
Agent2Agent (A2A) Protocol v1.0 dual-end plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
3
|
+
Agent2Agent (A2A) Protocol v1.0.1 dual-end plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) — **English** · [中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@hanphone/dsh-a2a` is an independent, open-source A2A plugin that turns a
|
|
6
|
+
DeepSeek Harness profile into a multi-faced agent-to-agent citizen: it can
|
|
7
|
+
simultaneously serve **multiple inbound A2A servers**, each bound to its own
|
|
8
|
+
agent preset with its own endpoint, AgentCard, declared skills and auth, and
|
|
9
|
+
connect to **multiple outbound A2A servers**, each with its own preset, whose
|
|
10
|
+
remote skills appear as model tools. Every server instance is created,
|
|
11
|
+
started, stopped, edited and removed entirely from the GUI — no config-file
|
|
12
|
+
editing.
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **Inbound server** — AgentCard derived from the live tool registry, JSON-RPC + SSE, durable task store, pluggable session/subagent executors, and a policy gate (`a2a/inbound-task`) with audit.
|
|
10
|
-
- **Outbound client** — a persisted multi-agent AgentCard registry, remote skills mapped to model tools (`a2a__<name>__<skill>`), sync calls with per-agent timeout.
|
|
11
|
-
- **GUI dashboard** — an **A2A 连接** settings page in the Harness Web UI: toggle the inbound server, manage outbound agents, view and cancel tasks — no config files required.
|
|
12
|
-
|
|
13
|
-
Design decisions are recorded in [docs/architecture.md](docs/architecture.md). Current scope is P0 of that document.
|
|
14
|
+
Architecture and design decisions: [docs/architecture.md](docs/architecture.md).
|
|
14
15
|
|
|
15
16
|
## Features
|
|
16
17
|
|
|
17
|
-
- **A2A v1.0 protocol surface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- **
|
|
18
|
+
- **A2A v1.0.1 protocol surface, aligned with the official spec** —
|
|
19
|
+
`SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`,
|
|
20
|
+
`GetExtendedAgentCard`, `SubscribeToTask` over JSON-RPC; SSE streaming with
|
|
21
|
+
catch-up frames; official `TASK_STATE_*` / `ROLE_*` enums and AgentCard
|
|
22
|
+
structure (including `supportedInterfaces`, `capabilities`, full error-code
|
|
23
|
+
table).
|
|
24
|
+
- **Multiple inbound servers** — one per persona. Each instance gets its own
|
|
25
|
+
endpoint (`/a2a/<id>`), AgentCard route, authentication env, and skill
|
|
26
|
+
declarations.
|
|
27
|
+
- **Per-instance agent preset** — every inbound server binds an agent preset
|
|
28
|
+
(e.g. `ptc`, `standard`, `minimal`, …); inbound tasks execute in sessions
|
|
29
|
+
composed from that preset through the standard `agentPresets` resolve+mount
|
|
30
|
+
path.
|
|
31
|
+
- **Creator-declared skill declarations** — the AgentCard's skills are the
|
|
32
|
+
text the creator entered (id/name/description per skill); when left empty at
|
|
33
|
+
creation, the default is the bound preset's display name (built-in `chat`
|
|
34
|
+
otherwise). The v0.2 tool white-list derivation is removed.
|
|
35
|
+
- **Multiple outbound servers** — each connection has its own remote URL,
|
|
36
|
+
auth env, timeout and optional preset; enabled instances map remote skills
|
|
37
|
+
to `a2a__<name>__<skill>` model tools.
|
|
38
|
+
- **Durable task store** — tasks live in the `a2a` storage domain (JSON
|
|
39
|
+
backend by default, SQLite per deployment choice); server-generated ids
|
|
40
|
+
survive restarts, and each task records the inbound server it arrived
|
|
41
|
+
through.
|
|
42
|
+
- **Executors** — `session` (one DSH session per `contextId`) and `subagent`
|
|
43
|
+
(delegates to `ctx.subagents`, streams tool-call artifacts back).
|
|
44
|
+
- **Governed inbound** — every inbound task passes through the
|
|
45
|
+
`a2a/inbound-task` waterfall so policy plugins can veto or audit.
|
|
46
|
+
- **Inbound connection monitoring** — the dashboard shows which remote peers
|
|
47
|
+
are talking to each instance and can close a peer.
|
|
48
|
+
- **Auth by environment variable** — each instance's bearer token is
|
|
49
|
+
referenced by env-var name (`authTokenEnv`), never stored as plaintext.
|
|
50
|
+
- **Minimal plugin config** — instances are created through the GUI and live
|
|
51
|
+
in the domain; the plugin `Config` only carries host-level defaults
|
|
52
|
+
(`baseUrl`, `subagentProvider`, `defaultTimeoutMs`).
|
|
24
53
|
|
|
25
54
|
## Installation
|
|
26
55
|
|
|
27
|
-
### From npm
|
|
56
|
+
### From npm
|
|
28
57
|
|
|
29
58
|
```sh
|
|
30
59
|
dsh plugin --profile web add @hanphone/dsh-a2a
|
|
31
60
|
```
|
|
32
61
|
|
|
33
|
-
|
|
62
|
+
Any profile name works (`web`, custom profiles, headless etc.):
|
|
34
63
|
|
|
35
64
|
```sh
|
|
36
65
|
dsh plugin --profile <name> add @hanphone/dsh-a2a
|
|
@@ -42,72 +71,85 @@ dsh plugin --profile <name> add @hanphone/dsh-a2a
|
|
|
42
71
|
cd dsh-a2a
|
|
43
72
|
pnpm build
|
|
44
73
|
npm pack
|
|
45
|
-
dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a
|
|
74
|
+
dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a-<version>.tgz
|
|
46
75
|
```
|
|
47
76
|
|
|
48
77
|
## Quick start
|
|
49
78
|
|
|
50
79
|
1. **Install** — `dsh plugin --profile web add @hanphone/dsh-a2a`.
|
|
51
|
-
2. **Restart the GUI** — the browser half is scanned at host startup, so
|
|
52
|
-
|
|
80
|
+
2. **Restart the GUI** — the browser half is scanned at host startup, so
|
|
81
|
+
restart once after installing (`pnpm dsh web` or your profile launcher).
|
|
82
|
+
3. **Open Settings → A2A 连接** — create your first inbound server (pick a
|
|
83
|
+
preset, declare skills, optionally set an auth env). It is enabled
|
|
84
|
+
immediately and publishes its own endpoint and AgentCard.
|
|
53
85
|
|
|
54
|
-
|
|
86
|
+
Each inbound server listens on the profile's webServer:
|
|
55
87
|
|
|
56
88
|
```sh
|
|
57
|
-
|
|
89
|
+
# the created instance's AgentCard (see the GUI for the exact id)
|
|
90
|
+
curl http://127.0.0.1:3080/a2a/<id>/agent-card.json
|
|
58
91
|
```
|
|
59
92
|
|
|
60
|
-
Send a task (
|
|
93
|
+
Send a task to an instance (its declared `chat` skill):
|
|
61
94
|
|
|
62
95
|
```sh
|
|
63
|
-
curl -X POST http://127.0.0.1:3080/a2a \
|
|
96
|
+
curl -X POST http://127.0.0.1:3080/a2a/<id> \
|
|
64
97
|
-H "content-type: application/json" \
|
|
65
98
|
-d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{"role":"user","parts":[{"text":"hello"}],"metadata":{"skill":"chat"}}}}'
|
|
66
99
|
```
|
|
67
100
|
|
|
68
101
|
## GUI dashboard
|
|
69
102
|
|
|
70
|
-
The browser half registers an **A2A 连接** page under Settings. From it you
|
|
103
|
+
The browser half registers an **A2A 连接** page under Settings. From it you
|
|
104
|
+
can, without touching any file:
|
|
71
105
|
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
106
|
+
- **入站 Servers** — create inbound servers (name/description/version, agent
|
|
107
|
+
preset picker, auth env, skill-declaration textarea), start/stop, edit, and
|
|
108
|
+
remove them; each row shows its endpoint, preset, declared skills and live
|
|
109
|
+
AgentCard URL.
|
|
110
|
+
- **出站 Servers** — add outbound connections (name, remote AgentCard URL,
|
|
111
|
+
preset picker, bearer env, timeout), start/stop, refresh, and remove them;
|
|
112
|
+
each row shows connection state and tool registration counts.
|
|
113
|
+
- **任务** — view and cancel inbound tasks (each task carries its source
|
|
114
|
+
server).
|
|
115
|
+
- **入站连接** — see which remote peers are talking to the instances and
|
|
116
|
+
close one.
|
|
75
117
|
|
|
76
|
-
All dashboard traffic goes through the **loopback-only** `/a2a/api` route
|
|
118
|
+
All dashboard traffic goes through the **loopback-only** `/a2a/api` route —
|
|
119
|
+
remote peers can never drive it.
|
|
77
120
|
|
|
78
121
|
## Configuration
|
|
79
122
|
|
|
80
|
-
The
|
|
123
|
+
The GUI covers instance management day-to-day. The plugin `Config` only has
|
|
124
|
+
host-level defaults, set through the profile's user patch layer
|
|
125
|
+
(`$DSH_HOME/profiles/<name>/cordis.patch.yml`) if you want to override them:
|
|
81
126
|
|
|
82
127
|
```yaml
|
|
83
128
|
- id: a2a
|
|
84
129
|
config:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
description: A DeepSeek Harness agent exposed over A2A v1.0
|
|
89
|
-
version: 0.1.0
|
|
90
|
-
baseUrl: http://127.0.0.1:<port> # omit to derive from the webServer address
|
|
91
|
-
endpointPath: /a2a
|
|
92
|
-
authTokenEnv: A2A_INBOUND_TOKEN # optional; an env var NAME, never the token
|
|
93
|
-
skills:
|
|
94
|
-
ids: [] # explicit tool ids to expose; chat is built-in
|
|
95
|
-
exclude: []
|
|
96
|
-
executors:
|
|
97
|
-
chat: session # or subagent (needs the subagent seam)
|
|
98
|
-
subagentProvider: in-process
|
|
99
|
-
client:
|
|
100
|
-
toolPrefix: a2a
|
|
101
|
-
agents: [] # or declare agents declaratively
|
|
130
|
+
baseUrl: http://127.0.0.1:<port> # omit to derive from the webServer address
|
|
131
|
+
subagentProvider: in-process
|
|
132
|
+
defaultTimeoutMs: 60000 # default outbound connection timeout
|
|
102
133
|
```
|
|
103
134
|
|
|
135
|
+
Instances are **not** configured via patches — they are created in the GUI and
|
|
136
|
+
persisted in the `a2a` domain (`inbound_servers` / `outbound_servers` tables).
|
|
137
|
+
|
|
104
138
|
### Required host services
|
|
105
139
|
|
|
106
|
-
Base-backed profiles mount them all: `webServer` (`@deepseek-ai/dsh-host-webserver`),
|
|
140
|
+
Base-backed profiles mount them all: `webServer` (`@deepseek-ai/dsh-host-webserver`),
|
|
141
|
+
the storage stack (`@deepseek-ai/dsh-storage` + `@deepseek-ai/dsh-storage-domain`),
|
|
142
|
+
the tools registry (`@deepseek-ai/dsh-tools`), an agent loop
|
|
143
|
+
(`@deepseek-ai/dsh-agent` + `@deepseek-ai/dsh-agent-loop`), and agent presets
|
|
144
|
+
(`@deepseek-ai/dsh-agent-presets`; the preset pickers and preset-bound session
|
|
145
|
+
composition need it). The subagent executor additionally needs
|
|
146
|
+
`@deepseek-ai/dsh-subagent`.
|
|
107
147
|
|
|
108
148
|
### Storage backend
|
|
109
149
|
|
|
110
|
-
The task
|
|
150
|
+
The task and instance stores live in the `a2a` storage domain. The base
|
|
151
|
+
composition routes storage through the `json` backend; to use SQLite, route
|
|
152
|
+
the domain and add the backend in the same patch layer:
|
|
111
153
|
|
|
112
154
|
```yaml
|
|
113
155
|
- id: storage-domain
|
|
@@ -122,38 +164,31 @@ The task store lives in the `a2a` storage domain. The base composition routes st
|
|
|
122
164
|
path: /absolute/path/to/a2a.sqlite
|
|
123
165
|
```
|
|
124
166
|
|
|
125
|
-
### Outbound agents (file-declared, optional — the GUI manages the same list)
|
|
126
|
-
|
|
127
|
-
```yaml
|
|
128
|
-
- id: a2a
|
|
129
|
-
config:
|
|
130
|
-
client:
|
|
131
|
-
toolPrefix: a2a
|
|
132
|
-
agents:
|
|
133
|
-
- name: my-remote-agent
|
|
134
|
-
agentCardUrl: https://remote.example/.well-known/agent-card.json
|
|
135
|
-
bearerTokenEnv: A2A_REMOTE_TOKEN # optional; env var NAME
|
|
136
|
-
enabled: true
|
|
137
|
-
timeoutMs: 60000
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Each enabled remote agent's skills become model tools named `a2a__<name>__<skill>` (normalized, collision-hashed). The registry persists across restarts.
|
|
141
|
-
|
|
142
167
|
## CLI
|
|
143
168
|
|
|
144
|
-
A `/a2a` chat command mirrors the dashboard:
|
|
169
|
+
A `/a2a` chat command mirrors the dashboard (a text backup to the GUI):
|
|
145
170
|
|
|
146
171
|
```
|
|
147
|
-
a2a status |
|
|
148
|
-
|
|
172
|
+
a2a status | presets | peers |
|
|
173
|
+
inbound list|create|remove|enable|disable |
|
|
174
|
+
outbound list|create|remove|enable|disable|refresh |
|
|
149
175
|
tasks | task get|cancel <id> | help
|
|
150
176
|
```
|
|
151
177
|
|
|
152
178
|
## How it works
|
|
153
179
|
|
|
154
|
-
- **Inbound** —
|
|
155
|
-
|
|
156
|
-
|
|
180
|
+
- **Inbound** — an `InboundServerManager` owns every instance: one preset-bound
|
|
181
|
+
session pool + `A2AServer` + routes per instance. Each instance persists in
|
|
182
|
+
the `inbound_servers` table and serves its own endpoint + AgentCard. Tasks
|
|
183
|
+
flow through `a2a/inbound-task` → executor → task store, with SSE frames
|
|
184
|
+
streamed to subscribers.
|
|
185
|
+
- **Outbound** — an `OutboundServerManager` owns every connection: one
|
|
186
|
+
`OutboundAgentRegistry` with an isolated agent store per instance,
|
|
187
|
+
persisted in the `outbound_servers` table. `A2AClient` discovers an
|
|
188
|
+
AgentCard, and each skill registers as a tool.
|
|
189
|
+
- **Dashboard** — the browser half (React, `settings.section`) reads/writes
|
|
190
|
+
the loopback-only `/a2a/api` route; the host half feeds it snapshots of
|
|
191
|
+
inbound/outbound server views and the preset roster (`/a2a/api/presets`).
|
|
157
192
|
|
|
158
193
|
See [docs/architecture.md](docs/architecture.md) for the full design.
|
|
159
194
|
|
|
@@ -161,19 +196,24 @@ See [docs/architecture.md](docs/architecture.md) for the full design.
|
|
|
161
196
|
|
|
162
197
|
```
|
|
163
198
|
src/
|
|
164
|
-
api.ts
|
|
165
|
-
index.ts
|
|
166
|
-
protocol.ts
|
|
167
|
-
jsonrpc.ts
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
199
|
+
api.ts # loopback dashboard API (/a2a/api, /a2a/api/presets)
|
|
200
|
+
index.ts # Cordis plugin entry (apply)
|
|
201
|
+
protocol.ts # A2A v1.0.1 protocol constants + types
|
|
202
|
+
jsonrpc.ts # JSON-RPC framing
|
|
203
|
+
servers/ # multi-instance managers
|
|
204
|
+
inbound-manager.ts # inbound server instances (CRUD, routes, lifecycle)
|
|
205
|
+
outbound-manager.ts # outbound connection instances (CRUD, tools)
|
|
206
|
+
server/ # single-instance internals: store, card, a2a-server,
|
|
207
|
+
# routes, executors, inbound-registry
|
|
208
|
+
outbound/ # outbound internals: A2AClient, registry, tools
|
|
209
|
+
client/ # browser half: settings dashboard (React)
|
|
210
|
+
service.ts # ctx.a2a service facade
|
|
211
|
+
commands.ts # /a2a chat command
|
|
173
212
|
tests/
|
|
174
|
-
unit/
|
|
175
|
-
|
|
176
|
-
|
|
213
|
+
unit/ # protocol, framing, card, store, registry, server,
|
|
214
|
+
# client, api, inbound-registry, identity
|
|
215
|
+
composition/ # apply() on a real Cordis Context with stub host services
|
|
216
|
+
cordis.patch.yml # bundle patch (mounts the plugin; instances are GUI-managed)
|
|
177
217
|
```
|
|
178
218
|
|
|
179
219
|
## Development
|
|
@@ -187,9 +227,15 @@ pnpm test # vitest run (unit + composition suites)
|
|
|
187
227
|
pnpm build # tsc + tsdown → lib/index.js (host) + lib/client.js (browser)
|
|
188
228
|
```
|
|
189
229
|
|
|
190
|
-
##
|
|
230
|
+
## Acknowledgements
|
|
191
231
|
|
|
192
|
-
|
|
232
|
+
This plugin was inspired by and developed alongside
|
|
233
|
+
[ryubyte/dsh-a2a](https://github.com/ryubyte/dsh-a2a), an earlier A2A plugin
|
|
234
|
+
for DeepSeek Harness. That project's design — dual-end scope, AgentCard
|
|
235
|
+
advertisement, and the settings-dashboard pattern — set the direction this
|
|
236
|
+
implementation follows. Our protocol layer, task store, and executor seam are
|
|
237
|
+
independent implementations; the GUI management model owes a direct debt to
|
|
238
|
+
ryubyte's connection dashboard.
|
|
193
239
|
|
|
194
240
|
## License
|
|
195
241
|
|
package/README.zh.md
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
# dsh-a2a
|
|
2
2
|
|
|
3
|
-
Agent2Agent(A2A)v1.0 双端插件,用于 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
3
|
+
Agent2Agent(A2A)v1.0.1 双端插件,用于 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) — [English](README.md) · **中文**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@hanphone/dsh-a2a` 是一个独立开源的 A2A 插件,把 DeepSeek Harness profile 变成多面手 A2A 一等公民:能**同时对外发布多个入站 A2A server**,每个绑定一个自己的 agent preset、拥有独立端点 / AgentCard / 技能宣告 / 鉴权;也能**同时接入多个出站 A2A server**,每个独立 preset,远端技能映射为模型工具。所有 server 实例的创建、启停、编辑、删除全部在 GUI 完成——无需改任何配置文件。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **入站服务端** —— 从实时工具注册表派生的 AgentCard、JSON-RPC + SSE、持久化任务存储、可插拔的会话/子代理执行器,以及带审计的策略门禁(`a2a/inbound-task`)。
|
|
10
|
-
- **出站客户端** —— 持久化的多 Agent 注册表,远程技能映射为模型工具(`a2a__<name>__<skill>`),带每 agent 超时的同步调用。
|
|
11
|
-
- **GUI 面板** —— Harness Web UI 设置中的 **A2A 连接** 页:开关入站服务端、管理出站 agent、查看与取消任务——无需改任何配置文件。
|
|
12
|
-
|
|
13
|
-
设计决策见 [docs/architecture.md](docs/architecture.md)。当前范围为该文档中的 P0。
|
|
7
|
+
架构与设计决策:[docs/architecture.md](docs/architecture.md)。
|
|
14
8
|
|
|
15
9
|
## 功能
|
|
16
10
|
|
|
17
|
-
- **A2A v1.0
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
11
|
+
- **A2A v1.0.1 协议面,对齐官方规范** — JSON-RPC 上的 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`GetExtendedAgentCard`、`SubscribeToTask`;SSE 流式带补发帧;官方 `TASK_STATE_*` / `ROLE_*` 枚举与 AgentCard 结构(含 `supportedInterfaces`、`capabilities`、完整错误码表)。
|
|
12
|
+
- **多入站 server** — 每实例一个"人格":独立端点(`/a2a/<id>`)、AgentCard 路由、鉴权 env、技能宣告。
|
|
13
|
+
- **每实例 agent preset** — 每个入站 server 绑定一个 agent preset(如 `ptc`、`standard`、`minimal`、…);入站任务在按该 preset 组装的会话中执行(标准 `agentPresets` resolve+mount 路径)。
|
|
14
|
+
- **创建者输入技能宣告** — AgentCard 技能即创建者输入的文字(每个技能 id/name/description);创建留空时默认取所绑 preset 展示名(否则内置 `chat`)。v0.2 工具白名单派生机制已删除。
|
|
15
|
+
- **多出站 server** — 每个连接独立远端 URL、鉴权 env、超时与可选 preset;启用实例把远端技能映射为 `a2a__<name>__<skill>` 模型工具。
|
|
16
|
+
- **持久化任务存储** — 任务存于 `a2a` 存储域(默认 json 后端,可按部署切 SQLite);服务端生成 id 跨重启存活,每个任务记录来源入站实例。
|
|
17
|
+
- **执行器** — `session`(每个 `contextId` 一个 DSH 会话)与 `subagent`(委托 `ctx.subagents`,工具调用过程流式回传)。
|
|
18
|
+
- **受治理入站** — 每个入站任务经过 `a2a/inbound-task` waterfall,策略插件可否决或审计。
|
|
19
|
+
- **入站连接监控** — 面板展示每个实例的对端连接,可关闭某个对端。
|
|
20
|
+
- **环境变量鉴权** — 每个实例的 Bearer token 只以环境变量名(`authTokenEnv`)引用,不落明文。
|
|
21
|
+
- **最小插件配置** — 实例经 GUI 创建并存于域中;插件 `Config` 只承载宿主级默认值(`baseUrl`、`subagentProvider`、`defaultTimeoutMs`)。
|
|
24
22
|
|
|
25
23
|
## 安装
|
|
26
24
|
|
|
27
|
-
### 从 npm
|
|
25
|
+
### 从 npm
|
|
28
26
|
|
|
29
27
|
```sh
|
|
30
28
|
dsh plugin --profile web add @hanphone/dsh-a2a
|
|
31
29
|
```
|
|
32
30
|
|
|
33
|
-
任意 profile
|
|
31
|
+
任意 profile 名均可(`web`、自定义 profile、headless 等):
|
|
34
32
|
|
|
35
33
|
```sh
|
|
36
34
|
dsh plugin --profile <name> add @hanphone/dsh-a2a
|
|
@@ -42,25 +40,26 @@ dsh plugin --profile <name> add @hanphone/dsh-a2a
|
|
|
42
40
|
cd dsh-a2a
|
|
43
41
|
pnpm build
|
|
44
42
|
npm pack
|
|
45
|
-
dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a
|
|
43
|
+
dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a-<version>.tgz
|
|
46
44
|
```
|
|
47
45
|
|
|
48
46
|
## 快速开始
|
|
49
47
|
|
|
50
|
-
1. **安装**
|
|
51
|
-
2. **重启 GUI**
|
|
52
|
-
3. **打开 设置 → A2A 连接**
|
|
48
|
+
1. **安装** — `dsh plugin --profile web add @hanphone/dsh-a2a`。
|
|
49
|
+
2. **重启 GUI** — 浏览器端插件表在 host 启动时扫描,装完请重启一次(`pnpm dsh web` 或对应 profile 启动命令)。
|
|
50
|
+
3. **打开 设置 → A2A 连接** — 创建第一个入站 server(选 preset、填技能宣告、可选鉴权 env)。创建即时启用并发布自己的端点与 AgentCard。
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
每个入站 server 监听在 profile 的 webServer 上:
|
|
55
53
|
|
|
56
54
|
```sh
|
|
57
|
-
|
|
55
|
+
# 所建实例的 AgentCard(确切 id 见 GUI)
|
|
56
|
+
curl http://127.0.0.1:3080/a2a/<id>/agent-card.json
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
向实例发任务(其宣告的 `chat` 技能):
|
|
61
60
|
|
|
62
61
|
```sh
|
|
63
|
-
curl -X POST http://127.0.0.1:3080/a2a \
|
|
62
|
+
curl -X POST http://127.0.0.1:3080/a2a/<id> \
|
|
64
63
|
-H "content-type: application/json" \
|
|
65
64
|
-d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{"role":"user","parts":[{"text":"hello"}],"metadata":{"skill":"chat"}}}}'
|
|
66
65
|
```
|
|
@@ -69,45 +68,34 @@ curl -X POST http://127.0.0.1:3080/a2a \
|
|
|
69
68
|
|
|
70
69
|
浏览器端在设置中注册 **A2A 连接** 页。无需改文件即可:
|
|
71
70
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
71
|
+
- **入站 Servers** — 创建入站 server(名称/描述/版本、agent preset 选择器、鉴权 env、技能宣告表单),启停、编辑、删除;每行显示端点、preset、宣告技能与实时 AgentCard URL。
|
|
72
|
+
- **出站 Servers** — 添加出站连接(名称、远端 AgentCard URL、preset 选择器、Bearer env、超时),启停、刷新、删除;每行显示连接状态与工具注册数。
|
|
73
|
+
- **任务** — 查看与取消入站任务(每个任务携带来源 server)。
|
|
74
|
+
- **入站连接** — 查看哪些远程对端在调用各实例,可关闭某个对端。
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
所有面板流量走 profile webServer 上的**仅回环** `/a2a/api` 路由——远程对端永远无法驱动它。
|
|
77
77
|
|
|
78
78
|
## 配置
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
GUI 覆盖日常实例管理。插件 `Config` 只有宿主级默认值,如需覆盖可经 profile 用户 patch 层(`$DSH_HOME/profiles/<name>/cordis.patch.yml`)设置:
|
|
81
81
|
|
|
82
82
|
```yaml
|
|
83
83
|
- id: a2a
|
|
84
84
|
config:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
description: A DeepSeek Harness agent exposed over A2A v1.0
|
|
89
|
-
version: 0.1.0
|
|
90
|
-
baseUrl: http://127.0.0.1:<port> # 省略则从 webServer 地址派生
|
|
91
|
-
endpointPath: /a2a
|
|
92
|
-
authTokenEnv: A2A_INBOUND_TOKEN # 可选;环境变量名,绝不写 token 明文
|
|
93
|
-
skills:
|
|
94
|
-
ids: [] # 暴露的显式工具 id;chat 为内置
|
|
95
|
-
exclude: []
|
|
96
|
-
executors:
|
|
97
|
-
chat: session # 或 subagent(需要 subagent 接缝)
|
|
98
|
-
subagentProvider: in-process
|
|
99
|
-
client:
|
|
100
|
-
toolPrefix: a2a
|
|
101
|
-
agents: [] # 或声明式列出 agent
|
|
85
|
+
baseUrl: http://127.0.0.1:<port> # 省略则从 webServer 地址派生
|
|
86
|
+
subagentProvider: in-process
|
|
87
|
+
defaultTimeoutMs: 60000 # 出站连接默认超时
|
|
102
88
|
```
|
|
103
89
|
|
|
90
|
+
实例**不**经 patch 配置——它们在 GUI 中创建并持久化于 `a2a` 域(`inbound_servers` / `outbound_servers` 表)。
|
|
91
|
+
|
|
104
92
|
### 所需宿主服务
|
|
105
93
|
|
|
106
|
-
base 类 profile 全部挂载:`webServer`(`@deepseek-ai/dsh-host-webserver`)、存储栈(`@deepseek-ai/dsh-storage` + `@deepseek-ai/dsh-storage-domain`)、工具注册表(`@deepseek-ai/dsh-tools`)、agent 循环(`@deepseek-ai/dsh-agent` + `@deepseek-ai/dsh-agent-loop`;subagent 执行器还需要 `@deepseek-ai/dsh-subagent
|
|
94
|
+
base 类 profile 全部挂载:`webServer`(`@deepseek-ai/dsh-host-webserver`)、存储栈(`@deepseek-ai/dsh-storage` + `@deepseek-ai/dsh-storage-domain`)、工具注册表(`@deepseek-ai/dsh-tools`)、agent 循环(`@deepseek-ai/dsh-agent` + `@deepseek-ai/dsh-agent-loop`)、agent presets(`@deepseek-ai/dsh-agent-presets`;preset 选择器与 preset 化会话组装需要它)。subagent 执行器还需要 `@deepseek-ai/dsh-subagent`。
|
|
107
95
|
|
|
108
96
|
### 存储后端
|
|
109
97
|
|
|
110
|
-
|
|
98
|
+
任务与实例存储位于 `a2a` 存储域。base 组合默认走 `json` 后端;要切 SQLite,在同一 patch 层路由域并加后端:
|
|
111
99
|
|
|
112
100
|
```yaml
|
|
113
101
|
- id: storage-domain
|
|
@@ -122,38 +110,22 @@ base 类 profile 全部挂载:`webServer`(`@deepseek-ai/dsh-host-webserver`
|
|
|
122
110
|
path: /absolute/path/to/a2a.sqlite
|
|
123
111
|
```
|
|
124
112
|
|
|
125
|
-
### 出站 agent(文件声明,可选——GUI 管理同一列表)
|
|
126
|
-
|
|
127
|
-
```yaml
|
|
128
|
-
- id: a2a
|
|
129
|
-
config:
|
|
130
|
-
client:
|
|
131
|
-
toolPrefix: a2a
|
|
132
|
-
agents:
|
|
133
|
-
- name: my-remote-agent
|
|
134
|
-
agentCardUrl: https://remote.example/.well-known/agent-card.json
|
|
135
|
-
bearerTokenEnv: A2A_REMOTE_TOKEN # 可选;环境变量名
|
|
136
|
-
enabled: true
|
|
137
|
-
timeoutMs: 60000
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
每个启用远程 agent 的技能会注册为 `a2a__<name>__<skill>` 模型工具(规范化、冲突哈希)。注册表跨重启持久化。
|
|
141
|
-
|
|
142
113
|
## CLI
|
|
143
114
|
|
|
144
|
-
`/a2a`
|
|
115
|
+
`/a2a` 聊天命令与面板对应(GUI 的文字备用路线):
|
|
145
116
|
|
|
146
117
|
```
|
|
147
|
-
a2a status |
|
|
148
|
-
|
|
118
|
+
a2a status | presets | peers |
|
|
119
|
+
inbound list|create|remove|enable|disable |
|
|
120
|
+
outbound list|create|remove|enable|disable|refresh |
|
|
149
121
|
tasks | task get|cancel <id> | help
|
|
150
122
|
```
|
|
151
123
|
|
|
152
124
|
## 工作原理
|
|
153
125
|
|
|
154
|
-
- **入站**
|
|
155
|
-
- **出站**
|
|
156
|
-
- **面板**
|
|
126
|
+
- **入站** — `InboundServerManager` 拥有每个实例:每实例 = preset 化会话池 + `A2AServer` + 路由。实例持久化于 `inbound_servers` 表,各自服务独立端点与 AgentCard。任务流经 `a2a/inbound-task` → 执行器 → 任务存储,SSE 帧推送给订阅者。
|
|
127
|
+
- **出站** — `OutboundServerManager` 拥有每个连接:每实例一个带独立 agent 存储的 `OutboundAgentRegistry`,实例持久化于 `outbound_servers` 表。`A2AClient` 发现 AgentCard,每个技能注册为一个工具。
|
|
128
|
+
- **面板** — 浏览器端(React,`settings.section`)经仅回环 `/a2a/api` 路由读写;host 端喂给它入站/出站 server 视图与 preset 名单(`/a2a/api/presets`)。
|
|
157
129
|
|
|
158
130
|
完整设计见 [docs/architecture.md](docs/architecture.md)。
|
|
159
131
|
|
|
@@ -161,19 +133,24 @@ a2a status | enable | disable | card | agents |
|
|
|
161
133
|
|
|
162
134
|
```
|
|
163
135
|
src/
|
|
164
|
-
api.ts
|
|
165
|
-
index.ts
|
|
166
|
-
protocol.ts
|
|
167
|
-
jsonrpc.ts
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
136
|
+
api.ts # 回环面板 API (/a2a/api、/a2a/api/presets)
|
|
137
|
+
index.ts # Cordis 插件入口 (apply)
|
|
138
|
+
protocol.ts # A2A v1.0.1 协议常量与类型
|
|
139
|
+
jsonrpc.ts # JSON-RPC 帧
|
|
140
|
+
servers/ # 多实例管理器
|
|
141
|
+
inbound-manager.ts # 入站 server 实例(CRUD、路由、生命周期)
|
|
142
|
+
outbound-manager.ts # 出站连接实例(CRUD、工具)
|
|
143
|
+
server/ # 单实例内部件:store、card、a2a-server、
|
|
144
|
+
# routes、executors、inbound-registry
|
|
145
|
+
outbound/ # 出站内部件:A2AClient、registry、tools
|
|
146
|
+
client/ # 浏览器半区:设置面板 (React)
|
|
147
|
+
service.ts # ctx.a2a 服务 facade
|
|
148
|
+
commands.ts # /a2a 聊天命令
|
|
173
149
|
tests/
|
|
174
|
-
unit/
|
|
175
|
-
|
|
176
|
-
|
|
150
|
+
unit/ # protocol、framing、card、store、registry、server、
|
|
151
|
+
# client、api、inbound-registry、identity
|
|
152
|
+
composition/ # 在真实 Cordis Context 上以 stub 宿主服务跑 apply()
|
|
153
|
+
cordis.patch.yml # bundle patch(挂载插件;实例由 GUI 管理)
|
|
177
154
|
```
|
|
178
155
|
|
|
179
156
|
## 开发
|
|
@@ -186,9 +163,9 @@ pnpm test # vitest run(单元 + 组合套件)
|
|
|
186
163
|
pnpm build # tsc + tsdown → lib/index.js(host)+ lib/client.js(浏览器)
|
|
187
164
|
```
|
|
188
165
|
|
|
189
|
-
##
|
|
166
|
+
## 致谢
|
|
190
167
|
|
|
191
|
-
|
|
168
|
+
本项目受 [ryubyte/dsh-a2a](https://github.com/ryubyte/dsh-a2a) 启发并与其并行开发——那是 DeepSeek Harness 上更早的 A2A 插件。其设计——双端范围、AgentCard 宣告、设置面板模式——为本文实现指明了方向。我们的协议层、任务存储与执行器接缝均为独立实现;GUI 管理模型则直接承袭了 ryubyte 的连接面板。
|
|
192
169
|
|
|
193
170
|
## 许可证
|
|
194
171
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# @hanphone/dsh-a2a bundle patch: mount the plugin as one row, enabled by
|
|
2
|
-
# default
|
|
3
|
-
#
|
|
2
|
+
# default. v1.0 instances are NOT patch-configured — inbound/outbound server
|
|
3
|
+
# instances are created, preset-bound, and enabled/disabled entirely from the
|
|
4
|
+
# GUI (persisted in the a2a domain); this row only mounts the plugin core.
|
|
4
5
|
- insert:
|
|
5
6
|
- id: a2a
|
|
6
7
|
name: '@hanphone/dsh-a2a'
|
|
7
|
-
config:
|
|
8
|
-
server:
|
|
9
|
-
enabled: true
|
|
10
|
-
client:
|
|
11
|
-
agents: []
|
|
8
|
+
config: {}
|