dsh-ssh-tui 0.8.1 → 0.8.2

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/README.en.md +118 -15
  3. package/README.md +78 -13
  4. package/docs/release-notes-0.7.4.md +91 -0
  5. package/docs/release-notes-0.8.0.md +56 -0
  6. package/docs/release-notes-0.8.1.md +79 -0
  7. package/docs/release-notes-0.8.2-rc.1.md +159 -0
  8. package/docs/release-notes-0.8.2.md +62 -0
  9. package/docs/screenshots/reconnect.gif +0 -0
  10. package/docs/terminals.md +23 -0
  11. package/docs/windows.md +13 -0
  12. package/lib/approval-state.js +148 -0
  13. package/lib/approval-state.js.map +1 -0
  14. package/lib/auth-failure.js +38 -0
  15. package/lib/auth-failure.js.map +1 -1
  16. package/lib/color-depth.js +26 -0
  17. package/lib/color-depth.js.map +1 -1
  18. package/lib/copy-text.js +30 -8
  19. package/lib/copy-text.js.map +1 -1
  20. package/lib/dialogs.js +81 -0
  21. package/lib/dialogs.js.map +1 -1
  22. package/lib/display-sock.js +138 -23
  23. package/lib/display-sock.js.map +1 -1
  24. package/lib/footer-accents.js +28 -0
  25. package/lib/footer-accents.js.map +1 -0
  26. package/lib/footer-budget.js +328 -0
  27. package/lib/footer-budget.js.map +1 -0
  28. package/lib/footer-format.js +433 -0
  29. package/lib/footer-format.js.map +1 -0
  30. package/lib/footer-layout.js +407 -0
  31. package/lib/footer-layout.js.map +1 -0
  32. package/lib/footer-meters.js +185 -0
  33. package/lib/footer-meters.js.map +1 -0
  34. package/lib/footer.js +19 -558
  35. package/lib/footer.js.map +1 -1
  36. package/lib/i18n/en.js +65 -5
  37. package/lib/i18n/en.js.map +1 -1
  38. package/lib/i18n/zh.js +67 -5
  39. package/lib/i18n/zh.js.map +1 -1
  40. package/lib/paint.js +53 -8
  41. package/lib/paint.js.map +1 -1
  42. package/lib/plan-projection.js +291 -0
  43. package/lib/plan-projection.js.map +1 -0
  44. package/lib/plan.js +50 -0
  45. package/lib/plan.js.map +1 -1
  46. package/lib/question-state.js +83 -0
  47. package/lib/question-state.js.map +1 -0
  48. package/lib/representation.js +283 -0
  49. package/lib/representation.js.map +1 -0
  50. package/lib/screen.js +105 -0
  51. package/lib/screen.js.map +1 -0
  52. package/lib/selection.js +5 -3
  53. package/lib/selection.js.map +1 -1
  54. package/lib/startup.js +49 -0
  55. package/lib/startup.js.map +1 -1
  56. package/lib/stats.js +101 -3
  57. package/lib/stats.js.map +1 -1
  58. package/lib/term-text.js +154 -55
  59. package/lib/term-text.js.map +1 -1
  60. package/lib/theme.js +20 -3
  61. package/lib/theme.js.map +1 -1
  62. package/lib/throughput.js +305 -0
  63. package/lib/throughput.js.map +1 -0
  64. package/lib/tool-present.js +57 -7
  65. package/lib/tool-present.js.map +1 -1
  66. package/lib/tui.js +3776 -1105
  67. package/lib/tui.js.map +1 -1
  68. package/lib/types/approval-state.d.ts +75 -0
  69. package/lib/types/auth-failure.d.ts +25 -0
  70. package/lib/types/color-depth.d.ts +21 -0
  71. package/lib/types/copy-text.d.ts +18 -1
  72. package/lib/types/dialogs.d.ts +111 -1
  73. package/lib/types/display-sock.d.ts +15 -0
  74. package/lib/types/footer-accents.d.ts +22 -0
  75. package/lib/types/footer-budget.d.ts +204 -0
  76. package/lib/types/footer-format.d.ts +221 -0
  77. package/lib/types/footer-layout.d.ts +292 -0
  78. package/lib/types/footer-meters.d.ts +151 -0
  79. package/lib/types/footer.d.ts +19 -269
  80. package/lib/types/paint.d.ts +48 -0
  81. package/lib/types/plan-projection.d.ts +162 -0
  82. package/lib/types/plan.d.ts +13 -0
  83. package/lib/types/question-state.d.ts +96 -0
  84. package/lib/types/representation.d.ts +498 -0
  85. package/lib/types/screen.d.ts +126 -0
  86. package/lib/types/startup.d.ts +24 -0
  87. package/lib/types/stats.d.ts +64 -1
  88. package/lib/types/term-text.d.ts +45 -1
  89. package/lib/types/throughput.d.ts +163 -0
  90. package/lib/types/tool-present.d.ts +41 -0
  91. package/lib/types/transcript-types.d.ts +63 -2
  92. package/lib/types/tui.d.ts +862 -11
  93. package/package.json +16 -6
