Skip to content

✨ agrctl: 管理 Agent / 部门 / 项目 / LLM / Agent 后端,写操作分流审批,多端实时刷新 - #67

Merged
CodFrm merged 39 commits into
mainfrom
2026-09-22-agrctl-resource-management
Sep 25, 2026
Merged

CodFrm merged 39 commits into
mainfrom
2026-09-22-agrctl-resource-management

Conversation

@CodFrm

@CodFrm CodFrm commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

变更说明 / Summary

agrctl 从 hook shim + send 扩成配置管理 CLI:顶层动词 list / get / create / update / delete / help / send,覆盖 Agent、部门、项目、LLM 提供方、模型(提供方/ModelID)、Agent 后端 六类资源(spec:docs/specs/2026-09-22-agrctl-resource-management.md)。写操作按调用方分流审批,写入后桌面端 / 其它桌面端 / Web 控制台实时刷新。

配套的 server 改动:agentre-hub/agentre-server#26(server 执行者 + 控制台作答,pin 本分支 d5df824)。合并顺序:先合本 PR,再合 server#26。

调用方与审批

调用方 执行者 审批
桌面主机上的 Agentre 会话(含控制台派发到该桌面) 本机桌面端 会话对话流里一张 ctl 审批卡,桌面端与控制台都能答
桌面端派发到 agentred 的会话 agentred ctl 代理 → 隧道回拥有会话的桌面端 卡在桌面端该会话里
控制台派发到 agentred 的会话 agentred ctl 代理 → server agentred 出卡、控制台经 relay 作答,批准后才提交 server
外部调用(非 TTY,或 TTY 但环境里带 agent CLI 标记;无会话 token) 本机桌面端 桌面端全局审批弹窗(调用方信息、排队、删除危险形态、4 分钟自动拒绝)
人工(TTY 且无 agent CLI 标记) 本机桌面端 直接执行,delete 终端再问 y/N
agentred 主机上无会话 token — 退出码 1 并说明

主要内容

  • 契约:Ctl* 消息并入 pkg/wire/proto/agentre/wire/wire.proto(单一 schema);新增 relay 方法 toolApproval.answer,协议 0.3.0 → 0.4.0;新增正式事件对 tool_approval_requested / tool_approval_resolved(oneof 33/34);RuntimeRunRequest 带桌面会话 token(26/27);CtlBackend 带 token_state(set/unset/unknown)、sync_id、device_fingerprint。
  • 桌面端:ctl_svc 读写经现有服务层(不绕校验与同步通知);会话级 HMAC token 注入 CLI 子进程;写工具审批卡在挂起时就进本轮 transcript 落库并发 durable 帧(控制台待审批期间可见可答),决议原位更新;五类资源写入统一发 config:changed,页面同时订阅 sync:applied。
  • agentred:随包带 agrctl,启动时安装 agrctl + ctlskill;gateway 上的 ctl 代理按会话归属转发;绑在本机的 OpenClaw 后端 --token 在本地写入。
  • agentre-ui:CtlApprovalCard / CtlChangeList,桌面与控制台共用;密钥字段只显示「已更新(值不显示)」。
  • 修正轮 2:伪终端里带 AI_AGENT / CLAUDECODE / CLAUDE_CODE_ENTRYPOINT / GEMINI_CLI / OPENCODE / CODEX_SANDBOX 的调用判为外部;纯空白 --token 在规划阶段即拒(exit 2);--device <名字> 本地找不到时由后端向 server 拉取并收养(不再依赖先开「远端」设置页),本机名解析为本机;agentred 上 --device <本机账号名> 认本机;插话分段后旧消息里的卡的决议写回所属消息;留有旧桌面握手文件的 agentred 主机给出 agentred 提示。
  • 修正轮 3:openclaw 网关地址不合法时 agrctl 给出具体原因(… (--config openclawGatewayUrl),GUI 只见原因);删除 Agent 后端的确认文案如实说明「引用它的执行目标会一并删除,Agent 本身保留」(agentre-ui,桌面与控制台共用)。
  • 已合入 origin/main(2996e334),与 8cc0054 的 Hermes/OpenClaw 审批卡共存:main 的 unsupported_request_notice 保留 oneof 32,本分支事件对为 33/34,生成物由 pnpm run proto:generate 重生成,buf lint / buf breaking 通过。

