🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

远程主机工作区 weicy98/dsh-permission-remote

DeepSeek Harness
weicy984 天前有提交已提供中文文档扫描:含敏感能力

把远端 Linux 或 macOS 目录接入会话,命令在远端执行

29.7/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add weicy98/dsh-permission-remote
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · vision
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 4 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 143 处,展示前 66 条)
  • 读取本地文件
    lib/index.js:1386
    const body = await readFile(localPath);
  • 读取本地文件
    lib/index.js:1400
    const body = await readFile(localPath);
  • 读取本地文件
    lib/index.js:1520
    void readFile(host.privateKeyPath).then((key) => {
  • 读取本地文件
    lib/index.js:1966
    if (existsSync(candidate)) return candidate;
  • 发起网络请求
    lib/index.js:2022
    request: (req) => approver.request({
  • 删除本地文件
    lib/index.js:2256
    await rm(target, { force: true });
  • 删除本地文件
    lib/index.js:2301
    await rm(placeholder, { force: true });
  • 写入本地文件
    lib/index.js:2369
    await mkdir(local, { recursive: true });
  • 读取本地文件
    lib/index.js:2972
    projects = await readdir2(sessionsRoot);
  • 读取本地文件
    lib/index.js:2984
    entries = await readdir2(projectDir);
  • 删除本地文件
    lib/index.js:2999
    if (pid === process.pid || !isProcessAlive(pid)) await rm2(joinPath(dir, name2), { force: true }).catch(() => void 0);
  • 删除本地文件递归
    lib/index.js:3012
    if ((await readdir2(projectDir)).length === 0) await rm2(projectDir, { recursive: true, force: false });
  • 写入本地文件
    lib/index.js:3091
    await writeFile(joinPath(dir, staged), rewritten, { mode: 384 });
  • 写入本地文件
    lib/index.js:3092
    if (srcMode !== void 0) await chmod(joinPath(dir, staged), srcMode);
  • 删除本地文件
    lib/index.js:3094
    await rm2(joinPath(dir, staged)).catch(() => void 0);
  • 写入本地文件
    lib/index.js:3098
    await mkdir2(dirname(destDir), { recursive: true });
  • 写入本地文件
    lib/index.js:3099
    await rename(dir, destDir);
  • 删除本地文件
    lib/index.js:3101
    await rm2(joinPath(dir, staged)).catch(() => void 0);
  • 写入本地文件
    lib/index.js:3105
    await rename(joinPath(destDir, staged), joinPath(destDir, logName));
  • 写入剪贴板
    lib/client.js:2783
    (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-ssh-button", onClick: () => void navigator.clipboard.writeText(item.command),
  • 读取本地文件
    src/execute.ts:213
    if (existsSync(candidate)) return candidate
  • 发起网络请求
    src/execute.ts:303
    request: req => approver.request({
  • 删除本地文件
    src/execute.ts:633
    await rm(target, { force: true })
  • 删除本地文件
    src/execute.ts:713
    await rm(placeholder, { force: true })
  • 写入本地文件
    src/execute.ts:797
    await mkdir(local, { recursive: true })
  • 读取本地文件
    src/index.ts:750
    if (!existsSync(expanded)) {
  • 写入本地文件
    src/index.ts:934
    await mkdir(local, { recursive: true })
  • 读取本地文件
    src/index.ts:1061
    if (existsSync(root)) {
  • 写入本地文件
    src/index.ts:1435
    await mkdir(local, { recursive: true })
  • 读取本地文件
    src/migrate.ts:90
    const info = follow ? await stat(target) : await lstat(target)
  • 读取本地文件
    src/migrate.ts:90
    const info = follow ? await stat(target) : await lstat(target)
  • 读取本地文件
    src/migrate.ts:101
    return (await readdir(target)).length === 0
  • 读取本地文件
    src/migrate.ts:106
    for (const entry of await readdir(root, { withFileTypes: true })) {
  • 读取本地文件
    src/migrate.ts:130
    return await realpath(resolved)
  • 读取本地文件
    src/migrate.ts:141
    ancestor = await realpath(cursor)
  • 写入本地文件
    src/migrate.ts:318
    await copyFile(source, target)
  • 写入本地文件
    src/migrate.ts:360
    await mkdir(target)
  • 写入本地文件
    src/migrate.ts:365
    await copyFile(source, target, constants.COPYFILE_EXCL)
  • 写入本地文件
    src/migrate.ts:386
    await mkdir(plan.newRoot, { recursive: true })
  • 删除本地文件
    src/migrate.ts:391
    if (before === 'missing') await rmdir(plan.newRoot).catch(() => undefined)
  • 写入本地文件
    src/migrate.ts:429
    await mkdir(dir, { recursive: true })
  • 写入本地文件
    src/migrate.ts:436
    await copyFile(step.source, step.target, constants.COPYFILE_EXCL)
  • 读取本地文件
    src/migration-state.ts:138
    text = await readFile(migrationStatePath(env), 'utf8')
  • 读取本地文件
    src/session-relocate.ts:406
    projects = await readdir(sessionsRoot)
  • 读取本地文件
    src/session-relocate.ts:420
    entries = await readdir(projectDir)
  • 读取本地文件
    src/session-relocate.ts:428
    names = await readdir(dir)
  • 删除本地文件
    src/session-relocate.ts:438
    if (pid === process.pid || !isProcessAlive(pid)) await rm(joinPath(dir, name), { force: true }).catch(() => undefined)
  • 读取本地文件
    src/session-relocate.ts:453
    if ((await readdir(projectDir)).length === 0) await rm(projectDir, { recursive: true, force: false })
  • 删除本地文件递归
    src/session-relocate.ts:453
    if ((await readdir(projectDir)).length === 0) await rm(projectDir, { recursive: true, force: false })
  • 读取本地文件
    src/session-relocate.ts:484
    size = (await stat(logPath)).size
  • 读取本地文件
    src/session-relocate.ts:525
    destExists = (await stat(destDir)).isDirectory()
  • 写入本地文件
    src/session-relocate.ts:576
    await writeFile(joinPath(dir, staged), rewritten, { mode: 0o600 })
  • 写入本地文件
    src/session-relocate.ts:577
    if (srcMode !== undefined) await chmod(joinPath(dir, staged), srcMode)
  • 删除本地文件
    src/session-relocate.ts:579
    await rm(joinPath(dir, staged)).catch(() => undefined)
  • 写入本地文件
    src/session-relocate.ts:586
    await mkdir(dirname(destDir), { recursive: true })
  • 写入本地文件
    src/session-relocate.ts:587
    await rename(dir, destDir)
  • 删除本地文件
    src/session-relocate.ts:589
    await rm(joinPath(dir, staged)).catch(() => undefined)
  • 写入本地文件
    src/session-relocate.ts:593
    await rename(joinPath(destDir, staged), joinPath(destDir, logName))
  • 写入本地文件
    src/session-relocate.ts:597
    await rename(destDir, dir)
  • 删除本地文件
    src/session-relocate.ts:600
    await rm(joinPath(dir, staged)).catch(() => undefined)
  • 删除本地文件递归
    src/session-relocate.ts:658
    await rm(dir, { recursive: true, force: false })
  • 写入本地文件
    src/sidebar-terminal.ts:205
    await mkdir(cwd, { recursive: true }).catch(() => undefined)
  • 读取本地文件
    src/ssh-pool.ts:446
    const body = await readFile(localPath)
  • 读取本地文件
    src/ssh-pool.ts:461
    const body = await readFile(localPath)
  • 读取本地文件
    src/ssh-pool.ts:595
    void readFile(host.privateKeyPath).then(key => {
  • 读取本地文件
    src/ssh-pool.ts:804
    readFile(path: string, callback: (error: Error | undefined, data: Buffer) => void): void
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/cosmokit@deepseek-ai/dsh-atomic-write@deepseek-ai/dsh-credentials@deepseek-ai/dsh-fs@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-sandbox@deepseek-ai/dsh-settings@deepseek-ai/dsh-tools@deepseek-ai/dsh-typert-protocol@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
展开完整介绍(175 字)↓

把一台 Linux 或 macOS 机器上的目录做成 DeepSeek Harness(DSH)工作区。标准会话里的 bash、read、write、edit、read_image、glob、grep 在那台机器上执行。只读、工作区可写、完全放开,以及一次放宽,仍用这个会话现有的 Permissions 和审批卡片。并支持普通会话中连接已添加的主机

README

由 DeepSeek 最新模型翻译生成
dsh-permission-remote 把一台 Linux 或 macOS 机器上的目录做成 DeepSeek Harness(DSH)工作区。标准会话里的 bash、read、write、edit、read_image、glob、grep 在那台机器上执行。只读、工作区可写、完全放开,以及一次放宽,仍用这个会话现有的 Permissions 和审批卡片。 English。 安装 前置:DSH >=0.2.1-rc.3 <0.3.0、web 配置档、Node >= 22.5。仓库:weicy98/dsh-permission-remote。 从 GitHub 安装: dsh plugin --profile web add github:weicy98/dsh-permission-remote 从 npm 安装: dsh plugin --profile web add dsh-permission-remote 不需要改配置档的 pnpm-workspace.yaml。SSH 库随插件带上,安装时没有要批准的构建脚本。 也可以用本机目录:dsh plugin --profile web add /绝对路径/dsh-permission-remote。 然后重启 dsh web 并硬刷新页面。
展开完整文档(README 剩余 1 节)
dsh-permission-remote

把一台 Linux 或 macOS 机器上的目录做成 DeepSeek Harness(DSH)工作区。标准会话里的 bash、read、write、edit、read_image、glob、grep 在那台机器上执行。只读、工作区可写、完全放开,以及一次放宽,仍用这个会话现有的 Permissions 和审批卡片。

English。

安装

前置:DSH >=0.2.1-rc.3 <0.3.0、web 配置档、Node >= 22.5。仓库:weicy98/dsh-permission-remote。

从 GitHub 安装:

dsh plugin --profile web add github:weicy98/dsh-permission-remote

从 npm 安装:

dsh plugin --profile web add dsh-permission-remote

不需要改配置档的 pnpm-workspace.yaml。SSH 库随插件带上,安装时没有要批准的构建脚本。

也可以用本机目录:dsh plugin --profile web add /绝对路径/dsh-permission-remote。

然后重启 dsh web 并硬刷新页面。设置里会出现 SSH 主机,左侧栏上方也会多一个 SSH 主机 图标(在「插件」和「定时任务」之后),点它直接在中间打开同一页,右上角的「返回会话」回到对话。两个入口是同一份界面和同一份配置。

SSH hosts

用法

1. 添加主机。 从两个地方都能进来:左侧栏的 SSH 主机 图标,或 设置 → SSH 主机。填写地址、端口、用户,以及私钥路径或口令。口令和私钥口令只写入 DSH 凭据存储;私钥只记录本机路径。已保存的主机可以点 编辑 改地址、端口、用户、认证方式和运维策略;改地址或端口会清掉旧指纹,需要重新测试并信任。
2. 测试并信任。 第一次连接会给出主机指纹。确认后再信任,随后用只读沙箱配置档真跑一次 true。
3. 沙箱依赖。 Linux 上没有可用的 bubblewrap 时,可以点 安装远端沙箱依赖。按钮按发行版执行 sudo -n,避免卡在密码提示。页上同时有可复制的自行安装命令:
- Debian/Ubuntu:sudo apt-get update && sudo apt-get install -y bubblewrap
- Fedora/RHEL:sudo dnf install -y bubblewrap
- Arch:sudo pacman -S bubblewrap
- Alpine:sudo apk add bubblewrap
- macOS 使用系统自带的 sandbox-exec,没有安装按钮。探测失败时,受限 bash 需要经审批把这一次升到 danger-full-access。
4. ripgrep。 glob / grep 需要远端有 rg。连接时会按远端架构上传随插件带上的二进制。本机没有该平台的包时(典型是 Windows 上的 DSH 管 Linux 远端,包管理器只装本平台的 @vscode/ripgrep-win32-x64),如果远端 $HOME/.dsh-permission-remote/rg 已经能跑 --version,探测直接用它,不再下载。还没有这份文件时,主机行会出现「安装 ripgrep」:按远端平台从 npm 源下载 @vscode/ripgrep-<platform>,取出 package/bin/rg 缓存到 $DSH_HOME/cache/ripgrep/<platform>/rg,再重新探测;上传被远端拒绝时也用同一个按钮重试。只有点这个按钮才联网,下载源在设置页填(留空用 npmjs,可填镜像)。都不想用的话,可以在远端自己安装 ripgrep,让 rg 出现在 PATH 里。
5. 创建工作区。 在同一页浏览远端目录,把当前目录作成工作区。点某台主机的「浏览目录」,目录面板就展开在这台主机那一行下面(再点一次收起),不会跑到整个列表底下;面板内再点「把当前目录做成工作区」后自动收起,新工作区直接出现在这台主机的列表里。面板上方的筛选框边打字边过滤当前已列出的名字,不发远端请求;换目录时清空,同一目录刷新时保留。「新建文件夹」 在筛选框下方展开一个输入行,回车即在该目录下建一级子目录,列表随即刷新、名字保留在输入框里,方便连着建几个;名字里带 / 或为空会被拦下,重名会照远端返回的结果提示。键盘:/ 聚焦筛选框(只在 SSH 主机页生效,输入框里按 / 照常输入),↑/↓ 在目录间移动高亮,Enter 进入高亮的目录,Esc 先清空筛选、再按一次退出输入框,Home/End 跳到首尾。它会出现在原来的工作区列表里。左侧文件列表仍是本机的占位目录,远端文件要在对话的执行结果里看;右侧栏终端会连到远端,见下面「侧栏终端」。
6. 查看各台服务器的工作区。 每台主机下面有「已添加的工作区」,列出从这台机器创建的工作区、远端目录和会话数。可以只从 DSH 列表移除(远端文件不动),也可以把侧栏标题重新标注。
7. 侧栏认主机。 DSH 的侧栏工作区行只显示标题、没有可挂标记的 slot,所以插件把主机名写进标题:服务器 · 目录名。加载时只标注标题仍是目录名的那些;你改过的名字不会被自动改写,可以随时用该主机下的「标注侧栏标题」手动重标——它会给该主机的全部工作区标注,并把旧服务器的前缀换成当前的。设置页的开关(tagWorkspaceTitles)可以整体关掉标注,重新打开时会立即标注一遍。
8. 删除与恢复主机。 删除主机只是把它从列表移走,工作区仍然注册在 DSH 里,标题保留原前缀。设置页的「已删除的主机」分组里可以「恢复主机」(配置和指纹都还在),也可以逐个「注销工作区」。
打开这个工作区的会话后,对话和 Permissions 与本地会话相同。相对路径按远端工作目录解析。工具结果里的路径写成占位目录,模型下一轮仍用这条路径。working_directory 也留在这条占位路径上:切换目录会确认远端目录存在,再把会话目录改成对应的占位路径,不会改到你这台电脑上的真实目录。每轮的运行时上下文还会写明这条占位路径对应的远端真实目录(Remote directory: "..."),working_directory 的结果也会在占位路径下面多给一行 (remote: /home/...),命令里可以直接用这条绝对路径,不必先跑 pwd。

主机设置:远程调用审批与环境变量来源

每台主机卡片上的两个设置都在编辑对话框里。卡片上平时只显示操作按钮;某一项偏离默认值时,卡片上会多出一个标签写明它,点标签直接打开编辑对话框。

远程调用审批(默认「每次询问」)

控制其他工作区里的会话用 ssh_exec / ssh_write 操作这台主机时放不放行:

| 选项 | 行为 |
| 每次询问 | 执行命令和写文件各要一次 danger-full-access 审批(默认) |
| 不再询问 | 直接执行,不弹卡。卡片上显示琥珀色标签「远程调用免审批」 |
| 禁止调用 | 这台主机拒绝所有远程命令和写入。卡片上显示灰色标签「远程调用已禁止」 |

读取远端文件不受这个设置影响。

环境变量来源(默认「SSH 登录环境」)

远端工作区里的 bash 用哪一套环境变量:

| 选项 | 行为 |
| SSH 登录环境 | 沿用 SSH 登录时拿到的变量,通常只有 /etc/environment 里那几个(默认) |
| 读取 bash 启动脚本 | 捕获一次启动文件导出的变量并复用。卡片上显示标签「读取 bash 启动脚本」和「重新读取环境」 |

选「读取 bash 启动脚本」后,这条 SSH 连接上的第一条工作区命令会读一次启动文件:/etc/profile,然后 ~/.bash_profile、~/.bash_login、~/.profile 里第一个能读的,再是 /etc/bash.bashrc 和 ~/.bashrc。前面已经 source 过的 ~/.bashrc 和 /etc/bash.bashrc 不会再读。读取时丢掉这些文件的输出,最多 10 秒。超时或失败时,这次连接后面的命令继续用 SSH 原来的环境,也不会反复重试。

读到的变量会复用,直到断开连接、切回「SSH 登录环境」,或在卡片上点「重新读取环境」。重新读取只丢掉这份缓存,不断开 SSH;下一条工作区命令会再读一次,同样最多 10 秒。bashrc 里导出的 PATH(例如 nvm、cargo)会保留。如果 bashrc 把 PATH 设成了错误的值,之后的命令会一直用这份 PATH,直到重新读取、断开连接或切回。

PWD、OLDPWD 和 SHLVL 是当前会话的状态,不会进缓存。每条命令自己的 bash 再设置它们。不读取 ~/.zshrc。别名和函数不会保留:靠 PATH 找到的命令会生效,靠 shell 函数的(如 nvm)不会。每条用户命令仍是一个新的 bash。ssh_exec 不受这个设置影响,它还是 bash -lc。

workspace-write 的文件写入在远端沙箱进程里完成:内容先进入同目录的临时文件,再 mv -f 换成目标文件。中途断开留下的是临时文件,原来的内容还在。danger-full-access 用同一步替换,但不包沙箱。

权限

| 档位 | 文件写入 | bash |
| read-only | 拒绝 | 有可用 runner 时在约束下执行;没有则 SANDBOX_UNAVAILABLE,命令不跑 |
| workspace-write | 只允许远端工作区和远端临时目录 | 同上。bubblewrap 的 /tmp 是私有 tmpfs;文件工具写的是真实临时目录 |
| danger-full-access | 按远端登录用户执行 | 不包壳。没有 runner 时,停掉任务可能收不齐子孙进程 |

模型带 sandbox_permissions 和一句话理由重试时,仍走现有审批。批准只放宽这一次调用。

远程运维

普通工作区(目录在你这台电脑上)也可以让模型操作一台已配置的 SSH 主机,用来查日志、重启服务、改配置。打开会话即可看到四个工具:

| 工具 | 作用 |
| ssh_hosts | 列出主机 id、名称、地址、用户、运维策略和指纹是否已信任 |
| ssh_exec | 在那台机器上执行 bash -lc,可选绝对路径 workdir 和 timeoutMs |
| ssh_read | 读取远端文件,1 MiB 上限,按行分页;支持 ~/ |
| ssh_write | 用临时文件加 mv -f 原子替换远端文件。已有文件保留权限位,新文件按远端 umask 创建,目录路径会被拒绝 |

每台主机在设置里有一个远程运维策略:

| 策略 | 读取 ssh_read | 命令 ssh_exec / 写入 ssh_write |
| 询问(默认) | 允许 | 只在本会话是 danger-full-access 时直接执行;否则第一次被拒,模型必须带 sandbox_permissions: "danger-full-access" 和理由重试后弹审批卡。卡片上有主机和截断后的命令或路径,批准只放宽这一次。其它档位不会弹卡 |
| 信任 | 允许 | 直接执行,不弹审批 |
| 禁止 | 拒绝 | 拒绝 |

ssh_exec 和 ssh_write 在远端不受远端工作区沙箱约束,因为运维目标通常是本机工作区之外的机器。它们要么走当前会话的 danger-full-access,要么走这台主机的信任策略,要么走一次审批。读取只受「禁止」限制。

运维工具的前提:

- 目标主机必须已经在设置里信任过指纹,否则连接失败
- ssh_exec 用 bash -lc,会读登录 shell 的 profile,这样 PATH 里才有 systemctl、docker 这类命令;profile 若往 stdout 打东西,会出现在命令输出里
- sudo 只能走免密(sudo -n)。SSH 非交互执行没有 TTY,需要口令的 sudo 会卡住
- 只支持直连的 Linux 或 macOS;不支持跳板机和 Windows 远端
- 目前没有上传和下载工具
- 网络中断时,正在执行的远端命令会失败;已经写入的文件留在远端,不会回滚

侧栏终端

在远端工作区里,右侧栏的终端连的是那台服务器,不是你这台电脑:新建终端时 shell 菜单里列的是远端 $SHELL 和远端装了的 bash / zsh / fish / sh,标签名带主机名(lo · zsh)。缩放、滚屏、复制、刷新页面后重连都由 DSH 原来的终端界面负责。

几点要注意:

- 权限。 侧栏终端是远端登录用户自己的 shell,和本地会话里的侧栏终端一样不套 bubblewrap,也不按按键走 Agent 审批。闸门只有「这台主机的指纹已经信任」。要按当前权限档位跑命令,用对话里的 bash 工具。
- 连接是共享的。 一个标签页一条 SSH 通道,和文件传输、远端命令共用这台主机的那一条连接。OpenSSH 默认 MaxSessions 10,而 DSH 每个会话最多 8 个终端。通道占满时新标签页会报这台主机拒绝再开一个 SSH 会话;只关侧栏终端不一定腾得出名额,也可以在远端调高 MaxSessions。
- 不跟随后续的 working_directory。 已经开着的终端留在打开时那个远端目录;新开的用当时的目录。占位子目录不存在时,新建终端会先把它建出来,这样不会退回工作区根。建不出来时仍由 DSH 退回工作区根。
- 断线即结束。 连接断了终端就结束,不会自动重连,也没有回滚:关掉标签页重开一个即可。
- 只有侧栏接上了。 模型侧的 terminal_open 在远端工作区里会直接报错并提示改用 bash 或侧栏终端——它请求的是本机 PTY 后端,会把 shell 开在你这台电脑的占位目录里。terminal_send / terminal_read / terminal_list 不动:它们操作的是已经存在的终端。

使用上要注意

对话里的读文件、改文件、搜索和运行命令发生在远端。左侧的文件列表还是本机的占位目录,里面看不到远端工程;右侧栏终端见上一节,它连的是远端。

DSH 本身跑在 Windows 上时,远端工作区的对话只暴露远端 bash。pwsh、powershell、cmd 不会出现在原生工具列表里,官方那段 Windows 进程结束说明(tool:pwsh)在停在这个工作区时也会被盖住。回到本机目录后,这些 Windows 工具和原来的说明恢复。minimal 这类把提示词写成完整 persona 的预设不会被改写;挡住 Windows shell 的是工具列表过滤和执行守卫。内置预设的 pwsh 在祖先层,进入远端时会被从原生工具列表和 ptc SDK 正文里去掉。注册在 agent 本层的 pwsh 去不掉:SDK 正文仍可能列出它,原生列表会被滤掉,调用会被拒绝。文件工具的参数继续用工具返回的占位路径。

模型用「运行代码」,或者会话要读取项目里的 AGENTS.md 时,读到的也是你电脑上的这个工作区目录,不是远端那一份。

目前只能直接连接一台 Linux 或 macOS。不能经跳板机连接,也不能把 Windows 当作远端。网络中断时,正在进行的操作会失败。已经写到远端的文件会留在那里:重新连上之后不会再写一遍,也不会自动撤回。远程工作区里的 bash、read、write、edit 只能操作那台机器;要在当前本地工作区操作另一台主机,用上面的 ssh_hosts / ssh_exec / ssh_read / ssh_write。

Linux 上要让命令遵守当前权限,需要在那台机器上安装 bubblewrap。设置页可以代为安装,页面上也有可以自己执行的命令。

开发

npm install
npm test
npm run typecheck
npm run build

lib/ 是安装入口,改完源码需要重新构建。

SSH remote workspaces for the DSH web GUI. On a workspace created from Settings → SSH hosts, the standard tools bash, read, write, edit, read_image, glob, and grep run on that Linux or macOS host. The session keeps its existing Permissions selector and approval card.

Requires DSH >=0.2.1-rc.3 <0.3.0, the web profile, and Node >= 22.5. Source: weicy98/dsh-permission-remote.

From GitHub:

dsh plugin --profile web add github:weicy98/dsh-permission-remote

From npm:

dsh plugin --profile web add dsh-permission-remote

No pnpm-workspace.yaml edit. The SSH library ships inside the plugin, so install does not ask to approve a build script.

A local checkout also works: dsh plugin --profile web add /absolute/path/to/dsh-permission-remote.
重启 dsh web,然后强制刷新。设置里会出现 SSH hosts,左侧边栏顶部也会多出一个 SSH hosts 图标(排在 Plugins 和任务管理器之后):点它会在中栏打开同一个页面,右上角的 Back to session 则回到对话。两个入口共用同一份配置,只是界面不同。添加主机、信任其指纹,然后浏览远程目录并创建工作区。Browse 会在该主机所在行内打开选择器,位置就在你按下的按钮正下方(再按一次关闭),而不是在整个列表下方。创建工作区会关闭选择器,让新行可见。列表上方的过滤框会随输入缩小列出的名称,无需远程往返;切换到另一个目录时清空,刷新同一目录时保留。New folder 会在过滤框下方打开输入行,按 Enter 在所列目录下创建一个子目录:列表就地刷新,名称留在输入框里,因此可以连续创建多个文件夹。名称含 / 或为空会被拒绝;重名由远程自己的回复报告。键盘操作:/ 聚焦过滤框(仅在 SSH hosts 页面,且在任何输入框内输入字面 / 仍正常输入),↑/↓ 移动高亮,Enter 打开高亮的目录,Esc 先清空查询再离开输入框,Home/End 跳到两端。密码保存在 DSH 凭据存储中。私钥通过本地路径引用。已保存的主机可以编辑,修改地址、端口、用户、认证方式或运维策略;修改地址或端口会清除已存指纹,需要重新信任新的主机密钥。

每台主机会列出从它创建的工作区,以及远程目录和会话数。注销只从 DSH 移除该行;主机上的文件保留。DSH 根据标题渲染侧边栏工作区行,且该行不暴露插槽,因此插件把服务器写进标题,格式为 server · directory。加载时打标签只处理标题仍是目录名的工作区;你改过的名称不动,每台主机都有一个「Tag sidebar titles」按钮,为它下面的每个工作区加前缀,替换旧的服务器前缀。设置里的开关(tagWorkspaceTitles)可完全关闭打标签;重新打开会立即给现有工作区打上标签。

删除主机只会把它从列表移除:其工作区仍保持注册,标题前缀不变。设置里的「Deleted hosts」分组可以恢复主机(配置和指纹都保留),或逐个注销它的工作区。
working_directory 会停留在占位路径上,指向那个远程目录。每轮运行的上下文里也会写明占位路径对应的真实远程目录(Remote directory: "..."),并且 working_directory 的结果会在占位路径下面多出一行 (remote: /home/...),这样命令不用先跑 pwd 就能直接用绝对路径。在 workspace-write 下,受限命令会在同一目录里写一个临时文件,再用 mv -f 替换目标文件,所以连接断开也不会截断原文件。danger-full-access 用同样的替换方式,只是不做限制。

Linux 上的 bash 限制需要 bubblewrap。设置页可以用 sudo -n 安装它,也会给出 Debian、Fedora、Arch、Alpine 的手动命令。macOS 用系统自带的 sandbox-exec。

glob 和 grep 需要远程上有 ripgrep;插件手上有匹配的二进制文件时会把它上传过去。包管理器只装自己平台的包,所以 Windows 上的 DSH 管理 Linux 远程时,没有 Linux 二进制文件可上传。如果 $HOME/.dsh-permission-remote/rg 已经有能跑 --version 的二进制文件,就直接用它。否则主机那一行会提供「Install ripgrep」:从配置的 npm 源(留空表示 npmjs;镜像也行)为远程下载 @vscode/ripgrep-<platform>,从 tarball 里取出 package/bin/rg,缓存到 $DSH_HOME/cache/ripgrep/<platform>/rg,然后重新探测。远程拒绝上传后,也是点这个按钮重试。不点这个按钮就不会有任何网络请求。另一种办法是在远程上装好 ripgrep,让 rg 出现在 PATH 里。

按主机配置:远程调用审批与环境来源

这两项设置都在编辑对话框里。卡片平时只显示操作;其中某一项离开默认值时,卡片上会多出一个标注它的标签,点这个标签就能打开对话框。

远程调用审批(默认:每次询问)

控制其他工作区里的会话能否对本主机使用 ssh_exec / ssh_write:

| 选项 | 行为 |
| 每次询问 | 每条命令、每次写入都需要一次 danger-full-access 审批(默认) |
| 不询问 | 直接运行,不弹提示。卡片显示琥珀色标签:远程调用无需审批 |
| 阻止调用 | 本主机拒绝所有远程命令和写入。卡片显示灰色标签:远程调用已阻止 |

读取远程文件不受这项设置影响。

环境来源(默认:SSH 登录环境)

远程工作区的 bash 运行时用哪些变量:

| 选项 | 行为 |
| SSH 登录环境 | 保留 SSH 登录时的环境,通常只有 /etc/environment 里的那几个(默认) |
| 读取 bash 启动文件 | 抓取启动文件导出的变量一次,之后复用。卡片显示标签和「Re-read environment」 |
开启 Read bash startup files 后,该 SSH 连接上的第一条工作区命令会读取 /etc/profile,再读取 ~/.bash_profile、~/.bash_login、~/.profile 中第一个可读的文件,然后读取 /etc/bash.bashrc 和 ~/.bashrc。如果前面的文件已经 source 过 ~/.bashrc 或 /etc/bash.bashrc,就不会再读一次。这些文件的输出会被丢弃。读取超过 10 秒就停止。如果超时或失败,该连接上后续命令继续沿用 SSH 环境,且不会重试读取。

这些变量会一直复用,直到连接断开、开关切回 SSH login environment,或在卡片上按 Re-read environment。重新读取只清掉缓存,SSH 连接保持不断;下一条工作区命令会再次读取这些文件,同样有 10 秒上限。bashrc 导出的 PATH 会保留,比如 nvm 或 cargo 设置的。如果 bashrc 把 PATH 设坏了,后续命令会一直用这个坏 PATH,直到重新读取、断开连接或切回。

PWD、OLDPWD、SHLVL 属于会话状态,不缓存。每条命令自己的 bash 会设置它们。~/.zshrc 不读取。别名和函数不保留:通过 PATH 找到的命令能用,nvm 这类 shell 函数不能用。每条用户命令仍然是全新的 bash。ssh_exec 忽略这个设置,始终用 bash -lc。

在对话中,读取、编辑、搜索和 shell 命令都在远程机器上运行。左侧的文件列表仍是本地的占位目录。

DSH 自身跑在 Windows 上时,远程工作区只暴露远程的 bash 工具。pwsh、powershell、cmd 会从原生工具列表里移除,关于 Windows 进程终止的静态说明 tool:pwsh 在会话停留于该工作区期间会被覆盖。回到本地目录后,这些 Windows 工具和原始说明都会恢复。complete: true 的 persona(比如 minimal)不会被改写;工具列表过滤和一道执行守卫会阻止 Windows shell 运行。内置预设的 pwsh 位于祖先作用域,所以进入远程工作区会把它从原生工具列表和 ptc SDK 文本中移除。注册在智能体作用域本身的 pwsh 无法这样移除:SDK 文本可能仍会提到它,原生列表会被过滤,调用会被拒绝。文件工具的参数保留工具已经返回的占位路径。

侧边栏终端

远程工作区右侧边栏的终端连接的是那台主机,不是你的电脑:shell 菜单列出远程的 $SHELL,以及主机上有的 bash、zsh、fish、sh,每个标签页以服务器命名(lo · zsh)。调整大小、回滚、复制,以及页面重载后重新挂接,都由 DSH 自己的终端 UI 负责。
- 权限。 侧边栏终端就是远程登录用户自己的 shell。和本地侧边栏终端一样,它不套 bubblewrap,也不会每次按键都弹确认;唯一的关卡是受信任的主机指纹。如果命令需要遵循会话的权限模式,请改用 bash 工具。
- 一条共享连接。 每个标签页都是这台主机唯一连接上的一条 SSH 通道,与文件传输和远程命令共用。OpenSSH 默认 MaxSessions 10,DSH 每个会话允许 8 个终端。连接占满后,下一个标签页会失败,因为主机拒绝再开 SSH 会话;关闭侧边栏终端未必能腾出槽位。腾不出来时,请在远程调高 MaxSessions。
- working_directory 不跟随。 已打开的终端保持它启动时的远程目录;新标签页使用当前目录。占位子目录不存在时,会在终端打开前创建,这样会话不会回退到工作区根目录。如果该目录创建失败,DSH 照常恢复工作区根目录。
- 连接断开即终端结束。 不会重连,也不会回滚;请开新标签页。
- 仅限侧边栏。 terminal_open 在远程工作区会拒绝,并指向 bash 或侧边栏终端:它会向本地 PTY 后端要一个 shell,那会在你自己的电脑上、占位目录里打开。terminal_send、terminal_read、terminal_list 不受影响;它们操作的是已存在的终端。

从普通工作区做远程操作

目录在你本机的工作区,也能对配置好的 SSH 主机执行操作。每个会话都有四个工具:

| 工具 | 用途 |
| ssh_hosts | 列出主机 id、名称、地址、用户、操作策略,以及主机密钥是否受信任 |
| ssh_exec | 在该主机上运行 bash -lc,可选绝对路径 workdir 和 timeoutMs |
| ssh_read | 读取远程文件,上限 1 MiB,按行分页;~/ 会展开 |
| ssh_write | 用临时文件加 mv -f 原子替换远程文件。已有文件保留权限位,新文件使用远程 umask,目录路径会被拒绝 |

每台主机在设置里都有 Remote ops 策略:

| 策略 | ssh_read | ssh_exec / ssh_write |
| Ask(默认) | 允许 | 仅当会话为 danger-full-access 时运行;否则首次调用被拒,重试必须传 sandbox_permissions: "danger-full-access" 并附理由。审批卡片会写明主机名和截断后的命令或路径,只授权这一次调用。其他模式不会弹卡片 |
| Trust | 允许 | 不弹提示直接运行 |
| Deny | 拒绝 | 拒绝 |

操作类命令和写入不套远程工作区沙箱,因为目标通常是本地工作区之外的机器。它们需要会话的 danger-full-access、受信任的主机,或一次性审批。读取只受 Deny 限制。
前置条件:主机指纹必须已经受信任;ssh_exec 运行 bash -lc,因此会加载登录配置档,让 systemctl、docker 这类命令进入 PATH(配置档的输出会混进命令输出里);sudo 只在不需要输入密码时可用(sudo -n),因为 SSH exec 没有 TTY;只支持直连 Linux 或 macOS,不支持跳板机和 Windows 远程主机;目前还没有上传、下载工具。网络中断时,正在运行的命令会失败,已经写入的文件会留在远程主机上。

运行代码,以及会话读取 AGENTS.md,用的也是你电脑上的工作区目录,不是远程主机上的副本。

你可以直连一台 Linux 或 macOS 机器。不支持跳板机和 Windows 远程主机。网络中断时,正在进行的操作会失败。已经写入远程主机的文件会留在那里:重新连接不会再次写入,也不会撤销。

在 Linux 上,如果希望命令遵循当前权限模式,请在那台机器上安装 bubblewrap。设置页面可以安装它,也会显示你可以自己运行的命令。
预览分享卡片长什么样 ↓weicy98/dsh-permission-remote
分享 · DeepSeek Harness Hub
weicy98/dsh-permission-remote

把远端 Linux 或 macOS 目录接入会话,命令在远端执行

这是你的仓库?认领条目

回到顶部