dsh-a2a-groups 0.0.0-stage → 0.1.2

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/docs/DEPLOY.md ADDED
@@ -0,0 +1,645 @@
1
+ # 部署到生产服务器
2
+
3
+ 这份文档讲**怎么把一套协作群系统跑起来**,从一台服务器、到一个部门、到全公司。
4
+
5
+ 面向两种人:**运维**(照着做就行)和**开发**(想知道为什么这么做)。
6
+
7
+ ---
8
+
9
+ ## 一、先明确三个角色
10
+
11
+ ```
12
+ ┌──────────────────────────────────────────────────────────────┐
13
+ │ ① AISIX 网关(可选,但生产必须) │
14
+ │ 作用:统一鉴权、限流、审计、卡片 URL 改写 │
15
+ │ 位置:一台能同时被宿主机和所有同事访问的机器 │
16
+ └──────────────────────────────────────────────────────────────┘
17
+ ↓
18
+ ┌──────────────────────────────────────────────────────────────┐
19
+ │ ② 群宿主 agent-groups(必须,一台就够) │
20
+ │ 作用:存群、存消息、对外提供 A2A 接口 │
21
+ │ 位置:一台常开的服务器,不需要 GPU、不需要大模型 │
22
+ └──────────────────────────────────────────────────────────────┘
23
+ ↓
24
+ ┌──────────────────────────────────────────────────────────────┐
25
+ │ ③ 成员 DSH 实例(每人一个,跑在自己电脑上) │
26
+ │ 作用:同事的日常工作界面,加群、发言、干活 │
27
+ │ 位置:同事的笔记本/WSL │
28
+ └──────────────────────────────────────────────────────────────┘
29
+ ```
30
+
31
+ **关键认知**:
32
+
33
+ - 群宿主**不是** DSH 实例。它是一个独立的 Node.js 程序,**不加载大模型、不消耗 Token**。
34
+ - 群宿主**没有会话**。它是服务提供者,不是参与者。
35
+ - 一台群宿主可以承载**所有**群;不需要每个群单独部署。
36
+
37
+ ### 网关里的两个命名空间(最容易搞混的一处)
38
+
39
+ ```
40
+ agent-group-host 群宿主:一个入口,承载所有群
41
+ agent-person-<谁> 成员实例:一个人一个入口
42
+ ```
43
+
44
+ **为什么群能共用一个入口,人不能?**
45
+
46
+ | | 群 | 人 |
47
+ |---|---|---|
48
+ | 本质 | 一份**数据**(群状态存在宿主里) | 一个**进程**(DSH 跑在各人机器上) |
49
+ | 怎么区分 | 报文里的 `data.groupId` | URL 里的 agent 名 |
50
+ | 能否共入口 | **能**,数据可以共存于一个进程 | **不能**,一条路径只能转发到一个地址 |
51
+
52
+ 所以「加一个群」永远不需要改网关——URL 没变,只是 `groups.json` 里多一条记录。
53
+ 而「加一个人」必须改网关,因为那是一个新的地址要转发。
54
+
55
+ 这也意味着:**群宿主的名字起错了会非常难查**。如果宿主和成员都叫 `agent-group-*`,出问题时
56
+ 你看到一条 403 或超时,无法从名字判断是"群找不到"还是"某个人联系不上"。
57
+
58
+ ---
59
+
60
+ ## 二、服务器要求
61
+
62
+ | 项 | 最低 | 建议 |
63
+ |---|---|---|
64
+ | CPU | 1 核 | 2 核 |
65
+ | 内存 | 512 MB | 1 GB |
66
+ | 磁盘 | 1 GB | 20 GB(消息历史会增长) |
67
+ | Node.js | **20 或以上** | 22 LTS |
68
+ | 操作系统 | Linux(systemd) | Ubuntu 22.04 / Debian 12 |
69
+
70
+ **不需要**:数据库、Redis、Nginx(除非要 TLS)、Docker、GPU。
71
+
72
+ 群宿主是单进程、单文件存储。所有群数据在一个 `groups.json` 里。
73
+
74
+ ---
75
+
76
+ ## 三、安装(5 分钟)
77
+
78
+ ### 3.1 准备目录
79
+
80
+ ```bash
81
+ sudo useradd --system --home /var/lib/agent-groups --shell /usr/sbin/nologin agent-groups
82
+ sudo mkdir -p /var/lib/agent-groups /opt/agent-groups
83
+ sudo chown -R agent-groups:agent-groups /var/lib/agent-groups
84
+ ```
85
+
86
+ **为什么单独建用户**:`groups.json` 里有全部群消息,权限要收紧到只有服务自己能读。
87
+
88
+ ### 3.2 放代码
89
+
90
+ 从 Git 拉取(推荐,方便升级):
91
+
92
+ ```bash
93
+ sudo -u agent-groups git clone <你的仓库地址> /opt/agent-groups
94
+ cd /opt/agent-groups/dsh-a2a
95
+ sudo -u agent-groups npm install --omit=dev
96
+ ```
97
+
98
+ **没有外部依赖**。`npm install` 只装开发用的测试工具;运行本身只需要 Node.js 标准库。
99
+
100
+ 如果服务器不能访问外网,直接把整个 `dsh-a2a/` 目录 `scp` 上去即可,**不用 npm install**。
101
+
102
+ ### 3.3 生成访问令牌
103
+
104
+ ```bash
105
+ openssl rand -hex 32
106
+ # 例如:a3f8c2e1b9d4...(64 个字符)
107
+ ```
108
+
109
+ 这个令牌是**成员访问群宿主的凭证**。所有人共用一个(它是服务级别的,不是个人级别的)。
110
+
111
+ > **想按人区分?** 那要在 AISIX 网关层做(见第五节),群宿主不重复实现。
112
+
113
+ ---
114
+
115
+ ## 四、启动
116
+
117
+ ### 4.1 先手动跑一次,确认能起来
118
+
119
+ ```bash
120
+ sudo -u agent-groups AGENT_GROUPS_TOKEN='<你的令牌>' \
121
+ node /opt/agent-groups/dsh-a2a/bin/agent-groups.js \
122
+ --port 3095 \
123
+ --host 127.0.0.1 \
124
+ --data /var/lib/agent-groups \
125
+ --gateway http://<网关地址>:3000/a2a \
126
+ --agent agent-groups
127
+ ```
128
+
129
+ 应该看到:
130
+
131
+ ```
132
+ 2026-01-01T00:00:00.000Z info agent-groups 0.1.0 listening on 127.0.0.1:3095 (data: /var/lib/agent-groups)
133
+ 2026-01-01T00:00:00.000Z info agent card: http://127.0.0.1:3095/a2a/.well-known/agent-card.json
134
+ 2026-01-01T00:00:00.000Z info 0 group(s) hosted
135
+ 2026-01-01T00:00:00.000Z info invite links will point at http://<网关>/a2a/agent-group-host
136
+ 2026-01-01T00:00:00.000Z info AISIX entry (one per host, not per group) — resources.yaml:
137
+ 2026-01-01T00:00:00.000Z info - name: agent-group-host
138
+ 2026-01-01T00:00:00.000Z info url: http://127.0.0.1:3095/a2a
139
+ ```
140
+
141
+ **启动日志里已经有 AISIX 配置片段**,直接抄进网关配置即可。
142
+
143
+ 验证:
144
+
145
+ ```bash
146
+ curl -s http://127.0.0.1:3095/a2a/.well-known/agent-card.json | head -c 300
147
+ ```
148
+
149
+ ### 4.2 用 systemd 常驻
150
+
151
+ `/etc/systemd/system/agent-groups.service`:
152
+
153
+ ```ini
154
+ [Unit]
155
+ Description=DSH collaboration group host
156
+ Documentation=https://<你的文档地址>
157
+ After=network-online.target
158
+ Wants=network-online.target
159
+
160
+ [Service]
161
+ Type=simple
162
+ User=agent-groups
163
+ Group=agent-groups
164
+ WorkingDirectory=/opt/agent-groups/dsh-a2a
165
+ Environment=AGENT_GROUPS_TOKEN=<你的令牌>
166
+ Environment=AGENT_GROUPS_PORT=3095
167
+ Environment=AGENT_GROUPS_HOST=127.0.0.1
168
+ Environment=AGENT_GROUPS_DATA=/var/lib/agent-groups
169
+ Environment=AGENT_GROUPS_GATEWAY=http://<网关地址>:3000/a2a
170
+ Environment=AGENT_GROUPS_AGENT=agent-groups
171
+ ExecStart=/usr/bin/node /opt/agent-groups/dsh-a2a/bin/agent-groups.js
172
+ Restart=always
173
+ RestartSec=3
174
+
175
+ # 收紧权限:服务只需要读写自己的数据目录
176
+ NoNewPrivileges=yes
177
+ PrivateTmp=yes
178
+ ProtectSystem=strict
179
+ ProtectHome=yes
180
+ ReadWritePaths=/var/lib/agent-groups
181
+
182
+ # 日志进 journald
183
+ StandardOutput=journal
184
+ StandardError=journal
185
+
186
+ [Install]
187
+ WantedBy=multi-user.target
188
+ ```
189
+
190
+ 启用:
191
+
192
+ ```bash
193
+ sudo systemctl daemon-reload
194
+ sudo systemctl enable --now agent-groups
195
+ sudo systemctl status agent-groups
196
+ sudo journalctl -u agent-groups -f # 看日志
197
+ ```
198
+
199
+ **为什么 `Restart=always`**:群宿主是无状态的(数据在文件里),崩溃重启不丢数据。让它自己重启比等人发现好。
200
+
201
+ **为什么 `127.0.0.1`**:生产上群宿主不直接对外,由 AISIX 或反向代理转发。直接暴露 3095 会让令牌成为唯一防线。
202
+
203
+ > **单机试用**:如果只有一台机器、没有网关,把 `AGENT_GROUPS_HOST` 改成 `0.0.0.0`,
204
+ > 成员直接用 `http://<机器IP>:3095/a2a` 访问。此时**务必**设令牌,且建议加防火墙白名单。
205
+
206
+ ---
207
+
208
+ ## 五、配置 AISIX 网关
209
+
210
+ ### 5.1 注册群宿主
211
+
212
+ 编辑 AISIX 的 `resources.yaml`:
213
+
214
+ ```yaml
215
+ agents:
216
+ - name: agent-group-host # 这个名字要和各人 DSH 里配的 agentName 一致
217
+ url: http://127.0.0.1:3095/a2a
218
+ # 或者宿主机在另一台机器上:
219
+ # url: http://10.0.0.5:3095/a2a
220
+ ```
221
+
222
+ **一台宿主机只加一条**。新建群**不需要**改这里——这是整个设计的核心承诺之一。
223
+
224
+ ### 5.2 配置访问密钥
225
+
226
+ AISIX 的 `config.yaml` 里给每个同事发一个 key:
227
+
228
+ ```yaml
229
+ api_keys:
230
+ - key: <每人一个的密钥>
231
+ allowed_agents:
232
+ - agent-groups
233
+ # 想限制某人只能访问某些群?AISIX 是按 agent 授权的,
234
+ # 而所有群都在同一个 agent 下,所以群级别的隔离靠"群主审批",
235
+ # 不靠网关 ACL。
236
+ ```
237
+
238
+ **为什么不在网关做群级隔离**:所有群共享一个 A2A 端点(`/a2a/agent-group-host`),
239
+ 群是请求体里的参数。网关看到的是"访问 agent-groups",无法区分群。
240
+ 群级权限由群宿主自己的 `memberKey` 校验保证——那是更强的一道门。
241
+
242
+ ### 5.3 常见网关坑(都踩过)
243
+
244
+ | 现象 | 原因 | 解决 |
245
+ |---|---|---|
246
+ | 网关调用群宿主卡 ~100 秒后 499 | 网关和宿主不在同一网络命名空间(如 WSL→Windows),reqwest 连接被吞 | 让网关和宿主**同机**,或宿主 URL 用 `127.0.0.1` |
247
+ | 卡片里 URL 是 `https://` 但实际是 http | AISIX 默认假定 https | 成员侧配 `client.forwardProto: http`,或网关前置真 TLS |
248
+ | 401 | 令牌不一致 | 宿主 `AGENT_GROUPS_TOKEN` 与成员侧 `client.apiKey` 必须相同 |
249
+ | 403 | `allowed_agents` 没含 `agent-groups` | 补上 |
250
+
251
+ ---
252
+
253
+ ## 六、成员侧配置(每个同事)
254
+
255
+ 在 DSH 的插件设置里填三个值:
256
+
257
+ | 配置项 | 值 | 说明 |
258
+ |---|---|---|
259
+ | `groups.gatewayBase` | `http://<网关>:3000/a2a` | 网关基址 |
260
+ | `groups.agentName` | `agent-group-host` | 与网关 `resources.yaml` 里的 name 一致 |
261
+ | `client.apiKey` | 网关分配的密钥 | 每人一个 |
262
+
263
+ **`groups.embedded` 保持 `false`**(默认)。这表示"我不自己建群,群在共享宿主上"。
264
+
265
+ > 只有单机自娱自乐才把它设成 `true`,此时群存在本机。
266
+
267
+ 配好后在 DSH 里说:
268
+
269
+ ```
270
+ 看看有哪些协作群
271
+ ```
272
+
273
+ 助手会调 `a2a_group_directory`,列出共享宿主上的群。
274
+
275
+ ---
276
+
277
+ ## 六之二、成员侧:用 MCP 接入(不需要 DSH 的同事)
278
+
279
+ 有的同事用 CodeBuddy、Claude Code、Cursor,没装 DSH。他们也能进群,**服务器侧不用做任何改动** —— 不需要
280
+ 新网关注册、不需要新宿主机、不需要重启宿主。
281
+
282
+ ### 为什么不用改服务器
283
+
284
+ MCP 客户端走的是**同一个网关同一条路**:
285
+
286
+ ```
287
+ MCP 客户端 → 网关 /a2a/agent-group-host → 群宿主
288
+ DSH 成员 → 网关 /a2a/agent-group-host → 群宿主
289
+ ```
290
+
291
+ 差别只在最后一步:宿主的响应被谁消费。所以加人零运维。
292
+
293
+ ### 客户端配置
294
+
295
+ ```json
296
+ {
297
+ "mcpServers": {
298
+ "agent-groups": {
299
+ "command": "node",
300
+ "args": ["/opt/dsh-a2a/dsh-a2a/lib/mcp-server.js"],
301
+ "env": {
302
+ "AGENT_GROUPS_GATEWAY": "http://gw.internal:3000/a2a",
303
+ "AGENT_GROUPS_AGENT": "agent-group-host",
304
+ "AGENT_GROUPS_TOKEN": "sk-...",
305
+ "AGENT_GROUPS_DATA": "/home/zhangsan/.dsh-a2a-mcp"
306
+ }
307
+ }
308
+ }
309
+ }
310
+ ```
311
+
312
+ | 变量 | 填什么 | 常见错误 |
313
+ |---|---|---|
314
+ | `AGENT_GROUPS_GATEWAY` | 网关 base,**含 `/a2a`** | 漏 `/a2a` → 404 |
315
+ | `AGENT_GROUPS_AGENT` | 群宿主的网关注册名 | 填成成员名 |
316
+ | `AGENT_GROUPS_TOKEN` | **网关调用方密钥**(`sk-...`) | 填成宿主令牌 → 401 |
317
+ | `AGENT_GROUPS_DATA` | 该人的私有目录,**不要共用** | 多人共用一个目录会串号 |
318
+
319
+ > **`AGENT_GROUPS_TOKEN` 是最容易填错的一项。** 它是网关的 API key,不是宿主的 `AGENT_GROUPS_TOKEN`。
320
+ > 两个变量同名纯属历史原因,含义完全不同。
321
+
322
+ ### MCP 客户端收不到推送 —— 这点必须告诉同事
323
+
324
+ MCP 没有能让模型被唤醒的推送。**同事必须明确要求助手去查**:
325
+
326
+ ```bash
327
+ codebuddy -p "先调用 a2a_pending 看有没有协作群消息,有未读先处理。然后:<任务>"
328
+ ```
329
+
330
+ 不这么说,模型不会主动查,群消息就一直躺着。
331
+
332
+ **这是协议和客户端的限制,不是部署问题。** 详见 [MCP.md](MCP.md) 第四节。
333
+
334
+ ### 验收
335
+
336
+ ```bash
337
+ # 协议层:26 个工具
338
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' \
339
+ | node lib/mcp-server.js 2>/dev/null | head -1
340
+ ```
341
+
342
+ 有 `serverInfo.name = "agent-groups"` 就通了。
343
+
344
+ ---
345
+
346
+ ## 七、备份与恢复
347
+
348
+ ### 7.1 备份什么
349
+
350
+ **只有一样东西**:`/var/lib/agent-groups/groups.json`
351
+
352
+ 这里面是:
353
+ - 所有群的定义
354
+ - 所有成员的哈希凭证(明文不存)
355
+ - 所有消息历史
356
+
357
+ **没有任何其他东西能重建它**。丢了就是全丢。
358
+
359
+ ### 7.2 怎么备份
360
+
361
+ ```bash
362
+ # 每天 3 点备份,保留 30 天
363
+ sudo tee /etc/cron.daily/agent-groups-backup >/dev/null <<'EOF'
364
+ #!/bin/sh
365
+ set -e
366
+ DIR=/var/backups/agent-groups
367
+ mkdir -p "$DIR"
368
+ STAMP=$(date +%Y%m%d-%H%M%S)
369
+ # 强制落盘再复制:服务可能在内存里有还没写入的改动。
370
+ # 这个端点需要令牌,且必须用 POST(GET 只读,不会触发落盘)。
371
+ curl -sf -X POST http://127.0.0.1:3095/__health \
372
+ -H "authorization: Bearer $AGENT_GROUPS_TOKEN" >/dev/null || true
373
+ cp /var/lib/agent-groups/groups.json "$DIR/groups-$STAMP.json"
374
+ find "$DIR" -name 'groups-*.json' -mtime +30 -delete
375
+ EOF
376
+ sudo chmod +x /etc/cron.daily/agent-groups-backup
377
+ ```
378
+
379
+ > `groups.json` 每次改动都是**原子写**(写临时文件再 rename),所以任何时刻读到的
380
+ > 都是完整的一份,不会读到半个 JSON。
381
+
382
+ ### 7.3 恢复
383
+
384
+ ```bash
385
+ sudo systemctl stop agent-groups
386
+ sudo cp /var/backups/agent-groups/groups-20260101-030000.json /var/lib/agent-groups/groups.json
387
+ sudo chown agent-groups:agent-groups /var/lib/agent-groups/groups.json
388
+ sudo chmod 600 /var/lib/agent-groups/groups.json
389
+ sudo systemctl start agent-groups
390
+ ```
391
+
392
+ **恢复后成员不用重新加群**——凭证都在文件里,成员本机也存着。只有换了宿主机地址才需要重新邀请。
393
+
394
+ ---
395
+
396
+ ## 八、升级
397
+
398
+ ```bash
399
+ cd /opt/agent-groups
400
+ sudo -u agent-groups git pull
401
+ # 有依赖变更时:
402
+ # sudo -u agent-groups npm install --omit=dev
403
+ sudo systemctl restart agent-groups
404
+ ```
405
+
406
+ **升级前先备份** `groups.json`。数据结构有版本号,老文件会被拒绝加载(而不是猜测转换),
407
+ 遇到这种情况看日志,按提示处理。
408
+
409
+ 查看版本:
410
+
411
+ ```bash
412
+ node /opt/agent-groups/dsh-a2a/bin/agent-groups.js --version
413
+ ```
414
+
415
+ ---
416
+
417
+ ## 九、监控
418
+
419
+ ### 9.1 健康检查
420
+
421
+ ```bash
422
+ curl -sf http://127.0.0.1:3095/a2a/.well-known/agent-card.json >/dev/null && echo OK
423
+ ```
424
+
425
+ 放进任何监控系统都行(Zabbix/Prometheus blackbox/Uptime Kuma)。
426
+
427
+ ### 9.2 关键日志
428
+
429
+ ```bash
430
+ sudo journalctl -u agent-groups --since '1 hour ago' | grep -E 'warn|error'
431
+ ```
432
+
433
+ 值得关注的:
434
+
435
+ | 日志 | 含义 | 处理 |
436
+ |---|---|---|
437
+ | `no token set` | 没配令牌 | 立刻配,否则任何能连上的人都是可信的 |
438
+ | `no --gateway set` | 生成不了邀请链接 | 配 `--gateway` |
439
+ | `cannot use --data` | 数据目录不可写 | 检查属主和权限 |
440
+ | `could not persist` | 落盘失败 | 检查磁盘空间 |
441
+
442
+ ### 9.3 容量
443
+
444
+ ```bash
445
+ ls -lh /var/lib/agent-groups/groups.json
446
+ ```
447
+
448
+ 估算法:**1 条消息约 200 字节**。10 个群 × 每天 50 条 = 每天 100 KB。一年约 36 MB。
449
+
450
+ 超过 1 GB 说明该做归档了(未来功能:按 `retentionDays` 自动清理)。
451
+
452
+ ---
453
+
454
+ ## 十、安全要点
455
+
456
+ | 项 | 做法 |
457
+ |---|---|
458
+ | 传输加密 | 群宿主只听 127.0.0.1,对外由网关或 Nginx 提供 TLS |
459
+ | 凭证存储 | `groups.json` 只存 sha256/scrypt 哈希,`chmod 600` |
460
+ | 成员凭证 | 明文存在**成员自己电脑**的 `memberships.json`(`chmod 600`),必须如此才能出示 |
461
+ | 群密码 | **已取消**。入群靠邀请令牌(可撤销)或群主审批 |
462
+ | 邀请链接 | 谁拿到谁能用,**没有次数限制**;泄漏了让群主撤销那一条 |
463
+ | 审批路径 | 申请人拿 `claimKey` 领取,群主只决定"同意/拒绝",不接触凭证 |
464
+ | 群列表隔离 | `unlisted` 的群**完全不出现在目录**,不是"标了隐藏" |
465
+
466
+ ### 撤销一条泄漏的邀请
467
+
468
+ 在任意会话里(群主身份):
469
+
470
+ ```bash
471
+ curl -X POST http://127.0.0.1:3095/a2a \
472
+ -H 'authorization: Bearer <令牌>' \
473
+ -H 'content-type: application/json' \
474
+ -H 'a2a-version: 1.0' \
475
+ -d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{
476
+ "role":"user","messageId":"m1","parts":[{"kind":"text","text":"revoke"}],
477
+ "metadata":{"skill":"group/revoke-invite","data":{
478
+ "groupId":"g-xxxx","memberId":"m-yyyy","memberKey":"<群主凭证>",
479
+ "inviteId":"i-zzzz"}}}}}'
480
+ ```
481
+
482
+ 或在 DSH 里让助手做。**撤销立刻生效**,已经进群的人不受影响(要移除他们需要 `group/remove`,尚未实现)。
483
+
484
+ ---
485
+
486
+ ## 十一、高可用(可选)
487
+
488
+ **单点故障**:群宿主进程挂了 → 所有群的发言/读取中断。
489
+
490
+ **但影响有限**:
491
+ - 已经进群的成员**本机存着凭证和历史**,能读能看
492
+ - 只是发新消息会失败,重试即可
493
+ - `Restart=always` 通常 3 秒恢复
494
+
495
+ **真的需要高可用**时的做法:多个群宿主实例共享同一个 `groups.json`,前面用负载均衡。
496
+ **但这有并发写风险** —— 当前设计假设**单写者**。
497
+
498
+ 如果要上多实例,需要:
499
+ 1. 把 `groups.json` 换成数据库(Postgres)
500
+ 2. 或者按群分片(群 A 在宿主 1,群 B 在宿主 2)
501
+
502
+ **当前建议**:单实例 + `Restart=always` + 每日备份。对几百人的部门足够。
503
+
504
+ ---
505
+
506
+ ## 十二、部署后验收清单
507
+
508
+ 按顺序打勾,全部通过才算部署成功。
509
+
510
+ ### 宿主机上
511
+
512
+ - [ ] `systemctl status agent-groups` 是 `active (running)`
513
+ - [ ] `curl http://127.0.0.1:3095/a2a/.well-known/agent-card.json` 返回 200
514
+ - [ ] 卡片里的 `protocolVersion` 是 `1.0`
515
+ - [ ] `groups.json` 属主是 `agent-groups`,权限 `600`
516
+ - [ ] `journalctl -u agent-groups` 里没有 `warn`(除了启动时的 token/gateway 提示)
517
+ - [ ] 重启一次 `systemctl restart`,数据和群都还在
518
+
519
+ ### 网关
520
+
521
+ - [ ] `resources.yaml` 里有 `agent-groups` 条目
522
+ - [ ] 通过网关访问卡片能拿到 200(带 key)
523
+ - [ ] 不带 key 访问返回 401
524
+ - [ ] key 的 `allowed_agents` 含 `agent-groups`
525
+
526
+ ### 成员侧
527
+
528
+ - [ ] 能列出目录(`a2a_group_directory`)
529
+ - [ ] 能建群(`a2a_group_create`),返回 `groupId` 和 `ownerKey`
530
+ - [ ] 能生成邀请(`a2a_group_invite`),提示词里**没有**"群密码"
531
+ - [ ] 第二个 DSH 实例能用链接入群(**只要链接,不要密码**)
532
+ - [ ] 两个实例能互相发消息、收到
533
+ - [ ] 侧边栏「协作群」面板能看到群名、人数、消息
534
+
535
+ ### 安全
536
+
537
+ - [ ] 错误的邀请令牌被拒绝
538
+ - [ ] 撤销的邀请立刻失效
539
+ - [ ] `unlisted` 的群不出现在目录里
540
+ - [ ] 目录里看不到成员姓名和消息内容
541
+
542
+ ---
543
+
544
+ ## 十三、故障排查
545
+
546
+ ### 群宿主起不来
547
+
548
+ ```bash
549
+ sudo journalctl -u agent-groups -n 50 --no-pager
550
+ ```
551
+
552
+ 常见:
553
+
554
+ | 报错 | 原因 | 解决 |
555
+ |---|---|---|
556
+ | `EADDRINUSE` | 端口被占 | `ss -tlnp \| grep 3095` 找到占用者 |
557
+ | `cannot use --data` | 目录无权限 | `chown agent-groups:agent-groups` |
558
+ | `memberships.json is from an older version` | 数据结构升级 | 按日志提示处理(旧文件保留不动) |
559
+
560
+ ### 成员连不上群宿主
561
+
562
+ ```bash
563
+ # 从成员机器上测
564
+ curl -v http://<网关>:3000/a2a/.well-known/agent-card.json
565
+ ```
566
+
567
+ - 连接超时 → 网络/防火墙
568
+ - 401 → 密钥不对
569
+ - 403 → `allowed_agents` 没配
570
+ - 200 但调用失败 → 看群宿主日志
571
+
572
+ ### 消息延迟或者收不到
573
+
574
+ 1. 检查群宿主日志有没有对应时间的请求
575
+ 2. 成员侧看 `a2a_group_inbox` 的状态
576
+ 3. 确认成员实例**开着**(关机的机器收不到实时消息,下次打开会补上)
577
+
578
+ ---
579
+
580
+ ## 十四、和旧部署的区别(给已经跑过旧版的人)
581
+
582
+ 旧版把群宿主跑成一个 **DSH 实例**(`dsh web --port 3095`,`skills.session: false`)。
583
+
584
+ 新版是**独立 Node 程序**。迁移:
585
+
586
+ 1. 停掉旧的 DSH 实例
587
+ 2. 数据目录不变(`groups.json` 兼容)
588
+ 3. 用新版命令启动:
589
+ ```bash
590
+ node bin/agent-groups.js --port 3095 --data <旧的数据目录> --gateway ... --agent ...
591
+ ```
592
+ 4. 成员侧**不用改配置**(网关地址和 agent 名不变)
593
+
594
+ **`memberships.json` 会重置**:旧版是"实例级",新版是"会话级",结构不兼容。
595
+ 成员需要重新加群(但群本身和消息都在,不会丢)。
596
+
597
+ ---
598
+
599
+ ## 附:一份最小可用的完整配置
600
+
601
+ **服务器上**(`/etc/systemd/system/agent-groups.service`):
602
+
603
+ ```ini
604
+ [Unit]
605
+ Description=DSH collaboration group host
606
+ After=network-online.target
607
+
608
+ [Service]
609
+ User=agent-groups
610
+ Environment=AGENT_GROUPS_TOKEN=换成你的64位随机串
611
+ Environment=AGENT_GROUPS_DATA=/var/lib/agent-groups
612
+ Environment=AGENT_GROUPS_GATEWAY=http://127.0.0.1:3000/a2a
613
+ Environment=AGENT_GROUPS_AGENT=agent-groups
614
+ ExecStart=/usr/bin/node /opt/agent-groups/dsh-a2a/bin/agent-groups.js --port 3095
615
+ Restart=always
616
+ RestartSec=3
617
+
618
+ [Install]
619
+ WantedBy=multi-user.target
620
+ ```
621
+
622
+ **AISIX `resources.yaml`**:
623
+
624
+ ```yaml
625
+ agents:
626
+ - name: agent-group-host
627
+ url: http://127.0.0.1:3095/a2a
628
+ ```
629
+
630
+ **AISIX `config.yaml`**(每人一份 key):
631
+
632
+ ```yaml
633
+ api_keys:
634
+ - key: 同事A的密钥
635
+ allowed_agents: [agent-groups]
636
+ ```
637
+
638
+ **同事的 DSH 插件设置**:
639
+
640
+ ```
641
+ groups.gatewayBase = http://127.0.0.1:3000/a2a
642
+ groups.agentName = agent-groups
643
+ client.apiKey = 同事A的密钥
644
+ groups.embedded = false
645
+ ```