OpenHands Agent Canvas 开发指南:本地 Dev Stack 架构、Agent Server 版本选择与嵌入式定制(基于 docs/DEVELOPMENT.md)
【免费下载链接】OpenHands🙌 OpenHands: AI-Driven Development项目地址: https://gitcode.com/GitHub_Trending/ope/OpenHands
本文以 docs/DEVELOPMENT.md 为主体,面向需要参与agent-canvas本身开发的贡献者与集成方,完整梳理其本地开发工作流:无 Docker 的全栈开发启动方式、agent-server 版本选择优先级、各种替代开发模式(静态构建、前后端分离、最小模式、Mock 模式)、构建与变异测试流程,以及将 UI 嵌入宿主应用时的 CSS 隔离与主题定制策略。读完后,你可以独立搭起完整本地开发栈、理解各服务的端口与隔离机制,并能基于源码定位每个启动参数的实际落点。
仓库定位与仓库边界
docs/DEVELOPMENT.md开宗明义:本文档面向agent-canvas自身(@openhands/agent-canvas,见 package.json)的贡献者。该仓库包含Agent Canvas 前端与本地开发栈编排(scripts/下的启动脚本),而后端能力分属若干兄弟仓库。文档给出了清晰的归属划分:
OpenHands/software-agent-sdk拥有 Python SDK、Agent Server、agent/tool 行为、conversations、workspaces、events 与 server API;OpenHands/typescript-client拥有面向浏览器、兼容该 Agent Server API 的类型化客户端。新 API 调用方法应加在该仓库,而不是在 Canvas 里重新实现;OpenHands/extensions拥有可复用的 skills、plugins、automations 与 integrations;OpenHands/automation拥有 automation 定义、调度、webhooks、运行历史与派发;agent-server/SDK 侧负责执行被派发的 conversation。
跨仓库功能的标准协作顺序是:先在 SDK 中实现后端契约,再通过typescript-client暴露,最后在 Canvas 中消费;automation 生命周期变更需在automation仓库协调。package.json 的依赖列表印证了这一关系:前端以 npm 依赖形式消费@openhands/typescript-client(1.39.0)与@openhands/extensions(0.19.0)。文档同时要求每个 PR 遵循仓库 贡献者说明 与 自定义 code-review 指南。
推荐本地工作流:npm run dev全栈开发
文档推荐的核心工作流是npm run dev,它一次性拉起完整本地栈,无需 Docker:
- 通过
uvx临时安装并运行agent-server(后端); - 通过
uvx运行 automation 后端; - Vite 开发服务器(带热更新);
- 一个 ingress 代理统一入口。
npm run dev实际执行的是node --env-file-if-exists=.env scripts/dev-with-automation.mjs(见 package.json 的 scripts 定义)。从 dev-with-automation.mjs 顶部的架构注释看,整条链路是:
┌──────────────────────────────────────────────────────────────────────────┐ │ http://localhost:8000 (Ingress Proxy) │ │ /api/automation/* → Automation Backend │ │ /api/*, /sockets → Agent Server │ │ /* → Vite Dev Server │ └──────────────────────────────────────────────────────────────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌───────────────┐ ┌──────────────────┐ │ Vite │ │ Agent Server │ │ Automation │ │ :3001 │ │ (uvx) :18000 │ │ Backend (uvx) │ │ │ │ │ │ :18001 │ └─────────────┘ └───────────────┘ └──────────────────┘各端口的默认值集中定义在 config/defaults.json —— 该文件是版本钉扎、端口与路径的唯一事实来源,被scripts/dev-safe.mjs、scripts/dev-with-automation.mjs与 Docker 入口共同读取:agent-server 端口18000、automation 端口18001、ingress 代理端口8000。该文件同时钉扎了默认版本(agent-server1.44.0、automation1.9.0)与兼容性下限(minimumAgentServer: 1.28.0)。
启动成功后有两个入口(来自 dev-with-automation.mjs 的帮助文本):
- 主 UI:
http://localhost:<PORT>/ - automation API 文档:
http://localhost:<PORT>/api/automation/docs
ingress 路由表与部分栈模式
路由前缀在 dev-with-automation.mjs 中硬编码:/api/automation前缀指向 automation 后端,/api、/sockets、/server_info、/health等前缀指向 agent-server,其余请求回落到 Vite。此外,捆绑编辑器(openvscode-server)不单独发布端口,而是通过/vscode前缀路由(vscodeBasePath同样来自 config/defaults.json)。
文档指出,发布的agent-canvas二进制还支持部分栈模式,以便把前端与后端进程分开运行:
agent-canvas --frontend-only agent-canvas --backend-only两种模式都会启动 ingress 代理,代理只把流量路由到该模式实际启动的服务。从源码可以印证其“宁可 503 也不误回 SPA”的设计:getRejectPrefixes()(dev-with-automation.mjs)在 frontend-only 模式下会把所有无后端的 API 前缀加入--reject-prefix,使其返回 503,而不是被 SPA fallback 成index.html;--frontend-only与--backend-only互斥,--public模式还强制要求显式设置LOCAL_BACKEND_API_KEY。
开发栈的隔离机制
文档对npm run dev的隔离机制有详细说明,值得逐条展开:
- 开发栈使用
uvx在127.0.0.1:18000上运行一个临时agent-server安装,并让前端指向它; - 通过设置相互独立的
OH_CONVERSATIONS_PATH、OH_BASH_EVENTS_DIR与OH_VSCODE_PORT来隔离 conversation 持久化,使其不与其它本地或云后端 OpenHands 会话冲突(文档以.openhands-dev/作为隔离位置表述;从 dev-safe.mjs 的buildConfigFromPorts实现看,这些路径统一从一个隔离 state dir 派生,默认~/.openhands/agent-canvas,可用OH_CANVAS_SAFE_STATE_DIR覆盖,其下含dev_conversations、workspaces、bash_events等子目录); - tmux socket 位于
~/.openhands/agent-canvas/tmux(通过TMUX_TMPDIR环境变量传递)。源码注释解释了为何默认不用系统临时目录:macOS 上$TMPDIR会被系统定期清理,删除存活的 tmux socket 会导致后续 new-window 全部失败; - 若
$HOME位于不支持 Unix domain socket 的文件系统(某些 devcontainer、NFS/CIFS home),应把标准环境变量TMUX_TMPDIR设为本地路径(如/tmp),开发栈会直接使用它(dev-safe.mjs 中tmuxTmpDir: env.TMUX_TMPDIR || path.join(stateDir, "tmux"))。
会话密钥也有对应的持久化约定:LOCAL_BACKEND_API_KEY未设置时,启动器会自动生成一个 256-bit 十六进制 key 并写入~/.openhands/agent-canvas/api-key.txt(dev-safe.mjs),保证跨重启稳定,使前端烘焙的VITE_SESSION_API_KEY与 localStorage 中的 backend 注册条目保持同步;该 key 同时作为OH_SESSION_API_KEYS_0注入 agent-server,并会被种子进 automation 侧(见 dev-with-automation.mjs)。
前置依赖
启动器会做两项前置检查(dev-with-automation.mjs 的checkPrerequisites):uvx与npm必须在 PATH 中,前端依赖(cross-env、react-router等 bin)必须已安装。缺uvx时会打印 uv 安装指引并建议改用npm run dev:frontend或npm run dev:mock;缺前端依赖时提示在仓库根目录执行npm ci。另外 package.json 声明了运行环境前提:node >= 22.12.0(engines与volta配置一致)。
启动器环境变量(文档第一张表)
文档给出的核心启动器变量如下:
| 变量 | 说明 | 默认值 |
|---|---|---|
PORT | Ingress 端口 | 8000 |
OH_AUTOMATION_GIT_REF | automation 后端的 Git ref | main |
OH_AGENT_SERVER_GIT_REF | agent-server 的 Git ref | main |
静态前端构建:npm run dev:static
对于慢网络、远程访问或隧道场景,文档推荐使用静态前端构建:
npm run dev:static它对应 scripts/dev-static.mjs,即先构建生产前端、再交由静态服务器托管(dev-with-automation.mjs同样暴露--static/--static-dir/--skip-build等开关,可直接复用已有的build/产物)。静态服务器在运行时向index.html注入会话 key(window.__AGENT_CANVAS_SESSION_API_KEY__)与鉴权标记,这也是发布二进制在预构建 bundle 中VITE_SESSION_API_KEY为空时仍能完成 onboarding 的路径 —— 见 agent-server-config.ts 中getBakedSessionApiKey()的两个来源说明。
最小模式(无 Automation)
若不想启动 automation 服务,文档给出:
npm run dev:minimal该模式只运行 agent-server + Vite(无 automation 后端、无 ingress),访问地址为http://localhost:3001/。其实现是 scripts/dev-safe.mjs:先以buildSafeDevConfigAsync()做端口预检(assertPortsFree会在端口被占时立即失败并提示可能已有实例在运行),轮询GET /server_info确认后端就绪后,再拉起npm run dev:frontend。注意此模式与完整模式的一个差异:VS Code 旁路端口默认是backend port + 1(dev-safe.mjs),而完整栈中编辑器端口派生为backend port + 1000(dev-with-automation.mjs);两种模式都支持OH_CANVAS_SAFE_VSCODE_PORT覆盖。
Agent Server 版本选择
文档说明默认使用 PyPI 上最新发布的版本,并给出(按优先级从高到低)三种覆盖方式:
# 针对本地 software-agent-sdk 检出运行 OH_AGENT_SERVER_LOCAL_PATH=/abs/path/to/software-agent-sdk npm run dev # 使用 git 分支或提交(优先于 version) OH_AGENT_SERVER_GIT_REF=main npm run dev OH_AGENT_SERVER_GIT_REF=abc1234 npm run dev # 使用指定 PyPI 版本 OH_AGENT_SERVER_VERSION=1.18.0 npm run dev这段逻辑完整实现在 dev-safe.mjs 的buildAgentServerCommand()中,源码补充了几个文档未展开的关键细节:
- 本地路径要求:
OH_AGENT_SERVER_LOCAL_PATH必须是绝对路径,且指向包含openhands-agent-server、openhands-sdk、openhands-tools、openhands-workspace四个 workspace 包的software-agent-sdk检出(validateLocalAgentServerPath逐项校验,缺失即启动失败)。agent-server 本体每次启动都用uvx --reinstall从本地源码重建,其余三个包以 editable 方式安装,源码修改无需重新安装即可生效; - git ref 为何要
--reinstall:分支上的版本号字符串可能与 PyPI 当前发布相同,不加--reinstall时 uv 会静默复用缓存的 PyPI wheel,导致你指定的 ref 根本没被使用; - 版本钉扎的一致性:无论走哪条路径,
openhands-sdk、openhands-tools、openhands-workspace都与 agent-server 取同一版本/ref,保证跨包 API 同步; - 所有启动方式最终都会追加
--import-modules canvas_ui_tool(dev-safe.mjs),在创建 conversation 前注册 tools/canvas_ui_tool.py。
automation 侧有对称的一套变量(OH_AUTOMATION_LOCAL_PATH/OH_AUTOMATION_GIT_REF/OH_AUTOMATION_VERSION,见 dev-with-automation.mjs 的buildAutomationCommand()),且--automation-git-ref命令行参数会显式压过OH_AUTOMATION_LOCAL_PATH,避免 shell 中残留的环境变量让你误以为自己复现的是指定 ref。
其它有用覆盖项
文档列出的补充变量,均可在源码中找到对应解析点(dev-safe.mjs、dev-with-automation.mjs):
OH_CANVAS_SAFE_BACKEND_PORT— 隔离服务器端口(默认18000);OH_CANVAS_SAFE_VSCODE_PORT— VS Code 旁路端口(默认backend port + 1,见上文最小模式说明);OH_CANVAS_SAFE_STATE_DIR— 隔离服务器状态的基础目录;VITE_WORKING_DIR— 新建 conversation 使用的仓库根目录(默认当前检出)。
替代开发工作流
多本地后端(共享持久化)
要一边npm run dev、一边再挂一个独立 agent-server 并共享其会话历史与加密 secrets,用文档提供的辅助脚本:
npm run dev:extra-backend它由 scripts/dev-extra-backend.mjs 实现:在:18002上启动一个额外服务器,复用捆绑实例的 state dir,从而看到同一份会话与密钥数据。
前端对接已有后端
仅在你确实自行启动了agent-server、或希望前端指向别的后端时使用:
npm run dev:frontend该工作流默认期望后端位于127.0.0.1:8000。若设置了LOCAL_BACKEND_API_KEY,它会被用作 agent-server 的 API key(内部映射到OH_SESSION_API_KEYS_0);未设置时启动器自动生成并持久化一个 key。dev:frontend在 package.json 中定义为make-i18n && cross-env VITE_MOCK_API=false react-router dev,即标准 Vite/React Router 开发服务器,API 转发目标由VITE_BACKEND_HOST决定(dev-safe.mjs 的注释区分了VITE_BACKEND_HOST只供开发代理使用、VITE_BACKEND_BASE_URL则刻意留空让前端回落到同源 origin,从而在 SSH 隧道/ngrok 等场景下保持可移植)。
Mock 模式
想在没有真实后端的情况下运行前端:
npm run dev:mock即cross-env VITE_MOCK_API=true react-router dev,通过 MSW 拦截 API(mock worker 位于 public/mockServiceWorker.js)。
构建与测试
文档给出的三条基础命令:
npm run test npm run build npm run start对应关系(package.json):test会先执行make-i18n再跑vitest run;build走build:app(make-i18n && react-router build);start用sirv-cli build/ --single提供构建产物。
针对隔离开发启动器的定向验证,文档推荐:
npm run test -- __tests__/api/agent-server-config.test.ts __tests__/scripts/dev-safe.test.ts两个测试文件分别覆盖前端侧的 agent-server 配置解析(对应 src/api/agent-server-config.ts)与启动器核心的端口分配、API key 持久化、命令构建等纯函数。ingress 路由相关行为另有tests/scripts/ingress.test.ts 等脚本测试。
变异测试(Mutation Testing)
文档介绍了 Stryker 对src/下第一方 TypeScript 源码做变异验证,确认 Vitest 套件能捕获被刻意引入的缺陷:
# 全量变异运行(对整个前端而言开销大) npm run test:mutation # 复用上一次运行的结果 npm run test:mutation:incremental # 只变异相对本地 main 分支变更的生产文件 npm run test:mutation:diff # 与其它 base ref 比较,例如最新的远程 main npm run test:mutation:diff -- origin/mainHTML 报告写入reports/mutation.html。文档特别强调:变异分数目前仅作报告用途,应先建立稳定基线,再考虑加入失败阈值。
stryker.config.mjs 展示了文档所称的“默认排除”具体规则:mutate模式为src/**/*.{ts,tsx},并排除测试文件(*.test.*/*.spec.*)、__tests__/、.d.ts、*.types.ts、.gen/.generated.ts、i18n/declaration.ts以及src/{fixtures,mocks,dev}/**;vitest runner 开启related: true,只执行与被变异文件相关的测试,显著降低成本。文档同时说明:Stryker 不覆盖仓库中的少量 Python 代码面,那部分需要 Python 测试框架与 Python 专用变异工具。
CSS 隔离与宿主应用定制
文档的这一节讲的是把 Agent Canvas UI 嵌入宿主应用时的样式边界。独立应用与导出的 provider/root 包装器现在把所有捆绑 CSS 限定在一个带data-agent-server-ui属性专用 shell 元素之下 —— Tailwind 工具类、HeroUI 组件样式、xterm 样式与本地 CSS 只作用于 OpenHands UI 子树内,不会泄漏到宿主应用。
源码层面的证据是 src/styles/agent-server-ui-style-scope.ts:
- 作用域常量
AGENT_SERVER_UI_SCOPE_ATTRIBUTE = "data-agent-server-ui",选择器[data-agent-server-ui]; transformAgentServerUISelector()在构建期改写 CSS 选择器::root/body/html这类全局选择器直接改写为作用域前缀,:host也替换为前缀,从而保证没有任何选择器逃逸出子树;- 主题令牌以 CSS 自定义属性(
--oh-*)形式挂在作用域根上,默认值集中在AGENT_SERVER_UI_DEFAULT_CSS_VARIABLES(--oh-color-base、--oh-accent、--oh-surface、--oh-border等 60 余项);其中--oh-color-primary、--oh-accent、--oh-warning三个品牌变量被单独列为“可被颜色主题在运行时覆盖”的变量,刻意不做内联,避免被element.style压住。
嵌入策略
- 宿主应用使用
AgentServerUIProviders(src/components/providers/agent-server-ui-providers.tsx),默认渲染一个带作用域的样式根; - 需要直接控制包装层时使用
AgentServerUIRoot(src/components/providers/agent-server-ui-root.tsx); - 独立应用(standalone app)因为路由布局已经渲染了带作用域的根,反而选择退出 provider 包装。
定制策略
主题与表面令牌通过作用域根上的 CSS 自定义属性暴露,可以两种途径覆盖:provider/root 的styleOverridesprop,或宿主 CSS 直接选择[data-agent-server-ui]。文档示例:
<AgentServerUIProviders styleOverrides={{ "--oh-color-base": "#101820", "--oh-color-content-2": "#f5f7ff", "--oh-accent": "#8b5cf6", }} > <App /> </AgentServerUIProviders>styleOverrides的类型AgentServerUIStyleOverrides(agent-server-ui-style-scope.ts)对键名做了约束:只接受AGENT_SERVER_UI_DEFAULT_CSS_VARIABLES的键或三个主题化品牌变量,避免拼错变量名静默失效。
还有一个文档明确点出的坑:若希望内层主题化容器拥有 Tailwind 布局工具类,应传contentClassName而不是className—— 因为外层作用域元素才是所有生成选择器的锚点,把工具类挂在锚点之外会导致样式失效。
项目.env环境变量
文档最后给出基于.env.sample在项目中创建.env的变量表,完整继承如下:
| 变量 | 说明 | 默认值 |
|---|---|---|
VITE_BACKEND_BASE_URL | 浏览器直接请求使用的 agent server 完整 base URL | 当前浏览器 origin |
VITE_BACKEND_HOST | Vite 开发代理使用的后端 host | 127.0.0.1:8000 |
VITE_SESSION_API_KEY | (内部)由启动器注入的会话 API key —— 用户请改设LOCAL_BACKEND_API_KEY | - |
VITE_WORKING_DIR | 新建 conversation 时发送的工作区路径 | workspace/project |
VITE_ENABLE_BROWSER_TOOLS | 设为false可从新 conversation 载荷中省略BrowserToolSet | true |
VITE_BASE_PATH | 在子路径(如/canvas)下构建/托管 SPA | / |
VITE_MOCK_API | 开关 MSW API 模拟 | false |
VITE_USE_TLS | Vite 代理目标使用 HTTPS/WSS | false |
VITE_FRONTEND_PORT | 前端应用运行端口 | 3001 |
VITE_INSECURE_SKIP_VERIFY | 代理后端请求时跳过 TLS 证书校验 | false |
其中VITE_WORKING_DIR的默认值workspace/project与前端常量DEFAULT_WORKING_DIR(agent-server-config.ts)一致;VITE_MOCK_API正是上文dev:mock/dev:frontend脚本切换的开关;VITE_USE_TLS在VITE_BACKEND_BASE_URL为https://而用户未显式指定时会被启动器自动推导为true(dev-with-automation.mjs)。
小结:按场景选工作流
把文档中的工作流汇总成一张选型表:
| 场景 | 命令 | 组成 | 入口 |
|---|---|---|---|
| 完整本地开发(推荐) | npm run dev | agent-server + automation + Vite + ingress | http://localhost:8000 |
| 慢网络 / 隧道 | npm run dev:static | 静态前端 + 后端 + ingress | ingress 端口 |
| 前后端分离 | agent-canvas --frontend-only/--backend-only | 单侧进程 + ingress | ingress 端口 |
| 仅 agent-server 联调 | npm run dev:minimal | agent-server + Vite(无 automation/ingress) | http://localhost:3001 |
| 已有后端 | npm run dev:frontend | 仅前端,默认指向127.0.0.1:8000 | http://localhost:3001 |
| 纯前端 / 演示 | npm run dev:mock | 前端 + MSW | http://localhost:3001 |
| 共享持久化的第二后端 | npm run dev:extra-backend | 额外 agent-server 于:18002 | - |
配合 agent-server 版本三变量(OH_AGENT_SERVER_LOCAL_PATH>OH_AGENT_SERVER_GIT_REF>OH_AGENT_SERVER_VERSION> 默认 PyPI 钉扎版本)与PORT/OH_CANVAS_SAFE_*覆盖项,开发者可以在不改动代码的前提下覆盖绝大多数本地联调与集成调试场景;而所有默认值最终都收敛在 config/defaults.json 这一份配置里,便于审计与升级。
【免费下载链接】OpenHands🙌 OpenHands: AI-Driven Development项目地址: https://gitcode.com/GitHub_Trending/ope/OpenHands
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考