1. 飞书里点下「Always」那一刻Hermes Agent 到底做了什么Hermes Agent 是一个把大模型能力接到聊天平台里的 Agent 框架飞书、Telegram 都能挂。它最实用的一个设计是当模型准备执行一条有副作用的命令时不会闷头就跑而是先在聊天窗口里弹出一排审批按钮让你人工点一下。这四个按钮就是 Allow Once、Session、Always、Deny对应「本次放行」「本会话放行」「永久放行」「拒绝」。如果你正在做飞书机器人、想让 Agent 帮你跑 git、ls、cat 这类命令又不想它哪天手滑执行 rm -rf那这套审批机制就是你的安全阀。而它的核心实现就藏在feishu.py第 1906 到 1909 行这短短几行里。很多人第一次看到这段代码会懵四个按钮怎么映射到四种权限audit log 又是在哪一步落盘的exec_allowlist 和按钮是什么关系这篇就按「先看问题场景 → 再讲 TaoToken 前置 → 给出可复制配置 → 验证请求 → 排错 → 收尾」的顺序走一遍。我会把feishu.py:1906-1909的分支逻辑逐行拆开给你能直接抄的审批回调配置片段再带你用 exec_allowlist 验证按钮行为、确认日志真的写进了磁盘。全程本地可复现不需要你先把整个 Agent 跑通才看得懂。先说清楚适合谁一是已经在用 Hermes Agent、被「Always」按钮坑过的人二是想给自己的飞书机器人加审批交互的开发者三是想搞明白「按钮回调 审计日志 命令白名单」这套组合拳怎么落地的人。如果你只是想让模型跑起来聊天那这篇偏底层可以先收藏。2. TaoToken 前置统一 Key 与 API 通道别让审批链路卡在鉴权上在拆按钮逻辑之前得先把模型调用这条链路铺好。Hermes Agent 的审批按钮之所以会弹出来前提是模型已经能正常对话、能发起工具调用。如果 Key 或 Base URL 配错你连按钮都见不到直接 401 就结束了。所以这一步是后面所有验证的地基。我这边统一用 TaoToken 来做模型通道。它的作用是给你一个兼容 OpenAI 风格的 API 入口把 Key 和 Base URL 收敛成一套飞书场景、本地调试、CI 里都能复用同一个通道不用每个平台各配一份。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 注意这个 API 地址后面不加任何查询参数。具体要准备三样东西这也是后面所有配置里反复出现的「三件套」配置项取值说明Base URLhttps://taotoken.net/apiOpenAI 兼容入口末尾不要带斜杠API Key在控制台生成形如sk-...只显示一次记得存好Model ID按你订阅的模型填例如claude-sonnet-4-5之类以控制台实际列表为准Key 的生成入口在控制台的 API Keys 页面地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。生成之后先别急着填进 Hermes建议用一条 curl 单独验证通道通不通curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-5, messages: [{role: user, content: ping}], max_tokens: 16 }返回里能看到choices[0].message.content就说明通道没问题。这一步很关键因为 Hermes 的审批按钮依赖模型能正常返回工具调用意图通道不通的话你后面调feishu.py的按钮逻辑会一直以为是代码问题其实是 Key 没生效。如果你打算长期跑编码类 Agent、频繁触发审批可以考虑用 Coding Plan 那套订阅入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。它更适合高频调用场景省得每次都在额度上卡壳。模型对话的在线调试入口则是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 想先确认某个 Model ID 能不能用直接在那里试一句最省事。把这三件套记牢Base URL、Key、Model ID。后面无论是 Hermes 的 config.yaml还是 Claude Code 的 settings.json填的都是这三样只是字段名不同。3. 可复制配置feishu.py:1906-1909 分支逻辑与审批回调片段现在进入正题。feishu.py第 1906 到 1909 行是飞书审批按钮回调的核心分支。它的结构大致是这样收到按钮点击事件后先取出button_id和user_id写一条 audit log然后根据button_id决定这次命令的放行范围。四个按钮对应四种语义allow_once只放行当前这一条命令执行完权限即失效。allow_session在当前会话生命周期内同类命令不再弹窗。allow_always写入持久化白名单之后一直放行这是最危险的一个。deny直接拒绝命令不执行同时记录拒绝原因。用伪代码还原这四行的分支意图方便你对照自己仓库里的真实实现# feishu.py:1906-1909 语义还原 def on_approval_button(button_id, user_id, command, session_id): audit_log(user_iduser_id, button_idbutton_id, commandcommand, tsnow()) if button_id allow_once: grant(command, scopeonce, session_idsession_id) elif button_id allow_session: grant(command, scopesession, session_idsession_id) elif button_id allow_always: require_admin_confirmation(user_id) # 二次确认别省 grant(command, scopealways) else: # deny reject(command, reasonuser_denied)注意allow_always那一步我在还原里特意加了require_admin_confirmation。原实现里如果没做二次确认强烈建议你补上因为「永远批」一旦写进白名单后面再想收回就得手动改配置文件。这是踩过的坑早期版本里 Always 是直接生效的结果有人点了之后Agent 连着跑了一串危险命令都没再拦。接下来是配置层。Hermes 的配置在~/.hermes/config.yaml和审批、沙箱、白名单相关的字段集中在这几块# ~/.hermes/config.yaml terminal: backend: docker # 危险命令丢进沙箱影响面小 security: tirith_enabled: true # 自动拦截高危命令 exec_allowlist: - git status - ls - cat - pwd exec_denylist: - rm -rf - curl|sh - sudoexec_allowlist和审批按钮是互补关系白名单里的命令直接放行不弹按钮白名单外、又不在 denylist 里的命令才会触发那四个按钮。所以你想复现按钮行为就得先确保测试命令不在 allowlist 里否则你点不到按钮会误以为回调没生效。如果你用的是 Claude Code 那套配置字段名不一样但三件套还是那三样。settings.json里长这样{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-5 } }飞书侧的审批回调需要在飞书开放平台把卡片按钮的 action 指向你的回调地址payload 里带上button_id、command、session_id。回调处理函数就复用上面on_approval_button的逻辑。这样按钮点下去audit log 先落盘再决定放行范围顺序不能反——先授权后记日志出了事你查不到是谁批的。4. 验证请求跑一条危险命令看四个按钮和日志落盘配置写完得验证。第一步先确认 Hermes 能起来uv run python hermes启动后在飞书里给机器人发一条会触发审批的命令比如让它执行rm -rf /tmp/hermes-test测试目录别拿真实数据试。正常情况下聊天窗口会弹出那张带四个按钮的卡片。点Allow Once命令执行一次再发同样的命令应该再次弹窗说明 once 作用域没持久化。点Session同一会话内再发同类命令不再弹窗。点Deny命令不执行返回拒绝提示。这三个行为验证完基本能确认feishu.py:1906-1909的分支是通的。然后是 audit log。日志默认落在 Hermes 的数据目录下具体路径看你的配置常见的是~/.hermes/logs/audit.log。点完按钮后用这条命令确认落盘tail -n 20 ~/.hermes/logs/audit.log你应该能看到类似这样的记录{ts:2025-01-01T10:00:00Z,user_id:ou_xxx,button_id:allow_once,command:rm -rf /tmp/hermes-test,scope:once}每条记录都带user_id、button_id、command、scope这就是审计的四个关键字段。如果日志里只有 command 没有 user_id说明你的audit_log调用漏了参数回去补上。再验证 exec_allowlist。把git status加进 allowlist重启 Hermes发一条git status这次应该直接执行、不弹按钮。把rm -rf加进 denylist发一条rm -rf /tmp/x应该直接被拦连按钮都不弹。这两步验证完白名单和黑名单的优先级就清楚了denylist 优先于 allowlistallowlist 优先于按钮弹窗。最后确认模型通道没拖后腿。如果按钮弹出来了但点完没反应先看 Hermes 的日志里有没有模型调用报错。用前面那条 curl 再测一次通道确认 Base URL 和 Key 还有效。通道、按钮、日志三者都通这条链路才算完整。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节按真实会撞到的报错来对。每个报错我都给现象、根因、修法。401 Unauthorized。现象是 Hermes 启动后模型调用直接失败飞书里连按钮都看不到。根因九成是 Key 没生效或 Base URL 写错。检查config.yaml或settings.json里的 Key 是不是完整、有没有多余空格Base URL 是不是https://taotoken.net/api末尾别加斜杠也别加/v1具体以文档为准。修完用 curl 复测一次。local proxy failed。现象是请求发不出去日志里出现连接失败。根因通常是本地网络环境或代理配置干扰了出站请求。检查你的环境变量里有没有残留的HTTP_PROXY、HTTPS_PROXY有的话先清掉再试。Hermes 走的是标准 HTTPS 出站不需要额外代理层。reading choices 报错。现象是模型返回了内容但解析choices字段时报错常见于返回体不是预期的 OpenAI 结构。根因可能是 Model ID 填错或者通道返回了错误对象。先确认 Model ID 和控制台列表一致再用 curl 看原始返回体里有没有choices字段。如果返回的是{error: ...}那就是通道侧的问题检查 Key 权限。OAuth 相关报错。现象是飞书侧回调鉴权失败按钮点了没反应。根因是飞书应用的 OAuth 配置或回调地址没对齐。检查飞书开放平台里的事件订阅地址、卡片回调地址是否和你的服务一致token 校验是否通过。这块和模型通道无关别混在一起查。排查顺序建议固定成先 curl 验通道 → 再看 Hermes 启动日志 → 再查飞书回调 → 最后看 audit log。按这个顺序走能避免在错误的方向上浪费时间。如果通道侧需要重新生成 Key去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 处理接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。6. 把审批链路收进一套 Key长期跑更省心feishu.py:1906-1909这四行本质是把「一次授权」拆成了四个作用域再用 audit log 把每次点击钉在磁盘上。你复现的时候重点盯三件事按钮分支对不对、日志有没有 user_id、allowlist 和 denylist 的优先级是否符合预期。这三件都对了审批链路就稳了。模型通道这边用 TaoToken 把 Base URL、Key、Model ID 收敛成一套飞书、本地、CI 复用同一个入口省得每换一个平台就重配一遍。长期跑编码类 Agent 的话Coding Plan 那套订阅更适合高频场景入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。想先在线试模型用 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 最快。最后留个实用习惯每次改完config.yaml先跑一条 allowlist 里的命令确认不弹窗再跑一条 denylist 里的确认被拦最后跑一条两者都不在的确认弹按钮。三步走完配置才算真正生效。