☰
JiuwenSymbiosis GUI 插件开发指南:契约、共享 Runtime 与故障隔离
2026/10/14 15:42:46 网站建设 项目流程
  • 具身智能
  • 人工智能
  • AI Agent
  • 机器人
  • 计算机视觉
  • Agent 编排

【免费下载链接】jiuwensymbiosis

Jiuwen Symbiosis就是一个"能懂人话、看得见物理世界、长了四肢的智能助手"。用户不需要示教,不需要教它怎么抓东西,怎么走路,就像指挥一个经验丰富的工人一样,用自然语言下任务,它就能自己完成感知、理解、规划、执行。

项目地址:https://gitcode.com/openJiuwen/jiuwensymbiosis
点击查看免费下载

本文基于仓库内的接入指南 添加 GUI 插件 与配套可运行样例 examples/gui_plugin 展开,讲清楚如何把一套独立界面作为已安装的 GUI 插件接入 JiuwenSymbiosis:声明GuiSpec、注册jiuwensymbiosis.guientry point、通过统一Runtime提交和取消任务,并遵守依赖隔离与测试边界。读完后,你可以为一个自研界面(NiceGUI、Qt、纯 HTTP 页面均可)写出符合当前契约的插件骨架,并让它与官方工作台共享同一套本机资源准入。

插件边界:轻量契约与延迟加载

GUI 插件是一个独立的 Python distribution,通过 entry point 组jiuwensymbiosis.gui注册到统一启动器。插件使用核心包提供的框架无关契约 jiuwensymbiosis_gui.plugin,声明GuiSpec,并提供接收LaunchOptions的main(options):

from jiuwensymbiosis_gui.plugin import GUI_API_VERSION, GuiSpec, LaunchOptions def describe() -> GuiSpec: return GuiSpec( key="example", display_name="Example GUI", api_version=GUI_API_VERSION, launch_target="my_gui.app:main", ) def main(options: LaunchOptions) -> int: ...

契约本身只有四个导出:GUI_API_VERSION、GuiSpec、LaunchOptions和StartupError。从源码看:

  • GuiSpec是冻结 dataclass,字段为key(与 entry point 名称一致)、display_name、api_version、launch_target("module:callable"形式);
  • LaunchOptions是启动器归一化后的启动参数:gui(默认workbench)、config_path、workspace、host(默认127.0.0.1)、port(默认8770)、open_browser、gui_config_path;
  • StartupError是专门用于“可以安全展示给用户、无需 traceback”的可操作启动错误。

在插件的pyproject.toml中声明入口,入口名称必须与GuiSpec.key相同(样例写法见 examples/gui_plugin/pyproject.toml):

[project.entry-points."jiuwensymbiosis.gui"] example = "my_gui.plugin:describe"

启动器的发现与校验逻辑在 launcher.py 中:discover_guis()先内置注册workbench,再遍历entry_points(group="jiuwensymbiosis.gui"),逐个加载并调用describe()(discover_guis);_validate()会检查spec.key与注册名一致、api_version匹配当前GUI_API_VERSION(版本不兼容会抛出带明确提示的StartupError)、display_name非空且launch_target包含:(_validate)。

由此推出两条重要的工程约束,也是原指南反复强调的边界:

  1. describe()只应导入契约和轻量模块。它在--list-guis列出和验证插件时就会被调用,绝不能依赖 NiceGUI、Qt、WebGL、点云等专属 UI 包。把 UI 框架的导入延迟到所选插件的main()启动路径中——只有用户真正选中某个插件后,启动器才会通过_load_target()导入其launch_target指向的main(_load_target)。
  2. UI 框架依赖写在各插件自己的 distribution 中,写在插件包元数据的dependencies或 extra 里,不要加入核心包依赖。
  3. 同一进程只有一套 Python 包版本。各插件的依赖可以分别写在各自包元数据中,但如果两个界面对某个同名包要求互不兼容的版本,应把它们安装到不同虚拟环境,再从对应环境启动。启动器不会为每个插件自动建立独立解释器。

安装、发现与选择

在仓库根目录安装核心开发包与示例插件:

