@hyzyn/dsh-docker 0.6.0 → 0.6.2

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/README.en.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - **"Needs attention" reads authoritative fields**: unhealthy / repeatedly restarting / OOM-killed / non-zero exit / dead; OOM and the real exit code come from one `docker inspect` — the 137 in a `docker ps` summary cannot separate an OOM kill from a manual kill, so filtering on the summary alone must misreport.
12
12
  - **Four long-lived SSE streams on one substrate**: log FOLLOW, `docker stats`, `docker events` and `docker pull` all run through the same `openSseStream` (heartbeat / active-stream registry / teardown on disconnect) and differ only in how they end — logs and pulls finish on their own, stats and events are aborted by the browser. Multi-select merged logs recover true cross-container ordering from the `--timestamps` prefix; "pause" freezes rendering only (the stream keeps receiving and flushes in one batch on resume).
13
13
  - **Read-only by default, capability switches in three tiers**: start / stop / remove, exec and image mutations are independent switches; while one is off the agent tools are **not registered** and the HTTP routes return 403 (the capability does not exist, rather than failing when called). Container names and IDs pass a whitelist, every command is built as argv with single-quote escaping, and passwords / passphrases are referenced as `env:NAME` (resolved through the official credential layer, falling back to the environment) and never sent back to the browser.
14
- - **Right-click a log into the agent**: select the failing lines in the log view, then right-click for "send to the current session / fill the input box so I can edit first" — the selection travels with its target, container, time window and 20 lines of context on each side (the menu states plainly that the content enters the model context and may carry credentials). Delivery reports itself twice: a **viewport-level toast** (attached to `body`, above the panel and the terminal modal), and — when the terminal panel is open — an **automatic fold of the terminal** (`minimize()` on the `ttyPanel` v2 contract; sessions keep running and the sidebar "Terminal" entry's badge restores it), so the conversation is simply there. On older tty without that call it degrades to the toast's "the conversation is behind the panel" hint. To edit first, use the draft action — **the session input box is the only editing surface** (multi-line, with the full context in view, and exactly what the agent receives), instead of a second, weaker card editor.
14
+ - **Right-click a log into the agent**: select the failing lines in the log view, then right-click for "send to the current session / fill the input box so I can edit first" — the selection travels with its target, container, time window and 20 lines of context on each side (the menu states plainly that the content enters the model context and may carry credentials). Delivery reports itself twice: a **viewport-level toast** (attached to `body`, above the panel and the terminal modal), and — when the terminal panel is open — an **automatic fold of the terminal** (`minimize()` on the `ttyPanel` v2 contract; sessions keep running and the sidebar "Terminal" entry's badge restores it), so the conversation is simply there. On older tty without that call it degrades to the toast's "the conversation is behind the panel" hint. To edit first, use the draft action — **the session input box is the only editing surface** (multi-line, with the full context in view, and exactly what the agent receives), instead of a second, weaker card editor. Locating the target session is version-tolerant: **≤0.1.5** reads `current` off the `sessions.list` snapshot, while **since 0.1.6** that field moved out of the session domain together with view selection, so it reads `retainedBy.mainView > 0` instead (the same test `dsh-client-ui-session` and this repo's codegraph use) — trusting only the legacy field greys out both menu items as "no session open". The same read drives "carry the containers tab across a session switch".
15
15
  - **Data-level reuse of dsh-tty, no code coupling**: no tty code is imported and tty needs no source change, so the two install and upgrade independently; with tty present three optional extension points are consumed — connection-bar actions (`ttyConnbar`), the terminal host (`ttyTerminal`: a new tab under the tab/dock carriers, an in-place drawer under the modal) and the terminal-side dock (`ttyPanel.mountPane`, used only by the fallback path) — and each degrades silently without tty or below the required version.
16
16
 
17
17
  ## Relationship with dsh-tty
@@ -80,7 +80,7 @@ on the right, without getting in the way of watching it work.
80
80
  | Carrier | When | Behaviour |
81
81
  | --- | --- | --- |
82
82
  | **Session right-sidebar tab** (default) | the host provides `sidebarRight` / `sidebarRightTabs` | side by side with the conversation; collapsing hides it without losing state (panel-level state lives in a module store, see below); the right sidebar's fullscreen mode gives it the whole viewport |
83
- | **Dock right of the terminal** | arriving from the **terminal connection bar's** "Containers" button (that button sits on the tty modal, which would hide a tab), or no right-sidebar service / `localStorage['dsh-docker:carrier'] = 'modal'` with tty ≥ 0.16 and its panel open | docked to the right of the terminal panel (resize / collapse / ✕ provided by tty, the terminal stays usable); one dock at a time — docking this plugin takes down the previous occupant (for example tty's own SFTP) |
83
+ | **Dock right of the terminal** | arriving from the **terminal connection bar's** "Containers" button (that button sits on the tty modal, which would hide a tab), or no right-sidebar service / `localStorage['dsh-docker:carrier'] = 'modal'` with tty ≥ 0.16 and its panel open | docked to the right of the terminal panel (resize / collapse / ✕ provided by tty, the terminal stays usable); one dock at a time — docking this plugin takes down the previous occupant (for example tty's own SFTP); since tty 0.18.4 the pane is **owned by the tab it was opened from**: switching tabs hides it, switching back restores it and closing that tab tears it down (while hidden the React tree and polling keep running) |
84
84
  | **Full-screen modal** (fallback) | neither of the above | its own backdrop, closes on outside click; the panel sits above tty's modal in z-order |
85
85
 
86
86
  Rolling back to the old shape is one console line: `localStorage.setItem('dsh-docker:carrier', 'modal')`
@@ -429,7 +429,7 @@ return 400).
429
429
  | --- | --- | --- |
