OpenHands Agent Canvas 架构详解:系统边界、运行模式、运行时服务与打包分发机制
本文基于 OpenHands 仓库中的 docs/architecture.md 展开,系统讲解 Agent Canvas 前端的技术定位与架构设计:它负责渲染与状态管理,但不执行 Agent 动作;主后端为 OpenHands Agent Server,可选搭配 ingress 代理、Automation Server 与 Cloud API。读完本文,你将理解 Agent Canvas 的模块划分、六种运行模式的落地实现(端口、进程与路由)、/server_info.runtime_services 的运行期服务发现机制,以及 npm 包的导出结构与 CI 质量门禁,从而能独立部署、二次开发或以库形式嵌入宿主应用。
一、系统边界:Agent Canvas 的职责与不做什么
Agent Canvas 是一个 React + TypeScript 前端,用于在本地、远程和托管环境中运行并监控 OpenHands Agent。它由 OpenHands 前端适配而来,直接与 OpenHands Agent Server 及相关自动化服务通信(docs/architecture.md)。
从架构文档看,它的职责边界是明确的:
- 渲染 Agent 会话、终端、浏览器、文件、设置与自动化(automation)界面;
- 管理会话、后端选择、设置、profile 与本地元数据等前端状态;
- 将 UI 操作翻译成 OpenHands Agent Server 的 API 调用;
- 同时以独立应用和库入口(library entrypoints)两种形态打包,供宿主应用嵌入。
它的非职责同样重要,这决定了部署时的信任模型:
- 不直接执行 Agent 动作(执行发生在 Agent Server 端);
- 不提供沙箱或工作区隔离层;
- 不在配置的后端之外托管 LLM 提供商凭据;
- 没有 automation backend 时不运行定时或事件触发的自动化。
这个"前端只做翻译与呈现"的边界,在源码中对应一套后端注册与切换机制:src/api/backend-registry/types.ts 定义了 Backend 结构,其中 kind 区分 local 与 cloud,authMode 支持 api-key 与 cookie 两种鉴权方式,并带有 connectionRevision 字段——凭据变化时使其失效以刷新按后端隔离的缓存数据。这正是文档中"可连接一个或多个 Agent Server 实例并从 UI 切换"的实现基础。
二、运行时服务:Agent Server、Ingress 与运行期服务发现
2.1 服务拓扑
主后端是 OpenHands Agent Server(来自 OpenHands software-agent-sdk 仓库的 openhands-agent-server 包)。可选的运行时服务包括:
- Ingress 服务:将前端、Agent Server 与自动化流量收敛到同一个本地 origin 之后;
- Automation Server:负责定时或事件触发的 Agent 运行;
- OpenHands Cloud API:托管沙箱与组织级工作流。
各服务的端口与版本在 config/defaults.json 中集中管理,该文件被 npm 启动脚本、Docker entrypoint 与 CI 工作流共同读取,是整个栈的"单一事实来源":
| 服务 | 默认端口 | 版本 |
|---|---|---|
Agent Server(openhands-agent-server) |
18000 | 1.44.0 |
Automation Backend(openhands-automation) |
18001 | 1.9.0 |
| Ingress 代理(proxy) | 8000 | — |
| 容器内 VS Code(openvscode-server) | 8001(容器内) | — |
npm run dev 模式下的完整拓扑在 scripts/dev-with-automation.mjs 头部注释中有 ASCII 架构图:所有流量先进入 http://localhost:8000 的 ingress 代理,其中 /api/automation/* 转发到 Automation Backend(18001),/api/* 与 /sockets 转发到 Agent Server(18000),其余路径转发到 Vite 开发服务器(3001)。Agent Server 与 Automation Backend 均通过 uvx 直接从 git 引用启动,无需克隆。
2.2 Ingress:单 origin 反向代理的实现
Ingress 是一个独立的 HTTP 反向代理,实现在 scripts/ingress.mjs,与任何后端实现完全解耦。其关键行为:
- 路由按最长前缀优先匹配,因此
/api/automation会优先于/api(这是拓扑正确的关键); - 支持 CLI 参数(
--port、--route "/path=http://host:port"、--default)与环境变量(INGRESS_PORT、INGRESS_ROUTES、INGRESS_DEFAULT)两种配置方式; - 支持
--no-referrer-prefix,对 URL 查询串中携带凭据的上游发送Referrer-Policy: no-referrer响应头,避免凭据经 Referrer 泄漏; - 通过
--runtime-services-info/INGRESS_RUNTIME_SERVICES_INFO将运行时服务 JSON 附加到/server_info端点。
2.3 运行期服务发现:让 Agent 不再"猜端口"
架构文档中一个容易被忽略但非常实用的机制是:前端把后端提供的运行时服务信息作为 Agent 上下文后缀转发进新会话,使 Agent 能使用正确 URL 而不是自行探测端口。
该机制的单一实现是 scripts/runtime-services-info.mjs,其核心函数 buildRuntimeServicesInfo():
- 以 Agent 的视角(而非浏览器视角)构造各服务的 URL——即 Agent 在沙箱内应如何 curl/fetch 这些服务。例如 Agent Server 对 Agent 而言始终在回环地址上;
- 输出结构包含
mode(如dev:safe)、agent_host_alias(Agent 访问宿主侧服务的主机名)以及services对象,后者按需包含agent_server、ingress、frontend、automation四段,每段带description与url_from_agent; - 对 automation 服务,额外暴露
api_prefix(默认/api/automation)、docs_url、openapi_url与鉴权环境变量名(默认OPENHANDS_AUTOMATION_API_KEY,对应X-Session-API-Key请求头); - 快速失败设计:若既未提供
agentServerUrl也未提供agentServerPort,直接抛错——因为那样会在 Agent 的系统提示词中写入http://localhost:undefined,比启动失败更糟。
这个模块有两个调用方,体现了它在本地与容器两种形态中的复用:
- 开发启动器(
scripts/dev-*.mjs)以端口集合的方式调用它,结果交给 ingress 或静态服务器挂到/server_info; - Docker 镜像(docker/entrypoint.sh)在容器启动时以 CLI 方式运行该脚本——因为容器内的 URL 是运行期配置(端口与 base URL 可在
docker run时覆盖),无法在构建期写死进镜像,打印的 JSON 再传给静态服务器经/server_info暴露。
前端随后读取该后端提供的信息,渲染为 AgentContext 的系统消息后缀(一个 <RUNTIME_SERVICES> 块),Agent 无需探测即可获得可用服务清单。相关行为在 tests/scripts/runtime-services-info.test.ts 与 tests/scripts/ingress.test.ts 中有测试覆盖。
三、前端模块结构
架构文档列出的核心源码区域与实际仓库目录一一对应:
| 目录 | 职责 |
|---|---|
| src/api/ | Agent Server、cloud、设置、git、skills、自动化与后端注册行为的服务适配器 |
| src/components/ | 路由与特性 UI:会话、聊天、浏览器、文件、设置、后端、自动化、onboarding |
| src/hooks/ | 可复用的 React Query、状态与特性 hooks |
| src/stores/ | 会话与 UI 状态的 Zustand 存储 |
| src/i18n/ | 翻译资源与生成的 bundle(由 make-i18n 脚本从 src/i18n/translation.json 生成,并有 scripts/check-translation-completeness.cjs 校验完整性) |
| src/mocks/ | MSW 处理器,用于 mock 模式开发与测试(worker 目录配置在 package.json 的 msw.workerDirectory 中,指向 public/) |
| bin/ 与 scripts/ | CLI 与开发栈启动器 |
从源码结构看,src/api/ 下按服务名分子目录组织(如 backend-registry/、automation-service/、conversation-service/、cloud/、mcp-service/ 等),每个服务适配器配套独立的 __tests__/api/ 测试;src/hooks/ 再细分为 query/ 与 mutation/ 两类 React Query hook。peerDependencies 声明了 React 19、react-router 7 等宿主必须提供的依赖,说明库形态下这些依赖由宿主应用统一管理。
四、运行模式:从 npm run dev 到库构建
架构文档给出的六种模式表完整如下,右列结合 package.json 的 scripts 与 scripts/ 下的启动器给出落地实现:
| 模式 | 用途 |
|---|---|
npm run dev |
直接在宿主机上启动完整本地栈:通过 uvx 启动 agent-server 与 automation backend,加 Vite 开发服务器与 ingress 代理。Agent 可访问宿主文件系统;仅建议在可信环境中使用 |
npm run dev:minimal |
只启动 agent-server 加 Vite 开发服务器,不带 automation backend |
npm run dev:static |
与 dev 相同,但用生产构建的前端替代 Vite 开发服务器 |
npm run dev:mock |
前端跑在 MSW mock 之上,用于 UI 开发与测试 |
npm run build |
构建独立应用(即 build:app) |
npm run build:lib |
构建用于嵌入 Agent Canvas 组件的库入口 |
与 package.json 的对应关系:
dev→scripts/dev-with-automation.mjs(自动读取可选.env);dev:minimal→ scripts/dev-safe.mjs,它是"安全"的最小栈:只跑 agent-server 与前端,并复用其中导出的buildAgentServerCommand、assertPortsFree、buildRuntimeServicesInfo等工具函数(dev-with-automation.mjs直接import这些函数);dev:static→ scripts/dev-static.mjs,serve 生产构建;dev:mock→cross-env VITE_MOCK_API=true react-router dev,并先执行make-i18n;build:lib以BUILD_LIB=true环境变量触发 vite 的库构建分支,再用 tsconfig.lib.json 产出类型声明。
dev 模式还有几个值得注意的运维细节(均来自 scripts/dev-with-automation.mjs 的头部注释):
- 环境变量
OH_AUTOMATION_GIT_REF可切换 automation 的 git 引用(默认main);OH_AGENT_SERVER_LOCAL_PATH指向本地 software-agent-sdk checkout 时优先级最高,会从本地源码以 editable 方式安装 SDK 包,源码修改即时生效; - 会话 API key 会自动种入 agent-server 的 secrets(键名
OPENHANDS_AUTOMATION_API_KEY),agent-server 与 automation backend 使用同一个 key、同一个X-Session-API-Key请求头鉴权;AUTOMATION_KV_SECRET未显式设置时从会话 key 派生,KV 存储在本地开发开箱即用; - 版本 pin(如
config/defaults.json中constraints.agentClientProtocol = "agent-client-protocol<0.11")在注释中记录了 pin 的原因——上游 ACP 0.11 重排了prompt()参数导致 SDK 校验失败,属于有明确依据的临时上界约束。
dev:mock 模式依赖 src/mocks/ 下按领域拆分的 MSW 处理器(会话、设置、secret、MCP、工作区等),mock worker 的启动判定见 src/mocks/should-start-mock-worker.ts。
五、打包与分发:@openhands/agent-canvas npm 包
npm 包名为 @openhands/agent-canvas(当前仓库版本 1.16.0,要求 Node >= 22.12.0)。package.json 中 files 字段声明打包内容为 dist、bin、build、config、scripts、tools,导出结构包括:
agent-canvas二进制(bin/agent-canvas.mjs):用于启动本地栈。postinstall脚本还会在全局安装后打印启动提示,告知默认入口为http://localhost:8000;- 独立应用构建(
build/,由react-router build产出,可用npm run start经 sirv 静态服务); - 库入口(
exports字段),覆盖架构文档所列全部模块:
| 子路径 | 产物 |
|---|---|
. |
主入口 dist/index.js(含 .d.ts 与 CJS 版本) |
./browser |
浏览器组件模块 |
./conversation |
会话组件模块 |
./files |
文件组件模块 |
./settings |
设置组件模块 |
./sidebar |
侧边栏组件模块 |
./terminal |
终端组件模块 |
./i18n |
国际化模块 |
每个子路径均同时提供 ESM/CJS 双格式与 TypeScript 类型声明,宿主应用可按需只引入所需模块。
带标签(tagged release)的版本通过 Publish to npm GitHub Actions 工作流发布(.github/workflows/npm-publish.yml),使用 npm 可信发布(trusted publishing)并附带 provenance 产物签名,发布链路不依赖长期有效的 npm token。
六、质量门禁:CI 工作流
主 CI 工作流 ci.yml 与架构文档描述的"质量门禁"一一对应,且采用双操作系统测试矩阵(ubuntu-24.04 跑全量检查,windows 跑构建子集,fail-fast: false):
npm ci安装依赖(Node 固定为 24.15.x 并启用 npm 缓存;注释说明 24.16.0 存在一个 zip 解包回归会导致旧版 Playwright 安装挂起);npm run lint——其定义为npm run typecheck && eslint src && prettier --check src/**/*.{ts,tsx},即类型检查、ESLint 与 Prettier 三者合一;npm test——单测与组件测试(Vitest,先执行make-i18n生成翻译 bundle);npm run build——独立应用构建;npm run build:lib——库构建;npm pack --dry-run——校验包内容(对应文档中的 "package verification")。
此外,仓库还有多组补充工作流覆盖文档所说的"可选的实时端到端 QA":mock-llm-e2e.yml 与 mock-llm-docker-e2e.yml 跑 Playwright 的 mock-LLM E2E(对应 test:e2e:mock-llm / test:e2e:mock-llm:docker 脚本),test:e2e:live 则用于对指定 PR 运行实时 E2E。仓库还配置了 Stryker 变异测试(test:mutation* 脚本与 stryker.config.mjs)。
七、安全姿态:本地模式的信任假设
架构文档的安全结论可以概括为一句话:本地运行模式可能让 Agent 访问用户工作区。npm run dev 明确给出警告——该模式下 Agent 具有宿主文件系统访问权限,只能在可信环境使用。
对应到仓库中的缓解手段:
- docs/SELF_HOSTING.md 与 README 提示笔记本场景应使用 Docker 沙箱模式(docker/Dockerfile 构建
ghcr.io/openhands/agent-canvas镜像,docker/entrypoint.sh 在启动时注入运行期服务信息); - 自托管部署应采用常规服务器加固实践:鉴权、HTTPS、防火墙规则与审慎的工作区作用域控制;
- 从
config/defaults.json的注释可见一个具体的同源权衡:捆绑编辑器(openvscode-server)通过/vscode路径前缀挂在 canvas 同一 origin 上(OH_VSCODE_BASE_PATH),这样单 origin 部署无需额外发布端口,但路径前缀只做路由不做隔离——编辑器与 canvas 共享浏览器 localStorage,文档将其标注为已知取舍; - ingress 的
--no-referrer-prefix能力专门用于防止查询串中的凭据经Referrer头外泄。
小结
Agent Canvas 的架构核心是清晰的职责切割:前端只做渲染、状态管理与 API 翻译;执行、隔离与凭据托管全部落在 Agent Server 及其后端生态中。理解它的关键在于三个机制:backend registry 让 UI 可切换多个 local/cloud 后端;ingress + /server_info.runtime_services 让浏览器与 Agent 各自获得正确的服务 URL;六种运行模式则覆盖了从可信宿主全栈开发、最小栈、mock 开发到独立应用与库嵌入的完整场景。进一步阅读建议从 docs/architecture.md 出发,结合 docs/DEVELOPMENT.md、docs/SELF_HOSTING.md 与 docs/TESTING_MATRIX.md 深入开发、部署与测试细节。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00