@ryubyte/dsh-a2a 0.1.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/LICENSE +21 -0
- package/README.md +224 -0
- package/README.zh-CN.md +196 -0
- package/cordis.patch.yml +12 -0
- package/lib/client.js +574 -0
- package/lib/index.js +1896 -0
- package/lib/types/a2a-config.d.ts +107 -0
- package/lib/types/a2a-config.d.ts.map +1 -0
- package/lib/types/card.d.ts +40 -0
- package/lib/types/card.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +22 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client.d.ts +52 -0
- package/lib/types/client.d.ts.map +1 -0
- package/lib/types/dashboard.d.ts +144 -0
- package/lib/types/dashboard.d.ts.map +1 -0
- package/lib/types/errors.d.ts +24 -0
- package/lib/types/errors.d.ts.map +1 -0
- package/lib/types/index.d.ts +89 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/jsonrpc.d.ts +40 -0
- package/lib/types/jsonrpc.d.ts.map +1 -0
- package/lib/types/outbound.d.ts +45 -0
- package/lib/types/outbound.d.ts.map +1 -0
- package/lib/types/protocol.d.ts +330 -0
- package/lib/types/protocol.d.ts.map +1 -0
- package/lib/types/server.d.ts +142 -0
- package/lib/types/server.d.ts.map +1 -0
- package/package.json +89 -0
- package/src/a2a-config.ts +201 -0
- package/src/card.ts +143 -0
- package/src/client/index.ts +613 -0
- package/src/client.ts +141 -0
- package/src/dashboard.ts +350 -0
- package/src/errors.ts +39 -0
- package/src/index.ts +659 -0
- package/src/jsonrpc.ts +185 -0
- package/src/outbound.ts +186 -0
- package/src/protocol.ts +308 -0
- package/src/server.ts +467 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ryubyte
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# dsh-a2a
|
|
2
|
+
|
|
3
|
+
[中文文档](./README.zh-CN.md) | **English**
|
|
4
|
+
|
|
5
|
+
A **DeepSeek Harness (DSH)** plugin that implements the **Agent2Agent (A2A) Protocol v1.0** (Linux Foundation, `a2aproject/A2A`, Apache-2.0).
|
|
6
|
+
|
|
7
|
+
It turns a DSH instance into a full A2A peer in both directions:
|
|
8
|
+
|
|
9
|
+
- **Client mode (outbound)** — discover a remote agent via its **AgentCard**, map each **skill** to a DSH model tool (`a2a__<name>__<skill>`), and execute remote tasks through A2A **JSON-RPC**.
|
|
10
|
+
- **Server mode (inbound)** — expose this DSH to other agents: `/.well-known/agent-card.json` plus a JSON-RPC endpoint (`SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SendStreamingMessage` over SSE) on the DSH webserver.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
### Protocol surface (A2A v1.0)
|
|
15
|
+
|
|
16
|
+
- **Discovery**: `GET /.well-known/agent-card.json` (well-known first, configured URL fallback)
|
|
17
|
+
- **Unary**: `SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, `GetExtendedAgentCard`
|
|
18
|
+
- **Streaming**: `SendStreamingMessage` (SSE) / `SubscribeToTask`, with `TaskStatusUpdateEvent` + `TaskArtifactUpdateEvent`
|
|
19
|
+
- **Task lifecycle**: `SUBMITTED → WORKING → COMPLETED / FAILED / CANCELED / REJECTED / INPUT_REQUIRED / AUTH_REQUIRED`
|
|
20
|
+
- **Types**: `Task`, `Message`, `Part` (text/raw/url/data), `Artifact`, `AgentCard`, security schemes, extensions
|
|
21
|
+
|
|
22
|
+
### Connection dashboard (设置 → A2A 连接)
|
|
23
|
+
|
|
24
|
+
- **Outbound agents** — remote agents this DSH connects to (name, connection state, AgentCard URL, skill/tool counts, last activity), each with **Reconnect / Enable / Disable / Delete** controls.
|
|
25
|
+
- **Inbound peers** — who is calling this DSH (label, source address, first/last seen, task count, streaming flag), with a close control.
|
|
26
|
+
- **A2A server** — inbound server status with an online/offline toggle, endpoint and skill overview.
|
|
27
|
+
- The panel refreshes every 3 s; `/a2a/api` serves a JSON snapshot and control endpoints, fenced to loopback/same-origin.
|
|
28
|
+
|
|
29
|
+
### Runtime configuration (a2a.json)
|
|
30
|
+
|
|
31
|
+
Configuration is driven by **`a2a.json`** (per profile, UI-editable) — manage
|
|
32
|
+
agents without touching the composition. The file is resolved from the active
|
|
33
|
+
profile, not the launch directory: the plugin parses `--profile <name>` from
|
|
34
|
+
the process command line and reads/writes `~/.dsh/profiles/<name>/a2a.json`.
|
|
35
|
+
|
|
36
|
+
## Screenshots
|
|
37
|
+
|
|
38
|
+
| Outbound agents (who this DSH connects to) | Inbound peers (who calls this DSH) |
|
|
39
|
+
|---|---|
|
|
40
|
+
|  |  |
|
|
41
|
+
|
|
42
|
+
| Inbound with a live record | A2A server status panel |
|
|
43
|
+
|---|---|
|
|
44
|
+
|  |  |
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
### Option 1: from npm (recommended)
|
|
49
|
+
|
|
50
|
+
Once published to npm, install into the target profile with one command and
|
|
51
|
+
restart:
|
|
52
|
+
|
|
53
|
+
dsh plugin --profile <profile> add @ryubyte/dsh-a2a
|
|
54
|
+
dsh --profile <profile>
|
|
55
|
+
|
|
56
|
+
`dsh plugin` installs the package into the profile and auto-registers the
|
|
57
|
+
plugin layer (this package declares `dsh.bundle.patch` in `package.json`) — no
|
|
58
|
+
manual configuration is needed.
|
|
59
|
+
|
|
60
|
+
### Option 2: from the repository (development)
|
|
61
|
+
|
|
62
|
+
For hacking on the code, requires Node.js >= 22:
|
|
63
|
+
|
|
64
|
+
# 1. Clone the repo
|
|
65
|
+
git clone https://github.com/ryubyte/dsh-a2a.git
|
|
66
|
+
cd dsh-a2a
|
|
67
|
+
|
|
68
|
+
# 2. Install deps and build
|
|
69
|
+
npm install
|
|
70
|
+
npm run build
|
|
71
|
+
|
|
72
|
+
# 3. Link into the target profile
|
|
73
|
+
dsh plugin --profile <profile> add link:$(pwd)
|
|
74
|
+
|
|
75
|
+
# 4. Restart
|
|
76
|
+
dsh --profile <profile>
|
|
77
|
+
|
|
78
|
+
After changing source, re-run `npm run build` and restart.
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
### 1. Configure a remote agent (outbound)
|
|
83
|
+
|
|
84
|
+
Declare agents to connect to in the profile's `a2a.json`:
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
{
|
|
88
|
+
"agents": [
|
|
89
|
+
{
|
|
90
|
+
"name": "research",
|
|
91
|
+
"agentCardUrl": "https://research.example.com/.well-known/agent-card.json",
|
|
92
|
+
// "bearerToken": "...",
|
|
93
|
+
// "timeoutMs": 8000,
|
|
94
|
+
// "mapSkills": true,
|
|
95
|
+
// "enabled": false // keep the entry, but don't auto-connect
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Alternatively, in the Web GUI go to 设置 → A2A 连接 → 添加 Agent, paste an
|
|
102
|
+
AgentCard URL to import and connect — the config is written back to
|
|
103
|
+
`a2a.json`.
|
|
104
|
+
|
|
105
|
+
Once connected, the remote agent's skills appear as model tools:
|
|
106
|
+
`a2a__<name>__<skill>`.
|
|
107
|
+
|
|
108
|
+
### 2. Expose this DSH as an A2A agent (inbound)
|
|
109
|
+
|
|
110
|
+
Enable server mode in `a2a.json`:
|
|
111
|
+
|
|
112
|
+
```jsonc
|
|
113
|
+
{
|
|
114
|
+
"mode": "server",
|
|
115
|
+
"server": {
|
|
116
|
+
"enabled": true, // explicitly turn the inbound server on
|
|
117
|
+
// "baseUrl": "http://127.0.0.1:3080", // optional; default: derive from webServer
|
|
118
|
+
"agentName": "My DSH Agent",
|
|
119
|
+
"agentDescription": "A DSH agent over A2A v1.0",
|
|
120
|
+
"agentVersion": "0.1.0",
|
|
121
|
+
"skills": [
|
|
122
|
+
{ "id": "coding", "name": "Coding", "description": "Execute coding and shell tasks.", "tags": ["coding", "shell"] }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Other A2A clients discover this DSH via
|
|
129
|
+
`http://<host>:<port>/.well-known/agent-card.json` and call the `/a2a` endpoint.
|
|
130
|
+
|
|
131
|
+
> Note: the default inbound executor runs shell commands — fine for local
|
|
132
|
+
> testing only; for production, inject a constrained executor
|
|
133
|
+
> programmatically (see below) and put a TLS reverse proxy in front.
|
|
134
|
+
|
|
135
|
+
### 3. Programmatic use
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { A2AClient, A2AServer, TaskStore } from '@ryubyte/dsh-a2a';
|
|
139
|
+
|
|
140
|
+
// outbound: connect to a remote agent
|
|
141
|
+
const client = await A2AClient.connect('https://agent.example.com');
|
|
142
|
+
const { task } = await client.sendMessage({
|
|
143
|
+
messageId: crypto.randomUUID(),
|
|
144
|
+
role: 'ROLE_USER',
|
|
145
|
+
parts: [{ text: 'Draft a report' }],
|
|
146
|
+
});
|
|
147
|
+
for await (const ev of client.streamMessage({ messageId: crypto.randomUUID(), role: 'ROLE_USER', parts: [{ text: 'hi' }] })) {
|
|
148
|
+
// ev.statusUpdate | ev.artifactUpdate | ev.task | ev.message
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// inbound: wire a custom executor to your own agent loop
|
|
152
|
+
const store = new TaskStore();
|
|
153
|
+
const server = new A2AServer({
|
|
154
|
+
baseUrl: 'http://127.0.0.1:3080',
|
|
155
|
+
agentName: 'My DSH',
|
|
156
|
+
agentDescription: '...',
|
|
157
|
+
agentVersion: '1.0.0',
|
|
158
|
+
execute: async ({ message }) => ({
|
|
159
|
+
messageId: crypto.randomUUID(),
|
|
160
|
+
role: 'ROLE_AGENT',
|
|
161
|
+
parts: [{ text: await myAgentRun(message.parts) }],
|
|
162
|
+
}),
|
|
163
|
+
}, store);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Directory structure
|
|
167
|
+
|
|
168
|
+
src/
|
|
169
|
+
protocol.ts # A2A v1.0 types & constants
|
|
170
|
+
card.ts # AgentCard discovery / validation
|
|
171
|
+
jsonrpc.ts # JSON-RPC 2.0 + SSE transport
|
|
172
|
+
client.ts # outbound client (A2AClient)
|
|
173
|
+
server.ts # inbound server + TaskStore (A2AServer)
|
|
174
|
+
outbound.ts # skill → model-tool bridge (registerAgentTools)
|
|
175
|
+
dashboard.ts # connection registry + control
|
|
176
|
+
a2a-config.ts # runtime a2a.json parsing / persistence
|
|
177
|
+
index.ts # Cordis plugin entry (client / server modes)
|
|
178
|
+
client/
|
|
179
|
+
index.ts # browser half: settings "A2A 连接" panel
|
|
180
|
+
lib/ # build output (index.js + client.js + types/)
|
|
181
|
+
scripts/
|
|
182
|
+
build-client.mjs # wrap client bundle (__ModuleLoader__)
|
|
183
|
+
capture-shots.mjs # README screenshot capture (headless Chrome + CDP)
|
|
184
|
+
cordis.patch.yml # plugin registration row (self-mounted bundle)
|
|
185
|
+
package.json # dsh.bundle.patch + dsh.client manifest
|
|
186
|
+
|
|
187
|
+
## How it works
|
|
188
|
+
|
|
189
|
+
- The plugin reads `ctx.webServer` and `ctx.tools` as context properties inside
|
|
190
|
+
`ctx.inject(...)` / `ctx.effect(...)` — the native Cordis pattern used by DSH
|
|
191
|
+
bundles (`dsh-client-hmr`, …). A composition missing either half simply idles
|
|
192
|
+
that half; nothing crashes.
|
|
193
|
+
- **`baseUrl` is optional in server mode**: when omitted the AgentCard
|
|
194
|
+
advertises the webserver's real listen address, so ephemeral ports never
|
|
195
|
+
publish a stale URL. Set it explicitly only when a reverse proxy fronts the
|
|
196
|
+
instance.
|
|
197
|
+
- Client-mode connection lifecycle hooks (`onReady` / `onDispose`) feed a
|
|
198
|
+
**process-wide shared registry**; multiple instances (client + server) share
|
|
199
|
+
it, and `/a2a/api` is registered once per process.
|
|
200
|
+
- All registrations are fiber-scoped: Cordis disposes them on plugin
|
|
201
|
+
stop/update.
|
|
202
|
+
- Disabled agents (`enabled: false`) show as grayed-out placeholder cards and
|
|
203
|
+
are **not** reconnected on restart until re-enabled.
|
|
204
|
+
|
|
205
|
+
## Known limitations
|
|
206
|
+
|
|
207
|
+
- The dashboard API (`/a2a/api`) listens on loopback / same-origin only; it
|
|
208
|
+
carries no secrets and exists for operator visibility.
|
|
209
|
+
- The default inbound executor runs shell commands directly (`/bin/sh -c`), not
|
|
210
|
+
an LLM agent; replace it with a constrained executor (e.g. DSH's
|
|
211
|
+
sandbox/approval pipeline) in production.
|
|
212
|
+
- The A2A spec requires HTTPS for production `AgentInterface.url`; DSH's
|
|
213
|
+
webserver binds loopback by default, so put a TLS reverse proxy in front
|
|
214
|
+
before exposing beyond localhost.
|
|
215
|
+
- Outbound bearer tokens are stored in plaintext in `a2a.json`; secure the file
|
|
216
|
+
yourself.
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npm install
|
|
222
|
+
npm run build # tsc(types) + tsdown(lib) + wrap client → lib/client.js
|
|
223
|
+
npm test # node:test + tsx (unit/integration) + client-bundle smoke
|
|
224
|
+
```
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# dsh-a2a
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | **中文文档**
|
|
4
|
+
|
|
5
|
+
一个 **DeepSeek Harness (DSH)** 插件,实现 **Agent2Agent (A2A) 协议 v1.0**(Linux 基金会,`a2aproject/A2A`,Apache-2.0)。
|
|
6
|
+
|
|
7
|
+
让一个 DSH 实例成为完整的 A2A 双端节点:
|
|
8
|
+
|
|
9
|
+
- **Client 模式(出站)**——通过远程 Agent 的 **AgentCard** 发现它,把每个 **skill** 映射为 DSH 模型工具(`a2a__<name>__<skill>`),通过 A2A **JSON-RPC** 执行远程任务。
|
|
10
|
+
- **Server 模式(入站)**——把本 DSH 暴露给其他 Agent:在 DSH webserver 上提供 `/.well-known/agent-card.json` 与 JSON-RPC 端点(`SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、基于 SSE 的 `SendStreamingMessage`)。
|
|
11
|
+
|
|
12
|
+
## 功能
|
|
13
|
+
|
|
14
|
+
### 协议能力(A2A v1.0)
|
|
15
|
+
|
|
16
|
+
- **发现**:`GET /.well-known/agent-card.json`(优先 well-known,配置的 URL 兜底)
|
|
17
|
+
- **单次调用**:`SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、`GetExtendedAgentCard`
|
|
18
|
+
- **流式**:`SendStreamingMessage`(SSE)/ `SubscribeToTask`,含 `TaskStatusUpdateEvent` + `TaskArtifactUpdateEvent`
|
|
19
|
+
- **任务生命周期**:`SUBMITTED → WORKING → COMPLETED / FAILED / CANCELED / REJECTED / INPUT_REQUIRED / AUTH_REQUIRED`
|
|
20
|
+
- **类型**:`Task`、`Message`、`Part`(text/raw/url/data)、`Artifact`、`AgentCard`、安全方案、扩展
|
|
21
|
+
|
|
22
|
+
### 连接面板(设置 → A2A 连接)
|
|
23
|
+
|
|
24
|
+
- **出站 Agent**:列出本 DSH 连接的远程 Agent(名称、连接状态、AgentCard URL、技能/工具数、最近活动),每个 Agent 支持 **重连 / 启用 / 禁用 / 删除**。
|
|
25
|
+
- **入站连接**:谁在调用本 DSH(来源、地址、首/末次连接、任务数、流式标记),支持关闭。
|
|
26
|
+
- **A2A 服务**:入站服务的运行状态,上线 / 下线开关,端点与技能一览。
|
|
27
|
+
- 面板每 3 秒自动刷新;`/a2a/api` 提供 JSON 快照与控制接口,受 loopback/same-origin 保护。
|
|
28
|
+
|
|
29
|
+
### 运行时配置(a2a.json)
|
|
30
|
+
|
|
31
|
+
配置以 **`a2a.json`(按 profile 存放,UI 可编辑)为准**,无需改动组合配置即可管理 Agent。文件定位不依赖启动目录:插件解析进程命令行里的 `--profile <name>`,读写 `~/.dsh/profiles/<name>/a2a.json`。
|
|
32
|
+
|
|
33
|
+
## 截图
|
|
34
|
+
|
|
35
|
+
| 出站 Agent(本 DSH 连接了谁) | 入站连接(谁在调用本 DSH) |
|
|
36
|
+
|---|---|
|
|
37
|
+
|  |  |
|
|
38
|
+
|
|
39
|
+
| 含真实记录的入站 | A2A 服务状态面板 |
|
|
40
|
+
|---|---|
|
|
41
|
+
|  |  |
|
|
42
|
+
|
|
43
|
+
## 安装
|
|
44
|
+
|
|
45
|
+
### 方式一:从 npm 安装(推荐)
|
|
46
|
+
|
|
47
|
+
插件发布到 npm 后,一行命令装进目标 profile,重启即生效:
|
|
48
|
+
|
|
49
|
+
dsh plugin --profile <profile> add @ryubyte/dsh-a2a
|
|
50
|
+
dsh --profile <profile>
|
|
51
|
+
|
|
52
|
+
`dsh plugin` 会把包装进 profile 并自动加入插件层(本包在 package.json 里声明了 `dsh.bundle.patch`),无需手动改任何配置。
|
|
53
|
+
|
|
54
|
+
### 方式二:从仓库安装(开发调试)
|
|
55
|
+
|
|
56
|
+
用于改代码调试,需要 Node.js >= 22 与 pnpm:
|
|
57
|
+
|
|
58
|
+
# 1. 克隆仓库
|
|
59
|
+
git clone https://github.com/ryubyte/dsh-a2a.git
|
|
60
|
+
cd dsh-a2a
|
|
61
|
+
|
|
62
|
+
# 2. 安装依赖并构建
|
|
63
|
+
npm install
|
|
64
|
+
npm run build
|
|
65
|
+
|
|
66
|
+
# 3. 链接进目标 profile
|
|
67
|
+
dsh plugin --profile <profile> add link:$(pwd)
|
|
68
|
+
|
|
69
|
+
# 4. 重启
|
|
70
|
+
dsh --profile <profile>
|
|
71
|
+
|
|
72
|
+
改完源码后重跑 `npm run build` 再重启即可。
|
|
73
|
+
|
|
74
|
+
## 使用
|
|
75
|
+
|
|
76
|
+
### 1. 配置远程 Agent(出站)
|
|
77
|
+
|
|
78
|
+
在 profile 目录下的 `a2a.json` 里声明要连接的 Agent:
|
|
79
|
+
|
|
80
|
+
```jsonc
|
|
81
|
+
{
|
|
82
|
+
"agents": [
|
|
83
|
+
{
|
|
84
|
+
"name": "research",
|
|
85
|
+
"agentCardUrl": "https://research.example.com/.well-known/agent-card.json",
|
|
86
|
+
// "bearerToken": "...",
|
|
87
|
+
// "timeoutMs": 8000,
|
|
88
|
+
// "mapSkills": true,
|
|
89
|
+
// "enabled": false // 保留条目,但不自动连接
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
也可以在 Web GUI「设置 → A2A 连接 → 添加 Agent」里输入 AgentCard URL 导入并连接,配置会写回 `a2a.json`。
|
|
96
|
+
|
|
97
|
+
连接成功后,对方 Agent 的 skills 会作为模型工具出现:`a2a__<name>__<skill>`。
|
|
98
|
+
|
|
99
|
+
### 2. 对外提供 A2A 服务(入站)
|
|
100
|
+
|
|
101
|
+
同样在 `a2a.json` 里开启 server 模式:
|
|
102
|
+
|
|
103
|
+
```jsonc
|
|
104
|
+
{
|
|
105
|
+
"mode": "server",
|
|
106
|
+
"server": {
|
|
107
|
+
"enabled": true, // 显式开启入站服务
|
|
108
|
+
// "baseUrl": "http://127.0.0.1:3080", // 可选;默认从 webServer 监听地址推导
|
|
109
|
+
"agentName": "My DSH Agent",
|
|
110
|
+
"agentDescription": "A DSH agent over A2A v1.0",
|
|
111
|
+
"agentVersion": "0.1.0",
|
|
112
|
+
"skills": [
|
|
113
|
+
{ "id": "coding", "name": "Coding", "description": "Execute coding and shell tasks.", "tags": ["coding", "shell"] }
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
之后其他 A2A 客户端通过 `http://<host>:<port>/.well-known/agent-card.json` 发现本 DSH,通过 `/a2a` 端点调用。
|
|
120
|
+
|
|
121
|
+
> 注:默认入站 executor 会执行 shell 命令,仅适合本机自测;生产部署请通过编程方式注入受限 executor(见下文),并置于 TLS 反向代理之后。
|
|
122
|
+
|
|
123
|
+
### 3. 编程接入
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { A2AClient, A2AServer, TaskStore } from '@ryubyte/dsh-a2a';
|
|
127
|
+
|
|
128
|
+
// 出站:连接远程 Agent
|
|
129
|
+
const client = await A2AClient.connect('https://agent.example.com');
|
|
130
|
+
const { task } = await client.sendMessage({
|
|
131
|
+
messageId: crypto.randomUUID(),
|
|
132
|
+
role: 'ROLE_USER',
|
|
133
|
+
parts: [{ text: 'Draft a report' }],
|
|
134
|
+
});
|
|
135
|
+
for await (const ev of client.streamMessage({ messageId: crypto.randomUUID(), role: 'ROLE_USER', parts: [{ text: 'hi' }] })) {
|
|
136
|
+
// ev.statusUpdate | ev.artifactUpdate | ev.task | ev.message
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 入站:自定义 executor 接入 DSH agent 循环
|
|
140
|
+
const store = new TaskStore();
|
|
141
|
+
const server = new A2AServer({
|
|
142
|
+
baseUrl: 'http://127.0.0.1:3080',
|
|
143
|
+
agentName: 'My DSH',
|
|
144
|
+
agentDescription: '...',
|
|
145
|
+
agentVersion: '1.0.0',
|
|
146
|
+
execute: async ({ message }) => ({
|
|
147
|
+
messageId: crypto.randomUUID(),
|
|
148
|
+
role: 'ROLE_AGENT',
|
|
149
|
+
parts: [{ text: await myAgentRun(message.parts) }],
|
|
150
|
+
}),
|
|
151
|
+
}, store);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 目录结构
|
|
155
|
+
|
|
156
|
+
src/
|
|
157
|
+
protocol.ts # A2A v1.0 类型与常量
|
|
158
|
+
card.ts # AgentCard 发现 / 校验
|
|
159
|
+
jsonrpc.ts # JSON-RPC 2.0 + SSE 传输
|
|
160
|
+
client.ts # 出站客户端(A2AClient)
|
|
161
|
+
server.ts # 入站服务 + TaskStore(A2AServer)
|
|
162
|
+
outbound.ts # skill → 模型工具桥(registerAgentTools)
|
|
163
|
+
dashboard.ts # 连接注册表 + 控制
|
|
164
|
+
a2a-config.ts # 运行时 a2a.json 解析 / 持久化
|
|
165
|
+
index.ts # Cordis 插件入口(client / server 两种模式)
|
|
166
|
+
client/
|
|
167
|
+
index.ts # 浏览器半区:设置页「A2A 连接」面板
|
|
168
|
+
lib/ # 构建产物(index.js + client.js + types/)
|
|
169
|
+
scripts/
|
|
170
|
+
build-client.mjs # 客户端 bundle 包装(__ModuleLoader__)
|
|
171
|
+
capture-shots.mjs # README 截图抓取(headless Chrome + CDP)
|
|
172
|
+
cordis.patch.yml # 插件注册行(bundle 自挂载,无需手动配置)
|
|
173
|
+
package.json # dsh.bundle.patch + dsh.client 清单
|
|
174
|
+
|
|
175
|
+
## 工作原理
|
|
176
|
+
|
|
177
|
+
- 插件在 `ctx.inject(...)` / `ctx.effect(...)` 内读取 `ctx.webServer` 与 `ctx.tools`(原生 Cordis 模式,与 `dsh-client-hmr` 等 bundle 一致);缺少任一半的组合只会闲置该半区,不会崩溃。
|
|
178
|
+
- **server 模式 `baseUrl` 可选**:省略时 AgentCard 公布 webServer 的真实监听地址,临时端口不会发布过期 URL;仅在反向代理前置时才需显式设置。
|
|
179
|
+
- 客户端模式的连接生命周期钩子(`onReady` / `onDispose`)上报到**进程级共享注册表**;多个实例(client + server)共用,`/a2a/api` 每个进程注册一次。
|
|
180
|
+
- 所有注册 fiber 作用域化:插件停止 / 更新时由 Cordis 自动清理。
|
|
181
|
+
- 禁用(`enabled:false`)的 Agent 显示为灰化占位卡,**不自动连接**,直到重新启用。
|
|
182
|
+
|
|
183
|
+
## 已知限制
|
|
184
|
+
|
|
185
|
+
- 面板 API(`/a2a/api`)只监听 loopback / same-origin,供运维可见性使用,不承载机密。
|
|
186
|
+
- 默认入站 executor 直接执行 shell 命令(`/bin/sh -c`),非 LLM 代理;生产环境必须替换为受限 executor(如 DSH sandbox / 审批管线)。
|
|
187
|
+
- A2A 规范要求生产 `AgentInterface.url` 使用 HTTPS;DSH webServer 默认仅绑 loopback,对外暴露前需自行加 TLS。
|
|
188
|
+
- 出站的 bearer token 明文存于 `a2a.json`,请自行保证该文件权限。
|
|
189
|
+
|
|
190
|
+
## 开发
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npm install
|
|
194
|
+
npm run build # tsc(types) + tsdown(lib) + wrap client → lib/client.js
|
|
195
|
+
npm test # node:test + tsx(单元 / 集成)+ client-bundle smoke
|
|
196
|
+
```
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# dsh-a2a bundle patch — self-mounting bundle layer.
|
|
2
|
+
# Profile list `@ryubyte/dsh-a2a` in dsh.profile.bundles (via
|
|
3
|
+
# `dsh plugin --profile <profile> add @ryubyte/dsh-a2a`); no manual
|
|
4
|
+
# cordis.patch.yml entry needed. `id` stays stable so user overlays can
|
|
5
|
+
# toggle it; `name` is the published package name and must match
|
|
6
|
+
# package.json. Runtime config (mode, agents, server) lives in the
|
|
7
|
+
# profile's a2a.json:
|
|
8
|
+
# - mode: client | server | both (default client — server off unless enabled)
|
|
9
|
+
# - server: { enabled: true, ... } explicitly turns the inbound server on.
|
|
10
|
+
- insert:
|
|
11
|
+
- id: a2a
|
|
12
|
+
name: '@ryubyte/dsh-a2a'
|