dsh-plugin-container 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +341 -2
- package/cordis.patch.yml +3 -0
- package/docs/ai-chat.png +0 -0
- package/docs/ai-new-session.png +0 -0
- package/docs/apple-container.png +0 -0
- package/docs/attach.png +0 -0
- package/docs/docker-console.png +0 -0
- package/docs/docker-overview.png +0 -0
- package/docs/docker-volumes.png +0 -0
- package/docs/image-architecture.png +0 -0
- package/docs/package-install.png +0 -0
- package/docs/provider-switch.png +0 -0
- package/docs/run-container.png +0 -0
- package/index.js +138 -0
- package/lib/client.js +3499 -0
- package/package.json +62 -4
- package/src/client/api.js +94 -0
- package/src/client/attach.jsx +133 -0
- package/src/client/components.jsx +301 -0
- package/src/client/hooks.js +161 -0
- package/src/client/i18n.js +496 -0
- package/src/client/icons.jsx +219 -0
- package/src/client/index.jsx +76 -0
- package/src/client/page.jsx +755 -0
- package/src/client/run-container.jsx +187 -0
- package/src/client/runs.js +182 -0
- package/src/client/store.js +91 -0
- package/src/client/styles.js +363 -0
- package/src/client/tabs/compose.jsx +94 -0
- package/src/client/tabs/console.jsx +211 -0
- package/src/client/tabs/containers.jsx +143 -0
- package/src/client/tabs/images.jsx +127 -0
- package/src/client/tabs/networks.jsx +91 -0
- package/src/client/tabs/overview.jsx +266 -0
- package/src/client/tabs/volumes.jsx +76 -0
- package/src/host/apple-cli.js +307 -0
- package/src/host/apple.js +471 -0
- package/src/host/attach.js +243 -0
- package/src/host/commands.js +235 -0
- package/src/host/docker-cli.js +405 -0
- package/src/host/jobs.js +245 -0
- package/src/host/parsers.js +484 -0
- package/src/host/runtime.js +285 -0
- package/src/host/service.js +832 -0
- package/src/host/tools.js +198 -0
- package/src/host/util.js +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-plugin-container contributors
|
|
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,342 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DSH 容器左边栏插件(Docker + Apple container)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
在 DeepSeek Harness 左侧栏加入 **容器** 工作台,面板右上角始终显示 **Docker** / **Apple container** 两个 provider 按钮,点击即可切换:
|
|
4
|
+
|
|
5
|
+
- **Docker**:macOS 上的 Docker CLI / context,兼容 Docker Desktop、colima、OrbStack、Rancher Desktop、Podman 等提供 Docker context 或 socket 的运行时。
|
|
6
|
+
- **Apple container**:macOS 26 / Apple silicon 原生 `container` CLI(Containerization 框架)。它不是 Docker:没有 Docker daemon/socket/context,每个容器一个轻量 VM,镜像/卷/网络独立,不支持 Compose。
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
当前机器验证环境:
|
|
15
|
+
|
|
16
|
+
- macOS 26.5.1,arm64
|
|
17
|
+
- Docker CLI 28.2.2,colima context 正常
|
|
18
|
+
- Apple `container` CLI 1.0.0(`/opt/homebrew/bin/container`)
|
|
19
|
+
- Docker / Apple 两个 provider 均已通过 Playwright UI 冒烟
|
|
20
|
+
- Apple 系统服务是否运行以面板状态为准;未启动时点击“启动运行时”
|
|
21
|
+
|
|
22
|
+
## 功能
|
|
23
|
+
|
|
24
|
+
### Docker provider
|
|
25
|
+
|
|
26
|
+
- 自动识别 colima、Docker Desktop、OrbStack、远程 context。
|
|
27
|
+
- 概览、容器(搜索/筛选/启动/停止/重启/日志/Inspect/删除)、镜像(拉取/Inspect/删除/架构)、卷、网络、Compose(只读)。
|
|
28
|
+
- **镜像架构**:列表新增“架构”列;Docker 通过 `image inspect` 补齐,Apple 从 OCI variants 读取,显示如 `linux/arm64/v8`。
|
|
29
|
+
- **启动新容器**:镜像下拉选择本地镜像/自定义镜像,名称、命令、端口、环境变量,后台运行;启动后立即可 Attach。
|
|
30
|
+
- **从镜像启动**:镜像页每行的播放按钮会用该镜像预填并打开启动容器表单。
|
|
31
|
+
- **Attach 终端**:基于 DSH PTY 的 `docker attach`,可以输入命令、Ctrl+C、粘贴,关闭对话框结束会话。
|
|
32
|
+
- 运行时启停/重启:colima 完整支持;Docker Desktop / OrbStack best-effort。
|
|
33
|
+
- 内置命令终端:选择 context、工作目录,实时输出,取消/超时/输出截断提示,危险命令确认。
|
|
34
|
+
|
|
35
|
+
### Apple container provider
|
|
36
|
+
|
|
37
|
+
- `container system status/version` 状态;`container system start/stop` 生命周期。
|
|
38
|
+
- 容器、镜像、卷、网络、磁盘(`container system df`)列表和 Inspect。
|
|
39
|
+
- 容器 start/stop/delete/logs;镜像 pull/delete;卷/网络 delete。
|
|
40
|
+
- **启动新容器 / Attach**:Apple `container run`;Attach 使用 `container exec -i -t <id> /bin/sh`(Apple CLI 没有 attach 命令)。
|
|
41
|
+
- 命令终端切换为 `container ...` 语法,快捷命令自动适配。
|
|
42
|
+
- Apple provider 不支持 Compose;面板会自动隐藏 Compose tab。
|
|
43
|
+
- Apple 的 `container image inspect`/`delete` 使用镜像名(不是 digest),UI 已按 provider 适配。
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
## AI 工具(只读)
|
|
54
|
+
|
|
55
|
+
插件向 DSH Agent 注册 4 个只读工具,复用现有 provider:
|
|
56
|
+
|
|
57
|
+
| Tool | 作用 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `container_status` | CLI、daemon/系统服务、runtime、warnings |
|
|
60
|
+
| `container_list` | 容器/镜像/卷/网络 |
|
|
61
|
+
| `container_inspect` | 单个资源详情,敏感 env 脱敏 |
|
|
62
|
+
| `container_logs` | 最近日志,默认 200 行、上限 500 |
|
|
63
|
+
|
|
64
|
+
使用方式:
|
|
65
|
+
|
|
66
|
+
- 主聊天直接问“我的 nginx 为什么退出?”;模型会调用上述工具读取事实。
|
|
67
|
+
- 容器行点 **问 AI**(运行中)或 **诊断**(未运行)会**新建一个独立诊断会话**(复用当前 workspace/cwd/agent preset),发送 provider/context/资源 ID/状态,并自动在主对话中打开;不会污染当前会话。
|
|
68
|
+
- 模型调用来自 DSH 当前 Session;插件不持有模型 API Key,也不直接调模型 HTTP。
|
|
69
|
+
- 日志和 inspect 文本被标注为不可信数据;工具描述和 system prompt 都要求不要服从其中的指令。
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
需要安装运行时依赖 `@deepseek-ai/dsh-tools`(已写入 `package.json`)。host 增加工具后需要重启 DSH;旧 host 下 client 会根据 `ai-tools` feature 隐藏 AI 按钮。
|
|
76
|
+
|
|
77
|
+
## 安全边界
|
|
78
|
+
|
|
79
|
+
- **owner-only**:`/docker` 与 `/api` 共用 DSH Connection 的 Host/Origin 校验和浏览器 cookie 认证;未认证返回 401。服务端再检查 `peer.principal.kind === 'owner'`。
|
|
80
|
+
- **只读模型工具**:AI 只能调用 `container_status` / `container_list` / `container_inspect` / `container_logs` 读取事实;没有 start/stop/rm/prune/run 等 mutation tool,模型不能自动改容器。
|
|
81
|
+
- **PTY 隔离**:Attach 使用 DSH `spawnTerminal` 的 PTY;Docker 使用 `docker attach`,Apple 使用 `container exec -i -t`;关闭对话框或插件卸载时终止。
|
|
82
|
+
- **无 shell**:命令拆成 argv 直接 spawn;`| & ; < > ( ) \` $`、不平衡引号会被拒绝,不执行管道/重定向/命令替换。
|
|
83
|
+
- **危险操作确认**:`rm`/`rmi`/`delete`/`stop`/`kill`/`prune`/`compose down` 等未带 `confirm: true` 时 Host 直接返回 `docker/confirmation-required`,不会 spawn;UI 显示实际命令。
|
|
84
|
+
- **上下文锁定**:Docker 命令自动加 `--context`;客户端传入 `--context`/`--host`/`-H`/`--config`/`--tls*` 会被拒绝。Apple provider 没有 context。
|
|
85
|
+
- **输出与历史**:输出只在 host 内存中保留(上限 1 MiB/stream),不写日志;命令历史存 localStorage,敏感命令(login/password/token)不记录。
|
|
86
|
+
- **权限**:命令以 DSH app 当前用户权限执行,等价于本机终端。`container system start` 可能触发管理员权限/launchd/kernel 安装;插件不会静默 sudo。
|
|
87
|
+
|
|
88
|
+
## 安装与生效
|
|
89
|
+
|
|
90
|
+
插件以 DSH bundle 包发布,安装时会自动写入 `dependencies` 和 `dsh.profile.bundles`,**不需要手改 profile 的 `cordis.patch.yml`**。
|
|
91
|
+
|
|
92
|
+
本地 tarball:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
dsh plugin --profile desktop add /path/to/dsh-plugin-container-0.2.0.tgz
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
发布到 npm 后:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
dsh plugin --profile desktop add dsh-plugin-container
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
安装后 profile 会变成:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
"dependencies": {
|
|
108
|
+
"dsh-plugin-container": "file:/path/to/dsh-plugin-container-0.2.0.tgz"
|
|
109
|
+
},
|
|
110
|
+
"dsh": {
|
|
111
|
+
"profile": {
|
|
112
|
+
"bundles": [
|
|
113
|
+
"@deepseek-ai/dsh-base",
|
|
114
|
+
"@deepseek-ai/dsh-web-app",
|
|
115
|
+
"dsh-plugin-container"
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
包内的 `cordis.patch.yml` 会被 DSH 自动应用:
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
- insert:
|
|
125
|
+
- id: docker-sidebar
|
|
126
|
+
name: dsh-plugin-container
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
本机安装后的效果:
|
|
130
|
+
|
|
131
|
+

|
|
132
|
+
|
|
133
|
+
**host 侧代码更新后必须退出并重开 DSH 一次**;client bundle 改动由 `client-hmr` 热替换。bundle 变化需要重启 host。
|
|
134
|
+
|
|
135
|
+
如果右上角只看到 Docker / Apple container 但没有“启动容器”和 Attach 按钮,说明当前 host 还是旧进程:client 会显示“重启 DSH 以启用启动容器 / Attach”,退出并重开 DSH 即可。
|
|
136
|
+
|
|
137
|
+
### 源码开发安装
|
|
138
|
+
|
|
139
|
+
仍可用绝对路径方式开发:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
node scripts/install-profile.mjs --home "$HOME/.dsh" --profile desktop
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
它会写入带标记的 Loader row,指向当前工作区的 `index.js`,适合改 host 代码时热重载。源码模式不要和 bundle 包同时启用,否则会加载两份插件。
|
|
146
|
+
|
|
147
|
+
### 手动安装(源码模式)
|
|
148
|
+
|
|
149
|
+
直接编辑 patch 文件:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
# >>> dsh-plugin-container >>>
|
|
153
|
+
- insert:
|
|
154
|
+
- id: docker-sidebar
|
|
155
|
+
name: '/Users/zaynfang/Codes/dsh/docker/index.js'
|
|
156
|
+
# <<< dsh-plugin-container <<<
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 卸载
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
dsh plugin --profile desktop remove dsh-plugin-container
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
再从 `dsh.profile.bundles` 中确认已移除;重启 DSH。源码方式则运行:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
node scripts/install-profile.mjs --remove --home "$HOME/.dsh" --profile desktop
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 配置
|
|
172
|
+
|
|
173
|
+
Loader row 可带可选 `config`:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
- insert:
|
|
177
|
+
- id: docker-sidebar
|
|
178
|
+
name: '/Users/zaynfang/Codes/dsh/docker/index.js'
|
|
179
|
+
config:
|
|
180
|
+
dockerPath: /opt/homebrew/bin/docker # 显式配置后不回退自动探测
|
|
181
|
+
colimaPath: /opt/homebrew/bin/colima
|
|
182
|
+
containerPath: /opt/homebrew/bin/container # Apple container CLI
|
|
183
|
+
defaultTimeoutMs: 120000
|
|
184
|
+
maxOutputBytes: 1048576
|
|
185
|
+
maxConcurrentRuns: 4
|
|
186
|
+
enableRuntimeControl: true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
不配置时:
|
|
190
|
+
|
|
191
|
+
- `docker` 依次探测 `/opt/homebrew/bin/docker`、`/usr/local/bin/docker`、`~/.docker/bin/docker`、Docker.app 内置路径,再走 PATH。
|
|
192
|
+
- `colima` 依次探测 `/opt/homebrew/bin/colima`、`/usr/local/bin/colima`、`~/go/bin/colima`,再走 PATH。
|
|
193
|
+
- `container` 依次探测 `/usr/local/bin/container`、`/opt/homebrew/bin/container`、`~/.local/bin/container`,再走 PATH。
|
|
194
|
+
- 运行时启停只在 macOS 且 `enableRuntimeControl !== false` 时可用。
|
|
195
|
+
|
|
196
|
+
## macOS 运行时说明
|
|
197
|
+
|
|
198
|
+
Docker 兼容运行时包括 Docker Desktop、Colima、OrbStack、Rancher Desktop、Podman machine,以及 Lima/UTM/Parallels/VMware 里自建的 Docker;它们通常通过 Docker CLI/context 工作,因此由 **Docker provider** 统一管理(具体 context 由各运行时提供)。
|
|
199
|
+
|
|
200
|
+
Apple `container` 是独立平台:
|
|
201
|
+
|
|
202
|
+
- 仅 macOS 26 + Apple silicon。
|
|
203
|
+
- 每个容器一个轻量 VM,没有共享 Docker daemon。
|
|
204
|
+
- 安装:从 [apple/container releases](https://github.com/apple/container/releases) 安装,或使用 Homebrew;然后 `container system start`。
|
|
205
|
+
- `container system status` 在部分安装上会显示 `unregistered`,但 apiserver 实际可达;插件会用 `container ls` 做健康探测兜底。
|
|
206
|
+
- 系统服务启动可能需要管理员权限/launchd/kernel;插件使用 `--disable-kernel-install` 避免交互式挂起,失败时提示在终端执行 `container system start`。
|
|
207
|
+
- 不支持 Compose;镜像/卷/网络与 Docker 不共享;不支持一键 restart(需先 stop 再 start)。
|
|
208
|
+
|
|
209
|
+
## 工作原理
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
左栏 sidebar.panellist + main keyed panel "docker"
|
|
213
|
+
│
|
|
214
|
+
▼
|
|
215
|
+
Browser ctx.connection.rpc.call('/docker', method, { provider, ... })
|
|
216
|
+
│
|
|
217
|
+
▼
|
|
218
|
+
Host ctx.webServer prefix route /docker
|
|
219
|
+
ctx.connection.admit(req)
|
|
220
|
+
DockerService (router)
|
|
221
|
+
├─ Docker provider → docker CLI + context + JobManager(idPrefix=docker)
|
|
222
|
+
└─ Apple provider → container CLI + JobManager(idPrefix=apple)
|
|
223
|
+
│
|
|
224
|
+
▼
|
|
225
|
+
ctx.subprocess.spawn(argv) ← 无 shell,provider 决定 CLI 语法
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- `providers` 返回可用 provider 和默认 provider。
|
|
229
|
+
- `status/list/inspect/run/action/runtimeAction` 都接受 `provider`;不传默认 Docker。
|
|
230
|
+
- `poll/cancel` 按 run id 前缀(`docker_` / `apple_`)路由,切换 provider 时也能取消旧 run。
|
|
231
|
+
- 语义化 `action`(container start/stop/delete/logs、image pull/delete、volume/network delete)由 host 映射成对应 CLI,UI 不拼 Docker/Apple 语法。
|
|
232
|
+
|
|
233
|
+
### RPC 方法
|
|
234
|
+
|
|
235
|
+
| method | 说明 |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `providers` | 可用 provider、版本、运行状态 |
|
|
238
|
+
| `status` | CLI、daemon/system、context/runtime、资源计数 |
|
|
239
|
+
| `list` | containers / images / volumes / networks / compose(Docker) / disk |
|
|
240
|
+
| `inspect` | container / image / volume / network |
|
|
241
|
+
| `action` | 语义化快捷操作(含启动新容器) |
|
|
242
|
+
| `attach` | 启动 Docker attach / Apple exec PTY 会话 |
|
|
243
|
+
| `attachPoll` / `attachInput` / `attachResize` / `attachClose` | Attach 输出、输入、尺寸、关闭 |
|
|
244
|
+
| `run` | 启动自由命令作业 |
|
|
245
|
+
| `poll` / `cancel` | 增量输出 / 取消 |
|
|
246
|
+
| `runtimeAction` | Docker 运行时或 Apple system start/stop |
|
|
247
|
+
|
|
248
|
+
## 发布到 npm
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
npm login
|
|
252
|
+
npm publish --access public
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
发布后安装:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
dsh plugin --profile desktop add dsh-plugin-container
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
当前包声明了 `dsh.bundle.patch`,安装器会自动写入 `dependencies` 和 `dsh.profile.bundles`,不需要手改 `cordis.patch.yml`。
|
|
262
|
+
|
|
263
|
+
## 开发
|
|
264
|
+
|
|
265
|
+
构建期依赖只有 esbuild;`lib/client.js` 已提交,运行不需要 `npm install`。
|
|
266
|
+
|
|
267
|
+
本机网络走 SOCKS5 8119(8119 不是 HTTP proxy):
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
env -u http_proxy -u https_proxy -u ALL_PROXY -u all_proxy -u NODE_USE_ENV_PROXY \
|
|
271
|
+
ALL_PROXY=socks5h://127.0.0.1:8119 \
|
|
272
|
+
npm_config_proxy=socks5h://127.0.0.1:8119 \
|
|
273
|
+
npm_config_https_proxy=socks5h://127.0.0.1:8119 \
|
|
274
|
+
NO_PROXY=127.0.0.1,localhost,::1 \
|
|
275
|
+
npm install --no-audit --no-fund
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
npm run build # src/client/index.jsx -> lib/client.js
|
|
280
|
+
node --check lib/client.js
|
|
281
|
+
npm test # 45 个 node:test 单测
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
修改 client 后重新 build;`client-hmr` 会热替换。修改 `index.js` 或 `src/host/*` 后重启 DSH。
|
|
285
|
+
|
|
286
|
+
### UI 冒烟
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
PLAYWRIGHT_CHROMIUM_EXECUTABLE="/Users/zaynfang/Library/Caches/ms-playwright/chromium-1208/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" \
|
|
290
|
+
node scripts/ui-smoke.mjs "http://127.0.0.1:<port>/?token=..."
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
冒烟覆盖:
|
|
294
|
+
|
|
295
|
+
- Docker provider:状态、容器/镜像/卷/网络/Compose、`docker ps -a`、`docker events` 取消、危险命令确认、Inspect。
|
|
296
|
+
- 切换到 Apple provider:provider 选择器、Apple 运行状态、Compose 隐藏、`container ls -a`。
|
|
297
|
+
- 不删除资源、不停止运行时;截图写入 `docs/`。
|
|
298
|
+
|
|
299
|
+
## 文件结构
|
|
300
|
+
|
|
301
|
+
```text
|
|
302
|
+
index.js Host 入口 / /docker 认证路由
|
|
303
|
+
cordis.patch.yml bundle 安装时自动插入的 Loader row
|
|
304
|
+
lib/client.js 构建产物
|
|
305
|
+
src/host/service.js provider 路由 + Docker provider
|
|
306
|
+
src/host/apple.js Apple container provider
|
|
307
|
+
src/host/apple-cli.js container CLI 探测/执行
|
|
308
|
+
src/host/attach.js PTY attach 会话管理
|
|
309
|
+
src/host/tools.js 只读 Agent Tools
|
|
310
|
+
src/host/docker-cli.js Docker CLI
|
|
311
|
+
src/host/runtime.js colima/Docker Desktop/OrbStack/remote
|
|
312
|
+
src/host/jobs.js 命令作业(docker_/apple_ 前缀)
|
|
313
|
+
src/host/parsers.js Docker / Apple JSON 归一化
|
|
314
|
+
src/client/ provider 切换、页面、tabs、i18n、styles
|
|
315
|
+
src/client/attach.jsx Attach 终端模态
|
|
316
|
+
src/client/run-container.jsx 启动新容器表单
|
|
317
|
+
scripts/build-client.mjs
|
|
318
|
+
scripts/install-profile.mjs
|
|
319
|
+
scripts/ui-smoke.mjs
|
|
320
|
+
test/*.test.mjs
|
|
321
|
+
docs/*.png
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## 限制
|
|
325
|
+
|
|
326
|
+
- Docker `attach` 需要容器有 TTY;“启动容器”表单默认使用 `-dit`,已有容器若创建时没有 TTY,`docker attach` 可能无法交互,请改用日志或重新创建容器。
|
|
327
|
+
- Apple container 没有 `attach` 命令;面板使用 `container exec -i -t <id> /bin/sh`,镜像内必须存在 `/bin/sh`。
|
|
328
|
+
- Attach 依赖 DSH 的 PTY(`spawnTerminal`);若部署环境禁用 PTY,会返回明确的 `attach-unsupported` 错误。
|
|
329
|
+
- Attach 是单终端文本视图,不包含完整 xterm 能力(鼠标、复杂 TUI、图片协议可能不完整)。
|
|
330
|
+
- 启动新容器固定后台运行(`-d`);交互式前台运行请使用命令终端。
|
|
331
|
+
- Apple provider 仍然不支持 Compose。
|
|
332
|
+
- host 新增 Attach/启动新容器后需要再重启 DSH 一次;旧 host 下 client 会显示“重启 DSH 以启用启动容器 / Attach”,不会出现无效按钮。
|
|
333
|
+
|
|
334
|
+
## 故障排查
|
|
335
|
+
|
|
336
|
+
- **左栏没有“容器”**:确认 patch 块存在;刷新 GUI;host 代码更新后退出并重开 DSH。
|
|
337
|
+
- **只有 Docker,没有 Apple container 选项**:确认 `container` CLI 可执行(`container --version`);刷新;host 需要新版本。
|
|
338
|
+
- **Apple 服务显示未启动**:点击“启动运行时”,或终端执行 `container system start`。首次可能需要管理员权限/kernel。
|
|
339
|
+
- **Apple 启动卡住/失败**:查看命令终端里的 `container system start` 输出;`--disable-kernel-install` 下缺 kernel 会失败,需先在终端启动一次。
|
|
340
|
+
- **Apple 镜像 Inspect 失败**:请使用镜像名(UI 已自动这样做),不要用 digest。
|
|
341
|
+
- **Docker Desktop 停止失败**:macOS Automation 权限可能拦截 `osascript`;从菜单栏退出或授予权限。
|
|
342
|
+
- **patch 损坏**:用 `cordis.patch.yml.docker.bak` 恢复,或 `install-profile.mjs --remove` 后重试。
|
package/cordis.patch.yml
ADDED
package/docs/ai-chat.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/attach.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host entry for the Docker sidebar plugin.
|
|
3
|
+
*
|
|
4
|
+
* This file is loaded by the DSH Loader from an absolute path, so it imports
|
|
5
|
+
* only local modules and Node built-ins. The browser half is served separately
|
|
6
|
+
* from `lib/client.js` through the package's `dsh.client` declaration.
|
|
7
|
+
*
|
|
8
|
+
* The browser calls the same `/docker/...` envelope used by the Connection
|
|
9
|
+
* generic RPC channel. We mount the route ourselves because Connection's
|
|
10
|
+
* `rpc.handle` helper registers on the service owner's context, which does not
|
|
11
|
+
* inject `webServer`; the direct route below reuses `connection.admit()` for
|
|
12
|
+
* the same Host/Origin fence and browser authentication.
|
|
13
|
+
*/
|
|
14
|
+
import { createDockerService } from './src/host/service.js'
|
|
15
|
+
import { registerContainerTools } from './src/host/tools.js'
|
|
16
|
+
|
|
17
|
+
export const name = 'docker-sidebar'
|
|
18
|
+
export const inject = ['subprocess', 'connection', 'webServer', 'tools', 'systemPrompt']
|
|
19
|
+
|
|
20
|
+
const CHANNEL = '/docker'
|
|
21
|
+
const MAX_BODY_BYTES = 1024 * 1024
|
|
22
|
+
|
|
23
|
+
/** @param {import('node:http').IncomingMessage} req */
|
|
24
|
+
function readBody(req) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const chunks = []
|
|
27
|
+
let size = 0
|
|
28
|
+
req.on('data', (chunk) => {
|
|
29
|
+
size += chunk.byteLength
|
|
30
|
+
if (size > MAX_BODY_BYTES) {
|
|
31
|
+
reject(new Error('request body too large'))
|
|
32
|
+
req.destroy()
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
chunks.push(chunk)
|
|
36
|
+
})
|
|
37
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
|
|
38
|
+
req.on('error', reject)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @param {import('node:http').ServerResponse} res @param {number} status @param {unknown} body */
|
|
43
|
+
function send(res, status, body, contentType = 'application/json; charset=utf-8') {
|
|
44
|
+
if (res.writableEnded) return
|
|
45
|
+
const text = typeof body === 'string' ? body : JSON.stringify(body)
|
|
46
|
+
res.writeHead(status, { 'content-type': contentType, 'cache-control': 'no-store' })
|
|
47
|
+
res.end(text)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {import('node:http').IncomingMessage} req
|
|
52
|
+
* @param {import('node:http').ServerResponse} res
|
|
53
|
+
* @param {any} ctx
|
|
54
|
+
* @param {ReturnType<typeof createDockerService>} service
|
|
55
|
+
*/
|
|
56
|
+
async function handleRequest(req, res, ctx, service) {
|
|
57
|
+
let admission
|
|
58
|
+
try {
|
|
59
|
+
admission = ctx.connection.admit(req)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
send(res, 403, `forbidden: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if ('rejection' in admission) {
|
|
65
|
+
send(res, admission.rejection, admission.rejection === 401 ? 'unauthorized' : 'forbidden', 'text/plain; charset=utf-8')
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
if (req.method !== 'POST') {
|
|
69
|
+
send(res, 405, 'method not allowed', 'text/plain; charset=utf-8')
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const url = new URL(req.url ?? CHANNEL, 'http://dsh.internal')
|
|
73
|
+
const endpoint = url.pathname.startsWith(`${CHANNEL}/`) ? url.pathname.slice(CHANNEL.length + 1) : ''
|
|
74
|
+
if (endpoint === '' || endpoint.includes('/')) {
|
|
75
|
+
send(res, 404, 'not found', 'text/plain; charset=utf-8')
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let message
|
|
80
|
+
try {
|
|
81
|
+
message = JSON.parse(await readBody(req))
|
|
82
|
+
} catch (error) {
|
|
83
|
+
send(res, 400, `invalid request body: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
message === null ||
|
|
88
|
+
typeof message !== 'object' ||
|
|
89
|
+
message.type !== 'client-request' ||
|
|
90
|
+
typeof message.rpcId !== 'string' ||
|
|
91
|
+
typeof message.method !== 'string' ||
|
|
92
|
+
message.method !== endpoint
|
|
93
|
+
) {
|
|
94
|
+
send(res, 400, 'invalid client-request envelope', 'text/plain; charset=utf-8')
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const controller = new AbortController()
|
|
99
|
+
const abort = () => controller.abort()
|
|
100
|
+
res.on('close', abort)
|
|
101
|
+
try {
|
|
102
|
+
const result = await service.handle(message.method, message.payload, controller.signal, admission.peer)
|
|
103
|
+
send(res, 200, { type: 'server-response', rpcId: message.rpcId, result })
|
|
104
|
+
} catch (error) {
|
|
105
|
+
send(res, 500, `handler failure: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
|
|
106
|
+
} finally {
|
|
107
|
+
res.off('close', abort)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx
|
|
113
|
+
* @param {Record<string, any>} [config]
|
|
114
|
+
*/
|
|
115
|
+
export function apply(ctx, config = {}) {
|
|
116
|
+
const service = createDockerService({
|
|
117
|
+
subprocess: ctx.subprocess,
|
|
118
|
+
logger: ctx.logger,
|
|
119
|
+
config: config ?? {},
|
|
120
|
+
})
|
|
121
|
+
try {
|
|
122
|
+
registerContainerTools(ctx, service)
|
|
123
|
+
service.enableTools()
|
|
124
|
+
} catch (error) {
|
|
125
|
+
ctx.logger?.warn?.('docker: container tool registration failed', error)
|
|
126
|
+
}
|
|
127
|
+
ctx.effect(
|
|
128
|
+
() => ctx.webServer.register({
|
|
129
|
+
kind: 'prefix',
|
|
130
|
+
path: CHANNEL,
|
|
131
|
+
handler: (req, res) => handleRequest(req, res, ctx, service).catch((error) => {
|
|
132
|
+
send(res, 500, `handler failure: ${error instanceof Error ? error.message : String(error)}`, 'text/plain; charset=utf-8')
|
|
133
|
+
}),
|
|
134
|
+
}),
|
|
135
|
+
'docker: rpc route',
|
|
136
|
+
)
|
|
137
|
+
ctx.effect(() => () => service.dispose(), 'docker: jobs')
|
|
138
|
+
}
|