首页
/ OpenHands Agent Canvas 开发指南:本地 Dev Stack 架构、Agent Server 版本选择与嵌入式定制(基于 docs/DEVELOPMENT.md)

OpenHands Agent Canvas 开发指南:本地 Dev Stack 架构、Agent Server 版本选择与嵌入式定制(基于 docs/DEVELOPMENT.md)

2026-09-04 16:24:32作者:何举烈Damon

本文以 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.mjsscripts/dev-with-automation.mjs 与 Docker 入口共同读取:agent-server 端口 18000、automation 端口 18001、ingress 代理端口 8000。该文件同时钉扎了默认版本(agent-server 1.44.0、automation 1.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 的隔离机制有详细说明,值得逐条展开:

  • 开发栈使用 uvx127.0.0.1:18000 上运行一个临时 agent-server 安装,并让前端指向它;
  • 通过设置相互独立的 OH_CONVERSATIONS_PATHOH_BASH_EVENTS_DIROH_VSCODE_PORT 来隔离 conversation 持久化,使其不与其它本地或云后端 OpenHands 会话冲突(文档以 .openhands-dev/ 作为隔离位置表述;从 dev-safe.mjsbuildConfigFromPorts 实现看,这些路径统一从一个隔离 state dir 派生,默认 ~/.openhands/agent-canvas,可用 OH_CANVAS_SAFE_STATE_DIR 覆盖,其下含 dev_conversationsworkspacesbash_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.mjstmuxTmpDir: env.TMUX_TMPDIR || path.join(stateDir, "tmux"))。

会话密钥也有对应的持久化约定:LOCAL_BACKEND_API_KEY 未设置时,启动器会自动生成一个 256-bit 十六进制 key 并写入 ~/.openhands/agent-canvas/api-key.txtdev-safe.mjs),保证跨重启稳定,使前端烘焙的 VITE_SESSION_API_KEY 与 localStorage 中的 backend 注册条目保持同步;该 key 同时作为 OH_SESSION_API_KEYS_0 注入 agent-server,并会被种子进 automation 侧(见 dev-with-automation.mjs)。

前置依赖

启动器会做两项前置检查(dev-with-automation.mjscheckPrerequisites):uvxnpm 必须在 PATH 中,前端依赖(cross-envreact-router 等 bin)必须已安装。缺 uvx 时会打印 uv 安装指引并建议改用 npm run dev:frontendnpm run dev:mock;缺前端依赖时提示在仓库根目录执行 npm ci。另外 package.json 声明了运行环境前提:node >= 22.12.0enginesvolta 配置一致)。

启动器环境变量(文档第一张表)

文档给出的核心启动器变量如下:

变量 说明 默认值
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.tsgetBakedSessionApiKey() 的两个来源说明。

最小模式(无 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 + 1dev-safe.mjs),而完整栈中编辑器端口派生为 backend port + 1000dev-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.mjsbuildAgentServerCommand() 中,源码补充了几个文档未展开的关键细节:

  • 本地路径要求OH_AGENT_SERVER_LOCAL_PATH 必须是绝对路径,且指向包含 openhands-agent-serveropenhands-sdkopenhands-toolsopenhands-workspace 四个 workspace 包的 software-agent-sdk 检出(validateLocalAgentServerPath 逐项校验,缺失即启动失败)。agent-server 本体每次启动都用 uvx --reinstall 从本地源码重建,其余三个包以 editable 方式安装,源码修改无需重新安装即可生效;
  • git ref 为何要 --reinstall:分支上的版本号字符串可能与 PyPI 当前发布相同,不加 --reinstall 时 uv 会静默复用缓存的 PyPI wheel,导致你指定的 ref 根本没被使用;
  • 版本钉扎的一致性:无论走哪条路径,openhands-sdkopenhands-toolsopenhands-workspace 都与 agent-server 取同一版本/ref,保证跨包 API 同步;
  • 所有启动方式最终都会追加 --import-modules canvas_ui_tooldev-safe.mjs),在创建 conversation 前注册 tools/canvas_ui_tool.py

automation 侧有对称的一套变量(OH_AUTOMATION_LOCAL_PATH / OH_AUTOMATION_GIT_REF / OH_AUTOMATION_VERSION,见 dev-with-automation.mjsbuildAutomationCommand()),且 --automation-git-ref 命令行参数会显式压过 OH_AUTOMATION_LOCAL_PATH,避免 shell 中残留的环境变量让你误以为自己复现的是指定 ref。

其它有用覆盖项

文档列出的补充变量,均可在源码中找到对应解析点(dev-safe.mjsdev-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:frontendpackage.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 runbuildbuild:appmake-i18n && react-router build);startsirv-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/main

HTML 报告写入 reports/mutation.html。文档特别强调:变异分数目前仅作报告用途,应先建立稳定基线,再考虑加入失败阈值。

stryker.config.mjs 展示了文档所称的“默认排除”具体规则:mutate 模式为 src/**/*.{ts,tsx},并排除测试文件(*.test.* / *.spec.*)、__tests__/.d.ts*.types.ts.gen/.generated.tsi18n/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 压住。

嵌入策略

定制策略

主题与表面令牌通过作用域根上的 CSS 自定义属性暴露,可以两种途径覆盖:provider/root 的 styleOverrides prop,或宿主 CSS 直接选择 [data-agent-server-ui]。文档示例:

<AgentServerUIProviders
  styleOverrides={{
    "--oh-color-base": "#101820",
    "--oh-color-content-2": "#f5f7ff",
    "--oh-accent": "#8b5cf6",
  }}
>
  <App />
</AgentServerUIProviders>

styleOverrides 的类型 AgentServerUIStyleOverridesagent-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_DIRagent-server-config.ts)一致;VITE_MOCK_API 正是上文 dev:mock / dev:frontend 脚本切换的开关;VITE_USE_TLSVITE_BACKEND_BASE_URLhttps:// 而用户未显式指定时会被启动器自动推导为 truedev-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 这一份配置里,便于审计与升级。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384