oh-my-dsh 架构
oh-my-dsh 是一个通过组合已发布 DeepSeek Harness 软件包构建的终端 Coding Agent。TUI 负责终端表现和人机交互;会话、模型、工具、命令、权限、Skills、MCP 集成和 Projection 仍由 Harness 插件负责。
边界
- 运行时:
@deepseek-ai/*软件包以固定版本从 npm 安装,并通过其公开导出使用。 - 产品:
apps/omdsh负责 CLI 和运行时组合;packages/tui/omdsh-tui负责可复用的终端插件套件。 - 参考项目:
refs/deepseek-harness、refs/oh-my-pi和refs/pi是只读的架构与交互设计参考,不参与依赖解析、构建、测试或运行时执行。
本项目刻意避免建立第二套 Agent Core。它将 Harness 能力适配到本地终端,而不重新实现这些能力的领域状态。
软件包布局
apps/omdsh/ @agi-fans/oh-my-dsh
├── src/bin.ts CLI 入口与参数处理
├── src/boot.ts 启动 Harness 插件树
├── src/plugin.ts `omdsh plugin` Profile 安装器
└── config/cordis.yml 产品 bundle insert
packages/tui/omdsh-tui/ @agi-fans/dsh-tui
├── src/index.ts 本地 Provider 插件入口
├── src/definition.ts 与 Provider 无关的 TUI Service
├── src/runtime/ 终端 Provider、会话运行时、Runner、启动提示
├── src/commands/ 斜杠命令贡献插件
├── src/chrome/ 主题、Markdown、渲染、Status、工具卡片
├── src/input/ 按键、编辑器、剪贴板、粘贴
├── src/views/ 会话记录、浮层、搜索、复制
└── src/session/ 会话控制器与 TUI 设置
TUI 软件包从同一个 npm 软件包公开多个 Cordis 入口,因为它们共享依赖与发布周期。只有当一项能力具备独立复用、所有权、依赖或版本管理需求时,才值得拆成新的 npm 软件包。
插件所有权
“一切皆插件”描述的是所有权,而不是文件数量。当一项能力拥有独立的生命周期、配置、依赖集合、注册协议、作用域或替换点时,它才应该成为插件。
- 只有本地 Provider 负责 raw mode、按键解码、光标定位、viewport 状态和原子化终端写入。
session-runtime负责创建 Agent、创建与恢复持久会话、替换活跃会话、选择模型、挂载 Agent preset、应用 preset 决定的工具暴露方式、读取 Projection 和清理资源。- 命令插件通过
dsh-commands注册元数据和处理器;Runner 不维护第二份命令注册表。 - Loop 命令以独立插件负责进程内调度与 Footer Projection。重复 Prompt 仍通过
session-runtime提交;Loop 状态不会写入持久会话历史,并在活跃 Agent 发生变化时丢弃。 - 工具插件负责工具语义和与 Provider 无关的展示意图。TUI 将
ToolDefinition.presentCall和presentResult映射为终端卡片,并保留通用回退展示。 - Harness Projection 插件负责 token、上下文、耗时、标题和会话统计;状态栏只负责格式化这些输出。
session-runtime把带origin: subagent的后代会话投影为 Composer 上方的实时名册,并可以把视口切换到其中一个孩子的 Transcript。可续写的孩子通过 subagent 宿主队列(@deepseek-ai/dsh-subagent/internal的queueHostSubagentPrompt)接收 Composer 的后续消息,保留用户署名与 FIFO 轮次顺序;一次性运行保持只读。子会话日志留在各自的 Session 中,不会回放到父 Transcript。- 三种委托 transport 并存,按次选择而非常量配置。两个进程内后端(
subagent、subagent_fork)在 TUI 自己的事件循环上运行子 Agent,并支持全部由父级强制的启动能力。进程外后端(subagent_isolated,基于dsh-subagent-acp)改为派生一整个子运行时,把重负载委托的 CPU 移出该事件循环;它不声明任何启动期能力、也不实现可续写运行,因此这样的孩子在派发后无法被 steering。 - 人机交互适配器将审批和提问 Service 连接到终端选择器,而不把这些领域迁入终端 Provider。
纯算法仍然保留为内部模块,包括 ANSI 解析、终端显示宽度、Markdown 格式化、编辑器移动、路径匹配、主题映射、帧差分、viewport 切片和 Overlay 状态转换。除非出现第二个拥有独立所有权的适配器并形成真实边界,否则不应将它们改造成运行时插件。
运行时组合
apps/omdsh/config/cordis.yml 是 @agi-fans/oh-my-dsh 产品 bundle,叠在空的 $OMDSH_HOME/profiles/omdsh 根上,其中组合了:
- Cordis Loader 与 Timer 基础设施;
- 官方 DeepSeek LLM Adapter、休眠挂载的 pi-ai 多提供方 Adapter、设置、凭据、默认模型、Agent preset roster、Code Runtime 和 Agent Runtime;
- 持久化 JSONL 会话、Checkpoint、查询、文件引用与跨会话引用、标题、统计与 Token Projection;
- 本地附件、文件系统、子进程、Sandbox 和权限 Provider,以及每台主机唯一的一套 Shell 栈(POSIX 上是
bash,Windows 上是pwsh)及其对应的模型工具; - Standard、PTC、Minimal 与 Cordis 的 Agent-plane 组合,以及 Harness 命令、Compaction、Todo、Goal、Plan、审批、提问和 Subagent;
- 文件系统 Skill 发现以及项目级和用户级 MCP Server Adapter;
- 本地 TUI Provider、工具展示适配桥、Session Runtime、人机交互适配器、命令贡献插件、启动提示和 Runner。
Skills 与 MCP 的部署细节见 skills-and-mcp.md。omdsh plugin add 装入的用户 bundle、Profile 的 cordis.patch.yml,以及可选的 $OMDSH_HOME/cordis.patch.yml 会在启动时叠加在这棵 composition 之上;omdsh --dump-config 打印结果。examples/hello 是可安装的编写样例。见 plugins.md。
数据与交互流
终端输入
→ 本地 TUI Provider
→ Runner 或命令注册表
→ Session Runtime / Harness 能力
→ 持久化会话事件与 Projection
→ 与 Provider 无关的对话和状态 View
→ 差分终端 Renderer
普通消息通过 session-runtime 进入活跃 Agent。Slash Command 通过当前作用域内的 Harness Registry 执行。Agent preset 会在 Agent 发布前完成组合,并通过 Harness 的 Session Metadata 与 Event 记录下来以供重建;工具暴露方式从该 preset 推导,不再写入产品私有 Session Event。产生第一条 Prompt 后,模型可见组合会被锁定。Workflow 与 Access 仍是相互独立、由 Harness 拥有的会话状态。Session Event 是对话回放的持久化事实来源;Projection Service 提供派生状态,TUI 不维护重复计数。工具调用及其结果最终合并为一张卡片,并以 Input 和 Output 分区展示。Composer 旁的 subagent 名册展示任务名称和生命周期状态,后台流式增量不触发名册刷新。Agent Hub 保留持久工具活动,打开子会话对话时会恢复其正在生成的输出。
终端保证
- 布局以终端显示单元格为准,正确处理 ANSI 序列、CJK 文本、emoji、组合字符和不可断行的长内容。
- Composer 和两行状态 Footer 固定在底部,Transcript Viewport 可以独立滚动。
MainScreenRenderer将终端原生 scrollback 视为不可改写的视觉快照。在终端尺寸稳定时,已最终化的行会在离开实时屏幕前完成绘制。进行中的回合保持可变且不成组,结束后实时投影将过程折成一段。生产启动等待初始 session projection。打开或替换文档时采用新的行索引:idle 恢复完整重放快照,running 恢复保留可变尾部,并从缓冲事件重建流式内容。preset、工具列表更新和子 agent 检视等投影刷新只采用当前视口,不重放历史。resume、会话切换、fork 和/clear添加带标签的分界线,/new仅在此前转录存在实际正文时添加;首次启动和投影刷新不需要分界线。原生 scrollback 永不擦除。direct terminal 为全屏界面借用 alternate screen;multiplexer 与 ConPTY 保留宿主 scrollback,multiplexer 的 resize 突发先合并再重新锚定。主转录超出视口时,终端 provider 在折叠态与展开态都会开启 1000/1006 鼠标报告;全屏界面暂时关闭报告,返回后恢复阅读位置。固定在顶部的用户请求和回到最新消息控件属于视口装饰,不写入转录历史。用户命令返回文本时回到实时底部显示结果,后台通知和更新说明保留阅读位置。报告开启期间无法使用原生拖选,键盘复制路径仍可用。Renderer 负责绘制,每帧包在一次 DEC 2026 同步写入中。- 已完成的 Transcript 布局会被缓存,Renderer 只输出发生变化的行,而不重绘整个屏幕。
- Modal Selector 在交互结束前独占输入和光标可见性,结束后恢复 Composer;Prompt 会暂时移开并恢复它所打断的全屏 Overlay,任何确认都不可能在被遮挡的旧屏幕背后被接受。
- 第一次 Ctrl-C 清空输入或中断任务,第二次 Ctrl-C 退出。Ctrl-D 直接退出;存在持久会话时会输出
omdsh --resume <session-id>提示。 - Pipe 模式使用相同的命令与会话语义,但不接管交互式屏幕。
公共接口
软件包支持的公开导出包括 Provider、Service Definition、Session Runtime、人机交互适配器、工具展示适配桥、命令组、启动提示和 Runner。Renderer、Editor、Markdown、Width、Overlay、Clipboard 和 Selector 模块仍是实现细节,即使测试通过相对路径导入它们。
只有在主题、状态栏 Segment、Overlay 或按键 Action 至少出现两个拥有独立所有权的贡献者时,才应引入新的 Contribution Registry。在此之前,配置和内部状态机比预设的公共边界更合适。
验证
- 纯渲染测试覆盖宽度、ANSI、CJK、emoji、边框、Markdown、工具卡片、viewport 行为和光标目标。
- Runtime 测试覆盖命令注册、会话创建与恢复、消息队列、Projection、模型与权限选择、人机交互和资源清理。
pnpm smoke:happy使用已发布 Harness Mock LLM 路径启动完整组合。pnpm smoke通过真实 PTY 运行构建后的命令,验证 raw input、渲染、中断和退出行为。- 依赖边界检查要求使用已发布的 npm 软件包、保持参考子模块干净,并禁止指向
refs/的链接或别名。
改动所需执行的具体命令由 AGENTS.md 规定。