430
430
  | `enabled` | true | disables the whole plugin (**takes effect after restarting `dsh web`**, same semantics as tty) |
431
431
  | `announceToAgent` | true | whether to inject a capability announcement into the agent (systemPrompt section `plugin:dsh-docker`) |
432
- | `dockerBin` | `docker` | the docker CLI executable name or path (`podman` works here); only letters, digits and `_ . / -` are allowed |
432
+ | `dockerBin` | `docker` | the docker CLI executable name or path (`podman` works here); only letters, digits and `_ . / \ : -` plus interior spaces are allowed, and it may not start with `-` (**Windows drive letters and `\` must be allowed**, otherwise no absolute path can be entered at all) |
433
433
  | `allowMutations` | false | allows **mutating operations**: container start / stop / restart / remove, image removal / dangling pruning / pulling (the panel buttons and the `docker_action`, `docker_image_remove`, `docker_image_prune`, `docker_image_pull` tools; while off, `/action`, `/images/remove`, `/images/prune`, `/images/pull/stream` return 403 and the corresponding tools are not registered) |
434
434
  | `allowExec` | false | allows a one-shot `docker exec` (the panel's exec input and the `docker_exec` tool; while off, `/exec` returns 403) |
435
435
  | `execTimeoutSec` | 30 | default exec timeout in seconds (1–120) |
@@ -665,7 +665,24 @@ read-only first:
665
665
  error which the panel and the tools pass through verbatim, without attempting automatic sudo escalation.
666
666
  - **Docker not installed on the remote**: `probe` fails (`command not found` / exit code 127) and the panel shows
667
667
  the error; when PATH differs, fill `dockerBin` with an absolute path.
668
- - **Podman compatibility through `dockerBin`**: filling in `podman` runs, but the fields and output formats of
668
+ - **`dockerBin` is validated before it is persisted**: an illegal value returns 400 with a reason and is never
669
+ written to `settings.yaml` (the earlier implementation validated after `scope.update`, so the illegal value was
670
+ stored anyway and the user only received a bodyless 400; the next start silently reverted the entire docker
671
+ section to defaults).
672
+ - **A `dockerBin` pointing at a `.cmd` / `.bat` cannot start the local channel**: local execution goes through the
673
+ bare `spawn` in `runLocal` / `runLocalStream`, and Node refuses to execute `.cmd` directly on Windows
674
+ (`EINVAL`). Docker ships `docker.exe`, so this is not hit in practice; with a hand-written `.cmd` wrapper, use
675
+ an `.exe` instead, or wait for the local channel to move to kit's `spawnPortable` as well.
676
+ - **The Compose project view only knows `com.docker.compose.project`**: a service deployed with
677
+ `docker stack deploy` carries `com.docker.stack.namespace` / `com.docker.swarm.service.name` instead, so its
678
+ containers show up as ungrouped (measured on a real three-node swarm, Ubuntu 24.04 + Docker 29.3.1, where every
679
+ stack container reported `composeProject: null`). Supporting it needs a separate stack-grouping notion rather
680
+ than being folded into the compose one.
681
+ - **`volume prune` only reclaims anonymous unused volumes on Docker 29**: a named volume stays even while it
682
+ appears in `docker volume ls -f dangling=true`, and `docker volume prune -f` reports `Total reclaimed space: 0B`
683
+ (measured; an anonymous volume, by contrast, is deleted and named in the output). That matches this plugin's
684
+ deliberate refusal to pass `--all`; use `/volumes/remove` (the panel's volume delete) for named volumes.
685
+ - **Podman compatibility through `dockerBin`**: filling in `podman` runs, but the fields and output formats of- **Podman compatibility through `dockerBin`**: filling in `podman` runs, but the fields and output formats of
669
686
  `stats` and `--format '{{json .}}'` differ from docker's, so only the parser's degradation paths are relied on;
670
687
  this has not been verified item by item.
671
688
  - **No image builds / Compose orchestration changes**: images support pulling / removal / dangling pruning, but there
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ## 特性
8
8
 
9
9
  - **常驻会话右侧栏**:面板作为会话右侧栏标签(`sidebar.right.pane.tab`)与对话**同屏**——发完错误交给 Agent 后日志留在右边,不影响看它干活;折叠即退出视野、不占屏。面板级状态(目标 / 视图 / 过滤词 / 选中容器及它的页签)**跨会话保留**,而折叠会主动断流、把 SSH 通道还回去。拿不到右侧栏服务的老宿主自动退回原有的 dock / 模态,行为与旧版一致。
10
- - **日志右键交给 Agent**:日志页拖选报错行 → 右键「直接发送到当前会话 / 填入输入框,我先改改」,把「选中的行 + 目标 / 容器 / 时间窗 + 前后各 20 行上下文」交给会话(内容会进模型上下文,菜单里明写了留意凭证)。投递成功有两处回执:**视口级 toast**(挂 `body`,盖过面板与终端弹窗),以及——终端面板正开着时——**自动折起终端**(`ttyPanel` 契约 v2 的 `minimize()`;会话继续跑,恢复靠侧边栏「终端」入口的徽标),让会话直接露出来。老版本 tty 没有这个能力时退化成 toast 里「会话在面板后面」的指引。需要改稿就先「填入输入框」——**编辑面只有会话输入框一个**(它多行、能看到完整上下文,Agent 收到的就是它),不再另开一张更弱的卡片编辑器。
10
+ - **日志右键交给 Agent**:日志页拖选报错行 → 右键「直接发送到当前会话 / 填入输入框,我先改改」,把「选中的行 + 目标 / 容器 / 时间窗 + 前后各 20 行上下文」交给会话(内容会进模型上下文,菜单里明写了留意凭证)。投递成功有两处回执:**视口级 toast**(挂 `body`,盖过面板与终端弹窗),以及——终端面板正开着时——**自动折起终端**(`ttyPanel` 契约 v2 的 `minimize()`;会话继续跑,恢复靠侧边栏「终端」入口的徽标),让会话直接露出来。老版本 tty 没有这个能力时退化成 toast 里「会话在面板后面」的指引。需要改稿就先「填入输入框」——**编辑面只有会话输入框一个**(它多行、能看到完整上下文,Agent 收到的就是它),不再另开一张更弱的卡片编辑器。目标会话的定位跨宿主版本:≤0.1.5 读 `sessions.list` 快照的 `current`,**0.1.6 起**该字段随「视图选中项」一起搬出了会话域,改看 `retainedBy.mainView > 0`(官方 `dsh-client-ui-session` 与本地 codegraph 的同一判据)——只认老字段会让菜单判成「当前没有打开的会话」而整组置灰;同一份取值也驱动「切会话把容器标签带过去」。
11
11
  - **多目标聚合取数**:总览页对全部 `targets[]` 并行请求,单个目标不可达只污染自己那一格;agent 侧同一口径由 `docker_ps target:"*"` / `docker_attention target:"*"` 暴露,跨目标不互相阻塞。
12
12
  - **「需关注」读权威字段**:不健康 / 反复重启 / OOM 被杀 / 非零退出 / 僵死;OOM 与真实退出码由一次 `docker inspect` 补齐——`docker ps` 摘要里的 137 分不出 OOM 与手动 kill,只按摘要筛必然误报。
13
13
  - **四条 SSE 长流共用一套基建**:日志 FOLLOW、`docker stats`、`docker events`、`docker pull` 走同一个 `openSseStream`(心跳 / 活跃流登记 / 断开清理),差异只在收尾语义——日志与拉取自然结束,统计与事件由前端主动断。多选聚合日志按 `--timestamps` 前缀还原跨容器真实时序,「暂停」只冻结渲染(流继续接收,恢复时一次性补齐)。
@@ -70,13 +70,16 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
70
70
  视口,开标签会被整个挡住、看着像「点了没反应」。目标照旧带进面板并重挂到那台主机。
71
71
  于是规则是「**入口决定承载**」:框架侧边栏点 → 右侧栏标签(与对话同屏);
72
72
  终端弹窗里点 → 终端右侧 dock(与终端同屏)。
73
+ dock 面板**归属点它的那个标签**(tty ≥ 0.18.4):切到别的 SSH 标签时整块收起
74
+ (React 树与轮询都保活,切回来恢复原样),关掉该标签则一并收掉;别的标签上再点
75
+ 「容器」会用那台主机的目标重开一块。收起 ≠ 关掉:tty 不重挂,面板状态不丢。
73
76
 
74
77
  ### 承载形态
75
78
 
76
79
  | 承载 | 何时用 | 行为 |
77
80
  | --- | --- | --- |
78
81
  | **会话右侧栏标签**(默认) | 宿主提供 `sidebarRight` / `sidebarRightTabs` | 与会话同屏;折叠=退出视野但不丢状态(面板级状态按模块保留,见下);支持右侧栏的 fullscreen 铺满 |
79
- | **终端右侧 dock** | 从**终端连接栏**的「容器」按钮进来(按钮就在 tty 弹窗上,标签会被它挡住),或拿不到右侧栏服务 / `localStorage['dsh-docker:carrier'] = 'modal'` 且 tty ≥ 0.16 面板开着 | 挂在终端面板右侧(拖宽 / 折叠 / ✕ 由 tty 提供,终端继续可用);同一时刻只挂一个,挂上去会收掉前一个(例如 tty 自己的 SFTP) |
82
+ | **终端右侧 dock** | 从**终端连接栏**的「容器」按钮进来(按钮就在 tty 弹窗上,标签会被它挡住),或拿不到右侧栏服务 / `localStorage['dsh-docker:carrier'] = 'modal'` 且 tty ≥ 0.16 面板开着 | 挂在终端面板右侧(拖宽 / 折叠 / ✕ 由 tty 提供,终端继续可用);同一时刻只挂一个,挂上去会收掉前一个(例如 tty 自己的 SFTP);tty ≥ 0.18.4 起**归属点它的那个标签**:切走收起、切回恢复、标签关掉一并收掉(收起期间 React 树与轮询保活) |
80
83
  | **全屏模态**(兜底) | 上面两条都不成立 | 自带 backdrop、点击外部关闭;面板 z-index 高于 tty 弹窗 |
81
84
 
82
85
  回滚到旧形态只要在控制台执行 `localStorage.setItem('dsh-docker:carrier', 'modal')`
@@ -381,7 +384,7 @@ add。装完重启 `dsh web`,侧边栏出现「容器」入口;设置 →
381
384
  | --- | --- | --- |
382
385
  | `enabled` | true | 关闭整个插件(**需重启 `dsh web` 生效**,与 tty 同语义) |
383
386
  | `announceToAgent` | true | 是否向 agent 注入能力公告(systemPrompt section `plugin:dsh-docker`) |
384
- | `dockerBin` | `docker` | docker CLI 可执行名或路径(podman 可填 `podman`);只允许字母、数字与 `_ . / -` |
387
+ | `dockerBin` | `docker` | docker CLI 可执行名或路径(podman 可填 `podman`);只允许字母、数字与 `_ . / \ : -` 及内部空格,且不能以 `-` 开头(**Windows 盘符与 `\` 必须放行**,否则任何绝对路径都填不进来) |
385
388
  | `allowMutations` | false | 允许**变更操作**:容器 start / stop / restart / remove、镜像删除 / dangling 清理 / 拉取(面板按钮与 `docker_action`、`docker_image_remove`、`docker_image_prune`、`docker_image_pull` 工具;关闭时 `/action`、`/images/remove`、`/images/prune`、`/images/pull/stream` 返回 403,对应工具不注册) |
386
389
  | `allowExec` | false | 允许一次性 `docker exec`(面板 exec 输入与 `docker_exec` 工具;关闭时 `/exec` 返回 403) |
387
390
  | `execTimeoutSec` | 30 | exec 默认超时秒数(1~120) |
@@ -616,7 +619,24 @@ abort)、客户端断开静默中止。各自只差执行器与结束原因:
616
619
  面板与工具原样透出,不做自动 sudo 提权。
617
620
  - **远端未安装 docker**:`probe` 失败(`command not found` / 退出码 127),
618
621
  面板显示错误;PATH 不一致时可把 `dockerBin` 填成绝对路径。
619
- - **podman 兼容靠 `dockerBin`**:填 `podman` 即可跑,但 `stats` 与
622
+ - **`dockerBin` 的校验发生在落盘之前**:非法值直接 400 并带上原因,不会被写进
623
+ `settings.yaml`(早先的实现在 `scope.update` 之后才校验,于是非法值照样写盘、
624
+ 用户只拿到一个没有正文的 400;下次启动整段 docker 配置会静默退回默认)。
625
+ - **`dockerBin` 指向 `.cmd` / `.bat` 时本机通道起不来**:本机执行走
626
+ `runLocal` / `runLocalStream` 的裸 `spawn`,Windows 上 Node 会拒绝对 `.cmd` 的
627
+ 直接执行(`EINVAL`)。docker 官方发行的是 `docker.exe`,日常不受影响;手写
628
+ `.cmd` 包装脚本时请改用 `.exe`,或等待本机通道也切到 kit 的 `spawnPortable`。
629
+ - **Compose 项目视图只认 `com.docker.compose.project`**:`docker stack deploy` 起的
630
+ swarm 服务带的是 `com.docker.stack.namespace` / `com.docker.swarm.service.name`,
631
+ 于是它们的容器在面板里显示为「未分组」(真机实测:Ubuntu 24.04 + Docker 29.3.1 的
632
+ 三节点 swarm,stack 容器 `composeProject` 全为 `null`)。要支持得另立一套 stack
633
+ 分组语义,不能直接塞进 compose 口径。
634
+ - **`volume prune` 在 Docker 29 只回收匿名未用卷**:命名卷即使在
635
+ `docker volume ls -f dangling=true` 里,`docker volume prune -f` 也不会删它
636
+ (真机实测:命名卷留着,回 `Total reclaimed space: 0B`;匿名卷被删并列出名字)。
637
+ 这与本插件「刻意不加 `--all`、避免误删」的取向一致 —— 要删命名卷请用
638
+ `/volumes/remove`(面板的卷删除)。
639
+ - **podman 兼容靠 `dockerBin`**:填 `podman` 即可跑,但 `stats` 与- **podman 兼容靠 `dockerBin`**:填 `podman` 即可跑,但 `stats` 与
620
640
  `--format '{{json .}}'` 的字段和输出格式与 docker 有差异,只能依赖解析器
621
641
  的降级路径,未逐项验证。
622
642
  - **没有镜像构建 / Compose 编排变更**:镜像支持拉取 / 删除 / 清理 dangling,