@hyzyn/dsh-tty 0.19.3 → 0.20.1

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 CHANGED
@@ -99,11 +99,14 @@ Both were reproduced on **Windows 11 ARM (24H2) + Node 22 ARM64**. The fix:
99
99
 
100
100
  ## Agent tools (P1)
101
101
 
102
- The plugin injects thirteen tools into the agent (with the same power as the bash tool; operations show up live in the user’s terminal):
102
+ The plugin injects sixteen tools into the agent (with the same power as the bash tool; operations show up live in the user’s terminal):
103
103
 
104
104
  | Tool | Purpose |
105
105
  | --- | --- |
106
- | `tty_list` | List active terminal sessions (sid / kind (`local\|ssh`) / target / pid / **cwd tracked live as you `cd`** / activity time; tmux persistent sessions carry a `persist` marker) |
106
+ | `tty_list` | List active terminal sessions (sid / kind (`local\|ssh`) / target / pid / **cwd tracked live as you `cd`** / activity time; tmux persistent sessions carry a `persist` marker; sessions the agent opened carry `owner: 'agent'`) |
107
+ | `tty_open` | **Open a terminal session yourself** (0.20.0): a local shell, or a long-running command via `command` (dev server / watch), with optional tmux persistence via `persistName`. The session **shows up in the user’s terminal panel** as an ordinary tab the user can see and take over — never a hidden session |
108
+ | `tty_close` | Close a session opened by `tty_open` (0.20.0). **Only the agent’s own sessions may be closed**: a tab the user opened is refused, so the agent never ends a terminal the user is working in |
109
+ | `tty_stats` | Read live host metrics for a session’s machine (0.20.0): CPU / memory / disk / TCP connections / network rates / temperature / uptime. Local sessions report the host; SSH sessions report that remote host over a separate non-PTY channel that never touches the terminal. Check it before deploying or load-testing |
107
110
  | `tty_capture` | Read recent output (last N lines, ANSI stripped by default, `raw:true` for the raw stream); **`last:true` returns only the output + exit code of the previous completed command** (shell integration markers, see the next section); when a command is **in flight** (just sent, completion marker not in yet) it returns `inProgress:true` without the stale result, so the previous command is never mistaken for this one (0.19.0) |
108
111
  | `tty_screen` | Read the **currently visible screen** as rendered (xterm-headless virtual screen, plain text) — it can genuinely read TUI interfaces such as vim / htop / menus |
109
112
  | `tty_expect` | Wait with a regex for a readiness signal in **subsequent output** (dev server URL, build finished, …); a timeout does not throw (`matched:false` + tail output), and a command that ends early also returns early with its exit code; at most 5 in-flight calls per session, and the accumulated window keeps only the last 64KB (0.19.0) |