@@ -0,0 +1,56 @@
1
+ # 0.8.0 发版说明
2
+
3
+ > **发布方式:只上 `next`,`latest` 保持 0.7.4。** 计划 2026-10-01 再把 `0.8.0` 提升为 `latest`。
4
+ > 上一次发版:**0.7.4**(2026-09-29,npm `latest` + `next`)。
5
+
6
+ ## 头等事:跟上宿主的 0.2.0-rc 线,同时摘掉 0.1.5
7
+
8
+ 上游把 `next` 指向 **0.2.0-rc.1**。这条线保留了 0.1.7 引入的两处结构(设置表单由 loader entry 自己的
9
+ `Config` 投影、终端 profile 在进程级组合代理),所以**插件源代码一行没改**就跑了:类型面 0 错、七条真机探针
10
+ 全过。真正需要改的是三处"宿主代际判定"——它们原来用 `0.1.7` 前缀判断,把 `0.2.x` 读成了旧代,会让
11
+ profile 挂错行。现在统一读一个按版本元组比较的共用规则。
12
+
13
+ **0.1.5 这条线不再支持。** 还在 0.1.5 的用户会从商店读到明确的 `incompatible`(而不是沉默),
14
+ 请先升级宿主到 `0.1.7-rc` 或 `0.2.0-rc` 再装这一版。
15
+
16
+ ## 401/403 不再混为一谈,并可自动重试一次
17
+
18
+ 宿主的 `llm/retry` **不重试 AUTH 类错误**,所以一次**提供商侧**的瞬时 403 会直接终结整轮,而屏幕上只有
19
+ `permission_error` 一行。实测过一次:同一会话 37 轮在同一条路由上成功,失败只有间隔 45 秒的两次,
20
+ 随后同一把 key 打同一模型 200。
21
+
22
+ - 失败时补一行来源判断:`提供商拒绝了请求(HTTP 403)…{ENV} 已配置`(意思是**别改 key,重发即可**)
23
+ 或 `本机没有可用的 {ENV}`(意思是先去配)。判据是真的解析一次凭据,不是猜。
24
+ - `/retryauth on|off`(**默认关**):只在"提供商拒绝 + 本机有凭据 + 这一轮还没重试过"时自动重发上一条
25
+ 消息**一次**。默认关是因为重试会把整段上下文再发一遍,长会话很贵。
26
+ - 排查三步(三种错误信封、两条文件检查、一条直连 `curl`)写进 `docs/remote-ops.md` §4.8;
27
+ 附带一个会误导人的坑:`GET /provider/v1/models` 是公开端点,带不带 key 都 200,不能用它验鉴权。
28
+
29
+ ## 上游兼容
30
+
31
+ | 通道 | 版本 | 表态 |
32
+ |---|---|---|
33
+ | `latest` | `0.1.7-rc.2` | `compatible`:完整 CI 腿 |
34
+ | `next` | `0.2.0-rc.1` | `compatible`:本版默认线(干净树安装、`tsc` 0 错、套件 1021 通过 / 0 失败、七条真机探针) |
35
+ | 0.1.7 线 | `0.1.7-rc.1` | `compatible`:typecheck + 套件 |
36
+ | 已摘 | `0.1.3-alpha.*` / `0.1.5-*` | **`incompatible`**(明确表态,范围不再收;请求升级到 `0.1.7-rc` 或 `0.2.0-rc`) |
37
+
38
+ CI 四条腿:`0.2.0-rc.1`(默认,6 条探针)、`0.1.7-rc.2`(完整)、`0.1.7-rc.1`(类型+套件)、Windows(ConPTY 真机)。
39
+ 验证细节见 [`docs/upstream-0.2.0.md`](upstream-0.2.0.md)。
40
+
41
+ ## 安装 / 升级
42
+
43
+ ```sh
44
+ dsh plugin --profile tui add dsh-ssh-tui@next # 0.8.0(宿主 0.2.0-rc 或 0.1.7-rc 都可以)
45
+ dsh plugin --profile tui add dsh-ssh-tui@latest # 0.7.4(仍是默认;10-01 之后 latest 会变成 0.8.0)
46
+ ```
47
+
48
+ 装完退出再启动。`/status` 可确认当前版本;装不上先看 `/doctor`。
49
+
50
+ ## 已知限制
51
+
52
+ - **0.1.5 及更早不再支持**(见上)。
53
+ - 真实 Windows 上仍只能人工确认的两条:宿主那个控制台窗口确实是隐藏的;`%USERPROFILE%` 与 `$HOME`
54
+ 不一致的账户行为。
55
+ - `/retryauth` 的重试会把整段上下文再发一遍(长会话成本由你决定是否开启);它只对"提供商侧鉴权失败"
56
+ 生效,本机缺凭据时不会重试。
@@ -0,0 +1,79 @@
1
+ # 0.8.1 发版说明
2
+
3
+ > **发布方式:`latest` + `next`。** 上一次发版:**0.8.0**(2026-09-29,`next`,随后提前提升为 `latest`)。
4
+ > 这是一版**修稳定性**的补丁:桌面版装上它不会再把宿主带崩;终端侧的几处"残留"也一并清掉。
5
+
6
+ ## 头等事:桌面版装了**不会再弄坏宿主**
7
+
8
+ 0.8.0 在没有终端的进程里是**抛错**退出插件的。桌面版 Harness 的启动器是 Electron-as-Node(GUI 子系统,
9
+ 没有 console),于是插件管理把它显示成"1 entry did not activate / 插件失败",在桌面版上还会把 GUI 带崩。
10
+
11
+ 0.8.1 起按"能不能画"分四种情形,只有真能画时才挂载:
12
+
13
+ | 情形 | 行为 |
14
+ |---|---|
15
+ | 本进程就是分离的 TUI Host | 照常服务(它的终端在随后接入的那个窗口里) |
16
+ | stdin 与 stdout 都是 TTY | 照常挂载 TUI |
17
+ | 桌面版启动器(Electron-as-Node / `app.asar` / `DeepSeek Harness.exe`) | **不挂载任何东西**(不取锁、不装定时器、不查更新),记**一行**说明,措辞是"此处不启用"而不是报错 |
18
+ | 其它没有终端的情形(管道 `… \| tee`、cron、服务) | 同上,文案换成"没有终端"。**退出码 0** |
19
+
20
+ 需要"没终端就报错"的场合(脚本断言某个终端 profile 真能起来)在 profile 行加 `requireTerminal: true`
21
+ ——那是唯一会抛错的路径,而且只在显式要求时。桌面版用户不需要这个插件:用应用本身或 `dsh web`;
22
+ 想在终端里用它,用 npm 安装的 CLI(`npm i -g @deepseek-ai/dsh`)在真实终端/SSH 里跑 `dsh --profile tui`。
23
+
24
+ ## 新增:宿主自带终端控件时,可以把这个 TUI 当子进程用
25
+
26
+ 桌面版自己**不需要**它,但"宿主会开终端控件"(PTY 面板、xterm.js、GUI 里嵌的终端)是另一条路:
27
+ 声明 `DSH_TUI_DISPLAY=stdio`(或 `--display stdio`),插件就不再要求 TTY——画面走 stdout、按键走 stdin、
28
+ 面板尺寸由宿主用 `CSI 8 ; rows ; cols t` 报告(管道没有 `resize` 事件也没有 `SIGWINCH`)。协议、父进程
29
+ 必须做的五件事、Node 最小示例与限制写进 [`display-mode.md`](display-mode.md)(这一版起随包发布)。
30
+
31
+ ## 终端侧修掉的三处"残留"
32
+
33
+ 1. **会卡住的"处理中"卡片**:渲染缓存把"活尾巴"(等待卡、流式缓冲、还没有回复的突发摘要)收进了
34
+ 最后一行的缓存条目,于是每一帧都把那一份**冻结在旧秒数**的副本重放出来。真实会话里攒到过四条
35
+ (0s / 12s / 19s / 30s)夹在工具卡中间,`0s` 那条永远不动。现在缓存条目只包含它自己那行画的内容。
36
+ 2. **退出后提示符旁的 `①—…“”·•Ⅰ` 与 `^[[25;1R`**:歧义字形探针打印的那行自己没有擦(调用方可能一帧
37
+ 都没画),而它的光标回答要一个往返才回来——慢链路上那时终端已经交还给 shell,tty 就把回答回显成了
38
+ 文本。现在探针自己擦行、归还终端前多留一个往返吞掉回答、并且不再对已经交还的终端探测;探针期间
39
+ 暂存 Host 的输出,避免擦除打在刚画好的帧上。
40
+ 3. **极简视图**:单文件编辑卡不再重复自己的标题与 diffstat;失败的工具行补回状态球(`●`),与其它卡片行一致。
41
+
42
+ ## 性能与渲染(0.8.0 之后陆续落地)
43
+
44
+ - 每行渲染缓存 + 逐行脏比较:5000 行会话的整帧重画从约 1050ms 降到约 22ms,按键帧从约 1040ms 降到约 13ms;
45
+ 多行帧用 `DEC 2026` 同步刷新,避免慢链路上一行行"落下来"的撕裂(这就是本插件存在的意义的那个不闪屏)。
46
+ - 歧义宽度字形(`①`–`⑩`、`—`、`“”`、CJK 标点…)改为**问终端**:接入时打印探针并用 `CSI 6n` 读回列号,
47
+ 答案随帧下发给绘制的 Host;终端不回答就退回按 locale 推断,`DSH_TUI_NO_GLYPH_PROBE=1` 可整体关掉探针。
48
+
49
+ ## 上游兼容
50
+
51
+ | 通道 | 版本 | 表态 |
52
+ |---|---|---|
53
+ | `latest` / `next` | `0.2.0-rc.2` | `compatible`:**本版默认线**(干净树安装、`tsc` 0 错、套件 1086 项 1082 通过 / 0 失败 / 4 跳过、真机探针全过;桌面版基线) |
54
+ | 线内 | `0.2.0-rc.1` | `compatible`:typecheck + 套件 |
55
+ | 线内 | `0.1.7-rc.2` | `compatible`:完整探针 |
56
+ | 线内 | `0.1.7-rc.1` | `compatible`:typecheck + 套件 |
57
+ | 已摘 | `0.1.5-*` 及更早 | **`incompatible`**(明确表态,请求升级到 `0.1.7-rc` 或 `0.2.0-rc`) |
58
+
59
+ 声明窗口:`>=0.1.7-rc.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1`。CI 四条腿:`0.2.0-rc.2`(默认,完整探针)、
60
+ `0.1.7-rc.2`(完整)、`0.2.0-rc.1` 与 `0.1.7-rc.1`(typecheck + 套件)、外加 Windows leg(ConPTY 真机)
61
+ ——两条腿新增**无 PTY** 的管道探针(`scripts/tui-stdio-probe.mjs`)。
62
+
63
+ ## 安装 / 升级
64
+
65
+ ```sh
66
+ dsh plugin --profile tui add dsh-ssh-tui@latest # 0.8.1
67
+ ```
68
+
69
+ 装完退出再启动。`/status` 可确认当前版本;装不上先看 `/doctor`。
70
+
71
+ ## 已知限制
72
+
73
+ - **0.1.5 及更早不再支持**(见上)。
74
+ - 桌面版**应用本身**仍然不能跑终端 profile(它的启动器没有 console)——这是上游的事,见
75
+ [`docs/desktop.md`](https://github.com/cyjyyd/dsh-ssh-tui/blob/main/docs/desktop.md)。本版保证的是
76
+ "装了它不会把宿主弄坏",以及"宿主愿意开终端控件时能用"。
77
+ - 管道宿主**必须**自己报告尺寸变化(`CSI 8 ; rows ; cols t`),否则会停在启动时声明的 `COLUMNS`/`LINES`。
78
+ - 真实 Windows 上仍只能人工确认的两条:宿主那个控制台窗口确实是隐藏的;`%USERPROFILE%` 与 `$HOME`
79
+ 不一致的账户行为。
@@ -0,0 +1,159 @@
1
+ # 0.8.2-rc.1 发版说明(候选版)
2
+
3
+ > **发布方式:只上 `next`。** 这是候选版(RC):`latest` 保持 **0.8.1**,只有显式装 `@next` 或指定
4
+ > 版本号的人才会拿到它。上一次发版:**0.8.1**(2026-09-30,`latest` + `next`)。
5
+ > 这一版是**架构收尾 + 稳定性**:把 0.8.x 期间分批落地的终端架构(screen / 表示管线 / 工作区区域)
6
+ > 冻结下来,并修掉两处**只有全新安装或弱链路才会遇到**的问题。
7
+
8
+ ## 头等事:全新安装**第一次就会出现配置向导**(此前根本不会出现)
9
+
10
+ 0.8.1 及以前,一台**没有任何配置**的机器上装好插件、启动 `dsh --profile tui`,看到的是:
11
+ 一个空工作区、一行都没有的提示,模型调用必然失败 —— **配置向导永远不会自动出现**。
12
+
13
+ 原因不在向导本身(它是好的),而在触发它的判断:前端拉起后台 Host 时**永远**带上
14
+ `--resume=<id>`(连它自己刚生成的那个新 id 也带),而首启判定把 `--resume` 当成"这台机器配置过",
15
+ 于是直接跳过了首启流程。
16
+
17
+ 0.8.2 起这条判断只看**凭据存不存在**:没有凭据、环境里也没有 key,就是首启,向导自动打开(Enter 走步骤、
18
+ Esc 随时退出),并且工作区会同时留下一行说明告诉你怎么手动重开 `/setup`。**第二次启动不会再出现**。
19
+
20
+ 这同时修掉一个连带问题:向导不再借用 composer 的输入框。0.8.1 里进入向导会**清掉你已经打了一半的草稿**;
21
+ 现在向导有自己的字段、自己的光标,工作区里的草稿、历史、正在进行的输入一个字都不动。
22
+
23
+ ## 工具卡与输入法:两处"画面串行"的修复
24
+
25
+ - **工具卡内容含多行代码时不再窜到输入区**。`bash` 卡片的标题就是命令行原文,遇到 heredoc(`<<'PY'`)会带
26
+ 着换行进入"一行"——终端因此真的换了行,而这一版之前按绝对行号绘制的帧随后把下面每一行都写错位:卡片内容
27
+ 落在输入框和页脚上,甚至两段文字拼在同一行。现在标题行会把换行折成空格,单行裁剪器也不再把换行交给终端。
28
+ - **Windows Terminal + 微软输入法:预输入不再在处理中卡片那一行闪烁**。终端把输入法的预输入画在光标处,
29
+ 而"写一行"会把光标留在该行末尾;跑一轮时实时区域每 tick 重画又正好在输入框上方,所以光标停在处理中卡片
30
+ 上。现在每写完一行就把光标交还输入框,预输入只会出现在它该在的地方。
31
+
32
+ ## resume 不再黑屏,思考卡片不再"发白",`q` 真的能关掉报告
33
+
34
+ - **resume 进入时的 1–3 秒黑屏**。前端先在备用屏打出"正在载入历史会话…",relay 接入时宿主又写了一次
35
+ `?1049h`——终端已经在备用屏时这个序列会**清屏**,而重建会话日志期间按设计不合成任何帧,于是那块加载
36
+ 提示被擦掉、屏幕空着,直到历史重建完成(实测 20k 事件:9.1 s 清屏、12.6 s 首帧落地,中间 3.5 s 全黑)。
37
+ 现在这条"进屏"序列跟着第一帧一起发出,同一 tick 内清屏并重画;没有东西要等的重连行为不变。
38
+ - **思考卡片的文字不再发白**。"思考 / 已思考"这一角色此前只写 `2;3` 两个**属性**、不写颜色——"暗"完全
39
+ 靠 faint,而不实现 faint 的终端(报告的 256 色终端)于是退回**默认前景色**,看起来和旁边的回复一样亮。
40
+ 四个调色板现在各自给出 muted 颜色,属性保留给支持的终端(`mono` 仍然只用属性)。
41
+ - **`q` / `Q` 能关掉报告**。`inspectClosesOn` 一直声明接受它们("全屏报告理应响应 q"),但可打印字符的
42
+ 提示分支在它之前返回,于是这两个键只会打印"这是报告视图,按 Esc 返回"。
43
+
44
+ ## 状态行不再把"绘制节奏"当成"链路延迟"
45
+
46
+ 现场报告(`/diag`):Linux + xterm-256color + SSH,`探测 未知(终端未回 DSR)`——这台终端**多数时候不回答
47
+ `CSI 6n`**,于是状态行常年显示 `SSH ○○○○ 160ms`。两个部分要分开看:
48
+
49
+ - **空心圆是对的**:没测到往返就是 `unknown`,不该假装知道链路好坏。
50
+ - **`160ms` 是错的**:那不是延迟,是**回退的绘制节奏**(探测不到时用的安全默认值)。它出现在"延迟"该在的
51
+ 位置上,读者无法分辨"离我 160ms"和"从来没测过"。这一格现在改为 `SSH ○○○○ 未测` / `SSH ○○○○ n/a`;
52
+ 测到过的链路照旧显示真实值,绘制节奏仍然出现在 `/diag` 那一行、并且写明是"绘制"。
53
+
54
+ 另外两个"看起来像 bug"的现象是设计使然,不是缺陷:
55
+
56
+ - **有回 DSR 时数值也不怎么动**:显示的是最近 3 次测量的**中位数**,重测间隔 20 秒;稳定的 SSH 链路上
57
+ 中位数本来就该是一条直线。Windows 本机之所以看得见跳动,是因为那些数字只有 1–12ms,同样抖动在两位数
58
+ 里显得大。
59
+ - **连续 3 次探测失败后不再每 20 秒重试**:退避到 10 分钟——避免"不回答 DSR 的终端被永远每 20 秒扫一次"。
60
+
61
+ ## 命令提示会自己让位(排队提示除外)
62
+
63
+ 命令的确认("已复制 412 字")与警告(命令失败)从此**显示约 6 秒后自动让位**,恢复原来的遥测行;按 Enter
64
+ 提交仍会立刻清掉。**唯一的例外**是"运行中已提交,将在下个步骤生效"这条:它显示到那条消息**真正被提交**
65
+ 为止——它是"你有话在排队"的唯一标记,提前消失等于把排队这件事藏起来。
66
+
67
+ ## 弱链路 / SSH:画面更稳,流式不再整屏重画
68
+
69
+ 这一版把 0.8.x 期间逐块落地的渲染架构冻结下来,对**远程、跳板机、弱网**最直接的三条:
70
+
71
+ - **流式输出不再每个 tick 整屏重画**:正在生成的回复是一个**投影**,不再是往历史里追加行,所以每个
72
+ token 批次只重画它自己那几行。实测(100×20):一次流式 delta 寻址 **3 行 / 478 字节 / 0 次全屏清屏**。
73
+ - **报告不再污染对话记录**:`/status`、`/help`、`/doctor`、`/subagents`、`/diag` 现在是**独立的屏**,
74
+ 打开不改变历史窗口位置,翻页只重画正文,Esc 回到你原来的位置,也不会在重连后被重新画出来。
75
+ - **断线重接后表示一致**:提问、审批、计划三种卡片从**会话日志**折叠出来,因此掉线重接、换窗口、
76
+ `--resume` 之后读到的状态与当时一致,而不是"重连后多出一张卡"或"状态回到未知"。
77
+
78
+ ## `/clear` 只清可见视图
79
+
80
+ `/clear` 不删除任何东西:不删会话日志、不动模型上下文、不动计划 artifact。它只是把**可见视图**切到
81
+ 当前时刻之后(并且用一行提示告诉你这件事)。`/resume` 进来仍然是完整历史。`/find` 只在你**看得见**的
82
+ 范围里搜索,这一点也在提示里说明。
83
+
84
+ ## 计划与实时处理的抢占关系
85
+
86
+ 计划条(Plan Dock)不再被"处理中"的实时区域盖住:实时区域现在是**自己的行**,而不是画在历史上面。
87
+ 计划条高度上限为工作区一半,所以它不会把输入框和光标挤出屏幕(中文输入法的预输入串也不会再落到
88
+ 计划卡上)。终端很矮时计划条按三档降级(完整 / 紧凑 / 极简),不会直接消失。
89
+
90
+ ## 反馈去哪了("我执行了命令但没有任何提示")
91
+
92
+ - 控制面的确认(切主题、切视图、preset 写入…)进**页脚**,显示到下一次输入为止;
93
+ - 警告与失败进**身份行**(原来 `目录:` 那一行),不盖住你正在读的内容;
94
+ - 只有**会改变历史**的事实才进对话记录。
95
+ 页脚挤不下时确认会退到身份行显示,所以再也不会有"执行了但什么都没有"。
96
+
97
+ ## 上游 400:现在会告诉你它是什么
98
+
99
+ `command-code` 这类思考模式网关偶尔会**成串**拒收请求(实测:一次长会话里 13 个失败轮次中 12 个是这种),
100
+ 以前你只看到一行原始错误。现在:
101
+
102
+ - 明确说出**网关拒收的是请求本身、不是你的 key**;
103
+ - 指名已知的那一族缺陷(上游 #1780 / #231,思考模式的 reasoning 回传);
104
+ - 给出可操作的两条绕法(该路线 `/effort off`,或换同一网关的 command-code-messages);
105
+ - **不会自动重试** —— 实测这类失败会成串出现(连续 8 轮),重发通常值得,但同一个请求确定性失败时
106
+ 自动重试会白花一整段上下文。什么时候重发由你决定。
107
+
108
+ ## 数字(本版实测,100×20)
109
+
110
+ | 动作 | 毫秒(中位) | 字节 | 寻址行 | 全屏清屏 |
111
+ |---|---|---|---|---|
112
+ | 流式 delta | 1.43 | 501 | 3 | **0** |
113
+ | 等待卡时钟 | 1.25 | 465 | 3 | **0** |
114
+ | 报告屏翻页 | 1.19 | 2457 | 17 | **0** |
115
+ | 菜单光标移动 | 0.97 | 588 | 4 | **0** |
116
+ | 向导字段打字 | 0.22 | 140 | 1 | **0** |
117
+
118
+ 发版候选(`c2fe073`)自检,逐条可复现:
119
+
120
+ | 自检 | 结果 | 命令 |
121
+ |---|---|---|
122
+ | 冻结不变量 | 八条全绿 · 七条退役路径 **0 命中** | `npm run freeze` |
123
+ | 性能基线 | 上表五行 · 全部 **0 次整屏清屏** | `npm run bench` |
124
+ | 全套测试(Linux) | 1376 项 · **1372 通过 · 0 失败 · 4 跳过** | `npm test` |
125
+ | 全套测试(Windows 实机) | 1376 项 · **1365 通过 · 0 失败 · 11 跳过**(跳过均为平台互斥用例) | `npm test` |
126
+ | 打包 | **243 个文件** · tarball **1.4 MB** · 解包 **4.2 MB** · shasum `92b7f517…` · 无凭据 / 无 home / 无 sqlite | `npm pack --dry-run` |
127
+
128
+ 跳过只代表"这台机器上没跑",不代表通过:`verify-batch` 见到 `SKIP:` 就收尾 `INCOMPLETE`(见
129
+ [`release.md`](release.md))。
130
+
131
+ ## 兼容
132
+
133
+ 上游兼容窗口与 0.8.1 相同(`0.2.0-rc.1` / `0.2.0-rc.2` / `0.1.7-rc.1` / `0.1.7-rc.2`)。
134
+ 这一版**不改协议、不加命令、不动上游接口**。
135
+
136
+ ## 安装 / 升级
137
+
138
+ ```sh
139
+ dsh plugin --profile tui add dsh-ssh-tui@0.8.2-rc.1
140
+ ```
141
+
142
+ 装完退出再启动。`/status` 可确认当前版本;装不上先看 `/doctor`。
143
+
144
+ ## 已知限制
145
+
146
+ - **Windows / ConPTY 实机人工验收:已完成(2026-10-03,维护者)**。用一份单独安装的 CLI dsh 逐条走过
147
+ 12 项清单,逐项结论见 [`checkpoints.md`](checkpoints.md)「Windows 实机轮」。分三类:
148
+ **可自动化的部分全绿**(`verify-batch --home <dir>`:12 PASS · 2 SKIP · 0 FAIL);**两处 ConPTY 盲区**
149
+ 明确打印 `SKIP:` 并附理由(ConPTY 自己回 `CSI 6n`,"慢链路"与"终端静默但没断连"这两个形状它扮演不了);
150
+ **没有自动化、只能人眼的两目已由维护者确认通过**——微软拼音 IME 组合、多行中文粘贴(清单与走法见
151
+ [`release.md`](release.md)「发布前人工门槛」)。两处 `SKIP` 是**覆盖边界**,不是通过:它们
152
+ **在 Linux 腿上是真跑的**(`link` 第一段、`cut` 的"静默终端"那一半),在 ConPTY 上才无信号。
153
+ - **`未测` 那一格在 Windows 本机看不到**:ConPTY 代答光标位置,本机链路芯片因此永远是"已测量"。
154
+ 该形状由 `scripts/tui-unmeasured-probe.mjs` 用管道父进程覆盖(两条腿:不回话读 `未测`、每条必答读
155
+ 真值),已进 CI 两条腿。
156
+ - **拖放探针此前约 1/7 概率误报**(探针自身与自己的 `/status` 重试竞态,不是产品行为)。本版已修,
157
+ 并连跑 20 次验证。
158
+ - `--resume` 与首启的关系见上文:本版修的是"新机器不显示向导",不是"resume 不再触发向导"。
159
+ - 依赖旧版 inbox 通知(inbox P3)与新协议的命令不在本版范围内。
@@ -0,0 +1,62 @@
1
+ # 0.8.2 发版说明
2
+
3
+ > **发布方式:`latest` + `next`。** 上一次发版:**0.8.1**(2026-09-30,`latest` + `next`)。
4
+ > 本版由 **0.8.2-rc.1**(2026-10-04 只上 `next`)沉淀一周后转正:候选版期间没有收到新的回归报告,
5
+ > CI 五条腿与全部真 PTY 探针持续全绿,Windows / ConPTY 实机人工验收也已完成(见
6
+ > [`checkpoints.md`](checkpoints.md)「Windows 实机轮」)。**内容与 0.8.2-rc.1 完全相同**,
7
+ > 逐项说明见 [`release-notes-0.8.2-rc.1.md`](release-notes-0.8.2-rc.1.md)。
8
+
9
+ > `dsh-ssh-tui` is now presented as DSH Relay.
10
+ > The npm package name and the install command have not changed.
11
+ >
12
+ > **`dsh-ssh-tui` 现在以 DSH Relay 为产品名展示;npm 包名与安装命令没有变化。**
13
+ > 发布坐标仍是 `dsh-ssh-tui`,安装命令仍是
14
+ > `dsh plugin --profile tui add dsh-ssh-tui@latest`。依据:[`decisions/brand-dsh-relay.md`](decisions/brand-dsh-relay.md)。
15
+
16
+ ## 头等事:全新安装**第一次就会出现配置向导**(此前根本不会出现)
17
+
18
+ 0.8.1 及以前,一台没有任何配置的机器上装好插件、启动 `dsh --profile tui`,看到的是一个空工作区、
19
+ 一行提示都没有,模型调用必然失败——**配置向导永远不会自动出现**。原因是前端拉起的 Host 永远带
20
+ `--resume=<id>`(连它自己刚铸的新 id 也带),而首启判定把 `resume` 当成"这台机器配置过"。
21
+ 0.8.2 起只看凭据存不存在。详见候选版说明第一节。
22
+
23
+ ## 另外两件用户直接看得见的事
24
+
25
+ - **resume 不再有一段 1–3 秒黑屏**:进屏序列现在跟着第一帧一起发出,加载提示全程在屏
26
+ (20k 事件实测:清屏与落地同一 tick,此前中间有 3.5 秒全黑)。
27
+ - **思考卡片不再发白**:`reasoning` 角色此前只写属性、不写颜色,"暗"完全靠 faint;不实现 faint 的终端
28
+ 会退回默认前景色。四个调色板现在各自给出 muted 颜色。
29
+ - **链路那一格不再把"绘制节奏"当延迟**:终端不回 `CSI 6n` 时显示 `未测` / `n/a`,不再印一个看起来像
30
+ 测量值的 160ms。
31
+
32
+ ## 兼容
33
+
34
+ 上游兼容窗口与 0.8.1 相同:`0.2.0-rc.1` / `0.2.0-rc.2` / `0.1.7-rc.1` / `0.1.7-rc.2`。
35
+ **0.1.5 及更早的宿主不再支持。** 本版不改协议、不加命令、不动上游接口。
36
+
37
+ ## 安装 / 升级
38
+
39
+ ```sh
40
+ dsh plugin --profile tui add dsh-ssh-tui@latest
41
+ ```
42
+
43
+ 装完退出再启动。`/status` 可确认当前版本;装不上先看 `/doctor`。
44
+ 更新时请显式带 `@latest`,否则已有 lockfile 可能继续沿用旧版本。
45
+
46
+ ## 实测数字(本版,100×20)
47
+
48
+ | 动作 | 毫秒(中位) | 字节 | 寻址行 | 全屏清屏 |
49
+ |---|---|---|---|---|
50
+ | 流式 delta | 1.47 | 501 | 3 | **0** |
51
+ | 等待卡时钟 | 1.26 | 465 | 3 | **0** |
52
+ | 报告屏翻页 | 1.40 | 2457 | 17 | **0** |
53
+ | 菜单光标移动 | 1.09 | 588 | 4 | **0** |
54
+ | 向导字段打字 | 0.25 | 140 | 1 | **0** |
55
+
56
+ 复现:`npm run bench`;README 里的同一张表由 `npm run bench:report` 从这次真跑生成。
57
+
58
+ ## 已知问题
59
+
60
+ 见候选版说明的「已知限制」一节。要点:Windows / ConPTY 实机人工验收**已完成**(12 项清单;
61
+ 两处 ConPTY 盲区在探针里显式 `SKIP` 并在 Linux 腿上真跑,`SKIP` 不算通过);`未测` 那一格在
62
+ Windows 本机看不到(ConPTY 代答光标位置),由管道父进程形状的探针覆盖。
Binary file
package/docs/terminals.md CHANGED
@@ -52,6 +52,29 @@
52
52
  - **`?` 的含义**:该终端*可能*生效但需要用户配置(xterm 的 `allowWindowOps`、tmux 的 `set-clipboard on`),
53
53
  所以仍然**照发**;而 `—` 的终端上一次会话只提醒一次,告诉你剪贴板可能没写进去。
54
54
 
55
+ ### 证据等级:作者/CI 实测 vs 社区实测
56
+
57
+ 上面那张表是**作者与 CI 实测**的一等证据:判定代码有单元用例(`tests/terminal-caps.test.mjs`),
58
+ 每种终端真起一次 TUI 看它到底发了哪些序列(`scripts/tui-term-probe.mjs`),CI 每条腿都跑。
59
+ 下面这张表是**社区实测**:读者在自己的终端上试出来的结果,**证据等级不同,不要混着读**。
60
+
61
+ | 终端 | 环境(OS / 传输) | 结论 | 来源 | 日期 |
62
+ |---|---|---|---|---|
63
+ | _(待填)_ | | | | |
64
+
65
+ **怎么贡献一行**:在 GitHub 上提一份
66
+ [终端兼容报告](https://github.com/cyjyyd/dsh-ssh-tui/issues/new?template=terminal-compatibility.yml) ——
67
+ 不用读代码,填一张表(OS、终端、本地/SSH、TERM、版本,以及哪些能力试过、哪些没试过)。
68
+ 报告会由维护者整理到这里,并注明来源与日期。
69
+
70
+ 两栏的差别要一直保留:
71
+
72
+ - **作者/CI 实测** = 有可复现的判定代码与探针,改坏了 CI 会红;
73
+ - **社区实测** = 一个人的一次观察,能证明"在这个环境下能跑",不能证明"在别的版本上也行"。
74
+
75
+ 一条报告即使结论是"能用",也只进社区栏——除非它同时补上了对应的 fixture 或探针,那时它升级为
76
+ 作者/CI 一栏,并在矩阵表里出现一行。
77
+
55
78
  ## 覆盖方式(用户可强制指定)
56
79
 
57
80
  自动判定错了就手动覆盖,两个环境变量都长期有效:
package/docs/windows.md CHANGED
@@ -118,6 +118,19 @@ Host 的 stderr 记在 `%USERPROFILE%\.dsh\tui-socks\`,锁在 `%USERPROFILE%\.
118
118
  **终端能力。** Windows Terminal 与老的 conhost 能力不同(剪贴板写入、超链接只在 Windows Terminal 上承诺)。
119
119
  判定写在 `/diag` 的「终端」一行,判错了用 `DSH_TUI_TERM_CAPS` 覆盖,详见 [terminals.md](terminals.md)。
120
120
 
121
+ ## ConPTY 会替你的终端回答两件事
122
+
123
+ 这两件事在 Windows 上按"实测"读会读错,写在这里免得当成缺陷追:
124
+
125
+ 1. **`CSI 6n`(问光标位置)由 ConPTY 代答。** 所以底栏链路芯片在 Windows 本机永远是"已测量"
126
+ (`SSH ●●●● 0–12ms`),**看不到 `未测`**——`未测` 是"终端不回 DSR"的形状,在你的 Linux/SSH 会话
127
+ 里才会出现。要看这一格,用管道父进程的形状:
128
+ `node scripts/probe-home.mjs --probe --script tui-unmeasured-probe.mjs`(两条腿:不回话读 `未测`、
129
+ 每条必答读真值)。
130
+ 2. **输出流不是插件写出去的那份。** ConPTY 把每一帧重新发成以换行分隔的批次,还会自己写清屏和回车:
131
+ 同一次 boot 加 `/diag`,`ESC[<row>;1H` 在 ConPTY 上是 3 个、在管道上是 95 个。所以"逐行寻址""不许
132
+ 清屏"这类检查在 ConPTY 上会打印 `SKIP:`(不是通过,也不是失败),详见 [release.md](release.md)。
133
+
121
134
  ## CI 证明不了、需要你自己看一眼的
122
135
 
123
136
  这两项在自动化里只能证明一半:
@@ -0,0 +1,148 @@
1
+ /**
2
+ * What a tool card may say about the approval that guarded it.
3
+ *
4
+ * Approval is a *state of the tool call*, not an event with a history of its own
5
+ * (B2.4): a tool that needed a decision says so on its own card, and nothing else
6
+ * in the transcript claims to be that decision.
7
+ *
8
+ * The hard half is not the state, it is the *evidence*. Two facts are durable and
9
+ * both belong to the Harness, not to this plugin:
10
+ *
11
+ * `approval/asked` — `{ id, toolName, callId?, reason? }`
12
+ * `approval/decided` — `{ id, outcome }`, `outcome ∈ allowed-once|rejected|cancelled|unavailable`
13
+ *
14
+ * Everything else is weaker, and the difference is carried in {@link
15
+ * ApprovalProvenance} rather than smoothed over: reading the log after a resume
16
+ * answers with `durable`, a live decision answers with `live` (a human) or
17
+ * `policy` (a rule, a cached verdict, a reviewer), and a log that predates the
18
+ * audit pair can only be read by `inferred`. The rule this module exists to hold:
19
+ * **a weaker source may never claim more than it proves** — in particular nothing
20
+ * here can ever produce `approved` from an inference, and an ask with no recorded
21
+ * decision is `unknown`, never `approved`.
22
+ *
23
+ * This module is pure (no session, no TUI) so both the live path and a replay go
24
+ * through the same mapping, and so the mapping is testable without a TUI.
25
+ *
26
+ * @module dsh-ssh-tui/approval-state
27
+ */
28
+ import { t } from './i18n/index.js';
29
+ /** Strongest first: a weaker source never overwrites a stronger one. */
30
+ const PROVENANCE_ORDER = {
31
+ live: 3,
32
+ policy: 3,
33
+ durable: 2,
34
+ inferred: 1,
35
+ };
36
+ /**
37
+ * One approval fact onto a card's current field.
38
+ *
39
+ * `next` wins on state when it is at least as strong as what is already there. A
40
+ * live decision is not overwritten by the durable event of the *same* decision
41
+ * (which arrives right behind it on the live path and says strictly less), and an
42
+ * inference never replaces anything but an inference.
43
+ * @param current - the field as it stands, if any.
44
+ * @param next - the fact being applied.
45
+ * @returns the field the card should carry.
46
+ */
47
+ export function mergeApproval(current, next) {
48
+ if (current === undefined)
49
+ return next;
50
+ const strength = PROVENANCE_ORDER[next.provenance];
51
+ const existing = PROVENANCE_ORDER[current.provenance];
52
+ if (strength < existing) {
53
+ // Keep the stronger state, but a reason is still worth having.
54
+ return current.reason !== undefined || next.reason === undefined
55
+ ? current
56
+ : { ...current, reason: next.reason };
57
+ }
58
+ if (strength === existing && current.state === next.state) {
59
+ return current.auto === next.auto && current.reason === next.reason
60
+ ? current
61
+ : { ...current, ...next, provenance: current.provenance };
62
+ }
63
+ // Same strength, or a stronger fact: it wins, and it keeps the better label.
64
+ return existing > strength ? current : { ...current, ...next };
65
+ }
66
+ /**
67
+ * A recorded outcome as a card state.
68
+ *
69
+ * `cancelled` and `unavailable` are deliberately **unknown**, not `rejected`: the
70
+ * reader's Esc, or a Host that had no approval channel, is not the human refusing
71
+ * the tool, and printing a refusal they did not make is exactly the kind of
72
+ * comfortable lie this field exists to prevent.
73
+ * @param outcome - the `approval/decided` outcome, as recorded.
74
+ * @returns the state the card shows.
75
+ */
76
+ export function approvalStateFromOutcome(outcome) {
77
+ if (outcome === 'allowed-once')
78
+ return 'approved';
79
+ if (outcome === 'rejected')
80
+ return 'rejected';
81
+ return 'unknown';
82
+ }
83
+ /**
84
+ * Whether a tool result proves an approval was **refused** when no audit events
85
+ * survived.
86
+ *
87
+ * Only the Harness's own refusal sentences count. A generic denial (`denied by
88
+ * policy`, `blocked`) does *not* prove an approval was ever needed, and the whole
89
+ * point of the `inferred` provenance is that it is used only where the text can
90
+ * carry it: the phrase below is what `dsh-tools` writes when `approval.request()`
91
+ * came back `rejected` or `cancelled`, so it cannot appear for a tool that was
92
+ * refused by a rule instead.
93
+ *
94
+ * It can never produce `approved` — running successfully is not evidence that
95
+ * anyone approved anything (a tool with no approval requirement runs too).
96
+ * @param text - the tool result's text.
97
+ * @returns `rejected` when the text is that refusal, undefined otherwise.
98
+ */
99
+ export function inferredApprovalFromResult(text) {
100
+ if (/the user rejected tool "[^"]*"/u.test(text))
101
+ return 'rejected';
102
+ if (/approval for tool "[^"]*" was cancelled/u.test(text))
103
+ return 'unknown';
104
+ return undefined;
105
+ }
106
+ /** A one-word label for the card chip. */
107
+ export function approvalStateKey(state) {
108
+ return `approval.badge.${state}`;
109
+ }
110
+ /**
111
+ * The provenance note shown beside the state.
112
+ *
113
+ * `live` and `durable` say nothing extra: a person's own decision and the log of
114
+ * it are the same fact at two moments. `policy` and `inferred` do, because the
115
+ * reader is being told something weaker and has to be able to tell.
116
+ */
117
+ export function approvalProvenanceKey(approval) {
118
+ if (approval.provenance === 'inferred')
119
+ return 'approval.badge.inferred';
120
+ if (approval.auto === true)
121
+ return 'approval.badge.auto';
122
+ return undefined;
123
+ }
124
+ /**
125
+ * The expanded card's own line for the approval.
126
+ *
127
+ * It names the state, the strength of the evidence, and the reason when one
128
+ * survived: `live`/`durable` need no apology, `policy` is marked automatic, and
129
+ * `inferred` says outright that the card is reading the log rather than quoting it.
130
+ * @param approval - the card's approval field.
131
+ * @returns the sentence, already translated.
132
+ */
133
+ export function approvalDetailText(approval) {
134
+ const state = t(approvalStateKey(approval.state));
135
+ const provenance = t(`approval.badge.${approval.provenance}`);
136
+ const reason = approval.reason === undefined || approval.reason === '' ? '' : ` · ${approval.reason}`;
137
+ return t(approval.auto === true ? 'approval.badge.detailAuto' : 'approval.badge.detail', { state, provenance, reason });
138
+ }
139
+ /** Whether two approval fields would render identically. */
140
+ export function sameApproval(left, right) {
141
+ if (left === undefined || right === undefined)
142
+ return left === right;
143
+ return left.state === right.state
144
+ && left.provenance === right.provenance
145
+ && left.auto === right.auto
146
+ && left.reason === right.reason;
147
+ }
148
+ //# sourceMappingURL=approval-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-state.js","sourceRoot":"","sources":["../src/approval-state.ts"],"names":[],"mappings":"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"}
@@ -125,4 +125,42 @@ export function isReasoningReplayFailure(message) {
125
125
  return /reasoning_text[^.]{0,80}must be passed back/iu.test(text)
126
126
  || /thinking mode must be passed back/iu.test(text);
127
127
  }
128
+ /**
129
+ * A gateway that refused the request itself, without saying why.
130
+ *
131
+ * The sibling of the failure above, and the one a reader actually meets most: the
132
+ * same thinking-mode route answers a bare envelope with a trace id and nothing else.
133
+ * Measured in one long session (2026-10-03, command-code, `effort: max`):
134
+ *
135
+ * 13 of 14 failed turns were this shape, 12 of them bare
136
+ * they arrived in runs — eight consecutive turns over twelve minutes, then the
137
+ * same route, model and key answered normally again
138
+ * the harness made 14 attempts and 0 retries (a 400 is outside its retryable set)
139
+ *
140
+ * Those three facts decide what the plugin may say and may do. It may say the
141
+ * request body was refused (not the credential) and that the known member of this
142
+ * family is the reasoning-replay defect. It may *not* claim that sending again will
143
+ * work: the envelope carries no reason, and a run that deep may equally be a request
144
+ * the API will keep refusing. What it must not do is retry on its own — the sibling's
145
+ * case below is deterministic, and this one re-sends a whole context to find out.
146
+ *
147
+ * The envelope is deliberately narrow: a 400 whose body names a field
148
+ * (`unknown field "summary"`) is a different fix and is not this.
149
+ * @param message - the failure text the host attached to the turn.
150
+ * @returns true when this is a bare 400 rejection.
151
+ */
152
+ export function isRequestRejectedFailure(message) {
153
+ const text = String(message ?? '');
154
+ if (text === '')
155
+ return false;
156
+ if (isReasoningReplayFailure(text))
157
+ return false;
158
+ if (statusOf(text) !== 400)
159
+ return false;
160
+ if (!/invalid[_ ]request/i.test(text))
161
+ return false;
162
+ // A body that names a field, a parameter or a type is diagnosable, so it is not
163
+ // this case: the notice exists because there is nothing else to go on.
164
+ return !/unknown (?:field|parameter)|unrecognized|missing (?:field|parameter)|json:/iu.test(text);
165
+ }
128
166
  //# sourceMappingURL=auth-failure.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-failure.js","sourceRoot":"","sources":["../src/auth-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAWH;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAsB;IAC3C,yBAAyB;IACzB,6BAA6B;IAC7B,wBAAwB;IACxB,uBAAuB;IACvB,iBAAiB;IACjB,iCAAiC;IACjC,0BAA0B;IAC1B,gCAAgC;IAChC,qBAAqB;IACrB,iBAAiB;CAClB,CAAA;AAED,6DAA6D;AAC7D,MAAM,cAAc,GAAsB;IACxC,wCAAwC;IACxC,6EAA6E;IAC7E,6CAA6C;IAC7C,kDAAkD;IAClD,OAAO;IACP,+BAA+B;CAChC,CAAA;AAED,wEAAwE;AACxE,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,6EAA6E;IAC7E,wBAAwB;IACxB,MAAM,KAAK,GAAG,qCAAqC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACjE,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACpC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IAClD,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AACrD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAClC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC7B,0EAA0E;IAC1E,uEAAuE;IACvE,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;IAC/H,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAA;IAC3E,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;IACrI,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAClC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAA;IAC7B,OAAO,+CAA+C,CAAC,IAAI,CAAC,IAAI,CAAC;WAC5D,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACvD,CAAC"}
1
+ {"version":3,"file":"auth-failure.js","sourceRoot":"","sources":["../src/auth-failure.ts"],"names":[],"mappings":"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"}