@hyzyn/dsh-tty 0.31.0 → 0.32.0

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
@@ -136,11 +136,22 @@ Both were reproduced on **Windows 11 ARM (24H2) + Node 22 ARM64**. The fix:
136
136
  no new prompt, looking like “it was sent but never ran”). For **local sessions on win32** the plugin now
137
137
  turns a bare LF into CRLF (an existing CRLF is not doubled; non-Windows and SSH sessions pass through
138
138
  untouched), so the documented `\n` path just works;
139
- - **Three things are not supported** (the host turns them off and the settings card explains why):
140
- - **Shell integration (OSC 133/7)**: injection relies on POSIX rc stubs plus the `-c` wrapper, neither of
141
- which exists for cmd / PowerShell, so it is permanently off — **local** tabs therefore lose cwd tracking
142
- (`tty_list.cwd` following `cd`), `tty_capture{last}` and command-granular `tty_expect` (remote Linux /
143
- macOS hosts are unaffected);
139
+ - **Shell integration (OSC 133/7) depends on the shell family**: the **PowerShell family is supported**
140
+ (`powershell.exe` / `pwsh.exe`) through a stable `-File` stub that hooks `prompt` and
141
+ `PSConsoleHostReadLine` — this yields cwd tracking (`tty_list.cwd` following `cd`), exit codes, the
142
+ `running` state and command-granular `tty_expect`. **cmd is not** (it has no command-boundary hooks), so
143
+ the settings card greys the switch out and points at “switch the shell path to PowerShell”. **Known
144
+ limit**: on PowerShell 7 the command output reaches ConPTY *after* the D marker (pane redraws are
145
+ asynchronous and batched — the same root cause as in the tmux case), so `tty_capture{last}` may come back
146
+ empty; use `tty_capture` to read the tail there. Windows PowerShell 5.1 is unaffected (both were measured
147
+ on a real machine). The stub only loads through `-ExecutionPolicy Bypass` (process-scoped); two boundaries
148
+ are recorded as they are: when **Group Policy** locks the execution policy to Restricted / AllSigned that
149
+ parameter does **not** override GPO, so the stub fails to load and the tab opens with integration silently
150
+ absent (the same degradation as “the user turned PSReadLine off”); the parameter also writes the value into
151
+ `$Env:PSExecutionPolicyPreference`, which PowerShell processes started from that session inherit. One more
152
+ known side effect: wrapping `prompt` makes **the user’s original prompt see its own `$?`** as the result of
153
+ our writes (always true), so failure indicators that rely on `$?` stop working;
154
+ - **Two things are not supported** (the host turns them off and the settings card explains why):
144
155
  - **tmux persistence**: there is no tmux on Windows, so local tabs open normally but are not managed
145
156
  (SSH into a Linux host still works);
146
157
  - **Server status bar**: locally only CPU / memory / uptime are available (`node:os`); disk / TCP / network
@@ -154,7 +165,13 @@ Both were reproduced on **Windows 11 ARM (24H2) + Node 22 ARM64**. The fix:
154
165
  - **End-to-end coverage (0.19.0)**: `scripts/windows-smoke.mjs` drives a real `cmd.exe` through
155
166
  spawn → input echo → kill → respawn on the CI windows-latest runner, plus two links that only hold on
156
167
  a real machine (W6: after normalisation a bare LF really does submit in conhost, D74; W7: a `command`
157
- session that exits turns into read-only retention and `tty_capture` still reads its pre-exit output, D77). It caught and now pins a
168
+ session that exits turns into read-only retention and `tty_capture` still reads its pre-exit output, D77).
169
+ **W8 (the PowerShell OSC 133/7 stage) runs on CI** (since 2026-10-13): it used to be guarded by the
170
+ **real-machine lane** only, because killing a ConPTY session there tripped `0xC0000374` — the root cause
171
+ (the same PTY being natively `kill()`ed twice) is now located and fixed, see **D100** in
172
+ [DEFECTS.md](./DEFECTS.md) and the 2026-10-13 section of [ROADMAP.md](./ROADMAP.md). Where no PowerShell
173
+ exists the stage honestly reports its own `SKIP` row (excluded from the denominator, never dressed up as
174
+ `12/12`). It caught and now pins a
158
175
  Windows-only failure class: force-killing a local PTY crashed the host — node-pty rejects signals
