@hyzyn/dsh-tty 0.26.0 → 0.27.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 +37 -1
- package/README.md +31 -1
- package/client.js +26 -11
- package/lib/index.d.ts +4 -0
- package/lib/index.js +44 -5
- package/lib/index.js.map +1 -1
- package/package.json +4 -4
package/README.en.md
CHANGED
|
@@ -114,7 +114,7 @@ Both were reproduced on **Windows 11 ARM (24H2) + Node 22 ARM64**. The fix:
|
|
|
114
114
|
|
|
115
115
|
## Agent tools
|
|
116
116
|
|
|
117
|
-
The plugin injects nineteen tools into the agent (
|
|
117
|
+
The plugin injects nineteen tools into the agent (operations show up live in the user’s terminal; **every call is subject to the session permission tier**, see below):
|
|
118
118
|
|
|
119
119
|
| Tool | Purpose |
|
|
120
120
|
| --- | --- |
|
|
@@ -137,6 +137,26 @@ The plugin injects nineteen tools into the agent (with the same power as the bas
|
|
|
137
137
|
| `tunnel_list` | List port-forwarding tunnels and their live state (active/connecting/error/stopped, rules, connection counts) |
|
|
138
138
|
| `tunnel_start` / `tunnel_stop` | Start or stop one port-forwarding tunnel (0.25.0; `name` comes from `tunnel_list`). These **rewrite the configuration** — the same thing as ticking/unticking “Enable” in the settings card (settings → hot apply → `reconcile`), so the change is persistent and visible to the user. The returned snapshot usually still says `connecting` (dialling is async); call `tunnel_list` again for the final verdict |
|
|
139
139
|
|
|
140
|
+
### Session permission tier gate (tier gate)
|
|
141
|
+
|
|
142
|
+
Tools are classified by risk; **every call** is judged against the session’s permission tier
|
|
143
|
+
(Read Only / Workspace Write / Full access / Auto review) and allowed, asked, or denied — the tier is
|
|
144
|
+
switched by the user in the session, and the very next call follows the new tier:
|
|
145
|
+
|
|
146
|
+
| Class | Tools | Behaviour under a confined tier (Read Only / Workspace Write) |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| Read | `tty_list` `tty_capture` `tty_screen` `tty_expect` `tty_stats` `tunnel_list` `sftp_list` `sftp_read` `sftp_tree` `tty_close` | Allowed on every tier |
|
|
149
|
+
| Write (bounded mutation) | `sftp_write` `sftp_mkdir` `sftp_rename` `sftp_remove` `tunnel_stop` | Approval prompt (runs only if the user allows this one call); deterministically denied when unattended (approval policy `never`) |
|
|
150
|
+
| Execute (unbounded command surface) | `tty_open` `tty_run` `tty_send` `tunnel_start` | Same — these tools can run arbitrary commands on the local or a remote shell and open listening ports; they are the fence-sensitive surface |
|
|
151
|
+
|
|
152
|
+
**Full access never prompts** (that tier’s intent is fewer confirmation steps); under **Auto review the
|
|
153
|
+
plugin does not add its own prompt** (the host’s model pre-review already examines every call). When the
|
|
154
|
+
host has not composed the `sandboxPolicy` / `approval` services (older hosts, minimal deployments) the
|
|
155
|
+
gate is **off** (behaviour identical to before the gate), and the startup log says so:
|
|
156
|
+
`tier-gate: services absent (…), per-call gate disabled`. The mechanism (decision matrix / the two-layer
|
|
157
|
+
relationship with the static `allowProxyCommand` switch) lives in
|
|
158
|
+
[docs/permission-tier-plan.md](../../docs/permission-tier-plan.md).
|
|
159
|
+
|
|
140
160
|
Typical agent flow (recommended): for a command that simply runs to completion, use `tty_run` to get its
|
|
141
161
|
output + exit code in one call; for a **long-lived** session, `tty_open` opens one (pass `persistName` for tmux
|
|
142
162
|
persistence on long-running work) → `tty_send` starts the command → `tty_expect` waits for the readiness marker →
|
|
@@ -852,6 +872,22 @@ ctx.inject(['ttyTerminal'], (c) => {
|
|
|
852
872
|
- The service name `ttyTerminal` is likewise not declared on the `Context` type surface; inject it as an
|
|
853
873
|
optional dependency; when tty is not installed or is older than 0.14.0 it never fires (dsh-docker degrades
|
|
854
874
|
to “copy command”).
|
|
875
|
+
- **`ttyTerminal` / WS `spawn` is a command surface with no static capability gate** (added 2026-10-07;
|
|
876
|
+
consumers must not describe it the other way round): this service and the `{t:'spawn'}` frame both accept an
|
|
877
|
+
arbitrary `command` and are **not governed by any `DSH_*_ALLOW_*`** (this plugin’s own static gate covers only
|
|
878
|
+
ProxyCommand, the “a config field drives a local command” tier). That is deliberate — the terminal panel *is*
|
|
879
|
+
the product, and **its permission ceiling rests on the user acting directly**: a human clicks “+” to open a
|
|
880
|
+
tab and types commands inside it. **Note carefully that the tier gate does not reach here**: the tier gate
|
|
881
|
+
above hangs on the `tools/pre-execute` waterfall and filters by the `tty_` / `sftp_` / `tunnel_` **prefixes**
|
|
882
|
+
(`TTY_TIER_PREFIXES`) — i.e. it gates **agent tool calls only**. A browser `spawn` frame or a
|
|
883
|
+
`ttyTerminal.open` call is a **WS / client-service** path and **never passes through it**. So do not treat it
|
|
884
|
+
as this command surface’s gate: it does not block a single byte of it (only the `tty_open` agent path is
|
|
885
|
+
governed).
|
|
886
|
+
**The cost**: no consumer should advertise “the plugin’s own exec switch” as the boundary that keeps you out of
|
|
887
|
+
a container — once tty is installed, an in-page script can use `ttyTerminal.open` or a WS `spawn` frame to get
|
|
888
|
+
an arbitrary command surface strictly stronger than a one-shot exec. See
|
|
889
|
+
[architecture.md § 7](../../docs/architecture.md#7-一条请求经过什么) for the threat-model boundary; docker states
|
|
890
|
+
the same boundary in its own README under “Known limitations”.
|
|
855
891
|
|
|
856
892
|
### Embedded in-place terminal (`ttyTerminal.mount`, 0.15.0)
|
|
857
893
|
|
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
|
|
|
105
105
|
|
|
106
106
|
## agent 工具
|
|
107
107
|
|
|
108
|
-
插件向 agent
|
|
108
|
+
插件向 agent 注入十九个工具(操作实时显示在用户终端里;**每次调用受会话权限档位约束**,见下节):
|
|
109
109
|
|
|
110
110
|
| 工具 | 作用 |
|
|
111
111
|
| --- | --- |
|
|
@@ -128,6 +128,23 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
|
|
|
128
128
|
| `tunnel_list` | 列出端口转发隧道及其实时状态(活跃/连接中/错误/停止、规则、连接数);`fatal:true` = 人工介入级故障(本地监听失败 / 连接簿缺失),**不会自动重试**,修配置后重建 |
|
|
129
129
|
| `tunnel_start` / `tunnel_stop` | 启停一条端口转发隧道(0.25.0,`name` 来自 `tunnel_list`)。**会改写配置**——与在设置卡片里勾选/取消那个「启用」是同一件事(写回 settings → 热应用 → `reconcile`),所以是持久的、用户在卡片上看得见。返回值是操作后的状态快照:`state` 通常还是 `connecting`(拨号异步),要最终结论稍后 `tunnel_list` |
|
|
130
130
|
|
|
131
|
+
### 会话权限档位闸(tier gate)
|
|
132
|
+
|
|
133
|
+
工具按风险分三类,**每次调用**按会话的权限档位(仅可查看 / 工作区内修改 / 完全权限 / Auto review)
|
|
134
|
+
决定放行、询问还是拒绝——档位是用户在会话里切的,切完下一个调用立即按新档走:
|
|
135
|
+
|
|
136
|
+
| 类 | 工具 | 受限档(仅可查看 / 工作区内修改)下的行为 |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| 读 | `tty_list` `tty_capture` `tty_screen` `tty_expect` `tty_stats` `tunnel_list` `sftp_list` `sftp_read` `sftp_tree` `tty_close` | 全档放行 |
|
|
139
|
+
| 写(有界变更) | `sftp_write` `sftp_mkdir` `sftp_rename` `sftp_remove` `tunnel_stop` | 弹授权询问(人允许这一次才执行);无人值守(审批策略 never)确定性拒绝 |
|
|
140
|
+
| 执行(无界命令面) | `tty_open` `tty_run` `tty_send` `tunnel_start` | 同上——这些工具能在本机或远程 shell 里跑任意命令、开监听端口,是围栏敏感面 |
|
|
141
|
+
|
|
142
|
+
**完全权限档零询问**(该档的 intent 就是减少确认步骤);**Auto review 档插件不叠加询问**
|
|
143
|
+
(宿主的模型预审已在逐调用审查)。宿主未组合 `sandboxPolicy` / `approval` 服务(老宿主、最小
|
|
144
|
+
部署)时**不闸**(与引入前行为一致),启动日志会打一行
|
|
145
|
+
`tier-gate: services absent (…), per-call gate disabled` 说明这件事。机制(决策矩阵 / 与静态能力
|
|
146
|
+
开关 `allowProxyCommand` 的双层关系)见 [docs/permission-tier-plan.md](../../docs/permission-tier-plan.md)。
|
|
147
|
+
|
|
131
148
|
典型 agent 流程(推荐):跑完就算完的命令用 `tty_run` 一次调用拿回输出 + 退出码;
|
|
132
149
|
需要**长驻**的会话用 `tty_open`(长驻进程用 `persistName` 要 tmux 持久化)→ `tty_send` 启动命令
|
|
133
150
|
→ `tty_expect` 等就绪标记 → `tty_capture{last:true}` 拿单条命令结果 → 用完 `tty_close`。
|
|
@@ -774,6 +791,19 @@ ctx.inject(['ttyTerminal'], (c) => {
|
|
|
774
791
|
只受工具参数的字符串约束。两条通道的入参规则不同,别把这里当 `tty_open` 的限制。
|
|
775
792
|
- 服务名 `ttyTerminal` 同样未声明在 `Context` 类型面上,按可选依赖注入;tty 未安装
|
|
776
793
|
或版本 < 0.14.0 时不会触发(dsh-docker 会退化为「复制命令」)。
|
|
794
|
+
- **`ttyTerminal` / WS `spawn` 是「没有静态能力闸」的命令面**(2026-10-07 补,消费方文案别写反):
|
|
795
|
+
本服务与 `{t:'spawn'}` 帧都直接接受任意 `command`,**不受任何 `DSH_*_ALLOW_*` 管辖**(本插件的
|
|
796
|
+
那个静态闸只覆盖 ProxyCommand 这一档「配置字段驱动本机命令」)。这是刻意的——终端面板本身就是
|
|
797
|
+
产品,**它的权限上界由用户的直接操作承担**:人点「+」开标签、人在里面敲命令。
|
|
798
|
+
**务必看清 tier gate 管不到这里**:上面的 tier gate 挂在 `tools/pre-execute` 瀑布上、只按
|
|
799
|
+
`tty_` / `sftp_` / `tunnel_` **前缀**过滤(`TTY_TIER_PREFIXES`),也就是只闸 **agent 工具调用**;
|
|
800
|
+
浏览器发来的 `spawn` 帧与 `ttyTerminal.open` 是 **WS / 客户端服务**通道,**完全不经过它**。
|
|
801
|
+
所以别把它当这条命令面的闸门——它一个字节都不拦(`tty_open` 那条 agent 路径才受它管)。
|
|
802
|
+
**代价**:任何消费方都不该把「配上 tty 之后本插件自己的 exec 开关」宣传成「进不了容器」的
|
|
803
|
+
边界——装上 tty 后,页内脚本经 `ttyTerminal.open` 或 WS `spawn` 就能拿到比一次性 exec 更强的
|
|
804
|
+
任意命令面。威胁模型边界见
|
|
805
|
+
[architecture.md § 7](../../docs/architecture.md#7-一条请求经过什么);docker 侧的同一句边界写在
|
|
806
|
+
它的 README「已知限制」。
|
|
777
807
|
|
|
778
808
|
### 就地嵌入终端(`ttyTerminal.mount`,0.15.0)
|
|
779
809
|
|