@ai_mind/dsh-a2a 0.0.0-stage → 1.0.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 +444 -2
- package/client.js +868 -0
- package/cordis.patch.yml +21 -0
- package/icon.svg +15 -0
- package/index.js +59 -0
- package/lib/auth.js +196 -0
- package/lib/bridge.js +518 -0
- package/lib/card.js +214 -0
- package/lib/client-discovery.js +58 -0
- package/lib/config.js +565 -0
- package/lib/executor.js +357 -0
- package/lib/files.js +52 -0
- package/lib/limits.js +133 -0
- package/lib/panel.js +357 -0
- package/lib/prompt.js +161 -0
- package/lib/server.js +456 -0
- package/lib/store.js +288 -0
- package/lib/util.js +221 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +54 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ai-mind
|
|
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
CHANGED
|
@@ -1,3 +1,445 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ai_mind/dsh-a2a
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
把本机 DeepSeek Harness 智能体暴露为 **A2A(Agent2Agent)v1.0 服务端**的生产级插件。
|
|
4
|
+
|
|
5
|
+
协议实现基于官方 [`@a2a-js/sdk` 1.3.0](https://www.npmjs.com/package/@a2a-js/sdk)(A2A Protocol Specification v1.0 的官方 TypeScript SDK),本插件负责把它挂到 Harness 的 Web 服务器上,并把 A2A 任务桥接到真实的 Harness Agent:每个 A2A `contextId` 对应一个可复用的 Harness 会话,任务取消、超时、并发上限、沙箱与审批策略都由插件统一管理。
|
|
6
|
+
|
|
7
|
+
- 规范:[A2A Protocol v1.0.0](https://a2a-protocol.org/v1.0.0/specification/)
|
|
8
|
+
- 绑定:**JSON-RPC 2.0** + **HTTP+JSON/REST**(gRPC 未暴露,见[已知限制](#已知限制))
|
|
9
|
+
- 传输:HTTP/SSE(`text/event-stream`)
|
|
10
|
+
- 发现:`GET /.well-known/agent-card.json`
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [功能与协议一致性](#功能与协议一致性)
|
|
17
|
+
- [快速开始](#快速开始)
|
|
18
|
+
- [协议端点](#协议端点)
|
|
19
|
+
- [鉴权与安全](#鉴权与安全)
|
|
20
|
+
- [Agent 运行时映射](#agent-运行时映射)
|
|
21
|
+
- [配置参考](#配置参考)
|
|
22
|
+
- [运维](#运维)
|
|
23
|
+
- [已知限制](#已知限制)
|
|
24
|
+
- [开发与测试](#开发与测试)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 功能与协议一致性
|
|
29
|
+
|
|
30
|
+
| A2A 能力 | 状态 | 说明 |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| Agent Card 发现 | ✅ | `/.well-known/agent-card.json`,支持 ETag / `304` / `Cache-Control` |
|
|
33
|
+
| Agent Card JWS 签名 | ✅ | RFC 7515 JWS + RFC 8785 JCS,`ES256/ES384/ES512/RS*/PS*/EdDSA` |
|
|
34
|
+
| `SendMessage` | ✅ | 阻塞与 `returnImmediately` 非阻塞两种语义 |
|
|
35
|
+
| `SendStreamingMessage` | ✅ | SSE:Task → 状态/产物增量 → 终态 |
|
|
36
|
+
| `GetTask` / `ListTasks` | ✅ | 游标分页、按状态时间倒序、`historyLength`、`includeArtifacts` |
|
|
37
|
+
| `CancelTask` | ✅ | 可取消排队中或执行中的回合 |
|
|
38
|
+
| `SubscribeToTask` | ✅ | 从当前状态续订到终态 |
|
|
39
|
+
| Push Notification CRUD | ✅ | Create / Get / List / Delete,Webhook 投递 |
|
|
40
|
+
| `GetExtendedAgentCard` | ✅ | 仅在启用鉴权时可开启 |
|
|
41
|
+
| 多轮上下文 | ✅ | 同一 `contextId` 复用同一 Harness 会话 |
|
|
42
|
+
| 协议版本协商 | ✅ | 仅 `1.0`;缺失或 `0.3` 返回 `VersionNotSupportedError (-32009)` |
|
|
43
|
+
| 扩展协商 | ✅ | `card.extensions` + `A2A-Extensions` 头,缺失必需扩展返回 `-32008` |
|
|
44
|
+
| JSON-RPC 错误码 | ✅ | 规范 `-32001`…`-32009`、`-32600`、`-32601`、`-32602`、`-32700` |
|
|
45
|
+
| REST 错误体 | ✅ | `google.rpc.Status` + `google.rpc.ErrorInfo` |
|
|
46
|
+
| ProtoJSON 序列化 | ✅ | camelCase、枚举字符串、省略默认值、UTC ISO-8601 时间戳 |
|
|
47
|
+
|
|
48
|
+
> **A2A 任务与 Harness 会话的关系**:一个 A2A `contextId` 绑定一个长期存活的 Harness Agent 会话;任务本身是单回合的(完成即终态),多轮对话通过在同一 `contextId` 下创建新任务实现。这是 A2A “任务状态机”与 Harness “会话”最自然的映射方式。
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 快速开始
|
|
53
|
+
|
|
54
|
+
1. 安装依赖并安装插件包(在插件目录):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. 通过 `plugin_manager` 的 `install_bundle` 安装本目录(绝对路径)。安装采用 `link:` 方式,因此**先在本目录执行 `npm install`**(依赖会从本目录的 `node_modules` 解析);打包发布时 `npm pack` 也可用,依赖会随 profile 安装。随后在 profile 的 `cordis.patch.yml` 中追加配置:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
- id: a2a-server
|
|
64
|
+
name: '@ai_mind/dsh-a2a'
|
|
65
|
+
config:
|
|
66
|
+
server:
|
|
67
|
+
publicUrl: 'https://agent.example.com'
|
|
68
|
+
auth:
|
|
69
|
+
mode: bearer
|
|
70
|
+
tokenEnv: A2A_AUTH_TOKEN
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
3. 验证 Agent Card:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl -s http://127.0.0.1:57133/.well-known/agent-card.json | jq .
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
4. 发一条消息(注意必须带 `A2A-Version: 1.0`):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
curl -s http://127.0.0.1:57133/a2a \
|
|
83
|
+
-H 'content-type: application/json' \
|
|
84
|
+
-H 'a2a-version: 1.0' \
|
|
85
|
+
-H "authorization: Bearer $A2A_AUTH_TOKEN" \
|
|
86
|
+
-d '{
|
|
87
|
+
"jsonrpc": "2.0",
|
|
88
|
+
"id": 1,
|
|
89
|
+
"method": "SendMessage",
|
|
90
|
+
"params": {
|
|
91
|
+
"message": {
|
|
92
|
+
"messageId": "m-1",
|
|
93
|
+
"role": "ROLE_USER",
|
|
94
|
+
"parts": [{"text": "用一句话介绍这个工作区"}]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}' | jq .
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
5. 流式调用:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
curl -N http://127.0.0.1:57133/a2a \
|
|
104
|
+
-H 'content-type: application/json' \
|
|
105
|
+
-H 'a2a-version: 1.0' \
|
|
106
|
+
-d '{"jsonrpc":"2.0","id":2,"method":"SendStreamingMessage","params":{"message":{"messageId":"m-2","role":"ROLE_USER","parts":[{"text":"列出当前目录的文件"}]}}}'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 图形界面
|
|
112
|
+
|
|
113
|
+
浏览器半已并入同一个包(`client.js`),不再需要安装第二个 bundle。安装本包后刷新 Web 页面,打开 **设置 → 插件**,即可看到 **「A2A 服务端」** 标签页。
|
|
114
|
+
|
|
115
|
+
面板分三部分:
|
|
116
|
+
|
|
117
|
+
1. **概览**:实时 Agent Card、JSON-RPC / HTTP+JSON 地址(一键复制)、流式/推送/扩展卡片能力、鉴权状态、运行状态与可复制的 `curl` / 官方 JS 客户端示例。
|
|
118
|
+
2. **配置表单**:直接编辑名称、描述、对外地址、挂载路径、流式/推送/扩展卡片、鉴权模式与令牌环境变量、访问令牌(写入后不再回显)、Agent 预设/模型/工作目录/沙箱/审批/并发/超时,以及请求体与任务保留限额。
|
|
119
|
+
3. **保存**:写入 profile 的 `cordis.patch.yml`(`a2a-server` 行),校验通过后插件自动重载;修改挂载路径后刷新页面即可。
|
|
120
|
+
|
|
121
|
+
配置接口 `GET/PUT <mountPath>/panel/config` 使用 **Harness Web 连接凭据**(不是 A2A token)鉴权:只有能打开 Settings 的已登录浏览器可以读写,远程 A2A 客户端即使知道路径也会收到 401。令牌字段是只写字段,GET 只返回“是否已配置”。
|
|
122
|
+
|
|
123
|
+
`lib/client-discovery.js` 会在插件挂载时清掉 DSH client 模块扫描器对“这个包没有浏览器半”的进程级缓存并触发一次重新扫描,因此从旧版本热升级后无需重启即可看到面板;全新启动时该逻辑自动跳过。
|
|
124
|
+
|
|
125
|
+
## 作为客户端请求
|
|
126
|
+
|
|
127
|
+
A2A 客户端必须发送 `A2A-Version: 1.0`,并带 `Content-Type: application/json`(REST 为 `application/a2a+json`)。启用鉴权后附带 `Authorization: Bearer <token>`。
|
|
128
|
+
|
|
129
|
+
### 1. 发现 Agent Card
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl -s http://127.0.0.1:57133/.well-known/agent-card.json | jq .
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2. JSON-RPC 发送消息(阻塞)
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
curl -s http://127.0.0.1:57133/a2a \
|
|
139
|
+
-H 'content-type: application/json' \
|
|
140
|
+
-H 'a2a-version: 1.0' \
|
|
141
|
+
-d '{
|
|
142
|
+
"jsonrpc": "2.0",
|
|
143
|
+
"id": 1,
|
|
144
|
+
"method": "SendMessage",
|
|
145
|
+
"params": {
|
|
146
|
+
"message": {
|
|
147
|
+
"messageId": "m-1",
|
|
148
|
+
"role": "ROLE_USER",
|
|
149
|
+
"parts": [{"text": "用一句话介绍当前工作区"}]
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}' | jq .
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 3. 流式(SSE)
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
curl -N http://127.0.0.1:57133/a2a \
|
|
159
|
+
-H 'content-type: application/json' \
|
|
160
|
+
-H 'a2a-version: 1.0' \
|
|
161
|
+
-d '{"jsonrpc":"2.0","id":2,"method":"SendStreamingMessage","params":{"message":{"messageId":"m-2","role":"ROLE_USER","parts":[{"text":"列出当前目录的文件"}]}}}'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 4. 官方 JavaScript 客户端
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm install @a2a-js/sdk
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
import { ClientFactory, Role } from '@a2a-js/sdk/client'
|
|
172
|
+
|
|
173
|
+
const client = await new ClientFactory().createFromUrl('http://127.0.0.1:57133')
|
|
174
|
+
const result = await client.sendMessage({
|
|
175
|
+
message: {
|
|
176
|
+
messageId: crypto.randomUUID(),
|
|
177
|
+
role: Role.ROLE_USER,
|
|
178
|
+
parts: [{ content: { $case: 'text', value: '你好' } }],
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
console.log(result)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
流式版本把 `sendMessage` 换成 `client.sendMessageStream(params)` 并 `for await`。
|
|
185
|
+
|
|
186
|
+
### 5. REST
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
curl -s http://127.0.0.1:57133/a2a/rest/message:send \
|
|
190
|
+
-H 'content-type: application/a2a+json' \
|
|
191
|
+
-H 'a2a-version: 1.0' \
|
|
192
|
+
-d '{"message":{"messageId":"m-3","role":"ROLE_USER","parts":[{"text":"你好"}]}}' | jq .
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
同一 `contextId` 下的多个任务共享 Harness 会话上下文;任务终态后继续发消息会被 A2A 规范拒绝(终态不可变),请用新的 `messageId` 发起新任务并复用 `contextId`。
|
|
196
|
+
|
|
197
|
+
## 协议端点
|
|
198
|
+
|
|
199
|
+
| 端点 | 绑定 | 说明 |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| `GET /.well-known/agent-card.json` | 发现 | 公开 Agent Card(默认 5 分钟缓存) |
|
|
202
|
+
| `POST /a2a` | JSON-RPC | 所有 JSON-RPC 方法 |
|
|
203
|
+
| `POST /a2a/rest/message:send` | REST | 发送消息 |
|
|
204
|
+
| `POST /a2a/rest/message:stream` | REST | SSE 流式发送 |
|
|
205
|
+
| `GET /a2a/rest/tasks/{id}` | REST | 查询任务 |
|
|
206
|
+
| `GET /a2a/rest/tasks` | REST | 列出任务 |
|
|
207
|
+
| `POST /a2a/rest/tasks/{id}:cancel` | REST | 取消任务 |
|
|
208
|
+
| `GET/POST /a2a/rest/tasks/{id}:subscribe` | REST | 订阅任务 |
|
|
209
|
+
| `…/tasks/{id}/pushNotificationConfigs` | REST | Push 配置 CRUD |
|
|
210
|
+
| `GET /a2a/rest/extendedAgentCard` | REST | 扩展 Agent Card |
|
|
211
|
+
| `GET /a2a/health` | 运维 | 健康与运行指标(需鉴权) |
|
|
212
|
+
|
|
213
|
+
JSON-RPC 方法名:`SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`CreateTaskPushNotificationConfig`、`GetTaskPushNotificationConfig`、`ListTaskPushNotificationConfigs`、`DeleteTaskPushNotificationConfig`、`GetExtendedAgentCard`。
|
|
214
|
+
|
|
215
|
+
挂载路径可通过 `server.mountPath` 修改;Agent Card 的 `supportedInterfaces` 会自动使用配置后的 URL。
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 鉴权与安全
|
|
220
|
+
|
|
221
|
+
### 鉴权模式
|
|
222
|
+
|
|
223
|
+
| `auth.mode` | 行为 |
|
|
224
|
+
| --- | --- |
|
|
225
|
+
| `none`(默认) | 无凭据;**仅允许 loopback 调用方**。来自非回环地址的请求返回 401,除非显式设置 `auth.allowRemoteUnauthenticated: true` |
|
|
226
|
+
| `bearer` | `Authorization: Bearer <token>`,常量时间比较 |
|
|
227
|
+
| `apiKey` | `auth.headerName`(默认 `x-api-key`)头携带 token |
|
|
228
|
+
|
|
229
|
+
凭据来源(可组合):`auth.clients[{id,token}]`、`auth.tokens[]`、`auth.token`、`auth.tokenEnv`(环境变量,支持逗号/换行分隔多个)。启用鉴权后 Agent Card 会自动声明 `securitySchemes` 与 `securityRequirements`。
|
|
230
|
+
|
|
231
|
+
**每个凭据是一个独立的租户边界**:任务与 Push 配置按凭据 id 隔离,客户端 A 看不到客户端 B 的任务。未鉴权时所有 loopback 调用方共享 `anonymous` 作用域。
|
|
232
|
+
|
|
233
|
+
### 默认安全姿态
|
|
234
|
+
|
|
235
|
+
- Agent 默认在 `workspace-write` 沙箱内运行,审批策略为 `never`(无人值守时不会挂起等待 UI 审批)。
|
|
236
|
+
- Push Notification Webhook 默认拒绝 loopback / 私有地址(SSRF 防护),需要内网 Webhook 时显式设置 `push.allowPrivateTargets: true`。
|
|
237
|
+
- 请求体默认上限 8 MiB;单个 `raw` 附件默认上限 16 MiB。
|
|
238
|
+
- 公开卡与受保护端点分离:卡片可公开发现,其余端点一律鉴权。
|
|
239
|
+
|
|
240
|
+
> **重要**:暴露到公网前请务必配置 `auth`、`server.publicUrl`,并将 `agent.sandboxMode`/`agent.approvalPolicy` 调整到符合你信任模型的取值。A2A 调用方能够让 Agent 在其工作区内执行任务,这与给陌生人一个 Harness 会话等价。
|
|
241
|
+
>
|
|
242
|
+
> 强烈建议把 `agent.cwd` 指向一个**专用工作区**(而不是默认的家目录):`workspace-write` 的边界就是该目录。公网部署还应在反向代理层配置限流、请求体上限与 TLS。
|
|
243
|
+
|
|
244
|
+
### CORS
|
|
245
|
+
|
|
246
|
+
默认关闭(服务对服务场景不需要 CORS)。如需浏览器直连:
|
|
247
|
+
|
|
248
|
+
```yaml
|
|
249
|
+
server:
|
|
250
|
+
cors:
|
|
251
|
+
enabled: true
|
|
252
|
+
origins: ['https://app.example.com'] # 或 ['*'],但不能与 allowCredentials 同时使用
|
|
253
|
+
allowCredentials: false
|
|
254
|
+
maxAgeSeconds: 600
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Agent 运行时映射
|
|
260
|
+
|
|
261
|
+
### 会话与并发
|
|
262
|
+
|
|
263
|
+
- **一个 `contextId` = 一个 Harness Agent 会话**。同一 context 下的多个任务串行执行,保留对话历史;不同 context 并行执行。
|
|
264
|
+
- 创建 Agent 时会挂载 `agent.preset` 指定的 Agent 预设(默认取部署的默认预设),因此 A2A Agent 拥有与 GUI 会话一致的工具与提示词组合,再叠加下面的沙箱与审批策略。
|
|
265
|
+
- 空闲 context 在 `agent.contextIdleTtlMs`(默认 15 分钟)后被回收,Agent 会话随之销毁。
|
|
266
|
+
- 全局并发上限 `agent.maxConcurrentTasks`(默认 4);超出的任务最多等待 `agent.queueTimeoutMs`,仍无法开始时进入 `TASK_STATE_REJECTED`。
|
|
267
|
+
- 单回合上限 `agent.turnTimeoutMs`(默认 15 分钟);超时会取消该回合并返回 `TASK_STATE_FAILED`。
|
|
268
|
+
|
|
269
|
+
### 提示词与附件
|
|
270
|
+
|
|
271
|
+
- `text` 部分拼接为提示词;`data` 部分以 JSON 代码块注入;`url` 部分以引用形式注入。
|
|
272
|
+
- `raw` 文件部分写入 `<agent.cwd>/<files.directory>/<taskId>/`,并在提示词中给出绝对路径(文件名会被安全化、权限 `0600`)。超出 `limits.maxFileBytes` 将拒绝该请求。
|
|
273
|
+
- 入站媒体类型需在 `card.defaultInputModes` 中声明,否则返回 `ContentTypeNotSupportedError (-32005)`。
|
|
274
|
+
- 助手的最终文本回复作为 `artifactId = "response"` 的产物返回。流式模式下先按增量 `append` 推送,结束时用 `append=false + lastChunk=true` 提交一份权威完整文本(对重试与中断都安全)。
|
|
275
|
+
|
|
276
|
+
### 沙箱与审批
|
|
277
|
+
|
|
278
|
+
```yaml
|
|
279
|
+
agent:
|
|
280
|
+
sandboxMode: workspace-write # inherit | read-only | workspace-write | danger-full-access
|
|
281
|
+
approvalPolicy: never # inherit | ask | never
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`approvalPolicy: ask` 在无人值守场景下会导致工具调用等待审批而超时,除非该部署另有审批回答器;生产环境建议 `never` 或 `inherit` 配合外部审批集成。
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 配置参考
|
|
289
|
+
|
|
290
|
+
完整默认值见 [`lib/config.js`](lib/config.js)。所有字段均可在 bundle 行 `config` 中覆盖;未知字段会令插件激活失败(避免拼写错误静默生效)。
|
|
291
|
+
|
|
292
|
+
```yaml
|
|
293
|
+
enabled: true
|
|
294
|
+
|
|
295
|
+
server:
|
|
296
|
+
mountPath: /a2a
|
|
297
|
+
cardPath: /.well-known/agent-card.json
|
|
298
|
+
publicUrl: null # 对外可达的 origin;不填则由 WebServer 的绑定地址推导
|
|
299
|
+
trustProxy: false # 反向代理后设为 true,以使用 X-Forwarded-For
|
|
300
|
+
streaming: true
|
|
301
|
+
pushNotifications: true
|
|
302
|
+
extendedCard: false # 启用鉴权后才生效
|
|
303
|
+
cardCacheSeconds: 300
|
|
304
|
+
health: true
|
|
305
|
+
logRequests: false
|
|
306
|
+
cors: { enabled: false, origins: [], allowCredentials: false, maxAgeSeconds: 600 }
|
|
307
|
+
|
|
308
|
+
card:
|
|
309
|
+
name: DeepSeek Harness Agent
|
|
310
|
+
description: '...'
|
|
311
|
+
version: 1.0.0
|
|
312
|
+
provider: null # { organization, url }
|
|
313
|
+
documentationUrl: null
|
|
314
|
+
iconUrl: null
|
|
315
|
+
defaultInputModes: [text/plain, application/json, image/png, image/jpeg, image/webp]
|
|
316
|
+
defaultOutputModes: [text/plain, application/json]
|
|
317
|
+
skills:
|
|
318
|
+
- id: general
|
|
319
|
+
name: General-purpose coding assistant
|
|
320
|
+
description: '...'
|
|
321
|
+
tags: [general, coding, analysis, files, automation]
|
|
322
|
+
examples: ['...']
|
|
323
|
+
extensions: [] # [{ uri, description, required, params }]
|
|
324
|
+
extended:
|
|
325
|
+
description: null
|
|
326
|
+
documentationUrl: null
|
|
327
|
+
skills: []
|
|
328
|
+
|
|
329
|
+
auth:
|
|
330
|
+
mode: none # none | bearer | apiKey
|
|
331
|
+
tokens: []
|
|
332
|
+
clients: [] # [{ id, token }]
|
|
333
|
+
token: null
|
|
334
|
+
tokenEnv: null
|
|
335
|
+
headerName: x-api-key
|
|
336
|
+
allowRemoteUnauthenticated: false
|
|
337
|
+
realm: a2a
|
|
338
|
+
|
|
339
|
+
agent:
|
|
340
|
+
preset: null # Agent 预设;留空用部署默认预设(与 GUI 新会话一致)
|
|
341
|
+
provider: null # provider 与 model 必须同时设置或同时留空(留空用部署默认模型)
|
|
342
|
+
model: null
|
|
343
|
+
reasoningEffort: null
|
|
344
|
+
maxTokens: null
|
|
345
|
+
cwd: null # 绝对路径;留空用 workingDirectory.defaultDirectory
|
|
346
|
+
sandboxMode: workspace-write
|
|
347
|
+
approvalPolicy: never
|
|
348
|
+
maxConcurrentTasks: 4
|
|
349
|
+
queueTimeoutMs: 60000
|
|
350
|
+
turnTimeoutMs: 900000
|
|
351
|
+
contextIdleTtlMs: 900000
|
|
352
|
+
|
|
353
|
+
limits:
|
|
354
|
+
maxRequestBytes: 8388608
|
|
355
|
+
maxFileBytes: 16777216
|
|
356
|
+
maxPromptChars: 200000
|
|
357
|
+
maxArtifactChars: 200000
|
|
358
|
+
|
|
359
|
+
files:
|
|
360
|
+
enabled: true
|
|
361
|
+
directory: .a2a-inbox
|
|
362
|
+
retentionMs: 86400000
|
|
363
|
+
|
|
364
|
+
tasks:
|
|
365
|
+
ttlMs: 86400000
|
|
366
|
+
maxTasks: 2000
|
|
367
|
+
sweepIntervalMs: 60000
|
|
368
|
+
|
|
369
|
+
push:
|
|
370
|
+
allowPrivateTargets: false
|
|
371
|
+
timeoutMs: 5000
|
|
372
|
+
|
|
373
|
+
signing:
|
|
374
|
+
enabled: false
|
|
375
|
+
privateKeyPem: null # 或 privateKeyFile(二者只能选一个)
|
|
376
|
+
privateKeyFile: null
|
|
377
|
+
keyId: null
|
|
378
|
+
algorithm: ES256
|
|
379
|
+
jku: null
|
|
380
|
+
cacheSeconds: 300
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 签名 Agent Card 示例
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
signing:
|
|
387
|
+
enabled: true
|
|
388
|
+
privateKeyFile: /etc/dsh/a2a-signing-key.pem
|
|
389
|
+
keyId: dsh-a2a-2026
|
|
390
|
+
algorithm: ES256
|
|
391
|
+
jku: 'https://agent.example.com/.well-known/jwks.json'
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
签名覆盖 JCS 规范化后的卡片(不含 `signatures` 字段),可用任意符合规范的 A2A 客户端或 `@a2a-js/sdk` 的 `verifyAgentCardSignature()` 验证。
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## 运维
|
|
399
|
+
|
|
400
|
+
- **健康检查**:`GET /a2a/health`(受鉴权保护)返回协议版本、接口 URL、任务数、活跃 context、并发占用、鉴权模式、签名状态等。
|
|
401
|
+
- **保留策略**:终态任务在 `tasks.ttlMs` 后清理,超过 `tasks.maxTasks` 时按最旧终态任务淘汰;执行中的任务永不被清理。附件目录按 `files.retentionMs` 清理。
|
|
402
|
+
- **日志**:通过 Harness logger 输出前缀 `[a2a]`。`server.logRequests: true` 可记录每个请求的方法、路径、状态码与耗时。
|
|
403
|
+
- **优雅停止**:插件卸载时注销路由、取消进行中的回合并销毁所有 Agent 会话。
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## 已知限制
|
|
408
|
+
|
|
409
|
+
- **仅 A2A v1.0**:未实现 v0.3 兼容层;客户端必须发送 `A2A-Version: 1.0`。v0.3 客户端会收到 `VersionNotSupportedError`。
|
|
410
|
+
- **不暴露 gRPC 绑定**:需要 gRPC 的部署可通过官方 SDK 的 `grpcService` 自行扩展;当前插件只挂载 JSON-RPC 与 HTTP+JSON。
|
|
411
|
+
- **任务存储为进程内存储**:重启后进行中的任务与任务历史不会保留(A2A 任务 TTL 内有效)。生产持久化可替换 `lib/store.js` 中的 `A2ATaskStore` 为数据库实现。
|
|
412
|
+
- **无 `INPUT_REQUIRED` 交互式暂停**:Harness 回合是原子的,任务不会中途暂停等待 A2A 客户端补充输入;多轮通过同一 `contextId` 下的新任务实现。
|
|
413
|
+
- **Push Notification** 的 SSRF 防护只拦截字面量私有地址与常见内网域名,不做 DNS 解析期校验;高安全场景应叠加网络层出站策略。
|
|
414
|
+
- **A2A 会话会出现在 Harness 会话存储中**:每个 context 的 Harness 会话由其 id 标识(`session-a2a-*`),便于在 GUI 中审计;持久化清理依赖 DSH 的会话保留策略。
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## 开发与测试
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
npm install
|
|
422
|
+
npm test # node --test test/*.test.js
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
测试覆盖:配置校验、凭据解析、Agent Card 构建与 JWS 验签、任务存储分页/隔离/淘汰、鉴权、提示词与文件落盘、并发信号量、附件回收,以及基于真实 `@a2a-js/sdk` 传输层的端到端用例(JSON-RPC、SSE、REST、取消、订阅、Push、扩展、错误码、体积限制)。
|
|
426
|
+
|
|
427
|
+
目录结构:
|
|
428
|
+
|
|
429
|
+
```text
|
|
430
|
+
index.js Cordis 插件入口(apply / inject)
|
|
431
|
+
lib/config.js 配置默认值、校验、凭据解析
|
|
432
|
+
lib/card.js Agent Card 构建与 JWS 签名
|
|
433
|
+
lib/auth.js Bearer / API Key / loopback 鉴权
|
|
434
|
+
lib/server.js Express + 官方 SDK 传输层装配、路由、健康检查
|
|
435
|
+
lib/executor.js A2A AgentExecutor:任务生命周期、取消、并发
|
|
436
|
+
lib/bridge.js contextId → Harness Agent 会话、流式事件桥接
|
|
437
|
+
lib/prompt.js A2A Message → 提示词、附件落盘
|
|
438
|
+
lib/store.js 任务存储(隔离、分页、TTL/容量)与 Webhook 防 SSRF
|
|
439
|
+
lib/files.js 附件目录回收
|
|
440
|
+
lib/limits.js 信号量与超时工具
|
|
441
|
+
lib/util.js 通用工具(URL/常量时间比较/地址分类等)
|
|
442
|
+
client.js 浏览器半:设置 → 插件 → A2A 服务端(概览 + 配置表单)
|
|
443
|
+
lib/panel.js 面板配置 API(GUI 会话鉴权、校验、configEditor 写入)
|
|
444
|
+
lib/client-discovery.js 热升级时清除 client 半的负缓存
|
|
445
|
+
```
|