Files
goodbuddy/docs/features/device-sharing/technical-design.md
T
mesalogo c8f327fc4e perf: reduce streaming render cost and refresh activity page on demand
- Segment streaming Markdown so completed blocks are reused and only the growing tail is re-parsed; fall back to whole-document parsing for document-wide constructs
- Memoize conversation sidebar rows and cache per-conversation derived activity, ordering and pending approvals
- Activity page loads on entry, refreshes every 15 s while visible, and has a single header refresh button
- Include pending settings, model download source, device sharing and knowledge gateway changes in the working tree
- Add Electron streaming input benchmark and regression tests
2026-10-01 21:49:09 +08:00

248 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设备发现与共享技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 本地元数据首期已接入;完整共享网络为后续设计 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 共享协议 | [共享网络总体设计](../../architecture/share-network-architecture.md) |
## 当前首期实现
本节实现 [FR-M1 至 FR-M5](./prd.md#当前首期本地元数据)。后文模块树、SQLite 表、共享协议包、
局域网与网关协议都是后续设计,当前不创建这些基础设施。
`src/shared/device-sharing-contracts.ts` 定义严格输入和返回类型;`DesktopApi.sharing` 暴露
`getSettings`、`saveSettings`、`registerDevice`、`getCatalog`、`publish`、`revoke` 六个方法。
生产 preload 对应六个固定 IPC 通道,`device-sharing-ipc.ts` 对每次调用检查可信主窗口主帧,
对变更输入进行 Zod 校验,不暴露通用网络入口。
`DeviceSharingService` 在 Main 内使用 `fetch`,每个请求含响应体读取采用 10 秒超时,非成功
响应保留 HTTP 状态;响应再经 Zod 校验。请求只在页面读取或用户动作时发出,不启动后台发现。
URL 接受 HTTP/HTTPS,禁止内嵌凭据、查询和片段。没有令牌或 Agent/Runtime 改动。
| 方法 | HTTP 请求 |
| --- | --- |
| 注册 | `POST /api/v1/sharing/devices/register`,字段为 `id,name,platform,appVersion` |
| 设备列表 | `GET /api/v1/sharing/devices`,读取 `{devices:[]}` |
| 发布列表 | `GET /api/v1/sharing/publications`,读取 `{publications:[]}` |
| 发布 | `POST /api/v1/sharing/publications`,Main 生成条目 ID 并填入本机设备 ID |
| 撤销 | 先按 `?deviceId=` 获取本机条目,再 `POST /api/v1/sharing/publications/:id/revoke` |
设备 ID、名称与服务 URL 存在用户数据目录的 `device-sharing-settings.json`,版本为 1;
复用 `writeJsonFileAtomically`,加载 Promise 去重、保存串行。损坏设置报错而不重置 ID。
此文件不保存目录副本或凭据。导航沿用已有存储归一化,把 `device-sharing` 追加到旧顺序,
保留既有应用相对顺序与设置;新入口可排序,不增加常驻字段。应用设置中的
`deviceSharingEnabled` 使用共享 schema 的 `false` 默认值补齐缺失字段,显式 `true` 保留;
沿用现有版本与原子持久化、变更事件。更新 schema 将该字段设为可选,避免无关更新重置选择。
菜单复用启用过滤,App 收到关闭设置时返回对话并卸载页面。共享服务和目录数据不受此开关修改。
服务测试使用真实 loopback HTTP,验证契约、独立权限、识别范围、持久化、错误和超时;
Electron 测试通过环境变量选择相邻真实 ShareServer 源码,使用临时服务数据与桌面设置。
具体命令和实测边界见[实施进度](./progress.md)。
## 1. 进程边界
```text
Renderer
-> 窄 Preload API
Main
-> DeviceSharingService
-> LanDiscoveryService
-> PeerPairingService
-> GatewayConnectionService
-> CapabilityCatalogService
-> CapabilityPublicationService
-> RemoteTaskService
-> PackageAcquisitionService
-> SharingAuditStore
-> 既有 Capability / Runtime / Skill / MCP 服务
```
Renderer 只取得脱敏快照和调用显式动作。设备私钥、Peer 证书、网关凭据、网络 socket、原始
任务令牌、任意文件读取和包缓存路径只存在于 Main。
## 2. 模块职责
| 模块 | 职责 |
| --- | --- |
| `LanDiscoveryService` | mDNS browse/advertise、手动地址握手、接口变化 |
| `PeerPairingService` | 临时握手、验证码、双端确认、配对凭据与撤销 |
| `GatewayConnectionService` | 浏览器登录、设备注册、WebSocket、重连和策略同步 |
| `CapabilityCatalogService` | 合并本机、Peer 和网关目录,保留来源身份 |
| `CapabilityPublicationService` | 校验本机可共享能力并向目标范围发布 |
| `RemoteTaskService` | 授权、确认、provider 冻结、传输、取消和终态 |
| `PackageAcquisitionService` | 固定来源下载、校验、安装协调和权限差异 |
| `SharingAuditStore` | 本机有界审计查询、筛选和清理 |
目录合并只生成 UI 视图,不能把不同 provider 的授权、版本和执行位置压成一个身份。
## 3. 共享契约
实现前建立独立 `packages/share-protocol`。Desktop 只从该包导入 Zod schema 和 TypeScript
类型。协议输入在网络边界和 IPC 边界分别验证,不能因已经过网络校验而跳过 Renderer 输入
校验。
建议导出:
```text
DeviceSummary
PairingChallenge
GatewayDescriptor
CapabilityManifest
Publication
GrantConstraint
AuthorizationDecision
RemoteTaskSnapshot
RemoteTaskEvent
PackageDescriptor
AuditEventSummary
ProtocolError
```
错误只向 Renderer 返回稳定 code、可本地化参数和可恢复动作,不转发远端堆栈、响应正文或
内部地址。
## 4. 持久化
建议在现有 Desktop SQLite 中增加:
```text
device_identity_metadata
peer_pairings
gateway_connections
capability_publications
local_grants
remote_tasks
sharing_audit_events
installed_shared_packages
```
- 私钥和 refresh credential 存系统安全存储;SQLite 只存引用和脱敏元数据。
- 附近广播、在线状态、配对验证码、短期任务令牌和目录候选只存在于内存。
- 网关能力目录可有界缓存,用于离线说明,不作为离线调用授权。
- 审计正文不落库,字段和保留上限在实现前固定。
- 数据库迁移只覆盖已发布 schema;未发布设计不预建兼容读取器。
## 5. 局域网网络实现
- 使用系统支持的 mDNS/DNS-SD 库 browse `_goodbuddy-share._tcp.local`。
- 广播绑定用户允许的网络接口;接口切换时重建监听,不保留旧地址为身份。
- 服务端口由 Main 持有,使用设备证书建立 TLS。
- 未配对握手仅暴露配对所需字段,并限制并发、频率、消息大小和有效时间。
- 手动地址只替代发现,不替代证书指纹与双端确认。
- 停止广播时关闭未完成配对监听;已配对活动连接按任务生命周期处理。
## 6. 网关连接
1. Main 校验 HTTPS URL,并取得服务公开描述。
2. 生成带 PKCE、随机 state 和设备挑战的系统浏览器登录请求。
3. 本机 loopback 或已注册应用链接只接收一次性回调 code。
4. Main 交换设备凭据,展示组织身份,用户确认后持久化。
5. Main 建立带设备身份的 WebSocket 出站连接。
6. 心跳更新在线状态;断线按有界指数退避重连。
服务器返回的跳转 URL 必须符合已取得服务描述和 HTTPS 限制。Renderer 不能提交认证头、设备
证书、WebSocket URL 或任意回调地址。
## 7. 本机能力适配
不是所有现有 Capability 都自动可共享。每种可共享类型需要显式 adapter:
```ts
interface ShareableCapabilityAdapter {
describe(): CapabilityManifest
validatePublication(input: PublicationDraft): ValidationResult
invoke(input: unknown, context: ProviderTaskContext): AsyncIterable<TaskEvent>
cancel(taskId: string): Promise<CancelResult>
}
```
adapter 负责把共享输入映射到既有生产服务,并保留原权限边界。首批类型在实现前根据真实
产品需求确定;未有 adapter 的 Skill、MCP、知识库和 Runtime 不出现在共享列表。
## 8. 远端调用生命周期
```text
Renderer 选择能力和输入
-> Main 解析候选并取得当前授权
-> 必要时返回确认描述
-> 用户批准
-> Main 冻结 task/provider/route/policy
-> 建立数据通道并流式发送
-> Provider 再校验并接受
-> Main 转发有界进度和结果
-> 终态与审计事务保存
```
每次任务使用稳定 `taskId` 和 attempt ID。Provider 未接受前的连接失败可以明确失败;已接受
后的断线先查询同一任务,不新建 attempt。无法恢复权威终态时保存 `outcome_unknown`。
附件从 Main 已授权的用户选择结果读取,按声明大小流式发送,不把任意本机路径交给远端。
接收成果先写 GoodBuddy-owned 暂存并校验,再通过现有 Artifact 边界进入产品。
## 9. IPC
Preload 只暴露按产品动作命名的方法,例如:
```text
sharing.getSnapshot
sharing.updateLanSettings
sharing.startPairing / confirmPairing / cancelPairing
sharing.revokePeer
sharing.addGateway / completeGatewayLogin / removeGateway
sharing.listCapabilities
sharing.savePublication / pausePublication / deletePublication
sharing.prepareInvocation / confirmInvocation / cancelTask
sharing.listAuditEvents / getAuditEvent
```
所有变更方法校验 trusted sender、严格 schema、对象当前 revision 和归属关系。不得暴露通用
HTTP、WebSocket、mDNS、证书、文件传输或数据库查询 IPC。
## 10. 生命周期与资源边界
- 应用启动按保存设置启动主动发现;不因保存了网关而阻塞窗口创建。
- 广播只有在用户开启后启动。
- 网关连接在后台恢复,认证过期只影响对应连接。
- 应用退出停止接收新任务,取消未接受任务,并有界通知活动 Provider 后关闭连接。
- 单个 Peer/网关/任务设置连接数、帧、字节、并发和重连上限。
- 大文件全程流式处理,不进入单个 Main `Buffer` 或 Renderer state。
## 11. 与现有模块集成
- `capability-service` 提供本机能力事实;共享 adapter 明确选择可公开子集。
- 已安装共享 Skill/MCP 继续使用现有配置、启停和 Runtime 分配,不建立第二套执行器。
- SSH Host 保持独立;除非未来提供专门 adapter 和用户发布,否则不进入目录。
- 远端 Task 的公开活动复用现有工具/任务展示语义,但保留 provider 和 route 字段。
- 应用更新源、模型下载源、Agent 下载源和工具下载源不因添加 ShareServer 自动改变。
## 12. 验证策略
### 12.1 自动化
- schema 边界、未知枚举和协议版本协商。
- 三个独立开关及默认值。
- mDNS 去重、接口变化、广播停止和手动地址。
- 配对双方确认、拒绝、超时、身份变化和撤销。
- 多网关隔离、认证过期、重连和移除。
- provider 排序、冻结、授权变化和失败不切换。
- 调用确认最小输入、附件上限、取消和结果未知。
- Publication 校验、暂停、撤销和 adapter 消失。
- 包签名、权限差异、来源冻结和失败保留旧安装。
- IPC sender、revision、归属和无通用网络/文件接口。
### 12.2 真实环境
- Windows、macOS、Linux 至少两种组合完成同网段发现和配对。
- mDNS 可用、被防火墙阻断、网络接口切换和设备离线场景。
- 真实 ShareServer 登录、设备出站连接、目录、调用和认证过期。
- 小型 JSON、文件流、取消、Provider 进程失败和网络中断。
- 确认日志、Renderer、诊断和审计中没有凭据或文件正文。
设计文档完成不代表功能实施或上述验证通过。