@@ -117,10 +120,19 @@ The plugin injects thirteen tools into the agent (with the same power as the bas
117
120
  | `sftp_tree` | Recursively list a remote directory structure (depth-first, directories first; `maxDepth` 1~8 / `maxEntries` 1~2000 cap it, `truncated:true` when exceeded; symlinks are not followed, to avoid cycles) |
118
121
  | `tunnel_list` | List port-forwarding tunnels and their live state (active/connecting/error/stopped, rules, connection counts) |
119
122
 
120
- Typical agent flow (recommended): `tty_send` starts a long-running task → `tty_expect` waits for the
121
- readiness marker → `tty_capture{last:true}` gets the result of that single command. In addition, a dynamic
123
+ Typical agent flow (recommended): `tty_open` opens a session (pass `persistName` for tmux persistence on
124
+ long-running work) → `tty_send` starts the command → `tty_expect` waits for the readiness marker →
125
+ `tty_capture{last:true}` gets the result of that single command → `tty_close` when done. In addition, a dynamic
122
126
  context is registered in `systemPrompt` so that every turn automatically carries a snapshot of active
123
- terminals (sid / kind / cwd) — you have context without calling `tty_list` first.
127
+ terminals (sid / kind / cwd / owner) — you have context without calling `tty_list` first.
128
+
129
+ **Agent-opened session boundaries (0.20.0)**: a session opened by `tty_open` is an **ordinary tab in the
130
+ panel** (marked “agent”) — the user can see it, switch to it, take it over and close it. Hidden sessions
131
+ are deliberately not used, because “the user does not know what is running on the machine” is exactly
132
+ where zombie sessions come from. Such a session has no client bound from birth, so it is **not reaped by
133
+ the orphan collector** (which only reaps disconnected *user* sessions); it ends via the agent’s
134
+ `tty_close` or the user closing the tab. Conversely the agent **cannot close a user-opened tab**
135
+ (`tty_close` refuses explicitly).
124
136
 
125
137
  ### Shell integration (OSC 133/7, 0.4.0)
126
138
 
@@ -180,11 +192,11 @@ lacks that service the box is switched back off and disabled, with a note that o
180
192
  “empty / `22` / `" 22 "`” all collapse into one key and the same account never ends up with two names or one
181
193
  password stored twice; a non-default port does take part (the same host on another port is often a different
182
194
  box behind NAT). `field` is `PASSWORD` / `PASSPHRASE`; e.g. `hsadmin@192.0.2.10:22` →
183
- `DSH_TTY_HSADMIN_192_168_80_248_PASSWORD`. It is **derived from the resource identity and carries no hash** — the
195
+ `DSH_TTY_HSADMIN_192_0_2_10_PASSWORD` (the dots of a dotted IP fold to `_`). It is **derived from the resource identity and carries no hash** — the
184
196
  same school as git-credential-store's `protocol://username@host` and docker credential helpers'
185
197
  `ServerURL` + `Username`: host and username **are ASCII identifiers already**, so nothing needs sanitizing and
186
198
  nothing needs a hash to disambiguate. The old hash-based version was patching over "sanitize a human label into a
187
- key": the reference grammar accepts ASCII only, so `HS 248` / `lab-a` / `HS_248` collapse to exactly the same
199
+ key": the reference grammar accepts ASCII only, so `web 01` / `web_01` (space vs underscore) collapse to exactly the same
188
200
  string and only a hash could stop them silently overwriting each other. A resource identity has no such trap — a
189
201
  collision can only happen for **the same host, the same user, the same port**, which is the same password by
190
202
  definition (sharing it is correct behaviour). **The connection name never takes part in the key**, so renaming a
@@ -239,6 +251,34 @@ references one connection-book entry (host and authentication come with it), in
239
251
  - **Status badges**: while the card is expanded it polls live status every 2s (active green/connecting
240
252
  blue/error red/stopped grey + last error); connection-book entries in the “+” menu show a `⇄N` tunnel
241
253
  badge; the agent can query status with the `tunnel_list` tool;
254
+ - **Connbar entry (0.20.0)**: a connection **with enabled tunnels** gets a permanent “隧道 N” button
255
+ (click for live status); one **without** them folds into a “**⋯ More**” menu — previously such a
256
+ connection gave **no hint at all** that port forwarding existed, so the user had to stumble onto the
257
+ settings card. Folding it into “⋯” adds a discovery path without occupying permanent width (the connbar
258
+ gives its width to the tab strip first). Note a tunnel must reference a connection-book entry, so only
259
+ book-backed connections get this entry; an unsaved ad-hoc connection from the “new connection” dialog
260
+ cannot have tunnels.
261
+ - **Form layout (0.20.0)**: direction uses a **segmented control** (local -L / remote -R — two values do
262
+ not deserve a dropdown); the two ends are shown in pairs as **host:port**, with an **arrow** between them
263
+ showing the data flow (it flips when you switch to -R). Each direction has **exactly one fixed end**
264
+ (the host hardcodes it) and that end renders as **static text rather than an input** — an input you cannot
265
+ change would be a lie:
266
+ - `-L`: the left end is fixed at `127.0.0.1` (bound locally, not exposed to the LAN); the right end takes
267
+ the SSH-server-side target;
268
+ - `-R`: the left end takes the SSH-server-side listen address; the right end is fixed at `127.0.0.1`
269
+ (the local service being reached).
270
+ - **Editing (0.20.0)**: “Edit” on a row loads it back into the form below and the button pair becomes
271
+ “Save changes / Cancel”. Saving locates the entry by its **original name** — a tunnel name is derived
272
+ (`<book>-L<localPort>`), so changing the port *is* changing the name and the new name cannot find the old
273
+ row; `enabled` **keeps its previous value** (editing a spec never silently enables a stopped tunnel); the
274
+ row being edited gets a left accent bar. **A name clash is reported explicitly** (no more silent `-2`
275
+ suffix, which used to spawn a second tunnel differing only by a trailing number).
276
+ - **A failed local listen is sticky**: when the port is taken (`EADDRINUSE`) the state stays red `error`
277
+ with the reason kept, and is **not** rewritten to “connecting” by the SSH-side retry (fixed in 0.20.0).
278
+ Recover by changing the port (with the editor) or freeing it.
279
+ ⚠️ `~/.dsh/settings.yaml` is **shared across profiles**: with several profiles on one machine the same
280
+ tunnel gets started by each of them and they fight over the same local port — a configuration conflict,
281
+ not a plugin bug.
242
282
  - TOFU shares the same `hostKeys` pinning as terminal sessions; ports do not consume `maxSessions` slots.
243
283
 
244
284
  ## SFTP file transfer (0.7.0, enhanced in 0.8.0/0.9.0)
package/README.md CHANGED
@@ -93,11 +93,14 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
93
93
 
94
94
  ## agent 工具(P1)
95
95
 
96
- 插件向 agent 注入十三个工具(与 bash 工具同权,操作实时显示在用户终端里):
96
+ 插件向 agent 注入十六个工具(与 bash 工具同权,操作实时显示在用户终端里):
97
97
 
98
98
  | 工具 | 作用 |
99
99
  | --- | --- |
100
- | `tty_list` | 列出活跃终端会话(sid / kind(local\|ssh)/ target / pid / **cwd 实时跟随 cd** / 活动时间;tmux 持久会话带 `persist` 标记) |
100
+ | `tty_list` | 列出活跃终端会话(sid / kind(local\|ssh)/ target / pid / **cwd 实时跟随 cd** / 活动时间;tmux 持久会话带 `persist` 标记;agent 自己开的带 `owner: 'agent'`) |
101
+ | `tty_open` | **自己开一个终端会话**(0.20.0):本地 shell,或 `command` 直接跑一条长驻命令(dev server / watch),`persistName` 可要 tmux 持久化。**开出来的会话出现在用户的终端面板里**(普通标签、用户可见可接管),不做隐形会话 |
102
+ | `tty_close` | 关掉一个由 `tty_open` 开的会话(0.20.0)。**只允许关 agent 自己开的**:用户在面板里开的标签会被拒绝——agent 不越权结束用户正在用的终端 |
103
+ | `tty_stats` | 读会话所在机器的实时指标(0.20.0):CPU / 内存 / 磁盘 / TCP 连接数 / 网速 / 温度 / 在线时长。本地会话取宿主机;SSH 会话取那台远程主机(另开一段非 PTY 通道,不影响终端)。部署、压测前先看它 |
101
104
  | `tty_capture` | 读取近期输出(尾部 N 行,默认清洗 ANSI,`raw:true` 取原始流);**`last:true` 只返回上一条已完成命令的输出 + 退出码**(shell 集成标记,见下节);命令**在途**时(刚发送、完成标记未到)返回 `inProgress:true` 且不带旧结果——避免把上一条的输出当成这一条(0.19.0) |
102
105
  | `tty_screen` | 读取**当前可见屏幕**的渲染结果(xterm-headless 虚拟屏,纯文本)——能真正读懂 vim / htop / 菜单等 TUI 界面 |
103
106
  | `tty_expect` | 用正则**等待后续输出**中的就绪信号(dev server URL、构建完成等);超时不抛错(`matched:false` + 尾部输出),命令提前结束也会带退出码早停;同一会话在途调用最多 5 个,累积窗口只保留尾部 64KB(0.19.0) |
@@ -111,10 +114,16 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
111
114
  | `sftp_tree` | 递归列举远程目录结构(深度优先、目录优先;`maxDepth` 1~8 / `maxEntries` 1~2000 限流,超限 `truncated:true`;symlink 不跟随防环) |
112
115
  | `tunnel_list` | 列出端口转发隧道及其实时状态(活跃/连接中/错误/停止、规则、连接数) |
113
116
 
114
- 典型 agent 流程(推荐):`tty_send` 启动长任务 → `tty_expect` 等就绪标记 →
115
- `tty_capture{last:true}` 拿单条命令结果。此外 `systemPrompt` 里注册了动态
116
- context,每轮对话自动携带活跃终端快照(sid / kind / cwd),无需先调
117
- `tty_list` 也有上下文。
117
+ 典型 agent 流程(推荐):`tty_open` 开一个会话(长驻进程用 `persistName` 要 tmux 持久化)
118
+ → `tty_send` 启动命令 → `tty_expect` 等就绪标记 → `tty_capture{last:true}` 拿单条命令结果
119
+ → 用完 `tty_close`。此外 `systemPrompt` 里注册了动态 context,每轮对话自动携带活跃终端快照
120
+ (sid / kind / cwd / owner),无需先调 `tty_list` 也有上下文。
121
+
122
+ **agent 开的会话边界(0.20.0)**:`tty_open` 开出来的会话是**面板里的普通标签**(带「agent」
123
+ 标识),用户看得见、点得开、接管得了、关得掉——刻意不做隐形会话,因为用户不知道机器上
124
+ 跑着什么正是僵尸会话的来源。它从出生起没有客户端绑定,因此**不被孤儿回收器回收**
125
+ (回收器只收「断连的用户会话」),关闭入口是 agent 的 `tty_close` 或用户在面板里关标签;
126
+ 反过来说,agent 也**关不掉用户开的标签**(`tty_close` 会明确拒绝)。
118
127
 
119
128
  ### shell 集成(OSC 133/7,0.4.0)
120
129
 
@@ -167,11 +176,11 @@ SSH 会话同表调度:`tty_list` 里 `kind: 'ssh'` 的条目按 `target`
167
176
  —— 与连接侧的 `spec.port ?? 22` 一致(留空即 22),所以"留空 / `22` / `" 22 "`"三种写法归成同一个键,
168
177
  同一个账号不会有两个名字、同一个密码不会存两份;非默认端口进键(同一主机不同端口常是 NAT 后面的
169
178
  不同盒子)。字段 = `PASSWORD` / `PASSPHRASE`;例 `hsadmin@192.0.2.10:22` →
170
- `DSH_TTY_HSADMIN_192_168_80_248_PASSWORD`。**按资源身份派生、不带哈希**——与
179
+ `DSH_TTY_HSADMIN_192_0_2_10_PASSWORD`(点分 IP 的 `.` 折成 `_`)。**按资源身份派生、不带哈希**——与
171
180
  git-credential-store 的 `protocol://username@host`、docker credential helpers 的
172
181
  `ServerURL` + `Username` 同一派:host 与 username **本来就是 ASCII 标识符**,不需要清洗、
173
182
  也就不需要哈希兜底。曾经的哈希版是在补救"把人类标签清洗成键":引用文法只认 ASCII,
174
- `HS 248` / `lab-a` / `HS_248` 折出来完全一样,只能靠哈希避免静默覆盖 ✗。资源身份没有这个
183
+ `web 01` / `web_01`(空格与下划线)折出来完全一样,只能靠哈希避免静默覆盖 ✗。资源身份没有这个
175
184
  死结——撞名只可能发生在**同一主机、同一用户、同一端口**,而那本来就该是同一个密码(共享是
176
185
  正确行为)。**连接名完全不参与键**,所以改连接名/改备注都不会换键。空主机或空用户名则拒绝
177
186
  存入(键的全部来源,缺一就退化成常量)。代价是可读性弱于人类标签:本对话框的「存入」**只按
@@ -215,6 +224,27 @@ SSH 会话同表调度:`tty_list` 里 `kind: 'ssh'` 的条目按 `target`
215
224
  - **状态徽标**:卡片展开期间 2s 轮询实时状态(活跃绿/连接中蓝/错误红/停止
216
225
  灰 + 最近错误);「+」菜单的连接簿条目显示 `⇄N` 隧道徽标;agent 可用
217
226
  `tunnel_list` 工具查询状态;
227
+ - **连接栏入口(0.20.0)**:该连接**有启用隧道**时常驻「隧道 N」按钮(点开看实时状态);
228
+ **没有隧道**时收进「**⋯ 更多**」——此前没配隧道的连接在界面上**完全没有线索**,
229
+ 用户只能自己摸到设置卡片。收进「⋯」既补上发现路径、又不占常驻宽度(连接栏宽度优先
230
+ 给标签条)。注意隧道必须引用连接簿条目,所以只有条目式连接有这个入口;
231
+ 「新建连接」对话框里未保存的临时连接配不了隧道。
232
+ - **表单布局(0.20.0)**:方向用**分段控件**(本地 -L / 远程 -R,只有两个值,下拉太重);
233
+ 两端按「**主机:端口**」成对呈现,中间用**箭头**示明数据流向(切到 -R 时箭头翻转)。
234
+ 每个方向都**恰好有一端是固定的**(宿主硬编码),固定端渲染成**静态文本而不是输入框**
235
+ ——给一个改不动的框是骗人:
236
+ - `-L`:左端固定 `127.0.0.1`(本机监听,不暴露到局域网),右端填 SSH 服务器侧目标;
237
+ - `-R`:左端填 SSH 服务器侧监听地址,右端固定 `127.0.0.1`(本机被访问的服务)。
238
+ - **编辑(0.20.0)**:每行「编辑」把该条回填到下方表单,按钮切成「保存修改 / 取消」。
239
+ 保存按**原始名字**定位替换——隧道名由规则派生(`<连接簿条目>-L<本地端口>`),
240
+ 「改端口」就等于换名字,用新名字在列表里找不到自己;`enabled` **保留原值**
241
+ (编辑规格不会顺手把停用的隧道启用);正在编辑的行有左侧色条标记。
242
+ **撞名会明确报错**(不再静默加 `-2` 后缀——那会凭空多出一条同名不同尾的隧道)。
243
+ - **本地监听失败是粘性的**:端口被占(`EADDRINUSE`)时状态稳定停在红色 `error` 并
244
+ 保留原因,**不会**被 SSH 侧的重试刷成「连接中」(0.20.0 修)。恢复办法是改端口
245
+ (用编辑功能)或先腾出端口。
246
+ ⚠️ `~/.dsh/settings.yaml` 是**各 profile 共享**的:同一台机器上跑多个 profile 时,
247
+ 同一条隧道会被各自启动、抢同一个本地端口——这是配置层面的冲突,不是插件 bug。
218
248
  - TOFU 与终端会话共享同一份 hostKeys 钉扎;端口不占用 maxSessions 名额。
219
249
 
220
250
  ## SFTP 文件传输(0.7.0,0.8.0/0.9.0 增强)