159
176
  on Windows, and its `_deferNoArgs` re-throws that error from a socket callback, where the caller’s
160
177
  try/catch cannot see it.
@@ -165,7 +182,7 @@ The plugin injects nineteen tools into the agent (operations show up live in the
165
182
 
166
183
  | Tool | Purpose |
167
184
  | --- | --- |
168
- | `tty_list` | List 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'`). **Includes sessions whose process has exited but which are still inside their read-only retention window** (`exited:true` + exit code/signal, see below). **`running` says whether a command is currently executing** (true = it is: do not send into it now; false = the command has finished; **omitted = cannot tell** — non-persistent SSH / fish·csh / Windows local sessions carry no shell-integration markers, which is *not* the same as “idle”), and the same-source `lastExitCode` / `lastExitAt` are the exit code and end time of “the previous completed command” (0.23.0). The rendered text spells the three states `[空闲]` / `[运行中——现在别往里发命令]` / `[命令状态未知——…]`; **“unknown” also covers one more case** — the host has seen **no marker for that session since this boot** (typically a tmux persistent session already sitting at its prompt before the host restarted): send it a bare Enter or run a command and the marker arrives, turning it into `[空闲]`; non-persistent SSH / fish·csh / Windows local sessions never emit markers, so `[命令状态未知]` is their normal state |
185
+ | `tty_list` | List 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'`). **Includes sessions whose process has exited but which are still inside their read-only retention window** (`exited:true` + exit code/signal, see below). **`running` says whether a command is currently executing** (true = it is: do not send into it now; false = the command has finished; **omitted = cannot tell** — non-persistent SSH / fish·csh / Windows cmd sessions carry no shell-integration markers, which is *not* the same as “idle”), and the same-source `lastExitCode` / `lastExitAt` are the exit code and end time of “the previous completed command” (0.23.0). The rendered text spells the three states `[空闲]` / `[运行中——现在别往里发命令]` / `[命令状态未知——…]`; **“unknown” also covers one more case** — the host has seen **no marker for that session since this boot** (typically a tmux persistent session already sitting at its prompt before the host restarted): send it a bare Enter or run a command and the marker arrives, turning it into `[空闲]`; non-persistent SSH / fish·csh / Windows cmd sessions never emit markers, so `[命令状态未知]` is their normal state |
169
186
  | `tty_open` | **Open a terminal session yourself** (0.20.0): a local shell, or a command via `command` (dev server / watch; it runs as **a whole piece of code in the host shell’s syntax** — on a POSIX shell `cd x && cmd`, `a; b` and multi-line scripts work, while **Windows cmd / PowerShell use their own syntax**; the current shell and syntax are restated every turn in the systemPrompt terminal line; it is *not* subject to the “single line ≤2000” client rule below — D78), 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 |
170
187
  | `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. It also works on an **exited session that is still in read-only retention** — that is its release entry point (by default it is retained **until explicitly closed**, never on a timer) |
171
188
  | `tty_run` | **One-shot command** (0.23.0): open a session, run `command`, wait for it to finish and return the tail output + exit code directly — collapsing “`tty_open` → wait → `tty_capture{last}` → `tty_close`” into a single call. It **closes the session once the command finishes** by default (the result is already in this call); `keep:true` leaves it in read-only retention instead. If it is still running at `timeoutSec` (1~600, default 120) it returns `running:true` and **does not kill the session** (whether to keep waiting or close it is the caller’s call). When to use this instead of the bash tool: when the user should **see** the command, or when it must run inside a terminal session; for purely non-interactive commands bash is more direct |
@@ -257,7 +274,9 @@ This is why “`tty_open` a command that finishes and read the result afterwards
257
274
 
258
275
  ### Shell integration (OSC 133/7, 0.4.0)
259
276
 
260
- At spawn time hooks are injected through the existing `-c` wrapper layer according to the shell type (transparent to the user, no rc changes):
277
+ On POSIX, hooks are injected through the existing `-c` wrapper layer according to the shell type; on
278
+ Windows the PowerShell family gets a stable `-File` stub instead (see the Windows section above). Both are
279
+ transparent to the user and change no rc:
261
280
 
262
281
  - **zsh**: `ZDOTDIR` points at a temporary stub directory (the same approach VS Code uses); the stub sources
263
282
  the user’s original rc first and then appends `precmd`/`preexec` hooks;
@@ -279,8 +298,9 @@ buffer). The look-back lower bound is raised to “just after the last `B` marke
279
298
  the echo sits between `A` and `B`, so text in the echo never counts as a hit (deliberately no text
280
299
  comparison here: line wrapping / ANSI / multi-line paste would break it).
281
300
 
282
- - **Environments without `B` markers** (integration off / local Windows tabs / a remote host without the
283
- hooks) have no boundary to cut at, so since D75 the echo is excluded by a different test: the command
301
+ - **Environments without `B` markers** (integration off / local Windows cmd tabs / a remote host without
302
+ the hooks / PSReadLine turned off by the user) have no boundary to cut at, so since D75 the echo is
303
+ excluded by a different test: the command
284
304
  lines that the most recent `tty_send` submitted are stripped out of the candidate text and the pattern
285
305
  is retried — only “no longer matches after stripping” counts as a pure echo. That removes the false
286
306
  positive where the command never ran yet the wait reported `matched:true`, while the same text really
@@ -845,7 +865,7 @@ sub-page under the Plugins sidebar, and `≤0.1.5` uses the settings-page card.
845
865
  | `reconnectGraceSec` | 120 | Seconds a session is kept alive after an abnormal disconnect (0~3600): the session survives a page refresh/network blip waiting for a reconnect, and the reaper ends it on timeout; `0` = the old behavior, end immediately on disconnect |
846
866
  | `sshHosts` | `[]` | SSH connection book (selectable in the panel “+” menu): entries `{name, host, port=22, username, auth=agent\|key\|password, keyPath, passphrase, password, agentForward, hostKeyAlias='', persist=false}`; saved as a whole-set replacement, the same name overwrites; `password` / `passphrase` support `env:VAR` references so no plaintext is stored; with persistence on, clicking an entry opens a tmux persistent session by default, and `persist=false` is an **opt-out**; an empty `hostKeyAlias` locates fingerprints by host address (TOFU), while a non-empty one locates them by alias (several addresses of one host share a record — see “SSH connections”) |
847
867
  | `hostKeys` | `[]` | SSH host key records (TOFU, maintained automatically): entries `{host, port, fingerprints[]}` (the legacy single `fingerprint` field is migrated and merged on read); unique by host:port, one record holds all of a host’s keys, appended automatically on the first connection, any matching fingerprint is allowed, and a full mismatch rejects the connection; the settings card can delete them to reset |
848
- | `shellIntegration` | true | Injects the OSC 133/7 shell integration (command boundary markers + cwd reporting; `tty_capture{last}` depends on it); zsh/bash supported, other shells skipped automatically; can be turned off when compatibility problems appear |
868
+ | `shellIntegration` | true | Injects the OSC 133/7 shell integration (command boundary markers + cwd reporting; `tty_capture{last}` depends on it); zsh/bash on POSIX and the PowerShell family on Windows (`-File` stub), other shells (including Windows cmd) skipped automatically; can be turned off when compatibility problems appear |
849
869
  | `tunnels` | `[]` | Port-forwarding tunnels: entries `{name, bookName, direction=local\|remote, localPort?, remoteHost?, remotePort?, localTargetHost?, localTargetPort?, enabled}`; `bookName` references a connection-book entry for host and authentication; maintained graphically in the “Port forwarding” block of the card |
850
870
  | `sftpStyle` | `dialog` | SFTP File Browser UI style: `dialog` single pane (remote directory + upload/download/drag & drop) / `dual` two panes (local left / remote right, inline `⇨/⇦` server-side direct transfer); reopen SFTP for it to take effect |
851
871
  | `persistence` | `off` | Session persistence: `off` sessions live and die with the host (default); `tmux` makes **every newly opened tab hosted by the tmux server by default**, recoverable across a host restart (requires tmux locally/remotely); the SSH dialog can opt out for a single connection |
@@ -1198,8 +1218,10 @@ verify things like “is there still a white panel after switching light/dark th
1198
1218
  only for `reconnectGraceSec` (120s by default), and a host process restart ends all sessions; sessions not
1199
1219
  reconnected before the deadline are ended by the reaper, and scroll history beyond the output buffer
1200
1220
  (last 256KB) cannot be recovered.
1201
- - **Shell integration is zsh / bash only**: other shells are skipped automatically (`tty_capture{last}`
1202
- reports a clear error rather than a wrong one). bash < 4.4 uses the DEBUG trap fallback: internal commands
1221
+ - **Shell integration covers zsh / bash on POSIX and the PowerShell family on Windows**: other shells
1222
+ (Windows cmd) are skipped automatically (`tty_capture{last}` reports a clear error rather than a wrong
1223
+ one); on PowerShell 7 the command output can land after the D marker, so `tty_capture{last}` may be empty
1224
+ there (read the tail with `tty_capture` instead). bash < 4.4 uses the DEBUG trap fallback: internal commands
1203
1225
  of compound commands such as loops emit extra B markers, and `tty_capture{last}` captures only the output
1204
1226
  after the last internal command for those commands (exit codes and `tty_expect` are unaffected). If the
1205
1227
  user’s rc overrides `PROMPT_COMMAND`/the hook array, the integration may stop working — turn off
package/README.md CHANGED
@@ -124,10 +124,18 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
124
124
  下移一格、**不提交命令行**——`tty_send` 按工具描述发 `echo X\n` 时命令停在输入行上(没有输出、
125
125
  没有新提示符,看起来像「发出去了但没执行」)。**win32 的本地会话**上插件现在把裸 LF 补成 CRLF
126
126
  (已经是 CRLF 的不重复补;非 Windows 与 SSH 会话原样透传),所以照描述写 `\n` 这条主路径直接可用;
127
- - **不支持的三项(宿主侧自动关掉,设置卡片里写明原因)**:
128
- - **shell 集成(OSC 133/7)**:注入靠 POSIX rc 桩 + `-c` 包装层,cmd / PowerShell 上都不成立,
129
- 因此恒关 —— **本地标签**的 `tty_list.cwd` 跟随 `cd`、`tty_capture{last}` 与 `tty_expect`
130
- 的命令粒度不可用(远程 Linux / macOS 主机照旧支持);
127
+ - **shell 集成(OSC 133/7)按家族分**:**PowerShell 家族支持**(`powershell.exe` / `pwsh.exe`)——
128
+ 注入走 `-File` 稳定桩(挂 `prompt` + `PSConsoleHostReadLine` 钩子),拿到 `tty_list.cwd` 跟随
129
+ `cd`、退出码、`running` 三态与命令粒度的 `tty_expect`;**cmd 不支持**(没有命令开始/结束钩子),
130
+ 设置卡片把开关置灰并指向「把 Shell 路径换成 PowerShell」。**已知限制**:PowerShell 7 上命令输出
131
+ 在 ConPTY 里晚于 D 标记抵达(渲染重画是异步批量的,与 tmux 场景同根因),`tty_capture{last}`
132
+ 可能取到空 —— 那里用 `tty_capture` 读尾部;Windows PowerShell 5.1 不受影响(两版都真机实测过)。
133
+ 桩靠 `-ExecutionPolicy Bypass`(进程级)才载得进来,两条边界如实记着:**组策略**把执行策略锁成
134
+ Restricted / AllSigned 时该参数压不过 GPO,桩载不进去、标签照开而集成静默失效(与「用户关了
135
+ PSReadLine」同一种降级);这个参数还会把值写进 `$Env:PSExecutionPolicyPreference`,从该会话里
136
+ 再起的 PowerShell 会继承它。另有一条已知副作用:包装 `prompt` 会让**用户原 prompt 读到的 `$?`**
137
+ 变成我们那几次写入的成败(恒真),依赖 `$?` 的失败提示会失效;
138
+ - **不支持的两项(宿主侧自动关掉,设置卡片里写明原因)**:
131
139
  - **tmux 持久化**:Windows 上没有 tmux,本地标签照常打开但不受托管(SSH 到 Linux 主机仍可用);
132
140
  - **服务器状态条**:本地只拿得到 CPU / 内存 / 在线时长(`node:os`),磁盘 / TCP / 网速 / 温度
133
141
  显示「无」(**远端** Windows 主机走 PowerShell 那一跳,见「服务器状态条」一节);
@@ -138,7 +146,11 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
138
146
  - **端到端覆盖(0.19.0)**:`scripts/windows-smoke.mjs` 在 CI 的 windows-latest 上跑真实 `cmd.exe`
139
147
  的 spawn → 输入回显 → kill → 重开,外加两条只在真机上成立的链路(W6:`tty_send` 的裸 LF 经归一化
140
148
  后 conhost 真的执行了命令,D74;W7:`command` 型会话跑完退出后转只读保留、`tty_capture` 仍读得到
141
- 退出前的输出,D77)。它抓出并钉住了「强杀本地 PTY 会崩宿主」这类只在 Windows
149
+ 退出前的输出,D77)。**W8(PowerShell 的 OSC 133/7 档)在 CI 上照常跑**(2026-10-13 起):
150
+ 它此前只在真机车道守,是因为「杀一个 ConPTY 会话」会踩 `0xC0000374`——根因(同一个 PTY 被原生
151
+ `kill()` 两次)已定位并修好,见 [DEFECTS.md](./DEFECTS.md) 的 **D100** 与
152
+ [ROADMAP.md](./ROADMAP.md) 的 2026-10-13 一节;本机找不到 PowerShell 时它会如实进 `SKIP` 一档
153
+ (分母扣掉、汇总行点名,不许装成 `12/12`)。它抓出并钉住了「强杀本地 PTY 会崩宿主」这类只在 Windows
142
154
  出现的问题——node-pty 在 Windows 上不接受 signal,而它的 `_deferNoArgs` 会把这个异常推迟到
143
155
  socket 回调里抛出,调用方的 try/catch 拦不住。
144
156
 
@@ -148,7 +160,7 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
148
160
 
149
161
  | 工具 | 作用 |
150
162
  | --- | --- |
151
- | `tty_list` | 列出终端会话(sid / kind(local\|ssh)/ target / pid / **cwd 实时跟随 cd** / 活动时间;tmux 持久会话带 `persist` 标记;agent 自己开的带 `owner: 'agent'`)。**含进程已退出但仍在只读保留期内的会话**(`exited:true` + 退出码/信号,见下节);**`running` 说明这个会话有没有命令在跑**(true = 在跑,现在别往里发命令;false = 命令已结束;**省略 = 无法判断**——非持久 SSH / fish·csh / Windows 本地没有 shell 集成标记,**不是**「没在跑」),同源的 `lastExitCode` / `lastExitAt` 是「上一条已完成命令」的退出码与结束时刻(0.23.0)。文本里三态写作 `[空闲]` / `[运行中——现在别往里发命令]` / `[命令状态未知——…]`;**「未知」还包含一种情形**:宿主**本次启动之后没见过该会话的标记**(典型是 tmux 持久会话在宿主重启前就停在提示符上)——对它发一个回车或跑一条命令,标记补上就变 `[空闲]`;而非持久 SSH / fish·csh / Windows 本地永远不会有标记,`[命令状态未知]` 是它们的常态 |
163
+ | `tty_list` | 列出终端会话(sid / kind(local\|ssh)/ target / pid / **cwd 实时跟随 cd** / 活动时间;tmux 持久会话带 `persist` 标记;agent 自己开的带 `owner: 'agent'`)。**含进程已退出但仍在只读保留期内的会话**(`exited:true` + 退出码/信号,见下节);**`running` 说明这个会话有没有命令在跑**(true = 在跑,现在别往里发命令;false = 命令已结束;**省略 = 无法判断**——非持久 SSH / fish·csh / Windows 的 cmd 没有 shell 集成标记,**不是**「没在跑」),同源的 `lastExitCode` / `lastExitAt` 是「上一条已完成命令」的退出码与结束时刻(0.23.0)。文本里三态写作 `[空闲]` / `[运行中——现在别往里发命令]` / `[命令状态未知——…]`;**「未知」还包含一种情形**:宿主**本次启动之后没见过该会话的标记**(典型是 tmux 持久会话在宿主重启前就停在提示符上)——对它发一个回车或跑一条命令,标记补上就变 `[空闲]`;而非持久 SSH / fish·csh / Windows 的 cmd 永远不会有标记,`[命令状态未知]` 是它们的常态 |
152
164
  | `tty_open` | **自己开一个终端会话**(0.20.0):本地 shell,或 `command` 直接跑一条命令(dev server / watch;**按宿主 shell 的语法整段执行**——POSIX shell 上 `cd x && cmd`、`a; b`、多行脚本都可以;**Windows 的 cmd / PowerShell 则按它们自己的语法**,当前 shell 与语法每轮都写在 systemPrompt 的终端一行里;不受下面「命令标签单行 ≤2000」那条客户端约束,D78),`persistName` 可要 tmux 持久化。**开出来的会话出现在用户的终端面板里**(普通标签、用户可见可接管),不做隐形会话 |
153
165
  | `tty_close` | 关掉一个由 `tty_open` 开的会话(0.20.0)。**只允许关 agent 自己开的**:用户在面板里开的标签会被拒绝——agent 不越权结束用户正在用的终端。对**已退出但仍只读保留着**的会话同样可用——那是它的释放入口(默认**保留到显式关闭**,不按时间释放) |
154
166
  | `tty_run` | **一次性命令**(0.23.0):开一个会话跑 `command`,等它结束,直接返回尾部输出 + 退出码——把「`tty_open` → 等 → `tty_capture{last}` → `tty_close`」四步压成一次调用。默认**跑完即关**(结果已在本次调用返回),`keep:true` 则留在只读保留态;到 `timeoutSec`(1~600,默认 120)还没结束就返回 `running:true` 且**不杀会话**(继续等或关掉由调用方决定)。何时用它而不是 bash 工具:需要用户**看得见**这条命令、或要跑在终端会话里时用它,纯非交互命令用 bash 更直接 |
@@ -228,7 +240,8 @@ dsh plugin --profile web add link:$(pwd)/packages/tty # 仓库开发调试
228
240
 
229
241
  ### shell 集成(OSC 133/7,0.4.0)
230
242
 
231
- spawn 时经既有的 `-c` 包装层按 shell 类型注入钩子(对用户透明,不改 rc):
243
+ POSIX 上经既有的 `-c` 包装层按 shell 类型注入钩子,Windows 上走 PowerShell 的 `-File` 稳定桩
244
+ (都对用户透明,不改 rc):
232
245
 
233
246
  - **zsh**:`ZDOTDIR` 指向临时桩目录(VS Code 同款方案),桩文件先 source
234
247
  用户原 rc 再追加 `precmd`/`preexec` 钩子;
@@ -240,17 +253,26 @@ spawn 时经既有的 `-c` 包装层按 shell 类型注入钩子(对用户透
240
253
  副作用:循环体等复合命令的内部命令会多发 B 标记,仅影响
241
254
  `tty_capture{last}` 对这类命令的截取起点,D/退出码与 `tty_expect` 不受影响;
242
255
  PROMPT_COMMAND 挂钩兼容字符串与数组(bash 5.1+)两种形态;
256
+ - **PowerShell(Windows)**:`-File` 稳定桩(`<DSH_HOME|~/.dsh>/tty[/<profile>]/shell/powershell/bootstrap.ps1`,
257
+ 纯 ASCII —— Windows PowerShell 5.1 按当前 ANSI 代码页读无 BOM 的 .ps1,一个非 ASCII 字节就炸解析),
258
+ 包装 `prompt` 与 `PSConsoleHostReadLine`;用户 profile 照常生效(桩在 profile **之后**载入,拿到的就是
259
+ 用户自己那个 prompt,包装后原样调用)。退出码口径:B 时刻快照 `$LASTEXITCODE`、D 时刻**变了**才报它
260
+ (原生命令),否则按 `$?` 报 0/1(cmdlet)——刻意**不清空** `$LASTEXITCODE`,免得用户紧接着的
261
+ `echo $LASTEXITCODE` 读到空值;代价是连续两次同码的非零原生命令,第二次降级报 1。
262
+ 拿不到原 `PSConsoleHostReadLine`(用户关了 PSReadLine)时**一个标记都不发**:只发 A/7 会让宿主
263
+ 报「没在跑」,比「无法判断」更危险(D77);
243
264
  - 标记语义:`133;A` prompt 开始 / `133;B` 命令开始 / `133;D;<exit>` 命令
244
265
  结束带退出码 / `OSC 7 file://…` cwd 上报(`tty_list.cwd` 跟随 `cd`,
245
266
  SSH 会话则上报远程路径);
