@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.
Files changed (88) hide show
  1. package/README.md +133 -87
  2. package/README.zh.md +64 -87
  3. package/cordis.patch.yml +4 -7
  4. package/lib/client.js +228 -36
  5. package/lib/index.js +1271 -625
  6. package/lib/types/api.d.ts +43 -13
  7. package/lib/types/api.d.ts.map +1 -1
  8. package/lib/types/api.js +60 -20
  9. package/lib/types/api.js.map +1 -1
  10. package/lib/types/client/index.d.ts +50 -14
  11. package/lib/types/client/index.d.ts.map +1 -1
  12. package/lib/types/commands.d.ts +4 -2
  13. package/lib/types/commands.d.ts.map +1 -1
  14. package/lib/types/commands.js +59 -46
  15. package/lib/types/commands.js.map +1 -1
  16. package/lib/types/events.d.ts +32 -9
  17. package/lib/types/events.d.ts.map +1 -1
  18. package/lib/types/index.d.ts +21 -32
  19. package/lib/types/index.d.ts.map +1 -1
  20. package/lib/types/index.js +178 -213
  21. package/lib/types/index.js.map +1 -1
  22. package/lib/types/outbound/calls.d.ts.map +1 -1
  23. package/lib/types/outbound/calls.js +8 -6
  24. package/lib/types/outbound/calls.js.map +1 -1
  25. package/lib/types/protocol.d.ts +174 -109
  26. package/lib/types/protocol.d.ts.map +1 -1
  27. package/lib/types/protocol.js +58 -34
  28. package/lib/types/protocol.js.map +1 -1
  29. package/lib/types/server/a2a-server.d.ts +7 -1
  30. package/lib/types/server/a2a-server.d.ts.map +1 -1
  31. package/lib/types/server/a2a-server.js +11 -3
  32. package/lib/types/server/a2a-server.js.map +1 -1
  33. package/lib/types/server/card.d.ts +6 -30
  34. package/lib/types/server/card.d.ts.map +1 -1
  35. package/lib/types/server/card.js +13 -49
  36. package/lib/types/server/card.js.map +1 -1
  37. package/lib/types/server/exec/agent-runtime.d.ts +3 -0
  38. package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
  39. package/lib/types/server/exec/agent-runtime.js +2 -1
  40. package/lib/types/server/exec/agent-runtime.js.map +1 -1
  41. package/lib/types/server/identity.d.ts +38 -0
  42. package/lib/types/server/identity.d.ts.map +1 -0
  43. package/lib/types/server/identity.js +89 -0
  44. package/lib/types/server/identity.js.map +1 -0
  45. package/lib/types/server/inbound-registry.d.ts +60 -0
  46. package/lib/types/server/inbound-registry.d.ts.map +1 -0
  47. package/lib/types/server/inbound-registry.js +86 -0
  48. package/lib/types/server/inbound-registry.js.map +1 -0
  49. package/lib/types/server/routes.d.ts +5 -1
  50. package/lib/types/server/routes.d.ts.map +1 -1
  51. package/lib/types/server/routes.js +9 -5
  52. package/lib/types/server/routes.js.map +1 -1
  53. package/lib/types/server/store.d.ts +37 -1
  54. package/lib/types/server/store.d.ts.map +1 -1
  55. package/lib/types/server/store.js +14 -0
  56. package/lib/types/server/store.js.map +1 -1
  57. package/lib/types/servers/inbound-manager.d.ts +131 -0
  58. package/lib/types/servers/inbound-manager.d.ts.map +1 -0
  59. package/lib/types/servers/inbound-manager.js +312 -0
  60. package/lib/types/servers/inbound-manager.js.map +1 -0
  61. package/lib/types/servers/outbound-manager.d.ts +129 -0
  62. package/lib/types/servers/outbound-manager.d.ts.map +1 -0
  63. package/lib/types/servers/outbound-manager.js +238 -0
  64. package/lib/types/servers/outbound-manager.js.map +1 -0
  65. package/lib/types/service.d.ts +108 -23
  66. package/lib/types/service.d.ts.map +1 -1
  67. package/lib/types/service.js +39 -18
  68. package/lib/types/service.js.map +1 -1
  69. package/package.json +3 -3
  70. package/src/api.ts +70 -31
  71. package/src/client/index.ts +339 -81
  72. package/src/commands.ts +59 -43
  73. package/src/events.ts +14 -7
  74. package/src/index.ts +204 -249
  75. package/src/outbound/calls.ts +8 -6
  76. package/src/protocol.ts +204 -95
  77. package/src/server/a2a-server.ts +16 -4
  78. package/src/server/card.ts +13 -62
  79. package/src/server/exec/agent-runtime.ts +5 -2
  80. package/src/server/identity.ts +109 -0
  81. package/src/server/inbound-registry.ts +129 -0
  82. package/src/server/routes.ts +6 -4
  83. package/src/server/store.ts +50 -4
  84. package/src/servers/inbound-manager.ts +385 -0
  85. package/src/servers/outbound-manager.ts +289 -0
  86. package/src/service.ts +136 -28
  87. package/lib/tsconfig.client.tsbuildinfo +0 -1
  88. 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
