Client/Agent 角色融合与自动注册实施计划
Client/Agent 角色融合与自动注册实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 在现有 client 配置中增加可选的 client.agent_id,配置后让 client 进程同时承载 client 隧道和 Agent 会话,并让独立 tunnelmesh-agent 在缺少 agent.id 时使用已绑定的 Agent token 查询服务端并回写 ID;macOS 与 Windows 托盘界面同步展示和编辑该配置。
Architecture: 复用现有 client 的 Server URL、client token、transport、连接池、stream、metadata 和 instance identity;client 的 Agent 角色使用独立 Agent WebSocket 会话和既有 Agent dispatcher,但不新增独立 Agent 配置块。Server 在 Agent WebSocket 首次收到 client token+agent_id 时按 token owner 创建同名 Agent,已存在时执行 owner、enabled、scope 校验;独立 Agent 仍使用 agent.token,通过新增 Agent 作用域只读身份接口获取 token 绑定的 ID。托盘只扩展共享配置读写和提示,不分叉 macOS/Windows 原生壳。
Tech Stack: Go、Viper/mapstructure、YAML AST 原子写回、现有 WebSocket protocol、SQLite/MySQL Repository、Vue 3 + TypeScript + Element Plus + vue-i18n、现有 macOS WKWebView/Windows WebView2 托盘壳。
Spec: 用户确认的 client.agent_id 角色融合、首次连接自动创建同名 Agent、独立 Agent 读取已绑定 ID 后回写、agent-only client 允许启动、托盘 UI 标注说明。
Global Constraints
- 基于当前
origin/main实现;不创建新的 git worktree。 client.agent_id为空时,client 行为保持现状;配置后 client 可仅运行 Agent 角色,即client.tunnels可以为空。- client Agent 角色复用
client.token;空scope.AgentIDs代表该 token owner 的全部启用 Agent,有 scope 时必须包含client.agent_id。 - 独立
tunnelmesh-agent继续复用agent.token;缺少agent.id时只查询 token 已绑定的 Agent,不生成新 ID、不创建新 Agent、不覆盖非空且不一致的 ID。 - Server 自动创建的 Agent 使用
ID=client.agent_id、Name=client.agent_id、当前 token owner、Enabled=true,并写入审计日志;并发注册必须幂等且不能越权。 - 所有 API 保持
/api/v1、{code,msg,data}、服务端权限校验和敏感信息不回显;OpenAPI、用户文档和docs/README.md索引同步更新。 - 必须先写失败测试并观察红灯,再写最小实现;不提交 Token、构建缓存或
node_modules。 - 不执行 commit、push、merge 或创建远端 MR,除非用户另行明确授权。
Task 1: 配置模型与 YAML 安全回写
Files:
- Modify:
internal/config/config.go - Modify:
internal/tray/configstore.go - Modify:
internal/tray/app.go - Modify:
web-tray/src/api/types.ts - Modify:
web-tray/src/stores/routing.ts - Test:
internal/config/config_test.go,internal/tray/configstore_test.go,internal/tray/app_test.go
Interfaces:
config.ClientConfig.AgentID string使用mapstructure:"agent_id" json:"agent_id" yaml:"agent_id"。tray.ClientSettings.AgentID string、tray.RoutingView.AgentID string、tray.RoutingUpdate.AgentID string。client.agent_id可为空;当client.tunnels为空且client.agent_id非空时配置加载和运行时预校验通过;两者都为空时仍拒绝启动。-
ConfigStore.Load/Save/BuildConfig必须读写顶层client.agent_id,保留未知根键、注释、权限和 token。 - Step 1: 写失败测试:覆盖
config.Load读取client.agent_id、空 tunnels+agent_id 通过、空 tunnels+空 agent_id 失败;覆盖 tray round-trip、0600、旧配置无字段默认空、未知字段和注释保留。 - Step 2: 运行红灯:
go test ./internal/config ./internal/tray -run 'AgentID|AgentOnly|ConfigStore' -count=1;预期失败于缺少字段/校验分支。 - Step 3: 最小实现:增加配置字段和默认/校验规则;在 YAML AST 的 client mapping 中安全更新
agent_id,空值删除或保持不写入;扩展 tray 视图和 pending routing payload。 - Step 4: 运行绿灯:重复上述定向测试,确认通过且 token 不出现在 JSON/视图。
- Step 5: 重构:复用已有 tunnel YAML 节点更新和原子写回逻辑,避免重复序列化路径。
Task 2: Server Agent 作用域鉴权与首次自动注册
Files:
- Modify:
internal/server/middleware.go,internal/server/runtime.go - Modify:
internal/server/api.go或新增internal/server/agent_scoped_api.go - Modify:
internal/server/client_scoped_api.go - Test:
internal/server/runtime_test.go,internal/server/client_scoped_api_test.go, 新增 Agent 作用域 API/注册测试 - Modify:
docs/api/openapi.yaml
Interfaces:
- 新增只读
GET /api/v1/agent/self,仅接受TokenTypeAgent,返回最小{id,name},不返回 token、owner 或 metadata。 - 新增
AgentTokenValidator接口,或复用窄接口注入ValidateAs(context.Context,string,storage.TokenType);缺少 validator 返回 503,错误 token 返回 401。 - Agent WebSocket 鉴权上下文扩展为
Agent/Client两种身份;hello 后统一进入authenticateAgentConnection。 -
client token 注册规则:目标 ID 非空且格式受限;空 Agent scope 允许 owner 的启用 Agent;非空 scope 必须包含目标 ID;已有记录必须 owner 相同且 enabled;缺失记录按 ID 创建同名启用 Agent;重复创建竞态回读并再次校验。
- Step 1: 写失败测试:覆盖
/api/v1/agent/self未授权、client token/console token 拒绝、有效 agent token 返回绑定 ID;覆盖 client token Agent WS 首次连接自动创建、重连幂等、scope 拒绝、跨 owner 拒绝、disabled 拒绝、并发创建不重复;覆盖已有 Agent token 行为不变。 - Step 2: 运行红灯:
go test ./internal/server -run 'AgentSelf|ClientToken.*Agent|AutoRegister|AgentWebSocket' -count=1;预期新接口/鉴权路径失败。 - Step 3: 最小实现:在
ServeHTTPpre-auth 段加入 Agent 作用域路由;在 runtime 的 agent handshake 中保留 agent token 绑定校验,并为 client token 增加 owner/scope/自动创建分支;自动创建写agent.created或专用agent.auto_registered审计,审计只记录 ID/name/owner,不记录 token。 - Step 4: 运行绿灯:执行定向 Server 测试,确认原 agent WS、client WS、client scoped agents 测试全部通过。
- Step 5: 文档契约:同步 OpenAPI operation、security scheme、响应 schema、401/403/503;增加 endpoint 契约测试,确保没有敏感字段。
Task 3: client Runtime 同时承载 Agent 角色
Files:
- Modify:
internal/client/runtime.go,internal/client/runtime_test.go - Modify:
internal/agent/connection_pool.go或新增可复用配置适配文件 - Modify:
internal/cli/client_run.go,internal/cli/root.go - Test:
internal/client/runtime_test.go,internal/cli/client_runtime_test.go
Interfaces:
NewRuntimeFromConfig接受cfg.Client.AgentID;client tunnels 为空但 AgentID 非空时仍构造 Runtime。- Runtime 启动/停止同时管理 local forwarders 和 Agent connection pool;任一角色停止时通过同一 context 优雅退出,互斥锁仍按 config 路径只占一份。
- Agent 角色使用 client
server_url、token、transport、connections、stream、metadata、instance identity;NodeID/Agent InstanceID 使用同一稳定 client instance identity,不能生成第二套身份。 -
Runtime.Stats增加 Agent-role 状态/错误可观测信息,但保留现有 client tunnel stats 字段兼容性。 - Step 1: 写失败测试:覆盖 agent-only runtime 能构造并 Start;client tunnels+agent role 同时启动;无 agent_id 时不启动 agent role;两角色 Stop 可释放锁;client run 的原 tunnel 行为和错误文本保持兼容。
- Step 2: 运行红灯:
go test ./internal/client ./internal/cli -run 'AgentRole|AgentOnly|Runtime' -count=1。 - Step 3: 最小实现:引入 Agent pool 的适配装配,使用现有
agent.NewAgentDispatcherFactory需要的 stream 参数;将 Agent pool goroutine 纳入 Runtime supervisor;把“至少一个 tunnel 或 agent_id”作为唯一启动前置条件。 - Step 4: 运行绿灯:执行定向 runtime/CLI 测试,确认锁、停止、重连和错误传播正常。
- Step 5: 重构:隔离 client forward 和 embedded agent 的生命周期代码,避免让 CLI、tray 各自维护一套装配逻辑。
Task 4: 独立 tunnelmesh-agent 自动读取并回写 ID
Files:
- Modify:
internal/cli/root.go - Modify:
internal/config/config.go(新增通用安全InitializeAgentID/YAML 回写 helper) - Test:
internal/cli/agent_runtime_test.go,internal/config/config_test.go - Modify:
docs/user-guide/agent.md
Interfaces:
GET /api/v1/agent/self返回的id是agent.token的服务端绑定 ID。agent run在cfg.Agent.ID为空时,先使用agent.server_url、agent.token查询 self;成功后原子回写agent.id并继续现有连接池启动。- 非空
agent.id不查询或覆盖;若服务端绑定 ID 与配置 ID 不一致,启动失败并明确指出冲突。 -
查询失败、token 类型错误、服务端不支持接口或回写失败均快速失败,不生成随机 Agent ID。
- Step 1: 写失败测试:覆盖空 ID 查询成功并回写 YAML、保留注释/权限/其他字段;空 ID 查询失败;非空 ID 不被覆盖;配置 ID 与 token 绑定 ID 不一致时拒绝。
- Step 2: 运行红灯:
go test ./internal/cli ./internal/config -run 'Agent.*ID|AgentSelf|Backfill' -count=1。 - Step 3: 最小实现:在 CLI run 之前调用 self endpoint,复用
client.HTTPURLFromWebSocket生成 API URL;新增 YAML AST 原子回写agent.id,不改变 token 处理和现有 Agent pool。 - Step 4: 运行绿灯:执行定向测试和既有 Agent runtime 测试。
- Step 5: 文档:更新最小配置、运行和故障排查,明确“agent.id 可省略,首次运行从 agent.token 对应节点回写;不会自动生成节点”。
Task 5: macOS/Windows 托盘路由配置与说明
Files:
- Modify:
internal/tray/app.go,internal/tray/configstore.go,internal/tray/validate.go,internal/tray/api.go - Modify:
web-tray/src/api/types.ts,web-tray/src/stores/routing.ts,web-tray/src/views/RoutingView.vue - Modify:
web-tray/src/i18n/zh-CN.ts,web-tray/src/i18n/en-US.ts - Test:
internal/tray/*_test.go,web-tray/src/tests/routing-view.spec.ts,web-tray/src/tests/api.spec.ts
Interfaces:
GET /api/routing返回agentId和配置摘要;PUT /api/routing接受agentId,保存前执行真实 config validation。- 路由 Tab 在 server URL/token 下方增加
client.agent_id输入框/下拉;优先从/api/agents读取作用域内 Agent,允许输入服务端尚未存在的 ID 以触发首次自动注册。 - UI 明确标注:
client.agent_id配置后,客户端会同时作为 Agent;复用当前 client token;首次连接时若不存在会自动创建同名 Agent;留空则仅运行 client 隧道。 -
client.agent_id非空且 tunnels 为空时,保存、检测和启动按钮均可用;agent-only 状态在统计页显示。 - Step 1: 写失败测试:Go API round-trip 不泄露 token;Vue 渲染字段、说明文案、保存 payload、agent-only 空 tunnels、已有 Agent 下拉和手工输入回归。
- Step 2: 运行红灯:
go test ./internal/tray -run 'Routing|AgentID' -count=1;cd web-tray && npx vitest run src/tests/routing-view.spec.ts。 - Step 3: 最小实现:扩展本地 API、store 和表单;保持 token masked;复用已有 agent picker,新增“自定义 agent_id/首次连接自动注册”提示。
- Step 4: 运行绿灯:定向 Go/Vitest 测试通过。
- Step 5: UI 复核:确认 macOS WKWebView 与 Windows WebView2 使用同一 bundle,未新增平台分叉;检查中英文、浅深色和窄窗口布局。
Task 6: 文档、OpenAPI 索引与全量验证
Files:
- Modify:
docs/api/openapi.yaml - Modify:
docs/user-guide/client-tray.md,docs/user-guide/agent.md,docs/user-guide/client.md(若存在) - Modify:
docs/README.md(仅由索引脚本生成) - Create:
docs/pull-requests/2026-10-25-client-agent-role.md
Interfaces:
- 文档必须描述
client.agent_id、agent-only 模式、client token scope、首次连接自动注册、独立 Agent ID 回写和失败回滚/排障。 -
PR 描述包含目标分支
main、用户影响、API/配置/授权影响、安全边界、测试证据、发布和回滚步骤;不包含真实 token、DSN 或完整 Authorization。 - Step 1: 写文档/契约测试:补 OpenAPI schema 断言、配置示例和 UI 说明测试。
- Step 2: 运行文档检查:
python3 scripts/gen_doc_index.py,随后git diff --check,确认索引没有孤儿入口。 - Step 3: 全量验证:依次运行
go test ./... -count=1、go test -race ./...、go vet ./...、cd web-tray && npm test -- --run && npm run build、git diff --check。 - Step 4: 平台构建验证:在可用环境运行
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -tags tray ./cmd/tunnelmesh-client-tray,并运行 Windows 交叉编译/守卫测试;无法在当前主机完成的原生 GUI 冒烟必须明确记录为未执行。 - Step 5: 结果复核:检查
git diff --cached前不进行提交;确认没有.syso、node_modules、token 或构建缓存进入变更集。
回滚注意事项
- Server 先发布 API/鉴权兼容改动,再发布 client/agent;旧 client/agent 仍按原 token 类型工作。
- 若自动注册引发错误资源创建,可关闭 client 的
agent_id或回退 client/agent 二进制;已创建 Agent 记录必须由管理 API 显式禁用/删除,不由客户端自动删除。 - 若托盘保存失败,YAML 原子写回保留旧文件;若运行时启动失败,保留用户配置并在统计/检测页显示错误,不静默清空
agent_id。