python -m pip install -e ".[dev]" python -m pip install -e examples/gui_plugin python -m jiuwensymbiosis_gui --list-guis python -m jiuwensymbiosis_gui --gui example \ --gui-config examples/gui_plugin/jiuwensymbiosis_example_gui/configs/self-check.json \ --workspace /tmp/jiuwensymbiosis-example --no-browser

几个要点:

  • --list-guis的输出为每行key<TAB>显示名<TAB>API 版本;发现阶段失败的插件会以key<TAB>不可用: 错误形式列出而不中断其他插件(launcher.py)。
  • 启动器入口有两条等价路径:python -m jiuwensymbiosis_gui或 pyproject.toml 中声明的 console scriptjiuwensymbiosis-gui(映射到jiuwensymbiosis_gui.__main__:main)。
  • 默认 GUI 是workbench(官方工作台)。它单独需要 NiceGUI,可安装pip install -e ".[gui]";gui extra 只引入nicegui>=2,<3和pillow>=10,刻意不引入[full]的重型 GPU 栈。仅使用无 NiceGUI 依赖的插件(如示例 HTTP 插件)时不需要安装该 extra。
  • 统一启动参数、默认工作台以及临时兼容入口的完整说明见 GUI 配置和启动 与 CLI 参考。

实际任务运行时,用--config指向机器人运行 YAML:

python -m jiuwensymbiosis_gui --gui example \ --config configs/piper/piper.yaml \ --workspace /tmp/jiuwensymbiosis-example

参数语义上有一个常见误区需要澄清:--gui-config仅传给所选 GUI 自己(布局、主题、插件私有参数),它不替代--config,也不会自动把真实适配器变成 mock。样例中的self-check模式(self-check.json 内容就是{"mode": "self-check"})只验证插件选择和Runtime的空闲生命周期,不连接机器人;真实执行需要有效的--config,以及对应适配器和模型的运行依赖。启动器在校验阶段就会拒绝不存在的--config/--gui-config路径文件(launcher.py)。

通过共享 Runtime 执行任务

界面应把用户命令交给统一Runtime,而不是自己构建 Agent、Planner、Rails 或RobotSession。不同 GUI 通过 Runtime 共享任务状态和本机资源准入;已接入的官方 CLI 目前只共享本机资源准入,仍保留各自执行和输出,不会写入 Runtime 的 job 记录。

下面用start_ui_event_loop表示插件自己的 UI 生命周期;实际页面回调调用submit、read_job、cancel,而 Runtime 在整个 UI 存活期间保持打开:

from uuid import uuid4 from jiuwensymbiosis.runtime import Runtime runtime = Runtime(workspace=options.workspace) try: binding = runtime.prepare_binding(options.config_path) def submit(query, request_id=None): return runtime.submit_task( binding.binding_id, request_id=request_id or uuid4().hex, query=query, ) def read_job(job_id, after_seq): return { "snapshot": runtime.get_job(job_id), "events": runtime.read_events(job_id, after_seq=after_seq, limit=128), } def cancel(job_id): return runtime.cancel(job_id) start_ui_event_loop(submit, read_job, cancel) # plugin-specific; handlers return promptly finally: closing = runtime.close(timeout=5.0) if not closing["closed"]: raise RuntimeError("cleanup is unconfirmed; keep this host stopped and disable automatic restart")

submit()、read_job()和cancel()是可供 UI handler 调用的短操作;不要在 UI handler 中调用阻塞的wait_for_job()(Runtime.wait_for_job 的实现就是对任务线程做join,适合命令行脚本或测试,不适合事件循环线程)。样例在关闭期间会继续等待仍在执行的任务;若 Runtime 最终返回closed=false,会报错退出且要求宿主禁用自动重启。Runtime不会因close()超时自动释放资源——从源码看,Runtime.close 只负责发出取消信号、等待线程退出并汇总状态,closed为 true 的条件是所有任务与 maintenance 租约均已确认released。因此不能把未确认收尾的实例当作正常关闭。

样例插件 plugin.py 中main()的实现比文档示例更完整:它在finally中先以timeout=5.0关闭,若返回既非closed也非blocked则循环重试等待清理;最终仍未确认时记录 critical 日志并抛出RuntimeError,明确禁止自动重启。

binding:解析并冻结配置