- > **English** | [中文](README.zh.md)
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
- `@hanphone/dsh-a2a` turns a DeepSeek Harness profile into a first-class A2A agent:
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** `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `GetExtendedAgentCard`, `SubscribeToTask` over JSON-RPC; SSE streaming with catch-up frames.
18
- - **Dynamic AgentCard** — skills derived from the live `ctx.tools` registry (explicit id list, loud failure on missing referents) plus a built-in `chat` skill so a fresh install is immediately exercisable.
19
- - **Durable task store** — tasks live in the `a2a` storage domain (JSON backend by default, SQLite per deployment choice); task ids are server-generated and survive restarts.
20
- - **Executors** `session` (one DSH session per `contextId`) and `subagent` (delegates to `ctx.subagents`, streams tool-call artifacts back) as built-in implementations.
21
- - **Governed inbound** every inbound task passes through the `a2a/inbound-task` waterfall, so policy plugins can veto or audit before execution.
22
- - **Auth by environment variable** — inbound bearer token is referenced by env-var name (`authTokenEnv`), never stored in config as plaintext.
23
- - **Install-and-use** — both halves are enabled by default after `dsh plugin add`; no manual patch required to start.
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 (published)
56
+ ### From npm
28
57
 
29
58
  ```sh
30
59
  dsh plugin --profile web add @hanphone/dsh-a2a
31
60
  ```
32
61
 
33
- This works for any profile name (`web`, custom profiles, etc.):
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-0.1.0.tgz
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 restart `pnpm dsh web` (or your profile launcher) once after installing.
52
- 3. **Open Settings A2A 连接** you will see the inbound server status, the outbound agent list, and the task list.
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
- The inbound server listens on the profile's webServer (default `http://127.0.0.1:3080`):
86
+ Each inbound server listens on the profile's webServer:
55
87
 
