dsh-remote 0.8.74 → 0.8.75
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 +4 -0
- package/README.md +9 -5
- package/lib/index.js +11 -1
- package/lib/remote-shell.js +39 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -333,6 +333,7 @@ Deploying to a product profile is a separate, explicit action (`./sync.sh`) for
|
|
|
333
333
|
| `webAttachDshHome` | string | `''` | `DSH_HOME` exported on the remote when attaching; empty reuses the remote user's own harness home |
|
|
334
334
|
| `webAttachWaitSeconds` | int | `45` | how long to wait for a freshly started remote `dsh web` to print its launch token |
|
|
335
335
|
| `localTools` | string | `bootstrap` | which `rw_*` tools a LOCAL session (cwd outside every remote mirror) sees (issue #63): `bootstrap` keeps only the configuration loop `rw_machines`/`rw_connect`/`rw_pick_workspace` (the other 19 refuse a local session with "this session is LOCAL", so exposing them just wastes schema budget); `none` hides all `rw_*` (remote setup happens via the settings page only); `all` restores the legacy full surface. REMOTE sessions (cwd inside a mirror) always see everything; an invalid value degrades to `bootstrap` |
|
|
336
|
+
| `remoteTerminalDefault` | bool | `true` | make the shell menu's "remote ssh" launcher the terminal's DEFAULT shell when neither the host (`config.shell`) nor the user (a remembered menu choice) has picked one: a session rooted in a remote mirror opens a terminal already sshed into the remote workspace, any other session transparently falls back to a local shell (issue #55); `false` keeps it menu-only |
|
|
336
337
|
| `tui` | bool | `true` | master switch for the TUI (dsh-tui) layer; `false` registers no `/dsh-remote` page, workspace provider or status field |
|
|
337
338
|
| `tuiProviderListRecent` | bool | `true` | list each machine's recent remote projects in the TUI workspace picker |
|
|
338
339
|
| `tuiProviderRecentLimit` | int | `5` | recent remote projects listed per machine (1–20) |
|
|
@@ -348,6 +349,9 @@ Deploying to a product profile is a separate, explicit action (`./sync.sh`) for
|
|
|
348
349
|
**`@` lists remote files but the built-in read tool cannot open them** — the harness's own file tools see the **local mirror**, which stays empty until `rw_sync` downloads it. Read remote files with `rw_read_file` or the sidebar remote tab.
|
|
349
350
|
|
|
350
351
|
**A file link in a remote Session does not open** (issue #55) — fixed. When a link is clicked the harness turns it into `dsh-resource://file/session/<session>/<path>` and hands it to the built-in text preview, which reads the **local** filesystem; a remote Session's cwd is a **local mirror directory** (empty until `rw_sync`), so the open simply failed. The plugin now registers a higher-priority handler for that address and opens it over SFTP through the same editor as the sidebar "Remote Files" tab. The safety rule is deliberately conservative: it takes over only when the Session's cwd is **provably inside a mirror** and the mirror registry has loaded, so local Sessions — and the instant right after a page load — keep the built-in preview and no local file is ever mistaken for a remote one. The two cases that still cannot be mapped say so instead of showing a blank page: an address with no mirror mapping, and a Session whose workspace is not a remote mirror directory.
|
|
352
|
+
|
|
353
|
+
**The sidebar terminal in a remote Session is a LOCAL one** (issue #55, the terminal half) — solved in 0.8.75, no clicks needed. The shell menu has long carried the plugin's **remote ssh** entry: a cwd-aware launcher that ssh'es into the remote workspace when the session sits in a remote mirror and falls back to a local shell otherwise. As of 0.8.75 it also becomes the **default shell**: unless the host administrator configured a default (`config.shell`) or you picked a shell from the menu yourself, "open terminal" in a remote session lands on the remote host directly, while local sessions keep opening a local shell (the launcher routes by cwd). Two safeguards: a shell you picked manually from the menu always wins, and `remoteTerminalDefault: false` restores menu-only behaviour. For a heavier-weight answer there is still the "remote DSH UI" above — your browser opens the remote machine's OWN harness, whose terminals natively run on the far side. To just run one remote command, `rw_exec` (add `pty: true` for an interactive pty).
|
|
354
|
+
|
|
351
355
|
`.md`/`.markdown` links in a remote session take the same SFTP path and preview in the tab (issue #74). They used to be handed back to the built-in Markdown renderer (issue #64), which reads the local mirror and fails with `ENOENT` until `rw_sync`. The preview is now the remote bytes, rendered with the host Markdown component; builds without that component show the source. Local sessions still use the built-in preview. Images render inline in the sidebar remote tree for png/jpg/gif/webp/svg/bmp (issue #66; files over 2MB fall back to the download button).
|
|
352
356
|
|
|
353
357
|
**One SFTP timeout wedged every SFTP tool on that machine** (issue #65) — fixed (0.8.52+). A single operation hitting the 20s timeout used to poison the whole process for that machine (every later SFTP call paid its own 20s, while `rw_exec` stayed fine) until a disconnect/reconnect. Now a timeout kills the channel so later requests fail fast; the native backend tears the connection down and re-handshakes after two consecutive timeouts, and the openssh backend rebuilds its sftp child — a transient network blip no longer ends with "disconnect and reconnect". As a bonus, `rw_edit` now writes atomically (temp file + rename), so an interrupted write can no longer truncate the remote file to zero bytes, and the original permission bits are preserved.
|
package/README.md
CHANGED
|
@@ -308,6 +308,7 @@ scripts/dev-run.sh --status # 是否在运行
|
|
|
308
308
|
| `webAttachDshHome` | string | `''` | 远端启动时导出的 `DSH_HOME`;留空则复用远端用户自己的 harness home |
|
|
309
309
|
| `webAttachWaitSeconds` | int | `45` | 等待远端 `dsh web` 打印启动令牌的秒数 |
|
|
310
310
|
| `localTools` | string | `bootstrap` | 本地 session(cwd 不在远程镜像里)暴露哪些 `rw_*` 工具(issue #63):`bootstrap`=只暴露配置闭环 `rw_machines`/`rw_connect`/`rw_pick_workspace`(其余 19 个在本地 session 必然报 "this session is LOCAL",暴露只是白占 schema);`none`=完全不暴露 `rw_*`(远程配置只能走设置页);`all`=旧的 full 暴露。远程 session(cwd 在镜像里)不受影响,始终全量;非法值按 `bootstrap` 处理 |
|
|
311
|
+
| `remoteTerminalDefault` | bool | `true` | 让 shell 菜单里的「remote ssh」启动器成为终端**默认 shell**(宿主未配置、用户未手动选择时):远程会话开终端即落到远端工作目录,本地会话自动回落本地 shell(issue #55);`false` 则只保留菜单项 |
|
|
311
312
|
| `tui` | bool | `true` | TUI 适配层(dsh-tui)总开关;`false` 时不注册 `/dsh-remote` 页面、工作区 provider 与状态行字段 |
|
|
312
313
|
| `tuiProviderListRecent` | bool | `true` | TUI 工作区选择器里是否列出每台机器最近的远程项目 |
|
|
313
314
|
| `tuiProviderRecentLimit` | int | `5` | 每台机器列出的最近远程项目数(1–20) |
|
|
@@ -422,11 +423,14 @@ system prompt,提示模型编辑前用 `rw_read_file` 阅读嵌套层的指令
|
|
|
422
423
|
`jobs` 服务;没有该服务的组合(最小嵌入、测试替身)自动退化为只有前台执行——即 0.8.52 的行为,
|
|
423
424
|
不会因此加载失败。
|
|
424
425
|
|
|
425
|
-
**远程会话里的终端还是本机的**(issue #55)——
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
**远程会话里的终端还是本机的**(issue #55)—— 已解决(0.8.75+),什么都不用点。
|
|
427
|
+
侧栏终端的 shell 菜单里一直有插件贡献的 **remote ssh**:那是一个按 cwd 智能路由的启动器——
|
|
428
|
+
会话落在远程镜像里就 `ssh` 进远端工作目录,否则自动回落成本地 shell。0.8.75 起它进一步成为
|
|
429
|
+
**默认 shell**:只要宿主没另外配置默认终端、你也没在 shell 菜单里手动选过别的,点「打开终端」
|
|
430
|
+
在远程会话里就直接落进远端(本地会话照旧开本地终端,启动器会替你分流)。两个保险:菜单里手动
|
|
431
|
+
选过其他 shell 的话你的选择永远优先;想回到纯手动就在配置里设 `remoteTerminalDefault: false`
|
|
432
|
+
(remote ssh 仍会留在菜单里)。想要更重度的方案还有**「远程 DSH 界面」**(见上文):本机浏览器里
|
|
433
|
+
打开**远端机器自己**的 DSH,那里的终端原生跑在远端。只想偶尔跑一条远程命令用 `rw_exec`
|
|
430
434
|
(要交互式 PTY 就加 `pty: true`)。
|
|
431
435
|
|
|
432
436
|
**主机指纹变了** —— `/remote forget-key`(或设置页 → 机器 → 重新信任)。
|
package/lib/index.js
CHANGED
|
@@ -284,6 +284,13 @@ export const Config = z.object({
|
|
|
284
284
|
* plaintext is the one outcome you did not ask for.
|
|
285
285
|
* `plain`: the pre-0.8.54 fallback (write it and warn). */
|
|
286
286
|
credentialFallback: z.string().default('refuse'),
|
|
287
|
+
/** Make the "remote ssh" launcher the terminal's DEFAULT shell when neither
|
|
288
|
+
* the host admin (config.shell) nor the user (remembered menu choice) has
|
|
289
|
+
* picked one. The launcher is cwd-aware: a session rooted in a remote mirror
|
|
290
|
+
* gets a terminal already sshed into the remote workspace; any other session
|
|
291
|
+
* transparently falls back to a local shell. `false` keeps "remote ssh"
|
|
292
|
+
* menu-only. */
|
|
293
|
+
remoteTerminalDefault: z.boolean().default(true),
|
|
287
294
|
/** TUI integration layer (docs/tui.md): register the `/dsh-remote` scene,
|
|
288
295
|
* the workspace provider and the `/dsh-remote` slash command when a
|
|
289
296
|
* dsh-tui profile is present. `false` disables the whole layer — on a Web or
|
|
@@ -4802,7 +4809,10 @@ export async function apply(ctx, config) {
|
|
|
4802
4809
|
// service off it (as if it were the controller) is a silent no-op, because a
|
|
4803
4810
|
// context has no `config.shellCandidates`.
|
|
4804
4811
|
ctx.inject(['terminalController'], (inner) => {
|
|
4805
|
-
installTerminalShell(ctx, inner.get('terminalController'),
|
|
4812
|
+
installTerminalShell(ctx, inner.get('terminalController'), {
|
|
4813
|
+
...remoteShellSpec(),
|
|
4814
|
+
claimDefault: config.remoteTerminalDefault !== false,
|
|
4815
|
+
})
|
|
4806
4816
|
})
|
|
4807
4817
|
|
|
4808
4818
|
// ── auto-update: check on load + on an interval; apply silently ───────────
|
package/lib/remote-shell.js
CHANGED
|
@@ -401,6 +401,35 @@ function envValue(env, name) {
|
|
|
401
401
|
return match === undefined ? undefined : env[match]
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Claim the terminal's default shell slot when nobody else holds it.
|
|
406
|
+
*
|
|
407
|
+
* `config.shell` is what `discoverShells` puts FIRST and what a terminal spawn
|
|
408
|
+
* without an explicit shellPath resolves to — i.e. where a bare "open
|
|
409
|
+
* terminal" click lands. Pointing it at our launcher makes the default
|
|
410
|
+
* terminal SMART: the child script ssh'es into the remote workspace when the
|
|
411
|
+
* session cwd is a mirror and falls back to a local shell when it is not.
|
|
412
|
+
* A user-configured shell (config.shell already set) is never touched, and an
|
|
413
|
+
* explicit pick from the shell menu still wins (the client's remembered
|
|
414
|
+
* preference beats the first list entry).
|
|
415
|
+
*
|
|
416
|
+
* @param {object} controller - the terminalController service
|
|
417
|
+
* @param {string} launcherPath - absolute launcher path
|
|
418
|
+
* @returns {(() => void) | undefined} undo that only fires while the claim is still ours
|
|
419
|
+
*/
|
|
420
|
+
function claimDefaultShell(controller, launcherPath) {
|
|
421
|
+
const config = controller && controller.config
|
|
422
|
+
if (!config || typeof config !== 'object') return undefined
|
|
423
|
+
if (config.shell !== undefined) return undefined
|
|
424
|
+
const ours = { path: launcherPath, name: SHELL_OPTION_NAME, args: [] }
|
|
425
|
+
config.shell = ours
|
|
426
|
+
return () => {
|
|
427
|
+
// Someone else (host settings, another plugin) took the slot since:
|
|
428
|
+
// restoring `undefined` would clobber THEIR choice.
|
|
429
|
+
if (config.shell === ours) config.shell = undefined
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
404
433
|
/**
|
|
405
434
|
* Write the launcher and put it in the terminal's shell menu.
|
|
406
435
|
*
|
|
@@ -414,7 +443,9 @@ function envValue(env, name) {
|
|
|
414
443
|
* @param {object} controller - the resolved `terminalController` *service*
|
|
415
444
|
* @param {{ dir: string, childPath: string, machinesFile: string, platform?: string,
|
|
416
445
|
* nodePath?: string, electronPath?: string, electron?: boolean, logger?: object,
|
|
417
|
-
* env?: object, fs?: { existsSync: (p: string) => boolean }
|
|
446
|
+
* env?: object, fs?: { existsSync: (p: string) => boolean },
|
|
447
|
+
* claimDefault?: boolean }} spec - `claimDefault: false` keeps the
|
|
448
|
+
* launcher menu-only (never touches `config.shell`)
|
|
418
449
|
* @returns {boolean} whether the option is (or already was) in the menu
|
|
419
450
|
*/
|
|
420
451
|
export function installTerminalShell(ctx, controller, spec = {}) {
|
|
@@ -494,6 +525,11 @@ export function installTerminalShell(ctx, controller, spec = {}) {
|
|
|
494
525
|
// pairs cannot strand a live registration without PATH.
|
|
495
526
|
let restorePath
|
|
496
527
|
let candidate = launcher.path
|
|
528
|
+
// The default-shell claim uses the ABSOLUTE launcher path, never the bare
|
|
529
|
+
// menu name: it must keep resolving even while the PATH entry is gone.
|
|
530
|
+
const releaseDefault = spec.claimDefault === false
|
|
531
|
+
? undefined
|
|
532
|
+
: claimDefaultShell(controller, launcher.path)
|
|
497
533
|
if (needsPath) {
|
|
498
534
|
try {
|
|
499
535
|
restorePath = appendPathEntry(path.dirname(launcher.path), env)
|
|
@@ -524,10 +560,12 @@ export function installTerminalShell(ctx, controller, spec = {}) {
|
|
|
524
560
|
return () => {
|
|
525
561
|
if (undo) undo()
|
|
526
562
|
if (restorePath) restorePath()
|
|
563
|
+
if (releaseDefault) releaseDefault()
|
|
527
564
|
}
|
|
528
565
|
} catch (err) {
|
|
529
566
|
// The host advertises a shell list but will not take a write.
|
|
530
567
|
if (restorePath) restorePath()
|
|
568
|
+
if (releaseDefault) releaseDefault()
|
|
531
569
|
warn(`the terminal shell list rejected the remote shell (${reason(err)}); the remote ssh option is unavailable`)
|
|
532
570
|
return undefined
|
|
533
571
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.75",
|
|
4
4
|
"description": "Remote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive, multi-hop ProxyJump), list hosts from ~/.ssh/config, pick a remote workspace, operate on it with 21 rw_* tools, conflict-aware mirror sync, an embedded PTY terminal, remote DSH web UI attach, port forwarding, host grouping, remote sandbox fencing, audit log, keychain passwords.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "flymysql <flyphp@outlook.com>",
|