验证 / Verification

门禁(coding.lan,HEAD 727f6c5):

make test-backend    → exit 0(186 包 ok)
make test-frontend   → exit 0(tsc + vitest 454 文件 / 5528 测试)
make lint            → exit 0(0 issues)
pnpm run proto:check → exit 0

真机验证(报告:e2e/scratch/2026-09-22-agrctl-resource-management/report.md,本地证据未入库)——本轮 server + 本轮 agentred(coding.lan)+ 两个隔离桌面端 + 真实 Claude Code 会话:

# 需求 结论
V1 人工 TTY 直接执行;delete y/N holds
V2 外部调用走全局审批弹窗(调用方、危险形态、拒绝 exit 1) holds(修正轮 2 补观测:排队「第 1 个,共 2 个待审批」、Esc 即拒绝、4 分钟整自动拒绝 approval timed out、失焦时调用系统通知;macOS 横幅本身未目视)
V3 桌面主机会话出卡,桌面端作答;超时 approval timed out holds(正式包隔离启动已观测自装 agrctl 与 skill;agent 在正式包会话里自行发现 agrctl 未观测——插件在 Claude 全局设置默认关闭、按会话开启)
V4 桌面端拥有的会话,控制台也能答 holds(首轮 does not hold,经修正轮修复;合入 main 后复验)
V5 桌面端派发到 agentred,卡在桌面端 holds
V6 控制台派发到 agentred,控制台作答后才提交 server holds(合入 main 后复验)
V7 agentred 主机无会话 token → exit 1 并说明 holds(修正轮 2 在真实 agentred 主机 coding.lan 上复验,含 09-20 遗留的旧桌面握手文件)
V8 本机 / 第二台桌面 / 控制台实时刷新 holds(TTY、外部弹窗、agentred 会话三条路径均不刷新页面观测;修正轮 2 只起了一台桌面端,第二台桌面在首轮观测)
V9 非 TTY 裸 --api-key exit 3;卡片与日志无明文 holds
V10 list / get 直接执行 holds
V11 agentred 附带并安装 agrctl + ctlskill holds
R2 修正轮 2:pty 标记判外部 / 空白 token / --device 名字与本机名 / agentred 本机名 / 旧握手提示 / server 删除级联 holds
R2 修正轮 2:server 多行写一个事务、插话分段后作答写回 运行期未观测(成功路径无外部差异;真实 CLI 下插话要等在途工具返回才分段,场景不出现),单测覆盖
R3 修正轮 3:server 拒不合规 openclaw(出卡前、说明原因),合规的桌面端即时落地;桌面具体报错;删除后端新文案 holds

已知限制(未在本 PR 修)

  • --env 值按产品决定保持明文(审批卡、get/list、会话命令行里都可见)。
  • codex 以 --dangerously-bypass-approvals-and-sandbox 运行时不设 CODEX_SANDBOX,也没有其它可用标记,此时它在伪终端里仍被判为人工调用。
  • agentre-ui 表单把 *.localhost 视为本地、允许 ws://,Go 侧不认(既有),此时 GUI 显示英文原因。
  • server 的 openclaw 校验是桌面端规则的逐条复刻,目前一致,但没有跨仓对齐守卫(要做需把规则移进共享 pkg/ 并升 pin)。

截图 / Screenshots

审批卡、外部审批弹窗、控制台作答截图在本地验证证据目录(e2e/scratch/…/screenshots、console/screenshots),未随 PR 上传。

自检 / Checklist

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