56
88
  ```sh
57
- curl http://127.0.0.1:3080/.well-known/agent-card.json
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 (the built-in `chat` skill):
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 can, without touching any file:
103
+ The browser half registers an **A2A 连接** page under Settings. From it you
104
+ can, without touching any file:
71
105
 
72
- - toggle the inbound server (`server.enable` / `server.disable`),
73
- - list, add, enable/disable, refresh, and remove outbound agents,
74
- - view and cancel inbound tasks.
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 on the profile's webServer remote peers can never drive it.
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 dashboard covers the day-to-day operations. Values the dashboard does not edit (name/description, baseUrl, `authTokenEnv`, skills, executors, toolPrefix) are configured through the profile's user patch layer (`$DSH_HOME/profiles/<name>/cordis.patch.yml`) the reserve path:
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
- server:
86
- enabled: true
87
- name: My DSH Agent
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`), the storage stack (`@deepseek-ai/dsh-storage` + `@deepseek-ai/dsh-storage-domain`), the tools registry (`@deepseek-ai/dsh-tools`), and an agent loop (`@deepseek-ai/dsh-agent` + `@deepseek-ai/dsh-agent-loop`; the subagent executor additionally needs `@deepseek-ai/dsh-subagent`).
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 store lives in the `a2a` storage domain. The base composition routes storage through the `json` backend; to use SQLite, route the domain and add the backend in the same patch layer:
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 | enable | disable | card | agents |
148
- agent add|remove|enable|disable|refresh |
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** — `POST /a2a` (JSON-RPC) and `GET /.well-known/agent-card.json`; the AgentCard is derived from the live tool registry. Tasks flow through `a2a/inbound-task` → executor → task store, with SSE frames streamed to subscribers.
155
- - **Outbound** a persisted `agents` table in the `a2a` domain; `A2AClient` discovers an AgentCard, and each skill registers as a tool.
156
- - **Dashboard** — the browser half (React, `settings.section`) reads/writes the loopback-only `/a2a/api` route.
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 # loopback dashboard API (/a2a/api)
165
- index.ts # Cordis plugin entry (apply)
166
- protocol.ts # A2A v1.0 protocol constants + types
167
- jsonrpc.ts # JSON-RPC framing
168
- server/ # inbound half: store, card, a2a-server, routes, executors
169
- outbound/ # outbound half: A2AClient, registry, tools
170
- client/ # browser half: settings dashboard (React)
171
- service.ts # ctx.a2a service facade
172
- commands.ts # /a2a chat command
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/ # protocol, framing, card, store, registry, server, client, api
175
- composition/ # apply() on a real Cordis Context with stub host services
176
- cordis.patch.yml # bundle patch (mounts the plugin, enabled by default)
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
- ## Known limitations
230
+ ## Acknowledgements
191
231
 
192
- OAuth 2.0 / per-client credentials, gRPC binding, push notifications, `INPUT_REQUIRED` ↔ approval, and passive outbound result injection are listed as P1 or explicitly-not-doing in [docs/architecture.md](docs/architecture.md). They are documented gaps, not a roadmap.
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
- > [English](README.md) | **中文**
5
+ `@hanphone/dsh-a2a` 是一个独立开源的 A2A 插件,把 DeepSeek Harness profile 变成多面手 A2A 一等公民:能**同时对外发布多个入站 A2A server**,每个绑定一个自己的 agent preset、拥有独立端点 / AgentCard / 技能宣告 / 鉴权;也能**同时接入多个出站 A2A server**,每个独立 preset,远端技能映射为模型工具。所有 server 实例的创建、启停、编辑、删除全部在 GUI 完成——无需改任何配置文件。
6
6
 
7
- `@hanphone/dsh-a2a` 把 DeepSeek Harness profile 变成 A2A 一等公民:
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 协议面** —— JSON-RPC 上的 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`GetExtendedAgentCard`、`SubscribeToTask`;SSE 流式带补发帧。
18
- - **动态 AgentCard** —— 技能从实时 `ctx.tools` 注册表派生(显式 id 清单,缺失引用大声失败),外加内置 `chat` 技能,装完即可直接验证。
19
- - **持久化任务存储** —— 任务存于 `a2a` 存储域(默认 json 后端,可按部署切 SQLite);任务 id 服务端生成且跨重启存活。
20
- - **执行器** —— `session`(每个 `contextId` 一个 DSH 会话)与 `subagent`(委托 `ctx.subagents`,把工具调用过程流式回传)两种内置实现。
21
- - **受治理的入站** —— 每个入站任务都经过 `a2a/inbound-task` waterfall,策略插件可在执行前否决或审计。
22
- - **环境变量鉴权** —— 入站 Bearer token 只以环境变量名(`authTokenEnv`)引用,不以明文落配置。
23
- - **装完即用** —— `dsh plugin add` 后两端默认启用,无需手动 patch。
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-0.1.0.tgz
43
+ dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a-<version>.tgz
46
44
  ```
47
45
 
48
46
  ## 快速开始
49
47
 
50
- 1. **安装** —— `dsh plugin --profile web add @hanphone/dsh-a2a`。
51
- 2. **重启 GUI** —— 浏览器端插件表在 host 启动时扫描,装完请重启一次 `pnpm dsh web`(或对应 profile 启动命令)。
52
- 3. **打开 设置 → A2A 连接** —— 会看到入站服务端状态、出站 agent 列表与任务列表。
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
- 入站服务端监听在 profile 的 webServer 上(默认 `http://127.0.0.1:3080`):
52
+ 每个入站 server 监听在 profile 的 webServer 上:
55
53
 
