Skip to content

Commit f9bf321

Browse files
committed
feat: enhance ConnectForm with input mode detection and local node shortcut
1 parent 41d5724 commit f9bf321

File tree

4 files changed

+469
-44
lines changed

4 files changed

+469
-44
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
# ClawNet 团队协作请求:P2P 事件订阅代理(Event Subscription Proxy)
2+
3+
| 字段 ||
4+
| --- | --- |
5+
| 优先级 | **P2 — 用户体验优化(非阻塞性)** |
6+
| 提出方 | TelagentNode 团队 |
7+
| 提出日期 | 2026-03-09 |
8+
| 影响范围 | DID 远程访问时的实时消息推送 |
9+
| 当前临时方案 | Webapp 轮询 HTTP API(活跃 3s / 空闲 15s) |
10+
| 前置依赖 | DID 远程接入(API Proxy)已实现 |
11+
12+
---
13+
14+
## 1. 问题背景
15+
16+
### 1.1 当前架构
17+
18+
TelagentNode 已实现基于 DID 的远程节点接入(API Proxy),Webapp 可通过网关节点访问 NAT 内网的目标节点 REST API:
19+
20+
```
21+
Webapp ──HTTP──► Gateway Node ──P2P──► Target Node (NAT 内)
22+
23+
ClawNet WS 订阅
24+
topic: telagent/*
25+
```
26+
27+
目标节点通过 **WebSocket 订阅 ClawNet** 获取实时 P2P 消息(信封、回执、群组同步等),响应延迟 <100ms。
28+
29+
**Webapp 与节点之间** 没有实时通道,全部通过 HTTP 轮询获取更新:
30+
31+
| 轮询内容 | 间隔 | 端点 |
32+
|---------|------|------|
33+
| 当前会话新消息 | 3 秒 | `GET /api/v1/messages/pull?conversation_id=X` |
34+
| 全局新消息 + 会话列表 | 15 秒 | `GET /api/v1/messages/pull` + `GET /api/v1/conversations` |
35+
| 撤回消息 | 15 秒 | `GET /api/v1/messages/retracted` |
36+
37+
### 1.2 本地连接 vs DID 远程连接
38+
39+
**本地连接**(Webapp 直连本机节点):
40+
41+
- 轮询延迟可接受(3s 内可见新消息,localhost 无网络延迟)
42+
- 后续可在节点本地加 SSE/WebSocket 端点直接推送(纯 TelagentNode 改造,不需要 ClawNet)
43+
44+
**DID 远程连接**(Webapp 通过网关中继访问远端节点):
45+
46+
- 每次轮询经过完整 P2P round-trip(Webapp → Gateway → P2P → Target → P2P → Gateway → Webapp)
47+
- 单次 round-trip 延迟 200-800ms,3s 轮询间隔意味着大量浪费的空查询
48+
- 15s 全局轮询延迟导致新消息通知严重滞后
49+
50+
### 1.3 理想架构
51+
52+
```
53+
┌─ SSE/WS ──── Webapp
54+
55+
Gateway Node ◄═══ P2P 事件订阅 ═══► Target Node
56+
│ │
57+
│ ClawNet WS 订阅
58+
│ topic: telagent/*
59+
60+
└── 收到 Target 的
61+
新消息事件后
62+
立即推送给 Webapp
63+
```
64+
65+
当目标节点收到新的 P2P 消息(信封、回执等)时,网关节点能立即得到通知,并推送给已连接的 Webapp 客户端。
66+
67+
---
68+
69+
## 2. 现有方案评估
70+
71+
### 2.1 方案 A:纯应用层 — 事件转发(不需要 ClawNet 改动)
72+
73+
TelagentNode 可在应用层完全自建事件转发,不依赖 ClawNet 新能力:
74+
75+
```
76+
1. Webapp 通过 Gateway 向 Target 发送"订阅注册"请求
77+
POST /relay/{targetDid}/api/v1/events/subscribe
78+
Body: { gatewayDid, sessionId, topics: ["envelope", "receipt"] }
79+
80+
2. Target Node 维护一张 subscribers 表:
81+
Map<gatewayDid, { sessionIds, subscribedTopics, lastPingMs }>
82+
83+
3. 当 Target 处理完新消息后,向所有注册的 Gateway 推送轻量事件:
84+
P2P Topic: telagent/event-push
85+
Payload: { sessionId, event: "new-envelope", conversationId, envelopeId, atMs }
86+
87+
4. Gateway 收到 event-push → 查找对应的 SSE/WS 连接 → 转发给 Webapp
88+
89+
5. Webapp 收到通知 → 立即 fetch 具体数据(增量拉取,非全量轮询)
90+
```
91+
92+
**此方案的问题:**
93+
94+
| 问题 | 影响 |
95+
|------|------|
96+
| Target 必须追踪所有 Gateway 订阅状态 | 增加复杂度、状态管理负担 |
97+
| 订阅注册/注销/超时清理都需要自建 | 大量 edge case(Target 重启、Gateway 断连) |
98+
| Target 每条消息都要额外向 N 个 Gateway 发送通知 | 放大 P2P 消息量,O(N) |
99+
| 无法利用 ClawNet 原生的消息投递保障 | 通知可能丢失,需自建重试 |
100+
| Target 是唯一知道"有新消息到达"的节点 | 瓶颈在 Target 的事件分发 |
101+
102+
**结论:可行但笨重。** 本质上是在 ClawNet P2P 之上重新构建了一套发布-订阅系统。
103+
104+
### 2.2 方案 B:ClawNet 层 — 事件订阅代理(需要 ClawNet 支持)⭐️ 推荐
105+
106+
ClawNet 在协议层支持"授权代理订阅":一个 DID(Gateway)可由另一个 DID(Target)授权,接收发送给 Target 的特定 topic 消息副本。
107+
108+
**核心优势:Target 节点无需做任何额外工作。** ClawNet 在消息投递时自动向已授权的代理也投递一份副本。
109+
110+
---
111+
112+
## 3. 期望 ClawNet 提供的能力
113+
114+
### 3.1 授权 API — 目标节点发出授权
115+
116+
```typescript
117+
// Target Node 授权 Gateway 代理订阅自己的指定 topic
118+
const delegation = await client.messaging.createSubscriptionDelegation({
119+
delegateDid: 'did:claw:zGateway...', // 被授权方 DID
120+
topics: ['telagent/envelope', 'telagent/receipt', 'telagent/group-sync'],
121+
expiresInSec: 3600, // 授权有效期(秒)
122+
// 可选:只转发 metadata,不转发完整 payload
123+
metadataOnly: true,
124+
});
125+
// 返回: { delegationId: string, expiresAtMs: number }
126+
127+
// Target Node 撤销授权
128+
await client.messaging.revokeSubscriptionDelegation({
129+
delegationId: 'dlg_xxx',
130+
});
131+
132+
// Target Node 查看当前授权列表
133+
const delegations = await client.messaging.listSubscriptionDelegations();
134+
// 返回: [{ delegationId, delegateDid, topics, expiresAtMs, createdAtMs }]
135+
```
136+
137+
### 3.2 代理订阅 API — 网关节点使用授权
138+
139+
```typescript
140+
// Gateway Node 使用授权订阅 Target 的消息
141+
const unsub = await client.messaging.subscribeDelegated({
142+
delegationId: 'dlg_xxx',
143+
onMessage: (msg: DelegatedMessage) => {
144+
// msg.originalTargetDid — 消息原始目标 DID
145+
// msg.sourceDid — 消息来源 DID
146+
// msg.topic — 原始 topic
147+
// msg.payload — 完整 payload(如果 metadataOnly=false)
148+
// 或 msg.metadata — 仅元数据(如果 metadataOnly=true)
149+
// msg.seq — 序列号(支持 sinceSeq 重连)
150+
}
151+
});
152+
```
153+
154+
### 3.3 REST API 方式(如果 SDK 暂不支持)
155+
156+
```
157+
# 创建授权(Target Node 调用)
158+
POST /api/v1/messaging/subscription-delegations
159+
{
160+
"delegateDid": "did:claw:zGateway...",
161+
"topics": ["telagent/envelope", "telagent/receipt"],
162+
"expiresInSec": 3600,
163+
"metadataOnly": true
164+
}
165+
→ { "delegationId": "dlg_abc123", "expiresAtMs": 1741564800000 }
166+
167+
# 撤销授权(Target Node 调用)
168+
DELETE /api/v1/messaging/subscription-delegations/{delegationId}
169+
170+
# 代理订阅(Gateway Node 调用)
171+
WS /api/v1/messaging/subscribe-delegated?delegationId=dlg_abc123&sinceSeq=0
172+
→ 帧格式同现有 messaging/subscribe,增加 originalTargetDid 字段
173+
```
174+
175+
### 3.4 metadataOnly 模式(推荐默认开启)
176+
177+
为了减少数据传输和保护隐私,推荐支持"仅转发元数据"模式:
178+
179+
```typescript
180+
// metadataOnly: true 时,Gateway 收到的消息:
181+
{
182+
"type": "delegated-message",
183+
"originalTargetDid": "did:claw:zTarget...",
184+
"sourceDid": "did:claw:zPeerC...",
185+
"topic": "telagent/envelope",
186+
"metadata": {
187+
"messageId": "msg_xxx",
188+
"seq": 456,
189+
"payloadSizeBytes": 2048,
190+
"receivedAtMs": 1741564800000
191+
}
192+
// 注意:不包含 payload 内容
193+
}
194+
```
195+
196+
**Gateway 不需要消息全文**,只需要知道"Target 收到了新消息"这一事件即可。Gateway 收到通知后推送给 Webapp,Webapp 再通过 API Proxy 拉取实际消息内容。
197+
198+
这样设计的好处:
199+
- **隐私安全**:Gateway 看不到消息 payload,只知道"有新消息到达"
200+
- **带宽节省**:元数据 ~100 bytes vs 完整信封 ~2-5 KB
201+
- **授权粒度**:Target 可以随时撤销,不影响消息本身的安全性
202+
203+
---
204+
205+
## 4. TelagentNode 侧的使用场景
206+
207+
### 4.1 完整事件流(有 ClawNet 支持时)
208+
209+
```
210+
Webapp Gateway Node ClawNet Target Node
211+
│ │ │ │
212+
├── SSE Connect ───────►│ │ │
213+
│ /relay/{did}/events │ │ │
214+
│ │ │ │
215+
│ ├── API Proxy ──────────────────────────────►│
216+
│ │ POST /events/subscribe │
217+
│ │ { gatewayDid } │
218+
│ │ │ │
219+
│ │ │◄── createDelegation ┤
220+
│ │ │ { delegateDid, │
221+
│ │ │ topics, │
222+
│ │ │ metadataOnly } │
223+
│ │ │ │
224+
│ │◄── delegationId ──────────────────────────── │
225+
│ │ │ │
226+
│ ├── subscribeDelegated ──► │
227+
│ │ (WS to ClawNet) │ │
228+
│ │ │ │
229+
│ │ ╔══════════════════╗ │
230+
│ │ ║ Peer C sends ║ │
231+
│ │ ║ message to ║──────────────────►│
232+
│ │ ║ Target ║ │
233+
│ │ ╚══════════════════╝ │
234+
│ │ │ │
235+
│ │◄── delegated-message ──┤ │
236+
│ │ { topic, metadata } │ │
237+
│ │ │ │
238+
│◄── SSE event ─────────┤ │ │
239+
│ { type: "new-envelope", │ │
240+
│ conversationId } │ │ │
241+
│ │ │ │
242+
├── fetch (API Proxy) ──►──────────────────────────────────────────►│
243+
│ GET /messages/pull │ │ │
244+
│◄── actual messages ───┤◄─────────────────────────────────────────── │
245+
```
246+
247+
### 4.2 降级方案(无 ClawNet 支持时)
248+
249+
如果 ClawNet 暂不实现,TelagentNode 将自行实现方案 A(应用层事件转发),使用新的 P2P topic:
250+
251+
- `telagent/event-subscribe` — Gateway → Target:注册订阅
252+
- `telagent/event-unsubscribe` — Gateway → Target:取消订阅
253+
- `telagent/event-push` — Target → Gateway:事件推送
254+
- `telagent/event-heartbeat` — 双向心跳保活
255+
256+
我们优先希望减少自建复杂度,所以如果 ClawNet 能在协议层支持,效果会好很多。
257+
258+
---
259+
260+
## 5. 对 ClawNet P2P 层的具体要求
261+
262+
| 能力 | 说明 | 优先级 |
263+
|-----|------|--------|
264+
| 创建订阅授权 | Target DID 授权 Gateway DID 代理订阅指定 topics | **必须** |
265+
| 撤销授权 | Target DID 可随时撤销 | **必须** |
266+
| 授权有效期 | 支持 TTL 自动过期 | **必须** |
267+
| 代理 WebSocket 订阅 | Gateway 通过 WS 接收 Target 的消息副本 | **必须** |
268+
| sinceSeq 重连恢复 | 代理订阅断线后可从断点续接 | **必须** |
269+
| metadataOnly 模式 | 只转发消息元数据不含 payload | **强烈推荐** |
270+
| 多 Gateway 支持 | 一个 Target 可授权多个 Gateway | 推荐 |
271+
| 授权列表查询 | Target 查看/管理所有授权 | 推荐 |
272+
| 授权配额限制 | 防滥用:每个 DID 最多 N 个活跃授权 | 推荐 |
273+
274+
---
275+
276+
## 6. 安全考量
277+
278+
### 6.1 授权模型
279+
280+
- **单向授权**:只有 Target 能创建授权,Gateway 无法自行订阅
281+
- **Scope 限制**:授权只能指定具体 topic,不能用 `*` 通配
282+
- **TTL 强制**:授权必须有有效期,到期自动清除
283+
- **撤销即失效**:Target 撤销后 ClawNet 立即停止向 Gateway 投递
284+
285+
### 6.2 隐私保护
286+
287+
- `metadataOnly=true` 时 Gateway 无法获取消息内容
288+
- Gateway 只知道"Target 收到了来自某 DID 的某 topic 消息"
289+
- Webapp 获取实际内容仍需通过 API Proxy 调用目标节点(经目标节点鉴权)
290+
291+
### 6.3 防滥用
292+
293+
- 每个 DID 的活跃授权数量应有上限(建议 10)
294+
- 代理订阅产生的消息投递应计入 Target 的配额
295+
- 恶意 Gateway 无法扩大授权范围
296+
297+
---
298+
299+
## 7. 不需要 ClawNet 改动的部分
300+
301+
以下工作由 TelagentNode 团队自行完成,无需 ClawNet 支持:
302+
303+
| 工作项 | 说明 |
304+
|--------|------|
305+
| 节点本地 SSE 端点 | `GET /api/v1/events`(SSE),推送新消息/回执/撤回事件给本地 Webapp |
306+
| Gateway SSE 转发 | `GET /relay/{targetDid}/api/v1/events`,Gateway → Webapp 的 SSE 桥接 |
307+
| Webapp SSE 客户端 | 替换 `usePollMessages` 轮询为 EventSource 自动重连 |
308+
| 事件类型定义 | `new-envelope`, `receipt`, `retraction`, `conversation-update`|
309+
310+
---
311+
312+
## 8. 时间线建议
313+
314+
| 阶段 | 内容 | 依赖 |
315+
|------|------|------|
316+
| **v0.2.x(近期)** | TelagentNode 实现本地 SSE 端点(无 ClawNet 依赖) ||
317+
| **v0.3.0** | 如有 ClawNet 支持 → 实现代理订阅方案(方案 B) | ClawNet 订阅代理 API |
318+
| **v0.3.0 降级** | 如无 ClawNet 支持 → 自建应用层事件转发(方案 A) ||
319+
320+
---
321+
322+
## 9. 参考
323+
324+
- DID 远程接入实现文档:`docs/implementation/did-remote-access-implementation.md`
325+
- P2P 消息 RFC:`docs/design/p2p-messaging-rfc.md`
326+
- 当前 Webapp 轮询逻辑:`packages/webapp/src/hooks/use-poll-messages.ts`
327+
- API Proxy Service:`packages/node/src/services/api-proxy-service.ts`
328+
- ClawNet Transport Service:`packages/node/src/services/clawnet-transport-service.ts`

0 commit comments

Comments
 (0)