246
- - 其他 shell 静默关闭;配置 `shellIntegration: false` 可整体关掉(逃生门)。
267
+ - 其他 shell(含 Windows 的 cmd)静默关闭;配置 `shellIntegration: false` 可整体关掉(逃生门)。
247
268
 
248
269
  **回溯匹配与已读水位线(D72)**:`tty_expect` 会先回看**还没被读过**的输出(命令瞬间跑完时
249
270
  要等的标记早就在缓冲区里了),回看的起点抬到「未读区内最后一个 `B` 标记之后」——回显落在 `A`
250
271
  与 `B` 之间,所以回显里的字样不会被当成命中(这里刻意不做文本比对:折行 / ANSI / 多行粘贴
251
272
  都会把它打碎)。
252
273
 
253
- - **没有 `B` 标记的环境**(未开集成 / Windows 本地标签 / 远端未装集成)没有边界可切,D75 起
274
+ - **没有 `B` 标记的环境**(未开集成 / Windows 的 cmd 标签 / 远端未装集成 / 用户关了 PSReadLine)
275
+ 没有边界可切,D75 起
254
276
  改用另一条判据剔除回显:把**最近 `tty_send` 提交过的命令行**从候选文本里削掉再试一次,
255
277
  只有「削掉后不再命中」才算纯回显——这样「命令没执行、等待却自称 matched」这条假阳性没了,
256
278
  而回显之后真的跑出来的同一段文本照旧命中。等满超时且 pattern 只命中过回显时,结果带