CLI 内部机制
本文是 hermes_cli/AGENTS.md(规则)的配套——这里放更长的解释。
更新管线
分阶段契约(plan → snapshot → apply → restart-per-kind → verify → report)以及每个阶段守护的字段失败,记录在 hermes_cli/AGENTS.md;用户可见行为(回执、--plan、快照模式)见更新。
systemd 粗暴重启回退等待 unit 的 TimeoutStopUSec 加 TimeoutStartUSec,外加 15 秒客户端余量。它在与重启相同的 manager scope 内读取目标 unit;初次尝试与重试都使用这个预算,包括被中断更新后的追赶重启。优雅 drain 之后的启动只使用启动预算加余量。缺失、不可解析或无穷大的阶段限制回退到该阶段 90 秒,让无人值守更新有界。systemctl 客户端超时不会取消 manager 的事务。自定义多命令停止链或 EXTEND_TIMEOUT_USEC 仍可能超出该估计;真正的超时仍是一次不完整的重启,而成功的命令仍需通过既有的服务健康与集群版本校验。原始数字 *USec 值单位是微秒,而格式化值使用 systemd 的固定单位,包括天、周、月、年。合并超时被限制在有符号 32 位毫秒轮询原生上限之下(带取整余量),因此异常长的 unit 限制不会让子进程轮询溢出。零/未知/无穷大的阶段限制使用有界回退。这不改变活动轮次的 drain 设置。
进程身份:绝不从 argv 子串推断
约 10 个集群更新问题背后的 bug 类别(#90778、#87594、#78089、#76129、#91964……):用 "serve" in cmdline 之类给进程分类。kanban --preserve-cache 含有 "serve";一个标志值可能等于子命令(-m dashboard serve);被截断的 cmdline 会隐藏真正的子命令。规则:
- 使用规范匹配器:
gateway.status.looks_like_gateway_command_line(gateway 运行)、hermes_cli.update_cmd._hermes_holder_subcommand(任意 Hermes argv 的顶层子命令)。绝不手写 token 扫描。 - 标志集合必须从解析器派生(
_holder_value_flags()内省build_top_level_parser()),绝不手写列表——它们会漂移。 - 绝不从进程扫描中 blanket 排除祖先:当
/update作为 gateway 的子进程运行时,一个 gateway 祖先必须对暂停机制保持可见(#87594)。排除交互式祖先,为 gateway 形态的祖先开例外。 - 对完整 cmdline 匹配;只在显示时截断(#78089)。
- 在加入任何新的扫描启发式之前,先读 #92091——gateway 控制 socket 取代扫描成为主要协调机制;扫描是为旧/崩溃进程准备的回退层。
皮肤引擎——皮肤定制什么
| 元素 | 皮肤键 | 使用者 |
|---|---|---|
| Banner 面板边框 / 标题 / 小节标题 / dim / 正文 | colors.banner_border、banner_title、banner_accent、banner_dim、banner_text | banner.py |
| 响应框边框 | colors.response_border | cli.py |
| Spinner 面孔(等待 / 思考) | spinner.waiting_faces、spinner.thinking_faces | display.py |
| Spinner 动词 / 翼片(可选) | spinner.thinking_verbs、spinner.wings | display.py |
| 工具输出前缀 / 每工具 emoji | tool_prefix、tool_emojis | display.py → get_tool_emoji() |
| Agent 名 / 欢迎语 / 响应标签 / 提示符 | branding.agent_name、welcome、response_label、prompt_symbol | banner.py、cli.py |
内置皮肤(hermes_cli/skin_engine.py 中的 _BUILTIN_SKINS):default(经典金/卡哇伊)、ares(深红/古铜,带自定义 spinner 翼片)、mono(灰度)、slate(冷蓝)。新增内置皮肤就是加一个字典项 {"name", "description", "colors", "spinner", "branding", "tool_prefix"}。用户皮肤是带相同键的 ~/.hermes/skins/<name>.yaml,用 /skin <name> 或 display.skin: <name> 激活;完整 YAML 模板见皮肤与主题用户指南。
Profile:多实例支持
Hermes 支持 profile——完全隔离的实例,各自有自己的 HERMES_HOME(配置、API key、记忆、会话、skill、gateway)。对单 profile 命令(hermes -p x <cmd>),hermes_cli/main.py 中的 _apply_profile_override() 在任何模块导入之前设置 HERMES_HOME,因此每个 get_hermes_home() 引用都按活动 profile 划定范围。多路复用 gateway 与 Desktop/dashboard 的 serve 后端则改为从一个进程服务多个 profile:活动 profile 是按 activity 绑定的 contextvar 覆盖,os.environ["HERMES_HOME"] 保持为启动 profile 的,而从 home 派生的模块级常量冻结为启动 profile(见Gateway 内部机制 § 多路复用 profile)。Profile 操作以 HOME 为锚(_get_profiles_root() 返回 Path.home() / ".hermes" / "profiles",而不是 get_hermes_home() / "profiles"),因此无论哪个 profile 处于活动,hermes -p coder profile list 都能看到所有 profile——这是有意为之。Profile 安全的编码规则在根 AGENTS.md;多路复用机密 scope 规则在 gateway/AGENTS.md。