56
54
  ```sh
57
- curl http://127.0.0.1:3080/.well-known/agent-card.json
55
+ # 所建实例的 AgentCard(确切 id 见 GUI)
56
+ curl http://127.0.0.1:3080/a2a/<id>/agent-card.json
58
57
  ```
59
58
 
60
- 发一个任务(内置 `chat` 技能):
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
- - 开关入站服务端(`server.enable` / `server.disable`),
73
- - 列出、添加、启用/停用、刷新、删除出站 agent,
74
- - 查看与取消入站任务。
71
+ - **入站 Servers** — 创建入站 server(名称/描述/版本、agent preset 选择器、鉴权 env、技能宣告表单),启停、编辑、删除;每行显示端点、preset、宣告技能与实时 AgentCard URL。
72
+ - **出站 Servers** — 添加出站连接(名称、远端 AgentCard URL、preset 选择器、Bearer env、超时),启停、刷新、删除;每行显示连接状态与工具注册数。
73
+ - **任务** — 查看与取消入站任务(每个任务携带来源 server)。
74
+ - **入站连接** — 查看哪些远程对端在调用各实例,可关闭某个对端。
75
75
 
76
- 所有面板流量都走 profile webServer 上的**仅回环** `/a2a/api` 路由——远程对端永远无法驱动它。
76
+ 所有面板流量走 profile webServer 上的**仅回环** `/a2a/api` 路由——远程对端永远无法驱动它。
77
77
 
78
78
  ## 配置
79
79
 
80
- 面板覆盖日常操作。面板不编辑的项(name/description、baseUrl、`authTokenEnv`、skills、executors、toolPrefix)通过 profile 用户 patch 层(`$DSH_HOME/profiles/<name>/cordis.patch.yml`)配置——保留方式:
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
- server:
86
- enabled: true
87
- name: My DSH Agent
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
- 任务存储位于 `a2a` 存储域。base 组合默认走 `json` 后端;要切 SQLite,在同一 patch 层路由域并加后端:
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 | enable | disable | card | agents |
148
- agent add|remove|enable|disable|refresh |
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
- - **入站** —— `POST /a2a`(JSON-RPC)与 `GET /.well-known/agent-card.json`;AgentCard 从实时工具注册表派生。任务流经 `a2a/inbound-task` → 执行器 → 任务存储,SSE 帧推送给订阅者。
155
- - **出站** —— `a2a` 域中的持久化 `agents` 表;`A2AClient` 发现 AgentCard,每个技能注册为一个工具。
156
- - **面板** —— 浏览器端(React,`settings.section`)经仅回环 `/a2a/api` 路由读写。
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 # 回环面板 API (/a2a/api)
165
- index.ts # Cordis 插件入口 (apply)
166
- protocol.ts # A2A v1.0 协议常量与类型
167
- jsonrpc.ts # JSON-RPC 帧
168
- server/ # 入站半区:store、card、a2a-server、routes、executors
169
- outbound/ # 出站半区:A2AClient、registry、tools
170
- client/ # 浏览器半区:设置面板 (React)
171
- service.ts # ctx.a2a 服务 facade
172
- commands.ts # /a2a 聊天命令
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/ # protocol、framing、card、store、registry、server、client、api
175
- composition/ # 在真实 Cordis Context 上以 stub 宿主服务跑 apply()
176
- cordis.patch.yml # bundle patch(挂载插件,默认启用)
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
- OAuth 2.0 / 每客户端凭据、gRPC 绑定、push notifications、`INPUT_REQUIRED` 审批、被动出站结果注入,在 [docs/architecture.md](docs/architecture.md) 中列为 P1 或明确不做。它们是已记录的空缺,不是路线图。
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 (install-and-use; turn either half off through the GUI dashboard or
3
- # an overlay patch on this row).
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: {}