prepare_binding()解析并冻结配置,不连接硬件;每次任务提交时 Runtime 再做资源准入,获准后才创建 session 并执行(准入发生在 Runtime.submit_task 内,通过self.resources.acquire(binding.resources)获取租约)。binding 固定了有效配置、来源路径、工作区和设备资源身份,避免页面改配置或环境变量变化后任务被静默切换到另一台设备。不同 GUI 应使用同一个标准配置和 workspace 语义。

从 runtime/bindings.py 的实现可以看到冻结内容的具体构成:

  • config_source必须是真实存在的 YAML 文件,用于提供来源路径(相对路径解析的基准目录)和溯源信息,即使同时提供了编辑后的config_snapshot也不可省略;
  • binding_id是对source、adapter、配置数据、有效配置、workspace、资源键整体做 SHA-256 得到的指纹;
  • 配置在 binding 阶段一次性解析成类型化配置,其中捕获当时的进程环境(例如 Cruzr 的ros_domain_id未显式配置时取当时的ROS_DOMAIN_ID,默认 0,见 cruzr/config.py);后续build_session()只深拷贝该结果,不会二次解析环境变量。驱动和子进程也必须使用这些捕获值。

因此,重跑或编辑后执行时,应将配置快照和它当前的来源路径一起交给prepare_binding(),不要沿用另一份配置的来源目录。未传工作区时,Runtime 和绑定共用runtime.default_workspace()(即~/.jiuwensymbiosis/gui_workspace);需要显示默认目录的插件应调用该接口,避免复制路径字面量。

request_id 去重与准入错误

request_id用于网络重试去重:Runtime.submit_task 会对binding.fingerprint + query + options计算指纹,同一request_id命中已有记录时直接返回既有 job,但同一 ID 携带不同内容会触发冲突(RequestConflictError)。样例的浏览器端为每次提交生成crypto.randomUUID()作为request_id,并在 HTTP 响应丢失时复用它重试,从而安全去重(webapp.py)。

处理准入错误时要区分两种状态:

  • ResourceBusyError:资源目前正在使用,映射为 HTTP 409;
  • ResourceBlockedError:上次收尾未确认,映射为 HTTP 423。blocked 状态需要检查并确认真实清理,不能删除占用记录来强行继续。

取消是协作式停止:Runtime.cancel 先设置CancelToken控制信号(保证存储故障也不会压制停止),再记录cancel_requested事件;收到取消请求后仍需等待清理结果。任务终态和run_finished表示执行结果;资源可复用与否以任务快照的cleanup.released=true为准。Runtime 在释放前完成产物登记、结果保存和任务日志 handler 移除。启动取消且清理已确认可正常释放;无法确认子进程退出或力矩恢复时保持 blocked,并保留具体原因和人工处理指引。

事件流与 artifact 读取上限

事件读取使用read_events(job_id, after_seq, limit),通过返回的next_seq续读;每个 job 最多保留最近 10,000 条事件(store.py 中按seq <= 当前-10000清理旧事件)。gap=true表示游标之前的事件已过期,前端应重新读取当前 job snapshot 重建状态。

Runtime 还提供latest_frame()和read_artifact()读取不透明的预览/trace 引用:每个 job 至多存 128 个 step frame,每个 artifact 读取上限为 32 MiB(artifacts.py)。前端应按需读取,并限制自己持有的预览缓存。

需要说明的是:读取上限不保证 UI 延迟。展示侧自行负责帧解码与编码;现有工作台暂在 UI 轮询中同步处理,如实测卡顿再移到展示侧线程池,不阻塞核心动作线程。

网络与信任边界

资源准入覆盖 Runtime 任务以及已经接入统一准入的官方 CLI;CLI 的执行状态和输出仍由各自入口管理,不进入 Runtime job store。仅供可信 Python 工作流使用的维护操作通过 Runtime 的 maintenance 接口协调(Runtime.acquire_maintenance),不要把 lease、任意回调或维护接口暴露给浏览器客户端。浏览器端只提交受 Runtime 管理的单次任务,并查看状态、事件和 artifact。

