DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

supengpeng /

supengpeng/dsh-ssh

Verified

SSH plugin for DSH (DeepSeek Harness): connection, exec/PTY, SFTP transfer, right-sidebar UI

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@d96dbd75

@local/dsh-ssh · DSH SSH 插件

CI License: MIT

在 DSH 右侧边栏里管理 SSH 连接:连接档案、多会话工作区(终端 / 命令 / 文件 / 日志)、SFTP 传输、审计日志,并同时以 Agent 工具的形式暴露给模型。

  • host 半边:Node + Cordis 插件(src/** → lib/**),用 ssh2 实现连接池、命令执行、PTY 与 SFTP。
  • client 半边:自包含懒加载 bundle(client/src/** → lib/client.js,唯一外部依赖 react),注册右侧栏标签与面板,主题全部走 --dsw-* token。
  • 文档:docs/DESIGN.md(总体设计)、docs/ICD.md(冻结接口契约 v1.0.x)、docs/M0-SPIKE.md(传输实测)、docs/TESTING.md(分层测试与运行命令)、docs/DEMO.md(验收走查)、docs/REAL-TARGET.md(真机信息,不含密码)、docs/SCREENSHOTS.md(截图工具链:为何布局缺陷必须靠真实浏览器)。

1. 安装

前置:一个 DSH profile —— 桌面版(Electron 应用,profile 名通常为 desktop)或网页端(dsh web,profile 名 web)。本包是免重启装载的本地插件。

1.1 选哪种 profile,以及两种装载路线

桌面端(Electron) 网页端(dsh web)
启动方式 打开 DSH 桌面应用 终端执行 dsh web,浏览器打开其地址
本机在用 --profile desktop --profile web
装载路线 patch 路线:patch 层插入一条托管 Loader 行 bundles 路线:写进 dsh.profile.bundles,再由 patch 层按 id 启用

两条路线都能用,一条路线装一个插件即可。同时用两条会各加一行(--check 会给告警;本机 desktop 就是这个状态,实测仍可用,但不是推荐形态)。

1.2 安装(按你的 profile 选一条)

# 桌面端(patch 路线,本机默认)
node scripts/profile-install.mjs --profile desktop --install-deps

# 网页端(bundles 路线;--install-deps 会代为在 profile 目录执行 pnpm install)
node scripts/profile-install.mjs --profile web --use-bundles --install-deps

--profile 既接受裸 profile 名(web / desktop,自动解析到 $DSH_HOME/profiles/<name>),也接受 profile 目录路径。每一步写入前都会生成时间戳备份(*.bak-dsh-ssh-<stamp>)。

1.3 验收:确认"真的装上了、而且能组成"

# 一条命令给结论:路线、各块是否存在、node_modules 链接、以及组成是否成功
node scripts/profile-install.mjs --profile web --check   # 有问题时退出码非 0

# 组成里应当恰好有一条 dsh-ssh 行,且带完整 config
dsh --profile web --dump-config | Select-String -Pattern 'dsh-ssh' -Context 0,8

--check 的 composition: ok 意味着 DSH 真的解析了这个 profile 并且只找到一条插件行——这是唯一能回答"它到底加载了吗"的检查。网页端随后在浏览器里应当看到:SSH 图标出现在侧栏面板列表 → 新建连接 → 会话页签(终端 / 命令 / 文件 / 日志 / 活动)。

回滚:node scripts/profile-install.mjs --profile web --uninstall。

1.4 故障排查(web 端)

  • dsh --profile web --dump-config 报 cannot resolve profile bundle "@deepseek-ai/dsh-experimental-…":这个 profile 的 dsh.profile.bundles 里声明了一个既不在 DSH 安装里、也不在 profile 依赖里的包(本机就是因为 @deepseek-ai/dsh-experimental-agent-team-profile / -auto-review 而整个 web profile 起不来,与 dsh-ssh 无关)。处置:把它从 dsh.profile.bundles 摘掉,或按报错提示 dsh plugin --profile web install 把它装进 profile 依赖。
  • route: none 或组成里没有 dsh-ssh 行:装到了另一个 profile;用 --profile <名字> 指对目标,然后重新 --check。
  • 改了 client/src/** 后网页端没变化:必须重建 lib/client.js(pnpm run build:client);页面只在该文件字节变化时重新执行 apply()。host 侧改了 src/** 要重建 lib/**(pnpm run build:host)——装载时写入的 hmr watch 块会让重建后的 lib/、src/、client/src/ 被热重载,无需重启应用。
  • --check 报 both routes are in use:patch 层托管块与 dsh.profile.bundles 各声明了一次。删掉其中一条(--uninstall 会同时清掉托管块与 bundle 条目)。

1.5 其它常用命令

node scripts/profile-install.mjs --profile web --status     # 只报告,不改文件
node scripts/profile-install.mjs --profile web --dry-run    # 只打印将要写入的文件
node scripts/profile-install.mjs --help

要点(M0 实测结论,见 docs/M0-SPIKE.md):

  • profile 的 cordis.patch.yml 是共写文件,本脚本只增删自己的 # >>> dsh-ssh … >>> 标记块;禁止整文件重写。
  • host 侧改动要重跑 apply() 时,用 plugin_manager 把 include:dsh-ssh 行 toggle 关→开(entryId 是 include:dsh-ssh)。

2. 配置

全部配置项与默认值在 cordis.patch.yml(ICD §6)。常用项:

配置 默认 说明
maxSessions 10 并发会话上限(与"10 会话并发"验收对齐)
connectTimeoutMs / operationTimeoutMs 15000 / 120000 建连 / 单次操作超时
graceKillMs 3000 timeoutMs 到期后 SIGTERM → SIGKILL 的宽限
keepaliveIntervalMs / keepaliveCountMax 20000 / 3 心跳与判死阈值(连续失败 → SSH_TIMEOUT_IDLE)
retries 2 / 500 / 5000 / true 仅对 retryable 错误重试,指数退避 + 抖动
hostKey.policy accept-new strict(首连必须人工确认)/ accept-new / insecure
hostKey.knownHostsFile '' → <DSH_HOME>/known_hosts OpenSSH 兼容;支持 `
sftp.chunkBytes / maxConcurrentChunks 262144 / 4 分块大小与并发分块数
sftp.resume / sftp.verify true / size+mtime 断点续传与校验(none/size+mtime/sha256)
secrets.provider / envPrefix credentials / DSH_SSH_ 凭据来源;环境变量优先级最高且只读
logging.redact / redactKeys true / […] 三层脱敏中的日志层
maxOutputBytes 262144 单命令输出上限(超限保留头尾并置 truncated)
activity.enabled true 是否记录 agent 经 ssh_* 工具做的事(「终端」标签的活动镜像,ICD §4.7)
activity.maxRecords 200 镜像保留的记录条数(环满先丢最老的已结束记录,运行中的不淘汰)
activity.maxRecordBytes 65536 单条记录的文本上限(超出丢尾部并置 truncated)
activity.maxTotalBytes 1048576 整个镜像的文本上限
ui.defaultWidthPx / terminalFontSize 420 / 13 侧栏宽度与终端字号

凭据永远不落配置文件:密码/passphrase 走 ctx.credentials,或用一次性内存凭据(connect 的 secrets),或 DSH_SSH_<PROFILE_SLUG>_PASSWORD 环境变量(只读、UI 显示 source:'env')。UI 与日志中只会出现固定 8 个圆点的掩码。

3. UI 使用

实测状态(2026-09-27,真实浏览器 + 真机 203.0.113.10):连接与交互式终端已端到端打通 —— console 首行 marker ssh-client-2026-09-26.4-console-clean,rpc stream sshPlugin/openShell 打开,逐键 shellWrite {"data":"l"/"s"/" "/"-"/"a"/"\r"} 全部 ok,终端有提示符且 ls -a 有真实输出,console 无未捕获错误。host 日志:connected to root@203.0.113.10:22 in 712ms (auth=password(••••••••), hostKey=accept-new)。原文与复现方法见 docs/ACCEPTANCE.md §11。 十条验收标准全部通过(用户 2026-09-27 实机走查):GUI 内 top 全屏、文件页签的目录导航(进入/.. 返回)、上传(rpc stream sshPlugin/upload + 传输条进度)、下载(download open + 6938 B 文件落地)、多会话标签与亮/暗主题切换,均已实测通过。逐条原话与 console/host 日志见 docs/ACCEPTANCE.md §11.7/§11.8。 证据边界(如实):100 MiB 级传输的闭环在引擎/工具层(真机 100.0 MiB 实测);UI 侧实测为小文件(6938 B),不宣称“UI 端完成 100 MiB 传输”。 平台限制:当前桌面端组合无凭据服务("persisted": false, "reason": "no credentials service in this composition"),密码只存在于会话内存,每次重启 DSH 需重新输入;属平台能力缺失,非插件缺陷。

  1. 打开右侧栏:点侧栏底部 SSH 动作(sidebar.footer.action),或会话输入框左侧的 SSH 图标;Ctrl/Cmd+Shift+S 亦可。
  2. 新建连接(Ctrl/Cmd+T):填 名称 / 主机 / 端口 / 用户 / 认证方式;测试连接(conn.test)会显示延迟、服务端 banner 与主机密钥指纹。
  3. 首次连接:策略为 strict 时弹指纹确认;accept-new 自动记住到 known_hosts;指纹变化一定触发二次确认(SSH_HOSTKEY_MISMATCH)。
  4. 连接后进入会话工作区,四个标签:
    • 终端:真 PTY,跑 top/vim 等全屏程序正常,支持复制粘贴、清屏、字号、重连。这个标签有两面,用标签内的一行切换:
      • 终端:交互式 PTY —— 你的会话;
      • AI 活动:镜像模型经 ssh_* 工具做过什么 —— 命令与其实时 stdout/stderr、上传/下载、列目录、连接/断开,每条带主机、状态、退出码与耗时;模型在另一台主机上干活时这里也看得见。切换器上的圆点是未读计数,每条记录可一键复制。
      • 跟随规则:有活动的会话直接开在活动面;新活动自动切过去,除非你正在终端里打字、或你自己选过面(显式选择不再被自动覆盖)。
    • 命令:单命令 stdout/stderr/退出码/耗时,历史上下翻;
    • 文件:远端目录浏览、上传/下载(进度、断点续传、校验)、新建/重命名/删除/chmod;
    • 日志:本会话审计(脱敏后),可导出。
  5. 多会话标签(Ctrl/Cmd+1..9 跳转、Ctrl/Cmd+W 关闭并二次确认),状态栏显示延迟、连接时长、流量。
  6. 断开:状态栏 断开,或关标签。快捷键总表见 ICD §8.4。

4. Agent 工具

allowAgentTools: true(默认)时向模型暴露 7 个工具:ssh_connect、ssh_disconnect、ssh_sessions、ssh_exec、ssh_upload、ssh_download、ssh_list_dir。工具名与 cordis.patch.yml 的 tools 列表一致(单测断言两者相等),也与 dsh.plugin.json 一致。

4.1 模型做的事在面板里看得见(agent 活动镜像,ICD §4.7)

工具调用发生在 host 侧,不经过浏览器,所以模型干活时面板本来是看不到的。现在每次工具调用都由 host 记进一个有界的活动镜像,在「终端」标签的 AI 活动 面按时间列出:命令及其实时 stdout/stderr、上传/下载(含进度行)、列目录、连接/断开/列会话;每条记录带主机(user@host)、状态、退出码、耗时与失败码,可一键复制整条。

  • 端点:sshPlugin/followActivity(流,无参数 —— 镜像是全局的,每条记录自带 sessionId,所以另一台主机上的工作也不会被藏起来)与 sshPlugin/clearActivity(清全局历史)。
  • 边界(刻意如此):镜像是视图不是日志 —— 只在内存里保留最近若干条(activity.*),不落盘、不脱敏、不承诺跨重载或重启存活;要事后追查请读审计文件(queryAudit,脱敏后 JSONL 落盘)。镜像持有的正是你自己会话的输出,所以命令打印的密钥也会显示在这里 —— 这是与审计文件的有意区别(ICD §12 R10)。
  • 关闭:activity.enabled: false 时整体不记录(零事件、空快照),工具行为不变。

4.2 会话里的 ssh_exec 也是一张终端卡片

同一次调用在会话流里显示为终端卡片:$ <命令>、按 host 的 [stderr] 标记分节的输出、退出码或信号徽标,以及 outcome / 耗时 / streamId 等事实行。这不是 DSH 自动给的:Web 客户端不消费 host 的 presentCall/presentResult 视图,而是按 wire 工具名分派键控槽位 tool.call.toolview,未注册就落回通用行(Tool call · ssh_exec · <第一个字符串参数> 加 Input/Output 文本)。插件因此在自己的 client 半边注册了 key: 'ssh_exec'(client/src/session/toolview.js)。

5. FAQ

Q. 面板是空的 / 标签打不开? 先看侧栏 SSH 标签内的诊断条:它显示客户端解析出的传输通道(carrier)。carrier: unresolved 表示浏览器→host 的通道都没命中;此时 host 侧仍可正常,问题在页面。诊断记录写在 <DSH_HOME>/logs/dsh-ssh/client-transport.json。

Q. 改了代码但界面没变?

  • 改 client/src/** → node scripts/build-client.mjs;只有 lib/client.js 字节变化时页面才会重新 apply()。
  • 改 src/**(host 半边)→ 本机(Windows)无法热更新:lib/** 重建后不会产生任何 reload 事件,toggle include:dsh-ssh 行也只是让已缓存的模块重跑一次 apply(),新代码进不来。要生效必须重启 DSH。
  • 改配置值(profile 的 cordis.patch.yml)→ toggle 行即可:apply() 会用新配置重跑;这与"模块代码是否重新加载"是两件事。
  • 判据(不要再靠猜):host 每次 apply() 都写 <DSH_HOME>\logs\dsh-ssh\host-ready.json,里面是这个活着的实例实际注册的 remoteMethods 列表 —— 方法数没变,就是新代码没进来。
  • 实测证据与根因(hmr 行的 ignored 默认值在 Windows 上把整棵监视树都忽略掉)见 docs/ACCEPTANCE.md §13 与 docs/TESTING.md §2.5。

Q. pnpm install 报 cpu-features 构建被跳过? 非致命:ssh2 自动回退纯 JS 加密。本机实测仍有 30 MiB/s 上传、20 MiB/s 下载(docs/TESTING.md 有实测数字)。

Q. 连接超时/一直转圈? 看错误码:SSH_NET_REFUSED(端口拒绝)、SSH_NET_DNS(域名解析失败)、SSH_TIMEOUT_CONNECT(建连超时,可调 connectTimeoutMs)、SSH_TIMEOUT_IDLE(keepalive 连续失败,链路已死)。retryable: true 的错误会自动退避重试。

Q. 为什么没有跳板机 / 端口转发 / 密钥生成? 本期非目标(见 docs/DESIGN.md §1)。密钥可用系统 ssh-keygen 生成后在档案里选私钥文件。

Q. 传输中断了要重传吗? 不用:sftp.resume: true 时续传,resumedFrom 回带;verify: 'sha256' 会在传输后比对,不一致报 SSH_SFTP_VERIFY_MISMATCH(可续传重试)。

Q. 测试怎么跑? 见 docs/TESTING.md:node scripts/verify-all.mjs(一键 lint + 类型 + 构建 + 单测 + 组件 + 集成 + E2E + 性能),真机层用 --real + DSH_SSH_TEST_REAL_* 显式开启。

6. 开发

node node_modules/typescript/bin/tsc -p tsconfig.json --noEmit   # 类型(第一道门)
node scripts/lint.mjs                                            # lint
node scripts/build-client.mjs && node scripts/build-client.mjs --check   # 客户端产物 + 确定性
node --test --test-concurrency=1 --test-force-exit "test/unit/*.test.mjs"        # host 单测
node --test --test-concurrency=1 --test-force-exit "test/client/*.test.mjs"      # 组件
node --test --test-concurrency=1 --test-force-exit "test/integration/*.test.mjs" # 集成(本地 sshd 靶机)
node test/e2e/run.mjs                                            # 无头 E2E 走查
node scripts/verify-all.mjs                                      # 全部(每层硬超时)

同一时刻只允许一份全仓测试在跑(ICD §12 R9):并发跑会让某个文件看起来"卡住"。verify-all 会先自检并在检测到另一份测试运行时拒绝启动(除非 --allow-concurrent)。

7. 截图

docs/img/ 下是组件测试与真机走查用的界面截图(亮/暗各一套):

亮色 暗色
终端-亮 终端-暗
命令-亮 命令-暗
文件-亮 文件-暗
日志-亮 日志-暗

8. 自测结果

(本节由交付者维护,见 docs/TESTING.md 的"自测结果"小节,含逐层命令、结果与已知缺口。)

—/ 5

No ratings yet

Verified DSH bundle

Commit d96dbd750eeb

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout