Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

终端环境提供者插件

Hermes 通过一组可插拔的终端后端来执行 Shell 命令。内置的后端(本地、Docker、Singularity、Modal、Daytona、Vercel Sandbox、SSH)位于核心代码库的 tools/environments/ 目录下。而第三方沙箱服务则通过插件的形式集成——这些独立的插件仓库会被安装到 ~/.hermes/plugins/ 目录中,用户可以通过 config.yaml 文件中的 terminal.backend 选项来注册这些插件,其使用方式与内置后端完全相同。

本页面的内容与 浏览器提供者插件 文档相对应——注册流程和作用域语义均保持一致。

提供者能控制哪些功能

已注册的后端会自动参与所有核心功能模块:

功能模块控制依据
命令分发(terminal、execute_code、文件工具)create_environment()
hermes setup 后端选择器display_name、description、setup_instructions()、post_setup()
仪表板终端后端选择器(用于检测状态)probe()
hermes status / hermes doctor 功能doctor_checks()
系统提示中的环境信息显示is_remote、env_description
跳过危险命令的审批流程skip_container_guards
容器路径/工作目录处理is_container
同步缓存文件的路径转换cache_path_base
从子进程中移除敏感信息strip_env_keys
每会话独立的沙箱隔离(container_persistent: false)session_isolated_when_nonpersistent
在 Provider 中声明这些标志即可彻底解决经典的“新后端缺失分类站点 N”问题——系统会从每个站点的注册表中查询信息,而不再依赖硬编码的站点名称列表。

最简版 Provider

from agent.terminal_env_provider import TerminalEnvironmentProvider


class AcmeBoxEnvironment:
    """Must satisfy the BaseEnvironment duck-typed contract."""

    def __init__(self, cwd, timeout, task_id):
        self.cwd, self.timeout, self.task_id = cwd, timeout, task_id

    def execute(self, command, timeout=None, **kwargs):
        ...  # run the command in the sandbox
        return {"output": "...", "exit_code": 0}

    def cleanup(self):
        ...  # tear down / detach


class AcmeBoxProvider(TerminalEnvironmentProvider):
    name = "acmebox"
    display_name = "AcmeBox"
    is_remote = True          # commands don't run on the host
    is_container = True       # container-style path/cwd semantics

    @property
    def description(self):
        return "Run commands in an AcmeBox cloud sandbox."

    @property
    def cache_path_base(self):
        return "~/.hermes"    # where synced cache files land, or None

    @property
    def strip_env_keys(self):
        return frozenset({"ACMEBOX_TOKEN"})

    def is_available(self):
        import importlib.util, os
        return (
            importlib.util.find_spec("acmebox") is not None
            and bool(os.getenv("ACMEBOX_TOKEN"))
        )

    def create_environment(self, *, cwd, timeout, task_id="default",
                           image=None, container_config=None, **kwargs):
        return AcmeBoxEnvironment(cwd, timeout, task_id)


def register(ctx):
    ctx.register_terminal_environment_provider(AcmeBoxProvider())
name: acmebox
version: 0.1.0
description: AcmeBox cloud sandbox terminal backend
kind: backend

启用该功能,选中它,然后运行:

hermes plugins enable acmebox
hermes config set terminal.backend acmebox

规则

  • 保留名称。与内置后端名称(local、docker、singularity、modal、managed_modal、daytona、vercel_sandbox、ssh)冲突的注册将被拒绝。插件仅用于扩展后端列表,绝不能替代内置后端。
  • create_environment 必须支持 **kwargs 参数,并对未知键值忽略不计——这种前向兼容机制使得函数签名可以演变,而不会影响旧版插件。
  • is_available() / probe() 方法必须高效。禁止进行任何网络调用,因为这些方法会在需求检查及界面渲染阶段执行。
  • 处处采用软失败机制。若某个提供者属性抛出异常,核心系统会将其视为默认行为(例如,抛出异常的 skip_container_guards 仍会保持审批功能开启状态)。请勿依赖异常来实现控制流逻辑。
  • 机密信息应放入 strip_env_keys 中。您的供应商令牌绝不能被模型生成的Shell命令读取;通过该机制,令牌会无条件地从所有启动的子进程中移除,其处理方式与内置的 MODAL_* / DAYTONA_API_KEY 类似。

环境对象契约

create_environment() 方法返回的对象需遵循与 tools.environments.base.BaseEnvironment 相同的鸭子型接口规范:

  • execute(command, timeout=None, ...) → {"output": str, "exit_code": int}
  • cleanup() —— 用于释放资源,会在会话终止或空闲状态回收时被调用
  • 可选功能:类似内置云后端的持久化钩子机制 建议继承 BaseEnvironment 类(这样即可复用通用的文件同步及后台进程机制),但并非强制要求。

会话隔离机制

如果您的沙箱是通过名称来恢复的(即后端重新连接到一个持久化的虚拟机),请将 session_isolated_when_nonpersistent 设置为 True。在同时设置 terminal.container_persistent: false 的情况下,每个会话都将拥有独立的沙箱标识,而不会共享同一个沙箱——否则,两个独立的临时运行实例可能会同时连接同一个正在运行的虚拟机,从而导致该虚拟机被意外删除。