Skip to content

feat(controller): add worker channel configuration proxy endpoints - #1219

Open
LUOSENGWA wants to merge 1 commit into
agentscope-ai:mainfrom
LUOSENGWA:feat/worker-channels-api
Open

feat(controller): add worker channel configuration proxy endpoints#1219
LUOSENGWA wants to merge 1 commit into
agentscope-ai:mainfrom
LUOSENGWA:feat/worker-channels-api

Conversation

@LUOSENGWA

@LUOSENGWA LUOSENGWA commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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-edit channels in the worker's agent.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, no docker exec, no hand-edited agent.json.

GET  /api/v1/workers/{name}/channels                       all channel configs
GET  /api/v1/workers/{name}/channels/types                 channel name list
GET  /api/v1/workers/{name}/channels/schemas               per-channel form schemas (UI render driver)
GET  /api/v1/workers/{name}/channels/{channel}             single channel config
PUT  /api/v1/workers/{name}/channels/{channel}             update (body = full channel config)
GET  /api/v1/workers/{name}/channels/{channel}/health      channel health / connection state
GET  /api/v1/workers/{name}/channels/{channel}/qrcode      QR auth (wechat / dingtalk scan)
GET  /api/v1/workers/{name}/channels/{channel}/qrcode/status?token=
POST /api/v1/workers/{name}/channels/{channel}/restart     stop/start channel (no agent restart)
POST /api/v1/workers/{name}/channels/{channel}/conflict-check   same-credential agent collision

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 a token-only whitelist; PUT body forwarded verbatim with empty-body 400 pre-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

  • Fixed-path forwarding only, never a generic reverse proxy — the attack surface stays bounded to the 10 documented qwenpaw endpoints, the same discipline as the checkpoint and approval worker proxies.
  • PUT is the qwenpaw-authoritative write path: upstream validates the body against the channel's pydantic model, persists into agent.json and hot-reloads the channel — no worker restart. Upstream 400/404/409/422 pass through verbatim (the 404 doubles as the version gate for a qwenpaw build without the router). An empty body is rejected 400 before the dial — upstream would treat it as an empty config and wipe the saved channel.
  • Read-back validation → X-AgentTeams-MinIO-Persisted header (true/false/skipped): after a 200, 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's push_loop remains 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's mirror_all then 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.
  • Layered boundary: admin any worker; L2 humans own teams only (middleware team check, then handler re-check); team leaders are read-only — mutations 403 (the middleware's worker ActionUpdate policy is same-team for leaders, so the handler is the real enforcement point); cross-team and standalone-worker access hides as 404 (never 403) so the endpoint cannot be used to probe which workers exist or which team owns them.
  • Credentials are not masked: scoped callers can only reach agents in their own teams, and the connect form needs the stored values to round-trip unchanged (a masked form cannot re-save without clobbering the secret). L1 sees all workers, consistent with its existing worker-management surface.
  • Global (non-agent-scoped) upstream path: a worker container runs a single qwenpaw agent profile; without X-Agent-Id the app resolves the active agent from config — the worker's own agent. No header plumbing needed.
  • Embedded mode only: worker addressing = effective container prefix + system-wins console port (default 8088), the same chain the container is created with. Kube mode → uniform 503 before any worker lookup (existence not probed).
  • Audit log: every successful mutation logs worker, upstream path, caller (role-qualified) and the read-back outcome.
  • Draft status: the L2 human write path (PUT/restart/conflict-check) requires the worker-scoped update policy of feat(controller): allow L2 humans to update worker skills within their teams #1212 at the middleware; until it lands, L2 writes are denied there and only L1 reaches the handler. Everything else — all L1 paths and all L2 reads — is fully functional on this PR alone.

Tests

  • 21 handler tests (worker_channels_test.go): verbatim forwarding of list/types/schemas/single; PUT body forwarding + read-back true (canonicalization makes the comparison field-order-independent — asserted with a deliberately reordered baseline); read-back converging on the second attempt (simulated push_loop lag); missing baseline → false; no storage client → skipped; empty body 400 with zero dials; invalid channel names (../qq, QQ, a.b, x/y) → 400 with zero dials; cross-team 404 (no dial); same-team L2 read 200 and handler-level PUT 200 (the middleware dependency is documented in the test); team-leader mutation 403 + read 200; kube mode 503 (no dial); unknown worker 404; standalone worker hidden from L2 humans / visible to admin; upstream 404 passthrough verbatim (version gate); upstream 500502; restart/conflict-check method+path+body forwarding; qrcode/status query whitelist (missing token 400, unknown param 400, valid token forwarded escaped).
  • Full go test ./... on the main baseline: all green except one pre-existing environment failure in internal/executor (the test sandbox lacks the unzip binary; CI has it) — untouched by this diff. gofmt/go vet clean.

Related


Worker 频道配置代理(GET/PUT /api/v1/workers/{name}/channels...)

摘要

Worker 接消息频道(QQ / Matrix / 钉钉 / 飞书 / 微信 …)此前必须宿主机操作:SSH → docker exec → 手改 agent.jsonchannels 段 → 盯热加载日志。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)」节,含角色权限矩阵。