- /ctl/v1/resources 经服务层网关读 agent/部门/项目/提供方与模型/后端,出口统一掩码 API key、清空 token
- agenttool.TokenSigner 抽出 HMAC 签名;ctl_svc 用自己的实例签发/校验会话级 token
- CLI 子进程(claudecode/codex/piagent/acp)env 注入 AGENTRE_CTL_ENDPOINT/TOKEN
- 会话 token 只能读与 send,stop/answer-permission 返回 403;握手 token 按上报区分人工与外部
- 写操作暂返回 501,审批闸门由后续任务接入
执行者对照当前数据算变更清单(密钥只标 secret、级联删除写明数量),
会话调用出 toolKey=ctl 的 tool_approval 卡,外部调用挂 ExternalApprovals
队列(4 分钟超时即撤下),人工调用直接执行;写入全部经现有服务层。
挂起前以 102 + Agentre-Ctl-Approval 头告知去向,agrctl 据此打印
waiting for approval in session #N / in the Agentre desktop。
Add CtlApprovalCard/CtlChangeList (spec 2026-09-22 decision 6): toolKey="ctl"
tool_approval blocks render command + per-change op/kind/name/field diffs
instead of raw JSON, with secret fields never showing a value and cascade
delete counts. ToolApprovalCard dispatches to it for toolKey="ctl" and falls
back to the existing JSON card when toolInput doesn't parse as
CtlApprovalInput (legacy/corrupt payload) or the toolKey is anything else.
新增 tool_approval_requested / tool_approval_resolved 事件对(照 exec_approval
的全链路:agentruntime 事件与 JSON、proto oneof 32/33、protowire 转换、生成物)。
历史投影把落库的 tool_approval 块折成请求帧,终态再补一帧决议;agentre-ui 的
reduceFrames 把它们落成 tool_approval 块,ctl 走变更清单卡、其余走原卡,
不再是 unrecognized_block 提示条。RpcMethod 集合不变,协议版本不动。
RuntimeRunRequest 新增 desktop_ctl_token / desktop_session_id(26/27),远端 runtime
从 RunRequest.CtlCredentials() 填充;agentred 的 RuntimeHandlers.DesktopCtlSession
按 conversation_id 暴露,供后续 ctl 代理转回桌面端。仅加字段,RpcMethod 集合不变,
不升协议版本。
…带 agrctl

- 歧义示例只替换引起歧义的那一处,完整路径仍有歧义时换候选或用 id;--default-model 接受数字 id
- 纯数字的名字 id 未命中时按名字定位;可重复引用 flag 的空值是用法错误
- 裸密钥 flag 无 TTY 时先于连接给出 NEEDS TTY;create 的多余参数不回显(可能是密钥)
- get 的 baseUrl 与 flag 同名;--cli-path 移除,执行者 cliPath 只读(Out of scope)
- OpenClaw tokenSet 问绑定设备;toolApproval 作答须属于点名的会话
- 外部审批通知点击切回窗口;agentred-linux 连带编 agrctl,prebuilt 镜像带上
- 技能文档:护栏说明、agentred 主机报错、list -o json 措辞
…claw 后端本地写 token

- wire: CtlBackend.token_set(bool) 换成 CtlTokenState token_state(set/unset/unknown,0=该类型没有 token),新增 sync_id、device_fingerprint;RpcMethod 集合不变,不升协议号
- 桌面执行者:填 syncId/设备指纹;问不到绑定设备时报 unknown,不再拿本机读模型冒充
- agrctl:list backends 加 TOKEN 列(set / - / unknown),get 报 set/unset/unknown;help 与技能文档同步
- agentred:控制台会话更新绑在本机的 openclaw 后端带 token 时,token 写进设备本地凭据(复用 BackendCredentialHandlers,按 syncId),其余字段去掉 token 再交 server,审批卡只标 secret;绑在别处照旧由 server 拒绝;读后端时本机绑定的 openclaw 后端报本地 set/unset
…、空 --api-key 先判、config:changed 补漏

- agentred:控制台会话 create openclaw 后端带 --token、且不指定别的设备时,token 从交 server 的请求里拆出,写完按新 id 取回后端确认绑在本机后写进设备本地凭据;同一条 update 同时改 device 时不在本机写,交 server 照旧拒绝;本地写失败时如实说其它字段已保存
- agentred:控制台路径的级联删除按 server 变更清单附注还原子部门 / Agent 数量上审批卡;附注句式收进 blocks.CtlApprovalCascade.Note / ParseCtlCascadeNote,桌面端执行者同用
- agrctl:歧义示例里的密钥 flag 写成裸 flag(不再带审批卡上的 …);显式空的 --api-key= 在连接执行者之前判为用法错误(退出码 2)
- config:changed:项目 create 在初始成员写完后再发;CLI 路径覆盖写入也发 agent_backend
- AGENTS.md / catalog.go:ctl 契约在 wire.proto 的 Ctl* 消息里,不再写 ctl.proto
…的作答不再提交、toolApproval.answer 只收已登录账号、审批弹窗快照有序并补拉、--cascade=false 不再级联

