dsh-remote-window 0.0.0-stage → 0.1.0-alpha.3
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 +24 -0
- package/README-zh.md +288 -0
- package/README.md +331 -2
- package/assets/official-ui/NOTICE.md +25 -0
- package/assets/official-ui/approval-client.js +367 -0
- package/assets/official-ui/chat-client.js +13343 -0
- package/assets/official-ui/conversation-client.js +20263 -0
- package/assets/official-ui/model-client.js +1931 -0
- package/assets/official-ui/tool-client.js +4633 -0
- package/assets/official-ui/trajectory-client.js +8257 -0
- package/assets/server-plugin/checksums.json +3 -0
- package/assets/server-plugin/dsh-remote-window-server-0.1.0-alpha.3.tgz +0 -0
- package/cordis.patch.yml +28 -0
- package/lib/client.js +13003 -0
- package/lib/client.js.map +1 -0
- package/lib/connection.js +242 -0
- package/lib/index.js +10974 -0
- package/lib/protocol.js +132 -0
- package/lib/routing-CQbG6gEF.js +473 -0
- package/lib/routing-qoFLGH4r.js +473 -0
- package/lib/rsession.js +250 -0
- package/lib/session-persistence.js +1 -0
- package/package.json +176 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 graydovee
|
|
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.
|
|
22
|
+
|
|
23
|
+
Client-specific framing code is adapted from DeepSeek Harness SSH helper code
|
|
24
|
+
under its MIT license.
|
package/README-zh.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# dsh-remote-window
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](#compatibility)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-remote-window)
|
|
9
|
+
|
|
10
|
+
把一台 SSH 主机打开成 DeepSeek Harness 工作区。**server 是一台能独立工作的普通 DSH;本插件让它成为
|
|
11
|
+
client 的远程入口。** 工作区、会话、会话日志与共享视图状态都以 server 为权威。client 只加一条浮动
|
|
12
|
+
target rail,并把 server 的**完整官方 DSH UI 以同源 iframe 全屏铺满主区**——sidebar、header、对话、
|
|
13
|
+
右侧栏与第三方 UI 插件都是 server 上跑的那一份;client 不再自绘对话。
|
|
14
|
+
|
|
15
|
+
连接时选择 agent 的**模型请求**由谁服务:
|
|
16
|
+
|
|
17
|
+
- **local(默认)**:agent、会话日志与工具仍在 server,但每次模型请求经 SSH 反向隧道转发到 client
|
|
18
|
+
本机。默认接入方式是 **自动代理本机模型**:client 用本机 DSH 的 `ctx.llm` 枚举全部 provider/model,
|
|
19
|
+
无需填 endpoint 或 key,每个 provider 在 server profile 的模型选择器里各成一条 route。**高级选项**
|
|
20
|
+
会列出发现到的全部 provider/model(默认全选,可逐项勾选、按 provider 全选/清空、刷新),选择会
|
|
21
|
+
保存并决定 profile 生成哪些 route,server 会话即可在你允许的本地模型之间自由切换。新 server 通常
|
|
22
|
+
没配模型 key,所以默认走这里;本机模型目录读不到时阻止连接,绝不静默回退到 server 模型。
|
|
23
|
+
- **server(可选)**:server profile 使用自己的模型配置;仅当 server 已配置模型凭据时显式选择。
|
|
24
|
+
|
|
25
|
+
两种来源写同一份 server 会话日志;切换模型来源需要重连,而不是换会话。
|
|
26
|
+
|
|
27
|
+
## Screenshots
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
client 只保留一个远程面:浮动 **target rail**(默认收起,hover 或 `Alt+R` 展开),列已保存连接、实时
|
|
32
|
+
阶段与连接/切换/忘记动作。target rail 可拖到任意屏幕边缘并自动吸附:收起时跟随吸附边(左右为 44px 竖排图标条、
|
|
33
|
+
上下为 44px 横排图标条),**展开后无论哪条边都是同一个 280px 竖向列表**(按视口限高、列表纵向滚动),
|
|
34
|
+
位置跨刷新记忆。连接数大于 1 时,收起条末端多一个折叠按钮(竖排在最后一个图标下方、横排在图标条右端,
|
|
35
|
+
箭头指向收起方向);按钮在同一张卡片里、但不属于 hover 展开组,指针移上去不会把条带展开。点击把 rail
|
|
36
|
+
最小化为单个 `+N` 图标(N 为连接数),此时 hover 不展开;再点图标直接一步展开(指针移开后回到收起条)。连接后,
|
|
37
|
+
server 自己的 sidebar
|
|
38
|
+
与对话就是导航;iframe 挂载期间 client shell sidebar 隐藏。
|
|
39
|
+
|
|
40
|
+
## Why this exists
|
|
41
|
+
|
|
42
|
+
Harness 只在自己运行的机器上干活。当工作必须发生在别处——构建机、GPU 主机、预发服务器——有三种做法:
|
|
43
|
+
|
|
44
|
+
1. **把文件复制回来。** 镜像会漂移、冲突,两个人碰同一棵树就会悄悄出问题。
|
|
45
|
+
2. **所有东西都跑在远端。** 模型凭据和对话也跟着搬过去,换台电脑就没法接着干。
|
|
46
|
+
3. **server 保持普通 DSH,client 做它的远程入口。** 这就是本插件:目标机上跑 per-client profile
|
|
47
|
+
server(`dsh web`),client 通过 SSH 访问它,server 官方数据保持权威。
|
|
48
|
+
|
|
49
|
+
由此得到三条不变量:
|
|
50
|
+
|
|
51
|
+
- **凭据不过线。** server 模型来源用 server 自己的凭据;local 来源的 key 只存 client 凭据库,由 bridge
|
|
52
|
+
注入。SSH 密钥只在 client。错误、日志与流式明细都会脱敏。
|
|
53
|
+
- **目标机不监听公网端口。** profile server 只绑 `127.0.0.1`,client 经 SSH `forwardOut` 访问。
|
|
54
|
+
- **每 client 一个 profile,每会话一个写者。** server 插件持有 client lease;官方 JSONL flock 跨进程
|
|
55
|
+
裁决会话写者,第二个写者会得到明确的 `session/writer-held`,而不是静默写坏日志。
|
|
56
|
+
|
|
57
|
+
如果你只需要“在一台服务器上改文件”,官方 SSH profile(`dsh-ssh` + `fs-ssh` + `subprocess-ssh` +
|
|
58
|
+
`sandbox-ssh`)更小,也不需要装插件。
|
|
59
|
+
|
|
60
|
+
## How it compares
|
|
61
|
+
|
|
62
|
+
| | 官方 SSH profile | 文件路由类插件 | 本插件 |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| 工具在哪执行 | 远端 | 远端 | 远端 |
|
|
65
|
+
| 一个界面里多个 target | 每台一个 profile | 支持 | 支持 |
|
|
66
|
+
| 会话日志权威 | 本地 | 本地 | **server** |
|
|
67
|
+
| 工作区/pin/archive/顺序 | 本地 | 本地 | **server(跨 client 共享)** |
|
|
68
|
+
| 连接的模型来源 | 固定本机 | 固定本机 | **local 隧道或 server** |
|
|
69
|
+
| 换台电脑继续 | 不行 | 不行 | **可以** |
|
|
70
|
+
|
|
71
|
+
## Architecture
|
|
72
|
+
|
|
73
|
+
```mermaid
|
|
74
|
+
flowchart LR
|
|
75
|
+
subgraph client [你的电脑]
|
|
76
|
+
UI[client Web UI<br/>target rail · 全屏 iframe]
|
|
77
|
+
CHOST[client DSH host<br/>portal · P1 客户端 · profile 生命周期]
|
|
78
|
+
LOCAL[本机模型 bridge<br/>127.0.0.1 临时端口]
|
|
79
|
+
LLM[(你的 OpenAI 兼容 LLM)]
|
|
80
|
+
end
|
|
81
|
+
subgraph server [SSH 目标机]
|
|
82
|
+
PROF[per-client profile server<br/>官方 DSH UI · agent]
|
|
83
|
+
PLUG[server 插件<br/>P1 RPC · SSE · mux · embed]
|
|
84
|
+
OFF[官方 DSH 服务<br/>workspaceRegistry · sessionPersistence · sessionController]
|
|
85
|
+
LOG[(server ~/.dsh/sessions<br/>workspace registry · 视图状态)]
|
|
86
|
+
end
|
|
87
|
+
UI -->|同源 iframe| CHOST
|
|
88
|
+
CHOST -->|portal 字节代理<br/>forwardOut| PROF
|
|
89
|
+
CHOST -->|P1 HTTP/SSE/WS| PLUG
|
|
90
|
+
PROF -->|模型请求经<br/>反向隧道 + bridge| LOCAL
|
|
91
|
+
LOCAL --> LLM
|
|
92
|
+
PLUG --> OFF --> LOG
|
|
93
|
+
PROF --> OFF
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
client host 负责 SSH、host key pin、runtime/插件部署、per-client profile 生命周期、P1 客户端、同源
|
|
97
|
+
portal 与本机模型 bridge/tunnel。server 插件负责握手/租约、包装官方服务的 workspace/session API、
|
|
98
|
+
registry 写守卫、执行世界 WS mux 与 embed bootstrap/CSS。`dshRwFull=1` 让 embed 不再注入隐藏 sidebar/
|
|
99
|
+
rightbar/bottom 的 CSS,因此 iframe 里就是真实的 server UI。逐字契约见
|
|
100
|
+
[doc/refactor-v3-full-ui.md](doc/refactor-v3-full-ui.md)、
|
|
101
|
+
[doc/refactor-v1-architecture.md](doc/refactor-v1-architecture.md) 与
|
|
102
|
+
[doc/refactor-v1-protocol.md](doc/refactor-v1-protocol.md)。
|
|
103
|
+
|
|
104
|
+
## Feature breakdown
|
|
105
|
+
|
|
106
|
+
**官方 UI 全屏 iframe。** client main slot 只挂一个同源 iframe,加载 profile server 的官方 `dsh web`
|
|
107
|
+
UI,请求带 `dshRwFull=1`。portal 剥掉 client GUI cookie、注入 profile 自己的 cookie、不转发
|
|
108
|
+
`Set-Cookie`,且 token 不进入 iframe URL。iframe sandbox 固定为
|
|
109
|
+
`allow-scripts allow-same-origin allow-forms allow-popups allow-downloads`。iframe 挂载期间 client
|
|
110
|
+
shell sidebar 隐藏,target rail 保留可用。
|
|
111
|
+
|
|
112
|
+
**local 模型来源。** server profile 的 `cordis.patch.yml` 声明若干官方 `llm-pi-ai` route,指向
|
|
113
|
+
`http://127.0.0.1:<随机>/<provider>/v1`。该端口是 SSH `forwardIn` 反向隧道,终止在 client 的
|
|
114
|
+
loopback-only bridge。默认的 **自动代理** 用本机 DSH 自己的 `ctx.llm` 枚举 provider/model
|
|
115
|
+
(`listProviders`/`listModels`),每个模型请求都以官方类型转成 `ctx.llm.stream()`,因此本机 DSH 的
|
|
116
|
+
凭据与协议原样生效、无需重填。**高级选项** 保存的 `localSelection` 决定代理哪些 provider/model:
|
|
117
|
+
未保存 = 全量,过滤后的集合裁剪生成的 route,过滤为空则报错拒绝。(旧的 **手动 endpoint** 路径仅为
|
|
118
|
+
兼容旧设置/测试保留,对话框不再提供;profile 始终只看到占位 key。)本机目录读不到、模型列表为空或
|
|
119
|
+
sshd 拒绝转发都返回带 code 的可操作错误,绝不静默回退 server 模型。
|
|
120
|
+
|
|
121
|
+
**server 权威数据。** workspace 清单/标题/顺序/pin/archive 来自 server 官方
|
|
122
|
+
`workspaceRegistry`;session 清单/标题/blank/running 来自 `sessionController` + 插件 JSONL title fold。
|
|
123
|
+
rename/fork/pin/archive/拖拽都调官方服务。搜索仅 client 内存过滤。
|
|
124
|
+
|
|
125
|
+
**per-client profile 与 runtime。** 每个 client 生成稳定 `clientId`,server 上用
|
|
126
|
+
`graydovee-remote-window-<clientId>`。专用 profile 与 server 插件从 client 包内置的
|
|
127
|
+
`dsh-remote-window-server-<version>.tgz` 离线安装;独立 Node+DSH runtime 自动安装/升级(或只读复用
|
|
128
|
+
已验证的全局 DSH)。profile lease 拒绝第二个驱动实例。
|
|
129
|
+
|
|
130
|
+
**Host key 必须确认。** 首次连接展示 host key SHA-256 并等待显式确认;之后 key 变化会中止连接。
|
|
131
|
+
|
|
132
|
+
**沙箱与权限。** server profile 为 iframe 里的 agent 挂官方 sandbox provider/policy;没有隔离 backend
|
|
133
|
+
时明确返回 partial enforcement 并在 server 执行,不假装已隔离。v2 executor 的远端执行世界仍保留在
|
|
134
|
+
代码/数据中供旧会话使用,但新主区不再进入。
|
|
135
|
+
|
|
136
|
+
## Quick start
|
|
137
|
+
|
|
138
|
+
装进 client 的 `web` profile(Plugins 页面或 CLI):
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
dsh plugin --profile web add dsh-remote-window
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
pnpm 可能拦截 `ssh2` 的可选 native 构建脚本;用 Plugins 页面安装,或首次运行加
|
|
145
|
+
`--config.strict-dep-builds=false`。已安装但未启用时,在 Plugins 页面启用(或把
|
|
146
|
+
`dsh-remote-window` 加进 `~/.dsh/profiles/web/package.json` 的 `dsh.profile.bundles`)。重启服务
|
|
147
|
+
`dsh web` 的进程并刷新页面。
|
|
148
|
+
|
|
149
|
+
从 checkout 构建安装:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm install && pnpm typecheck && pnpm test && pnpm build
|
|
153
|
+
pnpm pack --pack-destination ~/.dsh/tarballs
|
|
154
|
+
dsh plugin --profile web add ~/.dsh/tarballs/dsh-remote-window-<version>.tgz
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
client 包内置 server 插件 tarball,因此目标机不需要网络或包管理器。然后:
|
|
158
|
+
|
|
159
|
+
1. 打开浮动 **target rail**(hover 或按 `Alt+R`),点 **新建连接**。
|
|
160
|
+
2. 选择 `SSH`,填目标信息,首次连接确认 host key 指纹。
|
|
161
|
+
3. 选择模型来源:**local**(默认)或 **server**(使用 server profile 自己的模型配置)。选 **local**
|
|
162
|
+
时,**高级选项** 会列出本机 DSH 的全部 provider/model(默认全选);取消勾选即可不暴露某些模型,
|
|
163
|
+
点刷新重新发现,保存的选择决定 server profile 能切换到哪些模型。
|
|
164
|
+
4. 查看探测结果。client 检查 server 的 Node/DSH 运行时与 server 插件,决定后续步骤是部署、更新还是
|
|
165
|
+
跳过;随后启动 profile server,选 local 模型时打开 SSH 反向隧道,并完成握手鉴权。失败的尝试不会
|
|
166
|
+
保存。
|
|
167
|
+
5. 成功后该 target 自动激活,server 官方 DSH UI 在 iframe 中全屏打开。用 server 自己的 sidebar
|
|
168
|
+
建工作区、开会话;切换 target、重载 iframe、忘记连接都在 rail 上完成。
|
|
169
|
+
|
|
170
|
+
## Model source and session ownership
|
|
171
|
+
|
|
172
|
+
- 模型来源是连接设置,不是会话内热切换:更改它要按 D20 cancel → `whenIdle`/flush 后重启 profile。
|
|
173
|
+
对话、会话 id 与日志不变。
|
|
174
|
+
- local 模型来源下,本机每个 provider 在 profile 里各成一条 `remote-<provider>` route,全部由同一条
|
|
175
|
+
sshd 反向隧道与同一个 client bridge 服务。断开或异常退出会关闭 bridge、隧道、profile 及其进程,不
|
|
176
|
+
留下孤儿端口;重连会复用已保存的 `DSH_HOME`,profile patch 与隧道留在同一目录。
|
|
177
|
+
- **D20:** 如果 profile 还有 active turn,重启/切换对话框会列出它们并要求显式确认后才一起取消,绝不
|
|
178
|
+
静默打断会话。
|
|
179
|
+
- 同一会话只能有一个写者。iframe 里的官方 UI 打开/resume 会话并持有官方 JSONL 锁;第二个实例打开
|
|
180
|
+
同一会话会得到 `session/writer-held`。
|
|
181
|
+
- 断开连接会停止 per-client profile server、关闭 portal/隧道并取消该 profile 的 turn;server 日志、
|
|
182
|
+
工作区与共享视图状态不受影响。
|
|
183
|
+
|
|
184
|
+
## Compatibility
|
|
185
|
+
|
|
186
|
+
当前只对这一个运行时做过验证:
|
|
187
|
+
|
|
188
|
+
| 插件 | 已验证 DSH 运行时 | 状态 |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| 0.1.0-alpha.3 | 0.2.1-rc.3 | verified |
|
|
191
|
+
<!-- verified-runtimes -->
|
|
192
|
+
|
|
193
|
+
本版本针对 DSH `0.2.1-rc.3` 构建、打包并测试;`0.2.1-rc.2` 已不在已验证集合内,默认会被精确版本
|
|
194
|
+
守卫拒绝,除非显式豁免该组合。检查项、剩余范围与升级步骤见
|
|
195
|
+
[doc/dsh-rc3-compatibility.md](doc/dsh-rc3-compatibility.md)。
|
|
196
|
+
|
|
197
|
+
声明是**精确版本**,不是区间:清单把 `engines.dsh` 与全部 `@deepseek-ai/dsh-*` peer 钉在上面的运行时,
|
|
198
|
+
客户端也只在 server 版本属于已验证运行时时才接受它。DSH 还是开发者预览版;未验证版本会被一条可操作的
|
|
199
|
+
错误拒绝,而不是跑到一半失败。
|
|
200
|
+
|
|
201
|
+
如果确实要在本机试别的 DSH 版本,对这一对精确版本做豁免,并把结果视为不受支持:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
dsh plugin --profile web allow-version dsh-remote-window@<已安装版本> \
|
|
205
|
+
--dsh-version <运行时版本> --accept-risk
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
豁免只覆盖本机宿主;server 必须运行已验证运行时——client 安装的独立 runtime,或复用该精确版本的全局 DSH。
|
|
209
|
+
|
|
210
|
+
独立 runtime 会优先复用 `~/.dsh/remote-assets/` 中该平台/版本的缓存包;缓存没有时,client 用本机 pnpm
|
|
211
|
+
按同一 `node/` + `dsh/` 布局构建并上传,因此升级 DSH 后首次连接可能慢一些,之后直接复用。构建只写入
|
|
212
|
+
专用 profile 的独立 runtime,不改 server 的全局 DSH 或其它 profile。
|
|
213
|
+
|
|
214
|
+
## Data and security
|
|
215
|
+
|
|
216
|
+
| 位置 | 内容 |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| client `~/.dsh/storages/dsh_remote_window.json` | 连接记录(含模型来源)、本机模型 endpoint/模型/超时、包策略、旧 workspace refs(会清理) |
|
|
219
|
+
| client `~/.dsh/.credentials.yaml` | SSH 凭据与本机模型 API key(官方存储,`0600`) |
|
|
220
|
+
| client `~/.dsh/remote-windows/<clientId>/…` | v2 local 会话的私有 anchor(保留,非权威,不再展示) |
|
|
221
|
+
| client `~/.dsh/remote-assets/` | 本机 runtime/包缓存 |
|
|
222
|
+
| server `~/.dsh/sessions/**` | 权威会话日志(两种模型来源共用) |
|
|
223
|
+
| server `~/.dsh/storages/workspace.json` | 权威 workspace registry、pin/archive/顺序 |
|
|
224
|
+
| server `~/.dsh/profiles/graydovee-remote-window-<clientId>/` | 专用 profile、server 插件、lease、日志 |
|
|
225
|
+
| server `~/.dsh/remote-window/<clientId>/runtime/` | 独立 Node/DSH runtime |
|
|
226
|
+
| server `~/.dsh/remote-server-sessions/**`(旧) | 保留、不展示、不迁移 |
|
|
227
|
+
|
|
228
|
+
本机模型 API key 不会到 server 或浏览器:它只存 client 官方凭据库,只由 loopback bridge 读取并注入
|
|
229
|
+
转发的请求;server profile 只看到占位 `DSH_RW_LOCAL_LLM_KEY`。SSH 凭据也只在 client。profile server
|
|
230
|
+
的启动 token 与官方 cookie 只在 client host 内存,浏览器永远拿不到;portal 剥掉 client cookie,且不
|
|
231
|
+
转发 `Set-Cookie`。每个插件 HTTP 路由都做同源/可信主机校验并限制 body 大小;错误、日志与流式明细经过
|
|
232
|
+
脱敏。凭据文件本身由 DSH 凭据存储写入。
|
|
233
|
+
|
|
234
|
+
## Known limitations
|
|
235
|
+
|
|
236
|
+
- **D19:多 client 是刷新/重连后一致,不是实时推送。** 两个 client 共享 server 的 workspace/session/
|
|
237
|
+
视图状态,但另一个 client(或 server 全局 DSH)的改动要在刷新/重连后可见;server 全局 DSH 独立可用,
|
|
238
|
+
client 的改动可能需要重启全局 DSH 才显示。
|
|
239
|
+
- **v1 全局 runtime 只复用、不修改。** client 会检测并只读复用已验证的全局 DSH;安装/升级共享全局
|
|
240
|
+
runtime 或其它 profile 的插件会被拒绝并给出可操作错误;只有专用 per-client profile 与独立 runtime
|
|
241
|
+
会自动安装。
|
|
242
|
+
- **不删除会话。** 官方 DSH 没有该能力,本插件也不新增。
|
|
243
|
+
- **local 模型来源是默认。** 新连接默认走 client bridge(新 server 通常没配模型 key);默认子模式自动
|
|
244
|
+
代理本机 DSH 的全部模型目录,不需要手填。配置 server 凭据后再显式选 server 模型来源;更改来源需要
|
|
245
|
+
重连。
|
|
246
|
+
- **v2 executor(远端执行世界)不再被主区使用。** iframe 始终驱动 server agent。记录过 v2 `local`
|
|
247
|
+
executor 的旧会话留在数据里,但新 UI 不再进入;详见
|
|
248
|
+
[doc/refactor-v3-full-ui.md](doc/refactor-v3-full-ui.md)。
|
|
249
|
+
- **每个 client 只挂一个 iframe。** 打开第二个远程 target 会替换 frame(前一个 target 断开或在 rail
|
|
250
|
+
中保持非激活)。没有两个 server 的分屏。
|
|
251
|
+
- **旧的手动本机 endpoint 只支持 HTTP(`/v1/*`)。** 它仅为旧设置保留、对话框不再提供,TLS 从未
|
|
252
|
+
实现。自动代理模式没有这个问题(走本机 DSH 自己的 provider 客户端)。
|
|
253
|
+
- **sshd 必须允许反向转发。** 若 `AllowTcpForwarding`/`GatewayPorts` 策略拒绝 `forwardIn`,连接会以
|
|
254
|
+
可操作错误失败,绝不静默改用 server 模型。
|
|
255
|
+
- **重启 profile 会取消它的所有 turn。** 普通刷新只重读数据;加载 workspace registry 变更用进程内官方
|
|
256
|
+
service reload;完整重启 profile 是 fallback,且必须经过 D20 确认。
|
|
257
|
+
- **target rail 是 client 插件 UI。** 需要 client 的 `web` profile;desktop/headless profile 不是本界面支持的 target。
|
|
258
|
+
- **server 世界是 POSIX。** SSH 目标需提供 POSIX shell;v1 已退役 WSL/Docker 连接方式(Docker 只用于 QA)。
|
|
259
|
+
|
|
260
|
+
## Troubleshooting
|
|
261
|
+
|
|
262
|
+
| 症状 | 检查 |
|
|
263
|
+
| --- | --- |
|
|
264
|
+
| `incompatible-version` | 本机装 DSH `0.2.1-rc.3`,或对该精确版本做豁免(见 Compatibility)。 |
|
|
265
|
+
| 没有 target rail | 插件必须已安装并在 `web` 启用;重启 `dsh web` 并刷新。hover rail 或按 `Alt+R` 展开。 |
|
|
266
|
+
| `server-plugin-missing` / `server-plugin-version-mismatch` | client 包内置的 server 插件 tarball 丢失/版本不匹配;重装 client 插件后重连。 |
|
|
267
|
+
| `server-plugin-integrity-failed` | 内置/下载的 server 插件 sha256 校验失败;重装 client 包。 |
|
|
268
|
+
| `profile-in-use` / `profile-foreign-client` | 另一个 client 实例在驱动该 profile,或 clientId 被复制。用持有实例重试,或删除本机连接记录后重连。 |
|
|
269
|
+
| `registry-stale` | 另一个进程改了 server workspace registry。点远程段刷新;仍失败则按 D20 确认后重连(重启 profile)。 |
|
|
270
|
+
| `session/writer-held` | 该会话已被另一个实例驱动。等它结束,或刷新/重连;日志不会被写坏。 |
|
|
271
|
+
| `runtime-global-unsupported` / `plugin-profile-not-managed` | v1 不修改共享全局 runtime/其它 profile;使用专用 profile/独立 runtime。 |
|
|
272
|
+
| `local-llm-*`(如 `local-llm-providers-unavailable`、`local-llm-no-models`、`local-llm-unreachable`、`local-llm-tunnel-failed`) | 先确认本机 DSH 配了可用模型;在高级选项点「刷新」重读目录;再查 server sshd 的反向转发策略。修好后重试,或改选 server 模型来源。 |
|
|
273
|
+
| 连接对话框看起来卡住 | 对话框有逐步状态、`已用 Ns` 计时和等待提示;单个长步骤(例如隔离 runtime 部署 Node)会一直显示「进行中」,直到该步结束才更新文案。若 host 不可达会出现轮询告警,而不是静默卡住。 |
|
|
274
|
+
| `mode-switch/cancel-failed` / `shutdown-flush-timeout` | 重启前有 turn 取消失败/未完成落盘;UI 会列出,等它们收尾后重试。 |
|
|
275
|
+
| SSH 在部署前失败 | 检查账号、密钥权限、agent 与指纹;`ssh -v <host>` 是好用的下一步。 |
|
|
276
|
+
| 命令提示没有可用的沙箱 backend | 目标机没有隔离 backend;这是文档化的 partial enforcement 路径。 |
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
[AGENTS.md](AGENTS.md) 是开发契约(v3 边界、逐字节共享文件、存储与安全不变量、发布流程)。当前主区与
|
|
281
|
+
模型来源契约见 [doc/refactor-v3-full-ui.md](doc/refactor-v3-full-ui.md);协议仍见
|
|
282
|
+
[doc/refactor-v1-architecture.md](doc/refactor-v1-architecture.md) 与
|
|
283
|
+
[doc/refactor-v1-protocol.md](doc/refactor-v1-protocol.md)。server 半边在
|
|
284
|
+
[dsh-remote-window-server](https://github.com/graydovee/dsh-remote-window-server)。
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,3 +1,332 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-remote-window
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README-zh.md)
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](#compatibility)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-remote-window)
|
|
9
|
+
|
|
10
|
+
Open an SSH host as a DeepSeek Harness workspace. **The server is an ordinary DSH that can work on its
|
|
11
|
+
own; this plugin makes it your remote entry.** Workspaces, sessions, session logs and shared view state
|
|
12
|
+
stay authoritative on the server. The client adds a floating target rail and shows the server's
|
|
13
|
+
**complete official DSH UI in a full-screen same-origin iframe** — sidebar, header, conversation, right
|
|
14
|
+
sidebar and third-party UI plugins are exactly what the server runs. The client no longer redraws the
|
|
15
|
+
conversation.
|
|
16
|
+
|
|
17
|
+
The connection chooses where the agent's **model requests** are served:
|
|
18
|
+
|
|
19
|
+
- **Local model** (default): the agent, session log and tools stay on the server, but every model request
|
|
20
|
+
is forwarded over an SSH reverse tunnel to your machine. By default the client **auto-proxies every
|
|
21
|
+
provider and model your local DSH already has** (`ctx.llm` catalog) — no endpoint or API key to enter;
|
|
22
|
+
each provider becomes its own route in the server profile's model picker. Under **advanced options**
|
|
23
|
+
the whole discovered catalog is listed with checkboxes (all selected by default); the choice is saved
|
|
24
|
+
and decides which routes the profile gets, so the server session can freely switch between the models
|
|
25
|
+
you allow. Fresh servers usually have no model key, so this is the default; an unreadable catalog
|
|
26
|
+
blocks the connection instead of silently falling back to server models.
|
|
27
|
+
- **Server model** (optional): the server profile uses its own model configuration. Choose this
|
|
28
|
+
explicitly when the server already has model credentials.
|
|
29
|
+
|
|
30
|
+
Both sources write the same server-side session log; switching the model source requires a reconnect, not
|
|
31
|
+
a new session.
|
|
32
|
+
|
|
33
|
+
## Screenshots
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
The client keeps exactly one remote surface: a floating **target rail** (collapsed by default, hover or
|
|
38
|
+
`Alt+R` to expand) listing saved connections, their live phase and the connect/switch/forget actions. Drag
|
|
39
|
+
the rail to any screen edge and it snaps there: collapsed it follows the edge (a compact column on
|
|
40
|
+
the left/right, a strip of glyph chips on the top/bottom), and expanding it always opens the same
|
|
41
|
+
vertical 280px list, limited by the viewport height and scrolling inside. The dock is remembered
|
|
42
|
+
across reloads. With more than one connection a small fold button at the end of the strip (below the
|
|
43
|
+
column, or at the right end of the bar, chevron pointing the way it folds) collapses the rail into a
|
|
44
|
+
single `+N` badge (N = number of connections); clicking the badge expands the rail again in one step.
|
|
45
|
+
expands on hover, so pointing at it never reflows the strip. Clicking the badge restores the strip and
|
|
46
|
+
its hover-to-expand behaviour, and hover does not expand the badge itself. Once connected, the server's own
|
|
47
|
+
sidebar and conversation are the navigation; the client shell sidebar hides while the iframe is mounted.
|
|
48
|
+
|
|
49
|
+
## Why this exists
|
|
50
|
+
|
|
51
|
+
Harness works on the machine that runs it. When the work has to happen somewhere else — a build box, a
|
|
52
|
+
GPU host, a staging server — there are three options:
|
|
53
|
+
|
|
54
|
+
1. **Copy the files back.** A mirror drifts, conflicts, and quietly breaks the moment two people touch
|
|
55
|
+
the same tree.
|
|
56
|
+
2. **Run everything on the remote.** Then the model credentials and the conversation live there too, and
|
|
57
|
+
another computer cannot join without copying a session directory.
|
|
58
|
+
3. **Keep the server as a normal DSH and make the client its remote entry.** That is this plugin: one
|
|
59
|
+
per-client profile server runs `dsh web` on the target, the client reaches it over SSH, and the
|
|
60
|
+
server's official data stays authoritative.
|
|
61
|
+
|
|
62
|
+
Three invariants follow:
|
|
63
|
+
|
|
64
|
+
- **Credentials never cross the line.** With the server model source the profile uses the server's own
|
|
65
|
+
credentials; with the local model source the key stays in the client's credential store and only the
|
|
66
|
+
bridge injects it. SSH secrets stay on the client. Errors, logs and streamed details are scrubbed.
|
|
67
|
+
- **The target listens on nothing public.** The profile server binds `127.0.0.1` only and is reached
|
|
68
|
+
through an SSH `forwardOut` tunnel.
|
|
69
|
+
- **One profile per client, one writer per session.** The server plugin holds a client lease; the
|
|
70
|
+
official JSONL flock arbitrates session writes across processes, and a second writer gets an explicit
|
|
71
|
+
`session/writer-held` error instead of silently corrupting the log.
|
|
72
|
+
|
|
73
|
+
If you only need "edit files on one server", the official SSH profile (`dsh-ssh` + `fs-ssh` +
|
|
74
|
+
`subprocess-ssh` + `sandbox-ssh`) is smaller and needs no plugin.
|
|
75
|
+
|
|
76
|
+
## How it compares
|
|
77
|
+
|
|
78
|
+
| | Official SSH profile | File-routing plugins | This plugin |
|
|
79
|
+
| --- | --- | --- | --- |
|
|
80
|
+
| Where tools execute | remote | remote | remote |
|
|
81
|
+
| Several targets in one UI | one profile per host | yes | yes |
|
|
82
|
+
| Session log authority | local | local | **server** |
|
|
83
|
+
| Workspace / pin / archive / order | local | local | **server (shared by clients)** |
|
|
84
|
+
| Model source per connection | fixed local | fixed local | **local bridge or server** |
|
|
85
|
+
| Continuation from another computer | no | no | **yes** |
|
|
86
|
+
|
|
87
|
+
## Architecture
|
|
88
|
+
|
|
89
|
+
```mermaid
|
|
90
|
+
flowchart LR
|
|
91
|
+
subgraph client [Your computer]
|
|
92
|
+
UI[Client Web UI<br/>target rail · full-screen iframe]
|
|
93
|
+
CHOST[Client DSH host<br/>portal · P1 client · profile lifecycle]
|
|
94
|
+
LOCAL[Local model bridge<br/>127.0.0.1 ephemeral]
|
|
95
|
+
LLM[(your OpenAI-compatible LLM)]
|
|
96
|
+
end
|
|
97
|
+
subgraph server [SSH target]
|
|
98
|
+
PROF[per-client profile server<br/>official DSH UI · agent]
|
|
99
|
+
PLUG[server plugin<br/>P1 RPC · SSE · mux · embed]
|
|
100
|
+
OFF[official DSH services<br/>workspaceRegistry · sessionPersistence · sessionController]
|
|
101
|
+
LOG[(server ~/.dsh/sessions<br/>workspace registry · view state)]
|
|
102
|
+
end
|
|
103
|
+
UI -->|same-origin iframe| CHOST
|
|
104
|
+
CHOST -->|portal byte proxy<br/>forwardOut| PROF
|
|
105
|
+
CHOST -->|P1 HTTP/SSE/WS| PLUG
|
|
106
|
+
PROF -->|model request via<br/>reverse tunnel + bridge| LOCAL
|
|
107
|
+
LOCAL --> LLM
|
|
108
|
+
PLUG --> OFF --> LOG
|
|
109
|
+
PROF --> OFF
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The client host owns SSH, host-key pinning, runtime/plugin deployment, the per-client profile lifecycle,
|
|
113
|
+
the P1 client, the same-origin portal and the local-model bridge/tunnel. The server plugin owns the
|
|
114
|
+
handshake and lease, workspace and session APIs backed by official services, the registry write guard,
|
|
115
|
+
the execution-world WebSocket mux and the embed bootstrap/CSS. `dshRwFull=1` makes the embed leave the
|
|
116
|
+
official sidebar/rightbar/bottom in place, so the iframe is the real server UI. The detailed contracts are
|
|
117
|
+
in [doc/refactor-v3-full-ui.md](doc/refactor-v3-full-ui.md),
|
|
118
|
+
[doc/refactor-v1-architecture.md](doc/refactor-v1-architecture.md) and
|
|
119
|
+
[doc/refactor-v1-protocol.md](doc/refactor-v1-protocol.md).
|
|
120
|
+
|
|
121
|
+
## Feature breakdown
|
|
122
|
+
|
|
123
|
+
**Full-screen official UI.** The client main slot hosts one same-origin iframe of the profile server's
|
|
124
|
+
official `dsh web` UI, requested with `dshRwFull=1`. The portal strips the client GUI cookie, injects the
|
|
125
|
+
profile's own cookie, never forwards `Set-Cookie`, and keeps the profile token out of the iframe URL. The
|
|
126
|
+
iframe uses a minimal sandbox (`allow-scripts allow-same-origin allow-forms allow-popups allow-downloads`).
|
|
127
|
+
The client shell sidebar hides while the frame is mounted; the target rail stays available.
|
|
128
|
+
|
|
129
|
+
**Local model source.** The server profile's `cordis.patch.yml` declares official `llm-pi-ai` routes
|
|
130
|
+
that point at `http://127.0.0.1:<random>/<provider>/v1`. That port is an SSH `forwardIn` reverse
|
|
131
|
+
tunnel, terminated by a loopback-only bridge on the client. The default **auto proxy** enumerates the
|
|
132
|
+
local DSH's own `ctx.llm` providers/models (`listProviders`/`listModels`) and answers every model call
|
|
133
|
+
from `ctx.llm.stream()` with the harness' native types, so the local DSH's own credentials and protocols
|
|
134
|
+
keep working and nothing is re-entered. The client's **advanced options** persist which providers/models
|
|
135
|
+
are proxied (`localSelection`): unset means “all”, a filtered set trims the generated routes, and an
|
|
136
|
+
empty result is rejected. (The legacy manual endpoint path still exists for old saved settings and tests,
|
|
137
|
+
but the UI no longer offers it; the profile always sees a placeholder key.) An unreadable catalog, an
|
|
138
|
+
empty model list or an sshd that refuses forwarding is a coded, actionable failure — never a silent
|
|
139
|
+
fallback to server models.
|
|
140
|
+
|
|
141
|
+
**Server-authoritative data.** Workspace list/title/order/pin/archive come from the server's official
|
|
142
|
+
`workspaceRegistry`; session list/title/blank/running come from `sessionController` plus a plugin-side
|
|
143
|
+
JSONL title fold. Rename, fork, pin, archive and drag reorder call the official services. Search is a
|
|
144
|
+
client-only, in-memory filter.
|
|
145
|
+
|
|
146
|
+
**Per-client profile and runtime.** Each client generates a stable `clientId` and uses
|
|
147
|
+
`graydovee-remote-window-<clientId>` on the server. The dedicated profile and its server plugin are
|
|
148
|
+
installed offline from the client package's bundled `dsh-remote-window-server-<version>.tgz`; the
|
|
149
|
+
isolated Node + DSH runtime is installed/upgraded automatically (or a verified global DSH is reused
|
|
150
|
+
read-only). A profile lease rejects a second driver for the same profile.
|
|
151
|
+
|
|
152
|
+
**Host keys are pinned.** The first connection shows the host key's SHA-256 and waits for explicit
|
|
153
|
+
confirmation; a later key change stops the connection.
|
|
154
|
+
|
|
155
|
+
**Sandbox and permissions.** The server profile mounts the official sandbox provider and policy for the
|
|
156
|
+
agent running in the iframe; when no confinement backend exists it reports partial enforcement and runs
|
|
157
|
+
there explicitly instead of pretending. The v2 executor's remote execution world remains in code and data
|
|
158
|
+
for old sessions but is no longer reachable from the main area.
|
|
159
|
+
|
|
160
|
+
## Quick start
|
|
161
|
+
|
|
162
|
+
Install into the client `web` profile, from the **Plugins** page or the CLI:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
dsh plugin --profile web add dsh-remote-window
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
pnpm may refuse `ssh2`'s optional native build script; let the Plugins page install it or pass
|
|
169
|
+
`--config.strict-dep-builds=false` on the first run. If it is already installed but not enabled, enable it
|
|
170
|
+
on the Plugins page (or add `dsh-remote-window` to `dsh.profile.bundles` in
|
|
171
|
+
`~/.dsh/profiles/web/package.json`). Restart the process that serves `dsh web` and reload the page.
|
|
172
|
+
|
|
173
|
+
From a checkout:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pnpm install && pnpm typecheck && pnpm test && pnpm build
|
|
177
|
+
pnpm pack --pack-destination ~/.dsh/tarballs
|
|
178
|
+
dsh plugin --profile web add ~/.dsh/tarballs/dsh-remote-window-<version>.tgz
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The package ships the server plugin tarball it needs, so the target does not need network access or a
|
|
182
|
+
package manager. Then:
|
|
183
|
+
|
|
184
|
+
1. Open the floating **target rail** (hover it or press `Alt+R`) and choose **New connection**.
|
|
185
|
+
2. Choose `SSH`, fill in the target, and confirm the host-key fingerprint on first contact.
|
|
186
|
+
3. Choose the model source: **local** (default) or **server** (use the server profile's own model
|
|
187
|
+
configuration). For **local**, **advanced options** lists every provider/model your local DSH has,
|
|
188
|
+
all checked; untick what should not be exposed, refresh to re-discover, and the saved choice decides
|
|
189
|
+
which models the server profile can switch between.
|
|
190
|
+
4. Review the probe result. The client checks the server's Node/DSH runtime and server plugin, then decides
|
|
191
|
+
whether the next steps deploy, update, or skip; it starts the profile server, opens the SSH reverse
|
|
192
|
+
tunnel when the local model source is selected, and authenticates the handshake. A failed attempt is
|
|
193
|
+
not saved.
|
|
194
|
+
5. On success the target becomes active and the server's official DSH UI opens full-screen in the frame.
|
|
195
|
+
Use the server sidebar to create workspaces and sessions. Switching targets, reloading the frame and
|
|
196
|
+
forgetting a connection all happen from the rail.
|
|
197
|
+
|
|
198
|
+
## Model source and session ownership
|
|
199
|
+
|
|
200
|
+
- The model source is a connection setting, not a per-session switch: changing it restarts the profile
|
|
201
|
+
(D20) after cancel → `whenIdle`/flush. The conversation, session id and log stay the same.
|
|
202
|
+
- With the local model source each discovered provider becomes a `remote-<provider>` route in the
|
|
203
|
+
profile, all served by one sshd reverse tunnel and one client bridge. Stopping or losing the
|
|
204
|
+
connection closes the bridge, the tunnel, the profile and its process; no orphan port is left behind.
|
|
205
|
+
Reconnecting reuses the saved `DSH_HOME`, so the profile patch and the tunnel stay in the same place.
|
|
206
|
+
- **D20:** if the profile still has active turns, a restart/switch lists them and requires explicit
|
|
207
|
+
confirmation before canceling them. It never silently interrupts sessions.
|
|
208
|
+
- A session has only one writer. The official UI in the iframe opens/resumes the session and holds the
|
|
209
|
+
official JSONL lock; opening the same session from a second instance surfaces `session/writer-held`.
|
|
210
|
+
- Disconnecting stops the per-client profile server, closes the portal/tunnel and cancels the profile's
|
|
211
|
+
turns; server-side logs, workspaces and shared view state are untouched.
|
|
212
|
+
|
|
213
|
+
## Compatibility
|
|
214
|
+
|
|
215
|
+
Verified against exactly this runtime:
|
|
216
|
+
|
|
217
|
+
| Plugin | Verified DSH runtime | Status |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| 0.1.0-alpha.3 | 0.2.1-rc.3 | verified |
|
|
220
|
+
<!-- verified-runtimes -->
|
|
221
|
+
|
|
222
|
+
This release is built, packed and tested against DSH `0.2.1-rc.3`. `0.2.1-rc.2` is no longer in the
|
|
223
|
+
verified set, so the exact-version guard refuses it unless you explicitly exempt that pair. See
|
|
224
|
+
[doc/dsh-rc3-compatibility.md](doc/dsh-rc3-compatibility.md) for what was checked, what remains, and the
|
|
225
|
+
upgrade procedure.
|
|
226
|
+
|
|
227
|
+
The declaration is a **precise version**, not a range: the manifest pins `engines.dsh` and every
|
|
228
|
+
`@deepseek-ai/dsh-*` peer to the runtime above, and the client accepts the server only when its version is
|
|
229
|
+
one of the verified runtimes. DSH is a developer preview; an unverified release is refused with an
|
|
230
|
+
actionable message instead of failing halfway.
|
|
231
|
+
|
|
232
|
+
If you intentionally run another DSH version locally, exempt the exact pair and treat the result as
|
|
233
|
+
unsupported:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
dsh plugin --profile web allow-version dsh-remote-window@<installed version> \
|
|
237
|
+
--dsh-version <runtime version> --accept-risk
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
That exemption covers the local host only. The server side must run the verified runtime — either the
|
|
241
|
+
isolated runtime the client installs, or a global DSH reusing that exact verified version.
|
|
242
|
+
|
|
243
|
+
The isolated runtime reuses a platform/version cache under `~/.dsh/remote-assets/` first. When the cache is
|
|
244
|
+
missing, the client builds the same `node/` + `dsh/` layout with the host pnpm and uploads it, so the first
|
|
245
|
+
connection after a DSH upgrade can be slower; later connections reuse it. The build only populates the
|
|
246
|
+
dedicated profile's isolated runtime and never modifies the server's global DSH or another profile.
|
|
247
|
+
|
|
248
|
+
## Data and security
|
|
249
|
+
|
|
250
|
+
| Location | Contents |
|
|
251
|
+
| --- | --- |
|
|
252
|
+
| Client `~/.dsh/storages/dsh_remote_window.json` | Connection records (incl. model source), local-model endpoint/model/timeout, package policy, legacy workspace refs (cleaned up) |
|
|
253
|
+
| Client `~/.dsh/.credentials.yaml` | SSH credentials and the local-model API key (official store, mode `0600`) |
|
|
254
|
+
| Client `~/.dsh/remote-windows/<clientId>/…` | Private anchors for local-mode sessions (non-authoritative) |
|
|
255
|
+
| Client `~/.dsh/remote-assets/` | Local runtime/package cache |
|
|
256
|
+
| Server `~/.dsh/sessions/**` | Authoritative session logs (both model sources) |
|
|
257
|
+
| Server `~/.dsh/storages/workspace.json` | Authoritative workspace registry, pin/archive/order |
|
|
258
|
+
| Server `~/.dsh/profiles/graydovee-remote-window-<clientId>/` | Dedicated profile, server plugin, lease, logs |
|
|
259
|
+
| Server `~/.dsh/remote-window/<clientId>/runtime/` | Isolated Node/DSH runtime |
|
|
260
|
+
| Server `~/.dsh/remote-server-sessions/**` (legacy) | Retained but not shown and not migrated |
|
|
261
|
+
|
|
262
|
+
The local-model API key never reaches the server or the browser: it stays in the client's official
|
|
263
|
+
credential store, is read only by the loopback bridge, and is injected into the forwarded request. The
|
|
264
|
+
server profile only ever sees the placeholder `DSH_RW_LOCAL_LLM_KEY`. SSH credentials stay on the client
|
|
265
|
+
too. The profile server's launch token and official cookie live only in the client host's memory — the
|
|
266
|
+
browser never receives them, and the portal strips the client cookie while refusing to forward
|
|
267
|
+
`Set-Cookie`. Every plugin HTTP route is same-origin/trusted-host checked with a body size limit; errors,
|
|
268
|
+
logs and streamed details pass through a scrubber. The credential file itself is written by DSH's
|
|
269
|
+
credential store.
|
|
270
|
+
|
|
271
|
+
## Known limitations
|
|
272
|
+
|
|
273
|
+
- **D19: multi-client consistency is refresh/reconnect consistency, not real-time push.** Two clients
|
|
274
|
+
share the server's workspaces/sessions/view state, but a change made by another client (or by the
|
|
275
|
+
server's own global DSH) becomes visible after refresh/reconnect. The server's global DSH keeps working
|
|
276
|
+
independently; changes made by clients may need a restart of that global DSH to show up there.
|
|
277
|
+
- **Global runtime is reuse-only in v1.** The client detects and reuses a verified global DSH, but
|
|
278
|
+
installing/upgrading the shared global runtime or another profile's plugin is refused with an
|
|
279
|
+
actionable error; only the dedicated per-client profile and its isolated runtime are installed
|
|
280
|
+
automatically.
|
|
281
|
+
- **No session deletion.** The official DSH has no such action, so this plugin does not add one.
|
|
282
|
+
- **Local model source is the default.** A new connection starts with the client bridge because a fresh
|
|
283
|
+
server usually has no model key; the default sub-mode auto-proxies the local DSH's whole catalogue, so
|
|
284
|
+
there is nothing to type. Choose the server model source explicitly after configuring server
|
|
285
|
+
credentials; changing it requires a reconnect.
|
|
286
|
+
- **The remote execution world (v2 executor) is no longer used by the main area.** The iframe always
|
|
287
|
+
drives the server agent. Old sessions that recorded the v2 `local` executor are kept in data but are not
|
|
288
|
+
reachable from the new UI; this is documented in [doc/refactor-v3-full-ui.md](doc/refactor-v3-full-ui.md).
|
|
289
|
+
- **One iframe per client.** Opening a second remote target swaps the frame (the previous target is
|
|
290
|
+
disconnected or left in the rail as inactive). There is no split view of two servers.
|
|
291
|
+
- **Legacy manual local endpoint is HTTP-only (`/v1/*`).** It is kept for old saved settings only and is
|
|
292
|
+
no longer part of the dialog; TLS to it was never implemented.
|
|
293
|
+
- **sshd must allow reverse forwarding.** If `AllowTcpForwarding`/`GatewayPorts` policy refuses
|
|
294
|
+
`forwardIn`, the connection fails with an actionable error; it never silently uses the server model.
|
|
295
|
+
- **Restarting a profile cancels all of its turns.** Refresh normally only re-reads data;
|
|
296
|
+
loading a changed workspace registry uses the in-process official-service reload; a full profile
|
|
297
|
+
restart is the fallback and requires the D20 confirmation.
|
|
298
|
+
- **The target rail is client-plugin UI.** It needs the `web` profile of the client; the
|
|
299
|
+
desktop/headless profiles are not supported targets for the UI.
|
|
300
|
+
- **POSIX server world.** SSH targets must provide a POSIX shell; WSL/Docker connection methods are retired
|
|
301
|
+
in v1 (Docker is used only by QA).
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
| Symptom | What to check |
|
|
306
|
+
| --- | --- |
|
|
307
|
+
| `incompatible-version` | Install DSH `0.2.1-rc.3` locally, or exempt that exact pair (see Compatibility). |
|
|
308
|
+
| No target rail | The plugin must be installed and enabled in `web`; restart `dsh web` and reload. Hover the rail or press `Alt+R`. |
|
|
309
|
+
| `server-plugin-missing` / `server-plugin-version-mismatch` | The client package lost or mismatched its bundled server-plugin tarball; reinstall the client plugin and reconnect. |
|
|
310
|
+
| `server-plugin-integrity-failed` | The bundled/downloaded server plugin failed its sha256 check; reinstall the client package. |
|
|
311
|
+
| `profile-in-use` / `profile-foreign-client` | Another client instance drives this profile, or the clientId was copied. Retry with the owning instance, or remove this client's connection record and reconnect. |
|
|
312
|
+
| `registry-stale` | Another process changed the server workspace registry. Run the remote section's Refresh; if it still fails, reconnect (profile restart) after the D20 confirmation. |
|
|
313
|
+
| `session/writer-held` | The session is already being driven by another instance. Wait for it to finish, or refresh/reconnect; the log is not corrupted. |
|
|
314
|
+
| `runtime-global-unsupported` / `plugin-profile-not-managed` | v1 does not modify the shared global runtime or other profiles; use the dedicated profile/isolated runtime. |
|
|
315
|
+
| `local-llm-*` codes (e.g. `local-llm-providers-unavailable`, `local-llm-no-models`, `local-llm-unreachable`, `local-llm-tunnel-failed`) | Check that the local DSH has a usable model configured, re-run 刷新 in the advanced model list, then check the server sshd reverse-forwarding policy. Fix it and retry, or choose the server model source. |
|
|
316
|
+
| Connect dialog seems frozen | The dialog shows per-step status, an elapsed timer and a waiting hint; long single steps (e.g. isolated Node runtime deployment) stay 进行中 without changing text until they finish. If the host is unreachable, the poll warning appears instead of a silent stall. |
|
|
317
|
+
| `mode-switch/cancel-failed` / `shutdown-flush-timeout` | Some turns could not be canceled/flushed before a restart. The UI shows which ones; retry after they settle. |
|
|
318
|
+
| SSH fails before deployment | Check the account, key permissions, agent and fingerprint; `ssh -v <host>` is a good next step. |
|
|
319
|
+
| A command reports no usable sandbox backend | The target has no confinement backend; the partial-enforcement path is expected and visible. |
|
|
320
|
+
|
|
321
|
+
## Development
|
|
322
|
+
|
|
323
|
+
[AGENTS.md](AGENTS.md) holds the development contract (v3 boundaries, byte-identical files, storage and
|
|
324
|
+
security invariants, release procedure). The current main-area and model-source contract is
|
|
325
|
+
[doc/refactor-v3-full-ui.md](doc/refactor-v3-full-ui.md); wire contracts remain in
|
|
326
|
+
[doc/refactor-v1-architecture.md](doc/refactor-v1-architecture.md) and
|
|
327
|
+
[doc/refactor-v1-protocol.md](doc/refactor-v1-protocol.md). The server half lives in
|
|
328
|
+
[dsh-remote-window-server](https://github.com/graydovee/dsh-remote-window-server).
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
MIT
|