受 DeepSeek Harness 启发,以 Codex 为案例,探索 Cordis 插件架构在前端应用中的实践。
项目基于 React、TypeScript 和 Tauri,将页面、布局、设置与项目管理等功能组织为 Cordis 插件,探索前端应用的功能组合、依赖管理、UI 扩展和生命周期协作。
项目处于持续探索阶段,架构与功能会随着实际开发逐步调整。
前端应用规模增长后,功能之间的关系往往逐渐分散在路由配置、组件树、全局状态和各种初始化逻辑中。增加一个功能,可能需要同时修改多个位置;移除一个功能,也需要逐一检查它留下的依赖和资源。
这个项目尝试以插件作为功能组织单元,让功能能够声明自己依赖什么、提供什么,以及如何参与应用的运行。
我们关注的问题包括:
- 页面、布局、设置项和工具栏如何由不同插件共同组成?
- 插件之间如何通过明确的服务和扩展点协作?
- 功能依赖如何影响启动顺序与可用状态?
- UI 挂载点和插件的生命周期如何衔接?
- 功能卸载或启动失败时,如何清理事件监听、注册项等资源?
- 哪些能力适合沉淀为基础设施,哪些应由业务插件自己维护?
Codex 提供了一个具体的实践场景:项目与会话、编辑器、设置页面和桌面能力之间存在实际的协作需求,可以用来检验这些设计。
项目基于 Cordis 的插件、服务与生命周期机制,构建面向 React 应用的启动流程和 UI 扩展能力。
| 层次 | 职责 |
|---|---|
| Cordis 基础设施 | 插件目录解析、启动图、服务组织、路由、slot、渲染、国际化与主题 |
| 业务插件 | 应用布局、首页、会话界面、项目管理和设置功能 |
| React | 组件渲染与界面交互 |
| Tauri | 原生窗口、系统能力,以及与 Codex app-server 的通信 |
| Codex app-server | 提供项目、会话等 Agent 后端能力 |
应用通过 cordis.yml 声明参与组合的插件,结合包入口和依赖元数据生成启动图,再由客户端加载并激活:
cordis.yml
↓
插件目录解析与元数据校验
↓
启动图与 Vite 模块注册表
↓
Cordis 插件激活
↓
服务、路由与 UI 扩展点注册
↓
React 界面挂载
当前采用构建时确定插件集合的静态组合方式。插件的运行生命周期由 Cordis 管理;运行时安装、插件市场和不可信插件隔离尚未实现。
项目中的插件承担明确的功能职责。例如:
| 插件 | 职责 |
|---|---|
app-layout |
应用主布局与界面区域 |
conversation |
会话页面外壳与共享编辑器 |
new-thread |
首页内容与任务建议 |
workspace |
项目列表、项目选择、项目创建与会话树 |
settings-layout |
设置页面布局与设置项注册 |
settings-appearance |
外观设置 |
settings-language |
语言设置 |
插件可以注册路由、提供服务,或向其他插件声明的 slot 贡献 UI。
以首页的项目选择为例:
new-thread在首页标题中声明项目展示位置。conversation在编辑器上方声明上下文工具栏位置。workspace向这两个位置提供项目选择界面,共享项目数据与选择状态,并负责创建项目。
两个入口由同一个业务插件实现,宿主页面负责它们出现的位置。项目选择逻辑发生变化时,可以集中在 workspace 中维护。
slot 的声明、贡献与清理跟随相应的生命周期,插件之间无需通过全局 DOM 查询寻找挂载位置。
.
├── packages/
│ ├── shadcn-ui/ # 共享 UI 基础组件
│ └── react-cordis/ # 固定版本的 Git submodule
│ └── packages/
│ ├── boot/ # 客户端启动图与插件激活
│ ├── boot-config/ # 启动配置与包元数据解析
│ ├── vite/ # Vite 启动插件
│ └── … # renderer、router、slots、i18n、theme
├── plugins/ # 页面、布局与业务功能插件
├── src/ # 应用启动入口
├── src-tauri/ # Tauri 原生宿主
├── e2e/ # 浏览器端集成测试
├── docs/ # 设计与实现记录
└── cordis.yml # 应用插件组合清单
react-cordis 承载与具体业务无关的能力,plugins 承载 Codex 案例中的功能。宿主通过 pnpm workspace 引用子模块中的核心包,不安装其中的示例。基础设施不反向依赖业务插件。
核心包统一使用 @react-cordis/*。插件通过包根 exports 导出入口,cordis.inject 声明包依赖,源码 inject 声明服务依赖。插槽契约由声明该插槽的业务插件扩展 SlotContracts。应用导航、i18n Provider、主题样式与字体大小偏好由宿主维护。
目前已经具备:
- 插件配置解析、依赖校验与有序启动。
- 启动失败时的已激活插件回收。
- 基于插件注册的路由与 UI slot。
- 应用布局、独立设置页面、主题与语言设置。
- 首页、任务建议和共享编辑器。
- 项目搜索、选择、创建与项目会话树。
- 面向关键状态与交互的单元测试和浏览器测试。
会话内容展示、消息发送与完整 Agent 执行交互仍在建设中。当前实现适合用于阅读、实验和讨论前端插件架构。
准备以下环境:
- Node.js。
- pnpm,版本以
package.json中的packageManager字段为准。 - 开发桌面端时,需要 Rust 与对应平台的 Tauri 开发依赖。
- 使用项目和会话相关能力时,需要安装兼容当前接口的 Codex CLI,并完成登录。
初始化子模块后,在本仓库根目录安装依赖:
git submodule update --init --recursive
pnpm install首次克隆也可以使用 git clone --recurse-submodules https://github.com/yunzhencc/codex-desktop.git。应用联调的安装、测试和构建统一从宿主根目录执行,使用宿主的 pnpm-lock.yaml;不要在嵌入的子模块内单独安装依赖。
更新核心时,先在独立 react-cordis 仓库提交并推送,再在这里切换到明确的提交:
git -C packages/react-cordis fetch origin
git -C packages/react-cordis checkout --detach <commit>
pnpm install完成下面的检查后,将子模块指针和宿主锁文件一起提交。普通拉取后执行 git submodule update --init --recursive,即可恢复主仓库记录的核心版本。
启动 Web 开发服务器:
pnpm dev启动 Tauri 桌面应用:
pnpm tauri devWeb 模式适合开发和验证界面;目录选择与 app-server 通信等原生能力需要在 Tauri 环境中使用。
常用检查:
# 类型检查
pnpm typecheck
# 代码检查
pnpm lint
# 单元测试
pnpm exec vitest run
# 浏览器测试
pnpm test:e2e:install
pnpm test:e2e
# 前端构建
pnpm build欢迎围绕具体场景讨论和改进:
- 一个功能适合独立为插件,还是保留在已有插件内部?
- 插件接口是否清晰,调用方需要了解多少实现细节?
- UI 扩展点是否具有明确的用途和生命周期?
- 共享状态和副作用应该由谁拥有?
- 现有设计能否通过更直接的实现完成相同目标?
我们希望通过实际功能持续检验架构,在出现明确需求后逐步演进。
项目使用 @deepseek-ai/cordis,并结合 React、Tauri 和 shadcn/ui 开展实践。
本项目是独立的架构探索项目,与 OpenAI 无官方关联。