数据边界

  • 仅固定路径转发,非通用反代——攻击面限定在 10 个已文档化端点,与 checkpoint/approval worker 代理同纪律。
  • PUT = qwenpaw 官方写路径:上游按频道 pydantic 模型校验 body → 落 agent.json → 热加载免重启。上游 400/404/409/422 原样透传(404 兼作无此路由的 qwenpaw 构建的版本门)。空 body 拨号前 400——上游会把空对象当配置抹掉已存频道。
  • 读回校验 → X-AgentTeams-MinIO-Persistedtrue/false/skipped):200 后 Controller 读 MinIO 基线(至多 3 次、间隔 2s),canonical-JSON 比对频道块与已存值。push_loop 仍是基线唯一写者,该头只观测。动因:生产上手工只写本地曾使 MinIO 基线停旧,后续重建的 mirror_all 用旧基线覆盖了活配置——真实的凭据丢失事故。该头把这类缺口带内暴露;响应 body 保持原样。
  • 分层边界:admin 全量;L2 人类仅本团队(middleware 团队检查 + handler 复检);团队 Leader 只读——写操作 403(middleware 对 Leader 的 worker ActionUpdate 策略是同团队放行,handler 才是真强制点);跨团队与独立 worker 一律 404(从不 403),端点不可用于探测 worker 存在性或所属团队。
  • 凭据不脱敏:范围调用方只能到本团队 agent,且表单回写需要已存值(脱敏表单无法原样回存,会覆盖密钥);L1 看全量,与其既有 worker 管理面一致。
  • 全局(非 agent-scoped)上游路径:Worker 容器单 qwenpaw agent profile,无 X-Agent-Id 时 app 从配置解析活动 agent = worker 自身,无需 header 传递。
  • 仅 embedded 模式:worker 寻址 = 生效容器前缀 + system-wins console 端口(默认 8088),与容器创建同一链路。kube 模式 → 任何 worker 查找前统一 503(不探测存在性)。
  • 审计日志:每次成功变更记 worker、上游路径、调用方(带角色)、读回结果。
  • Draft 状态:L2 人类写路径(PUT/restart/conflict-check)依赖 feat(controller): allow L2 humans to update worker skills within their teams #1212 的 worker-scoped update policy(middleware 层);未合前 L2 写被 middleware 拒,仅 L1 可达 handler。其余功能(L1 全量 + L2 全部读)本 PR 独立完整可用。

测试

  • 21 个 handler 用例(worker_channels_test.go):list/types/schemas/single 原样透传;PUT body 转发 + 读回 true(canonical 化使比对与字段顺序无关——用刻意乱序基线断言);读回第 次收敛(模拟 push_loop 滞后);基线缺失 → false;无存储客户端 → skipped;空 body 400 零拨号;非法频道名(../qqQQa.bx/y)→ 400 零拨号;跨团队 404(不拨号);同团队 L2 读 200 与 handler 级 PUT 200(middleware 依赖在测试中注明);团队 Leader 写 403 + 读 200;kube 模式 503(不拨号);未知 worker 404;独立 worker 对 L2 隐藏 / 对 admin 可见;上游 404 原样透传(版本门);上游 500502restart/conflict-check 方法+路径+body 转发;qrcode/status 查询白名单(缺 token 400、未知参数 400、合法 token 转义后转发)。
  • main 基线全量 go test ./...:除 internal/executor 一处预存环境失败(测试容器缺 unzip,CI 有)外全绿,与本 diff 无关。gofmt/go vet 干净。

相关

@shiyiyue1102

Copy link
Copy Markdown
Collaborator

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 client_secret, app_secret, access_token, bot_token, passwords, and QR-login credentials require separate control. Please add an explicit FullAccess-like capability that an admin can grant or revoke for an L2 account. Responses should be generated according to that capability.

Even with FullAccess, the safer default API contract is:

  • return masked values or configured: true instead of plaintext secrets;
  • treat secret fields as write-only;
  • preserve an existing secret when it is omitted from an update;
  • return plaintext only through a separately authorized and audited operation, if plaintext reveal is genuinely required.

Please also use dedicated channel read, channel update, and sensitive-data actions instead of inheriting the generic Worker ActionUpdate permission.

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 conflict-check route, and the MinIO read-back timing should be reconciled with the Worker push-loop interval.


让本团队 L2 用户配置 Worker Channel 具有明确价值,普通配置变更不应该每次都依赖 Admin 或宿主机操作。但是,Channel 元数据和 Channel 凭据必须采用不同的权限边界。

默认 L2 应能查看和编辑本团队 Worker 的非敏感字段,例如 Channel 类型、地址、启用状态、展示配置、健康状态和重启操作。

client_secretapp_secretaccess_tokenbot_token、密码以及扫码登录凭据等敏感数据需要独立管控。建议增加由 Admin 授予和收回的 FullAccess 类 capability,并根据账号是否具备该 capability 决定接口响应。

即使具有 FullAccess,更安全的默认接口契约仍应是:

  • 返回脱敏值或 configured: true,而不是明文 Secret;
  • Secret 字段只写;
  • 更新时未提供 Secret 就保留原值;
  • 只有确实需要查看明文时,才通过独立鉴权和审计的接口返回。

请为 Channel 读取、Channel 修改和敏感数据访问定义独立 Action,不要继承通用 Worker ActionUpdate 权限。

在将本 PR 移出 Draft 前,请创建 GitHub Issue,说明角色/capability 矩阵、敏感字段分类、Secret 更新语义、二维码凭据处理和审计要求,并关联到本 PR,让社区参与讨论。

Issue 中还应确认 QwenPaw 版本契约:当前锁定的 QwenPaw 2.0.1 没有被代理的 conflict-check 路由;MinIO 回读时序也需要与 Worker push-loop 周期保持一致。

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.
@LUOSENGWA
LUOSENGWA force-pushed the feat/worker-channels-api branch from 9abdb96 to 620ad0f Compare September 10, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants