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/README-ui.md +350 -0
- package/README.md +160 -2
- package/bin/agent-groups.js +438 -0
- package/client.js +3782 -0
- package/cordis.patch.yml +17 -0
- package/docs/ARCHITECTURE.md +281 -0
- package/docs/BACKLOG.md +201 -0
- package/docs/CONTRACT.md +592 -0
- package/docs/DEPLOY.md +645 -0
- package/docs/DESIGN.md +390 -0
- package/docs/MCP.md +260 -0
- package/docs/PRODUCT.md +186 -0
- package/docs/RUNBOOK.md +406 -0
- package/lib/core/bridge.js +1167 -0
- package/lib/core/card.js +217 -0
- package/lib/core/colleague-cards.js +149 -0
- package/lib/core/config.js +235 -0
- package/lib/core/endpoint.js +69 -0
- package/lib/core/groups.js +3010 -0
- package/lib/core/invite.js +412 -0
- package/lib/core/memberships.js +366 -0
- package/lib/core/probe.js +219 -0
- package/lib/core/registry.js +344 -0
- package/lib/core/server.js +1304 -0
- package/lib/core/skills-group.js +1164 -0
- package/lib/core/skills-session.js +116 -0
- package/lib/core/skills.js +76 -0
- package/lib/core/wire.js +772 -0
- package/lib/dsh/deliver.js +601 -0
- package/lib/dsh/inbox.js +658 -0
- package/lib/dsh/profile.js +195 -0
- package/lib/dsh/prompt.js +55 -0
- package/lib/dsh/sessions.js +278 -0
- package/lib/dsh/toolkit.js +166 -0
- package/lib/dsh/tools.js +101 -0
- package/lib/index.js +648 -0
- package/lib/mcp/mcp-deps.js +145 -0
- package/lib/mcp-server.js +261 -0
- package/lib/shared/tool-runtime.js +1349 -0
- package/lib/shared/tool-specs.js +1123 -0
- package/lib/types/client.d.ts +144 -0
- package/package.json +84 -4
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# dsh-a2a bundle patch
|
|
2
|
+
#
|
|
3
|
+
# This is the bundle layer: `dsh.bundle.patch` in package.json points here, so
|
|
4
|
+
# an install through the plugin manager (or `dsh plugin add`) mounts the Host
|
|
5
|
+
# half by appending this row to the composed entry list.
|
|
6
|
+
#
|
|
7
|
+
# The row carries no config on purpose: every default is already the value the
|
|
8
|
+
# plugin ships with, so an upgrade never silently changes behaviour. Override
|
|
9
|
+
# anything you need in the profile's own cordis.patch.yml on the same `id`.
|
|
10
|
+
#
|
|
11
|
+
# The Host half needs `webServer` (to serve /a2a) and `sessionController` (to
|
|
12
|
+
# deliver an inbound A2A message into a Session). Both are declared by the
|
|
13
|
+
# plugin itself through `inject`, so a Host without them simply does not mount
|
|
14
|
+
# this row rather than failing at the first request.
|
|
15
|
+
- insert:
|
|
16
|
+
- id: a2a
|
|
17
|
+
name: 'dsh-a2a-groups'
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# 架构
|
|
2
|
+
|
|
3
|
+
## 一、全貌
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ ① 网关(AISIX,可选) │
|
|
8
|
+
│ /a2a/agent-group-host → 转发到群宿主 │
|
|
9
|
+
│ /a2a/agent-person-* → 转发到各人的 DSH(点对点时用) │
|
|
10
|
+
│ 职责:鉴权、限流、审计、卡片 URL 改写 │
|
|
11
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
12
|
+
↓ 出站
|
|
13
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
14
|
+
│ ② 群宿主(独立 Node 程序,一台) │
|
|
15
|
+
│ bin/agent-groups.js → lib/core/server.js │
|
|
16
|
+
│ 存 groups.json:群、成员、消息、persons、一对一消息 │
|
|
17
|
+
│ 不加载大模型、不消耗 Token │
|
|
18
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
19
|
+
↑ 出站订阅(SSE)
|
|
20
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
21
|
+
│ ③ 成员(每人一个 DSH 实例) │
|
|
22
|
+
│ lib/index.js → DSH 插件,或 lib/mcp-server.js → MCP 客户端 │
|
|
23
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**关键点**:成员的连接是**出站**的。成员主动连宿主订阅,宿主从不主动拨号给成员。
|
|
27
|
+
|
|
28
|
+
这决定了两件事:
|
|
29
|
+
|
|
30
|
+
- 成员不需要固定 IP、不需要在网关注册、不需要被访问
|
|
31
|
+
- 加人 **零运维**(不产生新的网关条目)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 二、目录边界:一句话判断文件该放哪
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
lib/
|
|
39
|
+
├── core/ 零 DSH 依赖,任何宿主可复用 15 个文件
|
|
40
|
+
├── dsh/ 唯一读 ctx 的地方 7 个文件
|
|
41
|
+
├── shared/ 两个宿主共用,谁也不属于 2 个文件
|
|
42
|
+
├── mcp/ 唯一读 MCP 协议的地方 1 个文件
|
|
43
|
+
├── index.js DSH 插件入口(包 main)
|
|
44
|
+
└── mcp-server.js MCP 入口(bin: agent-groups-mcp)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| 目录 | 判据 | 文件 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `core/` | 不 import `ctx` | groups, memberships, invite, card, wire, server, bridge, registry, skills*, probe, config, endpoint, colleague-cards |
|
|
50
|
+
| `dsh/` | 读 `ctx` | deliver, sessions, inbox, tools, toolkit, profile, prompt |
|
|
51
|
+
| `shared/` | 纯数据 + 纯逻辑 | tool-specs, tool-runtime |
|
|
52
|
+
| `mcp/` | 为「没有 DSH 的进程」接线 | mcp-deps |
|
|
53
|
+
|
|
54
|
+
**这条边界可检查**:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
grep -rn '\bctx\b' lib/ --include='*.js' | grep -v '^lib/dsh/' | grep -v '^lib/index.js'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
除注释外只剩 `core/server.js` 的 **2 处**(`ctx` 解构参数与 `ctx?.webServer`),都是**可选注入** —— 有 `ctx` 就用,
|
|
61
|
+
没有就靠调用方传入。这正是同一份代码能同时服务插件和独立宿主的原因。
|
|
62
|
+
|
|
63
|
+
> 实测命令(2026-11-04):`grep -rn '\bctx\b' lib/core lib/shared lib/mcp --include='*.js' | grep -vE "^\S+: *(\*|//)"`
|
|
64
|
+
> → 2 行。之前这里写「3 处」是估的,写文档时按实际数了一遍才发现。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 三、核心模块
|
|
69
|
+
|
|
70
|
+
### `core/groups.js`(2834 行)—— 群状态机
|
|
71
|
+
|
|
72
|
+
**唯一的真相来源。** 所有群、成员、消息、persons 表、一对一消息都在这里。
|
|
73
|
+
|
|
74
|
+
导出 40+ 个函数,关键几组:
|
|
75
|
+
|
|
76
|
+
| 组 | 函数 |
|
|
77
|
+
|---|---|
|
|
78
|
+
| 群生命周期 | `create` `invite` `join` `leave` `requestJoin` `decideRequest` `claimJoin` |
|
|
79
|
+
| 群内 | `post` `read` `subscribe` `members` `snapshot` `setProfile` |
|
|
80
|
+
| 群管 | `removeMember` `renameGroup` `archiveGroup` |
|
|
81
|
+
| 人 | `personProfile` `setPersonProfile` `listPersons` `touch` |
|
|
82
|
+
| 一对一 | `sendPersonMessage` `listPersonMessages` `ackPersonMessages` `isPersonOnline` |
|
|
83
|
+
|
|
84
|
+
**设计要点**:
|
|
85
|
+
|
|
86
|
+
- 密码用 scrypt,令牌/密钥用 sha256,**落盘永不明文**
|
|
87
|
+
- `tokenHash` 在群记录里,明文令牌只在生成时返回一次
|
|
88
|
+
- `subscribe(groupId, fn)` 是内存订阅,进程重启后由成员重连恢复
|
|
89
|
+
|
|
90
|
+
### `core/memberships.js`(347 行)—— 成员账本
|
|
91
|
+
|
|
92
|
+
**每个 DSH 实例自己的账**(`memberships.json`),键是 `(sessionId, groupId)`。
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
{
|
|
96
|
+
sessionId, groupId, personId, personKey,
|
|
97
|
+
local, // 群是否托管在本机
|
|
98
|
+
baseUrl, // 宿主地址(含 agent 名)
|
|
99
|
+
groupName, memberCount, lastSeq, // 缓存,用于离线时显示
|
|
100
|
+
members, // 名册缓存
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**为什么键是 `(sessionId, groupId)`**:一个 DSH 可以有多个会话,同一台机器上的两个会话可以是两个不同的成员。
|
|
105
|
+
|
|
106
|
+
### `core/server.js`(1240 行)—— A2A 1.0 服务端
|
|
107
|
+
|
|
108
|
+
- PascalCase 方法名、`oneof` payload、`TASK_STATE_*` 枚举
|
|
109
|
+
- SSE 流(`SendStreamingMessage`)+ 心跳
|
|
110
|
+
- 鉴权:Bearer token 或 loopback
|
|
111
|
+
|
|
112
|
+
**关键取舍**:`registrar` 注入而非硬读 `ctx.webServer`,见 §二。
|
|
113
|
+
|
|
114
|
+
### `core/bridge.js`(1079 行)—— A2A 客户端
|
|
115
|
+
|
|
116
|
+
- `discover()` 拿卡片并缓存
|
|
117
|
+
- `send()` 一元调用
|
|
118
|
+
- `stream()` SSE
|
|
119
|
+
- **卡片 url 与 baseUrl 冲突时用 baseUrl**(网关会把 url 写成 https,本地是 http)
|
|
120
|
+
|
|
121
|
+
### `core/skills-group.js`(1140 行)—— 宿主暴露的 22 个 skill
|
|
122
|
+
|
|
123
|
+
`group/*` 18 个 + `person/*` 4 个(`send` `profile` `inbox` `ack`),共 22 个。
|
|
124
|
+
|
|
125
|
+
**这是 A2A 层的 API**,和工具层同名但不同层:工具是「给人用的」,skill 是「给 A2A 调用方用的」。
|
|
126
|
+
|
|
127
|
+
### `shared/tool-specs.js`(1112 行)+ `shared/tool-runtime.js`(1296 行)
|
|
128
|
+
|
|
129
|
+
**26 个工具,两份文件,一份契约一次实现。**
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
tool-specs.js 纯数据:name / description / parameters / output.schema / render
|
|
133
|
+
tool-runtime.js 纯逻辑:createToolRuntime(deps).call(name, args, {sessionId})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**为什么拆开**:DSH 插件和 MCP server 需要**逐字节相同**的描述和 schema。分开写会漂移。
|
|
137
|
+
|
|
138
|
+
### `dsh/inbox.js`(622 行)—— 群消息订阅
|
|
139
|
+
|
|
140
|
+
- 每个群一条 SSE 订阅,断线重连带 `sinceSeq` 续传
|
|
141
|
+
- 心跳 15 秒,presence 刷新 50 秒
|
|
142
|
+
- 三档投递:`mention`/`notice` 唤醒,`ambient` 只进未读
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 四、两条数据流
|
|
147
|
+
|
|
148
|
+
### 群消息如何到达成员
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
成员 DSH 启动
|
|
152
|
+
↓ 出站
|
|
153
|
+
inbox 打开 SSE:bridge.stream → 网关 → 宿主 group/subscribe
|
|
154
|
+
↓
|
|
155
|
+
宿主保持连接,有新帖 emit('post')
|
|
156
|
+
↓
|
|
157
|
+
成员收到 → 按 mentions 分级 → 投递进会话
|
|
158
|
+
↓
|
|
159
|
+
连接断了自动重连,带 sinceSeq 补收
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 一对一消息如何送达
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
发送方 person/send
|
|
166
|
+
↓
|
|
167
|
+
宿主 sendPersonMessage → 存进 directMessages
|
|
168
|
+
↓ 返回 {dmId, online}
|
|
169
|
+
发送方看到「已邀请,对方在线/不在线」
|
|
170
|
+
↓
|
|
171
|
+
接收方上线,inbox 轮询 person/inbox(10 秒一次)
|
|
172
|
+
↓
|
|
173
|
+
投递进会话 → 自动 person/ack
|
|
174
|
+
↓
|
|
175
|
+
发送方的 deliveredAt 被标记
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**强确认的意义**:`deliveredAt` 由**接收方**写,不是发送方的 HTTP 返回。一个只证明「请求发出去了」的实现,会在对方没收到时显示「已送达」。
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 五、身份模型
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
personId 一个人。跨群、跨会话唯一。
|
|
186
|
+
├── group.members[] 里的行 群内身份(role / roleAbout / tags)
|
|
187
|
+
└── persons[] 里的行 全局画像(headline / tags)
|
|
188
|
+
|
|
189
|
+
personKey 每个群一份凭证。只在群内有效。
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**为什么不是同一个东西**:
|
|
193
|
+
- `role` 是**权限位**(能否发邀请)—— 客观
|
|
194
|
+
- `roleAbout`/`tags` 是**自我声明**(在这个群负责什么)—— 主观
|
|
195
|
+
- `headline` 是**跨群的自我描述**(我是谁)—— 名片数据源
|
|
196
|
+
|
|
197
|
+
混在一起会让「我是 PM」变成「我能踢人」。
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 六、两种宿主形态
|
|
202
|
+
|
|
203
|
+
| | DSH 插件 | MCP server |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| 入口 | `lib/index.js` | `lib/mcp-server.js` |
|
|
206
|
+
| 传输 | `ctx.webServer` / Connection RPC | stdio JSON-RPC |
|
|
207
|
+
| 工具来源 | `shared/tool-specs.js` | 同一份 |
|
|
208
|
+
| 界面 | 侧边栏 + 成员栏 + 名片 | 无 |
|
|
209
|
+
| 推送 | SSE 订阅群消息 | **不能**(协议限制) |
|
|
210
|
+
|
|
211
|
+
**共用 `shared/` 的代价与收益**:多一层间接,换来 26 个工具的契约只写一次。
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 七、面板写入通道(DSH 版特有)
|
|
216
|
+
|
|
217
|
+
面板渲染在任何会话之前,**拿不到成员凭证**。所以它不能直接调工具。
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
面板(浏览器)
|
|
221
|
+
↓ ctx.connection.rpc.call('/a2a-panel', 'profile', {...})
|
|
222
|
+
DSH 的 Connection 传输(同源、已鉴权)
|
|
223
|
+
↓
|
|
224
|
+
插件(Host 进程,持有全部凭证)
|
|
225
|
+
↓ groups.setProfile()
|
|
226
|
+
磁盘
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**为什么不把 personKey 发到页面**:DOM 对任何浏览器扩展、任何 XSS 都可见。凭证留在宿主进程,页面永远看不到。
|
|
230
|
+
|
|
231
|
+
**踩过的坑**(详见 `RUNBOOK.md`):
|
|
232
|
+
- `ctx.connection.rpc.handle` 从插件 fiber 调用会抛 `cannot get property "webServer" without inject`(上游 bug #8414),**必须用 `ctx.root`**
|
|
233
|
+
- 浏览器侧不是 `createWebConnectionRpc`,而是 `ctx.connection.rpc.call`
|
|
234
|
+
- `dsh.client.inject` 是**模块表**,不声明就 require 不到
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 八、测试策略
|
|
239
|
+
|
|
240
|
+
| 层 | 方式 | 数量 |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| 纯逻辑 | `node --test`,无宿主 | 427 |
|
|
243
|
+
| 集成 | 真实 HTTP `listen(0)` | 含在内 |
|
|
244
|
+
| 独立宿主 | `scripts/standalone-probe.mjs`,24 断言,全程无 DSH | — |
|
|
245
|
+
| MCP | `scripts/mcp-probe.mjs`,13 断言,spawn 真进程 | — |
|
|
246
|
+
|
|
247
|
+
**约定**:拒绝路径和成功路径都要测,并且断言拒绝**没有副作用**(没写文件、没投递)。
|
|
248
|
+
|
|
249
|
+
**测试覆盖不到的地方**(实际踩过):
|
|
250
|
+
|
|
251
|
+
- 工具只断言返回值,**不断言它不抛 ReferenceError** → 三个核心工具曾 100% 崩溃而测试全绿
|
|
252
|
+
- 断言面板文本,**不断言字段结构** → `from` 被降级成字符串,测试没发现
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 九、扩展点
|
|
257
|
+
|
|
258
|
+
| 想加什么 | 改哪里 |
|
|
259
|
+
|---|---|
|
|
260
|
+
| 新工具 | `shared/tool-specs.js` 加 spec + `tool-runtime.js` 加 handler |
|
|
261
|
+
| 新 A2A 能力 | `core/skills-group.js` 加 skill |
|
|
262
|
+
| 新群属性 | `core/groups.js` 的 `GroupRecord` + `sanitizeGroup` |
|
|
263
|
+
| 新宿主 | 实现 `core/server.js` 要的 deps,参考 `bin/agent-groups.js` |
|
|
264
|
+
| 新客户端 | 复用 `shared/tool-runtime.js`,参考 `mcp-server.js` 的 `createHostDepsFromEnv` |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 十、性能与限制
|
|
269
|
+
|
|
270
|
+
| 项 | 当前值 | 说明 |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| 群数量上限 | 1000 | `MAX_GROUPS` |
|
|
273
|
+
| 单群成员上限 | 500 | — |
|
|
274
|
+
| 消息保留 | 按 `retentionDays` 裁剪 | 默认不限 |
|
|
275
|
+
| 一对一消息 | 2000 条上限,先丢已送达的 | `MAX_DIRECT_MESSAGES` |
|
|
276
|
+
| SSE 心跳 | 15 秒 | 防中间层掐断 |
|
|
277
|
+
| presence 刷新 | 50 秒 | 必须 < `ONLINE_WINDOW_MS`(120 秒) |
|
|
278
|
+
|
|
279
|
+
**存储是单个 JSON 文件**(`groups.json`),原子写(写临时文件再 rename)。
|
|
280
|
+
|
|
281
|
+
当前规模够用。如果群数上千、消息百万级,需要换存储 —— 但那是另一个量级的问题。
|
package/docs/BACKLOG.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# 需求池
|
|
2
|
+
|
|
3
|
+
记录**已知但暂不实现**的需求。每条要写清:为什么需要、给谁用、以及为什么现在不做。
|
|
4
|
+
|
|
5
|
+
已实现的功能不在这里 —— 那些在 `DESIGN.md` 和 `CONTRACT.md` 里。这个文件只回答一个问题:
|
|
6
|
+
**「这件事我们想过了,但现在不做」**,以免同一个想法被反复提出又反复搁置。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. 群运营管理面板
|
|
11
|
+
|
|
12
|
+
**状态**:待设计,不实现
|
|
13
|
+
**提出时间**:2026-11-04
|
|
14
|
+
**优先级**:中(有真实需求,但不阻塞 MVP)
|
|
15
|
+
|
|
16
|
+
### 给谁用
|
|
17
|
+
|
|
18
|
+
**公司运营管理员**,不是群成员。这个区分决定了整个功能的设计。
|
|
19
|
+
|
|
20
|
+
### 要解决什么
|
|
21
|
+
|
|
22
|
+
一个公司里群会越来越多,管理员需要知道:
|
|
23
|
+
|
|
24
|
+
| 问题 | 为什么关心 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| 现在有多少个群? | 容量规划 |
|
|
27
|
+
| 哪些群还活着? | 找出废弃群,回收资源 |
|
|
28
|
+
| 每个群多少人? | 判断群是不是「建了没人来」 |
|
|
29
|
+
| 谁建的群?建了多少? | 异常检测(某人建 50 个群通常意味着误用) |
|
|
30
|
+
| 多久没有活动? | 归档建议 |
|
|
31
|
+
| 哪些群对外可见? | 合规审计 |
|
|
32
|
+
|
|
33
|
+
### 关键约束:只看治理指标,不看内容
|
|
34
|
+
|
|
35
|
+
**管理员不能看群里的消息内容。** 这不是技术限制,是设计决定:
|
|
36
|
+
|
|
37
|
+
- 群成员加入时预期的是「同事之间说话」,不是「被管理员审阅」
|
|
38
|
+
- 治理需要的是**指标**(多少人、多久没动),不是**内容**
|
|
39
|
+
- 企业治理的常规做法也是如此:审计日志记录「谁在什么时候做了什么」,不记录数据本身
|
|
40
|
+
|
|
41
|
+
**能看**:群名、主题、人数、创建时间、最后活动时间、创建者、可见性
|
|
42
|
+
**不能看**:消息正文、成员名单(除非他本来就是该群成员)、文件
|
|
43
|
+
|
|
44
|
+
### 数据来源
|
|
45
|
+
|
|
46
|
+
**几乎都有了**,不需要新存储:
|
|
47
|
+
|
|
48
|
+
| 指标 | 现有来源 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| 群列表、人数、最后活动 | `store.listDirectory()` 已返回 |
|
|
51
|
+
| 创建者 | `group.ownerPersonId` |
|
|
52
|
+
| 创建时间 | `group.createdAt` |
|
|
53
|
+
| 可见性、归档状态 | `group.settings.visibility` / `.archived` |
|
|
54
|
+
|
|
55
|
+
**缺少的只有一个**:跨群聚合(「某人建了多少群」)。这是 `state.groups` 的一次遍历,不需要新表。
|
|
56
|
+
|
|
57
|
+
### 为什么现在不做
|
|
58
|
+
|
|
59
|
+
1. **MVP 的场景是「同事协作」**,管理员视角是第二个场景,值得单独设计
|
|
60
|
+
2. 没有真实的管理员用户,设计出来一定偏离实际
|
|
61
|
+
3. 数据都齐了,什么时候做成本都一样
|
|
62
|
+
|
|
63
|
+
### 做的时候要注意
|
|
64
|
+
|
|
65
|
+
- 入口不能放在普通用户的左侧栏 —— 那会让人以为每个用户都能管
|
|
66
|
+
- 权限模型要先定:谁能当管理员?令牌?网关 ACL?
|
|
67
|
+
- 面板要能导出(管理员通常要贴到周报里)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 2. 跨会话的群可见性收敛
|
|
72
|
+
|
|
73
|
+
**状态**:设计问题,不实现
|
|
74
|
+
**提出时间**:2026-11-04
|
|
75
|
+
|
|
76
|
+
### 现状
|
|
77
|
+
|
|
78
|
+
群绑定到 Session(`membership.sessionId`),但**面板快照是全局注入的**:A 会话加入的群,它的摘要也会出现在 B 会话的页面 HTML 里。
|
|
79
|
+
|
|
80
|
+
### 为什么这是个问题
|
|
81
|
+
|
|
82
|
+
绑定到会话的意义就是「这个会话的群是这些」。既然是全局快照,绑定带来的隔离只体现在 UI 过滤上,数据仍然在 HTML 里。
|
|
83
|
+
|
|
84
|
+
### 为什么现在不做
|
|
85
|
+
|
|
86
|
+
要真隔离,得改注入机制:`webserver/index-inject` 阶段没有会话上下文,得改成按会话注入或者由前端带 sessionId 拉取。
|
|
87
|
+
|
|
88
|
+
**成本不小,收益在单用户单会话时不明显。** 等有人真的在多会话间切换、且群内容敏感度不同时再做。
|
|
89
|
+
|
|
90
|
+
### 做的时候要考虑
|
|
91
|
+
|
|
92
|
+
- 前端按 `sessionId` 过滤只是 UI 层,数据还在 HTML 里 —— 不算真隔离
|
|
93
|
+
- 真做法:`__DSH_A2A_GROUPS__` 变成「只含当前会话的群」,需要注入时知道是哪个会话
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 3. AI 组队:按同事画像推荐团队成员
|
|
98
|
+
|
|
99
|
+
**状态**:待设计,不实现
|
|
100
|
+
**提出时间**:2026-11-04
|
|
101
|
+
|
|
102
|
+
### 要解决什么
|
|
103
|
+
|
|
104
|
+
建群之后,「该拉谁进来」是个真实问题。「帮我找 2 个懂后端的人」比让用户自己翻名片列表自然得多。
|
|
105
|
+
|
|
106
|
+
### 需要的形态
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
用户:帮我找两个懂后端的人加入这个群
|
|
110
|
+
↓
|
|
111
|
+
AI 读同事名片(persons 表:headline + tags)
|
|
112
|
+
↓
|
|
113
|
+
推荐 3 个候选 + 理由
|
|
114
|
+
↓
|
|
115
|
+
用户确认 → 逐个 a2a_person_send 邀请
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 为什么现在不做
|
|
119
|
+
|
|
120
|
+
**名片的信息密度不够。** 现在每个名片只有:名字、一句话简介、几个标签。按标签匹配谁都会做,不需要 AI。
|
|
121
|
+
|
|
122
|
+
有价值的推荐需要更多信号:
|
|
123
|
+
|
|
124
|
+
- 他参与过哪些群、在里面扮演什么角色(历史)
|
|
125
|
+
- 他现在在几个群里(忙不忙)
|
|
126
|
+
- 他上次活跃是什么时候
|
|
127
|
+
|
|
128
|
+
**这些都是可以采集的,但需要先有真实的群活动数据。** 现在采集,得到的是测试数据。
|
|
129
|
+
|
|
130
|
+
### 两个设计选择留待决定
|
|
131
|
+
|
|
132
|
+
**推荐由谁做**
|
|
133
|
+
|
|
134
|
+
- **A**:skill 只返回候选数据,**会话里的模型**做判断(推荐)
|
|
135
|
+
- **B**:skill 内部调模型
|
|
136
|
+
|
|
137
|
+
A 更简单,符合「AI 通过名片推荐」的说法,也不需要给宿主接 LLM。
|
|
138
|
+
|
|
139
|
+
**入口在哪**
|
|
140
|
+
|
|
141
|
+
- 群里说「帮我找 X」(自然,但要模型理解)
|
|
142
|
+
- 面板按钮(明确,但要多做 UI)
|
|
143
|
+
|
|
144
|
+
### 做的时候要注意
|
|
145
|
+
|
|
146
|
+
- 推荐理由是给人看的,必须说清「为什么是他」(「李四在三个群做过对账」比「标签匹配」有说服力)
|
|
147
|
+
- 已经在群里的人不该出现在候选里
|
|
148
|
+
- 推荐是建议不是决定,**必须等人确认才发邀请**
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 4. 网关注册自助化
|
|
153
|
+
|
|
154
|
+
**状态**:受上游限制,不实现
|
|
155
|
+
**提出时间**:2026-11-04
|
|
156
|
+
|
|
157
|
+
### 现状
|
|
158
|
+
|
|
159
|
+
新成员加入不需要改网关(因为成员不注册,只出站连宿主)。但**新增一个群宿主**仍然要改 `resources.yaml` 并发 SIGHUP。
|
|
160
|
+
|
|
161
|
+
### 已经验证过的限制
|
|
162
|
+
|
|
163
|
+
AISIX 的 admin API **是只读的**:
|
|
164
|
+
- `GET /admin/v1/a2a_agents` → 200(能读全部注册)
|
|
165
|
+
- `POST/PUT/PATCH/DELETE` 同一路径 → **405 Method Not Allowed**
|
|
166
|
+
- `POST /admin/v1/reload` 之类的 → 404
|
|
167
|
+
|
|
168
|
+
所以「让同事自己注册」在 AISIX 上做不到,不是配置问题,是它没实现写端点。
|
|
169
|
+
|
|
170
|
+
### 可能的出路
|
|
171
|
+
|
|
172
|
+
1. **换网关**,或改 AISIX 源码加写端点 —— 成本高
|
|
173
|
+
2. **接受人工步骤**:改 YAML + SIGHUP(热重载,不重启容器,实测 `Up 3 hours, restarts=0`)
|
|
174
|
+
3. **重新审视是否真的需要更多宿主** —— 一台宿主承载所有群,第二台宿主只有在「跨部门隔离」或「异地容灾」时才需要
|
|
175
|
+
|
|
176
|
+
### 为什么现在不做
|
|
177
|
+
|
|
178
|
+
**当前只有一台宿主,这个痛点不存在。** 等真有第二台时再解决,那时也更清楚约束条件。
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 5. 一对一消息的持续会话
|
|
183
|
+
|
|
184
|
+
**状态**:明确不做(当前范围外)
|
|
185
|
+
**提出时间**:2026-11-04
|
|
186
|
+
|
|
187
|
+
### 现在的设计
|
|
188
|
+
|
|
189
|
+
一对一消息只有三种类型:`group-invite`、`join-notice`、`welcome`。它是**门铃**,不是聊天。
|
|
190
|
+
|
|
191
|
+
### 为什么不做成聊天
|
|
192
|
+
|
|
193
|
+
用户的原话是「第一次的消息只做拉群的邀请,入群的提示就行,不需要做持续私聊,私聊请切换到建群拉人模式」。
|
|
194
|
+
|
|
195
|
+
这个判断的理由很实在:**群消息有第三个人看到上下文,有历史,@ 有语义,关机也能补收。** 私聊这些全丢。
|
|
196
|
+
|
|
197
|
+
技术上通道已经在了(`person/send` + `person/inbox` + ACK 机制),真要扩成聊天,数据模型加一个字段、去掉类型限制即可。**但那是产品决定,不是技术决定。**
|
|
198
|
+
|
|
199
|
+
### 做的前提
|
|
200
|
+
|
|
201
|
+
先有一个必须私聊、不能在群里说的真实场景。目前想不出来。
|