dsh-ccgui 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -2
  3. package/THIRD_PARTY_NOTICES.md +64 -0
  4. package/assets/logo.png +0 -0
  5. package/cordis.patch.yml +32 -0
  6. package/lib/abortable.d.ts +12 -0
  7. package/lib/abortable.js +27 -0
  8. package/lib/adapter.d.ts +39 -0
  9. package/lib/adapter.js +64 -0
  10. package/lib/async-queue.d.ts +26 -0
  11. package/lib/async-queue.js +55 -0
  12. package/lib/cli-session.d.ts +99 -0
  13. package/lib/cli-session.js +384 -0
  14. package/lib/cli-tool.d.ts +18 -0
  15. package/lib/cli-tool.js +101 -0
  16. package/lib/client.js +4039 -0
  17. package/lib/constants.d.ts +22 -0
  18. package/lib/constants.js +22 -0
  19. package/lib/git/git-read.d.ts +65 -0
  20. package/lib/git/git-read.js +139 -0
  21. package/lib/git/git-requests.d.ts +44 -0
  22. package/lib/git/git-requests.js +153 -0
  23. package/lib/git/git-routes.d.ts +38 -0
  24. package/lib/git/git-routes.js +78 -0
  25. package/lib/git/git-run.d.ts +58 -0
  26. package/lib/git/git-run.js +97 -0
  27. package/lib/git/git-status.d.ts +51 -0
  28. package/lib/git/git-status.js +122 -0
  29. package/lib/git/git-write.d.ts +75 -0
  30. package/lib/git/git-write.js +134 -0
  31. package/lib/index.d.ts +78 -0
  32. package/lib/index.js +229 -0
  33. package/lib/model-cache.d.ts +33 -0
  34. package/lib/model-cache.js +101 -0
  35. package/lib/model-catalog.d.ts +63 -0
  36. package/lib/model-catalog.js +107 -0
  37. package/lib/model-options.d.ts +26 -0
  38. package/lib/model-options.js +46 -0
  39. package/lib/model-probe.d.ts +16 -0
  40. package/lib/model-probe.js +91 -0
  41. package/lib/npx-cache.d.ts +39 -0
  42. package/lib/npx-cache.js +97 -0
  43. package/lib/permission.d.ts +17 -0
  44. package/lib/permission.js +34 -0
  45. package/lib/prompt-images.d.ts +44 -0
  46. package/lib/prompt-images.js +46 -0
  47. package/lib/request-input.d.ts +42 -0
  48. package/lib/request-input.js +113 -0
  49. package/lib/session/connect.d.ts +41 -0
  50. package/lib/session/connect.js +68 -0
  51. package/lib/session/drive-tool.d.ts +39 -0
  52. package/lib/session/drive-tool.js +37 -0
  53. package/lib/session/model-state.d.ts +40 -0
  54. package/lib/session/model-state.js +68 -0
  55. package/lib/session/outcomes.d.ts +31 -0
  56. package/lib/session/outcomes.js +67 -0
  57. package/lib/session/permission-queue.d.ts +24 -0
  58. package/lib/session/permission-queue.js +33 -0
  59. package/lib/session/read-step.d.ts +31 -0
  60. package/lib/session/read-step.js +53 -0
  61. package/lib/session/tool-mirror.d.ts +65 -0
  62. package/lib/session/tool-mirror.js +131 -0
  63. package/lib/session/turn-queue.d.ts +76 -0
  64. package/lib/session/turn-queue.js +102 -0
  65. package/lib/session/types.d.ts +58 -0
  66. package/lib/session/types.js +6 -0
  67. package/lib/session-registry.d.ts +124 -0
  68. package/lib/session-registry.js +189 -0
  69. package/lib/stdio-transport.d.ts +28 -0
  70. package/lib/stdio-transport.js +164 -0
  71. package/lib/stream-blocks.d.ts +52 -0
  72. package/lib/stream-blocks.js +95 -0
  73. package/lib/tool-record.d.ts +100 -0
  74. package/lib/tool-record.js +167 -0
  75. package/lib/transport.d.ts +35 -0
  76. package/lib/transport.js +5 -0
  77. package/package.json +136 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zhukunpenglinyutong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,328 @@