- ctl_svc:审批通过后按批准时的数据重算再写(不回滚挂起期间别处的修改,目标已删则 404);批准之后脱离请求取消;桌面队列快照持锁推送、提供 Pending 补拉,超时那一刻抢先入账的批准照办
- 删部门 / Agent 的 config:changed 经 TxRunner 可测;删部门与重排只发一次;级联数量交给 department_svc.CascadeImpact
- agentred:会话凭证只看会话(跨主机派发 agent id 为 0);轮次收口后审批卡不再接受作答并立即回 409;toolApproval.answer 要求已登录账号的调用方;桌面 token 与交来它的桌面端成对记;收口的一轮从会话表放掉;去掉重复的 desktopCtl 表;桌面端那一跳失败时 /ctl/ 回 502 而不是 200 的 JSON-RPC 信封
- 审批卡措辞(资源名 / op / 脱敏命令行 / 结果行)收进 transcript/blocks 一份
- agrctl:--cascade=false / --force=false 不再打开;builtin / piagent 帮助示例可照抄
- 前端:审批弹窗挂载补拉队列、作答成功即摘除、失败只显示本地化提示;组织页写入进行中不被 config:changed 抢先重拉;SyncAppliedHost 只退自己的订阅
- runTurn 在 activeTurns.Store 之后才 initSegment 建累加器;runner 已起跑,写工具审批恰在 attachRuntime(回写 provider session id 等落库)期间到达时 beginApproval 对 nil 累加器 AddBlock 崩溃。累加器随 turnRun 一起建,initSegment 不再重建
- takeToolApprovals / 内存 overlay 已删,runtime_transcript、user_ask、use-chat-session 与 ChatSvc 接口注释改指 closeApprovals / 落库投影
- wire.proto:事件 oneof 里 main 的 unsupported_request_notice 保留 32,本分支的
  tool_approval_requested / tool_approval_resolved 顺延为 33 / 34(本分支字段未发布);
  协议版本仍为 0.4.0(main 未动 RpcMethod)。wire.pb.go 与 wire_pb.ts 用
  pnpm proto:generate 重生成。
- agentruntime event.go / event_wire.go:两侧新增事件(ToolApproval*、UnsupportedRequestNotice、
  execApprovalRequestedWire)并存。
- transcript projection:tool_approval 与 exec_approval 两个投影分支并存,两侧测试全部保留。
…自己的名字解析为本机;拉取+收养收进 remote_device_svc.EnsureFromAccount 与 ServerListDevices 共用
…_CODE_ENTRYPOINT/GEMINI_CLI/OPENCODE/CODEX_SANDBOX)时判为外部调用走审批弹窗,不再当人工直接执行
--token 此前没有 notEmpty 校验,纯空白值会绕过用法检查一路发到执行者;desktop
ctl_svc 的 mergeDoc 与 agentred ctlproxy 的 planConsoleWrite 又都用未 trim 的
值判「是否清除」,导致 server 端已经写完、本机 SetOpenClawToken 才在 trim 后
拒绝的半成品。现在 agrctl 侧对密钥 flag 的纯空白显式值直接判用法错误(退出码
2,不连接执行者);mergeDoc 与 planConsoleWrite 各自在收到 token 字段的唯一
入口 trim 一次,空白与显式清除同一路径,不再逐个消费点补判断。
…登录时 --device 解析按找不到报错而非未登录;skill 文档同步 agent 环境标记判定
…名/歧义与 server 同口径判本机、改绑设备只拆 openclaw 的 token;只有真拨号失败才回落 agentred 提示
…required (--config openclawGatewayUrl)),agrctl 不再只报「参数错误」
@CodFrm
CodFrm merged commit 34a159a into main Sep 25, 2026
6 checks passed
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.

1 participant