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