样例 HTTP 页面把这套边界落成了具体代码(webapp.py):

  • create_server()在host != "127.0.0.1"时直接拒绝创建,只绑定127.0.0.1;
  • 所有请求校验Host头必须精确等于127.0.0.1:<port>;
  • 写请求(POST)额外要求Origin精确等于http://127.0.0.1:<port>,不开放 CORS;
  • 请求体限制 16 KiB、query限制 1..4000 字符、request_id限制 1..128 字符,页面带有严格的 CSP 头。

采用其他传输方式的插件也必须明确监听范围、写请求来源校验和认证边界;不能把本机可信假设扩展成网络服务假设。

依赖和故障隔离

插件入口的describe()不能依赖所选 UI 框架,因此--list-guis可在未安装 NiceGUI 或其他可选界面包时显示插件;只有选中插件后才导入它的main及专属组件。启动器对加载失败是容错的:单个插件describe()抛错只会把它记入 per-plugin 错误表并在--list-guis中显示“不可用”,不会拖垮整个发现流程。缺少所选插件依赖时,应通过StartupError给出明确安装命令——启动器捕获后只打印错误消息而不输出 traceback(launcher.py)。

如果新增 GUI 依赖需要与工作台完全不同的 Python 版本或不兼容依赖版本,使用单独虚拟环境安装该插件和核心包。entry point 发现范围是当前解释器(importlib.metadata.entry_points),不会自动跨环境导入插件。

测试边界

仓库把 GUI 相关测试与核心测试在目录和 CI 上都做了切分:

  • tests/unit_tests/测核心 Agent、Runtime、适配器、准入和无 GUI 依赖的行为,运行make test-core;
  • tests/gui/测启动器、插件安装/发现,以及需要 GUI 可选依赖的界面和接入行为,运行make test-gui;
  • make test默认只运行核心测试,仅需.[dev];安装.[dev,gui]后可用make test test-gui运行两个无硬件套件。核心 CI 与 GUI CI 分开运行。make test-all还会收集 integration 测试,需准备对应依赖及环境。这些 target 的定义见 Makefile;
  • 插件专属的 view/layout 测试放在tests/gui/<plugin>/components/;其他 GUI/plugin 测试放在tests/gui/<plugin>/unit/。Runtime、资源准入与核心行为仍放在tests/unit_tests/runtime/。当前仓库中该布局已经有实例,例如 tests/gui/workbench/unit/test_run_engine.py 与 tests/unit_tests/runtime/;
  • 从jiuwensymbiosis.gui迁移的测试应改为导入jiuwensymbiosis_gui.workbench;只有兼容期入口测试继续实际执行旧模块转发。不要在核心单元测试中导入 NiceGUI 或具体工作台页面。

兼容入口与迁移说明

旧python -m jiuwensymbiosis.gui入口目前只用于迁移兼容:jiuwensymbiosis/gui/__init__.py 现在只是一个声明旧命名空间的 shim。新安装、文档和测试应使用python -m jiuwensymbiosis_gui或jiuwensymbiosis-gui。兼容 shim 移除后会以迁移说明公告。

小结

把独立界面接入 JiuwenSymbiosis 的完整动作序列是:新建一个独立 distribution,用jiuwensymbiosis_gui.plugin契约实现只导入轻量模块的describe()与延迟加载 UI 依赖的main(options);在pyproject.toml中注册与key同名的jiuwensymbiosis.guientry point;main内部建立Runtime(workspace=...)、prepare_binding()冻结配置,把提交/读事件/取消三个短操作交给页面 handler,退出时按close()结果决定是正常结束还是保持停机并禁止自动重启。可运行的最小参照实现始终在 examples/gui_plugin:它不依赖 NiceGUI 与工作台,仅用标准库 HTTP server 演示了 loopback 绑定、同源校验、request_id 重试去重与收尾确认的完整闭环。

  • 具身智能
  • 人工智能
  • AI Agent
  • 机器人
  • 计算机视觉
  • Agent 编排

【免费下载链接】jiuwensymbiosis

Jiuwen Symbiosis就是一个"能懂人话、看得见物理世界、长了四肢的智能助手"。用户不需要示教,不需要教它怎么抓东西,怎么走路,就像指挥一个经验丰富的工人一样,用自然语言下任务,它就能自己完成感知、理解、规划、执行。

项目地址:https://gitcode.com/openJiuwen/jiuwensymbiosis
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询