1
- # Temporary Holding Version
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/01-hero-dark.webp" />
3
+ <img alt="DSH 截图:输入框旁的 CLI 菜单列出 Claude Code、Codex CLI、PI CLI、OMP CLI、OpenCode、CodeBuddy CLI" src="./assets/illustrations/01-hero-light.webp" />
4
+ </picture>
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ ---
7
+ <div align="center"><a name="readme-top"></a>
8
+
9
+ <img width="120" alt="dsh-ccgui logo" src="./assets/logo.png" />
10
+
11
+ # DSH CCGUI
12
+
13
+ **在 DeepSeek Harness 里直接使用 Claude Code、Codex、PI、OMP 等 Agent CLI**
14
+
15
+ [![CI][ci-shield]][ci-link] [![Coverage][coverage-shield]][coverage-link] [![License][license-shield]][license-link] ![DSH][dsh-shield]
16
+
17
+ ![][stars-shield] ![][forks-shield] ![][issues-shield] ![][contributors-shield]
18
+
19
+ [快速开始](#快速开始) · [功能](#功能) · [接入其他 CLI](#接入其他-cli) · [已知限制](#已知限制) · [开发](#开发)
20
+
21
+ </div>
22
+
23
+ 在输入框旁选一个 CLI,就像用模型一样和它对话。工具调用、审批和改动摘要都走 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的原生界面。
24
+
25
+ ---
26
+
27
+ ## 快速开始
28
+
29
+ 需要 DSH `>=0.2.2-rc.1 <0.3.0`,以及已安装并登录的 CLI。
30
+
31
+ <picture>
32
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/02-install-dark.webp" />
33
+ <img alt="在 DSH 的插件页点「添加插件」,对话框里输入 dsh-ccgui 后点「安装」" src="./assets/illustrations/02-install-light.webp" />
34
+ </picture>
35
+
36
+ 1. 点 DSH 左侧栏的「插件」,再点右上角「添加插件」,输入 `dsh-ccgui`,点「安装」。
37
+ 2. 提示“请重启 DSH 加载它”时,Cmd+Q 完全退出再打开,不要点“立即启用”,否则新旧代码混在一个进程里会启动失败。
38
+ 3. 新建会话,点输入框 `+` 右边的 CLI 按钮选一个 CLI。
39
+
40
+ <details>
41
+ <summary>用命令行安装</summary>
42
+
43
+ ```sh
44
+ # DSH 桌面版:先 Cmd+Q 完全退出,再用应用内置的 dsh 安装
45
+ "/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop add dsh-ccgui@1.0.0
46
+
47
+ # DSH 命令行版(dsh web)
48
+ dsh plugin --profile web add dsh-ccgui@1.0.0
49
+ ```
50
+
51
+ 卸载把 `add ...` 换成 `remove dsh-ccgui`。
52
+
53
+ </details>
54
+
55
+ > [!TIP]
56
+ > 从源码安装时先 `pnpm install && pnpm run build`,再在「添加插件」里填仓库的本地路径(命令行则把 `dsh-ccgui@1.0.0` 换成仓库路径);pnpm 的 peerDependencies 警告可以忽略。
57
+
58
+ <div align="right"><a href="#readme-top">返回顶部 ↑</a></div>
59
+
60
+
61
+ | 功能 | Claude<br>Code | Codex<br>CLI | PI<br>CLI | OMP<br>CLI | Open<br>Code |
62
+ | --- | :---: | :---: | :---: | :---: | :---: |
63
+ | 真实账号验证 | ✅ | ✅ | ✅ | ✅ | ✅ |
64
+ | 免安装(`npx` 自动拉取) | ✅ | ✅ | ✅ | 先装 `omp` | 先装 `opencode` |
65
+ | 会话中途换模型<br><sub>输入框右侧的模型菜单</sub> | ✅ | ✅ | ✅ | ✅ | ✅ |
66
+
67
+ ## 功能
68
+
69
+ ### 切换 CLI 和模型
70
+
71
+ CLI 和模型各有一个菜单。模型菜单列出当前 CLI 真实可用的模型,第一项是它自己的默认模型,超过 4 项可搜索,中途换模型下一条消息生效。CLI 只能在发第一条消息前切换,要换就新建会话。
72
+
73
+ <picture>
74
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/03-model-menu-dark.webp" />
75
+ <img alt="Claude Code 和 Codex CLI 各自的模型菜单截图,第一项是 CLI 自己的默认模型,顶部可以搜索" src="./assets/illustrations/03-model-menu-light.webp" />
76
+ </picture>
77
+
78
+ <details>
79
+ <summary>细节</summary>
80
+
81
+ - 没安装的 CLI 也会列出来,发消息时提示无法启动。
82
+ - 选中 CLI 后插件才启动它询问模型,刚选中时菜单里只有它的名字,通常 1 秒左右换成真实列表;`npx` 启动的 CLI 第一次还要等下载。
83
+ - 费用和账号都走 CLI 自己的配置。比如 Codex 配了中转服务时,菜单里的模型不一定都能用,错误会原样显示。
84
+ - 只能在标准模式下使用。PTC 模式只接受 `run_code`,会提示切回标准模式。
85
+
86
+ </details>
87
+
88
+ ### 变更面板
89
+
90
+ 右侧栏「变更」把改动分成已暂存、未暂存、未跟踪,可以暂存、撤销、提交、推拉、切换分支,点开文件看行内 diff。
91
+
92
+ <picture>
93
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/04-changes-dark.webp" />
94
+ <img alt="变更面板截图:改动按已暂存、未暂存、未跟踪分组,旁边是一个文件的行内 diff" src="./assets/illustrations/04-changes-light.webp" />
95
+ </picture>
96
+
97
+ ### 启动脚本
98
+
99
+ 在会话标题栏保存常用命令,按工作目录分别记住。点一下在单独的终端标签里运行,再点会先关掉上一次的。
100
+
101
+ <picture>
102
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/05-launch-scripts-dark.webp" />
103
+ <img alt="启动脚本截图:会话标题栏的脚本菜单,和终端里运行中的 npm run dev" src="./assets/illustrations/05-launch-scripts-light.webp" />
104
+ </picture>
105
+
106
+ ### 文件和终端
107
+
108
+ 「工作区文件」里右键「添加到对话」,把 `@路径` 插到光标处;标题栏的终端按钮一键开关右侧栏终端。
109
+
110
+ <picture>
111
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/06-workspace-tools-dark.webp" />
112
+ <img alt="截图:文件右键菜单的「添加到对话」、插入输入框的 @路径,以及标题栏的终端按钮" src="./assets/illustrations/06-workspace-tools-light.webp" />
113
+ </picture>
114
+
115
+ ### 会话状态
116
+
117
+ 会话列表里每个会话前是它所用 CLI 的图标,回合进行中旁边多一个呼吸的蓝点。
118
+
119
+ <picture>
120
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/07-session-status-dark.webp" />
121
+ <img alt="会话列表截图:每个会话前是所用 CLI 的图标,运行中的会话旁有蓝点" src="./assets/illustrations/07-session-status-light.webp" />
122
+ </picture>
123
+
124
+ ### CC GUI 设置
125
+
126
+ DSH 设置里的「CC GUI 设置」可以调整 CLI 菜单的顺序和开关,插件加的每项界面交互也能单独关掉,恢复 DSH 原样。
127
+
128
+ <picture>
129
+ <source media="(prefers-color-scheme: dark)" srcset="./assets/illustrations/08-settings-dark.webp" />
130
+ <img alt="CC GUI 设置截图:CLI 列表的顺序和开关,后面是界面交互的开关" src="./assets/illustrations/08-settings-light.webp" />
131
+ </picture>
132
+
133
+ <details>
134
+ <summary>细节</summary>
135
+
136
+ - 入口在左侧栏底部的设置,排在「插件市场」后面。设置保存在浏览器本地。
137
+ - DeepSeek 是内置模型提供方,始终保留,不能关闭。关掉的 CLI 不再出现在菜单里,行内标出「已关闭」。
138
+ - 「界面交互」页签可分别关闭:右侧栏默认打开工作区文件、会话顶部的终端按钮、启动脚本。
139
+ - 「更多功能」页签列出还没开放的功能(会话自动命名、WorkTree、Git 信息自动生成、增强提示词、AI 账号限额提示),目前只有说明。
140
+ - CodeBuddy CLI 尚未用真实账号验证,默认关闭,需要时在这里打开。
141
+ - 列出的 CLI 来自打开过的会话,第一次使用前先打开任一会话。
142
+
143
+ </details>
144
+
145
+ <div align="right"><a href="#readme-top">返回顶部 ↑</a></div>
146
+
147
+ ## 审批与停止
148
+
149
+ > [!WARNING]
150
+ > **DSH 的沙箱管不到 CLI。** CLI 自己决定哪些操作要询问,它放行的操作不会弹审批。要拦住操作,请配置 CLI 自己的权限。
151
+
152
+ | CLI | 写文件和执行命令 | 按停止 |
153
+ | --- | --- | --- |
154
+ | Claude Code | auto 模式不询问;手动模式走 DSH 审批卡片 | 命令结束;**放到后台的命令继续跑** |
155
+ | Codex CLI | 不询问,直接执行 | 回合结束;**已启动的命令继续跑** |
156
+ | PI CLI | 不询问,直接执行 | 命令结束 |
157
+ | OMP CLI | 不询问,直接执行 | 回合结束 |
158
+ | OpenCode | 按它的权限配置询问,走 DSH 审批卡片 | 命令结束 |
159
+ | CodeBuddy CLI | 尚未验证 | 尚未验证 |
160
+
161
+ 继续跑的后台命令在会话关闭时才结束。表中是默认配置下在一台机器上的实测结果,配置和版本不同结果也可能不同。
162
+
163
+ <details>
164
+ <summary>让 Claude Code / OpenCode 先询问再执行</summary>
165
+
166
+ Claude Code(项目的 `.claude/settings.json`,直接用 `claude` 时同样生效):
167
+
168
+ ```json
169
+ { "permissions": { "defaultMode": "default" } }
170
+ ```
171
+
172
+ OpenCode(项目的 `opencode.json`):
173
+
174
+ ```json
175
+ { "permission": { "edit": "ask", "bash": "ask" } }
176
+ ```
177
+
178
+ </details>
179
+
180
+ ## 接入其他 CLI
181
+
182
+ 在 profile 的 `cordis.patch.yml` 里加一个 agent,它就会出现在 CLI 菜单里。这段 `config` 会整体替换默认配置,所以默认的几个也要写上:
183
+
184
+ ```yaml
185
+ - id: ccgui
186
+ name: dsh-ccgui
187
+ config:
188
+ agents:
189
+ - { id: claude, name: Claude Code, command: npx, args: [-y, '@agentclientprotocol/claude-agent-acp@0.101.0'] }
190
+ - { id: codex, name: Codex CLI, command: npx, args: [-y, '@agentclientprotocol/codex-acp@2.5.0'] }
191
+ - { id: pi, name: PI CLI, command: npx, args: [-y, 'pi-acp@0.0.34'] }
192
+ - { id: omp, name: OMP CLI, command: omp, args: [acp] }
193
+ - { id: opencode, name: OpenCode, command: opencode, args: [acp] }
194
+ - { id: codebuddy, name: CodeBuddy CLI, command: codebuddy, args: [--acp] }
195
+ - { id: kimi, name: Kimi, command: kimi, args: [acp] }
196
+ ```
197
+
198
+ > [!TIP]
199
+ > 用 `npx` 启动时请写死版本号。这样从第二次起插件会直接运行已缓存的包,省掉 `npx` 每次向 npm 询问的时间(实测 Pi 要多等 4 到 26 秒)。不要用 `--prefer-offline`:缓存元数据旧于固定版本时会启动失败,还会通过环境变量影响 Agent 在你项目里跑的 npm 命令。
200
+
201
+ <div align="right"><a href="#readme-top">返回顶部 ↑</a></div>
202
+
203
+ ## 已知限制
204
+
205
+ - 不支持 `/compact`、fork、回滚和运行中插话;运行中发的消息会排队到这一轮结束。
206
+ - 只转发文本和你自己写的消息、定时任务;图片和 DSH 写给自己模型的提醒不会发给 CLI。
207
+ - 上下文用量显示不准,因为 CLI 自己管理上下文。
208
+ - 每个打开的会话占一个 CLI 进程,会话关闭或 DSH 退出时才结束。
209
+
210
+ <details>
211
+ <summary>更多限制</summary>
212
+
213
+ - `/model` 命令和 DeepSeek 等提供方的模型菜单仍会列出所有提供方。不要用它们在同一会话里在 DeepSeek 和 CLI 之间切换:切到 CLI 时它没有之前的上下文,切出时压缩摘要可能失败。同一 CLI 内换模型没问题。
214
+ - 每次启动 DSH 后第一次用到某个 CLI,会短暂启动它询问模型。临时会话放在 DSH 主目录的 `ccgui/model-probe`;能删的会删掉,OMP 只支持关闭,每次会留下约 4KB 记录。
215
+ - CLI 启动的时间上限是 `startTimeoutMs`,默认 120 秒。Claude Code 的适配器连同依赖约 400MB,实测第一次启动约 100 秒,网速慢时要先调大。
216
+ - CodeBuddy CLI 要先登录或配好 API Key,否则提示 `Authentication required`。
217
+ - “默认”后面的模型名按用户级配置询问得到,项目配置(比如 `opencode.json`)指定了别的模型时名字可能对不上,但实际仍按 CLI 的设置走。
218
+ - Codex 按停止后已启动的命令留在后台,DSH 退出时才清理,ACP 没有提供停止它们的办法。Claude Code 会把预计超过 10 分钟的命令放到后台,同样如此;它也拒绝单独的 `sleep`。
219
+ - 失败那一轮的消息不会自动补发(见[工作原理](#工作原理)),需要重新发送。
220
+ - CLI 的错误原文会显示并进入会话日志,可能带路径等信息。
221
+ - 折叠行显示内部名 `cli_tool`,状态文字仍是 DSH 的“深度求索中”。
222
+
223
+ </details>
224
+
225
+ ## 工作原理
226
+
227
+ CLI 注册为 DSH 的模型提供方,不需要改 agent loop。一轮 CLI 回合跨越多个 DSH 步骤:每遇到工具调用就结束这一步,CLI 上报结果后下一步接着读。工具都由 CLI 自己执行,DSH 侧只做记录。
228
+
229
+ <details>
230
+ <summary>展开细节</summary>
231
+
232
+ - `cli_tool` 只负责记录,只对 CLI 的步骤开放,名字也不和任何真工具相同,不会把命令再执行一遍。
233
+ - Claude Code 不上报“运行中”。插件在命令的上报停下 1 秒后就显示它,否则长命令运行时界面一片空白。其他工具要等询问审批或完成时才显示,避免冻结一张没有 diff 的卡片。
234
+ - 模型列表来自 ACP 的 `configOptions`。它只在会话上报告,所以第一次选中某个 CLI 时插件会开一个临时会话询问,不发消息、不调用模型,清理放在后台。没选过的 CLI 不会被启动。换模型在每轮开始前用 `session/set_config_option`。
235
+ - 写死版本且已缓存的 `npx` 包直接运行;缓存不完整、版本不符、`.npmrc` 改过缓存目录或在 Windows 上时仍交给 `npx`。
236
+ - 权限请求转给 DSH 审批界面。CLI 的会话 ID 存在会话日志里,重启后优先用 `session/resume` 接回。
237
+ - 一条用户消息最多发给 CLI 一次,重发可能让它重复做事,所以重试是关闭的。
238
+ - DSH 这一轮一结束(包括在两步之间按停止),插件就取消 CLI 的回合并丢弃剩余输出。
239
+
240
+ </details>
241
+
242
+ ## 账号和条款
243
+
244
+ 插件不处理登录、不接触 token,只沿用 CLI 已有的登录状态。各 CLI 的订阅账号能否在第三方工具里使用,以它们自己的条款为准。
245
+
246
+ DSH 启动 CLI 时会去掉名字含 `KEY`、`PASSWORD`、`SECRET`、`TOKEN` 的环境变量,`ANTHROPIC_API_KEY` 这类变量要写在 agent 配置的 `env` 里。
247
+
248
+ <details>
249
+ <summary>关于 Claude Code</summary>
250
+
251
+ - **依赖**:经 [claude-agent-acp](https://github.com/agentclientprotocol/claude-agent-acp)(Apache-2.0)运行,它基于 Claude Agent SDK。SDK 不是开源协议,本插件没有依赖或打包它,是 `npx` 在你机器上从 npm 下载的。
252
+ - **账号**:登录订阅账号时,用量[计入订阅额度](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan)。Anthropic 的[条款](https://code.claude.com/docs/en/legal-and-compliance)要求基于 Agent SDK 开发产品的开发者使用 API Key,不允许代用户通过订阅凭据发送请求,并保留不经通知采取措施的权利。分发带这个默认项的版本前请先确认。
253
+ - **图标**:Claude 图标和 CC GUI 相同,图形来自 lobe-icons。条款允许纯文本提及 Claude Code,其他方式使用 Anthropic 的名称和 Logo 需要书面许可,分发前请一并确认。
254
+
255
+ </details>
256
+
257
+ <div align="right"><a href="#readme-top">返回顶部 ↑</a></div>
258
+
259
+ ## 开发
260
+
261
+ ```sh
262
+ pnpm test # 单元和集成测试
263
+ pnpm run coverage
264
+ pnpm run typecheck
265
+ ```
266
+
267
+ <details>
268
+ <summary>端到端测试</summary>
269
+
270
+ 驱动真实的 DSH Web 界面,全程隔离在 `/tmp` 并关闭遥测。需要一份已构建的 DSH 源码(没有 `.git` 时构建要加 `DSH_CLIENT_COMMIT_HASH=0000000`):
271
+
272
+ ```sh
273
+ export DSH_REPO=/path/to/deepseek-harness
274
+ export CHROMIUM_PATH=/path/to/chromium # Playwright 自带的浏览器版本不匹配时才需要
275
+
276
+ e2e/start-server.sh & # 另开一个终端也可以
277
+ node e2e/fake-cli.mjs # 脚本化的假 CLI,结果确定
278
+
279
+ # 真实 CLI,会消耗对应账号的额度,只发几条短消息。选一个便宜的模型:
280
+ E2E_CLI='Claude Code' E2E_CLI_ID=claude E2E_MODEL='Haiku 5.5' node e2e/real-cli.mjs
281
+ E2E_CLI='Codex CLI' E2E_CLI_ID=codex E2E_KEEPS_BACKGROUND_COMMANDS=1 node e2e/real-cli.mjs
282
+ E2E_CLI='PI CLI' E2E_CLI_ID=pi E2E_MODEL='deepseek/DeepSeek V4.1 Flash' node e2e/real-cli.mjs
283
+ E2E_CLI='OMP CLI' E2E_CLI_ID=omp node e2e/real-cli.mjs
284
+ E2E_CLI=OpenCode E2E_CLI_ID=opencode E2E_MODEL='opencode/DeepSeek V4.1 Flash Free' node e2e/real-cli.mjs
285
+ E2E_CLI='CodeBuddy CLI' E2E_CLI_ID=codebuddy node e2e/real-cli.mjs # 要先登录
286
+
287
+ # 停掉服务后重启,验证恢复会话、且不重放失败的消息:
288
+ e2e/start-server.sh --keep &
289
+ E2E_CLI_ID=opencode node e2e/real-cli-resume.mjs # 每个 CLI 各跑一次
290
+ node e2e/fake-cli-resume.mjs
291
+ ```
292
+
293
+ 真实 CLI 的测试会在它们各自的历史里留下几条工作目录在 `/tmp` 下的会话。
294
+
295
+ </details>
296
+
297
+ ## 贡献者
298
+
299
+ <a href="https://github.com/zhukunpenglinyutong/dsh-ccgui/graphs/contributors">
300
+ <img src="https://contrib.rocks/image?repo=zhukunpenglinyutong/dsh-ccgui" alt="Contributors" />
301
+ </a>
302
+
303
+ <details>
304
+ <summary><kbd>Star History</kbd></summary>
305
+
306
+ [![Star History Chart](https://star-history.dera.page/svg?repos=zhukunpenglinyutong/dsh-ccgui&type=date&legend=top-left)](https://star-history.dera.page/#zhukunpenglinyutong/dsh-ccgui&type=date&legend=top-left)
307
+
308
+ </details>
309
+
310
+ ---
311
+
312
+ ## 协议
313
+
314
+ MIT。`src/stdio-transport.ts` 改编自 DSH 的 `subagent-acp`(同为 MIT),见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。ACP 行为依据[公开规范](https://agentclientprotocol.com/protocol/overview)和对真实 CLI 的抓包实现。
315
+
316
+ <!-- LINK GROUP -->
317
+
318
+ [ci-shield]: https://img.shields.io/github/actions/workflow/status/zhukunpenglinyutong/dsh-ccgui/ci.yml?branch=main&label=CI&labelColor=black&style=flat-square
319
+ [ci-link]: https://github.com/zhukunpenglinyutong/dsh-ccgui/actions/workflows/ci.yml
320
+ [coverage-shield]: https://img.shields.io/codecov/c/github/zhukunpenglinyutong/dsh-ccgui?label=coverage&labelColor=black&style=flat-square
321
+ [coverage-link]: https://codecov.io/gh/zhukunpenglinyutong/dsh-ccgui
322
+ [license-shield]: https://img.shields.io/github/license/zhukunpenglinyutong/dsh-ccgui?color=white&labelColor=black&style=flat-square
323
+ [license-link]: ./LICENSE
324
+ [dsh-shield]: https://img.shields.io/badge/DSH-%3E%3D0.2.2--rc.1%20%3C0.3.0-4d6bfe?labelColor=black&style=flat-square
325
+ [stars-shield]: https://img.shields.io/github/stars/zhukunpenglinyutong/dsh-ccgui?color=ffcb47&labelColor=black&style=flat-square
326
+ [forks-shield]: https://img.shields.io/github/forks/zhukunpenglinyutong/dsh-ccgui?color=8ae8ff&labelColor=black&style=flat-square
327
+ [issues-shield]: https://img.shields.io/github/issues/zhukunpenglinyutong/dsh-ccgui?color=ff80eb&labelColor=black&style=flat-square
328
+ [contributors-shield]: https://img.shields.io/github/contributors/zhukunpenglinyutong/dsh-ccgui?color=c4f042&labelColor=black&style=flat-square
@@ -0,0 +1,64 @@
1
+ # Third-party notices
2
+
3
+ ## DeepSeek Harness
4
+
5
+ `src/stdio-transport.ts` adapts the process teardown ladder and ACP wiring from
6
+ `packages/subagent/subagent-acp/src/run.ts` in
7
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
8
+
9
+ MIT License
10
+
11
+ Copyright (c) 2026 DeepSeek
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+
31
+ ## Product marks
32
+
33
+ `src/client/marks.tsx` draws other projects' marks so a person can tell the CLIs apart.
34
+ The names and marks belong to their owners, are not covered by this project's MIT
35
+ license, and imply no endorsement.
36
+
37
+ - OMP: the mark from [omp.sh](https://omp.sh). No license for it has been confirmed.
38
+ - Claude: [Anthropic's terms](https://code.claude.com/docs/en/legal-and-compliance) call for
39
+ written permission to use its logo beyond naming Claude Code in plain text. No such
40
+ permission has been confirmed. The license below covers only the SVG path.
41
+ - OpenAI (shown for Codex), Claude, Pi, OpenCode, CodeBuddy and DeepSeek: the SVG paths from
42
+ [lobe-icons](https://github.com/lobehub/lobe-icons), under this license:
43
+
44
+ MIT License
45
+
46
+ Copyright (c) 2023 LobeHub
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining a copy
49
+ of this software and associated documentation files (the "Software"), to deal
50
+ in the Software without restriction, including without limitation the rights
51
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
52
+ copies of the Software, and to permit persons to whom the Software is
53
+ furnished to do so, subject to the following conditions:
54
+
55
+ The above copyright notice and this permission notice shall be included in all
56
+ copies or substantial portions of the Software.
57
+
58
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
59
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
60
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
61
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
62
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
63
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
64
+ SOFTWARE.
Binary file
@@ -0,0 +1,32 @@
1
+ # Each entry under `agents` becomes a provider named `cli-<id>`, which adds the CLI to
2
+ # the composer's switcher and the model menu. The names and their order follow
3
+ # desktop-cc-gui, whose experience this plugin brings to DSH.
4
+ - insert:
5
+ - id: ccgui
6
+ name: dsh-ccgui
7
+ config:
8
+ agents:
9
+ - id: claude
10
+ name: Claude Code
11
+ command: npx
12
+ args: [-y, '@agentclientprotocol/claude-agent-acp@0.101.0']
13
+ - id: codex
14
+ name: Codex CLI
15
+ command: npx
16
+ args: [-y, '@agentclientprotocol/codex-acp@2.5.0']
17
+ - id: pi
18
+ name: PI CLI
19
+ command: npx
20
+ args: [-y, 'pi-acp@0.0.34']
21
+ - id: omp
22
+ name: OMP CLI
23
+ command: omp
24
+ args: [acp]
25
+ - id: opencode
26
+ name: OpenCode
27
+ command: opencode
28
+ args: [acp]
29
+ - id: codebuddy
30
+ name: CodeBuddy CLI
31
+ command: codebuddy
32
+ args: [--acp]
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Abortable wait over a promise the caller does not own.
3
+ * @module dsh-ccgui/abortable
4
+ */
5
+ /**
6
+ * Wait for a promise, giving up early when the signal aborts. The underlying
7
+ * work keeps running; only this wait ends.
8
+ * @param promise - the work to wait for.
9
+ * @param signal - optional cancellation for the wait.
10
+ * @returns the promise's value.
11
+ */
12
+ export declare function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Abortable wait over a promise the caller does not own.
3
+ * @module dsh-ccgui/abortable
4
+ */
5
+ /**
6
+ * Wait for a promise, giving up early when the signal aborts. The underlying
7
+ * work keeps running; only this wait ends.
8
+ * @param promise - the work to wait for.
9
+ * @param signal - optional cancellation for the wait.
10
+ * @returns the promise's value.
11
+ */
12
+ export async function abortable(promise, signal) {
13
+ if (signal === undefined)
14
+ return promise;
15
+ signal.throwIfAborted();
16
+ const { promise: aborted, reject } = Promise.withResolvers();
17
+ const onAbort = () => {
18
+ reject(signal.reason);
19
+ };
20
+ signal.addEventListener('abort', onAbort, { once: true });
21
+ try {
22
+ return await Promise.race([promise, aborted]);
23
+ }
24
+ finally {
25
+ signal.removeEventListener('abort', onAbort);
26
+ }
27
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Presents CLIs to the harness as model providers, which puts them in the model
3
+ * menu and lets the agent loop drive them without loop changes.
4
+ * @module dsh-ccgui/adapter
5
+ */
6
+ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
7
+ import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm';
8
+ import type { CliSession } from './cli-session.ts';
9
+ export { DEFAULT_MODEL_ID } from './constants.ts';
10
+ /** What the adapter needs from the bridge. */
11
+ export interface AdapterDeps {
12
+ /** Display name for a provider route, or undefined when it is not a known CLI. */
13
+ labelFor(provider: string): string | undefined;
14
+ /** The live CLI session for a harness session; throws when it cannot run here. */
15
+ resolveSession(provider: string, sessionId: string): CliSession;
16
+ /** Ids of the user messages in the session's open turn. */
17
+ currentTurnMessageIds(sessionId: string): ReadonlySet<string>;
18
+ /** The models to offer for a route. */
19
+ modelsFor(provider: string): readonly LlmModelInfo[];
20
+ /** The display name of one model. */
21
+ modelName(provider: string, model: string): string;
22
+ }
23
+ /** Routes harness model requests to CLI sessions. */
24
+ export declare class CliBridgeAdapter extends LlmAdapter {
25
+ private readonly deps;
26
+ constructor(deps: AdapterDeps);
27
+ providerInfo(provider: string): LlmProviderInfo;
28
+ providerRetryPolicy(_provider: string): ResolvedRetryPolicy;
29
+ listModels(provider: string): Promise<readonly LlmModelInfo[]>;
30
+ resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
31
+ /**
32
+ * Run one harness step on the session's CLI. Auxiliary requests such as summaries
33
+ * expect a stateless model; sending one here would inject it into the user's
34
+ * real conversation, so they are refused.
35
+ * @param options - the assembled request; only messages, session, and signal apply.
36
+ * @yields stream chunks from the CLI session.
37
+ */
38
+ stream(options: GenerateOptions): AsyncGenerator<StreamChunk>;
39
+ }
package/lib/adapter.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Presents CLIs to the harness as model providers, which puts them in the model
3
+ * menu and lets the agent loop drive them without loop changes.
4
+ * @module dsh-ccgui/adapter
5
+ */
6
+ import { LlmAdapter, LlmError, resolveRetryPolicy } from '@deepseek-ai/dsh-llm';
7
+ export { DEFAULT_MODEL_ID } from "./constants.js";
8
+ /**
9
+ * Larger than any real conversation. Compaction reads this as remaining room, so
10
+ * it never starts on a history the CLI does not read, and declaring a value at
11
+ * all keeps a model switch to this route from failing its pressure check.
12
+ */
13
+ const CONTEXT_WINDOW = 1_000_000_000;
14
+ /** A retry would repeat a prompt the CLI may already have acted on. */
15
+ const NO_RETRY = resolveRetryPolicy({ mode: 'normal', maxRetries: 0 }, 'dsh-ccgui');
16
+ /** Routes harness model requests to CLI sessions. */
17
+ export class CliBridgeAdapter extends LlmAdapter {
18
+ deps;
19
+ constructor(deps) {
20
+ super();
21
+ this.deps = deps;
22
+ }
23
+ providerInfo(provider) {
24
+ return { id: provider, name: this.deps.labelFor(provider) ?? provider };
25
+ }
26
+ providerRetryPolicy(_provider) {
27
+ return NO_RETRY;
28
+ }
29
+ listModels(provider) {
30
+ return Promise.resolve(this.deps.modelsFor(provider));
31
+ }
32
+ resolveModel(provider, model) {
33
+ return Promise.resolve({
34
+ provider,
35
+ id: model,
36
+ name: this.deps.modelName(provider, model),
37
+ context: { contextWindow: CONTEXT_WINDOW },
38
+ });
39
+ }
40
+ /**
41
+ * Run one harness step on the session's CLI. Auxiliary requests such as summaries
42
+ * expect a stateless model; sending one here would inject it into the user's
43
+ * real conversation, so they are refused.
44
+ * @param options - the assembled request; only messages, session, and signal apply.
45
+ * @yields stream chunks from the CLI session.
46
+ */
47
+ async *stream(options) {
48
+ const { provider, model, sessionId, messages, signal, purpose } = options;
49
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- not in the current type, but other host versions may still send it
50
+ if (purpose !== undefined) {
51
+ throw new LlmError(`CLI 会话不支持辅助模型请求(${String(purpose)})。`, 'UNSUPPORTED_OPTION');
52
+ }
53
+ if (sessionId === undefined) {
54
+ throw new LlmError('CLI 只能在会话里使用,不支持一次性模型请求。', 'UNSUPPORTED_OPTION');
55
+ }
56
+ const session = this.deps.resolveSession(provider, sessionId);
57
+ yield* session.stream({
58
+ messages,
59
+ signal,
60
+ model,
61
+ currentTurnMessageIds: this.deps.currentTurnMessageIds(sessionId),
62
+ });
63
+ }
64
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Single-reader async queue. CLI output arrives on notification callbacks while
3
+ * the harness pulls it from an async generator, and a step can stop reading with
4
+ * events still buffered for the next step.
5
+ * @module dsh-ccgui/async-queue
6
+ */
7
+ /** FIFO buffer with one abortable waiting reader. */
8
+ export declare class AsyncQueue<T> {
9
+ private items;
10
+ private waiter;
11
+ /** Number of buffered items. */
12
+ get size(): number;
13
+ /**
14
+ * Hand an item to the waiting reader, or buffer it.
15
+ * @param item - the item to deliver.
16
+ */
17
+ push(item: T): void;
18
+ /**
19
+ * Read the next item, waiting if necessary. Aborting leaves buffered items in place.
20
+ * @param signal - optional cancellation for this read.
21
+ * @returns the next item.
22
+ */
23
+ next(signal?: AbortSignal): Promise<T>;
24
+ /** Drop all buffered items. */
25
+ clear(): void;
26
+ }