feat(controller): add worker channel configuration proxy endpoints - #1219
feat(controller): add worker channel configuration proxy endpoints#1219LUOSENGWA wants to merge 1 commit into
Conversation
cbb31e5 to
ad71f13
Compare
|
Making Worker channel configuration available to team-scoped L2 users is valuable and should not require admin or host access for every normal change. However, channel metadata and channel credentials must have different permission boundaries. By default, L2 users should be able to view and edit non-sensitive fields for Workers in their own teams, such as channel type, endpoint, enabled state, display settings, health, and restart operations. Sensitive values such as Even with FullAccess, the safer default API contract is:
Please also use dedicated channel read, channel update, and sensitive-data actions instead of inheriting the generic Worker Before moving this PR out of Draft, please open a GitHub Issue describing the role/capability matrix, sensitive-field classification, secret update semantics, QR credential handling, and audit requirements, then link it to this PR for community discussion. The Issue should also confirm the QwenPaw version contract: the locked QwenPaw 2.0.1 package does not currently expose the proxied 让本团队 L2 用户配置 Worker Channel 具有明确价值,普通配置变更不应该每次都依赖 Admin 或宿主机操作。但是,Channel 元数据和 Channel 凭据必须采用不同的权限边界。 默认 L2 应能查看和编辑本团队 Worker 的非敏感字段,例如 Channel 类型、地址、启用状态、展示配置、健康状态和重启操作。
即使具有 FullAccess,更安全的默认接口契约仍应是:
请为 Channel 读取、Channel 修改和敏感数据访问定义独立 Action,不要继承通用 Worker 在将本 PR 移出 Draft 前,请创建 GitHub Issue,说明角色/capability 矩阵、敏感字段分类、Secret 更新语义、二维码凭据处理和审计要求,并关联到本 PR,让社区参与讨论。 Issue 中还应确认 QwenPaw 版本契约:当前锁定的 QwenPaw 2.0.1 没有被代理的 |
ad71f13 to
9abdb96
Compare
Proxy a fixed surface of each worker's qwenpaw app channel API (list/types/schemas/get/put/health/restart/conflict-check/qrcode) so L1 admins and L2 humans can connect channels (QQ/Matrix/DingTalk/...) to agents in their teams from a graphical frontend, without SSH or container surgery on the worker's agent.json. - Embedded mode only (kube: uniform 503, before any worker lookup) - W8 scope: cross-team and standalone-worker access hides as 404; the handler is the real boundary (team leaders read-only, 403 on mutations) on top of the middleware's worker-scoped policy - PUT is the qwenpaw-authoritative write path (persist + hot reload, no worker restart); empty body rejected pre-dial (would wipe the channel); upstream 400/404/409/422 pass through verbatim - Read-back validation: after a 200, verify the worker's push_loop converged the MinIO baseline and report it via the X-AgentTeams-MinIO-Persisted header (true/false/skipped) — surfaces the manual-edit persistence gaps that burned channel secrets before - Fixed-path forwarding only (never a generic reverse proxy), strict channel-name charset, strict qrcode/status query whitelist, per-mutation audit log - 20 handler tests (forwarding, read-back branches, W8 404, leader 403, kube 503, upstream status mapping, query whitelist) - Contract doc (docs/design/worker-channels-api.md) + usage section (EN/zh-cn) under Worker channel configuration Note: L2 write authorization at the middleware rides on the worker-scoped update policy (separate PR); until it lands, L2 PUTs are denied by the middleware and only L1 reaches the handler.
9abdb96 to
620ad0f
Compare
Worker channel configuration proxy (GET/PUT /api/v1/workers/{name}/channels...)
Summary
Connecting a worker to a messaging channel (QQ / Matrix / DingTalk / Feishu / WeChat / ...) has always required host access: SSH in,
docker exec, hand-editchannelsin the worker'sagent.json, watch for the hot-reload log line. The worker's qwenpaw app port (8088) is not published to the host network, so no user — L1 admin or L2 team human — has any in-band path to this configuration.The worker's qwenpaw app already exposes the complete channel-configuration HTTP API (
/config/channels...— the same surface the QwenPaw console Channels page drives, present in production 2.0.1). This PR adds the Controller proxy in front of it, so channel onboarding becomes a graphical operation from the workbench plugin / dashboard: pick a worker, fill the form, save, verify with health — no SSH, nodocker exec, no hand-editedagent.json.What's included
agentteams-controller/internal/server/worker_channels.go(new) — 10 fixed-path endpoints, fixed forwarding to the worker's qwenpaw channel API;{channel}charset-validated pre-dial; qrcode/status query restricted to atoken-only whitelist; PUT body forwarded verbatim with empty-body400pre-dial.agentteams-controller/internal/server/http.go— route registration (adjacent block, no other routes touched).agentteams-controller/internal/server/worker_channels_test.go(new) — 21 handler tests.docs/design/worker-channels-api.md— design contract (endpoint table, read-back semantics, role matrix).docs/usage/resource-management.md(+docs/zh-cn/...) — "Worker channel configuration (proxy)" section with the role-permission matrix.Data boundary
agent.jsonand hot-reloads the channel — no worker restart. Upstream400/404/409/422pass through verbatim (the404doubles as the version gate for a qwenpaw build without the router). An empty body is rejected400before the dial — upstream would treat it as an empty config and wipe the saved channel.X-AgentTeams-MinIO-Persistedheader (true/false/skipped): after a200, the Controller reads the MinIO baseline (agents/{name}/.../agent.json) up to 3× (2s apart) and canonical-JSON-compares the channel block against what was saved. The worker'spush_loopremains the single writer of the baseline; the header only observes. This exists because manual local-only edits have left the MinIO baseline stale in production, and a subsequent rebuild'smirror_allthen overwrote the live config with the old baseline — a real credential-loss incident. The header surfaces that gap in-band; the response body stays verbatim.403(the middleware's workerActionUpdatepolicy is same-team for leaders, so the handler is the real enforcement point); cross-team and standalone-worker access hides as404(never403) so the endpoint cannot be used to probe which workers exist or which team owns them.X-Agent-Idthe app resolves the active agent from config — the worker's own agent. No header plumbing needed.503before any worker lookup (existence not probed).Tests
worker_channels_test.go): verbatim forwarding oflist/types/schemas/single; PUT body forwarding + read-backtrue(canonicalization makes the comparison field-order-independent — asserted with a deliberately reordered baseline); read-back converging on the second attempt (simulatedpush_looplag); missing baseline →false; no storage client →skipped; empty body400with zero dials; invalid channel names (../qq,QQ,a.b,x/y) →400with zero dials; cross-team404(no dial); same-team L2 read200and handler-level PUT200(the middleware dependency is documented in the test); team-leader mutation403+ read200; kube mode503(no dial); unknown worker404; standalone worker hidden from L2 humans / visible to admin; upstream404passthrough verbatim (version gate); upstream500→502;restart/conflict-checkmethod+path+body forwarding; qrcode/status query whitelist (missing token400, unknown param400, valid token forwarded escaped).go test ./...on themainbaseline: all green except one pre-existing environment failure ininternal/executor(the test sandbox lacks theunzipbinary; CI has it) — untouched by this diff.gofmt/go vetclean.Related
404anti-probing, handler-enforced leader read-only);authorizer.gois untouched by this PR, so there is no shared-hunk conflict with either.docs/design/worker-channels-api.md.Worker 频道配置代理(GET/PUT /api/v1/workers/{name}/channels...)
摘要
Worker 接消息频道(QQ / Matrix / 钉钉 / 飞书 / 微信 …)此前必须宿主机操作:SSH →
docker exec→ 手改agent.json的channels段 → 盯热加载日志。Worker 的 qwenpaw app 端口(8088)未发布到宿主网络,L1 admin 和 L2 团队用户都没有带内配置路径。Worker 的 qwenpaw app 已完整暴露频道配置 HTTP API(
/config/channels...——与官方 console Channels 页同源,生产 2.0.1 已具备)。本 PR 在其前加 Controller 代理,频道接入变成图形化操作(工作台插件 / dashboard):选 worker、填表单、保存、health 验证——无 SSH、无docker exec、无手改agent.json。包含内容
agentteams-controller/internal/server/worker_channels.go(新)— 10 个固定路径端点,固定转发到 Worker 的 qwenpaw 频道 API;{channel}拨号前字符合入表校验;qrcode/status 查询仅允许token白名单;PUT body 原样转发、空 body 拨号前400拒。agentteams-controller/internal/server/http.go— 路由注册(相邻块,不动其他路由)。agentteams-controller/internal/server/worker_channels_test.go(新)— 21 个 handler 测试。docs/design/worker-channels-api.md— 设计契约(端点表、读回语义、角色矩阵)。docs/usage/resource-management.md(+ zh-cn)—「Worker channel configuration (proxy)」节,含角色权限矩阵。数据边界
agent.json→ 热加载免重启。上游400/404/409/422原样透传(404兼作无此路由的 qwenpaw 构建的版本门)。空 body 拨号前400拒——上游会把空对象当配置抹掉已存频道。X-AgentTeams-MinIO-Persisted头(true/false/skipped):200后 Controller 读 MinIO 基线(至多 3 次、间隔 2s),canonical-JSON 比对频道块与已存值。push_loop仍是基线唯一写者,该头只观测。动因:生产上手工只写本地曾使 MinIO 基线停旧,后续重建的mirror_all用旧基线覆盖了活配置——真实的凭据丢失事故。该头把这类缺口带内暴露;响应 body 保持原样。403(middleware 对 Leader 的 workerActionUpdate策略是同团队放行,handler 才是真强制点);跨团队与独立 worker 一律404(从不403),端点不可用于探测 worker 存在性或所属团队。X-Agent-Id时 app 从配置解析活动 agent = worker 自身,无需 header 传递。503(不探测存在性)。测试
worker_channels_test.go):list/types/schemas/single原样透传;PUT body 转发 + 读回true(canonical 化使比对与字段顺序无关——用刻意乱序基线断言);读回第 二 次收敛(模拟push_loop滞后);基线缺失 →false;无存储客户端 →skipped;空 body400零拨号;非法频道名(../qq、QQ、a.b、x/y)→400零拨号;跨团队404(不拨号);同团队 L2 读200与 handler 级 PUT200(middleware 依赖在测试中注明);团队 Leader 写403+ 读200;kube 模式503(不拨号);未知 worker404;独立 worker 对 L2 隐藏 / 对 admin 可见;上游404原样透传(版本门);上游500→502;restart/conflict-check方法+路径+body 转发;qrcode/status 查询白名单(缺 token400、未知参数400、合法 token 转义后转发)。main基线全量go test ./...:除internal/executor一处预存环境失败(测试容器缺unzip,CI 有)外全绿,与本 diff 无关。gofmt/go vet干净。相关
404防探测、handler 强制 Leader 只读);本 PR 不碰authorizer.go,与两者均无共享 hunk 冲突。docs/design/worker-channels-api.md。) — the channel permission boundary and secret contract follow that issue; version contract in its §11.