- Home
- Plugin
- Integrazioni e accesso remoto
- dsh-computers
dsh-computers
9ashwin/dsh-computers
Connect your own machines to a DSH Web deployment and run commands on them from the agent.
Installazione
dsh plugin --profile web add github:9ashwin/dsh-computersREADME
dsh-computers
把你自己电脑(Mac / Windows / Linux)接到一个 DSH Web 部署上,让部署里的智能体在这些电脑上执行 shell 命令。
形态是「一台服务器作为唯一主控,其余电脑作为被控端接进来」:被控端只向外发起一条长连接,主控端负责注册表、在线状态和命令派发。
- 每台电脑都装同一个插件,所以不需要在终端里粘命令:桌面端 DSH 打开 设置 → 电脑 → 本机运行时,填服务器地址 + 加入密钥,点「连接」即可。
- 服务器那一侧是 电脑 列表:在线状态、重命名、删除,以及
+ 连接电脑生成的兜底命令(给不装 DSH 的机器,比如跑 launchd/systemd 的 Mac mini、Linux 服务器)。 - 对话里智能体用
computer_exec把命令派到指定电脑上跑,用computer_list看有哪些机器。
没有内网穿透、没有开放端口:被控端只向外发起一条 SSE 长连接,所以两台 Mac 在 NAT/家用网络后面也能连上。拓扑明确是 hub-and-spoke,不支持电脑之间互相控制,也不做 P2P。
┌──────────── 服务器(dsh web,role: hub)────────────┐
│ dsh-computers 插件 │
│ · 电脑注册表 + SSE hub + 命令派发 │ ◀──SSE──── ┌─ 这台 Mac(桌面端 DSH + 插件)─┐
│ · computer_list / computer_exec 工具 │ ──命令──▶ │ 设置 → 电脑 → 本机运行时 │
│ · 设置 → 电脑 列表 + 连接电脑 │ ◀─结果─── │ 连接后即成为被控端 │
└─────────────────────────────────────────────────────┘ └───────────────────────────────┘
▲ 浏览器(手机/笔记本都能控制全部电脑) ◀──SSE──── ┌─ 另一台 Mac / Linux ──────────┐
│ │ 同样装插件,或跑 agent 兜底 │
└─────────────────────────────────────────────────────────┴───────────────────────────────┘
1. 服务器:装插件(主控)
插件是纯 JavaScript,无构建、无第三方依赖,直接从 GitHub 装:
推荐从 npm 安装(和官方插件包一样):
# 方式 A:CLI
dsh plugin install dsh-computers
# 方式 B:DSH 设置 → 插件市场 → 安装,搜 dsh-computers
也可以直接从 GitHub 装(跟随仓库最新提交):
dsh plugin install github:9Ashwin/dsh-computers
更新
npm 安装:插件市场 → 已安装 → 「检查更新」即可,或者在 CLI 里装具体版本:
dsh plugin install dsh-computers@1.7.1
GitHub 安装:profile 里是按 spec + commit 固定的(github:user/repo 或 #v1.0.2),所以
- 用同一个 spec 再装一次只会得到
already-installed(这就是「再安装没法更新」的原因); - 装 tag 会被冻住,市场永远检测不到更新;装分支(不带
#)才能被市场「检查更新」发现新提交; - 手动升级到某个版本要换 spec:
dsh plugin install github:9Ashwin/dsh-computers#v1.9.2。
更新完记得重启 Host 并刷新页面,具体见 docs/deploy.md。
装好后重启 dsh web,设置里会出现 电脑 页。
服务端建议把 role 固定成 hub(只当主控)。对外地址会自动识别:你从哪个域名打开设置页(含反向代理的 X-Forwarded-Proto/Host、Forwarded 头),生成的命令就用哪个地址,通常不用配。需要强制指定时才写 publicUrl:
# ~/.dsh/profiles/<profile>/cordis.patch.yml
- id: dsh-computers
name: dsh-computers
config:
role: hub
# 留空 = 按浏览器请求自动推导(含反代的 X-Forwarded-* / Forwarded);
# 只有不想信任代理时才显式写死,例如 https://dsh.example.com
publicUrl: ''
execEnabled: true # false 时拒绝所有远程执行
execTimeoutMs: 120000 # 单条命令默认超时
keyTtlMinutes: 15 # 加入密钥有效期
安全前提:请开启 DSH 的浏览器登录/可信来源(
dsh web默认就有),不要把裸端口暴露给公网。插件给"能使用这个 DSH 的智能体"的权限,等于能在这台电脑上执行任意命令。
2. 每台电脑:装同一个插件(被控)
- 按上一节的方式在这台电脑的 DSH 里装上
dsh-computers(桌面端 / 命令行装法相同)。 - 打开 DSH → 设置 → 电脑,在 本机运行时 卡片里填:
- 服务器地址:你服务器的对外地址,如
https://dsh.example.com - 加入密钥:在服务器的 设置 → 电脑 →
+ 连接电脑里复制的dshc_…
- 服务器地址:你服务器的对外地址,如
- 点 连接,卡片变成「运行中」,服务器列表里随即出现这台电脑(在线)。
被控端默认 role: both,所以这台电脑自己也能当主控;只想当被控端就设 role: runtime。
运行时的开关是持久的:DSH 一启动就自动连回服务器,不用每次手动点。关掉 DSH 就离线。
兜底:不装 DSH 的机器
Mac mini 常驻、Linux 服务器这类不跑桌面的机器,用服务器生成的命令(launchd / systemd 常驻):
curl -fsSL 'https://<你的服务器>/api/dsh-computers/join.sh?key=dshc_xxx' | sh
它会下载 agent 到 ~/.dsh-computers/、注册系统服务并启动。手动管理:
node ~/.dsh-computers/agent.mjs status
node ~/.dsh-computers/agent.mjs service restart
node ~/.dsh-computers/agent.mjs service uninstall
tail -f ~/.dsh-computers/agent.log
3. 对话里怎么用
智能体会看到这些工具(computer 参数都可省略,省略时用输入框左侧选中的目标电脑):
| 工具 | 作用 |
|---|---|
computer_list | 列出可用电脑(名字、平台、在线、能力、当前固定目标) |
computer_exec | 执行命令,支持 run_in_background;超时默认转后台而不是杀掉 |
computer_read | 读远端文件(offset/limit 行) |
computer_write | 写远端文件(会自动建目录) |
computer_edit | 字面替换(old 必须唯一,除非 replaceAll) |
computer_glob | 远端 glob(*、?、**、{a,b}) |
computer_grep | 远端正则搜索(支持 include、命中上限) |
文件类工具需要那台电脑的 agent 上报 fs 能力(见下)。
系统提示里会自动附上当前电脑清单,所以直接说「看看两台 mac 的内存」它就会分别调用。
3.1 能力、流式与后台任务
- 能力协商:agent 在握手时上报
capabilities(exec/fs,以后还有pty)和workspaceBase(默认工作目录)。服务器据此决定是否提供文件类工具——旧 agent 不会因为版本落后而被拒绝,只是没有新能力;设置页会用可升级徽标提示(版本落后不影响computer_exec)。只有在配置里显式写minAgentVersion才会强制拦截旧 agent。 - 流式:长命令的分块输出会随帧回传(含心跳),后台任务的输出可在 DSH 的任务里查看。
- 超时转后台:前台命令超时默认不杀(
promoteOnTimeout: true),而是转入后台任务并返回 jobId,之后用内置的job_output/job_kill接管。 - 激活时机:新电脑要等 agent 首次建流后才出现在列表(从生成密钥到真正连上之间,弹窗会一直显示"等待电脑连接…")。
2.6 一键中继 + 单列表界面(v1.9.2)
中继一键启动:设置 → 电脑 → 高级 → 中继 → 「启动中继」。插件自己拉起来(代码就在插件包里), 所以升级插件就是升级中继,不用 git / systemd / root。启动后地址自动填好,点「启用」即可。
界面只剩一个列表:账号。「接入地址 / 复制接入地址 / 已连接的电脑」全部从界面移除, 加机器只有一条路:复制账号密钥 → 在新机器粘上登录。
本机不再默认连:自运行时第一次用到「本地电脑」时才启动;self: true 的本机记录不会出现在远端列表里。
斜杠菜单只显示当前选中机器的技能:选中哪台就看哪台的技能。
2.7 一把钥匙 + 任意机器执行(v1.9.1)
登录只要一把钥匙(随时可在 高级 → 账号中心 复制):
账号密钥 dshk_6d56dbd4988a3fe3_887d4f25dd9318bce58b4a47f46c664c
本地电脑真能执行了:插件在本机起一个独立的自运行时,「本地电脑」不再是占位。
跨机执行:输入框选择器里选「账号里的电脑」= 在那台机器上执行,
走 <entryUrl>/api/dsh-computers/fleet/exec,用账号密钥鉴权,不需要逐台配对。
被控开关:设置 → 接入 → 账号里的电脑,本机那行有开关。
关掉后别人能看到你、但不能在你机器上执行(403 not-controllable)。
2.8 远端技能进斜杠菜单(v1.9.0)
每台电脑把自己的技能报给主控,斜杠菜单里按机器区分:
/ ← 输入 / 打开
/image-compress ← 服务器本机
/air-image-compress ← Air 上的技能
/mini-video-cut ← mini 上的技能
- 用的是 DSH 官方扩展点
ctx.skills.registerProvider,和本机技能并列 - 选中后主控去那台机器读
SKILL.md,执行落在那台机器上 - 机器离线时它的技能从菜单消失
- 另有
computer_skills工具显式列出某台机器有什么技能
2.9 账号中心(v1.8.0)
同一个账号下的机器自动互相可见,不需要逐台配对:
中继机器:设置 → 电脑 → 高级 → 账号中心 → 创建账号中心
→ 得到 账号 ID + 密码(只显示这一次)
其他机器:设置 → 电脑 → 接入 → 登录账号 → 填 账号 ID + 密码
→ 自动注册,出现在同一账号的设备列表里
- 账号中心跑在中继上(
relay/main.mjs,同一进程,无需额外服务) - 密钥在服务端只存 sha256;插件的
/account与/state永不返回它 - 中继地址不用手写:
/account/discover会探测本机的中继并预填 - 旧的一次性密钥 / 接入地址方式全部保留
3.0 接入:填一个地址(v1.7.0)
主控那台 设置 → 电脑 → 复制接入地址,得到一串:
https://dsh.example.com#join=dshf_…
在另一台机器 设置 → 电脑 → 接入地址 里粘贴 → 连接,完成。不需要生成密钥、不需要记 15 分钟有效期。
#之后是 fragment,代理和浏览器都不会发给服务器,token 不进日志- 地址是长期凭据,可随时「重新生成」(旧地址立刻失效,已接入的机器不受影响)
- 一次性密钥仍保留(「用加入密钥」里),适合"用完即废"
- 详细说明见 docs/onboarding.md
3.1 能力位与执行终态
- 能力位(caps):agent 在握手里上报
caps: { exec, fs, jobs, pty, tunnel },服务器据此决定提供哪些工具,不再用版本号推断功能。minAgentVersion只剩"运维强制升级"这一个作用;版本落后只在 UI 显示可升级。 - 能力标签:设置页按 caps 显示
执行/文件/后台任务/终端,每项带一句人话说明;缺哪项能力时,工具报错会直接点名缺什么、怎么补。 - 执行终态:每次执行都以
{ status, disposition, confirmed, exitCode, signal, error }收尾——status:completed | failed | cancelled | timeout,disposition:reusable | broken。 取消不再一律报"已取消":agent 确认停止 →cancelled / confirmed:true;只发出取消但没等到确认 →cancelled / confirmed:false / broken,并明确提示"命令可能还在那台机器上运行";流中断 →failed / broken。 - 协议与常量:所有 wire 形状、错误码、能力与状态词表集中在
lib/contracts.js(零依赖、零状态),协议文档见 PROTOCOL.md §5.2 / §11。
3.1.1 权限、在线判定与路由健康(v1.5.0)
- 一张权限表:所有操作(exec / 后台 exec / cancel / 文件五件套 / fs 请求 / list)都在
lib/policy.js的冻结表里,加载时自检"没有操作漏登记";准入由唯一的authorize()决定,拒绝时给出码 + 被哪条规则挡住 + 怎么改(capability-missing/session-denied/preset-denied/offline/version-below-minimum…)。 - 在线 = 有 lease:只有已校验的入站帧(hello / 心跳 / chunk / result / fs result)才刷新在场,45 秒租约 + 10 秒扫描。OS 睡眠、代理假死、半开连接都会正确掉线,而不是一直显示在线。
- 路由健康:每个地址候选带
health { state, checkedAt, failures, cooldownUntil }。后台探活GET /api/dsh-computers/health(1.5 秒预算,不阻塞页面),失败即冷却 60 秒并让位给下一个可用地址——坏掉的隧道不会被复制进别人的连接命令。 - relay 传输层(预览,未接线):
relay/main.mjs独立零依赖中继 +lib/relay/{wire,mux,hub-link,bridge}.js已落地并有 47 项测试(S0/S1)。尚未接入插件:hub 目前还不能经 relay 出站,S2(WebSocket 升级)与接线在后续版本。
3.1.2 中继(v1.6.0)
让主控也只需要出站:任意一台机器(mini / air / 服务器上的 dsh web)都能当主控,不需要公网 IP、不需要开放端口。
relay:
enabled: true
url: wss://relay.example.com
- 自动生成并持久化
hubId到<stateDir>/relay.json(0600)——重置电脑列表不会让别人的收藏地址失效 - 设置 → 电脑 → 本机地址 出现一行 中继:
https://relay.example.com/r/hub_xxx,连接成功时是推荐地址 - 别的电脑把「服务器地址」填成这个 URL + 加入密钥即可接入;手机浏览器直接打开它就是完整 UI(含 WebSocket)
- 全部在界面里配(v1.7.1):高级 → 中继 → 填地址 → 启用,现场拨号/换址;
cordis.patch.yml可以完全留空 - 中继断开时该行标「未监听」并回落推荐,不会给你连不上的地址
- 安全边界:hub 侧 bridge 只打本机
127.0.0.1:<自己的端口>(拒绝绝对 URL、..、强制 Host),中继看不到浏览器登录态 - 部署(systemd + nginx + 证书)见 docs/relay-deploy.md,协议见 PROTOCOL.md §12
3.2 地址、可达性与 Tailscale
-
地址候选:设置 → 电脑 顶部「本机地址」会列出本机所有候选地址并标注是否可达——
公网(publicUrl)→转发地址→Tailscale→局域网→本机名→本机。 只有真正在监听的地址才会被推荐(例如桌面端只监听 127.0.0.1 时,Tailscale 那行会标「未监听」,推荐回落到 loopback),避免把连不上的地址复制给别人。 -
+ 连接电脑里的服务器地址是可下拉选择的,默认选中推荐地址,切换后命令/地址随之变化。 -
自动 Tailscale Serve(可选):
- id: dsh-computers config: tailscaleServe: enabled: true httpsPort: 8443插件加载后自动执行
tailscale serve --bg --https=8443 http://127.0.0.1:<当前端口>,把本机 DSH 暴露到 tailnet,并读取tailscale status的真实 DNSName 作为推荐地址。幂等(已指向本端口就跳过),端口每次随机变也会重新对接,失败只记录tunnel.error不推荐假地址。 -
强制偏好:
preferAddress: auto | public | tailscale | lan | loopback(只在可达的候选里选,选不到就回落 auto)。 -
管理电脑:访问权限弹窗里的
⚙ 管理电脑会直接跳到独立的「电脑管理」主面板(侧栏也有入口),不再只是文字提示。
桌面端注意:不要把
webserver.host改成非 loopback 地址(窗口写死连127.0.0.1:<port>,会连不上)。桌面端请用上面的tailscaleServe,独立dsh web才用--host。详见 docs/deploy.md。
4. 端点一览
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/dsh-computers/agent/enroll | 加入密钥换取电脑 token(runtime 卡片也走这里) |
| GET | /api/dsh-computers/agent/events | 被控端的 SSE 长连接(Bearer token) |
| POST | /api/dsh-computers/agent/results | 命令执行结果回传(最终结果,权威) |
| POST | /api/dsh-computers/agent/results/chunk | 流式输出分块(stdout/stderr/heartbeat) |
| POST | /api/dsh-computers/agent/fs/result | 远端文件操作结果 |
| POST | /api/dsh-computers/agent/heartbeat | 心跳 |
| GET | /api/dsh-computers/state | 设置页数据(角色、电脑列表,需要浏览器会话) |
| POST | /api/dsh-computers/keys | 生成一次性加入密钥 |
| GET | /api/dsh-computers/key-status | 弹窗轮询用 |
| POST | /api/dsh-computers/delete / rename | 删除 / 重命名电脑 |
| GET | /api/dsh-computers/runtime | 本机运行时状态 |
| POST | /api/dsh-computers/runtime/connect / enable / disconnect | 连接 / 启用开关 / 断开本机运行时 |
| GET | /api/dsh-computers/join.sh | 兜底安装脚本 |
| GET | /api/dsh-computers/agent.mjs | agent 本体(兜底脚本下载) |
| GET | /api/dsh-computers/health | 健康检查 |
浏览器端点都先过 DSH 的请求闸门(connection.requestRejection),未登录/不可信来源直接拒绝;被控端端点用 Bearer token 认证(服务端只存 sha256(token))。
5. 开发与验证
node scripts/host-smoke.mjs # Host 半:真实 HTTP 监听 + 假 agent/hub,覆盖注册/SSE/exec/超时/runtime
node --test agent/test/ # agent 半:enroll / SSE / exec / 超时 / 截断 / 内嵌运行时
node --check client.js # 浏览器半
协议细节(所有路由、SSE 事件、状态文件、工具 schema、runtime 契约)见 PROTOCOL.md。
6. 已知限制
- 一次执行返回一次性结果(不流式),长命令靠
timeoutMs;输出单流上限 1 MiB。 - 一台电脑同一时刻只保留一条连接(新的会顶掉旧的)。
- 在线判定只看 SSE 是否存活:杀掉进程立刻离线;网络静默(无 FIN)最多差一个心跳周期。
- Windows 被控端目前可用
node agent.mjs run手动运行,服务安装只覆盖 macOS/Linux。 - 电脑与 Bot 的绑定还没有(列表里的
1/1 Bot是占位徽标);v1 里所有电脑对智能体都可选。 - 不做 P2P / 电脑互控;要隔离环境请用 VPN,或把某台桌面端当 hub、另一台连过去(仍是单向)。
License
MIT
开发与测试
npm test # 全部:host smoke + agent + relay
npm run test:host # host smoke(186 组断言)
npm run test:agent # agent(43 项)
npm run test:relay # relay S0/S1(47 项)
Node 24 把裸目录参数当作模块说明符,所以 relay 测试用显式 glob(脚本里已带引号)。
relay.example.com是占位示例。中继跑在你自己机器上,域名和端口由你决定(配置文件或界面里填),项目不绑定任何域名。
Plugin correlati
dsh-web (dsh-ssh)
zhu1090093659/dsh-web
dsh-web (dsh-remote-web-ui)
zhu1090093659/dsh-web
dsh-web-ui (dsh-ssh)
zhu1090093659/dsh-web-ui
dsh-purge
kingcharleslzy-ai/dsh-purge